findmyphone.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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 Gio = imports.gi.Gio;
  7. const GObject = imports.gi.GObject;
  8. const Gtk = imports.gi.Gtk;
  9. const Components = imports.service.components;
  10. const PluginBase = imports.service.plugin;
  11. var Metadata = {
  12. label: _('Find My Phone'),
  13. description: _('Ring your paired device'),
  14. id: 'org.gnome.Shell.Extensions.GSConnect.Plugin.FindMyPhone',
  15. incomingCapabilities: ['kdeconnect.findmyphone.request'],
  16. outgoingCapabilities: ['kdeconnect.findmyphone.request'],
  17. actions: {
  18. ring: {
  19. label: _('Ring'),
  20. icon_name: 'phonelink-ring-symbolic',
  21. parameter_type: null,
  22. incoming: [],
  23. outgoing: ['kdeconnect.findmyphone.request'],
  24. },
  25. },
  26. };
  27. /**
  28. * FindMyPhone Plugin
  29. * https://github.com/KDE/kdeconnect-kde/tree/master/plugins/findmyphone
  30. */
  31. var Plugin = GObject.registerClass({
  32. GTypeName: 'GSConnectFindMyPhonePlugin',
  33. }, class Plugin extends PluginBase.Plugin {
  34. _init(device) {
  35. super._init(device, 'findmyphone');
  36. this._dialog = null;
  37. this._player = Components.acquire('sound');
  38. this._mixer = Components.acquire('pulseaudio');
  39. }
  40. handlePacket(packet) {
  41. switch (packet.type) {
  42. case 'kdeconnect.findmyphone.request':
  43. this._handleRequest();
  44. break;
  45. }
  46. }
  47. /**
  48. * Handle an incoming location request.
  49. */
  50. _handleRequest() {
  51. try {
  52. // If this is a second request, stop announcing and return
  53. if (this._dialog !== null) {
  54. this._dialog.response(Gtk.ResponseType.DELETE_EVENT);
  55. return;
  56. }
  57. this._dialog = new Dialog({
  58. device: this.device,
  59. plugin: this,
  60. });
  61. this._dialog.connect('response', () => {
  62. this._dialog = null;
  63. });
  64. } catch (e) {
  65. this._cancelRequest();
  66. logError(e, this.device.name);
  67. }
  68. }
  69. /**
  70. * Cancel any ongoing ringing and destroy the dialog.
  71. */
  72. _cancelRequest() {
  73. if (this._dialog !== null)
  74. this._dialog.response(Gtk.ResponseType.DELETE_EVENT);
  75. }
  76. /**
  77. * Request that the remote device announce it's location
  78. */
  79. ring() {
  80. this.device.sendPacket({
  81. type: 'kdeconnect.findmyphone.request',
  82. body: {},
  83. });
  84. }
  85. destroy() {
  86. this._cancelRequest();
  87. if (this._mixer !== undefined)
  88. this._mixer = Components.release('pulseaudio');
  89. if (this._player !== undefined)
  90. this._player = Components.release('sound');
  91. super.destroy();
  92. }
  93. });
  94. /*
  95. * Used to ensure 'audible-bell' is enabled for fallback
  96. */
  97. const _WM_SETTINGS = new Gio.Settings({
  98. schema_id: 'org.gnome.desktop.wm.preferences',
  99. path: '/org/gnome/desktop/wm/preferences/',
  100. });
  101. /**
  102. * A custom GtkMessageDialog for alerting of incoming requests
  103. */
  104. const Dialog = GObject.registerClass({
  105. GTypeName: 'GSConnectFindMyPhoneDialog',
  106. Properties: {
  107. 'device': GObject.ParamSpec.object(
  108. 'device',
  109. 'Device',
  110. 'The device associated with this window',
  111. GObject.ParamFlags.READWRITE,
  112. GObject.Object
  113. ),
  114. 'plugin': GObject.ParamSpec.object(
  115. 'plugin',
  116. 'Plugin',
  117. 'The plugin providing messages',
  118. GObject.ParamFlags.READWRITE,
  119. GObject.Object
  120. ),
  121. },
  122. }, class Dialog extends Gtk.MessageDialog {
  123. _init(params) {
  124. super._init({
  125. buttons: Gtk.ButtonsType.CLOSE,
  126. device: params.device,
  127. image: new Gtk.Image({
  128. icon_name: 'phonelink-ring-symbolic',
  129. pixel_size: 512,
  130. halign: Gtk.Align.CENTER,
  131. hexpand: true,
  132. valign: Gtk.Align.CENTER,
  133. vexpand: true,
  134. visible: true,
  135. }),
  136. plugin: params.plugin,
  137. urgency_hint: true,
  138. });
  139. this.set_keep_above(true);
  140. this.maximize();
  141. this.message_area.destroy();
  142. // If an output stream is available start fading the volume up
  143. if (this.plugin._mixer && this.plugin._mixer.output) {
  144. this._stream = this.plugin._mixer.output;
  145. this._previousMuted = this._stream.muted;
  146. this._previousVolume = this._stream.volume;
  147. this._stream.muted = false;
  148. this._stream.fade(0.85, 15);
  149. // Otherwise ensure audible-bell is enabled
  150. } else {
  151. this._previousBell = _WM_SETTINGS.get_boolean('audible-bell');
  152. _WM_SETTINGS.set_boolean('audible-bell', true);
  153. }
  154. // Start the alarm
  155. if (this.plugin._player !== undefined)
  156. this.plugin._player.loopSound('phone-incoming-call', this.cancellable);
  157. // Show the dialog
  158. this.show_all();
  159. }
  160. vfunc_key_press_event(event) {
  161. this.response(Gtk.ResponseType.DELETE_EVENT);
  162. return Gdk.EVENT_STOP;
  163. }
  164. vfunc_motion_notify_event(event) {
  165. this.response(Gtk.ResponseType.DELETE_EVENT);
  166. return Gdk.EVENT_STOP;
  167. }
  168. vfunc_response(response_id) {
  169. // Stop the alarm
  170. this.cancellable.cancel();
  171. // Restore the mixer level
  172. if (this._stream) {
  173. this._stream.muted = this._previousMuted;
  174. this._stream.fade(this._previousVolume);
  175. // Restore the audible-bell
  176. } else {
  177. _WM_SETTINGS.set_boolean('audible-bell', this._previousBell);
  178. }
  179. this.destroy();
  180. }
  181. get cancellable() {
  182. if (this._cancellable === undefined)
  183. this._cancellable = new Gio.Cancellable();
  184. return this._cancellable;
  185. }
  186. get device() {
  187. if (this._device === undefined)
  188. this._device = null;
  189. return this._device;
  190. }
  191. set device(device) {
  192. this._device = device;
  193. }
  194. get plugin() {
  195. if (this._plugin === undefined)
  196. this._plugin = null;
  197. return this._plugin;
  198. }
  199. set plugin(plugin) {
  200. this._plugin = plugin;
  201. }
  202. });