diff options
-rw-r--r-- | gpu-screen-recorder-overlay-daemon/main.c | 15 | ||||
-rw-r--r-- | include/Process.hpp | 14 | ||||
-rw-r--r-- | include/gui/Button.hpp | 3 | ||||
-rw-r--r-- | src/Process.cpp | 93 | ||||
-rw-r--r-- | src/gui/Button.cpp | 15 | ||||
-rw-r--r-- | src/main.cpp | 122 |
6 files changed, 244 insertions, 18 deletions
diff --git a/gpu-screen-recorder-overlay-daemon/main.c b/gpu-screen-recorder-overlay-daemon/main.c index 197d411..7db83bd 100644 --- a/gpu-screen-recorder-overlay-daemon/main.c +++ b/gpu-screen-recorder-overlay-daemon/main.c @@ -1,14 +1,19 @@ #include <stdio.h> #include <stdlib.h> +#include <signal.h> #include <X11/Xlib.h> #include <X11/keysym.h> -static int xerror_dummy(Display *dpy, XErrorEvent *ee) { - (void)dpy; +static int ignore_xerror(Display *display, XErrorEvent *ee) { + (void)display; (void)ee; return 0; } +static void sigterm_handler(int dummy) { + (void)dummy; +} + static const KeySym toggle_overlay_key = XK_Z; static void grab_keys(Display *display) { unsigned int numlockmask = 0; @@ -22,7 +27,7 @@ static void grab_keys(Display *display) { } XFreeModifiermap(modmap); - XErrorHandler prev_error_handler = XSetErrorHandler(xerror_dummy); + XErrorHandler prev_error_handler = XSetErrorHandler(ignore_xerror); Window root_window = DefaultRootWindow(display); unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; @@ -52,6 +57,10 @@ int main() { grab_keys(display); const KeyCode overlay_keycode = XKeysymToKeycode(display, toggle_overlay_key); + XSetErrorHandler(ignore_xerror); + /* Killing gpu-screen-recorder with SIGTERM also gives us SIGTERM. We want to ignore that as that has no meaning here */ + signal(SIGTERM, sigterm_handler); + XEvent xev; for(;;) { XNextEvent(display, &xev); diff --git a/include/Process.hpp b/include/Process.hpp new file mode 100644 index 0000000..a41b9a0 --- /dev/null +++ b/include/Process.hpp @@ -0,0 +1,14 @@ +#pragma once + +namespace gsr { + enum class GsrMode { + Replay, + Record, + Stream, + Unknown + }; + + // Arguments ending with NULL + bool exec_program_daemonized(const char **args); + bool is_gpu_screen_recorder_running(int &gsr_pid, GsrMode &mode); +}
\ No newline at end of file diff --git a/include/gui/Button.hpp b/include/gui/Button.hpp index ac9aa5c..145d869 100644 --- a/include/gui/Button.hpp +++ b/include/gui/Button.hpp @@ -2,6 +2,7 @@ #include "Widget.hpp" #include <string> +#include <functional> namespace gsr { class Button : public Widget { @@ -9,6 +10,8 @@ namespace gsr { Button(mgl::vec2f size); void on_event(mgl::Event &event, mgl::Window &window) override; void draw(mgl::Window &window) override; + + std::function<void()> on_click; private: mgl::vec2f size; bool mouse_inside = false; diff --git a/src/Process.cpp b/src/Process.cpp new file mode 100644 index 0000000..96f6840 --- /dev/null +++ b/src/Process.cpp @@ -0,0 +1,93 @@ +#include "../include/Process.hpp" +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <sys/wait.h> +#include <limits.h> +#include <fcntl.h> + +namespace gsr { + bool exec_program_daemonized(const char **args) { + /* 1 argument */ + if(args[0] == nullptr) + return -1; + + pid_t pid = vfork(); + if(pid == -1) { + perror("Failed to vfork"); + return false; + } else if(pid == 0) { /* child */ + setsid(); + signal(SIGHUP, SIG_IGN); + + // Daemonize child to make the parent the init process which will reap the zombie child + pid_t second_child = vfork(); + if(second_child == 0) { // child + execvp(args[0], (char* const*)args); + perror("execvp"); + _exit(127); + } else if(second_child != -1) { + _exit(0); + } + } else { /* parent */ + waitpid(pid, nullptr, 0); + } + return true; + } + + static const char *pid_file = "/tmp/gpu-screen-recorder"; + + static bool is_process_running_program(pid_t pid, const char *program_name) { + char filepath[256]; + snprintf(filepath, sizeof(filepath), "/proc/%ld/exe", (long)pid); + + char resolved_path[PATH_MAX]; + const ssize_t resolved_path_len = readlink(filepath, resolved_path, sizeof(resolved_path) - 1); + if(resolved_path_len == -1) + return false; + + resolved_path[resolved_path_len] = '\0'; + + const int program_name_len = strlen(program_name); + return resolved_path_len >= program_name_len && memcmp(resolved_path + resolved_path_len - program_name_len, program_name, program_name_len) == 0; + } + + bool is_gpu_screen_recorder_running(int &gsr_pid, GsrMode &mode) { + gsr_pid = -1; + mode = GsrMode::Unknown; + + char buffer[256]; + int fd = open(pid_file, O_RDONLY); + if(fd == -1) + return false; + + ssize_t bytes_read = read(fd, buffer, sizeof(buffer) - 1); + if(bytes_read < 0) { + perror("failed to read gpu-screen-recorder pid file"); + close(fd); + return true; + } + buffer[bytes_read] = '\0'; + close(fd); + + long pid = 0; + if(sscanf(buffer, "%ld %120s", &pid, buffer) == 2) { + gsr_pid = pid; + if(is_process_running_program(pid, "gpu-screen-recorder")) { + if(strcmp(buffer, "replay") == 0) + mode = GsrMode::Replay; + else if(strcmp(buffer, "record") == 0) + mode = GsrMode::Record; + else if(strcmp(buffer, "stream") == 0) + mode = GsrMode::Stream; + else + mode = GsrMode::Unknown; + return true; + } + } else { + fprintf(stderr, "Warning: gpu-screen-recorder pid file is in incorrect format, it's possible that its corrupt. Ignoring...\n"); + } + return false; + } +}
\ No newline at end of file diff --git a/src/gui/Button.cpp b/src/gui/Button.cpp index b266639..2250560 100644 --- a/src/gui/Button.cpp +++ b/src/gui/Button.cpp @@ -10,6 +10,7 @@ namespace gsr { } 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) { @@ -17,10 +18,24 @@ namespace gsr { } else if(!mouse_inside && inside) { mouse_inside = true; } + } else if(event.type == mgl::Event::MouseButtonPressed && mouse_inside) { + + } + */ + if(event.type == mgl::Event::MouseButtonPressed && mouse_inside) { + if(on_click) + on_click(); } } void Button::draw(mgl::Window &window) { + const bool inside = mgl::FloatRect(position, size).contains(window.get_mouse_position().to_vec2f()); + if(mouse_inside && !inside) { + mouse_inside = false; + } else if(!mouse_inside && inside) { + mouse_inside = true; + } + if(mouse_inside) { // Background /* diff --git a/src/main.cpp b/src/main.cpp index ad7f6d3..ac0b7dd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,14 @@ #include "../include/gui/Button.hpp" #include "../include/window_texture.h" +#include "../include/Process.hpp" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <libgen.h> +#include <signal.h> +#include <sys/wait.h> #include <X11/Xlib.h> #include <X11/cursorfont.h> @@ -26,6 +29,15 @@ extern "C" { #include <mgl/mgl.h> } +struct Config { + float scale = 1.0f; +}; + +static Config& get_config() { + static Config config; + return config; +} + static void usage() { fprintf(stderr, "usage: window-overlay <window>\n"); exit(1); @@ -86,9 +98,10 @@ int main(int argc, char **argv) { return 1; } - XSelectInput(display, target_window, VisibilityChangeMask | StructureNotifyMask); + XSelectInput(display, target_window, StructureNotifyMask | VisibilityChangeMask); mgl::vec2i target_window_size = { target_win_attr.width, target_win_attr.height }; + get_config().scale = std::min(1.0, (double)target_win_attr.width / 1920.0); mgl::Window::CreateParams window_create_params; window_create_params.size = target_window_size; @@ -108,11 +121,11 @@ int main(int argc, char **argv) { startup_error("failed to load file: fonts/Orbitron-Regular.ttf"); mgl::Font title_font; - if(!title_font.load_from_file(title_font_file, 32)) + if(!title_font.load_from_file(title_font_file, 32 * get_config().scale)) startup_error("failed to load font: fonts/Orbitron-Bold.ttf"); mgl::Font font; - if(!font.load_from_file(font_file, 20)) + if(!font.load_from_file(font_file, 20 * get_config().scale)) startup_error("failed to load font: fonts/Orbitron-Regular.ttf"); mgl::Texture replay_button_texture; @@ -132,6 +145,7 @@ int main(int argc, char **argv) { mgl::Text description; mgl::Sprite icon; gsr::Button button; + gsr::GsrMode mode; }; const char *titles[] = { @@ -140,12 +154,18 @@ int main(int argc, char **argv) { "Livestream" }; - const char *descriptions[] = { + const char *descriptions_off[] = { "Off", "Not recording", "Not streaming" }; + const char *descriptions_on[] = { + "On", + "Recording", + "Streaming" + }; + mgl::Texture *textures[] = { &replay_button_texture, &record_button_texture, @@ -159,17 +179,19 @@ int main(int argc, char **argv) { 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); + mgl::Text description(descriptions_off[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}); + sprite.set_scale(get_config().scale); + gsr::Button button(mgl::vec2f(425 * get_config().scale, 300 * get_config().scale).floor()); MainButton main_button = { std::move(title), std::move(description), std::move(sprite), - std::move(button) + std::move(button), + gsr::GsrMode::Unknown }; main_buttons.push_back(std::move(main_button)); @@ -179,8 +201,59 @@ int main(int argc, char **argv) { // 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 }; + // Replay + main_buttons[0].button.on_click = [&]() { + /* + 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 + + // Record + main_buttons[1].button.on_click = [&]() { + int gpu_screen_recorder_process = -1; + gsr::GsrMode gsr_mode = gsr::GsrMode::Unknown; + if(gsr::is_gpu_screen_recorder_running(gpu_screen_recorder_process, gsr_mode) && 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... */ + } + exit(0); + } + + 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); + exit(0); + }; + main_buttons[1].mode = gsr::GsrMode::Record; + + main_buttons[2].mode = gsr::GsrMode::Stream; + + const int per_button_width = 425 * get_config().scale; + const mgl::vec2i overlay_desired_size(per_button_width * (int)main_buttons.size(), 300 * get_config().scale); XGCValues xgcv; xgcv.foreground = WhitePixel(display, DefaultScreen(display)); @@ -191,11 +264,25 @@ int main(int argc, char **argv) { GC shape_gc = None; auto update_overlay_shape = [&]() { - const int main_button_margin = 20; + const int main_button_margin = 20 * get_config().scale; const mgl::vec2i main_buttons_start_pos = target_window_size/2 - overlay_desired_size/2; mgl::vec2i main_button_pos = main_buttons_start_pos; + int gpu_screen_recorder_process = -1; + gsr::GsrMode gsr_mode = gsr::GsrMode::Unknown; + gsr::is_gpu_screen_recorder_running(gpu_screen_recorder_process, gsr_mode); + 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].description.set_string(descriptions_on[i]); + main_buttons[i].description.set_color(mgl::Color(118, 185, 0)); + main_buttons[i].icon.set_color(mgl::Color(118, 185, 0)); + } else { + main_buttons[i].description.set_string(descriptions_off[i]); + main_buttons[i].description.set_color(mgl::Color(255, 255, 255)); + main_buttons[i].icon.set_color(mgl::Color(255, 255, 255)); + } + 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, @@ -208,8 +295,8 @@ int main(int argc, char **argv) { 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_button_pos.x + per_button_width * 0.5f - main_buttons[i].icon.get_texture()->get_size().x * main_buttons[i].icon.get_scale().x * 0.5f, + main_button_pos.y + overlay_desired_size.y * 0.5f - main_buttons[i].icon.get_texture()->get_size().y * main_buttons[i].icon.get_scale().y * 0.5f).floor()); main_buttons[i].button.set_position(main_button_pos.to_vec2f()); shapes[i] = { @@ -218,7 +305,7 @@ int main(int argc, char **argv) { main_button_pos.x += per_button_width; } - const mgl::vec2i settings_button_size(128, 128); + const mgl::vec2i settings_button_size(128 * get_config().scale, 128 * get_config().scale); 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 @@ -256,13 +343,18 @@ int main(int argc, char **argv) { 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); + 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, DestroyNotify, &xev)) { + window.close(); + break; + } + if(XCheckTypedWindowEvent(display, target_window, VisibilityNotify, &xev)) { if(xev.xvisibility.state) { @@ -290,7 +382,7 @@ int main(int argc, char **argv) { } } - window.clear(mgl::Color(37, 43, 47, 255)); + window.clear(mgl::Color(37, 43, 47)); for(auto &main_button : main_buttons) { main_button.button.draw(window); window.draw(main_button.icon); |