aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules3
m---------depends/mglpp0
-rw-r--r--fonts/Orbitron-Bold.ttfbin0 -> 24308 bytes
-rw-r--r--fonts/Orbitron-Regular.ttfbin0 -> 24368 bytes
-rw-r--r--images/record.pngbin0 -> 4196 bytes
-rw-r--r--images/replay.pngbin0 -> 5221 bytes
-rw-r--r--images/stream.pngbin0 -> 5235 bytes
-rw-r--r--include/gui/Button.hpp16
-rw-r--r--include/gui/Widget.hpp21
-rw-r--r--main.cpp71
-rw-r--r--project.conf5
-rw-r--r--src/gui/Button.cpp71
-rw-r--r--src/gui/Widget.cpp7
-rw-r--r--src/main.cpp276
14 files changed, 397 insertions, 73 deletions
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..d0e51e3
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "depends/mglpp"]
+ path = depends/mglpp
+ url = https://repo.dec05eba.com/mglpp
diff --git a/depends/mglpp b/depends/mglpp
new file mode 160000
+Subproject 2c879c34c9aeddae528d2818f7baa6a915d5d77
diff --git a/fonts/Orbitron-Bold.ttf b/fonts/Orbitron-Bold.ttf
new file mode 100644
index 0000000..faca54c
--- /dev/null
+++ b/fonts/Orbitron-Bold.ttf
Binary files differ
diff --git a/fonts/Orbitron-Regular.ttf b/fonts/Orbitron-Regular.ttf
new file mode 100644
index 0000000..53260a0
--- /dev/null
+++ b/fonts/Orbitron-Regular.ttf
Binary files differ
diff --git a/images/record.png b/images/record.png
new file mode 100644
index 0000000..92c2bcb
--- /dev/null
+++ b/images/record.png
Binary files differ
diff --git a/images/replay.png b/images/replay.png
new file mode 100644
index 0000000..65c9339
--- /dev/null
+++ b/images/replay.png
Binary files differ
diff --git a/images/stream.png b/images/stream.png
new file mode 100644
index 0000000..970a673
--- /dev/null
+++ b/images/stream.png
Binary files differ
diff --git a/include/gui/Button.hpp b/include/gui/Button.hpp
new file mode 100644
index 0000000..ac9aa5c
--- /dev/null
+++ b/include/gui/Button.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include "Widget.hpp"
+#include <string>
+
+namespace gsr {
+ class Button : public Widget {
+ public:
+ Button(mgl::vec2f size);
+ void on_event(mgl::Event &event, mgl::Window &window) override;
+ void draw(mgl::Window &window) override;
+ private:
+ mgl::vec2f size;
+ bool mouse_inside = false;
+ };
+} \ No newline at end of file
diff --git a/include/gui/Widget.hpp b/include/gui/Widget.hpp
new file mode 100644
index 0000000..cf81d69
--- /dev/null
+++ b/include/gui/Widget.hpp
@@ -0,0 +1,21 @@
+#pragma once
+
+#include <mglpp/system/vec.hpp>
+
+namespace mgl {
+ class Event;
+ class Window;
+}
+
+namespace gsr {
+ class Widget {
+ public:
+ virtual ~Widget() = default;
+
+ virtual void on_event(mgl::Event &event, mgl::Window &window) = 0;
+ virtual void draw(mgl::Window &window) = 0;
+ virtual void set_position(mgl::vec2f position);
+ protected:
+ mgl::vec2f position;
+ };
+} \ No newline at end of file
diff --git a/main.cpp b/main.cpp
deleted file mode 100644
index ee58e4d..0000000
--- a/main.cpp
+++ /dev/null
@@ -1,71 +0,0 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <X11/Xlib.h>
-#include <X11/cursorfont.h>
-#include <unistd.h>
-
-static int x11_error_handler(Display *dpy, XErrorEvent *ev) {
- return 0;
-}
-
-static void usage() {
- fprintf(stderr, "usage: window-overlay <window>\n");
- exit(1);
-}
-
-static bool string_to_i64(const char *str, int64_t *result) {
- errno = 0;
- char *endptr = nullptr;
- int64_t res = strtoll(str, &endptr, 0);
- if(endptr == str || errno != 0)
- return false;
-
- *result = res;
- return true;
-}
-
-int main(int argc, char **argv) {
- if(argc != 2)
- usage();
-
- int64_t target_window;
- if(!string_to_i64(argv[1], &target_window)) {
- fprintf(stderr, "Error: invalid number '%s' was specific for window argument\n", argv[1]);
- return 1;
- }
-
- XSetErrorHandler(x11_error_handler);
-
- Display *display = XOpenDisplay(NULL);
- if(!display) {
- fprintf(stderr, "Error: XOpenDisplay failed\n");
- return 1;
- }
-
- //const Window root_window = DefaultRootWindow(display);
- const int screen = DefaultScreen(display);
-
- XSetWindowAttributes attr;
- attr.background_pixel = XWhitePixel(display, screen);
- attr.override_redirect = True;
-
- Window overlay_window = XCreateWindow(display, target_window, 0, 0, 256, 256, 0, DefaultDepth(display, screen), InputOutput, XDefaultVisual(display, screen), CWBackPixel|CWOverrideRedirect, &attr);
- if(!overlay_window) {
- fprintf(stderr, "Error: failed to create overlay window\n");
- return 1;
- }
-
- Cursor default_cursor = XCreateFontCursor(display, XC_arrow);
-
- XMapWindow(display, overlay_window);
- XGrabPointer(display, overlay_window, True, ButtonPressMask|ButtonReleaseMask, GrabModeSync, GrabModeSync, None, default_cursor, CurrentTime);
- sleep(3);
-
- /*XEvent xev;
- for(;;) {
- XNextEvent(display, &xev);
- }*/
-
- return 0;
-}
diff --git a/project.conf b/project.conf
index 2805290..f767638 100644
--- a/project.conf
+++ b/project.conf
@@ -1,8 +1,9 @@
[package]
-name = "window-overlay"
+name = "gpu-screen-recorder-overlay"
type = "executable"
version = "0.1.0"
platforms = ["posix"]
[dependencies]
-x11 = ">=1"
+x11 = "1"
+xext = "1" \ No newline at end of file
diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp
new file mode 100644
index 0000000..b266639
--- /dev/null
+++ b/src/gui/Button.cpp
@@ -0,0 +1,71 @@
+#include "../../include/gui/Button.hpp"
+#include <mglpp/graphics/Rectangle.hpp>
+#include <mglpp/window/Window.hpp>
+#include <mglpp/window/Event.hpp>
+#include <mglpp/system/FloatRect.hpp>
+
+namespace gsr {
+ Button::Button(mgl::vec2f size) : size(size) {
+
+ }
+
+ void Button::on_event(mgl::Event &event, mgl::Window&) {
+ if(event.type == mgl::Event::MouseMoved) {
+ const bool inside = mgl::FloatRect(position, size).contains({ (float)event.mouse_move.x, (float)event.mouse_move.y });
+ if(mouse_inside && !inside) {
+ mouse_inside = false;
+ } else if(!mouse_inside && inside) {
+ mouse_inside = true;
+ }
+ }
+ }
+
+ void Button::draw(mgl::Window &window) {
+ if(mouse_inside) {
+ // Background
+ /*
+ {
+ mgl::Rectangle rect(size);
+ rect.set_position(position);
+ rect.set_color(mgl::Color(20, 20, 20, 255));
+ window.draw(rect);
+ }
+ */
+
+ const int border_size = 5;
+ const mgl::Color border_color(118, 185, 0);
+
+ // Green line at top
+ {
+ mgl::Rectangle rect({ size.x, border_size });
+ rect.set_position(position);
+ rect.set_color(border_color);
+ window.draw(rect);
+ }
+
+ // Green line at bottom
+ {
+ mgl::Rectangle rect({ size.x, border_size });
+ rect.set_position(position + mgl::vec2f(0.0f, size.y - border_size));
+ rect.set_color(border_color);
+ window.draw(rect);
+ }
+
+ // Green line at left
+ {
+ mgl::Rectangle rect({ border_size, size.y });
+ rect.set_position(position);
+ rect.set_color(border_color);
+ window.draw(rect);
+ }
+
+ // Green line at right
+ {
+ mgl::Rectangle rect({ border_size, size.y });
+ rect.set_position(position + mgl::vec2f(size.x - border_size, 0.0f));
+ rect.set_color(border_color);
+ window.draw(rect);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/gui/Widget.cpp b/src/gui/Widget.cpp
new file mode 100644
index 0000000..c30c1d1
--- /dev/null
+++ b/src/gui/Widget.cpp
@@ -0,0 +1,7 @@
+#include "../../include/gui/Widget.hpp"
+
+namespace gsr {
+ void Widget::set_position(mgl::vec2f position) {
+ this->position = position;
+ }
+} \ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..537011c
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,276 @@
+#include "../include/gui/Button.hpp"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <X11/Xlib.h>
+#include <X11/cursorfont.h>
+#include <X11/extensions/shape.h>
+
+#include <mglpp/mglpp.hpp>
+#include <mglpp/graphics/Font.hpp>
+#include <mglpp/graphics/Text.hpp>
+#include <mglpp/graphics/Texture.hpp>
+#include <mglpp/graphics/Sprite.hpp>
+#include <mglpp/window/Window.hpp>
+#include <mglpp/window/Event.hpp>
+#include <mglpp/system/MemoryMappedFile.hpp>
+
+#include <vector>
+
+extern "C" {
+#include <mgl/mgl.h>
+}
+
+static void usage() {
+ fprintf(stderr, "usage: window-overlay <window>\n");
+ exit(1);
+}
+
+static void startup_error(const char *msg) {
+ fprintf(stderr, "Error: %s\n", msg);
+ exit(1);
+}
+
+static bool string_to_i64(const char *str, int64_t *result) {
+ errno = 0;
+ char *endptr = nullptr;
+ int64_t res = strtoll(str, &endptr, 0);
+ if(endptr == str || errno != 0)
+ return false;
+
+ *result = res;
+ return true;
+}
+
+int main(int argc, char **argv) {
+ if(argc != 2)
+ usage();
+
+ int64_t target_window;
+ if(!string_to_i64(argv[1], &target_window)) {
+ fprintf(stderr, "Error: invalid number '%s' was specific for window argument\n", argv[1]);
+ return 1;
+ }
+
+ mgl::Init init;
+ Display *display = (Display*)mgl_get_context()->connection;
+
+ XWindowAttributes target_win_attr;
+ memset(&target_win_attr, 0, sizeof(target_win_attr));
+ if(!XGetWindowAttributes(display, target_window, &target_win_attr)) {
+ fprintf(stderr, "Error: window argument %s is not a valid window\n", argv[1]);
+ return 1;
+ }
+
+ XSelectInput(display, target_window, StructureNotifyMask);
+
+ mgl::vec2i target_window_size = { target_win_attr.width, target_win_attr.height };
+
+ mgl::Window::CreateParams window_create_params;
+ window_create_params.size = target_window_size;
+ window_create_params.parent_window = target_window;
+ window_create_params.hidden = true;
+
+ mgl::Window window;
+ if(!window.create("mglpp", window_create_params))
+ startup_error("failed to create window");
+
+ mgl::MemoryMappedFile title_font_file;
+ if(!title_font_file.load("fonts/Orbitron-Bold.ttf", mgl::MemoryMappedFile::LoadOptions{true, false}))
+ startup_error("failed to load file: fonts/Orbitron-Bold.ttf");
+
+ mgl::MemoryMappedFile font_file;
+ if(!font_file.load("fonts/Orbitron-Regular.ttf", mgl::MemoryMappedFile::LoadOptions{true, false}))
+ startup_error("failed to load file: fonts/Orbitron-Regular.ttf");
+
+ mgl::Font title_font;
+ if(!title_font.load_from_file(title_font_file, 32))
+ startup_error("failed to load font: fonts/Orbitron-Bold.ttf");
+
+ mgl::Font font;
+ if(!font.load_from_file(font_file, 20))
+ startup_error("failed to load font: fonts/Orbitron-Regular.ttf");
+
+ mgl::Texture replay_button_texture;
+ if(!replay_button_texture.load_from_file("images/replay.png"))
+ startup_error("failed to load texture: images/replay.png");
+
+ mgl::Texture record_button_texture;
+ if(!record_button_texture.load_from_file("images/record.png"))
+ startup_error("failed to load texture: images/record.png");
+
+ mgl::Texture stream_button_texture;
+ if(!stream_button_texture.load_from_file("images/stream.png"))
+ startup_error("failed to load texture: images/stream.png");
+
+ struct MainButton {
+ mgl::Text title;
+ mgl::Text description;
+ mgl::Sprite icon;
+ gsr::Button button;
+ };
+
+ const char *titles[] = {
+ "Instant Replay",
+ "Record",
+ "Livestream"
+ };
+
+ const char *descriptions[] = {
+ "Off",
+ "Not recording",
+ "Not streaming"
+ };
+
+ mgl::Texture *textures[] = {
+ &replay_button_texture,
+ &record_button_texture,
+ &stream_button_texture
+ };
+
+ std::vector<MainButton> main_buttons;
+ std::vector<XRectangle> shapes;
+
+ for(int i = 0; i < 3; ++i) {
+ mgl::Text title(titles[i], {0.0f, 0.0f}, title_font);
+ title.set_color(mgl::Color(255, 255, 255));
+
+ mgl::Text description(descriptions[i], {0.0f, 0.0f}, font);
+ description.set_color(mgl::Color(255, 255, 255, 150));
+
+ mgl::Sprite sprite(textures[i]);
+ gsr::Button button({425, 300});
+
+ MainButton main_button = {
+ std::move(title),
+ std::move(description),
+ std::move(sprite),
+ std::move(button)
+ };
+
+ main_buttons.push_back(std::move(main_button));
+ shapes.push_back({});
+ }
+
+ // Settings button
+ shapes.push_back({});
+
+ const int per_button_width = 425;
+ const mgl::vec2i overlay_desired_size = { per_button_width * (int)main_buttons.size(), 300 };
+
+ XGCValues xgcv;
+ xgcv.foreground = WhitePixel(display, DefaultScreen(display));
+ xgcv.line_width = 1;
+ xgcv.line_style = LineSolid;
+
+ Pixmap shape_pixmap = None;
+ GC shape_gc = None;
+
+ auto update_overlay_shape = [&]() {
+ const int main_button_margin = 20;
+ const mgl::vec2i main_buttons_start_pos = target_window_size/2 - overlay_desired_size/2;
+ mgl::vec2i main_button_pos = main_buttons_start_pos;
+
+ for(size_t i = 0; i < main_buttons.size(); ++i) {
+ main_buttons[i].title.set_position(
+ mgl::vec2f(
+ main_button_pos.x + per_button_width * 0.5f - main_buttons[i].title.get_bounds().size.x * 0.5f,
+ main_button_pos.y + main_button_margin).floor());
+
+ main_buttons[i].description.set_position(
+ mgl::vec2f(
+ main_button_pos.x + per_button_width * 0.5f - main_buttons[i].description.get_bounds().size.x * 0.5f,
+ main_button_pos.y + overlay_desired_size.y - main_buttons[i].description.get_bounds().size.y - main_button_margin).floor());
+
+ main_buttons[i].icon.set_position(
+ mgl::vec2f(
+ main_button_pos.x + per_button_width * 0.5f - main_buttons[i].icon.get_texture()->get_size().x * 0.5f,
+ main_button_pos.y + overlay_desired_size.y * 0.5f - main_buttons[i].icon.get_texture()->get_size().y * 0.5f).floor());
+
+ main_buttons[i].button.set_position(main_button_pos.to_vec2f());
+ shapes[i] = {
+ (short)main_button_pos.x, (short)main_button_pos.y, (unsigned short)per_button_width, (unsigned short)overlay_desired_size.y
+ };
+ main_button_pos.x += per_button_width;
+ }
+
+ const mgl::vec2i settings_button_size(128, 128);
+ shapes[main_buttons.size()] = {
+ (short)(main_buttons_start_pos.x + overlay_desired_size.x), (short)(main_buttons_start_pos.y - settings_button_size.y), (unsigned short)settings_button_size.x, (unsigned short)settings_button_size.y
+ };
+
+ if(shape_pixmap) {
+ XFreePixmap(display, shape_pixmap);
+ shape_pixmap = None;
+ }
+
+ if(shape_gc) {
+ XFreeGC(display, shape_gc);
+ shape_gc = None;
+ }
+
+ shape_pixmap = XCreatePixmap(display, window.get_system_handle(), target_window_size.x, target_window_size.y, 1);
+ if(!shape_pixmap)
+ fprintf(stderr, "Error: failed to create shape pixmap\n");
+
+ shape_gc = XCreateGC(display, shape_pixmap, 0, &xgcv);
+
+ XSetForeground(display, shape_gc, 0);
+ XFillRectangle(display, shape_pixmap, shape_gc, 0, 0, target_window_size.x, target_window_size.y);
+
+ XSetForeground(display, shape_gc, 1);
+ XDrawRectangles(display, shape_pixmap, shape_gc, shapes.data(), shapes.size());
+ XFillRectangles(display, shape_pixmap, shape_gc, shapes.data(), shapes.size());
+
+ XShapeCombineMask(display, window.get_system_handle(), ShapeBounding, 0, 0, shape_pixmap, ShapeSet);
+ };
+
+ update_overlay_shape();
+ window.set_visible(true);
+
+ Cursor default_cursor = XCreateFontCursor(display, XC_arrow);
+
+ // TODO: Retry if these fail
+ XGrabPointer(display, window.get_system_handle(), True, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime);
+ XGrabKeyboard(display, window.get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime);
+
+ XEvent xev;
+ mgl::Event event;
+
+ while(window.is_open()) {
+ if(XCheckTypedWindowEvent(display, target_window, ConfigureNotify, &xev) && (xev.xconfigure.width != target_window_size.x || xev.xconfigure.height != target_window_size.y)) {
+ while(XCheckTypedWindowEvent(display, target_window, ConfigureNotify, &xev)) {}
+ target_window_size.x = xev.xconfigure.width;
+ target_window_size.y = xev.xconfigure.height;
+ window.set_size(target_window_size);
+ update_overlay_shape();
+ }
+
+ if(window.poll_event(event)) {
+ for(auto &main_button : main_buttons) {
+ main_button.button.on_event(event, window);
+ }
+
+ if(event.type == mgl::Event::KeyPressed) {
+ if(event.key.code == mgl::Keyboard::Escape) {
+ window.close();
+ break;
+ }
+ }
+ }
+
+ window.clear(mgl::Color(37, 43, 47, 255));
+ for(auto &main_button : main_buttons) {
+ main_button.button.draw(window);
+ window.draw(main_button.icon);
+ window.draw(main_button.title);
+ window.draw(main_button.description);
+ }
+ window.display();
+ }
+
+ return 0;
+}