diff options
author | dec05eba <dec05eba@protonmail.com> | 2025-02-10 18:22:21 +0100 |
---|---|---|
committer | dec05eba <dec05eba@protonmail.com> | 2025-02-10 18:22:21 +0100 |
commit | f4e44cbef5dbbc2a2b71e7b9b70ee72d30b7c6a6 (patch) | |
tree | fba7a409ad73abc4b233c9dfd960f014c2df6b69 | |
parent | 3d6354c642244cde272c328a31c72a0adba54999 (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.md | 1 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | include/AudioPlayer.hpp | 22 | ||||
-rw-r--r-- | include/Overlay.hpp | 3 | ||||
-rw-r--r-- | include/WindowUtils.hpp | 2 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | project.conf | 1 | ||||
-rw-r--r-- | src/AudioPlayer.cpp | 87 | ||||
-rw-r--r-- | src/Overlay.cpp | 3 | ||||
-rw-r--r-- | src/WindowUtils.cpp | 41 |
10 files changed, 163 insertions, 4 deletions
@@ -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: @@ -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; |