clipboard.js 6.4 KB

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