diff -up gnome-shell-2.91.91/data/theme/gnome-shell.css.network gnome-shell-2.91.91/data/theme/gnome-shell.css
--- gnome-shell-2.91.91/data/theme/gnome-shell.css.network	2011-03-07 16:53:56.000000000 -0500
+++ gnome-shell-2.91.91/data/theme/gnome-shell.css	2011-03-09 07:14:22.879215557 -0500
@@ -158,6 +158,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 -up gnome-shell-2.91.91/js/Makefile.am.network gnome-shell-2.91.91/js/Makefile.am
--- gnome-shell-2.91.91/js/Makefile.am.network	2011-02-22 12:36:25.000000000 -0500
+++ gnome-shell-2.91.91/js/Makefile.am	2011-03-09 07:14:22.879215557 -0500
@@ -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 -up gnome-shell-2.91.91/js/misc/modemManager.js.network gnome-shell-2.91.91/js/misc/modemManager.js
--- gnome-shell-2.91.91/js/misc/modemManager.js.network	2011-03-09 07:14:22.880215545 -0500
+++ gnome-shell-2.91.91/js/misc/modemManager.js	2011-03-09 07:14:22.880215545 -0500
@@ -0,0 +1,103 @@
+// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
+const DBus = imports.dbus;
+const Lang = imports.lang;
+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);
+
+
+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) {
+            if (name.length > 0)
+                this.operator_name = name;
+            else
+                this.operator_name = null;
+            this.emit('notify::operator-name');
+        }));
+        this._proxy.GetRegistrationInfoRemote(Lang.bind(this, function(status, code, name) {
+            if (name.length > 0)
+                this.operator_name = name;
+            else
+                this.operator_name = null;
+            this.emit('notify::operator-name');
+        }));
+        this._proxy.GetSignalQualityRemote(Lang.bind(this, function(quality) {
+            this.signal_quality = quality;
+            this.emit('notify::signal-quality');
+        }));
+    }
+}
+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(quality) {
+            this.signal_quality = quality;
+            this.emit('notify::signal-quality');
+        }));
+        this._proxy.GetSignalQualityRemote(Lang.bind(this, function(proxy, quality) {
+            this.signal_quality = quality;
+            this.emit('notify::signal-quality');
+        }));
+        this._proxy.GetServingSystemRemote(Lang.bind(this, function(proxy, status, name, code) {
+            if (name.length > 0)
+                this.operator_name = name;
+            else
+                this.operator_name = null;
+            this.emit('notify::operator-name');
+        }));
+    }
+};
+Signals.addSignalMethods(ModemCdma.prototype);
diff -up gnome-shell-2.91.91/js/ui/panel.js.network gnome-shell-2.91.91/js/ui/panel.js
--- gnome-shell-2.91.91/js/ui/panel.js.network	2011-03-07 15:41:40.000000000 -0500
+++ gnome-shell-2.91.91/js/ui/panel.js	2011-03-09 07:14:22.881215533 -0500
@@ -41,7 +41,8 @@ const STANDARD_TRAY_ICON_SHELL_IMPLEMENT
     'a11y': imports.ui.status.accessibility.ATIndicator,
     'volume': imports.ui.status.volume.Indicator,
     'battery': imports.ui.status.power.Indicator,
-    'keyboard': imports.ui.status.keyboard.XKBIndicator
+    'keyboard': imports.ui.status.keyboard.XKBIndicator,
+    'network': imports.ui.status.network.NMApplet
 };
 
 if (Config.HAVE_BLUETOOTH)
