diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index fac0006e0..c64c23209 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 55bb11131..a085bfc31 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 000000000..f33023805 --- /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 e2ec2c164..f68907ec2 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 + +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 da27f6fbe..d606fd741 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 000000000..3b240f3bc --- /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) || _(""); + + 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) || _(""); + 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 1a4e2720b..51303bbf6 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 a24a71e56..e878f20bf 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 000000000..5d985ed09 --- /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 +#include +#include + +#include + +#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) (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 000000000..d70b8e2f0 --- /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 + * 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 +#include + +#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 */