aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--TODO12
-rw-r--r--meson.build1
-rw-r--r--project.conf1
-rw-r--r--src/main.cpp178
5 files changed, 42 insertions, 152 deletions
diff --git a/README.md b/README.md
index e73f002..4639e80 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ More info at [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/
## 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 `install.sh`: `meson gtk3 libx11 libpulse ayatana-appindicator3-0.1`.\
+Dependencies needed when building using `install.sh`: `meson gtk3 libx11 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
diff --git a/TODO b/TODO
index 13eeae4..3059d8e 100644
--- a/TODO
+++ b/TODO
@@ -20,7 +20,7 @@ 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.
@@ -49,6 +49,16 @@ Notifications are not shown on kde plasma while using desktop portal capture (sy
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. \ No newline at end of file
diff --git a/meson.build b/meson.build
index c762460..ff98634 100644
--- a/meson.build
+++ b/meson.build
@@ -15,7 +15,6 @@ src = [
dep = [
dependency('gtk+-3.0'),
dependency('x11'),
- dependency('libpulse'),
dependency('ayatana-appindicator3-0.1'),
]
diff --git a/project.conf b/project.conf
index 9792588..82d0b5b 100644
--- a/project.conf
+++ b/project.conf
@@ -11,5 +11,4 @@ error_on_warning = "true"
[dependencies]
gtk+-3.0 = "3"
x11 = "1"
-libpulse = ">=13"
ayatana-appindicator3-0.1 = ">=0"
diff --git a/src/main.cpp b/src/main.cpp
index 3fc6f24..4e94474 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -10,7 +10,6 @@ extern "C" {
#include <X11/cursorfont.h>
#include <assert.h>
#include <string>
-#include <pulse/pulseaudio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/prctl.h>
@@ -160,13 +159,7 @@ struct AudioInput {
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,
@@ -498,128 +491,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 void pa_sourcelist_cb(pa_context*, const pa_source_info *source_info, int eol, void *userdata) {
- if(eol > 0)
- return;
+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;
- 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() {
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 --info' 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 --info' 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) {
@@ -1564,31 +1473,19 @@ static bool kill_gpu_screen_recorder_get_result(bool *already_dead) {
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)) });
});
}
}
@@ -2424,14 +2321,6 @@ 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;
- }
- return false;
-}
-
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)
@@ -3933,13 +3822,6 @@ static void activate(GtkApplication *app, gpointer) {
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" });
-
- 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(gsr_info.system_info.display_server != DisplayServer::WAYLAND)
crosshair_cursor = XCreateFontCursor(gdk_x11_get_default_xdisplay(), XC_crosshair);