// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later #include "watch.h" static GQueue changes = G_QUEUE_INIT; static gboolean watchEnabled; static unsigned long long watchEpoch; void clipboard_watch_free(ClipboardChange *change) { if (--change->references) return; g_free(change->text); g_free(change); } static void text_received(GtkClipboard *clipboard, const gchar *text, gpointer data) { (void)clipboard; ClipboardChange *change = data; change->text = g_strdup(text); change->done = TRUE; clipboard_watch_free(change); } static void targets_received(GtkClipboard *clipboard, GdkAtom *targets, gint count, gpointer data) { ClipboardChange *change = data; if (!watchEnabled || watchEpoch != change->epoch || !targets || gtk_targets_include_image(targets, count, FALSE) || gtk_targets_include_uri(targets, count) || !gtk_targets_include_text(targets, count)) { change->done = TRUE; clipboard_watch_free(change); return; } // No image or file payload is requested, only a text target. gtk_clipboard_request_text(clipboard, text_received, change); } static void owner_changed(GtkClipboard *clipboard, GdkEventOwnerChange *event, gpointer data) { (void)data; if (!watchEnabled) return; if (g_queue_get_length(&changes) >= 128) clipboard_watch_free(g_queue_pop_head(&changes)); ClipboardChange *change = g_new0(ClipboardChange, 1); change->epoch = watchEpoch; change->timestamp = event->time; change->deadline = g_get_monotonic_time() + 3 * G_TIME_SPAN_SECOND; change->references = 2; // queue and asynchronous callback g_queue_push_tail(&changes, change); gtk_clipboard_request_targets(clipboard, targets_received, change); } int clipboard_watch_init(void) { if (!gtk_init_check(NULL, NULL)) return 0; GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); if (!clipboard) return 0; g_signal_connect(clipboard, "owner-change", G_CALLBACK(owner_changed), NULL); return 1; } void clipboard_watch_enable(int enabled, unsigned long long epoch) { watchEnabled = enabled; watchEpoch = epoch; } void clipboard_watch_iterate(void) { while (gtk_events_pending()) gtk_main_iteration_do(FALSE); } ClipboardChange *clipboard_watch_next(void) { ClipboardChange *change = g_queue_peek_head(&changes); if (!change || (!change->done && g_get_monotonic_time() < change->deadline)) return NULL; return g_queue_pop_head(&changes); }