[gnome-shell] Status area: add NetworkManager indicator
- From: Giovanni Campagna <gcampagna src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] Status area: add NetworkManager indicator
- Date: Wed, 16 Mar 2011 14:59:54 +0000 (UTC)
commit c8ac3fd4f51a4d85a396602fc0d79abcb3e255fa
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Tue Jan 25 22:08:12 2011 +0100
Status area: add NetworkManager indicator
Adds an implementation of nm-applet in javascript. Uses the new
introspection from NetworkManager, and temporarily requires
nm-applet to be running for the secret service.
Features a renewed interface, with each device controllable through
a switch, which if toggled off disconnects, and if toggled on
connects to the most recently used valid connection. More esoteric
features like creation of ad-hoc networks have been moved to the
control center panel.
https://bugzilla.gnome.org/show_bug.cgi?id=621707
data/theme/gnome-shell.css | 8 +
js/Makefile.am | 2 +
js/misc/modemManager.js | 225 +++++
js/misc/util.js | 80 ++
js/ui/panel.js | 6 +
js/ui/status/network.js | 2062 ++++++++++++++++++++++++++++++++++++++++++
po/POTFILES.in | 2 +
src/Makefile.am | 5 +-
src/shell-mobile-providers.c | 816 +++++++++++++++++
src/shell-mobile-providers.h | 96 ++
10 files changed, 3301 insertions(+), 1 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index fac0006..c64c232 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -153,6 +153,14 @@ StTooltip StLabel {
spacing: .5em;
}
+.popup-inactive-menu-item {
+ font-style: italic;
+}
+
+.popup-subtitle-menu-item {
+ font-weight: bold;
+}
+
.popup-menu-icon {
icon-size: 1.14em;
}
diff --git a/js/Makefile.am b/js/Makefile.am
index 55bb111..a085bfc 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -8,6 +8,7 @@ nobase_dist_js_DATA = \
misc/format.js \
misc/gnomeSession.js \
misc/history.js \
+ misc/modemManager.js \
misc/params.js \
misc/util.js \
perf/core.js \
@@ -50,6 +51,7 @@ nobase_dist_js_DATA = \
ui/statusMenu.js \
ui/status/accessibility.js \
ui/status/keyboard.js \
+ ui/status/network.js \
ui/status/power.js \
ui/status/volume.js \
ui/status/bluetooth.js \
diff --git a/js/misc/modemManager.js b/js/misc/modemManager.js
new file mode 100644
index 0000000..f330238
--- /dev/null
+++ b/js/misc/modemManager.js
@@ -0,0 +1,225 @@
+// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
+
+const DBus = imports.dbus;
+const Lang = imports.lang;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+
+// The following are not the complete interfaces, just the methods we need
+// (or may need in the future)
+
+const ModemGsmNetworkInterface = {
+ name: 'org.freedesktop.ModemManager.Modem.Gsm.Network',
+ methods: [
+ { name: 'GetRegistrationInfo', inSignature: '', outSignature: 'uss' },
+ { name: 'GetSignalQuality', inSignature: '', outSignature: 'u' }
+ ],
+ properties: [
+ { name: 'AccessTechnology', signature: 'u', access: 'read' }
+ ],
+ signals: [
+ { name: 'SignalQuality', inSignature: 'u' },
+ { name: 'RegistrationInfo', inSignature: 'uss' }
+ ]
+};
+const ModemGsmNetworkProxy = DBus.makeProxyClass(ModemGsmNetworkInterface);
+
+const ModemCdmaInterface = {
+ name: 'org.freedesktop.ModemManager.Modem.Cdma',
+ methods: [
+ { name: 'GetSignalQuality', inSignature: '', outSignature: 'u' },
+ { name: 'GetServingSystem', inSignature: '', outSignature: 'usu' }
+ ],
+ signals: [
+ { name: 'SignalQuality', inSignature: 'u' }
+ ]
+};
+const ModemCdmaProxy = DBus.makeProxyClass(ModemCdmaInterface);
+
+let _providersTable;
+function _getProvidersTable() {
+ if (_providersTable)
+ return _providersTable;
+ let [providers, countryCodes] = Shell.mobile_providers_parse();
+ return _providersTable = providers;
+}
+
+function ModemGsm() {
+ this._init.apply(this, arguments);
+}
+
+ModemGsm.prototype = {
+ _init: function(path) {
+ this._proxy = new ModemGsmNetworkProxy(DBus.system, 'org.freedesktop.ModemManager', path);
+
+ this.signal_quality = 0;
+ this.operator_name = null;
+
+ // Code is duplicated because the function have different signatures
+ this._proxy.connect('SignalQuality', Lang.bind(this, function(proxy, quality) {
+ this.signal_quality = quality;
+ this.emit('notify::signal-quality');
+ }));
+ this._proxy.connect('RegistrationInfo', Lang.bind(this, function(proxy, status, code, name) {
+ this.operator_name = this._findOperatorName(name, code);
+ this.emit('notify::operator-name');
+ }));
+ this._proxy.GetRegistrationInfoRemote(Lang.bind(this, function(result, err) {
+ if (err) {
+ log(err);
+ return;
+ }
+
+ let [status, code, name] = result;
+ this.operator_name = this._findOperatorName(name, code);
+ this.emit('notify::operator-name');
+ }));
+ this._proxy.GetSignalQualityRemote(Lang.bind(this, function(result, err) {
+ if (err) {
+ // it will return an error if the device is not connected
+ this.signal_quality = 0;
+ } else {
+ let [quality] = result;
+ this.signal_quality = quality;
+ }
+ this.emit('notify::signal-quality');
+ }));
+ },
+
+ _findOperatorName: function(name, opCode) {
+ if (name.length != 0 && (name.length > 6 || name.length < 5)) {
+ // this looks like a valid name, i.e. not an MCCMNC (that some
+ // devices return when not yet connected
+ return name;
+ }
+ if (isNaN(parseInt(name))) {
+ // name is definitely not a MCCMNC, so it may be a name
+ // after all; return that
+ return name;
+ }
+
+ let needle;
+ if (name.length == 0 && opCode)
+ needle = opCode;
+ else if (name.length == 6 || name.length == 5)
+ needle = name;
+ else // nothing to search
+ return null;
+
+ return this._findProviderForMCCMNC(needle);
+ },
+
+ _findProviderForMCCMNC: function(needle) {
+ let table = _getProvidersTable();
+ let needlemcc = needle.substring(0, 3);
+ let needlemnc = needle.substring(3, needle.length);
+
+ let name2, name3;
+ for (let iter in table) {
+ let providers = table[iter];
+
+ // Search through each country's providers
+ for (let i = 0; i < providers.length; i++) {
+ let provider = providers[i];
+
+ // Search through MCC/MNC list
+ let list = provider.get_gsm_mcc_mnc();
+ for (let j = 0; j < list.length; j++) {
+ let mccmnc = list[j];
+
+ // Match both 2-digit and 3-digit MNC; prefer a
+ // 3-digit match if found, otherwise a 2-digit one.
+ if (mccmnc.mcc != needlemcc)
+ continue; // MCC was wrong
+
+ if (!name3 && needle.length == 6 && needlemnc == mccmnc.mnc)
+ name3 = provider.name;
+
+ if (!name2 && needlemnc.substring(0, 2) == mccmnc.mnc.substring(0, 2))
+ name2 = provider.name;
+
+ if (name2 && name3)
+ break;
+ }
+ }
+ }
+
+ return name3 || name2 || null;
+ }
+}
+Signals.addSignalMethods(ModemGsm.prototype);
+
+function ModemCdma() {
+ this._init.apply(this, arguments);
+}
+
+ModemCdma.prototype = {
+ _init: function(path) {
+ this._proxy = new ModemCdmaProxy(DBus.system, 'org.freedesktop.ModemManager', path);
+
+ this.signal_quality = 0;
+ this.operator_name = null;
+ this._proxy.connect('SignalQuality', Lang.bind(this, function(proxy, quality) {
+ this.signal_quality = quality;
+ this.emit('notify::signal-quality');
+
+ // receiving this signal means the device got activated
+ // and we can finally call GetServingSystem
+ if (this.operator_name == null)
+ this._refreshServingSystem();
+ }));
+ this._proxy.GetSignalQualityRemote(Lang.bind(this, function(result, err) {
+ if (err) {
+ // it will return an error if the device is not connected
+ this.signal_quality = 0;
+ } else {
+ let [quality] = result;
+ this.signal_quality = quality;
+ }
+ this.emit('notify::signal-quality');
+ }));
+ },
+
+ _refreshServingSystem: function() {
+ this._proxy.GetServingSystemRemote(Lang.bind(this, function(result, err) {
+ if (err) {
+ // it will return an error if the device is not connected
+ this.operator_name = null;
+ } else {
+ let [bandClass, band, id] = result;
+ if (name.length > 0)
+ this.operator_name = this._findProviderForSid(id);
+ else
+ this.operator_name = null;
+ }
+ this.emit('notify::operator-name');
+ }));
+ },
+
+ _findProviderForSid: function(sid) {
+ if (sid == 0)
+ return null;
+
+ let table = _getProvidersTable();
+
+ // Search through each country
+ for (let iter in table) {
+ let providers = table[iter];
+
+ // Search through each country's providers
+ for (let i = 0; i < providers.length; i++) {
+ let provider = providers[i];
+ let cdma_sid = provider.get_cdma_sid();
+
+ // Search through CDMA SID list
+ for (let j = 0; j < cdma_sid.length; j++) {
+ if (cdma_sid[j] == sid)
+ return provider.name;
+ }
+ }
+ }
+
+ return null;
+ }
+};
+Signals.addSignalMethods(ModemCdma.prototype);
diff --git a/js/misc/util.js b/js/misc/util.js
index e2ec2c1..f68907e 100644
--- a/js/misc/util.js
+++ b/js/misc/util.js
@@ -178,3 +178,83 @@ function killall(processName) {
logError(e, 'Failed to kill ' + processName);
}
}
+
+// This was ported from network-manager-applet
+// Copyright 2007 - 2011 Red Hat, Inc.
+// Author: Dan Williams <dcbw redhat com>
+
+const _IGNORED_WORDS = [
+ 'Semiconductor',
+ 'Components',
+ 'Corporation',
+ 'Communications',
+ 'Company',
+ 'Corp.',
+ 'Corp',
+ 'Co.',
+ 'Inc.',
+ 'Inc',
+ 'Incorporated',
+ 'Ltd.',
+ 'Limited.',
+ 'Intel?',
+ 'chipset',
+ 'adapter',
+ '[hex]',
+ 'NDIS',
+ 'Module'
+];
+
+const _IGNORED_PHRASES = [
+ 'Multiprotocol MAC/baseband processor',
+ 'Wireless LAN Controller',
+ 'Wireless LAN Adapter',
+ 'Wireless Adapter',
+ 'Network Connection',
+ 'Wireless Cardbus Adapter',
+ 'Wireless CardBus Adapter',
+ '54 Mbps Wireless PC Card',
+ 'Wireless PC Card',
+ 'Wireless PC',
+ 'PC Card with XJACK(r) Antenna',
+ 'Wireless cardbus',
+ 'Wireless LAN PC Card',
+ 'Technology Group Ltd.',
+ 'Communication S.p.A.',
+ 'Business Mobile Networks BV',
+ 'Mobile Broadband Minicard Composite Device',
+ 'Mobile Communications AB',
+ '(PC-Suite Mode)'
+];
+
+function fixupPCIDescription(desc) {
+ desc.replace(/[_,]/, ' ');
+
+ /* Attempt to shorten ID by ignoring certain phrases */
+ for (let i = 0; i < _IGNORED_PHRASES.length; i++) {
+ let item = _IGNORED_PHRASES[i];
+ let pos = desc.indexOf(item);
+ if (pos != -1) {
+ let before = desc.substring(0, pos);
+ let after = desc.substring(pos + item.length, desc.length);
+ desc = before + after;
+ }
+ }
+
+ /* Attmept to shorten ID by ignoring certain individual words */
+ let words = desc.split(' ');
+ let out = [ ];
+ for (let i = 0; i < words; i++) {
+ let item = words[i];
+
+ // skip empty items (that come out from consecutive spaces)
+ if (item.length == 0)
+ continue;
+
+ if (_IGNORED_WORDS.indexOf(item) == -1) {
+ out.push(item);
+ }
+ }
+
+ return out.join(' ');
+}
diff --git a/js/ui/panel.js b/js/ui/panel.js
index da27f6f..d606fd7 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -44,6 +44,12 @@ const STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION = {
if (Config.HAVE_BLUETOOTH)
STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['bluetooth'] = imports.ui.status.bluetooth.Indicator;
+try {
+ STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['network'] = imports.ui.status.network.NMApplet;
+} catch(e) {
+ log('NMApplet is not supported. It is possible that your NetworkManager version is too old');
+}
+
// To make sure the panel corners blend nicely with the panel,
// we draw background and borders the same way, e.g. drawing
// them as filled shapes from the outside inwards instead of
diff --git a/js/ui/status/network.js b/js/ui/status/network.js
new file mode 100644
index 0000000..3b240f3
--- /dev/null
+++ b/js/ui/status/network.js
@@ -0,0 +1,2062 @@
+// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
+const ByteArray = imports.byteArray;
+const DBus = imports.dbus;
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const NetworkManager = imports.gi.NetworkManager;
+const NMClient = imports.gi.NMClient;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+const St = imports.gi.St;
+
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+const MessageTray = imports.ui.messageTray;
+const ModemManager = imports.misc.modemManager;
+const Util = imports.misc.util;
+
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
+const NMConnectionCategory = {
+ WIRED: 'wired',
+ WIRELESS: 'wireless',
+ WWAN: 'wwan',
+ VPN: 'vpn'
+};
+
+const NMAccessPointSecurity = {
+ UNKNOWN: 0,
+ NONE: 1,
+ WEP: 2,
+ WPA: 3,
+ WPA2: 4
+};
+
+// small optimization, to avoid using [] all the time
+const NM80211Mode = NetworkManager['80211Mode'];
+const NM80211ApFlags = NetworkManager['80211ApFlags'];
+const NM80211ApSecurityFlags = NetworkManager['80211ApSecurityFlags'];
+
+function macToArray(string) {
+ return string.split(':').map(function(el) {
+ return parseInt(el, 16);
+ });
+}
+
+function macCompare(one, two) {
+ for (let i = 0; i < 6; i++) {
+ if (one[i] != two[i])
+ return false;
+ }
+ return true;
+}
+
+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;
+}
+
+// shared between NMNetworkMenuItem and NMDeviceWWAN
+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';
+}
+
+// shared between NMNetworkMenuItem and NMDeviceWireless
+function sortAccessPoints(accessPoints) {
+ return accessPoints.sort(function (one, two) {
+ return two.strength - one.strength;
+ });
+}
+
+function NMNetworkMenuItem() {
+ this._init.apply(this, arguments);
+}
+
+NMNetworkMenuItem.prototype = {
+ __proto__: PopupMenu.PopupImageMenuItem.prototype,
+
+ _init: function(accessPoints, title, params) {
+ accessPoints = sortAccessPoints(accessPoints);
+ this.bestAP = accessPoints[0];
+
+ let ssid = this.bestAP.get_ssid();
+ title = title || NetworkManager.utils_ssid_to_utf8(ssid) || _("<unknown>");
+
+ PopupMenu.PopupImageMenuItem.prototype._init.call(this, title, this._getIcon(), params);
+
+ this._accessPoints = [ ];
+ for (let i = 0; i < accessPoints.length; i++) {
+ let ap = accessPoints[i];
+ // need a wrapper object here, because the access points can be shared
+ // between many NMNetworkMenuItems
+ let apObj = {
+ ap: ap,
+ updateId: ap.connect('notify::strength', Lang.bind(this, this._updated))
+ };
+ this._accessPoints.push(apObj);
+ }
+ },
+
+ _updated: function(ap, strength) {
+ if (strength > this.bestAP.strength)
+ this.bestAP = ap;
+
+ this.setIcon(this._getIcon());
+ },
+
+ _getIcon: function() {
+ return 'network-wireless-signal-' + signalToIcon(this.bestAP.strength);
+ },
+
+ updateAccessPoints: function(accessPoints) {
+ for (let i = 0; i < this._accessPoints.length; i++) {
+ let apObj = this._accessPoints[i];
+ apObj.ap.disconnect(apObj.updateId);
+ apObj.updateId = 0;
+ }
+
+ accessPoints = sortAccessPoints(accessPoints);
+ this.bestAP = accessPoints[0];
+ this._accessPoints = [ ];
+ for (let i = 0; i < accessPoints; i++) {
+ let ap = accessPoints[i];
+ let apObj = {
+ ap: ap,
+ updateId: ap.connect('notify::strength', Lang.bind(this, this._updated))
+ };
+ this._accessPoints.push(apObj);
+ }
+ },
+
+ destroy: function() {
+ for (let i = 0; i < this._accessPoints.length; i++) {
+ let apObj = this._accessPoints[i];
+ apObj.ap.disconnect(apObj.updateId);
+ apObj.updateId = 0;
+ }
+
+ PopupMenu.PopupImageMenuItem.prototype.destroy.call(this);
+ }
+};
+
+function NMDeviceTitleMenuItem() {
+ this._init.apply(this, arguments);
+}
+
+NMDeviceTitleMenuItem.prototype = {
+ __proto__: PopupMenu.PopupBaseMenuItem.prototype,
+
+ _init: function(description, params) {
+ PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params);
+
+ this._descriptionLabel = new St.Label({ text: description,
+ style_class: 'popup-subtitle-menu-item'
+ });
+ this.addActor(this._descriptionLabel);
+
+ this._statusBin = new St.Bin({ x_align: St.Align.END });
+ this.addActor(this._statusBin, { align: St.Align.END });
+
+ this._statusLabel = new St.Label({ text: '',
+ style_class: 'popup-inactive-menu-item'
+ });
+ this._switch = new PopupMenu.Switch(false);
+ this._statusBin.child = this._switch.actor;
+ },
+
+ setStatus: function(text) {
+ if (text) {
+ this._statusLabel.text = text;
+ this._statusBin.child = this._statusLabel;
+ this.actor.reactive = false;
+ this.actor.can_focus = false;
+ } else {
+ this._statusBin.child = this._switch.actor;
+ this.actor.reactive = true;
+ this.actor.can_focus = true;
+ }
+ },
+
+ activate: function(event) {
+ if (this._switch.actor.mapped) {
+ this._switch.toggle();
+ this.emit('toggled', this._switch.state);
+ }
+
+ PopupMenu.PopupBaseMenuItem.prototype.activate.call(this, event);
+ },
+
+ get state() {
+ return this._switch.state;
+ },
+
+ setToggleState: function(newval) {
+ this._switch.setToggleState(newval);
+ }
+};
+
+function NMWiredSectionTitleMenuItem() {
+ this._init.apply(this, arguments);
+}
+
+NMWiredSectionTitleMenuItem.prototype = {
+ __proto__: NMDeviceTitleMenuItem.prototype,
+
+ updateForDevice: function(device) {
+ if (device) {
+ this._device = device;
+ this.setStatus(device.getStatusLabel());
+ this.setToggleState(device.connected);
+ } else
+ this.setStatus('');
+ },
+
+ activate: function(event) {
+ NMDeviceTitleMenuItem.prototype.activate.call(this, event);
+
+ if (!this._device) {
+ log('Section title activated when there is more than one device, should be non reactive');
+ return;
+ }
+
+ if (this._switch.state)
+ this._device.activate();
+ else
+ this._device.deactivate();
+ }
+};
+
+function NMWirelessSectionTitleMenuItem() {
+ this._init.apply(this, arguments);
+}
+
+NMWirelessSectionTitleMenuItem.prototype = {
+ __proto__: NMDeviceTitleMenuItem.prototype,
+
+ _init: function(client, property, title, params) {
+ NMDeviceTitleMenuItem.prototype._init.call(this, title, params);
+
+ this._client = client;
+ this._property = property + '_enabled';
+ this._propertyHardware = property + '_hardware_enabled';
+ this._setEnabledFunc = property + '_set_enabled';
+
+ this._client.connect('notify::' + property + '-enabled', Lang.bind(this, this._propertyChanged));
+ this._client.connect('notify::' + property + '-hardware-enabled', Lang.bind(this, this._propertyChanged));
+
+ this._propertyChanged();
+ },
+
+ updateForDevice: function(device) {
+ // we show the switch
+ // - if there not just one device
+ // - if the switch is off
+ // - if the device is activated or disconnected
+ if (device && this._softwareEnabled && this._hardwareEnabled) {
+ let text = device.getStatusLabel();
+ this.setStatus(text);
+ } else
+ this.setStatus(null);
+ },
+
+ activate: function(event) {
+ NMDeviceTitleMenuItem.prototype.activate.call(this, event);
+
+ this._client[this._setEnabledFunc](this._switch.state);
+ },
+
+ _propertyChanged: function() {
+ this._softwareEnabled = this._client[this._property];
+ this._hardwareEnabled = this._client[this._propertyHardware];
+
+ let enabled = this._softwareEnabled && this._hardwareEnabled;
+ this.setToggleState(enabled);
+ if (!this._hardwareEnabled)
+ /* Translators: this indicates that wireless or wwan is disabled by hardware killswitch */
+ this.setStatus(_("disabled"));
+
+ this.emit('enabled-changed', enabled);
+ }
+};
+
+function NMDevice() {
+ throw new TypeError('Instantanting abstract class NMDevice');
+}
+
+NMDevice.prototype = {
+ _init: function(client, device, connections) {
+ this.device = device;
+ if (device) {
+ this.device._delegate = this;
+ this._stateChangedId = this.device.connect('state-changed', Lang.bind(this, this._deviceStateChanged));
+ } else
+ this._stateChangedId = 0;
+
+ // protected
+ this._client = client;
+ this._connections = [ ];
+ for (let i = 0; i < connections.length; i++) {
+ if (!connections[i]._uuid)
+ continue;
+ if (!this.connectionValid(connections[i]))
+ continue;
+ // record the connection
+ let obj = {
+ connection: connections[i],
+ name: connections[i]._name,
+ uuid: connections[i]._uuid,
+ timestamp: connections[i]._timestamp,
+ };
+ this._connections.push(obj);
+ }
+ this._connections.sort(function(one, two) {
+ return two.timestamp - one.timestamp;
+ });
+ this._activeConnection = null;
+ this._activeConnectionItem = null;
+ this._autoConnectionItem = null;
+
+ if (this.device) {
+ this.statusItem = new NMDeviceTitleMenuItem(this._getDescription());
+ this._statusChanged = this.statusItem.connect('toggled', Lang.bind(this, function(item, state) {
+ if (state)
+ this.activate();
+ else
+ this.deactivate();
+ this.emit('enabled-changed');
+ }));
+ }
+ this.section = new PopupMenu.PopupMenuSection();
+
+ this._createSection();
+ },
+
+ destroy: function() {
+ if (this.device)
+ this.device._delegate = null;
+
+ if (this._stateChangedId) {
+ // Need to go through GObject.Object.prototype because
+ // nm_device_disconnect conflicts with g_signal_disconnect
+ GObject.Object.prototype.disconnect.call(this.device, this._stateChangedId);
+ this._stateChangedId = 0;
+ }
+
+ this._clearSection();
+ if (this.titleItem)
+ this.titleItem.destroy();
+ this.section.destroy();
+ },
+
+ deactivate: function() {
+ this.device.disconnect(null);
+ },
+
+ activate: function() {
+ if (this._activeConnection)
+ // nothing to do
+ return;
+
+ // pick the most recently used connection and connect to that
+ // or if no connections ever set, create an automatic one
+ if (this._connections.length > 0) {
+ this._client.activate_connection(this._connections[0].connection.path, this.device, null, null);
+ } else if (this._autoConnectionName) {
+ let connection = this._createAutomaticConnection();
+ this._client.add_and_activate_connection(connection, this.device, null, null);
+ }
+ },
+
+ get connected() {
+ return this.device.state == NetworkManager.DeviceState.ACTIVATED;
+ },
+
+ setActiveConnection: function(activeConnection) {
+ if (activeConnection == this._activeConnection)
+ // nothing to do
+ return;
+
+ // remove any UI
+ if (this._activeConnectionItem) {
+ this._activeConnectionItem.destroy();
+ this._activeConnectionItem = null;
+ }
+
+ this._activeConnection = activeConnection;
+
+ this._clearSection();
+ this._createSection();
+ },
+
+ checkConnection: function(connection) {
+ let exists = this._findConnection(connection) != -1;
+ let valid = this.connectionValid(connection);
+ if (exists && !valid)
+ this.removeConnection(connection);
+ else if (!exists && valid)
+ this.addConnection(connection);
+ },
+
+ addConnection: function(connection) {
+ // record the connection
+ let obj = {
+ connection: connection,
+ name: connection._name,
+ uuid: connection._uuid,
+ timestamp: connection._timestamp,
+ };
+ this._connections.push(obj);
+ this._connections.sort(function(one, two) {
+ return two.timestamp - one.timestamp;
+ });
+
+ this._clearSection();
+ this._createSection();
+ },
+
+ removeConnection: function(connection) {
+ if (!connection._uuid) {
+ log('Cannot remove a connection without an UUID');
+ return;
+ }
+ let pos = this._findConnection(connection._uuid);
+ if (pos == -1) {
+ // this connection was never added, nothing to do here
+ return;
+ }
+
+ let obj = this._connections[pos];
+ if (obj.item)
+ obj.item.destroy();
+ this._connections.splice(pos, 1);
+
+ if (this._connections.length == 0) {
+ // We need to show the automatic connection again
+ this._clearSection();
+ this._createSection();
+ }
+ },
+
+ connectionValid: function(connection) {
+ throw new TypeError('Invoking pure virtual function NMDevice.connectionValid');
+ },
+
+ setEnabled: function(enabled) {
+ // do nothing by default, we want to keep the conneciton list visible
+ // in the majority of cases (wired, wwan, vpn)
+ },
+
+ getStatusLabel: function() {
+ switch(this.device.state) {
+ case NetworkManager.DeviceState.DISCONNECTED:
+ case NetworkManager.DeviceState.ACTIVATED:
+ return null;
+ case NetworkManager.DeviceState.PREPARE:
+ case NetworkManager.DeviceState.CONFIG:
+ case NetworkManager.DeviceState.IP_CONFIG:
+ return _("connecting...");
+ case NetworkManager.DeviceState.NEED_AUTH:
+ /* Translators: this is for network connections that require some kind of key or password */
+ return _("authentication required");
+ case NetworkManager.DeviceState.UNAVAILABLE:
+ // we don't check if the carrier property is actually false, as that causes race
+ // conditions if state is changed before the new carrier value is picked by libnm-glib
+ if (this.device.capabilities & NetworkManager.DeviceCapabilities.CARRIER_DETECT)
+ /* Translators: this is for wired network devices that are physically disconnected */
+ return _("cable unplugged");
+ else
+ /* Translators: this is for a network device that cannot be activated (for example it
+ is disabled by rfkill, or it has no coverage */
+ return _("unavailable");
+ case NetworkManager.DeviceState.FAILED:
+ return _("connection failed");
+ default:
+ log('Device state invalid, is %d'.format(this.device.state));
+ return 'invalid';
+ }
+ },
+
+ // protected
+ _createAutomaticConnection: function() {
+ throw new TypeError('Invoking pure virtual function NMDevice.createAutomaticConnection');
+ },
+
+ _findConnection: function(uuid) {
+ for (let i = 0; i < this._connections.length; i++) {
+ let obj = this._connections[i];
+ if (obj.uuid == uuid)
+ return i;
+ }
+ return -1;
+ },
+
+ _clearSection: function() {
+ // Clear everything
+ this.section.removeAll();
+ this._autoConnectionItem = null;
+ this._activeConnectionItem = null;
+ for (let i = 0; i < this._connections.length; i++) {
+ this._connections[i].item = null;
+ }
+ },
+
+ _shouldShowConnectionList: function() {
+ return (this.device.state == NetworkManager.DeviceState.DISCONNECTED ||
+ this.device.state == NetworkManager.DeviceState.ACTIVATED);
+ },
+
+ _createSection: function() {
+ if (!this._shouldShowConnectionList())
+ return;
+
+ if (this._activeConnection) {
+ this._createActiveConnectionItem();
+ this.section.addMenuItem(this._activeConnectionItem);
+ }
+ if (this._connections.length > 0) {
+ for(let j = 0; j < this._connections.length; ++j) {
+ let obj = this._connections[j];
+ if (this._activeConnection &&
+ obj.connection == this._activeConnection._connection)
+ continue;
+ obj.item = this._createConnectionItem(obj);
+ this.section.addMenuItem(obj.item);
+ }
+ } else if (this._autoConnectionName) {
+ this._autoConnectionItem = new PopupMenu.PopupMenuItem(this._autoConnectionName);
+ this._autoConnectionItem.connect('activate', Lang.bind(this, function() {
+ let connection = this._createAutomaticConnection();
+ this._client.add_and_activate_connection(connection, this.device, null, null);
+ }));
+ this.section.addMenuItem(this._autoConnectionItem);
+ }
+ },
+
+ _createConnectionItem: function(obj) {
+ let path = obj.connection.path;
+ let item = new PopupMenu.PopupMenuItem(obj.name);
+ item.connect('activate', Lang.bind(this, function() {
+ this._client.activate_connection(path, this.device, null, null);
+ }));
+ return item;
+ },
+
+ _createActiveConnectionItem: function() {
+ let title;
+ let active = this._activeConnection._connection;
+ if (active) {
+ title = active._name;
+ } else {
+ /* TRANSLATORS: this is the indication that a connection for another logged in user is active,
+ and we cannot access its settings (including the name) */
+ title = _("Connected (private)");
+ }
+ this._activeConnectionItem = new PopupMenu.PopupMenuItem(title, { reactive: false });
+ this._activeConnectionItem.setShowDot(true);
+ },
+
+ _deviceStateChanged: function(device, newstate, oldstate, reason) {
+ if (newstate == oldstate) {
+ log('device emitted state-changed without actually changing state');
+ return;
+ }
+
+ if (oldstate == NetworkManager.DeviceState.ACTIVATED) {
+ this.emit('network-lost');
+ }
+
+ switch(newstate) {
+ case NetworkManager.DeviceState.NEED_AUTH:
+ // FIXME: make this have a real effect
+ // (currently we rely on a running nm-applet)
+ this.emit('need-auth');
+ break;
+ case NetworkManager.DeviceState.FAILED:
+ this.emit('activation-failed', reason);
+ break;
+ }
+
+ this.statusItem.setStatus(this.getStatusLabel());
+ this.statusItem.setToggleState(this.connected);
+
+ this._clearSection();
+ this._createSection();
+ this.emit('state-changed');
+ },
+
+ _getDescription: function() {
+ let dev_product = this.device.get_product();
+ let dev_vendor = this.device.get_vendor();
+ if (!dev_product || !dev_vendor)
+ return null;
+
+ let product = Util.fixupPCIDescription(dev_product);
+ let vendor = Util.fixupPCIDescription(dev_vendor);
+ let out = '';
+
+ // Another quick hack; if all of the fixed up vendor string
+ // is found in product, ignore the vendor.
+ if (product.indexOf(vendor) == -1)
+ out += vendor + ' ';
+ out += product;
+
+ return out;
+ }
+};
+Signals.addSignalMethods(NMDevice.prototype);
+
+
+function NMDeviceWired() {
+ this._init.apply(this, arguments);
+}
+
+NMDeviceWired.prototype = {
+ __proto__: NMDevice.prototype,
+
+ _init: function(client, device, connections) {
+ this._autoConnectionName = _("Auto Ethernet");
+ this.category = NMConnectionCategory.WIRED;
+
+ NMDevice.prototype._init.call(this, client, device, connections);
+ },
+
+ connectionValid: function(connection) {
+ if (connection._type != NetworkManager.SETTING_WIRED_SETTING_NAME)
+ return false;
+
+ let ethernetSettings = connection.get_setting_by_name(NetworkManager.SETTING_WIRED_SETTING_NAME);
+ let fixedMac = ethernetSettings.get_mac_address();
+ if (fixedMac)
+ return macCompare(fixedMac, macToArray(this.device.perm_hw_address));
+ return true;
+ },
+
+ _createAutomaticConnection: function() {
+ let connection = new NetworkManager.Connection();
+ connection._uuid = NetworkManager.utils_uuid_generate();
+ connection.add_setting(new NetworkManager.SettingWired());
+ connection.add_setting(new NetworkManager.SettingConnection({
+ uuid: connection._uuid,
+ id: this._autoConnectionName,
+ type: NetworkManager.SETTING_WIRED_SETTING_NAME,
+ autoconnect: true
+ }));
+ return connection;
+ }
+};
+
+function NMDeviceModem() {
+ this._init.apply(this, arguments);
+}
+
+NMDeviceModem.prototype = {
+ __proto__: NMDevice.prototype,
+
+ _init: function(client, device, connections) {
+ let is_wwan = false;
+
+ this._enabled = true;
+ this.mobileDevice = null;
+ this._connectionType = 'ppp';
+
+ this._capabilities = device.current_capabilities;
+ if (this._capabilities & NetworkManager.DeviceModemCapabilities.GSM_UMTS) {
+ is_wwan = true;
+ this.mobileDevice = new ModemManager.ModemGsm(device.udi);
+ this._connectionType = NetworkManager.SETTING_GSM_SETTING_NAME;
+ } else if (this._capabilities & NetworkManager.DeviceModemCapabilities.CDMA_EVDO) {
+ is_wwan = true;
+ this.mobileDevice = new ModemManager.ModemCdma(device.udi);
+ this._connectionType = NetworkManager.SETTING_CDMA_SETTING_NAME;
+ } else if (this._capabilities & NetworkManager.DeviceModemCapabilities.LTE) {
+ is_wwan = true;
+ // FIXME: support signal quality
+ }
+
+ if (is_wwan) {
+ this.category = NMConnectionCategory.WWAN;
+ this._autoConnectionName = _("Auto broadband");
+ } else {
+ this.category = NMConnectionCategory.WIRED;
+ this._autoConnectionName = _("Auto dial-up");
+ }
+
+ if (this.mobileDevice) {
+ this._operatorNameId = this.mobileDevice.connect('notify::operator-name', Lang.bind(this, function() {
+ if (this._operatorItem) {
+ let name = this.mobileDevice.operator_name;
+ if (name) {
+ this._operatorItem.label.text = name;
+ this._operatorItem.actor.show();
+ } else
+ this._operatorItem.actor.hide();
+ }
+ }));
+ this._signalQualityId = this.mobileDevice.connect('notify::signal-quality', Lang.bind(this, function() {
+ if (this._operatorItem) {
+ this._operatorItem.setIcon(this._getSignalIcon());
+ }
+ }));
+ }
+
+ NMDevice.prototype._init.call(this, client, device, connections, 1);
+ },
+
+ setEnabled: function(enabled) {
+ this._enabled = enabled;
+ if (this.category == NMConnectionCategory.WWAN) {
+ if (enabled) {
+ // prevent "network unavailable" statuses
+ this.statusItem.setStatus(null);
+ } else
+ this.statusItem.setStatus(this.getStatusLabel());
+ }
+
+ NMDevice.prototype.setEnabled.call(this, enabled);
+ },
+
+ get connected() {
+ return this._enabled && this.device.state == NetworkManager.DeviceState.CONNECTED;
+ },
+
+ destroy: function() {
+ if (this._operatorNameId) {
+ this.mobileDevice.disconnect(this._operatorNameId);
+ this._operatorNameId = 0;
+ }
+ if (this._signalQualityId) {
+ this.mobileDevice.disconnect(this._signalQualityId);
+ this._signalQualityId = 0;
+ }
+
+ NMDevice.prototype.destroy.call(this);
+ },
+
+ _getSignalIcon: function() {
+ return 'network-cellular-signal-' + signalToIcon(this.mobileDevice.signal_quality);
+ },
+
+ _createSection: function() {
+ if (this.mobileDevice) {
+ // If operator_name is null, just pass the empty string, as the item is hidden anyway
+ this._operatorItem = new PopupMenu.PopupImageMenuItem(this.mobileDevice.operator_name || '',
+ this._getSignalIcon(),
+ { reactive: false });
+ if (this.mobileDevice.operator_name)
+ this._operatorItem.actor.hide();
+ this.section.addMenuItem(this._operatorItem);
+ }
+
+ NMDevice.prototype._createSection.call(this);
+ },
+
+ clearSection: function() {
+ this._operatorItem = null;
+
+ NMDevice.prototype._clearSection.call(this);
+ },
+
+ connectionValid: function(connection) {
+ return connection._type == this._connectionType;
+ },
+
+ _createAutomaticConnection: function() {
+ // FIXME: we need to summon the mobile wizard here
+ // or NM will not have the necessary parameters to complete the connection
+ // pending a DBus method on nm-applet
+
+ let connection = new NetworkManager.Connection;
+ connection._uuid = NetworkManager.utils_uuid_generate();
+ connection.add_setting(new NetworkManager.SettingConnection({
+ uuid: connection._uuid,
+ id: this._autoConnectionName,
+ type: this._connectionType,
+ autoconnect: false
+ }));
+ return connection;
+ }
+};
+
+function NMDeviceBluetooth() {
+ this._init.apply(this, arguments);
+}
+
+NMDeviceBluetooth.prototype = {
+ __proto__: NMDevice.prototype,
+
+ _init: function(client, device, connections) {
+ this._autoConnectionName = this._makeConnectionName(device);
+ device.connect('notify::name', Lang.bind(this, this._updateAutoConnectionName));
+
+ this.category = NMConnectionCategory.WWAN;
+
+ NMDevice.prototype._init.call(this, client, device, connections);
+ },
+
+ connectionValid: function(connection) {
+ if (connection._type != NetworkManager.SETTING_BLUETOOTH_SETTING_NAME)
+ return false;
+
+ let bluetoothSettings = connection.get_setting_by_name(NetworkManager.SETTING_BLUETOOTH_SETTING_NAME);
+ let fixedBdaddr = bluetoothSettings.get_bdaddr();
+ if (fixedBdaddr)
+ return macCompare(fixedBdaddr, macToArray(this.device.hw_address));
+
+ return true;
+ },
+
+ _createAutomaticConnection: function() {
+ let connection = new NetworkManager.Connection;
+ connection._uuid = NetworkManager.utils_uuid_generate();
+ connection.add_setting(new NetworkManager.SettingBluetooth);
+ connection.add_setting(new NetworkManager.SettingConnection({
+ uuid: connection._uuid,
+ id: this._autoConnectionName,
+ type: NetworkManager.SETTING_BLUETOOTH_SETTING_NAME,
+ autoconnect: false
+ }));
+ return connection;
+ },
+
+ _makeConnectionName: function(device) {
+ let name = device.name;
+ if (name)
+ return _("Auto %s").format(name);
+ else
+ return _("Auto bluetooth");
+ },
+
+ _updateAutoConnectionName: function() {
+ this._autoConnectionName = this._makeConnectioName(this.device);
+
+ this._clearSection();
+ this._createSection();
+ }
+};
+
+
+// Not a real device, but I save a lot code this way
+function NMDeviceVPN() {
+ this._init.apply(this, arguments);
+}
+
+NMDeviceVPN.prototype = {
+ __proto__: NMDevice.prototype,
+
+ _init: function(client) {
+ // Disable autoconnections
+ this._autoConnectionName = null;
+ this.category = NMConnectionCategory.VPN;
+
+ NMDevice.prototype._init.call(this, client, null, [ ]);
+ },
+
+ connectionValid: function(connection) {
+ return connection._type == NetworkManager.SETTING_VPN_SETTING_NAME;
+ },
+
+ get empty() {
+ return this._connections.length == 0;
+ },
+
+ get connected() {
+ return true;
+ },
+
+ _shouldShowConnectionList: function() {
+ return true;
+ },
+
+ deactivate: function() {
+ if (this._activeConnection)
+ this._client.deactivate_connection(this._activeConnection);
+ },
+
+ getStatusLabel: function() {
+ return null;
+ }
+};
+
+function NMDeviceWireless() {
+ this._init.apply(this, arguments);
+}
+
+NMDeviceWireless.prototype = {
+ __proto__: NMDevice.prototype,
+
+ _init: function(client, device, connections) {
+ this.category = NMConnectionCategory.WIRELESS;
+
+ this._overflowItem = null;
+ this._networks = [ ];
+
+ // breaking the layers with this, but cannot call
+ // this.connectionValid until I have a device
+ this.device = device;
+
+ let validConnections = connections.filter(Lang.bind(this, function(connection) {
+ return this.connectionValid(connection);
+ }));
+ let accessPoints = device.get_access_points() || [ ];
+ for (let i = 0; i < accessPoints.length; i++) {
+ // Access points are grouped by network
+ let ap = accessPoints[i];
+ let pos = this._findNetwork(ap);
+ let obj;
+ if (pos != -1) {
+ obj = this._networks[pos];
+ obj.accessPoints.push(ap);
+ } else {
+ obj = { ssid: ap.get_ssid(),
+ mode: ap.mode,
+ security: this._getApSecurityType(ap),
+ connections: [ ],
+ item: null,
+ accessPoints: [ ap ]
+ };
+ this._networks.push(obj);
+ }
+
+ // Check if some connection is valid for this AP
+ for (let j = 0; j < validConnections.length; j++) {
+ let connection = validConnections[j];
+ if (this._connectionValidForAP(connection, ap) &&
+ obj.connections.indexOf(connection) == -1) {
+ obj.connections.push(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));
+
+ NMDevice.prototype._init.call(this, client, device, validConnections);
+ },
+
+ destroy: function() {
+ if (this._apAddedId) {
+ // see above for this HACK
+ 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;
+ }
+
+ NMDevice.prototype.destroy.call(this);
+ },
+
+ setEnabled: function(enabled) {
+ if (enabled) {
+ this.statusItem.actor.show();
+ this.section.actor.show();
+ } else {
+ this.statusItem.actor.hide();
+ this.section.actor.hide();
+ }
+ },
+
+ activate: function() {
+ if (this._activeConnection)
+ // nothing to do
+ return;
+
+ // among all visible networks, pick the last recently used connection
+ let best = null;
+ let bestApObj = null;
+ let bestTime = 0;
+ for (let i = 0; i < this._networks.length; i++) {
+ let apObj = this._networks[i];
+ for (let j = 0; j < apObj.connections.length; j++) {
+ let connection = apObj.connections[j];
+ if (connection._timestamp > bestTime) {
+ best = connection;
+ bestTime = connection._timestamp;
+ bestApObj = apObj;
+ }
+ }
+ }
+
+ if (best) {
+ for (let i = 0; i < bestApObj.accessPoints.length; i++) {
+ let ap = bestApObj.accessPoints[i];
+ if (this._connectionValidForAP(best, ap)) {
+ this._client.activate_connection(best.path, this.device, ap.dbus_path, null);
+ break;
+ }
+ }
+ return;
+ }
+
+ // XXX: what else to do?
+ // for now, just pick a random network
+ // (this function is called in a corner case anyway, that is, only when
+ // the user toggles the switch and has more than one wireless device)
+ if (this._networks.length > 0) {
+ let connection = this._createAutomaticConnection(this._networks[0]);
+ let accessPoints = sortAccessPoints(this._networks[0].accessPoints);
+ this._client.add_and_activate_connection(connection, this.device, accessPoints[0].dbus_path, null);
+ }
+ },
+
+ _getApSecurityType: function(accessPoint) {
+ if (accessPoint._secType)
+ return accessPoint._secType;
+ // XXX: have this checked by someone familiar with IEEE 802.1x
+
+ let flags = accessPoint.flags;
+ let wpa_flags = accessPoint.wpa_flags;
+ let rsn_flags = accessPoint.rsn_flags;
+ let type;
+ if ( !(flags & NM80211ApFlags.PRIVACY)
+ && (wpa_flags == NM80211ApSecurityFlags.NONE)
+ && (rsn_flags == NM80211ApSecurityFlags.NONE))
+ type = NMAccessPointSecurity.NONE;
+ else if ( (flags & NM80211ApFlags.PRIVACY)
+ && (wpa_flags == NM80211ApSecurityFlags.NONE)
+ && (rsn_flags == NM80211ApSecurityFlags.NONE))
+ type = NMAccessPointSecurity.WEP;
+ else if ( !(flags & NM80211ApFlags.PRIVACY)
+ && (wpa_flags != NM80211ApSecurity.NONE)
+ && (rsn_flags != NM80211ApSecurity.NONE))
+ type = NMAccessPointSecurity.WPA;
+ else
+ type = NMAccessPointSecurity.WPA2;
+
+ // cache the found value to avoid checking flags all the time
+ accessPoint._secType = type;
+ return type;
+ },
+
+ _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;
+ },
+
+ _findNetwork: function(accessPoint) {
+ for (let i = 0; i < this._networks.length; i++) {
+ if (this._networkCompare(this._networks[i], accessPoint))
+ return i;
+ }
+ return -1;
+ },
+
+ _accessPointAdded: function(device, accessPoint) {
+ let pos = this._findNetwork(accessPoint);
+ let apObj;
+ if (pos != -1) {
+ apObj = this._networks[pos];
+ if (apObj.accessPoints.indexOf(accessPoint) != -1) {
+ log('Access point was already seen, not adding again');
+ return;
+ }
+
+ apObj.accessPoints.push(accessPoint);
+ } else {
+ apObj = { ssid: accessPoint.get_ssid(),
+ mode: accessPoint.mode,
+ security: this._getApSecurityType(accessPoint),
+ connections: [ ],
+ item: null,
+ accessPoints: [ accessPoint ]
+ };
+ this._networks.push(apObj);
+ }
+
+ // check if this enables new connections for this group
+ for (let i = 0; i < this._connections.length; i++) {
+ let connection = this._connections[i].connection;
+ if (this._connectionValidForAP(connection, accessPoint) &&
+ apObj.connections.indexOf(connection) == -1) {
+ apObj.connections.push(connection);
+ }
+ }
+
+ // update everything
+ this._clearSection();
+ this._createSection();
+ },
+
+ _accessPointRemoved: function(device, accessPoint) {
+ let pos = this._findNetwork(accessPoint);
+
+ if (pos == -1) {
+ log('Removing an access point that was never added');
+ return;
+ }
+
+ let apObj = this._networks[pos];
+ let i = apObj.accessPoints.indexOf(accessPoint);
+
+ if (i == -1) {
+ log('Removing an access point that was never added');
+ return;
+ }
+
+ apObj.accessPoints.splice(i, 1);
+
+ if (apObj.accessPoints.length == 0) {
+ if (apObj.item)
+ apObj.item.destroy();
+ this._networks.splice(pos, 1);
+ } else if (apObj.item)
+ apObj.item.updateAccessPoints(apObj.accessPoints);
+ },
+
+ _createAPItem: function(connection, accessPointObj, useConnectionName) {
+ let item = new NMNetworkMenuItem(accessPointObj.accessPoints, useConnectionName ? connection._name : undefined);
+ item._connection = connection;
+ item.connect('activate', Lang.bind(this, function() {
+ let accessPoints = sortAccessPoints(accessPointObj.accessPoints);
+ for (let i = 0; i < accessPoints.length; i++) {
+ if (this._connectionValidForAP(connection, accessPoints[i])) {
+ this._client.activate_connection(connection.path, this.device, accessPoints[i].dbus_path, null);
+ break;
+ }
+ }
+ }));
+ return item;
+ },
+
+ connectionValid: function(connection) {
+ if (connection._type != NetworkManager.SETTING_WIRELESS_SETTING_NAME)
+ return false;
+
+ let wirelessSettings = connection.get_setting_by_name(NetworkManager.SETTING_WIRELESS_SETTING_NAME);
+ let wirelessSecuritySettings = connection.get_setting_by_name(NetworkManager.SETTING_WIRELESS_SECURITY_SETTING_NAME);
+
+ let fixedMac = wirelessSettings.get_mac_address();
+ if (fixedMac && !macCompare(fixedMac, macToArray(this.device.perm_hw_address)))
+ return false;
+
+ if (wirelessSecuritySettings &&
+ wirelessSecuritySettings.key_mgmt != 'none' &&
+ wirelessSecuritySettings.key_mgmt != 'ieee8021x') {
+ let capabilities = this.device.wireless_capabilities;
+ if (!(capabilities & NetworkManager.DeviceWifiCapabilities.WPA) ||
+ !(capabilities & NetworkManager.DeviceWifiCapabilities.CIPHER_TKIP))
+ return false;
+ if (wirelessSecuritySettings.get_num_protos() == 1 &&
+ wirelessSecuritySettings.get_proto(0) == 'rsn' &&
+ !(capabilities & NetworkManager.DeviceWifiCapabilities.RSN))
+ return false;
+ if (wirelessSecuritySettings.get_num_pairwise() == 1 &&
+ wirelessSecuritySettings.get_pairwise(0) == 'ccmp' &&
+ !(capabilities & NetworkManager.DeviceWifiCapabilities.CIPHER_CCMP))
+ return false;
+ if (wirelessSecuritySettings.get_num_groups() == 1 &&
+ wirelessSecuritySettings.get_group(0) == 'ccmp' &&
+ !(capabilities & NetworkManager.DeviceWifiCapabilities.CIPHER_CCMP))
+ return false;
+ }
+ return true;
+ },
+
+ _clearSection: function() {
+ NMDevice.prototype._clearSection.call(this);
+
+ for (let i = 0; i < this._networks.length; i++)
+ this._networks[i].item = null;
+ this._overflowItem = null;
+ },
+
+ removeConnection: function(connection) {
+ if (!connection._uuid)
+ return;
+ let pos = this._findConnection(connection._uuid);
+ if (pos == -1) {
+ // removing connection that was never added
+ return;
+ }
+
+ let obj = this._connections[pos];
+ this._connections.splice(pos, 1);
+
+ let anyauto = false, forceupdate = false;
+ for (let i = 0; i < this._networks.length; i++) {
+ let apObj = this._networks[i];
+ let connections = apObj.connections;
+ for (let k = 0; k < connections.length; k++) {
+ if (connections[k]._uuid == connection._uuid) {
+ // remove the connection from the access point group
+ connections.splice(k);
+ anyauto = connections.length == 0;
+ if (apObj.item) {
+ if (apObj.item instanceof PopupMenu.PopupSubMenuMenuItem) {
+ let items = apObj.item.menu.getMenuItems();
+ if (items.length == 2) {
+ // we need to update the connection list to convert this to a normal item
+ forceupdate = true;
+ } else {
+ for (let j = 0; j < items.length; j++) {
+ if (items[j]._connection._uuid == connection._uuid) {
+ items[j].destroy();
+ break;
+ }
+ }
+ }
+ } else {
+ apObj.item.destroy();
+ apObj.item = null;
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if (forceupdate || anyauto) {
+ this._clearSection();
+ this._createSection();
+ }
+ },
+
+ addConnection: function(connection) {
+ // record the connection
+ let obj = {
+ connection: connection,
+ name: connection._name,
+ uuid: connection._uuid,
+ };
+ this._connections.push(obj);
+
+ // find an appropriate access point
+ let any = false, forceupdate = false;
+ for (let i = 0; i < this._networks.length; i++) {
+ let apObj = this._networks[i];
+
+ // Check if connection is valid for any of these access points
+ let any = false;
+ for (let k = 0; k < apObj.accessPoints.length; k++) {
+ let ap = apObj.accessPoints[k];
+ if (this._connectionValidForAP(connection, ap)) {
+ apObj.connections.push(connection);
+ any = true;
+ break;
+ }
+ }
+
+ if (any && this._shouldShowConnectionList()) {
+ // we need to show this connection
+ if (apObj.item && apObj.item.menu) {
+ // We're already showing the submenu for this access point
+ apObj.item.menu.addMenuItem(this._createAPItem(connection, apObj, true));
+ } else {
+ if (apObj.item)
+ apObj.item.destroy();
+ if (apObj.connections.length == 1) {
+ apObj.item = this._createAPItem(connection, apObj, false);
+ this.section.addMenuItem(apObj.item);
+ } else {
+ apObj.item = null;
+ // we need to force an update to create the submenu
+ forceupdate = true;
+ }
+ }
+ }
+ }
+
+ if (forceupdate) {
+ this._clearSection();
+ this._createSection();
+ }
+ },
+
+ _connectionValidForAP: function(connection, ap) {
+ // copied and adapted from nm-applet
+ let wirelessSettings = connection.get_setting_by_name(NetworkManager.SETTING_WIRELESS_SETTING_NAME);
+ if (!ssidCompare(wirelessSettings.get_ssid(), ap.get_ssid()))
+ return false;
+
+ let wirelessSecuritySettings = connection.get_setting_by_name(NetworkManager.SETTING_WIRELESS_SECURITY_SETTING_NAME);
+
+ let fixedBssid = wirelessSettings.get_bssid();
+ if (fixedBssid && !macCompare(fixedBssid, macToArray(ap.hw_address)))
+ return false;
+
+ let fixedBand = wirelessSettings.band;
+ if (fixedBand) {
+ let freq = ap.frequency;
+ if (fixedBand == 'a' && (freq < 4915 || freq > 5825))
+ return false;
+ if (fixedBand == 'bg' && (freq < 2412 || freq > 2484))
+ return false;
+ }
+
+ let fixedChannel = wirelessSettings.channel;
+ if (fixedChannel && fixedChannel != NetworkManager.utils_wifi_freq_to_channel(ap.frequency))
+ return false;
+
+ if (!wirelessSecuritySettings)
+ return true;
+
+ return wirelessSettings.ap_security_compatible(wirelessSecuritySettings, ap.flags, ap.wpa_flags, ap.rsn_flags, ap.mode);
+ },
+
+ _createActiveConnectionItem: function() {
+ let activeAp = this.device.active_access_point;
+ let icon, title;
+ if (this._activeConnection._connection) {
+ let connection = this._activeConnection._connection;
+ if (activeAp)
+ this._activeConnectionItem = new NMNetworkMenuItem([ activeAp ], undefined,
+ { reactive: false });
+ else
+ this._activeConnectionItem = new PopupMenu.PopupImageMenuItem(connection._name,
+ 'network-wireless-connected',
+ { reactive: false });
+ } else {
+ // We cannot read the connection (due to ACL, or API incompatibility), but we still show signal if we have it
+ let menuItem;
+ if (activeAp)
+ this._activeConnectionItem = new NMNetworkMenuItem([ activeAp ], undefined,
+ { reactive: false });
+ else
+ this._activeConnectionItem = new PopupMenu.PopupImageMenuItem(_("Connected (private)"),
+ 'network-wireless-connected',
+ { reactive: false });
+ }
+ this._activeConnectionItem.setShowDot(true);
+ },
+
+ _createAutomaticConnection: function(apObj) {
+ let name;
+ let ssid = NetworkManager.utils_ssid_to_utf8(apObj.ssid);
+ if (ssid) {
+ /* TRANSLATORS: this the automatic wireless connection name (including the network name) */
+ name = _("Auto %s").format(ssid);
+ } else
+ name = _("Auto wireless");
+
+ let connection = new NetworkManager.Connection();
+ connection.add_setting(new NetworkManager.SettingWireless());
+ connection.add_setting(new NetworkManager.SettingConnection({
+ id: name,
+ autoconnect: true, // NetworkManager will know to ignore this if appropriate
+ uuid: NetworkManager.utils_uuid_generate(),
+ type: NetworkManager.SETTING_WIRELESS_SETTING_NAME
+ }));
+ return connection;
+ },
+
+ _createSection: function() {
+ if (!this._shouldShowConnectionList())
+ return;
+
+ if(this._activeConnection) {
+ this._createActiveConnectionItem();
+ this.section.addMenuItem(this._activeConnectionItem);
+ }
+
+ let activeAp = this.device.active_access_point;
+ let activeApSsid = activeAp ? activeAp.get_ssid() : null;
+
+ // we want five access points in the menu, including the active one
+ let numItems = this._activeConnection ? 4 : 5;
+
+ for(let j = 0; j < this._networks.length; j++) {
+ let apObj = this._networks[j];
+ if(activeAp && ssidCompare(apObj.ssid, activeApSsid))
+ continue;
+
+ let menuItem;
+ if(apObj.connections.length > 0) {
+ if (apObj.connections.length == 1)
+ apObj.item = this._createAPItem(apObj.connections[0], apObj, false);
+ else {
+ let title = NetworkManager.utils_ssid_to_utf8(apObj.ssid) || _("<unknown>");
+ apObj.item = new PopupMenu.PopupSubMenuMenuItem(title);
+ apObj.item._apObj = apObj;
+ for (let i = 0; i < apObj.connections.length; i++)
+ apObj.item.menu.addMenuItem(this._createAPItem(apObj.connections[i], apObj, true));
+ }
+ } else {
+ apObj.item = new NMNetworkMenuItem(apObj.accessPoints);
+ apObj.item._apObj = apObj;
+ apObj.item.connect('activate', Lang.bind(this, function() {
+ let connection = this._createAutomaticConnection(apObj);
+ let accessPoints = sortAccessPoints(apObj.accessPoints);
+ this._client.add_and_activate_connection(connection, this.device, accessPoints[0].dbus_path, null)
+ }));
+ }
+
+ if (j < numItems)
+ this.section.addMenuItem(apObj.item);
+ else {
+ if (!this._overflowItem) {
+ this._overflowItem = new PopupMenu.PopupSubMenuMenuItem(_("More..."));
+ this.section.addMenuItem(this._overflowItem);
+ }
+ this._overflowItem.menu.addMenuItem(apObj.item);
+ }
+ }
+ },
+};
+
+function NMApplet() {
+ this._init.apply(this, arguments);
+}
+NMApplet.prototype = {
+ __proto__: PanelMenu.SystemStatusButton.prototype,
+
+ _init: function() {
+ PanelMenu.SystemStatusButton.prototype._init.call(this, 'network-error');
+
+ this._client = NMClient.Client.new();
+
+ this._statusSection = new PopupMenu.PopupMenuSection();
+ this._statusItem = new PopupMenu.PopupMenuItem('', { style_class: 'popup-inactive-menu-item', reactive: false });
+ this._statusSection.addMenuItem(this._statusItem);
+ this._statusSection.addAction(_("Enable networking"), Lang.bind(this, function() {
+ this._client.networking_enabled = true;
+ }));
+ this._statusSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._statusSection.actor.hide();
+ this.menu.addMenuItem(this._statusSection);
+
+ this._devices = { };
+
+ this._devices.wired = {
+ section: new PopupMenu.PopupMenuSection(),
+ devices: [ ],
+ item: new NMWiredSectionTitleMenuItem(_("Wired"))
+ };
+
+ this._devices.wired.section.addMenuItem(this._devices.wired.item);
+ this._devices.wired.section.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._devices.wired.section.actor.hide();
+ this.menu.addMenuItem(this._devices.wired.section);
+
+ this._devices.wireless = {
+ section: new PopupMenu.PopupMenuSection(),
+ devices: [ ],
+ item: this._makeToggleItem('wireless', _("Wireless"))
+ };
+ this._devices.wireless.section.addMenuItem(this._devices.wireless.item);
+ this._devices.wireless.section.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._devices.wireless.section.actor.hide();
+ this.menu.addMenuItem(this._devices.wireless.section);
+
+ this._devices.wwan = {
+ section: new PopupMenu.PopupMenuSection(),
+ devices: [ ],
+ item: this._makeToggleItem('wwan', _("Mobile broadband"))
+ };
+ this._devices.wwan.section.addMenuItem(this._devices.wwan.item);
+ this._devices.wwan.section.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._devices.wwan.section.actor.hide();
+ this.menu.addMenuItem(this._devices.wwan.section);
+
+ this._devices.vpn = {
+ section: new PopupMenu.PopupMenuSection(),
+ device: new NMDeviceVPN(this._client),
+ item: new NMWiredSectionTitleMenuItem(_("VPN Connections"))
+ };
+ this._devices.vpn.item.updateForDevice(this._devices.vpn.device);
+ this._devices.vpn.section.addMenuItem(this._devices.vpn.item);
+ this._devices.vpn.section.addMenuItem(this._devices.vpn.device.section);
+ this._devices.vpn.section.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+ this._devices.vpn.section.actor.hide();
+ this.menu.addMenuItem(this._devices.vpn.section);
+
+ this.menu.addAction(_("Network Settings"), function() {
+ Util.spawnDesktop('gnome-network-panel');
+ });
+
+ this._activeConnections = [ ];
+ this._connections = [ ];
+
+ this._mainConnection = null;
+ this._activeAccessPointUpdateId = 0;
+ this._activeAccessPoint = null;
+ this._mobileUpdateId = 0;
+ this._mobileUpdateDevice = null;
+
+ // 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_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS;
+ this._ctypes[NetworkManager.SETTING_WIRED_SETTING_NAME] = NMConnectionCategory.WIRED;
+ this._ctypes[NetworkManager.SETTING_PPPOE_SETTING_NAME] = NMConnectionCategory.WIRED;
+ this._ctypes[NetworkManager.SETTING_PPP_SETTING_NAME] = NMConnectionCategory.WIRED;
+ 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;
+
+ this._settings = NMClient.RemoteSettings.new(null);
+ this._connectionsReadId = this._settings.connect('connections-read', Lang.bind(this, function() {
+ this._readConnections();
+ this._readDevices();
+ this._syncNMState();
+
+ // Connect to signals late so that early signals don't find in inconsistent state
+ // and connect only once (this signal handler can be called again if NetworkManager goes up and down)
+ if (!this._inited) {
+ this._inited = true;
+ 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::active-connections', Lang.bind(this, this._updateIcon));
+ 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));
+ }
+ }));
+ },
+
+ _ensureSource: function() {
+ if (!this._source) {
+ this._source = new NMMessageTraySource();
+ this._source._destroyId = this._source.connect('destroy', Lang.bind(this, function() {
+ this._source._destroyId = 0;
+ this._source = null;
+ }));
+ Main.messageTray.add(this._source);
+ }
+ },
+
+ _makeToggleItem: function(type, title) {
+ let item = new NMWirelessSectionTitleMenuItem(this._client, type, title);
+ item.connect('enabled-changed', Lang.bind(this, function(item, enabled) {
+ let devices = this._devices[type].devices;
+ devices.forEach(function(dev) {
+ dev.setEnabled(enabled);
+ });
+ this._syncSectionTitle(type);
+ }));
+ return item;
+ },
+
+ _syncSectionTitle: function(category) {
+ let devices = this._devices[category].devices;
+ let managedDevices = devices.filter(function(dev) {
+ return dev.device.state != NetworkManager.DeviceState.UNMANAGED;
+ });
+ let item = this._devices[category].item;
+ let section = this._devices[category].section;
+ if (managedDevices.length == 0)
+ section.actor.hide();
+ else {
+ section.actor.show();
+ if (managedDevices.length == 1) {
+ let dev = managedDevices[0];
+ dev.statusItem.actor.hide();
+ item.updateForDevice(dev);
+ } else {
+ managedDevices.forEach(function(dev) {
+ dev.statusItem.actor.show();
+ });
+ }
+ }
+ },
+
+ _readDevices: function() {
+ let devices = this._client.get_devices() || [ ];
+ for (let i = 0; i < devices.length; ++i) {
+ this._deviceAdded(this._client, devices[i]);
+ }
+ },
+
+ _deviceAdded: function(client, device) {
+ 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._connections);
+
+ // FIXME: these notifications are duplicate with those exposed by nm-applet
+ // uncomment this code in 3.2, when we'll conflict with and kill nm-applet
+ /* wrapper._networkLostId = wrapper.connect('network-lost', Lang.bind(this, function(emitter) {
+ this._ensureSource();
+ let icon = new St.Icon({ icon_name: 'network-offline',
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this._source.ICON_SIZE
+ });
+ let notification = new MessageTray.Notification(this._source,
+ _("Connectivity lost"),
+ _("You're no longer connected to the network"),
+ { icon: icon });
+ this._source.notify(notification);
+ }));
+ wrapper._activationFailedId = wrapper.connect('activation-failed', Lang.bind(this, function(wrapper, reason) {
+ this._ensureSource();
+ let icon = new St.Icon({ icon_name: 'network-error',
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this._source.ICON_SIZE,
+ });
+ let banner;
+ // XXX: nm-applet has no special text depending on reason
+ // but I'm not sure of this generic message
+ let notification = new MessageTray.Notification(this._source,
+ _("Connection failed"),
+ _("Activation of network connection failed"),
+ { icon: icon });
+ this._source.notify(notification);
+ })); */
+ wrapper._stateChangedId = wrapper.connect('state-changed', Lang.bind(this, function(dev) {
+ this._syncSectionTitle(dev.category);
+ }));
+ wrapper._destroyId = wrapper.connect('destroy', function(wrapper) {
+ //wrapper.disconnect(wrapper._networkLostId);
+ //wrapper.disconnect(wrapper._activationFailedId);
+ wrapper.disconnect(wrapper._stateChangedId);
+ });
+ let section = this._devices[wrapper.category].section;
+ let devices = this._devices[wrapper.category].devices;
+
+ section.addMenuItem(wrapper.section, 1);
+ section.addMenuItem(wrapper.statusItem, 1);
+ devices.push(wrapper);
+
+ this._syncSectionTitle(wrapper.category);
+ } else
+ log('Invalid network device type, is ' + device.get_device_type());
+ },
+
+ _deviceRemoved: function(client, device) {
+ if (!device._delegate) {
+ log('Removing a network device that was not added');
+ return;
+ }
+
+ let wrapper = device._delegate;
+ wrapper.destroy();
+
+ let devices = this._devices[wrapper.category].devices;
+ let pos = devices.indexOf(wrapper);
+ devices.splice(pos, 1);
+
+ this._syncSectionTitle(wrapper.category)
+ },
+
+ _syncActiveConnections: function() {
+ let closedConnections = [ ];
+ let newActiveConnections = this._client.get_active_connections() || [ ];
+ for (let i = 0; i < this._activeConnections.length; i++) {
+ let a = this._activeConnections[i];
+ if (newActiveConnections.indexOf(a) == -1) // connection is removed
+ closedConnections.push(a);
+ }
+
+ for (let i = 0; i < closedConnections.length; i++) {
+ let active = closedConnections[i];
+ if (active._primaryDevice)
+ active._primaryDevice.setActiveConnection(null);
+ if (active._notifyStateId) {
+ active.disconnect(active._notifyStateId);
+ active._notifyStateId = 0;
+ }
+ if (active._inited) {
+ active.disconnect(active._notifyDefaultId);
+ active.disconnect(active._notifyDefault6Id);
+ active._inited = false;
+ }
+ }
+
+ this._activeConnections = newActiveConnections;
+ this._mainConnection = null;
+ let activating = null;
+ let default_ip4 = null;
+ let default_ip6 = null;
+ for (let i = 0; i < this._activeConnections.length; i++) {
+ let a = this._activeConnections[i];
+
+ if (!a._inited) {
+ a._notifyDefaultId = a.connect('notify::default', Lang.bind(this, this._updateIcon));
+ a._notifyDefault6Id = a.connect('notify::default6', Lang.bind(this, this._updateIcon));
+ if (a.state == NetworkManager.ActiveConnectionState.ACTIVATING) // prepare to notify to the user
+ a._notifyStateId = a.connect('notify::state', Lang.bind(this, this._notifyActiveConnection));
+ else {
+ // notify as soon as possible
+ Mainloop.idle_add(Lang.bind(this, function() {
+ this._notifyActiveConnection(a);
+ }));
+ }
+
+ a._inited = true;
+ }
+
+ if (!a._connection) {
+ a._connection = this._settings.get_connection_by_path(a.connection);
+
+ if (a._connection) {
+ a._type = a._connection._type;
+ a._section = this._ctypes[a._type];
+ } else {
+ a._connection = null;
+ a._type = null;
+ a._section = null;
+ log('Cannot find connection for active (or connection cannot be read)');
+ }
+ }
+
+ if (a['default'])
+ default_ip4 = a;
+ if (a.default6)
+ default_ip6 = a;
+
+ if (a.state == NetworkManager.ActiveConnectionState.ACTIVATING) {
+ activating = a;
+
+ // don't set activating connections to devices, NMDevice:state-changed
+ // should take care of rebuilding the menu
+ continue;
+ }
+
+ if (!a._primaryDevice) {
+ if (a._type != NetworkManager.SETTING_VPN_SETTING_NAME) {
+ // find a good device to be considered primary
+ a._primaryDevice = null;
+ let devices = a.get_devices();
+ for (let j = 0; j < devices.length; j++) {
+ let d = devices[j];
+ if (d._delegate) {
+ a._primaryDevice = d._delegate;
+ break;
+ }
+ }
+ } else
+ a._primaryDevice = this._vpnDevice;
+
+ if (a._primaryDevice)
+ a._primaryDevice.setActiveConnection(a);
+ }
+ }
+
+ this._mainConnection = activating || default_ip4 || default_ip6 || this._activeConnections[0] || null;
+ },
+
+ _notifyActiveConnection: function(active) {
+ // FIXME: duplicate notifications when nm-applet is running
+ // This code will come back when nm-applet is killed
+ this._syncNMState();
+ return;
+
+ if (active.state == NetworkManager.ActiveConnectionState.ACTIVATED) {
+
+ // notify only connections that are visible
+ if (active._connection) {
+ this._ensureSource();
+
+ let icon;
+ let banner;
+ switch (active._section) {
+ case NMConnectionCategory.WWAN:
+ icon = 'network-cellular-signal-excellent';
+ banner = _("You're now connected to mobile broadband connection '%s'").format(active._connection._name);
+ break;
+ case NMConnectionCategory.WIRELESS:
+ icon = 'network-wireless-signal-excellent';
+ banner = _("You're now connected to wireless network '%s'").format(active._connection._name);
+ break;
+ case NMConnectionCategory.WIRED:
+ icon = 'network-wired';
+ banner = _("You're now connected to wired network '%s'").format(active._connection._name);
+ break;
+ case NMConnectionCategory.VPN:
+ icon = 'network-vpn';
+ banner = _("You're now connected to VPN network '%s'").format(active._connection._name);
+ break;
+ default:
+ // a fallback for a generic 'connected' icon
+ icon = 'network-transmit-receive';
+ banner = _("You're now connected to '%s'").format(active._connection._name);
+ }
+
+ let iconActor = new St.Icon({ icon_name: icon,
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this._source.ICON_SIZE
+ });
+ let notification = new MessageTray.Notification(this._source,
+ _("Connection estabilished"),
+ banner,
+ { icon: iconActor });
+ this._source.notify(notification);
+ }
+
+ if (active._stateChangeId) {
+ active.disconnect(active._stateChangeId);
+ active._stateChangeId = 0;
+ }
+ }
+
+ this._syncNMState();
+ },
+
+ _readConnections: function() {
+ let connections = this._settings.list_connections();
+ for (let i = 0; i < connections.length; i++) {
+ let connection = connections[i];
+ if (connection._uuid) {
+ // connection was already seen (for example because NetworkManager was restarted)
+ continue;
+ }
+ 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);
+ }
+ },
+
+ _newConnection: function(settings, connection) {
+ if (connection._uuid) {
+ // 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);
+
+ this._updateIcon();
+ },
+
+ _connectionRemoved: function(connection) {
+ let pos = this._connections.indexOf(connection);
+ if (pos != -1)
+ this._connections.splice(connection);
+
+ let section = connection._section;
+ if (section == NMConnectionCategory.VPN) {
+ this._devices.vpn.device.removeConnection(connection);
+ if (this._devices.vpn.device.empty)
+ this._devices.vpn.section.actor.hide();
+ } else {
+ let devices = this._devices[section].devices;
+ for (let i = 0; i < devices.length; i++)
+ devices[i].removeConnection(connection);
+ }
+
+ connection._uuid = null;
+ connection.disconnect(connection._removedId);
+ connection.disconnect(connection._updatedId);
+ },
+
+ _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];
+ connection._name = connectionSettings.id;
+ connection._uuid = connectionSettings.uuid;
+ connection._timestamp = connectionSettings.timestamp;
+
+ let section = connection._section;
+ if (section == NMConnectionCategory.VPN) {
+ this._devices.vpn.device.checkConnection(connection);
+ this._devices.vpn.section.actor.show();
+ connection._everAdded = true;
+ } else {
+ let devices = this._devices[section].devices;
+ for (let i = 0; i < devices.length; i++) {
+ devices[i].checkConnection(connection);
+ }
+ }
+ },
+
+ _hideDevices: function() {
+ this._devicesHidden = true;
+
+ for (let category in this._devices)
+ this._devices[category].section.actor.hide();
+ },
+
+ _showNormal: function() {
+ if (!this._devicesHidden) // nothing to do
+ return;
+ this._devicesHidden = false;
+
+ this._statusSection.actor.hide();
+
+ this._syncSectionTitle('wired');
+ this._syncSectionTitle('wireless');
+ this._syncSectionTitle('wwan');
+
+ if (!this._devices.vpn.device.empty)
+ this._devices.vpn.section.actor.show();
+ },
+
+ _syncNMState: function() {
+ if (!this._client.manager_running) {
+ log('NetworkManager is not running, hiding...');
+ this.menu.close();
+ this.actor.hide();
+ return;
+ } else
+ this.actor.show();
+
+ if (!this._client.networking_enabled) {
+ this.setIcon('network-offline');
+ this._hideDevices();
+ this._statusItem.label.text = _("Networking is disabled");
+ this._statusSection.actor.show();
+ return;
+ }
+
+ this._showNormal();
+ this._updateIcon();
+ },
+
+ _updateIcon: function() {
+ this._syncActiveConnections();
+ let mc = this._mainConnection;
+ let hasApIcon = false;
+ let hasMobileIcon = false;
+
+ if (!mc) {
+ this.setIcon('network-offline');
+ } else if (mc.state == NetworkManager.ActiveConnectionState.ACTIVATING) {
+ switch (mc._section) {
+ case NMConnectionCategory.WWAN:
+ this.setIcon('network-cellular-acquiring');
+ break;
+ case NMConnectionCategory.WIRELESS:
+ this.setIcon('network-wireless-acquiring');
+ break;
+ case NMConnectionCategory.WIRED:
+ this.setIcon('network-wired-acquiring');
+ break;
+ case NMConnectionCategory.VPN:
+ this.setIcon('network-vpn-acquiring');
+ break;
+ default:
+ // fallback to a generic connected icon
+ // (it could be a private connection of some other user)
+ this.setIcon('network-wired-acquiring');
+ }
+ } else {
+ let dev;
+ switch (mc._section) {
+ case NMConnectionCategory.WIRELESS:
+ dev = mc._primaryDevice;
+ if (dev) {
+ let ap = dev.device.active_access_point;
+ let mode = dev.device.mode;
+ if (!ap) {
+ if (mode != NetworkManager['80211Mode'].ADHOC) {
+ log('An active wireless connection, in infrastructure mode, involves no access point?');
+ break;
+ }
+ this.setIcon('network-wireless-connected');
+ } else {
+ if (this._accessPointUpdateId && this._activeAccessPoint != ap) {
+ this._activeAccessPoint.disconnect(this._accessPointUpdateId);
+ this._activeAccessPoint = ap;
+ this._activeAccessPointUpdateId = ap.connect('notify::strength', Lang.bind(function() {
+ this.setIcon('network-wireless-signal-' + signalToIcon(ap.strength));
+ }));
+ }
+ this.setIcon('network-wireless-signal-' + signalToIcon(ap.strength));
+ hasApIcon = true;
+ }
+ break;
+ } else {
+ log('Active connection with no primary device?');
+ break;
+ }
+ case NMConnectionCategory.WIRED:
+ this.setIcon('network-wired');
+ break;
+ case NMConnectionCategory.WWAN:
+ dev = mc._primaryDevice;
+ if (!dev) {
+ log('Active connection with no primary device?');
+ break;
+ }
+ if (!dev.mobileDevice) {
+ // this can happen for bluetooth in PAN mode
+ this.setIcon('network-cellular-connected');
+ break;
+ }
+
+ if (this._mobileUpdateId && this._mobileUpdateDevice != dev) {
+ this._mobileUpdateDevice.disconnect(this._mobileUpdateId);
+ this._mobileUpdateDevice = dev.mobileDevice;
+ this._mobileUpdateId = dev.mobileDevice.connect('notify::signal-quality', Lang.bind(this, function() {
+ this.setIcon('network-cellular-signal-' + signalToIcon(dev.mobileDevice.signal_quality));
+ }));
+ }
+ this.setIcon('network-cellular-signal-' + signalToIcon(dev.mobileDevice.signal_quality));
+ hasMobileIcon = true;
+ break;
+ case NMConnectionCategory.VPN:
+ this.setIcon('network-vpn');
+ break;
+ default:
+ // fallback to a generic connected icon
+ // (it could be a private connection of some other user)
+ this.setIcon('network-wired');
+ break;
+ }
+ }
+
+ // cleanup stale signal connections
+
+ if (!hasApIcon && this._activeAccessPointUpdateId) {
+ this._activeAccessPoint.disconnect(this._activeAccessPointUpdateId);
+ this._activeAccessPoint = null;
+ this._activeAccessPointUpdateId = 0;
+ }
+ if (!hasMobileIcon && this._mobileUpdateId) {
+ this._mobileUpdateDevice.disconnect(this._mobileUpdateId);
+ this._mobileUpdateDevice = null;
+ this._mobileUpdateId = 0;
+ }
+ }
+};
+
+function NMMessageTraySource() {
+ this._init();
+}
+
+NMMessageTraySource.prototype = {
+ __proto__: MessageTray.Source.prototype,
+
+ _init: function() {
+ MessageTray.Source.prototype._init.call(this, _("Network Manager"));
+
+ let icon = new St.Icon({ icon_name: 'network-transmit-receive',
+ icon_type: St.IconType.SYMBOLIC,
+ icon_size: this.ICON_SIZE
+ });
+ this._setSummaryIcon(icon);
+ }
+};
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1a4e272..51303bb 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,7 @@ js/ui/statusMenu.js
js/ui/status/accessibility.js
js/ui/status/bluetooth.js
js/ui/status/keyboard.js
+js/ui/status/network.js
js/ui/status/power.js
js/ui/status/volume.js
js/ui/telepathyClient.js
@@ -31,6 +32,7 @@ src/gdmuser/gdm-user.c
src/main.c
src/shell-app-system.c
src/shell-global.c
+src/shell-mobile-providers.c
src/shell-polkit-authentication-agent.c
src/shell-util.c
diff --git a/src/Makefile.am b/src/Makefile.am
index a24a71e..e878f20 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,7 +62,8 @@ gnome_shell_cflags = \
$(GNOME_SHELL_CFLAGS) \
-I$(srcdir)/tray \
-DVERSION=\"$(VERSION)\" \
- -DLOCALEDIR=\"$(datadir)/locale\" \
+ -DLOCALEDIR=\"$(datadir)/locale\" \
+ -DDATADIR=\"$(datadir)\" \
-DGNOME_SHELL_LIBEXECDIR=\"$(libexecdir)\" \
-DGNOME_SHELL_DATADIR=\"$(pkgdatadir)\" \
-DGNOME_SHELL_PKGLIBDIR=\"$(pkglibdir)\" \
@@ -90,6 +91,7 @@ shell_public_headers_h = \
shell-generic-container.h \
shell-gtk-embed.h \
shell-global.h \
+ shell-mobile-providers.h \
shell-perf-log.h \
shell-slicer.h \
shell-stack.h \
@@ -121,6 +123,7 @@ libgnome_shell_la_SOURCES = \
shell-generic-container.c \
shell-gtk-embed.c \
shell-global.c \
+ shell-mobile-providers.c \
shell-perf-log.c \
shell-polkit-authentication-agent.h \
shell-polkit-authentication-agent.c \
diff --git a/src/shell-mobile-providers.c b/src/shell-mobile-providers.c
new file mode 100644
index 0000000..5d985ed
--- /dev/null
+++ b/src/shell-mobile-providers.c
@@ -0,0 +1,816 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2009 Novell, Inc.
+ * Author: Tambet Ingo (tambet gmail com).
+ *
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include <glib/gi18n.h>
+
+#include "shell-mobile-providers.h"
+
+#ifndef MOBILE_BROADBAND_PROVIDER_INFO
+#define MOBILE_BROADBAND_PROVIDER_INFO DATADIR "/mobile-broadband-provider-info/serviceproviders.xml"
+#endif
+
+#define ISO_3166_COUNTRY_CODES DATADIR "/zoneinfo/iso3166.tab"
+
+
+
+static GHashTable *
+read_country_codes (void)
+{
+ GHashTable *table;
+ GIOChannel *channel;
+ GString *buffer;
+ GError *error = NULL;
+ GIOStatus status;
+
+ channel = g_io_channel_new_file (ISO_3166_COUNTRY_CODES, "r", &error);
+ if (!channel) {
+ if (error) {
+ g_warning ("Could not read " ISO_3166_COUNTRY_CODES ": %s", error->message);
+ g_error_free (error);
+ } else
+ g_warning ("Could not read " ISO_3166_COUNTRY_CODES ": Unknown error");
+
+ return NULL;
+ }
+
+ table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ buffer = g_string_sized_new (32);
+
+ status = G_IO_STATUS_NORMAL;
+ while (status == G_IO_STATUS_NORMAL) {
+ status = g_io_channel_read_line_string (channel, buffer, NULL, &error);
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ if (buffer->str[0] != '#') {
+ char **pieces;
+
+ pieces = g_strsplit (buffer->str, "\t", 2);
+
+ /* Hack for rh#556292; iso3166.tab is just wrong */
+ pieces[1] = pieces[1] ? g_strchomp (pieces[1]) : NULL;
+ if (pieces[1] && !strcmp (pieces[1], "Britain (UK)")) {
+ g_free (pieces[1]);
+ pieces[1] = g_strdup (_("United Kingdom"));
+ }
+
+ g_hash_table_insert (table, pieces[0], pieces[1]);
+ g_free (pieces);
+ }
+
+ g_string_truncate (buffer, 0);
+ break;
+ case G_IO_STATUS_EOF:
+ break;
+ case G_IO_STATUS_ERROR:
+ g_warning ("Error while reading: %s", error->message);
+ g_error_free (error);
+ break;
+ case G_IO_STATUS_AGAIN:
+ /* FIXME: Try again a few times, but really, it never happes, right? */
+ break;
+ }
+ }
+
+ g_string_free (buffer, TRUE);
+ g_io_channel_unref (channel);
+
+ return table;
+}
+
+/* XML Parser */
+
+typedef enum {
+ PARSER_TOPLEVEL = 0,
+ PARSER_COUNTRY,
+ PARSER_PROVIDER,
+ PARSER_METHOD_GSM,
+ PARSER_METHOD_GSM_APN,
+ PARSER_METHOD_CDMA,
+ PARSER_ERROR
+} MobileContextState;
+
+typedef struct {
+ GHashTable *country_codes;
+ GHashTable *table;
+
+ char *current_country;
+ GSList *current_providers;
+ ShellMobileProvider *current_provider;
+ ShellMobileAccessMethod *current_method;
+
+ char *text_buffer;
+ MobileContextState state;
+} MobileParser;
+
+static ShellGsmMccMnc *
+mcc_mnc_new (const char *mcc, const char *mnc)
+{
+ ShellGsmMccMnc *m;
+
+ m = g_slice_new (ShellGsmMccMnc);
+ m->mcc = g_strstrip (g_strdup (mcc));
+ m->mnc = g_strstrip (g_strdup (mnc));
+ return m;
+}
+
+/* added in porting */
+static ShellGsmMccMnc *
+mcc_mnc_copy (const ShellGsmMccMnc *other) {
+ ShellGsmMccMnc *ret;
+
+ ret = g_slice_new (ShellGsmMccMnc);
+ ret->mcc = g_strdup (other->mcc);
+ ret->mnc = g_strdup (other->mnc);
+ return ret;
+}
+
+static void
+mcc_mnc_free (ShellGsmMccMnc *m)
+{
+ g_return_if_fail (m != NULL);
+ g_free (m->mcc);
+ g_free (m->mnc);
+ g_slice_free (ShellGsmMccMnc, m);
+}
+
+/* added in porting */
+G_DEFINE_BOXED_TYPE (ShellGsmMccMnc, shell_gsm_mcc_mnc, mcc_mnc_copy, mcc_mnc_free)
+
+static ShellMobileAccessMethod *
+access_method_new (void)
+{
+ ShellMobileAccessMethod *method;
+
+ method = g_slice_new0 (ShellMobileAccessMethod);
+ method->refs = 1;
+ method->lcl_names = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ return method;
+}
+
+ShellMobileAccessMethod *
+shell_mobile_access_method_ref (ShellMobileAccessMethod *method)
+{
+ g_return_val_if_fail (method != NULL, NULL);
+ g_return_val_if_fail (method->refs > 0, NULL);
+
+ method->refs++;
+
+ return method;
+}
+
+void
+shell_mobile_access_method_unref (ShellMobileAccessMethod *method)
+{
+ g_return_if_fail (method != NULL);
+ g_return_if_fail (method->refs > 0);
+
+ if (--method->refs == 0) {
+ g_free (method->name);
+ g_hash_table_destroy (method->lcl_names);
+ g_free (method->username);
+ g_free (method->password);
+ g_free (method->gateway);
+ g_free (method->gsm_apn);
+ g_slist_foreach (method->dns, (GFunc) g_free, NULL);
+ g_slist_free (method->dns);
+
+ g_slice_free (ShellMobileAccessMethod, method);
+ }
+}
+
+GType
+shell_mobile_access_method_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ type = g_boxed_type_register_static ("ShellMobileAccessMethod",
+ (GBoxedCopyFunc) shell_mobile_access_method_ref,
+ (GBoxedFreeFunc) shell_mobile_access_method_unref);
+ }
+ return type;
+}
+
+
+static ShellMobileProvider *
+provider_new (void)
+{
+ ShellMobileProvider *provider;
+
+ provider = g_slice_new0 (ShellMobileProvider);
+ provider->refs = 1;
+ provider->lcl_names = g_hash_table_new_full (g_str_hash, g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ return provider;
+}
+
+ShellMobileProvider *
+shell_mobile_provider_ref (ShellMobileProvider *provider)
+{
+ provider->refs++;
+
+ return provider;
+}
+
+void
+shell_mobile_provider_unref (ShellMobileProvider *provider)
+{
+ if (--provider->refs == 0) {
+ g_free (provider->name);
+ g_hash_table_destroy (provider->lcl_names);
+
+ g_slist_foreach (provider->methods, (GFunc) shell_mobile_access_method_unref, NULL);
+ g_slist_free (provider->methods);
+
+ g_slist_foreach (provider->gsm_mcc_mnc, (GFunc) mcc_mnc_free, NULL);
+ g_slist_free (provider->gsm_mcc_mnc);
+
+ g_slist_free (provider->cdma_sid);
+
+ g_slice_free (ShellMobileProvider, provider);
+ }
+}
+
+GType
+shell_mobile_provider_get_type (void)
+{
+ static GType type = 0;
+
+ if (G_UNLIKELY (type == 0)) {
+ type = g_boxed_type_register_static ("ShellMobileProvider",
+ (GBoxedCopyFunc) shell_mobile_provider_ref,
+ (GBoxedFreeFunc) shell_mobile_provider_unref);
+ }
+ return type;
+}
+
+static void
+provider_list_free (gpointer data)
+{
+ GSList *list = (GSList *) data;
+
+ while (list) {
+ shell_mobile_provider_unref ((ShellMobileProvider *) list->data);
+ list = g_slist_delete_link (list, list);
+ }
+}
+
+static void
+parser_toplevel_start (MobileParser *parser,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ int i;
+
+ if (!strcmp (name, "serviceproviders")) {
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (!strcmp (attribute_names[i], "format")) {
+ if (strcmp (attribute_values[i], "2.0")) {
+ g_warning ("%s: mobile broadband provider database format '%s'"
+ " not supported.", __func__, attribute_values[i]);
+ parser->state = PARSER_ERROR;
+ break;
+ }
+ }
+ }
+ } else if (!strcmp (name, "country")) {
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (!strcmp (attribute_names[i], "code")) {
+ char *country_code;
+ char *country;
+
+ country_code = g_ascii_strup (attribute_values[i], -1);
+ country = g_hash_table_lookup (parser->country_codes, country_code);
+ if (country) {
+ parser->current_country = g_strdup (country);
+ g_free (country_code);
+ } else
+ parser->current_country = country_code;
+
+ parser->state = PARSER_COUNTRY;
+ break;
+ }
+ }
+ }
+}
+
+static void
+parser_country_start (MobileParser *parser,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ if (!strcmp (name, "provider")) {
+ parser->state = PARSER_PROVIDER;
+ parser->current_provider = provider_new ();
+ }
+}
+
+static void
+parser_provider_start (MobileParser *parser,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ if (!strcmp (name, "gsm"))
+ parser->state = PARSER_METHOD_GSM;
+ else if (!strcmp (name, "cdma")) {
+ parser->state = PARSER_METHOD_CDMA;
+ parser->current_method = access_method_new ();
+ }
+}
+
+static void
+parser_gsm_start (MobileParser *parser,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ if (!strcmp (name, "network-id")) {
+ const char *mcc = NULL, *mnc = NULL;
+ int i;
+
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (!strcmp (attribute_names[i], "mcc"))
+ mcc = attribute_values[i];
+ else if (!strcmp (attribute_names[i], "mnc"))
+ mnc = attribute_values[i];
+
+ if (mcc && strlen (mcc) && mnc && strlen (mnc)) {
+ parser->current_provider->gsm_mcc_mnc = g_slist_prepend (parser->current_provider->gsm_mcc_mnc,
+ mcc_mnc_new (mcc, mnc));
+ break;
+ }
+ }
+ } else if (!strcmp (name, "apn")) {
+ int i;
+
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (!strcmp (attribute_names[i], "value")) {
+
+ parser->state = PARSER_METHOD_GSM_APN;
+ parser->current_method = access_method_new ();
+ parser->current_method->gsm_apn = g_strstrip (g_strdup (attribute_values[i]));
+ break;
+ }
+ }
+ }
+}
+
+static void
+parser_cdma_start (MobileParser *parser,
+ const char *name,
+ const char **attribute_names,
+ const char **attribute_values)
+{
+ if (!strcmp (name, "sid")) {
+ int i;
+
+ for (i = 0; attribute_names && attribute_names[i]; i++) {
+ if (!strcmp (attribute_names[i], "value")) {
+ unsigned long tmp;
+
+ errno = 0;
+ tmp = strtoul (attribute_values[i], NULL, 10);
+ if (errno == 0 && tmp > 0)
+ parser->current_provider->cdma_sid = g_slist_prepend (parser->current_provider->cdma_sid,
+ GUINT_TO_POINTER ((guint32) tmp));
+ break;
+ }
+ }
+ }
+}
+
+static void
+mobile_parser_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer data,
+ GError **error)
+{
+ MobileParser *parser = (MobileParser *) data;
+
+ if (parser->text_buffer) {
+ g_free (parser->text_buffer);
+ parser->text_buffer = NULL;
+ }
+
+ switch (parser->state) {
+ case PARSER_TOPLEVEL:
+ parser_toplevel_start (parser, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_COUNTRY:
+ parser_country_start (parser, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_PROVIDER:
+ parser_provider_start (parser, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_METHOD_GSM:
+ parser_gsm_start (parser, element_name, attribute_names, attribute_values);
+ break;
+ case PARSER_METHOD_CDMA:
+ parser_cdma_start (parser, element_name, attribute_names, attribute_values);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+parser_country_end (MobileParser *parser,
+ const char *name)
+{
+ if (!strcmp (name, "country")) {
+ g_hash_table_insert (parser->table, parser->current_country, parser->current_providers);
+ parser->current_country = NULL;
+ parser->current_providers = NULL;
+ parser->text_buffer = NULL;
+ parser->state = PARSER_TOPLEVEL;
+ }
+}
+
+static void
+parser_provider_end (MobileParser *parser,
+ const char *name)
+{
+ if (!strcmp (name, "name")) {
+ if (!parser->current_provider->name) {
+ /* Use the first one. */
+ parser->current_provider->name = parser->text_buffer;
+ parser->text_buffer = NULL;
+ }
+ } else if (!strcmp (name, "provider")) {
+ parser->current_provider->methods = g_slist_reverse (parser->current_provider->methods);
+
+ parser->current_provider->gsm_mcc_mnc = g_slist_reverse (parser->current_provider->gsm_mcc_mnc);
+ parser->current_provider->cdma_sid = g_slist_reverse (parser->current_provider->cdma_sid);
+
+ parser->current_providers = g_slist_prepend (parser->current_providers, parser->current_provider);
+ parser->current_provider = NULL;
+ parser->text_buffer = NULL;
+ parser->state = PARSER_COUNTRY;
+ }
+}
+
+static void
+parser_gsm_end (MobileParser *parser,
+ const char *name)
+{
+ if (!strcmp (name, "gsm")) {
+ parser->text_buffer = NULL;
+ parser->state = PARSER_PROVIDER;
+ }
+}
+
+static void
+parser_gsm_apn_end (MobileParser *parser,
+ const char *name)
+{
+ if (!strcmp (name, "name")) {
+ if (!parser->current_method->name) {
+ /* Use the first one. */
+ parser->current_method->name = parser->text_buffer;
+ parser->text_buffer = NULL;
+ }
+ } else if (!strcmp (name, "username")) {
+ parser->current_method->username = parser->text_buffer;
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "password")) {
+ parser->current_method->password = parser->text_buffer;
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "dns")) {
+ parser->current_method->dns = g_slist_prepend (parser->current_method->dns, parser->text_buffer);
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "gateway")) {
+ parser->current_method->gateway = parser->text_buffer;
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "apn")) {
+ parser->current_method->type = SHELL_MOBILE_ACCESS_METHOD_TYPE_GSM;
+ parser->current_method->dns = g_slist_reverse (parser->current_method->dns);
+
+ if (!parser->current_method->name)
+ parser->current_method->name = g_strdup (_("Default"));
+
+ parser->current_provider->methods = g_slist_prepend (parser->current_provider->methods,
+ parser->current_method);
+ parser->current_method = NULL;
+ parser->text_buffer = NULL;
+ parser->state = PARSER_METHOD_GSM;
+ }
+}
+
+static void
+parser_cdma_end (MobileParser *parser,
+ const char *name)
+{
+ if (!strcmp (name, "username")) {
+ parser->current_method->username = parser->text_buffer;
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "password")) {
+ parser->current_method->password = parser->text_buffer;
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "dns")) {
+ parser->current_method->dns = g_slist_prepend (parser->current_method->dns, parser->text_buffer);
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "gateway")) {
+ parser->current_method->gateway = parser->text_buffer;
+ parser->text_buffer = NULL;
+ } else if (!strcmp (name, "cdma")) {
+ parser->current_method->type = SHELL_MOBILE_ACCESS_METHOD_TYPE_CDMA;
+ parser->current_method->dns = g_slist_reverse (parser->current_method->dns);
+
+ if (!parser->current_method->name)
+ parser->current_method->name = g_strdup (parser->current_provider->name);
+
+ parser->current_provider->methods = g_slist_prepend (parser->current_provider->methods,
+ parser->current_method);
+ parser->current_method = NULL;
+ parser->text_buffer = NULL;
+ parser->state = PARSER_PROVIDER;
+ }
+}
+
+static void
+mobile_parser_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer data,
+ GError **error)
+{
+ MobileParser *parser = (MobileParser *) data;
+
+ switch (parser->state) {
+ case PARSER_COUNTRY:
+ parser_country_end (parser, element_name);
+ break;
+ case PARSER_PROVIDER:
+ parser_provider_end (parser, element_name);
+ break;
+ case PARSER_METHOD_GSM:
+ parser_gsm_end (parser, element_name);
+ break;
+ case PARSER_METHOD_GSM_APN:
+ parser_gsm_apn_end (parser, element_name);
+ break;
+ case PARSER_METHOD_CDMA:
+ parser_cdma_end (parser, element_name);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+mobile_parser_characters (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer data,
+ GError **error)
+{
+ MobileParser *parser = (MobileParser *) data;
+
+ g_free (parser->text_buffer);
+ parser->text_buffer = g_strdup (text);
+}
+
+static const GMarkupParser mobile_parser = {
+ mobile_parser_start_element,
+ mobile_parser_end_element,
+ mobile_parser_characters,
+ NULL, /* passthrough */
+ NULL /* error */
+};
+
+/**
+ * shell_mobile_providers_parse:
+ * @out_ccs: (out) (allow-none): (element-type utf8 utf8): a #GHashTable containing
+ * country codes
+ *
+ * Returns: (element-type utf8 GList<Shell.MobileProvider>) (transfer container): a
+ * hash table where keys are country names 'char *', values are a 'GSList *'
+ * of 'ShellMobileProvider *'. Everything is destroyed with g_hash_table_destroy ().
+*/
+GHashTable *
+shell_mobile_providers_parse (GHashTable **out_ccs)
+{
+ GMarkupParseContext *ctx;
+ GIOChannel *channel;
+ MobileParser parser;
+ GError *error = NULL;
+ char buffer[4096];
+ GIOStatus status;
+ gsize len = 0;
+
+ memset (&parser, 0, sizeof (MobileParser));
+
+ parser.country_codes = read_country_codes ();
+ if (!parser.country_codes)
+ goto out;
+
+ channel = g_io_channel_new_file (MOBILE_BROADBAND_PROVIDER_INFO, "r", &error);
+ if (!channel) {
+ if (error) {
+ g_warning ("Could not read " MOBILE_BROADBAND_PROVIDER_INFO ": %s", error->message);
+ g_error_free (error);
+ } else
+ g_warning ("Could not read " MOBILE_BROADBAND_PROVIDER_INFO ": Unknown error");
+
+ goto out;
+ }
+
+ parser.table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, provider_list_free);
+ parser.state = PARSER_TOPLEVEL;
+
+ ctx = g_markup_parse_context_new (&mobile_parser, 0, &parser, NULL);
+
+ status = G_IO_STATUS_NORMAL;
+ while (status == G_IO_STATUS_NORMAL) {
+ status = g_io_channel_read_chars (channel, buffer, sizeof (buffer), &len, &error);
+
+ switch (status) {
+ case G_IO_STATUS_NORMAL:
+ if (!g_markup_parse_context_parse (ctx, buffer, len, &error)) {
+ status = G_IO_STATUS_ERROR;
+ g_warning ("Error while parsing XML: %s", error->message);
+ g_error_free (error);;
+ }
+ break;
+ case G_IO_STATUS_EOF:
+ break;
+ case G_IO_STATUS_ERROR:
+ g_warning ("Error while reading: %s", error->message);
+ g_error_free (error);
+ break;
+ case G_IO_STATUS_AGAIN:
+ /* FIXME: Try again a few times, but really, it never happes, right? */
+ break;
+ }
+ }
+
+ g_io_channel_unref (channel);
+ g_markup_parse_context_free (ctx);
+
+ if (parser.current_provider) {
+ g_warning ("pending current provider");
+ shell_mobile_provider_unref (parser.current_provider);
+ }
+
+ if (parser.current_providers) {
+ g_warning ("pending current providers");
+ provider_list_free (parser.current_providers);
+ }
+
+ g_free (parser.current_country);
+ g_free (parser.text_buffer);
+
+ out:
+ if (parser.country_codes) {
+ if (out_ccs)
+ *out_ccs = parser.country_codes;
+ else
+ g_hash_table_destroy (parser.country_codes);
+ }
+
+ return parser.table;
+}
+
+static void
+dump_generic (ShellMobileAccessMethod *method)
+{
+ GSList *iter;
+ GString *dns;
+
+ g_print (" username: %s\n", method->username ? method->username : "");
+ g_print (" password: %s\n", method->password ? method->password : "");
+
+ dns = g_string_new (NULL);
+ for (iter = method->dns; iter; iter = g_slist_next (iter))
+ g_string_append_printf (dns, "%s%s", dns->len ? ", " : "", (char *) iter->data);
+ g_print (" dns : %s\n", dns->str);
+ g_string_free (dns, TRUE);
+
+ g_print (" gateway : %s\n", method->gateway ? method->gateway : "");
+}
+
+static void
+dump_cdma (ShellMobileAccessMethod *method)
+{
+ g_print (" CDMA: %s\n", method->name);
+
+ dump_generic (method);
+}
+
+static void
+dump_gsm (ShellMobileAccessMethod *method)
+{
+ g_print (" APN: %s (%s)\n", method->name, method->gsm_apn);
+
+ dump_generic (method);
+}
+
+static void
+dump_country (gpointer key, gpointer value, gpointer user_data)
+{
+ GSList *citer, *miter;
+
+ for (citer = value; citer; citer = g_slist_next (citer)) {
+ ShellMobileProvider *provider = citer->data;
+
+ g_print ("Provider: %s (%s)\n", provider->name, (const char *) key);
+ for (miter = provider->methods; miter; miter = g_slist_next (miter)) {
+ ShellMobileAccessMethod *method = miter->data;
+ GSList *liter;
+
+
+ for (liter = provider->gsm_mcc_mnc; liter; liter = g_slist_next (liter)) {
+ ShellGsmMccMnc *m = liter->data;
+ g_print (" MCC/MNC: %s-%s\n", m->mcc, m->mnc);
+ }
+
+ for (liter = provider->cdma_sid; liter; liter = g_slist_next (liter))
+ g_print (" SID: %d\n", GPOINTER_TO_UINT (liter->data));
+
+ switch (method->type) {
+ case SHELL_MOBILE_ACCESS_METHOD_TYPE_CDMA:
+ dump_cdma (method);
+ break;
+ case SHELL_MOBILE_ACCESS_METHOD_TYPE_GSM:
+ dump_gsm (method);
+ break;
+ default:
+ break;
+ }
+ g_print ("\n");
+ }
+ }
+}
+
+void
+shell_mobile_providers_dump (GHashTable *providers)
+{
+ g_return_if_fail (providers != NULL);
+ g_hash_table_foreach (providers, dump_country, NULL);
+}
+
+/* All the following don't exist in nm-applet, because C doesn't need
+ those. They're only needed for the introspection annotations
+*/
+
+/**
+ * shell_mobile_provider_get_gsm_mcc_mnc:
+ * @provider: a #ShellMobileProvider
+ *
+ * Returns: (element-type Shell.GsmMccMnc) (transfer none): the
+ * list of #ShellGsmMccMnc this provider exposes
+ */
+GSList *
+shell_mobile_provider_get_gsm_mcc_mnc (ShellMobileProvider *provider)
+{
+ return provider->gsm_mcc_mnc;
+}
+
+/**
+ * shell_mobile_provider_get_cdma_sid:
+ * @provider: a #ShellMobileProvider
+ *
+ * Returns: (element-type guint32) (transfer none): the
+ * list of CDMA sids this provider exposes
+ */
+GSList *
+shell_mobile_provider_get_cdma_sid (ShellMobileProvider *provider)
+{
+ return provider->cdma_sid;
+}
diff --git a/src/shell-mobile-providers.h b/src/shell-mobile-providers.h
new file mode 100644
index 0000000..d70b8e2
--- /dev/null
+++ b/src/shell-mobile-providers.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2009 Novell, Inc.
+ * Author: Tambet Ingo (tambet gmail com).
+ *
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ *
+ * Ported to GNOME Shell by Giovanni Campagna <scampa giovanni gmail com>
+ * Porting consisted only in replacing nmn with shell, to be compatible with
+ * GObject Introspection namespacing
+ */
+
+#ifndef SHELL_MOBILE_PROVIDERS_H
+#define SHELL_MOBILE_PROVIDERS_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#define SHELL_TYPE_MOBILE_PROVIDER (shell_mobile_provider_get_type ())
+#define SHELL_TYPE_MOBILE_ACCESS_METHOD (shell_mobile_access_method_get_type ())
+
+typedef enum {
+ SHELL_MOBILE_ACCESS_METHOD_TYPE_UNKNOWN = 0,
+ SHELL_MOBILE_ACCESS_METHOD_TYPE_GSM,
+ SHELL_MOBILE_ACCESS_METHOD_TYPE_CDMA
+} ShellMobileAccessMethodType;
+
+typedef struct {
+ char *mcc;
+ char *mnc;
+} ShellGsmMccMnc;
+
+typedef struct {
+ char *name;
+ /* maps lang (char *) -> name (char *) */
+ GHashTable *lcl_names;
+
+ char *username;
+ char *password;
+ char *gateway;
+ GSList *dns; /* GSList of 'char *' */
+
+ /* Only used with SHELL_PROVIDER_TYPE_GSM */
+ char *gsm_apn;
+
+ ShellMobileAccessMethodType type;
+
+ gint refs;
+} ShellMobileAccessMethod;
+
+typedef struct {
+ char *name;
+ /* maps lang (char *) -> name (char *) */
+ GHashTable *lcl_names;
+
+ GSList *methods; /* GSList of ShellMobileAccessMethod */
+
+ GSList *gsm_mcc_mnc; /* GSList of ShellGsmMccMnc */
+ GSList *cdma_sid; /* GSList of guint32 */
+
+ gint refs;
+} ShellMobileProvider;
+
+
+GType shell_gsm_mcc_mnc_get_type (void); /* added in porting */
+GType shell_mobile_provider_get_type (void);
+GType shell_mobile_access_method_get_type (void);
+
+ShellMobileProvider *shell_mobile_provider_ref (ShellMobileProvider *provider);
+void shell_mobile_provider_unref (ShellMobileProvider *provider);
+GSList * shell_mobile_provider_get_gsm_mcc_mnc (ShellMobileProvider *provider);
+GSList * shell_mobile_provider_get_cdma_sid (ShellMobileProvider *provider);
+
+ShellMobileAccessMethod *shell_mobile_access_method_ref (ShellMobileAccessMethod *method);
+void shell_mobile_access_method_unref (ShellMobileAccessMethod *method);
+
+GHashTable *shell_mobile_providers_parse (GHashTable **out_ccs);
+
+void shell_mobile_providers_dump (GHashTable *providers);
+
+#endif /* SHELL_MOBILE_PROVIDERS_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]