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