aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordec05eba <dec05eba@protonmail.com>2025-02-10 18:22:21 +0100
committerdec05eba <dec05eba@protonmail.com>2025-02-10 18:22:21 +0100
commitf4e44cbef5dbbc2a2b71e7b9b70ee72d30b7c6a6 (patch)
treefba7a409ad73abc4b233c9dfd960f014c2df6b69
parent3d6354c642244cde272c328a31c72a0adba54999 (diff)
Prepare for sound. Fix game name being gsr-ui on wayland in some cases when saving video when the ui is open
-rw-r--r--README.md1
-rw-r--r--TODO5
-rw-r--r--include/AudioPlayer.hpp22
-rw-r--r--include/Overlay.hpp3
-rw-r--r--include/WindowUtils.hpp2
-rw-r--r--meson.build2
-rw-r--r--project.conf1
-rw-r--r--src/AudioPlayer.cpp87
-rw-r--r--src/Overlay.cpp3
-rw-r--r--src/WindowUtils.cpp41
10 files changed, 163 insertions, 4 deletions
diff --git a/README.md b/README.md
index dd9805d..061989c 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ These are the dependencies needed to build GPU Screen Recorder UI:
* libxcursor
* libglvnd (which provides libgl, libglx and libegl)
* linux-api-headers
+* libpulse (libpulse-simple)
## Runtime dependencies
There are also additional dependencies needed at runtime:
diff --git a/TODO b/TODO
index e8f5f63..5bb1970 100644
--- a/TODO
+++ b/TODO
@@ -116,4 +116,7 @@ Instead of installing gsr-global-hotkeys in flatpak use kms-server-proxy to laun
Check if "modprobe uinput" is needed on some systems (old fedora?).
-Add recording timer to see duration of recording/streaming. \ No newline at end of file
+Add recording timer to see duration of recording/streaming.
+
+Saving video into a folder with the name of the game doesn't always work on wayland. This happens when trying to save a video with the ui open and the ui opens without override redirect.
+ Maybe a solution would be to query all windows (top to bottom) and check which window the cursor is inside, ignoring the gsr-ui window \ No newline at end of file
diff --git a/include/AudioPlayer.hpp b/include/AudioPlayer.hpp
new file mode 100644
index 0000000..22c3be8
--- /dev/null
+++ b/include/AudioPlayer.hpp
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <thread>
+
+namespace gsr {
+ // Only plays raw stereo PCM audio in 48000hz in s16le format.
+ // Use this command to convert an audio file (input.wav) to a format playable by this class (output.pcm):
+ // ffmpeg -i input.wav -f s16le -acodec pcm_s16le -ar 48000 output.pcm
+ class AudioPlayer {
+ public:
+ AudioPlayer() = default;
+ ~AudioPlayer();
+ AudioPlayer(const AudioPlayer&) = delete;
+ AudioPlayer& operator=(const AudioPlayer&) = delete;
+
+ bool play(const char *filepath);
+ private:
+ std::thread thread;
+ bool stop_playing_audio = false;
+ int audio_file_fd = -1;
+ };
+} \ No newline at end of file
diff --git a/include/Overlay.hpp b/include/Overlay.hpp
index 4fbf54e..f3025b2 100644
--- a/include/Overlay.hpp
+++ b/include/Overlay.hpp
@@ -8,6 +8,7 @@
#include "WindowUtils.hpp"
#include "GlobalHotkeysLinux.hpp"
#include "GlobalHotkeysJoystick.hpp"
+#include "AudioPlayer.hpp"
#include <mglpp/window/Window.hpp>
#include <mglpp/window/Event.hpp>
@@ -189,5 +190,7 @@ namespace gsr {
mgl::Clock replay_save_clock;
bool replay_save_show_notification = false;
+
+ AudioPlayer audio_player;
};
} \ No newline at end of file
diff --git a/include/WindowUtils.hpp b/include/WindowUtils.hpp
index c8806df..ddecaea 100644
--- a/include/WindowUtils.hpp
+++ b/include/WindowUtils.hpp
@@ -18,6 +18,8 @@ namespace gsr {
Window get_focused_window(Display *dpy, WindowCaptureType cap_type);
std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type);
+ std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window);
+ std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window);
mgl::vec2i get_cursor_position(Display *dpy, Window *window);
mgl::vec2i create_window_get_center_position(Display *display);
std::string get_window_manager_name(Display *display);
diff --git a/meson.build b/meson.build
index ba3b7cd..fc2f4f4 100644
--- a/meson.build
+++ b/meson.build
@@ -39,6 +39,7 @@ src = [
'src/GlobalHotkeysX11.cpp',
'src/GlobalHotkeysLinux.cpp',
'src/GlobalHotkeysJoystick.cpp',
+ 'src/AudioPlayer.cpp',
'src/Hotplug.cpp',
'src/Rpc.cpp',
'src/main.cpp',
@@ -65,6 +66,7 @@ executable(
dependency('xfixes'),
dependency('xi'),
dependency('xcursor'),
+ dependency('libpulse-simple'),
],
cpp_args : '-DGSR_UI_RESOURCES_PATH="' + gsr_ui_resources_path + '"',
)
diff --git a/project.conf b/project.conf
index 971b083..cbe812c 100644
--- a/project.conf
+++ b/project.conf
@@ -15,3 +15,4 @@ xcomposite = ">=0"
xfixes = ">=0"
xi = ">=0"
xcursor = ">=1"
+libpulse-simple = ">=0" \ No newline at end of file
diff --git a/src/AudioPlayer.cpp b/src/AudioPlayer.cpp
new file mode 100644
index 0000000..8d30ee6
--- /dev/null
+++ b/src/AudioPlayer.cpp
@@ -0,0 +1,87 @@
+#include "../include/AudioPlayer.hpp"
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <pulse/simple.h>
+#include <pulse/error.h>
+
+#define BUFSIZE 4096
+
+namespace gsr {
+ AudioPlayer::~AudioPlayer() {
+ if(thread.joinable()) {
+ stop_playing_audio = true;
+ thread.join();
+ }
+
+ if(audio_file_fd > 0)
+ close(audio_file_fd);
+ }
+
+ bool AudioPlayer::play(const char *filepath) {
+ if(thread.joinable()) {
+ stop_playing_audio = true;
+ thread.join();
+ }
+
+ stop_playing_audio = false;
+ audio_file_fd = open(filepath, O_RDONLY);
+ if(audio_file_fd == -1)
+ return false;
+
+ thread = std::thread([this]() {
+ const pa_sample_spec ss = {
+ .format = PA_SAMPLE_S16LE,
+ .rate = 48000,
+ .channels = 2
+ };
+
+ pa_simple *s = NULL;
+ int error;
+
+ /* Create a new playback stream */
+ if(!(s = pa_simple_new(NULL, "gsr-ui-audio-playback", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) {
+ fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ uint8_t buf[BUFSIZE];
+ for(;;) {
+ ssize_t r;
+
+ if(stop_playing_audio)
+ goto finish;
+
+ if((r = read(audio_file_fd, buf, sizeof(buf))) <= 0) {
+ if(r == 0) /* EOF */
+ break;
+
+ fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ if(pa_simple_write(s, buf, (size_t) r, &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+ }
+
+ if(pa_simple_drain(s, &error) < 0) {
+ fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error));
+ goto finish;
+ }
+
+ finish:
+ if(s)
+ pa_simple_free(s);
+
+ close(audio_file_fd);
+ audio_file_fd = -1;
+ });
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/Overlay.cpp b/src/Overlay.cpp
index 47f41a9..8a1eefc 100644
--- a/src/Overlay.cpp
+++ b/src/Overlay.cpp
@@ -1450,7 +1450,8 @@ namespace gsr {
Display *display = (Display*)context->connection;
const std::string video_filename = filepath_get_filename(video_filepath);
- std::string focused_window_name = get_focused_window_name(display, WindowCaptureType::FOCUSED);
+ const Window gsr_ui_window = window ? window->get_system_handle() : None;
+ std::string focused_window_name = get_window_name_at_cursor_position(display, gsr_ui_window);
if(focused_window_name.empty())
focused_window_name = "Game";
diff --git a/src/WindowUtils.cpp b/src/WindowUtils.cpp
index d8e3508..3d3a6a6 100644
--- a/src/WindowUtils.cpp
+++ b/src/WindowUtils.cpp
@@ -105,8 +105,7 @@ namespace gsr {
unsigned int dummy_u;
mgl::vec2i root_pos;
XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, window, &root_pos.x, &root_pos.y, &dummy_i, &dummy_i, &dummy_u);
- if(window)
- *window = window_get_target_window_child(dpy, *window);
+ *window = window_get_target_window_child(dpy, *window);
return root_pos;
}
@@ -236,6 +235,44 @@ namespace gsr {
return result;
}
+ std::string get_window_name_at_position(Display *dpy, mgl::vec2i position, Window ignore_window) {
+ std::string result;
+
+ Window root;
+ Window parent;
+ Window *children = nullptr;
+ unsigned int num_children = 0;
+ if(!XQueryTree(dpy, DefaultRootWindow(dpy), &root, &parent, &children, &num_children) || !children)
+ return result;
+
+ for(int i = (int)num_children - 1; i >= 0; --i) {
+ if(children[i] == ignore_window)
+ continue;
+
+ XWindowAttributes attr;
+ memset(&attr, 0, sizeof(attr));
+ XGetWindowAttributes(dpy, children[i], &attr);
+ if(attr.override_redirect || attr.c_class != InputOutput)
+ continue;
+
+ if(position.x >= attr.x && position.x <= attr.x + attr.width && position.y >= attr.y && position.y <= attr.y + attr.height && window_is_user_program(dpy, children[i])) {
+ const std::optional<std::string> window_title = get_window_title(dpy, children[i]);
+ if(window_title)
+ result = strip(window_title.value());
+ break;
+ }
+ }
+
+ XFree(children);
+ return result;
+ }
+
+ std::string get_window_name_at_cursor_position(Display *dpy, Window ignore_window) {
+ Window cursor_window;
+ const mgl::vec2i cursor_position = get_cursor_position(dpy, &cursor_window);
+ return get_window_name_at_position(dpy, cursor_position, ignore_window);
+ }
+
typedef struct {
unsigned long flags;
unsigned long functions;