diff -up gnome-shell-2.91.91/js/ui/status/network.js.network gnome-shell-2.91.91/js/ui/status/network.js
--- gnome-shell-2.91.91/js/ui/status/network.js.network	2011-03-09 07:14:22.883215508 -0500
+++ gnome-shell-2.91.91/js/ui/status/network.js	2011-03-09 09:24:52.728042901 -0500
@@ -0,0 +1,1783 @@
+// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
+const ByteArray = imports.byteArray;
+const DBus = imports.dbus;
+const GLib = imports.gi.GLib;
+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 NMAppletIface = {
+    name: 'org.gnome.network-manager-applet',
+    methods: [
+        { name: 'ConnectToHiddenNetwork', outSignature: '', inSignature: '' },
+        { name: 'CreateWifiNetwork', outSignature: '', inSignature: '' },
+    ]
+};
+const NMAppletProxy = DBus.makeProxyClass(NMAppletIface);
+
+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.length != two.length)
+        return false;
+    for (let i = 0; i < one.length; i++) {
+        if (one[i] != two[i])
+            return false;
+    }
+    return true;
+}
+
+// shared between NMNetworkMenuItem and NMDeviceWWAN
+function signalToIcon(value) {
+    if (value > 80)
+        return 'excellent';
+    if (value > 55)
+        return 'good';
+    if (value > 30)
+        return 'ok';
+    if (value > 5)
+        return 'weak';
+    return 'none';
+}
+
+// shared between NMNetworkMenuItem and NMDeviceWireless
+function sortAccessPoints(accessPoints) {
+    return accessPoints.sort(function (one, two) {
+        return two.strength - one.strength;
+    });
+}
+
+function NMNetworkMenuItem() {
+    this._init.apply(this, arguments);
+}
+
+NMNetworkMenuItem.prototype = {
+    __proto__: PopupMenu.PopupImageMenuItem.prototype,
+
+    _init: function(accessPoints, title, params) {
+        accessPoints = sortAccessPoints(accessPoints);
+        this.bestAP = accessPoints[0];
+
+        let ssid = this.bestAP.get_ssid();
+        title = title || NetworkManager.utils_ssid_to_utf8(ssid) || _("<unknown>");
+
+        PopupMenu.PopupImageMenuItem.prototype._init.call(this, title, this._getIcon(), params);
+
+        this._accessPoints = [ ];
+        for (let i = 0; i < accessPoints.length; i++) {
+            let ap = accessPoints[i];
+            // need a wrapper object here, because the access points can be shared
+            // between many NMNetworkMenuItems
+            let apObj = {
+                ap: ap,
+                updateId: ap.connect('notify::strength', Lang.bind(this, this._updated))
+            };
+            this._accessPoints.push(apObj);
+        }
+    },
+
+    _updated: function(ap, strength) {
+        if (strength > this.bestAP.strength)
+            this.bestAP = ap;
+
+        this.setIcon(this._getIcon());
+    },
+
+    _getIcon: function() {
+        return 'network-wireless-signal-' + signalToIcon(this.bestAP.strength);
+    },
+
+    updateAccessPoints: function(accessPoints) {
+        for (let i = 0; i < this._accessPoints.length; i++) {
+            let apObj = this._accessPoints[i];
+            apObj.ap.disconnect(apObj.updateId);
+            apObj.updateId = 0;
+        }
+
+        accessPoints = sortAccessPoints(accessPoints);
+        this.bestAP = accessPoints[0];
+        this._accessPoints = [ ];
+        for (let i = 0; i < accessPoints; i++) {
+            let ap = accessPoints[i];
+            let apObj = {
+                ap: ap,
+                updateId: ap.connect('notify::strength', Lang.bind(this, this._updated))
+            };
+            this._accessPoints.push(apObj);
+        }
+    },
+
+    destroy: function() {
+        for (let i = 0; i < this._accessPoints.length; i++) {
+            let apObj = this._accessPoints[i];
+            apObj.ap.disconnect(apObj.updateId);
+            apObj.updateId = 0;
+        }
+
+        PopupMenu.PopupImageMenuItem.prototype.destroy.call(this);
+    }
+};
+
+function NMDevice() {
+    throw new TypeError('Instantanting abstract class NMDevice');
+}
+
+NMDevice.prototype = {
+    _init: function(client, device, connections, activeConnectionPosition) {
+        this.device = device;
+        if (device) {
+            this.device._delegate = this;
+            this.device.connect('state-changed', Lang.bind(this, this.deviceStateChanged));
+        }
+
+        // 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,
+            };
+            this._connections.push(obj);
+        }
+        this._activeConnection = null;
+        this._activeConnectionItem = null;
+        this._disconnectItem = null;
+        this._autoConnectionItem = null;
+        this._statusItem = null;
+
+        // private
+        this._activeConnectionPosition = activeConnectionPosition || 0;
+
+        if (this.device)
+            this.titleItem = new PopupMenu.PopupMenuItem(this.device._description, { reactive: false, style_class: 'popup-subtitle-menu-item' });
+        this.section = new PopupMenu.PopupMenuSection();
+
+        this._createSection();
+    },
+
+    setActiveConnection: function(activeConnection) {
+        if (activeConnection == this._activeConnection)
+            // nothing to do
+            return;
+
+        // remove any UI
+        if (this._activeConnectionItem) {
+            this._activeConnectionItem.destroy();
+            this._activeConnectionItem = null;
+        }
+
+        if (this._disconnectItem)  {
+            this._disconnectItem.destroy();
+            this._disconnectItem = null;
+        }
+
+        let previousActive = this._activeConnection;
+        this._activeConnection == null;
+
+        if (previousActive && previousActive._connection) {
+            // add the connection back as a normal one, by removing and adding it
+            this.removeConnection(previousActive._connection, true);
+            this.addConnection(previousActive._connection);
+        }
+
+        if (activeConnection) {
+            if (activeConnection._connection) {
+                // remove the connection if it was already seen
+                let pos = this._findConnection(activeConnection._connection._uuid);
+                if (pos != -1) {
+                    let obj = this._connections[pos];
+                    if (obj.item)
+                        obj.item.destroy();
+                    obj.item = null;
+                }
+            }
+
+            this._activeConnection = activeConnection;
+            this._createActiveConnectionItems();
+            if (this._disconnectItem)
+                this.section.addMenuItem(this._disconnectItem, this._activeConnectionPosition);
+            this.section.addMenuItem(this._activeConnectionItem, this._activeConnectionPosition);
+        }
+    },
+
+    addConnection: function(connection) {
+        if (this._findConnection(connection._uuid) != -1) {
+            log('Connection already added to menu, not adding again');
+            return;
+        }
+
+        // record the connection
+        let obj = {
+            connection: connection,
+            name: connection._name,
+            uuid: connection._uuid,
+        };
+        this._connections.push(obj);
+
+        if (!this.device ||
+            (this.device.state == NetworkManager.DeviceState.DISCONNECTED ||
+             this.device.state == NetworkManager.DeviceState.ACTIVATED)) {
+            // we're showing a connection list, so we need to append this one
+            if (this._connections.length > 1) {
+                // already showing real connections
+                obj.item = this._createConnectionItem(obj);
+                this.section.addMenuItem(obj.item);
+            } else {
+                // first connection in the list, destroy the automatic one
+                this._clearSection();
+                this._createSection();
+            }
+        }
+    },
+
+    removeConnection: function(connection, skipCreateAuto) {
+        if (!connection._uuid) {
+            log('Cannot remove a connection without an UUID');
+            return;
+        }
+        let pos = this._findConnection(connection._uuid);
+        if (pos == -1) {
+            log('Connection was never added, cannot remove');
+            return;
+        }
+
+        let obj = this._connections[pos];
+        if (obj.item)
+            obj.item.destroy();
+        this._connections.splice(pos, 1);
+
+        if (this._connections.length == 0 && !skipCreateAuto) {
+            // We need to show the automatic connection again
+            this._clearSection();
+            this._createSection();
+        }
+    },
+
+    connectionValid: function(connection) {
+        throw new TypeError('Invoking pure virtual function NMDevice.connectionValid');
+    },
+
+    // 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;
+        this._disconnectItem = null;
+        this._statusItem = null;
+        for (let i = 0; i < this._connections.length; i++) {
+            this._connections[i].item = null;
+        }
+    },
+
+    _createSection: function() {
+        let status;
+        if (!this.device ||
+            (this.device.state == NetworkManager.DeviceState.DISCONNECTED ||
+             this.device.state == NetworkManager.DeviceState.ACTIVATED))
+            this._createConnectionList();
+        else if (this.device.state != NetworkManager.DeviceState.UNMANAGED) {
+            let title = this._getStatusLabel();
+            this._statusItem = new PopupMenu.PopupMenuItem(title,
+                                                           { reactive: false,
+                                                             style_class: 'popup-inactive-menu-item'
+                                                           });
+            this.section.addMenuItem(this._statusItem);
+        }
+        // else do nothing, the menu should remain empty
+    },
+
+    _createConnectionList: function() {
+        if (this._activeConnection) {
+            this._createActiveConnectionItems();
+            this.section.addMenuItem(this._activeConnectionItem);
+            if (this._disconnectItem)
+                this.section.addMenuItem(this._disconnectItem);
+        }
+        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;
+    },
+
+    _createActiveConnectionItems: function() {
+        let title;
+        let active = this._activeConnection._connection;
+        this._disconnectItem = null;
+        if (active) {
+            title = active._name;
+            this._disconnectItem = new PopupMenu.PopupMenuItem(_("Disconnect"));
+            this._disconnectItem.connect('activate', Lang.bind(this, function() {
+                /* The correct approach would be
+                   this.client.deactivate_connection(this._activeConnection);
+                   but with this, NM insists in restarting the connection immediately
+                */
+                this.device.disconnect(function() { });
+            }));
+        } 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);
+    },
+
+    // private
+    _getStatusLabel: function() {
+        switch(this.device.state) {
+        case NetworkManager.DeviceState.DISCONNECTED:
+        case NetworkManager.DeviceState.ACTIVATED:
+            log('Attempt to show status for a disconnected / activate device, should be showing connection list instead');
+            return 'invalid';
+        case NetworkManager.DeviceState.PREPARE:
+        case NetworkManager.DeviceState.CONFIG:
+        case NetworkManager.DeviceState.IP_CONFIG:
+            return _("connecting...");
+        case NetworkManager.DeviceState.NEED_AUTH:
+            return _("authentication required");
+        case NetworkManager.DeviceState.UNAVAILABLE:
+            if ((this.device.capabilities & NetworkManager.DeviceCapabilities.CARRIER_DETECT) &&
+                !this.device.carrier)
+                return _("network cable unplugged");
+            else
+                return _("network unavailable");
+        case NetworkManager.DeviceState.FAILED:
+            return _("connection failed");
+        default:
+            log('Device state invalid, is %d'.format(this.device.state));
+            return 'invalid';
+        }
+    },
+
+    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.UNMANAGED:
+            // clear everything and be quiet
+            this._clearSection();
+            return;
+        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;
+        }
+
+        if (newstate != NetworkManager.DeviceState.DISCONNECTED &&
+            newstate != NetworkManager.DeviceState.ACTIVATED &&
+            oldstate != NetworkManager.DeviceState.DISCONNECTED &&
+            oldstate != NetworkManager.DeviceState.ACTIVATED &&
+            oldstate != NetworkManager.DeviceState.UNMANAGED) {
+            // a transition between states that show the status label
+            this._statusItem.label.text = this._getStatusLabel();
+            return;
+        }
+
+        // just refresh everything
+        this._clearSection();
+        this._createSection();
+    }
+};
+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.mobileDevice = null;
+        this._connecitonType = 'ppp';
+
+        // FIXME: listen for this property changing and recreate modem stuff
+        this._capabilities = device.current_capabilites;
+        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._capabilites & NetworkManager.DeviceModemCapabilites.LTE) {
+            is_wwan = true;
+            // FIXME: support signal quality
+        }
+
+        if (is_wwan) {
+            this.category = NMConnectionCategory.WWAN;
+            this._autoConnectionName = _("New Mobile Broadband connection...");
+        } else {
+            this.category = NMConnectionCategory.WIRED;
+            this._autoConnectionName = _("Auto dial-up");
+        }
+
+        if (this.mobileDevice) {
+            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.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);
+    },
+
+    _getSignalIcon: function() {
+        return 'network-cellular-signal-' + signalToIcon(this.mobileDevice.signal_quality);
+    },
+
+    _createSection: function() {
+        if (this.mobileDevice) {
+            this._operatorItem = new PopupMenu.PopupImageMenuItem(this.mobileDevice.operator_name || '',
+                                                                  this._getSignalIcon(),
+                                                                  { reactive: false });
+            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
+        // (the same FIXME is currently in nm-applet, actually)
+        // (this does not apply to dial-up)
+
+        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 = _("New Mobile Broadband connection...");
+        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() {
+        // XXX: is this enough? or do we need other stuff from bluetoothd?
+
+        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;
+    }
+};
+
+
+// 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;
+    }
+};
+
+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 = [ ];
+
+        // XXX: 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 name
+            let ap = accessPoints[i];
+            let ssid = ap.get_ssid();
+            let pos = this._findNetwork(ssid);
+            let obj;
+            if (pos != -1) {
+                obj = this._networks[pos];
+                obj.accessPoints.push(ap);
+            } else {
+                obj = { ssid: ssid,
+                        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);
+                }
+            }
+        }
+        device.connect('access-point-added', Lang.bind(this, this._accessPointAdded));
+        device.connect('access-point-removed', Lang.bind(this, this._accessPointRemoved));
+
+        NMDevice.prototype._init.call(this, client, device, validConnections);
+    },
+
+    _findNetwork: function(ssid) {
+        for (let i = 0; i < this._networks.length; i++) {
+            if (ssidCompare(this._networks[i].ssid, ssid))
+                return i;
+        }
+        return -1;
+    },
+
+    _accessPointAdded: function(device, accessPoint) {
+        let ssid = accessPoint.get_ssid();
+        let pos = this._findNetwork(ssid);
+        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: ssid,
+                      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);
+            }
+        }
+
+        if (this.device.state == NetworkManager.DeviceState.DISCONNECTED ||
+            this.device.state == NetworkManager.DeviceState.ACTIVATED) {
+            // update everything (it would be too complicated to update just what has changed)
+            this._clearSection();
+            this._createConnectionList();
+        }
+    },
+
+    _accessPointRemoved: function(device, accessPoint) {
+        let ssid = accessPoint.get_ssid();
+        let pos = this._findNetwork(ssid);
+
+        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, skipCreateAuto) {
+        if (!connection._uuid)
+            return;
+        let pos = this._findConnection(connection._uuid);
+        if (pos == -1) {
+            log('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 = true;
+                    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;
+                        }
+                    }
+                }
+            }
+        }
+
+        if (forceupdate || (anyauto && !skipCreateAuto)) {
+            this._clearSection();
+            this._createConnectionList();
+        }
+    },
+
+    addConnection: function(connection) {
+        if (this._findConnection(connection._uuid) != -1) {
+            log('Connection already added to menu, not adding again');
+            return;
+        }
+
+        // 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.device.state == NetworkManager.DeviceState.DISCONNECTED ||
+                 this.device.state == NetworkManager.DeviceState.ACTIVATED)) {
+                // 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 false;
+
+        if (!wirelessSettings.ap_security_compatible(wirelessSecuritySettings, ap.flags, ap.wpa_flags, ap.rsn_flags, ap.mode))
+            return false;
+        return true;
+    },
+
+    _createActiveConnectionItems: 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 });
+            this._disconnectItem = new PopupMenu.PopupMenuItem(_("Disconnect"));
+            this._disconnectItem.connect('activate', Lang.bind(this, function() {
+                this.device.disconnect(function() { });
+            }));
+        } 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);
+    },
+
+    _createConnectionList: function() {
+        if(this._activeConnection) {
+            this._createActiveConnectionItems();
+            this.section.addMenuItem(this._activeConnectionItem);
+            if (this._disconnectItem)
+                this.section.addMenuItem(this._disconnectItem);
+        }
+
+        let activeAp = this.device.active_access_point;
+        // Use undefined instead of null, so we're sure not to match anything
+        // that comes from NetworkManager
+        let activeApSsid = activeAp ? activeAp.get_ssid() : null;
+
+        for(let j = 0; j < this._networks.length; j++) {
+            let apObj = this._networks[j];
+            if(activeAp && ssidCompare(apObj.ssid, activeApSsid))
+                continue;
+
+            let menuItem;
+            if(apObj.connections.length > 0) {
+                if (apObj.connections.length == 1)
+                    apObj.item = this._createAPItem(apObj.connections[0], apObj, false);
+                else {
+                    let title = NetworkManager.utils_ssid_to_utf8(apObj.ssid) || _("<unknown>");
+                    apObj.item = new PopupMenu.PopupSubMenuMenuItem(title);
+                    apObj.item._apObj = apObj;
+                    for (let i = 0; i < apObj.connections.length; i++)
+                        apObj.item.menu.addMenuItem(this._createAPItem(apObj.connections[i], apObj, true));
+                }
+            } else {
+                apObj.item = new NMNetworkMenuItem(apObj.accessPoints);
+                apObj.item._apObj = apObj;
+                apObj.item.connect('activate', Lang.bind(this, function() {
+                    let 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
+                    }));
+
+                    let accessPoints = sortAccessPoints(apObj.accessPoints);
+                    this._client.add_and_activate_connection(connection, this.device, accessPoints[0].dbus_path, null)
+                }));
+            }
+
+            if (j <= 5)
+                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._nmApplet = new NMAppletProxy(DBus.session, 'org.gnome.network-manager-applet', '/org/gnome/network-manager-applet');
+
+        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.menu.addMenuItem(this._statusSection);
+
+        this._deviceSections = [ ];
+
+        this._wiredSection = new PopupMenu.PopupMenuSection();
+        this._wiredItem = new PopupMenu.PopupMenuItem(_("Wired networks"),
+                                                      { style_class: 'popup-subtitle-menu-item', reactive: false });
+        this._wiredDevices = [ ];
+
+        this._wiredSection.addMenuItem(this._wiredItem);
+        this._wiredSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this._wiredSection.actor.hide();
+        this._deviceSections.push(this._wiredSection);
+        this.menu.addMenuItem(this._wiredSection);
+
+        this._wirelessSection = new PopupMenu.PopupMenuSection();
+        this._wirelessDevices = [ ];
+        this._makeToggleItem('wireless', _("Wireless networks"));
+        this._wirelessSection.addMenuItem(this._wirelessItem);
+
+        let newAdhocWireless = new PopupMenu.PopupMenuItem(_("Create new wireless network..."));
+        newAdhocWireless.connect('activate', Lang.bind(this, function() {
+            this._nmApplet.CreateWirelessNetworkRemote();
+        }));
+        this._wirelessSection.addMenuItem(newAdhocWireless);
+
+        let newHiddenWireless = new PopupMenu.PopupMenuItem(_("Other networks..."));
+        newHiddenWireless.connect('activate', Lang.bind(this, function() {
+            this._nmApplet.ConnectToHiddenNetworkRemote();
+        }));
+        this._wirelessSection.addMenuItem(newHiddenWireless);
+
+        this._wirelessSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this._wirelessSection.actor.hide();
+        this._deviceSections.push(this._wirelessSection);
+        this.menu.addMenuItem(this._wirelessSection);
+
+        this._wwanSection = new PopupMenu.PopupMenuSection();
+        this._wwanDevices = [ ];
+        this._makeToggleItem('wwan', _("Mobile broadband"));
+        this._wwanSection.addMenuItem(this._wwanItem);
+        this._wwanSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+
+        this._wwanSection.actor.hide();
+        this._deviceSections.push(this._wwanSection);
+        this.menu.addMenuItem(this._wwanSection);
+
+        this._vpnDevice = new NMDeviceVPN();
+        this._vpnItem = new PopupMenu.PopupMenuItem(_("VPN Connections"),
+                                                    { style_class: 'popup-subtitle-menu-item', reactive: false });
+        this._vpnSection = new PopupMenu.PopupMenuSection();
+        this._vpnSection.addMenuItem(this._vpnItem);
+        this._vpnSection.addMenuItem(this._vpnDevice.section);
+        this._vpnSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
+        this._deviceSections.push(this._vpnSection);
+        this.menu.addMenuItem(this._vpnSection);
+
+        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;
+        // FIXME: WiMax support (if enabled)
+
+        // 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._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
+            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 enabledKey = type + '_enabled';
+        let hardwareKey = type + '_hardware_enabled';
+        let devicesKey = '_' + type + 'Devices';
+        let setEnabledFunc = type + '_set_enabled';
+
+        let item = new PopupMenu.PopupSwitchMenuItem(title, false, { style_class: 'popup-subtitle-menu-item' });
+        item.connect('toggled', Lang.bind(this, function(item, state) {
+            this._client[setEnabledFunc](state);
+        }));
+
+        let handler = Lang.bind(this, function() {
+            let software = this._client[enabledKey];
+            let hardware = this._client[hardwareKey];
+
+            let enabled = software && hardware;
+            item.setToggleState(enabled);
+            item.actor.reactive = hardware;
+            item.actor.can_focus = hardware;
+
+            let devices = this[devicesKey];
+            for (let i = 0; i < devices.length; i++) {
+                if (enabled) {
+                    if (devices.length > 1)
+                        devices[i].titleItem.actor.show();
+                    else
+                        devices[i].titleItem.actor.hide();
+                    devices[i].section.actor.show();
+                } else {
+                    devices[i].titleItem.actor.hide();
+                    devices[i].section.actor.hide();
+                }
+            }
+        });
+
+        this._client.connect('notify::' + type + '-enabled', handler);
+        this._client.connect('notify::' + type + '-hardware-enabled', handler);
+        this['_' + type + 'Item'] = item;
+        handler();
+    },
+
+    _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) {
+        let wrapperClass = this._dtypes[device.get_device_type()];
+        if (wrapperClass) {
+            // XXX: check what nm-applet does here
+            device._description = device.get_product();
+
+            let wrapper = new wrapperClass(this._client, device, this._connections);
+            wrapper._networkLostId = wrapper.connect('network-lost', Lang.bind(this, function() {
+                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._destroyId = wrapper.connect('destroy', function(wrapper) {
+                wrapper.disconnect(wrapper._networkLostId);
+                wrapper.disconnect(wrapper._activationFailedId);
+            });
+            let section = this['_' + wrapper.category + 'Section'];
+            let devices = this['_' + wrapper.category + 'Devices'];
+
+            section.addMenuItem(wrapper.section, 1);
+            section.addMenuItem(wrapper.titleItem, 1);
+            devices.push(wrapper);
+
+            // sync the visibility of titleItems
+            for (let i = 0; i < devices.length; i++) {
+                if (devices.length > 1)
+                    devices[i].titleItem.actor.show();
+                else
+                    devices[i].titleItem.actor.hide();
+            }
+        } 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 (race condition?)');
+            return;
+        }
+
+        let wrapper = device._delegate;
+        wrapper.titleItem.destroy();
+        wrapper.section.destroy();
+
+        let section = this['_' + wrapper.category + 'Section'];
+        let devices = this['_' + wrapper.category + 'Devices'];
+
+        let pos = devices.indexOf(wrapper);
+        devices.splice(pos, 1);
+
+        // sync the visibility of titleItems
+        for (let i = 0; i < devices.length; i++) {
+            if (devices.length > 1)
+                devices[i].titleItem.show();
+            else
+                devices[i].titleItem.hide();
+        }
+    },
+
+    _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._connection)
+                active._connection._active = null;
+            if (active._primaryDevice)
+                active._primaryDevice.setActiveConnection(null);
+            if (active._notifyStateId)
+                active.disconnect(active._notifyStateId);
+            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 immediately
+                    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._connection)
+                a._connection._active = this;
+
+            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
+                    // XXX: check what nm-applet does here
+                    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) {
+        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) {
+                log('Connection was already seen, when reading the connections for the first time (race condition?)');
+                continue;
+            }
+            connection.connect('removed', Lang.bind(this, this._connectionRemoved));
+            connection.connect('updated', Lang.bind(this, this._updateConnection));
+
+            this._updateConnection(connection);
+            this._connections.push(connection);
+        }
+    },
+
+    _newConnection: function(settings, connection) {
+        if (connection._uuid) {
+            log('Connection was already seen, not adding again...');
+            return;
+        }
+
+        connection.connect('removed', Lang.bind(this, this._connectionRemoved));
+        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);
+
+        if (!connection._everAdded)
+            return;
+
+        let section = connection._section;
+        if (section == NMConnectionCategory.VPN) {
+            this._vpnDevice.removeConnection(connection);
+            if (this._vpnDevice.empty)
+                this._vpnSection.actor.hide();
+        } else {
+            let devices = this['_' + section + 'Devices'];
+            for (let i = 0; i < devices.length; i++)
+                devices[i].removeConnection(connection);
+        }
+    },
+
+    _updateConnection: function(connection) {
+        this._connectionRemoved(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;
+
+        let section = connection._section;
+        if (section == NMConnectionCategory.VPN) {
+            this._vpnDevice.addConnection(connection);
+            this._vpnSection.actor.show();
+            connection._everAdded = true;
+        } else {
+            let devices = this['_' + section + 'Devices'];
+            for (let i = 0; i < devices.length; i++) {
+                if (devices[i].connectionValid(connection)) {
+                    devices[i].addConnection(connection);
+                    connection._everAdded = true;
+                }
+            }
+        }
+    },
+
+    _hideDevices: function() {
+        for (let i = 0; i < this._deviceSections.length; i++) {
+            let item = this._deviceSections[i];
+            item.actor.hide();
+        }
+    },
+
+    _showNormal: function() {
+        this._statusSection.actor.hide();
+
+        if (this._wiredDevices.length > 0)
+            this._wiredSection.actor.show();
+        else
+            this._wiredSection.actor.hide();
+
+        if (this._wirelessDevices.length > 0)
+            this._wirelessSection.actor.show();
+        else
+            this._wirelessSection.actor.hide();
+
+        if (this._wwanDevices.length > 0)
+            this._wwanSection.actor.show();
+        else
+            this._wwanSection.actor.hide();
+
+        if (!this._vpnDevice.empty)
+            this._vpnSection.actor.show();
+        else
+            this._vpnSection.actor.hide();
+    },
+
+    _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;
+
+        switch (this._client.state) {
+        case NetworkManager.State.DISCONNECTED:
+            this.setIcon('network-offline');
+            break;
+        case NetworkManager.State.CONNECTING:
+            if (!mc || mc.state != NetworkManager.ActiveConnectionState.ACTIVATING) {
+                log('NetworkManager is connecting, but we have no ActiveConnection activating');
+                break;
+            }
+            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:
+                // XXX: there is no network-vpn-acquiring
+                this.setIcon('network-vpn');
+                break;
+            default:
+                this.setIcon('network-error');
+                log('Invalid active connection type ' + mc._section);
+            }
+            break;
+        case NetworkManager.State.CONNECTED_LOCAL:
+        case NetworkManager.State.CONNECTED_SITE:
+        case NetworkManager.State.CONNECTED_GLOBAL:
+            if (!mc) {
+                log('NetworkManager is connected, but reports no active connection');
+                break;
+            }
+            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;
+                        }
+                        // XXX: there is no network-wireless-connected
+                        this.setIcon('network-wireless-signal-excellent');
+                    } 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-symbolic');
+                break;
+            case NMConnectionCategory.WWAN:
+                if (mc._type == NetworkManager.SETTING_BLUETOOTH_SETTING_NAME) {
+                    // XXX: NetworkManager does not give us signal for bluetooth
+                    this.setIcon('network-cellular-signal-excellent');
+                    break;
+                }
+
+                dev = mc._primaryDevice;
+                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:
+                log('Invalid active connection type ' + mc._section);
+                this.setIcon('network-error');
+                break;
+            }
+            break;
+        default:
+            log('NetworkManager is in an invalid state: ' + this._client.state);
+        }
+
+        // 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);
+    }
+};
