aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gui/Button.cpp71
-rw-r--r--src/gui/Widget.cpp7
-rw-r--r--src/main.cpp276
3 files changed, 354 insertions, 0 deletions
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;
+}