// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const Clutter = imports.gi.Clutter; const GLib = imports.gi.GLib; const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet; const GnomeBluetooth = imports.gi.GnomeBluetooth; const Lang = imports.lang; const St = imports.gi.St; const Main = imports.ui.main; const MessageTray = imports.ui.messageTray; const NotificationDaemon = imports.ui.notificationDaemon; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; const Indicator = new Lang.Class({ Name: 'BTIndicator', Extends: PanelMenu.SystemIndicator, _init: function() { this.parent(); this._indicator = this.addIndicator(new Gio.ThemedIcon({ name: 'bluetooth-active-symbolic' })); // The Bluetooth menu only appears when Bluetooth is in use, // so just statically build it with a "Turn Off" menu item. this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true); this._item.icon.icon_name = 'bluetooth-active-symbolic'; this._item.menu.addAction(_("Turn Off"), Lang.bind(this, function() { this._applet.killswitch_state = GnomeBluetooth.KillswitchState.SOFT_BLOCKED; })); this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop'); this._applet = new GnomeBluetoothApplet.Applet(); this._applet.connect('devices-changed', Lang.bind(this, this._sync)); this._sync(); this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest)); this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest)); this._applet.connect('auth-request', Lang.bind(this, this._authRequest)); this._applet.connect('auth-service-request', Lang.bind(this, this._authServiceRequest)); this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest)); }, _sync: function() { let connectedDevices = this._applet.get_devices().filter(function(device) { return device.connected; }); let nDevices = connectedDevices.length; let on = nDevices > 0; this._indicator.visible = on; this._item.actor.visible = on; if (on) this._item.status.text = ngettext("%d Connected Device", "%d Connected Devices").format(nDevices); }, _ensureSource: function() { if (!this._source) { this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active'); this._source.policy = new NotificationDaemon.NotificationApplicationPolicy('gnome-bluetooth-panel'); Main.messageTray.add(this._source); } }, _authRequest: function(applet, device_path, name, long_name) { this._ensureSource(); this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name)); }, _authServiceRequest: function(applet, device_path, name, long_name, uuid) { this._ensureSource(); this._source.notify(new AuthServiceNotification(this._source, this._applet, device_path, name, long_name, uuid)); }, _confirmRequest: function(applet, device_path, name, long_name, pin) { this._ensureSource(); this._source.notify(new ConfirmNotification(this._source, this._applet, device_path, name, long_name, pin)); }, _pinRequest: function(applet, device_path, name, long_name, numeric) { this._ensureSource(); this._source.notify(new PinNotification(this._source, this._applet, device_path, name, long_name, numeric)); }, _cancelRequest: function() { this._source.destroy(); } }); const AuthNotification = new Lang.Class({ Name: 'AuthNotification', Extends: MessageTray.Notification, _init: function(source, applet, device_path, name, long_name) { this.parent(source, _("Bluetooth"), _("Authorization request from %s").format(name), { customContent: true }); this.setResident(true); this._applet = applet; this._devicePath = device_path; this.addBody(_("Device %s wants to pair with this computer").format(long_name)); this.addButton('allow', _("Allow")); this.addButton('deny', _("Deny")); this.connect('action-invoked', Lang.bind(this, function(self, action) { if (action == 'allow') this._applet.agent_reply_confirm(this._devicePath, true); else this._applet.agent_reply_confirm(this._devicePath, false); this.destroy(); })); } }); const AuthServiceNotification = new Lang.Class({ Name: 'AuthServiceNotification', Extends: MessageTray.Notification, _init: function(source, applet, device_path, name, long_name, uuid) { this.parent(source, _("Bluetooth"), _("Authorization request from %s").format(name), { customContent: true }); this.setResident(true); this._applet = applet; this._devicePath = device_path; this.addBody(_("Device %s wants access to the service '%s'").format(long_name, uuid)); this.addButton('always-grant', _("Always grant access")); this.addButton('grant', _("Grant this time only")); this.addButton('reject', _("Reject")); this.connect('action-invoked', Lang.bind(this, function(self, action) { switch (action) { case 'always-grant': this._applet.agent_reply_auth_service(this._devicePath, true, true); break; case 'grant': this._applet.agent_reply_auth_service(this._devicePath, true, false); break; case 'reject': default: this._applet.agent_reply_auth_service(this._devicePath, false, false); } this.destroy(); })); } }); const ConfirmNotification = new Lang.Class({ Name: 'ConfirmNotification', Extends: MessageTray.Notification, _init: function(source, applet, device_path, name, long_name, pin) { this.parent(source, _("Bluetooth"), /* Translators: argument is the device short name */ _("Pairing confirmation for %s").format(name), { customContent: true }); this.setResident(true); this._applet = applet; this._devicePath = device_path; this.addBody(_("Device %s wants to pair with this computer").format(long_name)); this.addBody(_("Please confirm whether the Passkey '%06d' matches the one on the device.").format(pin)); /* Translators: this is the verb, not the noun */ this.addButton('matches', _("Matches")); this.addButton('does-not-match', _("Does not match")); this.connect('action-invoked', Lang.bind(this, function(self, action) { if (action == 'matches') this._applet.agent_reply_confirm(this._devicePath, true); else this._applet.agent_reply_confirm(this._devicePath, false); this.destroy(); })); } }); const PinNotification = new Lang.Class({ Name: 'PinNotification', Extends: MessageTray.Notification, _init: function(source, applet, device_path, name, long_name, numeric) { this.parent(source, _("Bluetooth"), _("Pairing request for %s").format(name), { customContent: true }); this.setResident(true); this._applet = applet; this._devicePath = device_path; this._numeric = numeric; this.addBody(_("Device %s wants to pair with this computer").format(long_name)); this.addBody(_("Please enter the PIN mentioned on the device.")); this._entry = new St.Entry(); this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) { let key = event.get_key_symbol(); if (key == Clutter.KEY_Return) { if (this._canActivateOkButton()) this.emit('action-invoked', 'ok'); return true; } else if (key == Clutter.KEY_Escape) { this.emit('action-invoked', 'cancel'); return true; } return false; })); this.addActor(this._entry); this.addButton('ok', _("OK")); this.addButton('cancel', _("Cancel")); this.setButtonSensitive('ok', this._canActivateOkButton()); this._entry.clutter_text.connect('text-changed', Lang.bind(this, function() { this.setButtonSensitive('ok', this._canActivateOkButton()); })); this.connect('action-invoked', Lang.bind(this, function(self, action) { if (action == 'ok') { if (this._numeric) { let num = parseInt(this._entry.text); if (isNaN(num)) { // user reply was empty, or was invalid // cancel the operation num = -1; } this._applet.agent_reply_passkey(this._devicePath, num); } else this._applet.agent_reply_pincode(this._devicePath, this._entry.text); } else { if (this._numeric) this._applet.agent_reply_passkey(this._devicePath, -1); else this._applet.agent_reply_pincode(this._devicePath, null); } this.destroy(); })); }, _canActivateOkButton: function() { // PINs have a fixed length of 6 if (this._numeric) return this._entry.clutter_text.text.length == 6; else return true; } });