// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include "native_applications.h" #include // Used only in the isolated preferences helper. The reader's AT-SPI loop // and timeout remain untouched. AccessibleApplication *prefs_application_names(int *count) { *count = 0; if (atspi_init() != 0) return NULL; atspi_set_timeout(250, 250); AtspiAccessible *desktop = atspi_get_desktop(0); if (!desktop) { atspi_exit(); return NULL; } GError *error = NULL; int children = MIN(atspi_accessible_get_child_count(desktop, &error), 4096); g_clear_error(&error); AccessibleApplication *names = g_new0(AccessibleApplication, MAX(children, 1)); for (int i = 0; i < children; i++) { AtspiAccessible *application = atspi_accessible_get_child_at_index(desktop, i, &error); g_clear_error(&error); if (!application) continue; char *name = atspi_accessible_get_name(application, &error); g_clear_error(&error); unsigned int pid = atspi_accessible_get_process_id(application, &error); g_clear_error(&error); if ((name && *name) || pid) names[(*count)++] = (AccessibleApplication){name, pid}; else g_free(name); g_object_unref(application); } g_object_unref(desktop); atspi_exit(); return names; } void prefs_free_application_names(AccessibleApplication *names, int count) { for (int i = 0; i < count; i++) g_free(names[i].name); g_free(names); } static GtkWidget *chooser, *applicationTree; static GtkListStore *applicationStore; static int chooserAction; static void chooser_response(GtkDialog *dialog, gint response, gpointer data) { (void)dialog; (void)data; chooserAction = response == GTK_RESPONSE_OK ? 1 : 2; } static void chooser_activate(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *column, gpointer data) { (void)view; (void)path; (void)column; (void)data; chooserAction = 1; } static gboolean chooser_delete(GtkWidget *widget, GdkEvent *event, gpointer data) { (void)widget; (void)event; (void)data; chooserAction = 2; return TRUE; } static gboolean chooser_key(GtkWidget *widget, GdkEventKey *event, gpointer data) { (void)widget; (void)data; if (event->keyval != GDK_KEY_Escape) return FALSE; chooserAction = 2; return TRUE; } int prefs_application_chooser_init(void) { if (!gtk_init_check(NULL, NULL)) return 0; chooserAction = 0; chooser = gtk_dialog_new_with_buttons("Choose Application Preferences", NULL, 0, "_Cancel", GTK_RESPONSE_CANCEL, "_Open Preferences", GTK_RESPONSE_OK, NULL); gtk_window_set_default_size(GTK_WINDOW(chooser), 480, 420); gtk_dialog_set_default_response(GTK_DIALOG(chooser), GTK_RESPONSE_OK); GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(chooser)); GtkWidget *label = gtk_label_new_with_mnemonic("Choose an _application:"); gtk_box_pack_start(GTK_BOX(content), label, FALSE, FALSE, 6); GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_vexpand(scroll, TRUE); gtk_box_pack_start(GTK_BOX(content), scroll, TRUE, TRUE, 6); applicationStore = gtk_list_store_new(1, G_TYPE_STRING); applicationTree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(applicationStore)); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(applicationTree), -1, "Application", gtk_cell_renderer_text_new(), "text", 0, NULL); gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(applicationTree)), GTK_SELECTION_SINGLE); gtk_label_set_mnemonic_widget(GTK_LABEL(label), applicationTree); atk_object_set_name(gtk_widget_get_accessible(applicationTree), "Applications"); atk_object_set_description(gtk_widget_get_accessible(applicationTree), "Use arrow keys to choose a running application. Enter opens its preferences. Escape cancels. Tab leaves the list."); gtk_container_add(GTK_CONTAINER(scroll), applicationTree); g_signal_connect(chooser, "response", G_CALLBACK(chooser_response), NULL); g_signal_connect(chooser, "delete-event", G_CALLBACK(chooser_delete), NULL); g_signal_connect(chooser, "key-press-event", G_CALLBACK(chooser_key), NULL); g_signal_connect(applicationTree, "row-activated", G_CALLBACK(chooser_activate), NULL); return 1; } void prefs_application_chooser_add(const char *name) { GtkTreeIter iter; gtk_list_store_append(applicationStore, &iter); gtk_list_store_set(applicationStore, &iter, 0, name, -1); } void prefs_application_chooser_present(void) { gtk_widget_show_all(chooser); gtk_window_present(GTK_WINDOW(chooser)); GtkTreePath *path = NULL; gtk_tree_view_get_cursor(GTK_TREE_VIEW(applicationTree), &path, NULL); if (!path && gtk_tree_model_iter_n_children(GTK_TREE_MODEL(applicationStore), NULL)) { path = gtk_tree_path_new_first(); gtk_tree_view_set_cursor(GTK_TREE_VIEW(applicationTree), path, NULL, FALSE); } if (path) gtk_tree_path_free(path); gtk_widget_grab_focus(applicationTree); } void prefs_application_chooser_iterate(void) { while (gtk_events_pending()) gtk_main_iteration_do(FALSE); } int prefs_application_chooser_action(void) { return chooserAction; } char *prefs_application_chooser_selected(void) { GtkTreeIter iter; if (!gtk_tree_selection_get_selected(gtk_tree_view_get_selection(GTK_TREE_VIEW(applicationTree)), NULL, &iter)) { chooserAction = 0; return NULL; } char *name = NULL; gtk_tree_model_get(GTK_TREE_MODEL(applicationStore), &iter, 0, &name, -1); return name; } void prefs_application_chooser_close(void) { gtk_widget_destroy(chooser); g_object_unref(applicationStore); chooser = applicationTree = NULL; applicationStore = NULL; }