aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md47
-rw-r--r--TODO46
-rw-r--r--com.dec05eba.gpu_screen_recorder.appdata.xml132
-rw-r--r--com.dec05eba.gpu_screen_recorder.desktop (renamed from gpu-screen-recorder-gtk.desktop)0
-rwxr-xr-xinstall.sh3
-rw-r--r--meson.build17
-rw-r--r--project.conf7
-rw-r--r--src/config.hpp52
-rw-r--r--src/egl.c380
-rw-r--r--src/egl.h114
-rw-r--r--src/global_shortcuts.c336
-rw-r--r--src/global_shortcuts.h41
-rw-r--r--src/library_loader.c34
-rw-r--r--src/library_loader.h17
-rw-r--r--src/main.cpp2495
15 files changed, 1951 insertions, 1770 deletions
diff --git a/README.md b/README.md
index 82a89db..ad83c73 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
![](https://dec05eba.com/images/gpu_screen_recorder_logo_small.png)
-gtk frontend for [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/).
+GTK frontend for [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/).
This is a screen recorder that has minimal impact on system performance by recording your monitor using the GPU only,
similar to shadowplay on windows. This is the fastest screen recording tool for Linux.
@@ -8,34 +8,29 @@ similar to shadowplay on windows. This is the fastest screen recording tool for
This screen recorder can be used for recording your desktop offline, for live streaming and for nvidia shadowplay-like instant replay,
where only the last few minutes are saved.
-More info at [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/).
-
-## Note
-This software works with x11 and wayland, but when using wayland only monitors can be recorded. Hotkeys are also not supported on wayland (wayland doesn't really support this properly yet). Use X11 if you want a proper desktop experience in general.
-### TEMPORARY ISSUES
-1) Videos are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode.
-### AMD/Intel/Wayland root permission
-When recording a window under AMD/Intel no special user permission is required, however when recording a monitor (or when using wayland) the program needs root permission (to access KMS).\
-To make this safer, the part that needs root access has been moved to its own executable (to make it as small as possible).\
-For you as a user this only means that if you installed GPU Screen Recorder as a flatpak then a prompt asking for root password will show up when you start recording.
-# Performance
-On a system with a i5 4690k CPU and a GTX 1080 GPU:\
-When recording Legend of Zelda Breath of the Wild at 4k, fps drops from 30 to 7 when using OBS Studio + nvenc, however when using this screen recorder the fps remains at 30.\
-When recording GTA V at 4k on highest settings, fps drops from 60 to 23 when using obs-nvfbc + nvenc, however when using this screen recorder the fps only drops to 58. The quality is also much better when using gpu-screen-recorder.\
-It is recommended to save the video to a SSD because of the large file size, which a slow HDD might not be fast enough to handle.
-## Note about optimal performance on NVIDIA
-NVIDIA driver has a "feature" (read: bug) where it will downclock memory transfer rate when a program uses cuda (or nvenc, which uses cuda), such as GPU Screen Recorder. See https://git.dec05eba.com/gpu-screen-recorder/about/ for more information and how to overcome this.
-## Hotkey
-Hotkeys are currently only supported on X11. Most Wayland compositors are missing a way to bind hotkeys programatically. If you want to have hotkeys then you can bind hotkeys in your Wayland compositors settings.
-Bind a key to `killall -SIGINT gpu-screen-recorder` to stop recording (also saves the video when recording a regular video). Bind another key to `killall -SIGUSR1 gpu-screen-recorder` to save a replay and another key to `killall -SIGUSR2 gpu-screen-recorder` to pause/unpause the recording (when recording a regular video).
-
-## Installation
-This program depends on [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/) which needs to be installed first.\
+More info at [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/).
+
+# Installation
+This program depends on [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/) which needs to be installed first.\
Run `sudo ./install.sh` or if you are running Arch Linux, then you can find gpu screen recorder gtk on aur under the name gpu-screen-recorder-gtk-git (`yay -S gpu-screen-recorder-gtk-git`).\
-Dependencies needed when building using `build.sh` or `install.sh`: `gtk3 libx11 libxrandr libpulse libdrm wayland-client ayatana-appindicator3-0.1`.\
You can also install gpu screen recorder (the gtk gui version) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder). This flatpak includes gpu-screen-recorder so no need to install that first.
-## Screenshots
+# Dependencies
+GPU Screen Recorder GTK uses meson build system so you need to install `meson` to build GPU Screen Recorder GTK.
+
+## Build dependencies
+These are the dependencies needed to build GPU Screen Recorder GTK:
+
+* gtk3
+* libx11
+* ayatana-appindicator3-0.1c
+
+## Runtime dependencies
+There are also additional dependencies needed at runtime:
+
+* [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/)
+
+# Screenshots
![](https://www.dec05eba.com/images/gpu-screen-recorder.png)
# Donations
diff --git a/TODO b/TODO
index 2c3c517..cf8f5c3 100644
--- a/TODO
+++ b/TODO
@@ -3,8 +3,6 @@ Make sure the resolution is allowed for streaming.
Add list of windows to select from. This makes it easier to select another window that is not in the view to be clickable.
Disable overclocking and show some kind of sign that overclocking is not possible (if coolbits not set).
Button (in the field) to remove hotkey.
-Implement global hotkeys on wayland.
-Add pipewire capture option.
Error if polkit agent is not running (pkexec --help may work but the agent might not be running anyways).
Fix gui crash on stop recording on sway.
Re-enable wlroots based capture when it's fixed.
@@ -22,4 +20,46 @@ Remove the need to install gpu screen recorder flatpak as system. This can now b
Implement profiles to quickly switch between settings.
Use https://hosted.weblate.org/ for translation.
-Detect game name by using x11 window class or title. Fallback to finding pressure vessel, find the binary is runs and get the directory name directly under the proton game list directory. Fallback to pure wine.
+Detect game name by using x11 window class or title. Fallback to finding pressure vessel, find the binary is runs and get the directory name directly under the proton game list directory. Fallback to pure wine. x11 window class works in xwayland too.
+
+Have separate options for each record option (stream, record and replay) or have option to use profiles. Remake the gui and have a proper overlay! on wlroots and kde use https://wayland.app/protocols/wlr-layer-shell-unstable-v1.
+
+Add refresh button for audio devices. Put it beside the "add" button. In the new ui this should update automatically without a button.
+
+Gray out monitor capture on intel if plane is compressed. Show the user to desktop portal capture instead.
+
+Look at showmethekey https://github.com/AlynxZhou/showmethekey to see how to do global hotkeys without x11/wayland. The password prompt for this can be removed by using polkit rules, this is how SwayOSD does it.
+ doing this in the flatpak requires --device=input in the flatpak manifest if we are doing it without hack where we launch an external process, running outside the flatpak with root access.
+
+A single flatpak can only be installed either system-wide or user, so there can be a check if it's installed system-wide or user and it will only match one. With this information we can guaranteed know the flatpak directory of the running gpu screen recorder instance. The command `flatpak info -l com.dec05eba.gpu_screen_recorder` can also be used and is available for all flatpak users.
+
+Re-renable hotkeys on hyprland after it's fixed in the hyprland desktop portal. Or use hyprland specific protocol to do it ourselves, and it also works better.
+
+Replay on startup should be its own page with its own settings for everything and should not allow enabling the feature unless global hotkeys can be enable.
+ To make sure this works, create a separate program to handle global hotkeys with root access to make it work for every user and keys should be registered by requiring root access (every time its changed), with a confirm button.
+ Or have a predefined set of keys that can be changed. Also have a key to show the gui.
+ The replay on startup should be this gpu screen recorder gtk program but should launch a separate gui (in a different cpp file)
+ and should be a single page with start/stop button and whatever. It should also show a systray similar to the default one but with only
+ start/stop/save/exit button. The UI is needed for gnome users that dont have a systray!
+ Maybe create gnome extension so that gnome users can see recording status, or mention to the user that it's a gnome limitation.
+ The system startup program should work like the gpu screen recorder systemd daemon but start gpu screen recorder gtk with a special argument to launch it into this
+ replay mode. The replay mode program should loop and launch gpu screen recorder, restarting it if it crashes. If it crashes show an error dialog to the user
+ and if hotkeys cant be registered then also show an error.
+
+Notifications are not shown on kde plasma while using desktop portal capture (system-wide). This is a design choice by kde, see https://invent.kde.org/plasma/xdg-desktop-portal-kde/-/blob/master/src/screencast.cpp?ref_type=heads#L41 . The DoNotDisturb.WhenScreenSharing config controls this. This can be bypassed by making the notification critical. Maybe notifications should be set as critical? but only on kde.
+ Maybe we should create our own notification system with gtk layer shell (which is supported by every platform except gnome wayland).
+ On gnome wayland maybe we can fallback to x11? we need to add back --socket=x11 and remove --socket=fallback-x11 from flatpak manifest.
+ Maybe use this: https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/264 when that gets supported. But will gnome support that?
+ Maybe map the drm framebuffer to opengl with write permission and draw to that opengl texture with a framebuffer. That would allow us to draw on the screen anywhere on any wayland compositor (and x11 amd/intel). We can also do this after the video frame has been captured to not include it in the video.
+ Maybe use drm overlay plane, if possible.
+
+For replay on system startup add functionality to only record if an application is fullscreen (on the focused monitor, or any monitor if not possible).
+ This is easy to do with x11. For hyprland/sway we can maybe use hyprctl/swaymsg. On kde maybe we can do something similar to this: https://github.com/jinliu/kdotool. For gnome maybe do something like this: https://github.com/ActivityWatch/aw-watcher-window/pull/46/files
+
+When replay on system startup option is added also install gsr-nvidia.conf and tell the user to reboot and explain the issue.
+
+Start recording after showing start recording notification has disappeared, not at the same time.
+
+Use modprobe command. modprobe on system startup in modprobe.d directory is only available for udev, other systems need to add it to linux kernel boot parameters (is this also needed for nvidia open kernel module driver?).
+
+Save gpu screen recorder status in $XDG_RUNTIME_DIR.
diff --git a/com.dec05eba.gpu_screen_recorder.appdata.xml b/com.dec05eba.gpu_screen_recorder.appdata.xml
index 7d05e52..25b4af8 100644
--- a/com.dec05eba.gpu_screen_recorder.appdata.xml
+++ b/com.dec05eba.gpu_screen_recorder.appdata.xml
@@ -18,7 +18,7 @@
<description>
<p>
- This is a screen recorder that has minimal impact on system performance by recording a monitor using the GPU only, similar to shadowplay on windows. This is the fastest screen recording tool for Linux. This screen recorder works with both X11 and Wayland.
+ This is a screen recorder that has minimal impact on system performance by recording a monitor using the GPU only, similar to shadowplay on windows. This is the fastest screen recording tool for Linux. This screen recorder works on X11 and Wayland on AMD, Intel and NVIDIA.
</p>
<p>
This screen recorder can be used for recording your desktop offline, for live streaming and for nvidia-like instant replay, where only the last few minutes are saved.
@@ -26,8 +26,10 @@
<p>Supported video codecs:</p>
<ul>
<li>H264 (default)</li>
- <li>HEVC</li>
- <li>AV1 (not currently supported on NVIDIA in the flatpak)</li>
+ <li>HEVC (Optionally with HDR)</li>
+ <li>AV1 (Optionally with HDR. Not currently supported on NVIDIA if you use GPU Screen Recorder flatpak)</li>
+ <li>VP8</li>
+ <li>VP9</li>
</ul>
<p>Supported audio codecs:</p>
<ul>
@@ -35,12 +37,11 @@
<li>AAC</li>
</ul>
<p>
- Recording a monitor requires (restricted) root access which means that you have to install GPU Screen Recorder system-wide: flatpak install flathub --system com.dec05eba.gpu_screen_recorder
+ Recording a monitor requires (restricted) root access which means that you have to install GPU Screen Recorder system-wide: "flatpak install flathub --system com.dec05eba.gpu_screen_recorder"
and pkexec needs to be installed on the system and a polkit agent needs to be running.
+ Note that this only applies to when recording a monitor on AMD/Intel or when recording on Wayland without using the desktop portal option.
</p>
- <p>Recording a single window is only possible on X11. Hotkeys are not supported on wayland either (wayland doesn't really support this). Use X11 if you want a proper desktop experience in general.</p>
<p>AV1 is currently not supported in the flatpak for Nvidia since GPU Screen Recorder uses an older ffmpeg version to support older Nvidia cards. Install GPU Screen Recorder from source or from AUR if you want to use AV1 on Nvidia.</p>
- <p>On some Intel integrated GPUs the video can appear glitched when recording on Wayland. The only known workaround at the moment is to record on X11.</p>
<p>
Videos are in variable framerate format. Very out of date video players might have an issue playing such videos. It's recommend to use MPV to play such videos, otherwise you might experience stuttering in the video.
You can select constant frame rate mode in advanced view if you need it.
@@ -48,13 +49,10 @@
<p>
If the video doesn't play or you get green/yellow overlay then your video player is missing H264/HEVC video codec. Either install the video codecs or use mpv.
</p>
- <p>
- If the video is glitched with checkerboard pattern and you are using and Intel integrated GPU on wayland then this is a known issue and right now the only solution is to record on X11 instead.
- </p>
- <p>AMD has a driver/hardware fault that causes black bars/distorted colors on the right side/bottom of the video for certain video resolutions. This happens for both av1 and hevc, so if you have this issue then switch to h264 video codec option in advanced settings.
+ <p>AMD has a driver/hardware fault that causes black bars/distorted colors on the sides of the video for certain video resolutions. This happens for both av1 and hevc, so if you have this issue then switch to h264 video codec option in advanced settings.
</p>
<p>
- If H264/HEVC video encoding option is not available on your AMD/Intel system but you know that your supports those codecs then you may need to install mesa-extra freedesktop runtime by running this command: "flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra".
+ If H264/HEVC video encoding option is not available on your AMD/Intel system but you know that your GPU supports those codecs for encoding then you may need to install mesa-extra freedesktop runtime by running this command: "flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra".
</p>
<p>
On NVIDIA systems the flatpak NVIDIA driver needs to match the systems NVIDIA driver version or the NVIDIA driver will fail to properly load in flatpaks.
@@ -82,6 +80,118 @@
</screenshots>
<releases>
+ <release version="4.1.11" date="2024-09-21">
+ <description>
+ <ul>
+ <li>Fix recording not working on some amd systems</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.10" date="2024-09-21">
+ <description>
+ <ul>
+ <li>Revert frame timing change as it badly affected slower devices</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.9" date="2024-09-20">
+ <description>
+ <ul>
+ <li>Improve frame timing for smoother video</li>
+ <li>Improve screen recording performance on AMD when the system usage is very high</li>
+ <li>Fix recording not working on old nvidia gpus (that are stuck on nvidia driver version 470)</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.8" date="2024-09-17">
+ <description>
+ <ul>
+ <li>Improve video capture sync, making the video smoother. The video is smoother on X11 than Wayland</li>
+ <li>Improve video recording performance on AMD when the system usage is 100%</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.7" date="2024-09-06">
+ <description>
+ <ul>
+ <li>Fix tearing/stutter/cursor flicker on amd after recent amd driver update</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.6" date="2024-08-30">
+ <description>
+ <ul>
+ <li>Workaround steam deck driver bug and enable steam deck support again</li>
+ <li>Improve startup time on certain systems</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.5" date="2024-08-20">
+ <description>
+ <ul>
+ <li>Disable support for steam deck at the moment because steam deck drivers are broken</li>
+ <li>Improve quality again</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.4" date="2024-08-20">
+ <description>
+ <ul>
+ <li>Reduce video file size</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.3" date="2024-08-18">
+ <description>
+ <ul>
+ <li>Fix capture not working on wayland when recording a monitor instead of desktop portal</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.2" date="2024-08-17">
+ <description>
+ <ul>
+ <li>Allow capturing external monitors on a laptop with a dedicated gpu on X11 (for example an intel laptop with a nvidia gpu), where the monitor is connected to the dedicated gpu</li>
+ <li>Fix capture not working if audio device id has space in it (happens with virtual audio sources)</li>
+ <li>Add 10 bit color depth options for hevc and av1 to reduce banding</li>
+ <li>Fix cursor not visible when using multiple monitors in some wayland compositors</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.1" date="2024-08-15">
+ <description>
+ <ul>
+ <li>Fix error when starting application on wayland when portal capture option is available but laptop monitor is disabled</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.1.0" date="2024-08-08">
+ <description>
+ <ul>
+ <li>Fix possible lag on monitor/desktop portal capture</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.0.1" date="2024-07-23">
+ <description>
+ <ul>
+ <li>Fix capture broken on amd on wayland</li>
+ <li>Fix hdr capture causing crash when in replay mode</li>
+ </ul>
+ </description>
+ </release>
+ <release version="4.0.0" date="2024-07-22">
+ <description>
+ <ul>
+ <li>Added desktop portal (pipewire) capture option. This fixes issue with glitched capture on certain Intel iGPUS (on Wayland)</li>
+ <li>Added global hotkeys on Wayland. KDE Plasma is the only Wayland environment that supports this properly at the moment</li>
+ <li>Add separate hotkeys for start and stop and option to show notification when starting/stopping recording</li>
+ <li>Fix HDR capture (HDR metadata is now correct). Note that HDR capture is only available on Wayland and when recording a monitor without the desktop portal option</li>
+ <li>Added VP8 and VP9 video codecs if supported by the hardware</li>
+ <li>Added software encoding option</li>
+ </ul>
+ </description>
+ </release>
<release version="3.8.2" date="2024-06-22">
<description>
<ul>
diff --git a/gpu-screen-recorder-gtk.desktop b/com.dec05eba.gpu_screen_recorder.desktop
index 0a83558..0a83558 100644
--- a/gpu-screen-recorder-gtk.desktop
+++ b/com.dec05eba.gpu_screen_recorder.desktop
diff --git a/install.sh b/install.sh
index 78e45b8..0396ea8 100755
--- a/install.sh
+++ b/install.sh
@@ -7,7 +7,8 @@ cd "$script_dir"
echo "Warning: this install.sh script is deprecated. Use meson directly instead if possible"
-test -d build || meson setup build
+rm -rf build
+meson setup build
meson configure --prefix=/usr --buildtype=release -Dstrip=true build
ninja -C build install
diff --git a/meson.build b/meson.build
index cdfc095..0a7bf4b 100644
--- a/meson.build
+++ b/meson.build
@@ -1,4 +1,4 @@
-project('gpu-screen-recorder-gtk', ['c', 'cpp'], version : '3.8.0', default_options : ['warning_level=2'])
+project('gpu-screen-recorder-gtk', ['c', 'cpp'], version : '4.1.11', default_options : ['warning_level=2'])
add_project_arguments('-Wshadow', language : ['c', 'cpp'])
if get_option('buildtype') == 'debug'
@@ -8,28 +8,25 @@ elif get_option('buildtype') == 'release'
endif
src = [
- 'src/egl.c',
- 'src/library_loader.c',
+ 'src/global_shortcuts.c',
'src/main.cpp',
]
+
dep = [
dependency('gtk+-3.0'),
dependency('x11'),
- dependency('xrandr'),
- dependency('libpulse'),
- dependency('libdrm'),
- dependency('wayland-egl'),
- dependency('wayland-client'),
dependency('ayatana-appindicator3-0.1'),
]
+add_project_arguments('-DGSR_VERSION="' + meson.project_version() + '"', language: ['c', 'cpp'])
+
executable('gpu-screen-recorder-gtk', src, dependencies : dep, install : true)
prefix = get_option('prefix')
datadir = get_option('datadir')
-install_data(files('gpu-screen-recorder-gtk.desktop'), install_dir : join_paths(prefix, datadir, 'applications'))
+install_data(files('com.dec05eba.gpu_screen_recorder.desktop'), install_dir : join_paths(prefix, datadir, 'applications'))
install_data(files('com.dec05eba.gpu_screen_recorder.appdata.xml'), install_dir : join_paths(prefix, datadir, 'metainfo'))
install_subdir('icons/hicolor', install_dir : join_paths(prefix, datadir, 'icons'))
gnome = import('gnome')
-gnome.post_install(gtk_update_icon_cache : true, update_desktop_database : true) \ No newline at end of file
+gnome.post_install(gtk_update_icon_cache : true, update_desktop_database : true)
diff --git a/project.conf b/project.conf
index 164cf6b..b2da765 100644
--- a/project.conf
+++ b/project.conf
@@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder-gtk"
type = "executable"
-version = "3.8.0"
+version = "4.1.11"
platforms = ["posix"]
[config]
@@ -11,9 +11,4 @@ error_on_warning = "true"
[dependencies]
gtk+-3.0 = "3"
x11 = "1"
-xrandr = "1"
-libpulse = ">=13"
-libdrm = ">=2"
-wayland-egl = ">=15"
-wayland-client = ">=1"
ayatana-appindicator3-0.1 = ">=0"
diff --git a/src/config.hpp b/src/config.hpp
index be58704..4cd0524 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -33,9 +33,16 @@ struct MainConfig {
std::string framerate_mode;
bool advanced_view = false;
bool overclock = false;
- bool show_notifications = true;
+ bool show_recording_started_notifications = false;
+ bool show_recording_stopped_notifications = false;
+ bool show_recording_saved_notifications = true;
bool record_cursor = true;
bool hide_window_when_recording = false;
+ bool software_encoding_warning_shown = false;
+ bool steam_deck_warning_shown = false;
+ bool hevc_amd_bug_warning_shown = false;
+ bool av1_amd_bug_warning_shown = false;
+ bool restore_portal_session = true;
};
struct YoutubeStreamConfig {
@@ -56,21 +63,21 @@ struct StreamingConfig {
YoutubeStreamConfig youtube;
TwitchStreamConfig twitch;
CustomStreamConfig custom;
- ConfigHotkey start_recording_hotkey;
+ ConfigHotkey start_stop_recording_hotkey;
};
struct RecordConfig {
std::string save_directory;
std::string container;
- ConfigHotkey start_recording_hotkey;
- ConfigHotkey pause_recording_hotkey;
+ ConfigHotkey start_stop_recording_hotkey;
+ ConfigHotkey pause_unpause_recording_hotkey;
};
struct ReplayConfig {
std::string save_directory;
std::string container;
int32_t replay_time = 30;
- ConfigHotkey start_recording_hotkey;
+ ConfigHotkey start_stop_recording_hotkey;
ConfigHotkey save_recording_hotkey;
};
@@ -288,7 +295,7 @@ static bool config_split_key_value(const StringView str, StringView &key, String
value.str = str.str + index + 1;
value.size = str.size - (index + 1);
-
+
return true;
}
@@ -307,26 +314,33 @@ static std::map<std::string, ConfigValue> get_config_options(Config &config) {
{"main.framerate_mode", {CONFIG_TYPE_STRING, &config.main_config.framerate_mode}},
{"main.advanced_view", {CONFIG_TYPE_BOOL, &config.main_config.advanced_view}},
{"main.overclock", {CONFIG_TYPE_BOOL, &config.main_config.overclock}},
- {"main.show_notifications", {CONFIG_TYPE_BOOL, &config.main_config.show_notifications}},
+ {"main.show_recording_started_notifications", {CONFIG_TYPE_BOOL, &config.main_config.show_recording_started_notifications}},
+ {"main.show_recording_stopped_notifications", {CONFIG_TYPE_BOOL, &config.main_config.show_recording_stopped_notifications}},
+ {"main.show_recording_saved_notifications", {CONFIG_TYPE_BOOL, &config.main_config.show_recording_saved_notifications}},
{"main.record_cursor", {CONFIG_TYPE_BOOL, &config.main_config.record_cursor}},
{"main.hide_window_when_recording", {CONFIG_TYPE_BOOL, &config.main_config.hide_window_when_recording}},
+ {"main.software_encoding_warning_shown", {CONFIG_TYPE_BOOL, &config.main_config.software_encoding_warning_shown}},
+ {"main.steam_deck_warning_shown", {CONFIG_TYPE_BOOL, &config.main_config.steam_deck_warning_shown}},
+ {"main.hevc_amd_bug_warning_shown", {CONFIG_TYPE_BOOL, &config.main_config.hevc_amd_bug_warning_shown}},
+ {"main.av1_amd_bug_warning_shown", {CONFIG_TYPE_BOOL, &config.main_config.av1_amd_bug_warning_shown}},
+ {"main.restore_portal_session", {CONFIG_TYPE_BOOL, &config.main_config.restore_portal_session}},
{"streaming.service", {CONFIG_TYPE_STRING, &config.streaming_config.streaming_service}},
{"streaming.youtube.key", {CONFIG_TYPE_STRING, &config.streaming_config.youtube.stream_key}},
{"streaming.twitch.key", {CONFIG_TYPE_STRING, &config.streaming_config.twitch.stream_key}},
{"streaming.custom.url", {CONFIG_TYPE_STRING, &config.streaming_config.custom.url}},
{"streaming.custom.container", {CONFIG_TYPE_STRING, &config.streaming_config.custom.container}},
- {"streaming.start_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.streaming_config.start_recording_hotkey}},
+ {"streaming.start_stop_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.streaming_config.start_stop_recording_hotkey}},
{"record.save_directory", {CONFIG_TYPE_STRING, &config.record_config.save_directory}},
{"record.container", {CONFIG_TYPE_STRING, &config.record_config.container}},
- {"record.start_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.record_config.start_recording_hotkey}},
- {"record.pause_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.record_config.pause_recording_hotkey}},
+ {"record.start_stop_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.record_config.start_stop_recording_hotkey}},
+ {"record.pause_unpause_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.record_config.pause_unpause_recording_hotkey}},
{"replay.save_directory", {CONFIG_TYPE_STRING, &config.replay_config.save_directory}},
{"replay.container", {CONFIG_TYPE_STRING, &config.replay_config.container}},
{"replay.time", {CONFIG_TYPE_I32, &config.replay_config.replay_time}},
- {"replay.start_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.replay_config.start_recording_hotkey}},
+ {"replay.start_stop_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.replay_config.start_stop_recording_hotkey}},
{"replay.save_recording_hotkey", {CONFIG_TYPE_HOTKEY, &config.replay_config.save_recording_hotkey}}
};
}
@@ -349,13 +363,13 @@ static Config read_config(bool &config_empty) {
auto config_options = get_config_options(config);
string_split_char(file_content, '\n', [&](StringView line) {
- StringView key, value;
- if(!config_split_key_value(line, key, value)) {
+ StringView key, sv_val;
+ if(!config_split_key_value(line, key, sv_val)) {
fprintf(stderr, "Warning: Invalid config option format: %.*s\n", (int)line.size, line.str);
return true;
}
- if(key.size == 0 || value.size == 0)
+ if(key.size == 0 || sv_val.size == 0)
return true;
auto it = config_options.find(std::string(key.str, key.size));
@@ -364,15 +378,15 @@ static Config read_config(bool &config_empty) {
switch(it->second.type) {
case CONFIG_TYPE_BOOL: {
- *(bool*)it->second.data = value == "true";
+ *(bool*)it->second.data = sv_val == "true";
break;
}
case CONFIG_TYPE_STRING: {
- ((std::string*)it->second.data)->assign(value.str, value.size);
+ ((std::string*)it->second.data)->assign(sv_val.str, sv_val.size);
break;
}
case CONFIG_TYPE_I32: {
- std::string value_str(value.str, value.size);
+ std::string value_str(sv_val.str, sv_val.size);
int32_t *value = (int32_t*)it->second.data;
if(sscanf(value_str.c_str(), FORMAT_I32, value) != 1) {
fprintf(stderr, "Warning: Invalid config option value for %.*s\n", (int)key.size, key.str);
@@ -381,7 +395,7 @@ static Config read_config(bool &config_empty) {
break;
}
case CONFIG_TYPE_HOTKEY: {
- std::string value_str(value.str, value.size);
+ std::string value_str(sv_val.str, sv_val.size);
ConfigHotkey *config_hotkey = (ConfigHotkey*)it->second.data;
if(sscanf(value_str.c_str(), FORMAT_I64 " " FORMAT_U32, &config_hotkey->keysym, &config_hotkey->modifiers) != 2) {
fprintf(stderr, "Warning: Invalid config option value for %.*s\n", (int)key.size, key.str);
@@ -391,7 +405,7 @@ static Config read_config(bool &config_empty) {
break;
}
case CONFIG_TYPE_STRING_ARRAY: {
- std::string array_value(value.str, value.size);
+ std::string array_value(sv_val.str, sv_val.size);
((std::vector<std::string>*)it->second.data)->push_back(std::move(array_value));
break;
}
diff --git a/src/egl.c b/src/egl.c
deleted file mode 100644
index 79aab1f..0000000
--- a/src/egl.c
+++ /dev/null
@@ -1,380 +0,0 @@
-#include "egl.h"
-#include "library_loader.h"
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <dlfcn.h>
-#include <assert.h>
-
-#include <wayland-client.h>
-#include <wayland-egl.h>
-#include <unistd.h>
-
-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;
- (void)transform;
- gsr_wayland_output *gsr_output = data;
- gsr_output->pos.x = x;
- gsr_output->pos.y = y;
-}
-
-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;
- gsr_wayland_output *gsr_output = data;
- gsr_output->size.x = width;
- gsr_output->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;
- gsr_wayland_output *gsr_output = data;
- if(gsr_output->name) {
- free(gsr_output->name);
- gsr_output->name = NULL;
- }
- gsr_output->name = strdup(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 = {
- .geometry = output_handle_geometry,
- .mode = output_handle_mode,
- .done = output_handle_done,
- .scale = output_handle_scale,
- .name = output_handle_name,
- .description = 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;
- gsr_egl *egl = data;
- if (strcmp(interface, "wl_compositor") == 0) {
- if(egl->wayland.compositor) {
- wl_compositor_destroy(egl->wayland.compositor);
- egl->wayland.compositor = NULL;
- }
- egl->wayland.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1);
- } else if(strcmp(interface, wl_output_interface.name) == 0) {
- if(version < 4) {
- fprintf(stderr, "gsr warning: wl output interface version is < 4, expected >= 4 to capture a monitor. Using KMS capture instead\n");
- return;
- }
-
- if(egl->wayland.num_outputs == GSR_MAX_OUTPUTS) {
- fprintf(stderr, "gsr warning: reached maximum outputs (32), ignoring output %u\n", name);
- return;
- }
-
- gsr_wayland_output *gsr_output = &egl->wayland.outputs[egl->wayland.num_outputs];
- egl->wayland.num_outputs++;
- *gsr_output = (gsr_wayland_output) {
- .wl_name = name,
- .output = wl_registry_bind(registry, name, &wl_output_interface, 4),
- .pos = { .x = 0, .y = 0 },
- .size = { .x = 0, .y = 0 },
- .name = NULL,
- };
- wl_output_add_listener(gsr_output->output, &output_listener, gsr_output);
- }
-}
-
-static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) {
- (void)data;
- (void)registry;
- (void)name;
-}
-
-static struct wl_registry_listener registry_listener = {
- .global = registry_add_object,
- .global_remove = registry_remove_object,
-};
-
-// TODO: Create egl context without surface (in other words, x11/wayland agnostic, doesn't require x11/wayland dependency)
-static bool gsr_egl_create_window(gsr_egl *self, bool wayland) {
- EGLConfig ecfg;
- int32_t num_config = 0;
-
- const int32_t attr[] = {
- EGL_BUFFER_SIZE, 24,
- EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT,
- EGL_NONE
- };
-
- const int32_t ctxattr[] = {
- EGL_CONTEXT_CLIENT_VERSION, 2,
- EGL_NONE
- };
-
- if(wayland) {
- self->wayland.dpy = wl_display_connect(NULL);
- if(!self->wayland.dpy) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: wl_display_connect failed\n");
- goto fail;
- }
-
- self->wayland.registry = wl_display_get_registry(self->wayland.dpy); // TODO: Error checking
- wl_registry_add_listener(self->wayland.registry, &registry_listener, self); // TODO: Error checking
-
- // Fetch globals
- wl_display_roundtrip(self->wayland.dpy);
-
- // fetch wl_output
- wl_display_roundtrip(self->wayland.dpy);
-
- if(!self->wayland.compositor) {
- fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to find compositor\n");
- goto fail;
- }
- } else {
- self->x11.window = XCreateWindow(self->x11.dpy, DefaultRootWindow(self->x11.dpy), 0, 0, 16, 16, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
-
- if(!self->x11.window) {
- fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n");
- goto fail;
- }
- }
-
- self->eglBindAPI(EGL_OPENGL_API);
-
- self->egl_display = self->eglGetDisplay(self->wayland.dpy ? (EGLNativeDisplayType)self->wayland.dpy : (EGLNativeDisplayType)self->x11.dpy);
- if(!self->egl_display) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n");
- goto fail;
- }
-
- if(!self->eglInitialize(self->egl_display, NULL, NULL)) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglInitialize failed\n");
- goto fail;
- }
-
- if(!self->eglChooseConfig(self->egl_display, attr, &ecfg, 1, &num_config) || num_config != 1) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to find a matching config\n");
- goto fail;
- }
-
- self->egl_context = self->eglCreateContext(self->egl_display, ecfg, NULL, ctxattr);
- if(!self->egl_context) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n");
- goto fail;
- }
-
- if(wayland) {
- self->wayland.surface = wl_compositor_create_surface(self->wayland.compositor);
- self->wayland.window = wl_egl_window_create(self->wayland.surface, 16, 16);
- self->egl_surface = self->eglCreateWindowSurface(self->egl_display, ecfg, (EGLNativeWindowType)self->wayland.window, NULL);
- } else {
- self->egl_surface = self->eglCreateWindowSurface(self->egl_display, ecfg, (EGLNativeWindowType)self->x11.window, NULL);
- }
-
- if(!self->egl_surface) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n");
- goto fail;
- }
-
- if(!self->eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface, self->egl_context)) {
- fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to make context current\n");
- goto fail;
- }
-
- return true;
-
- fail:
- gsr_egl_unload(self);
- return false;
-}
-
-static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
- const dlsym_assign required_dlsym[] = {
- { (void**)&self->eglGetDisplay, "eglGetDisplay" },
- { (void**)&self->eglInitialize, "eglInitialize" },
- { (void**)&self->eglTerminate, "eglTerminate" },
- { (void**)&self->eglChooseConfig, "eglChooseConfig" },
- { (void**)&self->eglCreateWindowSurface, "eglCreateWindowSurface" },
- { (void**)&self->eglCreateContext, "eglCreateContext" },
- { (void**)&self->eglMakeCurrent, "eglMakeCurrent" },
- { (void**)&self->eglDestroyContext, "eglDestroyContext" },
- { (void**)&self->eglDestroySurface, "eglDestroySurface" },
- { (void**)&self->eglBindAPI, "eglBindAPI" },
- { (void**)&self->eglGetProcAddress, "eglGetProcAddress" },
-
- { NULL, NULL }
- };
-
- if(!dlsym_load_list(library, required_dlsym)) {
- fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libEGL.so.1\n");
- return false;
- }
-
- return true;
-}
-
-static bool gsr_egl_load_gl(gsr_egl *self, void *library) {
- const dlsym_assign required_dlsym[] = {
- { (void**)&self->glGetString, "glGetString" },
-
- { NULL, NULL }
- };
-
- if(!dlsym_load_list(library, required_dlsym)) {
- fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libGL.so.1\n");
- return false;
- }
-
- return true;
-}
-
-static bool gsr_egl_proc_load_egl(gsr_egl *self) {
- self->eglQueryDisplayAttribEXT = (FUNC_eglQueryDisplayAttribEXT)self->eglGetProcAddress("eglQueryDisplayAttribEXT");
- self->eglQueryDeviceStringEXT = (FUNC_eglQueryDeviceStringEXT)self->eglGetProcAddress("eglQueryDeviceStringEXT");
-
- return true;
-}
-
-bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland) {
- memset(self, 0, sizeof(gsr_egl));
- self->x11.dpy = dpy;
-
- void *egl_lib = NULL;
- void *gl_lib = NULL;
-
- dlerror(); /* clear */
- egl_lib = dlopen("libEGL.so.1", RTLD_LAZY);
- if(!egl_lib) {
- fprintf(stderr, "gsr error: gsr_egl_load: failed to load libEGL.so.1, error: %s\n", dlerror());
- goto fail;
- }
-
- gl_lib = dlopen("libGL.so.1", RTLD_LAZY);
- if(!egl_lib) {
- fprintf(stderr, "gsr error: gsr_egl_load: failed to load libGL.so.1, error: %s\n", dlerror());
- goto fail;
- }
-
- if(!gsr_egl_load_egl(self, egl_lib))
- goto fail;
-
- if(!gsr_egl_load_gl(self, gl_lib))
- goto fail;
-
- if(!gsr_egl_proc_load_egl(self))
- goto fail;
-
- if(!gsr_egl_create_window(self, wayland))
- goto fail;
-
- if(self->eglQueryDisplayAttribEXT && self->eglQueryDeviceStringEXT) {
- intptr_t device = 0;
- if(self->eglQueryDisplayAttribEXT(self->egl_display, EGL_DEVICE_EXT, &device) && device)
- self->dri_card_path = self->eglQueryDeviceStringEXT((void*)device, EGL_DRM_DEVICE_FILE_EXT);
- }
-
- self->egl_library = egl_lib;
- self->gl_library = gl_lib;
- return true;
-
- fail:
- if(egl_lib)
- dlclose(egl_lib);
- if(gl_lib)
- dlclose(gl_lib);
- memset(self, 0, sizeof(gsr_egl));
- return false;
-}
-
-void gsr_egl_unload(gsr_egl *self) {
- if(self->egl_context) {
- self->eglDestroyContext(self->egl_display, self->egl_context);
- self->egl_context = NULL;
- }
-
- if(self->egl_surface) {
- self->eglDestroySurface(self->egl_display, self->egl_surface);
- self->egl_surface = NULL;
- }
-
- if(self->egl_display) {
- self->eglTerminate(self->egl_display);
- self->egl_display = NULL;
- }
-
- if(self->x11.window) {
- XDestroyWindow(self->x11.dpy, self->x11.window);
- self->x11.window = None;
- }
-
- if(self->wayland.window) {
- wl_egl_window_destroy(self->wayland.window);
- self->wayland.window = NULL;
- }
-
- if(self->wayland.surface) {
- wl_surface_destroy(self->wayland.surface);
- self->wayland.surface = NULL;
- }
-
- for(int i = 0; i < self->wayland.num_outputs; ++i) {
- if(self->wayland.outputs[i].output) {
- wl_output_destroy(self->wayland.outputs[i].output);
- self->wayland.outputs[i].output = NULL;
- }
-
- if(self->wayland.outputs[i].name) {
- free(self->wayland.outputs[i].name);
- self->wayland.outputs[i].name = NULL;
- }
- }
- self->wayland.num_outputs = 0;
-
- if(self->wayland.compositor) {
- wl_compositor_destroy(self->wayland.compositor);
- self->wayland.compositor = NULL;
- }
-
- if(self->wayland.registry) {
- wl_registry_destroy(self->wayland.registry);
- self->wayland.registry = NULL;
- }
-
- if(self->wayland.dpy) {
- wl_display_disconnect(self->wayland.dpy);
- self->wayland.dpy = NULL;
- }
-
- if(self->egl_library) {
- dlclose(self->egl_library);
- self->egl_library = NULL;
- }
-
- if(self->gl_library) {
- dlclose(self->gl_library);
- self->gl_library = NULL;
- }
-
- memset(self, 0, sizeof(gsr_egl));
-}
diff --git a/src/egl.h b/src/egl.h
deleted file mode 100644
index e46a6ab..0000000
--- a/src/egl.h
+++ /dev/null
@@ -1,114 +0,0 @@
-#ifndef GSR_EGL_H
-#define GSR_EGL_H
-
-/* OpenGL EGL library with a hidden window context (to allow using the opengl functions) */
-
-#include <X11/X.h>
-#include <X11/Xutil.h>
-#include <stdbool.h>
-#include <stdint.h>
-
-typedef struct {
- int x, y;
-} vec2i;
-
-#ifdef _WIN64
-typedef signed long long int khronos_intptr_t;
-typedef unsigned long long int khronos_uintptr_t;
-typedef signed long long int khronos_ssize_t;
-typedef unsigned long long int khronos_usize_t;
-#else
-typedef signed long int khronos_intptr_t;
-typedef unsigned long int khronos_uintptr_t;
-typedef signed long int khronos_ssize_t;
-typedef unsigned long int khronos_usize_t;
-#endif
-
-typedef void* EGLDisplay;
-typedef void* EGLNativeDisplayType;
-typedef uintptr_t EGLNativeWindowType;
-typedef uintptr_t EGLNativePixmapType;
-typedef void* EGLConfig;
-typedef void* EGLSurface;
-typedef void* EGLContext;
-typedef void* EGLClientBuffer;
-typedef void* EGLImage;
-typedef void* EGLImageKHR;
-typedef void *GLeglImageOES;
-typedef void (*__eglMustCastToProperFunctionPointerType)(void);
-
-typedef int (*FUNC_eglQueryDisplayAttribEXT)(EGLDisplay dpy, int32_t attribute, intptr_t *value);
-typedef const char* (*FUNC_eglQueryDeviceStringEXT)(void *device, int32_t name);
-
-#define EGL_BUFFER_SIZE 0x3020
-#define EGL_RENDERABLE_TYPE 0x3040
-#define EGL_OPENGL_BIT 0x0008
-#define EGL_OPENGL_API 0x30A2
-#define EGL_NONE 0x3038
-#define EGL_CONTEXT_CLIENT_VERSION 0x3098
-#define EGL_DEVICE_EXT 0x322C
-#define EGL_DRM_DEVICE_FILE_EXT 0x3233
-
-#define GL_VENDOR 0x1F00
-#define GL_RENDERER 0x1F01
-
-#define GSR_MAX_OUTPUTS 32
-
-typedef struct {
- Display *dpy;
- Window window;
-} gsr_x11;
-
-typedef struct {
- uint32_t wl_name;
- void *output;
- vec2i pos;
- vec2i size;
- char *name;
-} gsr_wayland_output;
-
-typedef struct {
- void *dpy;
- void *window;
- void *registry;
- void *surface;
- void *compositor;
- gsr_wayland_output outputs[GSR_MAX_OUTPUTS];
- int num_outputs;
-} gsr_wayland;
-
-typedef struct {
- void *egl_library;
- void *gl_library;
-
- EGLDisplay egl_display;
- EGLSurface egl_surface;
- EGLContext egl_context;
- const char *dri_card_path;
-
- gsr_x11 x11;
- gsr_wayland wayland;
- char card_path[128];
-
- EGLDisplay (*eglGetDisplay)(EGLNativeDisplayType display_id);
- unsigned int (*eglInitialize)(EGLDisplay dpy, int32_t *major, int32_t *minor);
- unsigned int (*eglTerminate)(EGLDisplay dpy);
- unsigned int (*eglChooseConfig)(EGLDisplay dpy, const int32_t *attrib_list, EGLConfig *configs, int32_t config_size, int32_t *num_config);
- EGLSurface (*eglCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const int32_t *attrib_list);
- EGLContext (*eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const int32_t *attrib_list);
- unsigned int (*eglMakeCurrent)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx);
- unsigned int (*eglDestroyContext)(EGLDisplay dpy, EGLContext ctx);
- unsigned int (*eglDestroySurface)(EGLDisplay dpy, EGLSurface surface);
- unsigned int (*eglBindAPI)(unsigned int api);
- __eglMustCastToProperFunctionPointerType (*eglGetProcAddress)(const char *procname);
-
- FUNC_eglQueryDisplayAttribEXT eglQueryDisplayAttribEXT;
- FUNC_eglQueryDeviceStringEXT eglQueryDeviceStringEXT;
-
- const unsigned char* (*glGetString)(unsigned int name);
-} gsr_egl;
-
-bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland);
-void gsr_egl_unload(gsr_egl *self);
-
-#endif /* GSR_EGL_H */
diff --git a/src/global_shortcuts.c b/src/global_shortcuts.c
new file mode 100644
index 0000000..9933891
--- /dev/null
+++ b/src/global_shortcuts.c
@@ -0,0 +1,336 @@
+#include "global_shortcuts.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/random.h>
+#include <gio/gio.h>
+
+/* TODO: Remove G_DBUS_CALL_FLAGS_NO_AUTO_START and G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START? also in gpu screen recorder equivalent */
+/* TODO: More error handling and clean up resources after done */
+/* TODO: Use GArray instead of GVariant where possible */
+
+static bool generate_random_characters(char *buffer, int buffer_size, const char *alphabet, size_t alphabet_size) {
+ /* TODO: Use other functions on other platforms than linux */
+ if(getrandom(buffer, buffer_size, 0) < buffer_size) {
+ fprintf(stderr, "gsr error: generate_random_characters: failed to get random bytes, error: %s\n", strerror(errno));
+ return false;
+ }
+
+ for(int i = 0; i < buffer_size; ++i) {
+ unsigned char c = *(unsigned char*)&buffer[i];
+ buffer[i] = alphabet[c % alphabet_size];
+ }
+
+ return true;
+}
+
+static void gsr_dbus_portal_get_unique_handle_token(gsr_global_shortcuts *self, char *buffer, int size) {
+ snprintf(buffer, size, "gpu_screen_recorder_gtk_handle_%s_%u", self->random_str, self->handle_counter++);
+}
+
+/* Assumes shortcuts is an array */
+static void handle_shortcuts_data(GVariant *shortcuts, gsr_shortcut_callback callback, void *userdata) {
+ for(guint i = 0; i < g_variant_n_children(shortcuts); ++i) {
+ gchar *shortcut_id = NULL;
+ GVariant *shortcut_values = NULL;
+ g_variant_get_child(shortcuts, i, "(s@a{sv})", &shortcut_id, &shortcut_values);
+
+ if(!shortcut_id || !shortcut_values)
+ continue;
+
+ // gchar *description = NULL;
+ // g_variant_lookup(shortcut_values, "description", "s", &description);
+
+ gchar *trigger_description = NULL;
+ g_variant_lookup(shortcut_values, "trigger_description", "s", &trigger_description);
+
+ gsr_shortcut shortcut;
+ shortcut.id = shortcut_id;
+ shortcut.trigger_description = trigger_description ? trigger_description : "";
+ callback(shortcut, userdata);
+ }
+}
+
+typedef struct {
+ gsr_global_shortcuts *self;
+ gsr_shortcut_callback callback;
+ void *userdata;
+} signal_list_bind_userdata;
+
+static void dbus_signal_list_bind(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, signal_list_bind_userdata *userdata) {
+ (void)proxy;
+ (void)sender_name;
+ if(g_strcmp0(signal_name, "Response") != 0)
+ goto done;
+
+ guint32 response = 0;
+ GVariant *results = NULL;
+ g_variant_get(parameters, "(u@a{sv})", &response, &results);
+
+ if(response != 0 || !results)
+ goto done;
+
+ GVariant *shortcuts = g_variant_lookup_value(results, "shortcuts", G_VARIANT_TYPE("a(sa{sv})"));
+ if(!shortcuts)
+ goto done;
+
+ handle_shortcuts_data(shortcuts, userdata->callback, userdata->userdata);
+
+ done:
+ free(userdata);
+}
+
+typedef struct {
+ gsr_global_shortcuts *self;
+ gsr_deactivated_callback deactivated_callback;
+ gsr_shortcut_callback shortcut_changed_callback;
+ void *userdata;
+} signal_userdata;
+
+static void signal_callback(GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer userdata)
+{
+ (void)connection;
+ (void)sender_name;
+ (void)object_path;
+ (void)interface_name;
+ (void)signal_name;
+ (void)parameters;
+ signal_userdata *cu = userdata;
+
+ /* Button released */
+ if(strcmp(signal_name, "Deactivated") == 0) {
+ gchar *session_handle = NULL;
+ gchar *shortcut_id = NULL;
+ gchar *timestamp = NULL;
+ GVariant *options = NULL;
+ g_variant_get(parameters, "(ost@a{sv})", &session_handle, &shortcut_id, &timestamp, &options);
+
+ if(session_handle && shortcut_id && strcmp(session_handle, cu->self->session_handle) == 0)
+ cu->deactivated_callback(shortcut_id, cu->userdata);
+ } else if(strcmp(signal_name, "ShortcutsChanged") == 0) {
+ gchar *session_handle = NULL;
+ GVariant *shortcuts = NULL;
+ g_variant_get(parameters, "(o@a(sa{sv}))", &session_handle, &shortcuts);
+
+ if(session_handle && shortcuts && strcmp(session_handle, cu->self->session_handle) == 0)
+ handle_shortcuts_data(shortcuts, cu->shortcut_changed_callback, cu->userdata);
+ }
+}
+
+typedef struct {
+ gsr_global_shortcuts *self;
+ gsr_init_callback callback;
+ void *userdata;
+} signal_create_session_userdata;
+
+static void dbus_signal_create_session(GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, signal_create_session_userdata *cu) {
+ (void)proxy;
+ (void)sender_name;
+ if(g_strcmp0(signal_name, "Response") != 0)
+ goto done;
+
+ guint32 response = 0;
+ GVariant *results = NULL;
+ g_variant_get(parameters, "(u@a{sv})", &response, &results);
+
+ if(response != 0 || !results) {
+ cu->callback(false, cu->userdata);
+ goto done;
+ }
+
+ gchar *session_handle = NULL;
+ if(g_variant_lookup(results, "session_handle", "s", &session_handle) && session_handle) {
+ cu->self->session_handle = strdup(session_handle);
+ cu->self->session_created = true;
+ cu->callback(true, cu->userdata);
+ }
+
+ done:
+ free(cu);
+}
+
+static bool gsr_global_shortcuts_create_session(gsr_global_shortcuts *self, gsr_init_callback callback, void *userdata) {
+ char handle_token[64];
+ gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
+
+ char session_handle_token[64];
+ snprintf(session_handle_token, sizeof(session_handle_token), "gpu_screen_recorder_gtk");
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(handle_token));
+ g_variant_builder_add(&builder, "{sv}", "session_handle_token", g_variant_new_string(session_handle_token));
+ GVariant *aa = g_variant_builder_end(&builder);
+
+ GVariant *ret = g_dbus_connection_call_sync(self->gdbus_con, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.GlobalShortcuts", "CreateSession", g_variant_new_tuple(&aa, 1), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, NULL, NULL);
+
+ if(ret) {
+ const gchar *val = NULL;
+ g_variant_get(ret, "(&o)", &val);
+ if(!val)
+ return false;
+ //g_variant_unref(ret);
+
+ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, "org.freedesktop.portal.Desktop", val, "org.freedesktop.portal.Request", NULL, NULL);
+ if(!proxy)
+ return false;
+ //g_object_unref(proxy);
+
+ signal_create_session_userdata *cu = malloc(sizeof(signal_create_session_userdata));
+ cu->self = self;
+ cu->callback = callback;
+ cu->userdata = userdata;
+ g_signal_connect(proxy, "g-signal", G_CALLBACK(dbus_signal_create_session), cu);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool gsr_global_shortcuts_init(gsr_global_shortcuts *self, gsr_init_callback callback, void *userdata) {
+ memset(self, 0, sizeof(*self));
+
+ self->random_str[DBUS_RANDOM_STR_SIZE] = '\0';
+ if(!generate_random_characters(self->random_str, DBUS_RANDOM_STR_SIZE, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 62)) {
+ fprintf(stderr, "gsr error: gsr_global_shortcuts_init: failed to generate random string\n");
+ return false;
+ }
+
+ self->gdbus_con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
+ if(!self->gdbus_con) {
+ fprintf(stderr, "gsr error: gsr_global_shortcuts_init: g_bus_get_sync failed\n");
+ return false;
+ }
+
+ if(!gsr_global_shortcuts_create_session(self, callback, userdata)) {
+ gsr_global_shortcuts_deinit(self);
+ return false;
+ }
+
+ return true;
+}
+
+void gsr_global_shortcuts_deinit(gsr_global_shortcuts *self) {
+ if(self->gdbus_con) {
+ /* TODO: Re-add this. Right now it causes errors as the connection is already closed, but checking if it's already closed here has no effect */
+ //g_dbus_connection_close(self->gdbus_con, NULL, NULL, NULL);
+ self->gdbus_con = NULL;
+ }
+
+ if(self->session_handle) {
+ free(self->session_handle);
+ self->session_handle = NULL;
+ }
+}
+
+bool gsr_global_shortcuts_list_shortcuts(gsr_global_shortcuts *self, gsr_shortcut_callback callback, void *userdata) {
+ if(!self->session_created)
+ return false;
+
+ char handle_token[64];
+ gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
+
+ GVariant *session_handle_obj = g_variant_new_object_path(self->session_handle);
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(handle_token));
+ GVariant *aa = g_variant_builder_end(&builder);
+
+ GVariant *args[2] = { session_handle_obj, aa };
+
+ GVariant *ret = g_dbus_connection_call_sync(self->gdbus_con, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.GlobalShortcuts", "ListShortcuts", g_variant_new_tuple(args, 2), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, 1000, NULL, NULL);
+ if(ret) {
+ const gchar *val = NULL;
+ g_variant_get(ret, "(&o)", &val);
+ if(!val)
+ return false;
+
+ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, "org.freedesktop.portal.Desktop", val, "org.freedesktop.portal.Request", NULL, NULL);
+ if(!proxy)
+ return false;
+ //g_object_unref(proxy);
+
+ signal_list_bind_userdata *cu = malloc(sizeof(signal_list_bind_userdata));
+ cu->self = self;
+ cu->callback = callback;
+ cu->userdata = userdata;
+ g_signal_connect(proxy, "g-signal", G_CALLBACK(dbus_signal_list_bind), cu);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool gsr_global_shortcuts_bind_shortcuts(gsr_global_shortcuts *self, const gsr_bind_shortcut *shortcuts, int num_shortcuts, gsr_shortcut_callback callback, void *userdata) {
+ if(!self->session_created)
+ return false;
+
+ char handle_token[64];
+ gsr_dbus_portal_get_unique_handle_token(self, handle_token, sizeof(handle_token));
+
+ GVariant *session_handle_obj = g_variant_new_object_path(self->session_handle);
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a(sa{sv})"));
+
+ for(int i = 0; i < num_shortcuts; ++i) {
+ GVariantBuilder shortcuts_builder;
+ g_variant_builder_init(&shortcuts_builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&shortcuts_builder, "{sv}", "description", g_variant_new_string(shortcuts[i].description));
+ g_variant_builder_add(&shortcuts_builder, "{sv}", "preferred_trigger", g_variant_new_string(shortcuts[i].shortcut.trigger_description));
+ GVariant *shortcuts_data = g_variant_builder_end(&shortcuts_builder);
+ GVariant *ss_l[2] = { g_variant_new_string(shortcuts[i].shortcut.id), shortcuts_data };
+ g_variant_builder_add_value(&builder, g_variant_new_tuple(ss_l, 2));
+ }
+ GVariant *aa = g_variant_builder_end(&builder);
+
+ GVariantBuilder builder_zzz;
+ g_variant_builder_init(&builder_zzz, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder_zzz, "{sv}", "handle_token", g_variant_new_string(handle_token));
+ GVariant *bb = g_variant_builder_end(&builder_zzz);
+
+ GVariant *parent_window = g_variant_new_string("");
+ GVariant *args[4] = { session_handle_obj, aa, parent_window, bb };
+
+ GVariant *ret = g_dbus_connection_call_sync(self->gdbus_con, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.GlobalShortcuts", "BindShortcuts", g_variant_new_tuple(args, 4), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, NULL);
+ if(ret) {
+ const gchar *val = NULL;
+ g_variant_get(ret, "(&o)", &val);
+ if(!val)
+ return false;
+
+ GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, "org.freedesktop.portal.Desktop", val, "org.freedesktop.portal.Request", NULL, NULL);
+ if(!proxy)
+ return false;
+ //g_object_unref(proxy);
+
+ signal_list_bind_userdata *cu = malloc(sizeof(signal_list_bind_userdata));
+ cu->self = self;
+ cu->callback = callback;
+ cu->userdata = userdata;
+ g_signal_connect(proxy, "g-signal", G_CALLBACK(dbus_signal_list_bind), cu);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool gsr_global_shortcuts_subscribe_activated_signal(gsr_global_shortcuts *self, gsr_deactivated_callback deactivated_callback, gsr_shortcut_callback shortcut_changed_callback, void *userdata) {
+ if(!self->session_created)
+ return false;
+
+ signal_userdata *cu = malloc(sizeof(signal_userdata));
+ cu->self = self;
+ cu->deactivated_callback = deactivated_callback;
+ cu->shortcut_changed_callback = shortcut_changed_callback;
+ cu->userdata = userdata;
+ g_dbus_connection_signal_subscribe(self->gdbus_con, "org.freedesktop.portal.Desktop", "org.freedesktop.portal.GlobalShortcuts", NULL, "/org/freedesktop/portal/desktop", NULL, G_DBUS_SIGNAL_FLAGS_NONE, signal_callback, cu, free);
+ return true;
+}
diff --git a/src/global_shortcuts.h b/src/global_shortcuts.h
new file mode 100644
index 0000000..087c177
--- /dev/null
+++ b/src/global_shortcuts.h
@@ -0,0 +1,41 @@
+#ifndef GLOBAL_SHORTCUTS_H
+#define GLOBAL_SHORTCUTS_H
+
+/* Global shortcuts via desktop portal */
+
+#include <stdbool.h>
+#include <gio/gio.h>
+
+#define DBUS_RANDOM_STR_SIZE 16
+
+typedef struct {
+ const char *id;
+ const char *trigger_description;
+} gsr_shortcut;
+
+typedef struct {
+ const char *description;
+ gsr_shortcut shortcut;
+} gsr_bind_shortcut;
+
+typedef void (*gsr_init_callback)(bool success, void *userdata);
+typedef void (*gsr_shortcut_callback)(gsr_shortcut shortcut, void *userdata);
+typedef void (*gsr_deactivated_callback)(const char *id, void *userdata);
+
+typedef struct {
+ GDBusConnection *gdbus_con;
+ char *session_handle;
+ bool session_created;
+ char random_str[DBUS_RANDOM_STR_SIZE + 1];
+ unsigned int handle_counter;
+} gsr_global_shortcuts;
+
+bool gsr_global_shortcuts_init(gsr_global_shortcuts *self, gsr_init_callback callback, void *userdata);
+void gsr_global_shortcuts_deinit(gsr_global_shortcuts *self);
+
+bool gsr_global_shortcuts_list_shortcuts(gsr_global_shortcuts *self, gsr_shortcut_callback callback, void *userdata);
+bool gsr_global_shortcuts_bind_shortcuts(gsr_global_shortcuts *self, const gsr_bind_shortcut *shortcuts, int num_shortcuts, gsr_shortcut_callback callback, void *userdata);
+
+bool gsr_global_shortcuts_subscribe_activated_signal(gsr_global_shortcuts *self, gsr_deactivated_callback deactivated_callback, gsr_shortcut_callback shortcut_changed_callback, void *userdata);
+
+#endif /* GLOBAL_SHORTCUTS_H */
diff --git a/src/library_loader.c b/src/library_loader.c
deleted file mode 100644
index fed1fe5..0000000
--- a/src/library_loader.c
+++ /dev/null
@@ -1,34 +0,0 @@
-#include "library_loader.h"
-
-#include <dlfcn.h>
-#include <stdbool.h>
-#include <stdio.h>
-
-void* dlsym_print_fail(void *handle, const char *name, bool required) {
- dlerror();
- void *sym = dlsym(handle, name);
- char *err_str = dlerror();
-
- if(!sym)
- fprintf(stderr, "%s: dlsym(handle, \"%s\") failed, error: %s\n", required ? "error" : "warning", name, err_str ? err_str : "(null)");
-
- return sym;
-}
-
-/* |dlsyms| should be null terminated */
-bool dlsym_load_list(void *handle, const dlsym_assign *dlsyms) {
- bool success = true;
- for(int i = 0; dlsyms[i].func; ++i) {
- *dlsyms[i].func = dlsym_print_fail(handle, dlsyms[i].name, true);
- if(!*dlsyms[i].func)
- success = false;
- }
- return success;
-}
-
-/* |dlsyms| should be null terminated */
-void dlsym_load_list_optional(void *handle, const dlsym_assign *dlsyms) {
- for(int i = 0; dlsyms[i].func; ++i) {
- *dlsyms[i].func = dlsym_print_fail(handle, dlsyms[i].name, false);
- }
-}
diff --git a/src/library_loader.h b/src/library_loader.h
deleted file mode 100644
index 47bc9f0..0000000
--- a/src/library_loader.h
+++ /dev/null
@@ -1,17 +0,0 @@
-#ifndef GSR_LIBRARY_LOADER_H
-#define GSR_LIBRARY_LOADER_H
-
-#include <stdbool.h>
-
-typedef struct {
- void **func;
- const char *name;
-} dlsym_assign;
-
-void* dlsym_print_fail(void *handle, const char *name, bool required);
-/* |dlsyms| should be null terminated */
-bool dlsym_load_list(void *handle, const dlsym_assign *dlsyms);
-/* |dlsyms| should be null terminated */
-void dlsym_load_list_optional(void *handle, const dlsym_assign *dlsyms);
-
-#endif /* GSR_LIBRARY_LOADER_H */
diff --git a/src/main.cpp b/src/main.cpp
index f5fbdbd..e40f5ae 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,13 +1,15 @@
#include "config.hpp"
+extern "C" {
+#include "global_shortcuts.h"
+}
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
+#include <gdk/gdkwayland.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/cursorfont.h>
-#include <X11/extensions/Xrandr.h>
#include <assert.h>
#include <string>
-#include <pulse/pulseaudio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
@@ -16,13 +18,17 @@
#include <dlfcn.h>
#include <functional>
#include <vector>
-extern "C" {
-#include "egl.h"
-}
-#include <xf86drmMode.h>
-#include <xf86drm.h>
#include <libayatana-appindicator/app-indicator.h>
+#ifndef GSR_VERSION
+#define GSR_VERSION "unknown"
+#endif
+
+// Start/stop recording also means start/stop streaming and start/stop replay
+#define SHORTCUT_ID_START_STOP_RECORDING "gpu_screen_recorder_start_stop_recording"
+#define SHORTCUT_ID_PAUSE_UNPAUSE_RECORDING "gpu_screen_recorder_pause_unpause_recording"
+#define SHORTCUT_ID_SAVE_REPLAY "gpu_screen_recorder_save_replay"
+
typedef struct {
Display *display;
GtkApplication *app;
@@ -52,7 +58,6 @@ static GtkSpinButton *area_height_entry;
static GtkComboBox *record_area_selection_menu;
static GtkTreeModel *record_area_selection_model;
static GtkComboBoxText *quality_input_menu;
-static GtkComboBoxText *video_codec_input_menu;
static GtkComboBoxText *audio_codec_input_menu;
static GtkComboBoxText *color_range_input_menu;
static GtkComboBoxText *framerate_mode_input_menu;
@@ -60,6 +65,8 @@ static GtkComboBoxText *stream_service_input_menu;
static GtkComboBoxText *record_container;
static GtkComboBoxText *replay_container;
static GtkComboBoxText *custom_stream_container;
+static GtkComboBox *video_codec_selection_menu;
+static GtkTreeModel *video_codec_selection_model;
static GtkLabel *stream_key_label;
static GtkButton *record_file_chooser_button;
static GtkButton *replay_file_chooser_button;
@@ -81,14 +88,17 @@ static GtkSpinButton *replay_time_entry;
static GtkButton *select_window_button;
static GtkWidget *audio_input_used_list;
static GtkWidget *add_audio_input_button;
-static GtkWidget *record_hotkey_button;
+static GtkWidget *record_start_stop_hotkey_button;
static GtkWidget *pause_unpause_hotkey_button;
static GtkWidget *replay_start_stop_hotkey_button;
static GtkWidget *replay_save_hotkey_button;
-static GtkWidget *streaming_hotkey_button;
+static GtkWidget *streaming_start_stop_hotkey_button;
static GtkWidget *merge_audio_tracks_button;
-static GtkWidget *show_notification_button;
+static GtkWidget *show_recording_started_notification_button;
+static GtkWidget *show_recording_stopped_notification_button;
+static GtkWidget *show_recording_saved_notification_button;
static GtkWidget *record_cursor_button;
+static GtkWidget *restore_portal_session_button;
static GtkGrid *video_codec_grid;
static GtkGrid *audio_codec_grid;
static GtkGrid *color_range_grid;
@@ -112,6 +122,12 @@ static GtkWidget *pause_recording_menu_item;
static GtkWidget *start_stop_replay_menu_item;
static GtkWidget *save_replay_menu_item;
static GtkWidget *hide_window_when_recording_menu_item;
+static GtkGrid *recording_hotkeys_grid;
+static GtkGrid *replay_hotkeys_grid;
+static GtkGrid *streaming_hotkeys_grid;
+static GtkWidget *recording_hotkeys_not_supported_label;
+static GtkWidget *replay_hotkeys_not_supported_label;
+static GtkWidget *streaming_hotkeys_not_supported_label;
static double record_start_time_sec = 0.0;
static double pause_start_sec = 0.0;
@@ -131,31 +147,23 @@ static std::string record_file_current_filename;
static bool nvfbc_installed = false;
static Display *dpy = NULL;
-static bool wayland = false;
static bool flatpak = false;
-static gsr_egl egl;
static bool showing_notification = false;
static double notification_timeout_seconds = 0.0;
static double notification_start_seconds = 0.0;
static AppIndicator *app_indicator;
-static const char *tray_idle_icon_name = "com.dec05eba.gpu_screen_recorder.tray-idle";
-static const char *tray_recording_icon_name = "com.dec05eba.gpu_screen_recorder.tray-recording";
-static const char *tray_paused_icon_name = "com.dec05eba.gpu_screen_recorder.tray-paused";
+
+static gsr_global_shortcuts global_shortcuts;
+static bool global_shortcuts_initialized = false;
struct AudioInput {
std::string name;
std::string description;
};
-struct PulseAudioServerInfo {
- std::string default_sink_name;
- std::string default_source_name;
-};
-
static std::vector<AudioInput> audio_inputs;
-static PulseAudioServerInfo pa_default_sources;
enum class HotkeyMode {
NoAction,
@@ -165,30 +173,115 @@ enum class HotkeyMode {
static HotkeyMode hotkey_mode = HotkeyMode::NoAction;
+typedef gboolean (*hotkey_trigger_handler)(GtkButton *button, gpointer userdata);
struct Hotkey {
uint32_t modkey_mask = 0;
KeySym keysym = None;
GtkWidget *hotkey_entry = nullptr;
GtkWidget *hotkey_active_label = nullptr;
+ ConfigHotkey *config = nullptr;
+ bool grab_success = false;
+ GtkWidget *page = nullptr;
+ hotkey_trigger_handler trigger_handler = nullptr;
+ GtkButton *associated_button = nullptr;
+ const char *shortcut_id = nullptr;
};
static Hotkey *current_hotkey = nullptr;
static Hotkey pressed_hotkey;
static Hotkey latest_hotkey;
-static Hotkey streaming_hotkey;
-static Hotkey record_hotkey;
+static Hotkey streaming_start_stop_hotkey;
+static Hotkey record_start_stop_hotkey;
static Hotkey pause_unpause_hotkey;
static Hotkey replay_start_stop_hotkey;
static Hotkey replay_save_hotkey;
+static Hotkey *hotkeys[] = {
+ &streaming_start_stop_hotkey,
+ &record_start_stop_hotkey,
+ &pause_unpause_hotkey,
+ &replay_start_stop_hotkey,
+ &replay_save_hotkey
+};
+static int num_hotkeys = 5;
+
struct SupportedVideoCodecs {
- bool h264;
- bool hevc;
- bool av1;
+ bool h264 = false;
+ bool h264_software = false;
+ bool hevc = false;
+ bool hevc_hdr = false;
+ bool hevc_10bit = false;
+ bool av1 = false;
+ bool av1_hdr = false;
+ bool av1_10bit = false;
+ bool vp8 = false;
+ bool vp9 = false;
};
-static SupportedVideoCodecs supported_video_codecs;
-static int supported_video_codecs_exit_status = 0;
+struct vec2i {
+ int x = 0;
+ int y = 0;
+};
+
+struct GsrMonitor {
+ std::string name;
+ vec2i size;
+};
+
+struct SupportedCaptureOptions {
+ bool window = false;
+ bool focused = false;
+ bool screen = false;
+ bool portal = false;
+ std::vector<GsrMonitor> monitors;
+};
+
+enum class DisplayServer {
+ UNKNOWN,
+ X11,
+ WAYLAND
+};
+
+struct SystemInfo {
+ DisplayServer display_server = DisplayServer::UNKNOWN;
+ bool is_steam_deck = false;
+};
+
+enum class GpuVendor {
+ UNKNOWN,
+ AMD,
+ INTEL,
+ NVIDIA
+};
+
+struct GpuInfo {
+ GpuVendor vendor = GpuVendor::UNKNOWN;
+};
+
+struct GsrInfo {
+ SystemInfo system_info;
+ GpuInfo gpu_info;
+ SupportedVideoCodecs supported_video_codecs;
+ SupportedCaptureOptions supported_capture_options;
+};
+
+static GsrInfo gsr_info;
+
+enum class GsrInfoExitStatus {
+ OK,
+ FAILED_TO_RUN_COMMAND,
+ OPENGL_FAILED,
+ NO_DRM_CARD
+};
+
+static GsrInfoExitStatus gsr_info_exit_status;
+
+enum class WaylandCompositor {
+ UNKNOWN,
+ HYPRLAND,
+ KDE // kwin
+};
+static WaylandCompositor wayland_compositor = WaylandCompositor::UNKNOWN;
struct Container {
const char *container_name;
@@ -209,18 +302,27 @@ struct AudioRow {
GtkComboBoxText *input_list;
};
-typedef enum {
- GPU_VENDOR_AMD,
- GPU_VENDOR_INTEL,
- GPU_VENDOR_NVIDIA
-} gpu_vendor;
+// Dumb hacks below!! why dont these fking paths work outside flatpak.. except in kde. TODO: fix this!
+static const char* get_tray_idle_icon_name() {
+ if(flatpak)
+ return "com.dec05eba.gpu_screen_recorder.tray-idle";
+ else
+ return "/usr/share/icons/hicolor/32x32/status/com.dec05eba.gpu_screen_recorder.tray-idle.png";
+}
-typedef struct {
- gpu_vendor vendor;
- int gpu_version; /* 0 if unknown */
-} gpu_info;
+static const char* get_tray_recording_icon_name() {
+ if(flatpak)
+ return "com.dec05eba.gpu_screen_recorder.tray-recording";
+ else
+ return "/usr/share/icons/hicolor/32x32/status/com.dec05eba.gpu_screen_recorder.tray-recording.png";
+}
-static gpu_info gpu_inf;
+static const char* get_tray_paused_icon_name() {
+ if(flatpak)
+ return "com.dec05eba.gpu_screen_recorder.tray-paused";
+ else
+ return "/usr/share/icons/hicolor/32x32/status/com.dec05eba.gpu_screen_recorder.tray-paused.png";
+}
static bool is_program_installed(const StringView program_name) {
const char *path = getenv("PATH");
@@ -391,7 +493,7 @@ static GtkMenuShell* create_systray_menu(GtkApplication *app, SystrayPage systra
static void setup_systray(GtkApplication *app) {
app_indicator = app_indicator_new("com.dec05eba.gpu_screen_recorder", "", APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
- app_indicator_set_icon_full(app_indicator, tray_idle_icon_name, "Idle");
+ app_indicator_set_icon_full(app_indicator, get_tray_idle_icon_name(), "Idle");
// This triggers Gdk assert: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed,
// dont know why but it works anyways
app_indicator_set_title(app_indicator, "GPU Screen Recorder");
@@ -399,128 +501,44 @@ static void setup_systray(GtkApplication *app) {
app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(app, SystrayPage::FRONT)));
}
-static void pa_state_cb(pa_context *c, void *userdata) {
- pa_context_state state = pa_context_get_state(c);
- int *pa_ready = (int*)userdata;
- switch(state) {
- case PA_CONTEXT_UNCONNECTED:
- case PA_CONTEXT_CONNECTING:
- case PA_CONTEXT_AUTHORIZING:
- case PA_CONTEXT_SETTING_NAME:
- default:
- break;
- case PA_CONTEXT_FAILED:
- case PA_CONTEXT_TERMINATED:
- *pa_ready = 2;
- break;
- case PA_CONTEXT_READY:
- *pa_ready = 1;
- break;
- }
-}
+static AudioInput parse_audio_device_line(const std::string &line) {
+ AudioInput audio_input;
+ const size_t space_index = line.find('|');
+ if(space_index == std::string::npos)
+ return audio_input;
-static void pa_sourcelist_cb(pa_context*, const pa_source_info *source_info, int eol, void *userdata) {
- if(eol > 0)
- return;
-
- std::vector<AudioInput> *inputs = (std::vector<AudioInput>*)userdata;
- inputs->push_back({ source_info->name, source_info->description });
+ const StringView audio_input_name = {line.c_str(), space_index};
+ const StringView audio_input_description = {line.c_str() + space_index + 1, line.size() - (space_index + 1)};
+ audio_input.name.assign(audio_input_name.str, audio_input_name.size);
+ audio_input.description.assign(audio_input_description.str, audio_input_description.size);
+ return audio_input;
}
-static std::vector<AudioInput> get_pulseaudio_inputs() {
+static std::vector<AudioInput> get_audio_devices() {
std::vector<AudioInput> inputs;
- pa_mainloop *main_loop = pa_mainloop_new();
-
- pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder-gtk");
- pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
- int state = 0;
- int pa_ready = 0;
- pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready);
-
- pa_operation *pa_op = NULL;
-
- for(;;) {
- // Not ready
- if(pa_ready == 0) {
- pa_mainloop_iterate(main_loop, 1, NULL);
- continue;
- }
-
- switch(state) {
- case 0: {
- pa_op = pa_context_get_source_info_list(ctx, pa_sourcelist_cb, &inputs);
- ++state;
- break;
- }
- }
-
- // Couldn't get connection to the server
- if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE)) {
- if(pa_op)
- pa_operation_unref(pa_op);
- pa_context_disconnect(ctx);
- pa_context_unref(ctx);
- pa_mainloop_free(main_loop);
- return inputs;
- }
- pa_mainloop_iterate(main_loop, 1, NULL);
+ FILE *f = popen("gpu-screen-recorder --list-audio-devices", "r");
+ if(!f) {
+ fprintf(stderr, "error: 'gpu-screen-recorder --list-audio-devices' failed\n");
+ return inputs;
}
- pa_mainloop_free(main_loop);
- return {};
-}
-
-static void server_info_callback(pa_context*, const pa_server_info *server_info, void *userdata) {
- PulseAudioServerInfo *u = (PulseAudioServerInfo*)userdata;
- if(server_info->default_sink_name)
- u->default_sink_name = std::string(server_info->default_sink_name) + ".monitor";
- if(server_info->default_source_name)
- u->default_source_name = server_info->default_source_name;
-}
-
-static PulseAudioServerInfo get_pulseaudio_default_inputs() {
- PulseAudioServerInfo server_info;
- pa_mainloop *main_loop = pa_mainloop_new();
-
- pa_context *ctx = pa_context_new(pa_mainloop_get_api(main_loop), "gpu-screen-recorder-gtk");
- pa_context_connect(ctx, NULL, PA_CONTEXT_NOFLAGS, NULL);
- int state = 0;
- int pa_ready = 0;
- pa_context_set_state_callback(ctx, pa_state_cb, &pa_ready);
-
- pa_operation *pa_op = NULL;
-
- for(;;) {
- // Not ready
- if(pa_ready == 0) {
- pa_mainloop_iterate(main_loop, 1, NULL);
- continue;
- }
-
- switch(state) {
- case 0: {
- pa_op = pa_context_get_server_info(ctx, server_info_callback, &server_info);
- ++state;
- break;
- }
- }
-
- // Couldn't get connection to the server
- if(pa_ready == 2 || (state == 1 && pa_op && pa_operation_get_state(pa_op) == PA_OPERATION_DONE)) {
- if(pa_op)
- pa_operation_unref(pa_op);
- pa_context_disconnect(ctx);
- pa_context_unref(ctx);
- pa_mainloop_free(main_loop);
- return server_info;
- }
-
- pa_mainloop_iterate(main_loop, 1, NULL);
+ char output[16384];
+ ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f);
+ if(bytes_read < 0 || ferror(f)) {
+ fprintf(stderr, "error: failed to read 'gpu-screen-recorder --list-audio-devices' output\n");
+ pclose(f);
+ return inputs;
}
+ output[bytes_read] = '\0';
+
+ string_split_char(output, '\n', [&](StringView line) {
+ const std::string line_str(line.str, line.size);
+ inputs.push_back(parse_audio_device_line(line_str));
+ return true;
+ });
- pa_mainloop_free(main_loop);
- return server_info;
+ return inputs;
}
static void used_audio_input_loop_callback(GtkWidget *row, gpointer userdata) {
@@ -584,6 +602,54 @@ static void drag_data_received(GtkWidget *widget, GdkDragContext*,
g_object_unref(source);
}
+static bool is_video_capture_option_enabled(const char *str) {
+ bool enabled = true;
+
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
+ enabled = strcmp(str, "window") != 0 && strcmp(str, "focused") != 0;
+
+ if(strcmp(str, "portal") == 0 && !gsr_info.supported_capture_options.portal)
+ enabled = false;
+
+ return enabled;
+}
+
+static bool is_video_codec_enabled(const char *str) {
+ bool enabled = true;
+
+ if(strcmp(str, "h264") == 0 && !gsr_info.supported_video_codecs.h264)
+ enabled = false;
+
+ if(strcmp(str, "h264_software") == 0 && !gsr_info.supported_video_codecs.h264_software)
+ enabled = false;
+
+ if(strcmp(str, "hevc") == 0 && !gsr_info.supported_video_codecs.hevc)
+ enabled = false;
+
+ if(strcmp(str, "hevc_hdr") == 0 && !gsr_info.supported_video_codecs.hevc_hdr)
+ enabled = false;
+
+ if(strcmp(str, "hevc_10bit") == 0 && !gsr_info.supported_video_codecs.hevc_10bit)
+ enabled = false;
+
+ if(strcmp(str, "av1") == 0 && !gsr_info.supported_video_codecs.av1)
+ enabled = false;
+
+ if(strcmp(str, "av1_hdr") == 0 && !gsr_info.supported_video_codecs.av1_hdr)
+ enabled = false;
+
+ if(strcmp(str, "av1_10bit") == 0 && !gsr_info.supported_video_codecs.av1_10bit)
+ enabled = false;
+
+ if(strcmp(str, "vp8") == 0 && !gsr_info.supported_video_codecs.vp8)
+ enabled = false;
+
+ if(strcmp(str, "vp9") == 0 && !gsr_info.supported_video_codecs.vp9)
+ enabled = false;
+
+ return enabled;
+}
+
static std::string record_area_selection_menu_get_active_id() {
std::string id_str;
GtkTreeIter iter;
@@ -598,6 +664,9 @@ static std::string record_area_selection_menu_get_active_id() {
}
static void record_area_selection_menu_set_active_id(const gchar *id) {
+ if(!is_video_capture_option_enabled(id))
+ return;
+
GtkTreeIter iter;
if(!gtk_tree_model_get_iter_first(record_area_selection_model, &iter))
return;
@@ -615,8 +684,42 @@ static void record_area_selection_menu_set_active_id(const gchar *id) {
} while(gtk_tree_model_iter_next(record_area_selection_model, &iter));
}
+static std::string video_codec_selection_menu_get_active_id() {
+ std::string id_str;
+ GtkTreeIter iter;
+ if(!gtk_combo_box_get_active_iter(video_codec_selection_menu, &iter))
+ return id_str;
+
+ gchar *id;
+ gtk_tree_model_get(video_codec_selection_model, &iter, 1, &id, -1);
+ id_str = id;
+ g_free(id);
+ return id_str;
+}
+
+static void video_codec_selection_menu_set_active_id(const gchar *id) {
+ if(!is_video_codec_enabled(id))
+ return;
+
+ GtkTreeIter iter;
+ if(!gtk_tree_model_get_iter_first(video_codec_selection_model, &iter))
+ return;
+
+ do {
+ gchar *row_id = nullptr;
+ gtk_tree_model_get(video_codec_selection_model, &iter, 1, &row_id, -1);
+
+ const bool found_row = strcmp(row_id, id) == 0;
+ g_free(row_id);
+ if(found_row) {
+ gtk_combo_box_set_active_iter(video_codec_selection_menu, &iter);
+ break;
+ }
+ } while(gtk_tree_model_iter_next(video_codec_selection_model, &iter));
+}
+
static void enable_stream_record_button_if_info_filled() {
- if(!wayland) {
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) {
const std::string selected_window_area = record_area_selection_menu_get_active_id();
if(strcmp(selected_window_area.c_str(), "window") == 0 && select_window_userdata.selected_window == None) {
gtk_widget_set_sensitive(GTK_WIDGET(replay_button), false);
@@ -673,9 +776,9 @@ static GtkWidget* create_used_audio_input_row(void) {
g_object_set_data(G_OBJECT(row), "audio-row", audio_row);
g_signal_connect(remove_button, "clicked", G_CALLBACK(+[](GtkButton*, gpointer userdata){
- AudioRow *audio_row = (AudioRow*)userdata;
- gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(audio_row->row)), audio_row->row);
- delete audio_row;
+ AudioRow *_audio_row = (AudioRow*)userdata;
+ gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(_audio_row->row)), _audio_row->row);
+ delete _audio_row;
return true;
}), audio_row);
@@ -746,7 +849,7 @@ static std::string get_date_str() {
static void save_configs() {
config.main_config.record_area_option = record_area_selection_menu_get_active_id();
- if(!wayland) {
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) {
config.main_config.record_area_width = gtk_spin_button_get_value_as_int(area_width_entry);
config.main_config.record_area_height = gtk_spin_button_get_value_as_int(area_height_entry);
}
@@ -759,324 +862,74 @@ static void save_configs() {
});
config.main_config.color_range = gtk_combo_box_get_active_id(GTK_COMBO_BOX(color_range_input_menu));
config.main_config.quality = gtk_combo_box_get_active_id(GTK_COMBO_BOX(quality_input_menu));
- config.main_config.codec = gtk_combo_box_get_active_id(GTK_COMBO_BOX(video_codec_input_menu));
+ config.main_config.codec = video_codec_selection_menu_get_active_id();
config.main_config.audio_codec = gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_codec_input_menu));
config.main_config.framerate_mode = gtk_combo_box_get_active_id(GTK_COMBO_BOX(framerate_mode_input_menu));
config.main_config.advanced_view = strcmp(gtk_combo_box_get_active_id(GTK_COMBO_BOX(view_combo_box)), "advanced") == 0;
config.main_config.overclock = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overclock_button));
- config.main_config.show_notifications = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_notification_button));
+ config.main_config.show_recording_started_notifications = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_started_notification_button));
+ config.main_config.show_recording_stopped_notifications = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_stopped_notification_button));
+ config.main_config.show_recording_saved_notifications = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_saved_notification_button));
config.main_config.record_cursor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(record_cursor_button));
config.main_config.hide_window_when_recording = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item));
+ config.main_config.restore_portal_session = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(restore_portal_session_button));
config.streaming_config.streaming_service = gtk_combo_box_get_active_id(GTK_COMBO_BOX(stream_service_input_menu));
config.streaming_config.youtube.stream_key = gtk_entry_get_text(youtube_stream_id_entry);
config.streaming_config.twitch.stream_key = gtk_entry_get_text(twitch_stream_id_entry);
config.streaming_config.custom.url = gtk_entry_get_text(custom_stream_url_entry);
config.streaming_config.custom.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(custom_stream_container));
- if(!wayland) {
- config.streaming_config.start_recording_hotkey.keysym = streaming_hotkey.keysym;
- config.streaming_config.start_recording_hotkey.modifiers = streaming_hotkey.modkey_mask;
- }
config.record_config.save_directory = gtk_button_get_label(record_file_chooser_button);
config.record_config.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_container));
- if(!wayland) {
- config.record_config.start_recording_hotkey.keysym = record_hotkey.keysym;
- config.record_config.start_recording_hotkey.modifiers = record_hotkey.modkey_mask;
-
- config.record_config.pause_recording_hotkey.keysym = pause_unpause_hotkey.keysym;
- config.record_config.pause_recording_hotkey.modifiers = pause_unpause_hotkey.modkey_mask;
- }
config.replay_config.save_directory = gtk_button_get_label(replay_file_chooser_button);
config.replay_config.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(replay_container));
config.replay_config.replay_time = gtk_spin_button_get_value_as_int(replay_time_entry);
- if(!wayland) {
- config.replay_config.start_recording_hotkey.keysym = replay_start_stop_hotkey.keysym;
- config.replay_config.start_recording_hotkey.modifiers = replay_start_stop_hotkey.modkey_mask;
-
- config.replay_config.save_recording_hotkey.keysym = replay_save_hotkey.keysym;
- config.replay_config.save_recording_hotkey.modifiers = replay_save_hotkey.modkey_mask;
- }
-
- save_config(config);
-}
-
-typedef struct {
- const char *name;
- int name_len;
- vec2i pos;
- vec2i size;
- XRRCrtcInfo *crt_info; /* Only on x11 */
- uint32_t connector_id; /* Only on drm */
-} gsr_monitor;
-
-typedef enum {
- GSR_CONNECTION_X11,
- GSR_CONNECTION_WAYLAND,
- GSR_CONNECTION_DRM
-} gsr_connection_type;
-
-using active_monitor_callback = std::function<void(const gsr_monitor *monitor, void *userdata)>;
-
-static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) {
- for(int i = 0; i < sr->nmode; ++i) {
- if(sr->modes[i].id == id)
- return &sr->modes[i];
- }
- return NULL;
-}
-
-static void for_each_active_monitor_output_x11(Display *display, active_monitor_callback callback, void *userdata) {
- XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
- if(!screen_res)
- return;
-
- char display_name[256];
- for(int i = 0; i < screen_res->noutput; ++i) {
- XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]);
- if(out_info && out_info->crtc && out_info->connection == RR_Connected) {
- XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc);
- if(crt_info && crt_info->mode) {
- const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
- if(mode_info && out_info->nameLen < (int)sizeof(display_name)) {
- memcpy(display_name, out_info->name, out_info->nameLen);
- display_name[out_info->nameLen] = '\0';
-
- gsr_monitor monitor;
- monitor.name = display_name;
- monitor.name_len = out_info->nameLen;
- monitor.pos = { (int)crt_info->x, (int)crt_info->y };
- monitor.size = { (int)crt_info->width, (int)crt_info->height };
- monitor.crt_info = crt_info;
- monitor.connector_id = 0; // TODO: Get connector id
- callback(&monitor, userdata);
- }
- }
- if(crt_info)
- XRRFreeCrtcInfo(crt_info);
- }
- if(out_info)
- XRRFreeOutputInfo(out_info);
- }
-
- XRRFreeScreenResources(screen_res);
-}
-
-typedef struct {
- int type;
- int count;
-} drm_connector_type_count;
-
-#define CONNECTOR_TYPE_COUNTS 32
-
-static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) {
- for(int i = 0; i < *num_type_counts; ++i) {
- if(type_counts[i].type == connector_type)
- return &type_counts[i];
- }
-
- if(*num_type_counts == CONNECTOR_TYPE_COUNTS)
- return NULL;
-
- const int index = *num_type_counts;
- type_counts[index].type = connector_type;
- type_counts[index].count = 0;
- ++*num_type_counts;
- return &type_counts[index];
-}
-
-static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props, const char *name, uint64_t *result) {
- for(int i = 0; i < props->count_props; ++i) {
- drmModePropertyPtr prop = drmModeGetProperty(drmfd, props->props[i]);
- if(prop) {
- if(strcmp(name, prop->name) == 0) {
- *result = props->prop_values[i];
- drmModeFreeProperty(prop);
- return true;
- }
- drmModeFreeProperty(prop);
- }
- }
- return false;
-}
-static void for_each_active_monitor_output_wayland(const gsr_egl *egl, active_monitor_callback callback, void *userdata) {
- for(int i = 0; i < egl->wayland.num_outputs; ++i) {
- if(!egl->wayland.outputs[i].name)
- continue;
-
- gsr_monitor monitor;
- monitor.name = egl->wayland.outputs[i].name;
- monitor.name_len = strlen(egl->wayland.outputs[i].name);
- monitor.pos = { egl->wayland.outputs[i].pos.x, egl->wayland.outputs[i].pos.y };
- monitor.size = { egl->wayland.outputs[i].size.x, egl->wayland.outputs[i].size.y };
- monitor.crt_info = NULL;
- monitor.connector_id = 0;
- callback(&monitor, userdata);
- }
-}
-
-static void for_each_active_monitor_output_drm(const gsr_egl *egl, active_monitor_callback callback, void *userdata) {
- int fd = open(egl->card_path, O_RDONLY);
- if(fd == -1)
- return;
-
- drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
-
- drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS];
- int num_type_counts = 0;
-
- char display_name[256];
- drmModeResPtr resources = drmModeGetResources(fd);
- if(resources) {
- for(int i = 0; i < resources->count_connectors; ++i) {
- drmModeConnectorPtr connector = drmModeGetConnectorCurrent(fd, resources->connectors[i]);
- if(!connector)
- continue;
-
- drm_connector_type_count *connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector->connector_type);
- const char *connection_name = drmModeGetConnectorTypeName(connector->connector_type);
- const int connection_name_len = strlen(connection_name);
- if(connector_type)
- ++connector_type->count;
-
- if(connector->connection != DRM_MODE_CONNECTED) {
- drmModeFreeConnector(connector);
- continue;
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) {
+ for(int i = 0; i < num_hotkeys; ++i) {
+ // This can also happen if we run multiple instances of gpu screen recorder, in which case it will fail to grab keys for the other windows.
+ // We dont want to overwrite hotkeys in that case.
+ if(hotkeys[i]->grab_success) {
+ hotkeys[i]->config->keysym = hotkeys[i]->keysym;
+ hotkeys[i]->config->modifiers = hotkeys[i]->modkey_mask;
}
-
- uint64_t crtc_id = 0;
- connector_get_property_by_name(fd, connector, "CRTC_ID", &crtc_id);
-
- drmModeCrtcPtr crtc = drmModeGetCrtc(fd, crtc_id);
- if(connector_type && crtc_id > 0 && crtc && connection_name_len + 5 < (int)sizeof(display_name)) {
- const int display_name_len = snprintf(display_name, sizeof(display_name), "%s-%d", connection_name, connector_type->count);
- gsr_monitor monitor;
- monitor.name = display_name;
- monitor.name_len = display_name_len;
- monitor.pos = { (int)crtc->x, (int)crtc->y };
- monitor.size = { (int)crtc->width, (int)crtc->height };
- monitor.crt_info = NULL;
- monitor.connector_id = connector->connector_id;
- callback(&monitor, userdata);
- }
-
- if(crtc)
- drmModeFreeCrtc(crtc);
-
- drmModeFreeConnector(connector);
}
- drmModeFreeResources(resources);
- }
-
- close(fd);
-}
-
-static void for_each_active_monitor_output(const gsr_egl *egl, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) {
- switch(connection_type) {
- case GSR_CONNECTION_X11:
- for_each_active_monitor_output_x11(egl->x11.dpy, callback, userdata);
- break;
- case GSR_CONNECTION_WAYLAND:
- for_each_active_monitor_output_wayland(egl, callback, userdata);
- break;
- case GSR_CONNECTION_DRM:
- for_each_active_monitor_output_drm(egl, callback, userdata);
- break;
- }
-}
-
-static bool try_card_has_valid_plane(const char *card_path) {
- drmVersion *ver = NULL;
- drmModePlaneResPtr planes = NULL;
- bool found_screen_card = false;
-
- int fd = open(card_path, O_RDONLY);
- if(fd == -1)
- return false;
-
- ver = drmGetVersion(fd);
- if(!ver || strstr(ver->name, "nouveau"))
- goto next;
-
- drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
-
- planes = drmModeGetPlaneResources(fd);
- if(!planes)
- goto next;
-
- for(uint32_t j = 0; j < planes->count_planes; ++j) {
- drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[j]);
- if(!plane)
- continue;
-
- if(plane->fb_id)
- found_screen_card = true;
-
- drmModeFreePlane(plane);
- if(found_screen_card)
- break;
}
- next:
- if(planes)
- drmModeFreePlaneResources(planes);
- if(ver)
- drmFreeVersion(ver);
- close(fd);
- if(found_screen_card)
- return true;
-
- return false;
-}
-
-static void string_copy(char *dst, const char *src, int len) {
- int src_len = strlen(src);
- int min_len = src_len;
- if(len - 1 < min_len)
- min_len = len - 1;
- memcpy(dst, src, min_len);
- dst[min_len] = '\0';
+ save_config(config);
}
-/* output should be >= 128 bytes */
-static bool gsr_get_valid_card_path(gsr_egl *egl, char *output) {
- if(egl->dri_card_path) {
- string_copy(output, egl->dri_card_path, 127);
- return try_card_has_valid_plane(output);
+static void show_notification(GtkApplication *app, const char *title, const char *body, GNotificationPriority priority) {
+ if(priority < G_NOTIFICATION_PRIORITY_URGENT) {
+ notification_timeout_seconds = 3.0;
+ } else {
+ notification_timeout_seconds = 10.0;
}
- for(int i = 0; i < 10; ++i) {
- snprintf(output, 127, DRM_DEV_NAME, DRM_DIR_NAME, i);
- if(try_card_has_valid_plane(output))
- return true;
- }
- return false;
-}
+ // KDE doesn't show notifications when using desktop portal capture unless either DoNotDisturb.WhenScreenSharing kde config
+ // has been changed by the user or if the priority for the notification is set as urgent
+ const std::string recording_window = record_area_selection_menu_get_active_id();
+ if((recording || replaying || streaming) && wayland_compositor == WaylandCompositor::KDE && recording_window == "portal")
+ priority = G_NOTIFICATION_PRIORITY_URGENT;
-static void show_notification(GtkApplication *app, const char *title, const char *body, GNotificationPriority priority) {
fprintf(stderr, "Notification: title: %s, body: %s\n", title, body);
GNotification *notification = g_notification_new(title);
g_notification_set_body(notification, body);
g_notification_set_priority(notification, priority);
g_application_send_notification(&app->parent, "gpu-screen-recorder", notification);
- showing_notification = true;
- if(priority < G_NOTIFICATION_PRIORITY_URGENT) {
- notification_timeout_seconds = 2.0;
- } else {
- notification_timeout_seconds = 5.0;
- }
notification_start_seconds = clock_get_monotonic_seconds();
+ showing_notification = true;
}
-static bool window_has_atom(Display *display, Window window, Atom atom) {
+static bool window_has_atom(Display *display, Window _window, Atom atom) {
Atom type;
unsigned long len, bytes_left;
int format;
unsigned char *properties = nullptr;
- if(XGetWindowProperty(display, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success)
+ if(XGetWindowProperty(display, _window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success)
return false;
if(properties)
@@ -1085,22 +938,22 @@ static bool window_has_atom(Display *display, Window window, Atom atom) {
return type != None;
}
-static Window window_get_target_window_child(Display *display, Window window) {
- if(window == None)
+static Window window_get_target_window_child(Display *display, Window _window) {
+ if(_window == None)
return None;
Atom wm_state_atom = XInternAtom(display, "_NET_WM_STATE", False);
if(!wm_state_atom)
return None;
- if(window_has_atom(display, window, wm_state_atom))
- return window;
+ if(window_has_atom(display, _window, wm_state_atom))
+ return _window;
Window root;
Window parent;
Window *children = nullptr;
unsigned int num_children = 0;
- if(!XQueryTree(display, window, &root, &parent, &children, &num_children) || !children)
+ if(!XQueryTree(display, _window, &root, &parent, &children, &num_children) || !children)
return None;
Window found_window = None;
@@ -1128,38 +981,38 @@ static Window window_get_target_window_child(Display *display, Window window) {
/* TODO: Look at xwininfo source to figure out how to make this work for different types of window managers */
static GdkFilterReturn filter_callback(GdkXEvent *xevent, GdkEvent*, gpointer userdata) {
- SelectWindowUserdata *select_window_userdata = (SelectWindowUserdata*)userdata;
+ SelectWindowUserdata *_select_window_userdata = (SelectWindowUserdata*)userdata;
XEvent *ev = (XEvent*)xevent;
//assert(ev->type == ButtonPress);
if(ev->type != ButtonPress)
return GDK_FILTER_CONTINUE;
Window target_win = ev->xbutton.subwindow;
- Window new_window = window_get_target_window_child(select_window_userdata->display, target_win);
+ Window new_window = window_get_target_window_child(_select_window_userdata->display, target_win);
if(new_window)
target_win = new_window;
- int status = XUngrabPointer(select_window_userdata->display, CurrentTime);
+ int status = XUngrabPointer(_select_window_userdata->display, CurrentTime);
if(!status) {
fprintf(stderr, "failed to ungrab pointer!\n");
- show_notification(select_window_userdata->app, "GPU Screen Recorder", "Failed to ungrab pointer!", G_NOTIFICATION_PRIORITY_URGENT);
+ show_notification(_select_window_userdata->app, "GPU Screen Recorder", "Failed to ungrab pointer!", G_NOTIFICATION_PRIORITY_URGENT);
exit(1);
}
if(target_win == None) {
- show_notification(select_window_userdata->app, "GPU Screen Recorder", "No window selected!", G_NOTIFICATION_PRIORITY_URGENT);
+ show_notification(_select_window_userdata->app, "GPU Screen Recorder", "No window selected!", G_NOTIFICATION_PRIORITY_URGENT);
GdkScreen *screen = gdk_screen_get_default();
GdkWindow *root_window = gdk_screen_get_root_window(screen);
- gdk_window_remove_filter(root_window, filter_callback, select_window_userdata);
+ gdk_window_remove_filter(root_window, filter_callback, _select_window_userdata);
return GDK_FILTER_REMOVE;
}
std::string window_name;
XTextProperty wm_name_prop;
- if(XGetWMName(select_window_userdata->display, target_win, &wm_name_prop) && wm_name_prop.nitems > 0) {
+ if(XGetWMName(_select_window_userdata->display, target_win, &wm_name_prop) && wm_name_prop.nitems > 0) {
char **list_return = NULL;
int num_items = 0;
- int ret = XmbTextPropertyToTextList(select_window_userdata->display, &wm_name_prop, &list_return, &num_items);
+ int ret = XmbTextPropertyToTextList(_select_window_userdata->display, &wm_name_prop, &list_return, &num_items);
if((ret == Success || ret > 0) && list_return) {
for(int i = 0; i < num_items; ++i) {
window_name += list_return[i];
@@ -1173,12 +1026,12 @@ static GdkFilterReturn filter_callback(GdkXEvent *xevent, GdkEvent*, gpointer us
}
fprintf(stderr, "window name: %s, window id: %ld\n", window_name.c_str(), target_win);
- gtk_button_set_label(select_window_userdata->select_window_button, window_name.c_str());
- select_window_userdata->selected_window = target_win;
+ gtk_button_set_label(_select_window_userdata->select_window_button, window_name.c_str());
+ _select_window_userdata->selected_window = target_win;
GdkScreen *screen = gdk_screen_get_default();
GdkWindow *root_window = gdk_screen_get_root_window(screen);
- gdk_window_remove_filter(root_window, filter_callback, select_window_userdata);
+ gdk_window_remove_filter(root_window, filter_callback, _select_window_userdata);
enable_stream_record_button_if_info_filled();
@@ -1323,7 +1176,7 @@ static int xerror_grab_error(Display*, XErrorEvent*) {
}
static void ungrab_keyboard(Display *display) {
- if(wayland)
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
return;
if(current_hotkey) {
@@ -1338,7 +1191,7 @@ static void ungrab_keyboard(Display *display) {
}
static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab) {
- if(wayland)
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
return true;
if(hotkey.keysym == None && hotkey.modkey_mask == 0)
@@ -1351,7 +1204,7 @@ static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab)
for(int i = 0; i < 8; ++i) {
for(int j = 0; j < modmap->max_keypermod; ++j) {
if(modmap->modifiermap[i * modmap->max_keypermod + j] == numlock_keycode)
- numlockmask = (1 << i);
+ numlockmask = (1 << i);
}
}
XFreeModifiermap(modmap);
@@ -1368,7 +1221,7 @@ static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab)
XSync(display, False);
x_failed = false;
XErrorHandler prev_error_handler = XSetErrorHandler(xerror_grab_error);
-
+
Window root_window = DefaultRootWindow(display);
unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
if(key_sym != None) {
@@ -1396,14 +1249,12 @@ static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab)
}
static void ungrab_keys(Display *display) {
- if(wayland)
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
return;
- grab_ungrab_hotkey_combo(display, streaming_hotkey, false);
- grab_ungrab_hotkey_combo(display, record_hotkey, false);
- grab_ungrab_hotkey_combo(display, pause_unpause_hotkey, false);
- grab_ungrab_hotkey_combo(display, replay_start_stop_hotkey, false);
- grab_ungrab_hotkey_combo(display, replay_save_hotkey, false);
+ for(int i = 0; i < num_hotkeys; ++i) {
+ grab_ungrab_hotkey_combo(display, *hotkeys[i], false);
+ }
}
static void set_hotkey_text_from_hotkey_data(GtkEntry *entry, Hotkey hotkey) {
@@ -1446,61 +1297,60 @@ static void set_hotkey_text_from_hotkey_data(GtkEntry *entry, Hotkey hotkey) {
gtk_entry_set_text(entry, hotkey_combo_str.c_str());
}
-struct HotkeyResult {
- bool record_hotkey_success = false;
- bool pause_unpause_hotkey_success = false;
- bool streaming_hotkey_success = false;
- bool replay_start_stop_hotkey_success = false;
- bool replay_save_hotkey_success = false;
-};
+static bool replace_grabbed_keys_depending_on_active_page() {
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
+ return true;
+
+ for(int i = 0; i < num_hotkeys; ++i) {
+ hotkeys[i]->grab_success = false;
+ }
-static HotkeyResult replace_grabbed_keys_depending_on_active_page() {
- HotkeyResult hotkey_result;
ungrab_keys(gdk_x11_get_default_xdisplay());
const GtkWidget *visible_page = gtk_stack_get_visible_child(page_navigation_userdata.stack);
- if(visible_page == page_navigation_userdata.recording_page) {
- bool grab_record_success = grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), record_hotkey, true);
- bool grab_pause_success = grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), pause_unpause_hotkey, true);
- hotkey_mode = HotkeyMode::Record;
- hotkey_result.record_hotkey_success = grab_record_success;
- hotkey_result.pause_unpause_hotkey_success = grab_pause_success;
- } else if(visible_page == page_navigation_userdata.streaming_page) {
- bool grab_record_success = grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), streaming_hotkey, true);
- hotkey_mode = HotkeyMode::Record;
- hotkey_result.streaming_hotkey_success = grab_record_success;
- } else if(visible_page == page_navigation_userdata.replay_page) {
- bool grab_record_success = grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), replay_start_stop_hotkey, true);
- bool grab_save_success = grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), replay_save_hotkey, true);
- hotkey_mode = HotkeyMode::Record;
-
- hotkey_result.replay_start_stop_hotkey_success = grab_record_success;
- hotkey_result.replay_save_hotkey_success = grab_save_success;
+ bool keys_successfully_grabbed = true;
+ for(int i = 0; i < num_hotkeys; ++i) {
+ if(visible_page == hotkeys[i]->page) {
+ hotkey_mode = HotkeyMode::Record;
+ hotkeys[i]->grab_success = grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), *hotkeys[i], true);
+ if(hotkeys[i]->grab_success) {
+ set_hotkey_text_from_hotkey_data(GTK_ENTRY(hotkeys[i]->hotkey_entry), *hotkeys[i]);
+ } else {
+ gtk_entry_set_text(GTK_ENTRY(hotkeys[i]->hotkey_entry), "");
+ hotkeys[i]->keysym = 0;
+ hotkeys[i]->modkey_mask = 0;
+ keys_successfully_grabbed = false;
+ }
+ }
}
- return hotkey_result;
+ return keys_successfully_grabbed;
+}
+
+static bool is_monitor_capture_drm() {
+ return gsr_info.system_info.display_server == DisplayServer::WAYLAND || gsr_info.gpu_info.vendor != GpuVendor::NVIDIA;
}
static bool show_pkexec_flatpak_error_if_needed() {
const std::string window_str = record_area_selection_menu_get_active_id();
- if((wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA) && window_str != "window" && window_str != "focused") {
+ if(is_monitor_capture_drm() && window_str != "window" && window_str != "focused" && window_str != "portal") {
if(!is_pkexec_installed()) {
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "pkexec needs to be installed to record a monitor with an AMD/Intel GPU. Please install and run polkit. Alternatively, record a single window which doesn't require root access.");
+ "pkexec needs to be installed to record a monitor with an AMD/Intel GPU. Please install and run polkit. Alternatively, record a single window or use portal option which doesn't require root access.");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
return true;
}
if(flatpak && !flatpak_is_installed_as_system()) {
- if(wayland) {
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "GPU Screen Recorder needs to be installed system-wide to record your monitor on Wayland. To install GPU Screen recorder system-wide, you can run this command:\n"
+ "GPU Screen Recorder needs to be installed system-wide to record your monitor on Wayland when not using the portal option. To install GPU Screen recorder system-wide, you can run this command:\n"
"flatpak install flathub --system com.dec05eba.gpu_screen_recorder\n");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
} else {
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "GPU Screen Recorder needs to be installed system-wide to record your monitor on AMD/Intel. To install GPU Screen recorder system-wide, you can run this command:\n"
+ "GPU Screen Recorder needs to be installed system-wide to record your monitor on AMD/Intel when not using the portal option. To install GPU Screen recorder system-wide, you can run this command:\n"
"flatpak install flathub --system com.dec05eba.gpu_screen_recorder\n"
"Alternatively, record a single window which doesn't have this restriction.");
gtk_dialog_run(GTK_DIALOG(dialog));
@@ -1512,27 +1362,42 @@ static bool show_pkexec_flatpak_error_if_needed() {
return false;
}
+static void show_bugged_driver_warning() {
+ if(gsr_info.gpu_info.vendor != GpuVendor::AMD)
+ return;
+
+ const std::string video_codec = video_codec_selection_menu_get_active_id();
+ if((video_codec == "hevc" || video_codec == "hevc_10bit" || video_codec == "hevc_hdr") && !config.main_config.hevc_amd_bug_warning_shown) {
+ GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "There is an AMD driver bug and FFmpeg bug that causes black bars to appear on the sides of the video at certain resolutions when using HEVC codec.\n"
+ "Select H264 video codec instead if this is an issue for you.");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ config.main_config.hevc_amd_bug_warning_shown = true;
+ }
+
+ if((video_codec == "av1" || video_codec == "av1_10bit" || video_codec == "av1_hdr") && !config.main_config.av1_amd_bug_warning_shown) {
+ GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "There is an AMD hardware bug that causes black bars to appear on the sides of the video at certain resolutions when using AV1 codec.\n"
+ "Select H264 video codec instead if this is an issue for you.");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ config.main_config.av1_amd_bug_warning_shown = true;
+ }
+}
+
static gboolean on_start_replay_click(GtkButton*, gpointer userdata) {
if(show_pkexec_flatpak_error_if_needed())
return true;
- PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
- gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->replay_page);
- app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::REPLAY)));
+ show_bugged_driver_warning();
- if(!wayland) {
- HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
- if(!hotkey_result.replay_start_stop_hotkey_success) {
- gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey.hotkey_entry), "");
- replay_start_stop_hotkey.keysym = 0;
- replay_start_stop_hotkey.modkey_mask = 0;
- }
- if(!hotkey_result.replay_save_hotkey_success) {
- gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey.hotkey_entry), "");
- replay_save_hotkey.keysym = 0;
- replay_save_hotkey.modkey_mask = 0;
- }
- }
+ PageNavigationUserdata *_page_navigation_userdata = (PageNavigationUserdata*)userdata;
+ gtk_stack_set_visible_child(_page_navigation_userdata->stack, _page_navigation_userdata->replay_page);
+ app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(_page_navigation_userdata->app, SystrayPage::REPLAY)));
+
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND)
+ replace_grabbed_keys_depending_on_active_page();
return true;
}
@@ -1541,23 +1406,14 @@ static gboolean on_start_recording_click(GtkButton*, gpointer userdata) {
if(show_pkexec_flatpak_error_if_needed())
return true;
- PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
- gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->recording_page);
- app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::RECORDING)));
+ show_bugged_driver_warning();
- if(!wayland) {
- HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
- if(!hotkey_result.record_hotkey_success) {
- gtk_entry_set_text(GTK_ENTRY(record_hotkey.hotkey_entry), "");
- record_hotkey.keysym = 0;
- record_hotkey.modkey_mask = 0;
- }
- if(!hotkey_result.pause_unpause_hotkey_success) {
- gtk_entry_set_text(GTK_ENTRY(pause_unpause_hotkey.hotkey_entry), "");
- pause_unpause_hotkey.keysym = 0;
- pause_unpause_hotkey.modkey_mask = 0;
- }
- }
+ PageNavigationUserdata *_page_navigation_userdata = (PageNavigationUserdata*)userdata;
+ gtk_stack_set_visible_child(_page_navigation_userdata->stack, _page_navigation_userdata->recording_page);
+ app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(_page_navigation_userdata->app, SystrayPage::RECORDING)));
+
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND)
+ replace_grabbed_keys_depending_on_active_page();
return true;
}
@@ -1572,6 +1428,8 @@ static gboolean on_start_streaming_click(GtkButton*, gpointer userdata) {
if(show_pkexec_flatpak_error_if_needed())
return true;
+ show_bugged_driver_warning();
+
int num_audio_tracks = 0;
for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&num_audio_tracks](const AudioRow*) {
++num_audio_tracks;
@@ -1585,28 +1443,22 @@ static gboolean on_start_streaming_click(GtkButton*, gpointer userdata) {
return true;
}
- PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
- gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->streaming_page);
- app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::STREAMING)));
+ PageNavigationUserdata *_page_navigation_userdata = (PageNavigationUserdata*)userdata;
+ gtk_stack_set_visible_child(_page_navigation_userdata->stack, _page_navigation_userdata->streaming_page);
+ app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(_page_navigation_userdata->app, SystrayPage::STREAMING)));
- if(!wayland) {
- HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
- if(!hotkey_result.streaming_hotkey_success) {
- gtk_entry_set_text(GTK_ENTRY(streaming_hotkey.hotkey_entry), "");
- streaming_hotkey.keysym = 0;
- streaming_hotkey.modkey_mask = 0;
- }
- }
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND)
+ replace_grabbed_keys_depending_on_active_page();
return true;
}
static gboolean on_streaming_recording_replay_page_back_click(GtkButton*, gpointer userdata) {
- PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
- gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->common_settings_page);
+ PageNavigationUserdata *_page_navigation_userdata = (PageNavigationUserdata*)userdata;
+ gtk_stack_set_visible_child(_page_navigation_userdata->stack, _page_navigation_userdata->common_settings_page);
ungrab_keys(gdk_x11_get_default_xdisplay());
hotkey_mode = HotkeyMode::NoAction;
- app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::FRONT)));
+ app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(_page_navigation_userdata->app, SystrayPage::FRONT)));
return true;
}
@@ -1643,9 +1495,11 @@ static gboolean on_replay_file_chooser_button_click(GtkButton *button, gpointer)
return res;
}
-static bool kill_gpu_screen_recorder_get_result() {
+static bool kill_gpu_screen_recorder_get_result(bool *already_dead) {
+ *already_dead = true;
bool exit_success = true;
if(gpu_screen_recorder_process != -1) {
+ *already_dead = false;
int status;
int wait_result = waitpid(gpu_screen_recorder_process, &status, WNOHANG);
if(wait_result == -1) {
@@ -1668,35 +1522,55 @@ static bool kill_gpu_screen_recorder_get_result() {
return exit_success;
}
-static const gchar* audio_row_get_id(const AudioRow *audio_row) {
- const char *text = gtk_combo_box_text_get_active_text(audio_row->input_list);
- if(strcmp(text, "Default output") == 0 && !pa_default_sources.default_sink_name.empty())
- return pa_default_sources.default_sink_name.c_str();
- else if(strcmp(text, "Default input") == 0 && !pa_default_sources.default_source_name.empty())
- return pa_default_sources.default_source_name.c_str();
- else
- return gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_row->input_list));
-}
-
static void add_audio_command_line_args(std::vector<const char*> &args, std::string &merge_audio_tracks_arg_value) {
- pa_default_sources = get_pulseaudio_default_inputs();
-
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(merge_audio_tracks_button))) {
for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&merge_audio_tracks_arg_value](const AudioRow *audio_row) {
if(!merge_audio_tracks_arg_value.empty())
merge_audio_tracks_arg_value += '|';
- merge_audio_tracks_arg_value += audio_row_get_id(audio_row);
+ merge_audio_tracks_arg_value += gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_row->input_list));
});
if(!merge_audio_tracks_arg_value.empty())
args.insert(args.end(), { "-a", merge_audio_tracks_arg_value.c_str() });
} else {
for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&args](const AudioRow *audio_row) {
- args.insert(args.end(), { "-a", audio_row_get_id(audio_row) });
+ args.insert(args.end(), { "-a", gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_row->input_list)) });
});
}
}
+static void change_container_if_codec_not_supported(const std::string &video_codec, const gchar **container_str) {
+ if(strcmp(video_codec.c_str(), "vp8") == 0 || strcmp(video_codec.c_str(), "vp9") == 0) {
+ if(strcmp(*container_str, "webm") != 0 && strcmp(*container_str, "matroska") != 0) {
+ fprintf(stderr, "Warning: container '%s' is not compatible with video codec '%s', using webm container instead\n", *container_str, video_codec.c_str());
+ *container_str = "webm";
+ }
+ } else if(strcmp(*container_str, "webm") == 0) {
+ fprintf(stderr, "Warning: container webm is not compatible with video codec '%s', using mp4 container instead\n", video_codec.c_str());
+ *container_str = "mp4";
+ }
+}
+
+static bool switch_video_codec_to_usable_hardware_encoder(std::string &video_codec) {
+ if(gsr_info.supported_video_codecs.h264) {
+ video_codec = "h264";
+ return true;
+ } else if(gsr_info.supported_video_codecs.hevc) {
+ video_codec = "hevc";
+ return true;
+ } else if(gsr_info.supported_video_codecs.av1) {
+ video_codec = "av1";
+ return true;
+ } else if(gsr_info.supported_video_codecs.vp8) {
+ video_codec = "vp8";
+ return true;
+ } else if(gsr_info.supported_video_codecs.vp9) {
+ video_codec = "vp9";
+ return true;
+ }
+ return false;
+}
+
static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdata) {
GtkApplication *app = (GtkApplication*)userdata;
const gchar *dir = gtk_button_get_label(replay_file_chooser_button);
@@ -1704,7 +1578,8 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
int exit_status = prev_exit_status;
prev_exit_status = -1;
if(replaying) {
- bool exit_success = kill_gpu_screen_recorder_get_result();
+ bool already_dead = true;
+ bool exit_success = kill_gpu_screen_recorder_get_result(&already_dead);
gtk_button_set_label(button, "Start replay");
replaying = false;
@@ -1716,14 +1591,21 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_replay_menu_item), "Start replay");
gtk_widget_set_sensitive(save_replay_menu_item, false);
- app_indicator_set_icon_full(app_indicator, tray_idle_icon_name, "Idle");
+ app_indicator_set_icon_full(app_indicator, get_tray_idle_icon_name(), "Idle");
if(exit_status == 10) {
show_notification(app, "GPU Screen Recorder",
"You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT);
- } else if(!exit_success) {
+ } else if(exit_status == 50) {
+ show_notification(app, "GPU Screen Recorder", "Desktop portal capture failed. Either you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system", G_NOTIFICATION_PRIORITY_URGENT);
+ } else if(exit_status == 60) {
+ // Canceled by the user
+ } else if(!exit_success || (already_dead && exit_status != 0)) {
show_notification(app, "GPU Screen Recorder",
"Failed to start replay. Either your graphics card doesn't support GPU Screen Recorder with the settings you used or you don't have enough disk space to record a video", G_NOTIFICATION_PRIORITY_URGENT);
+ } else if(exit_success) {
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_stopped_notification_button)))
+ show_notification(app, "GPU Screen Recorder", "Stopped replay", G_NOTIFICATION_PRIORITY_NORMAL);
}
return true;
@@ -1733,8 +1615,8 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
int fps = gtk_spin_button_get_value_as_int(fps_entry);
int replay_time = gtk_spin_button_get_value_as_int(replay_time_entry);
- int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
- int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
+ int record_width = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
+ int record_height = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
char dir_tmp[PATH_MAX];
strcpy(dir_tmp, dir);
@@ -1761,16 +1643,30 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
const gchar* container_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(replay_container));
const gchar* color_range_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(color_range_input_menu));
const gchar* quality_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(quality_input_menu));
- const gchar* video_codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(video_codec_input_menu));
const gchar* audio_codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_codec_input_menu));
const gchar* framerate_mode_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(framerate_mode_input_menu));
const bool record_cursor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(record_cursor_button));
+ const bool restore_portal_session = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(restore_portal_session_button));
+
+ const char *encoder = "gpu";
+ std::string video_codec_input_str = video_codec_selection_menu_get_active_id();
+ if(video_codec_input_str == "h264_software") {
+ video_codec_input_str = "h264";
+ encoder = "cpu";
+ } else if(video_codec_input_str == "auto") {
+ if(!switch_video_codec_to_usable_hardware_encoder(video_codec_input_str)) {
+ video_codec_input_str = "h264";
+ encoder = "cpu";
+ }
+ }
+
+ change_container_if_codec_not_supported(video_codec_input_str, &container_str);
char area[64];
snprintf(area, sizeof(area), "%dx%d", record_width, record_height);
std::vector<const char*> args = {
- "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str, "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-cr", color_range_input_str, "-r", replay_time_str.c_str(), "-o", dir
+ "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str.c_str(), "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-restore-portal-session", restore_portal_session ? "yes" : "no", "-cr", color_range_input_str, "-r", replay_time_str.c_str(), "-encoder", encoder, "-o", dir
};
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overclock_button)))
@@ -1805,7 +1701,7 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
if(getppid() != parent_pid)
_exit(3);
-
+
execvp(args[0], (char* const*)args.data());
perror("failed to launch gpu-screen-recorder");
_exit(127);
@@ -1823,7 +1719,10 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_replay_menu_item), "Stop replay");
gtk_widget_set_sensitive(save_replay_menu_item, true);
- app_indicator_set_icon_full(app_indicator, tray_recording_icon_name, "Recording");
+ app_indicator_set_icon_full(app_indicator, get_tray_recording_icon_name(), "Recording");
+
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_started_notification_button)))
+ show_notification(app, "GPU Screen Recorder", "Started replay", G_NOTIFICATION_PRIORITY_NORMAL);
record_start_time_sec = clock_get_monotonic_seconds();
return true;
@@ -1835,12 +1734,15 @@ static gboolean on_replay_save_button_click(GtkButton*, gpointer userdata) {
GtkApplication *app = (GtkApplication*)userdata;
kill(gpu_screen_recorder_process, SIGUSR1);
- if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_notification_button)))
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_saved_notification_button)))
show_notification(app, "GPU Screen Recorder", "Saved replay", G_NOTIFICATION_PRIORITY_NORMAL);
return true;
}
static gboolean on_pause_unpause_button_click(GtkButton*, gpointer) {
+ if(!recording)
+ return true;
+
if(gpu_screen_recorder_process == -1)
return true;
@@ -1850,13 +1752,13 @@ static gboolean on_pause_unpause_button_click(GtkButton*, gpointer) {
gtk_button_set_label(pause_recording_button, "Unpause recording");
gtk_image_set_from_icon_name(GTK_IMAGE(recording_record_icon), "media-playback-pause", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_menu_item_set_label(GTK_MENU_ITEM(pause_recording_menu_item), "Unpause recording");
- app_indicator_set_icon_full(app_indicator, tray_paused_icon_name, "Paused");
+ app_indicator_set_icon_full(app_indicator, get_tray_paused_icon_name(), "Paused");
pause_start_sec = clock_get_monotonic_seconds();
} else {
gtk_button_set_label(pause_recording_button, "Pause recording");
gtk_image_set_from_icon_name(GTK_IMAGE(recording_record_icon), "media-record", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_menu_item_set_label(GTK_MENU_ITEM(pause_recording_menu_item), "Pause recording");
- app_indicator_set_icon_full(app_indicator, tray_recording_icon_name, "Recording");
+ app_indicator_set_icon_full(app_indicator, get_tray_recording_icon_name(), "Recording");
paused_time_offset_sec += (clock_get_monotonic_seconds() - pause_start_sec);
}
return true;
@@ -1869,7 +1771,8 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
int exit_status = prev_exit_status;
prev_exit_status = -1;
if(recording) {
- bool exit_success = kill_gpu_screen_recorder_get_result();
+ bool already_dead = true;
+ bool exit_success = kill_gpu_screen_recorder_get_result(&already_dead);
gtk_button_set_label(button, "Start recording");
recording = false;
@@ -1886,18 +1789,22 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_recording_menu_item), "Start recording");
gtk_menu_item_set_label(GTK_MENU_ITEM(pause_recording_menu_item), "Pause recording");
gtk_widget_set_sensitive(pause_recording_menu_item, false);
- app_indicator_set_icon_full(app_indicator, tray_idle_icon_name, "Idle");
+ app_indicator_set_icon_full(app_indicator, get_tray_idle_icon_name(), "Idle");
if(exit_status == 10) {
show_notification(app, "GPU Screen Recorder",
"You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT);
+ } else if(exit_status == 50) {
+ show_notification(app, "GPU Screen Recorder", "Desktop portal capture failed. Either you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system", G_NOTIFICATION_PRIORITY_URGENT);
+ } else if(exit_status == 60) {
+ // Canceled by the user
+ } else if(!exit_success || (already_dead && exit_status != 0)) {
+ show_notification(app, "GPU Screen Recorder", "Failed to save video. Either your graphics card doesn't support GPU Screen Recorder with the settings you used or you don't have enough disk space to record a video. Run GPU Screen Recorder from the terminal to see more information when this failure happens", G_NOTIFICATION_PRIORITY_URGENT);
} else if(exit_success) {
- if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_notification_button))) {
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_saved_notification_button))) {
const std::string notification_body = std::string("The recording was saved to ") + record_file_current_filename;
show_notification(app, "GPU Screen Recorder", notification_body.c_str(), G_NOTIFICATION_PRIORITY_NORMAL);
}
- } else {
- show_notification(app, "GPU Screen Recorder", "Failed to save video. Either your graphics card doesn't support GPU Screen Recorder with the settings you used or you don't have enough disk space to record a video", G_NOTIFICATION_PRIORITY_URGENT);
}
return true;
}
@@ -1905,18 +1812,8 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
save_configs();
int fps = gtk_spin_button_get_value_as_int(fps_entry);
- int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
- int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
-
- char dir_tmp[PATH_MAX];
- strcpy(dir_tmp, dir);
- if(create_directory_recursive(dir_tmp) != 0) {
- std::string notification_body = std::string("Failed to start recording. Failed to create ") + dir_tmp;
- show_notification(app, "GPU Screen Recorder", notification_body.c_str(), G_NOTIFICATION_PRIORITY_URGENT);
- return true;
- }
-
- record_file_current_filename = std::string(dir_tmp) + "/Video_" + get_date_str() + "." + gtk_combo_box_text_get_active_text(record_container);
+ int record_width = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
+ int record_height = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
bool follow_focused = false;
std::string window_str = record_area_selection_menu_get_active_id();
@@ -1933,18 +1830,43 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
std::string fps_str = std::to_string(fps);
const gchar* container_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_container));
+ const gchar* container_name = gtk_combo_box_text_get_active_text(record_container);
const gchar* color_range_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(color_range_input_menu));
const gchar* quality_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(quality_input_menu));
- const gchar* video_codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(video_codec_input_menu));
const gchar* audio_codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_codec_input_menu));
const gchar* framerate_mode_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(framerate_mode_input_menu));
const bool record_cursor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(record_cursor_button));
+ const bool restore_portal_session = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(restore_portal_session_button));
+
+ const char *encoder = "gpu";
+ std::string video_codec_input_str = video_codec_selection_menu_get_active_id();
+ if(video_codec_input_str == "h264_software") {
+ video_codec_input_str = "h264";
+ encoder = "cpu";
+ } else if(video_codec_input_str == "auto") {
+ if(!switch_video_codec_to_usable_hardware_encoder(video_codec_input_str)) {
+ video_codec_input_str = "h264";
+ encoder = "cpu";
+ }
+ }
+
+ change_container_if_codec_not_supported(video_codec_input_str, &container_str);
+
+ char dir_tmp[PATH_MAX];
+ strcpy(dir_tmp, dir);
+ if(create_directory_recursive(dir_tmp) != 0) {
+ std::string notification_body = std::string("Failed to start recording. Failed to create ") + dir_tmp;
+ show_notification(app, "GPU Screen Recorder", notification_body.c_str(), G_NOTIFICATION_PRIORITY_URGENT);
+ return true;
+ }
+
+ record_file_current_filename = std::string(dir_tmp) + "/Video_" + get_date_str() + "." + container_name;
char area[64];
snprintf(area, sizeof(area), "%dx%d", record_width, record_height);
std::vector<const char*> args = {
- "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str, "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-cr", color_range_input_str, "-o", record_file_current_filename.c_str()
+ "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str.c_str(), "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-restore-portal-session", restore_portal_session ? "yes" : "no", "-cr", color_range_input_str, "-encoder", encoder, "-o", record_file_current_filename.c_str()
};
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overclock_button)))
@@ -1979,7 +1901,7 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
if(getppid() != parent_pid)
_exit(3);
-
+
execvp(args[0], (char* const*)args.data());
perror("failed to launch gpu-screen-recorder");
_exit(127);
@@ -1996,7 +1918,10 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_recording_menu_item), "Stop recording");
gtk_widget_set_sensitive(pause_recording_menu_item, true);
- app_indicator_set_icon_full(app_indicator, tray_recording_icon_name, "recording");
+ app_indicator_set_icon_full(app_indicator, get_tray_recording_icon_name(), "Recording");
+
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_started_notification_button)))
+ show_notification(app, "GPU Screen Recorder", "Started recording", G_NOTIFICATION_PRIORITY_NORMAL);
record_start_time_sec = clock_get_monotonic_seconds();
paused_time_offset_sec = 0.0;
@@ -2009,7 +1934,8 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
int exit_status = prev_exit_status;
prev_exit_status = -1;
if(streaming) {
- bool exit_success = kill_gpu_screen_recorder_get_result();
+ bool already_dead = true;
+ bool exit_success = kill_gpu_screen_recorder_get_result(&already_dead);
gtk_button_set_label(button, "Start streaming");
streaming = false;
@@ -2019,15 +1945,20 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
gtk_label_set_text(GTK_LABEL(streaming_record_time_label), "00:00:00");
gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_streaming_menu_item), "Start streaming");
- app_indicator_set_icon_full(app_indicator, tray_idle_icon_name, "Idle");
+ app_indicator_set_icon_full(app_indicator, get_tray_idle_icon_name(), "Idle");
if(exit_status == 10) {
show_notification(app, "GPU Screen Recorder",
"You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT);
- } else if(exit_success) {
- show_notification(app, "GPU Screen Recorder", "Stopped streaming", G_NOTIFICATION_PRIORITY_NORMAL);
- } else {
+ } else if(exit_status == 50) {
+ show_notification(app, "GPU Screen Recorder", "Desktop portal capture failed. Either you canceled the desktop portal or your Wayland compositor doesn't support desktop portal capture or it's incorrectly setup on your system", G_NOTIFICATION_PRIORITY_URGENT);
+ } else if(exit_status == 60) {
+ // Canceled by the user
+ } else if(!exit_success || (already_dead && exit_status != 0)) {
show_notification(app, "GPU Screen Recorder", "Failed to stream video. There is either an error in your streaming config or your graphics card doesn't support GPU Screen Recorder with the settings you used", G_NOTIFICATION_PRIORITY_URGENT);
+ } else if(exit_success) {
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_stopped_notification_button)))
+ show_notification(app, "GPU Screen Recorder", "Stopped streaming", G_NOTIFICATION_PRIORITY_NORMAL);
}
return true;
@@ -2036,8 +1967,8 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
save_configs();
int fps = gtk_spin_button_get_value_as_int(fps_entry);
- int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
- int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
+ int record_width = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_width_entry);
+ int record_height = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_height_entry);
bool follow_focused = false;
std::string window_str = record_area_selection_menu_get_active_id();
@@ -2086,16 +2017,30 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
const gchar* quality_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(quality_input_menu));
const gchar* color_range_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(color_range_input_menu));
- const gchar* video_codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(video_codec_input_menu));
const gchar* audio_codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(audio_codec_input_menu));
const gchar* framerate_mode_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(framerate_mode_input_menu));
const bool record_cursor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(record_cursor_button));
+ const bool restore_portal_session = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(restore_portal_session_button));
+
+ const char *encoder = "gpu";
+ std::string video_codec_input_str = video_codec_selection_menu_get_active_id();
+ if(video_codec_input_str == "h264_software") {
+ video_codec_input_str = "h264";
+ encoder = "cpu";
+ } else if(video_codec_input_str == "auto") {
+ if(!switch_video_codec_to_usable_hardware_encoder(video_codec_input_str)) {
+ video_codec_input_str = "h264";
+ encoder = "cpu";
+ }
+ }
+
+ change_container_if_codec_not_supported(video_codec_input_str, &container_str);
char area[64];
snprintf(area, sizeof(area), "%dx%d", record_width, record_height);
std::vector<const char*> args = {
- "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str, "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-cr", color_range_input_str, "-o", stream_url.c_str()
+ "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", video_codec_input_str.c_str(), "-ac", audio_codec_input_str, "-f", fps_str.c_str(), "-cursor", record_cursor ? "yes" : "no", "-restore-portal-session", restore_portal_session ? "yes" : "no", "-cr", color_range_input_str, "-encoder", encoder, "-o", stream_url.c_str()
};
if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overclock_button)))
@@ -2146,7 +2091,10 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user
gtk_widget_set_opacity(GTK_WIDGET(streaming_bottom_panel_grid), 1.0);
gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_streaming_menu_item), "Stop streaming");
- app_indicator_set_icon_full(app_indicator, tray_recording_icon_name, "Recording");
+ app_indicator_set_icon_full(app_indicator, get_tray_recording_icon_name(), "Recording");
+
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_recording_started_notification_button)))
+ show_notification(app, "GPU Screen Recorder", "Started streaming", G_NOTIFICATION_PRIORITY_NORMAL);
record_start_time_sec = clock_get_monotonic_seconds();
return true;
@@ -2181,11 +2129,12 @@ static void gtk_widget_set_margin(GtkWidget *widget, int top, int bottom, int le
static void record_area_item_change_callback(GtkComboBox *widget, gpointer userdata) {
(void)widget;
- GtkWidget *select_window_button = (GtkWidget*)userdata;
+ (void)userdata;
const std::string selected_window_area = record_area_selection_menu_get_active_id();
- gtk_widget_set_visible(select_window_button, strcmp(selected_window_area.c_str(), "window") == 0);
+ gtk_widget_set_visible(GTK_WIDGET(select_window_button), strcmp(selected_window_area.c_str(), "window") == 0);
gtk_widget_set_visible(GTK_WIDGET(area_size_label), strcmp(selected_window_area.c_str(), "focused") == 0);
gtk_widget_set_visible(GTK_WIDGET(area_size_grid), strcmp(selected_window_area.c_str(), "focused") == 0);
+ gtk_widget_set_visible(GTK_WIDGET(restore_portal_session_button), strcmp(selected_window_area.c_str(), "portal") == 0);
enable_stream_record_button_if_info_filled();
}
@@ -2197,8 +2146,10 @@ static void view_combo_box_change_callback(GtkComboBox *widget, gpointer userdat
gtk_widget_set_visible(GTK_WIDGET(video_codec_grid), advanced_view);
gtk_widget_set_visible(GTK_WIDGET(audio_codec_grid), advanced_view);
gtk_widget_set_visible(GTK_WIDGET(framerate_mode_grid), advanced_view);
- gtk_widget_set_visible(GTK_WIDGET(overclock_grid), advanced_view && gpu_inf.vendor == GPU_VENDOR_NVIDIA && !wayland);
- gtk_widget_set_visible(GTK_WIDGET(show_notification_button), advanced_view);
+ gtk_widget_set_visible(GTK_WIDGET(overclock_grid), advanced_view && gsr_info.gpu_info.vendor == GpuVendor::NVIDIA && gsr_info.system_info.display_server != DisplayServer::WAYLAND);
+ gtk_widget_set_visible(GTK_WIDGET(show_recording_started_notification_button), advanced_view);
+ gtk_widget_set_visible(GTK_WIDGET(show_recording_stopped_notification_button), advanced_view);
+ gtk_widget_set_visible(GTK_WIDGET(show_recording_saved_notification_button), advanced_view);
}
static void stream_service_item_change_callback(GtkComboBox *widget, gpointer userdata) {
@@ -2250,26 +2201,22 @@ static bool is_cuda_installed() {
return lib != nullptr;
}
-typedef gboolean (*KeyPressHandler)(GtkButton *button, gpointer userdata);
-static void keypress_toggle_recording(bool recording_state, GtkButton *record_button, KeyPressHandler keypress_handler, GtkApplication *app) {
- if(!gtk_widget_get_sensitive(GTK_WIDGET(record_button)))
- return;
-
- if(!recording_state) {
- keypress_handler(record_button, app);
- } else if(recording_state) {
- keypress_handler(record_button, app);
+static bool is_hotkey_already_bound_to_another_action_on_same_page(const Hotkey *_current_hotkey, const Hotkey new_hotkey, GtkWidget *page) {
+ for(int i = 0; i < num_hotkeys; ++i) {
+ if(hotkeys[i] != _current_hotkey && hotkeys[i]->page == page && hotkeys[i]->keysym == new_hotkey.keysym && hotkeys[i]->modkey_mask == new_hotkey.modkey_mask)
+ return true;
}
+ return false;
}
static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpointer userdata) {
- if(wayland)
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
return GDK_FILTER_CONTINUE;
if(hotkey_mode == HotkeyMode::NoAction)
return GDK_FILTER_CONTINUE;
- PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
+ PageNavigationUserdata *_page_navigation_userdata = (PageNavigationUserdata*)userdata;
XEvent *ev = (XEvent*)xevent;
if(ev->type != KeyPress && ev->type != KeyRelease)
return GDK_FILTER_CONTINUE;
@@ -2278,23 +2225,13 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi
KeySym key_sym = XLookupKeysym(&ev->xkey, 0);
if(hotkey_mode == HotkeyMode::Record && ev->type == KeyRelease) {
- const GtkWidget *visible_page = gtk_stack_get_visible_child(page_navigation_userdata->stack);
- if(visible_page == page_navigation_userdata->recording_page) {
- if(key_sym == record_hotkey.keysym && key_state_without_locks(ev->xkey.state) == key_mod_mask_to_x11_mask(record_hotkey.modkey_mask)) {
- keypress_toggle_recording(recording, start_recording_button, on_start_recording_button_click, page_navigation_userdata->app);
- } else if(key_sym == pause_unpause_hotkey.keysym && key_state_without_locks(ev->xkey.state) == key_mod_mask_to_x11_mask(pause_unpause_hotkey.modkey_mask) && recording) {
- on_pause_unpause_button_click(nullptr, page_navigation_userdata->app);
- }
- } else if(visible_page == page_navigation_userdata->streaming_page) {
- if(key_sym == streaming_hotkey.keysym && key_state_without_locks(ev->xkey.state) == key_mod_mask_to_x11_mask(streaming_hotkey.modkey_mask)) {
- keypress_toggle_recording(streaming, start_streaming_button, on_start_streaming_button_click, page_navigation_userdata->app);
- }
- } else if(visible_page == page_navigation_userdata->replay_page) {
- if(key_sym == replay_start_stop_hotkey.keysym && key_state_without_locks(ev->xkey.state) == key_mod_mask_to_x11_mask(replay_start_stop_hotkey.modkey_mask)) {
- keypress_toggle_recording(replaying, start_replay_button, on_start_replay_button_click, page_navigation_userdata->app);
- } else if(key_sym == replay_save_hotkey.keysym && key_state_without_locks(ev->xkey.state) == key_mod_mask_to_x11_mask(replay_save_hotkey.modkey_mask) && replaying && gpu_screen_recorder_process != -1) {
- on_replay_save_button_click(nullptr, page_navigation_userdata->app);
- }
+ const GtkWidget *visible_page = gtk_stack_get_visible_child(_page_navigation_userdata->stack);
+ for(int i = 0; i < num_hotkeys; ++i) {
+ if(visible_page != hotkeys[i]->page)
+ continue;
+
+ if(key_sym == hotkeys[i]->keysym && key_state_without_locks(ev->xkey.state) == key_mod_mask_to_x11_mask(hotkeys[i]->modkey_mask))
+ hotkeys[i]->trigger_handler(hotkeys[i]->associated_button, _page_navigation_userdata->app);
}
return GDK_FILTER_CONTINUE;
}
@@ -2314,7 +2251,9 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi
if(ev->type == KeyPress && key_sym == XK_BackSpace) {
if(pressed_hotkey.modkey_mask == None && pressed_hotkey.keysym == None) {
ungrab_keyboard(display);
+ grab_ungrab_hotkey_combo(gdk_x11_get_default_xdisplay(), *current_hotkey, false);
gtk_entry_set_text(GTK_ENTRY(current_hotkey->hotkey_entry), "");
+ current_hotkey->grab_success = true;
current_hotkey->keysym = None;
current_hotkey->modkey_mask = 0;
current_hotkey = nullptr;
@@ -2339,7 +2278,7 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi
latest_hotkey = pressed_hotkey;
set_hotkey_text_from_hotkey_data(GTK_ENTRY(current_hotkey->hotkey_entry), latest_hotkey);
}
-
+
if(ev->type == KeyRelease) {
if(key_is_modifier(key_sym)) {
pressed_hotkey.modkey_mask &= ~modkey_to_mask(key_sym);
@@ -2354,57 +2293,43 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi
if(pressed_hotkey.modkey_mask == None && pressed_hotkey.keysym == None) {
ungrab_keyboard(display);
- ungrab_keys(gdk_x11_get_default_xdisplay());
- bool hotkey_already_used_by_another_hotkey = false;
- if(current_hotkey == &replay_start_stop_hotkey)
- hotkey_already_used_by_another_hotkey = (latest_hotkey.keysym == replay_save_hotkey.keysym && latest_hotkey.modkey_mask == replay_save_hotkey.modkey_mask);
- else if(current_hotkey == &replay_save_hotkey)
- hotkey_already_used_by_another_hotkey = (latest_hotkey.keysym == replay_start_stop_hotkey.keysym && latest_hotkey.modkey_mask == replay_start_stop_hotkey.modkey_mask);
-
- if(hotkey_already_used_by_another_hotkey) {
- std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(current_hotkey->hotkey_entry));
- std::string error_text = "Hotkey " + hotkey_text + " can't be used because it's used for something else. Please choose another hotkey.";
+ if(is_hotkey_already_bound_to_another_action_on_same_page(current_hotkey, latest_hotkey, gtk_stack_get_visible_child(_page_navigation_userdata->stack))) {
+ const std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(current_hotkey->hotkey_entry));
+ const std::string error_text = "Hotkey " + hotkey_text + " can't be used because it's used for something else. Please choose another hotkey.";
set_hotkey_text_from_hotkey_data(GTK_ENTRY(current_hotkey->hotkey_entry), *current_hotkey);
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", error_text.c_str());
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
current_hotkey = nullptr;
+ hotkey_mode = HotkeyMode::Record;
return GDK_FILTER_CONTINUE;
}
- Hotkey prev_current_hotkey = *current_hotkey;
+ const Hotkey prev_current_hotkey = *current_hotkey;
current_hotkey->keysym = latest_hotkey.keysym;
current_hotkey->modkey_mask = latest_hotkey.modkey_mask;
- HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
- bool hotkey_success = false;
- if(current_hotkey == &record_hotkey)
- hotkey_success = hotkey_result.record_hotkey_success;
- else if(current_hotkey == &pause_unpause_hotkey)
- hotkey_success = hotkey_result.pause_unpause_hotkey_success;
- else if(current_hotkey == &streaming_hotkey)
- hotkey_success = hotkey_result.streaming_hotkey_success;
- else if(current_hotkey == &replay_start_stop_hotkey)
- hotkey_success = hotkey_result.replay_start_stop_hotkey_success;
- else if(current_hotkey == &replay_save_hotkey)
- hotkey_success = hotkey_result.replay_save_hotkey_success;
-
- if(hotkey_success) {
+ const std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(current_hotkey->hotkey_entry));
+ if(replace_grabbed_keys_depending_on_active_page()) {
save_configs();
+ current_hotkey = nullptr;
+ hotkey_mode = HotkeyMode::Record;
+ return GDK_FILTER_CONTINUE;
} else {
- *current_hotkey = prev_current_hotkey;
- std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(current_hotkey->hotkey_entry));
- std::string error_text = "Hotkey " + hotkey_text + " can't be used because it's used by another program. Please choose another hotkey.";
+ const std::string error_text = "Hotkey " + hotkey_text + " can't be used because it's used by another program. Please choose another hotkey.";
+
+ current_hotkey->keysym = prev_current_hotkey.keysym;
+ current_hotkey->modkey_mask = prev_current_hotkey.modkey_mask;
set_hotkey_text_from_hotkey_data(GTK_ENTRY(current_hotkey->hotkey_entry), *current_hotkey);
+
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", error_text.c_str());
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
- }
- current_hotkey = nullptr;
- return GDK_FILTER_CONTINUE;
+ return GDK_FILTER_CONTINUE;
+ }
}
}
@@ -2412,35 +2337,30 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi
}
static gboolean on_hotkey_entry_click(GtkWidget *button, gpointer) {
- if(wayland)
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
return true;
- hotkey_mode = HotkeyMode::NewHotkey;
-
pressed_hotkey.hotkey_entry = nullptr;
pressed_hotkey.hotkey_active_label = nullptr;
pressed_hotkey.keysym = None;
pressed_hotkey.modkey_mask = 0;
latest_hotkey = pressed_hotkey;
- if(button == record_hotkey_button) {
- current_hotkey = &record_hotkey;
- } else if(button == pause_unpause_hotkey_button) {
- current_hotkey = &pause_unpause_hotkey;
- } else if(button == streaming_hotkey_button) {
- current_hotkey = &streaming_hotkey;
- } else if(button == replay_start_stop_hotkey_button) {
- current_hotkey = &replay_start_stop_hotkey;
- } else if(button == replay_save_hotkey_button) {
- current_hotkey = &replay_save_hotkey;
- } else {
- current_hotkey = nullptr;
+ current_hotkey = nullptr;
+ for(int i = 0; i < num_hotkeys; ++i) {
+ if(button == hotkeys[i]->hotkey_entry) {
+ current_hotkey = hotkeys[i];
+ break;
+ }
}
- if(current_hotkey) {
- gtk_grab_add(current_hotkey->hotkey_entry);
- gtk_widget_set_visible(current_hotkey->hotkey_active_label, true);
- }
+ if(!current_hotkey)
+ return true;
+
+ hotkey_mode = HotkeyMode::NewHotkey;
+
+ gtk_grab_add(current_hotkey->hotkey_entry);
+ gtk_widget_set_visible(current_hotkey->hotkey_active_label, true);
Display *display = gdk_x11_get_default_xdisplay();
XErrorHandler prev_error_handler = XSetErrorHandler(xerror_dummy);
@@ -2450,71 +2370,197 @@ static gboolean on_hotkey_entry_click(GtkWidget *button, gpointer) {
return true;
}
-static bool audio_inputs_contains(const std::vector<AudioInput> &audio_inputs, const std::string &audio_input_name) {
- for(auto &audio_input : audio_inputs) {
- if(audio_input.name == audio_input_name)
- return true;
+static void parse_system_info_line(GsrInfo *_gsr_info, const std::string &line) {
+ const size_t space_index = line.find('|');
+ if(space_index == std::string::npos)
+ return;
+
+ const StringView attribute_name = {line.c_str(), space_index};
+ const StringView attribute_value = {line.c_str() + space_index + 1, line.size() - (space_index + 1)};
+ if(attribute_name == "display_server") {
+ if(attribute_value == "x11")
+ _gsr_info->system_info.display_server = DisplayServer::X11;
+ else if(attribute_value == "wayland")
+ _gsr_info->system_info.display_server = DisplayServer::WAYLAND;
+ } else if(attribute_name == "is_steam_deck") {
+ _gsr_info->system_info.is_steam_deck = attribute_value == "yes";
}
- return false;
}
-static gsr_connection_type get_connection_type() {
- if(wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA) {
- return GSR_CONNECTION_DRM;
- } else {
- return GSR_CONNECTION_X11;
- }
+static void parse_gpu_info_line(GsrInfo *_gsr_info, const std::string &line) {
+ const size_t space_index = line.find('|');
+ if(space_index == std::string::npos)
+ return;
+
+ const StringView attribute_name = {line.c_str(), space_index};
+ const StringView attribute_value = {line.c_str() + space_index + 1, line.size() - (space_index + 1)};
+ if(attribute_name == "vendor") {
+ if(attribute_value == "amd")
+ _gsr_info->gpu_info.vendor = GpuVendor::AMD;
+ else if(attribute_value == "intel")
+ _gsr_info->gpu_info.vendor = GpuVendor::INTEL;
+ else if(attribute_value == "nvidia")
+ _gsr_info->gpu_info.vendor = GpuVendor::NVIDIA;
+ }
+}
+
+static void parse_video_codecs_line(GsrInfo *_gsr_info, const std::string &line) {
+ if(line == "h264")
+ _gsr_info->supported_video_codecs.h264 = true;
+ else if(line == "h264_software")
+ _gsr_info->supported_video_codecs.h264_software = true;
+ else if(line == "hevc")
+ _gsr_info->supported_video_codecs.hevc = true;
+ else if(line == "hevc_hdr")
+ _gsr_info->supported_video_codecs.hevc_hdr = true;
+ else if(line == "hevc_10bit")
+ _gsr_info->supported_video_codecs.hevc_10bit = true;
+ else if(line == "av1")
+ _gsr_info->supported_video_codecs.av1 = true;
+ else if(line == "av1_hdr")
+ _gsr_info->supported_video_codecs.av1_hdr = true;
+ else if(line == "av1_10bit")
+ _gsr_info->supported_video_codecs.av1_10bit = true;
+ else if(line == "vp8")
+ _gsr_info->supported_video_codecs.vp8 = true;
+ else if(line == "vp9")
+ _gsr_info->supported_video_codecs.vp9 = true;
+}
+
+static GsrMonitor capture_option_line_to_monitor(const std::string &line) {
+ size_t space_index = line.find('|');
+ if(space_index == std::string::npos)
+ return { line, {0, 0} };
+
+ vec2i size = {0, 0};
+ if(sscanf(line.c_str() + space_index + 1, "%dx%d", &size.x, &size.y) != 2)
+ size = {0, 0};
+
+ return { line.substr(0, space_index), size };
+}
+
+static void parse_capture_options_line(GsrInfo *_gsr_info, const std::string &line) {
+ if(line == "window")
+ _gsr_info->supported_capture_options.window = true;
+ else if(line == "focused")
+ _gsr_info->supported_capture_options.focused = true;
+ else if(line == "screen")
+ _gsr_info->supported_capture_options.screen = true;
+ else if(line == "portal")
+ _gsr_info->supported_capture_options.portal = true;
+ else
+ _gsr_info->supported_capture_options.monitors.push_back(capture_option_line_to_monitor(line));
+}
+
+enum class GsrInfoSection {
+ UNKNOWN,
+ SYSTEM_INFO,
+ GPU_INFO,
+ VIDEO_CODECS,
+ CAPTURE_OPTIONS
+};
+
+static bool starts_with(const std::string &str, const char *substr) {
+ size_t len = strlen(substr);
+ return str.size() >= len && memcmp(str.data(), substr, len) == 0;
}
-// Returns the exit status
-static int get_supported_video_codecs(SupportedVideoCodecs *supported_video_codecs) {
- supported_video_codecs->h264 = false;
- supported_video_codecs->hevc = false;
- supported_video_codecs->av1 = false;
+static GsrInfoExitStatus get_gpu_screen_recorder_info(GsrInfo *_gsr_info) {
+ *_gsr_info = GsrInfo{};
- FILE *f = popen("gpu-screen-recorder --list-supported-video-codecs", "r");
+ FILE *f = popen("gpu-screen-recorder --info", "r");
if(!f) {
- fprintf(stderr, "error: 'gpu-screen-recorder --list-supported-video-codecs' failed\n");
- return -1;
+ fprintf(stderr, "error: 'gpu-screen-recorder --info' failed\n");
+ return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND;
}
- char output[1024];
+ char output[8192];
ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f);
if(bytes_read < 0 || ferror(f)) {
- fprintf(stderr, "error: failed to read 'gpu-screen-recorder --list-supported-video-codecs' output\n");
+ fprintf(stderr, "error: failed to read 'gpu-screen-recorder --info' output\n");
pclose(f);
- return -1;
+ return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND;
}
output[bytes_read] = '\0';
- if(strstr(output, "h264"))
- supported_video_codecs->h264 = true;
- if(strstr(output, "hevc"))
- supported_video_codecs->hevc = true;
- if(strstr(output, "av1"))
- supported_video_codecs->av1 = true;
+ GsrInfoSection section = GsrInfoSection::UNKNOWN;
+ string_split_char(output, '\n', [&](StringView line) {
+ const std::string line_str(line.str, line.size);
+
+ if(starts_with(line_str, "section=")) {
+ const char *section_name = line_str.c_str() + 8;
+ if(strcmp(section_name, "system_info") == 0)
+ section = GsrInfoSection::SYSTEM_INFO;
+ else if(strcmp(section_name, "gpu_info") == 0)
+ section = GsrInfoSection::GPU_INFO;
+ else if(strcmp(section_name, "video_codecs") == 0)
+ section = GsrInfoSection::VIDEO_CODECS;
+ else if(strcmp(section_name, "capture_options") == 0)
+ section = GsrInfoSection::CAPTURE_OPTIONS;
+ else
+ section = GsrInfoSection::UNKNOWN;
+ return true;
+ }
+
+ switch(section) {
+ case GsrInfoSection::UNKNOWN: {
+ break;
+ }
+ case GsrInfoSection::SYSTEM_INFO: {
+ parse_system_info_line(_gsr_info, line_str);
+ break;
+ }
+ case GsrInfoSection::GPU_INFO: {
+ parse_gpu_info_line(_gsr_info, line_str);
+ break;
+ }
+ case GsrInfoSection::VIDEO_CODECS: {
+ parse_video_codecs_line(_gsr_info, line_str);
+ break;
+ }
+ case GsrInfoSection::CAPTURE_OPTIONS: {
+ parse_capture_options_line(_gsr_info, line_str);
+ break;
+ }
+ }
+
+ return true;
+ });
int status = pclose(f);
- if(WIFEXITED(status))
- return WEXITSTATUS(status);
- return 0;
+ if(WIFEXITED(status)) {
+ switch(WEXITSTATUS(status)) {
+ case 0: return GsrInfoExitStatus::OK;
+ case 22: return GsrInfoExitStatus::OPENGL_FAILED;
+ case 23: return GsrInfoExitStatus::NO_DRM_CARD;
+ default: return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND;
+ }
+ }
+
+ return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND;
}
static void record_area_set_sensitive(GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) {
(void)cell_layout;
(void)data;
- if(wayland) {
- gchar *id;
- gtk_tree_model_get(tree_model, iter, 1, &id, -1);
- gboolean sensitive = g_strcmp0("window", id) != 0 && g_strcmp0("focused", id) != 0;
- g_free(id);
- g_object_set(cell, "sensitive", sensitive, NULL);
- } else {
- g_object_set(cell, "sensitive", true, NULL);
- }
+
+ gchar *id;
+ gtk_tree_model_get(tree_model, iter, 1, &id, -1);
+ g_object_set(cell, "sensitive", is_video_capture_option_enabled(id), NULL);
+ g_free(id);
}
-static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *app, const gpu_info &gpu_inf) {
+static void video_codec_set_sensitive(GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) {
+ (void)cell_layout;
+ (void)data;
+
+ gchar *id;
+ gtk_tree_model_get(tree_model, iter, 1, &id, -1);
+ g_object_set(cell, "sensitive", is_video_codec_enabled(id), NULL);
+ g_free(id);
+}
+
+static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *app) {
GtkGrid *grid = GTK_GRID(gtk_grid_new());
gtk_stack_add_named(stack, GTK_WIDGET(grid), "common-settings");
gtk_widget_set_vexpand(GTK_WIDGET(grid), true);
@@ -2553,59 +2599,55 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
GtkTreeIter iter;
record_area_selection_model = GTK_TREE_MODEL(store);
- if(wayland) {
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
gtk_list_store_append(store, &iter);
- gtk_list_store_set(store, &iter, 0, "Window (Unavailable on Wayland)", -1);
+ gtk_list_store_set(store, &iter, 0, "Window (Not available on Wayland)", -1);
gtk_list_store_set(store, &iter, 1, "window", -1);
gtk_list_store_append(store, &iter);
- gtk_list_store_set(store, &iter, 0, "Follow focused window (Unavailable on Wayland)", -1);
+ gtk_list_store_set(store, &iter, 0, "Follow focused window (Not available on Wayland)", -1);
gtk_list_store_set(store, &iter, 1, "focused", -1);
} else {
gtk_list_store_append(store, &iter);
- gtk_list_store_set(store, &iter, 0, "Window", -1, "window", -1);
+ gtk_list_store_set(store, &iter, 0, "Window", -1);
gtk_list_store_set(store, &iter, 1, "window", -1);
gtk_list_store_append(store, &iter);
- gtk_list_store_set(store, &iter, 0, "Follow focused window", -1, "focused", -1);
+ gtk_list_store_set(store, &iter, 0, "Follow focused window", -1);
gtk_list_store_set(store, &iter, 1, "focused", -1);
}
- const bool allow_screen_capture = wayland || nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA;
+ const bool allow_screen_capture = is_monitor_capture_drm() || nvfbc_installed;
if(allow_screen_capture) {
- if(!wayland && gpu_inf.vendor == GPU_VENDOR_NVIDIA) {
+ if(gsr_info.supported_capture_options.screen) {
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, 0, "All monitors", -1);
gtk_list_store_set(store, &iter, 1, "screen", -1);
}
- const gsr_connection_type connection_type = get_connection_type();
- int num_monitors = 0;
- for_each_active_monitor_output(&egl, connection_type, [&](const gsr_monitor *monitor, void*) {
+ for(const auto &monitor : gsr_info.supported_capture_options.monitors) {
std::string label = "Monitor ";
- label.append(monitor->name, monitor->name_len);
+ label += monitor.name;
label += " (";
- label += std::to_string(monitor->size.x);
+ label += std::to_string(monitor.size.x);
label += "x";
- label += std::to_string(monitor->size.y);
- if(flatpak && (wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA)) {
+ label += std::to_string(monitor.size.y);
+ if(flatpak && is_monitor_capture_drm()) {
label += ", requires root access";
}
label += ")";
// Leak on purpose, what are you gonna do? stab me?
- char *id = (char*)malloc(monitor->name_len + 1);
- memcpy(id, monitor->name, monitor->name_len);
- id[monitor->name_len] = '\0';
+ char *id = (char*)malloc(monitor.name.size() + 1);
+ memcpy(id, monitor.name.c_str(), monitor.name.size());
+ id[monitor.name.size()] = '\0';
gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, 0, label.c_str(), -1);
gtk_list_store_set(store, &iter, 1, id, -1);
+ }
- ++num_monitors;
- }, NULL);
-
- if(num_monitors == 0 && (wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA)) {
+ if(gsr_info.supported_capture_options.monitors.empty() && gsr_info.system_info.display_server == DisplayServer::WAYLAND && !gsr_info.supported_capture_options.portal) {
GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"No monitors to record found. Make sure GPU Screen Recorder is running on the same GPU device that is displaying graphics on the screen.");
gtk_dialog_run(GTK_DIALOG(dialog));
@@ -2615,6 +2657,16 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
}
}
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_capture_options.portal ? "Desktop portal (HDR not supported)" : "Desktop portal (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "portal", -1);
+ } else {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "Desktop portal (Not available on X11)", -1);
+ gtk_list_store_set(store, &iter, 1, "portal", -1);
+ }
+
record_area_selection_menu = GTK_COMBO_BOX(gtk_combo_box_new_with_model(record_area_selection_model));
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
@@ -2622,38 +2674,41 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(record_area_selection_menu), renderer, "text", 0, NULL);
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(record_area_selection_menu), renderer, record_area_set_sensitive, NULL, NULL);
- gtk_combo_box_set_active(record_area_selection_menu, allow_screen_capture ? 2 : 0);
+ gtk_combo_box_set_active(record_area_selection_menu, (allow_screen_capture || gsr_info.supported_capture_options.portal) ? 2 : 0);
gtk_widget_set_hexpand(GTK_WIDGET(record_area_selection_menu), true);
gtk_grid_attach(record_area_grid, GTK_WIDGET(record_area_selection_menu), 0, record_area_row++, 3, 1);
- if(!wayland) {
- select_window_button = GTK_BUTTON(gtk_button_new_with_label("Select window..."));
- gtk_widget_set_hexpand(GTK_WIDGET(select_window_button), true);
- g_signal_connect(select_window_button, "clicked", G_CALLBACK(on_select_window_button_click), app);
- gtk_grid_attach(record_area_grid, GTK_WIDGET(select_window_button), 0, record_area_row++, 3, 1);
+ g_signal_connect(record_area_selection_menu, "changed", G_CALLBACK(record_area_item_change_callback), NULL);
- g_signal_connect(record_area_selection_menu, "changed", G_CALLBACK(record_area_item_change_callback), select_window_button);
+ select_window_button = GTK_BUTTON(gtk_button_new_with_label("Select window..."));
+ gtk_widget_set_hexpand(GTK_WIDGET(select_window_button), true);
+ g_signal_connect(select_window_button, "clicked", G_CALLBACK(on_select_window_button_click), app);
+ gtk_grid_attach(record_area_grid, GTK_WIDGET(select_window_button), 0, record_area_row++, 3, 1);
- area_size_label = GTK_LABEL(gtk_label_new("Area size: "));
- gtk_label_set_xalign(area_size_label, 0.0f);
- gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_label), 0, record_area_row++, 2, 1);
+ area_size_label = GTK_LABEL(gtk_label_new("Area size: "));
+ gtk_label_set_xalign(area_size_label, 0.0f);
+ gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_label), 0, record_area_row++, 2, 1);
- area_size_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_grid), 0, record_area_row++, 3, 1);
+ area_size_grid = GTK_GRID(gtk_grid_new());
+ gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_grid), 0, record_area_row++, 3, 1);
- area_width_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0));
- gtk_spin_button_set_value(area_width_entry, 1920.0);
- gtk_widget_set_hexpand(GTK_WIDGET(area_width_entry), true);
- gtk_grid_attach(area_size_grid, GTK_WIDGET(area_width_entry), 0, 0, 1, 1);
+ area_width_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0));
+ gtk_spin_button_set_value(area_width_entry, 1920.0);
+ gtk_widget_set_hexpand(GTK_WIDGET(area_width_entry), true);
+ gtk_grid_attach(area_size_grid, GTK_WIDGET(area_width_entry), 0, 0, 1, 1);
- gtk_grid_attach(area_size_grid, gtk_label_new("x"), 1, 0, 1, 1);
+ gtk_grid_attach(area_size_grid, gtk_label_new("x"), 1, 0, 1, 1);
- area_height_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0));
- gtk_spin_button_set_value(area_height_entry, 1080.0);
- gtk_widget_set_hexpand(GTK_WIDGET(area_height_entry), true);
- gtk_grid_attach(area_size_grid, GTK_WIDGET(area_height_entry), 2, 0, 1, 1);
- }
+ area_height_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0));
+ gtk_spin_button_set_value(area_height_entry, 1080.0);
+ gtk_widget_set_hexpand(GTK_WIDGET(area_height_entry), true);
+ gtk_grid_attach(area_size_grid, GTK_WIDGET(area_height_entry), 2, 0, 1, 1);
+
+ restore_portal_session_button = gtk_check_button_new_with_label("Restore portal session");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(restore_portal_session_button), true);
+ gtk_widget_set_halign(restore_portal_session_button, GTK_ALIGN_START);
+ gtk_grid_attach(record_area_grid, restore_portal_session_button, 0, record_area_row++, 3, 1);
GtkFrame *audio_input_frame = GTK_FRAME(gtk_frame_new("Audio"));
gtk_grid_attach(grid, GTK_WIDGET(audio_input_frame), 0, grid_row++, 2, 1);
@@ -2671,25 +2726,6 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_grid_set_column_spacing(add_audio_grid, 10);
gtk_grid_attach(audio_grid, GTK_WIDGET(add_audio_grid), 0, audio_input_area_row++, 1, 1);
- // TODO:
- //const PulseAudioServerInfo pa_server_info = get_pulseaudio_default_inputs();
-
- // if(!pa_server_info.default_sink_name.empty() && audio_inputs_contains(audio_inputs, pa_server_info.default_sink_name)) {
- // gtk_combo_box_text_append(audio_input_menu_todo, pa_server_info.default_sink_name.c_str(), "Default output");
- // ++num_audio_inputs_addable;
- // }
-
- // if(!pa_server_info.default_source_name.empty() && audio_inputs_contains(audio_inputs, pa_server_info.default_source_name)) {
- // gtk_combo_box_text_append(audio_input_menu_todo, pa_server_info.default_source_name.c_str(), "Default input");
- // ++num_audio_inputs_addable;
- // }
-
- // for(const AudioInput &audio_input : audio_inputs) {
- // std::string text = audio_input.description;
- // gtk_combo_box_text_append(audio_input_menu_todo, audio_input.name.c_str(), audio_input.description.c_str());
- // ++num_audio_inputs_addable;
- // }
-
add_audio_input_button = gtk_button_new_with_label("Add audio track");
gtk_grid_attach(add_audio_grid, add_audio_input_button, 0, 0, 1, 1);
g_signal_connect(add_audio_input_button, "clicked", G_CALLBACK(+[](GtkButton*, gpointer){
@@ -2742,29 +2778,77 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
video_codec_grid = GTK_GRID(gtk_grid_new());
gtk_grid_attach(grid, GTK_WIDGET(video_codec_grid), 0, grid_row++, 2, 1);
gtk_grid_attach(video_codec_grid, gtk_label_new("Video codec: "), 0, 0, 1, 1);
- video_codec_input_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
- gtk_combo_box_text_append(video_codec_input_menu, "auto", "Auto (Recommended)");
- if(supported_video_codecs_exit_status == 0) {
- if(supported_video_codecs.h264)
- gtk_combo_box_text_append(video_codec_input_menu, "h264", "H264");
- if(supported_video_codecs.hevc)
- gtk_combo_box_text_append(video_codec_input_menu, "hevc", "HEVC");
- if(supported_video_codecs.av1)
- gtk_combo_box_text_append(video_codec_input_menu, "av1", "AV1");
-
- if(wayland) {
- if(supported_video_codecs.hevc)
- gtk_combo_box_text_append(video_codec_input_menu, "hevc_hdr", "HEVC (HDR)");
- if(supported_video_codecs.av1)
- gtk_combo_box_text_append(video_codec_input_menu, "av1_hdr", "AV1 (HDR)");
+
+ {
+ store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+ video_codec_selection_model = GTK_TREE_MODEL(store);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "Auto (Recommended, H264)", -1);
+ gtk_list_store_set(store, &iter, 1, "auto", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.h264 ? "H264 (Largest file size, best software compatibility)" : "H264 (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "h264", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.hevc ? "HEVC" : "HEVC (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "hevc", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.av1 ? "AV1 (Smallest file size, worst software compatibility)" : "AV1 (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "av1", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.vp8 ? "VP8" : "VP8 (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "vp8", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.vp9 ? "VP9" : "VP9 (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "vp9", -1);
+
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.hevc ? "HEVC (HDR)" : "HEVC (HDR, not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "hevc_hdr", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.av1 ? "AV1 (HDR)" : "AV1 (HDR, not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "av1_hdr", -1);
+ } else {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "HEVC (HDR, not available on X11)", -1);
+ gtk_list_store_set(store, &iter, 1, "hevc_hdr", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "AV1 (HDR, not available on X11)", -1);
+ gtk_list_store_set(store, &iter, 1, "av1_hdr", -1);
}
- } else {
- gtk_combo_box_text_append(video_codec_input_menu, "h264", "H264");
- gtk_combo_box_text_append(video_codec_input_menu, "hevc", "HEVC");
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.hevc ? "HEVC (10 bit, reduces banding)" : "HEVC (10 bit, not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "hevc_10bit", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.av1 ? "AV1 (10 bit, reduces banding)" : "AV1 (10 bit, not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "av1_10bit", -1);
+
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, gsr_info.supported_video_codecs.h264_software ? "H264 Software Encoder (Slow, not recommeded)" : "H264 Software Encoder (Not available on your system)", -1);
+ gtk_list_store_set(store, &iter, 1, "h264_software", -1);
+
+ video_codec_selection_menu = GTK_COMBO_BOX(gtk_combo_box_new_with_model(video_codec_selection_model));
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(video_codec_selection_menu), renderer, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(video_codec_selection_menu), renderer, "text", 0, NULL);
+ gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(video_codec_selection_menu), renderer, video_codec_set_sensitive, NULL, NULL);
+
+ gtk_combo_box_set_active(video_codec_selection_menu, 0);
+
+ gtk_widget_set_hexpand(GTK_WIDGET(video_codec_selection_menu), true);
+ gtk_grid_attach(video_codec_grid, GTK_WIDGET(video_codec_selection_menu), 1, 0, 1, 1);
}
- gtk_widget_set_hexpand(GTK_WIDGET(video_codec_input_menu), true);
- gtk_grid_attach(video_codec_grid, GTK_WIDGET(video_codec_input_menu), 1, 0, 1, 1);
- gtk_combo_box_set_active(GTK_COMBO_BOX(video_codec_input_menu), 0);
audio_codec_grid = GTK_GRID(gtk_grid_new());
gtk_grid_attach(grid, GTK_WIDGET(audio_codec_grid), 0, grid_row++, 2, 1);
@@ -2814,16 +2898,26 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
return true;
}), nullptr);
- show_notification_button = gtk_check_button_new_with_label("Show video saved notification");
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_notification_button), true);
- gtk_widget_set_halign(show_notification_button, GTK_ALIGN_START);
- gtk_grid_attach(grid, show_notification_button, 0, grid_row++, 2, 1);
-
record_cursor_button = gtk_check_button_new_with_label("Record cursor");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(record_cursor_button), true);
gtk_widget_set_halign(record_cursor_button, GTK_ALIGN_START);
gtk_grid_attach(grid, record_cursor_button, 0, grid_row++, 2, 1);
+ show_recording_started_notification_button = gtk_check_button_new_with_label("Show recording/streaming/replay started notification");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_recording_started_notification_button), false);
+ gtk_widget_set_halign(show_recording_started_notification_button, GTK_ALIGN_START);
+ gtk_grid_attach(grid, show_recording_started_notification_button, 0, grid_row++, 2, 1);
+
+ show_recording_stopped_notification_button = gtk_check_button_new_with_label("Show streaming/replay stopped notification");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_recording_stopped_notification_button), false);
+ gtk_widget_set_halign(show_recording_stopped_notification_button, GTK_ALIGN_START);
+ gtk_grid_attach(grid, show_recording_stopped_notification_button, 0, grid_row++, 2, 1);
+
+ show_recording_saved_notification_button = gtk_check_button_new_with_label("Show video saved notification");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_recording_saved_notification_button), true);
+ gtk_widget_set_halign(show_recording_saved_notification_button, GTK_ALIGN_START);
+ gtk_grid_attach(grid, show_recording_saved_notification_button, 0, grid_row++, 2, 1);
+
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, grid_row++, 2, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
@@ -2859,39 +2953,132 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
return GTK_WIDGET(grid);
}
+static void replace_meta_with_super(std::string &str) {
+ size_t index = str.find("meta");
+ if(index != std::string::npos)
+ str.replace(index, 4, "Super");
+
+ index = str.find("Meta");
+ if(index != std::string::npos)
+ str.replace(index, 4, "Super");
+}
+
+static void shortcut_changed_callback(gsr_shortcut shortcut, void *userdata) {
+ (void)userdata;
+ std::string trigger = shortcut.trigger_description;
+ replace_meta_with_super(trigger);
+ for(int i = 0; i < num_hotkeys; ++i) {
+ if(strcmp(shortcut.id, hotkeys[i]->shortcut_id) == 0) {
+ gtk_entry_set_text(GTK_ENTRY(hotkeys[i]->hotkey_entry), trigger.c_str());
+ }
+ }
+}
+
+static void deactivated_callback(const char *description, void *userdata) {
+ (void)userdata;
+ const GtkWidget *visible_page = gtk_stack_get_visible_child(page_navigation_userdata.stack);
+ for(int i = 0; i < num_hotkeys; ++i) {
+ if(visible_page != hotkeys[i]->page)
+ continue;
+
+ if(strcmp(description, hotkeys[i]->shortcut_id) == 0)
+ hotkeys[i]->trigger_handler(hotkeys[i]->associated_button, page_navigation_userdata.app);
+ }
+}
+
+static gboolean on_register_hotkeys_button_clicked(GtkButton *button, gpointer userdata) {
+ (void)button;
+ (void)userdata;
+
+ /*
+ Modifier key names are defined here: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-names.h.
+ Remove the XKB_MOD_NAME_ prefix from the name and use the remaining part.
+ Key names are defined here: https://github.com/xkbcommon/libxkbcommon/blob/master/include/xkbcommon/xkbcommon-keysyms.h.
+ Remove the XKB_KEY_ (or XKB_KEY_KP_) prefix from the name and user the remaining part.
+ */
+ /* LOGO = Super key */
+ /* Unfortunately global shortcuts cant handle same key for different shortcuts, even though GPU Screen Recorder has page specific hotkeys */
+ const gsr_bind_shortcut shortcuts[3] = {
+ {
+ "Start/stop recording/replay/streaming",
+ { SHORTCUT_ID_START_STOP_RECORDING, "LOGO+f1" }
+ },
+ {
+ "Pause/unpause recording",
+ { SHORTCUT_ID_PAUSE_UNPAUSE_RECORDING, "LOGO+f2" }
+ },
+ {
+ "Save replay",
+ { SHORTCUT_ID_SAVE_REPLAY, "LOGO+f3" }
+ }
+ };
+
+ if(global_shortcuts_initialized) {
+ if(!gsr_global_shortcuts_bind_shortcuts(&global_shortcuts, shortcuts, 3, shortcut_changed_callback, NULL)) {
+ fprintf(stderr, "gsr error: failed to bind shortcuts\n");
+ }
+ }
+
+ return true;
+}
+
static void add_wayland_global_hotkeys_ui(GtkGrid *grid, int &row, int width) {
- GtkWidget *label = gtk_label_new("Hotkeys not supported because Wayland doesn't support global hotkeys");
- gtk_widget_set_hexpand(label, true);
- gtk_grid_attach(grid, label, 0, row, width - 1, 1);
+ GtkGrid *aa_grid = GTK_GRID(gtk_grid_new());
+ gtk_widget_set_halign(GTK_WIDGET(aa_grid), GTK_ALIGN_CENTER);
+ gtk_grid_attach(grid, GTK_WIDGET(aa_grid), 0, row++, width, 1);
+ gtk_grid_set_column_spacing(aa_grid, 10);
+
+ gtk_grid_attach(aa_grid, gtk_label_new("On Wayland hotkeys are managed externally by the Wayland compositor, click here to change hotkeys:"), 0, 0, 1, 1);
- GtkButton *question_button = GTK_BUTTON(gtk_button_new_with_label("?"));
- gtk_grid_attach(grid, GTK_WIDGET(question_button), width - 1, row, 1, 1);
+ GtkButton *register_hotkeys_button = GTK_BUTTON(gtk_button_new_with_label("Change hotkeys"));
+ gtk_widget_set_hexpand(GTK_WIDGET(register_hotkeys_button), true);
+ //gtk_widget_set_halign(GTK_WIDGET(register_hotkeys_button), GTK_ALIGN_START);
+ g_signal_connect(register_hotkeys_button, "clicked", G_CALLBACK(on_register_hotkeys_button_clicked), nullptr);
+ gtk_grid_attach(aa_grid, GTK_WIDGET(register_hotkeys_button), 1, 0, 1, 1);
row++;
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row, width, 1);
+ row++;
+}
- g_signal_connect(question_button, "clicked", G_CALLBACK(+[](GtkButton *button, gpointer userdata){
- (void)button;
- (void)userdata;
- GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Wayland (desktop portal) doesn't support global hotkeys in any meaningful manner to most applications, including GPU Screen Recorder.\n"
- "If you want to use global hotkeys in GPU Screen Recorder then either use X11 or bind the following commands in your Wayland desktop environment hotkey settings to keys:\n"
- "Stop recording (saves video as well when not in replay mode):\n"
- " killall -SIGINT gpu-screen-recorder\n"
- "Save a replay:\n"
- " killall -SIGUSR1 gpu-screen-recorder\n"
- "Pause/unpause recording:\n"
- " killall -SIGUSR2 gpu-screen-recorder\n");
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
+static void create_replay_hotkey_items(GtkGrid *parent_grid, int row, int num_columns) {
+ replay_hotkeys_grid = GTK_GRID(gtk_grid_new());
+ gtk_widget_set_halign(GTK_WIDGET(replay_hotkeys_grid), GTK_ALIGN_START);
+ gtk_grid_set_row_spacing(replay_hotkeys_grid, 10);
+ gtk_grid_set_column_spacing(replay_hotkeys_grid, 10);
+ gtk_grid_attach(parent_grid, GTK_WIDGET(replay_hotkeys_grid), 0, row, num_columns, 1);
+ int hotkeys_row = 0;
- return true;
- }), nullptr);
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
+ add_wayland_global_hotkeys_ui(replay_hotkeys_grid, hotkeys_row, num_columns);
+
+ {
+ gtk_grid_attach(replay_hotkeys_grid, gtk_label_new("Press"), 0, hotkeys_row, 1, 1);
+
+ replay_start_stop_hotkey_button = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey_button), gsr_info.system_info.display_server == DisplayServer::WAYLAND ? "" : "Super + F1");
+ g_signal_connect(replay_start_stop_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_start_stop_hotkey_button);
+ gtk_grid_attach(replay_hotkeys_grid, replay_start_stop_hotkey_button, 1, hotkeys_row, 1, 1);
+
+ gtk_grid_attach(replay_hotkeys_grid, gtk_label_new("to start/stop the replay and"), 2, hotkeys_row, 1, 1);
+
+ replay_save_hotkey_button = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey_button), gsr_info.system_info.display_server == DisplayServer::WAYLAND ? "" : "Super + F2");
+ g_signal_connect(replay_save_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_save_hotkey_button);
+ gtk_grid_attach(replay_hotkeys_grid, replay_save_hotkey_button, 3, hotkeys_row, 1, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, width, 1);
+ GtkWidget *save_replay_label = gtk_label_new("to save the replay");
+ gtk_widget_set_halign(save_replay_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(save_replay_label, true);
+ gtk_grid_attach(replay_hotkeys_grid, save_replay_label, 4, hotkeys_row, 1, 1);
+
+ ++hotkeys_row;
+ }
}
static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
int row = 0;
+ const int num_columns = 5;
std::string video_filepath = get_videos_dir();
@@ -2903,54 +3090,29 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_set_column_spacing(grid, 10);
gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10);
- GtkWidget *hotkey_active_label = NULL;
- if(wayland) {
- add_wayland_global_hotkeys_ui(grid, row, 5);
+ GtkWidget *hotkey_active_label = nullptr;
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ replay_hotkeys_not_supported_label = gtk_label_new("Your Wayland compositor doesn't support global hotkeys. Use X11 or KDE Plasma on Wayland if you want to use hotkeys.");
+ gtk_grid_attach(grid, replay_hotkeys_not_supported_label, 0, row++, num_columns, 1);
} else {
- hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel");
- gtk_grid_attach(grid, hotkey_active_label, 0, row++, 5, 1);
-
- GtkWidget *a = gtk_label_new("Press");
- gtk_widget_set_halign(a, GTK_ALIGN_END);
-
- GtkWidget *b = gtk_label_new("to start/stop the replay and");
- gtk_widget_set_halign(b, GTK_ALIGN_START);
-
- GtkWidget *c = gtk_label_new("to save");
- gtk_widget_set_halign(c, GTK_ALIGN_START);
+ hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel.");
+ gtk_grid_attach(grid, hotkey_active_label, 0, row++, num_columns, 1);
+ }
- replay_start_stop_hotkey_button = gtk_entry_new();
- gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey_button), "Alt + F1");
- g_signal_connect(replay_start_stop_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_start_stop_hotkey_button);
+ create_replay_hotkey_items(grid, row, num_columns);
+ ++row;
- replay_save_hotkey_button = gtk_entry_new();
- gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey_button), "Alt + F2");
- gtk_widget_set_halign(replay_save_hotkey_button, GTK_ALIGN_START);
- g_signal_connect(replay_save_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_save_hotkey_button);
-
- gtk_grid_attach(grid, a, 0, row, 1, 1);
- gtk_grid_attach(grid, replay_start_stop_hotkey_button, 1, row, 1, 1);
- gtk_grid_attach(grid, b, 2, row, 1, 1);
- gtk_grid_attach(grid, replay_save_hotkey_button, 3, row, 1, 1);
- gtk_grid_attach(grid, c, 4, row, 1, 1);
- ++row;
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 5, 1);
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ gtk_widget_set_visible(GTK_WIDGET(replay_hotkeys_grid), false);
+ gtk_widget_set_visible(GTK_WIDGET(replay_hotkeys_not_supported_label), false);
}
- replay_start_stop_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
- replay_start_stop_hotkey.keysym = XK_F1;
- replay_start_stop_hotkey.hotkey_entry = replay_start_stop_hotkey_button;
- replay_start_stop_hotkey.hotkey_active_label = hotkey_active_label;
-
- replay_save_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
- replay_save_hotkey.keysym = XK_F2;
- replay_save_hotkey.hotkey_entry = replay_save_hotkey_button;
- replay_save_hotkey.hotkey_active_label = hotkey_active_label;
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, num_columns, 1);
GtkWidget *save_icon = gtk_image_new_from_icon_name("document-save", GTK_ICON_SIZE_BUTTON);
GtkGrid *file_chooser_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(file_chooser_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(file_chooser_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(file_chooser_grid, 10);
GtkWidget *file_chooser_label = gtk_label_new("Where do you want to save the replays?");
gtk_grid_attach(file_chooser_grid, file_chooser_label, 0, 0, 1, 1);
@@ -2963,18 +3125,21 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(file_chooser_grid, GTK_WIDGET(replay_file_chooser_button), 1, 0, 1, 1);
GtkGrid *container_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(container_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(container_grid), 0, row++, num_columns, 1);
gtk_grid_attach(container_grid, gtk_label_new("Container: "), 0, 0, 1, 1);
replay_container = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
for(auto &supported_container : supported_containers) {
gtk_combo_box_text_append(replay_container, supported_container.container_name, supported_container.file_extension);
}
+ if(gsr_info.supported_video_codecs.vp8 || gsr_info.supported_video_codecs.vp9) {
+ gtk_combo_box_text_append(replay_container, "webm", "webm");
+ }
gtk_widget_set_hexpand(GTK_WIDGET(replay_container), true);
gtk_grid_attach(container_grid, GTK_WIDGET(replay_container), 1, 0, 1, 1);
- gtk_combo_box_set_active(GTK_COMBO_BOX(replay_container), 0); // TODO:
+ gtk_combo_box_set_active(GTK_COMBO_BOX(replay_container), 0);
GtkGrid *replay_time_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(replay_time_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(replay_time_grid), 0, row++, num_columns, 1);
gtk_grid_attach(replay_time_grid, gtk_label_new("Replay time in seconds: "), 0, 0, 1, 1);
replay_time_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 1200.0, 1.0));
gtk_spin_button_set_value(replay_time_entry, 30.0);
@@ -2982,7 +3147,7 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(replay_time_grid, GTK_WIDGET(replay_time_entry), 1, 0, 1, 1);
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
replay_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
@@ -3004,10 +3169,10 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
gtk_widget_set_sensitive(GTK_WIDGET(replay_save_button), false);
gtk_grid_attach(start_button_grid, GTK_WIDGET(replay_save_button), 2, 0, 1, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 5, 1);
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, num_columns, 1);
replay_bottom_panel_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(replay_bottom_panel_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(replay_bottom_panel_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(replay_bottom_panel_grid, 5);
gtk_widget_set_opacity(GTK_WIDGET(replay_bottom_panel_grid), 0.5);
gtk_widget_set_halign(GTK_WIDGET(replay_bottom_panel_grid), GTK_ALIGN_END);
@@ -3020,11 +3185,67 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
gtk_widget_set_valign(replay_record_time_label, GTK_ALIGN_CENTER);
gtk_grid_attach(replay_bottom_panel_grid, replay_record_time_label, 1, 0, 1, 1);
+ replay_start_stop_hotkey.modkey_mask = modkey_to_mask(XK_Super_L);
+ replay_start_stop_hotkey.keysym = XK_F1;
+ replay_start_stop_hotkey.hotkey_entry = replay_start_stop_hotkey_button;
+ replay_start_stop_hotkey.hotkey_active_label = hotkey_active_label;
+ replay_start_stop_hotkey.config = &config.replay_config.start_stop_recording_hotkey;
+ replay_start_stop_hotkey.page = GTK_WIDGET(grid);
+ replay_start_stop_hotkey.trigger_handler = on_start_replay_button_click;
+ replay_start_stop_hotkey.associated_button = start_replay_button;
+ replay_start_stop_hotkey.shortcut_id = SHORTCUT_ID_START_STOP_RECORDING;
+
+ replay_save_hotkey.modkey_mask = modkey_to_mask(XK_Super_L);
+ replay_save_hotkey.keysym = XK_F2;
+ replay_save_hotkey.hotkey_entry = replay_save_hotkey_button;
+ replay_save_hotkey.hotkey_active_label = hotkey_active_label;
+ replay_save_hotkey.config = &config.replay_config.save_recording_hotkey;
+ replay_save_hotkey.page = GTK_WIDGET(grid);
+ replay_save_hotkey.trigger_handler = on_replay_save_button_click;
+ replay_save_hotkey.associated_button = replay_save_button;
+ replay_save_hotkey.shortcut_id = SHORTCUT_ID_SAVE_REPLAY;
+
return GTK_WIDGET(grid);
}
+static void create_recording_hotkey_items(GtkGrid *parent_grid, int row, int num_columns) {
+ recording_hotkeys_grid = GTK_GRID(gtk_grid_new());
+ gtk_widget_set_halign(GTK_WIDGET(recording_hotkeys_grid), GTK_ALIGN_START);
+ gtk_grid_set_row_spacing(recording_hotkeys_grid, 10);
+ gtk_grid_set_column_spacing(recording_hotkeys_grid, 10);
+ gtk_grid_attach(parent_grid, GTK_WIDGET(recording_hotkeys_grid), 0, row, num_columns, 1);
+ int hotkeys_row = 0;
+
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
+ add_wayland_global_hotkeys_ui(recording_hotkeys_grid, hotkeys_row, num_columns);
+
+ {
+ gtk_grid_attach(recording_hotkeys_grid, gtk_label_new("Press"), 0, hotkeys_row, 1, 1);
+
+ record_start_stop_hotkey_button = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(record_start_stop_hotkey_button), gsr_info.system_info.display_server == DisplayServer::WAYLAND ? "" : "Super + F1");
+ g_signal_connect(record_start_stop_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), record_start_stop_hotkey_button);
+ gtk_grid_attach(recording_hotkeys_grid, record_start_stop_hotkey_button, 1, hotkeys_row, 1, 1);
+
+ gtk_grid_attach(recording_hotkeys_grid, gtk_label_new("to start/stop recording and"), 2, hotkeys_row, 1, 1);
+
+ pause_unpause_hotkey_button = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(pause_unpause_hotkey_button), gsr_info.system_info.display_server == DisplayServer::WAYLAND ? "" : "Super + F2");
+ g_signal_connect(pause_unpause_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), pause_unpause_hotkey_button);
+ gtk_grid_attach(recording_hotkeys_grid, pause_unpause_hotkey_button, 3, hotkeys_row, 1, 1);
+
+ GtkWidget *pause_unpause_recording_label = gtk_label_new("to pause/unpause recording");
+ gtk_widget_set_halign(pause_unpause_recording_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(pause_unpause_recording_label, true);
+ gtk_grid_attach(recording_hotkeys_grid, pause_unpause_recording_label, 4, hotkeys_row, 1, 1);
+
+ ++hotkeys_row;
+ }
+}
+
static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
int row = 0;
+ const int num_columns = 5;
std::string video_filepath = get_videos_dir();
@@ -3036,54 +3257,29 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_set_column_spacing(grid, 10);
gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10);
- GtkWidget *hotkey_active_label = NULL;
- if(wayland) {
- add_wayland_global_hotkeys_ui(grid, row, 5);
+ GtkWidget *hotkey_active_label = nullptr;
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ recording_hotkeys_not_supported_label = gtk_label_new("Your Wayland compositor doesn't support global hotkeys. Use X11 or KDE Plasma on Wayland if you want to use hotkeys.");
+ gtk_grid_attach(grid, recording_hotkeys_not_supported_label, 0, row++, num_columns, 1);
} else {
- hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel");
- gtk_grid_attach(grid, hotkey_active_label, 0, row++, 5, 1);
-
- GtkWidget *a = gtk_label_new("Press");
- gtk_widget_set_halign(a, GTK_ALIGN_END);
-
- GtkWidget *b = gtk_label_new("to start/stop recording and");
- gtk_widget_set_halign(b, GTK_ALIGN_START);
-
- GtkWidget *c = gtk_label_new("to pause/unpause");
- gtk_widget_set_halign(c, GTK_ALIGN_START);
-
- record_hotkey_button = gtk_entry_new();
- gtk_entry_set_text(GTK_ENTRY(record_hotkey_button), "Alt + F1");
- g_signal_connect(record_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), record_hotkey_button);
+ hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel.");
+ gtk_grid_attach(grid, hotkey_active_label, 0, row++, num_columns, 1);
+ }
- pause_unpause_hotkey_button = gtk_entry_new();
- gtk_entry_set_text(GTK_ENTRY(pause_unpause_hotkey_button), "Alt + F2");
- gtk_widget_set_halign(pause_unpause_hotkey_button, GTK_ALIGN_START);
- g_signal_connect(pause_unpause_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), pause_unpause_hotkey_button);
+ create_recording_hotkey_items(grid, row, num_columns);
+ ++row;
- gtk_grid_attach(grid, a, 0, row, 1, 1);
- gtk_grid_attach(grid, record_hotkey_button, 1, row, 1, 1);
- gtk_grid_attach(grid, b, 2, row, 1, 1);
- gtk_grid_attach(grid, pause_unpause_hotkey_button, 3, row, 1, 1);
- gtk_grid_attach(grid, c, 4, row, 1, 1);
- ++row;
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 5, 1);
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ gtk_widget_set_visible(GTK_WIDGET(recording_hotkeys_grid), false);
+ gtk_widget_set_visible(GTK_WIDGET(recording_hotkeys_not_supported_label), false);
}
- record_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
- record_hotkey.keysym = XK_F1;
- record_hotkey.hotkey_entry = record_hotkey_button;
- record_hotkey.hotkey_active_label = hotkey_active_label;
-
- pause_unpause_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
- pause_unpause_hotkey.keysym = XK_F2;
- pause_unpause_hotkey.hotkey_entry = pause_unpause_hotkey_button;
- pause_unpause_hotkey.hotkey_active_label = hotkey_active_label;
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, num_columns, 1);
GtkWidget *save_icon = gtk_image_new_from_icon_name("document-save", GTK_ICON_SIZE_BUTTON);
GtkGrid *file_chooser_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(file_chooser_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(file_chooser_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(file_chooser_grid, 10);
GtkWidget *file_chooser_label = gtk_label_new("Where do you want to save the video?");
gtk_grid_attach(file_chooser_grid, GTK_WIDGET(file_chooser_label), 0, 0, 1, 1);
@@ -3096,18 +3292,21 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(file_chooser_grid, GTK_WIDGET(record_file_chooser_button), 1, 0, 1, 1);
GtkGrid *container_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(container_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(container_grid), 0, row++, num_columns, 1);
gtk_grid_attach(container_grid, gtk_label_new("Container: "), 0, 0, 1, 1);
record_container = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
for(auto &supported_container : supported_containers) {
gtk_combo_box_text_append(record_container, supported_container.container_name, supported_container.file_extension);
}
+ if(gsr_info.supported_video_codecs.vp8 || gsr_info.supported_video_codecs.vp9) {
+ gtk_combo_box_text_append(record_container, "webm", "webm");
+ }
gtk_widget_set_hexpand(GTK_WIDGET(record_container), true);
gtk_grid_attach(container_grid, GTK_WIDGET(record_container), 1, 0, 1, 1);
- gtk_combo_box_set_active(GTK_COMBO_BOX(record_container), 0); // TODO:
+ gtk_combo_box_set_active(GTK_COMBO_BOX(record_container), 0);
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
record_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
@@ -3129,10 +3328,10 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
gtk_widget_set_sensitive(GTK_WIDGET(pause_recording_button), false);
gtk_grid_attach(start_button_grid, GTK_WIDGET(pause_recording_button), 2, 0, 1, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 5, 1);
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, num_columns, 1);
recording_bottom_panel_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(recording_bottom_panel_grid), 0, row++, 5, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(recording_bottom_panel_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(recording_bottom_panel_grid, 5);
gtk_widget_set_opacity(GTK_WIDGET(recording_bottom_panel_grid), 0.5);
gtk_widget_set_halign(GTK_WIDGET(recording_bottom_panel_grid), GTK_ALIGN_END);
@@ -3145,11 +3344,60 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
gtk_widget_set_valign(recording_record_time_label, GTK_ALIGN_CENTER);
gtk_grid_attach(recording_bottom_panel_grid, recording_record_time_label, 1, 0, 1, 1);
+ record_start_stop_hotkey.modkey_mask = modkey_to_mask(XK_Super_L);
+ record_start_stop_hotkey.keysym = XK_F1;
+ record_start_stop_hotkey.hotkey_entry = record_start_stop_hotkey_button;
+ record_start_stop_hotkey.hotkey_active_label = hotkey_active_label;
+ record_start_stop_hotkey.config = &config.record_config.start_stop_recording_hotkey;
+ record_start_stop_hotkey.page = GTK_WIDGET(grid);
+ record_start_stop_hotkey.trigger_handler = on_start_recording_button_click;
+ record_start_stop_hotkey.associated_button = start_recording_button;
+ record_start_stop_hotkey.shortcut_id = SHORTCUT_ID_START_STOP_RECORDING;
+
+ pause_unpause_hotkey.modkey_mask = modkey_to_mask(XK_Super_L);
+ pause_unpause_hotkey.keysym = XK_F2;
+ pause_unpause_hotkey.hotkey_entry = pause_unpause_hotkey_button;
+ pause_unpause_hotkey.hotkey_active_label = hotkey_active_label;
+ pause_unpause_hotkey.config = &config.record_config.pause_unpause_recording_hotkey;
+ pause_unpause_hotkey.page = GTK_WIDGET(grid);
+ pause_unpause_hotkey.trigger_handler = on_pause_unpause_button_click;
+ pause_unpause_hotkey.associated_button = pause_recording_button;
+ pause_unpause_hotkey.shortcut_id = SHORTCUT_ID_PAUSE_UNPAUSE_RECORDING;
+
return GTK_WIDGET(grid);
}
+static void create_streaming_hotkey_items(GtkGrid *parent_grid, int row, int num_columns) {
+ streaming_hotkeys_grid = GTK_GRID(gtk_grid_new());
+ gtk_widget_set_halign(GTK_WIDGET(streaming_hotkeys_grid), GTK_ALIGN_START);
+ gtk_grid_set_row_spacing(streaming_hotkeys_grid, 10);
+ gtk_grid_set_column_spacing(streaming_hotkeys_grid, 10);
+ gtk_grid_attach(parent_grid, GTK_WIDGET(streaming_hotkeys_grid), 0, row, num_columns, 1);
+ int hotkeys_row = 0;
+
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND)
+ add_wayland_global_hotkeys_ui(streaming_hotkeys_grid, hotkeys_row, num_columns);
+
+ {
+ gtk_grid_attach(streaming_hotkeys_grid, gtk_label_new("Press"), 0, hotkeys_row, 1, 1);
+
+ streaming_start_stop_hotkey_button = gtk_entry_new();
+ gtk_entry_set_text(GTK_ENTRY(streaming_start_stop_hotkey_button), gsr_info.system_info.display_server == DisplayServer::WAYLAND ? "" : "Super + F1");
+ g_signal_connect(streaming_start_stop_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), streaming_start_stop_hotkey_button);
+ gtk_grid_attach(streaming_hotkeys_grid, streaming_start_stop_hotkey_button, 1, hotkeys_row, 1, 1);
+
+ GtkWidget *start_stop_streaming_label = gtk_label_new("to start/stop streaming");
+ gtk_widget_set_halign(start_stop_streaming_label, GTK_ALIGN_START);
+ gtk_widget_set_hexpand(start_stop_streaming_label, true);
+ gtk_grid_attach(streaming_hotkeys_grid, start_stop_streaming_label, 2, hotkeys_row, 1, 1);
+
+ ++hotkeys_row;
+ }
+}
+
static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
int row = 0;
+ const int num_columns = 3;
GtkGrid *grid = GTK_GRID(gtk_grid_new());
gtk_stack_add_named(stack, GTK_WIDGET(grid), "streaming");
@@ -3159,36 +3407,27 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_set_column_spacing(grid, 10);
gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10);
- GtkWidget *hotkey_active_label = NULL;
- if(wayland) {
- add_wayland_global_hotkeys_ui(grid, row, 3);
+ GtkWidget *hotkey_active_label = nullptr;
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ streaming_hotkeys_not_supported_label = gtk_label_new("Your Wayland compositor doesn't support global hotkeys. Use X11 or KDE Plasma on Wayland if you want to use hotkeys.");
+ gtk_grid_attach(grid, streaming_hotkeys_not_supported_label, 0, row++, num_columns, 1);
} else {
- hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel");
- gtk_grid_attach(grid, hotkey_active_label, 0, row++, 3, 1);
-
- GtkWidget *a = gtk_label_new("Press");
- gtk_widget_set_halign(a, GTK_ALIGN_END);
+ hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel.");
+ gtk_grid_attach(grid, hotkey_active_label, 0, row++, num_columns, 1);
+ }
- GtkWidget *b = gtk_label_new("to start/stop streaming");
- gtk_widget_set_halign(b, GTK_ALIGN_START);
+ create_streaming_hotkey_items(grid, row, num_columns);
+ ++row;
- streaming_hotkey_button = gtk_entry_new();
- gtk_entry_set_text(GTK_ENTRY(streaming_hotkey_button), "Alt + F1");
- g_signal_connect(streaming_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), streaming_hotkey_button);
- gtk_grid_attach(grid, a, 0, row, 1, 1);
- gtk_grid_attach(grid, streaming_hotkey_button, 1, row, 1, 1);
- gtk_grid_attach(grid, b, 2, row, 1, 1);
- ++row;
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 3, 1);
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ gtk_widget_set_visible(GTK_WIDGET(streaming_hotkeys_grid), false);
+ gtk_widget_set_visible(GTK_WIDGET(streaming_hotkeys_not_supported_label), false);
}
- streaming_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
- streaming_hotkey.keysym = XK_F1;
- streaming_hotkey.hotkey_entry = streaming_hotkey_button;
- streaming_hotkey.hotkey_active_label = hotkey_active_label;
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, num_columns, 1);
GtkGrid *stream_service_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(stream_service_grid), 0, row++, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(stream_service_grid), 0, row++, num_columns, 1);
gtk_grid_attach(stream_service_grid, gtk_label_new("Stream service: "), 0, 0, 1, 1);
stream_service_input_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
gtk_combo_box_text_append(stream_service_input_menu, "twitch", "Twitch");
@@ -3200,7 +3439,7 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(stream_service_grid, GTK_WIDGET(stream_service_input_menu), 1, 0, 1, 1);
GtkGrid *stream_id_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(stream_id_grid), 0, row++, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(stream_id_grid), 0, row++, num_columns, 1);
stream_key_label = GTK_LABEL(gtk_label_new("Stream key: "));
gtk_grid_attach(stream_id_grid, GTK_WIDGET(stream_key_label), 0, 0, 1, 1);
@@ -3218,18 +3457,21 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
}
custom_stream_container_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(custom_stream_container_grid), 0, row++, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(custom_stream_container_grid), 0, row++, num_columns, 1);
gtk_grid_attach(custom_stream_container_grid, gtk_label_new("Container: "), 0, 0, 1, 1);
custom_stream_container = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
for(auto &supported_container : supported_containers) {
gtk_combo_box_text_append(custom_stream_container, supported_container.container_name, supported_container.file_extension);
}
+ if(gsr_info.supported_video_codecs.vp8 || gsr_info.supported_video_codecs.vp9) {
+ gtk_combo_box_text_append(custom_stream_container, "webm", "webm");
+ }
gtk_widget_set_hexpand(GTK_WIDGET(custom_stream_container), true);
gtk_grid_attach(custom_stream_container_grid, GTK_WIDGET(custom_stream_container), 1, 0, 1, 1);
gtk_combo_box_set_active(GTK_COMBO_BOX(custom_stream_container), 1);
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
stream_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
@@ -3244,10 +3486,10 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
g_signal_connect(start_streaming_button, "clicked", G_CALLBACK(on_start_streaming_button_click), app);
gtk_grid_attach(start_button_grid, GTK_WIDGET(start_streaming_button), 1, 0, 1, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 3, 1);
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, num_columns, 1);
streaming_bottom_panel_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(streaming_bottom_panel_grid), 0, row++, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(streaming_bottom_panel_grid), 0, row++, num_columns, 1);
gtk_grid_set_column_spacing(streaming_bottom_panel_grid, 5);
gtk_widget_set_opacity(GTK_WIDGET(streaming_bottom_panel_grid), 0.5);
gtk_widget_set_halign(GTK_WIDGET(streaming_bottom_panel_grid), GTK_ALIGN_END);
@@ -3260,6 +3502,16 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
gtk_widget_set_valign(streaming_record_time_label, GTK_ALIGN_CENTER);
gtk_grid_attach(streaming_bottom_panel_grid, streaming_record_time_label, 1, 0, 1, 1);
+ streaming_start_stop_hotkey.modkey_mask = modkey_to_mask(XK_Super_L);
+ streaming_start_stop_hotkey.keysym = XK_F1;
+ streaming_start_stop_hotkey.hotkey_entry = streaming_start_stop_hotkey_button;
+ streaming_start_stop_hotkey.hotkey_active_label = hotkey_active_label;
+ streaming_start_stop_hotkey.config = &config.streaming_config.start_stop_recording_hotkey;
+ streaming_start_stop_hotkey.page = GTK_WIDGET(grid);
+ streaming_start_stop_hotkey.trigger_handler = on_start_streaming_button_click;
+ streaming_start_stop_hotkey.associated_button = start_streaming_button;
+ streaming_start_stop_hotkey.shortcut_id = SHORTCUT_ID_START_STOP_RECORDING;
+
return GTK_WIDGET(grid);
}
@@ -3363,38 +3615,35 @@ static void add_audio_input_track(const char *name) {
gtk_list_box_insert (GTK_LIST_BOX(audio_input_used_list), row, -1);
}
-static void load_config(const gpu_info &gpu_inf) {
+static void load_config() {
bool config_empty = false;
config = read_config(config_empty);
std::string first_monitor;
- if(!wayland && strcmp(config.main_config.record_area_option.c_str(), "window") == 0) {
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && strcmp(config.main_config.record_area_option.c_str(), "window") == 0) {
+ //
+ } else if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && strcmp(config.main_config.record_area_option.c_str(), "focused") == 0) {
//
- } else if(!wayland && strcmp(config.main_config.record_area_option.c_str(), "focused") == 0) {
+ } else if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && gsr_info.gpu_info.vendor == GpuVendor::NVIDIA && strcmp(config.main_config.record_area_option.c_str(), "screen") == 0) {
//
- } else if(!wayland && gpu_inf.vendor == GPU_VENDOR_NVIDIA && strcmp(config.main_config.record_area_option.c_str(), "screen") == 0) {
+ } else if(config.main_config.record_area_option == "portal" && gsr_info.supported_capture_options.portal && gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
//
} else {
- gsr_connection_type connection_type = get_connection_type();
-
bool found_monitor = false;
- int monitor_name_size = strlen(config.main_config.record_area_option.c_str());
- for_each_active_monitor_output(&egl, connection_type, [&](const gsr_monitor *monitor, void*) {
- if(first_monitor.empty()) {
- first_monitor.assign(monitor->name, monitor->name_len);
- }
+ for(const auto &monitor : gsr_info.supported_capture_options.monitors) {
+ if(first_monitor.empty())
+ first_monitor = monitor.name;
- if(monitor_name_size == monitor->name_len && strncmp(config.main_config.record_area_option.c_str(), monitor->name, monitor->name_len) == 0) {
+ if(config.main_config.record_area_option == monitor.name)
found_monitor = true;
- }
- }, NULL);
+ }
if(!found_monitor)
config.main_config.record_area_option.clear();
}
if(config.main_config.record_area_option.empty()) {
- const bool allow_screen_capture = wayland || nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA;
+ const bool allow_screen_capture = is_monitor_capture_drm() || nvfbc_installed;
if(allow_screen_capture) {
config.main_config.record_area_option = first_monitor;
} else {
@@ -3402,11 +3651,10 @@ static void load_config(const gpu_info &gpu_inf) {
}
}
- if(!wayland) {
- gtk_widget_set_visible(GTK_WIDGET(select_window_button), strcmp(config.main_config.record_area_option.c_str(), "window") == 0);
- gtk_widget_set_visible(GTK_WIDGET(area_size_label), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0);
- gtk_widget_set_visible(GTK_WIDGET(area_size_grid), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0);
- }
+ gtk_widget_set_visible(GTK_WIDGET(select_window_button), strcmp(config.main_config.record_area_option.c_str(), "window") == 0);
+ gtk_widget_set_visible(GTK_WIDGET(area_size_label), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0);
+ gtk_widget_set_visible(GTK_WIDGET(area_size_grid), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0);
+ gtk_widget_set_visible(GTK_WIDGET(restore_portal_session_button), strcmp(config.main_config.record_area_option.c_str(), "portal") == 0);
if(config.main_config.record_area_width == 0)
config.main_config.record_area_width = 1920;
@@ -3427,12 +3675,6 @@ static void load_config(const gpu_info &gpu_inf) {
if(config.main_config.quality != "medium" && config.main_config.quality != "high" && config.main_config.quality != "very_high" && config.main_config.quality != "ultra")
config.main_config.quality = "very_high";
- if(config.main_config.codec != "auto" && config.main_config.codec != "h264" && config.main_config.codec != "h265" && config.main_config.codec != "hevc" && config.main_config.codec != "av1" && config.main_config.codec != "hevc_hdr" && config.main_config.codec != "av1_hdr")
- config.main_config.codec = "auto";
-
- if(!wayland && (config.main_config.codec == "hevc_hdr" || config.main_config.codec == "av1_hdr"))
- config.main_config.codec = "auto";
-
if(config.main_config.audio_codec != "opus" && config.main_config.audio_codec != "aac")
config.main_config.audio_codec = "opus";
@@ -3454,7 +3696,7 @@ static void load_config(const gpu_info &gpu_inf) {
config.replay_config.replay_time = 1200;
record_area_selection_menu_set_active_id(config.main_config.record_area_option.c_str());
- if(!wayland) {
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) {
gtk_spin_button_set_value(area_width_entry, config.main_config.record_area_width);
gtk_spin_button_set_value(area_height_entry, config.main_config.record_area_height);
}
@@ -3470,229 +3712,166 @@ static void load_config(const gpu_info &gpu_inf) {
gtk_combo_box_set_active_id(GTK_COMBO_BOX(color_range_input_menu), config.main_config.color_range.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(quality_input_menu), config.main_config.quality.c_str());
- gtk_combo_box_set_active_id(GTK_COMBO_BOX(video_codec_input_menu), config.main_config.codec.c_str());
+ video_codec_selection_menu_set_active_id("auto");
+ video_codec_selection_menu_set_active_id(config.main_config.codec.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(audio_codec_input_menu), config.main_config.audio_codec.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(framerate_mode_input_menu), config.main_config.framerate_mode.c_str());
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(overclock_button), config.main_config.overclock);
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_notification_button), config.main_config.show_notifications);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_recording_started_notification_button), config.main_config.show_recording_started_notifications);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_recording_stopped_notification_button), config.main_config.show_recording_stopped_notifications);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_recording_saved_notification_button), config.main_config.show_recording_saved_notifications);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(record_cursor_button), config.main_config.record_cursor);
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item), config.main_config.hide_window_when_recording);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(restore_portal_session_button), config.main_config.restore_portal_session);
gtk_combo_box_set_active_id(GTK_COMBO_BOX(stream_service_input_menu), config.streaming_config.streaming_service.c_str());
gtk_entry_set_text(youtube_stream_id_entry, config.streaming_config.youtube.stream_key.c_str());
gtk_entry_set_text(twitch_stream_id_entry, config.streaming_config.twitch.stream_key.c_str());
gtk_entry_set_text(custom_stream_url_entry, config.streaming_config.custom.url.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(custom_stream_container), config.streaming_config.custom.container.c_str());
- if(!wayland && streaming_hotkey_button && !config_empty) {
- streaming_hotkey.keysym = config.streaming_config.start_recording_hotkey.keysym;
- streaming_hotkey.modkey_mask = config.streaming_config.start_recording_hotkey.modifiers;
- set_hotkey_text_from_hotkey_data(GTK_ENTRY(streaming_hotkey_button), streaming_hotkey);
- }
gtk_button_set_label(record_file_chooser_button, config.record_config.save_directory.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(record_container), config.record_config.container.c_str());
- if(!wayland && record_hotkey_button && !config_empty) {
- record_hotkey.keysym = config.record_config.start_recording_hotkey.keysym;
- record_hotkey.modkey_mask = config.record_config.start_recording_hotkey.modifiers;
- set_hotkey_text_from_hotkey_data(GTK_ENTRY(record_hotkey_button), record_hotkey);
- }
- if(!wayland && pause_unpause_hotkey_button && !config_empty) {
- pause_unpause_hotkey.keysym = config.record_config.pause_recording_hotkey.keysym;
- pause_unpause_hotkey.modkey_mask = config.record_config.pause_recording_hotkey.modifiers;
- set_hotkey_text_from_hotkey_data(GTK_ENTRY(pause_unpause_hotkey_button), pause_unpause_hotkey);
- }
gtk_button_set_label(replay_file_chooser_button, config.replay_config.save_directory.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(replay_container), config.replay_config.container.c_str());
gtk_spin_button_set_value(replay_time_entry, config.replay_config.replay_time);
- if(!wayland && replay_start_stop_hotkey_button && !config_empty) {
- replay_start_stop_hotkey.keysym = config.replay_config.start_recording_hotkey.keysym;
- replay_start_stop_hotkey.modkey_mask = config.replay_config.start_recording_hotkey.modifiers;
- set_hotkey_text_from_hotkey_data(GTK_ENTRY(replay_start_stop_hotkey_button), replay_start_stop_hotkey);
- }
- if(!wayland && replay_save_hotkey_button && !config_empty) {
- replay_save_hotkey.keysym = config.replay_config.save_recording_hotkey.keysym;
- replay_save_hotkey.modkey_mask = config.replay_config.save_recording_hotkey.modifiers;
- set_hotkey_text_from_hotkey_data(GTK_ENTRY(replay_save_hotkey_button), replay_save_hotkey);
- }
gtk_combo_box_set_active_id(GTK_COMBO_BOX(view_combo_box), config.main_config.advanced_view ? "advanced" : "simple");
view_combo_box_change_callback(GTK_COMBO_BOX(view_combo_box), view_combo_box);
- if(!wayland) {
- gtk_widget_set_visible(record_hotkey.hotkey_active_label, false);
- gtk_widget_set_visible(pause_unpause_hotkey.hotkey_active_label, false);
- gtk_widget_set_visible(streaming_hotkey.hotkey_active_label, false);
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) {
+ if(!config_empty) {
+ for(int i = 0; i < num_hotkeys; ++i) {
+ hotkeys[i]->keysym = hotkeys[i]->config->keysym;
+ hotkeys[i]->modkey_mask = hotkeys[i]->config->modifiers;
+ set_hotkey_text_from_hotkey_data(GTK_ENTRY(hotkeys[i]->hotkey_entry), *hotkeys[i]);
+ }
+ }
+
+ gtk_widget_set_visible(record_start_stop_hotkey.hotkey_active_label, false);
+ gtk_widget_set_visible(streaming_start_stop_hotkey.hotkey_active_label, false);
gtk_widget_set_visible(replay_start_stop_hotkey.hotkey_active_label, false);
- gtk_widget_set_visible(replay_save_hotkey.hotkey_active_label, false);
}
enable_stream_record_button_if_info_filled();
stream_service_item_change_callback(GTK_COMBO_BOX(stream_service_input_menu), nullptr);
- if(supported_video_codecs_exit_status != 0) {
- const char *cmd = flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4" : "gpu-screen-recorder -w screen -f 60 -o video.mp4";
- GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Failed to run 'gpu-screen-recorder' command. If you are using gpu-screen-recorder flatpak then this is a bug. Otherwise you need to make sure gpu-screen-recorder is installed on your system and working properly. Run:\n"
- "%s\n"
- "in a terminal to see more information about the issue.", cmd);
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- g_application_quit(G_APPLICATION(select_window_userdata.app));
- return;
- }
-
- if(!supported_video_codecs.h264 && !supported_video_codecs.hevc && gpu_inf.vendor != GPU_VENDOR_NVIDIA && config.main_config.codec != "av1") {
- if(supported_video_codecs.av1) {
- GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
- "Switched video codec to AV1 since H264/HEVC video encoding is either missing or disabled on your system. If you know that your system supports H264/HEVC video encoding and "
- "you are using the flatpak version of GPU Screen Recorder then try installing mesa-extra freedesktop runtime by running this command:\n"
- "flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n"
- "and then restart GPU Screen Recorder. If that doesn't work then you may have to install another mesa package for your distro.\n"
- "If you are using a distro such as manjaro which disables hardware accelerated video encoding then you can also try the <a href=\"https://flathub.org/apps/com.dec05eba.gpu_screen_recorder\">flatpak version of GPU Screen Recorder</a> instead which doesn't have this issue.");
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- config.main_config.codec = "av1";
- gtk_combo_box_set_active_id(GTK_COMBO_BOX(video_codec_input_menu), config.main_config.codec.c_str());
- } else {
- GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "H264/HEVC video encoding is either missing or disabled on your system. If you know that your system supports H264/HEVC video encoding and "
- "you are using the flatpak version of GPU Screen Recorder then try installing mesa-extra freedesktop runtime by running this command:\n"
- "flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n"
- "and then restart GPU Screen Recorder. If that doesn't work then you may have to install another mesa package for your distro.\n"
- "If you are using a distro such as manjaro which disables hardware accelerated video encoding then you can also try the <a href=\"https://flathub.org/apps/com.dec05eba.gpu_screen_recorder\">flatpak version of GPU Screen Recorder</a> instead which doesn't have this issue.");
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- g_application_quit(G_APPLICATION(select_window_userdata.app));
- return;
- }
+ std::string dummy;
+ if(!config.main_config.software_encoding_warning_shown && !switch_video_codec_to_usable_hardware_encoder(dummy)) {
+ GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "Unable to find a hardware video encoder on your system, using software video encoder instead (slow!). If you know that your system supports H264/HEVC hardware video encoding and "
+ "you are using the flatpak version of GPU Screen Recorder then try installing mesa-extra freedesktop runtime by running this command:\n"
+ "flatpak install --system org.freedesktop.Platform.GL.default//23.08-extra\n"
+ "and then restart GPU Screen Recorder. If that doesn't work then you may have to install another mesa package for your distro.\n"
+ "If you are using a distro such as manjaro which disables hardware accelerated video encoding then you can also try the <a href=\"https://flathub.org/apps/com.dec05eba.gpu_screen_recorder\">flatpak version of GPU Screen Recorder</a> instead which doesn't have this issue.");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ config.main_config.software_encoding_warning_shown = true;
}
- if(!supported_video_codecs.h264 && !supported_video_codecs.hevc && gpu_inf.vendor == GPU_VENDOR_NVIDIA) {
- GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Failed to find H264/HEVC video codecs. Your NVIDIA GPU may be missing support for H264/HEVC video codecs for video encoding.");
+ if(gsr_info.system_info.is_steam_deck && !config.main_config.steam_deck_warning_shown) {
+ GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "Steam deck has multiple driver bugs, some which have been introduced in the last few months. For example one of them has been reported here: "
+ "<a href=\"https://github.com/ValveSoftware/SteamOS/issues/1609\">https://github.com/ValveSoftware/SteamOS/issues/1609</a>.\n"
+ "If you have issues with GPU Screen Recorder on steam deck but not on a desktop computer then report the issue to Valve and/or AMD.");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
- g_application_quit(G_APPLICATION(select_window_userdata.app));
- return;
+ config.main_config.steam_deck_warning_shown = true;
}
}
-static bool gl_get_gpu_info(gsr_egl *egl, gpu_info *info) {
- const char *software_renderers[] = { "llvmpipe", "SWR", "softpipe", NULL };
- bool supported = true;
- const unsigned char *gl_vendor = egl->glGetString(GL_VENDOR);
- const unsigned char *gl_renderer = egl->glGetString(GL_RENDERER);
-
- info->gpu_version = 0;
-
- if(!gl_vendor) {
- fprintf(stderr, "Error: failed to get gpu vendor\n");
- supported = false;
- goto end;
- }
-
- if(gl_renderer) {
- for(int i = 0; software_renderers[i]; ++i) {
- if(strstr((const char*)gl_renderer, software_renderers[i])) {
- fprintf(stderr, "gsr error: your opengl environment is not properly setup. It's using %s (software rendering) for opengl instead of your graphics card. Please make sure your graphics driver is properly installed\n", software_renderers[i]);
- supported = false;
- goto end;
- }
+static void init_shortcuts_callback(bool success, void *userdata) {
+ (void)userdata;
+ global_shortcuts_initialized = success;
+ if(success) {
+ gtk_widget_set_visible(GTK_WIDGET(recording_hotkeys_grid), true);
+ gtk_widget_set_visible(GTK_WIDGET(replay_hotkeys_grid), true);
+ gtk_widget_set_visible(GTK_WIDGET(streaming_hotkeys_grid), true);
+
+ gtk_widget_set_visible(GTK_WIDGET(recording_hotkeys_not_supported_label), false);
+ gtk_widget_set_visible(GTK_WIDGET(replay_hotkeys_not_supported_label), false);
+ gtk_widget_set_visible(GTK_WIDGET(streaming_hotkeys_not_supported_label), false);
+
+ if(!gsr_global_shortcuts_list_shortcuts(&global_shortcuts, shortcut_changed_callback, NULL)) {
+ fprintf(stderr, "gsr error: failed to list shortcuts\n");
}
- }
-
- if(strstr((const char*)gl_vendor, "AMD"))
- info->vendor = GPU_VENDOR_AMD;
- else if(strstr((const char*)gl_vendor, "Intel"))
- info->vendor = GPU_VENDOR_INTEL;
- else if(strstr((const char*)gl_vendor, "NVIDIA"))
- info->vendor = GPU_VENDOR_NVIDIA;
- else {
- fprintf(stderr, "Error: unknown gpu vendor: %s\n", gl_vendor);
- supported = false;
- goto end;
- }
+ gsr_global_shortcuts_subscribe_activated_signal(&global_shortcuts, deactivated_callback, shortcut_changed_callback, NULL);
+ } else {
+ gtk_widget_set_visible(GTK_WIDGET(recording_hotkeys_grid), false);
+ gtk_widget_set_visible(GTK_WIDGET(replay_hotkeys_grid), false);
+ gtk_widget_set_visible(GTK_WIDGET(streaming_hotkeys_grid), false);
- if(gl_renderer) {
- if(info->vendor == GPU_VENDOR_NVIDIA)
- sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version);
+ gtk_widget_set_visible(GTK_WIDGET(recording_hotkeys_not_supported_label), true);
+ gtk_widget_set_visible(GTK_WIDGET(replay_hotkeys_not_supported_label), true);
+ gtk_widget_set_visible(GTK_WIDGET(streaming_hotkeys_not_supported_label), true);
}
-
- end:
- return supported;
}
-static bool is_xwayland(Display *dpy) {
- int opcode, event, error;
- if(XQueryExtension(dpy, "XWAYLAND", &opcode, &event, &error))
- return true;
-
- bool xwayland_found = false;
- for_each_active_monitor_output_x11(dpy, [&xwayland_found](const gsr_monitor *monitor, void*) {
- if(monitor->name_len >= 8 && strncmp(monitor->name, "XWAYLAND", 8) == 0)
- xwayland_found = true;
- else if(memmem(monitor->name, monitor->name_len, "X11", 3))
- xwayland_found = true;
- }, NULL);
- return xwayland_found;
-}
-
-static const char* gpu_vendor_to_name(gpu_vendor vendor) {
+static const char* gpu_vendor_to_name(GpuVendor vendor) {
switch(vendor) {
- case GPU_VENDOR_AMD: return "AMD";
- case GPU_VENDOR_INTEL: return "Intel";
- case GPU_VENDOR_NVIDIA: return "NVIDIA";
+ case GpuVendor::UNKNOWN: return "Unknown";
+ case GpuVendor::AMD: return "AMD";
+ case GpuVendor::INTEL: return "Intel";
+ case GpuVendor::NVIDIA: return "NVIDIA";
}
return "";
}
static void activate(GtkApplication *app, gpointer) {
flatpak = is_inside_flatpak();
+ nvfbc_installed = gsr_info.system_info.display_server != DisplayServer::WAYLAND && is_nv_fbc_installed();
+ page_navigation_userdata.app = app;
- if(!wayland && !dpy) {
- GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Neither X11 nor Wayland is running.");
+ if(gsr_info_exit_status == GsrInfoExitStatus::FAILED_TO_RUN_COMMAND) {
+ const char *cmd = flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4" : "gpu-screen-recorder -w screen -f 60 -o video.mp4";
+ GtkWidget *dialog = gtk_message_dialog_new_with_markup(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "Failed to run 'gpu-screen-recorder' command. If you are using gpu-screen-recorder flatpak then this is a bug. Otherwise you need to make sure gpu-screen-recorder is installed on your system and working properly (install necessary depedencies depending on your GPU, such as libva-mesa-driver, libva-intel-driver, intel-media-driver and linux-firmware). Run:\n"
+ "%s\n"
+ "in a terminal to see more information about the issue.", cmd);
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_application_quit(G_APPLICATION(app));
return;
}
- nvfbc_installed = !wayland && is_nv_fbc_installed();
+ if(gsr_info_exit_status == GsrInfoExitStatus::OPENGL_FAILED) {
+ GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "Failed to get OpenGL information. Make sure your GPU drivers are properly installed. "
+ "If you are using nvidia then make sure to run \"flatpak update\" to make sure that your flatpak nvidia driver version matches your distros nvidia driver version. If this doesn't work then you might need to manually install a flatpak nvidia driver version that matches your distros nvidia driver version.");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_application_quit(G_APPLICATION(app));
+ return;
+ }
- if(!gsr_egl_load(&egl, dpy, wayland)) {
+ if(gsr_info_exit_status == GsrInfoExitStatus::NO_DRM_CARD) {
GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Failed to load OpenGL. Make sure your GPU drivers are properly installed.");
+ "Failed to find a valid DRM card. If you are running GPU Screen Recorder with prime-run then try running without it.");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_application_quit(G_APPLICATION(app));
return;
}
- if(!gl_get_gpu_info(&egl, &gpu_inf)) {
+ if(gsr_info.system_info.display_server == DisplayServer::UNKNOWN) {
GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Failed to get OpenGL information. Make sure your GPU drivers are properly installed. "
- "If you are using nvidia then make sure to run \"flatpak update\" to make sure that your flatpak nvidia driver version matches your distros nvidia driver version. If this doesn't work then you might need to manually install a flatpak nvidia driver version that matches your distros nvidia driver version.");
+ "Neither X11 nor Wayland is running.");
gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
g_application_quit(G_APPLICATION(app));
return;
}
- if((gpu_inf.vendor != GPU_VENDOR_NVIDIA) || wayland) {
- if(!gsr_get_valid_card_path(&egl, egl.card_path)) {
- GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
- "Failed to find a valid DRM card. If you are running GPU Screen Recorder with prime-run then try running without it.");
- gtk_dialog_run(GTK_DIALOG(dialog));
- gtk_widget_destroy(dialog);
- g_application_quit(G_APPLICATION(app));
- return;
- }
- } else {
- egl.card_path[0] = '\0';
+ if(gsr_info.system_info.display_server == DisplayServer::X11 && !dpy) {
+ GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "Failed to connect to X11 server");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_application_quit(G_APPLICATION(app));
+ return;
}
- if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) {
+ if(gsr_info.gpu_info.vendor == GpuVendor::NVIDIA) {
if(!is_cuda_installed()) {
GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
"CUDA is not installed on your system. GPU Screen Recorder requires CUDA to be installed to work with a NVIDIA GPU.");
@@ -3712,10 +3891,8 @@ static void activate(GtkApplication *app, gpointer) {
}
}
- supported_video_codecs_exit_status = get_supported_video_codecs(&supported_video_codecs);
-
- std::string window_title = "GPU Screen Recorder | Running on ";
- window_title += gpu_vendor_to_name(gpu_inf.vendor);
+ std::string window_title = "GPU Screen Recorder v" + std::string(GSR_VERSION) + " | Running on ";
+ window_title += gpu_vendor_to_name(gsr_info.gpu_info.vendor);
window = gtk_application_window_new(app);
g_signal_connect(window, "destroy", G_CALLBACK(on_destroy_window), nullptr);
@@ -3723,16 +3900,9 @@ static void activate(GtkApplication *app, gpointer) {
gtk_window_set_resizable(GTK_WINDOW(window), false);
select_window_userdata.app = app;
- audio_inputs = get_pulseaudio_inputs();
- pa_default_sources = get_pulseaudio_default_inputs();
-
- if(!pa_default_sources.default_source_name.empty() && audio_inputs_contains(audio_inputs, pa_default_sources.default_source_name))
- audio_inputs.insert(audio_inputs.begin(), { pa_default_sources.default_source_name.c_str(), "Default input" });
+ audio_inputs = get_audio_devices();
- if(!pa_default_sources.default_sink_name.empty() && audio_inputs_contains(audio_inputs, pa_default_sources.default_sink_name))
- audio_inputs.insert(audio_inputs.begin(), { pa_default_sources.default_sink_name.c_str(), "Default output" });
-
- if(!wayland)
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND)
crosshair_cursor = XCreateFontCursor(gdk_x11_get_default_xdisplay(), XC_crosshair);
GtkStack *stack = GTK_STACK(gtk_stack_new());
@@ -3740,13 +3910,12 @@ static void activate(GtkApplication *app, gpointer) {
gtk_stack_set_transition_type(stack, GTK_STACK_TRANSITION_TYPE_NONE);
gtk_stack_set_transition_duration(stack, 0);
gtk_stack_set_homogeneous(stack, false);
- GtkWidget *common_settings_page = create_common_settings_page(stack, app, gpu_inf);
+ GtkWidget *common_settings_page = create_common_settings_page(stack, app);
GtkWidget *replay_page = create_replay_page(app, stack);
GtkWidget *recording_page = create_recording_page(app, stack);
GtkWidget *streaming_page = create_streaming_page(app, stack);
gtk_stack_set_visible_child(stack, common_settings_page);
- page_navigation_userdata.app = app;
page_navigation_userdata.stack = stack;
page_navigation_userdata.common_settings_page = common_settings_page;
page_navigation_userdata.replay_page = replay_page;
@@ -3762,7 +3931,7 @@ static void activate(GtkApplication *app, gpointer) {
g_signal_connect(stream_button, "clicked", G_CALLBACK(on_start_streaming_click), &page_navigation_userdata);
g_signal_connect(stream_back_button, "clicked", G_CALLBACK(on_streaming_recording_replay_page_back_click), &page_navigation_userdata);
- if(!wayland) {
+ if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) {
xim = XOpenIM(gdk_x11_get_default_xdisplay(), NULL, NULL, NULL);
xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
@@ -3775,7 +3944,33 @@ static void activate(GtkApplication *app, gpointer) {
g_timeout_add(500, timer_timeout_handler, app);
gtk_widget_show_all(window);
- load_config(gpu_inf);
+ load_config();
+
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ if(gdk_wayland_display_query_registry(gdk_display_get_default(), "hyprland_global_shortcuts_manager_v1")) {
+ wayland_compositor = WaylandCompositor::HYPRLAND;
+ } else if(gdk_wayland_display_query_registry(gdk_display_get_default(), "org_kde_plasma_shell")) {
+ wayland_compositor = WaylandCompositor::KDE;
+ }
+
+ init_shortcuts_callback(false, nullptr);
+ // TODO:
+ // Disable global hotkeys on Hyprland for now. It crashes the hyprland desktop portal.
+ // When it's re-enabled on Hyprland it will need special handing where it does BindShortcuts immediately on startup
+ // instead of having a "register hotkeys" button. This needed because Hyprland doesn't remember registered hotkeys after
+ // the desktop portal is restarted (when the computer is restarted for example).
+
+ if(wayland_compositor == WaylandCompositor::HYPRLAND) {
+ const char *hotkeys_not_supported_text = "Global hotkeys have been disabled on your system because of a Hyprland bug.\nUse X11 or KDE Plasma on Wayland if you want to use hotkeys.";
+ gtk_label_set_text(GTK_LABEL(recording_hotkeys_not_supported_label), hotkeys_not_supported_text);
+ gtk_label_set_text(GTK_LABEL(replay_hotkeys_not_supported_label), hotkeys_not_supported_text);
+ gtk_label_set_text(GTK_LABEL(streaming_hotkeys_not_supported_label), hotkeys_not_supported_text);
+ } else {
+ if(!gsr_global_shortcuts_init(&global_shortcuts, init_shortcuts_callback, NULL)) {
+ fprintf(stderr, "gsr error: failed to initialize global shortcuts\n");
+ }
+ }
+ }
}
int main(int argc, char **argv) {
@@ -3796,12 +3991,14 @@ int main(int argc, char **argv) {
unsetenv("vblank_mode");
dpy = XOpenDisplay(NULL);
- wayland = !dpy || is_xwayland(dpy);
+ gsr_info_exit_status = get_gpu_screen_recorder_info(&gsr_info);
- if(wayland) {
- setenv("GDK_BACKEND", "wayland", true);
- } else {
- setenv("GDK_BACKEND", "x11", true);
+ if(gsr_info_exit_status == GsrInfoExitStatus::OK) {
+ if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) {
+ setenv("GDK_BACKEND", "wayland", true);
+ } else {
+ setenv("GDK_BACKEND", "x11", true);
+ }
}
GtkApplication *app = gtk_application_new("com.dec05eba.gpu_screen_recorder", G_APPLICATION_NON_UNIQUE);