Compare commits

...

48 Commits

Author SHA1 Message Date
dec05eba
44123d35a5 1.10.7 2026-02-01 03:12:23 +01:00
dec05eba
a31bfbe288 Properly use system language when language is set to system language, add missing translations 2026-02-01 03:08:45 +01:00
Lalucira
f3d6d8bc53 Added Spanish translation file 2026-02-01 02:39:05 +01:00
dec05eba
74d6a05e2f 1.10.6 2026-01-28 01:41:14 +01:00
dec05eba
89995b805e Fix kwin window title workaround not working, rename com.dec05eba.gpu_screen_recorder.gsr_kwin_helper to com.dec05eba.gpu_screen_recorder, otherwise name reply primary owner error 2026-01-28 01:40:39 +01:00
dec05eba
f921be46c0 Fix camera settings not saving correctly in the ui 2026-01-28 00:11:28 +01:00
dec05eba
16ca12f29b Fix snprintf static string error 2026-01-27 20:35:49 +01:00
dec05eba
ee873e2000 Fix correct path for flatpak hyprland workaround 2026-01-27 20:14:21 +01:00
dec05eba
bed241eaa0 Capitalize ukranian 2026-01-27 19:49:06 +01:00
dec05eba
d007a12471 m 2026-01-27 19:44:58 +01:00
dec05eba
9b59b57352 Add menu to select language 2026-01-27 19:39:19 +01:00
dec05eba
29d2e66e28 1.10.5 2026-01-27 18:58:34 +01:00
dec05eba
a46027bfdc Dont give warning for en language 2026-01-27 18:56:34 +01:00
Andrew
44bb989cea Add translations for error messages regarding multiple instances of GPU Screen Recorder UI + fix low-power tip 2026-01-27 18:54:05 +01:00
dec05eba
1dbe34c891 gsr-hyprland-helper: workaround flatpak bug with environment variables 2026-01-27 18:49:18 +01:00
dec05eba
3b2a09f8e1 Move start/stop recording window and region to separate lines 2026-01-27 18:18:25 +01:00
Andrew
03b4407d11 Add Russian and Ukrainian translation and create translation template.
- Introduced a new translation template file for GPU Screen Recorder UI.
- Improved some translation methods
2026-01-27 18:10:01 +01:00
dec05eba
ca0e001376 Init translation with system language 2026-01-26 14:41:01 +01:00
dec05eba
9a8aac1ba0 Make create_frontpage_ui_components update all ui components 2026-01-26 14:35:25 +01:00
Andrew
03cacfdbf5 Implemented a basic translation system 2026-01-26 14:28:12 +01:00
dec05eba
3a57167d54 Remove (x11 applications only) from screenshot for kwin and hyprland 2026-01-26 13:41:29 +01:00
dec05eba
b48c971a8b Wording 2026-01-24 23:33:39 +01:00
dec05eba
12f27ac6a6 Better cursor position on hyprland and wlroots 2026-01-24 22:53:50 +01:00
dec05eba
9ed4bc3426 Better cursor position handling on wayland 2026-01-24 22:07:51 +01:00
dec05eba
c6339ac9c2 Rename dbus from com.dec05eba.gsr_kwin_helper to com.dec05eba.gpu_screen_recorder.gsr_kwin_helper for flatpak access 2026-01-24 18:14:11 +01:00
dec05eba
0341930394 Update flatpak version reference 2026-01-24 17:58:47 +01:00
dec05eba
3d673247a7 Remove (x11 applications only) for window title text on kde plasma wayland and hyprland 2026-01-24 17:58:08 +01:00
dec05eba
ed671e9d7c 1.10.4 2026-01-24 17:47:58 +01:00
dec05eba
6a72717fe5 Wayland: fix game minimizing sometimes 2026-01-24 17:38:48 +01:00
dec05eba
6ea867b9d2 Revert "Test workaround flatpak issue related to broken flatpak-spawn --host environment missing wayland display"
This reverts commit ec98533f1b.
2026-01-24 16:41:06 +01:00
dec05eba
756b993078 Revert "Attempt workaround flatpak issue"
This reverts commit 007e2546a9.
2026-01-24 16:41:00 +01:00
dec05eba
007e2546a9 Attempt workaround flatpak issue 2026-01-24 15:50:04 +01:00
dec05eba
ec98533f1b Test workaround flatpak issue related to broken flatpak-spawn --host environment missing wayland display 2026-01-24 15:48:11 +01:00
dec05eba
ebc460ecc8 Revert "Test dont set environment variables"
This reverts commit 540e2df322.
2026-01-24 15:41:22 +01:00
dec05eba
540e2df322 Test dont set environment variables 2026-01-24 15:20:06 +01:00
dec05eba
d0c581684b 1.10.3 2026-01-24 12:43:10 +01:00
dec05eba
d4dbb27213 Fix window name on x11 2026-01-24 01:09:11 +01:00
dec05eba
bfc7df5c56 Mention that libdbus is a new dependency 2026-01-24 01:01:26 +01:00
dec05eba
9c5688f61b Simplify gsr-hyprland-helper, some cleanups 2026-01-24 00:55:47 +01:00
Andrew
9ccb4dd541 Removed flatpak KWin title blocker in the overlay 2026-01-23 00:03:02 +01:00
Andrew
1e3e76fcee Hyprland and KDE workarounds should work with flatpak now 2026-01-22 10:41:02 +01:00
Andrew
9c9df47d62 Fix to ignore GSR Overlay in KWin workaround 2026-01-22 10:40:58 +01:00
Andrew
00ceaa989d Added KWin workaround to get current window title 2026-01-22 10:40:51 +01:00
Andrew
23b1526092 Added Hyprland workaround to get current window title 2026-01-22 10:40:40 +01:00
dec05eba
9339d6760e Force dont restore session portal when using window capture (portal) with hotkey on wayland 2026-01-21 01:51:25 +01:00
dec05eba
8bf6e533c5 1.10.2 2026-01-20 18:45:56 +01:00
dec05eba
902fc7f6a9 Force h264 for rumble/kick 2026-01-20 18:45:32 +01:00
dec05eba
794064a8b8 Fix incorrect region captured on wayland when using monitor scaling and without letting x11 scale monitors 2026-01-20 18:31:29 +01:00
35 changed files with 3488 additions and 719 deletions

3
.gitignore vendored
View File

@@ -6,3 +6,6 @@ compile_commands.json
**/xdg-output-unstable-v1-protocol.c
depends/.wraplock
.cache
build/

View File

