aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2023-02-18 21:13:55 +0100
committerdec05eba <dec05eba@protonmail.com>2023-02-19 13:59:59 +0100
commitc4100f3c6633607e5d3277af450bee00456f09f9 (patch)
treeb6306e1ddff7d1d084d9270eb19811d328d11015
parent0be5ae0720a45ce391a59530a3ef7d3c9318011e (diff)
Add hotkey/codec config, add option to merge audio tracks
-rw-r--r--.gitignore2
-rw-r--r--README.md4
-rw-r--r--TODO3
-rwxr-xr-xbuild.sh4
-rw-r--r--com.dec05eba.gpu_screen_recorder.appdata.xml12
-rw-r--r--project.conf2
-rw-r--r--src/config.hpp60
-rw-r--r--src/egl.c195
-rw-r--r--src/egl.h52
-rw-r--r--src/library_loader.h42
-rw-r--r--src/main.cpp997
11 files changed, 1239 insertions, 134 deletions
diff --git a/.gitignore b/.gitignore
index 03cb7dd..d339a3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,7 @@ compile_commands.json
tests/sibs-build/
tests/compile_commands.json
+*.o
+
.clangd/
gpu-screen-recorder-gtk
diff --git a/README.md b/README.md
index ed0e33b..90499d2 100644
--- a/README.md
+++ b/README.md
@@ -18,8 +18,8 @@ and then rebooting your laptop.
## Installation
This program depends on [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/) which needs to be installed first.\
Run `./install.sh` as root 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`.\
-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.\
+Dependencies needed when building using `build.sh` or `install.sh`: `gtk3 libx11 libxrandr libpulse libglvnd (which provides libgl and libegl)`.\
+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
![](https://www.dec05eba.com/images/gpu-screen-recorder.png)
diff --git a/TODO b/TODO
index 9163503..0456172 100644
--- a/TODO
+++ b/TODO
@@ -1,4 +1,3 @@
Capture stderr (ignore fps: 250) and show that in notification on error.
Make sure the resolution is allowed for streaming.
-Allow to change hotkeys.
-Add list of windows to select from. This makes it easier to select another window that is not in the view to be clickable.
+Add list of windows to select from. This makes it easier to select another window that is not in the view to be clickable. \ No newline at end of file
diff --git a/build.sh b/build.sh
index 8c38b31..563cfec 100755
--- a/build.sh
+++ b/build.sh
@@ -3,4 +3,6 @@
dependencies="gtk+-3.0 x11 xrandr libpulse"
includes="$(pkg-config --cflags $dependencies)"
libs="$(pkg-config --libs $dependencies) -ldl"
-g++ -o gpu-screen-recorder-gtk -O2 src/main.cpp -s $includes $libs
+gcc -c src/egl.c -O2 -g0 -DNDEBUG $includes
+g++ -c src/main.cpp -O2 -g0 -DNDEBUG $includes
+g++ -o gpu-screen-recorder-gtk -O2 egl.o main.o -s $libs
diff --git a/com.dec05eba.gpu_screen_recorder.appdata.xml b/com.dec05eba.gpu_screen_recorder.appdata.xml
index eac4f0b..9f83665 100644
--- a/com.dec05eba.gpu_screen_recorder.appdata.xml
+++ b/com.dec05eba.gpu_screen_recorder.appdata.xml
@@ -33,6 +33,18 @@
</screenshots>
<releases>
+ <release version="1.3.0" date="2023-02-20">
+ <description>
+ <ul>
+ <li>Switch to EGL (fixes possible window capture issues when using a compositor)</li>
+ <li>Add option to change hotkeys</li>
+ <li>Add option to merge audio tracks into one audio track</li>
+ <li>Add option to follow the focused window</li>
+ <li>Add option to force set h264/hevc (services such as discord can't play hevc videos directly in the application)</li>
+ <li>Show proper error when NVIDIA GPU is not in use</li>
+ </ul>
+ </description>
+ </release>
<release version="1.2.1" date="2022-11-24">
<description>
<p>Allow choosing between mp4, flv and mkv for record/replay. mkv survives system crashes</p>
diff --git a/project.conf b/project.conf
index 8720ecb..77eaf53 100644
--- a/project.conf
+++ b/project.conf
@@ -1,7 +1,7 @@
[package]
name = "gpu-screen-recorder-gtk"
type = "executable"
-version = "0.1.0"
+version = "1.3.0"
platforms = ["posix"]
[dependencies]
diff --git a/src/config.hpp b/src/config.hpp
index feb68dd..fc19cd2 100644
--- a/src/config.hpp
+++ b/src/config.hpp
@@ -11,29 +11,40 @@
#include <pwd.h>
#include <sys/stat.h>
+struct ConfigHotkey {
+ int64_t keysym = 0;
+ uint32_t modifiers = 0;
+};
+
struct MainConfig {
std::string record_area_option;
int record_area_width = 0;
int record_area_height = 0;
int fps = 60;
+ bool merge_audio_tracks = true;
std::vector<std::string> audio_input;
std::string quality;
+ std::string codec;
};
struct StreamingConfig {
std::string streaming_service;
std::string stream_key;
+ ConfigHotkey start_recording_hotkey;
};
struct RecordConfig {
std::string save_directory;
std::string container;
+ ConfigHotkey start_recording_hotkey;
};
struct ReplayConfig {
std::string save_directory;
std::string container;
int replay_time = 30;
+ ConfigHotkey start_recording_hotkey;
+ ConfigHotkey save_recording_hotkey;
};
struct Config {
@@ -207,40 +218,75 @@ static Config read_config() {
config.main_config.record_area_option.assign(value.str, value.size);
} else if(key == "main.record_area_width") {
if(!string_to_int(std::string(value.str, value.size), config.main_config.record_area_width)) {
- fprintf(stderr, "Warning: Invalid config option main.record_area_width\n");
+ fprintf(stderr, "Warning: Invalid config option value for main.record_area_width\n");
config.main_config.record_area_width = 0;
}
} else if(key == "main.record_area_height") {
if(!string_to_int(std::string(value.str, value.size), config.main_config.record_area_height)) {
- fprintf(stderr, "Warning: Invalid config option main.record_area_height\n");
+ fprintf(stderr, "Warning: Invalid config option value for main.record_area_height\n");
config.main_config.record_area_height = 0;
}
} else if(key == "main.fps") {
if(!string_to_int(std::string(value.str, value.size), config.main_config.fps)) {
- fprintf(stderr, "Warning: Invalid config option main.fps\n");
+ fprintf(stderr, "Warning: Invalid config option value for main.fps\n");
config.main_config.fps = 60;
}
+ } else if(key == "main.merge_audio_tracks") {
+ if(value == "true")
+ config.main_config.merge_audio_tracks = true;
+ else if(value == "false")
+ config.main_config.merge_audio_tracks = false;
} else if(key == "main.audio_input") {
config.main_config.audio_input.emplace_back(value.str, value.size);
} else if(key == "main.quality") {
config.main_config.quality.assign(value.str, value.size);
+ } else if(key == "main.codec") {
+ config.main_config.codec.assign(value.str, value.size);
} else if(key == "streaming.service") {
config.streaming_config.streaming_service.assign(value.str, value.size);
} else if(key == "streaming.key") {
config.streaming_config.stream_key.assign(value.str, value.size);
+ } else if(key == "streaming.start_recording_hotkey") {
+ std::string value_str(value.str, value.size);
+ if(sscanf(value_str.c_str(), "%ld %u", &config.streaming_config.start_recording_hotkey.keysym, &config.streaming_config.start_recording_hotkey.modifiers) != 2) {
+ fprintf(stderr, "Warning: Invalid config option value for streaming.start_recording_hotkey\n");
+ config.streaming_config.start_recording_hotkey.keysym = 0;
+ config.streaming_config.start_recording_hotkey.modifiers = 0;
+ }
} else if(key == "record.save_directory") {
config.record_config.save_directory.assign(value.str, value.size);
} else if(key == "record.container") {
config.record_config.container.assign(value.str, value.size);
+ } else if(key == "record.start_recording_hotkey") {
+ std::string value_str(value.str, value.size);
+ if(sscanf(value_str.c_str(), "%ld %u", &config.record_config.start_recording_hotkey.keysym, &config.record_config.start_recording_hotkey.modifiers) != 2) {
+ fprintf(stderr, "Warning: Invalid config option value for record.start_recording_hotkey\n");
+ config.record_config.start_recording_hotkey.keysym = 0;
+ config.record_config.start_recording_hotkey.modifiers = 0;
+ }
} else if(key == "replay.save_directory") {
config.replay_config.save_directory.assign(value.str, value.size);
} else if(key == "replay.container") {
config.replay_config.container.assign(value.str, value.size);
} else if(key == "replay.time") {
if(!string_to_int(std::string(value.str, value.size), config.replay_config.replay_time)) {
- fprintf(stderr, "Warning: Invalid config option replay.time\n");
+ fprintf(stderr, "Warning: Invalid config option value for replay.time\n");
config.replay_config.replay_time = 30;
}
+ } else if(key == "replay.start_recording_hotkey") {
+ std::string value_str(value.str, value.size);
+ if(sscanf(value_str.c_str(), "%ld %u", &config.replay_config.start_recording_hotkey.keysym, &config.replay_config.start_recording_hotkey.modifiers) != 2) {
+ fprintf(stderr, "Warning: Invalid config option value for replay.start_recording_hotkey\n");
+ config.replay_config.start_recording_hotkey.keysym = 0;
+ config.replay_config.start_recording_hotkey.modifiers = 0;
+ }
+ } else if(key == "replay.save_recording_hotkey") {
+ std::string value_str(value.str, value.size);
+ if(sscanf(value_str.c_str(), "%ld %u", &config.replay_config.save_recording_hotkey.keysym, &config.replay_config.save_recording_hotkey.modifiers) != 2) {
+ fprintf(stderr, "Warning: Invalid config option value for replay.save_recording_hotkey\n");
+ config.replay_config.save_recording_hotkey.keysym = 0;
+ config.replay_config.save_recording_hotkey.modifiers = 0;
+ }
} else {
fprintf(stderr, "Warning: Invalid config option: %.*s\n", (int)line.size, line.str);
}
@@ -273,20 +319,26 @@ static void save_config(const Config &config) {
fprintf(file, "main.record_area_width %d\n", config.main_config.record_area_width);
fprintf(file, "main.record_area_height %d\n", config.main_config.record_area_height);
fprintf(file, "main.fps %d\n", config.main_config.fps);
+ fprintf(file, "main.merge_audio_tracks %s\n", config.main_config.merge_audio_tracks ? "true" : "false");
for(const std::string &audio_input : config.main_config.audio_input) {
fprintf(file, "main.audio_input %s\n", audio_input.c_str());
}
fprintf(file, "main.quality %s\n", config.main_config.quality.c_str());
+ fprintf(file, "main.codec %s\n", config.main_config.codec.c_str());
fprintf(file, "streaming.service %s\n", config.streaming_config.streaming_service.c_str());
fprintf(file, "streaming.key %s\n", config.streaming_config.stream_key.c_str());
+ fprintf(file, "streaming.start_recording_hotkey %ld %u\n", config.streaming_config.start_recording_hotkey.keysym, config.streaming_config.start_recording_hotkey.modifiers);
fprintf(file, "record.save_directory %s\n", config.record_config.save_directory.c_str());
fprintf(file, "record.container %s\n", config.record_config.container.c_str());
+ fprintf(file, "record.start_recording_hotkey %ld %u\n", config.record_config.start_recording_hotkey.keysym, config.record_config.start_recording_hotkey.modifiers);
fprintf(file, "replay.save_directory %s\n", config.replay_config.save_directory.c_str());
fprintf(file, "replay.container %s\n", config.replay_config.container.c_str());
fprintf(file, "replay.time %d\n", config.replay_config.replay_time);
+ fprintf(file, "replay.start_recording_hotkey %ld %u\n", config.replay_config.start_recording_hotkey.keysym, config.replay_config.start_recording_hotkey.modifiers);
+ fprintf(file, "replay.save_recording_hotkey %ld %u\n", config.replay_config.save_recording_hotkey.keysym, config.replay_config.save_recording_hotkey.modifiers);
fclose(file);
}
diff --git a/src/egl.c b/src/egl.c
new file mode 100644
index 0000000..ecefdf4
--- /dev/null
+++ b/src/egl.c
@@ -0,0 +1,195 @@
+#include "egl.h"
+#include "library_loader.h"
+#include <string.h>
+
+static bool gsr_egl_create_window(gsr_egl *self) {
+ EGLConfig ecfg;
+ int32_t num_config = 0;
+ EGLDisplay egl_display = NULL;
+ EGLSurface egl_surface = NULL;
+ EGLContext egl_context = NULL;
+ Window window = None;
+
+ int32_t attr[] = {
+ EGL_BUFFER_SIZE, 24,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_NONE
+ };
+
+ int32_t ctxattr[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2,
+ EGL_NONE
+ };
+
+ window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL);
+
+ if(!window) {
+ fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n");
+ goto fail;
+ }
+
+ egl_display = self->eglGetDisplay(self->dpy);
+ if(!egl_display) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n");
+ goto fail;
+ }
+
+ if(!self->eglInitialize(egl_display, NULL, NULL)) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglInitialize failed\n");
+ goto fail;
+ }
+
+ if(!self->eglChooseConfig(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;
+ }
+
+ egl_surface = self->eglCreateWindowSurface(egl_display, ecfg, (EGLNativeWindowType)window, NULL);
+ if(!egl_surface) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n");
+ goto fail;
+ }
+
+ egl_context = self->eglCreateContext(egl_display, ecfg, NULL, ctxattr);
+ if(!egl_context) {
+ fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n");
+ goto fail;
+ }
+
+ self->eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context);
+
+ self->egl_display = egl_display;
+ self->egl_surface = egl_surface;
+ self->egl_context = egl_context;
+ self->window = window;
+ return true;
+
+ fail:
+ if(egl_context)
+ self->eglDestroyContext(egl_display, egl_context);
+ if(egl_surface)
+ self->eglDestroySurface(egl_display, egl_surface);
+ if(egl_display)
+ self->eglTerminate(egl_display);
+ if(window)
+ XDestroyWindow(self->dpy, window);
+ return false;
+}
+
+static bool gsr_egl_load_egl(gsr_egl *self, void *library) {
+ 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" },
+
+ { 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) {
+ 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;
+}
+
+bool gsr_egl_load(gsr_egl *self, Display *dpy) {
+ memset(self, 0, sizeof(gsr_egl));
+ self->dpy = dpy;
+
+ dlerror(); /* clear */
+ void *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());
+ return false;
+ }
+
+ void *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());
+ dlclose(egl_lib);
+ memset(self, 0, sizeof(gsr_egl));
+ return false;
+ }
+
+ if(!gsr_egl_load_egl(self, egl_lib)) {
+ dlclose(egl_lib);
+ dlclose(gl_lib);
+ memset(self, 0, sizeof(gsr_egl));
+ return false;
+ }
+
+ if(!gsr_egl_load_gl(self, gl_lib)) {
+ dlclose(egl_lib);
+ dlclose(gl_lib);
+ memset(self, 0, sizeof(gsr_egl));
+ return false;
+ }
+
+ if(!gsr_egl_create_window(self)) {
+ dlclose(egl_lib);
+ dlclose(gl_lib);
+ memset(self, 0, sizeof(gsr_egl));
+ return false;
+ }
+
+ self->egl_library = egl_lib;
+ self->gl_library = gl_lib;
+ return true;
+}
+
+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->window) {
+ XDestroyWindow(self->dpy, self->window);
+ self->window = None;
+ }
+
+ 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
new file mode 100644
index 0000000..52422bc
--- /dev/null
+++ b/src/egl.h
@@ -0,0 +1,52 @@
+#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 void* EGLDisplay;
+typedef void* EGLSurface;
+typedef void* EGLContext;
+typedef void* EGLConfig;
+typedef void* EGLNativeDisplayType;
+typedef uintptr_t EGLNativeWindowType;
+
+#define EGL_BUFFER_SIZE 0x3020
+#define EGL_RENDERABLE_TYPE 0x3040
+#define EGL_OPENGL_ES2_BIT 0x0004
+#define EGL_NONE 0x3038
+#define EGL_CONTEXT_CLIENT_VERSION 0x3098
+
+#define GL_VENDOR 0x1F00
+#define GL_RENDERER 0x1F01
+
+typedef struct {
+ void *egl_library;
+ void *gl_library;
+ Display *dpy;
+ EGLDisplay egl_display;
+ EGLSurface egl_surface;
+ EGLContext egl_context;
+ Window window;
+
+ 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);
+
+ const unsigned char* (*glGetString)(unsigned int name);
+} gsr_egl;
+
+bool gsr_egl_load(gsr_egl *self, Display *dpy);
+void gsr_egl_unload(gsr_egl *self);
+
+#endif /* GSR_EGL_H */
diff --git a/src/library_loader.h b/src/library_loader.h
new file mode 100644
index 0000000..1622521
--- /dev/null
+++ b/src/library_loader.h
@@ -0,0 +1,42 @@
+#ifndef GSR_LIBRARY_LOADER_H
+#define GSR_LIBRARY_LOADER_H
+
+#include <dlfcn.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+typedef struct {
+ void **func;
+ const char *name;
+} dlsym_assign;
+
+static 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 */
+static 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 */
+static 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);
+ }
+}
+
+#endif /* GSR_LIBRARY_LOADER_H */
diff --git a/src/main.cpp b/src/main.cpp
index 84940f6..71f9fec 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -16,6 +16,9 @@
#include <dlfcn.h>
#include <functional>
#include <vector>
+extern "C" {
+#include "egl.h"
+}
typedef struct {
Display *display;
@@ -33,6 +36,7 @@ typedef struct {
GtkWidget *streaming_page;
} PageNavigationUserdata;
+static GtkWidget *window;
static SelectWindowUserdata select_window_userdata;
static PageNavigationUserdata page_navigation_userdata;
static Cursor crosshair_cursor;
@@ -44,6 +48,7 @@ static GtkSpinButton *area_height_entry;
static GtkComboBoxText *record_area_selection_menu;
static GtkComboBoxText *audio_input_menu_todo;
static GtkComboBoxText *quality_input_menu;
+static GtkComboBoxText *codec_input_menu;
static GtkComboBoxText *stream_service_input_menu;
static GtkComboBoxText *record_container;
static GtkComboBoxText *replay_container;
@@ -65,6 +70,15 @@ 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 *replay_start_stop_hotkey_button;
+static GtkWidget *replay_save_hotkey_button;
+static GtkWidget *streaming_hotkey_button;
+static GtkWidget *merge_audio_tracks_button;
+
+static XIM xim;
+static XIC xic;
+
static bool replaying = false;
static bool recording = false;
static bool streaming = false;
@@ -72,6 +86,29 @@ static pid_t gpu_screen_recorder_process = -1;
static Config config;
static int num_audio_inputs_addable = 0;
static std::string record_file_current_filename;
+static bool nvfbc_installed = false;
+
+enum class HotkeyMode {
+ NoAction,
+ NewHotkey,
+ Record
+};
+
+static HotkeyMode hotkey_mode = HotkeyMode::NoAction;
+
+struct Hotkey {
+ uint32_t modkey_mask = 0;
+ KeySym keysym = None;
+ GtkWidget *hotkey_entry = nullptr;
+};
+
+static Hotkey *current_hotkey = nullptr;
+static Hotkey pressed_hotkey;
+static Hotkey latest_hotkey;
+static Hotkey streaming_hotkey;
+static Hotkey record_hotkey;
+static Hotkey replay_start_stop_hotkey;
+static Hotkey replay_save_hotkey;
struct Container {
const char *container_name;
@@ -174,17 +211,16 @@ static void drag_data_received (GtkWidget *widget, GdkDragContext *context,
static void enable_stream_record_button_if_info_filled() {
const gchar *selected_window_area = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_area_selection_menu));
- if(strcmp(selected_window_area, "window") == 0 && select_window_userdata.selected_window == None)
+ if(strcmp(selected_window_area, "window") == 0 && select_window_userdata.selected_window == None) {
+ gtk_widget_set_sensitive(GTK_WIDGET(replay_button), false);
+ gtk_widget_set_sensitive(GTK_WIDGET(record_button), false);
+ gtk_widget_set_sensitive(GTK_WIDGET(stream_button), false);
return;
-
- int num_audio_tracks = 0;
- for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&num_audio_tracks](const AudioRow *audio_row) {
- ++num_audio_tracks;
- });
+ }
gtk_widget_set_sensitive(GTK_WIDGET(replay_button), true);
gtk_widget_set_sensitive(GTK_WIDGET(record_button), true);
- gtk_widget_set_sensitive(GTK_WIDGET(stream_button), num_audio_tracks <= 1);
+ gtk_widget_set_sensitive(GTK_WIDGET(stream_button), true);
}
static GtkWidget* create_used_audio_input_row(const char *id, const char *text) {
@@ -318,22 +354,32 @@ static void save_configs() {
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);
config.main_config.fps = gtk_spin_button_get_value_as_int(fps_entry);
+ config.main_config.merge_audio_tracks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(merge_audio_tracks_button));
config.main_config.audio_input.clear();
for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [](const AudioRow *audio_row) {
config.main_config.audio_input.push_back(gtk_label_get_text(GTK_LABEL(audio_row->label)));
});
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(codec_input_menu));
config.streaming_config.streaming_service = gtk_combo_box_get_active_id(GTK_COMBO_BOX(stream_service_input_menu));
config.streaming_config.stream_key = gtk_entry_get_text(stream_id_entry);
+ 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));
+ config.record_config.start_recording_hotkey.keysym = record_hotkey.keysym;
+ config.record_config.start_recording_hotkey.modifiers = record_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);
+ 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);
}
@@ -520,15 +566,296 @@ static gboolean on_select_window_button_click(GtkButton *button, gpointer userda
return true;
}
+static bool key_is_modifier(KeySym key_sym) {
+ return key_sym >= XK_Shift_L && key_sym <= XK_Super_R && key_sym != XK_Caps_Lock && key_sym != XK_Shift_Lock;
+}
+
+static uint32_t modkey_to_mask(KeySym key_sym) {
+ assert(key_is_modifier(key_sym));
+ return 1 << (key_sym - XK_Shift_L);
+}
+
+static uint32_t key_mod_mask_to_x11_mask(uint32_t mask) {
+ uint32_t key_mod_masks = 0;
+ if(mask & (modkey_to_mask(XK_Control_L) | modkey_to_mask(XK_Control_R)))
+ key_mod_masks |= ControlMask;
+ if(mask & (modkey_to_mask(XK_Alt_L) | modkey_to_mask(XK_Alt_R)))
+ key_mod_masks |= Mod1Mask;
+ if(mask & (modkey_to_mask(XK_Shift_L) | modkey_to_mask(XK_Shift_R)))
+ key_mod_masks |= ShiftMask;
+ if(mask & (modkey_to_mask(XK_Super_L) | modkey_to_mask(XK_Super_R) | modkey_to_mask(XK_Meta_L)| modkey_to_mask(XK_Meta_R)))
+ key_mod_masks |= Mod4Mask;
+ //if(mask & (modkey_to_mask(XK_Caps_Lock) | modkey_to_mask(XK_Shift_Lock)))
+ // key_mod_masks |= LockMask;
+ return key_mod_masks;
+}
+
+static unsigned int key_state_without_locks(unsigned int key_state) {
+ return key_state & ~(Mod2Mask|LockMask);
+}
+
+struct CustomKeyName {
+ KeySym key_sym;
+ const char *name;
+};
+
+static int key_get_name(KeySym key_sym, char *buffer, int buffer_size) {
+ if(buffer_size == 0)
+ return 0;
+
+ #define CUSTOM_KEY_NAME_LEN 23
+ const CustomKeyName key_names[CUSTOM_KEY_NAME_LEN] = {
+ { XK_Caps_Lock, "Caps Lock" },
+ { XK_Shift_Lock, "Caps Lock" },
+ { XK_Return, "Return" },
+ { XK_BackSpace, "BackSpace" },
+ { XK_Tab, "Tab" },
+ { XK_Delete, "Delete" },
+ { XK_dead_acute, "`" },
+ { XK_dead_diaeresis, "^" },
+ { XK_Prior, "PageUp" },
+ { XK_Next, "PageDown" },
+ { ' ', "Space" },
+ { XK_KP_Insert, "KeyPad 0" },
+ { XK_KP_End, "KeyPad 1" },
+ { XK_KP_Down, "KeyPad 2" },
+ { XK_KP_Next, "KeyPad 3" },
+ { XK_KP_Left, "KeyPad 4" },
+ { XK_KP_Begin, "KeyPad 5" },
+ { XK_KP_Right, "KeyPad 6" },
+ { XK_KP_Home, "KeyPad 7" },
+ { XK_KP_Up, "KeyPad 8" },
+ { XK_KP_Prior, "KeyPad 9" },
+ { XK_KP_Enter, "KeyPad Return" },
+ { XK_KP_Delete, "KeyPad Delete" }
+ };
+
+ for(int i = 0; i < CUSTOM_KEY_NAME_LEN; ++i) {
+ const CustomKeyName custom_key_name = key_names[i];
+ if(key_sym == custom_key_name.key_sym) {
+ const int key_len = strlen(custom_key_name.name);
+ if(buffer_size < key_len)
+ return 0;
+
+ memcpy(buffer, custom_key_name.name, key_len);
+ return key_len;
+ }
+ }
+
+ XKeyPressedEvent event;
+ event.type = KeyPress;
+ event.display = gdk_x11_get_default_xdisplay();
+ event.state = 0;
+ event.keycode = XKeysymToKeycode(event.display, key_sym);
+
+ KeySym ignore;
+ Status return_status;
+ int buflen = Xutf8LookupString(xic, &event, buffer, buffer_size, &ignore, &return_status);
+ if(return_status != XBufferOverflow && buflen > 0)
+ return buflen;
+
+ const char *keysym_str = XKeysymToString(key_sym);
+ if(keysym_str) {
+ int keysym_str_len = strlen(keysym_str);
+ if(buffer_size >= keysym_str_len) {
+ memcpy(buffer, keysym_str, keysym_str_len);
+ return keysym_str_len;
+ }
+ }
+
+ return 0;
+}
+
+static int xerror_dummy(Display *dpy, XErrorEvent *ee) {
+ return 0;
+}
+
+static bool x_failed = false;
+static int xerror_grab_error(Display *dpy, XErrorEvent *ee) {
+ x_failed = true;
+ return 0;
+}
+
+static void ungrab_keyboard(Display *display) {
+ XErrorHandler prev_error_handler = XSetErrorHandler(xerror_dummy);
+ XUngrabKeyboard(display, CurrentTime);
+ XSync(display, False);
+ XSetErrorHandler(prev_error_handler);
+}
+
+static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab) {
+ if(hotkey.keysym == None && hotkey.modkey_mask == 0)
+ return true;
+
+ unsigned int numlockmask = 0;
+ KeyCode numlock_keycode = XKeysymToKeycode(display, XK_Num_Lock);
+ XModifierKeymap *modmap = XGetModifierMapping(display);
+ if(modmap) {
+ 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);
+ }
+ }
+ XFreeModifiermap(modmap);
+ }
+
+ unsigned int key_mod_masks = 0;
+ KeySym key_sym = hotkey.keysym;
+ if(key_sym == None) {
+ // TODO: Set key_sym to one of the modkey mask values and set key_mod_masks to the other modkeys
+ } else {
+ key_mod_masks = key_mod_mask_to_x11_mask(hotkey.modkey_mask);
+ }
+
+ 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) {
+ for(int i = 0; i < 4; ++i) {
+ if(grab) {
+ XGrabKey(display, XKeysymToKeycode(display, key_sym), key_mod_masks|modifiers[i], root_window, False, GrabModeAsync, GrabModeAsync);
+ } else {
+ XUngrabKey(display, XKeysymToKeycode(display, key_sym), key_mod_masks|modifiers[i], root_window);
+ }
+ }
+ }
+ XSync(display, False);
+
+ bool success = !x_failed;
+
+ if(!success && key_sym != None) {
+ for(int i = 0; i < 4; ++i) {
+ XUngrabKey(display, XKeysymToKeycode(display, key_sym), key_mod_masks|modifiers[i], root_window);
+ }
+ }
+ XSync(display, False);
+
+ XSetErrorHandler(prev_error_handler);
+ return success;
+}
+
+static void ungrab_keys(Display *display) {
+ grab_ungrab_hotkey_combo(display, streaming_hotkey, false);
+ grab_ungrab_hotkey_combo(display, record_hotkey, false);
+ grab_ungrab_hotkey_combo(display, replay_start_stop_hotkey, false);
+ grab_ungrab_hotkey_combo(display, replay_save_hotkey, false);
+}
+
+static void set_hotkey_text_from_hotkey_data(GtkEntry *entry, Hotkey hotkey) {
+ struct ModkeyName {
+ KeySym key_sym;
+ const char *name;
+ };
+
+ const ModkeyName modkey_names[] = {
+ { XK_Control_L, "Ctrl" },
+ { XK_Control_R, "Ctrl" },
+ { XK_Super_L, "Super" },
+ { XK_Super_R, "Super" },
+ { XK_Meta_L, "Super" },
+ { XK_Meta_R, "Super" },
+ { XK_Shift_L, "Shift" },
+ { XK_Shift_R, "Shift" },
+ { XK_Alt_L, "Alt" },
+ { XK_Alt_R, "Alt" },
+ };
+
+ std::string hotkey_combo_str;
+
+ for(auto modkey_name : modkey_names) {
+ if(hotkey.modkey_mask & modkey_to_mask(modkey_name.key_sym)) {
+ if(!hotkey_combo_str.empty())
+ hotkey_combo_str += " + ";
+ hotkey_combo_str += modkey_name.name;
+ }
+ }
+
+ if(!hotkey_combo_str.empty())
+ hotkey_combo_str += " + ";
+
+ char buffer[128];
+ int buflen = key_get_name(hotkey.keysym, buffer, sizeof(buffer));
+ if(buflen > 0)
+ hotkey_combo_str.append(buffer, buflen);
+
+ gtk_entry_set_text(entry, hotkey_combo_str.c_str());
+}
+
+struct HotkeyResult {
+ bool record_hotkey_success = false;
+ bool streaming_hotkey_success = false;
+ bool replay_start_stop_hotkey_success = false;
+ bool replay_save_hotkey_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);
+ hotkey_mode = HotkeyMode::Record;
+ hotkey_result.record_hotkey_success = grab_record_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;
+ }
+ return hotkey_result;
+}
+
static gboolean on_start_replay_click(GtkButton *button, gpointer userdata) {
PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->replay_page);
+ HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
+ if(!hotkey_result.replay_start_stop_hotkey_success) {
+ std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(replay_start_stop_hotkey.hotkey_entry));
+ std::string error_text = "Replay start/stop hotkey " + hotkey_text + " can't be used because it's used by another program. Please choose another hotkey";
+ 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;
+ 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);
+ }
+ if(!hotkey_result.replay_save_hotkey_success) {
+ std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(replay_save_hotkey.hotkey_entry));
+ std::string error_text = "Replay save hotkey " + hotkey_text + " can't be used because it's used by another program. Please choose another hotkey";
+ gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey.hotkey_entry), "");
+ replay_save_hotkey.keysym = 0;
+ replay_save_hotkey.modkey_mask = 0;
+ 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);
+ }
return true;
}
static gboolean on_start_recording_click(GtkButton *button, gpointer userdata) {
PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->recording_page);
+ HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
+ if(!hotkey_result.record_hotkey_success) {
+ std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(record_hotkey.hotkey_entry));
+ std::string error_text = "Record start/stop hotkey " + hotkey_text + " can't be used because it's used by another program. Please choose another hotkey";
+ gtk_entry_set_text(GTK_ENTRY(record_hotkey.hotkey_entry), "");
+ record_hotkey.keysym = 0;
+ record_hotkey.modkey_mask = 0;
+ 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);
+ }
return true;
}
@@ -540,14 +867,40 @@ void on_stream_key_icon_click(GtkWidget *widget, gpointer data) {
}
static gboolean on_start_streaming_click(GtkButton *button, gpointer userdata) {
+ 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;
+ });
+
+ if(num_audio_tracks > 1) {
+ GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "Streaming doesn't work with more than 1 audio track. Please remove all audio tracks or only use 1 audio track");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ return true;
+ }
+
PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->streaming_page);
+ HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page();
+ if(!hotkey_result.streaming_hotkey_success) {
+ std::string hotkey_text = gtk_entry_get_text(GTK_ENTRY(streaming_hotkey.hotkey_entry));
+ std::string error_text = "Streaming start/stop hotkey " + hotkey_text + " can't be used because it's used by another program. Please choose another hotkey";
+ gtk_entry_set_text(GTK_ENTRY(streaming_hotkey.hotkey_entry), "");
+ streaming_hotkey.keysym = 0;
+ streaming_hotkey.modkey_mask = 0;
+ 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);
+ }
return true;
}
-static gboolean on_streaming_recording_page_back_click(GtkButton *button, gpointer userdata) {
+static gboolean on_streaming_recording_replay_page_back_click(GtkButton *button, gpointer userdata) {
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;
return true;
}
@@ -658,17 +1011,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* quality_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(quality_input_menu));
+ const gchar* codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(codec_input_menu));
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, "-f", fps_str.c_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", codec_input_str, "-f", fps_str.c_str(), "-r", replay_time_str.c_str(), "-o", dir
};
- for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&args](const AudioRow *audio_row) {
- args.insert(args.end(), { "-a", audio_row->id.c_str() });
- });
+ std::string merge_audio_tracks_arg_value;
+ 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->id;
+ });
+
+ 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->id.c_str() });
+ });
+ }
if(follow_focused)
args.insert(args.end(), { "-s", area });
@@ -707,7 +1073,7 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat
static gboolean on_replay_save_button_click(GtkButton *button, gpointer userdata) {
GtkApplication *app = (GtkApplication*)userdata;
kill(gpu_screen_recorder_process, SIGUSR1);
- show_notification(app, "GPU Screen Recorder", "Saved replay", G_NOTIFICATION_PRIORITY_NORMAL);
+ //show_notification(app, "GPU Screen Recorder", "Saved replay", G_NOTIFICATION_PRIORITY_NORMAL);
return true;
}
@@ -725,7 +1091,7 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
if(exit_success) {
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);
+ //show_notification(app, "GPU Screen Recorder", notification_body.c_str(), G_NOTIFICATION_PRIORITY_NORMAL);
} else {
std::string notification_body = std::string("Failed to save the recording to ") + record_file_current_filename;
show_notification(app, "GPU Screen Recorder", notification_body.c_str(), G_NOTIFICATION_PRIORITY_URGENT);
@@ -765,17 +1131,30 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user
const gchar* container_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_container));
const gchar* quality_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(quality_input_menu));
+ const gchar* codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(codec_input_menu));
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, "-f", fps_str.c_str(), "-o", record_file_current_filename.c_str()
+ "gpu-screen-recorder", "-w", window_str.c_str(), "-c", container_str, "-q", quality_input_str, "-k", codec_input_str, "-f", fps_str.c_str(), "-o", record_file_current_filename.c_str()
};
- for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&args](const AudioRow *audio_row) {
- args.insert(args.end(), { "-a", audio_row->id.c_str() });
- });
+ std::string merge_audio_tracks_arg_value;
+ 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->id;
+ });
+
+ 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->id.c_str() });
+ });
+ }
if(follow_focused)
args.insert(args.end(), { "-s", area });
@@ -871,17 +1250,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* codec_input_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(codec_input_menu));
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", "flv", "-q", quality_input_str, "-f", fps_str.c_str(), "-o", stream_url.c_str()
+ "gpu-screen-recorder", "-w", window_str.c_str(), "-c", "flv", "-q", quality_input_str, "-k", codec_input_str, "-f", fps_str.c_str(), "-o", stream_url.c_str()
};
- for_each_used_audio_input(GTK_LIST_BOX(audio_input_used_list), [&args](const AudioRow *audio_row) {
- args.insert(args.end(), { "-a", audio_row->id.c_str() });
- });
+ std::string merge_audio_tracks_arg_value;
+ 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->id;
+ });
+
+ 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->id.c_str() });
+ });
+ }
if(follow_focused)
args.insert(args.end(), { "-s", area });
@@ -997,6 +1389,64 @@ static std::vector<AudioInput> get_pulseaudio_inputs() {
}
pa_mainloop_free(main_loop);
+ return {};
+}
+
+struct PulseAudioServerInfo {
+ std::string default_sink_name;
+ std::string default_source_name;
+};
+
+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);
+ }
+
+ pa_mainloop_free(main_loop);
+ return server_info;
}
static void record_area_item_change_callback(GtkComboBox *widget, gpointer userdata) {
@@ -1021,6 +1471,183 @@ static bool is_nv_fbc_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 GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent *event, gpointer userdata) {
+ if(hotkey_mode == HotkeyMode::NoAction)
+ return GDK_FILTER_CONTINUE;
+
+ PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
+ XEvent *ev = (XEvent*)xevent;
+ if(ev->type != KeyPress && ev->type != KeyRelease)
+ return GDK_FILTER_CONTINUE;
+
+ Display *display = gdk_x11_get_default_xdisplay();
+ 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(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);
+ }
+ }
+ return GDK_FILTER_CONTINUE;
+ }
+
+ if(hotkey_mode != HotkeyMode::NewHotkey)
+ return GDK_FILTER_CONTINUE;
+
+ if(ev->type == KeyPress && key_sym == XK_Escape) {
+ if(pressed_hotkey.modkey_mask == None && pressed_hotkey.keysym == None) {
+ ungrab_keyboard(display);
+ current_hotkey = nullptr;
+ hotkey_mode = HotkeyMode::Record;
+ }
+ return GDK_FILTER_CONTINUE;
+ }
+
+ if(ev->type == KeyPress) {
+ // Ignore already pressed key
+ if(key_is_modifier(key_sym)) {
+ if(pressed_hotkey.modkey_mask & modkey_to_mask(key_sym))
+ return GDK_FILTER_CONTINUE;
+ pressed_hotkey.modkey_mask |= modkey_to_mask(key_sym);
+ } else {
+ if(key_sym == pressed_hotkey.keysym)
+ return GDK_FILTER_CONTINUE;
+ pressed_hotkey.keysym = key_sym;
+ }
+
+ 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);
+ } else if(key_sym == pressed_hotkey.keysym) {
+ pressed_hotkey.keysym = None;
+ }
+
+ if(pressed_hotkey.modkey_mask == None && pressed_hotkey.keysym == None && latest_hotkey.keysym == None) {
+ set_hotkey_text_from_hotkey_data(GTK_ENTRY(current_hotkey->hotkey_entry), *current_hotkey);
+ return GDK_FILTER_CONTINUE;
+ }
+
+ 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";
+ 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;
+ }
+
+ 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 == &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) {
+ save_configs();
+ } 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";
+ 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;
+}
+
+static gboolean on_hotkey_entry_click(GtkWidget *button, gpointer) {
+ hotkey_mode = HotkeyMode::NewHotkey;
+
+ pressed_hotkey.hotkey_entry = 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 == 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;
+ }
+
+ Display *display = gdk_x11_get_default_xdisplay();
+ XErrorHandler prev_error_handler = XSetErrorHandler(xerror_dummy);
+ XGrabKeyboard(display, DefaultRootWindow(display), False, GrabModeAsync, GrabModeAsync, CurrentTime);
+ XSync(display, False);
+ XSetErrorHandler(prev_error_handler);
+ 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;
+ }
+ return false;
+}
+
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");
@@ -1048,7 +1675,7 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
record_area_selection_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
gtk_combo_box_text_append(record_area_selection_menu, "window", "Window");
gtk_combo_box_text_append(record_area_selection_menu, "focused", "Follow focused window");
- if(is_nv_fbc_installed()) {
+ if(nvfbc_installed) {
gtk_combo_box_text_append(record_area_selection_menu, "screen", "All monitors");
gtk_combo_box_text_append(record_area_selection_menu, "screen-direct", "All monitors, direct mode (VRR workaround)");
for_each_active_monitor_output(gdk_x11_get_default_xdisplay(), [](const XRROutputInfo *output_info, const XRRCrtcInfo*, const XRRModeInfo *mode_info) {
@@ -1070,7 +1697,7 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_combo_box_text_append(record_area_selection_menu, id, label.c_str());
});
}
- gtk_combo_box_set_active(GTK_COMBO_BOX(record_area_selection_menu), 0);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(record_area_selection_menu), nvfbc_installed ? 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);
@@ -1117,7 +1744,21 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_grid_attach(audio_grid, GTK_WIDGET(add_audio_grid), 0, audio_input_area_row++, 1, 1);
audio_input_menu_todo = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
- for(const AudioInput &audio_input : get_pulseaudio_inputs()) {
+ const PulseAudioServerInfo pa_server_info = get_pulseaudio_default_inputs();
+ const auto audio_inputs = get_pulseaudio_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;
}
@@ -1165,6 +1806,11 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_widget_set_halign(selected_audio_inputs_label, GTK_ALIGN_START);
gtk_grid_attach(add_audio_grid, selected_audio_inputs_label, 0, ++audio_input_area_row, 2, 1);
+ merge_audio_tracks_button = gtk_check_button_new_with_label("Merge audio tracks");
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(merge_audio_tracks_button), true);
+ gtk_widget_set_halign(merge_audio_tracks_button, GTK_ALIGN_START);
+ gtk_grid_attach(grid, merge_audio_tracks_button, 0, grid_row++, 2, 1);
+
GtkGrid *quality_grid = GTK_GRID(gtk_grid_new());
gtk_grid_attach(grid, GTK_WIDGET(quality_grid), 0, grid_row++, 2, 1);
gtk_grid_attach(quality_grid, gtk_label_new("Video quality: "), 0, 0, 1, 1);
@@ -1185,6 +1831,17 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a
gtk_widget_set_hexpand(GTK_WIDGET(fps_entry), true);
gtk_grid_attach(fps_grid, GTK_WIDGET(fps_entry), 1, 0, 1, 1);
+ GtkGrid *codec_grid = GTK_GRID(gtk_grid_new());
+ gtk_grid_attach(grid, GTK_WIDGET(codec_grid), 0, grid_row++, 2, 1);
+ gtk_grid_attach(codec_grid, gtk_label_new("Codec: "), 0, 0, 1, 1);
+ codec_input_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new());
+ gtk_combo_box_text_append(codec_input_menu, "auto", "Auto (Recommended)");
+ gtk_combo_box_text_append(codec_input_menu, "h264", "H264");
+ gtk_combo_box_text_append(codec_input_menu, "h265", "HEVC");
+ gtk_widget_set_hexpand(GTK_WIDGET(codec_input_menu), true);
+ gtk_grid_attach(codec_grid, GTK_WIDGET(codec_input_menu), 1, 0, 1, 1);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(codec_input_menu), 0);
+
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);
@@ -1220,17 +1877,46 @@ 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_label = gtk_label_new("Press Shift+Alt+F1 to start/stop the replay and Alt+F1 to save");
- gtk_grid_attach(grid, hotkey_label, 0, 0, 3, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 1, 3, 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);
+
+ 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);
+
+ 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, 0, 1, 1);
+ gtk_grid_attach(grid, replay_start_stop_hotkey_button, 1, 0, 1, 1);
+ gtk_grid_attach(grid, b, 2, 0, 1, 1);
+ gtk_grid_attach(grid, replay_save_hotkey_button, 3, 0, 1, 1);
+ gtk_grid_attach(grid, c, 4, 0, 1, 1);
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 1, 5, 1);
+
+ 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_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;
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, 2, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(file_chooser_grid), 0, 2, 5, 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, GTK_WIDGET(file_chooser_label), 0, 0, 1, 1);
+ gtk_grid_attach(file_chooser_grid, file_chooser_label, 0, 0, 1, 1);
replay_file_chooser_button = GTK_BUTTON(gtk_button_new_with_label(video_filepath.c_str()));
gtk_button_set_image(replay_file_chooser_button, save_icon);
gtk_button_set_always_show_image(replay_file_chooser_button, true);
@@ -1240,7 +1926,7 @@ 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, 3, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(container_grid), 0, 3, 5, 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) {
@@ -1251,7 +1937,7 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
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, 4, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(replay_time_grid), 0, 4, 5, 1);
gtk_grid_attach(replay_time_grid, gtk_label_new("Replay time: "), 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);
@@ -1260,7 +1946,7 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) {
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, 5, 3, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, 5, 5, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
replay_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
gtk_widget_set_hexpand(GTK_WIDGET(replay_back_button), true);
@@ -1292,14 +1978,28 @@ 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_label = gtk_label_new("Press Alt+F1 to start/stop recording");
- gtk_grid_attach(grid, hotkey_label, 0, 0, 2, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 1, 2, 1);
+ GtkWidget *a = gtk_label_new("Press");
+ gtk_widget_set_halign(a, GTK_ALIGN_END);
+
+ GtkWidget *b = gtk_label_new("to start/stop recording");
+ gtk_widget_set_halign(b, 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);
+ gtk_grid_attach(grid, a, 0, 0, 1, 1);
+ gtk_grid_attach(grid, record_hotkey_button, 1, 0, 1, 1);
+ gtk_grid_attach(grid, b, 2, 0, 1, 1);
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 1, 3, 1);
+
+ record_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
+ record_hotkey.keysym = XK_F1;
+ record_hotkey.hotkey_entry = record_hotkey_button;
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, 2, 2, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(file_chooser_grid), 0, 2, 3, 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);
@@ -1312,7 +2012,7 @@ 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, 3, 2, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(container_grid), 0, 3, 3, 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) {
@@ -1323,7 +2023,7 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) {
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, 4, 2, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, 4, 3, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
record_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
gtk_widget_set_hexpand(GTK_WIDGET(record_back_button), true);
@@ -1345,12 +2045,26 @@ 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_label = gtk_label_new("Press Alt+F1 to start/stop streaming");
- gtk_grid_attach(grid, hotkey_label, 0, 0, 2, 1);
- gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 1, 2, 1);
+ GtkWidget *a = gtk_label_new("Press");
+ gtk_widget_set_halign(a, GTK_ALIGN_END);
+
+ GtkWidget *b = gtk_label_new("to start/stop streaming");
+ gtk_widget_set_halign(b, GTK_ALIGN_START);
+
+ 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, 0, 1, 1);
+ gtk_grid_attach(grid, streaming_hotkey_button, 1, 0, 1, 1);
+ gtk_grid_attach(grid, b, 2, 0, 1, 1);
+ gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 1, 3, 1);
+
+ streaming_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L);
+ streaming_hotkey.keysym = XK_F1;
+ streaming_hotkey.hotkey_entry = streaming_hotkey_button;
GtkGrid *stream_service_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(stream_service_grid), 0, 2, 2, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(stream_service_grid), 0, 2, 3, 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");
@@ -1362,7 +2076,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, 3, 2, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(stream_id_grid), 0, 3, 3, 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);
stream_id_entry = GTK_ENTRY(gtk_entry_new());
@@ -1375,7 +2089,7 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) {
gtk_grid_attach(stream_id_grid, GTK_WIDGET(stream_id_entry), 1, 0, 1, 1);
GtkGrid *start_button_grid = GTK_GRID(gtk_grid_new());
- gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, 4, 2, 1);
+ gtk_grid_attach(grid, GTK_WIDGET(start_button_grid), 0, 4, 3, 1);
gtk_grid_set_column_spacing(start_button_grid, 10);
stream_back_button = GTK_BUTTON(gtk_button_new_with_label("Back"));
gtk_widget_set_hexpand(GTK_WIDGET(stream_back_button), true);
@@ -1401,77 +2115,6 @@ static gboolean on_destroy_window(GtkWidget *widget, GdkEvent *event, gpointer d
return true;
}
-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 hotkey_pressed = false;
-static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent *event, gpointer userdata) {
- PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata;
- XEvent *ev = (XEvent*)xevent;
-
- if((ev->type == KeyPress || ev->type == KeyRelease) && XLookupKeysym(&ev->xkey, 0) == XK_F1 && (ev->xkey.state & Mod1Mask)) {
- if(ev->type == KeyPress) {
- if(hotkey_pressed)
- return GDK_FILTER_CONTINUE;
- hotkey_pressed = true;
- } else if(ev->type == KeyRelease) {
- hotkey_pressed = false;
- return GDK_FILTER_CONTINUE;
- }
-
- GtkWidget *visible_page = gtk_stack_get_visible_child(page_navigation_userdata->stack);
- if(visible_page == page_navigation_userdata->recording_page) {
- keypress_toggle_recording(recording, start_recording_button, on_start_recording_button_click, page_navigation_userdata->app);
- } else if(visible_page == page_navigation_userdata->streaming_page) {
- 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 && (ev->xkey.state & ShiftMask)) {
- keypress_toggle_recording(replaying, start_replay_button, on_start_replay_button_click, page_navigation_userdata->app);
- } else if(visible_page == page_navigation_userdata->replay_page && replaying && gpu_screen_recorder_process != -1) {
- on_replay_save_button_click(nullptr, page_navigation_userdata->app);
- }
- }
-
- return GDK_FILTER_CONTINUE;
-}
-
-static int xerror_dummy(Display *dpy, XErrorEvent *ee) {
- return 0;
-}
-
-static void grabkeys(Display *display) {
- unsigned int numlockmask = 0;
- KeyCode numlock_keycode = XKeysymToKeycode(display, XK_Num_Lock);
- XModifierKeymap *modmap = XGetModifierMapping(display);
- 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);
- }
- }
- XFreeModifiermap(modmap);
-
- XErrorHandler prev_error_handler = XSetErrorHandler(xerror_dummy);
-
- Window root_window = DefaultRootWindow(display);
- unsigned int modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
- for(int i = 0; i < 4; ++i) {
- XGrabKey(display, XKeysymToKeycode(display, XK_F1), Mod1Mask|modifiers[i], root_window, False, GrabModeAsync, GrabModeAsync);
- XGrabKey(display, XKeysymToKeycode(display, XK_F1), Mod1Mask|ShiftMask|modifiers[i], root_window, False, GrabModeAsync, GrabModeAsync);
- }
-
- XSync(display, False);
- XSetErrorHandler(prev_error_handler);
-}
-
static gboolean handle_child_process_death(gpointer userdata) {
if(gpu_screen_recorder_process != -1) {
int status;
@@ -1526,6 +2169,13 @@ static void load_config() {
});
if(!found_monitor)
+ config.main_config.record_area_option.clear();
+ }
+
+ if(config.main_config.record_area_option.empty()) {
+ if(nvfbc_installed)
+ config.main_config.record_area_option = "screen";
+ else
config.main_config.record_area_option = "window";
}
@@ -1547,6 +2197,9 @@ static void load_config() {
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.quality != "h265")
+ config.main_config.codec = "auto";
+
if(config.streaming_config.streaming_service != "twitch" && config.streaming_config.streaming_service != "youtube" && config.streaming_config.streaming_service != "custom")
config.streaming_config.streaming_service = "twitch";
@@ -1568,26 +2221,122 @@ static void load_config() {
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);
gtk_spin_button_set_value(fps_entry, config.main_config.fps);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(merge_audio_tracks_button), config.main_config.merge_audio_tracks);
for(const std::string &audio_input : config.main_config.audio_input) {
add_audio_input_track(audio_input.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(codec_input_menu), config.main_config.codec.c_str());
gtk_combo_box_set_active_id(GTK_COMBO_BOX(stream_service_input_menu), config.streaming_config.streaming_service.c_str());
gtk_entry_set_text(stream_id_entry, config.streaming_config.stream_key.c_str());
+ if(config.streaming_config.start_recording_hotkey.keysym) {
+ 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(config.record_config.start_recording_hotkey.keysym) {
+ 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);
+ }
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(config.replay_config.start_recording_hotkey.keysym) {
+ 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(config.replay_config.save_recording_hotkey.keysym) {
+ 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);
+ }
enable_stream_record_button_if_info_filled();
}
+typedef enum {
+ GPU_VENDOR_AMD,
+ GPU_VENDOR_INTEL,
+ GPU_VENDOR_NVIDIA
+} gpu_vendor;
+
+typedef struct {
+ gpu_vendor vendor;
+ int gpu_version; /* 0 if unknown */
+} gpu_info;
+
+static bool gl_get_gpu_info(Display *dpy, gpu_info *info) {
+ gsr_egl gl;
+ if(!gsr_egl_load(&gl, dpy)) {
+ fprintf(stderr, "Error: failed to load opengl\n");
+ return false;
+ }
+
+ bool supported = true;
+ const unsigned char *gl_vendor = gl.glGetString(GL_VENDOR);
+ const unsigned char *gl_renderer = gl.glGetString(GL_RENDERER);
+
+ info->gpu_version = 0;
+
+ if(!gl_vendor) {
+ fprintf(stderr, "Error: failed to get gpu vendor\n");
+ supported = false;
+ goto end;
+ }
+
+ 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;
+ }
+
+ if(gl_renderer) {
+ if(info->vendor == GPU_VENDOR_NVIDIA)
+ sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version);
+ }
+
+ end:
+ gsr_egl_unload(&gl);
+ return supported;
+}
+
static void activate(GtkApplication *app, gpointer userdata) {
- GtkWidget *window = gtk_application_window_new(app);
+ nvfbc_installed = is_nv_fbc_installed();
+
+ gpu_info gpu_inf;
+ if(!gl_get_gpu_info(gdk_x11_get_default_xdisplay(), &gpu_inf)) {
+ GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "Failed to get OpenGL information. Make sure your are using a nvidia gpu and graphics drivers installed");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_application_quit(G_APPLICATION(app));
+ return;
+ }
+
+ // TODO: Remove once gpu screen recorder supports amd and intel properly
+ if(gpu_inf.vendor != GPU_VENDOR_NVIDIA) {
+ GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "GPU Screen Recorder does currently only support NVIDIA GPUs. You are using a laptop with a NVIDIA gpu then make sure you are running in NVIDIA performance mode, to make sure that everything runs on your NVIDIA GPU");
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+ g_application_quit(G_APPLICATION(app));
+ return;
+ }
+
+ window = gtk_application_window_new(app);
g_signal_connect(window, "destroy", G_CALLBACK(on_destroy_window), nullptr);
gtk_window_set_title(GTK_WINDOW(window), "GPU Screen Recorder");
gtk_window_set_resizable(GTK_WINDOW(window), false);
@@ -1615,18 +2364,18 @@ static void activate(GtkApplication *app, gpointer userdata) {
page_navigation_userdata.streaming_page = streaming_page;
g_signal_connect(replay_button, "clicked", G_CALLBACK(on_start_replay_click), &page_navigation_userdata);
- g_signal_connect(replay_back_button, "clicked", G_CALLBACK(on_streaming_recording_page_back_click), &page_navigation_userdata);
+ g_signal_connect(replay_back_button, "clicked", G_CALLBACK(on_streaming_recording_replay_page_back_click), &page_navigation_userdata);
g_signal_connect(record_button, "clicked", G_CALLBACK(on_start_recording_click), &page_navigation_userdata);
- g_signal_connect(record_back_button, "clicked", G_CALLBACK(on_streaming_recording_page_back_click), &page_navigation_userdata);
+ g_signal_connect(record_back_button, "clicked", G_CALLBACK(on_streaming_recording_replay_page_back_click), &page_navigation_userdata);
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_page_back_click), &page_navigation_userdata);
+ g_signal_connect(stream_back_button, "clicked", G_CALLBACK(on_streaming_recording_replay_page_back_click), &page_navigation_userdata);
+
+ xim = XOpenIM(gdk_x11_get_default_xdisplay(), NULL, NULL, NULL);
+ xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL);
- Display *display = gdk_x11_get_default_xdisplay();
- grabkeys(display);
GdkWindow *root_window = gdk_get_default_root_window();
- //gdk_window_set_events(root_window, GDK_BUTTON_PRESS_MASK);
gdk_window_add_filter(root_window, hotkey_filter_callback, &page_navigation_userdata);
g_timeout_add(1000, handle_child_process_death, app);
@@ -1637,7 +2386,7 @@ static void activate(GtkApplication *app, gpointer userdata) {
int main(int argc, char **argv) {
setlocale(LC_ALL, "C");
- GtkApplication *app = gtk_application_new("com.dec05eba.gpu_screen_recorder", G_APPLICATION_FLAGS_NONE);
+ GtkApplication *app = gtk_application_new("com.dec05eba.gpu_screen_recorder", G_APPLICATION_DEFAULT_FLAGS);
g_signal_connect(app, "activate", G_CALLBACK(activate), nullptr);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);