mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-04-09 04:44:52 +09:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fde1b438df | ||
|
|
1d96b73e1a | ||
|
|
1ce12067aa | ||
|
|
728ccc40a6 | ||
|
|
02db186232 | ||
|
|
44123d35a5 | ||
|
|
a31bfbe288 | ||
|
|
f3d6d8bc53 | ||
|
|
74d6a05e2f | ||
|
|
89995b805e | ||
|
|
f921be46c0 |
@@ -5,7 +5,7 @@ A fullscreen overlay UI for [GPU Screen Recorder](https://git.dec05eba.com/gpu-s
|
||||
The application is currently primarly designed for X11 but it can run on Wayland as well through XWayland, with some caveats because of Wayland limitations.
|
||||
|
||||
# Installation
|
||||
If you are using an Arch Linux based distro then you can find gpu screen recorder ui on aur under the name gpu-screen-recorder-ui (`yay -S gpu-screen-recorder-ui`).\
|
||||
If you are running an Arch Linux based distro then you can find gpu screen recorder ui in the official repositories under the name gpu-screen-recorder-ui (`sudo pacman -S gpu-screen-recorder-ui`).\
|
||||
If you are running another distro then you can run `sudo ./install.sh`, but you need to manually install the dependencies, as described below.\
|
||||
You can also install gpu screen recorder from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder) which includes this UI.
|
||||
|
||||
|
||||
2
TODO
2
TODO
@@ -117,8 +117,6 @@ System startup option should also support runit and some other init systems, not
|
||||
|
||||
Use x11 shm instead of XGetImage (https://stackoverflow.com/questions/43442675/how-to-use-xshmgetimage-and-xshmputimage).
|
||||
|
||||
Add a hotkey to record/stream/replay region.
|
||||
|
||||
Do xi grab for keys as well. Otherwise the ui cant be used for keyboard input if a program has grabbed the keyboard, and there could possibly be a game that grabs the keyboard as well.
|
||||
|
||||
Make inactive buttons gray (in dropdown boxes and in the front page with save, etc when replay is not running).
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "GlobalHotkeys/GlobalHotkeysJoystick.hpp"
|
||||
#include "AudioPlayer.hpp"
|
||||
#include "RegionSelector.hpp"
|
||||
#include "WindowSelector.hpp"
|
||||
#include "ClipboardFile.hpp"
|
||||
#include "LedIndicator.hpp"
|
||||
#include "CursorTracker/CursorTracker.hpp"
|
||||
@@ -275,9 +274,7 @@ namespace gsr {
|
||||
bool start_region_capture = false;
|
||||
std::function<void()> on_region_selected;
|
||||
|
||||
WindowSelector window_selector;
|
||||
bool start_window_capture = false;
|
||||
std::function<void()> on_window_selected;
|
||||
|
||||
std::string recording_capture_target;
|
||||
std::string screenshot_capture_target;
|
||||
|
||||
@@ -15,14 +15,26 @@ namespace gsr {
|
||||
mgl::vec2i size;
|
||||
};
|
||||
|
||||
struct RegionWindow {
|
||||
Window window = None;
|
||||
mgl::vec2i pos;
|
||||
mgl::vec2i size;
|
||||
};
|
||||
|
||||
class RegionSelector {
|
||||
public:
|
||||
enum class SelectionType {
|
||||
NONE,
|
||||
REGION,
|
||||
WINDOW
|
||||
};
|
||||
|
||||
RegionSelector();
|
||||
RegionSelector(const RegionSelector&) = delete;
|
||||
RegionSelector& operator=(const RegionSelector&) = delete;
|
||||
~RegionSelector();
|
||||
|
||||
bool start(mgl::Color border_color);
|
||||
bool start(SelectionType selection_type, mgl::Color border_color);
|
||||
void stop();
|
||||
bool is_started() const;
|
||||
|
||||
@@ -30,7 +42,11 @@ namespace gsr {
|
||||
bool poll_events();
|
||||
bool take_selection();
|
||||
bool take_canceled();
|
||||
Region get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const;
|
||||
Region get_region_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const;
|
||||
// Returns None (0) if none is selected
|
||||
Window get_window_selection() const;
|
||||
|
||||
SelectionType get_selection_type() const;
|
||||
private:
|
||||
void on_button_press(const void *de);
|
||||
void on_button_release(const void *de);
|
||||
@@ -50,6 +66,10 @@ namespace gsr {
|
||||
bool canceled = false;
|
||||
bool is_wayland = false;
|
||||
std::vector<Monitor> monitors;
|
||||
std::vector<RegionWindow> windows; // First window is the window that is on top
|
||||
std::optional<RegionWindow> focused_window;
|
||||
mgl::vec2i cursor_pos;
|
||||
|
||||
SelectionType selection_type = SelectionType::NONE;
|
||||
};
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
|
||||
#include <mglpp/graphics/Color.hpp>
|
||||
|
||||
namespace gsr {
|
||||
class WindowSelector {
|
||||
public:
|
||||
WindowSelector();
|
||||
WindowSelector(const WindowSelector&) = delete;
|
||||
WindowSelector& operator=(const WindowSelector&) = delete;
|
||||
~WindowSelector();
|
||||
|
||||
bool start(mgl::Color border_color);
|
||||
void stop();
|
||||
bool is_started() const;
|
||||
|
||||
bool failed() const;
|
||||
bool poll_events();
|
||||
bool take_selection();
|
||||
bool take_canceled();
|
||||
Window get_selection() const;
|
||||
private:
|
||||
Display *dpy = nullptr;
|
||||
Cursor crosshair_cursor = None;
|
||||
Colormap border_window_colormap = None;
|
||||
Window border_window = None;
|
||||
Window selected_window = None;
|
||||
bool selected = false;
|
||||
bool canceled = false;
|
||||
};
|
||||
}
|
||||
@@ -27,6 +27,7 @@ namespace gsr {
|
||||
std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window);
|
||||
void set_window_size_not_resizable(Display *dpy, Window window, int width, int height);
|
||||
Window window_get_target_window_child(Display *display, Window window);
|
||||
unsigned char* window_get_property(Display *dpy, Window window, Atom property_type, const char *property_name, unsigned int *property_size);
|
||||
mgl::vec2i get_cursor_position(Display *dpy, Window *window);
|
||||
mgl::vec2i create_window_get_center_position(Display *display);
|
||||
std::string get_window_manager_name(Display *display);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.10.5', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
project('gsr-ui', ['c', 'cpp'], version : '1.10.8', default_options : ['warning_level=2', 'cpp_std=c++17'], subproject_dir : 'depends')
|
||||
|
||||
add_project_arguments('-D_FILE_OFFSET_BITS=64', language : ['c', 'cpp'])
|
||||
|
||||
@@ -45,7 +45,6 @@ src = [
|
||||
'src/KwinWorkaround.cpp',
|
||||
'src/WindowUtils.cpp',
|
||||
'src/RegionSelector.cpp',
|
||||
'src/WindowSelector.cpp',
|
||||
'src/Config.cpp',
|
||||
'src/GsrInfo.cpp',
|
||||
'src/Process.cpp',
|
||||
@@ -71,7 +70,7 @@ 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.1"', language: ['c', 'cpp'])
|
||||
add_project_arguments('-DGSR_FLATPAK_VERSION="5.12.2"', language: ['c', 'cpp'])
|
||||
|
||||
add_project_arguments('-DKWIN_HELPER_SCRIPT_PATH="' + gsr_ui_resources_path + '/gsrkwinhelper.js"', language: ['c', 'cpp'])
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "gsr-ui"
|
||||
type = "executable"
|
||||
version = "1.10.5"
|
||||
version = "1.10.8"
|
||||
platforms = ["posix"]
|
||||
|
||||
[lang.cpp]
|
||||
|
||||
141
src/Overlay.cpp
141
src/Overlay.cpp
@@ -283,6 +283,8 @@ 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;
|
||||
@@ -802,24 +804,28 @@ namespace gsr {
|
||||
if(region_selector.take_canceled()) {
|
||||
on_region_selected = nullptr;
|
||||
} else if(region_selector.take_selection() && on_region_selected) {
|
||||
on_region_selected();
|
||||
on_region_selected = nullptr;
|
||||
}
|
||||
switch(region_selector.get_selection_type()) {
|
||||
case RegionSelector::SelectionType::NONE: {
|
||||
break;
|
||||
}
|
||||
case RegionSelector::SelectionType::REGION: {
|
||||
on_region_selected();
|
||||
break;
|
||||
}
|
||||
case RegionSelector::SelectionType::WINDOW: {
|
||||
mgl_context *context = mgl_get_context();
|
||||
Display *display = (Display*)context->connection;
|
||||
|
||||
window_selector.poll_events();
|
||||
if(window_selector.take_canceled()) {
|
||||
on_window_selected = nullptr;
|
||||
} else if(window_selector.take_selection() && on_window_selected) {
|
||||
mgl_context *context = mgl_get_context();
|
||||
Display *display = (Display*)context->connection;
|
||||
|
||||
const Window selected_window = window_selector.get_selection();
|
||||
if(selected_window && selected_window != DefaultRootWindow(display)) {
|
||||
on_window_selected();
|
||||
} else {
|
||||
show_notification(TR("No window selected"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
|
||||
const Window selected_window = region_selector.get_window_selection();
|
||||
if(selected_window && selected_window != DefaultRootWindow(display)) {
|
||||
on_region_selected();
|
||||
} else {
|
||||
show_notification(TR("No window selected"), notification_timeout_seconds, mgl::Color(255, 255, 255), mgl::Color(255, 0, 0), NotificationType::NOTICE, nullptr, NotificationLevel::ERROR);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
on_window_selected = nullptr;
|
||||
on_region_selected = nullptr;
|
||||
}
|
||||
|
||||
if(!visible || !window)
|
||||
@@ -873,7 +879,7 @@ namespace gsr {
|
||||
if(start_region_capture) {
|
||||
start_region_capture = false;
|
||||
hide();
|
||||
if(!region_selector.start(get_color_theme().tint_color)) {
|
||||
if(!region_selector.start(RegionSelector::SelectionType::REGION, get_color_theme().tint_color)) {
|
||||
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;
|
||||
}
|
||||
@@ -882,13 +888,13 @@ namespace gsr {
|
||||
if(start_window_capture) {
|
||||
start_window_capture = false;
|
||||
hide();
|
||||
if(!window_selector.start(get_color_theme().tint_color)) {
|
||||
if(!region_selector.start(RegionSelector::SelectionType::WINDOW, get_color_theme().tint_color)) {
|
||||
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;
|
||||
on_region_selected = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if(region_selector.is_started() || window_selector.is_started()) {
|
||||
if(region_selector.is_started()) {
|
||||
usleep(5 * 1000); // 5 ms
|
||||
return true;
|
||||
}
|
||||
@@ -1023,7 +1029,7 @@ namespace gsr {
|
||||
if(visible)
|
||||
return;
|
||||
|
||||
if(region_selector.is_started() || window_selector.is_started())
|
||||
if(region_selector.is_started())
|
||||
return;
|
||||
|
||||
drawn_first_frame = false;
|
||||
@@ -2039,6 +2045,7 @@ namespace gsr {
|
||||
if(focused_window_name.empty())
|
||||
focused_window_name = "Game";
|
||||
|
||||
focused_window_name = strip(focused_window_name);
|
||||
string_replace_characters(focused_window_name.data(), "/\\", ' ');
|
||||
|
||||
std::string video_directory = filepath_get_directory(video_filepath.c_str()) + "/" + focused_window_name;
|
||||
@@ -2589,7 +2596,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
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);
|
||||
Region region = region_selector.get_region_selection(x11_dpy, wayland_dpy);
|
||||
if(region.size.x <= 32 && region.size.y <= 32) {
|
||||
region.size.x = 0;
|
||||
region.size.y = 0;
|
||||
@@ -2673,7 +2680,7 @@ namespace gsr {
|
||||
|
||||
std::string Overlay::get_capture_target(const std::string &capture_target, const SupportedCaptureOptions &capture_options) {
|
||||
if(capture_target == "window") {
|
||||
return std::to_string(window_selector.get_selection());
|
||||
return std::to_string(region_selector.get_window_selection());
|
||||
} else if(capture_target == "focused_monitor") {
|
||||
std::optional<CursorInfo> cursor_info;
|
||||
if(cursor_tracker) {
|
||||
@@ -2868,7 +2875,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
bool Overlay::on_press_start_replay(bool disable_notification, bool finished_selection) {
|
||||
if(region_selector.is_started() || window_selector.is_started())
|
||||
if(region_selector.is_started())
|
||||
return false;
|
||||
|
||||
switch(recording_status) {
|
||||
@@ -2911,15 +2918,6 @@ namespace gsr {
|
||||
return true;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
|
||||
if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) {
|
||||
char err_msg[256];
|
||||
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;
|
||||
}
|
||||
|
||||
if(config.replay_config.record_options.record_area_option == "region" && !finished_selection) {
|
||||
start_region_capture = true;
|
||||
on_region_selected = [disable_notification, this]() {
|
||||
@@ -2930,12 +2928,21 @@ namespace gsr {
|
||||
|
||||
if(config.replay_config.record_options.record_area_option == "window" && !finished_selection) {
|
||||
start_window_capture = true;
|
||||
on_window_selected = [disable_notification, this]() {
|
||||
on_region_selected = [disable_notification, this]() {
|
||||
on_press_start_replay(disable_notification, true);
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
recording_capture_target = get_capture_target(config.replay_config.record_options.record_area_option, capture_options);
|
||||
if(!validate_capture_target(config.replay_config.record_options.record_area_option, capture_options)) {
|
||||
char err_msg[256];
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO: Validate input, fallback to valid values
|
||||
const std::string fps = std::to_string(config.replay_config.record_options.fps);
|
||||
const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate);
|
||||
@@ -3037,7 +3044,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
void Overlay::on_press_start_record(bool finished_selection, RecordForceType force_type) {
|
||||
if(region_selector.is_started() || window_selector.is_started())
|
||||
if(region_selector.is_started())
|
||||
return;
|
||||
|
||||
switch(recording_status) {
|
||||
@@ -3151,15 +3158,6 @@ namespace gsr {
|
||||
break;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
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), 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;
|
||||
}
|
||||
|
||||
if(record_area_option == "region" && !finished_selection) {
|
||||
start_region_capture = true;
|
||||
on_region_selected = [this, force_type]() {
|
||||
@@ -3170,12 +3168,21 @@ namespace gsr {
|
||||
|
||||
if(record_area_option == "window" && !finished_selection) {
|
||||
start_window_capture = true;
|
||||
on_window_selected = [this, force_type]() {
|
||||
on_region_selected = [this, force_type]() {
|
||||
on_press_start_record(true, force_type);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
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), 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;
|
||||
}
|
||||
|
||||
record_filepath.clear();
|
||||
|
||||
// TODO: Validate input, fallback to valid values
|
||||
@@ -3309,7 +3316,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
void Overlay::on_press_start_stream(bool finished_selection) {
|
||||
if(region_selector.is_started() || window_selector.is_started())
|
||||
if(region_selector.is_started())
|
||||
return;
|
||||
|
||||
switch(recording_status) {
|
||||
@@ -3349,15 +3356,6 @@ namespace gsr {
|
||||
return;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options);
|
||||
if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) {
|
||||
char err_msg[256];
|
||||
snprintf(err_msg, sizeof(err_msg), 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;
|
||||
}
|
||||
|
||||
if(config.streaming_config.record_options.record_area_option == "region" && !finished_selection) {
|
||||
start_region_capture = true;
|
||||
on_region_selected = [this]() {
|
||||
@@ -3368,12 +3366,21 @@ namespace gsr {
|
||||
|
||||
if(config.streaming_config.record_options.record_area_option == "window" && !finished_selection) {
|
||||
start_window_capture = true;
|
||||
on_window_selected = [this]() {
|
||||
on_region_selected = [this]() {
|
||||
on_press_start_stream(true);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
recording_capture_target = get_capture_target(config.streaming_config.record_options.record_area_option, capture_options);
|
||||
if(!validate_capture_target(config.streaming_config.record_options.record_area_option, capture_options)) {
|
||||
char err_msg[256];
|
||||
snprintf(err_msg, sizeof(err_msg), 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;
|
||||
}
|
||||
|
||||
// TODO: Validate input, fallback to valid values
|
||||
const std::string fps = std::to_string(config.streaming_config.record_options.fps);
|
||||
const std::string video_bitrate = std::to_string(config.streaming_config.record_options.video_bitrate);
|
||||
@@ -3466,7 +3473,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
void Overlay::on_press_take_screenshot(bool finished_selection, ScreenshotForceType force_type) {
|
||||
if(region_selector.is_started() || window_selector.is_started())
|
||||
if(region_selector.is_started())
|
||||
return;
|
||||
|
||||
if(gpu_screen_recorder_screenshot_process > 0) {
|
||||
@@ -3487,15 +3494,6 @@ namespace gsr {
|
||||
break;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
screenshot_capture_target = get_capture_target(record_area_option, capture_options);
|
||||
if(!validate_capture_target(record_area_option, capture_options)) {
|
||||
char err_msg[256];
|
||||
snprintf(err_msg, sizeof(err_msg), 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;
|
||||
}
|
||||
|
||||
if(record_area_option == "region" && !finished_selection) {
|
||||
start_region_capture = true;
|
||||
on_region_selected = [this, force_type]() {
|
||||
@@ -3506,12 +3504,21 @@ namespace gsr {
|
||||
|
||||
if(record_area_option == "window" && !finished_selection) {
|
||||
start_window_capture = true;
|
||||
on_window_selected = [this, force_type]() {
|
||||
on_region_selected = [this, force_type]() {
|
||||
on_press_take_screenshot(true, force_type);
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const SupportedCaptureOptions capture_options = get_supported_capture_options(gsr_info);
|
||||
screenshot_capture_target = get_capture_target(record_area_option, capture_options);
|
||||
if(!validate_capture_target(record_area_option, capture_options)) {
|
||||
char err_msg[256];
|
||||
snprintf(err_msg, sizeof(err_msg), 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;
|
||||
}
|
||||
|
||||
// TODO: Validate input, fallback to valid values
|
||||
std::string output_file;
|
||||
if(config.screenshot_config.save_screenshot_to_disk)
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/extensions/XInput2.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
#include <X11/extensions/shape.h>
|
||||
#include <mglpp/system/Rect.hpp>
|
||||
|
||||
namespace gsr {
|
||||
static const int cursor_window_size = 32;
|
||||
@@ -66,7 +68,11 @@ namespace gsr {
|
||||
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
|
||||
}, // Bottom
|
||||
};
|
||||
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted);
|
||||
|
||||
if(width == 0 && height == 0)
|
||||
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 0, ShapeSet, Unsorted);
|
||||
else
|
||||
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted);
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
@@ -96,7 +102,8 @@ namespace gsr {
|
||||
height = abs(height);
|
||||
}
|
||||
|
||||
XDrawRectangle(dpy, window, gc, x, y, width, height);
|
||||
if(width != 0 && height != 0)
|
||||
XDrawRectangle(dpy, window, gc, x, y, width, height);
|
||||
}
|
||||
|
||||
static Window create_cursor_window(Display *dpy, int width, int height, XVisualInfo *vinfo, unsigned long background_pixel) {
|
||||
@@ -115,6 +122,13 @@ namespace gsr {
|
||||
return window;
|
||||
}
|
||||
|
||||
static void draw_rectangle_or_region(Display *dpy, Window window, GC region_gc, int region_border_size, bool is_wayland, mgl::vec2i pos, mgl::vec2i size) {
|
||||
if(is_wayland)
|
||||
draw_rectangle(dpy, window, region_gc, pos.x, pos.y, size.x, size.y);
|
||||
else
|
||||
set_region_rectangle(dpy, window, pos.x, pos.y, size.x, size.y, region_border_size);
|
||||
}
|
||||
|
||||
static void draw_rectangle_around_selected_monitor(Display *dpy, Window window, GC region_gc, int region_border_size, bool is_wayland, const std::vector<Monitor> &monitors, mgl::vec2i cursor_pos) {
|
||||
const Monitor *focused_monitor = nullptr;
|
||||
for(const Monitor &monitor : monitors) {
|
||||
@@ -137,10 +151,7 @@ namespace gsr {
|
||||
height = focused_monitor->size.y;
|
||||
}
|
||||
|
||||
if(is_wayland)
|
||||
draw_rectangle(dpy, window, region_gc, x, y, width, height);
|
||||
else
|
||||
set_region_rectangle(dpy, window, x, y, width, height, region_border_size);
|
||||
draw_rectangle_or_region(dpy, window, region_gc, region_border_size, is_wayland, mgl::vec2i(x, y), mgl::vec2i(width, height));
|
||||
}
|
||||
|
||||
static void update_cursor_window(Display *dpy, Window window, Window cursor_window, bool is_wayland, int cursor_x, int cursor_y, int cursor_window_size, int thickness, GC cursor_gc) {
|
||||
@@ -222,6 +233,42 @@ namespace gsr {
|
||||
};
|
||||
}
|
||||
|
||||
static std::vector<RegionWindow> query_windows(Display *dpy) {
|
||||
std::vector<RegionWindow> windows;
|
||||
|
||||
Window root_return = None;
|
||||
Window parent_return = None;
|
||||
Window *children_return = nullptr;
|
||||
unsigned int num_children_return = 0;
|
||||
if(!XQueryTree(dpy, DefaultRootWindow(dpy), &root_return, &parent_return, &children_return, &num_children_return) || !children_return)
|
||||
return windows;
|
||||
|
||||
for(int i = (int)num_children_return - 1; i >= 0; --i) {
|
||||
const Window child_window = children_return[i];
|
||||
XWindowAttributes win_attr;
|
||||
if(XGetWindowAttributes(dpy, child_window, &win_attr) && !win_attr.override_redirect && win_attr.c_class == InputOutput && win_attr.map_state == IsViewable) {
|
||||
windows.push_back(
|
||||
RegionWindow{
|
||||
child_window,
|
||||
mgl::vec2i(win_attr.x, win_attr.y),
|
||||
mgl::vec2i(win_attr.width, win_attr.height)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
XFree(children_return);
|
||||
return windows;
|
||||
}
|
||||
|
||||
static std::optional<RegionWindow> get_window_by_position(const std::vector<RegionWindow> &windows, mgl::vec2i pos) {
|
||||
for(const RegionWindow &window : windows) {
|
||||
if(mgl::IntRect(window.pos, window.size).contains(pos))
|
||||
return window;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
RegionSelector::RegionSelector() {
|
||||
|
||||
}
|
||||
@@ -230,7 +277,7 @@ namespace gsr {
|
||||
stop();
|
||||
}
|
||||
|
||||
bool RegionSelector::start(mgl::Color border_color) {
|
||||
bool RegionSelector::start(SelectionType selection_type, mgl::Color border_color) {
|
||||
if(dpy)
|
||||
return false;
|
||||
|
||||
@@ -328,11 +375,20 @@ namespace gsr {
|
||||
hide_window_from_taskbar(dpy, cursor_window);
|
||||
}
|
||||
|
||||
draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos);
|
||||
windows = query_windows(dpy);
|
||||
|
||||
if(selection_type == SelectionType::WINDOW) {
|
||||
focused_window = get_window_by_position(windows, cursor_pos);
|
||||
if(focused_window)
|
||||
draw_rectangle_or_region(dpy, region_window, region_gc, region_border_size, is_wayland, focused_window->pos, focused_window->size);
|
||||
} else if(selection_type == SelectionType::REGION) {
|
||||
draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos);
|
||||
}
|
||||
|
||||
XFlush(dpy);
|
||||
selected = false;
|
||||
canceled = false;
|
||||
this->selection_type = selection_type;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -375,6 +431,8 @@ namespace gsr {
|
||||
XCloseDisplay(dpy);
|
||||
dpy = nullptr;
|
||||
selecting_region = false;
|
||||
monitors.clear();
|
||||
windows.clear();
|
||||
}
|
||||
|
||||
bool RegionSelector::is_started() const {
|
||||
@@ -441,20 +499,35 @@ namespace gsr {
|
||||
return result;
|
||||
}
|
||||
|
||||
Region RegionSelector::get_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const {
|
||||
Region RegionSelector::get_region_selection(Display *x11_dpy, struct wl_display *wayland_dpy) const {
|
||||
assert(selection_type == SelectionType::REGION);
|
||||
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;
|
||||
}
|
||||
|
||||
Window RegionSelector::get_window_selection() const {
|
||||
assert(selection_type == SelectionType::WINDOW);
|
||||
if(focused_window)
|
||||
return focused_window->window;
|
||||
else
|
||||
return None;
|
||||
}
|
||||
|
||||
RegionSelector::SelectionType RegionSelector::get_selection_type() const {
|
||||
return selection_type;
|
||||
}
|
||||
|
||||
void RegionSelector::on_button_press(const void *de) {
|
||||
const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
|
||||
if(device_event->detail != Button1)
|
||||
return;
|
||||
|
||||
region.pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
selecting_region = true;
|
||||
if(selection_type == SelectionType::REGION) {
|
||||
region.pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
selecting_region = true;
|
||||
}
|
||||
}
|
||||
|
||||
void RegionSelector::on_button_release(const void *de) {
|
||||
@@ -462,8 +535,23 @@ namespace gsr {
|
||||
if(device_event->detail != Button1)
|
||||
return;
|
||||
|
||||
if(!selecting_region)
|
||||
return;
|
||||
if(selection_type == SelectionType::WINDOW) {
|
||||
focused_window = get_window_by_position(windows, mgl::vec2i(device_event->root_x, device_event->root_y));
|
||||
if(focused_window) {
|
||||
const Window real_window = window_get_target_window_child(dpy, focused_window->window);
|
||||
XWindowAttributes win_attr;
|
||||
if(XGetWindowAttributes(dpy, real_window, &win_attr)) {
|
||||
focused_window = RegionWindow{
|
||||
real_window,
|
||||
mgl::vec2i(win_attr.x, win_attr.y),
|
||||
mgl::vec2i(win_attr.width, win_attr.height)
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if(selection_type == SelectionType::REGION) {
|
||||
if(!selecting_region)
|
||||
return;
|
||||
}
|
||||
|
||||
if(is_wayland) {
|
||||
XClearWindow(dpy, region_window);
|
||||
@@ -473,7 +561,11 @@ namespace gsr {
|
||||
}
|
||||
selecting_region = false;
|
||||
|
||||
cursor_pos = region.pos + region.size;
|
||||
if(selection_type == SelectionType::WINDOW) {
|
||||
cursor_pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
} else if(selection_type == SelectionType::REGION) {
|
||||
cursor_pos = region.pos + region.size;
|
||||
}
|
||||
|
||||
if(region.size.x < 0) {
|
||||
region.pos.x += region.size.x;
|
||||
@@ -497,19 +589,26 @@ namespace gsr {
|
||||
void RegionSelector::on_mouse_motion(const void *de) {
|
||||
const XIDeviceEvent *device_event = (XIDeviceEvent*)de;
|
||||
XClearWindow(dpy, region_window);
|
||||
|
||||
if(selecting_region) {
|
||||
region.size.x = device_event->root_x - region.pos.x;
|
||||
region.size.y = device_event->root_y - region.pos.y;
|
||||
cursor_pos = region.pos + region.size;
|
||||
|
||||
if(is_wayland)
|
||||
draw_rectangle(dpy, region_window, region_gc, region.pos.x, region.pos.y, region.size.x, region.size.y);
|
||||
draw_rectangle_or_region(dpy, region_window, region_gc, region_border_size, is_wayland, region.pos, region.size);
|
||||
} else if(selection_type == SelectionType::WINDOW) {
|
||||
cursor_pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
|
||||
focused_window = get_window_by_position(windows, cursor_pos);
|
||||
if(focused_window)
|
||||
draw_rectangle_or_region(dpy, region_window, region_gc, region_border_size, is_wayland, focused_window->pos, focused_window->size);
|
||||
else
|
||||
set_region_rectangle(dpy, region_window, region.pos.x, region.pos.y, region.size.x, region.size.y, region_border_size);
|
||||
} else {
|
||||
draw_rectangle_or_region(dpy, region_window, region_gc, region_border_size, is_wayland, mgl::vec2i(0, 0), mgl::vec2i(0, 0));
|
||||
} else if(selection_type == SelectionType::REGION) {
|
||||
cursor_pos = { (int)device_event->root_x, (int)device_event->root_y };
|
||||
draw_rectangle_around_selected_monitor(dpy, region_window, region_gc, region_border_size, is_wayland, monitors, cursor_pos);
|
||||
}
|
||||
|
||||
update_cursor_window(dpy, region_window, cursor_window, is_wayland, cursor_pos.x, cursor_pos.y, cursor_window_size, cursor_thickness, cursor_gc);
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "../include/Translation.hpp"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <fstream>
|
||||
|
||||
@@ -31,13 +30,6 @@ namespace gsr {
|
||||
return "en";
|
||||
}
|
||||
|
||||
std::string Translation::trim(const std::string& str) {
|
||||
size_t start = str.find_first_not_of(" \t\r\n");
|
||||
if (start == std::string::npos) return "";
|
||||
size_t end = str.find_last_not_of(" \t\r\n");
|
||||
return str.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
void Translation::process_escapes(std::string& str) {
|
||||
size_t pos = 0;
|
||||
while ((pos = str.find("\\n", pos)) != std::string::npos) {
|
||||
@@ -93,8 +85,6 @@ namespace gsr {
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
line = trim(line);
|
||||
|
||||
if (line.empty() || line[0] == '#') continue;
|
||||
|
||||
size_t eq_pos = line.find('=');
|
||||
@@ -124,6 +114,9 @@ namespace gsr {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
#include "../include/WindowSelector.hpp"
|
||||
#include "../include/WindowUtils.hpp"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/extensions/shape.h>
|
||||
#include <X11/cursorfont.h>
|
||||
#include <X11/keysym.h>
|
||||
|
||||
namespace gsr {
|
||||
static const int rectangle_border_size = 2;
|
||||
|
||||
static int max_int(int a, int b) {
|
||||
return a >= b ? a : b;
|
||||
}
|
||||
|
||||
static void set_region_rectangle(Display *dpy, Window window, int x, int y, int width, int height, int border_size) {
|
||||
if(width < 0) {
|
||||
x += width;
|
||||
width = abs(width);
|
||||
}
|
||||
|
||||
if(height < 0) {
|
||||
y += height;
|
||||
height = abs(height);
|
||||
}
|
||||
|
||||
XRectangle rectangles[] = {
|
||||
{
|
||||
(short)max_int(0, x), (short)max_int(0, y),
|
||||
(unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height)
|
||||
}, // Left
|
||||
{
|
||||
(short)max_int(0, x + width - border_size), (short)max_int(0, y),
|
||||
(unsigned short)max_int(0, border_size), (unsigned short)max_int(0, height)
|
||||
}, // Right
|
||||
{
|
||||
(short)max_int(0, x + border_size), (short)max_int(0, y),
|
||||
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
|
||||
}, // Top
|
||||
{
|
||||
(short)max_int(0, x + border_size), (short)max_int(0, y + height - border_size),
|
||||
(unsigned short)max_int(0, width - border_size*2), (unsigned short)max_int(0, border_size)
|
||||
}, // Bottom
|
||||
};
|
||||
XShapeCombineRectangles(dpy, window, ShapeBounding, 0, 0, rectangles, 4, ShapeSet, Unsorted);
|
||||
XFlush(dpy);
|
||||
}
|
||||
|
||||
static unsigned long mgl_color_to_x11_color(mgl::Color color) {
|
||||
if(color.a == 0)
|
||||
return 0;
|
||||
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 Window get_cursor_window(Display *dpy) {
|
||||
Window root_window = None;
|
||||
Window window = None;
|
||||
int dummy_i;
|
||||
unsigned int dummy_u;
|
||||
mgl::vec2i root_pos;
|
||||
XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
|
||||
return window;
|
||||
}
|
||||
|
||||
static void get_window_geometry(Display *dpy, Window window, mgl::vec2i &pos, mgl::vec2i &size) {
|
||||
Window root_window;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
unsigned int w = 0;
|
||||
unsigned int h = 0;
|
||||
unsigned int dummy_border, dummy_depth;
|
||||
XGetGeometry(dpy, window, &root_window, &x, &y, &w, &h, &dummy_border, &dummy_depth);
|
||||
pos.x = x;
|
||||
pos.y = y;
|
||||
size.x = w;
|
||||
size.y = h;
|
||||
}
|
||||
|
||||
WindowSelector::WindowSelector() {
|
||||
|
||||
}
|
||||
|
||||
WindowSelector::~WindowSelector() {
|
||||
stop();
|
||||
}
|
||||
|
||||
bool WindowSelector::start(mgl::Color border_color) {
|
||||
if(dpy)
|
||||
return false;
|
||||
|
||||
const unsigned long border_color_x11 = mgl_color_to_x11_color(border_color);
|
||||
dpy = XOpenDisplay(nullptr);
|
||||
if(!dpy) {
|
||||
fprintf(stderr, "Error: WindowSelector::start: failed to connect to the X11 server\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
const Window cursor_window = get_cursor_window(dpy);
|
||||
mgl::vec2i cursor_window_pos, cursor_window_size;
|
||||
get_window_geometry(dpy, cursor_window, cursor_window_pos, cursor_window_size);
|
||||
|
||||
XVisualInfo vinfo;
|
||||
memset(&vinfo, 0, sizeof(vinfo));
|
||||
XMatchVisualInfo(dpy, DefaultScreen(dpy), 32, TrueColor, &vinfo);
|
||||
border_window_colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo.visual, AllocNone);
|
||||
|
||||
XSetWindowAttributes window_attr;
|
||||
window_attr.background_pixel = border_color_x11;
|
||||
window_attr.border_pixel = 0;
|
||||
window_attr.override_redirect = true;
|
||||
window_attr.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
|
||||
window_attr.colormap = border_window_colormap;
|
||||
|
||||
Screen *screen = XDefaultScreenOfDisplay(dpy);
|
||||
border_window = XCreateWindow(dpy, DefaultRootWindow(dpy), 0, 0, XWidthOfScreen(screen), XHeightOfScreen(screen), 0,
|
||||
vinfo.depth, InputOutput, vinfo.visual, CWBackPixel | CWBorderPixel | CWOverrideRedirect | CWEventMask | CWColormap, &window_attr);
|
||||
if(!border_window) {
|
||||
fprintf(stderr, "Error: WindowSelector::start: failed to create region window\n");
|
||||
stop();
|
||||
return false;
|
||||
}
|
||||
set_window_size_not_resizable(dpy, border_window, XWidthOfScreen(screen), XHeightOfScreen(screen));
|
||||
|
||||
unsigned char data = 2; // Prefer being composed to allow transparency. Do this to prevent the compositor from getting turned on/off when taking a screenshot
|
||||
XChangeProperty(dpy, border_window, XInternAtom(dpy, "_NET_WM_BYPASS_COMPOSITOR", False), XA_CARDINAL, 32, PropModeReplace, &data, 1);
|
||||
|
||||
if(cursor_window && cursor_window != DefaultRootWindow(dpy))
|
||||
set_region_rectangle(dpy, border_window, cursor_window_pos.x, cursor_window_pos.y, cursor_window_size.x, cursor_window_size.y, rectangle_border_size);
|
||||
else
|
||||
set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0);
|
||||
make_window_click_through(dpy, border_window);
|
||||
XMapWindow(dpy, border_window);
|
||||
|
||||
crosshair_cursor = XCreateFontCursor(dpy, XC_crosshair);
|
||||
XGrabPointer(dpy, DefaultRootWindow(dpy), True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask, GrabModeAsync, GrabModeAsync, None, crosshair_cursor, CurrentTime);
|
||||
XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, GrabModeAsync, CurrentTime);
|
||||
XFlush(dpy);
|
||||
|
||||
selected = false;
|
||||
canceled = false;
|
||||
selected_window = None;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowSelector::stop() {
|
||||
if(!dpy)
|
||||
return;
|
||||
|
||||
XUngrabPointer(dpy, CurrentTime);
|
||||
XUngrabKeyboard(dpy, CurrentTime);
|
||||
|
||||
if(border_window_colormap) {
|
||||
XFreeColormap(dpy, border_window_colormap);
|
||||
border_window_colormap = 0;
|
||||
}
|
||||
|
||||
if(border_window) {
|
||||
XDestroyWindow(dpy, border_window);
|
||||
border_window = 0;
|
||||
}
|
||||
|
||||
if(crosshair_cursor) {
|
||||
XFreeCursor(dpy, crosshair_cursor);
|
||||
crosshair_cursor = None;
|
||||
}
|
||||
|
||||
XFlush(dpy);
|
||||
XSync(dpy, False);
|
||||
|
||||
XCloseDisplay(dpy);
|
||||
dpy = nullptr;
|
||||
}
|
||||
|
||||
bool WindowSelector::is_started() const {
|
||||
return dpy != nullptr;
|
||||
}
|
||||
|
||||
bool WindowSelector::failed() const {
|
||||
return !dpy;
|
||||
}
|
||||
|
||||
bool WindowSelector::poll_events() {
|
||||
if(!dpy || selected)
|
||||
return false;
|
||||
|
||||
XEvent xev;
|
||||
while(XPending(dpy)) {
|
||||
XNextEvent(dpy, &xev);
|
||||
|
||||
if(xev.type == MotionNotify) {
|
||||
const Window motion_window = xev.xmotion.subwindow;
|
||||
mgl::vec2i motion_window_pos, motion_window_size;
|
||||
get_window_geometry(dpy, motion_window, motion_window_pos, motion_window_size);
|
||||
if(motion_window && motion_window != DefaultRootWindow(dpy))
|
||||
set_region_rectangle(dpy, border_window, motion_window_pos.x, motion_window_pos.y, motion_window_size.x, motion_window_size.y, rectangle_border_size);
|
||||
else
|
||||
set_region_rectangle(dpy, border_window, 0, 0, 0, 0, 0);
|
||||
XFlush(dpy);
|
||||
} else if(xev.type == ButtonRelease && xev.xbutton.button == Button1) {
|
||||
selected_window = xev.xbutton.subwindow;
|
||||
const Window clicked_window_real = window_get_target_window_child(dpy, selected_window);
|
||||
if(clicked_window_real)
|
||||
selected_window = clicked_window_real;
|
||||
selected = true;
|
||||
|
||||
stop();
|
||||
break;
|
||||
} else if(xev.type == KeyRelease && XKeycodeToKeysym(dpy, xev.xkey.keycode, 0) == XK_Escape) {
|
||||
canceled = true;
|
||||
selected = false;
|
||||
stop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowSelector::take_selection() {
|
||||
const bool result = selected;
|
||||
selected = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool WindowSelector::take_canceled() {
|
||||
const bool result = canceled;
|
||||
canceled = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
Window WindowSelector::get_selection() const {
|
||||
return selected_window;
|
||||
}
|
||||
}
|
||||
@@ -229,7 +229,7 @@ namespace gsr {
|
||||
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) {
|
||||
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;
|
||||
unsigned long num_items = 0;
|
||||
@@ -284,14 +284,14 @@ namespace gsr {
|
||||
return None;
|
||||
|
||||
Window found_window = None;
|
||||
for(int i = num_children - 1; i >= 0; --i) {
|
||||
for(int i = (int)num_children - 1; i >= 0; --i) {
|
||||
if(children[i] && window_is_user_program(display, children[i])) {
|
||||
found_window = children[i];
|
||||
goto finished;
|
||||
}
|
||||
}
|
||||
|
||||
for(int i = num_children - 1; i >= 0; --i) {
|
||||
for(int i = (int)num_children - 1; i >= 0; --i) {
|
||||
if(children[i]) {
|
||||
Window win = window_get_target_window_child(display, children[i]);
|
||||
if(win) {
|
||||
|
||||
@@ -528,6 +528,7 @@ namespace gsr {
|
||||
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) {
|
||||
|
||||
@@ -231,21 +231,10 @@ namespace gsr {
|
||||
if(!it->mjpeg_setups.empty())
|
||||
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));
|
||||
@@ -259,6 +248,20 @@ namespace gsr {
|
||||
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 ¤t_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;
|
||||
}
|
||||
@@ -315,6 +318,12 @@ namespace gsr {
|
||||
webcam_video_setup_box_ptr->add_item(setup_str, setup_str, false);
|
||||
}
|
||||
}
|
||||
|
||||
const RecordOptions ¤t_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));
|
||||
@@ -1118,7 +1127,7 @@ 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), TR("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(TR("Don't turn on replay automatically"), "dont_turn_on_automatically");
|
||||
@@ -1131,7 +1140,7 @@ namespace gsr {
|
||||
|
||||
std::unique_ptr<CheckBox> SettingsPage::create_save_replay_in_game_folder() {
|
||||
char text[256];
|
||||
snprintf(text, sizeof(text), TR("Save video in a folder based on the focused applications name%s"), supports_window_title ? "" : " (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;
|
||||
@@ -1144,7 +1153,7 @@ namespace gsr {
|
||||
}
|
||||
|
||||
std::unique_ptr<Label> SettingsPage::create_estimated_replay_file_size() {
|
||||
auto label = std::make_unique<Label>(&get_theme().body_font, TR("Estimated video max file size in RAM: 57.60MB"), get_color_theme().text_color);
|
||||
auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video max file size in RAM: 57.60MB", get_color_theme().text_color);
|
||||
estimated_file_size_ptr = label.get();
|
||||
return label;
|
||||
}
|
||||
@@ -1263,7 +1272,7 @@ namespace gsr {
|
||||
general_list->add_widget(create_low_power_mode());
|
||||
|
||||
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("replay"), 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) {
|
||||
@@ -1284,14 +1293,14 @@ namespace gsr {
|
||||
|
||||
std::unique_ptr<CheckBox> SettingsPage::create_save_recording_in_game_folder() {
|
||||
char text[256];
|
||||
snprintf(text, sizeof(text), TR("Save video in a folder based on the focused applications name%s"), supports_window_title ? "" : " (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;
|
||||
}
|
||||
|
||||
std::unique_ptr<Label> SettingsPage::create_estimated_record_file_size() {
|
||||
auto label = std::make_unique<Label>(&get_theme().body_font, TR("Estimated video file size per minute (excluding audio): 345.60MB"), get_color_theme().text_color);
|
||||
auto label = std::make_unique<Label>(&get_theme().body_font, "Estimated video file size per minute (excluding audio): 345.60MB", get_color_theme().text_color);
|
||||
estimated_file_size_ptr = label.get();
|
||||
return label;
|
||||
}
|
||||
@@ -1320,7 +1329,7 @@ namespace gsr {
|
||||
general_list->add_widget(create_low_power_mode());
|
||||
|
||||
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("recording"), 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");
|
||||
@@ -1453,7 +1462,7 @@ namespace gsr {
|
||||
general_list->add_widget(create_low_power_mode());
|
||||
|
||||
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("streaming"), 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";
|
||||
@@ -1755,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);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const DAEMON_DBUS_NAME = "com.dec05eba.gpu_screen_recorder.gsr_kwin_helper";
|
||||
const DAEMON_DBUS_NAME = "com.dec05eba.gpu_screen_recorder";
|
||||
|
||||
// utils
|
||||
function sendNewActiveWindowTitle(title) {
|
||||
|
||||
@@ -7,7 +7,7 @@ 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.gsr_kwin_helper'>\n"
|
||||
" <interface name='com.dec05eba.gpu_screen_recorder'>\n"
|
||||
" <method name='setActiveWindowTitle'>\n"
|
||||
" <arg type='s' name='title' direction='in'/>\n"
|
||||
" </method>\n"
|
||||
@@ -41,7 +41,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
int ret = dbus_bus_request_name(connection, "com.dec05eba.gpu_screen_recorder.gsr_kwin_helper",
|
||||
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";
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cerr << "Info: gsr-kwin-helper: DBus server initialized on com.dec05eba.gpu_screen_recorder.gsr_kwin_helper\n";
|
||||
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;
|
||||
|
||||
@@ -83,7 +83,7 @@ public:
|
||||
|
||||
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.gsr_kwin_helper", "setActiveWindowTitle")) {
|
||||
} else if (dbus_message_is_method_call(msg, "com.dec05eba.gpu_screen_recorder", "setActiveWindowTitle")) {
|
||||
handle_set_title(msg);
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ public:
|
||||
}
|
||||
|
||||
void send_error_reply(DBusMessage* msg, const char* error_msg) {
|
||||
DBusMessage* reply = dbus_message_new_error(msg, "com.dec05eba.gpu_screen_recorder.gsr_kwin_helper.Error", 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);
|
||||
@@ -214,7 +214,7 @@ public:
|
||||
|
||||
~GsrKwinHelper() {
|
||||
if (connection) {
|
||||
dbus_bus_release_name(connection, "com.dec05eba.gpu_screen_recorder.gsr_kwin_helper", nullptr);
|
||||
dbus_bus_release_name(connection, "com.dec05eba.gpu_screen_recorder", nullptr);
|
||||
dbus_connection_unref(connection);
|
||||
}
|
||||
}
|
||||
|
||||
412
translations/es.txt
Normal file
412
translations/es.txt
Normal 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
|
||||
@@ -267,7 +267,6 @@ Directory to save screenshots:=Каталог для сохранения сни
|
||||
Image format:=Формат изображения:
|
||||
|
||||
Save screenshot in a folder based on the focused applications name=Сохранять снимок в папку по имени активного приложения
|
||||
Save screenshot in a folder based on the focused applications name (X11 applications only)=Сохранять снимок в папку по имени активного приложения (только X11-приложения)
|
||||
|
||||
Save screenshot to clipboard=Сохранять снимок в буфер обмена
|
||||
Save screenshot to clipboard (Not supported properly by Wayland)=Сохранять снимок в буфер обмена (в Wayland поддерживается некорректно)
|
||||
@@ -319,6 +318,7 @@ 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=Добавить вход
|
||||
@@ -408,4 +408,4 @@ Custom=Другое
|
||||
Stream URL:=URL трансляции:
|
||||
Stream key:=Ключ трансляции:
|
||||
Streaming info=Информация о трансляции
|
||||
Streaming indicator=Индикатор трансляции
|
||||
Streaming indicator=Индикатор трансляции
|
||||
@@ -278,7 +278,6 @@ Directory to save screenshots:=
|
||||
Image format:=
|
||||
|
||||
Save screenshot in a folder based on the focused applications name=
|
||||
Save screenshot in a folder based on the focused applications name (X11 applications only)=
|
||||
|
||||
Save screenshot to clipboard=
|
||||
Save screenshot to clipboard (Not supported properly by Wayland)=
|
||||
@@ -331,6 +330,7 @@ Application: =
|
||||
Custom...=
|
||||
|
||||
Save video in a folder based on the focused applications name%s=
|
||||
(X11 applications only)=
|
||||
|
||||
Add audio track=
|
||||
Add input device=
|
||||
@@ -387,6 +387,7 @@ Do not force the GPU to go into high performance mode when recording.\nMay affec
|
||||
Show %s notifications=
|
||||
Show %s status with scroll lock led=
|
||||
Recording indicator=
|
||||
recording=
|
||||
|
||||
Simple=
|
||||
Audio track #%d=
|
||||
@@ -397,6 +398,7 @@ 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=
|
||||
@@ -421,3 +423,4 @@ Stream URL:=
|
||||
Stream key:=
|
||||
Streaming info=
|
||||
Streaming indicator=
|
||||
streaming=
|
||||
@@ -266,7 +266,6 @@ Directory to save screenshots:=Каталог для збереження зні
|
||||
Image format:=Формат зображення:
|
||||
|
||||
Save screenshot in a folder based on the focused applications name=Зберігати знімок у папку за ім'ям активної програми
|
||||
Save screenshot in a folder based on the focused applications name (X11 applications only)=Зберігати знімок у папку за ім'ям активної програми (лише X11-програми)
|
||||
|
||||
Save screenshot to clipboard=Зберігати знімок до буфера обміну
|
||||
Save screenshot to clipboard (Not supported properly by Wayland)=Зберігати знімок до буфера обміну (в Wayland підтримується некоректно)
|
||||
@@ -317,6 +316,7 @@ 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=Додати вхід
|
||||
|
||||
Reference in New Issue
Block a user