From 2ea0a921e8d0c100d5f894536a93f0dfdf45f67b Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 28 Nov 2024 15:11:50 +0100 Subject: Fix restore portal session option not working, close other notifications when showing a new one --- include/Overlay.hpp | 27 +++-- include/Process.hpp | 4 +- include/WindowUtils.hpp | 13 +++ meson.build | 2 +- scripts/notify-saved-name.sh | 17 --- scripts/save-video-in-game-folder.sh | 26 ----- src/Overlay.cpp | 203 +++++++++++++++++++++++++---------- src/Process.cpp | 23 +++- src/WindowUtils.cpp | 150 ++++++++++++++++++++++++++ 9 files changed, 353 insertions(+), 112 deletions(-) create mode 100644 include/WindowUtils.hpp delete mode 100755 scripts/notify-saved-name.sh delete mode 100755 scripts/save-video-in-game-folder.sh create mode 100644 src/WindowUtils.cpp diff --git a/include/Overlay.hpp b/include/Overlay.hpp index d13d75b..4376893 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -60,15 +60,19 @@ namespace gsr { void xi_grab_all_devices(); void xi_warp_pointer(mgl::vec2i position); - void process_key_bindings(mgl::Event &event); + void close_gpu_screen_recorder_output(); void update_notification_process_status(); + void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type); + void update_gsr_replay_save(); void update_gsr_process_status(); void replay_status_update_status(); void update_focused_fullscreen_status(); void update_power_supply_status(); + void on_stop_recording(int exit_code); + void update_ui_recording_paused(); void update_ui_recording_unpaused(); @@ -89,26 +93,26 @@ namespace gsr { void force_window_on_top(); private: - using KeyBindingCallback = std::function; - struct KeyBinding { - mgl::Event::KeyEvent key_event; - KeyBindingCallback callback; - }; - std::unique_ptr window; mgl::Event event; std::string resources_path; GsrInfo gsr_info; egl_functions egl_funcs; + Config config; + + bool visible = false; + mgl::Texture window_texture_texture; mgl::Sprite window_texture_sprite; mgl::Texture screenshot_texture; mgl::Sprite screenshot_sprite; mgl::Rectangle bg_screenshot_overlay; + mgl::Texture cursor_texture; mgl::Sprite cursor_sprite; mgl::vec2i cursor_hotspot; bool cursor_drawn = false; + WindowTexture window_texture; PageStack page_stack; mgl::Rectangle top_bar_background; @@ -116,14 +120,17 @@ namespace gsr { 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; pid_t notification_process = -1; - Config config; + int gpu_screen_recorder_process_output_fd = -1; + FILE *gpu_screen_recorder_process_output_file = nullptr; + DropdownButton *replay_dropdown_button_ptr = nullptr; DropdownButton *record_dropdown_button_ptr = nullptr; DropdownButton *stream_dropdown_button_ptr = nullptr; + mgl::Clock force_window_on_top_clock; RecordingStatus recording_status = RecordingStatus::NONE; @@ -134,7 +141,7 @@ namespace gsr { bool power_supply_connected = false; bool focused_window_is_fullscreen = false; - std::array key_bindings; + std::string record_filepath; Display *xi_display = nullptr; int xi_opcode = 0; diff --git a/include/Process.hpp b/include/Process.hpp index 125a880..731062e 100644 --- a/include/Process.hpp +++ b/include/Process.hpp @@ -12,8 +12,8 @@ namespace gsr { // Arguments ending with NULL bool exec_program_daemonized(const char **args); - // Arguments ending with NULL - pid_t exec_program(const char **args); + // Arguments ending with NULL. |read_fd| can be NULL + pid_t exec_program(const char **args, int *read_fd); // |output_buffer| should be at least PATH_MAX in size bool read_cmdline_arg0(const char *filepath, char *output_buffer); } \ No newline at end of file diff --git a/include/WindowUtils.hpp b/include/WindowUtils.hpp new file mode 100644 index 0000000..93d0dee --- /dev/null +++ b/include/WindowUtils.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace gsr { + enum class WindowCaptureType { + FOCUSED, + CURSOR + }; + + std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type); +} \ No newline at end of file diff --git a/meson.build b/meson.build index aeb144c..324295a 100644 --- a/meson.build +++ b/meson.build @@ -30,6 +30,7 @@ src = [ 'src/gui/GsrPage.cpp', 'src/gui/Subsection.cpp', 'src/Utils.cpp', + 'src/WindowUtils.cpp', 'src/Config.cpp', 'src/GsrInfo.cpp', 'src/Process.cpp', @@ -80,7 +81,6 @@ executable( install_subdir('images', install_dir : gsr_ui_resources_path) install_subdir('fonts', install_dir : gsr_ui_resources_path) -install_subdir('scripts', install_dir : gsr_ui_resources_path, install_mode : 'rwxr-xr-x') if get_option('systemd') == true install_data(files('extra/gpu-screen-recorder-ui.service'), install_dir : 'lib/systemd/user') diff --git a/scripts/notify-saved-name.sh b/scripts/notify-saved-name.sh deleted file mode 100755 index c8ca399..0000000 --- a/scripts/notify-saved-name.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh - -[ "$GSR_SHOW_SAVED_NOTIFICATION" != "1" ] && exit 0 - -filepath="$1" -type="$2" - -file_name="$(basename "$filepath")" - -case "$type" in - "regular") - gsr-notify --text "Saved recording to '$file_name'" --timeout 3.0 --icon record --bg-color "$GSR_NOTIFY_BG_COLOR" - ;; - "replay") - gsr-notify --text "Saved replay to '$file_name'" --timeout 3.0 --icon replay --bg-color "$GSR_NOTIFY_BG_COLOR" - ;; -esac \ No newline at end of file diff --git a/scripts/save-video-in-game-folder.sh b/scripts/save-video-in-game-folder.sh deleted file mode 100755 index 3d07d6a..0000000 --- a/scripts/save-video-in-game-folder.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -filepath="$1" -type="$2" - -file_name="$(basename "$filepath")" -file_dir="$(dirname "$filepath")" - -game_name=$(gsr-window-name focused || echo "Game") -game_name="$(echo "$game_name" | tr '/\\' '_')" -target_dir="$file_dir/$game_name" -new_filepath="$target_dir/$file_name" - -mkdir -p "$target_dir" -mv "$filepath" "$new_filepath" - -[ "$GSR_SHOW_SAVED_NOTIFICATION" != "1" ] && exit 0 - -case "$type" in - "regular") - gsr-notify --text "Saved recording to '$game_name/$file_name'" --timeout 3.0 --icon record --bg-color "$GSR_NOTIFY_BG_COLOR" - ;; - "replay") - gsr-notify --text "Saved replay to '$game_name/$file_name'" --timeout 3.0 --icon replay --bg-color "$GSR_NOTIFY_BG_COLOR" - ;; -esac \ No newline at end of file diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 2802e70..1b3d9d2 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -9,6 +9,7 @@ #include "../include/gui/SettingsPage.hpp" #include "../include/gui/Utils.hpp" #include "../include/gui/PageStack.hpp" +#include "../include/WindowUtils.hpp" #include #include @@ -418,23 +419,11 @@ namespace gsr { memset(&window_texture, 0, sizeof(window_texture)); - key_bindings[0].key_event.code = mgl::Keyboard::Escape; - key_bindings[0].key_event.alt = false; - key_bindings[0].key_event.control = false; - key_bindings[0].key_event.shift = false; - key_bindings[0].key_event.system = false; - key_bindings[0].callback = [this]() { - page_stack.pop(); - }; - std::optional new_config = read_config(gsr_info); if(new_config) config = std::move(new_config.value()); init_color_theme(gsr_info); - // These environment variable are used by files in scripts/ folder - const std::string notify_bg_color_str = color_to_hex_str(get_color_theme().tint_color); - setenv("GSR_NOTIFY_BG_COLOR", notify_bg_color_str.c_str(), true); power_supply_online_filepath = get_power_supply_online_filepath(); @@ -455,6 +444,8 @@ namespace gsr { notification_process = -1; } + close_gpu_screen_recorder_output(); + if(gpu_screen_recorder_process > 0) { kill(gpu_screen_recorder_process, SIGINT); int status; @@ -520,21 +511,15 @@ namespace gsr { } } - static uint32_t key_event_to_bitmask(mgl::Event::KeyEvent key_event) { - return ((uint32_t)key_event.alt << (uint32_t)0) - | ((uint32_t)key_event.control << (uint32_t)1) - | ((uint32_t)key_event.shift << (uint32_t)2) - | ((uint32_t)key_event.system << (uint32_t)3); - } - - void Overlay::process_key_bindings(mgl::Event &event) { - if(event.type != mgl::Event::KeyReleased) - return; + void Overlay::close_gpu_screen_recorder_output() { + if(gpu_screen_recorder_process_output_file) { + fclose(gpu_screen_recorder_process_output_file); + gpu_screen_recorder_process_output_file = nullptr; + } - const uint32_t event_key_bitmask = key_event_to_bitmask(event.key); - for(const KeyBinding &key_binding : key_bindings) { - if(event.key.code == key_binding.key_event.code && event_key_bitmask == key_event_to_bitmask(key_binding.key_event)) - key_binding.callback(); + if(gpu_screen_recorder_process_output_fd > 0) { + close(gpu_screen_recorder_process_output_fd); + gpu_screen_recorder_process_output_fd = -1; } } @@ -613,15 +598,16 @@ namespace gsr { if(!visible || !window) return; - close_button_widget.on_event(event, *window, mgl::vec2f(0.0f, 0.0f)); - if(!page_stack.on_event(event, *window, mgl::vec2f(0.0f, 0.0f))) + if(!close_button_widget.on_event(event, *window, mgl::vec2f(0.0f, 0.0f))) return; - process_key_bindings(event); + if(!page_stack.on_event(event, *window, mgl::vec2f(0.0f, 0.0f))) + return; } bool Overlay::draw() { update_notification_process_status(); + update_gsr_replay_save(); update_gsr_process_status(); replay_status_update_status(); @@ -1113,7 +1099,7 @@ namespace gsr { waitpid(notification_process, &status, 0); } - notification_process = exec_program(notification_args); + notification_process = exec_program(notification_args, NULL); } bool Overlay::is_open() const { @@ -1133,17 +1119,100 @@ namespace gsr { notification_process = -1; } + static void string_replace_characters(char *str, const char *characters_to_replace, char new_character) { + for(; *str != '\0'; ++str) { + for(const char *p = characters_to_replace; *p != '\0'; ++p) { + if(*str == *p) + *str = new_character; + } + } + } + + static std::string filepath_get_directory(const char *filepath) { + std::string result = filepath; + const size_t last_slash_index = result.rfind('/'); + if(last_slash_index == std::string::npos) + result = "."; + else + result.erase(last_slash_index); + return result; + } + + static std::string filepath_get_filename(const char *filepath) { + std::string result = filepath; + const size_t last_slash_index = result.rfind('/'); + if(last_slash_index != std::string::npos) + result.erase(0, last_slash_index + 1); + return result; + } + + void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) { + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + const std::string video_filename = filepath_get_filename(video_filepath); + + std::string focused_window_name = get_focused_window_name(display, WindowCaptureType::FOCUSED); + if(focused_window_name.empty()) + focused_window_name = "Game"; + + string_replace_characters(focused_window_name.data(), "/\\", '_'); + + std::string video_directory = filepath_get_directory(video_filepath) + "/" + focused_window_name; + create_directory_recursive(video_directory.data()); + + const std::string new_video_filepath = video_directory + "/" + video_filename; + rename(video_filepath, new_video_filepath.c_str()); + + std::string text; + switch(notification_type) { + case NotificationType::RECORD: + text = "Saved recording to '" + focused_window_name + "/" + video_filename + "'"; + break; + case NotificationType::REPLAY: + text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'"; + break; + case NotificationType::NONE: + case NotificationType::STREAM: + break; + } + show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, notification_type); + } + + void Overlay::update_gsr_replay_save() { + if(gpu_screen_recorder_process_output_file) { + char buffer[1024]; + char *replay_saved_filepath = fgets(buffer, sizeof(buffer), gpu_screen_recorder_process_output_file); + if(!replay_saved_filepath || replay_saved_filepath[0] == '\0') + return; + + const int line_len = strlen(replay_saved_filepath); + if(replay_saved_filepath[line_len - 1] == '\n') + replay_saved_filepath[line_len - 1] = '\0'; + + if(config.replay_config.save_video_in_game_folder) { + save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY); + } else { + const std::string text = "Saved replay to '" + filepath_get_filename(replay_saved_filepath) + "'"; + show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY); + } + } else if(gpu_screen_recorder_process_output_fd > 0) { + char buffer[1024]; + read(gpu_screen_recorder_process_output_fd, buffer, sizeof(buffer)); + } + } + void Overlay::update_gsr_process_status() { if(gpu_screen_recorder_process <= 0) return; - errno = 0; int status; if(waitpid(gpu_screen_recorder_process, &status, WNOHANG) == 0) { // Still running return; } + close_gpu_screen_recorder_output(); + int exit_code = -1; if(WIFEXITED(status)) exit_code = WEXITSTATUS(status); @@ -1164,10 +1233,7 @@ namespace gsr { } case RecordingStatus::RECORD: { update_ui_recording_stopped(); - if(exit_code != 0) { - fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); - show_notification("Failed to start/save recording", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); - } + on_stop_recording(exit_code); break; } case RecordingStatus::STREAM: { @@ -1231,6 +1297,20 @@ namespace gsr { } } + void Overlay::on_stop_recording(int exit_code) { + if(exit_code == 0) { + if(config.record_config.save_video_in_game_folder) { + save_video_in_current_game_directory(record_filepath.c_str(), NotificationType::RECORD); + } else { + const std::string text = "Saved recording to '" + filepath_get_filename(record_filepath.c_str()) + "'"; + show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD); + } + } else { + fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); + show_notification("Failed to start/save recording", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD); + } + } + void Overlay::update_ui_recording_paused() { if(!visible || recording_status != RecordingStatus::RECORD) return; @@ -1390,6 +1470,11 @@ namespace gsr { args.push_back(audio_track.c_str()); } } + + if(record_options.restore_portal_session) { + args.push_back("-restore-portal-session"); + args.push_back("yes"); + } } void Overlay::on_press_save_replay() { @@ -1417,6 +1502,8 @@ namespace gsr { // window->close(); // usleep(1000 * 50); // 50 milliseconds + close_gpu_screen_recorder_output(); + if(gpu_screen_recorder_process > 0) { kill(gpu_screen_recorder_process, SIGINT); int status; @@ -1451,7 +1538,9 @@ namespace gsr { } char region[64]; - snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.record_area_width, (int)config.replay_config.record_options.record_area_height); + region[0] = '\0'; + if(config.replay_config.record_options.record_area_option == "focused") + snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.record_area_width, (int)config.replay_config.record_options.record_area_height); if(config.replay_config.record_options.record_area_option != "focused" && config.replay_config.record_options.change_video_resolution) snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.video_width, (int)config.replay_config.record_options.video_height); @@ -1473,14 +1562,9 @@ namespace gsr { add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged); - setenv("GSR_SHOW_SAVED_NOTIFICATION", config.replay_config.show_replay_saved_notifications ? "1" : "0", true); - const std::string script_to_run_on_save = resources_path + (config.replay_config.save_video_in_game_folder ? "scripts/save-video-in-game-folder.sh" : "scripts/notify-saved-name.sh"); - args.push_back("-sc"); - args.push_back(script_to_run_on_save.c_str()); - args.push_back(nullptr); - gpu_screen_recorder_process = exec_program(args.data()); + gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd); if(gpu_screen_recorder_process == -1) { // TODO: Show notification failed to start } else { @@ -1488,6 +1572,10 @@ namespace gsr { update_ui_replay_started(); } + const int fdl = fcntl(gpu_screen_recorder_process_output_fd, F_GETFL); + fcntl(gpu_screen_recorder_process_output_fd, F_SETFL, fdl | O_NONBLOCK); + gpu_screen_recorder_process_output_file = fdopen(gpu_screen_recorder_process_output_fd, "r"); + // 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: @@ -1525,17 +1613,22 @@ namespace gsr { if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { perror("waitpid failed"); /* Ignore... */ + } else { + int exit_code = -1; + if(WIFEXITED(status)) + exit_code = WEXITSTATUS(status); + on_stop_recording(exit_code); } - // window->set_visible(false); - // window->close(); - // return; - //exit(0); + gpu_screen_recorder_process = -1; recording_status = RecordingStatus::NONE; update_ui_recording_stopped(); + record_filepath.clear(); return; } + record_filepath.clear(); + // TODO: Validate input, fallback to valid values const std::string fps = std::to_string(config.record_config.record_options.fps); const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate); @@ -1551,7 +1644,9 @@ namespace gsr { } char region[64]; - snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.record_area_width, (int)config.record_config.record_options.record_area_height); + region[0] = '\0'; + if(config.record_config.record_options.record_area_option == "focused") + snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.record_area_width, (int)config.record_config.record_options.record_area_height); if(config.record_config.record_options.record_area_option != "focused" && config.record_config.record_options.change_video_resolution) snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.video_width, (int)config.record_config.record_options.video_height); @@ -1572,14 +1667,10 @@ namespace gsr { add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged); - setenv("GSR_SHOW_SAVED_NOTIFICATION", config.record_config.show_video_saved_notifications ? "1" : "0", true); - const std::string script_to_run_on_save = resources_path + (config.record_config.save_video_in_game_folder ? "scripts/save-video-in-game-folder.sh" : "scripts/notify-saved-name.sh"); - args.push_back("-sc"); - args.push_back(script_to_run_on_save.c_str()); - args.push_back(nullptr); - gpu_screen_recorder_process = exec_program(args.data()); + record_filepath = output_file; + gpu_screen_recorder_process = exec_program(args.data(), nullptr); if(gpu_screen_recorder_process == -1) { // TODO: Show notification failed to start } else { @@ -1685,7 +1776,9 @@ namespace gsr { const std::string url = streaming_get_url(config); char region[64]; - snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.record_area_width, (int)config.streaming_config.record_options.record_area_height); + region[0] = '\0'; + if(config.record_config.record_options.record_area_option == "focused") + snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.record_area_width, (int)config.streaming_config.record_options.record_area_height); if(config.record_config.record_options.record_area_option != "focused" && config.streaming_config.record_options.change_video_resolution) snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.video_width, (int)config.streaming_config.record_options.video_height); @@ -1708,7 +1801,7 @@ namespace gsr { args.push_back(nullptr); - gpu_screen_recorder_process = exec_program(args.data()); + gpu_screen_recorder_process = exec_program(args.data(), nullptr); if(gpu_screen_recorder_process == -1) { // TODO: Show notification failed to start } else { diff --git a/src/Process.cpp b/src/Process.cpp index 07d9dc6..e5886c0 100644 --- a/src/Process.cpp +++ b/src/Process.cpp @@ -9,6 +9,9 @@ #include #include +#define PIPE_READ 0 +#define PIPE_WRITE 1 + namespace gsr { static void debug_print_args(const char **args) { fprintf(stderr, "gsr-ui info: running command:"); @@ -51,22 +54,40 @@ namespace gsr { return true; } - pid_t exec_program(const char **args) { + pid_t exec_program(const char **args, int *read_fd) { + if(read_fd) + *read_fd = -1; + /* 1 argument */ if(args[0] == nullptr) return -1; + int fds[2] = {-1, -1}; + if(pipe(fds) == -1) + return -1; + debug_print_args(args); pid_t pid = vfork(); if(pid == -1) { + close(fds[PIPE_READ]); + close(fds[PIPE_WRITE]); perror("Failed to vfork"); return -1; } else if(pid == 0) { /* child */ + dup2(fds[PIPE_WRITE], STDOUT_FILENO); + close(fds[PIPE_READ]); + close(fds[PIPE_WRITE]); + execvp(args[0], (char* const*)args); perror("execvp"); _exit(127); } else { /* parent */ + close(fds[PIPE_WRITE]); + if(read_fd) + *read_fd = fds[PIPE_READ]; + else + close(fds[PIPE_READ]); return pid; } } diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp new file mode 100644 index 0000000..9b07915 --- /dev/null +++ b/src/WindowUtils.cpp @@ -0,0 +1,150 @@ +#include "../include/WindowUtils.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace gsr { + static bool window_has_atom(Display *dpy, Window window, Atom atom) { + Atom type; + unsigned long len, bytes_left; + int format; + unsigned char *properties = NULL; + if(XGetWindowProperty(dpy, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success) + return false; + + if(properties) + XFree(properties); + + return type != None; + } + + static bool window_is_user_program(Display *dpy, Window window) { + const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False); + const Atom wm_state_atom = XInternAtom(dpy, "WM_STATE", False); + return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom); + } + + static Window get_window_at_cursor_position(Display *dpy) { + 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(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u); + return window; + } + + static Window get_focused_window(Display *dpy, WindowCaptureType cap_type) { + const Atom net_active_window_atom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False); + Window focused_window = None; + + if(cap_type == WindowCaptureType::FOCUSED) { + // Atom type = None; + // int format = 0; + // unsigned long num_items = 0; + // unsigned long bytes_left = 0; + // unsigned char *data = NULL; + // XGetWindowProperty(dpy, DefaultRootWindow(dpy), net_active_window_atom, 0, 1, False, XA_WINDOW, &type, &format, &num_items, &bytes_left, &data); + + // fprintf(stderr, "focused window: %p\n", (void*)data); + + // if(type == XA_WINDOW && num_items == 1 && data) + // return *(Window*)data; + + int revert_to = 0; + XGetInputFocus(dpy, &focused_window, &revert_to); + if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window)) + return focused_window; + } + + focused_window = get_window_at_cursor_position(dpy); + if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window)) + return focused_window; + + return None; + } + + static char* get_window_title(Display *dpy, Window window) { + const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + const Atom wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False); + const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False); + + Atom type = None; + int format = 0; + unsigned long num_items = 0; + unsigned long bytes_left = 0; + unsigned char *data = NULL; + XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data); + + if(type == utf8_string_atom && format == 8 && data) + return (char*)data; + + type = None; + format = 0; + num_items = 0; + bytes_left = 0; + data = NULL; + XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data); + + if((type == XA_STRING || type == utf8_string_atom) && data) + return (char*)data; + + return NULL; + } + + static const char* strip(const char *str, int *len) { + int str_len = strlen(str); + for(int i = 0; i < str_len; ++i) { + if(str[i] != ' ') { + str += i; + str_len -= i; + break; + } + } + + for(int i = str_len - 1; i >= 0; --i) { + if(str[i] != ' ') { + str_len = i + 1; + break; + } + } + + *len = str_len; + return str; + } + + static std::string string_string(const char *str) { + int len = 0; + str = strip(str, &len); + return std::string(str, len); + } + + std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type) { + std::string result; + const Window focused_window = get_focused_window(dpy, window_capture_type); + if(focused_window == None) + return result; + + // Window title is not always ideal (for example for a browser), but for games its pretty much required + char *window_title = get_window_title(dpy, focused_window); + if(window_title) { + result = string_string(window_title); + return result; + } + + XClassHint class_hint = {nullptr, nullptr}; + XGetClassHint(dpy, focused_window, &class_hint); + if(class_hint.res_class) { + result = string_string(class_hint.res_class); + return result; + } + + return result; + } +} \ No newline at end of file -- cgit v1.2.3