da7db509e7
If the call to settings.get_connection_by_path in ensureActiveConnectionProps returns null, we'll hit a JS error here. Seems to happen always when activating a VPN connection. Avoid that. Giovanni says: "I believe this is papering over an existing bug, but it's possible for settings.get_connection_by_path() to legitimately return null (if the connection is owned by a different user and invisible to the current one), so the fix is correct anyway." https://bugzilla.gnome.org/show_bug.cgi?id=759793
2113 lines
76 KiB
JavaScript
2113 lines
76 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
const Clutter = imports.gi.Clutter;
|
|
const GLib = imports.gi.GLib;
|
|
const GObject = imports.gi.GObject;
|
|
const Gio = imports.gi.Gio;
|
|
const Gtk = imports.gi.Gtk;
|
|
const Lang = imports.lang;
|
|
const Mainloop = imports.mainloop;
|
|
const NetworkManager = imports.gi.NetworkManager;
|
|
const NMClient = imports.gi.NMClient;
|
|
const NMGtk = imports.gi.NMGtk;
|
|
const Signals = imports.signals;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
|
|
const Animation = imports.ui.animation;
|
|
const Main = imports.ui.main;
|
|
const PanelMenu = imports.ui.panelMenu;
|
|
const PopupMenu = imports.ui.popupMenu;
|
|
const MessageTray = imports.ui.messageTray;
|
|
const ModalDialog = imports.ui.modalDialog;
|
|
const ModemManager = imports.misc.modemManager;
|
|
const Rfkill = imports.ui.status.rfkill;
|
|
const Util = imports.misc.util;
|
|
|
|
const NMConnectionCategory = {
|
|
INVALID: 'invalid',
|
|
WIRED: 'wired',
|
|
WIRELESS: 'wireless',
|
|
WWAN: 'wwan',
|
|
VPN: 'vpn'
|
|
};
|
|
|
|
const NMAccessPointSecurity = {
|
|
NONE: 1,
|
|
WEP: 2,
|
|
WPA_PSK: 3,
|
|
WPA2_PSK: 4,
|
|
WPA_ENT: 5,
|
|
WPA2_ENT: 6
|
|
};
|
|
|
|
const MAX_DEVICE_ITEMS = 4;
|
|
|
|
// small optimization, to avoid using [] all the time
|
|
const NM80211Mode = NetworkManager['80211Mode'];
|
|
const NM80211ApFlags = NetworkManager['80211ApFlags'];
|
|
const NM80211ApSecurityFlags = NetworkManager['80211ApSecurityFlags'];
|
|
|
|
const PortalHelperResult = {
|
|
CANCELLED: 0,
|
|
COMPLETED: 1,
|
|
RECHECK: 2
|
|
};
|
|
|
|
const PortalHelperIface = '<node> \
|
|
<interface name="org.gnome.Shell.PortalHelper"> \
|
|
<method name="Authenticate"> \
|
|
<arg type="o" direction="in" name="connection" /> \
|
|
<arg type="s" direction="in" name="url" /> \
|
|
<arg type="u" direction="in" name="timestamp" /> \
|
|
</method> \
|
|
<method name="Close"> \
|
|
<arg type="o" direction="in" name="connection" /> \
|
|
</method> \
|
|
<method name="Refresh"> \
|
|
<arg type="o" direction="in" name="connection" /> \
|
|
</method> \
|
|
<signal name="Done"> \
|
|
<arg type="o" name="connection" /> \
|
|
<arg type="u" name="result" /> \
|
|
</signal> \
|
|
</interface> \
|
|
</node>';
|
|
const PortalHelperProxy = Gio.DBusProxy.makeProxyWrapper(PortalHelperIface);
|
|
|
|
function ssidCompare(one, two) {
|
|
if (!one || !two)
|
|
return false;
|
|
if (one.length != two.length)
|
|
return false;
|
|
for (let i = 0; i < one.length; i++) {
|
|
if (one[i] != two[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function signalToIcon(value) {
|
|
if (value > 80)
|
|
return 'excellent';
|
|
if (value > 55)
|
|
return 'good';
|
|
if (value > 30)
|
|
return 'ok';
|
|
if (value > 5)
|
|
return 'weak';
|
|
return 'none';
|
|
}
|
|
|
|
function ssidToLabel(ssid) {
|
|
let label = NetworkManager.utils_ssid_to_utf8(ssid);
|
|
if (!label)
|
|
label = _("<unknown>");
|
|
return label;
|
|
}
|
|
|
|
function ensureActiveConnectionProps(active, settings) {
|
|
if (!active._connection) {
|
|
active._connection = settings.get_connection_by_path(active.connection);
|
|
|
|
// This list is guaranteed to have only one device in it.
|
|
let device = active.get_devices()[0]._delegate;
|
|
active._primaryDevice = device;
|
|
}
|
|
}
|
|
|
|
function createSettingsAction(label, device) {
|
|
let item = new PopupMenu.PopupMenuItem(label);
|
|
|
|
item.connect('activate', function() {
|
|
Util.spawnApp(['gnome-control-center', 'network', 'show-device',
|
|
device.get_path()]);
|
|
});
|
|
|
|
return item;
|
|
}
|
|
|
|
const NMConnectionItem = new Lang.Class({
|
|
Name: 'NMConnectionItem',
|
|
|
|
_init: function(section, connection) {
|
|
this._section = section;
|
|
this._connection = connection;
|
|
this._activeConnection = null;
|
|
this._activeConnectionChangedId = 0;
|
|
|
|
this._buildUI();
|
|
this._sync();
|
|
},
|
|
|
|
_buildUI: function() {
|
|
this.labelItem = new PopupMenu.PopupMenuItem('');
|
|
this.labelItem.connect('activate', Lang.bind(this, this._toggle));
|
|
|
|
this.radioItem = new PopupMenu.PopupMenuItem(this._connection.get_id(), false);
|
|
this.radioItem.connect('activate', Lang.bind(this, this._activate));
|
|
},
|
|
|
|
destroy: function() {
|
|
this.labelItem.destroy();
|
|
this.radioItem.destroy();
|
|
},
|
|
|
|
updateForConnection: function(connection) {
|
|
// connection should always be the same object
|
|
// (and object path) as this._connection, but
|
|
// this can be false if NetworkManager was restarted
|
|
// and picked up connections in a different order
|
|
// Just to be safe, we set it here again
|
|
|
|
this._connection = connection;
|
|
this.radioItem.label.text = connection.get_id();
|
|
this._sync();
|
|
this.emit('name-changed');
|
|
},
|
|
|
|
getName: function() {
|
|
return this._connection.get_id();
|
|
},
|
|
|
|
isActive: function() {
|
|
if (this._activeConnection == null)
|
|
return false;
|
|
|
|
return this._activeConnection.state <= NetworkManager.ActiveConnectionState.ACTIVATED;
|
|
},
|
|
|
|
_sync: function() {
|
|
let isActive = this.isActive();
|
|
this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel();
|
|
this.radioItem.setOrnament(isActive ? PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE);
|
|
this.emit('icon-changed');
|
|
},
|
|
|
|
_toggle: function() {
|
|
if (this._activeConnection == null)
|
|
this._section.activateConnection(this._connection);
|
|
else
|
|
this._section.deactivateConnection(this._activeConnection);
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_activate: function() {
|
|
if (this._activeConnection == null)
|
|
this._section.activateConnection(this._connection);
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_connectionStateChanged: function(ac, newstate, reason) {
|
|
this._sync();
|
|
},
|
|
|
|
setActiveConnection: function(activeConnection) {
|
|
if (this._activeConnectionChangedId > 0) {
|
|
this._activeConnection.disconnect(this._activeConnectionChangedId);
|
|
this._activeConnectionChangedId = 0;
|
|
}
|
|
|
|
this._activeConnection = activeConnection;
|
|
|
|
if (this._activeConnection)
|
|
this._activeConnectionChangedId = this._activeConnection.connect('notify::state',
|
|
Lang.bind(this, this._connectionStateChanged));
|
|
|
|
this._sync();
|
|
},
|
|
});
|
|
Signals.addSignalMethods(NMConnectionItem.prototype);
|
|
|
|
const NMConnectionSection = new Lang.Class({
|
|
Name: 'NMConnectionSection',
|
|
Abstract: true,
|
|
|
|
_init: function(client) {
|
|
this._client = client;
|
|
|
|
this._connectionItems = new Map();
|
|
this._connections = [];
|
|
|
|
this._labelSection = new PopupMenu.PopupMenuSection();
|
|
this._radioSection = new PopupMenu.PopupMenuSection();
|
|
|
|
this.item = new PopupMenu.PopupSubMenuMenuItem('', true);
|
|
this.item.menu.addMenuItem(this._labelSection);
|
|
this.item.menu.addMenuItem(this._radioSection);
|
|
|
|
this._notifyConnectivityId = this._client.connect('notify::connectivity', Lang.bind(this, this._iconChanged));
|
|
},
|
|
|
|
destroy: function() {
|
|
if (this._notifyConnectivityId != 0) {
|
|
this._client.disconnect(this._notifyConnectivityId);
|
|
this._notifyConnectivityId = 0;
|
|
}
|
|
|
|
this.item.destroy();
|
|
},
|
|
|
|
_iconChanged: function() {
|
|
this._sync();
|
|
this.emit('icon-changed');
|
|
},
|
|
|
|
_sync: function() {
|
|
let nItems = this._connectionItems.size;
|
|
|
|
this._radioSection.actor.visible = (nItems > 1);
|
|
this._labelSection.actor.visible = (nItems == 1);
|
|
|
|
this.item.label.text = this._getStatus();
|
|
this.item.icon.icon_name = this._getMenuIcon();
|
|
},
|
|
|
|
_getMenuIcon: function() {
|
|
return this.getIndicatorIcon();
|
|
},
|
|
|
|
getConnectLabel: function() {
|
|
return _("Connect");
|
|
},
|
|
|
|
_connectionValid: function(connection) {
|
|
return true;
|
|
},
|
|
|
|
_connectionSortFunction: function(one, two) {
|
|
return GLib.utf8_collate(one.get_id(), two.get_id());
|
|
},
|
|
|
|
_makeConnectionItem: function(connection) {
|
|
return new NMConnectionItem(this, connection);
|
|
},
|
|
|
|
checkConnection: function(connection) {
|
|
if (!this._connectionValid(connection))
|
|
return;
|
|
|
|
// This function is called everytime connection is added or updated
|
|
// In the usual case, we already added this connection and UUID
|
|
// didn't change. So we need to check if we already have an item,
|
|
// and update it for properties in the connection that changed
|
|
// (the only one we care about is the name)
|
|
// But it's also possible we didn't know about this connection
|
|
// (eg, during coldplug, or because it was updated and suddenly
|
|
// it's valid for this device), in which case we add a new item
|
|
|
|
let item = this._connectionItems.get(connection.get_uuid());
|
|
if (item)
|
|
item.updateForConnection(connection);
|
|
else
|
|
this._addConnection(connection);
|
|
},
|
|
|
|
_addConnection: function(connection) {
|
|
let item = this._makeConnectionItem(connection);
|
|
if (!item)
|
|
return;
|
|
|
|
item.connect('icon-changed', Lang.bind(this, function() {
|
|
this._iconChanged();
|
|
}));
|
|
item.connect('activation-failed', Lang.bind(this, function(item, reason) {
|
|
this.emit('activation-failed', reason);
|
|
}));
|
|
item.connect('name-changed', Lang.bind(this, this._sync));
|
|
|
|
let pos = Util.insertSorted(this._connections, connection, Lang.bind(this, this._connectionSortFunction));
|
|
this._labelSection.addMenuItem(item.labelItem, pos);
|
|
this._radioSection.addMenuItem(item.radioItem, pos);
|
|
this._connectionItems.set(connection.get_uuid(), item);
|
|
this._sync();
|
|
},
|
|
|
|
removeConnection: function(connection) {
|
|
let uuid = connection.get_uuid();
|
|
let item = this._connectionItems.get(uuid);
|
|
if (item == undefined)
|
|
return;
|
|
|
|
item.destroy();
|
|
this._connectionItems.delete(uuid);
|
|
|
|
let pos = this._connections.indexOf(connection);
|
|
this._connections.splice(pos, 1);
|
|
|
|
this._sync();
|
|
},
|
|
});
|
|
Signals.addSignalMethods(NMConnectionSection.prototype);
|
|
|
|
const NMConnectionDevice = new Lang.Class({
|
|
Name: 'NMConnectionDevice',
|
|
Extends: NMConnectionSection,
|
|
Abstract: true,
|
|
|
|
_init: function(client, device, settings) {
|
|
this.parent(client);
|
|
this._device = device;
|
|
this._settings = settings;
|
|
this._description = '';
|
|
|
|
this._autoConnectItem = this.item.menu.addAction(_("Connect"), Lang.bind(this, this._autoConnect));
|
|
this._deactivateItem = this._radioSection.addAction(_("Turn Off"), Lang.bind(this, this.deactivateConnection));
|
|
|
|
this._stateChangedId = this._device.connect('state-changed', Lang.bind(this, this._deviceStateChanged));
|
|
this._activeConnectionChangedId = this._device.connect('notify::active-connection', Lang.bind(this, this._activeConnectionChanged));
|
|
},
|
|
|
|
_canReachInternet: function() {
|
|
if (this._client.primary_connection != this._device.active_connection)
|
|
return true;
|
|
|
|
return this._client.connectivity == NetworkManager.ConnectivityState.FULL;
|
|
},
|
|
|
|
_autoConnect: function() {
|
|
let connection = new NetworkManager.Connection();
|
|
this._client.add_and_activate_connection(connection, this._device, null, null);
|
|
},
|
|
|
|
destroy: function() {
|
|
if (this._stateChangedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId);
|
|
this._stateChangedId = 0;
|
|
}
|
|
if (this._activeConnectionChangedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._activeConnectionChangedId);
|
|
this._activeConnectionChangedId = 0;
|
|
}
|
|
|
|
this.parent();
|
|
},
|
|
|
|
_activeConnectionChanged: function() {
|
|
if (this._activeConnection) {
|
|
let item = this._connectionItems.get(this._activeConnection._connection.get_uuid());
|
|
item.setActiveConnection(null);
|
|
}
|
|
|
|
this._activeConnection = this._device.active_connection;
|
|
|
|
if (this._activeConnection) {
|
|
ensureActiveConnectionProps(this._activeConnection, this._settings);
|
|
let item = this._connectionItems.get(this._activeConnection._connection.get_uuid());
|
|
item.setActiveConnection(this._activeConnection);
|
|
}
|
|
},
|
|
|
|
_deviceStateChanged: function(device, newstate, oldstate, reason) {
|
|
if (newstate == oldstate) {
|
|
log('device emitted state-changed without actually changing state');
|
|
return;
|
|
}
|
|
|
|
/* Emit a notification if activation fails, but don't do it
|
|
if the reason is no secrets, as that indicates the user
|
|
cancelled the agent dialog */
|
|
if (newstate == NetworkManager.DeviceState.FAILED &&
|
|
reason != NetworkManager.DeviceStateReason.NO_SECRETS) {
|
|
this.emit('activation-failed', reason);
|
|
}
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_connectionValid: function(connection) {
|
|
return this._device.connection_valid(connection);
|
|
},
|
|
|
|
activateConnection: function(connection) {
|
|
this._client.activate_connection(connection, this._device, null, null);
|
|
},
|
|
|
|
deactivateConnection: function(activeConnection) {
|
|
this._device.disconnect(null);
|
|
},
|
|
|
|
setDeviceDescription: function(desc) {
|
|
this._description = desc;
|
|
this._sync();
|
|
},
|
|
|
|
_getDescription: function() {
|
|
return this._description;
|
|
},
|
|
|
|
_sync: function() {
|
|
let nItems = this._connectionItems.size;
|
|
this._autoConnectItem.actor.visible = (nItems == 0);
|
|
this._deactivateItem.actor.visible = this._device.state > NetworkManager.DeviceState.DISCONNECTED;
|
|
this.parent();
|
|
},
|
|
|
|
_getStatus: function() {
|
|
if (!this._device)
|
|
return '';
|
|
|
|
switch(this._device.state) {
|
|
case NetworkManager.DeviceState.DISCONNECTED:
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Off").format(this._getDescription());
|
|
case NetworkManager.DeviceState.ACTIVATED:
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Connected").format(this._getDescription());
|
|
case NetworkManager.DeviceState.UNMANAGED:
|
|
/* Translators: this is for network devices that are physically present but are not
|
|
under NetworkManager's control (and thus cannot be used in the menu);
|
|
%s is a network identifier */
|
|
return _("%s Unmanaged").format(this._getDescription());
|
|
case NetworkManager.DeviceState.DEACTIVATING:
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Disconnecting").format(this._getDescription());
|
|
case NetworkManager.DeviceState.PREPARE:
|
|
case NetworkManager.DeviceState.CONFIG:
|
|
case NetworkManager.DeviceState.IP_CONFIG:
|
|
case NetworkManager.DeviceState.IP_CHECK:
|
|
case NetworkManager.DeviceState.SECONDARIES:
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Connecting").format(this._getDescription());
|
|
case NetworkManager.DeviceState.NEED_AUTH:
|
|
/* Translators: this is for network connections that require some kind of key or password; %s is a network identifier */
|
|
return _("%s Requires Authentication").format(this._getDescription());
|
|
case NetworkManager.DeviceState.UNAVAILABLE:
|
|
// This state is actually a compound of various states (generically unavailable,
|
|
// firmware missing), that are exposed by different properties (whose state may
|
|
// or may not updated when we receive state-changed).
|
|
if (this._device.firmware_missing) {
|
|
/* Translators: this is for devices that require some kind of firmware or kernel
|
|
module, which is missing; %s is a network identifier */
|
|
return _("Firmware Missing For %s").format(this._getDescription());
|
|
}
|
|
/* Translators: this is for a network device that cannot be activated (for example it
|
|
is disabled by rfkill, or it has no coverage; %s is a network identifier */
|
|
return _("%s Unavailable").format(this._getDescription());
|
|
case NetworkManager.DeviceState.FAILED:
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Connection Failed").format(this._getDescription());
|
|
default:
|
|
log('Device state invalid, is %d'.format(this._device.state));
|
|
return 'invalid';
|
|
}
|
|
},
|
|
});
|
|
|
|
const NMDeviceWired = new Lang.Class({
|
|
Name: 'NMDeviceWired',
|
|
Extends: NMConnectionDevice,
|
|
category: NMConnectionCategory.WIRED,
|
|
|
|
_init: function(client, device, settings) {
|
|
this.parent(client, device, settings);
|
|
|
|
this.item.menu.addMenuItem(createSettingsAction(_("Wired Settings"), device));
|
|
},
|
|
|
|
_hasCarrier: function() {
|
|
if (this._device instanceof NMClient.DeviceEthernet)
|
|
return this._device.carrier;
|
|
else
|
|
return true;
|
|
},
|
|
|
|
_sync: function() {
|
|
this.item.actor.visible = this._hasCarrier();
|
|
this.parent();
|
|
},
|
|
|
|
getIndicatorIcon: function() {
|
|
if (this._device.active_connection) {
|
|
let state = this._device.active_connection.state;
|
|
|
|
if (state == NetworkManager.ActiveConnectionState.ACTIVATING) {
|
|
return 'network-wired-acquiring-symbolic';
|
|
} else if (state == NetworkManager.ActiveConnectionState.ACTIVATED) {
|
|
if (this._canReachInternet())
|
|
return 'network-wired-symbolic';
|
|
else
|
|
return 'network-wired-no-route-symbolic';
|
|
} else {
|
|
return 'network-wired-disconnected-symbolic';
|
|
}
|
|
} else
|
|
return 'network-wired-disconnected-symbolic';
|
|
}
|
|
});
|
|
|
|
const NMDeviceModem = new Lang.Class({
|
|
Name: 'NMDeviceModem',
|
|
Extends: NMConnectionDevice,
|
|
category: NMConnectionCategory.WWAN,
|
|
|
|
_init: function(client, device, settings) {
|
|
this.parent(client, device, settings);
|
|
|
|
this.item.menu.addMenuItem(createSettingsAction(_("Mobile Broadband Settings"), device));
|
|
|
|
this._mobileDevice = null;
|
|
|
|
let capabilities = device.current_capabilities;
|
|
if (device.udi.indexOf('/org/freedesktop/ModemManager1/Modem') == 0)
|
|
this._mobileDevice = new ModemManager.BroadbandModem(device.udi, capabilities);
|
|
else if (capabilities & NetworkManager.DeviceModemCapabilities.GSM_UMTS)
|
|
this._mobileDevice = new ModemManager.ModemGsm(device.udi);
|
|
else if (capabilities & NetworkManager.DeviceModemCapabilities.CDMA_EVDO)
|
|
this._mobileDevice = new ModemManager.ModemCdma(device.udi);
|
|
else if (capabilities & NetworkManager.DeviceModemCapabilities.LTE)
|
|
this._mobileDevice = new ModemManager.ModemGsm(device.udi);
|
|
|
|
if (this._mobileDevice) {
|
|
this._operatorNameId = this._mobileDevice.connect('notify::operator-name', Lang.bind(this, this._sync));
|
|
this._signalQualityId = this._mobileDevice.connect('notify::signal-quality', Lang.bind(this, function() {
|
|
this._iconChanged();
|
|
}));
|
|
}
|
|
},
|
|
|
|
_autoConnect: function() {
|
|
Util.spawn(['gnome-control-center', 'network',
|
|
'connect-3g', this._device.get_path()]);
|
|
},
|
|
|
|
destroy: function() {
|
|
if (this._operatorNameId) {
|
|
this._mobileDevice.disconnect(this._operatorNameId);
|
|
this._operatorNameId = 0;
|
|
}
|
|
if (this._signalQualityId) {
|
|
this._mobileDevice.disconnect(this._signalQualityId);
|
|
this._signalQualityId = 0;
|
|
}
|
|
|
|
this.parent();
|
|
},
|
|
|
|
_getStatus: function() {
|
|
if (!this._client.wwan_hardware_enabled)
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Hardware Disabled").format(this._getDescription());
|
|
else if (!this._client.wwan_enabled)
|
|
/* Translators: this is for a network device that cannot be activated
|
|
because it's disabled by rfkill (airplane mode); %s is a network identifier */
|
|
return _("%s Disabled").format(this._getDescription());
|
|
else if (this._device.state == NetworkManager.DeviceState.ACTIVATED &&
|
|
this._mobileDevice && this._mobileDevice.operator_name)
|
|
return this._mobileDevice.operator_name;
|
|
else
|
|
return this.parent();
|
|
},
|
|
|
|
getIndicatorIcon: function() {
|
|
if (this._device.active_connection) {
|
|
if (this._device.active_connection.state == NetworkManager.ActiveConnectionState.ACTIVATING)
|
|
return 'network-cellular-acquiring-symbolic';
|
|
|
|
return this._getSignalIcon();
|
|
} else {
|
|
return 'network-cellular-signal-none-symbolic';
|
|
}
|
|
},
|
|
|
|
_getSignalIcon: function() {
|
|
return 'network-cellular-signal-' + signalToIcon(this._mobileDevice.signal_quality) + '-symbolic';
|
|
},
|
|
});
|
|
|
|
const NMDeviceBluetooth = new Lang.Class({
|
|
Name: 'NMDeviceBluetooth',
|
|
Extends: NMConnectionDevice,
|
|
category: NMConnectionCategory.WWAN,
|
|
|
|
_init: function(client, device, settings) {
|
|
this.parent(client, device, settings);
|
|
|
|
this.item.menu.addMenuItem(createSettingsAction(_("Bluetooth Settings"), device));
|
|
},
|
|
|
|
_getDescription: function() {
|
|
return this._device.name;
|
|
},
|
|
|
|
getConnectLabel: function() {
|
|
return _("Connect to Internet");
|
|
},
|
|
|
|
getIndicatorIcon: function() {
|
|
if (this._device.active_connection) {
|
|
let state = this._device.active_connection.state;
|
|
if (state == NetworkManager.ActiveConnectionState.ACTIVATING)
|
|
return 'network-cellular-acquiring-symbolic';
|
|
else if (state == NetworkManager.ActiveConnectionState.ACTIVATED)
|
|
return 'network-cellular-connected-symbolic';
|
|
else
|
|
return 'network-cellular-signal-none-symbolic';
|
|
} else {
|
|
return 'network-cellular-signal-none-symbolic';
|
|
}
|
|
}
|
|
});
|
|
|
|
const NMWirelessDialogItem = new Lang.Class({
|
|
Name: 'NMWirelessDialogItem',
|
|
|
|
_init: function(network) {
|
|
this._network = network;
|
|
this._ap = network.accessPoints[0];
|
|
|
|
this.actor = new St.BoxLayout({ style_class: 'nm-dialog-item',
|
|
can_focus: true,
|
|
reactive: true });
|
|
this.actor.connect('key-focus-in', Lang.bind(this, function() {
|
|
this.emit('selected');
|
|
}));
|
|
let action = new Clutter.ClickAction();
|
|
action.connect('clicked', Lang.bind(this, function() {
|
|
this.actor.grab_key_focus();
|
|
}));
|
|
this.actor.add_action(action);
|
|
|
|
let title = ssidToLabel(this._ap.get_ssid());
|
|
this._label = new St.Label({ text: title });
|
|
|
|
this.actor.label_actor = this._label;
|
|
this.actor.add(this._label, { x_align: St.Align.START });
|
|
|
|
this._selectedIcon = new St.Icon({ style_class: 'nm-dialog-icon',
|
|
icon_name: 'object-select-symbolic' });
|
|
this.actor.add(this._selectedIcon);
|
|
|
|
this._icons = new St.BoxLayout({ style_class: 'nm-dialog-icons' });
|
|
this.actor.add(this._icons, { expand: true, x_fill: false, x_align: St.Align.END });
|
|
|
|
this._secureIcon = new St.Icon({ style_class: 'nm-dialog-icon' });
|
|
if (this._ap._secType != NMAccessPointSecurity.NONE)
|
|
this._secureIcon.icon_name = 'network-wireless-encrypted-symbolic';
|
|
this._icons.add_actor(this._secureIcon);
|
|
|
|
this._signalIcon = new St.Icon({ style_class: 'nm-dialog-icon' });
|
|
this._icons.add_actor(this._signalIcon);
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_sync: function() {
|
|
this._signalIcon.icon_name = this._getSignalIcon();
|
|
},
|
|
|
|
updateBestAP: function(ap) {
|
|
this._ap = ap;
|
|
this._sync();
|
|
},
|
|
|
|
setActive: function(isActive) {
|
|
this._selectedIcon.opacity = isActive ? 255 : 0;
|
|
},
|
|
|
|
_getSignalIcon: function() {
|
|
if (this._ap.mode == NM80211Mode.ADHOC)
|
|
return 'network-workgroup-symbolic';
|
|
else
|
|
return 'network-wireless-signal-' + signalToIcon(this._ap.strength) + '-symbolic';
|
|
}
|
|
});
|
|
Signals.addSignalMethods(NMWirelessDialogItem.prototype);
|
|
|
|
const NMWirelessDialog = new Lang.Class({
|
|
Name: 'NMWirelessDialog',
|
|
Extends: ModalDialog.ModalDialog,
|
|
|
|
_init: function(client, device, settings) {
|
|
this.parent({ styleClass: 'nm-dialog' });
|
|
|
|
this._client = client;
|
|
this._device = device;
|
|
|
|
this._wirelessEnabledChangedId = this._client.connect('notify::wireless-enabled',
|
|
Lang.bind(this, this._syncView));
|
|
|
|
this._rfkill = Rfkill.getRfkillManager();
|
|
this._airplaneModeChangedId = this._rfkill.connect('airplane-mode-changed',
|
|
Lang.bind(this, this._syncView));
|
|
|
|
this._networks = [];
|
|
this._buildLayout();
|
|
|
|
let connections = settings.list_connections();
|
|
this._connections = connections.filter(Lang.bind(this, function(connection) {
|
|
return device.connection_valid(connection);
|
|
}));
|
|
|
|
this._apAddedId = device.connect('access-point-added', Lang.bind(this, this._accessPointAdded));
|
|
this._apRemovedId = device.connect('access-point-removed', Lang.bind(this, this._accessPointRemoved));
|
|
this._activeApChangedId = device.connect('notify::active-access-point', Lang.bind(this, this._activeApChanged));
|
|
|
|
// accessPointAdded will also create dialog items
|
|
let accessPoints = device.get_access_points() || [ ];
|
|
accessPoints.forEach(Lang.bind(this, function(ap) {
|
|
this._accessPointAdded(this._device, ap);
|
|
}));
|
|
|
|
this._selectedNetwork = null;
|
|
this._activeApChanged();
|
|
this._updateSensitivity();
|
|
this._syncView();
|
|
|
|
this._scanTimeoutId = Mainloop.timeout_add_seconds(15, Lang.bind(this, this._onScanTimeout));
|
|
GLib.Source.set_name_by_id(this._scanTimeoutId, '[gnome-shell] this._onScanTimeout');
|
|
this._onScanTimeout();
|
|
},
|
|
|
|
destroy: function() {
|
|
if (this._apAddedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._apAddedId);
|
|
this._apAddedId = 0;
|
|
}
|
|
if (this._apRemovedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._apRemovedId);
|
|
this._apRemovedId = 0;
|
|
}
|
|
if (this._activeApChangedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._activeApChangedId);
|
|
this._activeApChangedId = 0;
|
|
}
|
|
if (this._wirelessEnabledChangedId) {
|
|
this._client.disconnect(this._wirelessEnabledChangedId);
|
|
this._wirelessEnabledChangedId = 0;
|
|
}
|
|
if (this._airplaneModeChangedId) {
|
|
this._rfkill.disconnect(this._airplaneModeChangedId);
|
|
this._airplaneModeChangedId = 0;
|
|
}
|
|
|
|
if (this._scanTimeoutId) {
|
|
Mainloop.source_remove(this._scanTimeoutId);
|
|
this._scanTimeoutId = 0;
|
|
}
|
|
|
|
this.parent();
|
|
},
|
|
|
|
_onScanTimeout: function() {
|
|
this._device.request_scan_simple(null);
|
|
return GLib.SOURCE_CONTINUE;
|
|
},
|
|
|
|
_activeApChanged: function() {
|
|
if (this._activeNetwork)
|
|
this._activeNetwork.item.setActive(false);
|
|
|
|
this._activeNetwork = null;
|
|
if (this._device.active_access_point) {
|
|
let idx = this._findNetwork(this._device.active_access_point);
|
|
if (idx >= 0)
|
|
this._activeNetwork = this._networks[idx];
|
|
}
|
|
|
|
if (this._activeNetwork)
|
|
this._activeNetwork.item.setActive(true);
|
|
this._updateSensitivity();
|
|
},
|
|
|
|
_updateSensitivity: function() {
|
|
let connectSensitive = this._client.wireless_enabled && this._selectedNetwork && (this._selectedNetwork != this._activeNetwork);
|
|
this._connectButton.reactive = connectSensitive;
|
|
this._connectButton.can_focus = connectSensitive;
|
|
},
|
|
|
|
_syncView: function() {
|
|
if (this._rfkill.airplaneMode) {
|
|
this._airplaneBox.show();
|
|
|
|
this._airplaneIcon.icon_name = 'airplane-mode-symbolic';
|
|
this._airplaneHeadline.text = _("Airplane Mode is On");
|
|
this._airplaneText.text = _("Wi-Fi is disabled when airplane mode is on.");
|
|
this._airplaneButton.label = _("Turn Off Airplane Mode");
|
|
|
|
this._airplaneButton.visible = !this._rfkill.hwAirplaneMode;
|
|
this._airplaneInactive.visible = this._rfkill.hwAirplaneMode;
|
|
this._noNetworksBox.hide();
|
|
} else if (!this._client.wireless_enabled) {
|
|
this._airplaneBox.show();
|
|
|
|
this._airplaneIcon.icon_name = 'dialog-information-symbolic';
|
|
this._airplaneHeadline.text = _("Wi-Fi is Off");
|
|
this._airplaneText.text = _("Wi-Fi needs to be turned on in order to connect to a network.");
|
|
this._airplaneButton.label = _("Turn On Wi-Fi");
|
|
|
|
this._airplaneButton.show();
|
|
this._airplaneInactive.hide();
|
|
this._noNetworksBox.hide();
|
|
} else {
|
|
this._airplaneBox.hide();
|
|
|
|
this._noNetworksBox.visible = (this._networks.length == 0);
|
|
}
|
|
|
|
if (this._noNetworksBox.visible)
|
|
this._noNetworksSpinner.play();
|
|
else
|
|
this._noNetworksSpinner.stop();
|
|
},
|
|
|
|
_buildLayout: function() {
|
|
let headline = new St.BoxLayout({ style_class: 'nm-dialog-header-hbox' });
|
|
|
|
let icon = new St.Icon({ style_class: 'nm-dialog-header-icon',
|
|
icon_name: 'network-wireless-signal-excellent-symbolic' });
|
|
|
|
let titleBox = new St.BoxLayout({ vertical: true });
|
|
let title = new St.Label({ style_class: 'nm-dialog-header',
|
|
text: _("Wi-Fi Networks") });
|
|
let subtitle = new St.Label({ style_class: 'nm-dialog-subheader',
|
|
text: _("Select a network") });
|
|
titleBox.add(title);
|
|
titleBox.add(subtitle);
|
|
|
|
headline.add(icon);
|
|
headline.add(titleBox);
|
|
|
|
this.contentLayout.style_class = 'nm-dialog-content';
|
|
this.contentLayout.add(headline);
|
|
|
|
this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
|
|
|
this._itemBox = new St.BoxLayout({ vertical: true });
|
|
this._scrollView = new St.ScrollView({ style_class: 'nm-dialog-scroll-view' });
|
|
this._scrollView.set_x_expand(true);
|
|
this._scrollView.set_y_expand(true);
|
|
this._scrollView.set_policy(Gtk.PolicyType.NEVER,
|
|
Gtk.PolicyType.AUTOMATIC);
|
|
this._scrollView.add_actor(this._itemBox);
|
|
this._stack.add_child(this._scrollView);
|
|
|
|
this._noNetworksBox = new St.BoxLayout({ vertical: true,
|
|
style_class: 'no-networks-box',
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
y_align: Clutter.ActorAlign.CENTER });
|
|
|
|
let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
|
|
this._noNetworksSpinner = new Animation.AnimatedIcon(file, 16, 16);
|
|
this._noNetworksBox.add_actor(this._noNetworksSpinner.actor);
|
|
this._noNetworksBox.add_actor(new St.Label({ style_class: 'no-networks-label',
|
|
text: _("No Networks") }));
|
|
this._stack.add_child(this._noNetworksBox);
|
|
|
|
this._airplaneBox = new St.BoxLayout({ vertical: true,
|
|
style_class: 'nm-dialog-airplane-box',
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
y_align: Clutter.ActorAlign.CENTER });
|
|
this._airplaneIcon = new St.Icon({ icon_size: 48 });
|
|
this._airplaneHeadline = new St.Label({ style_class: 'nm-dialog-airplane-headline headline' });
|
|
this._airplaneText = new St.Label({ style_class: 'nm-dialog-airplane-text' });
|
|
|
|
let airplaneSubStack = new St.Widget({ layout_manager: new Clutter.BinLayout });
|
|
this._airplaneButton = new St.Button({ style_class: 'modal-dialog-button button' });
|
|
this._airplaneButton.connect('clicked', Lang.bind(this, function() {
|
|
if (this._rfkill.airplaneMode)
|
|
this._rfkill.airplaneMode = false;
|
|
else
|
|
this._client.wireless_enabled = true;
|
|
}));
|
|
airplaneSubStack.add_actor(this._airplaneButton);
|
|
this._airplaneInactive = new St.Label({ style_class: 'nm-dialog-airplane-text',
|
|
text: _("Use hardware switch to turn off") });
|
|
airplaneSubStack.add_actor(this._airplaneInactive);
|
|
|
|
this._airplaneBox.add(this._airplaneIcon, { x_align: St.Align.MIDDLE });
|
|
this._airplaneBox.add(this._airplaneHeadline, { x_align: St.Align.MIDDLE });
|
|
this._airplaneBox.add(this._airplaneText, { x_align: St.Align.MIDDLE });
|
|
this._airplaneBox.add(airplaneSubStack, { x_align: St.Align.MIDDLE });
|
|
this._stack.add_child(this._airplaneBox);
|
|
|
|
this.contentLayout.add(this._stack, { expand: true });
|
|
|
|
this._disconnectButton = this.addButton({ action: Lang.bind(this, this.close),
|
|
label: _("Cancel"),
|
|
key: Clutter.Escape });
|
|
this._connectButton = this.addButton({ action: Lang.bind(this, this._connect),
|
|
label: _("Connect"),
|
|
key: Clutter.Return });
|
|
},
|
|
|
|
_connect: function() {
|
|
let network = this._selectedNetwork;
|
|
if (network.connections.length > 0) {
|
|
let connection = network.connections[0];
|
|
this._client.activate_connection(connection, this._device, null, null);
|
|
} else {
|
|
let accessPoints = network.accessPoints;
|
|
if ((accessPoints[0]._secType == NMAccessPointSecurity.WPA2_ENT)
|
|
|| (accessPoints[0]._secType == NMAccessPointSecurity.WPA_ENT)) {
|
|
// 802.1x-enabled APs require further configuration, so they're
|
|
// handled in gnome-control-center
|
|
Util.spawn(['gnome-control-center', 'network', 'connect-8021x-wifi',
|
|
this._device.get_path(), accessPoints[0].dbus_path]);
|
|
} else {
|
|
let connection = new NetworkManager.Connection();
|
|
this._client.add_and_activate_connection(connection, this._device, accessPoints[0].dbus_path, null)
|
|
}
|
|
}
|
|
|
|
this.close();
|
|
},
|
|
|
|
_notifySsidCb: function(accessPoint) {
|
|
if (accessPoint.get_ssid() != null) {
|
|
accessPoint.disconnect(accessPoint._notifySsidId);
|
|
accessPoint._notifySsidId = 0;
|
|
this._accessPointAdded(this._device, accessPoint);
|
|
}
|
|
},
|
|
|
|
_getApSecurityType: function(accessPoint) {
|
|
if (accessPoint._secType)
|
|
return accessPoint._secType;
|
|
|
|
let flags = accessPoint.flags;
|
|
let wpa_flags = accessPoint.wpa_flags;
|
|
let rsn_flags = accessPoint.rsn_flags;
|
|
let type;
|
|
if (rsn_flags != NM80211ApSecurityFlags.NONE) {
|
|
/* RSN check first so that WPA+WPA2 APs are treated as RSN/WPA2 */
|
|
if (rsn_flags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
|
|
type = NMAccessPointSecurity.WPA2_ENT;
|
|
else if (rsn_flags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
|
|
type = NMAccessPointSecurity.WPA2_PSK;
|
|
} else if (wpa_flags != NM80211ApSecurityFlags.NONE) {
|
|
if (wpa_flags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
|
|
type = NMAccessPointSecurity.WPA_ENT;
|
|
else if (wpa_flags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
|
|
type = NMAccessPointSecurity.WPA_PSK;
|
|
} else {
|
|
if (flags & NM80211ApFlags.PRIVACY)
|
|
type = NMAccessPointSecurity.WEP;
|
|
else
|
|
type = NMAccessPointSecurity.NONE;
|
|
}
|
|
|
|
// cache the found value to avoid checking flags all the time
|
|
accessPoint._secType = type;
|
|
return type;
|
|
},
|
|
|
|
_networkSortFunction: function(one, two) {
|
|
let oneHasConnection = one.connections.length != 0;
|
|
let twoHasConnection = two.connections.length != 0;
|
|
|
|
// place known connections first
|
|
// (-1 = good order, 1 = wrong order)
|
|
if (oneHasConnection && !twoHasConnection)
|
|
return -1;
|
|
else if (!oneHasConnection && twoHasConnection)
|
|
return 1;
|
|
|
|
let oneStrength = one.accessPoints[0].strength;
|
|
let twoStrength = two.accessPoints[0].strength;
|
|
|
|
// place stronger connections first
|
|
if (oneStrength != twoStrength)
|
|
return oneStrength < twoStrength ? 1 : -1;
|
|
|
|
let oneHasSecurity = one.security != NMAccessPointSecurity.NONE;
|
|
let twoHasSecurity = two.security != NMAccessPointSecurity.NONE;
|
|
|
|
// place secure connections first
|
|
// (we treat WEP/WPA/WPA2 the same as there is no way to
|
|
// take them apart from the UI)
|
|
if (oneHasSecurity && !twoHasSecurity)
|
|
return -1;
|
|
else if (!oneHasSecurity && twoHasSecurity)
|
|
return 1;
|
|
|
|
// sort alphabetically
|
|
return GLib.utf8_collate(one.ssidText, two.ssidText);
|
|
},
|
|
|
|
_networkCompare: function(network, accessPoint) {
|
|
if (!ssidCompare(network.ssid, accessPoint.get_ssid()))
|
|
return false;
|
|
if (network.mode != accessPoint.mode)
|
|
return false;
|
|
if (network.security != this._getApSecurityType(accessPoint))
|
|
return false;
|
|
|
|
return true;
|
|
},
|
|
|
|
_findExistingNetwork: function(accessPoint) {
|
|
for (let i = 0; i < this._networks.length; i++) {
|
|
let network = this._networks[i];
|
|
for (let j = 0; j < network.accessPoints.length; j++) {
|
|
if (network.accessPoints[j] == accessPoint)
|
|
return { network: i, ap: j };
|
|
}
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
_findNetwork: function(accessPoint) {
|
|
if (accessPoint.get_ssid() == null)
|
|
return -1;
|
|
|
|
for (let i = 0; i < this._networks.length; i++) {
|
|
if (this._networkCompare(this._networks[i], accessPoint))
|
|
return i;
|
|
}
|
|
return -1;
|
|
},
|
|
|
|
_checkConnections: function(network, accessPoint) {
|
|
this._connections.forEach(function(connection) {
|
|
if (accessPoint.connection_valid(connection) &&
|
|
network.connections.indexOf(connection) == -1) {
|
|
network.connections.push(connection);
|
|
}
|
|
});
|
|
},
|
|
|
|
_accessPointAdded: function(device, accessPoint) {
|
|
if (accessPoint.get_ssid() == null) {
|
|
// This access point is not visible yet
|
|
// Wait for it to get a ssid
|
|
accessPoint._notifySsidId = accessPoint.connect('notify::ssid', Lang.bind(this, this._notifySsidCb));
|
|
return;
|
|
}
|
|
|
|
let pos = this._findNetwork(accessPoint);
|
|
let network;
|
|
|
|
if (pos != -1) {
|
|
network = this._networks[pos];
|
|
if (network.accessPoints.indexOf(accessPoint) != -1) {
|
|
log('Access point was already seen, not adding again');
|
|
return;
|
|
}
|
|
|
|
Util.insertSorted(network.accessPoints, accessPoint, function(one, two) {
|
|
return two.strength - one.strength;
|
|
});
|
|
network.item.updateBestAP(network.accessPoints[0]);
|
|
this._checkConnections(network, accessPoint);
|
|
|
|
this._resortItems();
|
|
} else {
|
|
network = { ssid: accessPoint.get_ssid(),
|
|
mode: accessPoint.mode,
|
|
security: this._getApSecurityType(accessPoint),
|
|
connections: [ ],
|
|
item: null,
|
|
accessPoints: [ accessPoint ]
|
|
};
|
|
network.ssidText = ssidToLabel(network.ssid);
|
|
this._checkConnections(network, accessPoint);
|
|
|
|
let newPos = Util.insertSorted(this._networks, network, this._networkSortFunction);
|
|
this._createNetworkItem(network);
|
|
this._itemBox.insert_child_at_index(network.item.actor, newPos);
|
|
}
|
|
|
|
this._syncView();
|
|
},
|
|
|
|
_accessPointRemoved: function(device, accessPoint) {
|
|
let res = this._findExistingNetwork(accessPoint);
|
|
|
|
if (res == null) {
|
|
log('Removing an access point that was never added');
|
|
return;
|
|
}
|
|
|
|
let network = this._networks[res.network];
|
|
network.accessPoints.splice(res.ap, 1);
|
|
|
|
if (network.accessPoints.length == 0) {
|
|
network.item.actor.destroy();
|
|
this._networks.splice(res.network, 1);
|
|
} else {
|
|
network.item.updateBestAP(network.accessPoints[0]);
|
|
this._resortItems();
|
|
}
|
|
|
|
this._syncView();
|
|
},
|
|
|
|
_resortItems: function() {
|
|
let adjustment = this._scrollView.vscroll.adjustment;
|
|
let scrollValue = adjustment.value;
|
|
|
|
this._itemBox.remove_all_children();
|
|
this._networks.forEach(Lang.bind(this, function(network) {
|
|
this._itemBox.add_child(network.item.actor);
|
|
}));
|
|
|
|
adjustment.value = scrollValue;
|
|
},
|
|
|
|
_selectNetwork: function(network) {
|
|
if (this._selectedNetwork)
|
|
this._selectedNetwork.item.actor.remove_style_pseudo_class('selected');
|
|
|
|
this._selectedNetwork = network;
|
|
this._updateSensitivity();
|
|
|
|
if (this._selectedNetwork)
|
|
this._selectedNetwork.item.actor.add_style_pseudo_class('selected');
|
|
},
|
|
|
|
_createNetworkItem: function(network) {
|
|
network.item = new NMWirelessDialogItem(network);
|
|
network.item.setActive(network == this._selectedNetwork);
|
|
network.item.connect('selected', Lang.bind(this, function() {
|
|
Util.ensureActorVisibleInScrollView(this._scrollView, network.item.actor);
|
|
this._selectNetwork(network);
|
|
}));
|
|
},
|
|
});
|
|
|
|
const NMDeviceWireless = new Lang.Class({
|
|
Name: 'NMDeviceWireless',
|
|
category: NMConnectionCategory.WIRELESS,
|
|
|
|
_init: function(client, device, settings) {
|
|
this._client = client;
|
|
this._device = device;
|
|
this._settings = settings;
|
|
|
|
this._description = '';
|
|
|
|
this.item = new PopupMenu.PopupSubMenuMenuItem('', true);
|
|
this.item.menu.addAction(_("Select Network"), Lang.bind(this, this._showDialog));
|
|
|
|
this._toggleItem = new PopupMenu.PopupMenuItem('');
|
|
this._toggleItem.connect('activate', Lang.bind(this, this._toggleWifi));
|
|
this.item.menu.addMenuItem(this._toggleItem);
|
|
|
|
this.item.menu.addMenuItem(createSettingsAction(_("Wi-Fi Settings"), device));
|
|
|
|
this._wirelessEnabledChangedId = this._client.connect('notify::wireless-enabled', Lang.bind(this, this._sync));
|
|
this._wirelessHwEnabledChangedId = this._client.connect('notify::wireless-hardware-enabled', Lang.bind(this, this._sync));
|
|
this._activeApChangedId = this._device.connect('notify::active-access-point', Lang.bind(this, this._activeApChanged));
|
|
this._stateChangedId = this._device.connect('state-changed', Lang.bind(this, this._deviceStateChanged));
|
|
this._notifyConnectivityId = this._client.connect('notify::connectivity', Lang.bind(this, this._iconChanged));
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_iconChanged: function() {
|
|
this._sync();
|
|
this.emit('icon-changed');
|
|
},
|
|
|
|
destroy: function() {
|
|
if (this._activeApChangedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._activeApChangedId);
|
|
this._activeApChangedId = 0;
|
|
}
|
|
if (this._stateChangedId) {
|
|
GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId);
|
|
this._stateChangedId = 0;
|
|
}
|
|
if (this._strengthChangedId > 0) {
|
|
this._activeAccessPoint.disconnect(this._strengthChangedId);
|
|
this._strengthChangedId = 0;
|
|
}
|
|
if (this._wirelessEnabledChangedId) {
|
|
this._client.disconnect(this._wirelessEnabledChangedId);
|
|
this._wirelessEnabledChangedId = 0;
|
|
}
|
|
if (this._wirelessHwEnabledChangedId) {
|
|
this._client.disconnect(this._wirelessHwEnabledChangedId);
|
|
this._wirelessHwEnabledChangedId = 0;
|
|
}
|
|
if (this._dialog) {
|
|
this._dialog.destroy();
|
|
this._dialog = null;
|
|
}
|
|
if (this._notifyConnectivityId) {
|
|
this._client.disconnect(this._notifyConnectivityId);
|
|
this._notifyConnectivityId = 0;
|
|
}
|
|
|
|
this.item.destroy();
|
|
},
|
|
|
|
_deviceStateChanged: function(device, newstate, oldstate, reason) {
|
|
if (newstate == oldstate) {
|
|
log('device emitted state-changed without actually changing state');
|
|
return;
|
|
}
|
|
|
|
/* Emit a notification if activation fails, but don't do it
|
|
if the reason is no secrets, as that indicates the user
|
|
cancelled the agent dialog */
|
|
if (newstate == NetworkManager.DeviceState.FAILED &&
|
|
reason != NetworkManager.DeviceStateReason.NO_SECRETS) {
|
|
this.emit('activation-failed', reason);
|
|
}
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_toggleWifi: function() {
|
|
this._client.wireless_enabled = !this._client.wireless_enabled;
|
|
},
|
|
|
|
_showDialog: function() {
|
|
this._dialog = new NMWirelessDialog(this._client, this._device, this._settings);
|
|
this._dialog.connect('closed', Lang.bind(this, this._dialogClosed));
|
|
this._dialog.open();
|
|
},
|
|
|
|
_dialogClosed: function() {
|
|
this._dialog.destroy();
|
|
this._dialog = null;
|
|
},
|
|
|
|
_strengthChanged: function() {
|
|
this._iconChanged();
|
|
},
|
|
|
|
_activeApChanged: function() {
|
|
if (this._activeAccessPoint) {
|
|
this._activeAccessPoint.disconnect(this._strengthChangedId);
|
|
this._strengthChangedId = 0;
|
|
}
|
|
|
|
this._activeAccessPoint = this._device.active_access_point;
|
|
|
|
if (this._activeAccessPoint) {
|
|
this._strengthChangedId = this._activeAccessPoint.connect('notify::strength',
|
|
Lang.bind(this, this._strengthChanged));
|
|
}
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_sync: function() {
|
|
this._toggleItem.label.text = this._client.wireless_enabled ? _("Turn Off") : _("Turn On");
|
|
this._toggleItem.actor.visible = this._client.wireless_hardware_enabled;
|
|
|
|
this.item.icon.icon_name = this._getMenuIcon();
|
|
this.item.label.text = this._getStatus();
|
|
},
|
|
|
|
setDeviceDescription: function(desc) {
|
|
this._description = desc;
|
|
this._sync();
|
|
},
|
|
|
|
_getStatus: function() {
|
|
let ap = this._device.active_access_point;
|
|
|
|
if (this._isHotSpotMaster())
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Hotspot Active").format(this._description);
|
|
else if (this._device.state >= NetworkManager.DeviceState.PREPARE &&
|
|
this._device.state < NetworkManager.DeviceState.ACTIVATED)
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Connecting").format(this._description);
|
|
else if (ap)
|
|
return ssidToLabel(ap.get_ssid());
|
|
else if (!this._client.wireless_hardware_enabled)
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Hardware Disabled").format(this._description);
|
|
else if (!this._client.wireless_enabled)
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Off").format(this._description);
|
|
else if (this._device.state == NetworkManager.DeviceState.DISCONNECTED)
|
|
/* Translators: %s is a network identifier */
|
|
return _("%s Not Connected").format(this._description);
|
|
else
|
|
return '';
|
|
},
|
|
|
|
_getMenuIcon: function() {
|
|
if (this._device.active_connection)
|
|
return this.getIndicatorIcon();
|
|
else
|
|
return 'network-wireless-signal-none-symbolic';
|
|
},
|
|
|
|
_canReachInternet: function() {
|
|
if (this._client.primary_connection != this._device.active_connection)
|
|
return true;
|
|
|
|
return this._client.connectivity == NetworkManager.ConnectivityState.FULL;
|
|
},
|
|
|
|
_isHotSpotMaster: function() {
|
|
if (!this._device.active_connection)
|
|
return false;
|
|
|
|
let connection = this._settings.get_connection_by_path(this._device.active_connection.connection);
|
|
if (!connection)
|
|
return false;
|
|
|
|
let ip4config = connection.get_setting_ip4_config();
|
|
if (!ip4config)
|
|
return false;
|
|
|
|
return ip4config.get_method() == NetworkManager.SETTING_IP4_CONFIG_METHOD_SHARED;
|
|
},
|
|
|
|
getIndicatorIcon: function() {
|
|
if (this._device.state < NetworkManager.DeviceState.PREPARE)
|
|
return 'network-wireless-disconnected-symbolic';
|
|
if (this._device.state < NetworkManager.DeviceState.ACTIVATED)
|
|
return 'network-wireless-acquiring-symbolic';
|
|
|
|
if (this._isHotSpotMaster())
|
|
return 'network-wireless-hotspot-symbolic';
|
|
|
|
let ap = this._device.active_access_point;
|
|
if (!ap) {
|
|
if (this._device.mode != NM80211Mode.ADHOC)
|
|
log('An active wireless connection, in infrastructure mode, involves no access point?');
|
|
|
|
if (this._canReachInternet())
|
|
return 'network-wireless-connected-symbolic';
|
|
else
|
|
return 'network-wireless-no-route-symbolic';
|
|
}
|
|
|
|
if (this._canReachInternet())
|
|
return 'network-wireless-signal-' + signalToIcon(ap.strength) + '-symbolic';
|
|
else
|
|
return 'network-wireless-no-route-symbolic';
|
|
},
|
|
});
|
|
Signals.addSignalMethods(NMDeviceWireless.prototype);
|
|
|
|
const NMVPNConnectionItem = new Lang.Class({
|
|
Name: 'NMVPNConnectionItem',
|
|
Extends: NMConnectionItem,
|
|
|
|
isActive: function() {
|
|
if (this._activeConnection == null)
|
|
return false;
|
|
|
|
return this._activeConnection.vpn_state != NetworkManager.VPNConnectionState.DISCONNECTED;
|
|
},
|
|
|
|
_buildUI: function() {
|
|
this.labelItem = new PopupMenu.PopupMenuItem('');
|
|
this.labelItem.connect('activate', Lang.bind(this, this._toggle));
|
|
|
|
this.radioItem = new PopupMenu.PopupSwitchMenuItem(this._connection.get_id(), false);
|
|
this.radioItem.connect('toggled', Lang.bind(this, this._toggle));
|
|
},
|
|
|
|
_sync: function() {
|
|
let isActive = this.isActive();
|
|
this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel();
|
|
this.radioItem.setToggleState(isActive);
|
|
this.radioItem.setStatus(this._getStatus());
|
|
this.emit('icon-changed');
|
|
},
|
|
|
|
_getStatus: function() {
|
|
if (this._activeConnection == null)
|
|
return null;
|
|
|
|
switch(this._activeConnection.vpn_state) {
|
|
case NetworkManager.VPNConnectionState.DISCONNECTED:
|
|
case NetworkManager.VPNConnectionState.ACTIVATED:
|
|
return null;
|
|
case NetworkManager.VPNConnectionState.PREPARE:
|
|
case NetworkManager.VPNConnectionState.CONNECT:
|
|
case NetworkManager.VPNConnectionState.IP_CONFIG_GET:
|
|
return _("connecting...");
|
|
case NetworkManager.VPNConnectionState.NEED_AUTH:
|
|
/* Translators: this is for network connections that require some kind of key or password */
|
|
return _("authentication required");
|
|
case NetworkManager.VPNConnectionState.FAILED:
|
|
return _("connection failed");
|
|
default:
|
|
return 'invalid';
|
|
}
|
|
},
|
|
|
|
_connectionStateChanged: function(ac, newstate, reason) {
|
|
if (newstate == NetworkManager.VPNConnectionState.FAILED &&
|
|
reason != NetworkManager.VPNConnectionStateReason.NO_SECRETS) {
|
|
// FIXME: if we ever want to show something based on reason,
|
|
// we need to convert from NetworkManager.VPNConnectionStateReason
|
|
// to NetworkManager.DeviceStateReason
|
|
this.emit('activation-failed', reason);
|
|
}
|
|
|
|
this.emit('icon-changed');
|
|
this.parent();
|
|
},
|
|
|
|
setActiveConnection: function(activeConnection) {
|
|
if (this._activeConnectionChangedId > 0) {
|
|
this._activeConnection.disconnect(this._activeConnectionChangedId);
|
|
this._activeConnectionChangedId = 0;
|
|
}
|
|
|
|
this._activeConnection = activeConnection;
|
|
|
|
if (this._activeConnection)
|
|
this._activeConnectionChangedId = this._activeConnection.connect('vpn-state-changed',
|
|
Lang.bind(this, this._connectionStateChanged));
|
|
|
|
this._sync();
|
|
},
|
|
|
|
getIndicatorIcon: function() {
|
|
if (this._activeConnection) {
|
|
if (this._activeConnection.vpn_state < NetworkManager.VPNConnectionState.ACTIVATED)
|
|
return 'network-vpn-acquiring-symbolic';
|
|
else
|
|
return 'network-vpn-symbolic';
|
|
} else {
|
|
return '';
|
|
}
|
|
},
|
|
});
|
|
|
|
const NMVPNSection = new Lang.Class({
|
|
Name: 'NMVPNSection',
|
|
Extends: NMConnectionSection,
|
|
category: NMConnectionCategory.VPN,
|
|
|
|
_init: function(client) {
|
|
this.parent(client);
|
|
|
|
this._vpnSettings = new PopupMenu.PopupMenuItem('');
|
|
this.item.menu.addMenuItem(this._vpnSettings);
|
|
this._vpnSettings.connect('activate', Lang.bind(this, this._onSettingsActivate));
|
|
|
|
this._sync();
|
|
},
|
|
|
|
_sync: function() {
|
|
let nItems = this._connectionItems.size;
|
|
this.item.actor.visible = (nItems > 0);
|
|
|
|
if (nItems > 1)
|
|
this._vpnSettings.label.text = _("Network Settings");
|
|
else
|
|
this._vpnSettings.label.text = _("VPN Settings");
|
|
|
|
this.parent();
|
|
},
|
|
|
|
_onSettingsActivate: function() {
|
|
let nItems = this._connectionItems.size;
|
|
if (nItems > 1) {
|
|
let appSys = Shell.AppSystem.get_default();
|
|
let app = appSys.lookup_app('gnome-network-panel.desktop');
|
|
app.launch(0, -1, false);
|
|
} else {
|
|
let connection = this._connections[0];
|
|
Util.spawnApp(['gnome-control-center', 'network', 'show-device',
|
|
connection.get_path()]);
|
|
}
|
|
},
|
|
|
|
_getDescription: function() {
|
|
return _("VPN");
|
|
},
|
|
|
|
_getStatus: function() {
|
|
let values = this._connectionItems.values();
|
|
for (let item of values) {
|
|
if (item.isActive())
|
|
return item.getName();
|
|
}
|
|
|
|
return _("VPN Off");
|
|
},
|
|
|
|
_getMenuIcon: function() {
|
|
return this.getIndicatorIcon() || 'network-vpn-symbolic';
|
|
},
|
|
|
|
activateConnection: function(connection) {
|
|
this._client.activate_connection(connection, null, null, null);
|
|
},
|
|
|
|
deactivateConnection: function(activeConnection) {
|
|
this._client.deactivate_connection(activeConnection);
|
|
},
|
|
|
|
setActiveConnections: function(vpnConnections) {
|
|
let connections = this._connectionItems.values();
|
|
for (let item of connections) {
|
|
item.setActiveConnection(null);
|
|
}
|
|
vpnConnections.forEach(Lang.bind(this, function(a) {
|
|
if (a._connection) {
|
|
let item = this._connectionItems.get(a._connection.get_uuid());
|
|
item.setActiveConnection(a);
|
|
}
|
|
}));
|
|
},
|
|
|
|
_makeConnectionItem: function(connection) {
|
|
return new NMVPNConnectionItem(this, connection);
|
|
},
|
|
|
|
getIndicatorIcon: function() {
|
|
let items = this._connectionItems.values();
|
|
for (let item of items) {
|
|
let icon = item.getIndicatorIcon();
|
|
if (icon)
|
|
return icon;
|
|
}
|
|
return '';
|
|
},
|
|
});
|
|
Signals.addSignalMethods(NMVPNSection.prototype);
|
|
|
|
const DeviceCategory = new Lang.Class({
|
|
Name: 'DeviceCategory',
|
|
Extends: PopupMenu.PopupMenuSection,
|
|
|
|
_init: function(category) {
|
|
this.parent();
|
|
|
|
this._category = category;
|
|
|
|
this.devices = [];
|
|
|
|
this.section = new PopupMenu.PopupMenuSection();
|
|
this.section.box.connect('actor-added', Lang.bind(this, this._sync));
|
|
this.section.box.connect('actor-removed', Lang.bind(this, this._sync));
|
|
this.addMenuItem(this.section);
|
|
|
|
this._summaryItem = new PopupMenu.PopupSubMenuMenuItem('', true);
|
|
this._summaryItem.icon.icon_name = this._getSummaryIcon();
|
|
this.addMenuItem(this._summaryItem);
|
|
|
|
this._summaryItem.menu.addSettingsAction(_('Network Settings'),
|
|
'gnome-network-panel.desktop');
|
|
this._summaryItem.actor.hide();
|
|
|
|
},
|
|
|
|
_sync: function() {
|
|
let nDevices = this.section.box.get_children().reduce(
|
|
function(prev, child) {
|
|
return prev + (child.visible ? 1 : 0);
|
|
}, 0);
|
|
this._summaryItem.label.text = this._getSummaryLabel(nDevices);
|
|
let shouldSummarize = nDevices > MAX_DEVICE_ITEMS;
|
|
this._summaryItem.actor.visible = shouldSummarize;
|
|
this.section.actor.visible = !shouldSummarize;
|
|
},
|
|
|
|
_getSummaryIcon: function() {
|
|
switch(this._category) {
|
|
case NMConnectionCategory.WIRED:
|
|
return 'network-wired-symbolic';
|
|
case NMConnectionCategory.WIRELESS:
|
|
case NMConnectionCategory.WWAN:
|
|
return 'network-wireless-symbolic';
|
|
}
|
|
return '';
|
|
},
|
|
|
|
_getSummaryLabel: function(nDevices) {
|
|
switch(this._category) {
|
|
case NMConnectionCategory.WIRED:
|
|
return ngettext("%s Wired Connection",
|
|
"%s Wired Connections",
|
|
nDevices).format(nDevices);
|
|
case NMConnectionCategory.WIRELESS:
|
|
return ngettext("%s Wi-Fi Connection",
|
|
"%s Wi-Fi Connections",
|
|
nDevices).format(nDevices);
|
|
case NMConnectionCategory.WWAN:
|
|
return ngettext("%s Modem Connection",
|
|
"%s Modem Connections",
|
|
nDevices).format(nDevices);
|
|
}
|
|
return '';
|
|
}
|
|
});
|
|
|
|
const NMApplet = new Lang.Class({
|
|
Name: 'NMApplet',
|
|
Extends: PanelMenu.SystemIndicator,
|
|
|
|
_init: function() {
|
|
this.parent();
|
|
|
|
this._primaryIndicator = this._addIndicator();
|
|
this._vpnIndicator = this._addIndicator();
|
|
|
|
// Device types
|
|
this._dtypes = { };
|
|
this._dtypes[NetworkManager.DeviceType.ETHERNET] = NMDeviceWired;
|
|
this._dtypes[NetworkManager.DeviceType.WIFI] = NMDeviceWireless;
|
|
this._dtypes[NetworkManager.DeviceType.MODEM] = NMDeviceModem;
|
|
this._dtypes[NetworkManager.DeviceType.BT] = NMDeviceBluetooth;
|
|
// TODO: WiMax support
|
|
|
|
// Connection types
|
|
this._ctypes = { };
|
|
this._ctypes[NetworkManager.SETTING_WIRED_SETTING_NAME] = NMConnectionCategory.WIRED;
|
|
this._ctypes[NetworkManager.SETTING_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS;
|
|
this._ctypes[NetworkManager.SETTING_BLUETOOTH_SETTING_NAME] = NMConnectionCategory.WWAN;
|
|
this._ctypes[NetworkManager.SETTING_CDMA_SETTING_NAME] = NMConnectionCategory.WWAN;
|
|
this._ctypes[NetworkManager.SETTING_GSM_SETTING_NAME] = NMConnectionCategory.WWAN;
|
|
this._ctypes[NetworkManager.SETTING_VPN_SETTING_NAME] = NMConnectionCategory.VPN;
|
|
|
|
NMClient.Client.new_async(null, Lang.bind(this, this._clientGot));
|
|
NMClient.RemoteSettings.new_async(null, null, Lang.bind(this, this._remoteSettingsGot));
|
|
},
|
|
|
|
_clientGot: function(obj, result) {
|
|
this._client = NMClient.Client.new_finish(result);
|
|
|
|
this._tryLateInit();
|
|
},
|
|
|
|
_remoteSettingsGot: function(obj, result) {
|
|
this._settings = NMClient.RemoteSettings.new_finish(result);
|
|
|
|
this._tryLateInit();
|
|
},
|
|
|
|
_tryLateInit: function() {
|
|
if (!this._client || !this._settings)
|
|
return;
|
|
|
|
this._activeConnections = [ ];
|
|
this._connections = [ ];
|
|
this._connectivityQueue = [ ];
|
|
|
|
this._mainConnection = null;
|
|
this._mainConnectionIconChangedId = 0;
|
|
|
|
this._notification = null;
|
|
|
|
this._nmDevices = [];
|
|
this._devices = { };
|
|
|
|
let categories = [NMConnectionCategory.WIRED,
|
|
NMConnectionCategory.WIRELESS,
|
|
NMConnectionCategory.WWAN];
|
|
for (let category of categories) {
|
|
this._devices[category] = new DeviceCategory(category);
|
|
this.menu.addMenuItem(this._devices[category]);
|
|
}
|
|
|
|
this._vpnSection = new NMVPNSection(this._client);
|
|
this._vpnSection.connect('activation-failed', Lang.bind(this, this._onActivationFailed));
|
|
this._vpnSection.connect('icon-changed', Lang.bind(this, this._updateIcon));
|
|
this.menu.addMenuItem(this._vpnSection.item);
|
|
|
|
this._readConnections();
|
|
this._readDevices();
|
|
this._syncNMState();
|
|
this._syncMainConnection();
|
|
this._syncVPNConnections();
|
|
|
|
this._client.connect('notify::manager-running', Lang.bind(this, this._syncNMState));
|
|
this._client.connect('notify::networking-enabled', Lang.bind(this, this._syncNMState));
|
|
this._client.connect('notify::state', Lang.bind(this, this._syncNMState));
|
|
this._client.connect('notify::primary-connection', Lang.bind(this, this._syncMainConnection));
|
|
this._client.connect('notify::activating-connection', Lang.bind(this, this._syncMainConnection));
|
|
this._client.connect('notify::active-connections', Lang.bind(this, this._syncVPNConnections));
|
|
this._client.connect('notify::connectivity', Lang.bind(this, this._syncConnectivity));
|
|
this._client.connect('device-added', Lang.bind(this, this._deviceAdded));
|
|
this._client.connect('device-removed', Lang.bind(this, this._deviceRemoved));
|
|
this._settings.connect('new-connection', Lang.bind(this, this._newConnection));
|
|
|
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
|
this._sessionUpdated();
|
|
},
|
|
|
|
_sessionUpdated: function() {
|
|
let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
|
|
this.menu.setSensitive(sensitive);
|
|
},
|
|
|
|
_ensureSource: function() {
|
|
if (!this._source) {
|
|
this._source = new MessageTray.Source(_("Network Manager"),
|
|
'network-transmit-receive');
|
|
this._source.policy = new MessageTray.NotificationApplicationPolicy('gnome-network-panel');
|
|
|
|
this._source.connect('destroy', Lang.bind(this, function() {
|
|
this._source = null;
|
|
}));
|
|
Main.messageTray.add(this._source);
|
|
}
|
|
},
|
|
|
|
_readDevices: function() {
|
|
let devices = this._client.get_devices() || [ ];
|
|
for (let i = 0; i < devices.length; ++i) {
|
|
this._deviceAdded(this._client, devices[i], true);
|
|
}
|
|
this._syncDeviceNames();
|
|
},
|
|
|
|
_notify: function(iconName, title, text, urgency) {
|
|
if (this._notification)
|
|
this._notification.destroy();
|
|
|
|
this._ensureSource();
|
|
|
|
let gicon = new Gio.ThemedIcon({ name: iconName });
|
|
this._notification = new MessageTray.Notification(this._source, title, text, { gicon: gicon });
|
|
this._notification.setUrgency(urgency);
|
|
this._notification.setTransient(true);
|
|
this._notification.connect('destroy', function() {
|
|
this._notification = null;
|
|
});
|
|
this._source.notify(this._notification);
|
|
},
|
|
|
|
_onActivationFailed: function(device, reason) {
|
|
// XXX: nm-applet has no special text depending on reason
|
|
// but I'm not sure of this generic message
|
|
this._notify('network-error-symbolic',
|
|
_("Connection failed"),
|
|
_("Activation of network connection failed"),
|
|
MessageTray.Urgency.HIGH);
|
|
},
|
|
|
|
_syncDeviceNames: function() {
|
|
let names = NMGtk.utils_disambiguate_device_names(this._nmDevices);
|
|
for (let i = 0; i < this._nmDevices.length; i++) {
|
|
let device = this._nmDevices[i];
|
|
let description = names[i];
|
|
if (device._delegate)
|
|
device._delegate.setDeviceDescription(description);
|
|
}
|
|
},
|
|
|
|
_deviceAdded: function(client, device, skipSyncDeviceNames) {
|
|
if (device._delegate) {
|
|
// already seen, not adding again
|
|
return;
|
|
}
|
|
|
|
let wrapperClass = this._dtypes[device.get_device_type()];
|
|
if (wrapperClass) {
|
|
let wrapper = new wrapperClass(this._client, device, this._settings);
|
|
device._delegate = wrapper;
|
|
this._addDeviceWrapper(wrapper);
|
|
|
|
this._nmDevices.push(device);
|
|
if (!skipSyncDeviceNames)
|
|
this._syncDeviceNames();
|
|
|
|
if (wrapper instanceof NMConnectionSection) {
|
|
this._connections.forEach(function(connection) {
|
|
wrapper.checkConnection(connection);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
_addDeviceWrapper: function(wrapper) {
|
|
wrapper._activationFailedId = wrapper.connect('activation-failed',
|
|
Lang.bind(this, this._onActivationFailed));
|
|
|
|
let section = this._devices[wrapper.category].section;
|
|
section.addMenuItem(wrapper.item);
|
|
|
|
let devices = this._devices[wrapper.category].devices;
|
|
devices.push(wrapper);
|
|
},
|
|
|
|
_deviceRemoved: function(client, device) {
|
|
let pos = this._nmDevices.indexOf(device);
|
|
if (pos != -1) {
|
|
this._nmDevices.splice(pos, 1);
|
|
this._syncDeviceNames();
|
|
}
|
|
|
|
let wrapper = device._delegate;
|
|
if (!wrapper) {
|
|
log('Removing a network device that was not added');
|
|
return;
|
|
}
|
|
|
|
this._removeDeviceWrapper(wrapper);
|
|
},
|
|
|
|
_removeDeviceWrapper: function(wrapper) {
|
|
wrapper.disconnect(wrapper._activationFailedId);
|
|
wrapper.destroy();
|
|
|
|
let devices = this._devices[wrapper.category].devices;
|
|
let pos = devices.indexOf(wrapper);
|
|
devices.splice(pos, 1);
|
|
},
|
|
|
|
_getMainConnection: function() {
|
|
let connection;
|
|
|
|
connection = this._client.get_primary_connection();
|
|
if (connection) {
|
|
ensureActiveConnectionProps(connection, this._settings);
|
|
return connection;
|
|
}
|
|
|
|
connection = this._client.get_activating_connection();
|
|
if (connection) {
|
|
ensureActiveConnectionProps(connection, this._settings);
|
|
return connection;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
|
|
_syncMainConnection: function() {
|
|
if (this._mainConnectionIconChangedId > 0) {
|
|
this._mainConnection._primaryDevice.disconnect(this._mainConnectionIconChangedId);
|
|
this._mainConnectionIconChangedId = 0;
|
|
}
|
|
|
|
if (this._mainConnectionStateChangedId > 0) {
|
|
this._mainConnection.disconnect(this._mainConnectionStateChangedId);
|
|
this._mainConnectionStateChangedId = 0;
|
|
}
|
|
|
|
this._mainConnection = this._getMainConnection();
|
|
|
|
if (this._mainConnection) {
|
|
if (this._mainConnection._primaryDevice)
|
|
this._mainConnectionIconChangedId = this._mainConnection._primaryDevice.connect('icon-changed', Lang.bind(this, this._updateIcon));
|
|
this._mainConnectionStateChangedId = this._mainConnection.connect('notify::state', Lang.bind(this, this._mainConnectionStateChanged));
|
|
this._mainConnectionStateChanged();
|
|
}
|
|
|
|
this._updateIcon();
|
|
this._syncConnectivity();
|
|
},
|
|
|
|
_syncVPNConnections: function() {
|
|
let activeConnections = this._client.get_active_connections() || [];
|
|
let vpnConnections = activeConnections.filter(function(a) {
|
|
return (a instanceof NMClient.VPNConnection);
|
|
});
|
|
vpnConnections.forEach(Lang.bind(this, function(a) {
|
|
ensureActiveConnectionProps(a, this._settings);
|
|
}));
|
|
this._vpnSection.setActiveConnections(vpnConnections);
|
|
|
|
this._updateIcon();
|
|
},
|
|
|
|
_mainConnectionStateChanged: function() {
|
|
if (this._mainConnection.state == NetworkManager.ActiveConnectionState.ACTIVATED && this._notification)
|
|
this._notification.destroy();
|
|
},
|
|
|
|
_ignoreConnection: function(connection) {
|
|
let setting = connection.get_setting_connection();
|
|
if (!setting)
|
|
return true;
|
|
|
|
// Ignore slave connections
|
|
if (setting.get_master())
|
|
return true;
|
|
|
|
return false;
|
|
},
|
|
|
|
_addConnection: function(connection) {
|
|
if (this._ignoreConnection(connection))
|
|
return;
|
|
if (connection._updatedId) {
|
|
// connection was already seen
|
|
return;
|
|
}
|
|
|
|
connection._removedId = connection.connect('removed', Lang.bind(this, this._connectionRemoved));
|
|
connection._updatedId = connection.connect('updated', Lang.bind(this, this._updateConnection));
|
|
|
|
this._updateConnection(connection);
|
|
this._connections.push(connection);
|
|
},
|
|
|
|
_readConnections: function() {
|
|
let connections = this._settings.list_connections();
|
|
connections.forEach(Lang.bind(this, this._addConnection));
|
|
},
|
|
|
|
_newConnection: function(settings, connection) {
|
|
this._addConnection(connection);
|
|
},
|
|
|
|
_connectionRemoved: function(connection) {
|
|
let pos = this._connections.indexOf(connection);
|
|
if (pos != -1)
|
|
this._connections.splice(pos, 1);
|
|
|
|
let section = connection._section;
|
|
|
|
if (section == NMConnectionCategory.INVALID)
|
|
return;
|
|
|
|
if (section == NMConnectionCategory.VPN) {
|
|
this._vpnSection.removeConnection(connection);
|
|
} else {
|
|
let devices = this._devices[section].devices;
|
|
for (let i = 0; i < devices.length; i++) {
|
|
if (devices[i] instanceof NMConnectionSection)
|
|
devices[i].removeConnection(connection);
|
|
}
|
|
}
|
|
|
|
connection.disconnect(connection._removedId);
|
|
connection.disconnect(connection._updatedId);
|
|
connection._removedId = connection._updatedId = 0;
|
|
},
|
|
|
|
_updateConnection: function(connection) {
|
|
let connectionSettings = connection.get_setting_by_name(NetworkManager.SETTING_CONNECTION_SETTING_NAME);
|
|
connection._type = connectionSettings.type;
|
|
connection._section = this._ctypes[connection._type] || NMConnectionCategory.INVALID;
|
|
|
|
let section = connection._section;
|
|
|
|
if (section == NMConnectionCategory.INVALID)
|
|
return;
|
|
|
|
if (section == NMConnectionCategory.VPN) {
|
|
this._vpnSection.checkConnection(connection);
|
|
} else {
|
|
let devices = this._devices[section].devices;
|
|
devices.forEach(function(wrapper) {
|
|
if (wrapper instanceof NMConnectionSection)
|
|
wrapper.checkConnection(connection);
|
|
});
|
|
}
|
|
},
|
|
|
|
_syncNMState: function() {
|
|
this.indicators.visible = this._client.manager_running;
|
|
this.menu.actor.visible = this._client.networking_enabled;
|
|
|
|
this._syncConnectivity();
|
|
},
|
|
|
|
_flushConnectivityQueue: function() {
|
|
if (this._portalHelperProxy) {
|
|
for (let item of this._connectivityQueue)
|
|
this._portalHelperProxy.CloseRemote(item);
|
|
}
|
|
|
|
this._connectivityQueue = [];
|
|
},
|
|
|
|
_closeConnectivityCheck: function(path) {
|
|
let index = this._connectivityQueue.indexOf(path);
|
|
|
|
if (index >= 0) {
|
|
if (this._portalHelperProxy)
|
|
this._portalHelperProxy.CloseRemote(path);
|
|
|
|
this._connectivityQueue.splice(index, 1);
|
|
}
|
|
},
|
|
|
|
_portalHelperDone: function(proxy, emitter, parameters) {
|
|
let [path, result] = parameters;
|
|
|
|
if (result == PortalHelperResult.CANCELLED) {
|
|
// Keep the connection in the queue, so the user is not
|
|
// spammed with more logins until we next flush the queue,
|
|
// which will happen once he chooses a better connection
|
|
// or we get to full connectivity through other means
|
|
} else if (result == PortalHelperResult.COMPLETED) {
|
|
this._closeConnectivityCheck(path);
|
|
return;
|
|
} else if (result == PortalHelperResult.RECHECK) {
|
|
this._client.check_connectivity_async(null, Lang.bind(this, function(client, result) {
|
|
try {
|
|
let state = client.check_connectivity_finish(result);
|
|
if (state >= NetworkManager.ConnectivityState.FULL)
|
|
this._closeConnectivityCheck(path);
|
|
} catch(e) { }
|
|
}));
|
|
} else {
|
|
log('Invalid result from portal helper: ' + result);
|
|
}
|
|
},
|
|
|
|
_syncConnectivity: function() {
|
|
if (this._mainConnection == null ||
|
|
this._mainConnection.state != NetworkManager.ActiveConnectionState.ACTIVATED) {
|
|
this._flushConnectivityQueue();
|
|
return;
|
|
}
|
|
|
|
let isPortal = this._client.connectivity == NetworkManager.ConnectivityState.PORTAL;
|
|
// For testing, allow interpreting any value != FULL as PORTAL, because
|
|
// LIMITED (no upstream route after the default gateway) is easy to obtain
|
|
// with a tethered phone
|
|
// NONE is also possible, with a connection configured to force no default route
|
|
// (but in general we should only prompt a portal if we know there is a portal)
|
|
if (GLib.getenv('GNOME_SHELL_CONNECTIVITY_TEST') != null)
|
|
isPortal = isPortal || this._client.connectivity < NetworkManager.ConnectivityState.FULL;
|
|
if (!isPortal)
|
|
return;
|
|
|
|
let path = this._mainConnection.get_path();
|
|
for (let item of this._connectivityQueue) {
|
|
if (item == path)
|
|
return;
|
|
}
|
|
|
|
let timestamp = global.get_current_time();
|
|
if (this._portalHelperProxy) {
|
|
this._portalHelperProxy.AuthenticateRemote(path, '', timestamp);
|
|
} else {
|
|
new PortalHelperProxy(Gio.DBus.session, 'org.gnome.Shell.PortalHelper',
|
|
'/org/gnome/Shell/PortalHelper', Lang.bind(this, function (proxy, error) {
|
|
if (error) {
|
|
log('Error launching the portal helper: ' + error);
|
|
return;
|
|
}
|
|
|
|
this._portalHelperProxy = proxy;
|
|
proxy.connectSignal('Done', Lang.bind(this, this._portalHelperDone));
|
|
|
|
proxy.AuthenticateRemote(path, '', timestamp);
|
|
}));
|
|
}
|
|
|
|
this._connectivityQueue.push(path);
|
|
},
|
|
|
|
_updateIcon: function() {
|
|
if (!this._client.networking_enabled) {
|
|
this._primaryIndicator.visible = false;
|
|
} else {
|
|
let dev = null;
|
|
if (this._mainConnection)
|
|
dev = this._mainConnection._primaryDevice;
|
|
|
|
let state = this._client.get_state();
|
|
let connected = state == NetworkManager.State.CONNECTED_GLOBAL;
|
|
this._primaryIndicator.visible = (dev != null) || connected;
|
|
if (dev) {
|
|
this._primaryIndicator.icon_name = dev.getIndicatorIcon();
|
|
} else if (connected) {
|
|
if (this._client.connectivity == NetworkManager.ConnectivityState.FULL)
|
|
this._primaryIndicator.icon_name = 'network-wired-symbolic';
|
|
else
|
|
this._primaryIndicator.icon_name = 'network-wired-no-route-symbolic';
|
|
}
|
|
}
|
|
|
|
this._vpnIndicator.icon_name = this._vpnSection.getIndicatorIcon();
|
|
this._vpnIndicator.visible = (this._vpnIndicator.icon_name != '');
|
|
}
|
|
});
|