// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect // // SPDX-License-Identifier: GPL-2.0-or-later import Gdk from 'gi://Gdk'; import GLib from 'gi://GLib'; import Gtk from 'gi://Gtk'; import Gio from 'gi://Gio'; import GObject from 'gi://GObject'; const DBUS_NAME = 'org.gnome.Shell.Extensions.GSConnect.Clipboard'; const DBUS_PATH = '/org/gnome/Shell/Extensions/GSConnect/Clipboard'; /** * The service class for this component */ const Clipboard = GObject.registerClass({ GTypeName: 'GSConnectClipboard', Properties: { 'text': GObject.ParamSpec.string( 'text', 'Text Content', 'The current text content of the clipboard', GObject.ParamFlags.READWRITE, '' ), }, }, class Clipboard extends GObject.Object { _init() { super._init(); this._cancellable = new Gio.Cancellable(); this._clipboard = null; this._ownerChangeId = 0; this._nameWatcherId = Gio.bus_watch_name( Gio.BusType.SESSION, DBUS_NAME, Gio.BusNameWatcherFlags.NONE, this._onNameAppeared.bind(this), this._onNameVanished.bind(this) ); } get text() { if (this._text === undefined) this._text = ''; return this._text; } set text(content) { if (this.text === content) return; this._text = content; this.notify('text'); if (typeof content !== 'string') return; if (this._clipboard instanceof Gtk.Clipboard) this._clipboard.set_text(content, -1); if (this._clipboard instanceof Gio.DBusProxy) { this._clipboard.call('SetText', new GLib.Variant('(s)', [content]), Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable) .catch(debug); } } async _onNameAppeared(connection, name, name_owner) { try { // Cleanup the GtkClipboard if (this._clipboard && this._ownerChangeId > 0) { this._clipboard.disconnect(this._ownerChangeId); this._ownerChangeId = 0; } // Create a proxy for the remote clipboard this._clipboard = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION, g_name: DBUS_NAME, g_object_path: DBUS_PATH, g_interface_name: DBUS_NAME, g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES, }); await this._clipboard.init_async(GLib.PRIORITY_DEFAULT, this._cancellable); this._ownerChangeId = this._clipboard.connect('g-signal', this._onOwnerChange.bind(this)); this._onOwnerChange(); if (!globalThis.HAVE_GNOME) { // Directly subscrible signal this.signalHandler = Gio.DBus.session.signal_subscribe( DBUS_NAME, DBUS_NAME, 'OwnerChange', DBUS_PATH, null, Gio.DBusSignalFlags.NONE, this._onOwnerChange.bind(this) ); } } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { debug(e); this._onNameVanished(null, null); } } } _onNameVanished(connection, name) { if (this._clipboard && this._ownerChangeId > 0) { this._clipboard.disconnect(this._ownerChangeId); this._clipboardChangedId = 0; } const display = Gdk.Display.get_default(); this._clipboard = Gtk.Clipboard.get_default(display); this._ownerChangeId = this._clipboard.connect('owner-change', this._onOwnerChange.bind(this)); this._onOwnerChange(); } async _onOwnerChange() { try { if (this._clipboard instanceof Gtk.Clipboard) await this._gtkUpdateText(); else if (this._clipboard instanceof Gio.DBusProxy) await this._proxyUpdateText(); } catch (e) { if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) debug(e); } } _applyUpdate(text) { if (typeof text !== 'string' || this.text === text) return; this._text = text; this.notify('text'); } /* * Proxy Clipboard */ async _proxyUpdateText() { let reply = await this._clipboard.call('GetMimetypes', null, Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable); const mimetypes = reply.deepUnpack()[0]; // Special case for a cleared clipboard if (mimetypes.length === 0) return this._applyUpdate(''); // Special case to ignore copied files if (mimetypes.includes('text/uri-list')) return; reply = await this._clipboard.call('GetText', null, Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable); const text = reply.deepUnpack()[0]; this._applyUpdate(text); } /* * GtkClipboard */ async _gtkUpdateText() { const mimetypes = await new Promise((resolve, reject) => { this._clipboard.request_targets((clipboard, atoms) => resolve(atoms)); }); // Special case for a cleared clipboard if (mimetypes.length === 0) return this._applyUpdate(''); // Special case to ignore copied files if (mimetypes.includes('text/uri-list')) return; const text = await new Promise((resolve, reject) => { this._clipboard.request_text((clipboard, text) => resolve(text)); }); this._applyUpdate(text); } destroy() { if (this._cancellable.is_cancelled()) return; this._cancellable.cancel(); if (this._clipboard && this._ownerChangeId > 0) { this._clipboard.disconnect(this._ownerChangeId); this._ownerChangedId = 0; } if (this._nameWatcherId > 0) { Gio.bus_unwatch_name(this._nameWatcherId); this._nameWatcherId = 0; } if (!globalThis.HAVE_GNOME && this.signalHandler) Gio.DBus.session.signal_unsubscribe(this.signalHandler); } }); export default Clipboard; // vim:tabstop=2:shiftwidth=2:expandtab