diff options
-rw-r--r-- | README.md | 17 | ||||
-rw-r--r-- | TODO | 9 | ||||
m--------- | depends/mglpp | 0 | ||||
-rw-r--r-- | fonts/NotoSans-Bold.ttf | bin | 0 -> 616112 bytes | |||
-rw-r--r-- | fonts/NotoSans-Regular.ttf | bin | 0 -> 610392 bytes | |||
-rw-r--r-- | gpu-screen-recorder-overlay-daemon/main.c | 2 | ||||
-rw-r--r-- | include/GlobalHotkeys.hpp | 27 | ||||
-rw-r--r-- | include/GlobalHotkeysX11.hpp | 31 | ||||
-rw-r--r-- | include/GsrInfo.hpp | 4 | ||||
-rw-r--r-- | include/Overlay.hpp | 56 | ||||
-rw-r--r-- | include/Theme.hpp | 11 | ||||
-rw-r--r-- | include/gui/CustomRendererWidget.hpp | 1 | ||||
-rw-r--r-- | include/gui/SettingsPage.hpp | 12 | ||||
-rw-r--r-- | include/gui/Subsection.hpp | 24 | ||||
-rw-r--r-- | include/gui/Widget.hpp | 1 | ||||
-rw-r--r-- | meson.build | 15 | ||||
-rw-r--r-- | src/GlobalHotkeysX11.cpp | 137 | ||||
-rw-r--r-- | src/GsrInfo.cpp | 8 | ||||
-rw-r--r-- | src/Overlay.cpp | 567 | ||||
-rw-r--r-- | src/Theme.cpp | 49 | ||||
-rw-r--r-- | src/gui/Button.cpp | 6 | ||||
-rw-r--r-- | src/gui/ComboBox.cpp | 28 | ||||
-rw-r--r-- | src/gui/CustomRendererWidget.cpp | 4 | ||||
-rw-r--r-- | src/gui/List.cpp | 20 | ||||
-rw-r--r-- | src/gui/ScrollablePage.cpp | 1 | ||||
-rw-r--r-- | src/gui/SettingsPage.cpp | 115 | ||||
-rw-r--r-- | src/gui/Subsection.cpp | 60 | ||||
-rw-r--r-- | src/main.cpp | 620 | ||||
-rwxr-xr-x | uninstall.sh | 1 |
29 files changed, 1140 insertions, 686 deletions
@@ -1,8 +1,19 @@ # GPU Screen Recorder Overlay -A fullscreen overlay UI for [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/), in the style of NVIDIA ShadowPlay. +A fullscreen overlay UI for [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/), in the style of ShadowPlay. # Dependencies -x11, xrandr, xrender, xfixes, opengl +GPU Screen Recorder overlay uses meson build system so you need to install `meson` to build GPU Screen Recorder overlay. + +## Build dependencies +These are the dependencies needed to build GPU Screen Recorder overlay: + +* x11 (libx11, libxrandr, libxrender, libxfixes) +* libglvnd (which provides libgl, libglx and libegl) + +## Runtime dependencies +There are also additional dependencies needed at runtime: + +* Noto fonts # Installation -Run `sudo ./install.sh`. This will install gsr-overlay to `/usr/bin/gsr-overlay`. You can run meson commands manually to install gsr-overlay to another directory.
\ No newline at end of file +Run `sudo ./install.sh`. This will install gsr-overlay to `/usr/bin/gsr-overlay`. You can run meson commands manually to install gsr-overlay to another directory. @@ -24,6 +24,11 @@ Add support for window selection in capture. Add option to record the focused monitor. This works on wayland too when using kms capture since we can get cursor position without root and see which monitor (crtc) the cursor is on. -Add option to select hevc_10bit and av1_10bit. - For system startup dont allow default output/input audio, as that can change after startup. For example if default output is a bluetooth devices that gets connected after startup. + +Make hotkeys configurable. + +Move hotkey to gsr-overlay-daemon which should execute gpu-screen-recorder --info on start, write that output to /tmp/blabla (or $XDG_RUNTIME_DIR) and gsr-overlay + should read that tmp file. gsr-overlay should remove show/hide functions for overlay and run show on startup. + +Add scrollbar to scrollable page. diff --git a/depends/mglpp b/depends/mglpp -Subproject 685cd2d470b399a7aa82ae6a2942ffb0afefab9 +Subproject 8f6c5d220d3e4c250db4bf3022f99c38811b63f diff --git a/fonts/NotoSans-Bold.ttf b/fonts/NotoSans-Bold.ttf Binary files differnew file mode 100644 index 0000000..785ef8a --- /dev/null +++ b/fonts/NotoSans-Bold.ttf diff --git a/fonts/NotoSans-Regular.ttf b/fonts/NotoSans-Regular.ttf Binary files differnew file mode 100644 index 0000000..bdc1ffa --- /dev/null +++ b/fonts/NotoSans-Regular.ttf diff --git a/gpu-screen-recorder-overlay-daemon/main.c b/gpu-screen-recorder-overlay-daemon/main.c index 53ca5ec..e243efe 100644 --- a/gpu-screen-recorder-overlay-daemon/main.c +++ b/gpu-screen-recorder-overlay-daemon/main.c @@ -2,6 +2,8 @@ #include <signal.h> #include <stdbool.h> #include <sys/wait.h> +#include <unistd.h> + #include <X11/Xlib.h> #include <X11/keysym.h> diff --git a/include/GlobalHotkeys.hpp b/include/GlobalHotkeys.hpp new file mode 100644 index 0000000..020f5a5 --- /dev/null +++ b/include/GlobalHotkeys.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include <stdint.h> +#include <functional> +#include <string> + +namespace gsr { + struct Hotkey { + uint64_t key = 0; + uint32_t modifiers = 0; + }; + + using GlobalHotkeyCallback = std::function<void(const std::string &id)>; + + class GlobalHotkeys { + public: + GlobalHotkeys() = default; + GlobalHotkeys(const GlobalHotkeys&) = delete; + GlobalHotkeys& operator=(const GlobalHotkeys&) = delete; + virtual ~GlobalHotkeys() = default; + + virtual bool bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) = 0; + virtual void unbind_key_press(const std::string &id) = 0; + virtual void unbind_all_keys() = 0; + virtual void poll_events() = 0; + }; +}
\ No newline at end of file diff --git a/include/GlobalHotkeysX11.hpp b/include/GlobalHotkeysX11.hpp new file mode 100644 index 0000000..427e9f0 --- /dev/null +++ b/include/GlobalHotkeysX11.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "GlobalHotkeys.hpp" +#include <unordered_map> +#include <X11/Xlib.h> + +namespace gsr { + class GlobalHotkeysX11 : public GlobalHotkeys { + public: + GlobalHotkeysX11(); + GlobalHotkeysX11(const GlobalHotkeysX11&) = delete; + GlobalHotkeysX11& operator=(const GlobalHotkeysX11&) = delete; + ~GlobalHotkeysX11() override; + + bool bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) override; + void unbind_key_press(const std::string &id) override; + void unbind_all_keys() override; + void poll_events() override; + private: + void call_hotkey_callback(Hotkey hotkey) const; + private: + struct HotkeyData { + Hotkey hotkey; + GlobalHotkeyCallback callback; + }; + + Display *dpy = nullptr; + XEvent xev; + std::unordered_map<std::string, HotkeyData> bound_keys_by_id; + }; +}
\ No newline at end of file diff --git a/include/GsrInfo.hpp b/include/GsrInfo.hpp index d90d72f..fb12cd4 100644 --- a/include/GsrInfo.hpp +++ b/include/GsrInfo.hpp @@ -10,7 +10,11 @@ namespace gsr { bool h264 = false; bool h264_software = false; bool hevc = false; + bool hevc_hdr = false; + bool hevc_10bit = false; bool av1 = false; + bool av1_hdr = false; + bool av1_10bit = false; bool vp8 = false; bool vp9 = false; }; diff --git a/include/Overlay.hpp b/include/Overlay.hpp new file mode 100644 index 0000000..de5fa79 --- /dev/null +++ b/include/Overlay.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "gui/PageStack.hpp" +#include "gui/CustomRendererWidget.hpp" +#include "GsrInfo.hpp" +#include "Config.hpp" +#include "window_texture.h" + +#include <mglpp/window/Window.hpp> +#include <mglpp/graphics/Texture.hpp> +#include <mglpp/graphics/Sprite.hpp> +#include <mglpp/graphics/Rectangle.hpp> +#include <mglpp/graphics/Text.hpp> + +namespace gsr { + class Overlay { + public: + Overlay(mgl::Window &window, std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs, mgl::Color bg_color); + Overlay(const Overlay&) = delete; + Overlay& operator=(const Overlay&) = delete; + ~Overlay(); + + void on_event(mgl::Event &event, mgl::Window &window); + void draw(mgl::Window &window); + + void show(); + void hide(); + void toggle_show(); + bool is_open() const; + private: + bool update_compositor_texture(const mgl_monitor *monitor); + private: + mgl::Window &window; + std::string resources_path; + GsrInfo gsr_info; + egl_functions egl_funcs; + mgl::Color bg_color; + std::vector<gsr::AudioDevice> audio_devices; + mgl::Texture window_texture_texture; + mgl::Sprite window_texture_sprite; + mgl::Texture screenshot_texture; + mgl::Sprite screenshot_sprite; + mgl::Rectangle bg_screenshot_overlay; + WindowTexture window_texture; + gsr::PageStack page_stack; + mgl::Rectangle top_bar_background; + mgl::Text top_bar_text; + mgl::Sprite logo_sprite; + CustomRendererWidget close_button_widget; + bool close_button_pressed_inside = false; + bool visible = false; + uint64_t default_cursor = 0; + pid_t gpu_screen_recorder_process = -1; + std::optional<Config> config; + }; +}
\ No newline at end of file diff --git a/include/Theme.hpp b/include/Theme.hpp index c70167b..df25268 100644 --- a/include/Theme.hpp +++ b/include/Theme.hpp @@ -32,11 +32,20 @@ namespace gsr { mgl::Texture settings_texture; mgl::Texture folder_texture; mgl::Texture up_arrow_texture; + mgl::Texture replay_button_texture; + mgl::Texture record_button_texture; + mgl::Texture stream_button_texture; + mgl::Texture close_texture; + mgl::Texture logo_texture; double double_click_timeout_seconds = 0.4; + + // Reloads fonts + bool set_window_size(mgl::vec2i window_size); }; - bool init_theme(const GsrInfo &gsr_info, mgl::vec2i window_size, const std::string &resources_path); + bool init_theme(const GsrInfo &gsr_info, const std::string &resources_path); void deinit_theme(); + Theme& get_theme(); }
\ No newline at end of file diff --git a/include/gui/CustomRendererWidget.hpp b/include/gui/CustomRendererWidget.hpp index e16e532..20bfec8 100644 --- a/include/gui/CustomRendererWidget.hpp +++ b/include/gui/CustomRendererWidget.hpp @@ -15,6 +15,7 @@ namespace gsr { void draw(mgl::Window &window, mgl::vec2f offset) override; mgl::vec2f get_size() override; + void set_size(mgl::vec2f size); std::function<void(mgl::Window &window, mgl::vec2f pos, mgl::vec2f size)> draw_handler; // Return true to allow other widgets to handle events diff --git a/include/gui/SettingsPage.hpp b/include/gui/SettingsPage.hpp index 1ad4c67..b22506e 100644 --- a/include/gui/SettingsPage.hpp +++ b/include/gui/SettingsPage.hpp @@ -13,6 +13,7 @@ namespace gsr { class GsrPage; class PageStack; + class ScrollablePage; class SettingsPage : public StaticPage { public: @@ -31,7 +32,7 @@ namespace gsr { private: std::unique_ptr<RadioButton> create_view_radio_button(); std::unique_ptr<ComboBox> create_record_area_box(const GsrInfo &gsr_info); - std::unique_ptr<List> create_record_area(const GsrInfo &gsr_info); + std::unique_ptr<Widget> create_record_area(const GsrInfo &gsr_info); std::unique_ptr<List> create_select_window(); std::unique_ptr<Entry> create_area_width_entry(); std::unique_ptr<Entry> create_area_height_entry(); @@ -39,14 +40,14 @@ namespace gsr { std::unique_ptr<List> create_area_size_section(); std::unique_ptr<CheckBox> create_restore_portal_session_checkbox(); std::unique_ptr<List> create_restore_portal_session_section(); - std::unique_ptr<List> create_capture_target(const GsrInfo &gsr_info); + std::unique_ptr<Widget> create_capture_target(const GsrInfo &gsr_info); std::unique_ptr<ComboBox> create_audio_track_selection_checkbox(const std::vector<AudioDevice> &audio_devices); std::unique_ptr<Button> create_remove_audio_track_button(List *audio_device_list_ptr); std::unique_ptr<List> create_audio_track(const std::vector<AudioDevice> &audio_devices); std::unique_ptr<Button> create_add_audio_track_button(const std::vector<AudioDevice> &audio_devices); std::unique_ptr<List> create_audio_track_section(const std::vector<AudioDevice> &audio_devices); std::unique_ptr<CheckBox> create_merge_audio_tracks_checkbox(); - std::unique_ptr<List> create_audio_device_section(const std::vector<AudioDevice> &audio_devices); + std::unique_ptr<Widget> create_audio_device_section(const std::vector<AudioDevice> &audio_devices); std::unique_ptr<ComboBox> create_video_quality_box(); std::unique_ptr<List> create_video_quality(); std::unique_ptr<ComboBox> create_color_range_box(); @@ -62,7 +63,9 @@ namespace gsr { std::unique_ptr<ComboBox> create_framerate_mode_box(); std::unique_ptr<List> create_framerate_mode(); std::unique_ptr<List> create_framerate_section(); - std::unique_ptr<List> create_settings(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices); + std::unique_ptr<Widget> create_record_cursor_section(); + std::unique_ptr<Widget> create_video_section(const GsrInfo &gsr_info); + std::unique_ptr<Widget> create_settings(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices); void add_widgets(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices); void add_page_specific_widgets(); @@ -92,6 +95,7 @@ namespace gsr { std::optional<Config> &config; GsrPage *content_page_ptr = nullptr; + ScrollablePage *settings_scrollable_page_ptr = nullptr; List *settings_list_ptr = nullptr; List *select_window_list_ptr = nullptr; List *area_size_list_ptr = nullptr; diff --git a/include/gui/Subsection.hpp b/include/gui/Subsection.hpp new file mode 100644 index 0000000..90cb798 --- /dev/null +++ b/include/gui/Subsection.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "Widget.hpp" +#include "Label.hpp" +#include <memory> + +namespace gsr { + class Subsection : public Widget { + public: + // If size width or height is 0 then the size in that direction is the size of the widgets + Subsection(const char *title, std::unique_ptr<Widget> inner_widget, mgl::vec2f size); + Subsection(const Subsection&) = delete; + Subsection& operator=(const Subsection&) = delete; + + bool on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) override; + void draw(mgl::Window &window, mgl::vec2f offset) override; + + mgl::vec2f get_size() override; + private: + Label label; + std::unique_ptr<Widget> inner_widget; + mgl::vec2f size; + }; +}
\ No newline at end of file diff --git a/include/gui/Widget.hpp b/include/gui/Widget.hpp index 498ca05..9a18722 100644 --- a/include/gui/Widget.hpp +++ b/include/gui/Widget.hpp @@ -13,6 +13,7 @@ namespace gsr { friend class ScrollablePage; friend class List; friend class Page; + friend class Subsection; public: enum class Alignment { START, diff --git a/meson.build b/meson.build index ddc08f5..379d96e 100644 --- a/meson.build +++ b/meson.build @@ -7,6 +7,7 @@ elif get_option('buildtype') == 'release' endif src = [ + 'src/window_texture.c', 'src/Theme.cpp', 'src/gui/Widget.cpp', 'src/gui/ScrollablePage.cpp', @@ -26,11 +27,13 @@ src = [ 'src/gui/FileChooser.cpp', 'src/gui/SettingsPage.cpp', 'src/gui/GsrPage.cpp', + 'src/gui/Subsection.cpp', 'src/Utils.cpp', 'src/Config.cpp', 'src/GsrInfo.cpp', 'src/Process.cpp', - 'src/window_texture.c', + 'src/Overlay.cpp', + 'src/GlobalHotkeysX11.cpp', 'src/main.cpp', ] @@ -54,4 +57,12 @@ executable( cpp_args : '-DGSR_OVERLAY_RESOURCES_PATH="' + gsr_overlay_resources_path + '"', ) -install_subdir('images', install_dir : gsr_overlay_resources_path)
\ No newline at end of file +executable( + 'gsr-overlay-daemon', + ['gpu-screen-recorder-overlay-daemon/main.c'], + install : true, + dependencies : [dependency('x11')], +) + +install_subdir('images', install_dir : gsr_overlay_resources_path) +install_subdir('fonts', install_dir : gsr_overlay_resources_path)
\ No newline at end of file diff --git a/src/GlobalHotkeysX11.cpp b/src/GlobalHotkeysX11.cpp new file mode 100644 index 0000000..6b01bfd --- /dev/null +++ b/src/GlobalHotkeysX11.cpp @@ -0,0 +1,137 @@ +#include "../include/GlobalHotkeysX11.hpp" +#define XK_MISCELLANY +#include <X11/keysymdef.h> + +namespace gsr { + static bool x_failed = false; + static int xerror_grab_error(Display*, XErrorEvent*) { + x_failed = true; + return 0; + } + + static unsigned int x11_get_numlock_mask(Display *dpy) { + unsigned int numlockmask = 0; + KeyCode numlock_keycode = XKeysymToKeycode(dpy, XK_Num_Lock); + XModifierKeymap *modmap = XGetModifierMapping(dpy); + if(modmap) { + for(int i = 0; i < 8; ++i) { + for(int j = 0; j < modmap->max_keypermod; ++j) { + if(modmap->modifiermap[i * modmap->max_keypermod + j] == numlock_keycode) + numlockmask = (1 << i); + } + } + XFreeModifiermap(modmap); + } + return numlockmask; + } + + GlobalHotkeysX11::GlobalHotkeysX11() { + dpy = XOpenDisplay(NULL); + if(!dpy) + fprintf(stderr, "GlobalHotkeysX11 error: failed to connect to X11 server, global hotkeys wont be available\n"); + } + + GlobalHotkeysX11::~GlobalHotkeysX11() { + if(dpy) { + XCloseDisplay(dpy); + dpy = nullptr; + } + } + + bool GlobalHotkeysX11::bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) { + if(!dpy) + return false; + + auto it = bound_keys_by_id.find(id); + if(it != bound_keys_by_id.end()) + return false; + + x_failed = false; + XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); + + unsigned int numlock_mask = x11_get_numlock_mask(dpy); + unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; + for(int i = 0; i < 4; ++i) { + XGrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy), False, GrabModeAsync, GrabModeAsync); + } + XSync(dpy, False); + + if(x_failed) { + for(int i = 0; i < 4; ++i) { + XUngrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy)); + } + XSync(dpy, False); + XSetErrorHandler(prev_xerror); + return false; + } else { + XSetErrorHandler(prev_xerror); + bound_keys_by_id[id] = { hotkey, std::move(callback) }; + return true; + } + } + + void GlobalHotkeysX11::unbind_key_press(const std::string &id) { + if(!dpy) + return; + + auto it = bound_keys_by_id.find(id); + if(it == bound_keys_by_id.end()) + return; + + x_failed = false; + XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); + + unsigned int numlock_mask = x11_get_numlock_mask(dpy); + unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; + for(int i = 0; i < 4; ++i) { + XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), it->second.hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy)); + } + XSync(dpy, False); + + XSetErrorHandler(prev_xerror); + bound_keys_by_id.erase(id); + } + + void GlobalHotkeysX11::unbind_all_keys() { + if(!dpy) + return; + + x_failed = false; + XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); + + unsigned int numlock_mask = x11_get_numlock_mask(dpy); + unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; + for(auto it = bound_keys_by_id.begin(); it != bound_keys_by_id.end();) { + for(int i = 0; i < 4; ++i) { + XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), it->second.hotkey.modifiers | modifiers[i], DefaultRootWindow(dpy)); + } + } + bound_keys_by_id.clear(); + XSync(dpy, False); + + XSetErrorHandler(prev_xerror); + } + + void GlobalHotkeysX11::poll_events() { + while(XPending(dpy)) { + XNextEvent(dpy, &xev); + if(xev.type == KeyPress) { + const KeySym key_sym = XLookupKeysym(&xev.xkey, 0); + call_hotkey_callback({ key_sym, xev.xkey.state }); + } + } + } + + static unsigned int key_state_without_locks(unsigned int key_state) { + return key_state & ~(Mod2Mask|LockMask); + } + + void GlobalHotkeysX11::call_hotkey_callback(Hotkey hotkey) const { + for(const auto &[key, val] : bound_keys_by_id) { + if(val.hotkey.key == hotkey.key && key_state_without_locks(val.hotkey.modifiers) == key_state_without_locks(hotkey.modifiers)) { + val.callback(key); + return; + } + } + } +}
\ No newline at end of file diff --git a/src/GsrInfo.cpp b/src/GsrInfo.cpp index 5d9773d..916430c 100644 --- a/src/GsrInfo.cpp +++ b/src/GsrInfo.cpp @@ -46,8 +46,16 @@ namespace gsr { gsr_info->supported_video_codecs.h264_software = true; else if(line == "hevc") gsr_info->supported_video_codecs.hevc = true; + else if(line == "hevc_hdr") + gsr_info->supported_video_codecs.hevc_hdr = true; + else if(line == "hevc_10bit") + gsr_info->supported_video_codecs.hevc_10bit = true; else if(line == "av1") gsr_info->supported_video_codecs.av1 = true; + else if(line == "av1_hdr") + gsr_info->supported_video_codecs.av1_hdr = true; + else if(line == "av1_10bit") + gsr_info->supported_video_codecs.av1_10bit = true; else if(line == "vp8") gsr_info->supported_video_codecs.vp8 = true; else if(line == "vp9") diff --git a/src/Overlay.cpp b/src/Overlay.cpp new file mode 100644 index 0000000..a7b813f --- /dev/null +++ b/src/Overlay.cpp @@ -0,0 +1,567 @@ +#include "../include/Overlay.hpp" +#include "../include/Theme.hpp" +#include "../include/Config.hpp" +#include "../include/Process.hpp" +#include "../include/gui/StaticPage.hpp" +#include "../include/gui/DropdownButton.hpp" +#include "../include/gui/CustomRendererWidget.hpp" +#include "../include/gui/SettingsPage.hpp" +#include "../include/gui/Utils.hpp" +#include "../include/gui/PageStack.hpp" + +#include <string.h> +#include <assert.h> +// TODO: Remove +#include <signal.h> +#include <sys/wait.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/cursorfont.h> +#include <mglpp/system/Rect.hpp> +#include <mglpp/window/Event.hpp> + +extern "C" { +#include <mgl/mgl.h> +} + +namespace gsr { + static mgl::Texture texture_from_ximage(XImage *img) { + uint8_t *texture_data = (uint8_t*)malloc(img->width * img->height * 3); + // TODO: + + for(int y = 0; y < img->height; ++y) { + for(int x = 0; x < img->width; ++x) { + unsigned long pixel = XGetPixel(img, x, y); + unsigned char red = (pixel & img->red_mask) >> 16; + unsigned char green = (pixel & img->green_mask) >> 8; + unsigned char blue = pixel & img->blue_mask; + + const size_t texture_data_index = (x + y * img->width) * 3; + texture_data[texture_data_index + 0] = red; + texture_data[texture_data_index + 1] = green; + texture_data[texture_data_index + 2] = blue; + } + } + + mgl::Texture texture; + // TODO: + texture.load_from_memory(texture_data, img->width, img->height, MGL_IMAGE_FORMAT_RGB); + free(texture_data); + return texture; + } + + static char hex_value_to_str(uint8_t v) { + if(v <= 9) + return '0' + v; + else if(v >= 10 && v <= 15) + return 'A' + (v - 10); + else + return '0'; + } + + // Excludes alpha + static std::string color_to_hex_str(mgl::Color color) { + std::string result; + result.resize(6); + + result[0] = hex_value_to_str((color.r & 0xF0) >> 4); + result[1] = hex_value_to_str(color.r & 0x0F); + + result[2] = hex_value_to_str((color.g & 0xF0) >> 4); + result[3] = hex_value_to_str(color.g & 0x0F); + + result[4] = hex_value_to_str((color.b & 0xF0) >> 4); + result[5] = hex_value_to_str(color.b & 0x0F); + + return result; + } + + static Window get_window_at_cursor_position(Display *display) { + Window root_window = None; + Window window = None; + int dummy_i; + unsigned int dummy_u; + int cursor_pos_x = 0; + int cursor_pos_y = 0; + XQueryPointer(display, DefaultRootWindow(display), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u); + return window; + } + + struct DrawableGeometry { + int x, y, width, height; + }; + + static bool get_drawable_geometry(Display *display, Drawable drawable, DrawableGeometry *geometry) { + geometry->x = 0; + geometry->y = 0; + geometry->width = 0; + geometry->height = 0; + + Window root_window; + unsigned int w, h; + unsigned int dummy_border, dummy_depth; + Status s = XGetGeometry(display, drawable, &root_window, &geometry->x, &geometry->y, &w, &h, &dummy_border, &dummy_depth); + + geometry->width = w; + geometry->height = h; + return s != Success; + } + + static bool diff_int(int a, int b, int difference) { + return std::abs(a - b) <= difference; + } + + static bool is_window_fullscreen_on_monitor(Display *display, Window window, const mgl_monitor *monitor) { + if(!window) + return false; + + DrawableGeometry geometry; + if(!get_drawable_geometry(display, window, &geometry)) + return false; + + const int margin = 2; + return diff_int(geometry.x, monitor->pos.x, margin) && diff_int(geometry.y, monitor->pos.y, margin) + && diff_int(geometry.width, monitor->size.x, margin) && diff_int(geometry.height, monitor->size.y, margin); + } + + #define _NET_WM_STATE_REMOVE 0 + #define _NET_WM_STATE_ADD 1 + #define _NET_WM_STATE_TOGGLE 2 + + static Bool set_window_wm_state(Display *display, Window window, Atom atom) { + Atom net_wm_state_atom = XInternAtom(display, "_NET_WM_STATE", False); + if(!net_wm_state_atom) { + fprintf(stderr, "Error: failed to find atom _NET_WM_STATE\n"); + return False; + } + + XClientMessageEvent xclient; + memset(&xclient, 0, sizeof(xclient)); + + xclient.type = ClientMessage; + xclient.window = window; + xclient.message_type = net_wm_state_atom; + xclient.format = 32; + xclient.data.l[0] = _NET_WM_STATE_ADD; + xclient.data.l[1] = atom; + xclient.data.l[2] = 0; + xclient.data.l[3] = 0; + xclient.data.l[4] = 0; + + XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&xclient); + XFlush(display); + return True; + } + + static Bool make_window_sticky(Display* display, Window window) { + Atom net_wm_state_sticky_atom = XInternAtom(display, "_NET_WM_STATE_STICKY", False); + if(!net_wm_state_sticky_atom) { + fprintf(stderr, "Error: failed to find atom _NET_WM_STATE_STICKY\n"); + return False; + } + + return set_window_wm_state(display, window, net_wm_state_sticky_atom); + } + + // Returns the first monitor if not found. Assumes there is at least one monitor connected. + static const mgl_monitor* find_monitor_by_cursor_position(mgl::Window &window) { + const mgl_window *win = window.internal_window(); + assert(win->num_monitors > 0); + for(int i = 0; i < win->num_monitors; ++i) { + const mgl_monitor *mon = &win->monitors[i]; + if(mgl::IntRect({ mon->pos.x, mon->pos.y }, { mon->size.x, mon->size.y }).contains({ win->cursor_position.x, win->cursor_position.y })) + return mon; + } + return &win->monitors[0]; + } + + static bool is_compositor_running(Display *dpy, int screen) { + char prop_name[20]; + snprintf(prop_name, sizeof(prop_name), "_NET_WM_CM_S%d", screen); + Atom prop_atom = XInternAtom(dpy, prop_name, False); + return XGetSelectionOwner(dpy, prop_atom) != None; + } + + Overlay::Overlay(mgl::Window &window, std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs, mgl::Color bg_color) : + window(window), + resources_path(std::move(resources_path)), + gsr_info(std::move(gsr_info)), + egl_funcs(egl_funcs), + bg_color(bg_color), + bg_screenshot_overlay({0.0f, 0.0f}), + top_bar_background({0.0f, 0.0f}), + top_bar_text("GPU Screen Recorder", get_theme().top_bar_font), + logo_sprite(&get_theme().logo_texture), + close_button_widget({0.0f, 0.0f}) + { + memset(&window_texture, 0, sizeof(window_texture)); + } + + Overlay::~Overlay() { + hide(); + if(gpu_screen_recorder_process > 0) { + kill(gpu_screen_recorder_process, SIGINT); + int status; + if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { + perror("waitpid failed"); + /* Ignore... */ + } + gpu_screen_recorder_process = -1; + } + } + + void Overlay::on_event(mgl::Event &event, mgl::Window &window) { + if(!visible) + return; + + close_button_widget.on_event(event, window, mgl::vec2f(0.0f, 0.0f)); + page_stack.on_event(event, window, mgl::vec2f(0.0f, 0.0f)); + if(event.type == mgl::Event::KeyReleased) { + if(event.key.code == mgl::Keyboard::Escape) + page_stack.pop(); + } + } + + void Overlay::draw(mgl::Window &window) { + if(!visible) + return; + + if(page_stack.empty()) { + hide(); + return; + } + + if(window_texture_sprite.get_texture() && window_texture.texture_id) { + window.draw(window_texture_sprite); + window.draw(bg_screenshot_overlay); + } else if(screenshot_texture.is_valid()) { + window.draw(screenshot_sprite); + window.draw(bg_screenshot_overlay); + } + + window.draw(top_bar_background); + window.draw(top_bar_text); + window.draw(logo_sprite); + + close_button_widget.draw(window, mgl::vec2f(0.0f, 0.0f)); + page_stack.draw(window, mgl::vec2f(0.0f, 0.0f)); + } + + void Overlay::show() { + mgl_window *win = window.internal_window(); + if(win->num_monitors == 0) { + fprintf(stderr, "gsr warning: no monitors found, not showing overlay\n"); + return; + } + + const mgl_monitor *focused_monitor = find_monitor_by_cursor_position(window); + const mgl::vec2i window_pos(focused_monitor->pos.x, focused_monitor->pos.y); + const mgl::vec2i window_size(focused_monitor->size.x, focused_monitor->size.y); + get_theme().set_window_size(window_size); + + window.set_size(window_size); + window.set_size_limits(window_size, window_size); + window.set_position(window_pos); + + update_compositor_texture(focused_monitor); + + audio_devices = get_audio_devices(); + config = read_config(); + + bg_screenshot_overlay = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height)); + top_bar_background = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height*0.06f).floor()); + top_bar_text = mgl::Text("GPU Screen Recorder", get_theme().top_bar_font); + logo_sprite = mgl::Sprite(&get_theme().logo_texture); + close_button_widget.set_size(mgl::vec2f(top_bar_background.get_size().y * 0.3f, top_bar_background.get_size().y * 0.3f).floor()); + + bg_screenshot_overlay.set_color(bg_color); + top_bar_background.set_color(mgl::Color(0, 0, 0, 180)); + //top_bar_text.set_color(get_theme().tint_color); + top_bar_text.set_position((top_bar_background.get_position() + top_bar_background.get_size()*0.5f - top_bar_text.get_bounds().size*0.5f).floor()); + close_button_widget.set_position(mgl::vec2f(get_theme().window_width - close_button_widget.get_size().x - 50.0f, top_bar_background.get_size().y * 0.5f - close_button_widget.get_size().y * 0.5f).floor()); + + logo_sprite.set_height((int)(top_bar_background.get_size().y * 0.65f)); + logo_sprite.set_position(mgl::vec2f( + (top_bar_background.get_size().y - logo_sprite.get_size().y) * 0.5f, + top_bar_background.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f + ).floor()); + + while(!page_stack.empty()) { + page_stack.pop(); + } + + auto front_page = std::make_unique<StaticPage>(window_size.to_vec2f()); + StaticPage *front_page_ptr = front_page.get(); + page_stack.push(std::move(front_page)); + + const int button_height = window_size.y / 5.0f; + const int button_width = button_height; + + auto main_buttons_list = std::make_unique<List>(List::Orientation::HORIZONTAL); + main_buttons_list->set_spacing(0.0f); + { + auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Instant Replay", "On", "Off", &get_theme().replay_button_texture, + mgl::vec2f(button_width, button_height)); + button->add_item("Start", "start"); + button->add_item("Settings", "settings"); + button->on_click = [&](const std::string &id) { + if(id == "settings") { + auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(replay_settings_page)); + return; + } + /* + char window_to_record_str[32]; + snprintf(window_to_record_str, sizeof(window_to_record_str), "%ld", target_window); + + const char *args[] = { + "gpu-screen-recorder", "-w", window_to_record_str, + "-c", "mp4", + "-f", "60", + "-o", "/home/dec05eba/Videos/gpu-screen-recorder.mp4", + nullptr + }; + exec_program_daemonized(args); + */ + }; + main_buttons_list->add_widget(std::move(button)); + } + { + auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Record", "Recording", "Not recording", &get_theme().record_button_texture, + mgl::vec2f(button_width, button_height)); + DropdownButton *button_ptr = button.get(); + button->add_item("Start", "start"); + button->add_item("Settings", "settings"); + button->on_click = [&, button_ptr](const std::string &id) { + if(id == "settings") { + auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(record_settings_page)); + return; + } + + if(id != "start") + return; + + // window.close(); + // usleep(1000 * 50); // 50 milliseconds + + const std::string tint_color_as_hex = color_to_hex_str(get_theme().tint_color); + + if(gpu_screen_recorder_process > 0) { + kill(gpu_screen_recorder_process, SIGINT); + int status; + if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { + perror("waitpid failed"); + /* Ignore... */ + } + // window.set_visible(false); + // window.close(); + // return; + //exit(0); + gpu_screen_recorder_process = -1; + button_ptr->set_item_label(id, "Start"); + + // TODO: Show this with a slight delay to make sure it doesn't show up in the video + const std::string record_image_filepath = resources_path + "images/record.png"; + const char *notification_args[] = { + "gsr-notify", "--text", "Recording has been saved", "--timeout", "3.0", + "--icon", record_image_filepath.c_str(), + "--icon-color", "ffffff", "--bg-color", tint_color_as_hex.c_str(), + nullptr + }; + exec_program_daemonized(notification_args); + return; + } + + const char *args[] = { + "gpu-screen-recorder", "-w", "screen", + "-c", "mp4", + "-f", "60", + "-o", "/home/dec05eba/Videos/gpu-screen-recorder.mp4", + nullptr + }; + gpu_screen_recorder_process = exec_program(args); + if(gpu_screen_recorder_process == -1) { + // TODO: Show notification failed to start + } else { + button_ptr->set_item_label(id, "Stop"); + } + + // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. + // Make clear to the user that the recording starts after the notification is gone. + // Maybe have the option in notification to show timer until its getting hidden, then the notification can say: + // Starting recording in 3... + // 2... + // 1... + // TODO: Do not run this is a daemon. Instead get the pid and when launching another notification close the current notification + // program and start another one. This can also be used to check when the notification has finished by checking with waitpid NOWAIT + // to see when the program has exit. + const std::string record_image_filepath = resources_path + "images/record.png"; + const char *notification_args[] = { + "gsr-notify", "--text", "Recording has started", "--timeout", "3.0", + "--icon", record_image_filepath.c_str(), + "--icon-color", tint_color_as_hex.c_str(), "--bg-color", tint_color_as_hex.c_str(), + nullptr + }; + exec_program_daemonized(notification_args); + //exit(0); + // window.set_visible(false); + // window.close(); + + // TODO: Show notification with args: + // "Recording has started" 3.0 ./images/record.png 76b900 + }; + main_buttons_list->add_widget(std::move(button)); + } + { + auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Livestream", "Streaming", "Not streaming", &get_theme().stream_button_texture, + mgl::vec2f(button_width, button_height)); + button->add_item("Start", "start"); + button->add_item("Settings", "settings"); + button->on_click = [&](const std::string &id) { + if(id == "settings") { + auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(stream_settings_page)); + return; + } + }; + main_buttons_list->add_widget(std::move(button)); + } + + const mgl::vec2f main_buttons_list_size = main_buttons_list->get_size(); + main_buttons_list->set_position((mgl::vec2f(window_size.x * 0.5f, window_size.y * 0.25f) - main_buttons_list_size * 0.5f).floor()); + front_page_ptr->add_widget(std::move(main_buttons_list)); + + close_button_widget.draw_handler = [&](mgl::Window &window, mgl::vec2f pos, mgl::vec2f size) { + if(mgl::FloatRect(pos, size).contains(window.get_mouse_position().to_vec2f())) { + const float border_scale = 0.0015f; + const int border_size = std::max(1.0f, border_scale * get_theme().window_height); + draw_rectangle_outline(window, pos, size, get_theme().tint_color, border_size); + } + + mgl::Sprite close_sprite(&get_theme().close_texture); + close_sprite.set_position(pos); + close_sprite.set_size(size); + window.draw(close_sprite); + }; + + close_button_widget.event_handler = [&](mgl::Event &event, mgl::Window&, mgl::vec2f pos, mgl::vec2f size) { + if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { + close_button_pressed_inside = mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y)); + } else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left && close_button_pressed_inside) { + if(mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y))) { + while(!page_stack.empty()) { + page_stack.pop(); + } + return false; + } + } + return true; + }; + + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + + window.set_fullscreen(true); + window.set_visible(true); + make_window_sticky(display, window.get_system_handle()); + + if(default_cursor) { + XFreeCursor(display, default_cursor); + default_cursor = 0; + } + default_cursor = XCreateFontCursor(display, XC_arrow); + + // TODO: Retry if these fail. + // TODO: Hmm, these dont work in owlboy. Maybe owlboy uses xi2 and that breaks this (does it?). + // Remove these grabs when debugging with a debugger, or your X11 session will appear frozen + + XGrabPointer(display, window.get_system_handle(), True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | + Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | + ButtonMotionMask, + GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); + // TODO: This breaks global hotkeys + //XGrabKeyboard(display, window.get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); + + XSetInputFocus(display, window.get_system_handle(), RevertToParent, CurrentTime); + XFlush(display); + + //window.set_fullscreen(true); + + visible = true; + + mgl::Event event; + event.type = mgl::Event::MouseMoved; + event.mouse_move.x = window.get_mouse_position().x; + event.mouse_move.y = window.get_mouse_position().y; + on_event(event, window); + } + + void Overlay::hide() { + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + + if(default_cursor) { + XFreeCursor(display, default_cursor); + default_cursor = 0; + } + + //XUngrabKeyboard(display, CurrentTime); + XUngrabPointer(display, CurrentTime); + XFlush(display); + + window_texture_deinit(&window_texture); + visible = false; + window.set_visible(false); + } + + void Overlay::toggle_show() { + if(visible) + hide(); + else + show(); + } + + bool Overlay::is_open() const { + return visible; + } + + bool Overlay::update_compositor_texture(const mgl_monitor *monitor) { + window_texture_deinit(&window_texture); + window_texture_sprite.set_texture(nullptr); + screenshot_texture.clear(); + screenshot_sprite.set_texture(nullptr); + + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + + if(is_compositor_running(display, 0)) + return false; + + bool window_texture_loaded = false; + const Window window_at_cursor_position = get_window_at_cursor_position(display); + if(is_window_fullscreen_on_monitor(display, window_at_cursor_position, monitor) && window_at_cursor_position) + window_texture_loaded = window_texture_init(&window_texture, display, mgl_window_get_egl_display(window.internal_window()), window_at_cursor_position, egl_funcs) == 0; + + if(window_texture_loaded && window_texture.texture_id) { + window_texture_texture = mgl::Texture(window_texture.texture_id, MGL_TEXTURE_FORMAT_RGB); + window_texture_sprite.set_texture(&window_texture_texture); + } else { + XImage *img = XGetImage(display, DefaultRootWindow(display), monitor->pos.x, monitor->pos.y, monitor->size.x, monitor->size.y, AllPlanes, ZPixmap); + if(!img) + fprintf(stderr, "Error: failed to take a screenshot\n"); + + if(img) { + screenshot_texture = texture_from_ximage(img); + if(screenshot_texture.is_valid()) + screenshot_sprite.set_texture(&screenshot_texture); + XDestroyImage(img); + img = NULL; + } + } + + return true; + } +}
\ No newline at end of file diff --git a/src/Theme.cpp b/src/Theme.cpp index d498ef3..523a324 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -1,19 +1,36 @@ #include "../include/Theme.hpp" #include "../include/GsrInfo.hpp" + #include <assert.h> namespace gsr { static Theme *theme = nullptr; - bool init_theme(const GsrInfo &gsr_info, mgl::vec2i window_size, const std::string &resources_path) { + bool Theme::set_window_size(mgl::vec2i window_size) { + if(std::abs(window_size.x - window_width) < 0.1f && std::abs(window_size.y - window_height) < 0.1f) + return true; + + window_width = window_size.x; + window_height = window_size.y; + + if(!theme->title_font.load_from_file(theme->title_font_file, std::max(16.0f, window_size.y * 0.019f))) + return false; + + if(!theme->top_bar_font.load_from_file(theme->title_font_file, std::max(23.0f, window_size.y * 0.03f))) + return false; + + if(!theme->body_font.load_from_file(theme->body_font_file, std::max(13.0f, window_size.y * 0.015f))) + return false; + + return true; + } + + bool init_theme(const GsrInfo &gsr_info, const std::string &resources_path) { if(theme) return true; theme = new Theme(); - theme->window_width = window_size.x; - theme->window_height = window_size.y; - switch(gsr_info.gpu_info.vendor) { case GpuVendor::UNKNOWN: { break; @@ -32,31 +49,37 @@ namespace gsr { } } - if(!theme->body_font_file.load("/usr/share/fonts/noto/NotoSans-Regular.ttf", mgl::MemoryMappedFile::LoadOptions{true, false})) + if(!theme->body_font_file.load((resources_path + "fonts/NotoSans-Regular.ttf").c_str(), mgl::MemoryMappedFile::LoadOptions{true, false})) goto error; - if(!theme->title_font_file.load("/usr/share/fonts/noto/NotoSans-Bold.ttf", mgl::MemoryMappedFile::LoadOptions{true, false})) + if(!theme->title_font_file.load((resources_path + "fonts/NotoSans-Bold.ttf").c_str(), mgl::MemoryMappedFile::LoadOptions{true, false})) goto error; - if(!theme->title_font.load_from_file(theme->title_font_file, std::max(16.0f, window_size.y * 0.019f))) + if(!theme->combobox_arrow_texture.load_from_file((resources_path + "images/combobox_arrow.png").c_str())) goto error; - if(!theme->top_bar_font.load_from_file(theme->title_font_file, std::max(23.0f, window_size.y * 0.03f))) + if(!theme->settings_texture.load_from_file((resources_path + "images/settings.png").c_str())) goto error; - if(!theme->body_font.load_from_file(theme->body_font_file, std::max(13.0f, window_size.y * 0.015f))) + if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str())) + goto error; + + if(!theme->up_arrow_texture.load_from_file((resources_path + "images/up_arrow.png").c_str())) + goto error; + + if(!theme->replay_button_texture.load_from_file((resources_path + "images/replay.png").c_str())) goto error; - if(!theme->combobox_arrow_texture.load_from_file((resources_path + "images/combobox_arrow.png").c_str(), {false, false, false})) + if(!theme->record_button_texture.load_from_file((resources_path + "images/record.png").c_str())) goto error; - if(!theme->settings_texture.load_from_file((resources_path + "images/settings.png").c_str(), {false, false, false})) + if(!theme->stream_button_texture.load_from_file((resources_path + "images/stream.png").c_str())) goto error; - if(!theme->folder_texture.load_from_file((resources_path + "images/folder.png").c_str(), {false, false, false})) + if(!theme->close_texture.load_from_file((resources_path + "images/cross.png").c_str())) goto error; - if(!theme->up_arrow_texture.load_from_file((resources_path + "images/up_arrow.png").c_str(), {false, false, false})) + if(!theme->logo_texture.load_from_file((resources_path + "images/gpu_screen_recorder_logo.png").c_str())) goto error; return true; diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index 0cf5bee..bccf7b0 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -48,8 +48,10 @@ namespace gsr { window.draw(text); const bool mouse_inside = mgl::FloatRect(draw_pos, item_size).contains(window.get_mouse_position().to_vec2f()) && !has_parent_with_selected_child_widget(); - if(mouse_inside) - draw_rectangle_outline(window, draw_pos, item_size, get_theme().tint_color, std::max(1.0f, border_scale * get_theme().window_height)); + if(mouse_inside) { + const mgl::Color outline_color = (bg_color == get_theme().tint_color) ? mgl::Color(255, 255, 255) : get_theme().tint_color; + draw_rectangle_outline(window, draw_pos, item_size, outline_color, std::max(1.0f, border_scale * get_theme().window_height)); + } } mgl::vec2f Button::get_size() { diff --git a/src/gui/ComboBox.cpp b/src/gui/ComboBox.cpp index b344c4e..760ddae 100644 --- a/src/gui/ComboBox.cpp +++ b/src/gui/ComboBox.cpp @@ -22,6 +22,9 @@ namespace gsr { if(!visible) return true; + if(items.empty()) + return true; + const int padding_top = padding_top_scale * get_theme().window_height; const int padding_bottom = padding_bottom_scale * get_theme().window_height; const int padding_left = padding_left_scale * get_theme().window_height; @@ -73,9 +76,6 @@ namespace gsr { update_if_dirty(); - if(items.empty()) - return; - const int padding_top = padding_top_scale * get_theme().window_height; const int padding_bottom = padding_bottom_scale * get_theme().window_height; const int padding_left = padding_left_scale * get_theme().window_height; @@ -106,15 +106,17 @@ namespace gsr { const bool mouse_inside = mgl::FloatRect(draw_pos, item_size).contains(window.get_mouse_position().to_vec2f()) && !has_parent_with_selected_child_widget(); mgl::vec2f pos = draw_pos + mgl::vec2f(padding_left, padding_top); - Item &item = items[selected_item]; - item.text.set_position(pos.floor()); - if(show_dropdown || mouse_inside) { - const int border_size = std::max(1.0f, border_scale * get_theme().window_height); - const mgl::Color border_color = get_theme().tint_color; - draw_rectangle_outline(window, pos - mgl::vec2f(padding_left, padding_top), item_size.floor(), border_color, border_size); + if(selected_item < items.size()) { + Item &selected_item_widget = items[selected_item]; + selected_item_widget.text.set_position(pos.floor()); + if(show_dropdown || mouse_inside) { + const int border_size = std::max(1.0f, border_scale * get_theme().window_height); + const mgl::Color border_color = get_theme().tint_color; + draw_rectangle_outline(window, pos - mgl::vec2f(padding_left, padding_top), item_size.floor(), border_color, border_size); + } + window.draw(selected_item_widget.text); + pos.y += selected_item_widget.text.get_bounds().size.y + padding_top + padding_bottom; } - window.draw(item.text); - pos.y += item.text.get_bounds().size.y + padding_top + padding_bottom; for(size_t i = 0; i < items.size(); ++i) { Item &item = items[i]; @@ -185,6 +187,10 @@ namespace gsr { max_size.x = std::max(max_size.x, bounds.x + padding_left + padding_right); max_size.y += bounds.y + padding_top + padding_bottom; } + + if(max_size.x <= 0.001f) + max_size.x = 50.0f; + max_size.x += padding_left + get_dropdown_arrow_height(); dirty = false; } diff --git a/src/gui/CustomRendererWidget.cpp b/src/gui/CustomRendererWidget.cpp index 98b7caf..cfb113b 100644 --- a/src/gui/CustomRendererWidget.cpp +++ b/src/gui/CustomRendererWidget.cpp @@ -38,4 +38,8 @@ namespace gsr { return size; } + + void CustomRendererWidget::set_size(mgl::vec2f size) { + this->size = size; + } }
\ No newline at end of file diff --git a/src/gui/List.cpp b/src/gui/List.cpp index 849329f..81f0e80 100644 --- a/src/gui/List.cpp +++ b/src/gui/List.cpp @@ -52,6 +52,9 @@ namespace gsr { if(!widget->visible) continue; + if(i > 0) + draw_pos.y += spacing; + const auto widget_size = widget->get_size(); // TODO: Do this parent widget alignment for horizontal alignment and for other types of widget alignment // and other widgets. @@ -67,8 +70,6 @@ namespace gsr { if(widget.get() != selected_widget) widget->draw(window, mgl::vec2f(0.0f, 0.0f)); draw_pos.y += widget_size.y; - if(widget_size.y > 0.001f && i + 1 < widgets.size()) - draw_pos.y += spacing; } break; } @@ -78,6 +79,9 @@ namespace gsr { if(!widget->visible) continue; + if(i > 0) + draw_pos.x += spacing; + const auto widget_size = widget->get_size(); if(content_alignment == Alignment::CENTER) offset.y = floor(size.y * 0.5f - widget_size.y * 0.5f); @@ -88,8 +92,6 @@ namespace gsr { if(widget.get() != selected_widget) widget->draw(window, mgl::vec2f(0.0f, 0.0f)); draw_pos.x += widget_size.x; - if(widget_size.x > 0.001f && i + 1 < widgets.size()) - draw_pos.x += spacing; } break; } @@ -146,11 +148,12 @@ namespace gsr { if(!widget->visible) continue; + if(i > 0) + size.y += spacing; + const auto widget_size = widget->get_size(); size.x = std::max(size.x, widget_size.x); size.y += widget_size.y; - if(widget_size.y > 0.001f && i + 1 < widgets.size()) - size.y += spacing; } break; } @@ -160,10 +163,11 @@ namespace gsr { if(!widget->visible) continue; + if(i > 0) + size.x += spacing; + const auto widget_size = widget->get_size(); size.x += widget_size.x; - if(widget_size.x > 0.001f && i + 1 < widgets.size()) - size.x += spacing; size.y = std::max(size.y, widget_size.y); } break; diff --git a/src/gui/ScrollablePage.cpp b/src/gui/ScrollablePage.cpp index a791d75..7e15edf 100644 --- a/src/gui/ScrollablePage.cpp +++ b/src/gui/ScrollablePage.cpp @@ -140,6 +140,7 @@ namespace gsr { } void ScrollablePage::add_widget(std::unique_ptr<Widget> widget) { + widget->parent_widget = this; widgets.push_back(std::move(widget)); } diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 259df6c..12d1d99 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -3,8 +3,10 @@ #include "../../include/gui/Label.hpp" #include "../../include/gui/PageStack.hpp" #include "../../include/gui/FileChooser.hpp" +#include "../../include/gui/Subsection.hpp" #include "../../include/Theme.hpp" #include "../../include/GsrInfo.hpp" +#include "../../include/Utils.hpp" #include <mglpp/graphics/Rectangle.hpp> #include <mglpp/graphics/Sprite.hpp> @@ -61,9 +63,9 @@ namespace gsr { return record_area_box; } - std::unique_ptr<List> SettingsPage::create_record_area(const GsrInfo &gsr_info) { + std::unique_ptr<Widget> SettingsPage::create_record_area(const GsrInfo &gsr_info) { auto record_area_list = std::make_unique<List>(List::Orientation::VERTICAL); - record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Record area:", get_theme().text_color)); + record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Capture target:", get_theme().text_color)); record_area_list->add_widget(create_record_area_box(gsr_info)); return record_area_list; } @@ -121,14 +123,14 @@ namespace gsr { return restore_portal_session_list; } - std::unique_ptr<List> SettingsPage::create_capture_target(const GsrInfo &gsr_info) { + std::unique_ptr<Widget> SettingsPage::create_capture_target(const GsrInfo &gsr_info) { // TODO: List::Alignment::Center causes 1 frame glitch when switching record area but only the first time auto capture_target_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER); capture_target_list->add_widget(create_record_area(gsr_info)); capture_target_list->add_widget(create_select_window()); capture_target_list->add_widget(create_area_size_section()); capture_target_list->add_widget(create_restore_portal_session_section()); - return capture_target_list; + return std::make_unique<Subsection>("Record area", std::move(capture_target_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f)); } std::unique_ptr<ComboBox> SettingsPage::create_audio_track_selection_checkbox(const std::vector<AudioDevice> &audio_devices) { @@ -177,13 +179,12 @@ namespace gsr { return merge_audio_tracks_checkbox; } - std::unique_ptr<List> SettingsPage::create_audio_device_section(const std::vector<AudioDevice> &audio_devices) { + std::unique_ptr<Widget> SettingsPage::create_audio_device_section(const std::vector<AudioDevice> &audio_devices) { auto audio_device_section_list = std::make_unique<List>(List::Orientation::VERTICAL); - audio_device_section_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Audio:", get_theme().text_color)); audio_device_section_list->add_widget(create_add_audio_track_button(audio_devices)); audio_device_section_list->add_widget(create_audio_track_section(audio_devices)); audio_device_section_list->add_widget(create_merge_audio_tracks_checkbox()); - return audio_device_section_list; + return std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f)); } std::unique_ptr<ComboBox> SettingsPage::create_video_quality_box() { @@ -241,7 +242,14 @@ namespace gsr { video_codec_box->add_item("VP8", "vp8"); if(gsr_info.supported_video_codecs.vp9) video_codec_box->add_item("VP9", "vp9"); - // TODO: Add hdr options + if(gsr_info.supported_video_codecs.hevc_hdr) + video_codec_box->add_item("HEVC (HDR)", "hevc_hdr"); + if(gsr_info.supported_video_codecs.hevc_10bit) + video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit"); + if(gsr_info.supported_video_codecs.av1_hdr) + video_codec_box->add_item("AV1 (HDR)", "av1_hdr"); + if(gsr_info.supported_video_codecs.av1_10bit) + video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit"); if(gsr_info.supported_video_codecs.h264_software) video_codec_box->add_item("H264 Software Encoder (Slow, not recommended)", "h264_software"); video_codec_box_ptr = video_codec_box.get(); @@ -315,18 +323,39 @@ namespace gsr { framerate_info_list->add_widget(create_framerate_mode()); return framerate_info_list; } + + std::unique_ptr<Widget> SettingsPage::create_record_cursor_section() { + auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor"); + record_cursor_checkbox->set_checked(true); + record_cursor_checkbox_ptr = record_cursor_checkbox.get(); + return record_cursor_checkbox; + } - std::unique_ptr<List> SettingsPage::create_settings(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices) { + std::unique_ptr<Widget> SettingsPage::create_video_section(const GsrInfo &gsr_info) { + auto video_section_list = std::make_unique<List>(List::Orientation::VERTICAL); + video_section_list->add_widget(create_video_quality_section()); + video_section_list->add_widget(create_codec_section(gsr_info)); + video_section_list->add_widget(create_framerate_section()); + video_section_list->add_widget(create_record_cursor_section()); + return std::make_unique<Subsection>("Video", std::move(video_section_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f)); + } + + std::unique_ptr<Widget> SettingsPage::create_settings(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices) { auto settings_list = std::make_unique<List>(List::Orientation::VERTICAL); settings_list->set_spacing(0.018f); - settings_list->add_widget(create_view_radio_button()); settings_list->add_widget(create_capture_target(gsr_info)); settings_list->add_widget(create_audio_device_section(audio_devices)); - settings_list->add_widget(create_video_quality_section()); - settings_list->add_widget(create_codec_section(gsr_info)); - settings_list->add_widget(create_framerate_section()); + settings_list->add_widget(create_video_section(gsr_info)); settings_list_ptr = settings_list.get(); - return settings_list; + + auto page_list = std::make_unique<List>(List::Orientation::VERTICAL); + page_list->set_spacing(0.018f); + page_list->add_widget(create_view_radio_button()); + auto scrollable_page = std::make_unique<ScrollablePage>(content_page_ptr->get_inner_size() - mgl::vec2f(0.0f, page_list->get_size().y + 0.018f * get_theme().window_height)); + settings_scrollable_page_ptr = scrollable_page.get(); + scrollable_page->add_widget(std::move(settings_list)); + page_list->add_widget(std::move(scrollable_page)); + return page_list; } void SettingsPage::add_widgets(const GsrInfo &gsr_info, const std::vector<AudioDevice> &audio_devices) { @@ -346,6 +375,10 @@ namespace gsr { record_area_box_ptr->set_selected_item(gsr_info.supported_capture_options.monitors.front().name); else if(gsr_info.supported_capture_options.portal) record_area_box_ptr->set_selected_item("portal"); + else if(gsr_info.supported_capture_options.window) + record_area_box_ptr->set_selected_item("window"); + else + record_area_box_ptr->on_selection_changed("", ""); } void SettingsPage::add_page_specific_widgets() { @@ -365,7 +398,7 @@ namespace gsr { std::unique_ptr<List> SettingsPage::create_save_directory(const char *label) { auto save_directory_list = std::make_unique<List>(List::Orientation::VERTICAL); save_directory_list->add_widget(std::make_unique<Label>(&get_theme().body_font, label, get_theme().text_color)); - auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, "/home/dec05eba", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); + auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_videos_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120)); save_directory_button_ptr = save_directory_button.get(); save_directory_button->on_click = [this]() { auto select_directory_page = std::make_unique<GsrPage>(); @@ -424,15 +457,10 @@ namespace gsr { replay_data_list->add_widget(create_save_directory("Directory to save replays:")); replay_data_list->add_widget(create_container_section()); replay_data_list->add_widget(create_replay_time()); - settings_list_ptr->add_widget(std::move(replay_data_list)); + settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(replay_data_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); - auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor"); - record_cursor_checkbox->set_checked(true); - record_cursor_checkbox_ptr = record_cursor_checkbox.get(); - checkboxes_list->add_widget(std::move(record_cursor_checkbox)); - auto show_replay_started_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show replay started notification"); show_replay_started_notification_checkbox->set_checked(true); show_replay_started_notification_checkbox_ptr = show_replay_started_notification_checkbox.get(); @@ -448,17 +476,18 @@ namespace gsr { show_replay_saved_notification_checkbox_ptr = show_replay_saved_notification_checkbox.get(); checkboxes_list->add_widget(std::move(show_replay_saved_notification_checkbox)); - settings_list_ptr->add_widget(std::move(checkboxes_list)); + auto notifications_subsection = std::make_unique<Subsection>("Notifications", std::move(checkboxes_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f)); + Subsection *notifications_subsection_ptr = notifications_subsection.get(); + settings_list_ptr->add_widget(std::move(notifications_subsection)); - view_radio_button_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { (void)text; const bool advanced_view = id == "advanced"; color_range_list_ptr->set_visible(advanced_view); codec_list_ptr->set_visible(advanced_view); framerate_mode_list_ptr->set_visible(advanced_view); - show_replay_started_notification_checkbox_ptr->set_visible(advanced_view); - show_replay_stopped_notification_checkbox_ptr->set_visible(advanced_view); - show_replay_saved_notification_checkbox_ptr->set_visible(advanced_view); + notifications_subsection_ptr->set_visible(advanced_view); + settings_scrollable_page_ptr->reset_scroll(); }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); } @@ -467,15 +496,10 @@ namespace gsr { auto file_list = std::make_unique<List>(List::Orientation::HORIZONTAL); file_list->add_widget(create_save_directory("Directory to save the video:")); file_list->add_widget(create_container_section()); - settings_list_ptr->add_widget(std::move(file_list)); + settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); - auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor"); - record_cursor_checkbox->set_checked(true); - record_cursor_checkbox_ptr = record_cursor_checkbox.get(); - checkboxes_list->add_widget(std::move(record_cursor_checkbox)); - auto show_recording_started_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show recording started notification"); show_recording_started_notification_checkbox->set_checked(true); show_recording_started_notification_checkbox_ptr = show_recording_started_notification_checkbox.get(); @@ -486,16 +510,18 @@ namespace gsr { show_video_saved_notification_checkbox_ptr = show_video_saved_notification_checkbox.get(); checkboxes_list->add_widget(std::move(show_video_saved_notification_checkbox)); - settings_list_ptr->add_widget(std::move(checkboxes_list)); + auto notifications_subsection = std::make_unique<Subsection>("Notifications", std::move(checkboxes_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f)); + Subsection *notifications_subsection_ptr = notifications_subsection.get(); + settings_list_ptr->add_widget(std::move(notifications_subsection)); - view_radio_button_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { (void)text; const bool advanced_view = id == "advanced"; color_range_list_ptr->set_visible(advanced_view); codec_list_ptr->set_visible(advanced_view); framerate_mode_list_ptr->set_visible(advanced_view); - show_recording_started_notification_checkbox_ptr->set_visible(advanced_view); - show_video_saved_notification_checkbox_ptr->set_visible(advanced_view); + notifications_subsection_ptr->set_visible(advanced_view); + settings_scrollable_page_ptr->reset_scroll(); }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); } @@ -570,15 +596,10 @@ namespace gsr { streaming_info_list->add_widget(create_stream_key_section()); streaming_info_list->add_widget(create_stream_url_section()); streaming_info_list->add_widget(create_stream_container_section()); - settings_list_ptr->add_widget(std::move(streaming_info_list)); + settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f))); auto checkboxes_list = std::make_unique<List>(List::Orientation::VERTICAL); - auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor"); - record_cursor_checkbox->set_checked(true); - record_cursor_checkbox_ptr = record_cursor_checkbox.get(); - checkboxes_list->add_widget(std::move(record_cursor_checkbox)); - auto show_streaming_started_notification_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show streaming started notification"); show_streaming_started_notification_checkbox->set_checked(true); show_streaming_started_notification_checkbox_ptr = show_streaming_started_notification_checkbox.get(); @@ -589,7 +610,9 @@ namespace gsr { show_streaming_stopped_notification_checkbox_ptr = show_streaming_stopped_notification_checkbox.get(); checkboxes_list->add_widget(std::move(show_streaming_stopped_notification_checkbox)); - settings_list_ptr->add_widget(std::move(checkboxes_list)); + auto notifications_subsection = std::make_unique<Subsection>("Notifications", std::move(checkboxes_list), mgl::vec2f(content_page_ptr->get_inner_size().x, 0.0f)); + Subsection *notifications_subsection_ptr = notifications_subsection.get(); + settings_list_ptr->add_widget(std::move(notifications_subsection)); streaming_service_box_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { (void)text; @@ -604,14 +627,14 @@ namespace gsr { }; streaming_service_box_ptr->on_selection_changed("Twitch", "twitch"); - view_radio_button_ptr->on_selection_changed = [this](const std::string &text, const std::string &id) { + view_radio_button_ptr->on_selection_changed = [this, notifications_subsection_ptr](const std::string &text, const std::string &id) { (void)text; const bool advanced_view = id == "advanced"; color_range_list_ptr->set_visible(advanced_view); codec_list_ptr->set_visible(advanced_view); framerate_mode_list_ptr->set_visible(advanced_view); - show_streaming_started_notification_checkbox_ptr->set_visible(advanced_view); - show_streaming_stopped_notification_checkbox_ptr->set_visible(advanced_view); + notifications_subsection_ptr->set_visible(advanced_view); + settings_scrollable_page_ptr->reset_scroll(); }; view_radio_button_ptr->on_selection_changed("Simple", "simple"); } diff --git a/src/gui/Subsection.cpp b/src/gui/Subsection.cpp new file mode 100644 index 0000000..74b65e0 --- /dev/null +++ b/src/gui/Subsection.cpp @@ -0,0 +1,60 @@ +#include "../../include/gui/Subsection.hpp" +#include "../../include/Theme.hpp" + +#include <mglpp/window/Window.hpp> +#include <mglpp/graphics/Rectangle.hpp> + +namespace gsr { + static const float margin_top_scale = 0.015f; + static const float margin_bottom_scale = 0.015f; + static const float margin_left_scale = 0.015f; + static const float margin_right_scale = 0.015f; + static const float title_spacing_scale = 0.015f; + + Subsection::Subsection(const char *title, std::unique_ptr<Widget> inner_widget, mgl::vec2f size) : + label(&get_theme().title_font, title, get_theme().text_color), + inner_widget(std::move(inner_widget)), + size(size) + { + this->inner_widget->parent_widget = this; + } + + bool Subsection::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f) { + if(!visible) + return true; + + return inner_widget->on_event(event, window, mgl::vec2f(0.0f, 0.0f)); + } + + void Subsection::draw(mgl::Window &window, mgl::vec2f offset) { + if(!visible) + return; + + mgl::vec2f draw_pos = position + offset; + mgl::Rectangle background(draw_pos.floor(), get_size().floor()); + background.set_color(mgl::Color(25, 30, 34)); + window.draw(background); + + draw_pos += mgl::vec2f(margin_left_scale, margin_top_scale) * mgl::vec2f(get_theme().window_height, get_theme().window_height); + label.draw(window, draw_pos); + draw_pos.y += label.get_size().y + title_spacing_scale * get_theme().window_height; + + inner_widget->set_position(draw_pos); + inner_widget->draw(window, mgl::vec2f(0.0f, 0.0f)); + } + + mgl::vec2f Subsection::get_size() { + if(!visible) + return {0.0f, 0.0f}; + + const mgl::vec2f margin_size = mgl::vec2f(margin_left_scale + margin_right_scale, margin_top_scale + margin_bottom_scale) * mgl::vec2f(get_theme().window_height, get_theme().window_height); + mgl::vec2f widgets_size = mgl::vec2f(0.0f, label.get_size().y + title_spacing_scale * get_theme().window_height) + inner_widget->get_size() + margin_size; + + if(std::abs(size.x) > 0.001f) + widgets_size.x = size.x; + if(std::abs(size.y) > 0.001f) + widgets_size.y = size.y; + + return widgets_size; + } +}
\ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 042a9af..fca2f87 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,31 +1,18 @@ - -#include "../include/gui/StaticPage.hpp" -#include "../include/gui/DropdownButton.hpp" -#include "../include/gui/CustomRendererWidget.hpp" -#include "../include/gui/SettingsPage.hpp" -#include "../include/gui/Utils.hpp" -#include "../include/gui/PageStack.hpp" -#include "../include/Process.hpp" -#include "../include/Theme.hpp" #include "../include/GsrInfo.hpp" +#include "../include/Theme.hpp" #include "../include/window_texture.h" -#include "../include/Config.hpp" +#include "../include/Overlay.hpp" +#include "../include/GlobalHotkeysX11.hpp" +#include "../include/gui/Utils.hpp" -#include <string.h> +#include <unistd.h> #include <signal.h> -#include <sys/wait.h> -#include <assert.h> #include <X11/Xlib.h> -#include <X11/cursorfont.h> #include <X11/Xatom.h> -#include <X11/Xutil.h> - +#define XK_LATIN1 +#include <X11/keysymdef.h> #include <mglpp/mglpp.hpp> -#include <mglpp/graphics/Text.hpp> -#include <mglpp/graphics/Texture.hpp> -#include <mglpp/graphics/Sprite.hpp> -#include <mglpp/graphics/Rectangle.hpp> #include <mglpp/window/Window.hpp> #include <mglpp/window/Event.hpp> #include <mglpp/system/Clock.hpp> @@ -39,8 +26,6 @@ // to make it more efficient by doing record/replay/stream with the same encoded packets. // TODO: Make sure the overlay always stays on top. Test with starting the overlay and then opening youtube in fullscreen. -#include <vector> - extern "C" { #include <mgl/mgl.h> } @@ -57,169 +42,12 @@ static void startup_error(const char *msg) { exit(1); } -#define _NET_WM_STATE_REMOVE 0 -#define _NET_WM_STATE_ADD 1 -#define _NET_WM_STATE_TOGGLE 2 - -static Bool set_window_wm_state(Display *display, Window window, Atom atom) { - Atom net_wm_state_atom = XInternAtom(display, "_NET_WM_STATE", False); - if(!net_wm_state_atom) { - fprintf(stderr, "Error: failed to find atom _NET_WM_STATE\n"); - return False; - } - - XClientMessageEvent xclient; - memset(&xclient, 0, sizeof(xclient)); - - xclient.type = ClientMessage; - xclient.window = window; - xclient.message_type = net_wm_state_atom; - xclient.format = 32; - xclient.data.l[0] = _NET_WM_STATE_ADD; - xclient.data.l[1] = atom; - xclient.data.l[2] = 0; - xclient.data.l[3] = 0; - xclient.data.l[4] = 0; - - XSendEvent(display, DefaultRootWindow(display), False, SubstructureRedirectMask | SubstructureNotifyMask, (XEvent*)&xclient); - XFlush(display); - return True; -} - -static Bool make_window_sticky(Display* display, Window window) { - Atom net_wm_state_sticky_atom = XInternAtom(display, "_NET_WM_STATE_STICKY", False); - if(!net_wm_state_sticky_atom) { - fprintf(stderr, "Error: failed to find atom _NET_WM_STATE_STICKY\n"); - return False; - } - - return set_window_wm_state(display, window, net_wm_state_sticky_atom); -} - -static bool is_compositor_running(Display *dpy, int screen) { - char prop_name[20]; - snprintf(prop_name, sizeof(prop_name), "_NET_WM_CM_S%d", screen); - Atom prop_atom = XInternAtom(dpy, prop_name, False); - return XGetSelectionOwner(dpy, prop_atom) != None; -} - -static mgl::Texture texture_from_ximage(XImage *img) { - uint8_t *texture_data = (uint8_t*)malloc(img->width * img->height * 3); - // TODO: - - for(int y = 0; y < img->height; ++y) { - for(int x = 0; x < img->width; ++x) { - unsigned long pixel = XGetPixel(img, x, y); - unsigned char red = (pixel & img->red_mask) >> 16; - unsigned char green = (pixel & img->green_mask) >> 8; - unsigned char blue = pixel & img->blue_mask; - - const size_t texture_data_index = (x + y * img->width) * 3; - texture_data[texture_data_index + 0] = red; - texture_data[texture_data_index + 1] = green; - texture_data[texture_data_index + 2] = blue; - } - } - - mgl::Texture texture; - // TODO: - texture.load_from_memory(texture_data, img->width, img->height, MGL_IMAGE_FORMAT_RGB); - free(texture_data); - return texture; -} - static sig_atomic_t running = 1; -static void sigint_handler(int dummy) { - (void)dummy; +static void sigint_handler(int signal) { + (void)signal; running = 0; } -static char hex_value_to_str(uint8_t v) { - if(v <= 9) - return '0' + v; - else if(v >= 10 && v <= 15) - return 'A' + (v - 10); - else - return '0'; -} - -// Excludes alpha -static std::string color_to_hex_str(mgl::Color color) { - std::string result; - result.resize(6); - - result[0] = hex_value_to_str((color.r & 0xF0) >> 4); - result[1] = hex_value_to_str(color.r & 0x0F); - - result[2] = hex_value_to_str((color.g & 0xF0) >> 4); - result[3] = hex_value_to_str(color.g & 0x0F); - - result[4] = hex_value_to_str((color.b & 0xF0) >> 4); - result[5] = hex_value_to_str(color.b & 0x0F); - - return result; -} - -static Window get_window_at_cursor_position(Display *display) { - Window root_window = None; - Window window = None; - int dummy_i; - unsigned int dummy_u; - int cursor_pos_x = 0; - int cursor_pos_y = 0; - XQueryPointer(display, DefaultRootWindow(display), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u); - return window; -} - -struct DrawableGeometry { - int x, y, width, height; -}; - -static bool get_drawable_geometry(Display *display, Drawable drawable, DrawableGeometry *geometry) { - geometry->x = 0; - geometry->y = 0; - geometry->width = 0; - geometry->height = 0; - - Window root_window; - unsigned int w, h; - unsigned int dummy_border, dummy_depth; - Status s = XGetGeometry(display, drawable, &root_window, &geometry->x, &geometry->y, &w, &h, &dummy_border, &dummy_depth); - - geometry->width = w; - geometry->height = h; - return s != Success; -} - -static bool diff_int(int a, int b, int difference) { - return std::abs(a - b) <= difference; -} - -static bool is_window_fullscreen_on_monitor(Display *display, Window window, const mgl_monitor *monitor) { - if(!window) - return false; - - DrawableGeometry geometry; - if(!get_drawable_geometry(display, window, &geometry)) - return false; - - const int margin = 2; - return diff_int(geometry.x, monitor->pos.x, margin) && diff_int(geometry.y, monitor->pos.y, margin) - && diff_int(geometry.width, monitor->size.x, margin) && diff_int(geometry.height, monitor->size.y, margin); -} - -// Returns the first monitor if not found. Assumes there is at least one monitor connected. -static const mgl_monitor* find_monitor_by_cursor_position(mgl::Window &window) { - const mgl_window *win = window.internal_window(); - assert(win->num_monitors > 0); - for(int i = 0; i < win->num_monitors; ++i) { - const mgl_monitor *mon = &win->monitors[i]; - if(mgl::IntRect({ mon->pos.x, mon->pos.y }, { mon->size.x, mon->size.y }).contains({ win->cursor_position.x, win->cursor_position.y })) - return mon; - } - return &win->monitors[0]; -} - int main(int argc, char **argv) { (void)argv; if(argc != 1) @@ -235,8 +63,6 @@ int main(int argc, char **argv) { exit(1); } - const std::vector<gsr::AudioDevice> audio_devices = gsr::get_audio_devices(); - std::string resources_path; if(access("images/gpu_screen_recorder_logo.png", F_OK) == 0) { resources_path = "./"; @@ -263,8 +89,6 @@ int main(int argc, char **argv) { exit(1); } - const bool compositor_running = is_compositor_running(display, DefaultScreen(display)); - mgl::vec2i window_size = { 1280, 720 }; mgl::vec2i window_pos = { 0, 0 }; @@ -284,440 +108,48 @@ int main(int argc, char **argv) { if(!window.create("gsr overlay", window_create_params)) startup_error("failed to create window"); - mgl_window *win = window.internal_window(); - if(win->num_monitors == 0) - startup_error("no monitors found"); - - const mgl_monitor *focused_monitor = find_monitor_by_cursor_position(window); - window_pos.x = focused_monitor->pos.x; - window_pos.y = focused_monitor->pos.y; - window_size.x = focused_monitor->size.x; - window_size.y = focused_monitor->size.y; - - window.set_size_limits(window_size, window_size); - window.set_size(window_size); - window.set_position(window_pos); - - if(!gsr::init_theme(gsr_info, window_size, resources_path)) { - fprintf(stderr, "Error: failed to load theme\n"); - exit(1); - } - unsigned char data = 2; // Prefer being composed to allow transparency XChangeProperty(display, window.get_system_handle(), XInternAtom(display, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); data = 1; XChangeProperty(display, window.get_system_handle(), XInternAtom(display, "GAMESCOPE_EXTERNAL_OVERLAY", False), XA_CARDINAL, 32, PropModeReplace, &data, 1); - mgl::Texture replay_button_texture; - if(!replay_button_texture.load_from_file((resources_path + "images/replay.png").c_str())) - startup_error("failed to load texture: images/replay.png"); - - mgl::Texture record_button_texture; - if(!record_button_texture.load_from_file((resources_path + "images/record.png").c_str())) - startup_error("failed to load texture: images/record.png"); - - mgl::Texture stream_button_texture; - if(!stream_button_texture.load_from_file((resources_path + "images/stream.png").c_str())) - startup_error("failed to load texture: images/stream.png"); - - WindowTexture window_texture; - bool window_texture_loaded = false; - - mgl::Texture window_texture_texture; - mgl::Sprite window_texture_sprite; - - mgl::Texture screenshot_texture; - if(!compositor_running) { - const Window window_at_cursor_position = get_window_at_cursor_position(display); - if(is_window_fullscreen_on_monitor(display, window_at_cursor_position, focused_monitor) && window_at_cursor_position) - window_texture_loaded = window_texture_init(&window_texture, display, mgl_window_get_egl_display(window.internal_window()), window_at_cursor_position, egl_funcs) == 0; - - if(window_texture_loaded && window_texture.texture_id) { - window_texture_texture = mgl::Texture(window_texture.texture_id, MGL_TEXTURE_FORMAT_RGB); - window_texture_sprite.set_texture(&window_texture_texture); - } else { - XImage *img = XGetImage(display, DefaultRootWindow(display), window_pos.x, window_pos.y, window_size.x, window_size.y, AllPlanes, ZPixmap); - if(!img) - fprintf(stderr, "Error: failed to take a screenshot\n"); - - if(img) { - screenshot_texture = texture_from_ximage(img); - XDestroyImage(img); - img = NULL; - } - } - } - - mgl::Sprite screenshot_sprite; - if(screenshot_texture.is_valid()) - screenshot_sprite.set_texture(&screenshot_texture); - - mgl::Rectangle bg_screenshot_overlay(window_size.to_vec2f()); - bg_screenshot_overlay.set_color(bg_color); - - auto front_page = std::make_unique<gsr::StaticPage>(window_size.to_vec2f()); - gsr::StaticPage *front_page_ptr = front_page.get(); - - gsr::PageStack page_stack; - page_stack.push(std::move(front_page)); - - std::optional<gsr::Config> config = gsr::read_config(); - - struct MainButton { - gsr::DropdownButton* button; - gsr::GsrMode mode; - }; - - const int num_frontpage_buttons = 3; - - const char *titles[num_frontpage_buttons] = { - "Instant Replay", - "Record", - "Livestream" - }; - - const char *descriptions_off[num_frontpage_buttons] = { - "Off", - "Not recording", - "Not streaming" - }; - - const char *descriptions_on[num_frontpage_buttons] = { - "On", - "Recording", - "Streaming" - }; - - mgl::Texture *textures[num_frontpage_buttons] = { - &replay_button_texture, - &record_button_texture, - &stream_button_texture - }; - - const int button_height = window_size.y / 5.0f; - const int button_width = button_height; - - std::vector<MainButton> main_buttons; - - for(int i = 0; i < num_frontpage_buttons; ++i) { - auto button = std::make_unique<gsr::DropdownButton>(&gsr::get_theme().title_font, &gsr::get_theme().body_font, titles[i], descriptions_on[i], descriptions_off[i], textures[i], mgl::vec2f(button_width, button_height)); - button->add_item("Start", "start"); - button->add_item("Settings", "settings"); - gsr::DropdownButton *button_ptr = button.get(); - front_page_ptr->add_widget(std::move(button)); - - MainButton main_button = { - button_ptr, - gsr::GsrMode::Unknown - }; - - main_buttons.push_back(std::move(main_button)); + if(!gsr::init_theme(gsr_info, resources_path)) { + fprintf(stderr, "Error: failed to load theme\n"); + exit(1); } - gsr::GsrMode gsr_mode = gsr::GsrMode::Unknown; - - auto update_overlay_shape = [&]() { - fprintf(stderr, "update overlay shape!\n"); - const int spacing = 0;// * get_config().scale; - const int combined_spacing = spacing * std::max(0, (int)main_buttons.size() - 1); - - const int per_button_width = main_buttons[0].button->get_size().x;// * get_config().scale; - const mgl::vec2i overlay_desired_size(per_button_width * (int)main_buttons.size() + combined_spacing, main_buttons[0].button->get_size().y); - - const mgl::vec2i main_buttons_start_pos = mgl::vec2i(window_size.x*0.5f, window_size.y*0.25f) - overlay_desired_size/2; - mgl::vec2i main_button_pos = main_buttons_start_pos; - - // if(!gsr_mode.has_value()) { - // gsr_mode = gsr::GsrMode::Unknown; - // pid_t gpu_screen_recorder_process = -1; - // gsr::is_gpu_screen_recorder_running(gpu_screen_recorder_process, gsr_mode.value()); - // } - - for(size_t i = 0; i < main_buttons.size(); ++i) { - if(main_buttons[i].mode != gsr::GsrMode::Unknown && main_buttons[i].mode == gsr_mode) { - main_buttons[i].button->set_activated(true); - } else { - main_buttons[i].button->set_activated(false); - } - - main_buttons[i].button->set_position(main_button_pos.to_vec2f()); - main_button_pos.x += per_button_width + combined_spacing; - } - }; - - // Replay - main_buttons[0].button->on_click = [&](const std::string &id) { - if(id == "settings") { - auto replay_settings_page = std::make_unique<gsr::SettingsPage>(gsr::SettingsPage::Type::REPLAY, gsr_info, audio_devices, config, &page_stack); - page_stack.push(std::move(replay_settings_page)); - return; - } - /* - char window_to_record_str[32]; - snprintf(window_to_record_str, sizeof(window_to_record_str), "%ld", target_window); - - const char *args[] = { - "gpu-screen-recorder", "-w", window_to_record_str, - "-c", "mp4", - "-f", "60", - "-o", "/home/dec05eba/Videos/gpu-screen-recorder.mp4", - nullptr - }; - gsr::exec_program_daemonized(args); - */ - }; - main_buttons[0].mode = gsr::GsrMode::Replay; - - // TODO: Monitor /tmp/gpu-screen-recorder and update ui to match state - - pid_t gpu_screen_recorder_process = -1; - // Record - main_buttons[1].button->on_click = [&](const std::string &id) { - if(id == "settings") { - auto record_settings_page = std::make_unique<gsr::SettingsPage>(gsr::SettingsPage::Type::RECORD, gsr_info, audio_devices, config, &page_stack); - page_stack.push(std::move(record_settings_page)); - return; - } - - if(id != "start") - return; - - // window.close(); - // usleep(1000 * 50); // 50 milliseconds - - const std::string tint_color_as_hex = color_to_hex_str(gsr::get_theme().tint_color); - - if(gpu_screen_recorder_process != -1) { - kill(gpu_screen_recorder_process, SIGINT); - int status; - if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { - perror("waitpid failed"); - /* Ignore... */ - } - // window.set_visible(false); - // window.close(); - // return; - //exit(0); - gpu_screen_recorder_process = -1; - gsr_mode = gsr::GsrMode::Unknown; - update_overlay_shape(); - main_buttons[1].button->set_item_label(id, "Start"); - - // TODO: Show this with a slight delay to make sure it doesn't show up in the video - const std::string record_image_filepath = resources_path + "images/record.png"; - const char *notification_args[] = { - "gsr-notify", "--text", "Recording has been saved", "--timeout", "3.0", - "--icon", record_image_filepath.c_str(), - "--icon-color", "ffffff", "--bg-color", tint_color_as_hex.c_str(), - nullptr - }; - gsr::exec_program_daemonized(notification_args); - return; - } - - const char *args[] = { - "gpu-screen-recorder", "-w", "screen", - "-c", "mp4", - "-f", "60", - "-o", "/home/dec05eba/Videos/gpu-screen-recorder.mp4", - nullptr - }; - gpu_screen_recorder_process = gsr::exec_program(args); - if(gpu_screen_recorder_process == -1) { - // TODO: Show notification failed to start - } else { - gsr_mode = gsr::GsrMode::Record; - update_overlay_shape(); - main_buttons[1].button->set_item_label(id, "Stop"); - } - - // TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video. - // Make clear to the user that the recording starts after the notification is gone. - // Maybe have the option in notification to show timer until its getting hidden, then the notification can say: - // Starting recording in 3... - // 2... - // 1... - // TODO: Do not run this is a daemon. Instead get the pid and when launching another notification close the current notification - // program and start another one. This can also be used to check when the notification has finished by checking with waitpid NOWAIT - // to see when the program has exit. - const std::string record_image_filepath = resources_path + "images/record.png"; - const char *notification_args[] = { - "gsr-notify", "--text", "Recording has started", "--timeout", "3.0", - "--icon", record_image_filepath.c_str(), - "--icon-color", tint_color_as_hex.c_str(), "--bg-color", tint_color_as_hex.c_str(), - nullptr - }; - gsr::exec_program_daemonized(notification_args); - //exit(0); - // window.set_visible(false); - // window.close(); - - // TODO: Show notification with args: - // "Recording has started" 3.0 ./images/record.png 76b900 - }; - main_buttons[1].mode = gsr::GsrMode::Record; - - // Stream - main_buttons[2].button->on_click = [&](const std::string &id) { - if(id == "settings") { - auto stream_settings_page = std::make_unique<gsr::SettingsPage>(gsr::SettingsPage::Type::STREAM, gsr_info, audio_devices, config, &page_stack); - page_stack.push(std::move(stream_settings_page)); - return; - } - }; - main_buttons[2].mode = gsr::GsrMode::Stream; - - update_overlay_shape(); - - window.set_visible(true); - make_window_sticky(display, window.get_system_handle()); - - Cursor default_cursor = XCreateFontCursor(display, XC_arrow); - - // TODO: Retry if these fail. - // TODO: Hmm, these dont work in owlboy. Maybe owlboy uses xi2 and that breaks this (does it?). - // Remove these grabs when debugging with a debugger, or your X11 session will appear frozen - XGrabPointer(display, window.get_system_handle(), True, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask | - Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | - ButtonMotionMask, - GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); - XGrabKeyboard(display, window.get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); + gsr::Overlay overlay(window, resources_path, gsr_info, egl_funcs, bg_color); + overlay.show(); - XSetInputFocus(display, window.get_system_handle(), RevertToParent, CurrentTime); - XFlush(display); - - window.set_fullscreen(true); - - //XGrabServer(display); - - mgl::Rectangle top_bar_background(mgl::vec2f(window_size.x, window_size.y*0.06f).floor()); - top_bar_background.set_color(mgl::Color(0, 0, 0, 180)); - - mgl::Text top_bar_text("GPU Screen Recorder", gsr::get_theme().top_bar_font); - //top_bar_text.set_color(gsr::get_theme().tint_color); - top_bar_text.set_position((top_bar_background.get_position() + top_bar_background.get_size()*0.5f - top_bar_text.get_bounds().size*0.5f).floor()); - - mgl::Texture close_texture; - if(!close_texture.load_from_file((resources_path + "images/cross.png").c_str())) - startup_error("failed to load texture: images/cross.png"); - - gsr::CustomRendererWidget close_button_widget(mgl::vec2f(top_bar_background.get_size().y * 0.3f, top_bar_background.get_size().y * 0.3f).floor()); - close_button_widget.set_position(mgl::vec2f(window_size.x - close_button_widget.get_size().x - 50.0f, top_bar_background.get_size().y * 0.5f - close_button_widget.get_size().y * 0.5f).floor()); - close_button_widget.draw_handler = [&](mgl::Window &window, mgl::vec2f pos, mgl::vec2f size) { - if(mgl::FloatRect(pos, size).contains(window.get_mouse_position().to_vec2f())) { - const float border_scale = 0.0015f; - const int border_size = std::max(1.0f, border_scale * gsr::get_theme().window_height); - gsr::draw_rectangle_outline(window, pos, size, gsr::get_theme().tint_color, border_size); - } - - mgl::Sprite close_sprite(&close_texture); - close_sprite.set_position(pos); - close_sprite.set_size(size); - window.draw(close_sprite); - }; - bool close_button_pressed_inside = false; - close_button_widget.event_handler = [&](mgl::Event &event, mgl::Window&, mgl::vec2f pos, mgl::vec2f size) { - if(event.type == mgl::Event::MouseButtonPressed && event.mouse_button.button == mgl::Mouse::Left) { - close_button_pressed_inside = mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y)); - } else if(event.type == mgl::Event::MouseButtonReleased && event.mouse_button.button == mgl::Mouse::Left && close_button_pressed_inside) { - if(mgl::FloatRect(pos, size).contains(mgl::vec2f(event.mouse_button.x, event.mouse_button.y))) { - running = false; - return false; - } - } - return true; - }; + // gsr::GlobalHotkeysX11 global_hotkeys; + // global_hotkeys.bind_key_press({ XK_z, Mod1Mask }, "open/hide", [&](const std::string &id) { + // fprintf(stderr, "pressed %s\n", id.c_str()); + // overlay.toggle_show(); + // }); - mgl::Texture logo_texture; - if(!logo_texture.load_from_file((resources_path + "images/gpu_screen_recorder_logo.png").c_str())) - startup_error("failed to load texture: images/gpu_screen_recorder_logo.png"); - - mgl::Sprite logo_sprite(&logo_texture); - logo_sprite.set_height((int)(top_bar_background.get_size().y * 0.65f)); - - logo_sprite.set_position(mgl::vec2f( - (top_bar_background.get_size().y - logo_sprite.get_size().y) * 0.5f, - top_bar_background.get_size().y * 0.5f - logo_sprite.get_size().y * 0.5f - ).floor()); + //fprintf(stderr, "info: gsr overlay is now ready, waiting for inputs. Press alt+z to show/hide the overlay\n"); mgl::Event event; - - event.type = mgl::Event::MouseMoved; - event.mouse_move.x = window.get_mouse_position().x; - event.mouse_move.y = window.get_mouse_position().y; - page_stack.top()->on_event(event, window, mgl::vec2f(0.0f, 0.0f)); - - const auto render = [&] { - window.clear(bg_color); - if(window_texture_loaded && window_texture.texture_id) { - window.draw(window_texture_sprite); - window.draw(bg_screenshot_overlay); - } else if(screenshot_texture.is_valid()) { - window.draw(screenshot_sprite); - window.draw(bg_screenshot_overlay); - } - window.draw(top_bar_background); - window.draw(top_bar_text); - window.draw(logo_sprite); - close_button_widget.draw(window, mgl::vec2f(0.0f, 0.0f)); - page_stack.top()->draw(window, mgl::vec2f(0.0f, 0.0f)); - window.display(); - }; - mgl::Clock frame_delta_clock; - while(window.is_open()) { + while(window.is_open() && overlay.is_open() && running) { const double frame_delta_seconds = frame_delta_clock.get_elapsed_time_seconds(); frame_delta_clock.restart(); gsr::set_frame_delta_seconds(frame_delta_seconds); - if(page_stack.empty() || !running) { - running = false; - goto quit; - } - + //global_hotkeys.poll_events(); while(window.poll_event(event)) { - page_stack.top()->on_event(event, window, mgl::vec2f(0.0f, 0.0f)); - close_button_widget.on_event(event, window, mgl::vec2f(0.0f, 0.0f)); - if(event.type == mgl::Event::KeyReleased) { - if(event.key.code == mgl::Keyboard::Escape && !page_stack.empty()) { - page_stack.top()->on_navigate_away_from_page(); - page_stack.pop(); - } - } - - if(page_stack.empty()) - break; + overlay.on_event(event, window); } - // if(state_update_timer.get_elapsed_time_seconds() >= state_update_timeout_sec) { - // state_update_timer.restart(); - // update_overlay_shape(); - // } - - if(!page_stack.empty()) - render(); + window.clear(bg_color); + overlay.draw(window); + window.display(); } - quit: fprintf(stderr, "shutting down!\n"); - XUngrabKeyboard(display, CurrentTime); - XUngrabPointer(display, CurrentTime); - if(window_texture_loaded) - window_texture_deinit(&window_texture); gsr::deinit_theme(); window.close(); - if(gpu_screen_recorder_process != -1) { - kill(gpu_screen_recorder_process, SIGINT); - int status; - if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { - perror("waitpid failed"); - /* Ignore... */ - } - gpu_screen_recorder_process = -1; - } - return 0; } diff --git a/uninstall.sh b/uninstall.sh index 208c2b3..e6ebbee 100755 --- a/uninstall.sh +++ b/uninstall.sh @@ -8,3 +8,4 @@ cd "$script_dir" ninja -C build uninstall echo "Successfully uninstalled gsr-overlay" +c
\ No newline at end of file |