@@ -27,6 +27,7 @@ These are the dependencies needed to build GPU Screen Recorder UI:
* linux-api-headers
* libpulse (libpulse-simple)
* libdrm
* libdbus
* wayland (wayland-client, wayland-egl, wayland-scanner)
* setcap (libcap)
@@ -36,6 +37,11 @@ There are also additional dependencies needed at runtime:
* [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/) (version 5.0.0 or later)
* [GPU Screen Recorder Notification](https://git.dec05eba.com/gpu-screen-recorder-notification/)
## Translation guide
GPU Screen Recorder UI uses its own translation system for it. See the `translations/template.txt` file for the translation template.
You can even translate the program without building it from source code by just creating a translation file in `/usr/share/gsr-ui/translations/` folder (if installed as a system package).
## Program behavior notes
By default this program has to grab all keyboards and creates a virtual keyboard (`gsr-ui virtual keyboard`) to make global hotkeys work on all Wayland compositors.\
This might cause issues for you if you use keyboard remapping software. To workaround this you can go into settings and select "Yes, but only grab virtual devices" or "Yes, but don't grab devices".\

6
TODO
View File

@@ -256,4 +256,8 @@ The flatpak version can for some get stuck at shutdown when instant replay is ru
Redesign the UI to allow capturing multiple video sources. Move webcam to capture sources as well then. Maybe design the UI to work more like obs studio then, where you start recording and then add sources at capture time, with a preview.
Add option to choose video container (either flv or mpegts) for youtube livestreaming.
Add option to choose video container (either flv or mpegts) for youtube livestreaming.
Get wayland cursor position for region selector, otherwise the start position before the cursor moves is off.
Add option to set preset on nvidia. Use -ffmpeg-video-opts for that.

View File

@@ -85,6 +85,7 @@ namespace gsr {
std::string joystick_hotkeys_enable_option = "disable_hotkeys";
std::string tint_color;
std::string notification_speed = "normal";
std::string language;
ConfigHotkey show_hide_hotkey;
};

View File

@@ -1,44 +1,23 @@
#pragma once
#include "CursorTracker.hpp"
#include <stdint.h>
#include <vector>
struct wl_display;
struct wl_registry;
struct wl_output;
struct zxdg_output_manager_v1;
struct zxdg_output_v1;
namespace gsr {
struct WaylandOutput {
uint32_t wl_name;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
mgl::vec2i pos;
mgl::vec2i size;
int32_t transform;
std::string name;
};
class CursorTrackerWayland : public CursorTracker {
public:
CursorTrackerWayland(const char *card_path);
CursorTrackerWayland(const char *card_path, struct wl_display *wayland_dpy);
CursorTrackerWayland(const CursorTrackerWayland&) = delete;
CursorTrackerWayland& operator=(const CursorTrackerWayland&) = delete;
~CursorTrackerWayland();
void update() override;
std::optional<CursorInfo> get_latest_cursor_info() override;
std::vector<WaylandOutput> monitors;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
private:
void clear_monitors();
void set_monitor_outputs_from_xdg_output(struct wl_display *dpy);
private:
int drm_fd = -1;
mgl::vec2i latest_cursor_position; // Position of the cursor within the monitor
int latest_crtc_id = -1;
struct wl_display *wayland_dpy = nullptr;
};
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace gsr {
struct ActiveHyprlandWindow {
std::string window_id = "";
std::string title = "Game";
};
void start_hyprland_listener_thread();
std::string get_current_hyprland_window_title();
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace gsr {
struct ActiveKwinWindow {
std::string title = "Game";
};
void start_kwin_helper_thread();
std::string get_current_kwin_window_title();
}

View File

@@ -24,6 +24,8 @@
#include <array>
struct wl_display;
namespace gsr {
class DropdownButton;
class GlobalHotkeys;
@@ -115,7 +117,7 @@ namespace gsr {
void recreate_global_hotkeys(const char *hotkey_option);
void update_led_indicator_after_settings_change();
void create_frontpage_ui_components();
void recreate_frontpage_ui_components();
void xi_setup();
void handle_xi_events();
void process_key_bindings(mgl::Event &event);
@@ -162,6 +164,9 @@ namespace gsr {
void on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type);
bool update_compositor_texture(const Monitor &monitor);
void add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size);
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 std::string &region_area_option, RecordForceType force_type = RecordForceType::NONE);
std::string get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options);
void force_window_on_top();
@@ -250,6 +255,8 @@ namespace gsr {
Display *x11_dpy = nullptr;
XEvent x11_mapping_xev;
struct wl_display *wayland_dpy = nullptr;
mgl::Clock replay_save_clock;
bool replay_save_show_notification = false;
ReplayStartupMode replay_startup_mode = ReplayStartupMode::TURN_ON_AT_SYSTEM_STARTUP;
@@ -279,9 +286,12 @@ namespace gsr {
mgl::Clock cursor_tracker_update_clock;
bool hide_ui = false;
bool reload_ui = false;
double notification_duration_multiplier = 1.0;
ClipboardFile clipboard_file;
std::unique_ptr<LedIndicator> led_indicator = nullptr;
bool supports_window_title = false;
};
}

View File

@@ -7,6 +7,8 @@
#include <X11/Xlib.h>
struct wl_display;
namespace gsr {
struct Region {
mgl::vec2i pos;
@@ -28,7 +30,7 @@ namespace gsr {
bool poll_events();
bool take_selection();
bool take_canceled();
Region get_selection() const;
Region get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const;
private:
void on_button_press(const void *de);
void on_button_release(const void *de);

43
include/Translation.hpp Normal file
View File

@@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <unordered_map>
namespace gsr {
class Translation {
public:
static Translation& instance();
void init(const char* translations_directory, const char* initial_language = nullptr);
bool load_language(const char* lang);
bool is_language_supported(const char* lang);
bool plural_numbers_are_complex();
const char* translate(const char* key);
std::string get_complex_plural_number_key(const char* key, int number);
template<typename... Args>
std::string format(const char* key, Args&&... args) {
const char* fmt = translate(key);
// result buffer
char buffer[4096];
snprintf(buffer, sizeof(buffer), fmt, std::forward<Args>(args)...);
return std::string(buffer);
}
private:
std::string get_system_language();
std::string trim(const std::string& str);
void process_escapes(std::string& str);
private:
std::string translations_directory;
std::string current_language = "en";
std::unordered_map<std::string, std::string> translations;
};
}
#define TR(s) gsr::Translation::instance().translate(s)
#define TRF(s, ...) gsr::Translation::instance().format(s, __VA_ARGS__)
#define TRC(s, n) gsr::Translation::instance().get_complex_plural_number_key(s, n)
#define TRPF(s, n, ...) TRF(TRC(s, n).c_str(), __VA_ARGS__)

View File

@@ -6,6 +6,8 @@
#include <optional>
#include <X11/Xlib.h>
struct wl_display;
namespace gsr {
enum class WindowCaptureType {
FOCUSED,
@@ -13,8 +15,8 @@ namespace gsr {
};
struct Monitor {
mgl::vec2i position;
mgl::vec2i size;
mgl::vec2i position; // Logical position on Wayland
mgl::vec2i size; // Logical size on Wayland
std::string name;
};
@@ -30,6 +32,7 @@ namespace gsr {
std::string get_window_manager_name(Display *display);
bool is_compositor_running(Display *dpy, int screen);
std::vector<Monitor> get_monitors(Display *dpy);
std::vector<Monitor> get_monitors_wayland(struct wl_display *dpy);
void xi_grab_all_mouse_devices(Display *dpy);
void xi_ungrab_all_mouse_devices(Display *dpy);
void xi_warp_all_mouse_devices(Display *dpy, mgl::vec2i position);

View File

@@ -16,6 +16,7 @@ namespace gsr {
class RadioButton;
class Button;
class List;
class ComboBox;
class CustomRendererWidget;
enum ConfigureHotkeyType {
@@ -64,6 +65,7 @@ namespace gsr {
std::unique_ptr<List> create_replay_partial_save_hotkey_options();
std::unique_ptr<List> create_record_hotkey_options();
std::unique_ptr<List> create_record_hotkey_window_region_options();
std::unique_ptr<List> create_record_hotkey_window_options();
std::unique_ptr<List> create_stream_hotkey_options();
std::unique_ptr<List> create_screenshot_hotkey_options();
std::unique_ptr<List> create_screenshot_region_hotkey_options();
@@ -74,6 +76,7 @@ namespace gsr {
std::unique_ptr<Button> create_exit_program_button();
std::unique_ptr<Button> create_go_back_to_old_ui_button();
std::unique_ptr<List> create_notification_speed();
std::unique_ptr<List> create_language();
std::unique_ptr<Subsection> create_application_options_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_application_info_subsection(ScrollablePage *parent_page);
std::unique_ptr<Subsection> create_donate_subsection(ScrollablePage *parent_page);
@@ -111,6 +114,7 @@ namespace gsr {
Button *take_screenshot_window_button_ptr = nullptr;
Button *show_hide_button_ptr = nullptr;
RadioButton *notification_speed_button_ptr = nullptr;
ComboBox *language_combo_box_ptr = nullptr;
ConfigHotkey configure_config_hotkey;
ConfigureHotkeyType configure_hotkey_type = ConfigureHotkeyType::NONE;

View File

@@ -16,7 +16,7 @@ namespace gsr {
class ScreenshotSettingsPage : public StaticPage {
public:
ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack);
ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack, bool supports_window_title);
ScreenshotSettingsPage(const ScreenshotSettingsPage&) = delete;
ScreenshotSettingsPage& operator=(const ScreenshotSettingsPage&) = delete;
@@ -85,5 +85,7 @@ namespace gsr {
Entry *create_custom_script_screenshot_entry_ptr = nullptr;
PageStack *page_stack = nullptr;
bool supports_window_title = false;
};
}

View File

@@ -41,7 +41,7 @@ namespace gsr {
STREAM
};
SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack);
SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack, bool supports_window_title);
SettingsPage(const SettingsPage&) = delete;
SettingsPage& operator=(const SettingsPage&) = delete;
@@ -247,5 +247,7 @@ namespace gsr {
std::optional<GsrCamera> selected_camera;
std::optional<GsrCameraSetup> selected_camera_setup;
bool supports_window_title = false;
};
}

View File

@@ -1,4 +1,4 @@
project('gsr-ui', ['c', 'cpp'], version : '1.10.1', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
project('gsr-ui', ['c', 'cpp'], version : '1.10.7', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
@@ -41,6 +41,8 @@ src = [
'src/CursorTracker/CursorTrackerX11.cpp',
'src/CursorTracker/CursorTrackerWayland.cpp',
'src/Utils.cpp',
'src/HyprlandWorkaround.cpp',
'src/KwinWorkaround.cpp',
'src/WindowUtils.cpp',
'src/RegionSelector.cpp',
'src/WindowSelector.cpp',
@@ -53,6 +55,7 @@ src = [
'src/ClipboardFile.cpp',
'src/LedIndicator.cpp',
'src/Rpc.cpp',
'src/Translation.cpp',
'src/main.cpp',
]
@@ -68,7 +71,9 @@ gsr_ui_resources_path = join_paths(prefix, datadir, 'gsr-ui')
icons_path = join_paths(prefix, datadir, 'icons')
add_project_arguments('-DGSR_UI_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.12.0"', language: ['c', 'cpp'])
add_project_arguments('-DGSR_FLATPAK_VERSION="5.12.1"', language: ['c', 'cpp'])
add_project_arguments('-DKWIN_HELPER_SCRIPT_PATH="' + gsr_ui_resources_path + '/gsrkwinhelper.js"', language: ['c', 'cpp'])
executable(
meson.project_name(),
@@ -111,8 +116,34 @@ executable(
install : true
)
executable(
'gsr-kwin-helper',
[
'tools/gsr-kwin-helper/main.cpp'
],
install : true,
dependencies: [
dependency('dbus-1'),
]
)
install_data(
'tools/gsr-kwin-helper/gsrkwinhelper.js',
install_dir: gsr_ui_resources_path,
install_mode: 'rwxr-xr-x'
)
executable(
'gsr-hyprland-helper',
[
'tools/gsr-hyprland-helper/main.c'
],
install : true
)
install_subdir('images', install_dir : gsr_ui_resources_path)
install_subdir('fonts', install_dir : gsr_ui_resources_path)
install_subdir('translations', install_dir : gsr_ui_resources_path)
if get_option('desktop-files') == true
install_data(files('gpu-screen-recorder.desktop'), install_dir : join_paths(prefix, datadir, 'applications'))

View File

@@ -1,7 +1,7 @@
[package]
name = "gsr-ui"
type = "executable"
version = "1.10.1"
version = "1.10.7"
platforms = ["posix"]
[lang.cpp]

View File

@@ -178,6 +178,7 @@ namespace gsr {
{"main.joystick_hotkeys_enable_option", &config.main_config.joystick_hotkeys_enable_option},
{"main.tint_color", &config.main_config.tint_color},
{"main.notification_speed", &config.main_config.notification_speed},
{"main.language", &config.main_config.language},
{"main.show_hide_hotkey", &config.main_config.show_hide_hotkey},
{"streaming.record_options.record_area_option", &config.streaming_config.record_options.record_area_option},

View File

@@ -1,21 +1,24 @@
#include "../../include/CursorTracker/CursorTrackerWayland.hpp"
#include "../../include/WindowUtils.hpp"
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
#include <wayland-client.h>
#include "xdg-output-unstable-v1-client-protocol.h"
#include <mglpp/system/Rect.hpp>
namespace gsr {
static const int MAX_CONNECTORS = 32;
static const uint32_t plane_property_all = 0xF;
static const uint32_t plane_property_all = 0x3F;
typedef enum {
PLANE_PROPERTY_CRTC_X = 1 << 0,
PLANE_PROPERTY_CRTC_Y = 1 << 1,
PLANE_PROPERTY_CRTC_ID = 1 << 2,
PLANE_PROPERTY_TYPE_CURSOR = 1 << 3,
PLANE_PROPERTY_CRTC_W = 1 << 2,
PLANE_PROPERTY_CRTC_H = 1 << 3,
PLANE_PROPERTY_CRTC_ID = 1 << 4,
PLANE_PROPERTY_TYPE_CURSOR = 1 << 5,
} plane_property_mask;
typedef struct {
@@ -30,10 +33,17 @@ namespace gsr {
bool has_any_crtc_with_vrr_enabled;
} drm_connectors;
static bool rectangles_intersect(mgl::IntRect rect1, mgl::IntRect rect2) {
return rect1.position.x < rect2.position.x + rect2.size.x && rect1.position.x + rect1.size.x > rect2.position.x &&
rect1.position.y < rect2.position.y + rect2.size.y && rect1.position.y + rect1.size.y > rect2.position.y;
}
/* Returns plane_property_mask */
static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id) {
static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_w, int *crtc_h, int *crtc_id) {
*crtc_x = 0;
*crtc_y = 0;
*crtc_w = 0;
*crtc_h = 0;
*crtc_id = 0;
uint32_t property_mask = 0;
@@ -55,6 +65,12 @@ namespace gsr {
} else if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_Y") == 0) {
*crtc_y = (int)props->prop_values[i];
property_mask |= PLANE_PROPERTY_CRTC_Y;
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "CRTC_W") == 0) {
*crtc_w = (int)props->prop_values[i];
property_mask |= PLANE_PROPERTY_CRTC_W;
} else if((type & DRM_MODE_PROP_RANGE) && strcmp(prop->name, "CRTC_H") == 0) {
*crtc_h = (int)props->prop_values[i];
property_mask |= PLANE_PROPERTY_CRTC_H;
} else if((type & DRM_MODE_PROP_OBJECT) && strcmp(prop->name, "CRTC_ID") == 0) {
*crtc_id = (int)props->prop_values[i];
property_mask |= PLANE_PROPERTY_CRTC_ID;
@@ -136,177 +152,14 @@ namespace gsr {
}
// Name is the crtc name. TODO: verify if this works on all wayland compositors
static const WaylandOutput* get_wayland_monitor_by_name(const std::vector<WaylandOutput> &monitors, const std::string &name) {
for(const WaylandOutput &monitor : monitors) {
static const Monitor* get_wayland_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 WaylandOutput* get_wayland_monitor_by_output(CursorTrackerWayland &cursor_tracker_wayland, struct wl_output *output) {
for(WaylandOutput &monitor : cursor_tracker_wayland.monitors) {
if(monitor.output == output)
return &monitor;
}
return nullptr;
}
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
(void)wl_output;
(void)phys_width;
(void)phys_height;
(void)subpixel;
(void)make;
(void)model;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->pos.x = x;
monitor->pos.y = y;
monitor->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
(void)wl_output;
(void)flags;
(void)refresh;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->size.x = width;
monitor->size.y = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
(void)data;
(void)wl_output;
}
static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
(void)data;
(void)wl_output;
(void)factor;
}
static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
(void)wl_output;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output);
if(!monitor)
return;
monitor->name = name;
}
static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
(void)data;
(void)wl_output;
(void)description;
}
static const struct wl_output_listener output_listener = {
output_handle_geometry,
output_handle_mode,
output_handle_done,
output_handle_scale,
output_handle_name,
output_handle_description,
};
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data;
if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n");
return;
}
struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4);
cursor_tracker_wayland->monitors.push_back(
WaylandOutput{
name,
output,
nullptr,
mgl::vec2i{0, 0},
mgl::vec2i{0, 0},
0,
""
});
wl_output_add_listener(output, &output_listener, cursor_tracker_wayland);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n");
return;
}
if(cursor_tracker_wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(cursor_tracker_wayland->xdg_output_manager);
cursor_tracker_wayland->xdg_output_manager = NULL;
}
cursor_tracker_wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
registry_add_object,
registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->pos.x = x;
monitor->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)data;
(void)xdg_output;
(void)width;
(void)height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_logical_position,
xdg_output_handle_logical_size,
xdg_output_handle_done,
xdg_output_handle_name,
xdg_output_handle_description,
};
/* Returns nullptr if not found */
static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) {
for(int i = 0; i < connectors->num_connectors; ++i) {
@@ -390,7 +243,7 @@ namespace gsr {
drmModeFreeResources(resources);
}
CursorTrackerWayland::CursorTrackerWayland(const char *card_path) {
CursorTrackerWayland::CursorTrackerWayland(const char *card_path, struct wl_display *wayland_dpy) : wayland_dpy(wayland_dpy) {
drm_fd = open(card_path, O_RDONLY);
if(drm_fd <= 0) {
fprintf(stderr, "Error: CursorTrackerWayland: failed to open %s\n", card_path);
@@ -402,7 +255,6 @@ namespace gsr {
}
CursorTrackerWayland::~CursorTrackerWayland() {
clear_monitors();
if(drm_fd > 0)
close(drm_fd);
}
@@ -424,11 +276,17 @@ namespace gsr {
for(uint32_t i = 0; i < planes->count_planes; ++i) {
drmModePlanePtr plane = nullptr;
const drm_connector *connector = nullptr;
int crtc_x = 0;
int crtc_y = 0;
int crtc_w = 0;
int crtc_h = 0;
int crtc_id = 0;
uint32_t property_mask = 0;
mgl::IntRect monitor_rect;
mgl::IntRect cursor_rect;
plane = drmModeGetPlane(drm_fd, planes->planes[i]);
if(!plane)
goto next;
@@ -436,7 +294,7 @@ namespace gsr {
if(!plane->fb_id)
goto next;
property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id);
property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_w, &crtc_h, &crtc_id);
if(property_mask != plane_property_all || crtc_id <= 0)
goto next;
@@ -444,7 +302,10 @@ namespace gsr {
if(!connector)
goto next;
if(crtc_x >= 0 && crtc_x <= connector->size.x && crtc_y >= 0 && crtc_y <= connector->size.y) {
monitor_rect = { mgl::vec2i(0, 0), connector->size };
cursor_rect = { mgl::vec2i(crtc_x, crtc_y), mgl::vec2i(crtc_w, crtc_h) };
if(rectangles_intersect(cursor_rect, cursor_rect)) {
latest_cursor_position.x = crtc_x;
latest_cursor_position.y = crtc_y;
latest_crtc_id = crtc_id;
@@ -465,80 +326,19 @@ namespace gsr {
drmModeFreePlaneResources(planes);
}
void CursorTrackerWayland::set_monitor_outputs_from_xdg_output(struct wl_display *dpy) {
if(!xdg_output_manager) {
fprintf(stderr, "Warning: CursorTrackerWayland::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n");
return;
}
for(WaylandOutput &monitor : monitors) {
monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, monitor.output);
zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor);
}
// Fetch xdg_output
wl_display_roundtrip(dpy);
}
void CursorTrackerWayland::clear_monitors() {
for(WaylandOutput &monitor : monitors) {
if(monitor.output) {
wl_output_destroy(monitor.output);
monitor.output = nullptr;
}
if(monitor.xdg_output) {
zxdg_output_v1_destroy(monitor.xdg_output);
monitor.xdg_output = nullptr;
}
}
monitors.clear();
}
std::optional<CursorInfo> CursorTrackerWayland::get_latest_cursor_info() {
if(drm_fd <= 0 || latest_crtc_id == -1)
if(drm_fd <= 0 || latest_crtc_id == -1 || !wayland_dpy)
return std::nullopt;
std::string monitor_name = get_monitor_name_from_crtc_id(drm_fd, latest_crtc_id);
if(monitor_name.empty())
return std::nullopt;
struct wl_display *dpy = wl_display_connect(nullptr);
if(!dpy) {
fprintf(stderr, "Error: CursorTrackerWayland::get_latest_cursor_info: failed to connect to the wayland server\n");
const std::vector<Monitor> wayland_monitors = get_monitors_wayland(wayland_dpy);
const Monitor *wayland_monitor = get_wayland_monitor_by_name(wayland_monitors, monitor_name);
if(!wayland_monitor)
return std::nullopt;
}
clear_monitors();
struct wl_registry *registry = wl_display_get_registry(dpy);
wl_registry_add_listener(registry, &registry_listener, this);
// Fetch globals
wl_display_roundtrip(dpy);
// Fetch wl_output
wl_display_roundtrip(dpy);
set_monitor_outputs_from_xdg_output(dpy);
mgl::vec2i cursor_position = latest_cursor_position;
const WaylandOutput *wayland_monitor = get_wayland_monitor_by_name(monitors, monitor_name);
if(!wayland_monitor) {
clear_monitors();
return std::nullopt;
}
cursor_position = wayland_monitor->pos + latest_cursor_position;
clear_monitors();
if(xdg_output_manager) {
zxdg_output_manager_v1_destroy(xdg_output_manager);
xdg_output_manager = nullptr;
}
wl_registry_destroy(registry);
wl_display_disconnect(dpy);
return CursorInfo{ cursor_position, std::move(monitor_name) };
return CursorInfo{ wayland_monitor->position + latest_cursor_position, std::move(monitor_name) };
}
}

122
src/HyprlandWorkaround.cpp Normal file
View File

@@ -0,0 +1,122 @@
#include "../include/HyprlandWorkaround.hpp"
#include "../include/Process.hpp"
#include <sys/wait.h>
#include <unistd.h>
#include <thread>
namespace gsr {
static ActiveHyprlandWindow active_hyprland_window;
static bool hyprland_listener_thread_started = false;
static bool get_hyprland_socket_path(char *path, int path_len) {
const char* xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
const char* instance_sig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!xdg_runtime_dir || !instance_sig) {
fprintf(stderr, "Error: HyprlandWorkaround: environment variables not set\n");
return false;
}
if (snprintf(path, path_len, "%s/hypr/%s/.socket2.sock", xdg_runtime_dir, instance_sig) >= path_len) {
fprintf(stderr, "Error: HyprlandWorkaround: path to hyprland socket (%s/hypr/%s/.socket2.sock) is more than %d characters long\n", xdg_runtime_dir, instance_sig, path_len);
return false;
}
return true;
}
static void hyprland_listener_thread() {
char hyprland_socket_path[256];
char buffer[4096];
const std::string prefix = "Window title changed: ";
std::string line;
FILE *stdout_file = nullptr;
// Get path inside the flatpak before flatpak-spawn is called because of a bug in flatpak:
// https://github.com/flatpak/flatpak/issues/6486
// where environment variables are missing in flatpak-spawn --host
if(!get_hyprland_socket_path(hyprland_socket_path, sizeof(hyprland_socket_path))) {
fprintf(stderr, "Error: HyprlandWorkaround: failed to get hyprland socket path\n");
return;
}
const bool inside_flatpak = access("/app/manifest.json", F_OK) == 0;
size_t arg_index = 0;
const char *args[6];
if(inside_flatpak) {
args[arg_index++] = "flatpak-spawn";
args[arg_index++] = "--host";
args[arg_index++] = "--";
args[arg_index++] = "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/gsr-hyprland-helper";
} else {
args[arg_index++] = "gsr-hyprland-helper";
}
args[arg_index++] = hyprland_socket_path;
args[arg_index++] = nullptr;
int read_fd = -1;
const pid_t process_id = exec_program(args, &read_fd, false);
if(process_id == -1) {
fprintf(stderr, "Error: HyprlandWorkaround: failed to execute gsr-hyprland-helper\n");
return;
}
stdout_file = fdopen(read_fd, "r");
if (!stdout_file) {
perror("Error: HyprlandWorkaround: fdopen");
goto done;
return;
}
read_fd = -1;
fprintf(stderr, "Info: HyprlandWorkaround: started Hyprland helper thread\n");
while (fgets(buffer, sizeof(buffer), stdout_file) != nullptr) {
line = buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
size_t pos = line.find(prefix);
if (pos != std::string::npos) {
active_hyprland_window.title = line.substr(pos + prefix.length());
}
}
done:
if(stdout_file)
fclose(stdout_file);
if(read_fd > 0)
close(read_fd);
if(process_id > 0) {
kill(process_id, SIGKILL);
int status;
if(waitpid(process_id, &status, 0) == -1) {
perror("waitpid failed");
/* Ignore... */
}
}
}
std::string get_current_hyprland_window_title() {
return active_hyprland_window.title;
}
void start_hyprland_listener_thread() {
if (hyprland_listener_thread_started) {
return;
}
hyprland_listener_thread_started = true;
std::thread([&] {
hyprland_listener_thread();
}).detach();
}
}

67
src/KwinWorkaround.cpp Normal file
View File

@@ -0,0 +1,67 @@
#include "../include/KwinWorkaround.hpp"
#include <cstddef>
#include <iostream>
#include <sys/types.h>
#include <thread>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
namespace gsr {
static ActiveKwinWindow active_kwin_window;
static bool kwin_helper_thread_started = false;
void kwin_script_thread() {
FILE* pipe = popen("gsr-kwin-helper", "r");
if (!pipe) {
std::cerr << "Failed to start gsr-kwin-helper process\n";
return;
}
std::cerr << "Started a KWin helper thread\n";
char buffer[4096];
const std::string prefix = "Active window title set to: ";
std::string line;
while (fgets(buffer, sizeof(buffer), pipe) != nullptr) {
line = buffer;
if (!line.empty() && line.back() == '\n') {
line.pop_back();
}
size_t pos = line.find(prefix);
if (pos != std::string::npos) {
std::string title = line.substr(pos + prefix.length());
if (title == "gsr ui" || title == "gsr notify") {
continue; // ignore the overlay and notification
}
active_kwin_window.title = std::move(title);
}
}
pclose(pipe);
}
std::string get_current_kwin_window_title() {
return active_kwin_window.title;
}
void start_kwin_helper_thread() {
if (kwin_helper_thread_started) {
return;
}
kwin_helper_thread_started = true;
std::thread([&] {
kwin_script_thread();
}).detach();
}
}

View File

@@ -10,6 +10,9 @@
#include "../include/gui/ScreenshotSettingsPage.hpp"
#include "../include/gui/GlobalSettingsPage.hpp"
#include "../include/gui/Utils.hpp"
#include "../include/Translation.hpp"
#include "../include/KwinWorkaround.hpp"
#include "../include/HyprlandWorkaround.hpp"
#include "../include/gui/PageStack.hpp"
#include "../include/WindowUtils.hpp"
#include "../include/GlobalHotkeys/GlobalHotkeys.hpp"
@@ -39,6 +42,9 @@
#include <X11/extensions/XInput2.h>
#include <X11/extensions/shapeconst.h>
#include <X11/Xcursor/Xcursor.h>
#include <wayland-client.h>
#include <mglpp/system/Rect.hpp>
#include <mglpp/window/Event.hpp>
#include <mglpp/system/Utf8.hpp>
@@ -277,10 +283,12 @@ namespace gsr {
return true;
}
// Note that this doesn't work in the flatpak right now because of this flatpak bug:
// https://github.com/flatpak/flatpak/issues/6486
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)
if(exec_program_on_host_get_stdout(args, stdout_str, false) != 0)
return false;
int waybar_layer_level = -1;
@@ -487,6 +495,14 @@ namespace gsr {
top_bar_background({0.0f, 0.0f}),
close_button_widget({0.0f, 0.0f})
{
if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
wayland_dpy = wl_display_connect(nullptr);
if(!wayland_dpy)
fprintf(stderr, "Warning: failed to connect to the wayland server\n");
} else {
wayland_dpy = nullptr;
}
gsr_icon_path = this->resources_path + "images/gpu_screen_recorder_logo.png";
key_bindings[0].key_event.code = mgl::Keyboard::Escape;
@@ -526,16 +542,27 @@ namespace gsr {
else
fprintf(stderr, "Warning: XOpenDisplay failed to mapping notify\n");
if(this->gsr_info.system_info.display_server == DisplayServer::X11)
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) {
supports_window_title = true;
} else if(this->gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
if(!this->gsr_info.gpu_info.card_path.empty())
cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str());
cursor_tracker = std::make_unique<CursorTrackerWayland>(this->gsr_info.gpu_info.card_path.c_str(), wayland_dpy);
if(!config.main_config.wayland_warning_shown) {
config.main_config.wayland_warning_shown = true;
save_config(config);
show_notification("Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues.", notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues."), notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
}
const std::string wm_name = get_window_manager_name(x11_dpy);
if (wm_name.find("Hyprland") != std::string::npos) {
start_hyprland_listener_thread();
supports_window_title = true;
} else if (wm_name == "KWin") {
start_kwin_helper_thread();
supports_window_title = true;
}
}
@@ -582,6 +609,9 @@ namespace gsr {
if(x11_dpy)
XCloseDisplay(x11_dpy);
if(wayland_dpy)
wl_display_disconnect(wayland_dpy);
}
void Overlay::xi_setup() {
@@ -748,8 +778,8 @@ namespace gsr {
if(global_hotkeys_ungrab_keyboard) {
global_hotkeys_ungrab_keyboard = false;
show_notification(
"Some keyboard remapping software conflicts with GPU Screen Recorder on your system.\n"
"Keyboards have been ungrabbed, applications will now receive the hotkeys you press."
TR("Some keyboard remapping software conflicts with GPU Screen Recorder on your system.\n"
"Keyboards have been ungrabbed, applications will now receive the hotkeys you press.")
, 7.0, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
config.main_config.hotkeys_enable_option = "enable_hotkeys_no_grab";
@@ -789,7 +819,7 @@ namespace gsr {
if(selected_window && selected_window != DefaultRootWindow(display)) {
on_window_selected();
} else {
show_notification("No window selected", notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("No window selected"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
}
on_window_selected = nullptr;
}
@@ -824,6 +854,12 @@ namespace gsr {
bool Overlay::draw() {
remove_widgets_to_be_removed();
if(reload_ui) {
reload_ui = false;
if(visible)
recreate_frontpage_ui_components();
}
update_notification_process_status();
process_gsr_output();
update_gsr_process_status();
@@ -840,7 +876,7 @@ 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, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("Failed to start region capture"), notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
on_region_selected = nullptr;
}
}
@@ -849,7 +885,7 @@ namespace gsr {
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, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("Failed to start window capture"), notification_error_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
on_window_selected = nullptr;
}
}
@@ -1028,7 +1064,6 @@ namespace gsr {
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);
@@ -1039,11 +1074,16 @@ namespace gsr {
// 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 Window x11_focused_window = get_focused_window(display, WindowCaptureType::FOCUSED, false);
const bool prevent_game_minimizing = gsr_info.system_info.display_server != DisplayServer::WAYLAND
|| (x11_cursor_window && is_window_fullscreen_on_monitor(display, x11_cursor_window, *focused_monitor) && get_focused_window(display, WindowCaptureType::FOCUSED, false) == x11_cursor_window)
|| (x11_focused_window && is_window_fullscreen_on_monitor(display, x11_focused_window, *focused_monitor))
|| is_wlroots
|| is_hyprland;
const bool drm_cursor_pos = (!prevent_game_minimizing || is_wlroots || is_hyprland) && cursor_info;
if(drm_cursor_pos)
cursor_position = cursor_info->position;
if(prevent_game_minimizing) {
window_pos = focused_monitor->position;
window_size = focused_monitor->size;
@@ -1078,7 +1118,6 @@ namespace gsr {
window.reset();
return;
}
//window->set_low_latency(true);
unsigned char data = 2; // Prefer being composed to allow transparency
@@ -1109,7 +1148,8 @@ namespace gsr {
update_compositor_texture(*focused_monitor);
create_frontpage_ui_components();
visible = true;
recreate_frontpage_ui_components();
// 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.
@@ -1138,7 +1178,7 @@ namespace gsr {
// 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) {
if(drm_cursor_pos) {
win->cursor_position.x += cursor_hotspot.x;
win->cursor_position.y += cursor_hotspot.y;
}
@@ -1150,30 +1190,6 @@ namespace gsr {
if(!is_wlroots && !hyprland_waybar_is_dock)
window->set_fullscreen(true);
visible = true;
if(gpu_screen_recorder_process > 0) {
switch(recording_status) {
case RecordingStatus::NONE:
break;
case RecordingStatus::REPLAY:
update_ui_replay_started();
break;
case RecordingStatus::RECORD:
update_ui_recording_started();
break;
case RecordingStatus::STREAM:
update_ui_streaming_started();
break;
}
}
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;
@@ -1202,7 +1218,7 @@ namespace gsr {
}
}
void Overlay::create_frontpage_ui_components() {
void Overlay::recreate_frontpage_ui_components() {
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());
top_bar_text = mgl::Text("GPU Screen Recorder", get_theme().top_bar_font);
@@ -1237,16 +1253,16 @@ namespace gsr {
List * main_buttons_list_ptr = main_buttons_list.get();
main_buttons_list->set_spacing(0.0f);
{
auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Instant Replay", "Off", &get_theme().replay_button_texture,
auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, TR("Instant Replay"), TR("Off"), &get_theme().replay_button_texture,
mgl::vec2f(button_width, button_height));
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));
button->add_item(TR("Turn on"), "start", config.replay_config.start_stop_hotkey.to_string(false, false));
button->add_item(TR("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(TR("Save 1 min"), "save_1_min", config.replay_config.save_1_min_hotkey.to_string(false, false));
button->add_item(TR("Save 10 min"), "save_10_min", config.replay_config.save_10_min_hotkey.to_string(false, false));
}
button->add_item("Settings", "settings");
button->add_item(TR("Settings"), "settings");
button->set_item_icon("start", &get_theme().play_texture);
button->set_item_icon("save", &get_theme().save_texture);
button->set_item_icon("save_1_min", &get_theme().save_texture);
@@ -1254,11 +1270,11 @@ namespace gsr {
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);
auto replay_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::REPLAY, &gsr_info, config, &page_stack, supports_window_title);
replay_settings_page->on_config_changed = [this]() {
replay_startup_mode = replay_startup_string_to_type(config.replay_config.turn_on_replay_automatically_mode.c_str());
if(recording_status == RecordingStatus::REPLAY)
show_notification("Replay settings have been modified.\nYou may need to restart replay to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
show_notification(TR("Replay settings have been modified.\nYou may need to restart replay to apply the changes."), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
};
page_stack.push(std::move(replay_settings_page));
} else if(id == "save") {
@@ -1277,21 +1293,21 @@ namespace gsr {
main_buttons_list->add_widget(std::move(button));
}
{
auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Record", "Not recording", &get_theme().record_button_texture,
auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, TR("Record"), TR("Not recording"), &get_theme().record_button_texture,
mgl::vec2f(button_width, button_height));
record_dropdown_button_ptr = button.get();
button->add_item("Start", "start", config.record_config.start_stop_hotkey.to_string(false, false));
button->add_item("Pause", "pause", config.record_config.pause_unpause_hotkey.to_string(false, false));
button->add_item("Settings", "settings");
button->add_item(TR("Start"), "start", config.record_config.start_stop_hotkey.to_string(false, false));
button->add_item(TR("Pause"), "pause", config.record_config.pause_unpause_hotkey.to_string(false, false));
button->add_item(TR("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_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);
auto record_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::RECORD, &gsr_info, config, &page_stack, supports_window_title);
record_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::RECORD)
show_notification("Recording settings have been modified.\nYou may need to restart recording to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
show_notification(TR("Recording settings have been modified.\nYou may need to restart recording to apply the changes."), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
update_led_indicator_after_settings_change();
};
@@ -1306,19 +1322,19 @@ namespace gsr {
main_buttons_list->add_widget(std::move(button));
}
{
auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, "Livestream", "Not streaming", &get_theme().stream_button_texture,
auto button = std::make_unique<DropdownButton>(&get_theme().title_font, &get_theme().body_font, TR("Livestream"), TR("Not streaming"), &get_theme().stream_button_texture,
mgl::vec2f(button_width, button_height));
stream_dropdown_button_ptr = button.get();
button->add_item("Start", "start", config.streaming_config.start_stop_hotkey.to_string(false, false));
button->add_item("Settings", "settings");
button->add_item(TR("Start"), "start", config.streaming_config.start_stop_hotkey.to_string(false, false));
button->add_item(TR("Settings"), "settings");
button->set_item_icon("start", &get_theme().play_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);
auto stream_settings_page = std::make_unique<SettingsPage>(SettingsPage::Type::STREAM, &gsr_info, config, &page_stack, supports_window_title);
stream_settings_page->on_config_changed = [this]() {
if(recording_status == RecordingStatus::STREAM)
show_notification("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
show_notification(TR("Streaming settings have been modified.\nYou may need to restart streaming to apply the changes."), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
update_led_indicator_after_settings_change();
};
@@ -1342,6 +1358,7 @@ namespace gsr {
button->set_bg_hover_color(mgl::Color(0, 0, 0, 255));
button->set_icon(&get_theme().settings_small_texture);
button->on_click = [&]() {
std::string language_before = config.main_config.language;
auto settings_page = std::make_unique<GlobalSettingsPage>(this, &gsr_info, config, &page_stack);
settings_page->on_startup_changed = [&](bool enable, int exit_status) {
@@ -1350,12 +1367,12 @@ namespace gsr {
if(exit_status == 127) {
if(enable)
show_notification("Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add \"gsr-ui\" to system startup on systems that uses another init system.", 7.0, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add \"gsr-ui\" to system startup on systems that uses another init system."), 7.0, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
} else {
if(enable)
show_notification("Failed to add GPU Screen Recorder to system startup", notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("Failed to add GPU Screen Recorder to system startup"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
else
show_notification("Failed to remove GPU Screen Recorder from system startup", notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
show_notification(TR("Failed to remove GPU Screen Recorder from system startup"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
}
};
@@ -1376,7 +1393,7 @@ namespace gsr {
global_hotkeys_js.reset();
};
settings_page->on_page_closed = [this]() {
settings_page->on_page_closed = [this, language_before]() {
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));
@@ -1386,6 +1403,9 @@ namespace gsr {
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));
if(config.main_config.language != language_before)
reload_ui = true;
};
page_stack.push(std::move(settings_page));
@@ -1402,7 +1422,7 @@ namespace gsr {
button->set_icon(&get_theme().screenshot_texture);
button->set_icon_padding_scale(1.2f);
button->on_click = [&]() {
auto screenshot_settings_page = std::make_unique<ScreenshotSettingsPage>(&gsr_info, config, &page_stack);
auto screenshot_settings_page = std::make_unique<ScreenshotSettingsPage>(&gsr_info, config, &page_stack, supports_window_title);
screenshot_settings_page->on_config_changed = [this]() {
update_led_indicator_after_settings_change();
};
@@ -1437,6 +1457,28 @@ namespace gsr {
}
return true;
};
if(gpu_screen_recorder_process > 0) {
switch(recording_status) {
case RecordingStatus::NONE:
break;
case RecordingStatus::REPLAY:
update_ui_replay_started();
break;
case RecordingStatus::RECORD:
update_ui_recording_started();
break;
case RecordingStatus::STREAM:
update_ui_streaming_started();
break;
}
}
if(paused)
update_ui_recording_paused();
if(replay_recording)
update_ui_recording_started();
}
void Overlay::hide() {
@@ -1444,6 +1486,7 @@ namespace gsr {
return;
hide_ui = false;
reload_ui = false;
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
@@ -1558,12 +1601,12 @@ namespace gsr {
paused_clock.restart();
update_ui_recording_paused();
if(config.record_config.record_options.show_notifications)
show_notification("Recording has been paused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
show_notification(TR("Recording has been paused"), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
} else {
paused_total_time_seconds += paused_clock.get_elapsed_time_seconds();
update_ui_recording_unpaused();
if(config.record_config.record_options.show_notifications)
show_notification("Recording has been unpaused", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
show_notification(TR("Recording has been unpaused"), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
}
if(led_indicator && config.record_config.record_options.use_led_indicator)
@@ -1686,7 +1729,7 @@ namespace gsr {
static std::string capture_target_get_notification_name(const char *capture_target, bool save) {
std::string result;
if(is_capture_target_monitor(capture_target)) {
result = "this monitor";
result = TR("this monitor");
} else if(is_number(capture_target)) {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
@@ -1696,16 +1739,16 @@ namespace gsr {
const std::optional<std::string> window_title = get_window_title(display, window_id);
if(save) {
result = "window";
result = TR("window");
} else if(window_title) {
result = strip(window_title.value());
truncate_string(result, 30);
result = "window \"" + result + "\"";
result = TRF("window \"%s\"", result.c_str());
} else {
result = std::string("window ") + capture_target;
result = TRF("window %s", capture_target);
}
} else {
result = capture_target;
result = TR(capture_target);
}
return result;
}
@@ -1910,19 +1953,46 @@ namespace gsr {
seconds -= (minutes * 60);
std::string result;
if(hours > 0)
result += std::to_string(hours) + " hour" + (hours == 1 ? "" : "s");
if(hours > 0) {
if (Translation::instance().plural_numbers_are_complex()) {
result += TRPF("%d hour", hours, hours);
}
else {
if(hours == 1)
result += TRF("%d hour", hours);
else
result += TRF("%d hours", hours);
}
}
if(minutes > 0) {
if(!result.empty())
result += " ";
result += std::to_string(minutes) + " minute" + (minutes == 1 ? "" : "s");
if (Translation::instance().plural_numbers_are_complex()) {
result += TRPF("%d minute", minutes, minutes);
}
else {
if(minutes == 1)
result += TRF("%d minute", minutes);
else
result += TRF("%d minutes", minutes);
}
}
if(seconds > 0 || (hours == 0 && minutes == 0)) {
if(!result.empty())
result += " ";
result += std::to_string(seconds) + " second" + (seconds == 1 ? "" : "s");
if (Translation::instance().plural_numbers_are_complex()) {
result += TRPF("%d second", seconds, seconds);
}
else {
if(seconds == 1)
result += TRF("%d second", seconds);
else
result += TRF("%d seconds", seconds);
}
}
return result;
@@ -1950,11 +2020,24 @@ namespace gsr {
mgl_context *context = mgl_get_context();
Display *display = (Display*)context->connection;
const std::string video_filename = filepath_get_filename(video_filepath.c_str());
const std::string wm_name = get_window_manager_name(display);
const bool is_hyprland = wm_name.find("Hyprland") != std::string::npos;
const bool is_kwin_wayland = wm_name == "KWin" && gsr_info.system_info.display_server == DisplayServer::WAYLAND;
std::string focused_window_name;
if (is_hyprland) {
focused_window_name = get_current_hyprland_window_title();
} else if (is_kwin_wayland) {
focused_window_name = get_current_kwin_window_title();
} else {
const Window gsr_ui_window = window ? (Window)window->get_system_handle() : None;
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, false);
}
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, false);
if(focused_window_name.empty())
focused_window_name = "Game";
@@ -1977,7 +2060,7 @@ namespace gsr {
return;
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds() - paused_total_time_seconds - (paused ? paused_clock.get_elapsed_time_seconds() : 0.0));
snprintf(msg, sizeof(msg), "Saved a %s recording of %s\nto \"%s\"",
snprintf(msg, sizeof(msg), TR("Saved a %s recording of %s\nto \"%s\""),
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
capture_target = recording_capture_target.c_str();
@@ -1988,7 +2071,7 @@ namespace gsr {
return;
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
snprintf(msg, sizeof(msg), "Saved a %s replay of %s\nto \"%s\"",
snprintf(msg, sizeof(msg), TR("Saved a %s replay of %s\nto \"%s\""),
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
capture_target = recording_capture_target.c_str();
@@ -1998,7 +2081,7 @@ namespace gsr {
if(!config.screenshot_config.show_notifications)
return;
snprintf(msg, sizeof(msg), "Saved a screenshot of %s\nto \"%s\"",
snprintf(msg, sizeof(msg), TR("Saved a screenshot of %s\nto \"%s\""),
capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str(), focused_window_name.c_str());
capture_target = screenshot_capture_target.c_str();
break;
@@ -2030,7 +2113,7 @@ namespace gsr {
const std::string duration_str = to_duration_string(get_time_passed_in_replay_buffer_seconds());
char msg[512];
snprintf(msg, sizeof(msg), "Saved a %s replay of %s",
snprintf(msg, sizeof(msg), TR("Saved a %s replay of %s"),
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).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());
@@ -2044,7 +2127,7 @@ namespace gsr {
if(replay_save_show_notification && replay_save_clock.get_elapsed_time_seconds() >= replay_saving_notification_timeout_seconds) {
replay_save_show_notification = false;
if(config.replay_config.record_options.show_notifications)
show_notification("Saving replay, this might take some time", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
show_notification(TR("Saving replay, this might take some time"), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
}
if(gpu_screen_recorder_process_output_file) {
@@ -2089,17 +2172,17 @@ namespace gsr {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
} else if(exit_code == 51) {
show_notification("Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
show_notification(TR("Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings."), notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
} else if(exit_code == 52) {
show_notification("Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
show_notification(TR("Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec."), 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
} else if(exit_code == 53) {
show_notification("Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.", 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
show_notification(TR("Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264."), 10.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
} else if(exit_code == 54) {
show_notification("Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.", notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
show_notification(TR("Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again."), notification_error_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
} 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, nullptr, NotificationLevel::ERROR);
show_notification(TR("Stopped capture because the user canceled the desktop portal"), notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), notification_type, nullptr, NotificationLevel::ERROR);
} else {
const char *prefix = "";
switch(notification_type) {
@@ -2107,21 +2190,21 @@ namespace gsr {
case NotificationType::NOTICE:
break;
case NotificationType::SCREENSHOT:
prefix = "Failed to take a screenshot";
prefix = TR("Failed to take a screenshot");
break;
case NotificationType::RECORD:
prefix = "Failed to start/save recording";
prefix = TR("Failed to start/save recording");
break;
case NotificationType::REPLAY:
prefix = "Replay stopped because of an error";
prefix = TR("Replay stopped because of an error");
break;
case NotificationType::STREAM:
prefix = "Streaming stopped because of an error";
prefix = TR("Streaming stopped because of an error");
break;
}
char msg[256];
snprintf(msg, sizeof(msg), "%s. Verify if settings are correct", prefix);
snprintf(msg, sizeof(msg), TR("%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, nullptr, NotificationLevel::ERROR);
}
}
@@ -2150,7 +2233,7 @@ namespace gsr {
update_ui_replay_stopped();
if(exit_code == 0) {
if(config.replay_config.record_options.show_notifications)
show_notification("Replay stopped", short_notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
show_notification(TR("Replay stopped"), short_notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
} else {
on_gsr_process_error(exit_code, NotificationType::REPLAY);
}
@@ -2172,7 +2255,7 @@ namespace gsr {
update_ui_streaming_stopped();
if(exit_code == 0) {
if(config.streaming_config.record_options.show_notifications)
show_notification("Streaming has stopped", short_notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
show_notification(TR("Streaming has stopped"), short_notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
} else {
on_gsr_process_error(exit_code, NotificationType::STREAM);
}
@@ -2206,7 +2289,7 @@ namespace gsr {
save_video_in_current_game_directory(screenshot_filepath, NotificationType::SCREENSHOT);
} else if(config.screenshot_config.show_notifications) {
char msg[512];
snprintf(msg, sizeof(msg), "Saved a screenshot of %s", capture_target_get_notification_name(screenshot_capture_target.c_str(), true).c_str());
snprintf(msg, sizeof(msg), TR("Saved a screenshot of %s"), capture_target_get_notification_name(screenshot_capture_target.c_str(), true).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());
}
@@ -2225,7 +2308,7 @@ namespace gsr {
}
} else {
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_screenshot_process, exit_code);
show_notification("Failed to take a screenshot. Verify if settings are correct", notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT, nullptr, NotificationLevel::ERROR);
show_notification(TR("Failed to take a screenshot. Verify if settings are correct"), notification_timeout_seconds, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::SCREENSHOT, nullptr, NotificationLevel::ERROR);
}
gpu_screen_recorder_screenshot_process = -1;
@@ -2333,7 +2416,7 @@ namespace gsr {
const std::string duration_str = to_duration_string(recording_duration_clock.get_elapsed_time_seconds() - paused_total_time_seconds - (paused ? paused_clock.get_elapsed_time_seconds() : 0.0));
char msg[512];
snprintf(msg, sizeof(msg), "Saved a %s recording of %s",
snprintf(msg, sizeof(msg), TR("Saved a %s recording of %s"),
duration_str.c_str(),
capture_target_get_notification_name(recording_capture_target.c_str(), true).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());
@@ -2359,8 +2442,8 @@ namespace gsr {
if(!visible || recording_status != RecordingStatus::RECORD)
return;
record_dropdown_button_ptr->set_description("Paused");
record_dropdown_button_ptr->set_item_label("pause", "Unpause");
record_dropdown_button_ptr->set_description(TR("Paused"));
record_dropdown_button_ptr->set_item_label("pause", TR("Unpause"));
record_dropdown_button_ptr->set_item_icon("pause", &get_theme().play_texture);
}
@@ -2368,8 +2451,8 @@ namespace gsr {
if(!visible || recording_status != RecordingStatus::RECORD)
return;
record_dropdown_button_ptr->set_description("Recording");
record_dropdown_button_ptr->set_item_label("pause", "Pause");
record_dropdown_button_ptr->set_description(TR("Recording"));
record_dropdown_button_ptr->set_item_label("pause", TR("Pause"));
record_dropdown_button_ptr->set_item_icon("pause", &get_theme().pause_texture);
}
@@ -2377,9 +2460,9 @@ namespace gsr {
if(!visible)
return;
record_dropdown_button_ptr->set_item_label("start", "Stop and save");
record_dropdown_button_ptr->set_item_label("start", TR("Stop and save"));
record_dropdown_button_ptr->set_activated(true);
record_dropdown_button_ptr->set_description("Recording");
record_dropdown_button_ptr->set_description(TR("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);
}
@@ -2388,12 +2471,12 @@ namespace gsr {
if(!visible)
return;
record_dropdown_button_ptr->set_item_label("start", "Start");
record_dropdown_button_ptr->set_item_label("start", TR("Start"));
record_dropdown_button_ptr->set_activated(false);
record_dropdown_button_ptr->set_description("Not recording");
record_dropdown_button_ptr->set_description(TR("Not recording"));
record_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture);
record_dropdown_button_ptr->set_item_label("pause", "Pause");
record_dropdown_button_ptr->set_item_label("pause", TR("Pause"));
record_dropdown_button_ptr->set_item_icon("pause", &get_theme().pause_texture);
record_dropdown_button_ptr->set_item_enabled("pause", false);
update_upause_status();
@@ -2404,9 +2487,9 @@ namespace gsr {
if(!visible)
return;
stream_dropdown_button_ptr->set_item_label("start", "Stop");
stream_dropdown_button_ptr->set_item_label("start", TR("Stop"));
stream_dropdown_button_ptr->set_activated(true);
stream_dropdown_button_ptr->set_description("Streaming");
stream_dropdown_button_ptr->set_description(TR("Streaming"));
stream_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture);
}
@@ -2414,9 +2497,9 @@ namespace gsr {
if(!visible)
return;
stream_dropdown_button_ptr->set_item_label("start", "Start");
stream_dropdown_button_ptr->set_item_label("start", TR("Start"));
stream_dropdown_button_ptr->set_activated(false);
stream_dropdown_button_ptr->set_description("Not streaming");
stream_dropdown_button_ptr->set_description(TR("Not streaming"));
stream_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture);
update_ui_recording_stopped();
}
@@ -2425,9 +2508,9 @@ namespace gsr {
if(!visible)
return;
replay_dropdown_button_ptr->set_item_label("start", "Turn off");
replay_dropdown_button_ptr->set_item_label("start", TR("Turn off"));
replay_dropdown_button_ptr->set_activated(true);
replay_dropdown_button_ptr->set_description("On");
replay_dropdown_button_ptr->set_description(TR("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", current_recording_config.replay_config.replay_time >= 60);
@@ -2438,9 +2521,9 @@ namespace gsr {
if(!visible)
return;
replay_dropdown_button_ptr->set_item_label("start", "Turn on");
replay_dropdown_button_ptr->set_item_label("start", TR("Turn on"));
replay_dropdown_button_ptr->set_activated(false);
replay_dropdown_button_ptr->set_description("Off");
replay_dropdown_button_ptr->set_description(TR("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);
@@ -2507,8 +2590,8 @@ namespace gsr {
return result;
}
static void add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size, const RegionSelector &region_selector) {
Region region = region_selector.get_selection();
void Overlay::add_region_command(std::vector<const char*> &args, char *region_str, int region_str_size) {
Region region = region_selector.get_selection(x11_dpy, wayland_dpy);
if(region.size.x <= 32 && region.size.y <= 32) {
region.size.x = 0;
region.size.y = 0;
@@ -2518,7 +2601,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, char *region_str, int region_str_size, const RegionSelector &region_selector, const std::string &region_area_option) {
void Overlay::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 std::string &region_area_option, RecordForceType force_type) {
if(record_options.video_quality == "custom") {
args.push_back("-bm");
args.push_back("cbr");
@@ -2539,7 +2622,7 @@ namespace gsr {
args.push_back(audio_track.c_str());
}
if(record_options.restore_portal_session) {
if(record_options.restore_portal_session && force_type != RecordForceType::WINDOW) {
args.push_back("-restore-portal-session");
args.push_back("yes");
}
@@ -2550,7 +2633,7 @@ namespace gsr {
}
if(region_area_option == "region")
add_region_command(args, region_str, region_str_size, region_selector);
add_region_command(args, region_str, region_str_size);
}
static bool validate_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
@@ -2795,10 +2878,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, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
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, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
return false;
}
@@ -2825,7 +2908,7 @@ namespace gsr {
// TODO: Show this with a slight delay to make sure it doesn't show up in the video
if(!disable_notification && config.replay_config.record_options.show_notifications)
show_notification("Replay stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
show_notification(TR("Replay stopped"), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
return true;
}
@@ -2834,7 +2917,7 @@ namespace gsr {
recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) {
char err_msg[256];
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());
snprintf(err_msg, sizeof(err_msg), TR("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, nullptr, NotificationLevel::ERROR);
return false;
}
@@ -2906,7 +2989,7 @@ namespace gsr {
}
char region_str[128];
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector, config.replay_config.record_options.record_area_option);
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), config.replay_config.record_options.record_area_option);
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
args.push_back("-ro");
@@ -2919,7 +3002,7 @@ namespace gsr {
gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd);
if(gpu_screen_recorder_process == -1) {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
return false;
} else {
recording_status = RecordingStatus::REPLAY;
@@ -2942,7 +3025,7 @@ namespace gsr {
// to see when the program has exit.
if(!disable_notification && config.replay_config.record_options.show_notifications) {
char msg[256];
snprintf(msg, sizeof(msg), "Started replaying %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
snprintf(msg, sizeof(msg), TR("Started replaying %s"), capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::REPLAY, recording_capture_target.c_str());
}
@@ -2970,7 +3053,7 @@ namespace gsr {
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
if(!replay_recording) {
if(config.record_config.record_options.show_notifications)
show_notification("Started recording in the replay session", short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
show_notification(TR("Started recording in the replay session"), short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
update_ui_recording_started();
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
@@ -2991,7 +3074,7 @@ namespace gsr {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
}
return;
}
@@ -3002,7 +3085,7 @@ namespace gsr {
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
if(!replay_recording) {
if(config.record_config.record_options.show_notifications)
show_notification("Started recording in the streaming session", short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
show_notification(TR("Started recording in the streaming session"), short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD);
update_ui_recording_started();
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
@@ -3023,7 +3106,7 @@ namespace gsr {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
}
return;
}
@@ -3074,7 +3157,7 @@ namespace gsr {
recording_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 start recording, capture target \"%s\" is invalid.\nPlease change capture target in settings", recording_capture_target.c_str());
snprintf(err_msg, sizeof(err_msg), TR("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, nullptr, NotificationLevel::ERROR);
return;
}
@@ -3132,8 +3215,17 @@ namespace gsr {
"-o", output_file.c_str()
};
const std::string hotkey_window_capture_portal_session_token_filepath = get_config_dir() + "/gsr-ui-window-capture-token";
if(record_area_option == "portal") {
hide_ui = true;
if(force_type == RecordForceType::WINDOW) {
args.push_back("-portal-session-token-filepath");
args.push_back(hotkey_window_capture_portal_session_token_filepath.c_str());
}
}
char region_str[128];
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector, record_area_option);
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), record_area_option, force_type);
args.push_back(nullptr);
@@ -3142,7 +3234,7 @@ namespace gsr {
record_filepath = output_file;
gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd);
if(gpu_screen_recorder_process == -1) {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
return;
} else {
recording_status = RecordingStatus::RECORD;
@@ -3162,13 +3254,10 @@ namespace gsr {
// 1...
if(config.record_config.record_options.show_notifications) {
char msg[256];
snprintf(msg, sizeof(msg), "Started recording %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
snprintf(msg, sizeof(msg), TR("Started recording %s"), capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::RECORD, recording_capture_target.c_str());
}
if(record_area_option == "portal")
hide_ui = true;
// TODO: This will be incorrect if the user uses portal capture, as capture wont start until the user has
// selected what to capture and accepted it.
recording_duration_clock.restart();
@@ -3187,7 +3276,6 @@ namespace gsr {
url += "rtmp://rtmp.rumble.com/live/";
url += config.streaming_config.rumble.stream_key;
} else if(config.streaming_config.streaming_service == "kick") {
fprintf(stderr, "kick: %s, %s\n", config.streaming_config.kick.stream_url.c_str(), config.streaming_config.kick.stream_key.c_str());
url += config.streaming_config.kick.stream_url;
if(!url.empty() && url.back() != '/')
url += "/";
@@ -3231,10 +3319,10 @@ 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, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
return;
case RecordingStatus::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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
return;
}
@@ -3259,7 +3347,7 @@ namespace gsr {
// TODO: Show this with a slight delay to make sure it doesn't show up in the video
if(config.streaming_config.record_options.show_notifications)
show_notification("Streaming has stopped", notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
show_notification(TR("Streaming has stopped"), notification_timeout_seconds, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::STREAM);
return;
}
@@ -3267,7 +3355,7 @@ namespace gsr {
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.\nPlease change capture target in settings", recording_capture_target.c_str());
snprintf(err_msg, sizeof(err_msg), TR("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, nullptr, NotificationLevel::ERROR);
return;
}
@@ -3305,6 +3393,10 @@ namespace gsr {
choose_video_codec_and_container_with_fallback(gsr_info, &video_codec, &container, &encoder);
const std::string url = streaming_get_url(config);
if(config.streaming_config.streaming_service == "rumble" || config.streaming_config.streaming_service == "kick") {
fprintf(stderr, "Info: forcing video codec to h264 as rumble/kick supports only h264\n");
video_codec = "h264";
}
char size[64];
size[0] = '\0';
@@ -3331,7 +3423,7 @@ namespace gsr {
};
char region_str[128];
add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), region_selector, config.streaming_config.record_options.record_area_option);
add_common_gpu_screen_recorder_args(args, config.streaming_config.record_options, audio_tracks, video_bitrate, size, region_str, sizeof(region_str), config.streaming_config.record_options.record_area_option);
if(gsr_info.system_info.gsr_version >= GsrVersion{5, 4, 0}) {
args.push_back("-ro");
@@ -3344,7 +3436,7 @@ namespace gsr {
gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd);
if(gpu_screen_recorder_process == -1) {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
return;
} else {
recording_status = RecordingStatus::STREAM;
@@ -3367,7 +3459,7 @@ namespace gsr {
// to see when the program has exit.
if(config.streaming_config.record_options.show_notifications) {
char msg[256];
snprintf(msg, sizeof(msg), "Started streaming %s", capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
snprintf(msg, sizeof(msg), TR("Started streaming %s"), capture_target_get_notification_name(recording_capture_target.c_str(), false).c_str());
show_notification(msg, short_notification_timeout_seconds, get_color_theme().tint_color, get_color_theme().tint_color, NotificationType::STREAM, recording_capture_target.c_str());
}
@@ -3401,7 +3493,7 @@ namespace gsr {
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.\nPlease change capture target in settings", screenshot_capture_target.c_str());
snprintf(err_msg, sizeof(err_msg), TR("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, nullptr, NotificationLevel::ERROR);
return;
}
@@ -3463,7 +3555,7 @@ namespace gsr {
char region_str[128];
if(record_area_option == "region")
add_region_command(args, region_str, sizeof(region_str), region_selector);
add_region_command(args, region_str, sizeof(region_str));
args.push_back(nullptr);
@@ -3472,7 +3564,7 @@ namespace gsr {
screenshot_filepath = output_file;
gpu_screen_recorder_screenshot_process = exec_program(args.data(), nullptr);
if(gpu_screen_recorder_screenshot_process == -1) {
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, nullptr, NotificationLevel::ERROR);
show_notification(TR("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, nullptr, NotificationLevel::ERROR);
}
}

View File

@@ -166,6 +166,62 @@ namespace gsr {
return ((uint32_t)color.a << 24) | (((uint32_t)color.r * color.a / 0xFF) << 16) | (((uint32_t)color.g * color.a / 0xFF) << 8) | ((uint32_t)color.b * color.a / 0xFF);
}
static const Monitor* get_monitor_by_region_center(const std::vector<Monitor> &monitors, Region region) {
const mgl::vec2i center = {region.pos.x + region.size.x / 2, region.pos.y + region.size.y / 2};
for(const Monitor &monitor : monitors) {
if(center.x >= monitor.position.x && center.x <= monitor.position.x + monitor.size.x
&& center.y >= monitor.position.y && center.y <= monitor.position.y + monitor.size.y)
{
return &monitor;
}
}
return nullptr;
}
// Name is the x11 name. TODO: verify if this works on all wayland compositors
static const Monitor* get_wayland_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 mgl::vec2d to_vec2d(mgl::vec2i v) {
return { (double)v.x, (double)v.y };
}
static Region x11_region_to_wayland_region(Display *dpy, struct wl_display *wayland_dpy, Region x11_region) {
const std::vector<Monitor> x11_monitors = get_monitors(dpy);
const Monitor *x11_selected_monitor = get_monitor_by_region_center(x11_monitors, x11_region);
if(!x11_selected_monitor) {
fprintf(stderr, "Warning: RegionSelector: failed to get x11 monitor\n");
return x11_region;
}
const std::vector<Monitor> wayland_monitors = get_monitors_wayland(wayland_dpy);
const Monitor *wayland_monitor = get_wayland_monitor_by_name(wayland_monitors, x11_selected_monitor->name);
if(!wayland_monitor) {
fprintf(stderr, "Warning: RegionSelector: failed to get wayland monitor\n");
return x11_region;
}
const mgl::vec2d region_relative_pos = {
(double)(x11_region.pos.x - x11_selected_monitor->position.x) / (double)x11_selected_monitor->size.x,
(double)(x11_region.pos.y - x11_selected_monitor->position.y) / (double)x11_selected_monitor->size.y,
};
const mgl::vec2d region_relative_size = {
(double)x11_region.size.x / (double)x11_selected_monitor->size.x,
(double)x11_region.size.y / (double)x11_selected_monitor->size.y,
};
return Region {
wayland_monitor->position + (region_relative_pos * to_vec2d(wayland_monitor->size)).to_vec2i(),
(region_relative_size * to_vec2d(wayland_monitor->size)).to_vec2i(),
};
}
RegionSelector::RegionSelector() {
}
@@ -385,8 +441,11 @@ namespace gsr {
return result;
}
Region RegionSelector::get_selection() const {
return region;
Region RegionSelector::get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const {
Region returned_region = region;
if(is_wayland && x11_dpy && wayland_dpy)
returned_region = x11_region_to_wayland_region(x11_dpy, wayland_dpy, returned_region);
return returned_region;
}
void RegionSelector::on_button_press(const void *de) {

187
src/Translation.cpp Normal file
View File

@@ -0,0 +1,187 @@
#include "../include/Translation.hpp"
#include <cstdio>
#include <cstring>
#include <iostream>
#include <unordered_map>
#include <fstream>
namespace gsr {
std::string Translation::get_system_language() {
const char* lang = getenv("LANGUAGE");
if (!lang || !lang[0]) lang = getenv("LC_ALL");
if (!lang || !lang[0]) lang = getenv("LC_MESSAGES");
if (!lang || !lang[0]) lang = getenv("LANG");
if (lang && lang[0]) {
std::string lang_str(lang);
// we usually need only two symbols
size_t underscore = lang_str.find('_');
if (underscore != std::string::npos) {
return lang_str.substr(0, underscore);
}
size_t dot = lang_str.find('.');
if (dot != std::string::npos) {
return lang_str.substr(0, dot);
}
return lang_str;
}
return "en";
}
void Translation::process_escapes(std::string& str) {
size_t pos = 0;
while ((pos = str.find("\\n", pos)) != std::string::npos) {
str.replace(pos, 2, "\n");
pos += 1;
}
}
bool Translation::is_language_supported(const char* lang) {
if(strcmp(lang, "en") == 0)
return true;
std::string paths[] = {
std::string("translations/") + lang + ".txt",
std::string(this->translations_directory) + lang + ".txt"
};
for (const auto& path : paths) {
std::ifstream file(path);
if (file.is_open()) {
return true;
}
}
return false;
}
bool Translation::load_language(const char* lang) {
translations.clear();
if(lang[0] == '\0')
lang = "en";
if (!is_language_supported(lang)) {
fprintf(stderr, "Warning: language '%s' is not supported\n", lang);
return false;
}
current_language = lang;
if (strcmp(lang, "en") == 0) {
return true; // english is the base
}
std::string paths[] = {
std::string("translations/") + lang + ".txt",
std::string(this->translations_directory) + lang + ".txt"
};
for (const auto& path : paths) {
std::ifstream file(path);
if (!file.is_open()) continue;
std::string line;
while (std::getline(file, line)) {
if (line.empty() || line[0] == '#') continue;
size_t eq_pos = line.find('=');
if (eq_pos == std::string::npos) continue;
std::string key = line.substr(0, eq_pos);
std::string value = line.substr(eq_pos + 1);
// Process escape sequences in both key and value
process_escapes(key);
process_escapes(value);
translations[key] = value;
}
fprintf(stderr, "Info: Loaded translation file for '%s' from %s\n", lang, path.c_str());
return true;
}
fprintf(stderr, "Warning: translation file for '%s' not found\n", lang);
return false;
}
Translation& Translation::instance() {
static Translation tr;
return tr;
}
void Translation::init(const char* translations_directory, const char* initial_language) {
if(initial_language && initial_language[0] == '\0')
initial_language = nullptr;
this->translations_directory = translations_directory;
load_language(initial_language == nullptr ? get_system_language().c_str() : initial_language);
}
bool Translation::plural_numbers_are_complex() {
if (
current_language == "ru" ||
current_language == "uk" ||
current_language == "pl" ||
current_language == "cs" ||
current_language == "sk" ||
current_language == "hr" ||
current_language == "sl"
) {
return true;
}
return current_language != "en";
}
std::string Translation::get_complex_plural_number_key(const char* key, int number) {
std::string s_key = key;
if (current_language == "ru" || current_language == "uk") {
int n = number % 100;
if (n >= 11 && n <= 14) {
return s_key + "_many";
}
n = number % 10;
if (n == 1) {
return s_key + "_one";
}
if (n >= 2 && n <= 4) {
return s_key + "_few";
}
return s_key + "_many";
} else if (current_language == "pl") {
int n = number % 100;
if (n >= 12 && n <= 14) {
return s_key + "_many";
}
n = number % 10;
if (n == 1) {
return s_key + "_one";
}
if (n >= 2 && n <= 4) {
return s_key + "_few";
}
return s_key + "_many";
}
// Add more languages as needed
return key; // default fallback
}
const char* Translation::translate(const char* key) {
auto it = translations.find(key);
if (it != translations.end()) {
return it->second.c_str();
}
#ifndef DNDEBUG
if(current_language != "en")
fprintf(stderr, "Warning: translation key '%s' not found\n", key);
#endif
return key; // falling back if nothing found
}
}

View File

@@ -8,6 +8,9 @@
#include <X11/extensions/shapeconst.h>
#include <X11/extensions/Xrandr.h>
#include <wayland-client.h>
#include "xdg-output-unstable-v1-client-protocol.h"
#include <mglpp/system/Utf8.hpp>
extern "C" {
@@ -23,6 +26,209 @@ extern "C" {
#define MAX_PROPERTY_VALUE_LEN 4096
namespace gsr {
struct WaylandOutput {
uint32_t wl_name;
struct wl_output *output;
struct zxdg_output_v1 *xdg_output;
mgl::vec2i pos;
mgl::vec2i size;
int32_t transform;
std::string name;
};
struct Wayland {
std::vector<WaylandOutput> outputs;
struct zxdg_output_manager_v1 *xdg_output_manager = nullptr;
};
static WaylandOutput* get_wayland_monitor_by_output(Wayland &wayland, struct wl_output *output) {
for(WaylandOutput &monitor : wayland.outputs) {
if(monitor.output == output)
return &monitor;
}
return nullptr;
}
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t phys_width, int32_t phys_height,
int32_t subpixel, const char *make, const char *model,
int32_t transform) {
(void)wl_output;
(void)phys_width;
(void)phys_height;
(void)subpixel;
(void)make;
(void)model;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->pos.x = x;
monitor->pos.y = y;
monitor->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) {
(void)wl_output;
(void)flags;
(void)refresh;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->size.x = width;
monitor->size.y = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
(void)data;
(void)wl_output;
}
static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) {
(void)data;
(void)wl_output;
(void)factor;
}
static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) {
(void)wl_output;
Wayland *wayland = (Wayland*)data;
WaylandOutput *monitor = get_wayland_monitor_by_output(*wayland, wl_output);
if(!monitor)
return;
monitor->name = name;
}
static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) {
(void)data;
(void)wl_output;
(void)description;
}
static const struct wl_output_listener output_listener = {
output_handle_geometry,
output_handle_mode,
output_handle_done,
output_handle_scale,
output_handle_name,
output_handle_description,
};
static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) {
(void)version;
Wayland *wayland = (Wayland*)data;
if(strcmp(interface, wl_output_interface.name) == 0) {
if(version < 4) {
fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n");
return;
}
struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4);
wayland->outputs.push_back(
WaylandOutput{
name,
output,
nullptr,
mgl::vec2i{0, 0},
mgl::vec2i{0, 0},
0,
""
});
wl_output_add_listener(output, &output_listener, wayland);
} else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
if(version < 1) {
fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n");
return;
}
if(wayland->xdg_output_manager) {
zxdg_output_manager_v1_destroy(wayland->xdg_output_manager);
wayland->xdg_output_manager = NULL;
}
wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1);
}
}
static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
(void)data;
(void)registry;
(void)name;
// TODO: Remove output
}
static struct wl_registry_listener registry_listener = {
registry_add_object,
registry_remove_object,
};
static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) {
(void)zxdg_output_v1;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->pos.x = x;
monitor->pos.y = y;
}
static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) {
(void)xdg_output;
WaylandOutput *monitor = (WaylandOutput*)data;
monitor->size.x = width;
monitor->size.y = height;
}
static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) {
(void)data;
(void)xdg_output;
}
static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) {
(void)data;
(void)xdg_output;
(void)name;
}
static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) {
(void)data;
(void)xdg_output;
(void)description;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
xdg_output_logical_position,
xdg_output_handle_logical_size,
xdg_output_handle_done,
xdg_output_handle_name,
xdg_output_handle_description,
};
static const int transform_90 = 1;
static const int transform_270 = 3;
static void transform_monitors(Wayland &wayland) {
for(WaylandOutput &output : wayland.outputs) {
if(output.transform == transform_90 || output.transform == transform_270)
std::swap(output.size.x, output.size.y);
}
}
static void set_monitor_outputs_from_xdg_output(Wayland &wayland, struct wl_display *dpy) {
if(!wayland.xdg_output_manager) {
fprintf(stderr, "Warning: WindowUtils::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n");
return;
}
for(WaylandOutput &monitor : wayland.outputs) {
monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(wayland.xdg_output_manager, monitor.output);
zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor);
}
// Fetch xdg_output
wl_display_roundtrip(dpy);
}
static unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size) {
Atom ret_property_type = None;
int ret_format = 0;
@@ -519,6 +725,47 @@ namespace gsr {
return monitors;
}
std::vector<Monitor> get_monitors_wayland(struct wl_display *dpy) {
Wayland wayland;
struct wl_registry *registry = wl_display_get_registry(dpy);
wl_registry_add_listener(registry, &registry_listener, &wayland);
// Fetch globals
wl_display_roundtrip(dpy);
// Fetch wl_output
wl_display_roundtrip(dpy);
transform_monitors(wayland);
set_monitor_outputs_from_xdg_output(wayland, dpy);
std::vector<Monitor> monitors;
for(WaylandOutput &output : wayland.outputs) {
monitors.push_back(Monitor{output.pos, output.size, std::move(output.name)});
if(output.output) {
wl_output_destroy(output.output);
output.output = nullptr;
}
if(output.xdg_output) {
zxdg_output_v1_destroy(output.xdg_output);
output.xdg_output = nullptr;
}
}
wayland.outputs.clear();
if(wayland.xdg_output_manager) {
zxdg_output_manager_v1_destroy(wayland.xdg_output_manager);
wayland.xdg_output_manager = nullptr;
}
wl_registry_destroy(registry);
return monitors;
}
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)

View File

@@ -3,6 +3,7 @@
#include "../../include/Overlay.hpp"
#include "../../include/Theme.hpp"
#include "../../include/Process.hpp"
#include "../../include/Translation.hpp"
#include "../../include/gui/GsrPage.hpp"
#include "../../include/gui/PageStack.hpp"
#include "../../include/gui/ScrollablePage.hpp"
@@ -11,6 +12,7 @@
#include "../../include/gui/Label.hpp"
#include "../../include/gui/Image.hpp"
#include "../../include/gui/RadioButton.hpp"
#include "../../include/gui/ComboBox.hpp"
#include "../../include/gui/LineSeparator.hpp"
#include "../../include/gui/CustomRendererWidget.hpp"
@@ -80,8 +82,8 @@ namespace gsr {
gsr_info(gsr_info),
page_stack(page_stack)
{
auto content_page = std::make_unique<GsrPage>("Global", "Settings");
content_page->add_button("Back", "back", get_color_theme().page_bg_color);
auto content_page = std::make_unique<GsrPage>(TR("Global"), TR("Settings"));
content_page->add_button(TR("Back"), "back", get_color_theme().page_bg_color);
content_page->on_click = [page_stack](const std::string &id) {
if(id == "back")
page_stack->pop();
@@ -98,9 +100,9 @@ namespace gsr {
if(!configure_hotkey_button)
return;
mgl::Text title_text("Press a key combination to use for the hotkey \"" + hotkey_configure_action_name + "\":", get_theme().title_font);
mgl::Text title_text(TRF("Press a key combination to use for the hotkey: \"%s\"", hotkey_configure_action_name.c_str()), get_theme().title_font);
mgl::Text hotkey_text(configure_hotkey_button->get_text(), get_theme().top_bar_font);
mgl::Text description_text("Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey.", get_theme().body_font);
mgl::Text description_text(TR("Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey."), get_theme().body_font);
const float text_max_width = std::max(title_text.get_bounds().size.x, std::max(hotkey_text.get_bounds().size.x, description_text.get_bounds().size.x));
const float padding_horizontal = int(get_theme().window_height * 0.01f);
@@ -143,12 +145,12 @@ namespace gsr {
std::unique_ptr<Subsection> GlobalSettingsPage::create_appearance_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Accent color", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Accent color"), get_color_theme().text_color));
auto tint_color_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
tint_color_radio_button_ptr = tint_color_radio_button.get();
tint_color_radio_button->add_item("Red", "amd");
tint_color_radio_button->add_item("Green", "nvidia");
tint_color_radio_button->add_item("Blue", "intel");
tint_color_radio_button->add_item(TR("Red"), "amd");
tint_color_radio_button->add_item(TR("Green"), "nvidia");
tint_color_radio_button->add_item(TR("Blue"), "intel");
tint_color_radio_button->on_selection_changed = [](const std::string&, const std::string &id) {
if(id == "amd")
get_color_theme().tint_color = mgl::Color(221, 0, 49);
@@ -159,16 +161,16 @@ namespace gsr {
return true;
};
list->add_widget(std::move(tint_color_radio_button));
return std::make_unique<Subsection>("Appearance", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Appearance"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
std::unique_ptr<Subsection> GlobalSettingsPage::create_startup_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start program on system startup?", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Start program on system startup?"), get_color_theme().text_color));
auto startup_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
startup_radio_button_ptr = startup_radio_button.get();
startup_radio_button->add_item("Yes", "start_on_system_startup");
startup_radio_button->add_item("No", "dont_start_on_system_startup");
startup_radio_button->add_item(TR("Yes"), "start_on_system_startup");
startup_radio_button->add_item(TR("No"), "dont_start_on_system_startup");
startup_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
bool enable = false;
if(id == "dont_start_on_system_startup")
@@ -186,16 +188,16 @@ namespace gsr {
return exit_status == 0;
};
list->add_widget(std::move(startup_radio_button));
return std::make_unique<Subsection>("Startup", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Startup"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
std::unique_ptr<RadioButton> GlobalSettingsPage::create_enable_keyboard_hotkeys_button() {
auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL);
enable_keyboard_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get();
enable_hotkeys_radio_button->add_item("Yes", "enable_hotkeys");
enable_hotkeys_radio_button->add_item("Yes, but only grab virtual devices (supports some input remapping software)", "enable_hotkeys_virtual_devices");
enable_hotkeys_radio_button->add_item("Yes, but don't grab devices (supports all input remapping software)", "enable_hotkeys_no_grab");
enable_hotkeys_radio_button->add_item("No", "disable_hotkeys");
enable_hotkeys_radio_button->add_item(TR("Yes"), "enable_hotkeys");
enable_hotkeys_radio_button->add_item(TR("Yes, but only grab virtual devices (supports some input remapping software)"), "enable_hotkeys_virtual_devices");
enable_hotkeys_radio_button->add_item(TR("Yes, but don't grab devices (supports all input remapping software)"), "enable_hotkeys_no_grab");
enable_hotkeys_radio_button->add_item(TR("No"), "disable_hotkeys");
enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
if(on_keyboard_hotkey_changed)
on_keyboard_hotkey_changed(id.c_str());
@@ -207,8 +209,8 @@ namespace gsr {
std::unique_ptr<RadioButton> GlobalSettingsPage::create_enable_joystick_hotkeys_button() {
auto enable_hotkeys_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
enable_joystick_hotkeys_radio_button_ptr = enable_hotkeys_radio_button.get();
enable_hotkeys_radio_button->add_item("Yes", "enable_hotkeys");
enable_hotkeys_radio_button->add_item("No", "disable_hotkeys");
enable_hotkeys_radio_button->add_item(TR("Yes"), "enable_hotkeys");
enable_hotkeys_radio_button->add_item(TR("No"), "disable_hotkeys");
enable_hotkeys_radio_button->on_selection_changed = [&](const std::string&, const std::string &id) {
if(on_joystick_hotkey_changed)
on_joystick_hotkey_changed(id.c_str());
@@ -220,7 +222,7 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_show_hide_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Show/hide UI:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Show/hide UI:"), get_color_theme().text_color));
auto show_hide_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
show_hide_button_ptr = show_hide_button.get();
list->add_widget(std::move(show_hide_button));
@@ -235,12 +237,12 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_replay_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Turn replay on/off:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Turn replay on/off:"), get_color_theme().text_color));
auto turn_replay_on_off_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
turn_replay_on_off_button_ptr = turn_replay_on_off_button.get();
list->add_widget(std::move(turn_replay_on_off_button));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save replay:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Save replay:"), get_color_theme().text_color));
auto save_replay_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
save_replay_button_ptr = save_replay_button.get();
list->add_widget(std::move(save_replay_button));
@@ -259,12 +261,12 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_replay_partial_save_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save 1 minute replay:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Save 1 minute replay:"), get_color_theme().text_color));
auto save_replay_1_min_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
save_replay_1_min_button_ptr = save_replay_1_min_button.get();
list->add_widget(std::move(save_replay_1_min_button));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Save 10 minute replay:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Save 10 minute replay:"), get_color_theme().text_color));
auto save_replay_10_min_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
save_replay_10_min_button_ptr = save_replay_10_min_button.get();
list->add_widget(std::move(save_replay_10_min_button));
@@ -283,12 +285,12 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop recording:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Start/stop recording:"), get_color_theme().text_color));
auto start_stop_recording_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
start_stop_recording_button_ptr = start_stop_recording_button.get();
list->add_widget(std::move(start_stop_recording_button));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Pause/unpause recording:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Pause/unpause recording:"), get_color_theme().text_color));
auto pause_unpause_recording_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
pause_unpause_recording_button_ptr = pause_unpause_recording_button.get();
list->add_widget(std::move(pause_unpause_recording_button));
@@ -307,26 +309,32 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_window_region_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop recording a region:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Start/stop recording a region:"), get_color_theme().text_color));
auto start_stop_recording_region_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
start_stop_recording_region_button_ptr = start_stop_recording_region_button.get();
list->add_widget(std::move(start_stop_recording_region_button));
start_stop_recording_region_button_ptr->on_click = [this] {
configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP_REGION);
};
return list;
}
std::unique_ptr<List> GlobalSettingsPage::create_record_hotkey_window_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
char str[128];
if(gsr_info->system_info.display_server == DisplayServer::X11)
snprintf(str, sizeof(str), "Start/stop recording a window:");
snprintf(str, sizeof(str), "%s", TR("Start/stop recording a window:"));
else
snprintf(str, sizeof(str), "Start/stop recording with desktop portal:");
snprintf(str, sizeof(str), "%s", TR("Start/stop recording with desktop portal:"));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
auto start_stop_recording_window_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
start_stop_recording_window_button_ptr = start_stop_recording_window_button.get();
list->add_widget(std::move(start_stop_recording_window_button));
start_stop_recording_region_button_ptr->on_click = [this] {
configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP_REGION);
};
start_stop_recording_window_button_ptr->on_click = [this] {
configure_hotkey_start(ConfigureHotkeyType::RECORD_START_STOP_WINDOW);
};
@@ -337,7 +345,7 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_stream_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Start/stop streaming:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Start/stop streaming:"), get_color_theme().text_color));
auto start_stop_streaming_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
start_stop_streaming_button_ptr = start_stop_streaming_button.get();
list->add_widget(std::move(start_stop_streaming_button));
@@ -352,7 +360,7 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_screenshot_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Take a screenshot:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Take a screenshot:"), get_color_theme().text_color));
auto take_screenshot_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
take_screenshot_button_ptr = take_screenshot_button.get();
list->add_widget(std::move(take_screenshot_button));
@@ -367,7 +375,7 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_screenshot_region_hotkey_options() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Take a screenshot of a region:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Take a screenshot of a region:"), get_color_theme().text_color));
auto take_screenshot_region_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
take_screenshot_region_button_ptr = take_screenshot_region_button.get();
list->add_widget(std::move(take_screenshot_region_button));
@@ -384,9 +392,9 @@ namespace gsr {
char str[128];
if(gsr_info->system_info.display_server == DisplayServer::X11)
snprintf(str, sizeof(str), "Take a screenshot of a window:");
snprintf(str, sizeof(str), "%s", TR("Take a screenshot of a window:"));
else
snprintf(str, sizeof(str), "Take a screenshot with desktop portal:");
snprintf(str, sizeof(str), "%s", TR("Take a screenshot with desktop portal:"));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
auto take_screenshot_window_button = std::make_unique<Button>(&get_theme().body_font, "", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
@@ -403,7 +411,7 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_hotkey_control_buttons() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Clear hotkeys", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto clear_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, TR("Clear hotkeys"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
clear_hotkeys_button->on_click = [this] {
for_each_config_hotkey([&](ConfigHotkey *config_hotkey_item) {
*config_hotkey_item = {mgl::Keyboard::Unknown, 0};
@@ -413,7 +421,7 @@ namespace gsr {
};
list->add_widget(std::move(clear_hotkeys_button));
auto reset_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, "Reset hotkeys to default", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto reset_hotkeys_button = std::make_unique<Button>(&get_theme().body_font, TR("Reset hotkeys to default"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
reset_hotkeys_button->on_click = [this] {
config.set_hotkeys_to_default();
load_hotkeys();
@@ -426,9 +434,9 @@ namespace gsr {
static std::unique_ptr<List> create_joystick_hotkey_text(mgl::Texture *image1, mgl::Texture *image2, float max_height, const char *suffix) {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Press", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Press"), get_color_theme().text_color));
list->add_widget(std::make_unique<Image>(image1, mgl::vec2f{max_height, 1000.0f}, Image::ScaleBehavior::SCALE));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "and", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("and"), get_color_theme().text_color));
list->add_widget(std::make_unique<Image>(image2, mgl::vec2f{max_height, 1000.0f}, Image::ScaleBehavior::SCALE));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, suffix, get_color_theme().text_color));
return list;
@@ -437,9 +445,9 @@ namespace gsr {
std::unique_ptr<Subsection> GlobalSettingsPage::create_keyboard_hotkey_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
List *list_ptr = list.get();
auto subsection = std::make_unique<Subsection>("Keyboard hotkeys", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
auto subsection = std::make_unique<Subsection>(TR("Keyboard hotkeys"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Enable keyboard hotkeys?", get_color_theme().text_color));
list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Enable keyboard hotkeys?"), get_color_theme().text_color));
list_ptr->add_widget(create_enable_keyboard_hotkeys_button());
list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x));
list_ptr->add_widget(create_show_hide_hotkey_options());
@@ -447,6 +455,7 @@ namespace gsr {
list_ptr->add_widget(create_replay_partial_save_hotkey_options());
list_ptr->add_widget(create_record_hotkey_options());
list_ptr->add_widget(create_record_hotkey_window_region_options());
list_ptr->add_widget(create_record_hotkey_window_options());
list_ptr->add_widget(create_stream_hotkey_options());
list_ptr->add_widget(create_screenshot_hotkey_options());
list_ptr->add_widget(create_screenshot_region_hotkey_options());
@@ -458,23 +467,23 @@ namespace gsr {
std::unique_ptr<Subsection> GlobalSettingsPage::create_controller_hotkey_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
List *list_ptr = list.get();
auto subsection = std::make_unique<Subsection>("Controller hotkeys", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
auto subsection = std::make_unique<Subsection>(TR("Controller hotkeys"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, "Enable controller hotkeys?", get_color_theme().text_color));
list_ptr->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Enable controller hotkeys?"), get_color_theme().text_color));
list_ptr->add_widget(create_enable_joystick_hotkeys_button());
list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_options_texture, get_theme().body_font.get_character_size(), "to show/hide the UI"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_up_texture, get_theme().body_font.get_character_size(), "to take a screenshot"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_down_texture, get_theme().body_font.get_character_size(), "to save a replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_left_texture, get_theme().body_font.get_character_size(), "to start/stop recording"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_right_texture, get_theme().body_font.get_character_size(), "to turn replay on/off"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_cross_texture, get_theme().body_font.get_character_size(), "to save a 1 minute replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_triangle_texture, get_theme().body_font.get_character_size(), "to save a 10 minute replay"));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_options_texture, get_theme().body_font.get_character_size(), TR("to show/hide the UI")));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_up_texture, get_theme().body_font.get_character_size(), TR("to take a screenshot")));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_down_texture, get_theme().body_font.get_character_size(), TR("to save a replay")));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_left_texture, get_theme().body_font.get_character_size(), TR("to start/stop recording")));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_dpad_right_texture, get_theme().body_font.get_character_size(), TR("to turn replay on/off")));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_cross_texture, get_theme().body_font.get_character_size(), TR("to save a 1 minute replay")));
list_ptr->add_widget(create_joystick_hotkey_text(&get_theme().ps4_home_texture, &get_theme().ps4_triangle_texture, get_theme().body_font.get_character_size(), TR("to save a 10 minute replay")));
return subsection;
}
std::unique_ptr<Button> GlobalSettingsPage::create_exit_program_button() {
auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, "Exit program", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, TR("Exit program"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
exit_program_button->on_click = [&]() {
if(on_click_exit_program_button)
on_click_exit_program_button("exit");
@@ -483,7 +492,7 @@ namespace gsr {
}
std::unique_ptr<Button> GlobalSettingsPage::create_go_back_to_old_ui_button() {
auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, "Go back to the old UI", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto exit_program_button = std::make_unique<Button>(&get_theme().body_font, TR("Go back to the old UI"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
exit_program_button->on_click = [&]() {
if(on_click_exit_program_button)
on_click_exit_program_button("back-to-old-ui");
@@ -493,12 +502,12 @@ namespace gsr {
std::unique_ptr<List> GlobalSettingsPage::create_notification_speed() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Notification speed", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Notification speed"), get_color_theme().text_color));
auto radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
notification_speed_button_ptr = radio_button.get();
radio_button->add_item("Normal", "normal");
radio_button->add_item("Fast", "fast");
radio_button->add_item(TR("Normal"), "normal");
radio_button->add_item(TR("Fast"), "fast");
radio_button->on_selection_changed = [this](const std::string&, const std::string &id) {
if(id == "normal")
overlay->set_notification_speed(NotificationSpeed::NORMAL);
@@ -511,12 +520,38 @@ namespace gsr {
return list;
}
std::unique_ptr<List> GlobalSettingsPage::create_language() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Language"), get_color_theme().text_color));
auto combo_box = std::make_unique<ComboBox>(&get_theme().body_font);
language_combo_box_ptr = combo_box.get();
combo_box->add_item(TR("System language"), "");
combo_box->add_item("English", "en");
combo_box->add_item("Español", "es");
combo_box->add_item("Русский", "ru");
combo_box->add_item("Українська", "uk");
combo_box->on_selection_changed = [](const std::string&, const std::string &id) {
Translation::instance().load_language(id.c_str());
return true;
};
list->add_widget(std::move(combo_box));
return list;
}
std::unique_ptr<Subsection> GlobalSettingsPage::create_application_options_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
List *list_ptr = list.get();
auto subsection = std::make_unique<Subsection>("Application options", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
auto subsection = std::make_unique<Subsection>(TR("Application options"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
list_ptr->add_widget(create_notification_speed());
{
auto horizontal_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
horizontal_list->set_spacing(0.02f);
horizontal_list->add_widget(create_notification_speed());
horizontal_list->add_widget(create_language());
list_ptr->add_widget(std::move(horizontal_list));
}
list_ptr->add_widget(std::make_unique<LineSeparator>(LineSeparator::Orientation::HORIZONTAL, subsection->get_inner_size().x));
const bool inside_flatpak = getenv("FLATPAK_ID") != NULL;
@@ -535,28 +570,28 @@ namespace gsr {
char str[128];
const std::string gsr_version = gsr_info->system_info.gsr_version.to_string();
snprintf(str, sizeof(str), "GSR version: %s", gsr_version.c_str());
snprintf(str, sizeof(str), TR("GSR version: %s"), gsr_version.c_str());
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
snprintf(str, sizeof(str), "GSR-UI version: %s", GSR_UI_VERSION);
snprintf(str, sizeof(str), TR("GSR-UI version: %s"), GSR_UI_VERSION);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
if(inside_flatpak) {
snprintf(str, sizeof(str), "Flatpak version: %s", GSR_FLATPAK_VERSION);
snprintf(str, sizeof(str), TR("Flatpak version: %s"), GSR_FLATPAK_VERSION);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
}
snprintf(str, sizeof(str), "GPU vendor: %s", gpu_vendor_to_string(gsr_info->gpu_info.vendor));
snprintf(str, sizeof(str), TR("GPU vendor: %s"), gpu_vendor_to_string(gsr_info->gpu_info.vendor));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, str, get_color_theme().text_color));
return std::make_unique<Subsection>("Application info", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Application info"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
std::unique_ptr<Subsection> GlobalSettingsPage::create_donate_subsection(ScrollablePage *parent_page) {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:"), get_color_theme().text_color));
auto donate_button = std::make_unique<Button>(&get_theme().body_font, "Donate", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto donate_button = std::make_unique<Button>(&get_theme().body_font, TR("Donate"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
donate_button->on_click = [this] {
const char *args[] = { "xdg-open", "https://buymeacoffee.com/dec05eba", nullptr };
exec_program_daemonized(args);
@@ -564,8 +599,8 @@ namespace gsr {
};
list->add_widget(std::move(donate_button));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.", get_color_theme().text_color));
return std::make_unique<Subsection>("Donate", std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software."), get_color_theme().text_color));
return std::make_unique<Subsection>(TR("Donate"), std::move(list), mgl::vec2f(parent_page->get_inner_size().x, 0.0f));
}
void GlobalSettingsPage::add_widgets() {
@@ -606,6 +641,7 @@ namespace gsr {
enable_joystick_hotkeys_radio_button_ptr->set_selected_item(config.main_config.joystick_hotkeys_enable_option, false, false);
notification_speed_button_ptr->set_selected_item(config.main_config.notification_speed);
language_combo_box_ptr->set_selected_item(config.main_config.language);
load_hotkeys();
}
@@ -636,6 +672,7 @@ namespace gsr {
config.main_config.hotkeys_enable_option = enable_keyboard_hotkeys_radio_button_ptr->get_selected_id();
config.main_config.joystick_hotkeys_enable_option = enable_joystick_hotkeys_radio_button_ptr->get_selected_id();
config.main_config.notification_speed = notification_speed_button_ptr->get_selected_id();
config.main_config.language = language_combo_box_ptr->get_selected_id();
save_config(config);
}
@@ -792,50 +829,50 @@ namespace gsr {
hotkey_configure_action_name = "";
break;
case ConfigureHotkeyType::REPLAY_START_STOP:
hotkey_configure_action_name = "Turn replay on/off";
hotkey_configure_action_name = TR("Turn replay on/off");
break;
case ConfigureHotkeyType::REPLAY_SAVE:
hotkey_configure_action_name = "Save replay";
hotkey_configure_action_name = TR("Save replay");
break;
case ConfigureHotkeyType::REPLAY_SAVE_1_MIN:
hotkey_configure_action_name = "Save 1 minute replay";
hotkey_configure_action_name = TR("Save 1 minute replay");
break;
case ConfigureHotkeyType::REPLAY_SAVE_10_MIN:
hotkey_configure_action_name = "Save 10 minute replay";
hotkey_configure_action_name = TR("Save 10 minute replay");
break;
case ConfigureHotkeyType::RECORD_START_STOP:
hotkey_configure_action_name = "Start/stop recording";
hotkey_configure_action_name = TR("Start/stop recording");
break;
case ConfigureHotkeyType::RECORD_PAUSE_UNPAUSE:
hotkey_configure_action_name = "Pause/unpause recording";
hotkey_configure_action_name = TR("Pause/unpause recording");
break;
case ConfigureHotkeyType::RECORD_START_STOP_REGION:
hotkey_configure_action_name = "Start/stop recording a region";
hotkey_configure_action_name = TR("Start/stop recording a region");
break;
case ConfigureHotkeyType::RECORD_START_STOP_WINDOW:
if(gsr_info->system_info.display_server == DisplayServer::X11)
hotkey_configure_action_name = "Start/stop recording a window";
hotkey_configure_action_name = TR("Start/stop recording a window");
else
hotkey_configure_action_name = "Start/stop recording with desktop portal";
hotkey_configure_action_name = TR("Start/stop recording with desktop portal");
break;
case ConfigureHotkeyType::STREAM_START_STOP:
hotkey_configure_action_name = "Start/stop streaming";
hotkey_configure_action_name = TR("Start/stop streaming");
break;
case ConfigureHotkeyType::TAKE_SCREENSHOT:
hotkey_configure_action_name = "Take a screenshot";
hotkey_configure_action_name = TR("Take a screenshot");
break;
case ConfigureHotkeyType::TAKE_SCREENSHOT_REGION:
hotkey_configure_action_name = "Take a screenshot of a region";
hotkey_configure_action_name = TR("Take a screenshot of a region");
break;
case ConfigureHotkeyType::TAKE_SCREENSHOT_WINDOW: {
if(gsr_info->system_info.display_server == DisplayServer::X11)
hotkey_configure_action_name = "Take a screenshot of a window";
hotkey_configure_action_name = TR("Take a screenshot of a window");
else
hotkey_configure_action_name = "Take a screenshot with desktop portal";
hotkey_configure_action_name = TR("Take a screenshot with desktop portal");
break;
}
case ConfigureHotkeyType::SHOW_HIDE:
hotkey_configure_action_name = "Show/hide UI";
hotkey_configure_action_name = TR("Show/hide UI");
break;
}
}
@@ -866,7 +903,7 @@ namespace gsr {
}
if(hotkey_used_by_another_action) {
const std::string error_msg = "The hotkey \"" + configure_config_hotkey.to_string() + " is already used for something else";
const std::string error_msg = TR("The hotkey \"") + configure_config_hotkey.to_string() + TR("\" is already used for something else");
overlay->show_notification(error_msg.c_str(), 3.0, mgl::Color(255, 0, 0, 255), mgl::Color(255, 0, 0, 255), NotificationType::NONE);
config_hotkey_button->set_text(config_hotkey->to_string());
configure_config_hotkey = {0, 0};

View File

@@ -4,6 +4,7 @@
#include "../../include/Theme.hpp"
#include "../../include/GsrInfo.hpp"
#include "../../include/Utils.hpp"
#include "../../include/Translation.hpp"
#include "../../include/gui/List.hpp"
#include "../../include/gui/ScrollablePage.hpp"
#include "../../include/gui/Label.hpp"
@@ -11,16 +12,17 @@
#include "../../include/gui/FileChooser.hpp"
namespace gsr {
ScreenshotSettingsPage::ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
ScreenshotSettingsPage::ScreenshotSettingsPage(const GsrInfo *gsr_info, Config &config, PageStack *page_stack, bool supports_window_title) :
StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
config(config),
gsr_info(gsr_info),
page_stack(page_stack)
page_stack(page_stack),
supports_window_title(supports_window_title)
{
capture_options = get_supported_capture_options(*gsr_info);
auto content_page = std::make_unique<GsrPage>("Screenshot", "Settings");
content_page->add_button("Back", "back", get_color_theme().page_bg_color);
auto content_page = std::make_unique<GsrPage>(TR("Screenshot"), TR("Settings"));
content_page->add_button(TR("Back"), "back", get_color_theme().page_bg_color);
content_page->on_click = [page_stack](const std::string &id) {
if(id == "back")
page_stack->pop();
@@ -36,25 +38,25 @@ namespace gsr {
auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font);
// TODO: Show options not supported but disable them
if(capture_options.window)
record_area_box->add_item("Window", "window");
record_area_box->add_item(TR("Window"), "window");
if(capture_options.region)
record_area_box->add_item("Region", "region");
record_area_box->add_item(TR("Region"), "region");
if(!capture_options.monitors.empty())
record_area_box->add_item("Focused monitor", "focused_monitor");
record_area_box->add_item(TR("Focused monitor"), "focused_monitor");
for(const auto &monitor : capture_options.monitors) {
char name[256];
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
snprintf(name, sizeof(name), TR("Monitor %s (%dx%d)"), monitor.name.c_str(), monitor.size.x, monitor.size.y);
record_area_box->add_item(name, monitor.name);
}
if(capture_options.portal)
record_area_box->add_item("Desktop portal", "portal");
record_area_box->add_item(TR("Desktop portal"), "portal");
record_area_box_ptr = record_area_box.get();
return record_area_box;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_area() {
auto record_area_list = std::make_unique<List>(List::Orientation::VERTICAL);
record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Capture source:", get_color_theme().text_color));
record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Capture source:"), get_color_theme().text_color));
record_area_list->add_widget(create_record_area_box());
return record_area_list;
}
@@ -83,14 +85,14 @@ namespace gsr {
std::unique_ptr<List> ScreenshotSettingsPage::create_image_resolution_section() {
auto image_resolution_list = std::make_unique<List>(List::Orientation::VERTICAL);
image_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image resolution limit:", get_color_theme().text_color));
image_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Image resolution limit:"), get_color_theme().text_color));
image_resolution_list->add_widget(create_image_resolution());
image_resolution_list_ptr = image_resolution_list.get();
return image_resolution_list;
}
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_restore_portal_session_checkbox() {
auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restore portal session");
auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Restore portal session"));
restore_portal_session_checkbox->set_checked(true);
restore_portal_session_checkbox_ptr = restore_portal_session_checkbox.get();
return restore_portal_session_checkbox;
@@ -105,7 +107,7 @@ namespace gsr {
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_change_image_resolution_section() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Change image resolution");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Change image resolution"));
change_image_resolution_checkbox_ptr = checkbox.get();
return checkbox;
}
@@ -120,18 +122,18 @@ namespace gsr {
ll->add_widget(std::move(capture_target_list));
ll->add_widget(create_change_image_resolution_section());
return std::make_unique<Subsection>("Capture", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Capture"), std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<List> ScreenshotSettingsPage::create_image_quality_section() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image quality:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Image quality:"), get_color_theme().text_color));
auto image_quality_box = std::make_unique<ComboBox>(&get_theme().body_font);
image_quality_box->add_item("Medium", "medium");
image_quality_box->add_item("High", "high");
image_quality_box->add_item("Very high (Recommended)", "very_high");
image_quality_box->add_item("Ultra", "ultra");
image_quality_box->add_item(TR("Medium"), "medium");
image_quality_box->add_item(TR("High"), "high");
image_quality_box->add_item(TR("Very high (Recommended)"), "very_high");
image_quality_box->add_item(TR("Ultra"), "ultra");
image_quality_box->set_selected_item("very_high");
image_quality_box_ptr = image_quality_box.get();
@@ -141,7 +143,7 @@ namespace gsr {
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_record_cursor_section() {
auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor");
auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Record cursor"));
record_cursor_checkbox->set_checked(true);
record_cursor_checkbox_ptr = record_cursor_checkbox.get();
return record_cursor_checkbox;
@@ -151,7 +153,7 @@ namespace gsr {
auto image_section_list = std::make_unique<List>(List::Orientation::VERTICAL);
image_section_list->add_widget(create_image_quality_section());
image_section_list->add_widget(create_record_cursor_section());
return std::make_unique<Subsection>("Image", std::move(image_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Image"), std::move(image_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<List> ScreenshotSettingsPage::create_save_directory(const char *label) {
@@ -160,9 +162,9 @@ namespace gsr {
auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_pictures_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
save_directory_button_ptr = save_directory_button.get();
save_directory_button->on_click = [this]() {
auto select_directory_page = std::make_unique<GsrPage>("File", "Settings");
select_directory_page->add_button("Save", "save", get_color_theme().tint_color);
select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color);
auto select_directory_page = std::make_unique<GsrPage>(TR("File"), TR("Settings"));
select_directory_page->add_button(TR("Save"), "save", get_color_theme().tint_color);
select_directory_page->add_button(TR("Cancel"), "cancel", get_color_theme().page_bg_color);
auto file_chooser = std::make_unique<FileChooser>(save_directory_button_ptr->get_text().c_str(), select_directory_page->get_inner_size());
FileChooser *file_chooser_ptr = file_chooser.get();
@@ -195,48 +197,48 @@ namespace gsr {
std::unique_ptr<List> ScreenshotSettingsPage::create_image_format_section() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Image format:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Image format:"), get_color_theme().text_color));
list->add_widget(create_image_format_box());
return list;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_file_info_section() {
auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
file_info_data_list->add_widget(create_save_directory("Directory to save screenshots:"));
file_info_data_list->add_widget(create_save_directory(TR("Directory to save screenshots:")));
file_info_data_list->add_widget(create_image_format_section());
return std::make_unique<Subsection>("File info", std::move(file_info_data_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("File info"), std::move(file_info_data_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_in_game_folder() {
char text[256];
snprintf(text, sizeof(text), "Save screenshot in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)");
snprintf(text, sizeof(text), "%s%s", TR("Save screenshot in a folder based on the focused applications name"), supports_window_title ? "" : " (X11 applications only)");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text);
save_screenshot_in_game_folder_checkbox_ptr = checkbox.get();
return checkbox;
}
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_to_clipboard() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, gsr_info->system_info.display_server == DisplayServer::X11 ? "Save screenshot to clipboard" : "Save screenshot to clipboard (Not supported properly by Wayland)");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, gsr_info->system_info.display_server == DisplayServer::X11 ? TR("Save screenshot to clipboard") : TR("Save screenshot to clipboard (Not supported properly by Wayland)"));
save_screenshot_to_clipboard_checkbox_ptr = checkbox.get();
return checkbox;
}
std::unique_ptr<CheckBox> ScreenshotSettingsPage::create_save_screenshot_to_disk() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Save screenshot to disk");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Save screenshot to disk"));
save_screenshot_to_disk_checkbox_ptr = checkbox.get();
checkbox->set_checked(true);
return checkbox;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_notifications() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Show screenshot notifications");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Show screenshot notifications"));
checkbox->set_checked(true);
show_notification_checkbox_ptr = checkbox.get();
return checkbox;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_led_indicator() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Blink scroll lock led when taking a screenshot");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Blink scroll lock led when taking a screenshot"));
checkbox->set_checked(true);
led_indicator_checkbox_ptr = checkbox.get();
return checkbox;
@@ -247,14 +249,14 @@ namespace gsr {
list->add_widget(create_save_screenshot_in_game_folder());
list->add_widget(create_save_screenshot_to_clipboard());
list->add_widget(create_save_screenshot_to_disk());
return std::make_unique<Subsection>("General", std::move(list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("General"), std::move(list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_screenshot_indicator_section() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(create_notifications());
list->add_widget(create_led_indicator());
return std::make_unique<Subsection>("Screenshot indicator", std::move(list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Screenshot indicator"), std::move(list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<List> ScreenshotSettingsPage::create_custom_script_screenshot_entry() {
@@ -269,13 +271,13 @@ namespace gsr {
std::unique_ptr<List> ScreenshotSettingsPage::create_custom_script_screenshot() {
auto custom_script_screenshot_list = std::make_unique<List>(List::Orientation::VERTICAL);
custom_script_screenshot_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Command to open the screenshot with:", get_color_theme().text_color));
custom_script_screenshot_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Command to open the screenshot with:"), get_color_theme().text_color));
custom_script_screenshot_list->add_widget(create_custom_script_screenshot_entry());
return custom_script_screenshot_list;
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_custom_script_screenshot_section() {
return std::make_unique<Subsection>("Script", create_custom_script_screenshot(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Script"), create_custom_script_screenshot(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<Widget> ScreenshotSettingsPage::create_settings() {

View File

@@ -10,8 +10,10 @@
#include "../../include/Theme.hpp"
#include "../../include/GsrInfo.hpp"
#include "../../include/Utils.hpp"
#include "mglpp/window/Window.hpp"
#include "mglpp/window/Event.hpp"
#include "../../include/Translation.hpp"
#include <mglpp/window/Window.hpp>
#include <mglpp/window/Event.hpp>
#include <algorithm>
#include <cmath>
@@ -28,26 +30,27 @@ namespace gsr {
static const char* settings_page_type_to_title_text(SettingsPage::Type type) {
switch(type) {
case SettingsPage::Type::REPLAY: return "Instant Replay";
case SettingsPage::Type::RECORD: return "Record";
case SettingsPage::Type::STREAM: return "Livestream";
case SettingsPage::Type::REPLAY: return TR("Instant Replay");
case SettingsPage::Type::RECORD: return TR("Record");
case SettingsPage::Type::STREAM: return TR("Livestream");
}
return "";
}
SettingsPage::SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack) :
SettingsPage::SettingsPage(Type type, const GsrInfo *gsr_info, Config &config, PageStack *page_stack, bool supports_window_title) :
StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()),
type(type),
config(config),
gsr_info(gsr_info),
page_stack(page_stack)
page_stack(page_stack),
supports_window_title(supports_window_title)
{
audio_devices = get_audio_devices();
application_audio = get_application_audio();
capture_options = get_supported_capture_options(*gsr_info);
auto content_page = std::make_unique<GsrPage>(settings_page_type_to_title_text(type), "Settings");
content_page->add_button("Back", "back", get_color_theme().page_bg_color);
auto content_page = std::make_unique<GsrPage>(settings_page_type_to_title_text(type), TR("Settings"));
content_page->add_button(TR("Back"), "back", get_color_theme().page_bg_color);
content_page->on_click = [page_stack](const std::string &id) {
if(id == "back")
page_stack->pop();
@@ -62,8 +65,8 @@ namespace gsr {
std::unique_ptr<RadioButton> SettingsPage::create_view_radio_button() {
auto view_radio_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
view_radio_button->add_item("Simple view", "simple");
view_radio_button->add_item("Advanced view", "advanced");
view_radio_button->add_item(TR("Simple view"), "simple");
view_radio_button->add_item(TR("Advanced view"), "advanced");
view_radio_button->set_horizontal_alignment(Widget::Alignment::CENTER);
view_radio_button_ptr = view_radio_button.get();
return view_radio_button;
@@ -73,27 +76,27 @@ namespace gsr {
auto record_area_box = std::make_unique<ComboBox>(&get_theme().body_font);
// TODO: Show options not supported but disable them
if(capture_options.window)
record_area_box->add_item("Window", "window");
record_area_box->add_item(TR("Window"), "window");
if(capture_options.focused)
record_area_box->add_item("Follow focused window", "focused");
record_area_box->add_item(TR("Follow focused window"), "focused");
if(capture_options.region)
record_area_box->add_item("Region", "region");
record_area_box->add_item(TR("Region"), "region");
if(!capture_options.monitors.empty())
record_area_box->add_item("Focused monitor", "focused_monitor");
record_area_box->add_item(TR("Focused monitor"), "focused_monitor");
for(const auto &monitor : capture_options.monitors) {
char name[256];
snprintf(name, sizeof(name), "Monitor %s (%dx%d)", monitor.name.c_str(), monitor.size.x, monitor.size.y);
snprintf(name, sizeof(name), TR("Monitor %s (%dx%d)"), monitor.name.c_str(), monitor.size.x, monitor.size.y);
record_area_box->add_item(name, monitor.name);
}
if(capture_options.portal)
record_area_box->add_item("Desktop portal", "portal");
record_area_box->add_item(TR("Desktop portal"), "portal");
record_area_box_ptr = record_area_box.get();
return record_area_box;
}
std::unique_ptr<Widget> SettingsPage::create_record_area() {
auto record_area_list = std::make_unique<List>(List::Orientation::VERTICAL);
record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Capture source:", get_color_theme().text_color));
record_area_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Capture source:"), get_color_theme().text_color));
record_area_list->add_widget(create_record_area_box());
return record_area_list;
}
@@ -122,7 +125,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_area_size_section() {
auto area_size_list = std::make_unique<List>(List::Orientation::VERTICAL);
area_size_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Area size:", get_color_theme().text_color));
area_size_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Area size:"), get_color_theme().text_color));
area_size_list->add_widget(create_area_size());
area_size_list_ptr = area_size_list.get();
return area_size_list;
@@ -152,14 +155,14 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_video_resolution_section() {
auto video_resolution_list = std::make_unique<List>(List::Orientation::VERTICAL);
video_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video resolution limit:", get_color_theme().text_color));
video_resolution_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Video resolution limit:"), get_color_theme().text_color));
video_resolution_list->add_widget(create_video_resolution());
video_resolution_list_ptr = video_resolution_list.get();
return video_resolution_list;
}
std::unique_ptr<CheckBox> SettingsPage::create_restore_portal_session_checkbox() {
auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restore portal session");
auto restore_portal_session_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Restore portal session"));
restore_portal_session_checkbox->set_checked(true);
restore_portal_session_checkbox_ptr = restore_portal_session_checkbox.get();
return restore_portal_session_checkbox;
@@ -174,7 +177,7 @@ namespace gsr {
}
std::unique_ptr<Widget> SettingsPage::create_change_video_resolution_section() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Change video resolution");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Change video resolution"));
change_video_resolution_checkbox_ptr = checkbox.get();
return checkbox;
}
@@ -190,15 +193,15 @@ namespace gsr {
ll->add_widget(std::move(capture_target_list));
ll->add_widget(create_change_video_resolution_section());
return std::make_unique<Subsection>("Capture", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Capture"), std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<List> SettingsPage::create_webcam_sources() {
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, "Webcam source:", get_color_theme().text_color));
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Webcam source:"), get_color_theme().text_color));
auto combobox = std::make_unique<ComboBox>(&get_theme().body_font);
combobox->add_item("None", "");
combobox->add_item(TR("None"), "");
for(const GsrCamera &camera : capture_options.cameras) {
combobox->add_item(camera.path, camera.path);
}
@@ -220,29 +223,18 @@ namespace gsr {
return;
webcam_body_list_ptr->set_visible(true);
webcam_video_format_box_ptr->add_item("Auto (recommended)", "auto");
webcam_video_format_box_ptr->add_item(TR("Auto (recommended)"), "auto");
if(!it->yuyv_setups.empty())
webcam_video_format_box_ptr->add_item("YUYV", "yuyv");
webcam_video_format_box_ptr->add_item(TR("YUYV"), "yuyv");
if(!it->mjpeg_setups.empty())
webcam_video_format_box_ptr->add_item("Motion-JPEG", "mjpeg");
webcam_video_format_box_ptr->add_item(TR("Motion-JPEG"), "mjpeg");
webcam_video_format_box_ptr->set_selected_item("auto");
webcam_video_format_box_ptr->set_selected_item(get_current_record_options().webcam_video_format);
const std::string prev_webcam_video_format = get_current_record_options().webcam_video_format;
webcam_video_format_box_ptr->set_selected_item(webcam_video_format_box_ptr->get_selected_id());
webcam_video_format_box_ptr->set_selected_item(prev_webcam_video_format);
selected_camera = *it;
// TODO: Set from config
if(webcam_video_format_box_ptr->get_selected_id() == "yuyv" && !it->yuyv_setups.empty())
selected_camera_setup = selected_camera->yuyv_setups.front();
else if(webcam_video_format_box_ptr->get_selected_id() == "mjpeg" && !it->mjpeg_setups.empty())
selected_camera_setup = selected_camera->mjpeg_setups.front();
else if(webcam_video_format_box_ptr->get_selected_id() == "auto") {
if(!it->mjpeg_setups.empty())
selected_camera_setup = selected_camera->mjpeg_setups.front();
else if(!it->yuyv_setups.empty())
selected_camera_setup = selected_camera->yuyv_setups.front();
}
};
ll->add_widget(std::move(combobox));
@@ -251,11 +243,25 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_webcam_video_setups() {
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video setup:", get_color_theme().text_color));
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Video setup:"), get_color_theme().text_color));
auto combobox = std::make_unique<ComboBox>(&get_theme().body_font);
webcam_video_setup_box_ptr = combobox.get();
webcam_video_setup_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
int camera_width = 0;
int camera_height = 0;
int camera_fps = 0;
sscanf(id.c_str(), "%dx%d@%dhz", &camera_width, &camera_height, &camera_fps);
RecordOptions &current_record_options = get_current_record_options();
current_record_options.webcam_camera_width = camera_width;
current_record_options.webcam_camera_height = camera_height;
current_record_options.webcam_camera_fps = camera_fps;
selected_camera_setup = GsrCameraSetup{mgl::vec2i{camera_width, camera_height}, camera_fps};
};
ll->add_widget(std::move(combobox));
return ll;
}
@@ -272,7 +278,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_webcam_video_format() {
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video format:", get_color_theme().text_color));
ll->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Video format:"), get_color_theme().text_color));
auto combobox = std::make_unique<ComboBox>(&get_theme().body_font);
webcam_video_format_box_ptr = combobox.get();
@@ -312,6 +318,12 @@ namespace gsr {
webcam_video_setup_box_ptr->add_item(setup_str, setup_str, false);
}
}
const RecordOptions &current_record_options = get_current_record_options();
char webcam_setup_str[256];
snprintf(webcam_setup_str, sizeof(webcam_setup_str), "%dx%d@%dhz", current_record_options.webcam_camera_width, current_record_options.webcam_camera_height, current_record_options.webcam_camera_fps);
webcam_video_setup_box_ptr->set_selected_item(webcam_video_setup_box_ptr->get_selected_id());
webcam_video_setup_box_ptr->set_selected_item(webcam_setup_str);
};
ll->add_widget(std::move(combobox));
@@ -380,7 +392,7 @@ namespace gsr {
{
draw_rectangle_outline(window, pos, size, mgl::Color(255, 0, 0, 255), screen_border);
mgl::Text screen_text("Screen", get_theme().camera_setup_font);
mgl::Text screen_text(TR("Screen"), get_theme().camera_setup_font);
screen_text.set_position((pos + size * 0.5f - screen_text.get_bounds().size * 0.5f).floor());
window.draw(screen_text);
}
@@ -395,7 +407,7 @@ namespace gsr {
// resize_area.set_color(mgl::Color(0, 0, 255, 255));
// window.draw(resize_area);
mgl::Text webcam_text("Webcam", get_theme().camera_setup_font);
mgl::Text webcam_text(TR("Webcam"), get_theme().camera_setup_font);
webcam_text.set_position((webcam_box_drawn_pos + webcam_box_drawn_size * 0.5f - webcam_text.get_bounds().size * 0.5f).floor());
window.draw(webcam_text);
}
@@ -455,7 +467,7 @@ namespace gsr {
}
std::unique_ptr<CheckBox> SettingsPage::create_flip_camera_checkbox() {
auto flip_camera_horizontally_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Flip camera horizontally");
auto flip_camera_horizontally_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Flip camera horizontally"));
flip_camera_horizontally_checkbox_ptr = flip_camera_horizontally_checkbox.get();
return flip_camera_horizontally_checkbox;
}
@@ -465,7 +477,7 @@ namespace gsr {
webcam_body_list_ptr = body_list.get();
body_list->set_visible(false);
body_list->add_widget(create_webcam_location_widget());
body_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "* Right click in the bottom right corner to resize the webcam", get_color_theme().text_color));
body_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("* Right click in the bottom right corner to resize the webcam"), get_color_theme().text_color));
body_list->add_widget(create_flip_camera_checkbox());
body_list->add_widget(create_webcam_video_setup_list());
return body_list;
@@ -475,7 +487,7 @@ namespace gsr {
auto ll = std::make_unique<List>(List::Orientation::VERTICAL);
ll->add_widget(create_webcam_sources());
ll->add_widget(create_webcam_body());
return std::make_unique<Subsection>("Webcam", std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Webcam"), std::move(ll), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
static bool audio_device_is_output(const std::string &audio_device_id) {
@@ -532,14 +544,14 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_audio_device(AudioDeviceType device_type, List *audio_input_list_ptr) {
auto audio_device_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
audio_device_list->userdata = (void*)(uintptr_t)AudioTrackType::DEVICE;
audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, device_type == AudioDeviceType::OUTPUT ? "Output device:" : "Input device: ", get_color_theme().text_color));
audio_device_list->add_widget(std::make_unique<Label>(&get_theme().body_font, device_type == AudioDeviceType::OUTPUT ? TR("Output device:") : TR("Input device: "), get_color_theme().text_color));
audio_device_list->add_widget(create_audio_device_selection_combobox(device_type));
audio_device_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, audio_device_list.get()));
return audio_device_list;
}
std::unique_ptr<Button> SettingsPage::create_add_audio_track_button() {
auto button = std::make_unique<Button>(&get_theme().body_font, "Add audio track", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto button = std::make_unique<Button>(&get_theme().body_font, TR("Add audio track"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
button->on_click = [this]() {
audio_track_section_list_ptr->add_widget(create_audio_track_section(audio_section_ptr));
};
@@ -564,7 +576,7 @@ namespace gsr {
switch(audio_track_type) {
case AudioTrackType::DEVICE: {
Label *label = dynamic_cast<Label*>(audio_track_line->get_child_widget_by_index(0));
const bool is_output_device = starts_with(label->get_text().c_str(), "Output device");
const bool is_output_device = starts_with(label->get_text().c_str(), TR("Output device"));
if(is_output_device)
num_output_devices++;
break;
@@ -584,7 +596,7 @@ namespace gsr {
}
std::unique_ptr<Button> SettingsPage::create_add_audio_output_device_button(List *audio_input_list_ptr) {
auto button = std::make_unique<Button>(&get_theme().body_font, "Add output device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto button = std::make_unique<Button>(&get_theme().body_font, TR("Add output device"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
button->on_click = [this, audio_input_list_ptr]() {
audio_devices = get_audio_devices();
audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::OUTPUT, audio_input_list_ptr));
@@ -594,7 +606,7 @@ namespace gsr {
}
std::unique_ptr<Button> SettingsPage::create_add_audio_input_device_button(List *audio_input_list_ptr) {
auto button = std::make_unique<Button>(&get_theme().body_font, "Add input device", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto button = std::make_unique<Button>(&get_theme().body_font, TR("Add input device"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
button->on_click = [this, audio_input_list_ptr]() {
audio_devices = get_audio_devices();
audio_input_list_ptr->add_widget(create_audio_device(AudioDeviceType::INPUT, audio_input_list_ptr));
@@ -608,7 +620,7 @@ namespace gsr {
for(const auto &app_audio : application_audio) {
audio_device_box->add_item(app_audio, app_audio);
}
audio_device_box->add_item("Custom...", custom_app_audio_tag);
audio_device_box->add_item(TR("Custom..."), custom_app_audio_tag);
audio_device_box->on_selection_changed = [application_audio_row, audio_device_box_ptr](const std::string&, const std::string &id) {
if(id == custom_app_audio_tag) {
@@ -624,7 +636,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_application_audio(List *audio_input_list_ptr) {
auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION;
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color));
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Application: "), get_color_theme().text_color));
application_audio_list->add_widget(create_application_audio_selection_combobox(application_audio_list.get()));
application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get()));
return application_audio_list;
@@ -633,14 +645,14 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_custom_application_audio(List *audio_input_list_ptr) {
auto application_audio_list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
application_audio_list->userdata = (void*)(uintptr_t)AudioTrackType::APPLICATION_CUSTOM;
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Application: ", get_color_theme().text_color));
application_audio_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Application: "), get_color_theme().text_color));
application_audio_list->add_widget(std::make_unique<Entry>(&get_theme().body_font, "", (int)(get_theme().body_font.get_character_size() * 10.0f)));
application_audio_list->add_widget(create_remove_audio_device_button(audio_input_list_ptr, application_audio_list.get()));
return application_audio_list;
}
std::unique_ptr<Button> SettingsPage::create_add_application_audio_button(List *audio_input_list_ptr) {
auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, "Add application audio", mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
auto add_audio_track_button = std::make_unique<Button>(&get_theme().body_font, TR("Add application audio"), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
add_audio_track_button->on_click = [this, audio_input_list_ptr]() {
application_audio = get_application_audio();
if(application_audio.empty())
@@ -668,7 +680,7 @@ namespace gsr {
}
std::unique_ptr<CheckBox> SettingsPage::create_application_audio_invert_checkbox() {
auto application_audio_invert_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record audio from all applications except the selected ones");
auto application_audio_invert_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Record audio from all applications except the selected ones"));
application_audio_invert_checkbox->set_checked(false);
application_audio_invert_checkbox->on_changed = [this](bool) {
update_application_audio_warning_visibility();
@@ -683,7 +695,7 @@ namespace gsr {
const int font_character_size = get_theme().body_font.get_character_size();
list->add_widget(std::make_unique<Image>(&get_theme().warning_texture, mgl::vec2f(font_character_size, font_character_size), Image::ScaleBehavior::SCALE));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices.", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices."), get_color_theme().text_color));
return list;
}
@@ -692,7 +704,7 @@ namespace gsr {
int index = 0;
audio_track_section_list_ptr->for_each_child_widget([&index](std::unique_ptr<Widget> &widget) {
char audio_track_name[32];
snprintf(audio_track_name, sizeof(audio_track_name), "Audio track #%d", 1 + index);
snprintf(audio_track_name, sizeof(audio_track_name), TR("Audio track #%d"), 1 + index);
++index;
Subsection *subsection = dynamic_cast<Subsection*>(widget.get());
@@ -721,7 +733,7 @@ namespace gsr {
std::unique_ptr<Subsection> SettingsPage::create_audio_track_section(Widget *parent_widget) {
char audio_track_name[32];
snprintf(audio_track_name, sizeof(audio_track_name), "Audio track #%d", 1 + (int)audio_track_section_list_ptr->get_num_children());
snprintf(audio_track_name, sizeof(audio_track_name), TR("Audio track #%d"), 1 + (int)audio_track_section_list_ptr->get_num_children());
auto audio_input_section = create_audio_input_section();
List *audio_input_section_ptr = audio_input_section.get();
@@ -751,7 +763,7 @@ namespace gsr {
auto audio_device_section_list = std::make_unique<List>(List::Orientation::VERTICAL);
List *audio_device_section_list_ptr = audio_device_section_list.get();
auto subsection = std::make_unique<Subsection>("Audio", std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
auto subsection = std::make_unique<Subsection>(TR("Audio"), std::move(audio_device_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
audio_section_ptr = subsection.get();
audio_device_section_list_ptr->add_widget(create_add_audio_track_button());
audio_device_section_list_ptr->add_widget(create_audio_track_section_list());
@@ -761,20 +773,20 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_video_quality_box() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video quality:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Video quality:"), get_color_theme().text_color));
auto video_quality_box = std::make_unique<ComboBox>(&get_theme().body_font);
if(type == Type::REPLAY || type == Type::STREAM)
video_quality_box->add_item("Constant bitrate (Recommended)", "custom");
video_quality_box->add_item(TR("Constant bitrate (Recommended)"), "custom");
else
video_quality_box->add_item("Constant bitrate", "custom");
video_quality_box->add_item("Medium", "medium");
video_quality_box->add_item("High", "high");
video_quality_box->add_item(TR("Constant bitrate"), "custom");
video_quality_box->add_item(TR("Medium"), "medium");
video_quality_box->add_item(TR("High"), "high");
if(type == Type::REPLAY || type == Type::STREAM)
video_quality_box->add_item("Very high", "very_high");
video_quality_box->add_item(TR("Very high"), "very_high");
else
video_quality_box->add_item("Very high (Recommended)", "very_high");
video_quality_box->add_item("Ultra", "ultra");
video_quality_box->add_item(TR("Very high (Recommended)"), "very_high");
video_quality_box->add_item(TR("Ultra"), "ultra");
if(type == Type::REPLAY || type == Type::STREAM)
video_quality_box->set_selected_item("custom");
@@ -812,7 +824,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_video_bitrate() {
auto video_bitrate_list = std::make_unique<List>(List::Orientation::VERTICAL);
video_bitrate_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video bitrate (Kbps):", get_color_theme().text_color));
video_bitrate_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Video bitrate (Kbps):"), get_color_theme().text_color));
video_bitrate_list->add_widget(create_video_bitrate_entry());
video_bitrate_list_ptr = video_bitrate_list.get();
return video_bitrate_list;
@@ -820,15 +832,15 @@ namespace gsr {
std::unique_ptr<ComboBox> SettingsPage::create_color_range_box() {
auto color_range_box = std::make_unique<ComboBox>(&get_theme().body_font);
color_range_box->add_item("Limited", "limited");
color_range_box->add_item("Full", "full");
color_range_box->add_item(TR("Limited"), "limited");
color_range_box->add_item(TR("Full"), "full");
color_range_box_ptr = color_range_box.get();
return color_range_box;
}
std::unique_ptr<List> SettingsPage::create_color_range() {
auto color_range_list = std::make_unique<List>(List::Orientation::VERTICAL);
color_range_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Color range:", get_color_theme().text_color));
color_range_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Color range:"), get_color_theme().text_color));
color_range_list->add_widget(create_color_range_box());
color_range_list_ptr = color_range_list.get();
return color_range_list;
@@ -847,34 +859,34 @@ namespace gsr {
// TODO: Show options not supported but disable them.
// TODO: Show error if no encoders are supported.
// TODO: Show warning (once) if only software encoder is available.
video_codec_box->add_item("Auto (Recommended)", "auto");
video_codec_box->add_item(TR("Auto (Recommended)"), "auto");
if(gsr_info->supported_video_codecs.h264)
video_codec_box->add_item("H264", "h264");
video_codec_box->add_item(TR("H264"), "h264");
if(gsr_info->supported_video_codecs.hevc)
video_codec_box->add_item("HEVC", "hevc");
video_codec_box->add_item(TR("HEVC"), "hevc");
if(gsr_info->supported_video_codecs.hevc_10bit)
video_codec_box->add_item("HEVC (10 bit, reduces banding)", "hevc_10bit");
video_codec_box->add_item(TR("HEVC (10 bit, reduces banding)"), "hevc_10bit");
if(gsr_info->supported_video_codecs.hevc_hdr)
video_codec_box->add_item("HEVC (HDR)", "hevc_hdr");
video_codec_box->add_item(TR("HEVC (HDR)"), "hevc_hdr");
if(gsr_info->supported_video_codecs.av1)
video_codec_box->add_item("AV1", "av1");
video_codec_box->add_item(TR("AV1"), "av1");
if(gsr_info->supported_video_codecs.av1_10bit)
video_codec_box->add_item("AV1 (10 bit, reduces banding)", "av1_10bit");
video_codec_box->add_item(TR("AV1 (10 bit, reduces banding)"), "av1_10bit");
if(gsr_info->supported_video_codecs.av1_hdr)
video_codec_box->add_item("AV1 (HDR)", "av1_hdr");
video_codec_box->add_item(TR("AV1 (HDR)"), "av1_hdr");
if(gsr_info->supported_video_codecs.vp8)
video_codec_box->add_item("VP8", "vp8");
video_codec_box->add_item(TR("VP8"), "vp8");
if(gsr_info->supported_video_codecs.vp9)
video_codec_box->add_item("VP9", "vp9");
video_codec_box->add_item(TR("VP9"), "vp9");
if(gsr_info->supported_video_codecs.h264_software)
video_codec_box->add_item("H264 Software Encoder (Slow, not recommended)", "h264_software");
video_codec_box->add_item(TR("H264 Software Encoder (Slow, not recommended)"), "h264_software");
video_codec_box_ptr = video_codec_box.get();
return video_codec_box;
}
std::unique_ptr<List> SettingsPage::create_video_codec() {
auto video_codec_list = std::make_unique<List>(List::Orientation::VERTICAL);
video_codec_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Video codec:", get_color_theme().text_color));
video_codec_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Video codec:"), get_color_theme().text_color));
video_codec_list->add_widget(create_video_codec_box());
video_codec_ptr = video_codec_list.get();
return video_codec_list;
@@ -882,15 +894,15 @@ namespace gsr {
std::unique_ptr<ComboBox> SettingsPage::create_audio_codec_box() {
auto audio_codec_box = std::make_unique<ComboBox>(&get_theme().body_font);
audio_codec_box->add_item("Opus (Recommended)", "opus");
audio_codec_box->add_item("AAC", "aac");
audio_codec_box->add_item(TR("Opus (Recommended)"), "opus");
audio_codec_box->add_item(TR("AAC"), "aac");
audio_codec_box_ptr = audio_codec_box.get();
return audio_codec_box;
}
std::unique_ptr<List> SettingsPage::create_audio_codec() {
auto audio_codec_list = std::make_unique<List>(List::Orientation::VERTICAL);
audio_codec_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Audio codec:", get_color_theme().text_color));
audio_codec_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Audio codec:"), get_color_theme().text_color));
audio_codec_list->add_widget(create_audio_codec_box());
audio_codec_ptr = audio_codec_list.get();
return audio_codec_list;
@@ -905,27 +917,27 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_framerate() {
auto framerate_list = std::make_unique<List>(List::Orientation::VERTICAL);
framerate_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Frame rate:", get_color_theme().text_color));
framerate_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Frame rate:"), get_color_theme().text_color));
framerate_list->add_widget(create_framerate_entry());
return framerate_list;
}
std::unique_ptr<ComboBox> SettingsPage::create_framerate_mode_box() {
auto framerate_mode_box = std::make_unique<ComboBox>(&get_theme().body_font);
framerate_mode_box->add_item("Auto (Recommended)", "auto");
framerate_mode_box->add_item("Constant", "cfr");
framerate_mode_box->add_item("Variable", "vfr");
framerate_mode_box->add_item(TR("Auto (Recommended)"), "auto");
framerate_mode_box->add_item(TR("Constant"), "cfr");
framerate_mode_box->add_item(TR("Variable"), "vfr");
if(gsr_info->system_info.display_server == DisplayServer::X11)
framerate_mode_box->add_item("Sync to content", "content");
framerate_mode_box->add_item(TR("Sync to content"), "content");
else
framerate_mode_box->add_item("Sync to content (Only X11 or desktop portal capture)", "content");
framerate_mode_box->add_item(TR("Sync to content (Only X11 or desktop portal capture)"), "content");
framerate_mode_box_ptr = framerate_mode_box.get();
return framerate_mode_box;
}
std::unique_ptr<List> SettingsPage::create_framerate_mode() {
auto framerate_mode_list = std::make_unique<List>(List::Orientation::VERTICAL);
framerate_mode_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Frame rate mode:", get_color_theme().text_color));
framerate_mode_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Frame rate mode:"), get_color_theme().text_color));
framerate_mode_list->add_widget(create_framerate_mode_box());
framerate_mode_list_ptr = framerate_mode_list.get();
return framerate_mode_list;
@@ -939,7 +951,7 @@ namespace gsr {
}
std::unique_ptr<Widget> SettingsPage::create_record_cursor_section() {
auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record cursor");
auto record_cursor_checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Record cursor"));
record_cursor_checkbox->set_checked(true);
record_cursor_checkbox_ptr = record_cursor_checkbox.get();
return record_cursor_checkbox;
@@ -951,7 +963,7 @@ namespace gsr {
video_section_list->add_widget(create_video_codec());
video_section_list->add_widget(create_framerate_section());
video_section_list->add_widget(create_record_cursor_section());
return std::make_unique<Subsection>("Video", std::move(video_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
return std::make_unique<Subsection>(TR("Video"), std::move(video_section_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f));
}
std::unique_ptr<Widget> SettingsPage::create_settings() {
@@ -1032,10 +1044,10 @@ namespace gsr {
auto save_directory_button = std::make_unique<Button>(&get_theme().body_font, get_videos_dir().c_str(), mgl::vec2f(0.0f, 0.0f), mgl::Color(0, 0, 0, 120));
save_directory_button_ptr = save_directory_button.get();
save_directory_button->on_click = [this]() {
auto select_directory_page = std::make_unique<GsrPage>("File", "Settings");
select_directory_page->add_button("Save", "save", get_color_theme().tint_color);
select_directory_page->add_button("Cancel", "cancel", get_color_theme().page_bg_color);
auto select_directory_page = std::make_unique<GsrPage>(TR("File"), "Settings");
select_directory_page->add_button(TR("Save"), "save", get_color_theme().tint_color);
select_directory_page->add_button(TR("Cancel"), "cancel", get_color_theme().page_bg_color);
auto file_chooser = std::make_unique<FileChooser>(save_directory_button_ptr->get_text().c_str(), select_directory_page->get_inner_size());
FileChooser *file_chooser_ptr = file_chooser.get();
select_directory_page->add_widget(std::move(file_chooser));
@@ -1067,7 +1079,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_container_section() {
auto container_list = std::make_unique<List>(List::Orientation::VERTICAL);
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Container:", get_color_theme().text_color));
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Container:"), get_color_theme().text_color));
container_list->add_widget(create_container_box());
return container_list;
}
@@ -1089,18 +1101,18 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_replay_time() {
auto replay_time_list = std::make_unique<List>(List::Orientation::VERTICAL);
replay_time_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Replay duration in seconds:", get_color_theme().text_color));
replay_time_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Replay duration in seconds:"), get_color_theme().text_color));
replay_time_list->add_widget(create_replay_time_entry());
return replay_time_list;
}
std::unique_ptr<List> SettingsPage::create_replay_storage() {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Where should temporary replay data be stored?", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Where should temporary replay data be stored?"), get_color_theme().text_color));
auto replay_storage_button = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::HORIZONTAL);
replay_storage_button_ptr = replay_storage_button.get();
replay_storage_button->add_item("RAM", "ram");
replay_storage_button->add_item("Disk (Not recommended on SSDs)", "disk");
replay_storage_button->add_item(TR("RAM"), "ram");
replay_storage_button->add_item(TR("Disk (Not recommended on SSDs)"), "disk");
replay_storage_button->on_selection_changed = [this](const std::string&, const std::string &id) {
update_estimated_replay_file_size(id);
@@ -1113,28 +1125,29 @@ namespace gsr {
}
std::unique_ptr<RadioButton> SettingsPage::create_start_replay_automatically() {
// TODO: Support kde plasma wayland and hyprland (same ones that support getting window title)
char fullscreen_text[256];
snprintf(fullscreen_text, sizeof(fullscreen_text), "Turn on replay when starting a fullscreen application%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)");
snprintf(fullscreen_text, sizeof(fullscreen_text), TR("Turn on replay when starting a fullscreen application%s"), gsr_info->system_info.display_server == DisplayServer::X11 ? "" : TR(" (X11 applications only)"));
auto radiobutton = std::make_unique<RadioButton>(&get_theme().body_font, RadioButton::Orientation::VERTICAL);
radiobutton->add_item("Don't turn on replay automatically", "dont_turn_on_automatically");
radiobutton->add_item("Turn on replay when this program starts", "turn_on_at_system_startup");
radiobutton->add_item(TR("Don't turn on replay automatically"), "dont_turn_on_automatically");
radiobutton->add_item(TR("Turn on replay when this program starts"), "turn_on_at_system_startup");
radiobutton->add_item(fullscreen_text, "turn_on_at_fullscreen");
radiobutton->add_item("Turn on replay when power supply is connected", "turn_on_at_power_supply_connected");
radiobutton->add_item(TR("Turn on replay when power supply is connected"), "turn_on_at_power_supply_connected");
turn_on_replay_automatically_mode_ptr = radiobutton.get();
return radiobutton;
}
std::unique_ptr<CheckBox> SettingsPage::create_save_replay_in_game_folder() {
char text[256];
snprintf(text, sizeof(text), "Save video in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)");
snprintf(text, sizeof(text), TR("Save video in a folder based on the focused applications name%s"), supports_window_title ? "" : TR(" (X11 applications only)"));
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text);
save_replay_in_game_folder_ptr = checkbox.get();
return checkbox;
}
std::unique_ptr<CheckBox> SettingsPage::create_restart_replay_on_save() {
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Restart replay on save");
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Restart replay on save"));
restart_replay_on_save = checkbox.get();
return checkbox;
}
@@ -1151,7 +1164,7 @@ namespace gsr {
const double video_filesize_mb = ((double)replay_time_seconds * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024;
char buffer[256];
snprintf(buffer, sizeof(buffer), "Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.", replay_storage_type == "ram" ? "in RAM" : "on disk", video_filesize_mb);
snprintf(buffer, sizeof(buffer), TR("Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size."), replay_storage_type == "ram" ? TR("in RAM") : TR("on disk"), video_filesize_mb);
estimated_file_size_ptr->set_text(buffer);
}
@@ -1190,7 +1203,7 @@ namespace gsr {
std::unique_ptr<CheckBox> SettingsPage::create_led_indicator(const char *type) {
char label_str[256];
snprintf(label_str, sizeof(label_str), "Show %s status with scroll lock led", type);
snprintf(label_str, sizeof(label_str), TR("Show %s status with scroll lock led"), type);
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, label_str);
checkbox->set_checked(false);
@@ -1200,7 +1213,7 @@ namespace gsr {
std::unique_ptr<CheckBox> SettingsPage::create_notifications(const char *type) {
char label_str[256];
snprintf(label_str, sizeof(label_str), "Show %s notifications", type);
snprintf(label_str, sizeof(label_str), TR("Show %s notifications"), type);
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, label_str);
checkbox->set_checked(true);
show_notification_checkbox_ptr = checkbox.get();
@@ -1216,18 +1229,18 @@ namespace gsr {
std::unique_ptr<Widget> SettingsPage::create_low_power_mode() {
auto list = std::make_unique<List>(List::Orientation::HORIZONTAL, List::Alignment::CENTER);
list->set_visible(gsr_info->gpu_info.vendor == GpuVendor::AMD);
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, "Record in low-power mode");
checkbox->set_visible(gsr_info->gpu_info.vendor == GpuVendor::AMD);
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, TR("Record in low-power mode"));
low_power_mode_checkbox_ptr = checkbox.get();
list->add_widget(std::move(checkbox));
auto info = std::make_unique<Image>(&get_theme().question_mark_texture, low_power_mode_checkbox_ptr->get_size(), Image::ScaleBehavior::SCALE);
info->set_tooltip_text(
"Do not force the GPU to go into high performance mode when recording.\n"
"May affect recording performance, especially when playing a video at the same time.\n"
"If enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle."
TR("Do not force the GPU to go into high performance mode when recording.\n"
"May affect recording performance, especially when playing a video at the same time.\n"
"If enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle.")
);
Image *info_ptr = info.get();
info->on_mouse_move = [info_ptr](bool inside) {
@@ -1244,12 +1257,12 @@ namespace gsr {
void SettingsPage::add_replay_widgets() {
auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL);
auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
file_info_data_list->add_widget(create_save_directory("Directory to save replays:"));
file_info_data_list->add_widget(create_save_directory(TR("Directory to save replays:")));
file_info_data_list->add_widget(create_container_section());
file_info_data_list->add_widget(create_replay_time());
file_info_list->add_widget(std::move(file_info_data_list));
file_info_list->add_widget(create_estimated_replay_file_size());
settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("File info"), std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
general_list->add_widget(create_replay_storage());
@@ -1258,15 +1271,15 @@ namespace gsr {
general_list->add_widget(create_restart_replay_on_save());
general_list->add_widget(create_low_power_mode());
settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>("Replay indicator", create_indicator("replay"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>("Autostart", create_start_replay_automatically(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("General"), std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("Replay indicator"), create_indicator(TR("replay")), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("Autostart"), create_start_replay_automatically(), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
view_radio_button_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
view_changed(id == "advanced");
return true;
};
view_radio_button_ptr->on_selection_changed("Simple", "simple");
view_radio_button_ptr->on_selection_changed(TR("Simple"), "simple");
replay_time_entry_ptr->on_changed = [this](const std::string&) {
update_estimated_replay_file_size(replay_storage_button_ptr->get_selected_id());
@@ -1280,7 +1293,7 @@ namespace gsr {
std::unique_ptr<CheckBox> SettingsPage::create_save_recording_in_game_folder() {
char text[256];
snprintf(text, sizeof(text), "Save video in a folder with the name of the game%s", gsr_info->system_info.display_server == DisplayServer::X11 ? "" : " (X11 applications only)");
snprintf(text, sizeof(text), TR("Save video in a folder based on the focused applications name%s"), supports_window_title ? "" : TR(" (X11 applications only)"));
auto checkbox = std::make_unique<CheckBox>(&get_theme().body_font, text);
save_recording_in_game_folder_ptr = checkbox.get();
return checkbox;
@@ -1297,32 +1310,32 @@ namespace gsr {
const double video_filesize_mb_per_minute = (60.0 * (double)video_bitrate_bps) / 1000.0 / 1000.0 * 1.024;
char buffer[512];
snprintf(buffer, sizeof(buffer), "Estimated video file size per minute (excluding audio): %.2fMB", video_filesize_mb_per_minute);
snprintf(buffer, sizeof(buffer), TR("Estimated video file size per minute (excluding audio): %.2fMB"), video_filesize_mb_per_minute);
estimated_file_size_ptr->set_text(buffer);
}
void SettingsPage::add_record_widgets() {
auto file_info_list = std::make_unique<List>(List::Orientation::VERTICAL);
auto file_info_data_list = std::make_unique<List>(List::Orientation::HORIZONTAL);
file_info_data_list->add_widget(create_save_directory("Directory to save videos:"));
file_info_data_list->add_widget(create_save_directory(TR("Directory to save videos:")));
file_info_data_list->add_widget(create_container_section());
file_info_list->add_widget(std::move(file_info_data_list));
file_info_list->add_widget(create_estimated_record_file_size());
settings_list_ptr->add_widget(std::make_unique<Subsection>("File info", std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("File info"), std::move(file_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
general_list->add_widget(create_save_recording_in_game_folder());
general_list->add_widget(create_low_power_mode());
settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>("Recording indicator", create_indicator("recording"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("General"), std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("Recording indicator"), create_indicator(TR("recording")), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
view_radio_button_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
view_changed(id == "advanced");
return true;
};
view_radio_button_ptr->on_selection_changed("Simple", "simple");
view_radio_button_ptr->on_selection_changed(TR("Simple"), "simple");
video_bitrate_entry_ptr->on_changed = [this](const std::string&) {
update_estimated_record_file_size();
@@ -1331,18 +1344,18 @@ namespace gsr {
std::unique_ptr<ComboBox> SettingsPage::create_streaming_service_box() {
auto streaming_service_box = std::make_unique<ComboBox>(&get_theme().body_font);
streaming_service_box->add_item("Twitch", "twitch");
streaming_service_box->add_item("YouTube", "youtube");
streaming_service_box->add_item("Rumble", "rumble");
streaming_service_box->add_item("Kick", "kick");
streaming_service_box->add_item("Custom", "custom");
streaming_service_box->add_item(TR("Twitch"), "twitch");
streaming_service_box->add_item(TR("YouTube"), "youtube");
streaming_service_box->add_item(TR("Rumble"), "rumble");
streaming_service_box->add_item(TR("Kick"), "kick");
streaming_service_box->add_item(TR("Custom"), "custom");
streaming_service_box_ptr = streaming_service_box.get();
return streaming_service_box;
}
std::unique_ptr<List> SettingsPage::create_streaming_service_section() {
auto streaming_service_list = std::make_unique<List>(List::Orientation::VERTICAL);
streaming_service_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream service:", get_color_theme().text_color));
streaming_service_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Stream service:"), get_color_theme().text_color));
streaming_service_list->add_widget(create_streaming_service_box());
return streaming_service_list;
}
@@ -1373,7 +1386,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_stream_key_section() {
auto stream_key_list = std::make_unique<List>(List::Orientation::VERTICAL);
stream_key_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
stream_key_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Stream key:"), get_color_theme().text_color));
twitch_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
youtube_stream_key_entry_ptr = add_stream_key_entry_to_list(stream_key_list.get());
@@ -1389,7 +1402,7 @@ namespace gsr {
auto list = std::make_unique<List>(List::Orientation::VERTICAL);
auto stream_url_entry = std::make_unique<Entry>(&get_theme().body_font, "", get_theme().body_font.get_character_size() * 20);
stream_url_entry_ptr = stream_url_entry.get();
list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream URL:", get_color_theme().text_color));
list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Stream URL:"), get_color_theme().text_color));
list->add_widget(std::move(stream_url_entry));
return list;
}
@@ -1413,7 +1426,7 @@ namespace gsr {
stream_url_list->add_widget(create_stream_container());
custom_stream_list->add_widget(std::move(stream_url_list));
custom_stream_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Stream key:", get_color_theme().text_color));
custom_stream_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Stream key:"), get_color_theme().text_color));
custom_stream_list->add_widget(create_stream_custom_key());
custom_stream_list_ptr = custom_stream_list.get();
@@ -1432,7 +1445,7 @@ namespace gsr {
std::unique_ptr<List> SettingsPage::create_stream_container() {
auto container_list = std::make_unique<List>(List::Orientation::VERTICAL);
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, "Container:", get_color_theme().text_color));
container_list->add_widget(std::make_unique<Label>(&get_theme().body_font, TR("Container:"), get_color_theme().text_color));
container_list->add_widget(create_stream_container_box());
return container_list;
}
@@ -1443,13 +1456,13 @@ namespace gsr {
streaming_info_list->add_widget(create_stream_key_section());
streaming_info_list->add_widget(create_stream_custom_section());
settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming info", std::move(streaming_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("Streaming info"), std::move(streaming_info_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
auto general_list = std::make_unique<List>(List::Orientation::VERTICAL);
general_list->add_widget(create_low_power_mode());
settings_list_ptr->add_widget(std::make_unique<Subsection>("General", std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>("Streaming indicator", create_indicator("streaming"), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("General"), std::move(general_list), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
settings_list_ptr->add_widget(std::make_unique<Subsection>(TR("Streaming indicator"), create_indicator(TR("streaming")), mgl::vec2f(settings_scrollable_page_ptr->get_inner_size().x, 0.0f)));
streaming_service_box_ptr->on_selection_changed = [this](const std::string&, const std::string &id) {
const bool twitch_option = id == "twitch";
@@ -1472,7 +1485,7 @@ namespace gsr {
view_changed(id == "advanced");
return true;
};
view_radio_button_ptr->on_selection_changed("Simple", "simple");
view_radio_button_ptr->on_selection_changed(TR("Simple"), "simple");
}
void SettingsPage::on_navigate_away_from_page() {
@@ -1751,17 +1764,8 @@ namespace gsr {
if(selected_camera_setup.has_value())
webcam_box_size = clamp_keep_aspect_ratio(selected_camera_setup->resolution.to_vec2f(), webcam_box_size);
int camera_width = 0;
int camera_height = 0;
int camera_fps = 0;
sscanf(webcam_video_setup_box_ptr->get_selected_id().c_str(), "%dx%d@%dhz", &camera_width, &camera_height, &camera_fps);
record_options.webcam_source = webcam_sources_box_ptr->get_selected_id();
record_options.webcam_flip_horizontally = flip_camera_horizontally_checkbox_ptr->is_checked();
record_options.webcam_video_format = webcam_video_format_box_ptr->get_selected_id();
record_options.webcam_camera_width = camera_width;
record_options.webcam_camera_height = camera_height;
record_options.webcam_camera_fps = camera_fps;
record_options.webcam_x = std::round((webcam_box_pos.x / screen_inner_size.x) * 100.0f);
record_options.webcam_y = std::round((webcam_box_pos.y / screen_inner_size.y) * 100.0f);
record_options.webcam_width = std::round((webcam_box_size.x / screen_inner_size.x) * 100.0f);

View File

@@ -4,6 +4,7 @@
#include "../include/Process.hpp"
#include "../include/Rpc.hpp"
#include "../include/Theme.hpp"
#include "../include/Translation.hpp"
#include <signal.h>
#include <string.h>
@@ -196,6 +197,20 @@ int main(int argc, char **argv) {
mallopt(M_MMAP_THRESHOLD, 65536);
#endif
std::string resources_path;
if(access("sibs-build/linux_x86_64/debug/gsr-ui", F_OK) == 0) {
resources_path = "./";
} else {
#ifdef GSR_UI_RESOURCES_PATH
resources_path = GSR_UI_RESOURCES_PATH "/";
#else
resources_path = "/usr/share/gsr-ui/";
#endif
}
std::optional<gsr::Config> config = read_config(gsr::SupportedCaptureOptions{});
gsr::Translation::instance().init((resources_path + "translations/").c_str(), config.has_value() ? config.value().main_config.language.c_str() : nullptr);
if(geteuid() == 0) {
fprintf(stderr, "Error: don't run gsr-ui as the root user\n");
return 1;
@@ -224,17 +239,6 @@ int main(int argc, char **argv) {
set_display_server_environment_variables();
std::string resources_path;
if(access("sibs-build/linux_x86_64/debug/gsr-ui", F_OK) == 0) {
resources_path = "./";
} else {
#ifdef GSR_UI_RESOURCES_PATH
resources_path = GSR_UI_RESOURCES_PATH "/";
#else
resources_path = "/usr/share/gsr-ui/";
#endif
}
const std::string gsr_icon_path = resources_path + "images/gpu_screen_recorder_logo.png";
auto rpc = std::make_unique<gsr::Rpc>();
@@ -249,7 +253,7 @@ int main(int argc, char **argv) {
} else {
fprintf(stderr, "Error: failed to send command to running gsr-ui instance, user will have to open the UI manually with Alt+Z\n");
const char *args[] = {
"gsr-notify", "--text", "Another instance of GPU Screen Recorder UI is already running.\nPress Alt+Z to open the UI.", "--timeout", "5.0",
"gsr-notify", "--text", TR("Another instance of GPU Screen Recorder UI is already running.\nPress Alt+Z to open the UI."), "--timeout", "5.0",
"--icon-color", "ffffff", "--icon", gsr_icon_path.c_str(), "--bg-color", "ff0000", nullptr
};
gsr::exec_program_daemonized(args);
@@ -262,7 +266,7 @@ int main(int argc, char **argv) {
if(gsr::pidof("gpu-screen-recorder", -1) != -1) {
const char *args[] = {
"gsr-notify", "--text", "GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.",
"gsr-notify", "--text", TR("GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI."),
"--timeout", "5.0", "--icon-color", "ffffff", "--icon", gsr_icon_path.c_str(), "--bg-color", "ff0000", nullptr
};
gsr::exec_program_daemonized(args);

View File

@@ -0,0 +1,83 @@
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
static void print_window_title(const char *window_title) {
if (window_title[0] == 0) {
printf("Window title changed: %s\n", "Desktop");
fflush(stdout);
return;
}
printf("Window title changed: %s\n", window_title);
fflush(stdout);
}
static int handle_ipc(const char *hyprland_socket_path) {
const int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Error: gsr-hyprland-helper: socket");
return 1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if (snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", hyprland_socket_path) >= (int)sizeof(addr.sun_path)) {
fprintf(stderr, "Error: gsr-hypland-helper: path to hyprland socket (%s) is more than %d characters long\n", hyprland_socket_path, (int)sizeof(addr.sun_path));
return false;
}
if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("Error: gsr-hyprland-helper: connect");
close(sock_fd);
return 1;
}
fprintf(stderr, "Info: gsr-hyprland-helper: connected to Hyprland socket: %s\n", addr.sun_path);
FILE *sock_file = fdopen(sock_fd, "r");
if (!sock_file) {
perror("Error: gsr-hyprland-helper: fdopen");
close(sock_fd);
return 1;
}
char buffer[1024];
while (fgets(buffer, sizeof(buffer), sock_file)) {
int line_len = strlen(buffer);
if(line_len > 0 && buffer[line_len - 1] == '\n') {
buffer[line_len - 1] = '\0';
line_len -= 1;
}
if(line_len >= 14 && memcmp(buffer, "activewindow>>", 14) == 0) {
char *window_id = buffer + 14;
char *window_title = strchr(buffer + 14, ',');
if(!window_title)
continue;
window_title[0] = '\0';
window_title += 1;
if(strcmp(window_id, "gsr-ui") == 0 || strcmp(window_id, "gsr-notify") == 0)
continue;
print_window_title(window_title);
}
}
fclose(sock_file);
return 0;
}
int main(int argc, char **argv) {
if(argc != 2) {
fprintf(stderr, "usage: gsr-hyprland-helper <hyprland-socket-path>\n");
return 1;
}
return handle_ipc(argv[1]);
}

View File

@@ -0,0 +1,58 @@
const DAEMON_DBUS_NAME = "com.dec05eba.gpu_screen_recorder";
// utils
function sendNewActiveWindowTitle(title) {
callDBus(
DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME,
"setActiveWindowTitle", title
);
}
function sendNewActiveWindowFullscreen(isFullscreen) {
callDBus(
DAEMON_DBUS_NAME, "/", DAEMON_DBUS_NAME,
"setActiveWindowFullscreen", isFullscreen
);
}
// track handlers to avoid duplicates
const windowEventHandlers = new Map();
function subscribeToClient(client) {
if (!client || windowEventHandlers.has(client)) return;
const emitActiveTitle = () => {
if (workspace.activeWindow === client) {
sendNewActiveWindowTitle(client.caption || "");
}
};
const emitActiveFullscreen = () => {
if (workspace.activeWindow === client) {
sendNewActiveWindowFullscreen(client.fullScreen);
}
};
windowEventHandlers.set(client, {
title: emitActiveTitle,
fs: emitActiveFullscreen,
});
client.captionChanged.connect(emitActiveTitle);
client.fullScreenChanged.connect(emitActiveFullscreen);
}
function updateActiveWindow(client) {
if (!client) return;
sendNewActiveWindowTitle(client.caption || "");
sendNewActiveWindowFullscreen(client.fullScreen);
subscribeToClient(client);
}
// handle window focus changes
workspace.windowActivated.connect(updateActiveWindow);
// handle initial state
if (workspace.activeWindow) {
updateActiveWindow(workspace.activeWindow);
}

View File

@@ -0,0 +1,233 @@
#include <dbus/dbus.h>
#include <unistd.h>
#include <iostream>
#include <string>
static const char* INTROSPECTION_XML =
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\"\n"
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n"
"<node>\n"
" <interface name='com.dec05eba.gpu_screen_recorder'>\n"
" <method name='setActiveWindowTitle'>\n"
" <arg type='s' name='title' direction='in'/>\n"
" </method>\n"
" </interface>\n"
" <interface name='org.freedesktop.DBus.Introspectable'>\n"
" <method name='Introspect'>\n"
" <arg type='s' name='data' direction='out'/>\n"
" </method>\n"
" </interface>\n"
"</node>\n";
class GsrKwinHelper {
public:
std::string active_window_title;
DBusConnection* connection = nullptr;
DBusError err;
bool init() {
dbus_error_init(&err);
connection = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
std::cerr << "Error: gsr-kwin-helper: failed to connect to session bus: " << err.message << "\n";
dbus_error_free(&err);
return false;
}
if (!connection) {
std::cerr << "Error: gsr-kwin-helper: connection is null\n";
return false;
}
int ret = dbus_bus_request_name(connection, "com.dec05eba.gpu_screen_recorder",
DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if (dbus_error_is_set(&err)) {
std::cerr << "Error: gsr-kwin-helper: failed to request name: " << err.message << "\n";
dbus_error_free(&err);
return false;
}
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
std::cerr << "Error: gsr-kwin-helper: not primary owner of the name\n";
return false;
}
std::cerr << "Info: gsr-kwin-helper: DBus server initialized on com.dec05eba.gpu_screen_recorder\n";
const bool inside_flatpak = access("/app/manifest.json", F_OK) == 0;
const char *helper_path =
!inside_flatpak
? KWIN_HELPER_SCRIPT_PATH
: "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/share/gsr-ui/gsrkwinhelper.js";
std::cerr << "Info: gsr-kwin-helper: KWin script path: " << helper_path << std::endl;
if (!load_kwin_script(connection, helper_path)) {
std::cerr << "Warning: gsr-kwin-helper: failed to load KWin script\n";
}
return true;
}
void run() {
while (true) {
dbus_connection_read_write(connection, 100);
DBusMessage* msg = dbus_connection_pop_message(connection);
if (!msg) {
continue;
}
if (dbus_message_is_method_call(msg, "org.freedesktop.DBus.Introspectable", "Introspect")) {
handle_introspect(msg);
} else if (dbus_message_is_method_call(msg, "com.dec05eba.gpu_screen_recorder", "setActiveWindowTitle")) {
handle_set_title(msg);
}
dbus_message_unref(msg);
}
}
void handle_introspect(DBusMessage* msg) {
DBusMessage* reply = dbus_message_new_method_return(msg);
if (!reply) return;
DBusMessageIter args;
dbus_message_iter_init_append(reply, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &INTROSPECTION_XML)) {
dbus_message_unref(reply);
return;
}
dbus_connection_send(connection, reply, nullptr);
dbus_connection_flush(connection);
dbus_message_unref(reply);
}
void handle_set_title(DBusMessage* msg) {
DBusMessageIter args;
const char* title = nullptr;
if (!dbus_message_iter_init(msg, &args)) {
send_error_reply(msg, "No arguments provided");
return;
}
if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING) {
send_error_reply(msg, "Expected string argument");
return;
}
dbus_message_iter_get_basic(&args, &title);
if (title) {
active_window_title = title;
std::cout << "Active window title set to: " << active_window_title << "\n";
std::cout.flush();
send_success_reply(msg);
} else {
send_error_reply(msg, "Failed to read string");
}
}
void send_success_reply(DBusMessage* msg) {
DBusMessage* reply = dbus_message_new_method_return(msg);
if (reply) {
dbus_connection_send(connection, reply, nullptr);
dbus_connection_flush(connection);
dbus_message_unref(reply);
}
}
void send_error_reply(DBusMessage* msg, const char* error_msg) {
DBusMessage* reply = dbus_message_new_error(msg, "com.dec05eba.gpu_screen_recorder.Error", error_msg);
if (reply) {
dbus_connection_send(connection, reply, nullptr);
dbus_connection_flush(connection);
dbus_message_unref(reply);
}
}
bool call_kwin_method(DBusConnection* conn, const char* method,
const char* arg1 = nullptr, const char* arg2 = nullptr) {
DBusMessage* msg = dbus_message_new_method_call(
"org.kde.KWin",
"/Scripting",
"org.kde.kwin.Scripting",
method
);
if (!msg) {
std::cerr << "Error: gsr-kwin-helper: failed to create message for " << method << "\n";
return false;
}
if (arg1) {
dbus_message_append_args(msg, DBUS_TYPE_STRING, &arg1, DBUS_TYPE_INVALID);
if (arg2) {
dbus_message_append_args(msg, DBUS_TYPE_STRING, &arg2, DBUS_TYPE_INVALID);
}
}
DBusError err;
dbus_error_init(&err);
// Send message and wait for reply (with 1 second timeout)
DBusMessage* reply = dbus_connection_send_with_reply_and_block(conn, msg, 1000, &err);
dbus_message_unref(msg);
if (dbus_error_is_set(&err)) {
std::cerr << "Error: gsr-kwin-helper: error calling " << method << ": " << err.message << "\n";
dbus_error_free(&err);
return false;
}
if (reply) {
dbus_message_unref(reply);
}
return true;
}
bool load_kwin_script(DBusConnection* conn, const char* script_path) {
// Unload existing script
call_kwin_method(conn, "unloadScript", "gsrkwinhelper");
if (!call_kwin_method(conn, "loadScript", script_path, "gsrkwinhelper")) {
std::cerr << "Error: gsr-kwin-helper: failed to load KWin script\n";
return false;
}
if (!call_kwin_method(conn, "start")) {
std::cerr << "Error: gsr-kwin-helper: failed to start KWin script\n";
return false;
}
std::cerr << "Info: gsr-kwin-helper: KWin script loaded and started successfully\n";
return true;
}
~GsrKwinHelper() {
if (connection) {
dbus_bus_release_name(connection, "com.dec05eba.gpu_screen_recorder", nullptr);
dbus_connection_unref(connection);
}
}
};
int main() {
GsrKwinHelper helper;
if (!helper.init()) {
return 1;
}
helper.run();
return 0;
}

412
translations/es.txt Normal file
View File

@@ -0,0 +1,412 @@
# GPU Screen Recorder UI - Spanish translation
# General UI
Record=Grabar
Instant Replay=Repetición Instantánea
Livestream=Transmisión
Settings=Ajustes
# Status messages
Off=Desactivado
On=Activado
Not recording=No grabando
Recording=Grabando
Not streaming=No transmitiendo
Streaming=Transmitiendo
Paused=Pausado
# Button labels
Start=Iniciar
Stop=Detener
Stop and save=Detener y guardar
Pause=Pausar
Unpause=Reanudar
Save=Guardar
Save 1 min=Guardar 1 min
Save 10 min=Guardar 10 min
Turn on=Activar
Turn off=Desactivar
# Notifications - Recording
Recording has been paused=La grabación se ha pausado
Recording has been unpaused=La grabación se ha reanudado
Started recording %s=Iniciando la grabación %s
Saved a %s recording of %s\nto "%s"=Se guardó una grabación de %s de %s\nen "%s"
Saved a %s recording of %s=Se guardó una grabación de %s de %s
Failed to start/save recording=Fallo al iniciar/guardar grabación
# Notifications - Replay
Replay stopped=Repetición detenida
Started replaying %s=Iniciando la repetición %s
Saving replay, this might take some time=Guardando repetición, esto podría tardar un rato
Saved a %s replay of %s\nto "%s"=Se guardó una repetición de %s de %s\nen "%s"
Saved a %s replay of %s=Se guardó una repetición de %s de %s
Replay stopped because of an error=Repetición detenida debido a un error
Replay settings have been modified.\nYou may need to restart replay to apply the changes.=Los ajustes de repetición han sido modificados.\nUn reinicio puede ser necesario para aplicar los cambios.
# Notifications - Streaming
Streaming has stopped=La transmisión se ha detenido
Started streaming %s=Iniciando la transmisión %s
Streaming stopped because of an error=Transmisión detenida debido a un error
Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.=Los ajustes de transmisión han sido modificados.\nUn reinicio puede ser necesario para aplicar los cambios.
# Notifications - Screenshot
Saved a screenshot of %s\nto "%s"=Captura de pantalla de %s\nguardada en "%s"
Saved a screenshot of %s=Captura de pantalla de %s guardada
Failed to take a screenshot=Fallo al tomar la captura de pantalla
# Error messages
Another instance of GPU Screen Recorder UI is already running.\nPress Alt+Z to open the UI.=Ya hay otra instancia de GPU Screen Recorder UI en ejecución.\nPulsa Alt-Z para abrir la interfaz.
GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.=GPU Screen Recorder ya está en ejecución en otro proceso.\nPor favor, ciérralo antes de usar GPU Screen Recorder UI.
Failed to start replay, capture target "%s" is invalid.\nPlease change capture target in settings=Fallo al iniciar la repetición, el objetivo de captura "%s" es inválido.\nPor favor, cambia el objetivo de captura en los ajustes
Failed to start recording, capture target "%s" is invalid.\nPlease change capture target in settings=Fallo al iniciar la grabación, el objetivo de captura "%s" es inválido.\nPor favor, cambia el objetivo de captura en los ajustes
Failed to start streaming, capture target "%s" is invalid.\nPlease change capture target in settings=Fallo al iniciar la transmisión, el objetivo de captura "%s" es inválido.\nPor favor, cambia el objetivo de captura en los ajustes
Failed to take a screenshot, capture target "%s" is invalid.\nPlease change capture target in settings=Fallo al tomar la captura de pantalla, el objetivo de captura "%s" es inválido.\nPor favor, cambia el objetivo de captura en los ajustes
Unable to start recording when replay is turned on.\nTurn off replay before starting recording.=No se puede grabar cuando la repetición está activada.\nApaga la repetición antes de empezar a grabar.
Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.=No se puede transmitir cuando la repetición está activada.\nApaga la repetición antes de empezar a transmitir.
Unable to start streaming when recording.\nStop recording before starting streaming.=No se puede transmitir mientras se está grabando. Detén la grabación antes de empezar a transmitir.
Unable to start recording when streaming.\nStop streaming before starting recording.=No se puede grabar mientras se está transmitiendo. Detén la transmisión antes de empezar a grabar.
Unable to start replay when recording.\nStop recording before starting replay.=No se puede iniciar la repetición mientras se está grabando. Detén la grabación antes de iniciar la repetición.
Unable to start replay when streaming.\nStop streaming before starting replay.=No se puede iniciar la repetición mientras se está transmitiendo. Detén la transmisión antes de iniciar la repetición.
Started recording in the replay session=Grabación iniciada en la sesión de repetición
Started recording in the streaming session=Grabación iniciada en la sesión de transmisión
Failed to start region capture=Fallo al iniciar la captura de región
Failed to start window capture=Fallo al iniciar la captura de ventana
No window selected=Ventana no seleccionada
Streaming stopped because of an error. Verify if settings are correct=Transmisión detenida debido a un error. Comprueba que los ajustes sean correctos
%s. Verify if settings are correct=%s. Comprueba que los ajustes sean correctos
# GPU Screen Recorder errors
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.=Falló la captura del portal de escritorio.\nO cancelaste el portal de escritorio, o tu compositor de Wayland no admite la captura del portal de escritorio\no está configurado incorrectamente en tu sistema.
Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.=Falló la captura del monitor.\nEl monitor que intentas capturar no es válido.\nPor favor, valida la configuración de captura.
Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.=Falló la captura. Ni los códecs de video H264, HEVC ni AV1 son compatibles\ncon tu sistema o estás intentando capturar a una resolución superior a la que tu\nsistema admite para cada códec de video.
Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.=Falló la captura. Tu sistema no admite la resolución a la que intentas\ngrabar con el códec de video que has elegido.\nCambia la resolución de captura o el códec de video e inténtalo de nuevo.\nNota: AV1 admite la resolución más alta, luego HEVC y después H264.
Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.=Falló la captura. Tu sistema no es compatible con el códec de video que has elegido.\nCambia el códec de video e inténtalo de nuevo
Stopped capture because the user canceled the desktop portal=Se detuvo la captura porque el usuario canceló el portal de escritorio
Failed to take a screenshot. Verify if settings are correct=Fallo al tomar una captura de pantalla. Verifica si la configuración es correcta
# Launch errors
Failed to launch gpu-screen-recorder to start replay=Fallo al lanzar gpu-screen-recorder para iniciar la repetición
Failed to launch gpu-screen-recorder to start recording=Fallo al lanzar gpu-screen-recorder para iniciar la grabación
Failed to launch gpu-screen-recorder to start streaming=Fallo al lanzar gpu-screen-recorder para iniciar la transmisión
Failed to launch gpu-screen-recorder to take a screenshot=Fallo al lanzar gpu-screen-recorder para tomar una captura de pantalla
# System startup notifications
Failed to add GPU Screen Recorder to system startup=Fallo al añadir GPU Screen Recorder al inicio del sistema
Failed to remove GPU Screen Recorder from system startup=Fallo al eliminar GPU Screen Recorder del inicio del sistema
Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add "gsr-ui" to system startup on systems that uses another init system.=Fallo al añadir GPU Screen Recorder al inicio del sistema.\nEsta opción solo funciona en sistemas que usan systemd.\nTienes que añadir "gsr-ui" manualmente al inicio del sistema en aquellos que usen otro sistema de arranque.
# Wayland warning
Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues.=Wayland no ofrece soporte adecuado para GPU Screen Recorder UI;\nes posible que el funcionamiento no sea el esperado. Si experimentas problemas, utiliza X11.
# Hotkey conflicts
Some keyboard remapping software conflicts with GPU Screen Recorder on your system.\nKeyboards have been ungrabbed, applications will now receive the hotkeys you press.=Algún programa de reasignación de teclas entra en conflicto con GPU Screen Recorder en tu sistema.\nSe ha liberado la captura del teclado, las aplicaciones ahora recibirán las teclas de acceso rápido que presiones.
# Capture targets
this monitor=este monitor
window=ventana
window "%s"=ventana "%s"
window %s=ventana %s
focused=activa
region=región
portal=portal
# Time durations (used in recording/replay saved notifications)
%d second=%d segundo
%d minute=%d minuto
%d hour=%d hora
%d seconds=%d segundos
%d minutes=%d minutos
%d hours=%d horas
# Global Settings Page UI elements
Accent color=Color de acento
Red=Rojo
Green=Verde
Blue=Azul
Start program on system startup?=¿Iniciar programa al iniciar el sistema?
Yes=Sí
No=No
Enable keyboard hotkeys?=¿Habilitar las teclas de acceso rápido?
Yes, but only grab virtual devices (supports some input remapping software)=Sí, pero capturar solo dispositivos virtuales (ofrece soporte para algunos prog. de reasig. de teclas)
Yes, but don't grab devices (supports all input remapping software)=Sí, pero sin capturar dispositivos (ofrece soporte para todos los programas de reasignación de teclas)
Show/hide UI:=Mostrar/ocultar interfaz
Turn replay on/off:=Activar/desactivar repetición
Save replay:=Guardar repetición
Save 1 minute replay:=Guardar repetición de 1 minuto
Save 10 minute replay:=Guardar repetición de 10 minutos
Start/stop recording:=Iniciar/detener grabación
Pause/unpause recording:=Pausar/reanudar grabación
Start/stop recording a region:=Iniciar/detener grabación de una región
Start/stop streaming:=Iniciar/detener transmisión
Take a screenshot:=Tomar una captura de pantalla
Take a screenshot of a region:=Tomar una captura de pantalla de una región
Start/stop recording with desktop portal:=Iniciar/detener grabación con portal de escritorio
Take a screenshot with desktop portal:=Tomar una captura de pantalla con portal de escritorio
Start/stop recording a window:=Iniciar/detener grabación de una ventana
Take a screenshot of a window:=Tomar una captura de pantalla de una ventana
Clear hotkeys=Borrar teclas de acceso rápido
Reset hotkeys to default=Reestablecer teclas de acceso rápido a los valores predeterminados
Enable controller hotkeys?=¿Habilitar las teclas de acceso rápido para mando?
Press=Pulsa
and=y
Notification speed=Velocidad de notificación
Normal=Normal
Fast=Rápida
Language=Idioma
System language=Idioma del sistema
Exit program=Salir del programa
Go back to the old UI=Volver a la interfaz antigua
If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:=Si deseas hacer una donación, puedes hacerlo en https://buymeacoffee.com/dec05eba:
Donate=Donar
All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.=Todas las donaciones se destinan al desarrollo de software (incluido GPU Screen Recorder)\ny a la compra de hardware para probar el software.
# Subsection headers
Global=Global
Back=Atrás
Appearance=Apariencia
Startup=Inicio
Keyboard hotkeys=Teclas de acceso rápido
Controller hotkeys=Teclas de acceso rápido para mando
Application options=Opciones de la aplicación
Application info=Información de la aplicación
Donate=Donar
# Version info strings
GSR version: %s=GSR versión: %s
GSR-UI version: %s=GSR-UI versión: %s
Flatpak version: %s=Flatpak versión: %s
GPU vendor: %s=Proveedor de GPU: %s
# Hotkey configuration dialog
Press a key combination to use for the hotkey: "%s"=Pulsa una combinación de teclas para el acceso rápido: "%s"
Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey.=Las teclas alfanuméricas no se pueden usar solas en los atajos de teclado, deben usarse con una o más de estas teclas: Alt, Ctrl, Mayús y Super.\nPulsa Esc para cancelar o Retroceso para eliminar el atajo de teclado.
# Hotkey action names (without colons - these appear in the dialog)
Show/hide UI=Mostrar/ocultar interfaz
Turn replay on/off=Activar/desactivar repetición
Save replay=Guardar repetición
Save 1 minute replay=Guardar repetición de 1 minuto
Save 10 minute replay=Guardar repetición de 10 minutos
Start/stop recording=Iniciar/detener grabación
Pause/unpause recording=Pausar/reanudar grabación
Start/stop recording a region=Iniciar/detener grabación de una región
Start/stop recording a window=Iniciar/detener grabación de una ventana
Start/stop recording with desktop portal=Iniciar/detener grabación con un portal de escritorio
Start/stop streaming=Iniciar/detener transmisión
Take a screenshot=Tomar una captura de pantalla
Take a screenshot of a region=Tomar una captura de pantalla de una región
Take a screenshot of a window=Tomar una captura de pantalla de una ventana
Take a screenshot with desktop portal=Tomar una captura de pantalla con un portal de escritorio
# Controller hotkey descriptions
to show/hide the UI=para mostrar/ocultar la interfaz
to take a screenshot=para tomar una captura de pantalla
to save a replay=para guardar una repetición
to start/stop recording=para iniciar/detener una grabación
to turn replay on/off=para activar/desactivar la repetición
to save a 1 minute replay=para guardar una repetición de 1 minuto
to save a 10 minute replay=para guardar una repetición de 10 minutos
# Error message for duplicate hotkey
The hotkey %s is already used for something else=La combinación de teclas %s ya está en uso para otra cosa
# Screenshot settings page
Screenshot=Captura de pantalla
Capture=Captura
Image=Imagen
File info=Información del archivo
General=General
Screenshot indicator=Indicador de captura de pantalla
Script=Script
File=Archivo
Back=Atrás
Save=Guardar
Cancel=Cancelar
Capture source:=Fuente de captura:
Window=Ventana
Region=Región
Desktop portal=Portal de escritorio
Monitor %s (%dx%d)=Monitor %s (%dx%d)
Screen=Pantalla
Image resolution limit:=Límite de resolución de imagen:
Change image resolution=Cambiar la resolución de imagen
Restore portal session=Restaurar sesión de portal
Image quality:=Calidad de imagen
Medium=Media
High=Alta
Very high (Recommended)=Muy alta (recomendado)
Ultra=Ultra
Record cursor=Grabar cursor
Directory to save screenshots:=Directorio para guardar capturas de pantalla:
Image format:=Formato de imagen:
Save screenshot in a folder based on the focused applications name=Guardar captura de pantalla en un directorio basado en el nombre de la aplicación activa
Save screenshot to clipboard=Guardar captura de pantalla en el portapapeles
Save screenshot to clipboard (Not supported properly by Wayland)=Guardar captura de pantalla en el portapapeles (no admitido por Wayland)
Save screenshot to disk=Guardar la captura de pantalla en el disco
Show screenshot notifications=Mostrar notificaciones de captura de pantalla
Blink scroll lock led when taking a screenshot=Hacer parpadear el LED de Bloq Despl al tomar una captura de pantalla
Command to open the screenshot with:=Abrir la captura de pantalla con el comando:
# Settings Page UI elements - дополнения
# View modes
Simple view=Vista simple
Advanced view=Vista avanzada
# Capture settings
Follow focused window=Seguir la ventana activa
Focused monitor=Monitor activo
Area size:=Tamaño del área:
Video resolution limit:=Límite de resolución de vídeo:
Change video resolution=Cambiar la resolución de vídeo
Restore portal session=Restaurar sesión de portal
# Webcam settings
Webcam=Webcam
Webcam source:=Fuente de la webcam:
None=Ninguna
Video format:=Formato de vídeo:
Auto (recommended)=Auto (recomendado)
YUYV=YUYV
Motion-JPEG=Motion-JPEG
Video setup:=Configuración de vídeo:
* Right click in the bottom right corner to resize the webcam=* Clic derecho en la esquina inferior derecha para cambiar el tamaño de la webcam
Flip camera horizontally=Voltear la cámara horizontalmente
# Audio settings
Audio=Sonido
Audio codec:=Códec de sonido
Opus (Recommended)=Opus (recomendado)
AAC=AAC
Directory to save videos:=Directorio donde guardar los vídeos:
Output device:=Dispositivo de salida:
Input device: =Dispositivo de entrada:
# yes, these spaces are intentional
Application: =Aplicación:
Custom...=Personalizado...
Save video in a folder based on the focused applications name%s=Guardar el vídeo en un directorio basado en el nombre de la aplicación activa
(X11 applications only)= (solo para aplicaciones X11)
Add audio track=Añadir pista de sonido
Add input device=Añadir dispositivo de entrada
Add output device=Añadir dispositivo de salida
Add application audio=Añadir sonido de aplicación
Record audio from all applications except the selected ones=Grabar el sonido de todas las aplicaciones salvo las seleccionadas
Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices.=Grabar los dispositivos de salida y el audio de las aplicaciones puede que grabe todo el audio de salida, lo cual probablemente\nno es lo que quieres hacer. Elimina los dispositivos de salida.
Video=Vídeo
# Video codec settings
Video codec:=Códec de vídeo:
H264=H264
HEVC=HEVC
HEVC (10 bit, reduces banding)=HEVC (10 bits, reduce el banding)
HEVC (HDR)=HEVC (HDR)
AV1=AV1
AV1 (10 bit, reduces banding)=AV1 (10 bits, reduce el banding)
AV1 (HDR)=AV1 (HDR)
VP8=VP8
VP9=VP9
H264 Software Encoder (Slow, not recommended)=Codificador por software H264 (lento, no recomendado)
# Video quality and bitrate
Video quality:=Calidad de vídeo:
Very high=Muy alta
Video bitrate (Kbps):=Tasa de bits del vídeo (Kbps)
Constant bitrate=Tasa de bits constante
Constant bitrate (Recommended)=Tasa de bits constante (recomendado)
# Frame rate settings
Frame rate:=Tasa de fotogramas:
Frame rate mode:=Modo de tasa de fotogramas:
Auto (Recommended)=Automático (ecomendado)
Constant=Constante
Variable=Variable
Sync to content=Sincronizar con el contenido
Sync to content (Only X11 or desktop portal capture)=Sincronizar con el contenido (solo para X11 o captura de portal de escritorio)
# Color range
Color range:=Rango de color
Limited=Limitado
Full=Completo
# Container format
Container:=Contenedor:
# Recording settings
Record in low-power mode=Grabar en modo de bajo consumo
Record cursor=Grabar cursor
Do not force the GPU to go into high performance mode when recording.\nMay affect recording performance, especially when playing a video at the same time.\nIf enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle.=No forzar a la GPU a entrar en modo de alto rendimiento al grabar.\nPuede afectar al rendimiento de la grabación, especialmente al reproducir un vídeo al mismo tiempo.\nSi se activa, se recomienda usar el modo de sincronización con la velocidad de fotogramas del contenido para reducir el consumo de energía cuando está inactiva.
Show %s notifications=Mostrar notificación de %s
Show %s status with scroll lock led=Mostrar estado de %s con el LED de Bloq Despl
Recording indicator=Indicador de grabación
recording=grabando
Simple=Simple
Audio track #%d=Pista de sonido #%d
Output device=Dispositivo de salida
Input device: =Dispositivo de entrada:
Estimated video file size per minute (excluding audio): %.2fMB=Tamaño estimado del archivo de vídeo por minuto (sin sonido): %.2fMB
# Replay settings
Directory to save replays:=Directorio para guardar las repeticiones:
Replay indicator=Indicador de repetición
replay=repetición
Turn on replay when starting a fullscreen application%s=Activar la repetición al iniciar una aplicación a pantalla completa%s
Autostart=Inicio automático
in RAM=en RAM
Replay duration in seconds:=Duración de la repetición en segundos:
Where should temporary replay data be stored?=¿Dónde se deben almacenar los archivos temporales de la repetición?
RAM=RAM
Disk (Not recommended on SSDs)=Disco (no recomendado en SSDs)
Turn on replay when this program starts=Activar la repetición cuando se inicie este programa
Turn on replay when power supply is connected=Activar la repetición cuando se conecte la fuente de alimentación
Don't turn on replay automatically=No activar la repetición automáticamente
Restart replay on save=Reiniciar la repetición al guardar
Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.=Tamaño máximo estimado del archivo de vídeo %s: %.2fMB.\nCambia la tasa de bits del vídeo o la duración de la repetición para cambiar el tamaño del archivo.
# Streaming settings
Stream service:=Servicio de transmisión
Twitch=Twitch
YouTube=YouTube
Kick=Kick
Rumble=Rumble
Custom=Personalizado
Stream URL:=URL de la transmisión
Stream key:=Clave de transmisión
Streaming info=Información de transmisión
Streaming indicator=Indicador de transmisión
streaming=transmisión

411
translations/ru.txt Normal file
View File

@@ -0,0 +1,411 @@
# GPU Screen Recorder UI - Russian Translation
# General UI
Record=Запись
Instant Replay=Мгновенный повтор
Livestream=Прямая трансляция
Settings=Настройки
# Status messages
Off=Выкл
On=Вкл
Not recording=Запись неактивна
Recording=Идёт запись
Not streaming=Трансляция неактивна
Streaming=Идёт трансляция
Paused=Приостановлено
# Button labels
Start=Пуск
Stop=Остановить
Stop and save=Стоп и сохранить
Pause=Пауза
Unpause=Продолжить
Save=Сохранить
Save 1 min=Сохр. 1 мин
Save 10 min=Сохр. 10 мин
Turn on=Включить
Turn off=Выключить
# Notifications - Recording
Recording has been paused=Запись приостановлена
Recording has been unpaused=Запись возобновлена
Started recording %s=Начата запись %s
Saved a %s recording of %s\nto "%s"=Сохранено %s записи %s\nв "%s"
Saved a %s recording of %s=Сохранено %s записи %s
Failed to start/save recording=Не удалось начать/сохранить запись
# Notifications - Replay
Replay stopped=Повтор остановлен
Started replaying %s=Начат повтор %s
Saving replay, this might take some time=Сохранение повтора, это может занять некоторое время
Saved a %s replay of %s\nto "%s"=Сохранено %s повтора %s\nв "%s"
Saved a %s replay of %s=Сохранено %s повтора %s
Replay stopped because of an error=Повтор остановлен из-за ошибки
Replay settings have been modified.\nYou may need to restart replay to apply the changes.=Настройки повтора изменены.\nВозможно, потребуется перезапустить повтор для применения изменений.
# Notifications - Streaming
Streaming has stopped=Трансляция остановлена
Started streaming %s=Начата трансляция %s
Streaming stopped because of an error=Трансляция остановлена из-за ошибки
Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.=Настройки трансляции изменены.\nВозможно, потребуется перезапустить трансляцию для применения изменений.
# Notifications - Screenshot
Saved a screenshot of %s\nto "%s"=Сохранён снимок %s\nв "%s"
Saved a screenshot of %s=Сохранён снимок %s
Failed to take a screenshot=Не удалось сделать снимок экрана
# Error messages
Another instance of GPU Screen Recorder UI is already running.\nPress Alt+Z to open the UI.=Один экземпляр GPU Screen Recorder UI уже запущен.\nНажмите Alt+Z, чтобы открыть интерфейс.
GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.=GPU Screen Recorder CLI уже запущен в другом процессе.\nПожалуйста, закройте его перед использованием GPU Screen Recorder UI.
Failed to start replay, capture target "%s" is invalid.\nPlease change capture target in settings=Не удалось начать повтор, цель захвата "%s" недействительна.\nПожалуйста, измените цель захвата в настройках
Failed to start recording, capture target "%s" is invalid.\nPlease change capture target in settings=Не удалось начать запись, цель захвата "%s" недействительна.\nПожалуйста, измените цель захвата в настройках
Failed to start streaming, capture target "%s" is invalid.\nPlease change capture target in settings=Не удалось начать трансляцию, цель захвата "%s" недействительна.\nПожалуйста, измените цель захвата в настройках
Failed to take a screenshot, capture target "%s" is invalid.\nPlease change capture target in settings=Не удалось сделать снимок экрана, цель захвата "%s" недействительна.\nПожалуйста, измените цель захвата в настройках
Unable to start recording when replay is turned on.\nTurn off replay before starting recording.=Невозможно начать запись при включённом повторе.\nВыключите повтор перед началом записи.
Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.=Невозможно начать трансляцию при включённом повторе.\nВыключите повтор перед началом трансляции.
Unable to start streaming when recording.\nStop recording before starting streaming.=Невозможно начать трансляцию при записи.\nОстановите запись перед началом трансляции.
Unable to start recording when streaming.\nStop streaming before starting recording.=Невозможно начать запись при трансляции.\nОстановите трансляцию перед началом записи.
Unable to start replay when recording.\nStop recording before starting replay.=Невозможно начать повтор при записи.\nОстановите запись перед началом повтора.
Unable to start replay when streaming.\nStop streaming before starting replay.=Невозможно начать повтор при трансляции.\nОстановите трансляцию перед началом повтора.
Started recording in the replay session=Начата запись в сеансе повтора
Started recording in the streaming session=Начата запись в сеансе трансляции
Failed to start region capture=Не удалось начать захват региона
Failed to start window capture=Не удалось начать захват окна
No window selected=Окно не выбрано
Streaming stopped because of an error. Verify if settings are correct=Трансляция остановлена из-за ошибки. Проверьте правильность настроек
%s. Verify if settings are correct=%s. Проверьте правильность настроек
# GPU Screen Recorder errors
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.=Захват портала рабочего стола не удался.\nВы либо отменили портал рабочего стола, либо ваш композитор Wayland не поддерживает захват портала рабочего стола\nили он неправильно настроен в вашей системе.
Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.=Захват монитора не удался.\nМонитор, который вы пытаетесь захватить, недействителен.\nПожалуйста, проверьте настройки захвата.
Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.=Захват не удался. Ни один из видеокодеков H264, HEVC или AV1 не поддерживается\nвашей системой или вы пытаетесь захватить видео с разрешением выше, чем\nподдерживает ваша система для каждого видеокодека.
Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.=Захват не удался. Ваша система не поддерживает разрешение, с которым вы пытаетесь\nзаписывать с выбранным видеокодеком.\nИзмените разрешение захвата или видеокодек и попробуйте снова.\nПримечание: AV1 поддерживает максимальное разрешение, затем HEVC, затем H264.
Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.=Захват не удался. Ваша система не поддерживает выбранный видеокодек.\nИзмените видеокодек и попробуйте снова.
Stopped capture because the user canceled the desktop portal=Захват остановлен, так как пользователь отменил портал рабочего стола
Failed to take a screenshot. Verify if settings are correct=Не удалось сделать снимок экрана. Проверьте правильность настроек
# Launch errors
Failed to launch gpu-screen-recorder to start replay=Не удалось запустить gpu-screen-recorder для начала повтора
Failed to launch gpu-screen-recorder to start recording=Не удалось запустить gpu-screen-recorder для начала записи
Failed to launch gpu-screen-recorder to start streaming=Не удалось запустить gpu-screen-recorder для начала трансляции
Failed to launch gpu-screen-recorder to take a screenshot=Не удалось запустить gpu-screen-recorder для снимка экрана
# System startup notifications
Failed to add GPU Screen Recorder to system startup=Не удалось добавить GPU Screen Recorder в автозагрузку системы
Failed to remove GPU Screen Recorder from system startup=Не удалось удалить GPU Screen Recorder из автозагрузки системы
Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add "gsr-ui" to system startup on systems that uses another init system.=Не удалось добавить GPU Screen Recorder в автозагрузку системы.\nЭта опция работает только на системах, использующих systemd.\nВы должны вручную добавить "gsr-ui" в автозагрузку на системах с другой init-системой.
# Wayland warning
Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues.=Wayland не поддерживает интерфейс GPU Screen Recorder должным образом,\nнекоторые функции могут не работать. Используйте X11, если возникнут проблемы.
# Hotkey conflicts
Some keyboard remapping software conflicts with GPU Screen Recorder on your system.\nKeyboards have been ungrabbed, applications will now receive the hotkeys you press.=Некоторое программное обеспечение переназначения клавиатуры конфликтует с GPU Screen Recorder в вашей системе.\nЗахват клавиатуры отключён, приложения теперь будут получать нажатия горячих клавиш.
# Capture targets
this monitor=этого монитора
window=окна
window "%s"=окна "%s"
window %s=окна %s
focused=активного
region=региона
portal=портала
# Time durations (used in recording/replay saved notifications)
%d second_one=%d секунда
%d second_few=%d секунды
%d second_many=%d секунд
%d minute_one=%d минута
%d minute_few=%d минуты
%d minute_many=%d минут
%d hour_one=%d час
%d hour_few=%d часа
%d hour_many=%d часов
# Global Settings Page UI elements
Accent color=Акцентный цвет
Red=Красный
Green=Зелёный
Blue=Синий
Start program on system startup?=Запускать программу при старте системы?
Yes=Да
No=Нет
Enable keyboard hotkeys?=Включить горячие клавиши?
Yes, but only grab virtual devices (supports some input remapping software)=Да, но только виртуальные устройства (поддержка некоторых программ ремаппинга клавиш)
Yes, but don't grab devices (supports all input remapping software)=Да, но не захватывать устройства (поддержка всех программ ремаппинга клавиш)
Show/hide UI:=Показать/скрыть интерфейс:
Turn replay on/off:=Включить/выключить повтор:
Save replay:=Сохранить повтор:
Save 1 minute replay:=Сохранить 1 минуту повтора:
Save 10 minute replay:=Сохранить 10 минут повтора:
Start/stop recording:=Начать/остановить запись:
Pause/unpause recording:=Приостановить/возобновить запись:
Start/stop recording a region:=Начать/остановить запись региона:
Start/stop streaming:=Начать/остановить трансляцию:
Take a screenshot:=Сделать снимок экрана:
Take a screenshot of a region:=Сделать снимок региона:
Start/stop recording with desktop portal:=Запись через портал:
Take a screenshot with desktop portal:=Сделать снимок через портал рабочего стола:
Start/stop recording a window:=Начать/остановить запись окна:
Take a screenshot of a window:=Сделать снимок окна:
Clear hotkeys=Очистить горячие клавиши
Reset hotkeys to default=Сбросить горячие клавиши по умолчанию
Enable controller hotkeys?=Включить горячие клавиши геймпада?
Press=Нажмите
and=и
Notification speed=Скорость уведомлений
Normal=Обычная
Fast=Быстрая
Language=Язык
System language=Системный язык
Exit program=Выйти из программы
Go back to the old UI=Вернуться к старому интерфейсу
If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:=Если вы хотите поддержать проект, вы можете сделать это на https://buymeacoffee.com/dec05eba:
Donate=Поддержать
All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.=Все донаты идут на разработку ПО (включая GPU Screen Recorder) и покупку\nоборудования для тестирования.
# Subsection headers
Global=Глобальные
Back=Назад
Appearance=Внешний вид
Startup=Автозапуск
Keyboard hotkeys=Горячие клавиши клавиатуры
Controller hotkeys=Горячие клавиши геймпада
Application options=Настройки приложения
Application info=Информация о приложении
Donate=Поддержать
# Version info strings
GSR version: %s=Версия GSR: %s
GSR-UI version: %s=Версия GSR-UI: %s
Flatpak version: %s=Версия Flatpak: %s
GPU vendor: %s=Производитель GPU: %s
# Hotkey configuration dialog
Press a key combination to use for the hotkey: "%s"=Нажмите комбинацию клавиш для использования в качестве горячей клавиши: "%s"
Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey.=Буквенно-цифровые клавиши нельзя использовать отдельно, их нужно комбинировать с Alt, Ctrl, Shift или Super.\nНажмите Esc для отмены или Backspace для удаления горячей клавиши.
# Hotkey action names (without colons - these appear in the dialog)
Show/hide UI=Показать/скрыть интерфейс
Turn replay on/off=Включить/выключить повтор
Save replay=Сохранить повтор
Save 1 minute replay=Сохранить 1 минуту повтора
Save 10 minute replay=Сохранить 10 минут повтора
Start/stop recording=Начать/остановить запись
Pause/unpause recording=Приостановить/возобновить запись
Start/stop recording a region=Начать/остановить запись региона
Start/stop recording a window=Начать/остановить запись окна
Start/stop recording with desktop portal=Запись через портал
Start/stop streaming=Начать/остановить трансляцию
Take a screenshot=Сделать снимок экрана
Take a screenshot of a region=Сделать снимок региона
Take a screenshot of a window=Сделать снимок окна
Take a screenshot with desktop portal=Сделать снимок через портал рабочего стола
# Controller hotkey descriptions
to show/hide the UI=для показа/скрытия интерфейса
to take a screenshot=для снимка экрана
to save a replay=для сохранения повтора
to start/stop recording=для начала/остановки записи
to turn replay on/off=для включения/выключения повтора
to save a 1 minute replay=для сохранения 1 минуты повтора
to save a 10 minute replay=для сохранения 10 минут повтора
# Error message for duplicate hotkey
The hotkey %s is already used for something else=Горячая клавиша %s уже используется для другого действия
# Screenshot settings page
Screenshot=Снимок экрана
Capture=Захват
Image=Изображение
File info=Информация о файле
General=Общие
Screenshot indicator=Индикатор снимка
Script=Скрипт
File=Файл
Back=Назад
Save=Сохранить
Cancel=Отмена
Capture source:=Источник захвата:
Window=Окно
Region=Регион
Desktop portal=Портал рабочего стола
Monitor %s (%dx%d)=Монитор %s (%dx%d)
Screen=Экран
Image resolution limit:=Ограничение разрешения изображения:
Change image resolution=Изменять разрешение изображения
Restore portal session=Восстановить сессию портала
Image quality:=Качество изображения:
Medium=Среднее
High=Высокое
Very high (Recommended)=Очень высокое (рекомендуется)
Ultra=Ультра
Record cursor=Записывать курсор
Directory to save screenshots:=Каталог для сохранения снимков:
Image format:=Формат изображения:
Save screenshot in a folder based on the focused applications name=Сохранять снимок в папку по имени активного приложения
Save screenshot to clipboard=Сохранять снимок в буфер обмена
Save screenshot to clipboard (Not supported properly by Wayland)=Сохранять снимок в буфер обмена (в Wayland поддерживается некорректно)
Save screenshot to disk=Сохранять снимок на диск
Show screenshot notifications=Показывать уведомления о снимках
Blink scroll lock led when taking a screenshot=Мигать индикатором Scroll Lock при создании снимка
Command to open the screenshot with:=Команда для открытия снимка:
# Settings Page UI elements - дополнения
# View modes
Simple view=Простой вид
Advanced view=Расширенный вид
# Capture settings
Follow focused window=Следовать за активным окном
Focused monitor=Активный монитор
Area size:=Размер области:
Video resolution limit:=Ограничение разрешения видео:
Change video resolution=Изменять разрешение видео
Restore portal session=Восстановить сессию портала
# Webcam settings
Webcam=Веб-камера
Webcam source:=Источник веб-камеры:
None=Нет
Video format:=Формат видео:
Auto (recommended)=Авто (рекомендуется)
YUYV=YUYV
Motion-JPEG=Motion-JPEG
Video setup:=Настройка видео:
* Right click in the bottom right corner to resize the webcam=* Нажмите правой кнопкой мыши в нижнем правом углу для изменения размера веб-камеры
Flip camera horizontally=Отразить камеру горизонтально
# Audio settings
Audio=Аудио
Audio codec:=Аудиокодек:
Opus (Recommended)=Opus (рекомендуется)
AAC=AAC
Directory to save videos:=Каталог для сохранения видео:
Output device:=Выходное устройство:
Input device: =Входное устройство:
Application: =Приложение:
Custom...=Другое...
Save video in a folder based on the focused applications name%s=Сохранять видео в папку по имени активного приложения%s
(X11 applications only)= (только X11-приложения)
Add audio track=Добавить аудиодорожку
Add input device=Добавить вход
Add output device=Добавить вывод
Add application audio=Добавить аудио приложения
Record audio from all applications except the selected ones=Записывать аудио из всех приложений кроме выбранных
Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices.=Запись выходных устройств и аудио приложений может записать весь выходной звук,\nчто, вероятно, не то, что вы хотите. Удалите выходные устройства.
Video=Видео
# Video codec settings
Video codec:=Видеокодек:
H264=H264
HEVC=HEVC
HEVC (10 bit, reduces banding)=HEVC (10 бит, уменьшает полосы)
HEVC (HDR)=HEVC (HDR)
AV1=AV1
AV1 (10 bit, reduces banding)=AV1 (10 бит, уменьшает полосы)
AV1 (HDR)=AV1 (HDR)
VP8=VP8
VP9=VP9
H264 Software Encoder (Slow, not recommended)=H264 программный кодировщик \n(медленно, не рекомендуется)
# Video quality and bitrate
Video quality:=Качество видео:
Very high=Очень высокое
Video bitrate (Kbps):=Битрейт видео (Кбит/с):
Constant bitrate=Постоянный битрейт
Constant bitrate (Recommended)=Постоянный битрейт (рекомендуется)
# Frame rate settings
Frame rate:=Частота кадров:
Frame rate mode:=Режим частоты кадров:
Auto (Recommended)=Авто (рекомендуется)
Constant=Постоянный
Variable=Переменный
Sync to content=Синхр. с контентом
Sync to content (Only X11 or desktop portal capture)=Синхр. с контентом (только X11 или захват портала)
# Color range
Color range:=Цветовой диапазон:
Limited=Ограниченный
Full=Полный
# Container format
Container:=Формат:
# Recording settings
Record in low-power mode=Записывать в режиме низкого энергопотребления
Record cursor=Записывать курсор
Do not force the GPU to go into high performance mode when recording.\nMay affect recording performance, especially when playing a video at the same time.\nIf enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle.=Не заставлять GPU переходить в режим высокой производительности при записи.\nМожет повлиять на производительность записи, особенно при одновременном воспроизведении видео.\nЕсли включено, рекомендуется использовать режим частоты кадров синхронизации с контентом\nдля снижения энергопотребления в режиме ожидания.
Show %s notifications=Показывать уведомления %s
Show %s status with scroll lock led=Показывать статус %s с помощью индикатора Scroll Lock
Recording indicator=Индикатор записи
Simple=Простой
Audio track #%d=Аудиодорожка #%d
Output device=Выходное устройство
Input device: =Входное устройство:
Estimated video file size per minute (excluding audio): %.2fMB=Примерный размер видео файла в минуту (без учёта аудио): %.2fMB
# Replay settings
Directory to save replays:=Каталог для сохранения повторов:
Replay indicator=Индикатор повтора
Turn on replay when starting a fullscreen application%s=Включать повтор при запуске полноэкранного приложения%s
Autostart=Автозапуск
in RAM=в ОЗУ
Replay duration in seconds:=Длительность повтора в секундах:
Where should temporary replay data be stored?=Где должны храниться временные данные повтора?
RAM=ОЗУ
Disk (Not recommended on SSDs)=Диск (не рекомендуется для SSD)
Turn on replay when this program starts=Включать повтор при запуске программы
Turn on replay when power supply is connected=Включать повтор при подключении источника питания
Don't turn on replay automatically=Не включать повтор автоматически
Restart replay on save=Перезапускать повтор при сохранении
Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.=Примерный максимальный размер видео файла %s: %.2fMB.\nИзмените битрейт видео или длительность повтора, чтобы изменить размер файла.
# Streaming settings
Stream service:=Сервис трансляции:
Twitch=Twitch
YouTube=YouTube
Kick=Kick
Rumble=Rumble
Custom=Другое
Stream URL:=URL трансляции:
Stream key:=Ключ трансляции:
Streaming info=Информация о трансляции
Streaming indicator=Индикатор трансляции

426
translations/template.txt Normal file
View File

@@ -0,0 +1,426 @@
# GPU Screen Recorder UI - translation template
# Important warning: we f'ed up a little bit and used %s for both strings and numbers in some places, such as time durations (they're fixed by the moment).
# When translating, be careful to use the %d format specifier for numbers in those places.
# General UI
Record=
Instant Replay=
Livestream=
Settings=
# Status messages
Off=
On=
Not recording=
Recording=
Not streaming=
Streaming=
Paused=
# Button labels
Start=
Stop=
Stop and save=
Pause=
Unpause=
Save=
Save 1 min=
Save 10 min=
Turn on=
Turn off=
# Notifications - Recording
Recording has been paused=
Recording has been unpaused=
Started recording %s=
Saved a %s recording of %s\nto "%s"=
Saved a %s recording of %s=
Failed to start/save recording=
# Notifications - Replay
Replay stopped=
Started replaying %s=
Saving replay, this might take some time=
Saved a %s replay of %s\nto "%s"=
Saved a %s replay of %s=
Replay stopped because of an error=
Replay settings have been modified.\nYou may need to restart replay to apply the changes.=
# Notifications - Streaming
Streaming has stopped=
Started streaming %s=
Streaming stopped because of an error=
Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.=
# Notifications - Screenshot
Saved a screenshot of %s\nto "%s"=
Saved a screenshot of %s=
Failed to take a screenshot=
# Error messages
Another instance of GPU Screen Recorder UI is already running.\nPress Alt+Z to open the UI.=
GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.=
Failed to start replay, capture target "%s" is invalid.\nPlease change capture target in settings=
Failed to start recording, capture target "%s" is invalid.\nPlease change capture target in settings=
Failed to start streaming, capture target "%s" is invalid.\nPlease change capture target in settings=
Failed to take a screenshot, capture target "%s" is invalid.\nPlease change capture target in settings=
Unable to start recording when replay is turned on.\nTurn off replay before starting recording.=
Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.=
Unable to start streaming when recording.\nStop recording before starting streaming.=
Unable to start recording when streaming.\nStop streaming before starting recording.=
Unable to start replay when recording.\nStop recording before starting replay.=
Unable to start replay when streaming.\nStop streaming before starting replay.=
Started recording in the replay session=
Started recording in the streaming session=
Failed to start region capture=
Failed to start window capture=
No window selected=
Streaming stopped because of an error. Verify if settings are correct=
%s. Verify if settings are correct=
# GPU Screen Recorder errors
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.=
Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.=
Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.=
Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.=
Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.=
Stopped capture because the user canceled the desktop portal=
Failed to take a screenshot. Verify if settings are correct=
# Launch errors
Failed to launch gpu-screen-recorder to start replay=
Failed to launch gpu-screen-recorder to start recording=
Failed to launch gpu-screen-recorder to start streaming=
Failed to launch gpu-screen-recorder to take a screenshot=
# System startup notifications
Failed to add GPU Screen Recorder to system startup=
Failed to remove GPU Screen Recorder from system startup=
Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add "gsr-ui" to system startup on systems that uses another init system.=
# Wayland warning
Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues.=
# Hotkey conflicts
Some keyboard remapping software conflicts with GPU Screen Recorder on your system.\nKeyboards have been ungrabbed, applications will now receive the hotkeys you press.=
# Capture targets
this monitor=
window=
window "%s"=
window %s=
focused=
region=
portal=
# Time durations (used in recording/replay saved notifications, remember to use %d for numbers)
# if you have complex plural forms in your language, please use the following format:
%d second_one=
%d second_few=
%d second_many=
%d minute_one=
%d minute_few=
%d minute_many=
%d hour_one=
%d hour_few=
%d hour_many=
# if your language has simple plural forms, you can just use:
%d second=
%d minute=
%d hour=
%d seconds=
%d minutes=
%d hours=
# Global Settings Page UI elements
Accent color=
Red=
Green=
Blue=
Start program on system startup?=
Yes=
No=
Enable keyboard hotkeys?=
Yes, but only grab virtual devices (supports some input remapping software)=
Yes, but don't grab devices (supports all input remapping software)=
Show/hide UI:=
Turn replay on/off:=
Save replay:=
Save 1 minute replay:=
Save 10 minute replay:=
Start/stop recording:=
Pause/unpause recording:=
Start/stop recording a region:=
Start/stop streaming:=
Take a screenshot:=
Take a screenshot of a region:=
Start/stop recording with desktop portal:=
Take a screenshot with desktop portal:=
Start/stop recording a window:=
Take a screenshot of a window:=
Clear hotkeys=
Reset hotkeys to default=
Enable controller hotkeys?=
Press=
and=
Notification speed=
Normal=
Fast=
Language=
System language=
Exit program=
Go back to the old UI=
If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:=
Donate=
All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.=
# Subsection headers
Global=
Back=
Appearance=
Startup=
Keyboard hotkeys=
Controller hotkeys=
Application options=
Application info=
Donate=
# Version info strings
GSR version: %s=
GSR-UI version: %s=
Flatpak version: %s=
GPU vendor: %s=
# Hotkey configuration dialog
Press a key combination to use for the hotkey: "%s"=
Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey.=
# Hotkey action names (without colons - these appear in the dialog)
Show/hide UI=
Turn replay on/off=
Save replay=
Save 1 minute replay=
Save 10 minute replay=
Start/stop recording=
Pause/unpause recording=
Start/stop recording a region=
Start/stop recording a window=
Start/stop recording with desktop portal=
Start/stop streaming=
Take a screenshot=
Take a screenshot of a region=
Take a screenshot of a window=
Take a screenshot with desktop portal=
# Controller hotkey descriptions
to show/hide the UI=
to take a screenshot=
to save a replay=
to start/stop recording=
to turn replay on/off=
to save a 1 minute replay=
to save a 10 minute replay=
# Error message for duplicate hotkey
The hotkey %s is already used for something else=
# Screenshot settings page
Screenshot=
Capture=
Image=
File info=
General=
Screenshot indicator=
Script=
File=
Back=
Save=
Cancel=
Capture source:=
Window=
Region=
Desktop portal=
Monitor %s (%dx%d)=
Screen=
Image resolution limit:=
Change image resolution=
Restore portal session=
Image quality:=
Medium=
High=
Very high (Recommended)=
Ultra=
Record cursor=
Directory to save screenshots:=
Image format:=
Save screenshot in a folder based on the focused applications name=
Save screenshot to clipboard=
Save screenshot to clipboard (Not supported properly by Wayland)=
Save screenshot to disk=
Show screenshot notifications=
Blink scroll lock led when taking a screenshot=
Command to open the screenshot with:=
# Settings Page UI elements - дополнения
# View modes
Simple view=
Advanced view=
# Capture settings
Follow focused window=
Focused monitor=
Area size:=
Video resolution limit:=
Change video resolution=
Restore portal session=
# Webcam settings
Webcam=
Webcam source:=
None=
Video format:=
Auto (recommended)=
YUYV=
Motion-JPEG=
Video setup:=
* Right click in the bottom right corner to resize the webcam=
Flip camera horizontally=
# Audio settings
Audio=
Audio codec:=
Opus (Recommended)=
AAC=
Directory to save videos:=
Output device:=
Input device: =
# yes, these spaces are intentional
Application: =
Custom...=
Save video in a folder based on the focused applications name%s=
(X11 applications only)=
Add audio track=
Add input device=
Add output device=
Add application audio=
Record audio from all applications except the selected ones=
Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices.=
Video=
# Video codec settings
Video codec:=
H264=
HEVC=
HEVC (10 bit, reduces banding)=
HEVC (HDR)=
AV1=
AV1 (10 bit, reduces banding)=
AV1 (HDR)=
VP8=
VP9=
H264 Software Encoder (Slow, not recommended)=
# Video quality and bitrate
Video quality:=
Very high=
Video bitrate (Kbps):=
Constant bitrate=
Constant bitrate (Recommended)=
# Frame rate settings
Frame rate:=
Frame rate mode:=
Auto (Recommended)=
Constant=
Variable=
Sync to content=
Sync to content (Only X11 or desktop portal capture)=
# Color range
Color range:=
Limited=
Full=
# Container format
Container:=
# Recording settings
Record in low-power mode=
Record cursor=
Do not force the GPU to go into high performance mode when recording.\nMay affect recording performance, especially when playing a video at the same time.\nIf enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle.=
Show %s notifications=
Show %s status with scroll lock led=
Recording indicator=
recording=
Simple=
Audio track #%d=
Output device=
Input device: =
Estimated video file size per minute (excluding audio): %.2fMB=
# Replay settings
Directory to save replays:=
Replay indicator=
replay=
Turn on replay when starting a fullscreen application%s=
Autostart=
in RAM=
Replay duration in seconds:=
Where should temporary replay data be stored?=
RAM=
Disk (Not recommended on SSDs)=
Turn on replay when this program starts=
Turn on replay when power supply is connected=
Don't turn on replay automatically=
Restart replay on save=
Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.=
# Streaming settings
Stream service:=
Twitch=
YouTube=
Kick=
Rumble=
Custom=
Stream URL:=
Stream key:=
Streaming info=
Streaming indicator=
streaming=

409
translations/uk.txt Normal file
View File

@@ -0,0 +1,409 @@
# GPU Screen Recorder UI - Ukrainian Translation
# General UI
Record=Запис
Instant Replay=Миттєвий повтор
Livestream=Пряма трансляція
Settings=Налаштування
# Status messages
Off=Вимкнено
On=Увімкнено
Not recording=Запис неактивний
Recording=Йде запис
Not streaming=Трансляція неактивна
Streaming=Йде трансляція
Paused=Призупинено
# Button labels
Start=Пуск
Stop=Зупинити
Stop and save=Стоп та зберегти
Pause=Пауза
Unpause=Продовжити
Save=Зберегти
Save 1 min=Збер. 1 хв
Save 10 min=Збер. 10 хв
Turn on=Увімкнути
Turn off=Вимкнути
# Notifications - Recording
Recording has been paused=Запис призупинено
Recording has been unpaused=Запис відновлено
Started recording %s=Розпочато запис %s
Saved a %s recording of %s\nto "%s"=Збережено %s запису %s\nу "%s"
Saved a %s recording of %s=Збережено %s запису %s
Failed to start/save recording=Не вдалося розпочати/зберегти запис
# Notifications - Replay
Replay stopped=Повтор зупинено
Started replaying %s=Розпочато повтор %s
Saving replay, this might take some time=Збереження повтору, це може зайняти деякий час
Saved a %s replay of %s\nto "%s"=Збережено %s повтору %s\nу "%s"
Saved a %s replay of %s=Збережено %s повтору %s
Replay stopped because of an error=Повтор зупинено через помилку
Replay settings have been modified.\nYou may need to restart replay to apply the changes.=Налаштування повтору змінено.\nМожливо, знадобиться перезапустити повтор для застосування змін.
# Notifications - Streaming
Streaming has stopped=Трансляцію зупинено
Started streaming %s=Розпочато трансляцію %s
Streaming stopped because of an error=Трансляцію зупинено через помилку
Streaming settings have been modified.\nYou may need to restart streaming to apply the changes.=Налаштування трансляції змінено.\nМожливо, знадобиться перезапустити трансляцію для застосування змін.
# Notifications - Screenshot
Saved a screenshot of %s\nto "%s"=Збережено знімок %s\nу "%s"
Saved a screenshot of %s=Збережено знімок %s
Failed to take a screenshot=Не вдалося зробити знімок екрана
# Error messages
Another instance of GPU Screen Recorder UI is already running.\nPress Alt+Z to open the UI.=Один екземпляр GPU Screen Recorder UI вже запущено.\nНатисніть Alt+Z, щоб відкрити інтерфейс.
GPU Screen Recorder is already running in another process.\nPlease close it before using GPU Screen Recorder UI.=GPU Screen Recorder CLI вже запущено в іншому процесі.\nБудь ласка, закрийте його перед використанням GPU Screen Recorder UI.
Failed to start replay, capture target "%s" is invalid.\nPlease change capture target in settings=Не вдалося розпочати повтор, ціль захоплення "%s" недійсна.\nБудь ласка, змініть ціль захоплення в налаштуваннях
Failed to start recording, capture target "%s" is invalid.\nPlease change capture target in settings=Не вдалося розпочати запис, ціль захоплення "%s" недійсна.\nБудь ласка, змініть ціль захоплення в налаштуваннях
Failed to start streaming, capture target "%s" is invalid.\nPlease change capture target in settings=Не вдалося розпочати трансляцію, ціль захоплення "%s" недійсна.\nБудь ласка, змініть ціль захоплення в налаштуваннях
Failed to take a screenshot, capture target "%s" is invalid.\nPlease change capture target in settings=Не вдалося зробити знімок екрана, ціль захоплення "%s" недійсна.\nБудь ласка, змініть ціль захоплення в налаштуваннях
Unable to start recording when replay is turned on.\nTurn off replay before starting recording.=Неможливо розпочати запис при увімкненому повторі.\nВимкніть повтор перед початком запису.
Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming.=Неможливо розпочати трансляцію при увімкненому повторі.\nВимкніть повтор перед початком трансляції.
Unable to start streaming when recording.\nStop recording before starting streaming.=Неможливо розпочати трансляцію при записі.\nЗупиніть запис перед початком трансляції.
Unable to start recording when streaming.\nStop streaming before starting recording.=Неможливо розпочати запис при трансляції.\nЗупиніть трансляцію перед початком запису.
Unable to start replay when recording.\nStop recording before starting replay.=Неможливо розпочати повтор при записі.\nЗупиніть запис перед початком повтору.
Unable to start replay when streaming.\nStop streaming before starting replay.=Неможливо розпочати повтор при трансляції.\nЗупиніть трансляцію перед початком повтору.
Started recording in the replay session=Розпочато запис у сеансі повтору
Started recording in the streaming session=Розпочато запис у сеансі трансляції
Failed to start region capture=Не вдалося розпочати захоплення регіону
Failed to start window capture=Не вдалося розпочати захоплення вікна
No window selected=Вікно не вибрано
Streaming stopped because of an error. Verify if settings are correct=Трансляцію зупинено через помилку. Перевірте правильність налаштувань
%s. Verify if settings are correct=%s. Перевірте правильність налаштувань
# GPU Screen Recorder errors
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.=Захоплення порталу робочого стола не вдалося.\nВи або скасували портал робочого стола, або ваш композитор Wayland не підтримує захоплення порталу робочого стола\nабо він неправильно налаштований у вашій системі.
Monitor capture failed.\nThe monitor you are trying to capture is invalid.\nPlease validate your capture settings.=Захоплення монітора не вдалося.\nМонітор, який ви намагаєтесь захопити, недійсний.\nБудь ласка, перевірте налаштування захоплення.
Capture failed. Neither H264, HEVC nor AV1 video codecs are supported\non your system or you are trying to capture at a resolution higher than your\nsystem supports for each video codec.=Захоплення не вдалося. Жоден з відеокодеків H264, HEVC або AV1 не підтримується\nвашою системою або ви намагаєтесь захопити відео з роздільною здатністю вище, ніж\nпідтримує ваша система для кожного відеокодека.
Capture failed. Your system doesn't support the resolution you are trying to\nrecord at with the video codec you have chosen.\nChange capture resolution or video codec and try again.\nNote: AV1 supports the highest resolution, then HEVC and then H264.=Захоплення не вдалося. Ваша система не підтримує роздільну здатність, з якою ви намагаєтесь\nзаписувати з обраним відеокодеком.\nЗмініть роздільну здатність захоплення або відеокодек і спробуйте знову.\nПримітка: AV1 підтримує максимальну роздільну здатність, потім HEVC, потім H264.
Capture failed. Your system doesn't support the video codec you have chosen.\nChange video codec and try again.=Захоплення не вдалося. Ваша система не підтримує обраний відеокодек.\nЗмініть відеокодек і спробуйте знову.
Stopped capture because the user canceled the desktop portal=Захоплення зупинено, оскільки користувач скасував портал робочого стола
Failed to take a screenshot. Verify if settings are correct=Не вдалося зробити знімок екрана. Перевірте правильність налаштувань
# Launch errors
Failed to launch gpu-screen-recorder to start replay=Не вдалося запустити gpu-screen-recorder для початку повтору
Failed to launch gpu-screen-recorder to start recording=Не вдалося запустити gpu-screen-recorder для початку запису
Failed to launch gpu-screen-recorder to start streaming=Не вдалося запустити gpu-screen-recorder для початку трансляції
Failed to launch gpu-screen-recorder to take a screenshot=Не вдалося запустити gpu-screen-recorder для знімка екрана
# System startup notifications
Failed to add GPU Screen Recorder to system startup=Не вдалося додати GPU Screen Recorder до автозавантаження системи
Failed to remove GPU Screen Recorder from system startup=Не вдалося видалити GPU Screen Recorder з автозавантаження системи
Failed to add GPU Screen Recorder to system startup.\nThis option only works on systems that use systemd.\nYou have to manually add "gsr-ui" to system startup on systems that uses another init system.=Не вдалося додати GPU Screen Recorder до автозавантаження системи.\nЦя опція працює лише на системах, що використовують systemd.\nВи маєте вручну додати "gsr-ui" до автозавантаження на системах з іншою init-системою.
# Wayland warning
Wayland doesn't support GPU Screen Recorder UI properly,\nthings may not work as expected. Use X11 if you experience issues.=Wayland не підтримує інтерфейс GPU Screen Recorder належним чином,\nдеякі функції можуть не працювати. Використовуйте X11, якщо виникнуть проблеми.
# Hotkey conflicts
Some keyboard remapping software conflicts with GPU Screen Recorder on your system.\nKeyboards have been ungrabbed, applications will now receive the hotkeys you press.=Деяке програмне забезпечення перепризначення клавіатури конфліктує з GPU Screen Recorder у вашій системі.\nЗахоплення клавіатури вимкнено, додатки тепер отримуватимуть натискання гарячих клавіш.
# Capture targets
this monitor=цього монітора
window=вікна
window "%s"=вікна "%s"
window %s=вікна %s
focused=активного
region=регіону
portal=порталу
# Time durations (used in recording/replay saved notifications)
%d second_one=%d секунда
%d second_few=%d секунди
%d second_many=%d секунд
%d minute_one=%d хвилина
%d minute_few=%d хвилини
%d minute_many=%d хвилин
%d hour_one=%d година
%d hour_few=%d години
%d hour_many=%d годин
# Global Settings Page UI elements
Accent color=Акцентний колір
Red=Червоний
Green=Зелений
Blue=Синій
Start program on system startup?=Запускати програму при старті системи?
Yes=Так
No=Ні
Enable keyboard hotkeys?=Увімкнути гарячі клавіші?
Yes, but only grab virtual devices (supports some input remapping software)=Так, але лише віртуальні пристрої (підтримка деякого ПЗ ремапінгу клавіш)
Yes, but don't grab devices (supports all input remapping software)=Так, але не захоплювати пристрої (підтримка всього ПЗ ремапінгу клавіш)
Show/hide UI:=Показати/сховати інтерфейс:
Turn replay on/off:=Увімкнути/вимкнути повтор:
Save replay:=Зберегти повтор:
Save 1 minute replay:=Зберегти 1 хвилину повтору:
Save 10 minute replay:=Зберегти 10 хвилин повтору:
Start/stop recording:=Розпочати/зупинити запис:
Pause/unpause recording:=Призупинити/відновити запис:
Start/stop recording a region:=Розпочати/зупинити запис регіону:
Start/stop streaming:=Розпочати/зупинити трансляцію:
Take a screenshot:=Зробити знімок екрана:
Take a screenshot of a region:=Зробити знімок регіону:
Start/stop recording with desktop portal:=Запис через портал:
Take a screenshot with desktop portal:=Зробити знімок через портал робочого стола:
Start/stop recording a window:=Розпочати/зупинити запис вікна:
Take a screenshot of a window:=Зробити знімок вікна:
Clear hotkeys=Очистити гарячі клавіші
Reset hotkeys to default=Скинути гарячі клавіші за замовчуванням
Enable controller hotkeys?=Увімкнути гарячі клавіші геймпада?
Press=Натисніть
and=та
Notification speed=Швидкість сповіщень
Normal=Звичайна
Fast=Швидка
Language=Мова
System language=Системна мова
Exit program=Вийти з програми
Go back to the old UI=Повернутися до старого інтерфейсу
If you would like to donate you can do so by donating at https://buymeacoffee.com/dec05eba:=Якщо ви хочете підтримати проєкт, ви можете зробити це на https://buymeacoffee.com/dec05eba:
Donate=Підтримати
All donations go toward developing software (including GPU Screen Recorder)\nand buying hardware to test the software.=Всі донати йдуть на розробку ПЗ (включно з GPU Screen Recorder) та купівлю\nобладнання для тестування.
# Subsection headers
Global=Глобальні
Back=Назад
Appearance=Зовнішній вигляд
Startup=Автозапуск
Keyboard hotkeys=Гарячі клавіші клавіатури
Controller hotkeys=Гарячі клавіші геймпада
Application options=Налаштування програми
Application info=Інформація про програму
# Version info strings
GSR version: %s=Версія GSR: %s
GSR-UI version: %s=Версія GSR-UI: %s
Flatpak version: %s=Версія Flatpak: %s
GPU vendor: %s=Виробник GPU: %s
# Hotkey configuration dialog
Press a key combination to use for the hotkey: "%s"=Натисніть комбінацію клавіш для використання як гарячої клавіші: "%s"
Alpha-numerical keys can't be used alone in hotkeys, they have to be used one or more of these keys: Alt, Ctrl, Shift and Super.\nPress Esc to cancel or Backspace to remove the hotkey.=Буквено-цифрові клавіші не можна використовувати окремо, їх потрібно комбінувати з Alt, Ctrl, Shift або Super.\nНатисніть Esc для скасування або Backspace для видалення гарячої клавіші.
# Hotkey action names (without colons - these appear in the dialog)
Show/hide UI=Показати/сховати інтерфейс
Turn replay on/off=Увімкнути/вимкнути повтор
Save replay=Зберегти повтор
Save 1 minute replay=Зберегти 1 хвилину повтору
Save 10 minute replay=Зберегти 10 хвилин повтору
Start/stop recording=Розпочати/зупинити запис
Pause/unpause recording=Призупинити/відновити запис
Start/stop recording a region=Розпочати/зупинити запис регіону
Start/stop recording a window=Розпочати/зупинити запис вікна
Start/stop recording with desktop portal=Запис через портал
Start/stop streaming=Розпочати/зупинити трансляцію
Take a screenshot=Зробити знімок екрана
Take a screenshot of a region=Зробити знімок регіону
Take a screenshot of a window=Зробити знімок вікна
Take a screenshot with desktop portal=Зробити знімок через портал робочого стола
# Controller hotkey descriptions
to show/hide the UI=для показу/приховування інтерфейсу
to take a screenshot=для знімка екрана
to save a replay=для збереження повтору
to start/stop recording=для початку/зупинки запису
to turn replay on/off=для увімкнення/вимкнення повтору
to save a 1 minute replay=для збереження 1 хвилини повтору
to save a 10 minute replay=для збереження 10 хвилин повтору
# Error message for duplicate hotkey
The hotkey %s is already used for something else=Гаряча клавіша %s вже використовується для іншої дії
# Screenshot settings page
Screenshot=Знімок екрана
Capture=Захоплення
Image=Зображення
File info=Інформація про файл
General=Загальні
Screenshot indicator=Індикатор знімка
Script=Скрипт
File=Файл
Back=Назад
Save=Зберегти
Cancel=Скасувати
Capture source:=Джерело захоплення:
Window=Вікно
Region=Регіон
Desktop portal=Портал робочого стола
Monitor %s (%dx%d)=Монітор %s (%dx%d)
Screen=Екран
Image resolution limit:=Обмеження роздільної здатності зображення:
Change image resolution=Змінювати роздільну здатність зображення
Restore portal session=Відновити сесію порталу
Image quality:=Якість зображення:
Medium=Середня
High=Висока
Very high (Recommended)=Дуже висока (рекомендовано)
Ultra=Ультра
Record cursor=Записувати курсор
Directory to save screenshots:=Каталог для збереження знімків:
Image format:=Формат зображення:
Save screenshot in a folder based on the focused applications name=Зберігати знімок у папку за ім'ям активної програми
Save screenshot to clipboard=Зберігати знімок до буфера обміну
Save screenshot to clipboard (Not supported properly by Wayland)=Зберігати знімок до буфера обміну (в Wayland підтримується некоректно)
Save screenshot to disk=Зберігати знімок на диск
Show screenshot notifications=Показувати сповіщення про знімки
Blink scroll lock led when taking a screenshot=Блимати індикатором Scroll Lock при створенні знімка
Command to open the screenshot with:=Команда для відкриття знімка:
# Settings Page UI elements - дополнения
# View modes
Simple view=Простий вигляд
Advanced view=Розширений вигляд
# Capture settings
Follow focused window=Слідувати за активним вікном
Focused monitor=Активний монітор
Area size:=Розмір області:
Video resolution limit:=Обмеження роздільної здатності відео:
Change video resolution=Змінювати роздільну здатність відео
Restore portal session=Відновити сесію порталу
# Webcam settings
Webcam=Веб-камера
Webcam source:=Джерело веб-камери:
None=Немає
Video format:=Формат відео:
Auto (recommended)=Авто (рекомендовано)
YUYV=YUYV
Motion-JPEG=Motion-JPEG
Video setup:=Налаштування відео:
* Right click in the bottom right corner to resize the webcam=* Натисніть правою кнопкою миші в нижньому правому куті для зміни розміру веб-камери
Flip camera horizontally=Відобразити камеру горизонтально
# Audio settings
Audio=Аудіо
Audio codec:=Аудіокодек:
Opus (Recommended)=Opus (рекомендовано)
AAC=AAC
Directory to save videos:=Каталог для збереження відео:
Output device:=Вихідний пристрій:
Input device: =Вхідний пристрій:
Application: =Програма:
Custom...=Інше...
Save video in a folder based on the focused applications name%s=Зберігати відео у папку за ім'ям активної програми%s
(X11 applications only)= (лише X11-програми)
Add audio track=Додати аудіодоріжку
Add input device=Додати вхід
Add output device=Додати вихід
Add application audio=Додати аудіо програми
Record audio from all applications except the selected ones=Записувати аудіо з усіх програм окрім вибраних
Recording output devices and application audio may record all output audio, which is likely\nnot what you want to do. Remove the output devices.=Запис вихідних пристроїв та аудіо програм може записати весь вихідний звук,\nщо, ймовірно, не те, що ви хочете. Видаліть вихідні пристрої.
Video=Відео
# Video codec settings
Video codec:=Відеокодек:
H264=H264
HEVC=HEVC
HEVC (10 bit, reduces banding)=HEVC (10 біт, зменшує смуги)
HEVC (HDR)=HEVC (HDR)
AV1=AV1
AV1 (10 bit, reduces banding)=AV1 (10 біт, зменшує смуги)
AV1 (HDR)=AV1 (HDR)
VP8=VP8
VP9=VP9
H264 Software Encoder (Slow, not recommended)=H264 програмний кодувальник \n(повільно, не рекомендовано)
# Video quality and bitrate
Video quality:=Якість відео:
Very high=Дуже висока
Video bitrate (Kbps):=Бітрейт відео (Кбіт/с):
Constant bitrate=Постійний бітрейт
Constant bitrate (Recommended)=Постійний бітрейт (рекомендовано)
# Frame rate settings
Frame rate:=Частота кадрів:
Frame rate mode:=Режим частоти кадрів:
Auto (Recommended)=Авто (рекомендовано)
Constant=Постійний
Variable=Змінний
Sync to content=Синхр. з контентом
Sync to content (Only X11 or desktop portal capture)=Синхр. з контентом (лише X11 або захоплення порталу)
# Color range
Color range:=Колірний діапазон:
Limited=Обмежений
Full=Повний
# Container format
Container:=Формат:
# Recording settings
Record in low-power mode=Записувати в режимі низького енергоспоживання
Record cursor=Записувати курсор
Do not force the GPU to go into high performance mode when recording.\nMay affect recording performance, especially when playing a video at the same time.\nIf enabled then it's recommended to use sync to content frame rate mode to reduce power usage when idle.=Не змушувати GPU переходити в режим високої продуктивності при записі.\nМоже вплинути на продуктивність запису, особливо при одночасному відтворенні відео.\nЯкщо увімкнено, рекомендується використовувати режим частоти кадрів синхронізації з контентом\nдля зниження енергоспоживання в режимі очікування.
Show %s notifications=Показувати сповіщення %s
Show %s status with scroll lock led=Показувати статус %s за допомогою індикатора Scroll Lock
Recording indicator=Індикатор запису
Simple=Простий
Audio track #%d=Аудіодоріжка #%d
Output device=Вихідний пристрій
Input device: =Вхідний пристрій:
Estimated video file size per minute (excluding audio): %.2fMB=Приблизний розмір відео файлу за хвилину (без урахування аудіо): %.2fMB
# Replay settings
Directory to save replays:=Каталог для збереження повторів:
Replay indicator=Індикатор повтору
Turn on replay when starting a fullscreen application%s=Увімкнути повтор при запуску повноекранної програми%s
Autostart=Автозапуск
in RAM=в ОЗП
Replay duration in seconds:=Тривалість повтору в секундах:
Where should temporary replay data be stored?=Де мають зберігатися тимчасові дані повтору?
RAM=ОЗП
Disk (Not recommended on SSDs)=Диск (не рекомендовано для SSD)
Turn on replay when this program starts=Увімкнути повтор при запуску програми
Turn on replay when power supply is connected=Увімкнути повтор при підключенні джерела живлення
Don't turn on replay automatically=Не вмикати повтор автоматично
Restart replay on save=Перезапускати повтор при збереженні
Estimated video max file size %s: %.2fMB.\nChange video bitrate or replay duration to change file size.=Приблизний максимальний розмір відео файлу %s: %.2fMB.\nЗмініть бітрейт відео або тривалість повтору, щоб змінити розмір файлу.
# Streaming settings
Stream service:=Сервіс трансляції:
Twitch=Twitch
YouTube=YouTube
Kick=Kick
Rumble=Rumble
Custom=Інше
Stream URL:=URL трансляції:
Stream key:=Ключ трансляції:
Streaming info=Інформація про трансляцію
Streaming indicator=Індикатор трансляції