diff options
Diffstat (limited to 'src/Overlay.cpp')
-rw-r--r-- | src/Overlay.cpp | 326 |
1 files changed, 262 insertions, 64 deletions
diff --git a/src/Overlay.cpp b/src/Overlay.cpp index d71dd4e..c8e8b8f 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -164,7 +164,7 @@ namespace gsr { return std::abs(a - b) <= difference; } - static bool is_window_fullscreen_on_monitor(Display *display, Window window, const mgl_monitor *monitor) { + static bool is_window_fullscreen_on_monitor(Display *display, Window window, const Monitor &monitor) { if(!window) return false; @@ -173,8 +173,8 @@ namespace gsr { 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); + return diff_int(geometry.x, monitor.position.x, margin) && diff_int(geometry.y, monitor.position.y, margin) + && diff_int(geometry.width, monitor.size.x, margin) && diff_int(geometry.height, monitor.size.y, margin); } /*static bool is_window_fullscreen_on_monitor(Display *display, Window window, const mgl_monitor *monitors, int num_monitors) { @@ -279,15 +279,13 @@ namespace gsr { } // Returns the first monitor if not found. Assumes there is at least one monitor connected. - static const mgl_monitor* find_monitor_at_position(mgl::Window &window, mgl::vec2i pos) { - 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(pos)) - return mon; + static const Monitor* find_monitor_at_position(const std::vector<Monitor> &monitors, mgl::vec2i pos) { + assert(!monitors.empty()); + for(const Monitor &monitor : monitors) { + if(mgl::IntRect(monitor.position, monitor.size).contains(pos)) + return &monitor; } - return &win->monitors[0]; + return &monitors.front(); } static std::string get_power_supply_online_filepath() { @@ -338,6 +336,79 @@ namespace gsr { return true; } + static Hotkey config_hotkey_to_hotkey(ConfigHotkey config_hotkey) { + return { + (uint32_t)mgl::Keyboard::key_to_x11_keysym((mgl::Keyboard::Key)config_hotkey.key), + config_hotkey.modifiers + }; + } + + static void bind_linux_hotkeys(GlobalHotkeysLinux *global_hotkeys, Overlay *overlay) { + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().main_config.show_hide_hotkey), + "show_hide", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_show(); + }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().record_config.start_stop_hotkey), + "record", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_record(); + }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().record_config.pause_unpause_hotkey), + "pause", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_pause(); + }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().streaming_config.start_stop_hotkey), + "stream", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_stream(); + }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().replay_config.start_stop_hotkey), + "replay_start", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_replay(); + }); + + global_hotkeys->bind_key_press( + config_hotkey_to_hotkey(overlay->get_config().replay_config.save_hotkey), + "replay_save", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay(); + }); + } + + static std::unique_ptr<GlobalHotkeysLinux> register_linux_hotkeys(Overlay *overlay, GlobalHotkeysLinux::GrabType grab_type) { + auto global_hotkeys = std::make_unique<GlobalHotkeysLinux>(grab_type); + if(!global_hotkeys->start()) + fprintf(stderr, "error: failed to start global hotkeys\n"); + + bind_linux_hotkeys(global_hotkeys.get(), overlay); + return global_hotkeys; + } + + static std::unique_ptr<GlobalHotkeysJoystick> register_joystick_hotkeys(Overlay *overlay) { + auto global_hotkeys_js = std::make_unique<GlobalHotkeysJoystick>(); + if(!global_hotkeys_js->start()) + fprintf(stderr, "Warning: failed to start joystick hotkeys\n"); + + global_hotkeys_js->bind_action("save_replay", [overlay](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->save_replay(); + }); + + return global_hotkeys_js; + } + Overlay::Overlay(std::string resources_path, GsrInfo gsr_info, SupportedCaptureOptions capture_options, egl_functions egl_funcs) : resources_path(std::move(resources_path)), gsr_info(std::move(gsr_info)), @@ -368,6 +439,20 @@ namespace gsr { if(config.replay_config.turn_on_replay_automatically_mode == "turn_on_at_system_startup") on_press_start_replay(true); + + if(config.main_config.hotkeys_enable_option == "enable_hotkeys") + global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL); + else if(config.main_config.hotkeys_enable_option == "enable_hotkeys_virtual_devices") + global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::VIRTUAL); + + if(config.main_config.joystick_hotkeys_enable_option == "enable_hotkeys") + global_hotkeys_js = register_joystick_hotkeys(this); + + x11_mapping_display = XOpenDisplay(nullptr); + if(x11_mapping_display) + XKeysymToKeycode(x11_mapping_display, XK_F1); // If we dont call we will never get a MappingNotify + else + fprintf(stderr, "Warning: XOpenDisplay failed to mapping notify\n"); } Overlay::~Overlay() { @@ -395,6 +480,9 @@ namespace gsr { close_gpu_screen_recorder_output(); deinit_color_theme(); + + if(x11_mapping_display) + XCloseDisplay(x11_mapping_display); } void Overlay::xi_setup() { @@ -537,7 +625,32 @@ namespace gsr { } } - void Overlay::handle_events(gsr::GlobalHotkeys *global_hotkeys) { + void Overlay::handle_keyboard_mapping_event() { + if(!x11_mapping_display) + return; + + bool mapping_updated = false; + while(XPending(x11_mapping_display)) { + XNextEvent(x11_mapping_display, &x11_mapping_xev); + if(x11_mapping_xev.type == MappingNotify) { + XRefreshKeyboardMapping(&x11_mapping_xev.xmapping); + mapping_updated = true; + } + } + + if(mapping_updated) + rebind_all_keyboard_hotkeys(); + } + + void Overlay::handle_events() { + if(global_hotkeys) + global_hotkeys->poll_events(); + + if(global_hotkeys_js) + global_hotkeys_js->poll_events(); + + handle_keyboard_mapping_event(); + if(!visible || !window) return; @@ -586,39 +699,43 @@ namespace gsr { //force_window_on_top(); - window->clear(bg_color); + const bool draw_ui = show_overlay_clock.get_elapsed_time_seconds() >= show_overlay_timeout_seconds; - 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->clear(draw_ui ? bg_color : mgl::Color(0, 0, 0, 0)); - window->draw(top_bar_background); - window->draw(top_bar_text); - window->draw(logo_sprite); + if(draw_ui) { + 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); + } - close_button_widget.draw(*window, mgl::vec2f(0.0f, 0.0f)); - page_stack.draw(*window, mgl::vec2f(0.0f, 0.0f)); + window->draw(top_bar_background); + window->draw(top_bar_text); + window->draw(logo_sprite); - if(cursor_texture.is_valid()) { - cursor_sprite.set_position((window->get_mouse_position() - cursor_hotspot).to_vec2f()); - window->draw(cursor_sprite); - } + close_button_widget.draw(*window, mgl::vec2f(0.0f, 0.0f)); + page_stack.draw(*window, mgl::vec2f(0.0f, 0.0f)); - window->display(); + if(cursor_texture.is_valid()) { + cursor_sprite.set_position((window->get_mouse_position() - cursor_hotspot).to_vec2f()); + window->draw(cursor_sprite); + } - if(!drawn_first_frame) { - drawn_first_frame = 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); + if(!drawn_first_frame) { + drawn_first_frame = 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->display(); + return true; } @@ -669,7 +786,15 @@ namespace gsr { XcursorImageDestroy(cursor_image); } - void Overlay::xi_grab_all_devices() { + static bool device_is_mouse(const XIDeviceInfo *dev) { + for(int i = 0; i < dev->num_classes; ++i) { + if(dev->classes[i]->type == XIMasterPointer || dev->classes[i]->type == XISlavePointer) + return true; + } + return false; + } + + void Overlay::xi_grab_all_mouse_devices() { if(!xi_display) return; @@ -689,6 +814,9 @@ namespace gsr { for (int i = 0; i < num_devices; ++i) { const XIDeviceInfo *dev = &info[i]; + if(!device_is_mouse(dev)) + continue; + XIEventMask xi_masks; xi_masks.deviceid = dev->deviceid; xi_masks.mask_len = sizeof(mask); @@ -712,21 +840,36 @@ namespace gsr { mgl_context *context = mgl_get_context(); Display *display = (Display*)context->connection; + const std::vector<Monitor> monitors = get_monitors(display); + if(monitors.empty()) { + fprintf(stderr, "gsr warning: no monitors found, not showing overlay\n"); + window.reset(); + return; + } + const std::string wm_name = get_window_manager_name(display); const bool is_kwin = wm_name == "KWin"; + const bool is_wlroots = wm_name.find("wlroots") != std::string::npos; // The cursor position is wrong on wayland if an x11 window is not focused. On wayland we instead create a window and get the position where the wayland compositor puts it Window x11_cursor_window = None; const mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window); const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display); + const Monitor *focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value); + // Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused. // If the focused window is a wayland application then don't use override redirect and instead create // a fullscreen window for the ui. - const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window; + const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots; - window_size = { 32, 32 }; - window_pos = { 0, 0 }; + if(prevent_game_minimizing) { + window_pos = focused_monitor->position; + window_size = focused_monitor->size; + } else { + window_pos = {0, 0}; + window_size = focused_monitor->size / 2; + } mgl::Window::CreateParams window_create_params; window_create_params.size = window_size; @@ -734,53 +877,52 @@ namespace gsr { window_create_params.min_size = window_size; window_create_params.max_size = window_size; } - window_create_params.position = window_pos; + window_create_params.position = focused_monitor->position + focused_monitor->size / 2 - window_size / 2; window_create_params.hidden = prevent_game_minimizing; window_create_params.override_redirect = prevent_game_minimizing; - window_create_params.background_color = bg_color; + window_create_params.background_color = mgl::Color(0, 0, 0, 0); window_create_params.support_alpha = true; window_create_params.hide_decorations = true; // MGL_WINDOW_TYPE_DIALOG is needed for kde plasma wayland in some cases, otherwise the window will pop up on another activity // or may not be visible at all window_create_params.window_type = (is_kwin && gsr_info.system_info.display_server == DisplayServer::WAYLAND) ? MGL_WINDOW_TYPE_DIALOG : MGL_WINDOW_TYPE_NORMAL; - window_create_params.render_api = MGL_RENDER_API_EGL; + // Nvidia + Wayland + Egl doesn't work on some systems properly and it instead falls back to software rendering. + // Use Glx on Wayland to workaround this issue. This is fine since Egl is only needed for x11 to reliably get the texture of the fullscreen window on Nvidia + // when a compositor isn't running. + window_create_params.render_api = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? MGL_RENDER_API_GLX : MGL_RENDER_API_EGL; if(!window->create("gsr ui", window_create_params)) fprintf(stderr, "error: failed to create window\n"); + //window->set_low_latency(true); + 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); + const auto original_window_size = window_size; + window_pos = focused_monitor->position; + window_size = focused_monitor->size; if(!init_theme(resources_path)) { fprintf(stderr, "Error: failed to load theme\n"); - ::exit(1); - } - - mgl_window *win = window->internal_window(); - if(win->num_monitors == 0) { - fprintf(stderr, "gsr warning: no monitors found, not showing overlay\n"); window.reset(); return; } - - const mgl_monitor *focused_monitor = find_monitor_at_position(*window, monitor_position_query_value); - window_pos = {focused_monitor->pos.x, focused_monitor->pos.y}; - window_size = {focused_monitor->size.x, focused_monitor->size.y}; get_theme().set_window_size(window_size); if(prevent_game_minimizing) { window->set_size(window_size); window->set_size_limits(window_size, window_size); - window->set_position(window_pos); } + window->set_position(focused_monitor->position + focused_monitor->size / 2 - original_window_size / 2); + mgl_window *win = window->internal_window(); win->cursor_position.x = cursor_position.x - window_pos.x; win->cursor_position.y = cursor_position.y - window_pos.y; - update_compositor_texture(focused_monitor); + update_compositor_texture(*focused_monitor); 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()); @@ -902,7 +1044,8 @@ namespace gsr { button->set_bg_hover_color(mgl::Color(0, 0, 0, 255)); button->set_icon(&get_theme().settings_small_texture); button->on_click = [&]() { - auto settings_page = std::make_unique<GlobalSettingsPage>(&gsr_info, config, &page_stack); + auto settings_page = std::make_unique<GlobalSettingsPage>(this, &gsr_info, config, &page_stack); + settings_page->on_startup_changed = [&](bool enable, int exit_status) { if(exit_status == 0) return; @@ -917,10 +1060,30 @@ namespace gsr { show_notification("Failed to remove GPU Screen Recorder from system startup", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE); } }; - settings_page->on_click_exit_program_button = [&](const char *reason) { + + settings_page->on_click_exit_program_button = [this](const char *reason) { do_exit = true; exit_reason = reason; }; + + settings_page->on_keyboard_hotkey_changed = [this](const char *hotkey_option) { + global_hotkeys.reset(); + if(strcmp(hotkey_option, "enable_hotkeys") == 0) + global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::ALL); + else if(strcmp(hotkey_option, "enable_hotkeys_virtual_devices") == 0) + global_hotkeys = register_linux_hotkeys(this, GlobalHotkeysLinux::GrabType::VIRTUAL); + else if(strcmp(hotkey_option, "disable_hotkeys") == 0) + global_hotkeys.reset(); + }; + + settings_page->on_joystick_hotkey_changed = [this](const char *hotkey_option) { + global_hotkeys_js.reset(); + if(strcmp(hotkey_option, "enable_hotkeys") == 0) + global_hotkeys_js = register_joystick_hotkeys(this); + else if(strcmp(hotkey_option, "disable_hotkeys") == 0) + global_hotkeys_js.reset(); + }; + page_stack.push(std::move(settings_page)); }; front_page_ptr->add_widget(std::move(button)); @@ -955,7 +1118,8 @@ namespace gsr { // The focused application can be an xwayland application but the cursor can hover over a wayland application. // This is even the case when hovering over the titlebar of the xwayland application. - if(prevent_game_minimizing) + const bool fake_cursor = is_wlroots ? x11_cursor_window != None : prevent_game_minimizing; + if(fake_cursor) xi_setup(); //window->set_fullscreen(true); @@ -981,14 +1145,15 @@ namespace gsr { // We want to grab all devices to prevent any other application below the UI from receiving events. // Owlboy seems to use xi events and XGrabPointer doesn't prevent owlboy from receiving events. - xi_grab_all_devices(); + xi_grab_all_mouse_devices(); // if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { // set_focused_window(display, window->get_system_handle()); // XFlush(display); // } - window->set_fullscreen(true); + if(!is_wlroots) + window->set_fullscreen(true); visible = true; @@ -1010,6 +1175,12 @@ namespace gsr { if(paused) update_ui_recording_paused(); + + // Wayland compositors have retarded fullscreen animations that we cant disable in a proper way + // without messing up window position. + show_overlay_timeout_seconds = prevent_game_minimizing ? 0.0 : 0.15; + show_overlay_clock.restart(); + draw(); } void Overlay::hide() { @@ -1073,6 +1244,16 @@ namespace gsr { } if(window) { + if(show_overlay_timeout_seconds > 0.0001) { + window->clear(mgl::Color(0, 0, 0, 0)); + window->display(); + + mgl_context *context = mgl_get_context(); + context->gl.glFlush(); + context->gl.glFinish(); + usleep(50 * 1000); // EGL doesn't do an immediate flush for some reason + } + window->set_visible(false); window.reset(); } @@ -1183,6 +1364,18 @@ namespace gsr { return config; } + void Overlay::unbind_all_keyboard_hotkeys() { + if(global_hotkeys) + global_hotkeys->unbind_all_keys(); + } + + void Overlay::rebind_all_keyboard_hotkeys() { + unbind_all_keyboard_hotkeys(); + // TODO: Check if type is GlobalHotkeysLinux + if(global_hotkeys) + bind_linux_hotkeys(static_cast<GlobalHotkeysLinux*>(global_hotkeys.get()), this); + } + void Overlay::update_notification_process_status() { if(notification_process <= 0) return; @@ -1673,6 +1866,11 @@ namespace gsr { "-o", output_directory.c_str() }; + if(config.replay_config.restart_replay_on_save && gsr_info.system_info.gsr_version >= GsrVersion{5, 0, 3}) { + args.push_back("-restart-replay-on-save"); + args.push_back("yes"); + } + add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged); args.push_back(nullptr); @@ -1951,7 +2149,7 @@ namespace gsr { show_notification("Streaming has started", 3.0, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM); } - bool Overlay::update_compositor_texture(const mgl_monitor *monitor) { + bool Overlay::update_compositor_texture(const Monitor &monitor) { window_texture_deinit(&window_texture); window_texture_sprite.set_texture(nullptr); screenshot_texture.clear(); @@ -1974,7 +2172,7 @@ namespace gsr { 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); + XImage *img = XGetImage(display, DefaultRootWindow(display), monitor.position.x, monitor.position.y, monitor.size.x, monitor.size.y, AllPlanes, ZPixmap); if(!img) fprintf(stderr, "Error: failed to take a screenshot\n"); @@ -2000,4 +2198,4 @@ namespace gsr { XFlush(display); } } -}
\ No newline at end of file +} |