aboutsummaryrefslogtreecommitdiff
path: root/src/Overlay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Overlay.cpp')
-rw-r--r--src/Overlay.cpp1105
1 files changed, 863 insertions, 242 deletions
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
index 4eb8844..794ef92 100644
--- a/src/Overlay.cpp
+++ b/src/Overlay.cpp
@@ -12,8 +12,10 @@
#include "../include/gui/Utils.hpp"
#include "../include/gui/PageStack.hpp"
#include "../include/WindowUtils.hpp"
-#include "../include/GlobalHotkeys.hpp"
-#include "../include/GlobalHotkeysLinux.hpp"
+#include "../include/GlobalHotkeys/GlobalHotkeys.hpp"
+#include "../include/GlobalHotkeys/GlobalHotkeysLinux.hpp"
+#include "../include/CursorTracker/CursorTrackerX11.hpp"
+#include "../include/CursorTracker/CursorTrackerWayland.hpp"
#include <string.h>
#include <assert.h>
@@ -24,6 +26,7 @@
#include <malloc.h>
#include <stdexcept>
#include <algorithm>
+#include <inttypes.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
@@ -35,6 +38,7 @@
#include <X11/Xcursor/Xcursor.h>
#include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp>
+#include <mglpp/system/Utf8.hpp>
extern "C" {
#include <mgl/mgl.h>
@@ -45,8 +49,9 @@ namespace gsr {
static const double force_window_on_top_timeout_seconds = 1.0;
static const double replay_status_update_check_timeout_seconds = 1.5;
static const double replay_saving_notification_timeout_seconds = 0.5;
- static const double notification_timeout_seconds = 2.0;
+ static const double notification_timeout_seconds = 2.5;
static const double notification_error_timeout_seconds = 5.0;
+ static const double cursor_tracker_update_timeout_sec = 0.1;
static mgl::Texture texture_from_ximage(XImage *img) {
uint8_t *texture_data = (uint8_t*)malloc(img->width * img->height * 3);
@@ -204,14 +209,20 @@ namespace gsr {
return false;
}*/
- // Returns the first monitor if not found. Assumes there is at least one monitor connected.
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 &monitors.front();
+ return nullptr;
+ }
+
+ static const Monitor* find_monitor_by_name(const std::vector<Monitor> &monitors, const std::string &name) {
+ for(const Monitor &monitor : monitors) {
+ if(monitor.name == name)
+ return &monitor;
+ }
+ return nullptr;
}
static std::string get_power_supply_online_filepath() {
@@ -262,6 +273,33 @@ namespace gsr {
return true;
}
+ static bool is_hyprland_waybar_running_as_dock() {
+ const char *args[] = { "hyprctl", "layers", nullptr };
+ std::string stdout_str;
+ if(exec_program_on_host_get_stdout(args, stdout_str) != 0)
+ return false;
+
+ int waybar_layer_level = -1;
+ int current_layer_level = 0;
+ string_split_char(stdout_str, '\n', [&](const std::string_view line) {
+ if(line.find("Layer level 0") != std::string_view::npos)
+ current_layer_level = 0;
+ else if(line.find("Layer level 1") != std::string_view::npos)
+ current_layer_level = 1;
+ else if(line.find("Layer level 2") != std::string_view::npos)
+ current_layer_level = 2;
+ else if(line.find("Layer level 3") != std::string_view::npos)
+ current_layer_level = 3;
+ else if(line.find("namespace: waybar") != std::string_view::npos) {
+ waybar_layer_level = current_layer_level;
+ return false;
+ }
+ return true;
+ });
+
+ return waybar_layer_level >= 0 && waybar_layer_level <= 1;
+ }
+
static Hotkey config_hotkey_to_hotkey(ConfigHotkey config_hotkey) {
return {
(uint32_t)mgl::Keyboard::key_to_x11_keysym((mgl::Keyboard::Key)config_hotkey.key),
@@ -313,6 +351,20 @@ namespace gsr {
});
global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().replay_config.save_1_min_hotkey),
+ "replay_save_1_min", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->save_replay_1_min();
+ });
+
+ global_hotkeys->bind_key_press(
+ config_hotkey_to_hotkey(overlay->get_config().replay_config.save_10_min_hotkey),
+ "replay_save_10_min", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->save_replay_10_min();
+ });
+
+ global_hotkeys->bind_key_press(
config_hotkey_to_hotkey(overlay->get_config().screenshot_config.take_screenshot_hotkey),
"take_screenshot", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
@@ -325,6 +377,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) {
@@ -351,6 +410,16 @@ namespace gsr {
overlay->save_replay();
});
+ global_hotkeys_js->bind_action("save_1_min_replay", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->save_replay_1_min();
+ });
+
+ global_hotkeys_js->bind_action("save_10_min_replay", [overlay](const std::string &id) {
+ fprintf(stderr, "pressed %s\n", id.c_str());
+ overlay->save_replay_10_min();
+ });
+
global_hotkeys_js->bind_action("take_screenshot", [overlay](const std::string &id) {
fprintf(stderr, "pressed %s\n", id.c_str());
overlay->take_screenshot();
@@ -411,6 +480,11 @@ namespace gsr {
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");
+
+ if(this->gsr_info.system_info.display_server == DisplayServer::X11)
+ cursor_tracker = std::make_unique<CursorTrackerX11>((Display*)mgl_get_context()->connection);
+ else if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND && !this->gsr_info.gpu_info.card_path.empty())
+ cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str());
}
Overlay::~Overlay() {
@@ -531,8 +605,8 @@ namespace gsr {
memset(xi_output_xev, 0, sizeof(*xi_output_xev));
xi_output_xev->type = MotionNotify;
xi_output_xev->xmotion.display = display;
- xi_output_xev->xmotion.window = window->get_system_handle();
- xi_output_xev->xmotion.subwindow = window->get_system_handle();
+ xi_output_xev->xmotion.window = (Window)window->get_system_handle();
+ xi_output_xev->xmotion.subwindow = (Window)window->get_system_handle();
xi_output_xev->xmotion.x = de->root_x - window_pos.x;
xi_output_xev->xmotion.y = de->root_y - window_pos.y;
xi_output_xev->xmotion.x_root = de->root_x;
@@ -544,8 +618,8 @@ namespace gsr {
memset(xi_output_xev, 0, sizeof(*xi_output_xev));
xi_output_xev->type = cookie->evtype == XI_ButtonPress ? ButtonPress : ButtonRelease;
xi_output_xev->xbutton.display = display;
- xi_output_xev->xbutton.window = window->get_system_handle();
- xi_output_xev->xbutton.subwindow = window->get_system_handle();
+ xi_output_xev->xbutton.window = (Window)window->get_system_handle();
+ xi_output_xev->xbutton.subwindow = (Window)window->get_system_handle();
xi_output_xev->xbutton.x = de->root_x - window_pos.x;
xi_output_xev->xbutton.y = de->root_y - window_pos.y;
xi_output_xev->xbutton.x_root = de->root_x;
@@ -558,8 +632,8 @@ namespace gsr {
memset(xi_output_xev, 0, sizeof(*xi_output_xev));
xi_output_xev->type = cookie->evtype == XI_KeyPress ? KeyPress : KeyRelease;
xi_output_xev->xkey.display = display;
- xi_output_xev->xkey.window = window->get_system_handle();
- xi_output_xev->xkey.subwindow = window->get_system_handle();
+ xi_output_xev->xkey.window = (Window)window->get_system_handle();
+ xi_output_xev->xkey.subwindow = (Window)window->get_system_handle();
xi_output_xev->xkey.x = de->root_x - window_pos.x;
xi_output_xev->xkey.y = de->root_y - window_pos.y;
xi_output_xev->xkey.x_root = de->root_x;
@@ -617,6 +691,12 @@ namespace gsr {
if(global_hotkeys_js)
global_hotkeys_js->poll_events();
+ if(cursor_tracker_update_clock.get_elapsed_time_seconds() >= cursor_tracker_update_timeout_sec) {
+ cursor_tracker_update_clock.restart();
+ if(cursor_tracker)
+ cursor_tracker->update();
+ }
+
handle_keyboard_mapping_event();
region_selector.poll_events();
if(region_selector.take_canceled()) {
@@ -626,6 +706,22 @@ namespace gsr {
on_region_selected = nullptr;
}
+ window_selector.poll_events();
+ if(window_selector.take_canceled()) {
+ on_window_selected = nullptr;
+ } else if(window_selector.take_selection() && on_window_selected) {
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+
+ const Window selected_window = window_selector.get_selection();
+ if(selected_window && selected_window != DefaultRootWindow(display)) {
+ on_window_selected();
+ } else {
+ show_notification("No window selected", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
+ }
+ on_window_selected = nullptr;
+ }
+
if(!visible || !window)
return;
@@ -654,8 +750,10 @@ namespace gsr {
}
bool Overlay::draw() {
+ remove_widgets_to_be_removed();
+
update_notification_process_status();
- update_gsr_replay_save();
+ process_gsr_output();
update_gsr_process_status();
update_gsr_screenshot_process_status();
replay_status_update_status();
@@ -664,12 +762,21 @@ namespace gsr {
start_region_capture = false;
hide();
if(!region_selector.start(get_color_theme().tint_color)) {
- show_notification("Failed to start region capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::NONE);
+ show_notification("Failed to start region capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
on_region_selected = nullptr;
}
}
- if(region_selector.is_started()) {
+ if(start_window_capture) {
+ start_window_capture = false;
+ hide();
+ if(!window_selector.start(get_color_theme().tint_color)) {
+ show_notification("Failed to start window capture", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::NONE);
+ on_window_selected = nullptr;
+ }
+ }
+
+ if(region_selector.is_started() || window_selector.is_started()) {
usleep(5 * 1000); // 5 ms
return true;
}
@@ -734,13 +841,13 @@ namespace gsr {
// There should be a debug mode to not use these
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
- XGrabPointer(display, window->get_system_handle(), True,
+ XGrabPointer(display, (Window)window->get_system_handle(), True,
ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask |
ButtonMotionMask,
GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime);
// TODO: This breaks global hotkeys (when using x11 global hotkeys)
- XGrabKeyboard(display, window->get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime);
+ XGrabKeyboard(display, (Window)window->get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime);
XFlush(display);
}
@@ -803,7 +910,7 @@ namespace gsr {
if(visible)
return;
- if(region_selector.is_started())
+ if(region_selector.is_started() || window_selector.is_started())
return;
drawn_first_frame = false;
@@ -824,18 +931,36 @@ namespace gsr {
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;
+ const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos;
+ const bool hyprland_waybar_is_dock = is_hyprland && is_hyprland_waybar_running_as_dock();
+
+ std::optional<CursorInfo> cursor_info;
+ if(cursor_tracker) {
+ cursor_tracker->update();
+ cursor_info = cursor_tracker->get_latest_cursor_info();
+ }
// 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);
+ mgl::vec2i cursor_position = get_cursor_position(display, &x11_cursor_window);
+ const Monitor *focused_monitor = nullptr;
+ if(cursor_info) {
+ focused_monitor = find_monitor_by_name(monitors, cursor_info->monitor_name);
+ if(!focused_monitor)
+ focused_monitor = &monitors.front();
+ cursor_position = cursor_info->position;
+ } else {
+ 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);
+ focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value);
+ if(!focused_monitor)
+ focused_monitor = &monitors.front();
+ }
// 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 || is_wlroots;
+ // TODO: (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor))
+ const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND || x11_cursor_window || is_wlroots || is_hyprland;
if(prevent_game_minimizing) {
window_pos = focused_monitor->position;
@@ -863,18 +988,21 @@ namespace gsr {
// 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;
+ window_create_params.graphics_api = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? MGL_GRAPHICS_API_GLX : MGL_GRAPHICS_API_EGL;
- if(!window->create("gsr ui", window_create_params))
+ if(!window->create("gsr ui", window_create_params)) {
fprintf(stderr, "error: failed to create window\n");
+ window.reset();
+ return;
+ }
//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);
+ XChangeProperty(display, (Window)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);
+ XChangeProperty(display, (Window)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;
@@ -908,12 +1036,12 @@ namespace gsr {
//window->set_fullscreen(true);
if(gsr_info.system_info.display_server == DisplayServer::X11)
- make_window_click_through(display, window->get_system_handle());
+ make_window_click_through(display, (Window)window->get_system_handle());
window->set_visible(true);
- make_window_sticky(display, window->get_system_handle());
- hide_window_from_taskbar(display, window->get_system_handle());
+ make_window_sticky(display, (Window)window->get_system_handle());
+ hide_window_from_taskbar(display, (Window)window->get_system_handle());
if(default_cursor) {
XFreeCursor(display, default_cursor);
@@ -925,13 +1053,18 @@ namespace gsr {
grab_mouse_and_keyboard();
// The real cursor doesn't move when all devices are grabbed, so we create our own cursor and diplay that while grabbed
+ cursor_hotspot = {0, 0};
xi_setup_fake_cursor();
+ if(cursor_info && gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ win->cursor_position.x += cursor_hotspot.x;
+ win->cursor_position.y += cursor_hotspot.y;
+ }
// 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_mouse_devices(xi_display);
- if(!is_wlroots)
+ if(!is_wlroots && !hyprland_waybar_is_dock)
window->set_fullscreen(true);
visible = true;
@@ -955,6 +1088,9 @@ namespace gsr {
if(paused)
update_ui_recording_paused();
+ if(replay_recording)
+ update_ui_recording_started();
+
// 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;
@@ -1002,10 +1138,16 @@ namespace gsr {
replay_dropdown_button_ptr = button.get();
button->add_item("Turn on", "start", config.replay_config.start_stop_hotkey.to_string(false, false));
button->add_item("Save", "save", config.replay_config.save_hotkey.to_string(false, false));
+ if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
+ button->add_item("Save 1 min", "save_1_min", config.replay_config.save_1_min_hotkey.to_string(false, false));
+ button->add_item("Save 10 min", "save_10_min", config.replay_config.save_10_min_hotkey.to_string(false, false));
+ }
button->add_item("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("save", &get_theme().save_texture);
- button->set_item_icon("settings", &get_theme().settings_small_texture);
+ button->set_item_icon("save_1_min", &get_theme().save_texture);
+ button->set_item_icon("save_10_min", &get_theme().save_texture);
+ button->set_item_icon("settings", &get_theme().settings_extra_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack);
@@ -1017,10 +1159,17 @@ namespace gsr {
page_stack.push(std::move(replay_settings_page));
} else if(id == "save") {
on_press_save_replay();
+ } else if(id == "save_1_min") {
+ on_press_save_replay_1_min_replay();
+ } else if(id == "save_10_min") {
+ on_press_save_replay_10_min_replay();
} else if(id == "start") {
on_press_start_replay(false, false);
}
};
+ button->set_item_enabled("save", false);
+ button->set_item_enabled("save_1_min", false);
+ button->set_item_enabled("save_10_min", false);
main_buttons_list->add_widget(std::move(button));
}
{
@@ -1032,7 +1181,7 @@ namespace gsr {
button->add_item("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("pause", &get_theme().pause_texture);
- button->set_item_icon("settings", &get_theme().settings_small_texture);
+ button->set_item_icon("settings", &get_theme().settings_extra_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack);
@@ -1047,6 +1196,7 @@ namespace gsr {
on_press_start_record(false);
}
};
+ button->set_item_enabled("pause", false);
main_buttons_list->add_widget(std::move(button));
}
{
@@ -1056,7 +1206,7 @@ namespace gsr {
button->add_item("Start", "start", config.streaming_config.start_stop_hotkey.to_string(false, false));
button->add_item("Settings", "settings");
button->set_item_icon("start", &get_theme().play_texture);
- button->set_item_icon("settings", &get_theme().settings_small_texture);
+ button->set_item_icon("settings", &get_theme().settings_extra_small_texture);
button->on_click = [this](const std::string &id) {
if(id == "settings") {
auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack);
@@ -1125,23 +1275,15 @@ namespace gsr {
};
settings_page->on_page_closed = [this]() {
- if(global_hotkeys) {
- replay_dropdown_button_ptr->set_item_description("start", config.replay_config.start_stop_hotkey.to_string(false, false));
- replay_dropdown_button_ptr->set_item_description("save", config.replay_config.save_hotkey.to_string(false, false));
+ replay_dropdown_button_ptr->set_item_description("start", config.replay_config.start_stop_hotkey.to_string(false, false));
+ replay_dropdown_button_ptr->set_item_description("save", config.replay_config.save_hotkey.to_string(false, false));
+ replay_dropdown_button_ptr->set_item_description("save_1_min", config.replay_config.save_1_min_hotkey.to_string(false, false));
+ replay_dropdown_button_ptr->set_item_description("save_10_min", config.replay_config.save_10_min_hotkey.to_string(false, false));
- record_dropdown_button_ptr->set_item_description("start", config.record_config.start_stop_hotkey.to_string(false, false));
- record_dropdown_button_ptr->set_item_description("pause", config.record_config.pause_unpause_hotkey.to_string(false, false));
+ record_dropdown_button_ptr->set_item_description("start", config.record_config.start_stop_hotkey.to_string(false, false));
+ record_dropdown_button_ptr->set_item_description("pause", config.record_config.pause_unpause_hotkey.to_string(false, false));
- stream_dropdown_button_ptr->set_item_description("start", config.streaming_config.start_stop_hotkey.to_string(false, false));
- } else {
- replay_dropdown_button_ptr->set_item_description("start", "");
- replay_dropdown_button_ptr->set_item_description("save", "");
-
- record_dropdown_button_ptr->set_item_description("start", "");
- record_dropdown_button_ptr->set_item_description("pause", "");
-
- stream_dropdown_button_ptr->set_item_description("start", "");
- }
+ stream_dropdown_button_ptr->set_item_description("start", config.streaming_config.start_stop_hotkey.to_string(false, false));
};
page_stack.push(std::move(settings_page));
@@ -1202,6 +1344,7 @@ namespace gsr {
while(!page_stack.empty()) {
page_stack.pop();
}
+ remove_widgets_to_be_removed();
if(default_cursor) {
XFreeCursor(display, default_cursor);
@@ -1225,6 +1368,7 @@ namespace gsr {
visible = false;
drawn_first_frame = false;
start_region_capture = false;
+ start_window_capture = false;
if(xi_input_xev) {
free(xi_input_xev);
@@ -1296,10 +1440,12 @@ namespace gsr {
if(paused) {
update_ui_recording_unpaused();
- show_notification("Recording has been unpaused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
+ if(config.record_config.show_video_paused_notifications)
+ show_notification("Recording has been unpaused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
} else {
update_ui_recording_paused();
- show_notification("Recording has been paused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
+ if(config.record_config.show_video_paused_notifications)
+ show_notification("Recording has been paused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
}
kill(gpu_screen_recorder_process, SIGUSR2);
@@ -1318,6 +1464,14 @@ namespace gsr {
on_press_save_replay();
}
+ void Overlay::save_replay_1_min() {
+ on_press_save_replay_1_min_replay();
+ }
+
+ void Overlay::save_replay_10_min() {
+ on_press_save_replay_10_min_replay();
+ }
+
void Overlay::take_screenshot() {
on_press_take_screenshot(false, false);
}
@@ -1337,26 +1491,177 @@ 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 void truncate_string(std::string &str, int max_length) {
+ int index = 0;
+ size_t byte_index = 0;
+
+ while(index < max_length && byte_index < str.size()) {
+ uint32_t codepoint = 0;
+ size_t codepoint_length = 0;
+ mgl::utf8_decode((const unsigned char*)str.c_str() + byte_index, str.size() - byte_index, &codepoint, &codepoint_length);
+ if(codepoint_length == 0)
+ codepoint_length = 1;
+
+ index += 1;
+ byte_index += codepoint_length;
+ }
+
+ if(byte_index < str.size()) {
+ str.erase(byte_index);
+ str += "...";
+ }
+ }
+
+ 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_number(const char *str) {
+ const char *p = str;
+ while(*p) {
+ char c = *p;
+ if(c < '0' || c > '9')
+ return false;
+ ++p;
+ }
+ return true;
+ }
+
+ static bool is_capture_target_monitor(const char *capture_target) {
+ return strcmp(capture_target, "window") != 0 && strcmp(capture_target, "focused") != 0 && strcmp(capture_target, "region") != 0 && strcmp(capture_target, "portal") != 0 && contains_non_hex_number(capture_target);
+ }
+
+ static std::string capture_target_get_notification_name(const char *capture_target) {
+ std::string result;
+ if(is_capture_target_monitor(capture_target)) {
+ result = "this monitor";
+ } else if(is_number(capture_target)) {
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+
+ int64_t window_id = None;
+ sscanf(capture_target, "%" PRIi64, &window_id);
+
+ const std::optional<std::string> window_title = get_window_title(display, window_id);
+ if(window_title) {
+ result = strip(window_title.value());
+ truncate_string(result, 20);
+ result = "window \"" + result + "\"";
+ } else {
+ result = std::string("window ") + capture_target;
+ }
+ } else {
+ result = capture_target;
+ }
+ return result;
+ }
+
+ static std::string get_valid_monitor_x11(const std::string &target_monitor_name, const std::vector<Monitor> &monitors) {
+ std::string target_monitor_name_clean = target_monitor_name;
+ if(starts_with(target_monitor_name_clean, "HDMI-A"))
+ target_monitor_name_clean.replace(0, 6, "HDMI");
+
+ for(const Monitor &monitor : monitors) {
+ std::string monitor_name_clean = monitor.name;
+ if(starts_with(monitor_name_clean, "HDMI-A"))
+ monitor_name_clean.replace(0, 6, "HDMI");
+
+ if(target_monitor_name_clean == monitor_name_clean)
+ return monitor.name;
+ }
+
+ return "";
+ }
+
+ static std::string get_focused_monitor_by_cursor(CursorTracker *cursor_tracker, const GsrInfo &gsr_info, const std::vector<Monitor> &x11_monitors) {
+ std::optional<CursorInfo> cursor_info;
+ if(cursor_tracker) {
+ cursor_tracker->update();
+ cursor_info = cursor_tracker->get_latest_cursor_info();
+ }
+
+ std::string focused_monitor_name;
+ if(cursor_info) {
+ focused_monitor_name = std::move(cursor_info->monitor_name);
+ } else {
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+
+ Window x11_cursor_window = None;
+ 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(x11_monitors, monitor_position_query_value);
+ if(focused_monitor)
+ focused_monitor_name = focused_monitor->name;
+ }
+
+ return focused_monitor_name;
+ }
+
+ 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);
const std::string icon_color_str = color_to_hex_str(icon_color);
const std::string bg_color_str = color_to_hex_str(bg_color);
- const char *notification_args[12] = {
+ const char *notification_args[14] = {
"gsr-notify", "--text", str, "--timeout", timeout_seconds_str,
"--icon-color", icon_color_str.c_str(), "--bg-color", bg_color_str.c_str(),
};
+ int arg_index = 9;
const char *notification_type_str = notification_type_to_string(notification_type);
if(notification_type_str) {
- notification_args[9] = "--icon";
- notification_args[10] = notification_type_str;
- notification_args[11] = nullptr;
- } else {
- notification_args[9] = nullptr;
+ notification_args[arg_index++] = "--icon";
+ notification_args[arg_index++] = notification_type_str;
}
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+
+ std::string monitor_name;
+ const auto monitors = get_monitors(display);
+
+ if(capture_target && is_capture_target_monitor(capture_target))
+ monitor_name = capture_target;
+ else
+ monitor_name = get_focused_monitor_by_cursor(cursor_tracker.get(), gsr_info, monitors);
+
+ monitor_name = get_valid_monitor_x11(monitor_name, monitors);
+ if(!monitor_name.empty()) {
+ notification_args[arg_index++] = "--monitor";
+ notification_args[arg_index++] = monitor_name.c_str();
+ } else if(!monitors.empty()) {
+ notification_args[arg_index++] = "--monitor";
+ notification_args[arg_index++] = monitors.front().name.c_str();
+ }
+
+ notification_args[arg_index++] = nullptr;
+
if(notification_process > 0) {
kill(notification_process, SIGKILL);
int status = 0;
@@ -1381,6 +1686,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;
}
@@ -1437,17 +1755,12 @@ namespace gsr {
return result;
}
- static void truncate_string(std::string &str, int max_length) {
- if((int)str.size() > max_length)
- str.replace(str.begin() + max_length, str.end(), "...");
- }
-
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);
- const Window gsr_ui_window = window ? window->get_system_handle() : None;
+ const Window gsr_ui_window = window ? (Window)window->get_system_handle() : None;
std::string focused_window_name = get_window_name_at_cursor_position(display, gsr_ui_window);
if(focused_window_name.empty())
focused_window_name = get_focused_window_name(display, WindowCaptureType::FOCUSED);
@@ -1463,44 +1776,75 @@ 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];
+
switch(notification_type) {
case NotificationType::RECORD: {
if(!config.record_config.show_video_saved_notifications)
return;
- text = "Saved recording to '" + focused_window_name + "/" + video_filename + "'";
+
+ snprintf(msg, sizeof(msg), "Saved a recording of %s to \"%s\"", capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.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 + "'";
+
+ char duration[32];
+ if(replay_save_duration_min > 0)
+ snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
+ else
+ snprintf(duration, sizeof(duration), " ");
+
+ snprintf(msg, sizeof(msg), "Saved a%sreplay of %s to \"%s\"", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str(), focused_window_name.c_str());
+ capture_target = recording_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 + "'";
+
+ snprintf(msg, sizeof(msg), "Saved a screenshot of %s to \"%s\"", capture_target_get_notification_name(screenshot_capture_target.c_str()).c_str(), focused_window_name.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);
+ }
+
+ static NotificationType recording_status_to_notification_type(RecordingStatus recording_status) {
+ switch(recording_status) {
+ case RecordingStatus::NONE: return NotificationType::NONE;
+ case RecordingStatus::REPLAY: return NotificationType::REPLAY;
+ case RecordingStatus::RECORD: return NotificationType::RECORD;
+ case RecordingStatus::STREAM: return NotificationType::STREAM;
+ }
+ return NotificationType::NONE;
}
void Overlay::on_replay_saved(const char *replay_saved_filepath) {
replay_save_show_notification = false;
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);
+ } else if(config.replay_config.show_replay_saved_notifications) {
+ char duration[32];
+ if(replay_save_duration_min > 0)
+ snprintf(duration, sizeof(duration), " %d minute ", replay_save_duration_min);
+ else
+ snprintf(duration, sizeof(duration), " ");
+
+ char msg[512];
+ snprintf(msg, sizeof(msg), "Saved a%sreplay of %s", duration, capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
+ show_notification(msg, notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
}
}
- void Overlay::update_gsr_replay_save() {
+ void Overlay::process_gsr_output() {
if(replay_save_show_notification && replay_save_clock.get_elapsed_time_seconds() >= replay_saving_notification_timeout_seconds) {
replay_save_show_notification = false;
show_notification("Saving replay, this might take some time", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
@@ -1508,21 +1852,71 @@ namespace gsr {
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')
+ char *line = fgets(buffer, sizeof(buffer), gpu_screen_recorder_process_output_file);
+ if(!line || line[0] == '\0')
+ return;
+
+ const int line_len = strlen(line);
+ if(line[line_len - 1] == '\n')
+ line[line_len - 1] = '\0';
+
+ if(starts_with({line, (size_t)line_len}, "Error: ")) {
+ show_notification(line + 7, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), recording_status_to_notification_type(recording_status));
return;
+ }
- const int line_len = strlen(replay_saved_filepath);
- if(replay_saved_filepath[line_len - 1] == '\n')
- replay_saved_filepath[line_len - 1] = '\0';
+ const std::string video_filepath = filepath_get_filename(line);
+ if(starts_with(video_filepath, "Video_")) {
+ on_stop_recording(0, line);
+ return;
+ }
- on_replay_saved(replay_saved_filepath);
+ switch(recording_status) {
+ case RecordingStatus::NONE:
+ break;
+ case RecordingStatus::REPLAY:
+ on_replay_saved(line);
+ break;
+ case RecordingStatus::RECORD:
+ break;
+ case RecordingStatus::STREAM:
+ break;
+ }
} else if(gpu_screen_recorder_process_output_fd > 0) {
char buffer[1024];
read(gpu_screen_recorder_process_output_fd, buffer, sizeof(buffer));
}
}
+ void Overlay::on_gsr_process_error(int exit_code, NotificationType notification_type) {
+ fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
+ if(exit_code == 50) {
+ show_notification("Desktop portal capture failed.\nEither you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture\nor it's incorrectly setup on your system", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
+ } else if(exit_code == 60) {
+ show_notification("Stopped capture because the user canceled the desktop portal", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
+ } else {
+ const char *prefix = "";
+ switch(notification_type) {
+ case NotificationType::NONE:
+ case NotificationType::SCREENSHOT:
+ break;
+ case NotificationType::RECORD:
+ prefix = "Failed to start/save recording";
+ break;
+ case NotificationType::REPLAY:
+ prefix = "Replay stopped because of an error";
+ break;
+ case NotificationType::STREAM:
+ prefix = "Streaming stopped because of an error";
+ break;
+ }
+
+ char msg[256];
+ snprintf(msg, sizeof(msg), "%s. Verify if settings are correct", prefix);
+ show_notification(msg, notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type);
+ }
+ }
+
void Overlay::update_gsr_process_status() {
if(gpu_screen_recorder_process <= 0)
return;
@@ -1543,19 +1937,19 @@ namespace gsr {
case RecordingStatus::NONE:
break;
case RecordingStatus::REPLAY: {
+ replay_save_duration_min = 0;
update_ui_replay_stopped();
if(exit_code == 0) {
if(config.replay_config.show_replay_stopped_notifications)
show_notification("Replay stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
} else {
- fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
- show_notification("Replay stopped because of an error. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
+ on_gsr_process_error(exit_code, NotificationType::REPLAY);
}
break;
}
case RecordingStatus::RECORD: {
update_ui_recording_stopped();
- on_stop_recording(exit_code);
+ on_stop_recording(exit_code, record_filepath);
break;
}
case RecordingStatus::STREAM: {
@@ -1564,8 +1958,7 @@ namespace gsr {
if(config.streaming_config.show_streaming_stopped_notifications)
show_notification("Streaming has stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
} else {
- fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
- show_notification("Streaming stopped because of an error. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
+ on_gsr_process_error(exit_code, NotificationType::STREAM);
}
break;
}
@@ -1592,9 +1985,10 @@ namespace gsr {
if(exit_code == 0) {
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);
+ } else if(config.screenshot_config.show_screenshot_saved_notifications) {
+ char msg[512];
+ snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str()).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);
@@ -1604,28 +1998,25 @@ namespace gsr {
gpu_screen_recorder_screenshot_process = -1;
}
- static bool starts_with(std::string_view str, const char *substr) {
- size_t len = strlen(substr);
- return str.size() >= len && memcmp(str.data(), substr, len) == 0;
- }
-
- static bool are_all_audio_tracks_available_to_capture(const std::vector<std::string> &audio_tracks) {
+ static bool are_all_audio_tracks_available_to_capture(const std::vector<AudioTrack> &audio_tracks) {
const auto audio_devices = get_audio_devices();
- for(const std::string &audio_track : audio_tracks) {
- std::string_view audio_track_name(audio_track.c_str());
- const bool is_app_audio = starts_with(audio_track_name, "app:");
- if(is_app_audio)
- continue;
+ for(const AudioTrack &audio_track : audio_tracks) {
+ for(const std::string &audio_input : audio_track.audio_inputs) {
+ std::string_view audio_track_name(audio_input.c_str());
+ const bool is_app_audio = starts_with(audio_track_name, "app:");
+ if(is_app_audio)
+ continue;
- if(starts_with(audio_track_name, "device:"))
- audio_track_name.remove_prefix(7);
+ if(starts_with(audio_track_name, "device:"))
+ audio_track_name.remove_prefix(7);
- auto it = std::find_if(audio_devices.begin(), audio_devices.end(), [&](const auto &audio_device) {
- return audio_device.name == audio_track_name;
- });
- if(it == audio_devices.end()) {
- //fprintf(stderr, "Audio not ready\n");
- return false;
+ auto it = std::find_if(audio_devices.begin(), audio_devices.end(), [&](const auto &audio_device) {
+ return audio_device.name == audio_track_name;
+ });
+ if(it == audio_devices.end()) {
+ //fprintf(stderr, "Audio not ready\n");
+ return false;
+ }
}
}
return true;
@@ -1649,14 +2040,14 @@ namespace gsr {
Display *display = (Display*)context->connection;
const Window focused_window = get_focused_window(display, WindowCaptureType::FOCUSED);
- if(window && focused_window == window->get_system_handle())
+ if(window && focused_window == (Window)window->get_system_handle())
return;
const bool prev_focused_window_is_fullscreen = focused_window_is_fullscreen;
focused_window_is_fullscreen = focused_window != 0 && window_is_fullscreen(display, focused_window);
if(focused_window_is_fullscreen != prev_focused_window_is_fullscreen) {
if(recording_status == RecordingStatus::NONE && focused_window_is_fullscreen) {
- if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks))
+ if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks_list))
on_press_start_replay(false, false);
} else if(recording_status == RecordingStatus::REPLAY && !focused_window_is_fullscreen) {
on_press_start_replay(true, false);
@@ -1673,7 +2064,7 @@ namespace gsr {
power_supply_connected = power_supply_online_filepath.empty() || power_supply_is_connected(power_supply_online_filepath.c_str());
if(power_supply_connected != prev_power_supply_status) {
if(recording_status == RecordingStatus::NONE && power_supply_connected) {
- if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks))
+ if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks_list))
on_press_start_replay(false, false);
} else if(recording_status == RecordingStatus::REPLAY && !power_supply_connected) {
on_press_start_replay(false, false);
@@ -1685,22 +2076,24 @@ namespace gsr {
if(replay_startup_mode != ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP || recording_status != RecordingStatus::NONE || !try_replay_startup)
return;
- if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks))
+ if(are_all_audio_tracks_available_to_capture(config.replay_config.record_options.audio_tracks_list))
on_press_start_replay(true, false);
}
- void Overlay::on_stop_recording(int exit_code) {
+ void Overlay::on_stop_recording(int exit_code, const std::string &video_filepath) {
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(), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
+ save_video_in_current_game_directory(video_filepath.c_str(), NotificationType::RECORD);
+ } else if(config.record_config.show_video_saved_notifications) {
+ char msg[512];
+ snprintf(msg, sizeof(msg), "Saved a recording of %s", capture_target_get_notification_name(recording_capture_target.c_str()).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);
- show_notification("Failed to start/save recording. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
+ on_gsr_process_error(exit_code, NotificationType::RECORD);
}
+ update_ui_recording_stopped();
+ replay_recording = false;
}
void Overlay::update_ui_recording_paused() {
@@ -1729,6 +2122,7 @@ namespace gsr {
record_dropdown_button_ptr->set_activated(true);
record_dropdown_button_ptr->set_description("Recording");
record_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture);
+ record_dropdown_button_ptr->set_item_enabled("pause", recording_status == RecordingStatus::RECORD);
}
void Overlay::update_ui_recording_stopped() {
@@ -1742,7 +2136,9 @@ namespace gsr {
record_dropdown_button_ptr->set_item_label("pause", "Pause");
record_dropdown_button_ptr->set_item_icon("pause", &get_theme().pause_texture);
+ record_dropdown_button_ptr->set_item_enabled("pause", false);
paused = false;
+ replay_recording = false;
}
void Overlay::update_ui_streaming_started() {
@@ -1763,6 +2159,7 @@ namespace gsr {
stream_dropdown_button_ptr->set_activated(false);
stream_dropdown_button_ptr->set_description("Not streaming");
stream_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture);
+ update_ui_recording_stopped();
}
void Overlay::update_ui_replay_started() {
@@ -1773,6 +2170,9 @@ namespace gsr {
replay_dropdown_button_ptr->set_activated(true);
replay_dropdown_button_ptr->set_description("On");
replay_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture);
+ replay_dropdown_button_ptr->set_item_enabled("save", true);
+ replay_dropdown_button_ptr->set_item_enabled("save_1_min", true);
+ replay_dropdown_button_ptr->set_item_enabled("save_10_min", true);
}
void Overlay::update_ui_replay_stopped() {
@@ -1783,6 +2183,10 @@ namespace gsr {
replay_dropdown_button_ptr->set_activated(false);
replay_dropdown_button_ptr->set_description("Off");
replay_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture);
+ replay_dropdown_button_ptr->set_item_enabled("save", false);
+ replay_dropdown_button_ptr->set_item_enabled("save_1_min", false);
+ replay_dropdown_button_ptr->set_item_enabled("save_10_min", false);
+ update_ui_recording_stopped();
}
static std::string get_date_str() {
@@ -1804,29 +2208,43 @@ namespace gsr {
return container;
}
- static std::vector<std::string> create_audio_tracks_real_names(const std::vector<std::string> &audio_tracks, bool application_audio_invert, const GsrInfo &gsr_info) {
+ static std::vector<std::string> create_audio_tracks_cli_args(const std::vector<AudioTrack> &audio_tracks, const GsrInfo &gsr_info) {
std::vector<std::string> result;
- for(const std::string &audio_track : audio_tracks) {
- std::string audio_track_name = audio_track;
- const bool is_app_audio = starts_with(audio_track_name, "app:");
- if(is_app_audio && !gsr_info.system_info.supports_app_audio)
- continue;
+ result.reserve(audio_tracks.size());
- if(is_app_audio && application_audio_invert)
- audio_track_name.replace(0, 4, "app-inverse:");
+ for(const AudioTrack &audio_track : audio_tracks) {
+ std::string audio_track_merged;
+ int num_app_audio = 0;
- result.push_back(std::move(audio_track_name));
- }
- return result;
- }
+ for(const std::string &audio_input_name : audio_track.audio_inputs) {
+ std::string new_audio_input_name = audio_input_name;
+ const bool is_app_audio = starts_with(new_audio_input_name, "app:");
+ if(is_app_audio && !gsr_info.system_info.supports_app_audio)
+ continue;
- static std::string merge_audio_tracks(const std::vector<std::string> &audio_tracks) {
- std::string result;
- for(size_t i = 0; i < audio_tracks.size(); ++i) {
- if(i > 0)
- result += "|";
- result += audio_tracks[i];
+ if(is_app_audio && audio_track.application_audio_invert)
+ new_audio_input_name.replace(0, 4, "app-inverse:");
+
+ if(is_app_audio)
+ ++num_app_audio;
+
+ if(!audio_track_merged.empty())
+ audio_track_merged += "|";
+
+ audio_track_merged += new_audio_input_name;
+ }
+
+ if(num_app_audio == 0 && audio_track.application_audio_invert) {
+ if(!audio_track_merged.empty())
+ audio_track_merged += "|";
+
+ audio_track_merged += "app-inverse:";
+ }
+
+ if(!audio_track_merged.empty())
+ result.push_back(std::move(audio_track_merged));
}
+
return result;
}
@@ -1841,7 +2259,7 @@ namespace gsr {
args.push_back(region_str);
}
- static void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, const std::string &audio_devices_merged, char *region_str, int region_str_size, const RegionSelector &region_selector) {
+ static void add_common_gpu_screen_recorder_args(std::vector<const char*> &args, const RecordOptions &record_options, const std::vector<std::string> &audio_tracks, const std::string &video_bitrate, const char *region, char *region_str, int region_str_size, const RegionSelector &region_selector) {
if(record_options.video_quality == "custom") {
args.push_back("-bm");
args.push_back("cbr");
@@ -1857,16 +2275,9 @@ namespace gsr {
args.push_back(region);
}
- if(record_options.merge_audio_tracks) {
- if(!audio_devices_merged.empty()) {
- args.push_back("-a");
- args.push_back(audio_devices_merged.c_str());
- }
- } else {
- for(const std::string &audio_track : audio_tracks) {
- args.push_back("-a");
- args.push_back(audio_track.c_str());
- }
+ for(const std::string &audio_track : audio_tracks) {
+ args.push_back("-a");
+ args.push_back(audio_track.c_str());
}
if(record_options.restore_portal_session) {
@@ -1878,15 +2289,17 @@ namespace gsr {
add_region_command(args, region_str, region_str_size, region_selector);
}
- static bool validate_capture_target(const GsrInfo &gsr_info, const std::string &capture_target) {
- const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
- // TODO: Also check x11 window when enabled (check if capture_target is a decminal/hex number)
- if(capture_target == "region") {
- return capture_options.region;
+ static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
+ if(capture_target == "window") {
+ return capture_options.window;
} else if(capture_target == "focused") {
return capture_options.focused;
+ } else if(capture_target == "region") {
+ return capture_options.region;
} else if(capture_target == "portal") {
return capture_options.portal;
+ } else if(capture_target == "focused_monitor") {
+ return !capture_options.monitors.empty();
} else {
for(const GsrMonitor &monitor : capture_options.monitors) {
if(capture_target == monitor.name)
@@ -1896,17 +2309,139 @@ namespace gsr {
}
}
+ static std::string get_valid_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
+ std::string capture_target_clean = capture_target;
+ if(starts_with(capture_target_clean, "HDMI-A"))
+ capture_target_clean.replace(0, 6, "HDMI");
+
+ for(const GsrMonitor &monitor : capture_options.monitors) {
+ std::string monitor_name_clean = monitor.name;
+ if(starts_with(monitor_name_clean, "HDMI-A"))
+ monitor_name_clean.replace(0, 6, "HDMI");
+
+ if(capture_target_clean == monitor_name_clean)
+ return monitor.name;
+ }
+
+ return "";
+ }
+
+ std::string Overlay::get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
+ if(capture_target == "window") {
+ return std::to_string(window_selector.get_selection());
+ } else if(capture_target == "focused_monitor") {
+ std::optional<CursorInfo> cursor_info;
+ if(cursor_tracker) {
+ cursor_tracker->update();
+ cursor_info = cursor_tracker->get_latest_cursor_info();
+ }
+
+ std::string focused_monitor_name;
+ if(cursor_info) {
+ focused_monitor_name = std::move(cursor_info->monitor_name);
+ } else {
+ mgl_context *context = mgl_get_context();
+ Display *display = (Display*)context->connection;
+ focused_monitor_name = get_focused_monitor_by_cursor(cursor_tracker.get(), gsr_info, get_monitors(display));
+ }
+
+ focused_monitor_name = get_valid_capture_target(focused_monitor_name, capture_options);
+ if(!focused_monitor_name.empty())
+ return focused_monitor_name;
+ else if(!capture_options.monitors.empty())
+ return capture_options.monitors.front().name;
+ else
+ return "";
+ } else {
+ return capture_target;
+ }
+ }
+
+ void Overlay::prepare_gsr_output_for_reading() {
+ if(gpu_screen_recorder_process_output_fd <= 0)
+ return;
+
+ 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");
+ if(gpu_screen_recorder_process_output_file)
+ gpu_screen_recorder_process_output_fd = -1;
+ }
+
void Overlay::on_press_save_replay() {
if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0)
return;
+ replay_save_duration_min = 0;
replay_save_show_notification = true;
replay_save_clock.restart();
kill(gpu_screen_recorder_process, SIGUSR1);
}
- bool Overlay::on_press_start_replay(bool disable_notification, bool finished_region_selection) {
- if(region_selector.is_started())
+ void Overlay::on_press_save_replay_1_min_replay() {
+ if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0)
+ return;
+
+ replay_save_duration_min = 1;
+ replay_save_show_notification = true;
+ replay_save_clock.restart();
+ kill(gpu_screen_recorder_process, SIGRTMIN+3);
+ }
+
+ void Overlay::on_press_save_replay_10_min_replay() {
+ if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0)
+ return;
+
+ replay_save_duration_min = 10;
+ replay_save_show_notification = true;
+ replay_save_clock.restart();
+ kill(gpu_screen_recorder_process, SIGRTMIN+5);
+ }
+
+ static const char* switch_video_codec_to_usable_hardware_encoder(const GsrInfo &gsr_info) {
+ if(gsr_info.supported_video_codecs.h264)
+ return "h264";
+ else if(gsr_info.supported_video_codecs.hevc)
+ return "hevc";
+ else if(gsr_info.supported_video_codecs.av1)
+ return "av1";
+ else if(gsr_info.supported_video_codecs.vp8)
+ return "vp8";
+ else if(gsr_info.supported_video_codecs.vp9)
+ return "vp9";
+ return nullptr;
+ }
+
+ static const char* change_container_if_codec_not_supported(const char *video_codec, const char *container) {
+ if(strcmp(video_codec, "vp8") == 0 || strcmp(video_codec, "vp9") == 0) {
+ if(strcmp(container, "webm") != 0 && strcmp(container, "matroska") != 0) {
+ fprintf(stderr, "Warning: container '%s' is not compatible with video codec '%s', using webm container instead\n", container, video_codec);
+ return "webm";
+ }
+ } else if(strcmp(container, "webm") == 0) {
+ fprintf(stderr, "Warning: container webm is not compatible with video codec '%s', using mp4 container instead\n", video_codec);
+ return "mp4";
+ }
+ return container;
+ }
+
+ static void choose_video_codec_and_container_with_fallback(const GsrInfo &gsr_info, const char **video_codec, const char **container, const char **encoder) {
+ *encoder = "gpu";
+ if(strcmp(*video_codec, "h264_software") == 0) {
+ *video_codec = "h264";
+ *encoder = "cpu";
+ } else if(strcmp(*video_codec, "auto") == 0) {
+ *video_codec = switch_video_codec_to_usable_hardware_encoder(gsr_info);
+ if(!*video_codec) {
+ *video_codec = "h264";
+ *encoder = "cpu";
+ }
+ }
+ *container = change_container_if_codec_not_supported(*video_codec, *container);
+ }
+
+ bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) {
+ if(region_selector.is_started() || window_selector.is_started())
return false;
switch(recording_status) {
@@ -1914,10 +2449,10 @@ namespace gsr {
case RecordingStatus::REPLAY:
break;
case RecordingStatus::RECORD:
- show_notification("Unable to start replay when recording.\nStop recording before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
+ show_notification("Unable to start replay when recording.\nStop recording before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
return false;
case RecordingStatus::STREAM:
- show_notification("Unable to start replay when streaming.\nStop streaming before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
+ show_notification("Unable to start replay when streaming.\nStop streaming before starting replay.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
return false;
}
@@ -1925,9 +2460,6 @@ namespace gsr {
replay_save_show_notification = false;
try_replay_startup = false;
- // window->close();
- // usleep(1000 * 50); // 50 milliseconds
-
close_gpu_screen_recorder_output();
if(gpu_screen_recorder_process > 0) {
@@ -1940,6 +2472,7 @@ namespace gsr {
gpu_screen_recorder_process = -1;
recording_status = RecordingStatus::NONE;
+ replay_save_duration_min = 0;
update_ui_replay_stopped();
// TODO: Show this with a slight delay to make sure it doesn't show up in the video
@@ -1949,14 +2482,16 @@ namespace gsr {
return true;
}
- if(!validate_capture_target(gsr_info, config.replay_config.record_options.record_area_option)) {
+ const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
+ recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
+ if(!validate_capture_target(recording_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", config.replay_config.record_options.record_area_option.c_str());
- show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::REPLAY);
+ snprintf(err_msg, sizeof(err_msg), "Failed to start replay, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
+ show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
return false;
}
- if(config.replay_config.record_options.record_area_option == "region" && !finished_region_selection) {
+ if(config.replay_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true;
on_region_selected = [disable_notification, this]() {
on_press_start_replay(disable_notification, true);
@@ -1964,20 +2499,25 @@ namespace gsr {
return false;
}
+ if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) {
+ start_window_capture = true;
+ on_window_selected = [disable_notification, this]() {
+ on_press_start_replay(disable_notification, true);
+ };
+ return false;
+ }
+
// TODO: Validate input, fallback to valid values
const std::string fps = std::to_string(config.replay_config.record_options.fps);
const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate);
const std::string output_directory = config.replay_config.save_directory;
- const std::vector<std::string> audio_tracks = create_audio_tracks_real_names(config.replay_config.record_options.audio_tracks, config.replay_config.record_options.application_audio_invert, gsr_info);
- const std::string audio_tracks_merged = merge_audio_tracks(audio_tracks);
+ const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.replay_config.record_options.audio_tracks_list, gsr_info);
const std::string framerate_mode = config.replay_config.record_options.framerate_mode == "auto" ? "vfr" : config.replay_config.record_options.framerate_mode;
const std::string replay_time = std::to_string(config.replay_config.replay_time);
+ const char *container = config.replay_config.container.c_str();
const char *video_codec = config.replay_config.record_options.video_codec.c_str();
const char *encoder = "gpu";
- if(strcmp(video_codec, "h264_software") == 0) {
- video_codec = "h264";
- encoder = "cpu";
- }
+ choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
char size[64];
size[0] = '\0';
@@ -1988,8 +2528,8 @@ 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", config.replay_config.record_options.record_area_option.c_str(),
- "-c", config.replay_config.container.c_str(),
+ "gpu-screen-recorder", "-w", recording_capture_target.c_str(),
+ "-c", container,
"-ac", config.replay_config.record_options.audio_codec.c_str(),
"-cursor", config.replay_config.record_options.record_cursor ? "yes" : "no",
"-cr", config.replay_config.record_options.color_range.c_str(),
@@ -2007,24 +2547,31 @@ namespace gsr {
args.push_back("yes");
}
+ if(gsr_info.system_info.gsr_version >= GsrVersion{5, 5, 0}) {
+ args.push_back("-replay-storage");
+ args.push_back(config.replay_config.replay_storage.c_str());
+ }
+
char region_str[128];
- add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, audio_tracks_merged, region_str, sizeof(region_str), region_selector);
+ add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector);
+
+ if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
+ args.push_back("-ro");
+ args.push_back(config.record_config.save_directory.c_str());
+ }
args.push_back(nullptr);
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
+ show_notification("Failed to launch gpu-screen-recorder to start replay", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
+ return false;
} else {
recording_status = RecordingStatus::REPLAY;
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");
- if(gpu_screen_recorder_process_output_file)
- gpu_screen_recorder_process_output_fd = -1;
+ prepare_gsr_output_for_reading();
// 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.
@@ -2035,32 +2582,62 @@ 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];
+ snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
+ show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
+ }
return true;
}
- void Overlay::on_press_start_record(bool finished_region_selection) {
- if(region_selector.is_started())
+ void Overlay::on_press_start_record(bool finished_selection) {
+ if(region_selector.is_started() || window_selector.is_started())
return;
switch(recording_status) {
case RecordingStatus::NONE:
case RecordingStatus::RECORD:
break;
- case RecordingStatus::REPLAY:
- show_notification("Unable to start recording when replay is turned on.\nTurn off replay before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
+ case RecordingStatus::REPLAY: {
+ if(gpu_screen_recorder_process <= 0)
+ return;
+
+ if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
+ if(!replay_recording) {
+ if(config.record_config.show_recording_started_notifications)
+ show_notification("Started recording in the replay session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
+ update_ui_recording_started();
+ }
+ replay_recording = true;
+ kill(gpu_screen_recorder_process, SIGRTMIN);
+ } else {
+ show_notification("Unable to start recording when replay is turned on.\nTurn off replay before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), get_color_theme().tint_color, NotificationType::REPLAY);
+ }
return;
- case RecordingStatus::STREAM:
- show_notification("Unable to start recording when streaming.\nStop streaming before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
+ }
+ case RecordingStatus::STREAM: {
+ if(gpu_screen_recorder_process <= 0)
+ return;
+
+ if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
+ if(!replay_recording) {
+ if(config.record_config.show_recording_started_notifications)
+ show_notification("Started recording in the streaming session", notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
+ update_ui_recording_started();
+ }
+ replay_recording = true;
+ kill(gpu_screen_recorder_process, SIGRTMIN);
+ } else {
+ show_notification("Unable to start recording when streaming.\nStop streaming before starting recording.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), get_color_theme().tint_color, NotificationType::STREAM);
+ }
return;
+ }
}
paused = false;
- // window->close();
- // usleep(1000 * 50); // 50 milliseconds
+ close_gpu_screen_recorder_output();
if(gpu_screen_recorder_process > 0) {
kill(gpu_screen_recorder_process, SIGINT);
@@ -2072,7 +2649,7 @@ namespace gsr {
int exit_code = -1;
if(WIFEXITED(status))
exit_code = WEXITSTATUS(status);
- on_stop_recording(exit_code);
+ on_stop_recording(exit_code, record_filepath);
}
gpu_screen_recorder_process = -1;
@@ -2082,14 +2659,16 @@ namespace gsr {
return;
}
- if(!validate_capture_target(gsr_info, config.record_config.record_options.record_area_option)) {
+ const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
+ 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", config.record_config.record_options.record_area_option.c_str());
- show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::RECORD);
+ snprintf(err_msg, sizeof(err_msg), "Failed to start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
+ show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
return;
}
- if(config.record_config.record_options.record_area_option == "region" && !finished_region_selection) {
+ if(config.record_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true;
on_region_selected = [this]() {
on_press_start_record(true);
@@ -2097,21 +2676,26 @@ namespace gsr {
return;
}
+ if(config.record_config.record_options.record_area_option == "window" && !finished_selection) {
+ start_window_capture = true;
+ on_window_selected = [this]() {
+ on_press_start_record(true);
+ };
+ 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);
const std::string output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str());
- const std::vector<std::string> audio_tracks = create_audio_tracks_real_names(config.record_config.record_options.audio_tracks, config.record_config.record_options.application_audio_invert, gsr_info);
- const std::string audio_tracks_merged = merge_audio_tracks(audio_tracks);
+ const std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.record_config.record_options.audio_tracks_list, gsr_info);
const std::string framerate_mode = config.record_config.record_options.framerate_mode == "auto" ? "vfr" : config.record_config.record_options.framerate_mode;
+ const char *container = config.record_config.container.c_str();
const char *video_codec = config.record_config.record_options.video_codec.c_str();
const char *encoder = "gpu";
- if(strcmp(video_codec, "h264_software") == 0) {
- video_codec = "h264";
- encoder = "cpu";
- }
+ choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
char size[64];
size[0] = '\0';
@@ -2122,8 +2706,8 @@ 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", config.record_config.record_options.record_area_option.c_str(),
- "-c", config.record_config.container.c_str(),
+ "gpu-screen-recorder", "-w", recording_capture_target.c_str(),
+ "-c", container,
"-ac", config.record_config.record_options.audio_codec.c_str(),
"-cursor", config.record_config.record_options.record_cursor ? "yes" : "no",
"-cr", config.record_config.record_options.color_range.c_str(),
@@ -2136,27 +2720,33 @@ namespace gsr {
};
char region_str[128];
- add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, audio_tracks_merged, region_str, sizeof(region_str), region_selector);
+ add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector);
args.push_back(nullptr);
record_filepath = output_file;
- gpu_screen_recorder_process = exec_program(args.data(), nullptr);
+ 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
+ show_notification("Failed to launch gpu-screen-recorder to start recording", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
+ return;
} else {
recording_status = RecordingStatus::RECORD;
update_ui_recording_started();
}
+ prepare_gsr_output_for_reading();
+
// 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:
// 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];
+ snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str()).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) {
@@ -2167,6 +2757,9 @@ namespace gsr {
} else if(config.streaming_config.streaming_service == "youtube") {
url += "rtmp://a.rtmp.youtube.com/live2/";
url += config.streaming_config.youtube.stream_key;
+ } else if(config.streaming_config.streaming_service == "rumble") {
+ url += "rtmp://rtmp.rumble.com/live/";
+ url += config.streaming_config.rumble.stream_key;
} else if(config.streaming_config.streaming_service == "custom") {
url = config.streaming_config.custom.url;
if(url.size() >= 7 && strncmp(url.c_str(), "rtmp://", 7) == 0)
@@ -2191,8 +2784,8 @@ namespace gsr {
return url;
}
- void Overlay::on_press_start_stream(bool finished_region_selection) {
- if(region_selector.is_started())
+ void Overlay::on_press_start_stream(bool finished_selection) {
+ if(region_selector.is_started() || window_selector.is_started())
return;
switch(recording_status) {
@@ -2200,17 +2793,16 @@ namespace gsr {
case RecordingStatus::STREAM:
break;
case RecordingStatus::REPLAY:
- show_notification("Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
+ show_notification("Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY);
return;
case RecordingStatus::RECORD:
- show_notification("Unable to start streaming when recording.\nStop recording before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
+ show_notification("Unable to start streaming when recording.\nStop recording before starting streaming.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
return;
}
paused = false;
- // window->close();
- // usleep(1000 * 50); // 50 milliseconds
+ close_gpu_screen_recorder_output();
if(gpu_screen_recorder_process > 0) {
kill(gpu_screen_recorder_process, SIGINT);
@@ -2230,14 +2822,16 @@ namespace gsr {
return;
}
- if(!validate_capture_target(gsr_info, config.streaming_config.record_options.record_area_option)) {
+ const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
+ recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options);
+ if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) {
char err_msg[256];
- snprintf(err_msg, sizeof(err_msg), "Failed to start streaming, capture target \"%s\" is invalid. Please change capture target in settings", config.streaming_config.record_options.record_area_option.c_str());
- show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::STREAM);
+ snprintf(err_msg, sizeof(err_msg), "Failed to start streaming, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
+ show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
return;
}
- if(config.streaming_config.record_options.record_area_option == "region" && !finished_region_selection) {
+ if(config.streaming_config.record_options.record_area_option == "region" && !finished_selection) {
start_region_capture = true;
on_region_selected = [this]() {
on_press_start_stream(true);
@@ -2245,22 +2839,29 @@ namespace gsr {
return;
}
+ if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) {
+ start_window_capture = true;
+ on_window_selected = [this]() {
+ on_press_start_stream(true);
+ };
+ return;
+ }
+
// TODO: Validate input, fallback to valid values
const std::string fps = std::to_string(config.streaming_config.record_options.fps);
const std::string video_bitrate = std::to_string(config.streaming_config.record_options.video_bitrate);
- const std::vector<std::string> audio_tracks = create_audio_tracks_real_names(config.streaming_config.record_options.audio_tracks, config.streaming_config.record_options.application_audio_invert, gsr_info);
- const std::string audio_tracks_merged = merge_audio_tracks(audio_tracks);
+ std::vector<std::string> audio_tracks = create_audio_tracks_cli_args(config.streaming_config.record_options.audio_tracks_list, gsr_info);
+ // This isn't possible unless the user modified the config file manually,
+ // But we check it anyways as streaming on some sites can fail if there is more than one audio track
+ if(audio_tracks.size() > 1)
+ audio_tracks.resize(1);
const std::string framerate_mode = config.streaming_config.record_options.framerate_mode == "auto" ? "vfr" : config.streaming_config.record_options.framerate_mode;
+ const char *container = "flv";
+ if(config.streaming_config.streaming_service == "custom")
+ container = config.streaming_config.custom.container.c_str();
const char *video_codec = config.streaming_config.record_options.video_codec.c_str();
const char *encoder = "gpu";
- if(strcmp(video_codec, "h264_software") == 0) {
- video_codec = "h264";
- encoder = "cpu";
- }
-
- std::string container = "flv";
- if(config.streaming_config.streaming_service == "custom")
- container = config.streaming_config.custom.container;
+ choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
const std::string url = streaming_get_url(config);
@@ -2273,8 +2874,8 @@ namespace gsr {
snprintf(size, sizeof(size), "%dx%d", (int)config.streaming_config.record_options.video_width, (int)config.streaming_config.record_options.video_height);
std::vector<const char*> args = {
- "gpu-screen-recorder", "-w", config.streaming_config.record_options.record_area_option.c_str(),
- "-c", container.c_str(),
+ "gpu-screen-recorder", "-w", recording_capture_target.c_str(),
+ "-c", container,
"-ac", config.streaming_config.record_options.audio_codec.c_str(),
"-cursor", config.streaming_config.record_options.record_cursor ? "yes" : "no",
"-cr", config.streaming_config.record_options.color_range.c_str(),
@@ -2285,20 +2886,27 @@ namespace gsr {
"-o", url.c_str()
};
- config.streaming_config.record_options.merge_audio_tracks = true;
char region_str[128];
- add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, audio_tracks_merged, region_str, sizeof(region_str), region_selector);
+ add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector);
+
+ if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
+ args.push_back("-ro");
+ args.push_back(config.record_config.save_directory.c_str());
+ }
args.push_back(nullptr);
- gpu_screen_recorder_process = exec_program(args.data(), nullptr);
+ 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
+ show_notification("Failed to launch gpu-screen-recorder to start streaming", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM);
+ return;
} else {
recording_status = RecordingStatus::STREAM;
update_ui_streaming_started();
}
+ prepare_gsr_output_for_reading();
+
// 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:
@@ -2308,12 +2916,15 @@ 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];
+ snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str()).c_str());
+ show_notification(msg, notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
+ }
}
- void Overlay::on_press_take_screenshot(bool finished_region_selection, bool force_region_capture) {
- if(region_selector.is_started())
+ void Overlay::on_press_take_screenshot(bool finished_selection, bool force_region_capture) {
+ if(region_selector.is_started() || window_selector.is_started())
return;
if(gpu_screen_recorder_screenshot_process > 0) {
@@ -2323,14 +2934,16 @@ 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();
- if(!validate_capture_target(gsr_info, record_area_option)) {
+ const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
+ 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", record_area_option);
- show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0, 0), mgl::Color(255, 0, 0, 0), NotificationType::SCREENSHOT);
+ snprintf(err_msg, sizeof(err_msg), "Failed to take a screenshot, capture target \"%s\" is invalid.\nPlease change capture target in settings", screenshot_capture_target.c_str());
+ show_notification(err_msg, notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT);
return;
}
- if(region_capture && !finished_region_selection) {
+ if(region_capture && !finished_selection) {
start_region_capture = true;
on_region_selected = [this, force_region_capture]() {
usleep(200 * 1000); // Hack: wait 0.2 seconds before taking a screenshot to allow user to move cursor away. TODO: Remove this
@@ -2339,11 +2952,19 @@ namespace gsr {
return;
}
+ if(config.screenshot_config.record_area_option == "window" && !finished_selection) {
+ start_window_capture = true;
+ on_window_selected = [this, force_region_capture]() {
+ on_press_take_screenshot(true, force_region_capture);
+ };
+ return;
+ }
+
// TODO: Validate input, fallback to valid values
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", record_area_option,
+ "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(),
@@ -2372,7 +2993,7 @@ namespace gsr {
screenshot_filepath = output_file;
gpu_screen_recorder_screenshot_process = exec_program(args.data(), nullptr);
if(gpu_screen_recorder_screenshot_process == -1) {
- // TODO: Show notification failed to start
+ show_notification("Failed to launch gpu-screen-recorder to take a screenshot", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT);
}
}
@@ -2421,7 +3042,7 @@ namespace gsr {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
- XRaiseWindow(display, window->get_system_handle());
+ XRaiseWindow(display, (Window)window->get_system_handle());
XFlush(display);
}
}