mirror of
https://repo.dec05eba.com/gpu-screen-recorder-ui
synced 2026-03-31 09:17:04 +09:00
Fix restore portal session option not working, close other notifications when showing a new one
This commit is contained in:
@@ -60,15 +60,19 @@ namespace gsr {
|
|||||||
void xi_grab_all_devices();
|
void xi_grab_all_devices();
|
||||||
void xi_warp_pointer(mgl::vec2i position);
|
void xi_warp_pointer(mgl::vec2i position);
|
||||||
|
|
||||||
void process_key_bindings(mgl::Event &event);
|
void close_gpu_screen_recorder_output();
|
||||||
|
|
||||||
void update_notification_process_status();
|
void update_notification_process_status();
|
||||||
|
void save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type);
|
||||||
|
void update_gsr_replay_save();
|
||||||
void update_gsr_process_status();
|
void update_gsr_process_status();
|
||||||
|
|
||||||
void replay_status_update_status();
|
void replay_status_update_status();
|
||||||
void update_focused_fullscreen_status();
|
void update_focused_fullscreen_status();
|
||||||
void update_power_supply_status();
|
void update_power_supply_status();
|
||||||
|
|
||||||
|
void on_stop_recording(int exit_code);
|
||||||
|
|
||||||
void update_ui_recording_paused();
|
void update_ui_recording_paused();
|
||||||
void update_ui_recording_unpaused();
|
void update_ui_recording_unpaused();
|
||||||
|
|
||||||
@@ -89,26 +93,26 @@ namespace gsr {
|
|||||||
|
|
||||||
void force_window_on_top();
|
void force_window_on_top();
|
||||||
private:
|
private:
|
||||||
using KeyBindingCallback = std::function<void()>;
|
|
||||||
struct KeyBinding {
|
|
||||||
mgl::Event::KeyEvent key_event;
|
|
||||||
KeyBindingCallback callback;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<mgl::Window> window;
|
std::unique_ptr<mgl::Window> window;
|
||||||
mgl::Event event;
|
mgl::Event event;
|
||||||
std::string resources_path;
|
std::string resources_path;
|
||||||
GsrInfo gsr_info;
|
GsrInfo gsr_info;
|
||||||
egl_functions egl_funcs;
|
egl_functions egl_funcs;
|
||||||
|
Config config;
|
||||||
|
|
||||||
|
bool visible = false;
|
||||||
|
|
||||||
mgl::Texture window_texture_texture;
|
mgl::Texture window_texture_texture;
|
||||||
mgl::Sprite window_texture_sprite;
|
mgl::Sprite window_texture_sprite;
|
||||||
mgl::Texture screenshot_texture;
|
mgl::Texture screenshot_texture;
|
||||||
mgl::Sprite screenshot_sprite;
|
mgl::Sprite screenshot_sprite;
|
||||||
mgl::Rectangle bg_screenshot_overlay;
|
mgl::Rectangle bg_screenshot_overlay;
|
||||||
|
|
||||||
mgl::Texture cursor_texture;
|
mgl::Texture cursor_texture;
|
||||||
mgl::Sprite cursor_sprite;
|
mgl::Sprite cursor_sprite;
|
||||||
mgl::vec2i cursor_hotspot;
|
mgl::vec2i cursor_hotspot;
|
||||||
bool cursor_drawn = false;
|
bool cursor_drawn = false;
|
||||||
|
|
||||||
WindowTexture window_texture;
|
WindowTexture window_texture;
|
||||||
PageStack page_stack;
|
PageStack page_stack;
|
||||||
mgl::Rectangle top_bar_background;
|
mgl::Rectangle top_bar_background;
|
||||||
@@ -116,14 +120,17 @@ namespace gsr {
|
|||||||
mgl::Sprite logo_sprite;
|
mgl::Sprite logo_sprite;
|
||||||
CustomRendererWidget close_button_widget;
|
CustomRendererWidget close_button_widget;
|
||||||
bool close_button_pressed_inside = false;
|
bool close_button_pressed_inside = false;
|
||||||
bool visible = false;
|
|
||||||
uint64_t default_cursor = 0;
|
uint64_t default_cursor = 0;
|
||||||
|
|
||||||
pid_t gpu_screen_recorder_process = -1;
|
pid_t gpu_screen_recorder_process = -1;
|
||||||
pid_t notification_process = -1;
|
pid_t notification_process = -1;
|
||||||
Config config;
|
int gpu_screen_recorder_process_output_fd = -1;
|
||||||
|
FILE *gpu_screen_recorder_process_output_file = nullptr;
|
||||||
|
|
||||||
DropdownButton *replay_dropdown_button_ptr = nullptr;
|
DropdownButton *replay_dropdown_button_ptr = nullptr;
|
||||||
DropdownButton *record_dropdown_button_ptr = nullptr;
|
DropdownButton *record_dropdown_button_ptr = nullptr;
|
||||||
DropdownButton *stream_dropdown_button_ptr = nullptr;
|
DropdownButton *stream_dropdown_button_ptr = nullptr;
|
||||||
|
|
||||||
mgl::Clock force_window_on_top_clock;
|
mgl::Clock force_window_on_top_clock;
|
||||||
|
|
||||||
RecordingStatus recording_status = RecordingStatus::NONE;
|
RecordingStatus recording_status = RecordingStatus::NONE;
|
||||||
@@ -134,7 +141,7 @@ namespace gsr {
|
|||||||
bool power_supply_connected = false;
|
bool power_supply_connected = false;
|
||||||
bool focused_window_is_fullscreen = false;
|
bool focused_window_is_fullscreen = false;
|
||||||
|
|
||||||
std::array<KeyBinding, 1> key_bindings;
|
std::string record_filepath;
|
||||||
|
|
||||||
Display *xi_display = nullptr;
|
Display *xi_display = nullptr;
|
||||||
int xi_opcode = 0;
|
int xi_opcode = 0;
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ namespace gsr {
|
|||||||
|
|
||||||
// Arguments ending with NULL
|
// Arguments ending with NULL
|
||||||
bool exec_program_daemonized(const char **args);
|
bool exec_program_daemonized(const char **args);
|
||||||
// Arguments ending with NULL
|
// Arguments ending with NULL. |read_fd| can be NULL
|
||||||
pid_t exec_program(const char **args);
|
pid_t exec_program(const char **args, int *read_fd);
|
||||||
// |output_buffer| should be at least PATH_MAX in size
|
// |output_buffer| should be at least PATH_MAX in size
|
||||||
bool read_cmdline_arg0(const char *filepath, char *output_buffer);
|
bool read_cmdline_arg0(const char *filepath, char *output_buffer);
|
||||||
}
|
}
|
||||||
13
include/WindowUtils.hpp
Normal file
13
include/WindowUtils.hpp
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
|
||||||
|
namespace gsr {
|
||||||
|
enum class WindowCaptureType {
|
||||||
|
FOCUSED,
|
||||||
|
CURSOR
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type);
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ src = [
|
|||||||
'src/gui/GsrPage.cpp',
|
'src/gui/GsrPage.cpp',
|
||||||
'src/gui/Subsection.cpp',
|
'src/gui/Subsection.cpp',
|
||||||
'src/Utils.cpp',
|
'src/Utils.cpp',
|
||||||
|
'src/WindowUtils.cpp',
|
||||||
'src/Config.cpp',
|
'src/Config.cpp',
|
||||||
'src/GsrInfo.cpp',
|
'src/GsrInfo.cpp',
|
||||||
'src/Process.cpp',
|
'src/Process.cpp',
|
||||||
@@ -80,7 +81,6 @@ executable(
|
|||||||
|
|
||||||
install_subdir('images', install_dir : gsr_ui_resources_path)
|
install_subdir('images', install_dir : gsr_ui_resources_path)
|
||||||
install_subdir('fonts', install_dir : gsr_ui_resources_path)
|
install_subdir('fonts', install_dir : gsr_ui_resources_path)
|
||||||
install_subdir('scripts', install_dir : gsr_ui_resources_path, install_mode : 'rwxr-xr-x')
|
|
||||||
|
|
||||||
if get_option('systemd') == true
|
if get_option('systemd') == true
|
||||||
install_data(files('extra/gpu-screen-recorder-ui.service'), install_dir : 'lib/systemd/user')
|
install_data(files('extra/gpu-screen-recorder-ui.service'), install_dir : 'lib/systemd/user')
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
[ "$GSR_SHOW_SAVED_NOTIFICATION" != "1" ] && exit 0
|
|
||||||
|
|
||||||
filepath="$1"
|
|
||||||
type="$2"
|
|
||||||
|
|
||||||
file_name="$(basename "$filepath")"
|
|
||||||
|
|
||||||
case "$type" in
|
|
||||||
"regular")
|
|
||||||
gsr-notify --text "Saved recording to '$file_name'" --timeout 3.0 --icon record --bg-color "$GSR_NOTIFY_BG_COLOR"
|
|
||||||
;;
|
|
||||||
"replay")
|
|
||||||
gsr-notify --text "Saved replay to '$file_name'" --timeout 3.0 --icon replay --bg-color "$GSR_NOTIFY_BG_COLOR"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
filepath="$1"
|
|
||||||
type="$2"
|
|
||||||
|
|
||||||
file_name="$(basename "$filepath")"
|
|
||||||
file_dir="$(dirname "$filepath")"
|
|
||||||
|
|
||||||
game_name=$(gsr-window-name focused || echo "Game")
|
|
||||||
game_name="$(echo "$game_name" | tr '/\\' '_')"
|
|
||||||
target_dir="$file_dir/$game_name"
|
|
||||||
new_filepath="$target_dir/$file_name"
|
|
||||||
|
|
||||||
mkdir -p "$target_dir"
|
|
||||||
mv "$filepath" "$new_filepath"
|
|
||||||
|
|
||||||
[ "$GSR_SHOW_SAVED_NOTIFICATION" != "1" ] && exit 0
|
|
||||||
|
|
||||||
case "$type" in
|
|
||||||
"regular")
|
|
||||||
gsr-notify --text "Saved recording to '$game_name/$file_name'" --timeout 3.0 --icon record --bg-color "$GSR_NOTIFY_BG_COLOR"
|
|
||||||
;;
|
|
||||||
"replay")
|
|
||||||
gsr-notify --text "Saved replay to '$game_name/$file_name'" --timeout 3.0 --icon replay --bg-color "$GSR_NOTIFY_BG_COLOR"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
203
src/Overlay.cpp
203
src/Overlay.cpp
@@ -9,6 +9,7 @@
|
|||||||
#include "../include/gui/SettingsPage.hpp"
|
#include "../include/gui/SettingsPage.hpp"
|
||||||
#include "../include/gui/Utils.hpp"
|
#include "../include/gui/Utils.hpp"
|
||||||
#include "../include/gui/PageStack.hpp"
|
#include "../include/gui/PageStack.hpp"
|
||||||
|
#include "../include/WindowUtils.hpp"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
@@ -418,23 +419,11 @@ namespace gsr {
|
|||||||
|
|
||||||
memset(&window_texture, 0, sizeof(window_texture));
|
memset(&window_texture, 0, sizeof(window_texture));
|
||||||
|
|
||||||
key_bindings[0].key_event.code = mgl::Keyboard::Escape;
|
|
||||||
key_bindings[0].key_event.alt = false;
|
|
||||||
key_bindings[0].key_event.control = false;
|
|
||||||
key_bindings[0].key_event.shift = false;
|
|
||||||
key_bindings[0].key_event.system = false;
|
|
||||||
key_bindings[0].callback = [this]() {
|
|
||||||
page_stack.pop();
|
|
||||||
};
|
|
||||||
|
|
||||||
std::optional<Config> new_config = read_config(gsr_info);
|
std::optional<Config> new_config = read_config(gsr_info);
|
||||||
if(new_config)
|
if(new_config)
|
||||||
config = std::move(new_config.value());
|
config = std::move(new_config.value());
|
||||||
|
|
||||||
init_color_theme(gsr_info);
|
init_color_theme(gsr_info);
|
||||||
// These environment variable are used by files in scripts/ folder
|
|
||||||
const std::string notify_bg_color_str = color_to_hex_str(get_color_theme().tint_color);
|
|
||||||
setenv("GSR_NOTIFY_BG_COLOR", notify_bg_color_str.c_str(), true);
|
|
||||||
|
|
||||||
power_supply_online_filepath = get_power_supply_online_filepath();
|
power_supply_online_filepath = get_power_supply_online_filepath();
|
||||||
|
|
||||||
@@ -455,6 +444,8 @@ namespace gsr {
|
|||||||
notification_process = -1;
|
notification_process = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close_gpu_screen_recorder_output();
|
||||||
|
|
||||||
if(gpu_screen_recorder_process > 0) {
|
if(gpu_screen_recorder_process > 0) {
|
||||||
kill(gpu_screen_recorder_process, SIGINT);
|
kill(gpu_screen_recorder_process, SIGINT);
|
||||||
int status;
|
int status;
|
||||||
@@ -520,21 +511,15 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t key_event_to_bitmask(mgl::Event::KeyEvent key_event) {
|
void Overlay::close_gpu_screen_recorder_output() {
|
||||||
return ((uint32_t)key_event.alt << (uint32_t)0)
|
if(gpu_screen_recorder_process_output_file) {
|
||||||
| ((uint32_t)key_event.control << (uint32_t)1)
|
fclose(gpu_screen_recorder_process_output_file);
|
||||||
| ((uint32_t)key_event.shift << (uint32_t)2)
|
gpu_screen_recorder_process_output_file = nullptr;
|
||||||
| ((uint32_t)key_event.system << (uint32_t)3);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void Overlay::process_key_bindings(mgl::Event &event) {
|
if(gpu_screen_recorder_process_output_fd > 0) {
|
||||||
if(event.type != mgl::Event::KeyReleased)
|
close(gpu_screen_recorder_process_output_fd);
|
||||||
return;
|
gpu_screen_recorder_process_output_fd = -1;
|
||||||
|
|
||||||
const uint32_t event_key_bitmask = key_event_to_bitmask(event.key);
|
|
||||||
for(const KeyBinding &key_binding : key_bindings) {
|
|
||||||
if(event.key.code == key_binding.key_event.code && event_key_bitmask == key_event_to_bitmask(key_binding.key_event))
|
|
||||||
key_binding.callback();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -613,15 +598,16 @@ namespace gsr {
|
|||||||
if(!visible || !window)
|
if(!visible || !window)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
close_button_widget.on_event(event, *window, mgl::vec2f(0.0f, 0.0f));
|
if(!close_button_widget.on_event(event, *window, mgl::vec2f(0.0f, 0.0f)))
|
||||||
if(!page_stack.on_event(event, *window, mgl::vec2f(0.0f, 0.0f)))
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
process_key_bindings(event);
|
if(!page_stack.on_event(event, *window, mgl::vec2f(0.0f, 0.0f)))
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Overlay::draw() {
|
bool Overlay::draw() {
|
||||||
update_notification_process_status();
|
update_notification_process_status();
|
||||||
|
update_gsr_replay_save();
|
||||||
update_gsr_process_status();
|
update_gsr_process_status();
|
||||||
replay_status_update_status();
|
replay_status_update_status();
|
||||||
|
|
||||||
@@ -1113,7 +1099,7 @@ namespace gsr {
|
|||||||
waitpid(notification_process, &status, 0);
|
waitpid(notification_process, &status, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
notification_process = exec_program(notification_args);
|
notification_process = exec_program(notification_args, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Overlay::is_open() const {
|
bool Overlay::is_open() const {
|
||||||
@@ -1133,17 +1119,100 @@ namespace gsr {
|
|||||||
notification_process = -1;
|
notification_process = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void string_replace_characters(char *str, const char *characters_to_replace, char new_character) {
|
||||||
|
for(; *str != '\0'; ++str) {
|
||||||
|
for(const char *p = characters_to_replace; *p != '\0'; ++p) {
|
||||||
|
if(*str == *p)
|
||||||
|
*str = new_character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string filepath_get_directory(const char *filepath) {
|
||||||
|
std::string result = filepath;
|
||||||
|
const size_t last_slash_index = result.rfind('/');
|
||||||
|
if(last_slash_index == std::string::npos)
|
||||||
|
result = ".";
|
||||||
|
else
|
||||||
|
result.erase(last_slash_index);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string filepath_get_filename(const char *filepath) {
|
||||||
|
std::string result = filepath;
|
||||||
|
const size_t last_slash_index = result.rfind('/');
|
||||||
|
if(last_slash_index != std::string::npos)
|
||||||
|
result.erase(0, last_slash_index + 1);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlay::save_video_in_current_game_directory(const char *video_filepath, NotificationType notification_type) {
|
||||||
|
mgl_context *context = mgl_get_context();
|
||||||
|
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);
|
||||||
|
if(focused_window_name.empty())
|
||||||
|
focused_window_name = "Game";
|
||||||
|
|
||||||
|
string_replace_characters(focused_window_name.data(), "/\\", '_');
|
||||||
|
|
||||||
|
std::string video_directory = filepath_get_directory(video_filepath) + "/" + focused_window_name;
|
||||||
|
create_directory_recursive(video_directory.data());
|
||||||
|
|
||||||
|
const std::string new_video_filepath = video_directory + "/" + video_filename;
|
||||||
|
rename(video_filepath, new_video_filepath.c_str());
|
||||||
|
|
||||||
|
std::string text;
|
||||||
|
switch(notification_type) {
|
||||||
|
case NotificationType::RECORD:
|
||||||
|
text = "Saved recording to '" + focused_window_name + "/" + video_filename + "'";
|
||||||
|
break;
|
||||||
|
case NotificationType::REPLAY:
|
||||||
|
text = "Saved replay to '" + focused_window_name + "/" + video_filename + "'";
|
||||||
|
break;
|
||||||
|
case NotificationType::NONE:
|
||||||
|
case NotificationType::STREAM:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, notification_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Overlay::update_gsr_replay_save() {
|
||||||
|
if(gpu_screen_recorder_process_output_file) {
|
||||||
|
char buffer[1024];
|
||||||
|
char *replay_saved_filepath = fgets(buffer, sizeof(buffer), gpu_screen_recorder_process_output_file);
|
||||||
|
if(!replay_saved_filepath || replay_saved_filepath[0] == '\0')
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int line_len = strlen(replay_saved_filepath);
|
||||||
|
if(replay_saved_filepath[line_len - 1] == '\n')
|
||||||
|
replay_saved_filepath[line_len - 1] = '\0';
|
||||||
|
|
||||||
|
if(config.replay_config.save_video_in_game_folder) {
|
||||||
|
save_video_in_current_game_directory(replay_saved_filepath, NotificationType::REPLAY);
|
||||||
|
} else {
|
||||||
|
const std::string text = "Saved replay to '" + filepath_get_filename(replay_saved_filepath) + "'";
|
||||||
|
show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::REPLAY);
|
||||||
|
}
|
||||||
|
} else if(gpu_screen_recorder_process_output_fd > 0) {
|
||||||
|
char buffer[1024];
|
||||||
|
read(gpu_screen_recorder_process_output_fd, buffer, sizeof(buffer));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Overlay::update_gsr_process_status() {
|
void Overlay::update_gsr_process_status() {
|
||||||
if(gpu_screen_recorder_process <= 0)
|
if(gpu_screen_recorder_process <= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
errno = 0;
|
|
||||||
int status;
|
int status;
|
||||||
if(waitpid(gpu_screen_recorder_process, &status, WNOHANG) == 0) {
|
if(waitpid(gpu_screen_recorder_process, &status, WNOHANG) == 0) {
|
||||||
// Still running
|
// Still running
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close_gpu_screen_recorder_output();
|
||||||
|
|
||||||
int exit_code = -1;
|
int exit_code = -1;
|
||||||
if(WIFEXITED(status))
|
if(WIFEXITED(status))
|
||||||
exit_code = WEXITSTATUS(status);
|
exit_code = WEXITSTATUS(status);
|
||||||
@@ -1164,10 +1233,7 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
case RecordingStatus::RECORD: {
|
case RecordingStatus::RECORD: {
|
||||||
update_ui_recording_stopped();
|
update_ui_recording_stopped();
|
||||||
if(exit_code != 0) {
|
on_stop_recording(exit_code);
|
||||||
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
|
|
||||||
show_notification("Failed to start/save recording", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RecordingStatus::STREAM: {
|
case RecordingStatus::STREAM: {
|
||||||
@@ -1231,6 +1297,20 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Overlay::on_stop_recording(int exit_code) {
|
||||||
|
if(exit_code == 0) {
|
||||||
|
if(config.record_config.save_video_in_game_folder) {
|
||||||
|
save_video_in_current_game_directory(record_filepath.c_str(), NotificationType::RECORD);
|
||||||
|
} else {
|
||||||
|
const std::string text = "Saved recording to '" + filepath_get_filename(record_filepath.c_str()) + "'";
|
||||||
|
show_notification(text.c_str(), 3.0, mgl::Color(255, 255, 255), get_color_theme().tint_color, NotificationType::RECORD);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code);
|
||||||
|
show_notification("Failed to start/save recording", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::RECORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Overlay::update_ui_recording_paused() {
|
void Overlay::update_ui_recording_paused() {
|
||||||
if(!visible || recording_status != RecordingStatus::RECORD)
|
if(!visible || recording_status != RecordingStatus::RECORD)
|
||||||
return;
|
return;
|
||||||
@@ -1390,6 +1470,11 @@ namespace gsr {
|
|||||||
args.push_back(audio_track.c_str());
|
args.push_back(audio_track.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(record_options.restore_portal_session) {
|
||||||
|
args.push_back("-restore-portal-session");
|
||||||
|
args.push_back("yes");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Overlay::on_press_save_replay() {
|
void Overlay::on_press_save_replay() {
|
||||||
@@ -1417,6 +1502,8 @@ namespace gsr {
|
|||||||
// window->close();
|
// window->close();
|
||||||
// usleep(1000 * 50); // 50 milliseconds
|
// usleep(1000 * 50); // 50 milliseconds
|
||||||
|
|
||||||
|
close_gpu_screen_recorder_output();
|
||||||
|
|
||||||
if(gpu_screen_recorder_process > 0) {
|
if(gpu_screen_recorder_process > 0) {
|
||||||
kill(gpu_screen_recorder_process, SIGINT);
|
kill(gpu_screen_recorder_process, SIGINT);
|
||||||
int status;
|
int status;
|
||||||
@@ -1451,7 +1538,9 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char region[64];
|
char region[64];
|
||||||
snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.record_area_width, (int)config.replay_config.record_options.record_area_height);
|
region[0] = '\0';
|
||||||
|
if(config.replay_config.record_options.record_area_option == "focused")
|
||||||
|
snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.record_area_width, (int)config.replay_config.record_options.record_area_height);
|
||||||
|
|
||||||
if(config.replay_config.record_options.record_area_option != "focused" && config.replay_config.record_options.change_video_resolution)
|
if(config.replay_config.record_options.record_area_option != "focused" && config.replay_config.record_options.change_video_resolution)
|
||||||
snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.video_width, (int)config.replay_config.record_options.video_height);
|
snprintf(region, sizeof(region), "%dx%d", (int)config.replay_config.record_options.video_width, (int)config.replay_config.record_options.video_height);
|
||||||
@@ -1473,14 +1562,9 @@ namespace gsr {
|
|||||||
|
|
||||||
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged);
|
add_common_gpu_screen_recorder_args(args, config.replay_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged);
|
||||||
|
|
||||||
setenv("GSR_SHOW_SAVED_NOTIFICATION", config.replay_config.show_replay_saved_notifications ? "1" : "0", true);
|
|
||||||
const std::string script_to_run_on_save = resources_path + (config.replay_config.save_video_in_game_folder ? "scripts/save-video-in-game-folder.sh" : "scripts/notify-saved-name.sh");
|
|
||||||
args.push_back("-sc");
|
|
||||||
args.push_back(script_to_run_on_save.c_str());
|
|
||||||
|
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|
||||||
gpu_screen_recorder_process = exec_program(args.data());
|
gpu_screen_recorder_process = exec_program(args.data(), &gpu_screen_recorder_process_output_fd);
|
||||||
if(gpu_screen_recorder_process == -1) {
|
if(gpu_screen_recorder_process == -1) {
|
||||||
// TODO: Show notification failed to start
|
// TODO: Show notification failed to start
|
||||||
} else {
|
} else {
|
||||||
@@ -1488,6 +1572,10 @@ namespace gsr {
|
|||||||
update_ui_replay_started();
|
update_ui_replay_started();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const int fdl = fcntl(gpu_screen_recorder_process_output_fd, F_GETFL);
|
||||||
|
fcntl(gpu_screen_recorder_process_output_fd, F_SETFL, fdl | O_NONBLOCK);
|
||||||
|
gpu_screen_recorder_process_output_file = fdopen(gpu_screen_recorder_process_output_fd, "r");
|
||||||
|
|
||||||
// TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video.
|
// TODO: Start recording after this notification has disappeared to make sure it doesn't show up in the video.
|
||||||
// Make clear to the user that the recording starts after the notification is gone.
|
// Make clear to the user that the recording starts after the notification is gone.
|
||||||
// Maybe have the option in notification to show timer until its getting hidden, then the notification can say:
|
// Maybe have the option in notification to show timer until its getting hidden, then the notification can say:
|
||||||
@@ -1525,17 +1613,22 @@ namespace gsr {
|
|||||||
if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) {
|
if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) {
|
||||||
perror("waitpid failed");
|
perror("waitpid failed");
|
||||||
/* Ignore... */
|
/* Ignore... */
|
||||||
|
} else {
|
||||||
|
int exit_code = -1;
|
||||||
|
if(WIFEXITED(status))
|
||||||
|
exit_code = WEXITSTATUS(status);
|
||||||
|
on_stop_recording(exit_code);
|
||||||
}
|
}
|
||||||
// window->set_visible(false);
|
|
||||||
// window->close();
|
|
||||||
// return;
|
|
||||||
//exit(0);
|
|
||||||
gpu_screen_recorder_process = -1;
|
gpu_screen_recorder_process = -1;
|
||||||
recording_status = RecordingStatus::NONE;
|
recording_status = RecordingStatus::NONE;
|
||||||
update_ui_recording_stopped();
|
update_ui_recording_stopped();
|
||||||
|
record_filepath.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record_filepath.clear();
|
||||||
|
|
||||||
// TODO: Validate input, fallback to valid values
|
// TODO: Validate input, fallback to valid values
|
||||||
const std::string fps = std::to_string(config.record_config.record_options.fps);
|
const std::string fps = std::to_string(config.record_config.record_options.fps);
|
||||||
const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate);
|
const std::string video_bitrate = std::to_string(config.record_config.record_options.video_bitrate);
|
||||||
@@ -1551,7 +1644,9 @@ namespace gsr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
char region[64];
|
char region[64];
|
||||||
snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.record_area_width, (int)config.record_config.record_options.record_area_height);
|
region[0] = '\0';
|
||||||
|
if(config.record_config.record_options.record_area_option == "focused")
|
||||||
|
snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.record_area_width, (int)config.record_config.record_options.record_area_height);
|
||||||
|
|
||||||
if(config.record_config.record_options.record_area_option != "focused" && config.record_config.record_options.change_video_resolution)
|
if(config.record_config.record_options.record_area_option != "focused" && config.record_config.record_options.change_video_resolution)
|
||||||
snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.video_width, (int)config.record_config.record_options.video_height);
|
snprintf(region, sizeof(region), "%dx%d", (int)config.record_config.record_options.video_width, (int)config.record_config.record_options.video_height);
|
||||||
@@ -1572,14 +1667,10 @@ namespace gsr {
|
|||||||
|
|
||||||
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged);
|
add_common_gpu_screen_recorder_args(args, config.record_config.record_options, audio_tracks, video_bitrate, region, audio_tracks_merged);
|
||||||
|
|
||||||
setenv("GSR_SHOW_SAVED_NOTIFICATION", config.record_config.show_video_saved_notifications ? "1" : "0", true);
|
|
||||||
const std::string script_to_run_on_save = resources_path + (config.record_config.save_video_in_game_folder ? "scripts/save-video-in-game-folder.sh" : "scripts/notify-saved-name.sh");
|
|
||||||
args.push_back("-sc");
|
|
||||||
args.push_back(script_to_run_on_save.c_str());
|
|
||||||
|
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|
||||||
gpu_screen_recorder_process = exec_program(args.data());
|
record_filepath = output_file;
|
||||||
|
gpu_screen_recorder_process = exec_program(args.data(), nullptr);
|
||||||
if(gpu_screen_recorder_process == -1) {
|
if(gpu_screen_recorder_process == -1) {
|
||||||
// TODO: Show notification failed to start
|
// TODO: Show notification failed to start
|
||||||
} else {
|
} else {
|
||||||
@@ -1685,7 +1776,9 @@ namespace gsr {
|
|||||||
const std::string url = streaming_get_url(config);
|
const std::string url = streaming_get_url(config);
|
||||||
|
|
||||||
char region[64];
|
char region[64];
|
||||||
snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.record_area_width, (int)config.streaming_config.record_options.record_area_height);
|
region[0] = '\0';
|
||||||
|
if(config.record_config.record_options.record_area_option == "focused")
|
||||||
|
snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.record_area_width, (int)config.streaming_config.record_options.record_area_height);
|
||||||
|
|
||||||
if(config.record_config.record_options.record_area_option != "focused" && config.streaming_config.record_options.change_video_resolution)
|
if(config.record_config.record_options.record_area_option != "focused" && config.streaming_config.record_options.change_video_resolution)
|
||||||
snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.video_width, (int)config.streaming_config.record_options.video_height);
|
snprintf(region, sizeof(region), "%dx%d", (int)config.streaming_config.record_options.video_width, (int)config.streaming_config.record_options.video_height);
|
||||||
@@ -1708,7 +1801,7 @@ namespace gsr {
|
|||||||
|
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|
||||||
gpu_screen_recorder_process = exec_program(args.data());
|
gpu_screen_recorder_process = exec_program(args.data(), nullptr);
|
||||||
if(gpu_screen_recorder_process == -1) {
|
if(gpu_screen_recorder_process == -1) {
|
||||||
// TODO: Show notification failed to start
|
// TODO: Show notification failed to start
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define PIPE_READ 0
|
||||||
|
#define PIPE_WRITE 1
|
||||||
|
|
||||||
namespace gsr {
|
namespace gsr {
|
||||||
static void debug_print_args(const char **args) {
|
static void debug_print_args(const char **args) {
|
||||||
fprintf(stderr, "gsr-ui info: running command:");
|
fprintf(stderr, "gsr-ui info: running command:");
|
||||||
@@ -51,22 +54,40 @@ namespace gsr {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pid_t exec_program(const char **args) {
|
pid_t exec_program(const char **args, int *read_fd) {
|
||||||
|
if(read_fd)
|
||||||
|
*read_fd = -1;
|
||||||
|
|
||||||
/* 1 argument */
|
/* 1 argument */
|
||||||
if(args[0] == nullptr)
|
if(args[0] == nullptr)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
int fds[2] = {-1, -1};
|
||||||
|
if(pipe(fds) == -1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
debug_print_args(args);
|
debug_print_args(args);
|
||||||
|
|
||||||
pid_t pid = vfork();
|
pid_t pid = vfork();
|
||||||
if(pid == -1) {
|
if(pid == -1) {
|
||||||
|
close(fds[PIPE_READ]);
|
||||||
|
close(fds[PIPE_WRITE]);
|
||||||
perror("Failed to vfork");
|
perror("Failed to vfork");
|
||||||
return -1;
|
return -1;
|
||||||
} else if(pid == 0) { /* child */
|
} else if(pid == 0) { /* child */
|
||||||
|
dup2(fds[PIPE_WRITE], STDOUT_FILENO);
|
||||||
|
close(fds[PIPE_READ]);
|
||||||
|
close(fds[PIPE_WRITE]);
|
||||||
|
|
||||||
execvp(args[0], (char* const*)args);
|
execvp(args[0], (char* const*)args);
|
||||||
perror("execvp");
|
perror("execvp");
|
||||||
_exit(127);
|
_exit(127);
|
||||||
} else { /* parent */
|
} else { /* parent */
|
||||||
|
close(fds[PIPE_WRITE]);
|
||||||
|
if(read_fd)
|
||||||
|
*read_fd = fds[PIPE_READ];
|
||||||
|
else
|
||||||
|
close(fds[PIPE_READ]);
|
||||||
return pid;
|
return pid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
150
src/WindowUtils.cpp
Normal file
150
src/WindowUtils.cpp
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#include "../include/WindowUtils.hpp"
|
||||||
|
|
||||||
|
#include <X11/Xlib.h>
|
||||||
|
#include <X11/Xatom.h>
|
||||||
|
#include <X11/Xutil.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
namespace gsr {
|
||||||
|
static bool window_has_atom(Display *dpy, Window window, Atom atom) {
|
||||||
|
Atom type;
|
||||||
|
unsigned long len, bytes_left;
|
||||||
|
int format;
|
||||||
|
unsigned char *properties = NULL;
|
||||||
|
if(XGetWindowProperty(dpy, window, atom, 0, 1024, False, AnyPropertyType, &type, &format, &len, &bytes_left, &properties) < Success)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if(properties)
|
||||||
|
XFree(properties);
|
||||||
|
|
||||||
|
return type != None;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool window_is_user_program(Display *dpy, Window window) {
|
||||||
|
const Atom net_wm_state_atom = XInternAtom(dpy, "_NET_WM_STATE", False);
|
||||||
|
const Atom wm_state_atom = XInternAtom(dpy, "WM_STATE", False);
|
||||||
|
return window_has_atom(dpy, window, net_wm_state_atom) || window_has_atom(dpy, window, wm_state_atom);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Window get_window_at_cursor_position(Display *dpy) {
|
||||||
|
Window root_window = None;
|
||||||
|
Window window = None;
|
||||||
|
int dummy_i;
|
||||||
|
unsigned int dummy_u;
|
||||||
|
int cursor_pos_x = 0;
|
||||||
|
int cursor_pos_y = 0;
|
||||||
|
XQueryPointer(dpy, DefaultRootWindow(dpy), &root_window, &window, &dummy_i, &dummy_i, &cursor_pos_x, &cursor_pos_y, &dummy_u);
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Window get_focused_window(Display *dpy, WindowCaptureType cap_type) {
|
||||||
|
const Atom net_active_window_atom = XInternAtom(dpy, "_NET_ACTIVE_WINDOW", False);
|
||||||
|
Window focused_window = None;
|
||||||
|
|
||||||
|
if(cap_type == WindowCaptureType::FOCUSED) {
|
||||||
|
// Atom type = None;
|
||||||
|
// int format = 0;
|
||||||
|
// unsigned long num_items = 0;
|
||||||
|
// unsigned long bytes_left = 0;
|
||||||
|
// unsigned char *data = NULL;
|
||||||
|
// XGetWindowProperty(dpy, DefaultRootWindow(dpy), net_active_window_atom, 0, 1, False, XA_WINDOW, &type, &format, &num_items, &bytes_left, &data);
|
||||||
|
|
||||||
|
// fprintf(stderr, "focused window: %p\n", (void*)data);
|
||||||
|
|
||||||
|
// if(type == XA_WINDOW && num_items == 1 && data)
|
||||||
|
// return *(Window*)data;
|
||||||
|
|
||||||
|
int revert_to = 0;
|
||||||
|
XGetInputFocus(dpy, &focused_window, &revert_to);
|
||||||
|
if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window))
|
||||||
|
return focused_window;
|
||||||
|
}
|
||||||
|
|
||||||
|
focused_window = get_window_at_cursor_position(dpy);
|
||||||
|
if(focused_window && focused_window != DefaultRootWindow(dpy) && window_is_user_program(dpy, focused_window))
|
||||||
|
return focused_window;
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* get_window_title(Display *dpy, Window window) {
|
||||||
|
const Atom net_wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
|
||||||
|
const Atom wm_name_atom = XInternAtom(dpy, "_NET_WM_NAME", False);
|
||||||
|
const Atom utf8_string_atom = XInternAtom(dpy, "UTF8_STRING", False);
|
||||||
|
|
||||||
|
Atom type = None;
|
||||||
|
int format = 0;
|
||||||
|
unsigned long num_items = 0;
|
||||||
|
unsigned long bytes_left = 0;
|
||||||
|
unsigned char *data = NULL;
|
||||||
|
XGetWindowProperty(dpy, window, net_wm_name_atom, 0, 1024, False, utf8_string_atom, &type, &format, &num_items, &bytes_left, &data);
|
||||||
|
|
||||||
|
if(type == utf8_string_atom && format == 8 && data)
|
||||||
|
return (char*)data;
|
||||||
|
|
||||||
|
type = None;
|
||||||
|
format = 0;
|
||||||
|
num_items = 0;
|
||||||
|
bytes_left = 0;
|
||||||
|
data = NULL;
|
||||||
|
XGetWindowProperty(dpy, window, wm_name_atom, 0, 1024, False, 0, &type, &format, &num_items, &bytes_left, &data);
|
||||||
|
|
||||||
|
if((type == XA_STRING || type == utf8_string_atom) && data)
|
||||||
|
return (char*)data;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* strip(const char *str, int *len) {
|
||||||
|
int str_len = strlen(str);
|
||||||
|
for(int i = 0; i < str_len; ++i) {
|
||||||
|
if(str[i] != ' ') {
|
||||||
|
str += i;
|
||||||
|
str_len -= i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = str_len - 1; i >= 0; --i) {
|
||||||
|
if(str[i] != ' ') {
|
||||||
|
str_len = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*len = str_len;
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string string_string(const char *str) {
|
||||||
|
int len = 0;
|
||||||
|
str = strip(str, &len);
|
||||||
|
return std::string(str, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_focused_window_name(Display *dpy, WindowCaptureType window_capture_type) {
|
||||||
|
std::string result;
|
||||||
|
const Window focused_window = get_focused_window(dpy, window_capture_type);
|
||||||
|
if(focused_window == None)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
// Window title is not always ideal (for example for a browser), but for games its pretty much required
|
||||||
|
char *window_title = get_window_title(dpy, focused_window);
|
||||||
|
if(window_title) {
|
||||||
|
result = string_string(window_title);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
XClassHint class_hint = {nullptr, nullptr};
|
||||||
|
XGetClassHint(dpy, focused_window, &class_hint);
|
||||||
|
if(class_hint.res_class) {
|
||||||
|
result = string_string(class_hint.res_class);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user