aboutsummaryrefslogtreecommitdiff
path: root/src/Overlay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Overlay.cpp')
-rw-r--r--src/Overlay.cpp326
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
+}