From efdea8c290baf52fca53ddc25a41bf0774014999 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 2 Jun 2024 20:59:34 +0200 Subject: Add systray --- src/main.cpp | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 234 insertions(+), 3 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index 0df7c96..c7e7a4a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,7 @@ extern "C" { } #include #include +#include typedef struct { Display *display; @@ -38,6 +39,7 @@ typedef struct { GtkWidget *streaming_page; } PageNavigationUserdata; +static bool window_hidden = false; static GtkWidget *window; static SelectWindowUserdata select_window_userdata; static PageNavigationUserdata page_navigation_userdata; @@ -102,6 +104,14 @@ static GtkGrid *streaming_bottom_panel_grid; static GtkWidget *streaming_record_time_label; static GtkGrid *replay_bottom_panel_grid; static GtkWidget *replay_record_time_label; +static GtkWidget *show_hide_menu_item; +static GtkWidget *recording_menu_separator; +static GtkWidget *start_stop_streaming_menu_item; +static GtkWidget *start_stop_recording_menu_item; +static GtkWidget *pause_recording_menu_item; +static GtkWidget *start_stop_replay_menu_item; +static GtkWidget *save_replay_menu_item; +static GtkWidget *hide_window_when_recording_menu_item; static double record_start_time_sec = 0.0; static double pause_start_sec = 0.0; @@ -129,6 +139,11 @@ static bool showing_notification = false; static double notification_timeout_seconds = 0.0; static double notification_start_seconds = 0.0; +static AppIndicator *app_indicator; +static const char *tray_idle_icon_name = "tray_idle"; +static const char *tray_recording_icon_name = "tray_recording"; +static const char *tray_paused_icon_name = "tray_paused"; + struct AudioInput { std::string name; std::string description; @@ -253,6 +268,135 @@ static double clock_get_monotonic_seconds(void) { return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001; } +static void show_window() { + gdk_window_show(gtk_widget_get_window(window)); + gtk_menu_item_set_label(GTK_MENU_ITEM(show_hide_menu_item), "Hide window"); + window_hidden = false; +} + +static void hide_window() { + gdk_window_hide(gtk_widget_get_window(window)); + gtk_menu_item_set_label(GTK_MENU_ITEM(show_hide_menu_item), "Show window"); + window_hidden = true; +} + +static void systray_show_hide_callback(GtkMenuItem*, gpointer) { + if(window_hidden) { + show_window(); + } else { + hide_window(); + } +} + +static void hide_window_when_recording_systray_callback(GtkMenuItem*, gpointer) { + config.main_config.hide_window_when_recording = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item)); +} + +static void start_stop_streaming_menu_item_systray_callback(GtkMenuItem*, gpointer userdata); +static void start_stop_recording_systray_callback(GtkMenuItem*, gpointer userdata); +static void pause_recording_systray_callback(GtkMenuItem*, gpointer userdata); +static void start_stop_replay_systray_callback(GtkMenuItem*, gpointer userdata); +static void save_replay_systray_callback(GtkMenuItem*, gpointer userdata); + +static void systray_exit_callback(GtkMenuItem*, gpointer) { + gtk_window_close(GTK_WINDOW(window)); +} + +enum class SystrayPage { + FRONT, + STREAMING, + RECORDING, + REPLAY +}; + +static GtkMenuShell* create_systray_menu(GtkApplication *app, SystrayPage systray_page) { + GtkMenuShell *menu = GTK_MENU_SHELL(gtk_menu_new()); + + show_hide_menu_item = gtk_menu_item_new_with_label("Hide window"); + g_signal_connect(show_hide_menu_item, "activate", G_CALLBACK(systray_show_hide_callback), app); + gtk_menu_shell_append(menu, show_hide_menu_item); + + { + GtkMenuShell *options_menu = GTK_MENU_SHELL(gtk_menu_new()); + hide_window_when_recording_menu_item = gtk_check_menu_item_new_with_label("Hide window when recording starts"); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item), config.main_config.hide_window_when_recording); + g_signal_connect(hide_window_when_recording_menu_item, "activate", G_CALLBACK(hide_window_when_recording_systray_callback), nullptr); + gtk_menu_shell_append(options_menu, hide_window_when_recording_menu_item); + + GtkWidget *options_menu_item = gtk_menu_item_new_with_label("Options"); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(options_menu_item), GTK_WIDGET(options_menu)); + gtk_menu_shell_append(menu, options_menu_item); + } + + recording_menu_separator = gtk_separator_menu_item_new(); + gtk_menu_shell_append(menu, recording_menu_separator); + + start_stop_streaming_menu_item = gtk_menu_item_new_with_label("Start streaming"); + g_signal_connect(start_stop_streaming_menu_item, "activate", G_CALLBACK(start_stop_streaming_menu_item_systray_callback), app); + gtk_menu_shell_append(menu, start_stop_streaming_menu_item); + + start_stop_recording_menu_item = gtk_menu_item_new_with_label("Start recording"); + g_signal_connect(start_stop_recording_menu_item, "activate", G_CALLBACK(start_stop_recording_systray_callback), app); + gtk_menu_shell_append(menu, start_stop_recording_menu_item); + + pause_recording_menu_item = gtk_menu_item_new_with_label("Pause recording"); + g_signal_connect(pause_recording_menu_item, "activate", G_CALLBACK(pause_recording_systray_callback), app); + gtk_menu_shell_append(menu, pause_recording_menu_item); + + start_stop_replay_menu_item = gtk_menu_item_new_with_label("Start replay"); + g_signal_connect(start_stop_replay_menu_item, "activate", G_CALLBACK(start_stop_replay_systray_callback), app); + gtk_menu_shell_append(menu, start_stop_replay_menu_item); + + save_replay_menu_item = gtk_menu_item_new_with_label("Save replay"); + g_signal_connect(save_replay_menu_item, "activate", G_CALLBACK(save_replay_systray_callback), app); + gtk_menu_shell_append(menu, save_replay_menu_item); + + gtk_menu_shell_append(menu, gtk_separator_menu_item_new()); + + GtkWidget *exit_menu_item = gtk_menu_item_new_with_label("Exit"); + g_signal_connect(exit_menu_item, "activate", G_CALLBACK(systray_exit_callback), nullptr); + gtk_menu_shell_append(menu, exit_menu_item); + + gtk_widget_show_all(GTK_WIDGET(menu)); + gtk_widget_set_visible(recording_menu_separator, false); + gtk_widget_set_visible(start_stop_streaming_menu_item, false); + gtk_widget_set_visible(start_stop_recording_menu_item, false); + gtk_widget_set_visible(pause_recording_menu_item, false); + gtk_widget_set_visible(start_stop_replay_menu_item, false); + gtk_widget_set_visible(save_replay_menu_item, false); + + switch(systray_page) { + case SystrayPage::FRONT: + break; + case SystrayPage::STREAMING: + gtk_widget_set_visible(recording_menu_separator, true); + gtk_widget_set_visible(start_stop_streaming_menu_item, true); + break; + case SystrayPage::RECORDING: + gtk_widget_set_visible(recording_menu_separator, true); + gtk_widget_set_visible(start_stop_recording_menu_item, true); + gtk_widget_set_visible(pause_recording_menu_item, true); + gtk_widget_set_sensitive(pause_recording_menu_item, false); + break; + case SystrayPage::REPLAY: + gtk_widget_set_visible(recording_menu_separator, true); + gtk_widget_set_visible(start_stop_replay_menu_item, true); + gtk_widget_set_visible(save_replay_menu_item, true); + gtk_widget_set_sensitive(save_replay_menu_item, false); + break; + } + return menu; +} + +static void setup_systray(GtkApplication *app) { + app_indicator = app_indicator_new_with_path("com.dec05eba.gpu_screen_recorder", tray_idle_icon_name, APP_INDICATOR_CATEGORY_APPLICATION_STATUS, "/usr/share/com.dec05eba.gpu_screen_recorder"); + // This triggers Gdk assert: gdk_window_thaw_toplevel_updates: assertion 'window->update_and_descendants_freeze_count > 0' failed, + // dont know why but it works anyways + app_indicator_set_status(app_indicator, APP_INDICATOR_STATUS_ACTIVE); + app_indicator_set_title(app_indicator, "GPU Screen Recorder"); + app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(app, SystrayPage::FRONT))); +} + static void pa_state_cb(pa_context *c, void *userdata) { pa_context_state state = pa_context_get_state(c); int *pa_ready = (int*)userdata; @@ -620,6 +764,7 @@ static void save_configs() { config.main_config.overclock = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(overclock_button)); config.main_config.show_notifications = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_notification_button)); config.main_config.record_cursor = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(record_cursor_button)); + config.main_config.hide_window_when_recording = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item)); config.streaming_config.streaming_service = gtk_combo_box_get_active_id(GTK_COMBO_BOX(stream_service_input_menu)); config.streaming_config.youtube.stream_key = gtk_entry_get_text(youtube_stream_id_entry); @@ -1371,6 +1516,8 @@ static gboolean on_start_replay_click(GtkButton*, gpointer userdata) { PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata; gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->replay_page); + app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::REPLAY))); + if(!wayland) { HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); if(!hotkey_result.replay_start_stop_hotkey_success) { @@ -1384,6 +1531,7 @@ static gboolean on_start_replay_click(GtkButton*, gpointer userdata) { replay_save_hotkey.modkey_mask = 0; } } + return true; } @@ -1393,6 +1541,8 @@ static gboolean on_start_recording_click(GtkButton*, gpointer userdata) { PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata; gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->recording_page); + app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::RECORDING))); + if(!wayland) { HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); if(!hotkey_result.record_hotkey_success) { @@ -1406,6 +1556,7 @@ static gboolean on_start_recording_click(GtkButton*, gpointer userdata) { pause_unpause_hotkey.modkey_mask = 0; } } + return true; } @@ -1434,6 +1585,8 @@ static gboolean on_start_streaming_click(GtkButton*, gpointer userdata) { PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata; gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->streaming_page); + app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::STREAMING))); + if(!wayland) { HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); if(!hotkey_result.streaming_hotkey_success) { @@ -1442,6 +1595,7 @@ static gboolean on_start_streaming_click(GtkButton*, gpointer userdata) { streaming_hotkey.modkey_mask = 0; } } + return true; } @@ -1450,6 +1604,7 @@ static gboolean on_streaming_recording_replay_page_back_click(GtkButton*, gpoint gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->common_settings_page); ungrab_keys(gdk_x11_get_default_xdisplay()); hotkey_mode = HotkeyMode::NoAction; + app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::FRONT))); return true; } @@ -1557,6 +1712,10 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat gtk_widget_set_opacity(GTK_WIDGET(replay_bottom_panel_grid), 0.5); gtk_label_set_text(GTK_LABEL(replay_record_time_label), "00:00:00"); + gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_replay_menu_item), "Start replay"); + gtk_widget_set_sensitive(save_replay_menu_item, false); + app_indicator_set_icon(app_indicator, tray_idle_icon_name); + if(exit_status == 10) { show_notification(app, "GPU Screen Recorder", "You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT); @@ -1626,6 +1785,10 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat args.push_back(NULL); + if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item))) { + hide_window(); + } + pid_t parent_pid = getpid(); pid_t pid = fork(); if(pid == -1) { @@ -1646,18 +1809,28 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat _exit(127); } else { /* parent process */ gpu_screen_recorder_process = pid; - gtk_button_set_label(button, "Stop replay"); } replaying = true; + + gtk_button_set_label(button, "Stop replay"); + gtk_widget_set_sensitive(GTK_WIDGET(replay_back_button), false); gtk_widget_set_sensitive(GTK_WIDGET(replay_save_button), true); gtk_widget_set_opacity(GTK_WIDGET(replay_bottom_panel_grid), 1.0); + + gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_replay_menu_item), "Stop replay"); + gtk_widget_set_sensitive(save_replay_menu_item, true); + app_indicator_set_icon(app_indicator, tray_recording_icon_name); + record_start_time_sec = clock_get_monotonic_seconds(); return true; } static gboolean on_replay_save_button_click(GtkButton*, gpointer userdata) { + if(gpu_screen_recorder_process == -1) + return true; + GtkApplication *app = (GtkApplication*)userdata; kill(gpu_screen_recorder_process, SIGUSR1); if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_notification_button))) @@ -1666,15 +1839,22 @@ static gboolean on_replay_save_button_click(GtkButton*, gpointer userdata) { } static gboolean on_pause_unpause_button_click(GtkButton*, gpointer) { + if(gpu_screen_recorder_process == -1) + return true; + kill(gpu_screen_recorder_process, SIGUSR2); paused = !paused; if(paused) { gtk_button_set_label(pause_recording_button, "Unpause recording"); gtk_image_set_from_icon_name(GTK_IMAGE(recording_record_icon), "media-playback-pause", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_menu_item_set_label(GTK_MENU_ITEM(pause_recording_menu_item), "Unpause recording"); + app_indicator_set_icon(app_indicator, tray_paused_icon_name); pause_start_sec = clock_get_monotonic_seconds(); } else { gtk_button_set_label(pause_recording_button, "Pause recording"); gtk_image_set_from_icon_name(GTK_IMAGE(recording_record_icon), "media-record", GTK_ICON_SIZE_SMALL_TOOLBAR); + gtk_menu_item_set_label(GTK_MENU_ITEM(pause_recording_menu_item), "Pause recording"); + app_indicator_set_icon(app_indicator, tray_recording_icon_name); paused_time_offset_sec += (clock_get_monotonic_seconds() - pause_start_sec); } return true; @@ -1701,6 +1881,11 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user gtk_image_set_from_icon_name(GTK_IMAGE(recording_record_icon), "media-record", GTK_ICON_SIZE_SMALL_TOOLBAR); gtk_label_set_text(GTK_LABEL(recording_record_time_label), "00:00:00"); + gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_recording_menu_item), "Start recording"); + gtk_menu_item_set_label(GTK_MENU_ITEM(pause_recording_menu_item), "Pause recording"); + gtk_widget_set_sensitive(pause_recording_menu_item, false); + app_indicator_set_icon(app_indicator, tray_idle_icon_name); + if(exit_status == 10) { show_notification(app, "GPU Screen Recorder", "You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT); @@ -1774,6 +1959,10 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user args.push_back(NULL); + if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item))) { + hide_window(); + } + pid_t parent_pid = getpid(); pid_t pid = fork(); if(pid == -1) { @@ -1794,13 +1983,19 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user _exit(127); } else { /* parent process */ gpu_screen_recorder_process = pid; - gtk_button_set_label(button, "Stop recording"); } recording = true; + + gtk_button_set_label(button, "Stop recording"); gtk_widget_set_sensitive(GTK_WIDGET(record_back_button), false); gtk_widget_set_sensitive(GTK_WIDGET(pause_recording_button), true); gtk_widget_set_opacity(GTK_WIDGET(recording_bottom_panel_grid), 1.0); + + gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_recording_menu_item), "Stop recording"); + gtk_widget_set_sensitive(pause_recording_menu_item, true); + app_indicator_set_icon(app_indicator, tray_recording_icon_name); + record_start_time_sec = clock_get_monotonic_seconds(); paused_time_offset_sec = 0.0; return true; @@ -1821,6 +2016,9 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user gtk_widget_set_opacity(GTK_WIDGET(streaming_bottom_panel_grid), 0.5); gtk_label_set_text(GTK_LABEL(streaming_record_time_label), "00:00:00"); + gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_streaming_menu_item), "Start streaming"); + app_indicator_set_icon(app_indicator, tray_idle_icon_name); + if(exit_status == 10) { show_notification(app, "GPU Screen Recorder", "You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT); @@ -1906,6 +2104,10 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user args.push_back(NULL); + if(gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item))) { + hide_window(); + } + pid_t parent_pid = getpid(); pid_t pid = fork(); if(pid == -1) { @@ -1926,16 +2128,42 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user _exit(127); } else { /* parent process */ gpu_screen_recorder_process = pid; - gtk_button_set_label(button, "Stop streaming"); } streaming = true; + + gtk_button_set_label(button, "Stop streaming"); + gtk_widget_set_sensitive(GTK_WIDGET(stream_back_button), false); gtk_widget_set_opacity(GTK_WIDGET(streaming_bottom_panel_grid), 1.0); + + gtk_menu_item_set_label(GTK_MENU_ITEM(start_stop_streaming_menu_item), "Stop streaming"); + app_indicator_set_icon(app_indicator, tray_recording_icon_name); + record_start_time_sec = clock_get_monotonic_seconds(); return true; } +static void start_stop_streaming_menu_item_systray_callback(GtkMenuItem*, gpointer userdata) { + on_start_streaming_button_click(start_streaming_button, userdata); +} + +static void start_stop_recording_systray_callback(GtkMenuItem*, gpointer userdata) { + on_start_recording_button_click(start_recording_button, userdata); +} + +static void pause_recording_systray_callback(GtkMenuItem*, gpointer userdata) { + on_pause_unpause_button_click(pause_recording_button, userdata); +} + +static void start_stop_replay_systray_callback(GtkMenuItem*, gpointer userdata) { + on_start_replay_button_click(start_replay_button, userdata); +} + +static void save_replay_systray_callback(GtkMenuItem*, gpointer userdata) { + on_replay_save_button_click(replay_save_button, userdata); +} + static void gtk_widget_set_margin(GtkWidget *widget, int top, int bottom, int left, int right) { gtk_widget_set_margin_top(widget, top); gtk_widget_set_margin_bottom(widget, bottom); @@ -3237,6 +3465,7 @@ static void load_config(const gpu_info &gpu_inf) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(overclock_button), config.main_config.overclock); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(show_notification_button), config.main_config.show_notifications); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(record_cursor_button), config.main_config.record_cursor); + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(hide_window_when_recording_menu_item), config.main_config.hide_window_when_recording); gtk_combo_box_set_active_id(GTK_COMBO_BOX(stream_service_input_menu), config.streaming_config.streaming_service.c_str()); gtk_entry_set_text(youtube_stream_id_entry, config.streaming_config.youtube.stream_key.c_str()); @@ -3518,6 +3747,8 @@ static void activate(GtkApplication *app, gpointer) { gdk_window_add_filter(root_window, hotkey_filter_callback, &page_navigation_userdata); } + setup_systray(app); + g_timeout_add(500, timer_timeout_handler, app); gtk_widget_show_all(window); -- cgit v1.2.3