From 59dfd87c21026ef4dc713c3e0648cfa89d534557 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 27 Oct 2024 13:09:06 +0100 Subject: Add hotkeys for replay and streaming, finish everything --- src/Config.cpp | 23 ++- src/Overlay.cpp | 519 +++++++++++++++++++++++++++++++++++++++-------- src/Theme.cpp | 3 + src/gui/Entry.cpp | 7 +- src/gui/SettingsPage.cpp | 100 +++++---- src/main.cpp | 66 +++--- 6 files changed, 556 insertions(+), 162 deletions(-) (limited to 'src') diff --git a/src/Config.cpp b/src/Config.cpp index 0e256a9..4f68fa1 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -1,5 +1,6 @@ #include "../include/Config.hpp" #include "../include/Utils.hpp" +#include "../include/GsrInfo.hpp" #include #include #include @@ -12,11 +13,23 @@ #define CONFIG_FILE_VERSION 1 namespace gsr { - Config::Config() { + Config::Config(const GsrInfo &gsr_info) { const std::string default_save_directory = get_videos_dir(); + streaming_config.record_options.video_quality = "custom"; + streaming_config.record_options.audio_tracks.push_back("default_output"); + record_config.save_directory = default_save_directory; + record_config.record_options.audio_tracks.push_back("default_output"); + replay_config.save_directory = default_save_directory; + replay_config.record_options.audio_tracks.push_back("default_output"); + + if(!gsr_info.supported_capture_options.monitors.empty()) { + streaming_config.record_options.record_area_option = gsr_info.supported_capture_options.monitors.front().name; + record_config.record_options.record_area_option = gsr_info.supported_capture_options.monitors.front().name; + replay_config.record_options.record_area_option = gsr_info.supported_capture_options.monitors.front().name; + } } static std::optional parse_key_value(std::string_view line) { @@ -117,7 +130,7 @@ namespace gsr { }; } - std::optional read_config() { + std::optional read_config(const GsrInfo &gsr_info) { std::optional config; const std::string config_path = get_config_dir() + "/config_ui"; @@ -127,7 +140,11 @@ namespace gsr { return config; } - config = Config(); + config = Config(gsr_info); + config->streaming_config.record_options.audio_tracks.clear(); + config->record_config.record_options.audio_tracks.clear(); + config->replay_config.record_options.audio_tracks.clear(); + auto config_options = get_config_options(config.value()); string_split_char(file_content, '\n', [&](std::string_view line) { diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 96e389b..4603564 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -187,14 +187,15 @@ namespace gsr { Overlay::Overlay(mgl::Window &window, std::string resources_path, GsrInfo gsr_info, egl_functions egl_funcs, mgl::Color bg_color) : window(window), resources_path(std::move(resources_path)), - gsr_info(std::move(gsr_info)), + gsr_info(gsr_info), egl_funcs(egl_funcs), bg_color(bg_color), bg_screenshot_overlay({0.0f, 0.0f}), top_bar_background({0.0f, 0.0f}), top_bar_text("GPU Screen Recorder", get_theme().top_bar_font), logo_sprite(&get_theme().logo_texture), - close_button_widget({0.0f, 0.0f}) + close_button_widget({0.0f, 0.0f}), + config(gsr_info) { memset(&window_texture, 0, sizeof(window_texture)); @@ -206,6 +207,10 @@ namespace gsr { key_bindings[0].callback = [this]() { page_stack.pop(); }; + + std::optional new_config = read_config(gsr_info); + if(new_config) + config = std::move(new_config.value()); } Overlay::~Overlay() { @@ -231,7 +236,7 @@ namespace gsr { gpu_screen_recorder_process = -1; // TODO: Show this with a slight delay to make sure it doesn't show up in the video - if(config->record_config.show_video_saved_notifications) + if(recording_status == RecordingStatus::RECORD && config.record_config.show_video_saved_notifications) show_notification("Recording has been saved", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::RECORD); } } @@ -312,7 +317,6 @@ namespace gsr { update_compositor_texture(focused_monitor); audio_devices = get_audio_devices(); - config = read_config(); bg_screenshot_overlay = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height)); top_bar_background = mgl::Rectangle(mgl::vec2f(get_theme().window_width, get_theme().window_height*0.06f).floor()); @@ -351,9 +355,20 @@ namespace gsr { mgl::vec2f(button_width, button_height)); replay_dropdown_button_ptr = button.get(); button->add_item("Turn on", "start", "Alt+Shift+F10"); + button->add_item("Save", "save", "Alt+F10"); button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); - button->on_click = std::bind(&Overlay::on_press_start_replay, this, std::placeholders::_1); + button->set_item_icon("save", &get_theme().save_texture); + button->on_click = [this](const std::string &id) { + if(id == "settings") { + auto replay_settings_page = std::make_unique(SettingsPage::Type::REPLAY, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(replay_settings_page)); + } else if(id == "save") { + on_press_save_replay(); + } else if(id == "start") { + on_press_start_replay(); + } + }; main_buttons_list->add_widget(std::move(button)); } { @@ -365,7 +380,16 @@ namespace gsr { button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); button->set_item_icon("pause", &get_theme().pause_texture); - button->on_click = std::bind(&Overlay::on_press_start_record, this, std::placeholders::_1); + button->on_click = [this](const std::string &id) { + if(id == "settings") { + auto record_settings_page = std::make_unique(SettingsPage::Type::RECORD, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(record_settings_page)); + } else if(id == "pause") { + toggle_pause(); + } else if(id == "start") { + on_press_start_record(); + } + }; main_buttons_list->add_widget(std::move(button)); } { @@ -375,7 +399,14 @@ namespace gsr { button->add_item("Start", "start", "Alt+F8"); button->add_item("Settings", "settings"); button->set_item_icon("start", &get_theme().play_texture); - button->on_click = std::bind(&Overlay::on_press_start_stream, this, std::placeholders::_1); + button->on_click = [this](const std::string &id) { + if(id == "settings") { + auto stream_settings_page = std::make_unique(SettingsPage::Type::STREAM, gsr_info, audio_devices, config, &page_stack); + page_stack.push(std::move(stream_settings_page)); + } else if(id == "start") { + on_press_start_stream(); + } + }; main_buttons_list->add_widget(std::move(button)); } @@ -427,11 +458,11 @@ namespace gsr { // TODO: Hmm, these dont work in owlboy. Maybe owlboy uses xi2 and that breaks this (does it?). // Remove these grabs when debugging with a debugger, or your X11 session will appear frozen - XGrabPointer(display, window.get_system_handle(), True, - ButtonPressMask | ButtonReleaseMask | PointerMotionMask | - Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | - ButtonMotionMask, - GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); + // XGrabPointer(display, window.get_system_handle(), True, + // ButtonPressMask | ButtonReleaseMask | PointerMotionMask | + // Button1MotionMask | Button2MotionMask | Button3MotionMask | Button4MotionMask | Button5MotionMask | + // ButtonMotionMask, + // GrabModeAsync, GrabModeAsync, None, default_cursor, CurrentTime); // TODO: This breaks global hotkeys //XGrabKeyboard(display, window.get_system_handle(), True, GrabModeAsync, GrabModeAsync, CurrentTime); @@ -448,8 +479,21 @@ namespace gsr { event.mouse_move.y = window.get_mouse_position().y; on_event(event, window); - if(gpu_screen_recorder_process > 0 && recording_status == RecordingStatus::RECORD) - update_ui_recording_started(); + if(gpu_screen_recorder_process > 0) { + switch(recording_status) { + case RecordingStatus::NONE: + break; + case RecordingStatus::REPLAY: + update_ui_replay_started(); + break; + case RecordingStatus::RECORD: + update_ui_recording_started(); + break; + case RecordingStatus::STREAM: + update_ui_streaming_started(); + break; + } + } if(paused) update_ui_recording_paused(); @@ -481,7 +525,7 @@ namespace gsr { } void Overlay::toggle_record() { - on_press_start_record("start"); + on_press_start_record(); } void Overlay::toggle_pause() { @@ -500,6 +544,18 @@ namespace gsr { paused = !paused; } + void Overlay::toggle_stream() { + on_press_start_stream(); + } + + void Overlay::toggle_replay() { + on_press_start_replay(); + } + + void Overlay::save_replay() { + on_press_save_replay(); + } + static const char* notification_type_to_string(NotificationType notification_type) { switch(notification_type) { case NotificationType::NONE: return nullptr; @@ -533,7 +589,7 @@ namespace gsr { if(notification_process > 0) { kill(notification_process, SIGKILL); int status = 0; - waitpid(gpu_screen_recorder_process, &status, 0); + waitpid(notification_process, &status, 0); } notification_process = exec_program(notification_args); @@ -548,7 +604,7 @@ namespace gsr { return; int status; - if(waitpid(gpu_screen_recorder_process, &status, WNOHANG) == 0) { + if(waitpid(notification_process, &status, WNOHANG) == 0) { // Still running return; } @@ -583,17 +639,46 @@ namespace gsr { exit_code = WEXITSTATUS(status); } + switch(recording_status) { + case RecordingStatus::NONE: + break; + case RecordingStatus::REPLAY: { + update_ui_replay_stopped(); + if(exit_code == 0) { + if(config.replay_config.show_replay_stopped_notifications) + show_notification("Replay stopped", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::REPLAY); + } else { + fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); + show_notification("Replay stopped because of an error", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::REPLAY); + } + break; + } + case RecordingStatus::RECORD: { + update_ui_recording_stopped(); + if(exit_code == 0) { + if(config.record_config.show_video_saved_notifications) + show_notification("Recording has been saved", 3.0, mgl::Color(255, 255, 255), get_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); + } + break; + } + case RecordingStatus::STREAM: { + update_ui_streaming_stopped(); + if(exit_code == 0) { + if(config.streaming_config.show_streaming_stopped_notifications) + show_notification("Streaming has stopped", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::STREAM); + } else { + fprintf(stderr, "Warning: gpu-screen-recorder (%d) exited with exit status %d\n", (int)gpu_screen_recorder_process, exit_code); + show_notification("Streaming stopped because of an error", 3.0, mgl::Color(255, 0, 0), mgl::Color(255, 0, 0), NotificationType::STREAM); + } + break; + } + } + gpu_screen_recorder_process = -1; recording_status = RecordingStatus::NONE; - update_ui_recording_stopped(); - - if(exit_code == 0) { - if(config->record_config.show_video_saved_notifications) - show_notification("Recording has been saved", 3.0, mgl::Color(255, 255, 255), get_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() { @@ -634,25 +719,44 @@ namespace gsr { record_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); } - void Overlay::on_press_start_replay(const std::string &id) { - if(id == "settings") { - auto replay_settings_page = std::make_unique(SettingsPage::Type::REPLAY, gsr_info, audio_devices, config, &page_stack); - page_stack.push(std::move(replay_settings_page)); + void Overlay::update_ui_streaming_started() { + if(!visible) return; - } - /* - char window_to_record_str[32]; - snprintf(window_to_record_str, sizeof(window_to_record_str), "%ld", target_window); - const char *args[] = { - "gpu-screen-recorder", "-w", window_to_record_str, - "-c", "mp4", - "-f", "60", - "-o", "/home/dec05eba/Videos/gpu-screen-recorder.mp4", - nullptr - }; - exec_program_daemonized(args); - */ + stream_dropdown_button_ptr->set_item_label("start", "Stop"); + stream_dropdown_button_ptr->set_activated(true); + stream_dropdown_button_ptr->set_description("Streaming"); + stream_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture); + } + + void Overlay::update_ui_streaming_stopped() { + if(!visible) + return; + + stream_dropdown_button_ptr->set_item_label("start", "Start"); + stream_dropdown_button_ptr->set_activated(false); + stream_dropdown_button_ptr->set_description("Not streaming"); + stream_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); + } + + void Overlay::update_ui_replay_started() { + if(!visible) + return; + + replay_dropdown_button_ptr->set_item_label("start", "Turn off"); + replay_dropdown_button_ptr->set_activated(true); + replay_dropdown_button_ptr->set_description("On"); + replay_dropdown_button_ptr->set_item_icon("start", &get_theme().stop_texture); + } + + void Overlay::update_ui_replay_stopped() { + if(!visible) + return; + + replay_dropdown_button_ptr->set_item_label("start", "Turn on"); + replay_dropdown_button_ptr->set_activated(false); + replay_dropdown_button_ptr->set_description("Off"); + replay_dropdown_button_ptr->set_item_icon("start", &get_theme().play_texture); } static std::string get_date_str() { @@ -684,25 +788,138 @@ namespace gsr { return result; } - void Overlay::on_press_start_record(const std::string &id) { - audio_devices = get_audio_devices(); - - if(id == "settings") { - auto record_settings_page = std::make_unique(SettingsPage::Type::RECORD, gsr_info, audio_devices, config, &page_stack); - page_stack.push(std::move(record_settings_page)); + void Overlay::on_press_save_replay() { + if(recording_status != RecordingStatus::REPLAY || gpu_screen_recorder_process <= 0) return; + + kill(gpu_screen_recorder_process, SIGUSR1); + if(config.replay_config.show_replay_saved_notifications) + show_notification("Replay saved", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::REPLAY); + } + + void Overlay::on_press_start_replay() { + switch(recording_status) { + case RecordingStatus::NONE: + case RecordingStatus::REPLAY: + break; + case RecordingStatus::RECORD: + show_notification("Unable to start replay when recording.\nStop recording before starting replay", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::RECORD); + return; + case RecordingStatus::STREAM: + show_notification("Unable to start replay when streaming.\nStop streaming before starting replay", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::STREAM); + return; } - if(id == "pause") { - toggle_pause(); + paused = false; + + // window.close(); + // usleep(1000 * 50); // 50 milliseconds + + if(gpu_screen_recorder_process > 0) { + kill(gpu_screen_recorder_process, SIGINT); + int status; + if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { + perror("waitpid failed"); + /* Ignore... */ + } + + gpu_screen_recorder_process = -1; + recording_status = RecordingStatus::NONE; + update_ui_replay_stopped(); + + // TODO: Show this with a slight delay to make sure it doesn't show up in the video + if(config.replay_config.show_replay_stopped_notifications) + show_notification("Replay stopped", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::REPLAY); return; } - if(id != "start") - return; + audio_devices = get_audio_devices(); + + // TODO: Validate input, fallback to valid values + const std::string fps = std::to_string(config.replay_config.record_options.fps); + const std::string video_bitrate = std::to_string(config.replay_config.record_options.video_bitrate); + const std::string output_file = config.replay_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.replay_config.container.c_str()); + const std::string audio_tracks_merged = merge_audio_tracks(config.replay_config.record_options.audio_tracks); + const std::string framerate_mode = config.replay_config.record_options.framerate_mode == "auto" ? "vfr" : config.replay_config.record_options.framerate_mode; + + 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); + + 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); + + std::vector args = { + "gpu-screen-recorder", "-w", config.replay_config.record_options.record_area_option.c_str(), + "-c", config.replay_config.container.c_str(), + "-ac", config.replay_config.record_options.audio_codec.c_str(), + "-cursor", config.replay_config.record_options.record_cursor ? "yes" : "no", + "-cr", config.replay_config.record_options.color_range.c_str(), + "-fm", framerate_mode.c_str(), + "-k", config.replay_config.record_options.video_codec.c_str(), + "-f", fps.c_str(), + "-o", output_file.c_str() + }; + + if(config.replay_config.record_options.video_quality == "custom") { + args.push_back("-bm"); + args.push_back("cbr"); + args.push_back("-q"); + args.push_back(video_bitrate.c_str()); + } else { + args.push_back("-q"); + args.push_back(config.replay_config.record_options.video_quality.c_str()); + } + + if(config.replay_config.record_options.record_area_option == "focused" || config.replay_config.record_options.change_video_resolution) { + args.push_back("-s"); + args.push_back(region); + } + + if(config.replay_config.record_options.merge_audio_tracks) { + args.push_back("-a"); + args.push_back(audio_tracks_merged.c_str()); + } else { + for(const std::string &audio_track : config.replay_config.record_options.audio_tracks) { + args.push_back("-a"); + args.push_back(audio_track.c_str()); + } + } + + args.push_back(nullptr); + + gpu_screen_recorder_process = exec_program(args.data()); + if(gpu_screen_recorder_process == -1) { + // TODO: Show notification failed to start + } else { + recording_status = RecordingStatus::REPLAY; + update_ui_replay_started(); + } + + // 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. + // Maybe have the option in notification to show timer until its getting hidden, then the notification can say: + // Starting recording in 3... + // 2... + // 1... + // TODO: Do not run this is a daemon. Instead get the pid and when launching another notification close the current notification + // program and start another one. This can also be used to check when the notification has finished by checking with waitpid NOWAIT + // to see when the program has exit. + if(config.replay_config.show_replay_started_notifications) + show_notification("Replay has started", 3.0, get_theme().tint_color, get_theme().tint_color, NotificationType::REPLAY); + } - if(!config) - config = Config(); + void Overlay::on_press_start_record() { + switch(recording_status) { + case RecordingStatus::NONE: + case RecordingStatus::RECORD: + break; + case RecordingStatus::REPLAY: + show_notification("Unable to start recording when replay is turned on.\nTurn off replay before starting recording", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::REPLAY); + return; + case RecordingStatus::STREAM: + show_notification("Unable to start recording when streaming.\nStop streaming before starting recording", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::STREAM); + return; + } paused = false; @@ -725,56 +942,58 @@ namespace gsr { update_ui_recording_stopped(); // TODO: Show this with a slight delay to make sure it doesn't show up in the video - if(config->record_config.show_video_saved_notifications) + if(config.record_config.show_video_saved_notifications) show_notification("Recording has been saved", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::RECORD); return; } + audio_devices = get_audio_devices(); + // TODO: Validate input, fallback to valid values - 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 output_file = config->record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config->record_config.container.c_str()); - const std::string audio_tracks_merged = merge_audio_tracks(config->record_config.record_options.audio_tracks); - const std::string framerate_mode = config->record_config.record_options.framerate_mode == "auto" ? "vfr" : config->record_config.record_options.framerate_mode; + 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 output_file = config.record_config.save_directory + "/Video_" + get_date_str() + "." + container_to_file_extension(config.record_config.container.c_str()); + const std::string audio_tracks_merged = merge_audio_tracks(config.record_config.record_options.audio_tracks); + const std::string framerate_mode = config.record_config.record_options.framerate_mode == "auto" ? "vfr" : config.record_config.record_options.framerate_mode; 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); + 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) - snprintf(region, sizeof(region), "%dx%d", (int)config->record_config.record_options.video_width, (int)config->record_config.record_options.video_height); + 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); std::vector args = { - "gpu-screen-recorder", "-w", config->record_config.record_options.record_area_option.c_str(), - "-c", config->record_config.container.c_str(), - "-ac", config->record_config.record_options.audio_codec.c_str(), - "-cursor", config->record_config.record_options.record_cursor ? "yes" : "no", - "-cr", config->record_config.record_options.color_range.c_str(), + "gpu-screen-recorder", "-w", config.record_config.record_options.record_area_option.c_str(), + "-c", config.record_config.container.c_str(), + "-ac", config.record_config.record_options.audio_codec.c_str(), + "-cursor", config.record_config.record_options.record_cursor ? "yes" : "no", + "-cr", config.record_config.record_options.color_range.c_str(), "-fm", framerate_mode.c_str(), - "-k", config->record_config.record_options.video_codec.c_str(), + "-k", config.record_config.record_options.video_codec.c_str(), "-f", fps.c_str(), "-o", output_file.c_str() }; - if(config->record_config.record_options.video_quality == "custom") { + if(config.record_config.record_options.video_quality == "custom") { args.push_back("-bm"); args.push_back("cbr"); args.push_back("-q"); args.push_back(video_bitrate.c_str()); } else { args.push_back("-q"); - args.push_back(config->record_config.record_options.video_quality.c_str()); + args.push_back(config.record_config.record_options.video_quality.c_str()); } - 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) { args.push_back("-s"); args.push_back(region); } - if(config->record_config.record_options.merge_audio_tracks) { + if(config.record_config.record_options.merge_audio_tracks) { args.push_back("-a"); args.push_back(audio_tracks_merged.c_str()); } else { - for(const std::string &audio_track : config->record_config.record_options.audio_tracks) { + for(const std::string &audio_track : config.record_config.record_options.audio_tracks) { args.push_back("-a"); args.push_back(audio_track.c_str()); } @@ -799,7 +1018,7 @@ namespace gsr { // TODO: Do not run this is a daemon. Instead get the pid and when launching another notification close the current notification // program and start another one. This can also be used to check when the notification has finished by checking with waitpid NOWAIT // to see when the program has exit. - if(config->record_config.show_recording_started_notifications) + if(config.record_config.show_recording_started_notifications) show_notification("Recording has started", 3.0, get_theme().tint_color, get_theme().tint_color, NotificationType::RECORD); //exit(0); // window.set_visible(false); @@ -809,12 +1028,152 @@ namespace gsr { // "Recording has started" 3.0 ./images/record.png 76b900 } - void Overlay::on_press_start_stream(const std::string &id) { - if(id == "settings") { - auto stream_settings_page = std::make_unique(SettingsPage::Type::STREAM, gsr_info, audio_devices, config, &page_stack); - page_stack.push(std::move(stream_settings_page)); + static std::string streaming_get_url(const Config &config) { + std::string url; + if(config.streaming_config.streaming_service == "twitch") { + url += "rtmp://live.twitch.tv/app/"; + url += config.streaming_config.twitch.stream_key; + } else if(config.streaming_config.streaming_service == "youtube") { + url += "rtmp://a.rtmp.youtube.com/live2/"; + url += config.streaming_config.youtube.stream_key; + } else if(config.streaming_config.streaming_service == "custom") { + url = config.streaming_config.custom.url; + if(url.size() >= 7 && strncmp(url.c_str(), "rtmp://", 7) == 0) + {} + else if(url.size() >= 8 && strncmp(url.c_str(), "rtmps://", 8) == 0) + {} + else if(url.size() >= 7 && strncmp(url.c_str(), "rtsp://", 7) == 0) + {} + else if(url.size() >= 6 && strncmp(url.c_str(), "srt://", 6) == 0) + {} + else if(url.size() >= 7 && strncmp(url.c_str(), "http://", 7) == 0) + {} + else if(url.size() >= 8 && strncmp(url.c_str(), "https://", 8) == 0) + {} + else if(url.size() >= 6 && strncmp(url.c_str(), "tcp://", 6) == 0) + {} + else if(url.size() >= 6 && strncmp(url.c_str(), "udp://", 6) == 0) + {} + else + url = "rtmp://" + url; + } + return url; + } + + void Overlay::on_press_start_stream() { + switch(recording_status) { + case RecordingStatus::NONE: + case RecordingStatus::STREAM: + break; + case RecordingStatus::REPLAY: + show_notification("Unable to start streaming when replay is turned on.\nTurn off replay before starting streaming", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::REPLAY); + return; + case RecordingStatus::RECORD: + show_notification("Unable to start streaming when recording.\nStop recording before starting streaming", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::RECORD); + return; + } + + paused = false; + + // window.close(); + // usleep(1000 * 50); // 50 milliseconds + + if(gpu_screen_recorder_process > 0) { + kill(gpu_screen_recorder_process, SIGINT); + int status; + if(waitpid(gpu_screen_recorder_process, &status, 0) == -1) { + perror("waitpid failed"); + /* Ignore... */ + } + + gpu_screen_recorder_process = -1; + recording_status = RecordingStatus::NONE; + update_ui_streaming_stopped(); + + // TODO: Show this with a slight delay to make sure it doesn't show up in the video + if(config.streaming_config.show_streaming_stopped_notifications) + show_notification("Streaming has stopped", 3.0, mgl::Color(255, 255, 255), get_theme().tint_color, NotificationType::STREAM); return; } + + audio_devices = get_audio_devices(); + + // TODO: Validate input, fallback to valid values + const std::string fps = std::to_string(config.streaming_config.record_options.fps); + const std::string video_bitrate = std::to_string(config.streaming_config.record_options.video_bitrate); + const std::string audio_tracks_merged = merge_audio_tracks(config.streaming_config.record_options.audio_tracks); + const std::string framerate_mode = config.streaming_config.record_options.framerate_mode == "auto" ? "vfr" : config.streaming_config.record_options.framerate_mode; + + std::string container = "flv"; + if(config.streaming_config.streaming_service == "custom") + container = config.streaming_config.custom.container; + + const std::string url = streaming_get_url(config); + + 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); + + 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); + + std::vector args = { + "gpu-screen-recorder", "-w", config.streaming_config.record_options.record_area_option.c_str(), + "-c", container.c_str(), + "-ac", config.streaming_config.record_options.audio_codec.c_str(), + "-cursor", config.streaming_config.record_options.record_cursor ? "yes" : "no", + "-cr", config.streaming_config.record_options.color_range.c_str(), + "-fm", framerate_mode.c_str(), + "-k", config.streaming_config.record_options.video_codec.c_str(), + "-f", fps.c_str(), + "-o", url.c_str() + }; + + if(config.streaming_config.record_options.video_quality == "custom") { + args.push_back("-bm"); + args.push_back("cbr"); + args.push_back("-q"); + args.push_back(video_bitrate.c_str()); + } else { + args.push_back("-q"); + args.push_back(config.streaming_config.record_options.video_quality.c_str()); + } + + if(config.streaming_config.record_options.record_area_option == "focused" || config.streaming_config.record_options.change_video_resolution) { + args.push_back("-s"); + args.push_back(region); + } + + if(config.streaming_config.record_options.merge_audio_tracks) { + args.push_back("-a"); + args.push_back(audio_tracks_merged.c_str()); + } else { + for(const std::string &audio_track : config.streaming_config.record_options.audio_tracks) { + args.push_back("-a"); + args.push_back(audio_track.c_str()); + } + } + + args.push_back(nullptr); + + gpu_screen_recorder_process = exec_program(args.data()); + if(gpu_screen_recorder_process == -1) { + // TODO: Show notification failed to start + } else { + recording_status = RecordingStatus::STREAM; + update_ui_streaming_started(); + } + + // 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. + // Maybe have the option in notification to show timer until its getting hidden, then the notification can say: + // Starting recording in 3... + // 2... + // 1... + // TODO: Do not run this is a daemon. Instead get the pid and when launching another notification close the current notification + // program and start another one. This can also be used to check when the notification has finished by checking with waitpid NOWAIT + // to see when the program has exit. + if(config.streaming_config.show_streaming_started_notifications) + show_notification("Streaming has started", 3.0, get_theme().tint_color, get_theme().tint_color, NotificationType::STREAM); } bool Overlay::update_compositor_texture(const mgl_monitor *monitor) { diff --git a/src/Theme.cpp b/src/Theme.cpp index 3c8b981..8865796 100644 --- a/src/Theme.cpp +++ b/src/Theme.cpp @@ -97,6 +97,9 @@ namespace gsr { if(!theme->pause_texture.load_from_file((resources_path + "images/pause.png").c_str())) goto error; + if(!theme->save_texture.load_from_file((resources_path + "images/save.png").c_str())) + goto error; + return true; error: diff --git a/src/gui/Entry.cpp b/src/gui/Entry.cpp index 0bea60e..1cb8d16 100644 --- a/src/gui/Entry.cpp +++ b/src/gui/Entry.cpp @@ -21,7 +21,7 @@ namespace gsr { set_text(text); } - bool Entry::on_event(mgl::Event &event, mgl::Window&, mgl::vec2f offset) { + bool Entry::on_event(mgl::Event &event, mgl::Window &window, mgl::vec2f offset) { if(!visible) return true; @@ -33,6 +33,11 @@ namespace gsr { const size_t prev_index = mgl::utf8_get_start_of_codepoint((const unsigned char*)str.c_str(), str.size(), str.size()); str.erase(prev_index, std::string::npos); set_text(std::move(str)); + } else if(event.key.code == mgl::Keyboard::V && event.key.control) { + std::string clipboard_text = window.get_clipboard_string(); + std::string str = text.get_string(); + str += clipboard_text; + set_text(std::move(str)); } } else if(event.type == mgl::Event::TextEntered && selected && event.text.codepoint >= 32) { std::string str = text.get_string(); diff --git a/src/gui/SettingsPage.cpp b/src/gui/SettingsPage.cpp index 11571dc..9c755cc 100644 --- a/src/gui/SettingsPage.cpp +++ b/src/gui/SettingsPage.cpp @@ -14,7 +14,7 @@ #include namespace gsr { - SettingsPage::SettingsPage(Type type, const GsrInfo &gsr_info, std::vector audio_devices, std::optional &config, PageStack *page_stack) : + SettingsPage::SettingsPage(Type type, const GsrInfo &gsr_info, std::vector audio_devices, Config &config, PageStack *page_stack) : StaticPage(mgl::vec2f(get_theme().window_width, get_theme().window_height).floor()), type(type), config(config), @@ -718,9 +718,6 @@ namespace gsr { } void SettingsPage::load() { - if(!config) - return; - switch(type) { case Type::REPLAY: load_replay(); @@ -735,9 +732,6 @@ namespace gsr { } void SettingsPage::save() { - if(!config) - config = Config(); - switch(type) { case Type::REPLAY: save_replay(); @@ -749,7 +743,7 @@ namespace gsr { save_stream(); break; } - save_config(config.value()); + save_config(config); } void SettingsPage::load_audio_tracks(RecordOptions &record_options) { @@ -817,35 +811,35 @@ namespace gsr { } void SettingsPage::load_replay() { - load_common(config->replay_config.record_options); - show_replay_started_notification_checkbox_ptr->set_checked(config->replay_config.show_replay_started_notifications); - show_replay_stopped_notification_checkbox_ptr->set_checked(config->replay_config.show_replay_stopped_notifications); - show_replay_saved_notification_checkbox_ptr->set_checked(config->replay_config.show_replay_saved_notifications); - save_directory_button_ptr->set_text(config->replay_config.save_directory); - container_box_ptr->set_selected_item(config->replay_config.container); + load_common(config.replay_config.record_options); + show_replay_started_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_started_notifications); + show_replay_stopped_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_stopped_notifications); + show_replay_saved_notification_checkbox_ptr->set_checked(config.replay_config.show_replay_saved_notifications); + save_directory_button_ptr->set_text(config.replay_config.save_directory); + container_box_ptr->set_selected_item(config.replay_config.container); - if(config->replay_config.replay_time < 5) - config->replay_config.replay_time = 5; - replay_time_entry_ptr->set_text(std::to_string(config->replay_config.replay_time)); + if(config.replay_config.replay_time < 5) + config.replay_config.replay_time = 5; + replay_time_entry_ptr->set_text(std::to_string(config.replay_config.replay_time)); } void SettingsPage::load_record() { - load_common(config->record_config.record_options); - show_recording_started_notification_checkbox_ptr->set_checked(config->record_config.show_recording_started_notifications); - show_video_saved_notification_checkbox_ptr->set_checked(config->record_config.show_video_saved_notifications); - save_directory_button_ptr->set_text(config->record_config.save_directory); - container_box_ptr->set_selected_item(config->record_config.container); + load_common(config.record_config.record_options); + show_recording_started_notification_checkbox_ptr->set_checked(config.record_config.show_recording_started_notifications); + show_video_saved_notification_checkbox_ptr->set_checked(config.record_config.show_video_saved_notifications); + save_directory_button_ptr->set_text(config.record_config.save_directory); + container_box_ptr->set_selected_item(config.record_config.container); } void SettingsPage::load_stream() { - load_common(config->streaming_config.record_options); - show_streaming_started_notification_checkbox_ptr->set_checked(config->streaming_config.show_streaming_started_notifications); - show_streaming_stopped_notification_checkbox_ptr->set_checked(config->streaming_config.show_streaming_stopped_notifications); - streaming_service_box_ptr->set_selected_item(config->streaming_config.streaming_service); - youtube_stream_key_entry_ptr->set_text(config->streaming_config.youtube.stream_key); - twitch_stream_key_entry_ptr->set_text(config->streaming_config.twitch.stream_key); - stream_url_entry_ptr->set_text(config->streaming_config.custom.url); - container_box_ptr->set_selected_item(config->streaming_config.custom.container); + load_common(config.streaming_config.record_options); + show_streaming_started_notification_checkbox_ptr->set_checked(config.streaming_config.show_streaming_started_notifications); + show_streaming_stopped_notification_checkbox_ptr->set_checked(config.streaming_config.show_streaming_stopped_notifications); + streaming_service_box_ptr->set_selected_item(config.streaming_config.streaming_service); + youtube_stream_key_entry_ptr->set_text(config.streaming_config.youtube.stream_key); + twitch_stream_key_entry_ptr->set_text(config.streaming_config.twitch.stream_key); + stream_url_entry_ptr->set_text(config.streaming_config.custom.url); + container_box_ptr->set_selected_item(config.streaming_config.custom.container); } static void save_audio_tracks(std::vector &audio_tracks, List *audio_devices_list_ptr) { @@ -924,36 +918,36 @@ namespace gsr { } void SettingsPage::save_replay() { - save_common(config->replay_config.record_options); - config->replay_config.show_replay_started_notifications = show_replay_started_notification_checkbox_ptr->is_checked(); - config->replay_config.show_replay_stopped_notifications = show_replay_stopped_notification_checkbox_ptr->is_checked(); - config->replay_config.show_replay_saved_notifications = show_replay_saved_notification_checkbox_ptr->is_checked(); - config->replay_config.save_directory = save_directory_button_ptr->get_text(); - config->replay_config.container = container_box_ptr->get_selected_id(); - config->replay_config.replay_time = atoi(replay_time_entry_ptr->get_text().c_str()); - - if(config->replay_config.replay_time < 5) { - config->replay_config.replay_time = 5; + save_common(config.replay_config.record_options); + config.replay_config.show_replay_started_notifications = show_replay_started_notification_checkbox_ptr->is_checked(); + config.replay_config.show_replay_stopped_notifications = show_replay_stopped_notification_checkbox_ptr->is_checked(); + config.replay_config.show_replay_saved_notifications = show_replay_saved_notification_checkbox_ptr->is_checked(); + config.replay_config.save_directory = save_directory_button_ptr->get_text(); + config.replay_config.container = container_box_ptr->get_selected_id(); + config.replay_config.replay_time = atoi(replay_time_entry_ptr->get_text().c_str()); + + if(config.replay_config.replay_time < 5) { + config.replay_config.replay_time = 5; replay_time_entry_ptr->set_text("5"); } } void SettingsPage::save_record() { - save_common(config->record_config.record_options); - config->record_config.show_recording_started_notifications = show_recording_started_notification_checkbox_ptr->is_checked(); - config->record_config.show_video_saved_notifications = show_video_saved_notification_checkbox_ptr->is_checked(); - config->record_config.save_directory = save_directory_button_ptr->get_text(); - config->record_config.container = container_box_ptr->get_selected_id(); + save_common(config.record_config.record_options); + config.record_config.show_recording_started_notifications = show_recording_started_notification_checkbox_ptr->is_checked(); + config.record_config.show_video_saved_notifications = show_video_saved_notification_checkbox_ptr->is_checked(); + config.record_config.save_directory = save_directory_button_ptr->get_text(); + config.record_config.container = container_box_ptr->get_selected_id(); } void SettingsPage::save_stream() { - save_common(config->streaming_config.record_options); - config->streaming_config.show_streaming_started_notifications = show_streaming_started_notification_checkbox_ptr->is_checked(); - config->streaming_config.show_streaming_stopped_notifications = show_streaming_stopped_notification_checkbox_ptr->is_checked(); - config->streaming_config.streaming_service = streaming_service_box_ptr->get_selected_id(); - config->streaming_config.youtube.stream_key = youtube_stream_key_entry_ptr->get_text(); - config->streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text(); - config->streaming_config.custom.url = stream_url_entry_ptr->get_text(); - config->streaming_config.custom.container = container_box_ptr->get_selected_id(); + save_common(config.streaming_config.record_options); + config.streaming_config.show_streaming_started_notifications = show_streaming_started_notification_checkbox_ptr->is_checked(); + config.streaming_config.show_streaming_stopped_notifications = show_streaming_stopped_notification_checkbox_ptr->is_checked(); + config.streaming_config.streaming_service = streaming_service_box_ptr->get_selected_id(); + config.streaming_config.youtube.stream_key = youtube_stream_key_entry_ptr->get_text(); + config.streaming_config.twitch.stream_key = twitch_stream_key_entry_ptr->get_text(); + config.streaming_config.custom.url = stream_url_entry_ptr->get_text(); + config.streaming_config.custom.container = container_box_ptr->get_selected_id(); } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4f265fc..81694d2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,11 +31,6 @@ extern "C" { const mgl::Color bg_color(0, 0, 0, 100); -static void usage() { - fprintf(stderr, "usage: gsr-ui [toggle-record|toggle-pause]\n"); - exit(1); -} - static void startup_error(const char *msg) { fprintf(stderr, "Error: %s\n", msg); exit(1); @@ -48,15 +43,10 @@ static void sigint_handler(int signal) { } int main(int argc, char **argv) { + (void)argc; + (void)argv; setlocale(LC_ALL, "C"); // Sigh... stupid C - if(argc > 3) - usage(); - - const char *action = NULL; - if(argc > 1) - action = argv[1]; - signal(SIGINT, sigint_handler); gsr::GsrInfo gsr_info; @@ -73,7 +63,7 @@ int main(int argc, char **argv) { } std::string resources_path; - if(access("images/gpu_screen_recorder_logo.png", F_OK) == 0) { + if(access("sibs-build", F_OK) == 0) { resources_path = "./"; } else { #ifdef GSR_UI_RESOURCES_PATH @@ -130,33 +120,57 @@ int main(int argc, char **argv) { fprintf(stderr, "info: gsr ui is now ready, waiting for inputs. Press alt+z to show/hide the overlay\n"); - gsr::Overlay overlay(window, resources_path, gsr_info, egl_funcs, bg_color); + auto overlay = std::make_unique(window, resources_path, gsr_info, egl_funcs, bg_color); //overlay.show(); gsr::GlobalHotkeysX11 global_hotkeys; - const bool show_hotkey_registered = global_hotkeys.bind_key_press({ XK_z, Mod1Mask }, "open/hide", [&](const std::string &id) { + const bool show_hotkey_registered = global_hotkeys.bind_key_press({ XK_z, Mod1Mask }, "show/hide", [&](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_show(); + }); + + const bool record_hotkey_registered = global_hotkeys.bind_key_press({ XK_F9, Mod1Mask }, "record", [&](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_record(); + }); + + const bool pause_hotkey_registered = global_hotkeys.bind_key_press({ XK_F7, Mod1Mask }, "pause", [&](const std::string &id) { + fprintf(stderr, "pressed %s\n", id.c_str()); + overlay->toggle_pause(); + }); + + const bool stream_hotkey_registered = global_hotkeys.bind_key_press({ XK_F8, Mod1Mask }, "stream", [&](const std::string &id) { fprintf(stderr, "pressed %s\n", id.c_str()); - overlay.toggle_show(); + overlay->toggle_stream(); }); - const bool record_hotkey_registered = global_hotkeys.bind_key_press({ XK_F9, Mod1Mask }, "record/save", [&](const std::string &id) { + const bool replay_hotkey_registered = global_hotkeys.bind_key_press({ XK_F10, ShiftMask | Mod1Mask }, "replay start", [&](const std::string &id) { fprintf(stderr, "pressed %s\n", id.c_str()); - overlay.toggle_record(); + overlay->toggle_replay(); }); - const bool pause_hotkey_registered = global_hotkeys.bind_key_press({ XK_F7, Mod1Mask }, "pause/unpause", [&](const std::string &id) { + const bool replay_save_hotkey_registered = global_hotkeys.bind_key_press({ XK_F10, Mod1Mask }, "replay save", [&](const std::string &id) { fprintf(stderr, "pressed %s\n", id.c_str()); - overlay.toggle_pause(); + overlay->save_replay(); }); if(!show_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+z for showing the overlay because the hotkey is registed by another program\n"); + fprintf(stderr, "error: failed to register hotkey alt+z for showing the overlay because the hotkey is registered by another program\n"); if(!record_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+f9 for recording because the hotkey is registed by another program\n"); + fprintf(stderr, "error: failed to register hotkey alt+f9 for recording because the hotkey is registered by another program\n"); if(!pause_hotkey_registered) - fprintf(stderr, "error: failed to register hotkey alt+f7 for pausing because the hotkey is registed by another program\n"); + fprintf(stderr, "error: failed to register hotkey alt+f7 for pausing because the hotkey is registered by another program\n"); + + if(!stream_hotkey_registered) + fprintf(stderr, "error: failed to register hotkey alt+f8 for streaming because the hotkey is registered by another program\n"); + + if(!replay_hotkey_registered) + fprintf(stderr, "error: failed to register hotkey alt+shift+f10 for starting replay because the hotkey is registered by another program\n"); + + if(!replay_save_hotkey_registered) + fprintf(stderr, "error: failed to register hotkey alt+f10 for saving replay because the hotkey is registered by another program\n"); mgl::Event event; mgl::Clock frame_delta_clock; @@ -167,14 +181,16 @@ int main(int argc, char **argv) { global_hotkeys.poll_events(); while(window.poll_event(event)) { - overlay.on_event(event, window); + overlay->on_event(event, window); } window.clear(bg_color); - overlay.draw(window); + overlay->draw(window); window.display(); } + overlay.reset(); + fprintf(stderr, "info: shutting down!\n"); gsr::deinit_theme(); window.close(); -- cgit v1.2.3