clipboard.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
  2. //
  3. // SPDX-License-Identifier: GPL-2.0-or-later
  4. import Gdk from 'gi://Gdk';
  5. import GLib from 'gi://GLib';
  6. import Gtk from 'gi://Gtk';
  7. import Gio from 'gi://Gio';
  8. import GObject from 'gi://GObject';
  9. const DBUS_NAME = 'org.gnome.Shell.Extensions.GSConnect.Clipboard';
  10. const DBUS_PATH = '/org/gnome/Shell/Extensions/GSConnect/Clipboard';
  11. /**
  12. * The service class for this component
  13. */
  14. const Clipboard = GObject.registerClass({
  15. GTypeName: 'GSConnectClipboard',
  16. Properties: {
  17. 'text': GObject.ParamSpec.string(
  18. 'text',
  19. 'Text Content',
  20. 'The current text content of the clipboard',
  21. GObject.ParamFlags.READWRITE,
  22. ''
  23. ),
  24. },
  25. }, class Clipboard extends GObject.Object {
  26. _init() {
  27. super._init();
  28. this._cancellable = new Gio.Cancellable();
  29. this._clipboard = null;
  30. this._ownerChangeId = 0;
  31. this._nameWatcherId = Gio.bus_watch_name(
  32. Gio.BusType.SESSION,
  33. DBUS_NAME,
  34. Gio.BusNameWatcherFlags.NONE,
  35. this._onNameAppeared.bind(this),
  36. this._onNameVanished.bind(this)
  37. );
  38. }
  39. get text() {
  40. if (this._text === undefined)
  41. this._text = '';
  42. return this._text;
  43. }
  44. set text(content) {
  45. if (this.text === content)
  46. return;
  47. this._text = content;
  48. this.notify('text');
  49. if (typeof content !== 'string')
  50. return;
  51. if (this._clipboard instanceof Gtk.Clipboard)
  52. this._clipboard.set_text(content, -1);
  53. if (this._clipboard instanceof Gio.DBusProxy) {
  54. this._clipboard.call('SetText', new GLib.Variant('(s)', [content]),
  55. Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable)
  56. .catch(debug);
  57. }
  58. }
  59. async _onNameAppeared(connection, name, name_owner) {
  60. try {
  61. // Cleanup the GtkClipboard
  62. if (this._clipboard && this._ownerChangeId > 0) {
  63. this._clipboard.disconnect(this._ownerChangeId);
  64. this._ownerChangeId = 0;
  65. }
  66. // Create a proxy for the remote clipboard
  67. this._clipboard = new Gio.DBusProxy({
  68. g_bus_type: Gio.BusType.SESSION,
  69. g_name: DBUS_NAME,
  70. g_object_path: DBUS_PATH,
  71. g_interface_name: DBUS_NAME,
  72. g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES,
  73. });
  74. await this._clipboard.init_async(GLib.PRIORITY_DEFAULT,
  75. this._cancellable);
  76. this._ownerChangeId = this._clipboard.connect('g-signal',
  77. this._onOwnerChange.bind(this));
  78. this._onOwnerChange();
  79. if (!globalThis.HAVE_GNOME) {
  80. // Directly subscrible signal
  81. this.signalHandler = Gio.DBus.session.signal_subscribe(
  82. null,
  83. DBUS_NAME,
  84. 'OwnerChange',
  85. DBUS_PATH,
  86. null,
  87. Gio.DBusSignalFlags.NONE,
  88. this._onOwnerChange.bind(this)
  89. );
  90. }
  91. } catch (e) {
  92. if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
  93. debug(e);
  94. this._onNameVanished(null, null);
  95. }
  96. }
  97. }
  98. _onNameVanished(connection, name) {
  99. if (this._clipboard && this._ownerChangeId > 0) {
  100. this._clipboard.disconnect(this._ownerChangeId);
  101. this._clipboardChangedId = 0;
  102. }
  103. const display = Gdk.Display.get_default();
  104. this._clipboard = Gtk.Clipboard.get_default(display);
  105. this._ownerChangeId = this._clipboard.connect('owner-change',
  106. this._onOwnerChange.bind(this));
  107. this._onOwnerChange();
  108. }
  109. async _onOwnerChange() {
  110. try {
  111. if (this._clipboard instanceof Gtk.Clipboard)
  112. await this._gtkUpdateText();
  113. else if (this._clipboard instanceof Gio.DBusProxy)
  114. await this._proxyUpdateText();
  115. } catch (e) {
  116. if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
  117. debug(e);
  118. }
  119. }
  120. _applyUpdate(text) {
  121. if (typeof text !== 'string' || this.text === text)
  122. return;
  123. this._text = text;
  124. this.notify('text');
  125. }
  126. /*
  127. * Proxy Clipboard
  128. */
  129. async _proxyUpdateText() {
  130. let reply = await this._clipboard.call('GetMimetypes', null,
  131. Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable);
  132. const mimetypes = reply.deepUnpack()[0];
  133. // Special case for a cleared clipboard
  134. if (mimetypes.length === 0)
  135. return this._applyUpdate('');
  136. // Special case to ignore copied files
  137. if (mimetypes.includes('text/uri-list'))
  138. return;
  139. reply = await this._clipboard.call('GetText', null,
  140. Gio.DBusCallFlags.NO_AUTO_START, -1, this._cancellable);
  141. const text = reply.deepUnpack()[0];
  142. this._applyUpdate(text);
  143. }
  144. /*
  145. * GtkClipboard
  146. */
  147. async _gtkUpdateText() {
  148. const mimetypes = await new Promise((resolve, reject) => {
  149. this._clipboard.request_targets((clipboard, atoms) => resolve(atoms));
  150. });
  151. // Special case for a cleared clipboard
  152. if (mimetypes.length === 0)
  153. return this._applyUpdate('');
  154. // Special case to ignore copied files
  155. if (mimetypes.includes('text/uri-list'))
  156. return;
  157. const text = await new Promise((resolve, reject) => {
  158. this._clipboard.request_text((clipboard, text) => resolve(text));
  159. });
  160. this._applyUpdate(text);
  161. }
  162. destroy() {
  163. if (this._cancellable.is_cancelled())
  164. return;
  165. this._cancellable.cancel();
  166. if (this._clipboard && this._ownerChangeId > 0) {
  167. this._clipboard.disconnect(this._ownerChangeId);
  168. this._ownerChangedId = 0;
  169. }
  170. if (this._nameWatcherId > 0) {
  171. Gio.bus_unwatch_name(this._nameWatcherId);
  172. this._nameWatcherId = 0;
  173. }
  174. if (!globalThis.HAVE_GNOME && this.signalHandler)
  175. Gio.DBus.session.signal_unsubscribe(this.signalHandler);
  176. }
  177. });
  178. export default Clipboard;
  179. // vim:tabstop=2:shiftwidth=2:expandtab