diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | TODO | 10 | ||||
-rw-r--r-- | include/Overlay.hpp | 7 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | project.conf | 2 | ||||
-rw-r--r-- | src/Overlay.cpp | 182 | ||||
-rw-r--r-- | src/main.cpp | 3 |
7 files changed, 168 insertions, 45 deletions
@@ -38,8 +38,11 @@ There are also additional dependencies needed at runtime: * [GPU Screen Recorder Notification](https://git.dec05eba.com/gpu-screen-recorder-notification/) ## Program behavior notes -This program has to grab all keyboards and create a virtual keyboard (`gsr-ui virtual keyboard`) to make global hotkeys work on all Wayland compositors. -This might cause issues for you if you use input remapping software. To workaround this you can go into settings and select "Only grab virtual devices" +This program has to grab all keyboards and create a virtual keyboard (`gsr-ui virtual keyboard`) to make global hotkeys work on all Wayland compositors.\ +This might cause issues for you if you use keyboard remapping software. To workaround this you can go into settings and select "Only grab virtual devices".\ +If you use keyboard remapping software such as keyd then make sure to make it ignore "gsr-ui virtual keyboard" (-dec0:5eba device id), otherwise your keyboard can get locked +as gpu screen recorder tries to grab keys and keyd grabs gpu screen recorder, leading to a lock.\ +If you are stuck in such a lock where you cant press and keyboard keys you can press (left) ctrl+shift+alt+esc to close gpu screen recorder and remove it from system startup. # License This software is licensed under GPL3.0-only. Files under `fonts/` directory belong to the Noto Sans Google fonts project and they are licensed under `SIL Open Font License`.\ @@ -113,6 +113,8 @@ Check if "modprobe uinput" is needed on some systems (old fedora?). Add recording timer to see duration of recording/streaming. Make folder with window name work when using gamescope. Gamescope runs x11 itself so to get the window name inside that we have to connect to the gamescope X11 server (DISPLAY=:1 on x11 and DISPLAY=:2 on wayland, but not always). + Detect if the window is gamescope automatically (WM_CLASS = "gamescope") and get the x11 display automatically and connect to it to get the application its running. + This seems to only be an issue on wayland? the window title of the gamescope/steam bigpicture mode is the title of the game on x11, so it works automatically on x11. When clicking on current directory in file manager show a dropdown menu where you can select common directories (HOME, Videos, Downloads and mounted drives) for quick navigation. Maybe even button to search. @@ -153,4 +155,10 @@ Use /dev/input/eventN (or /dev/hidrawN) instead of /dev/input/jsN for joystick i Verify if cursor tracker monitor name is always correct. It uses the wayland monitor name for recording, but gpu screen recorder uses a custom name created from the drm connector name. -Notification with the focused monitor (with CursorTrackerWayland) assumes that the x11 monitor name is the same as the drm monitor name. Same for find_monitor_by_name.
\ No newline at end of file +Notification with the focused monitor (with CursorTrackerWayland) assumes that the x11 monitor name is the same as the drm monitor name. Same for find_monitor_by_name. + +If CursorTrackerWayland fails then fallback to getting focused monitor by window creation trick. Need to take into consideration prime laptop with dGPU that controls external monitors which cant be captured (different /dev/dri/card device). + Maybe automatically switch to recording with the device that controls the monitor. + In that case also add all monitors available to capture in the capture list and automatically choose the gpu that controls the monitor. + +Support cjk font. Use fc-match to find the location of the font. This also works in flatpak, in which case the fonts are in /run/host/..., where it lists system fonts. diff --git a/include/Overlay.hpp b/include/Overlay.hpp index 2aeb3f0..ac9d2b1 100644 --- a/include/Overlay.hpp +++ b/include/Overlay.hpp @@ -61,10 +61,11 @@ namespace gsr { void save_replay(); void take_screenshot(); void take_screenshot_region(); - void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type); + void show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target = nullptr); bool is_open() const; bool should_exit(std::string &reason) const; void exit(); + void go_back_to_old_ui(); const Config& get_config() const; @@ -209,6 +210,10 @@ namespace gsr { bool start_region_capture = false; std::function<void()> on_region_selected; + std::string recording_capture_target; + std::string replay_capture_target; + std::string screenshot_capture_target; + std::unique_ptr<CursorTracker> cursor_tracker; mgl::Clock cursor_tracker_update_clock; }; diff --git a/meson.build b/meson.build index 91172a0..9b91e15 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gsr-ui', ['c', 'cpp'], version : '1.3.2', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends') +project('gsr-ui', ['c', 'cpp'], version : '1.3.3', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends') if get_option('buildtype') == 'debug' add_project_arguments('-g3', language : ['c', 'cpp']) diff --git a/project.conf b/project.conf index e67a087..6b7c574 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gsr-ui" type = "executable" -version = "1.3.2" +version = "1.3.3" platforms = ["posix"] [lang.cpp] diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 3d99e46..2a4ca81 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -338,6 +338,13 @@ namespace gsr { fprintf(stderr, "pressed %s\n", id.c_str()); overlay->take_screenshot_region(); }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(ConfigHotkey{ mgl::Keyboard::Key::Escape, HOTKEY_MOD_LCTRL | HOTKEY_MOD_LSHIFT | HOTKEY_MOD_LALT }), + "exit", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->go_back_to_old_ui(); + }); } static std::unique_ptr<GlobalHotkeysLinux> register_linux_hotkeys(Overlay *overlay, GlobalHotkeysLinux::GrabType grab_type) { @@ -1377,7 +1384,38 @@ namespace gsr { return nullptr; } - void Overlay::show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type) { + static bool is_hex_num(char c) { + return (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f') || (c >= '0' && c <= '9'); + } + + static bool contains_non_hex_number(const char *str) { + bool hex_start = false; + size_t len = strlen(str); + if(len >= 2 && memcmp(str, "0x", 2) == 0) { + str += 2; + len -= 2; + hex_start = true; + } + + bool is_hex = false; + for(size_t i = 0; i < len; ++i) { + char c = str[i]; + if(c == '\0') + return false; + if(!is_hex_num(c)) + return true; + if((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) + is_hex = true; + } + + return is_hex && !hex_start; + } + + static bool is_capture_target_monitor(const char *capture_target) { + return strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target); + } + + void Overlay::show_notification(const char *str, double timeout_seconds, mgl::Color icon_color, mgl::Color bg_color, NotificationType notification_type, const char *capture_target) { char timeout_seconds_str[32]; snprintf(timeout_seconds_str, sizeof(timeout_seconds_str), "%f", timeout_seconds); @@ -1395,15 +1433,20 @@ namespace gsr { notification_args[arg_index++] = notification_type_str; } - std::optional<CursorInfo> cursor_info; - if(cursor_tracker) { - cursor_tracker->update(); - cursor_info = cursor_tracker->get_latest_cursor_info(); - } - - if(cursor_info) { + if(capture_target && is_capture_target_monitor(capture_target)) { notification_args[arg_index++] = "--monitor"; - notification_args[arg_index++] = cursor_info->monitor_name.c_str(); + notification_args[arg_index++] = capture_target; + } else { + std::optional<CursorInfo> cursor_info; + if(cursor_tracker) { + cursor_tracker->update(); + cursor_info = cursor_tracker->get_latest_cursor_info(); + } + + if(cursor_info) { + notification_args[arg_index++] = "--monitor"; + notification_args[arg_index++] = cursor_info->monitor_name.c_str(); + } } notification_args[arg_index++] = nullptr; @@ -1432,6 +1475,19 @@ namespace gsr { do_exit = true; } + void Overlay::go_back_to_old_ui() { + const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; + if(inside_flatpak) + exit_reason = "back-to-old-ui"; + else + exit_reason = "exit"; + + const char *args[] = { "systemctl", "disable", "--user", "gpu-screen-recorder-ui", nullptr }; + std::string stdout_str; + exec_program_on_host_get_stdout(args, stdout_str); + exit(); + } + const Config& Overlay::get_config() const { return config; } @@ -1514,31 +1570,52 @@ namespace gsr { rename(video_filepath, new_video_filepath.c_str()); truncate_string(focused_window_name, 20); - std::string text; + const char *capture_target = nullptr; + char msg[512]; + const std::string filename = focused_window_name + "/" + video_filename; + switch(notification_type) { case NotificationType::RECORD: { if(!config.record_config.show_video_saved_notifications) return; - text = "Saved recording to '" + focused_window_name + "/" + video_filename + "'"; + + if(is_capture_target_monitor(recording_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved recording of this monitor to '%s'", filename.c_str()); + else + snprintf(msg, sizeof(msg), "Saved recording of %s to '%s'", recording_capture_target.c_str(), filename.c_str()); + + capture_target = recording_capture_target.c_str(); break; } case NotificationType::REPLAY: { if(!config.replay_config.show_replay_saved_notifications) return; - text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'"; + + if(is_capture_target_monitor(replay_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved replay of this monitor to '%s'", filename.c_str()); + else + snprintf(msg, sizeof(msg), "Saved replay of %s to '%s'", replay_capture_target.c_str(), filename.c_str()); + + capture_target = replay_capture_target.c_str(); break; } case NotificationType::SCREENSHOT: { if(!config.screenshot_config.show_screenshot_saved_notifications) return; - text = "Saved screenshot to '" + focused_window_name + "/" + video_filename + "'"; + + if(is_capture_target_monitor(screenshot_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved screenshot of this monitor to '%s'", filename.c_str()); + else + snprintf(msg, sizeof(msg), "Saved screenshot of %s to '%s'", screenshot_capture_target.c_str(), filename.c_str()); + + capture_target = screenshot_capture_target.c_str(); break; } case NotificationType::NONE: case NotificationType::STREAM: break; } - show_notification(text.c_str(), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, notification_type); + show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, notification_type, capture_target); } void Overlay::on_replay_saved(const char *replay_saved_filepath) { @@ -1546,8 +1623,13 @@ namespace gsr { 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(), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY); + const std::string filename = filepath_get_filename(replay_saved_filepath); + char msg[512]; + if(is_capture_target_monitor(replay_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved replay of this monitor to '%s'", filename.c_str()); + else + snprintf(msg, sizeof(msg), "Saved replay of %s to '%s'", replay_capture_target.c_str(), filename.c_str()); + show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, replay_capture_target.c_str()); } } @@ -1644,8 +1726,13 @@ namespace gsr { if(config.screenshot_config.save_screenshot_in_game_folder) { save_video_in_current_game_directory(screenshot_filepath.c_str(), NotificationType::SCREENSHOT); } else { - const std::string text = "Saved screenshot to '" + filepath_get_filename(screenshot_filepath.c_str()) + "'"; - show_notification(text.c_str(), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT); + const std::string filename = filepath_get_filename(screenshot_filepath.c_str()); + char msg[512]; + if(is_capture_target_monitor(screenshot_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved screenshot of this monitor to '%s'", filename.c_str()); + else + snprintf(msg, sizeof(msg), "Saved screenshot of %s to '%s'", screenshot_capture_target.c_str(), filename.c_str()); + show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::SCREENSHOT, screenshot_capture_target.c_str()); } } else { fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_screenshot_process, exit_code); @@ -1745,8 +1832,13 @@ namespace gsr { 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(), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD); + const std::string filename = filepath_get_filename(record_filepath.c_str()); + char msg[512]; + if(is_capture_target_monitor(recording_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Saved recording of this monitor to '%s'", filename.c_str()); + else + snprintf(msg, sizeof(msg), "Saved recording of %s to '%s'", recording_capture_target.c_str(), filename.c_str()); + show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); } } else { fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); @@ -2021,10 +2113,10 @@ namespace gsr { } const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - const std::string capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options); - if(!validate_capture_target(capture_target, capture_options)) { + replay_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options); + if(!validate_capture_target(replay_capture_target, capture_options)) { char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", capture_target.c_str()); + snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid. Please change capture target in settings", replay_capture_target.c_str()); show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::REPLAY); return false; } @@ -2061,7 +2153,7 @@ namespace gsr { snprintf(size, sizeof(size), "%dx%d", (int)config.replay_config.record_options.video_width, (int)config.replay_config.record_options.video_height); std::vector<const char*> args = { - "gpu-screen-recorder", "-w", capture_target.c_str(), + "gpu-screen-recorder", "-w", replay_capture_target.c_str(), "-c", config.replay_config.container.c_str(), "-ac", config.replay_config.record_options.audio_codec.c_str(), "-cursor", config.replay_config.record_options.record_cursor ? "yes" : "no", @@ -2108,8 +2200,14 @@ namespace gsr { // 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. - if(!disable_notification && config.replay_config.show_replay_started_notifications) - show_notification("Replay has started", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY); + if(!disable_notification && config.replay_config.show_replay_started_notifications) { + char msg[256]; + if(is_capture_target_monitor(replay_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Started replaying this monitor"); + else + snprintf(msg, sizeof(msg), "Started replaying %s", replay_capture_target.c_str()); + show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, replay_capture_target.c_str()); + } return true; } @@ -2156,10 +2254,10 @@ namespace gsr { } const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - const std::string capture_target = get_capture_target(config.record_config.record_options.record_area_option, capture_options); + recording_capture_target = get_capture_target(config.record_config.record_options.record_area_option, capture_options); if(!validate_capture_target(config.record_config.record_options.record_area_option, capture_options)) { char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid. Please change capture target in settings", capture_target.c_str()); + snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid. Please change capture target in settings", recording_capture_target.c_str()); show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::RECORD); return; } @@ -2197,7 +2295,7 @@ namespace gsr { snprintf(size, sizeof(size), "%dx%d", (int)config.record_config.record_options.video_width, (int)config.record_config.record_options.video_height); std::vector<const char*> args = { - "gpu-screen-recorder", "-w", capture_target.c_str(), + "gpu-screen-recorder", "-w", recording_capture_target.c_str(), "-c", config.record_config.container.c_str(), "-ac", config.record_config.record_options.audio_codec.c_str(), "-cursor", config.record_config.record_options.record_cursor ? "yes" : "no", @@ -2230,8 +2328,14 @@ namespace gsr { // Starting recording in 3... // 2... // 1... - if(config.record_config.show_recording_started_notifications) - show_notification("Recording has started", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD); + if(config.record_config.show_recording_started_notifications) { + char msg[256]; + if(is_capture_target_monitor(recording_capture_target.c_str())) + snprintf(msg, sizeof(msg), "Started recording this monitor"); + else + snprintf(msg, sizeof(msg), "Started recording %s", recording_capture_target.c_str()); + show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str()); + } } static std::string streaming_get_url(const Config &config) { @@ -2385,8 +2489,14 @@ namespace gsr { // 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. - if(config.streaming_config.show_streaming_started_notifications) - show_notification("Streaming has started", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM); + if(config.streaming_config.show_streaming_started_notifications) { + char msg[256]; + if(is_capture_target_monitor(capture_target.c_str())) + snprintf(msg, sizeof(msg), "Started streaming this monitor"); + else + snprintf(msg, sizeof(msg), "Started streaming %s", capture_target.c_str()); + show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, capture_target.c_str()); + } } void Overlay::on_press_take_screenshot(bool finished_region_selection, bool force_region_capture) { @@ -2401,10 +2511,10 @@ namespace gsr { const bool region_capture = config.screenshot_config.record_area_option == "region" || force_region_capture; const char *record_area_option = region_capture ? "region" : config.screenshot_config.record_area_option.c_str(); const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info); - const std::string capture_target = get_capture_target(record_area_option, capture_options); + screenshot_capture_target = get_capture_target(record_area_option, capture_options); if(!validate_capture_target(record_area_option, capture_options)) { char err_msg[256]; - snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid. Please change capture target in settings", capture_target.c_str()); + snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid. Please change capture target in settings", screenshot_capture_target.c_str()); show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::SCREENSHOT); return; } @@ -2422,7 +2532,7 @@ namespace gsr { const std::string output_file = config.screenshot_config.save_directory + "/Screenshot_" + get_date_str() + "." + config.screenshot_config.image_format; // TODO: Validate image format std::vector<const char*> args = { - "gpu-screen-recorder", "-w", capture_target.c_str(), + "gpu-screen-recorder", "-w", screenshot_capture_target.c_str(), "-cursor", config.screenshot_config.record_cursor ? "yes" : "no", "-v", "no", "-q", config.screenshot_config.image_quality.c_str(), diff --git a/src/main.cpp b/src/main.cpp index 7c10a6e..be2f6d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -303,9 +303,6 @@ int main(int argc, char **argv) { if(exit_reason == "back-to-old-ui") { const char *args[] = { "gpu-screen-recorder-gtk", "use-old-ui", nullptr }; execvp(args[0], (char* const*)args); - } else if(exit_reason == "restart") { - const char *args[] = { "gsr-ui", "launch-show", nullptr }; - execvp(args[0], (char* const*)args); } return 0; |