#include "gsr-record-page.h" #include #include #include #include "gsr-window.h" #ifdef HAVE_X11 #include "gsr-shortcut-accel-dialog.h" #endif /* ═══════════════════════════════════════════════════════════════════ * GsrRecordPage — "Record" tab * * Groups: Output · Action · Status * ═══════════════════════════════════════════════════════════════════ */ struct _GsrRecordPage { AdwPreferencesPage parent_instance; const GsrInfo *info; /* borrowed */ /* ── Hotkey group ─── */ AdwPreferencesGroup *hotkey_group; #ifdef HAVE_WAYLAND GtkWidget *hotkey_not_supported_label; /* Wayland only */ GtkWidget *hotkey_info_row; /* Wayland only */ #endif #ifdef HAVE_X11 /* X11 interactive hotkey rows */ AdwActionRow *x11_start_stop_row; GtkShortcutLabel *x11_start_stop_label; char *x11_start_stop_accel; /* owned */ AdwActionRow *x11_pause_row; GtkShortcutLabel *x11_pause_label; char *x11_pause_accel; /* owned */ #endif /* ── Output group ─── */ AdwPreferencesGroup *output_group; AdwActionRow *save_dir_row; char *save_directory; /* owned */ AdwComboRow *container_row; /* ── Action group ─── */ AdwPreferencesGroup *action_group; GtkButton *start_button; GtkButton *pause_button; /* ── Status group ─── */ AdwPreferencesGroup *status_group; GtkBox *status_box; GtkImage *record_icon; GtkLabel *timer_label; gboolean is_active; gboolean is_paused; double start_time; double paused_time_offset; double pause_start; guint timer_source_id; }; G_DEFINE_FINAL_TYPE(GsrRecordPage, gsr_record_page, ADW_TYPE_PREFERENCES_PAGE) /* ── Helpers ─────────────────────────────────────────────────────── */ static char * get_default_videos_dir(void) { return gsr_config_get_videos_dir(); } /* ── Timer helpers ───────────────────────────────────────────────── */ static double clock_get_monotonic_seconds(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; } static void format_timer(double seconds, char *buf, size_t buf_size) { int total = (int)seconds; int h = total / 3600; int m = (total % 3600) / 60; int s = total % 60; snprintf(buf, buf_size, "%02d:%02d:%02d", h, m, s); } static gboolean on_timer_tick(gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); double now = clock_get_monotonic_seconds(); double elapsed = now - self->start_time - self->paused_time_offset; /* If currently paused, subtract the ongoing pause duration */ if (self->is_paused) elapsed -= (now - self->pause_start); char buf[32]; format_timer(elapsed, buf, sizeof(buf)); gtk_label_set_text(self->timer_label, buf); return G_SOURCE_CONTINUE; } /* ── File chooser ────────────────────────────────────────────────── */ static void on_folder_dialog_finish(GObject *source, GAsyncResult *result, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); GtkFileDialog *dialog = GTK_FILE_DIALOG(source); GFile *folder = gtk_file_dialog_select_folder_finish(dialog, result, NULL); if (!folder) return; g_free(self->save_directory); self->save_directory = g_file_get_path(folder); adw_action_row_set_subtitle(self->save_dir_row, self->save_directory); g_object_unref(folder); } static void on_save_dir_activated(AdwActionRow *row G_GNUC_UNUSED, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); g_autoptr(GtkFileDialog) dialog = gtk_file_dialog_new(); gtk_file_dialog_set_title(dialog, _("Select save directory")); if (self->save_directory) { g_autoptr(GFile) initial = g_file_new_for_path(self->save_directory); gtk_file_dialog_set_initial_folder(dialog, initial); } GtkWindow *win = GTK_WINDOW(gtk_widget_get_root(GTK_WIDGET(self))); gtk_file_dialog_select_folder(dialog, win, NULL, on_folder_dialog_finish, self); } /* ── Callbacks ───────────────────────────────────────────────────── */ static void on_start_recording_clicked(GtkButton *btn G_GNUC_UNUSED, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); GtkRoot *root = gtk_widget_get_root(GTK_WIDGET(self)); GsrWindow *window = (root && GSR_IS_WINDOW(root)) ? GSR_WINDOW(root) : NULL; if (self->is_active) { /* ── Stop ─── */ if (window) { gsr_window_stop_process(window, NULL); gsr_window_set_recording_active(window, FALSE); } gsr_record_page_set_active(self, FALSE); /* set_active(FALSE) resets is_active, is_paused, and stops timer */ } else { /* ── Start ─── */ if (!window) return; gboolean ok = gsr_window_start_process(window, GSR_ACTIVE_MODE_RECORD); if (!ok) return; /* fork failed — toast already shown by window */ self->is_active = TRUE; self->is_paused = FALSE; self->paused_time_offset = 0.0; self->pause_start = 0.0; gsr_record_page_set_active(self, TRUE); gsr_window_set_recording_active(window, TRUE); /* Start display timer */ self->start_time = clock_get_monotonic_seconds(); self->timer_source_id = g_timeout_add(500, on_timer_tick, self); } } static void on_pause_recording_clicked(GtkButton *btn G_GNUC_UNUSED, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); if (!self->is_active) return; GtkRoot *root = gtk_widget_get_root(GTK_WIDGET(self)); GsrWindow *window = (root && GSR_IS_WINDOW(root)) ? GSR_WINDOW(root) : NULL; /* Send SIGUSR2 to toggle pause/unpause */ if (window) gsr_window_send_signal(window, SIGUSR2); self->is_paused = !self->is_paused; gsr_record_page_set_paused(self, self->is_paused); if (self->is_paused) { self->pause_start = clock_get_monotonic_seconds(); } else { /* Accumulate paused duration */ self->paused_time_offset += clock_get_monotonic_seconds() - self->pause_start; self->pause_start = 0.0; } } /* ── Build groups ────────────────────────────────────────────────── */ #ifdef HAVE_WAYLAND static gboolean is_kde_wayland(void) { const char *desktop = g_getenv("XDG_CURRENT_DESKTOP"); return desktop && g_strstr_len(desktop, -1, "KDE") != NULL; } #endif /* ── X11 hotkey row callbacks ────────────────────────────────────── */ #ifdef HAVE_X11 static void on_x11_start_stop_shortcut_set(GsrShortcutAccelDialog *dialog, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); const char *accel = gsr_shortcut_accel_dialog_get_accelerator(dialog); g_set_str(&self->x11_start_stop_accel, accel); if (self->x11_start_stop_label) gtk_shortcut_label_set_accelerator(self->x11_start_stop_label, accel ? accel : ""); /* Save config & regrab hotkeys */ GtkRoot *root = gtk_widget_get_root(GTK_WIDGET(self)); if (root && GSR_IS_WINDOW(root)) gsr_window_on_hotkey_changed(GSR_WINDOW(root)); } static void on_x11_start_stop_activated(AdwActionRow *row G_GNUC_UNUSED, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); GsrShortcutAccelDialog *dialog = gsr_shortcut_accel_dialog_new( _("Start/Stop recording"), self->x11_start_stop_accel); g_signal_connect(dialog, "shortcut-set", G_CALLBACK(on_x11_start_stop_shortcut_set), self); adw_dialog_present(ADW_DIALOG(dialog), GTK_WIDGET(self)); } static void on_x11_pause_shortcut_set(GsrShortcutAccelDialog *dialog, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); const char *accel = gsr_shortcut_accel_dialog_get_accelerator(dialog); g_set_str(&self->x11_pause_accel, accel); if (self->x11_pause_label) gtk_shortcut_label_set_accelerator(self->x11_pause_label, accel ? accel : ""); /* Save config & regrab hotkeys */ GtkRoot *root = gtk_widget_get_root(GTK_WIDGET(self)); if (root && GSR_IS_WINDOW(root)) gsr_window_on_hotkey_changed(GSR_WINDOW(root)); } static void on_x11_pause_activated(AdwActionRow *row G_GNUC_UNUSED, gpointer user_data) { GsrRecordPage *self = GSR_RECORD_PAGE(user_data); GsrShortcutAccelDialog *dialog = gsr_shortcut_accel_dialog_new( _("Pause/Unpause recording"), self->x11_pause_accel); g_signal_connect(dialog, "shortcut-set", G_CALLBACK(on_x11_pause_shortcut_set), self); adw_dialog_present(ADW_DIALOG(dialog), GTK_WIDGET(self)); } #endif /* HAVE_X11 */ static void build_hotkey_group(GsrRecordPage *self) { self->hotkey_group = ADW_PREFERENCES_GROUP(adw_preferences_group_new()); adw_preferences_group_set_title(self->hotkey_group, _("Hotkeys")); GsrDisplayServer ds = self->info->system_info.display_server; #ifdef HAVE_WAYLAND if (ds == GSR_DISPLAY_SERVER_WAYLAND) { /* "Not supported" label — initially hidden, shown if portal fails */ self->hotkey_not_supported_label = gtk_label_new( _("Your Wayland compositor doesn't support global hotkeys.\n" "Use X11 or KDE Plasma on Wayland if you want to use hotkeys.")); gtk_label_set_wrap(GTK_LABEL(self->hotkey_not_supported_label), TRUE); gtk_widget_add_css_class(self->hotkey_not_supported_label, "dim-label"); gtk_widget_set_margin_top(self->hotkey_not_supported_label, 6); gtk_widget_set_margin_bottom(self->hotkey_not_supported_label, 6); gtk_widget_set_visible(self->hotkey_not_supported_label, FALSE); adw_preferences_group_add(self->hotkey_group, self->hotkey_not_supported_label); /* Info row about Wayland hotkeys */ AdwActionRow *info_row = ADW_ACTION_ROW(adw_action_row_new()); self->hotkey_info_row = GTK_WIDGET(info_row); if (is_kde_wayland()) { adw_preferences_row_set_title(ADW_PREFERENCES_ROW(info_row), _("Hotkeys are managed by KDE Plasma")); adw_action_row_set_subtitle(info_row, _("Click to configure hotkeys in system settings")); GtkButton *change_btn = GTK_BUTTON( gtk_button_new_with_label(_("Change hotkeys"))); gtk_widget_set_valign(GTK_WIDGET(change_btn), GTK_ALIGN_CENTER); g_object_set_data(G_OBJECT(info_row), "change-button", change_btn); adw_action_row_add_suffix(info_row, GTK_WIDGET(change_btn)); } else { adw_preferences_row_set_title(ADW_PREFERENCES_ROW(info_row), _("Hotkeys are managed by your compositor")); adw_action_row_set_subtitle(info_row, _("Go to system settings to change hotkeys")); } adw_preferences_group_add(self->hotkey_group, GTK_WIDGET(info_row)); /* Hotkey labels (shown after portal succeeds) */ AdwActionRow *start_row = ADW_ACTION_ROW(adw_action_row_new()); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(start_row), _("Start/Stop recording")); adw_action_row_set_subtitle(start_row, ""); adw_preferences_group_add(self->hotkey_group, GTK_WIDGET(start_row)); AdwActionRow *pause_row = ADW_ACTION_ROW(adw_action_row_new()); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(pause_row), _("Pause/Unpause recording")); adw_action_row_set_subtitle(pause_row, ""); adw_preferences_group_add(self->hotkey_group, GTK_WIDGET(pause_row)); /* Initially hidden on Wayland */ gtk_widget_set_visible(GTK_WIDGET(self->hotkey_info_row), FALSE); gtk_widget_set_visible(GTK_WIDGET(start_row), FALSE); gtk_widget_set_visible(GTK_WIDGET(pause_row), FALSE); } #endif /* HAVE_WAYLAND */ #ifdef HAVE_X11 if (ds == GSR_DISPLAY_SERVER_X11) { /* X11: interactive shortcut rows */ self->x11_start_stop_row = ADW_ACTION_ROW(adw_action_row_new()); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(self->x11_start_stop_row), _("Start/Stop recording")); gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(self->x11_start_stop_row), TRUE); self->x11_start_stop_label = GTK_SHORTCUT_LABEL( gtk_shortcut_label_new(self->x11_start_stop_accel ? self->x11_start_stop_accel : "")); gtk_widget_set_valign(GTK_WIDGET(self->x11_start_stop_label), GTK_ALIGN_CENTER); adw_action_row_add_suffix(self->x11_start_stop_row, GTK_WIDGET(self->x11_start_stop_label)); GtkImage *arrow1 = GTK_IMAGE(gtk_image_new_from_icon_name("go-next-symbolic")); gtk_widget_add_css_class(GTK_WIDGET(arrow1), "dim-label"); adw_action_row_add_suffix(self->x11_start_stop_row, GTK_WIDGET(arrow1)); g_signal_connect(self->x11_start_stop_row, "activated", G_CALLBACK(on_x11_start_stop_activated), self); adw_preferences_group_add(self->hotkey_group, GTK_WIDGET(self->x11_start_stop_row)); /* Pause/Unpause row */ self->x11_pause_row = ADW_ACTION_ROW(adw_action_row_new()); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(self->x11_pause_row), _("Pause/Unpause recording")); gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(self->x11_pause_row), TRUE); self->x11_pause_label = GTK_SHORTCUT_LABEL( gtk_shortcut_label_new(self->x11_pause_accel ? self->x11_pause_accel : "")); gtk_widget_set_valign(GTK_WIDGET(self->x11_pause_label), GTK_ALIGN_CENTER); adw_action_row_add_suffix(self->x11_pause_row, GTK_WIDGET(self->x11_pause_label)); GtkImage *arrow2 = GTK_IMAGE(gtk_image_new_from_icon_name("go-next-symbolic")); gtk_widget_add_css_class(GTK_WIDGET(arrow2), "dim-label"); adw_action_row_add_suffix(self->x11_pause_row, GTK_WIDGET(arrow2)); g_signal_connect(self->x11_pause_row, "activated", G_CALLBACK(on_x11_pause_activated), self); adw_preferences_group_add(self->hotkey_group, GTK_WIDGET(self->x11_pause_row)); } #endif /* HAVE_X11 */ (void)ds; adw_preferences_page_add(ADW_PREFERENCES_PAGE(self), self->hotkey_group); } static void build_output_group(GsrRecordPage *self) { self->output_group = ADW_PREFERENCES_GROUP(adw_preferences_group_new()); adw_preferences_group_set_title(self->output_group, _("Output")); /* Save directory */ self->save_directory = get_default_videos_dir(); self->save_dir_row = ADW_ACTION_ROW(adw_action_row_new()); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(self->save_dir_row), _("Save directory")); adw_action_row_set_subtitle(self->save_dir_row, self->save_directory); gtk_list_box_row_set_activatable(GTK_LIST_BOX_ROW(self->save_dir_row), TRUE); GtkImage *folder_icon = GTK_IMAGE( gtk_image_new_from_icon_name("document-open-symbolic")); adw_action_row_add_suffix(self->save_dir_row, GTK_WIDGET(folder_icon)); g_signal_connect(self->save_dir_row, "activated", G_CALLBACK(on_save_dir_activated), self); adw_preferences_group_add(self->output_group, GTK_WIDGET(self->save_dir_row)); /* Container format */ self->container_row = ADW_COMBO_ROW(adw_combo_row_new()); adw_preferences_row_set_title(ADW_PREFERENCES_ROW(self->container_row), _("Container")); GtkStringList *ct_model = gtk_string_list_new(NULL); const char *containers[] = { "mp4", "flv", "mkv", "mov", "ts", "m3u8", NULL }; for (int i = 0; containers[i]; i++) gtk_string_list_append(ct_model, containers[i]); if (self->info->supported_video_codecs.vp8 || self->info->supported_video_codecs.vp9) gtk_string_list_append(ct_model, "webm"); adw_combo_row_set_model(self->container_row, G_LIST_MODEL(ct_model)); adw_combo_row_set_selected(self->container_row, 0); /* default: mp4 */ adw_preferences_group_add(self->output_group, GTK_WIDGET(self->container_row)); adw_preferences_page_add(ADW_PREFERENCES_PAGE(self), self->output_group); } static void build_action_group(GsrRecordPage *self) { self->action_group = ADW_PREFERENCES_GROUP(adw_preferences_group_new()); GtkBox *box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 12)); gtk_widget_set_halign(GTK_WIDGET(box), GTK_ALIGN_CENTER); gtk_widget_set_margin_top(GTK_WIDGET(box), 6); gtk_widget_set_margin_bottom(GTK_WIDGET(box), 6); self->start_button = GTK_BUTTON( gtk_button_new_with_label(_("Start recording"))); gtk_widget_set_hexpand(GTK_WIDGET(self->start_button), TRUE); gtk_widget_add_css_class(GTK_WIDGET(self->start_button), "suggested-action"); g_signal_connect(self->start_button, "clicked", G_CALLBACK(on_start_recording_clicked), self); gtk_box_append(box, GTK_WIDGET(self->start_button)); self->pause_button = GTK_BUTTON( gtk_button_new_with_label(_("Pause recording"))); gtk_widget_set_hexpand(GTK_WIDGET(self->pause_button), TRUE); gtk_widget_set_sensitive(GTK_WIDGET(self->pause_button), FALSE); g_signal_connect(self->pause_button, "clicked", G_CALLBACK(on_pause_recording_clicked), self); gtk_box_append(box, GTK_WIDGET(self->pause_button)); adw_preferences_group_add(self->action_group, GTK_WIDGET(box)); adw_preferences_page_add(ADW_PREFERENCES_PAGE(self), self->action_group); } static void build_status_group(GsrRecordPage *self) { self->status_group = ADW_PREFERENCES_GROUP(adw_preferences_group_new()); self->status_box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8)); gtk_widget_set_halign(GTK_WIDGET(self->status_box), GTK_ALIGN_CENTER); gtk_widget_set_opacity(GTK_WIDGET(self->status_box), 0.5); self->record_icon = GTK_IMAGE( gtk_image_new_from_icon_name("media-record-symbolic")); gtk_box_append(self->status_box, GTK_WIDGET(self->record_icon)); self->timer_label = GTK_LABEL(gtk_label_new("00:00:00")); gtk_box_append(self->status_box, GTK_WIDGET(self->timer_label)); adw_preferences_group_add(self->status_group, GTK_WIDGET(self->status_box)); adw_preferences_page_add(ADW_PREFERENCES_PAGE(self), self->status_group); } /* ── GObject lifecycle ───────────────────────────────────────────── */ static void gsr_record_page_finalize(GObject *object) { GsrRecordPage *self = GSR_RECORD_PAGE(object); g_clear_handle_id(&self->timer_source_id, g_source_remove); g_free(self->save_directory); #ifdef HAVE_X11 g_free(self->x11_start_stop_accel); g_free(self->x11_pause_accel); #endif G_OBJECT_CLASS(gsr_record_page_parent_class)->finalize(object); } static void gsr_record_page_init(GsrRecordPage *self) { (void)self; } static void gsr_record_page_class_init(GsrRecordPageClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); obj_class->finalize = gsr_record_page_finalize; } /* ── Config apply/read ───────────────────────────────────────────── */ /* Helper: select a combo row item by matching string from model */ static void combo_row_select_string(AdwComboRow *row, const char *value) { if (!value || !value[0]) return; GListModel *model = adw_combo_row_get_model(row); guint n = g_list_model_get_n_items(model); for (guint i = 0; i < n; i++) { GtkStringObject *obj = GTK_STRING_OBJECT(g_list_model_get_item(model, i)); const char *str = gtk_string_object_get_string(obj); g_object_unref(obj); if (g_str_equal(str, value)) { adw_combo_row_set_selected(row, i); return; } } } static const char * combo_row_get_selected_string(AdwComboRow *row) { GtkStringObject *obj = GTK_STRING_OBJECT( adw_combo_row_get_selected_item(row)); return obj ? gtk_string_object_get_string(obj) : ""; } /* Map config container ID to display name */ static const char * container_id_to_display(const char *id) { if (!id) return "mp4"; if (g_str_equal(id, "matroska")) return "mkv"; if (g_str_equal(id, "mpegts")) return "ts"; if (g_str_equal(id, "hls")) return "m3u8"; return id; /* mp4, flv, mov, webm pass through */ } static const char * container_display_to_id(const char *display) { if (!display) return "mp4"; if (g_str_equal(display, "mkv")) return "matroska"; if (g_str_equal(display, "ts")) return "mpegts"; if (g_str_equal(display, "m3u8")) return "hls"; return display; /* mp4, flv, mov, webm pass through */ } void gsr_record_page_apply_config(GsrRecordPage *self, const GsrConfig *config) { const GsrRecordConfig *r = &config->record_config; /* Save directory */ if (r->save_directory && r->save_directory[0]) { g_set_str(&self->save_directory, r->save_directory); adw_action_row_set_subtitle(self->save_dir_row, self->save_directory); } /* Container */ combo_row_select_string(self->container_row, container_id_to_display(r->container)); /* Hotkeys (X11 only) */ #ifdef HAVE_X11 if (self->x11_start_stop_label) { g_free(self->x11_start_stop_accel); self->x11_start_stop_accel = gsr_config_hotkey_to_accel(&r->start_stop_hotkey); gtk_shortcut_label_set_accelerator(self->x11_start_stop_label, self->x11_start_stop_accel ? self->x11_start_stop_accel : ""); } if (self->x11_pause_label) { g_free(self->x11_pause_accel); self->x11_pause_accel = gsr_config_hotkey_to_accel(&r->pause_unpause_hotkey); gtk_shortcut_label_set_accelerator(self->x11_pause_label, self->x11_pause_accel ? self->x11_pause_accel : ""); } #endif } void gsr_record_page_read_config(GsrRecordPage *self, GsrConfig *config) { GsrRecordConfig *r = &config->record_config; /* Save directory */ g_set_str(&r->save_directory, self->save_directory ? self->save_directory : ""); /* Container */ g_set_str(&r->container, container_display_to_id( combo_row_get_selected_string(self->container_row))); /* Hotkeys */ #ifdef HAVE_X11 gsr_config_hotkey_from_accel(&r->start_stop_hotkey, self->x11_start_stop_accel); gsr_config_hotkey_from_accel(&r->pause_unpause_hotkey, self->x11_pause_accel); #endif } /* ── Process management API ──────────────────────────────────────── */ void gsr_record_page_set_active(GsrRecordPage *self, gboolean active) { if (active) { gtk_button_set_label(self->start_button, _("Stop recording")); gtk_widget_remove_css_class(GTK_WIDGET(self->start_button), "suggested-action"); gtk_widget_add_css_class(GTK_WIDGET(self->start_button), "destructive-action"); gtk_widget_set_sensitive(GTK_WIDGET(self->pause_button), TRUE); gtk_widget_set_opacity(GTK_WIDGET(self->status_box), 1.0); gtk_widget_add_css_class(GTK_WIDGET(self->record_icon), "recording-active"); gtk_widget_remove_css_class(GTK_WIDGET(self->record_icon), "recording-paused"); } else { gtk_button_set_label(self->start_button, _("Start recording")); gtk_widget_remove_css_class(GTK_WIDGET(self->start_button), "destructive-action"); gtk_widget_add_css_class(GTK_WIDGET(self->start_button), "suggested-action"); gtk_widget_set_sensitive(GTK_WIDGET(self->pause_button), FALSE); gtk_button_set_label(self->pause_button, _("Pause recording")); gtk_widget_set_opacity(GTK_WIDGET(self->status_box), 0.5); gtk_label_set_text(self->timer_label, "00:00:00"); gtk_image_set_from_icon_name(self->record_icon, "media-record-symbolic"); gtk_widget_remove_css_class(GTK_WIDGET(self->record_icon), "recording-active"); gtk_widget_remove_css_class(GTK_WIDGET(self->record_icon), "recording-paused"); /* Reset internal state (handles external stop via handle_child_death) */ self->is_active = FALSE; self->is_paused = FALSE; g_clear_handle_id(&self->timer_source_id, g_source_remove); } } void gsr_record_page_set_paused(GsrRecordPage *self, gboolean paused) { if (paused) { gtk_button_set_label(self->pause_button, _("Unpause recording")); gtk_image_set_from_icon_name(self->record_icon, "media-playback-pause-symbolic"); gtk_widget_remove_css_class(GTK_WIDGET(self->record_icon), "recording-active"); gtk_widget_add_css_class(GTK_WIDGET(self->record_icon), "recording-paused"); } else { gtk_button_set_label(self->pause_button, _("Pause recording")); gtk_image_set_from_icon_name(self->record_icon, "media-record-symbolic"); gtk_widget_remove_css_class(GTK_WIDGET(self->record_icon), "recording-paused"); gtk_widget_add_css_class(GTK_WIDGET(self->record_icon), "recording-active"); } } void gsr_record_page_update_timer(GsrRecordPage *self, const char *text) { gtk_label_set_text(self->timer_label, text); } const char * gsr_record_page_get_save_dir(GsrRecordPage *self) { return self->save_directory; } char * gsr_record_page_get_container(GsrRecordPage *self) { return g_strdup(container_display_to_id( combo_row_get_selected_string(self->container_row))); } /* ── Public API ──────────────────────────────────────────────────── */ GsrRecordPage * gsr_record_page_new(const GsrInfo *info) { GsrRecordPage *self = g_object_new(GSR_TYPE_RECORD_PAGE, NULL); self->info = info; adw_preferences_page_set_title(ADW_PREFERENCES_PAGE(self), _("Record")); adw_preferences_page_set_icon_name(ADW_PREFERENCES_PAGE(self), "media-record-symbolic"); build_hotkey_group(self); build_output_group(self); build_action_group(self); build_status_group(self); return self; } void gsr_record_page_activate_start_stop(GsrRecordPage *self) { g_return_if_fail(GSR_IS_RECORD_PAGE(self)); gtk_widget_activate(GTK_WIDGET(self->start_button)); } void gsr_record_page_activate_pause(GsrRecordPage *self) { g_return_if_fail(GSR_IS_RECORD_PAGE(self)); if (!self->is_active) return; gtk_widget_activate(GTK_WIDGET(self->pause_button)); } #ifdef HAVE_WAYLAND void gsr_record_page_set_wayland_hotkeys_supported(GsrRecordPage *self, gboolean supported) { g_return_if_fail(GSR_IS_RECORD_PAGE(self)); if (self->hotkey_not_supported_label) gtk_widget_set_visible(self->hotkey_not_supported_label, !supported); if (self->hotkey_info_row) gtk_widget_set_visible(self->hotkey_info_row, supported); } #endif /* HAVE_WAYLAND */