gnome-shell/js/ui/status/bluetooth.js
Jasper St. Pierre ca2e09fe8b sessionMode: Allow changing the session mode at runtime
Since we eventually want to add a system for changing the top panel
contents depending on the current state of the shell, let's use the
"session mode" feature for this, and add a mechanism for updating the
session mode at runtime. Add support for every key besides the two
functional keys, and make all the components update automatically when the
session mode is changed. Add a new lock-screen mode, and make the lock
screen change to this when locked.

https://bugzilla.gnome.org/show_bug.cgi?id=683156
2012-09-04 18:42:44 -03:00

467 lines
18 KiB
JavaScript

// -*- 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 PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const ConnectionState = {
DISCONNECTED: 0,
CONNECTED: 1,
DISCONNECTING: 2,
CONNECTING: 3
}
const Indicator = new Lang.Class({
Name: 'BTIndicator',
Extends: PanelMenu.SystemStatusButton,
_init: function() {
this.parent('bluetooth-disabled-symbolic', _("Bluetooth"));
this._applet = new GnomeBluetoothApplet.Applet();
this._killswitch = new PopupMenu.PopupSwitchMenuItem(_("Bluetooth"), false);
this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch));
this._killswitch.connect('toggled', Lang.bind(this, function() {
let current_state = this._applet.killswitch_state;
if (current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED &&
current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER) {
this._applet.killswitch_state = this._killswitch.state ?
GnomeBluetooth.KillswitchState.UNBLOCKED:
GnomeBluetooth.KillswitchState.SOFT_BLOCKED;
} else
this._killswitch.setToggleState(false);
}));
this._discoverable = new PopupMenu.PopupSwitchMenuItem(_("Visibility"), this._applet.discoverable);
this._applet.connect('notify::discoverable', Lang.bind(this, function() {
this._discoverable.setToggleState(this._applet.discoverable);
}));
this._discoverable.connect('toggled', Lang.bind(this, function() {
this._applet.discoverable = this._discoverable.state;
}));
this._updateKillswitch();
this.menu.addMenuItem(this._killswitch);
this.menu.addMenuItem(this._discoverable);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(),
new PopupMenu.PopupMenuItem(_("Send Files to Device...")),
new PopupMenu.PopupMenuItem(_("Set up a New Device...")),
new PopupMenu.PopupSeparatorMenuItem()];
this._hasDevices = false;
this._fullMenuItems[1].connect('activate', function() {
GLib.spawn_command_line_async('bluetooth-sendto');
});
this._fullMenuItems[2].connect('activate', function() {
GLib.spawn_command_line_async('bluetooth-wizard');
});
for (let i = 0; i < this._fullMenuItems.length; i++) {
let item = this._fullMenuItems[i];
this.menu.addMenuItem(item);
}
this._deviceItemPosition = 3;
this._deviceItems = [];
this._applet.connect('devices-changed', Lang.bind(this, this._updateDevices));
this._updateDevices();
this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu));
this._updateFullMenu();
this.menu.addSettingsAction(_("Bluetooth Settings"), 'bluetooth-properties.desktop');
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('cancel-request', Lang.bind(this, this._cancelRequest));
},
_updateKillswitch: function() {
let current_state = this._applet.killswitch_state;
let on = current_state == GnomeBluetooth.KillswitchState.UNBLOCKED;
let has_adapter = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER;
let can_toggle = current_state != GnomeBluetooth.KillswitchState.NO_ADAPTER &&
current_state != GnomeBluetooth.KillswitchState.HARD_BLOCKED;
this._killswitch.setToggleState(on);
if (can_toggle)
this._killswitch.setStatus(null);
else
/* TRANSLATORS: this means that bluetooth was disabled by hardware rfkill */
this._killswitch.setStatus(_("hardware disabled"));
this.actor.visible = has_adapter;
if (on) {
this._discoverable.actor.show();
this.setIcon('bluetooth-active-symbolic');
} else {
this._discoverable.actor.hide();
this.setIcon('bluetooth-disabled-symbolic');
}
},
_updateDevices: function() {
let devices = this._applet.get_devices();
let newlist = [ ];
for (let i = 0; i < this._deviceItems.length; i++) {
let item = this._deviceItems[i];
let destroy = true;
for (let j = 0; j < devices.length; j++) {
if (item._device.device_path == devices[j].device_path) {
this._updateDeviceItem(item, devices[j]);
destroy = false;
break;
}
}
if (destroy)
item.destroy();
else
newlist.push(item);
}
this._deviceItems = newlist;
this._hasDevices = newlist.length > 0;
for (let i = 0; i < devices.length; i++) {
let d = devices[i];
if (d._item)
continue;
let item = this._createDeviceItem(d);
if (item) {
this.menu.addMenuItem(item, this._deviceItemPosition + this._deviceItems.length);
this._deviceItems.push(item);
this._hasDevices = true;
}
}
},
_updateDeviceItem: function(item, device) {
if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE) {
item.destroy();
return;
}
let prevDevice = item._device;
let prevCapabilities = prevDevice.capabilities;
let prevCanConnect = prevDevice.can_connect;
// adopt the new device object
item._device = device;
device._item = item;
// update properties
item.label.text = device.alias;
if (prevCapabilities != device.capabilities ||
prevCanConnect != device.can_connect) {
// need to rebuild the submenu
item.menu.removeAll();
this._buildDeviceSubMenu(item, device);
}
// update connected property
if (device.can_connect)
item._connectedMenuItem.setToggleState(device.connected);
},
_createDeviceItem: function(device) {
if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE)
return null;
let item = new PopupMenu.PopupSubMenuMenuItem(device.alias);
// adopt the device object, and add a back link
item._device = device;
device._item = item;
this._buildDeviceSubMenu(item, device);
return item;
},
_buildDeviceSubMenu: function(item, device) {
if (device.can_connect) {
let menuitem = new PopupMenu.PopupSwitchMenuItem(_("Connection"), device.connected);
item._connected = device.connected;
item._connectedMenuItem = menuitem;
menuitem.connect('toggled', Lang.bind(this, function() {
if (item._connected > ConnectionState.CONNECTED) {
// operation already in progress, revert
// (should not happen anyway)
menuitem.setToggleState(menuitem.state);
}
if (item._connected) {
item._connected = ConnectionState.DISCONNECTING;
menuitem.setStatus(_("disconnecting..."));
this._applet.disconnect_device(item._device.device_path, function(applet, success) {
if (success) { // apply
item._connected = ConnectionState.DISCONNECTED;
menuitem.setToggleState(false);
} else { // revert
item._connected = ConnectionState.CONNECTED;
menuitem.setToggleState(true);
}
menuitem.setStatus(null);
});
} else {
item._connected = ConnectionState.CONNECTING;
menuitem.setStatus(_("connecting..."));
this._applet.connect_device(item._device.device_path, function(applet, success) {
if (success) { // apply
item._connected = ConnectionState.CONNECTED;
menuitem.setToggleState(true);
} else { // revert
item._connected = ConnectionState.DISCONNECTED;
menuitem.setToggleState(false);
}
menuitem.setStatus(null);
});
}
}));
item.menu.addMenuItem(menuitem);
}
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) {
item.menu.addAction(_("Send Files..."), Lang.bind(this, function() {
this._applet.send_to_address(device.bdaddr, device.alias);
}));
}
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_FILE_TRANSFER) {
item.menu.addAction(_("Browse Files..."), Lang.bind(this, function(event) {
this._applet.browse_address(device.bdaddr, event.get_time(),
Lang.bind(this, function(applet, result) {
try {
applet.browse_address_finish(result);
} catch (e) {
this._ensureSource();
this._source.notify(new MessageTray.Notification(this._source,
_("Bluetooth"),
_("Error browsing device"),
{ body: _("The requested device cannot be browsed, error is '%s'").format(e) }));
}
}));
}));
}
switch (device.type) {
case GnomeBluetoothApplet.Type.KEYBOARD:
item.menu.addSettingsAction(_("Keyboard Settings"), 'gnome-keyboard-panel.desktop');
break;
case GnomeBluetoothApplet.Type.MOUSE:
item.menu.addSettingsAction(_("Mouse Settings"), 'gnome-mouse-panel.desktop');
break;
case GnomeBluetoothApplet.Type.HEADSET:
case GnomeBluetoothApplet.Type.HEADPHONES:
case GnomeBluetoothApplet.Type.OTHER_AUDIO:
item.menu.addSettingsAction(_("Sound Settings"), 'gnome-sound-panel.desktop');
break;
default:
break;
}
},
_updateFullMenu: function() {
if (this._applet.show_full_menu) {
this._showAll(this._fullMenuItems);
if (this._hasDevices)
this._showAll(this._deviceItems);
} else {
this._hideAll(this._fullMenuItems);
this._hideAll(this._deviceItems);
}
},
_showAll: function(items) {
for (let i = 0; i < items.length; i++)
items[i].actor.show();
},
_hideAll: function(items) {
for (let i = 0; i < items.length; i++)
items[i].actor.hide();
},
_destroyAll: function(items) {
for (let i = 0; i < items.length; i++)
items[i].destroy();
},
_ensureSource: function() {
if (!this._source) {
this._source = new MessageTray.Source(_("Bluetooth"), 'bluetooth-active');
Main.messageTray.add(this._source);
}
},
_authRequest: function(applet, device_path, name, long_name, uuid) {
this._ensureSource();
this._source.notify(new AuthNotification(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, 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(this._devicePath, true, true);
break;
case 'grant':
this._applet.agent_reply_auth(this._devicePath, true, false);
break;
case 'reject':
default:
this._applet.agent_reply_auth(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"),
_("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 PIN '%06d' matches the one on the device.").format(pin));
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
return this._entry.clutter_text.text.length == 6;
},
grabFocus: function(lockTray) {
this.parent(lockTray);
global.stage.set_key_focus(this._entry);
}
});