~tkluck/ubuntu/precise/gnome-shell/bluetooth-fix-connection-from-gs-menu

« back to all changes in this revision

Viewing changes to .pc/07-NetworkMenu-fix-logic-for-updating-wifi-icon.patch/js/ui/status/network.js

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2011-12-20 02:25:09 UTC
  • mfrom: (18.1.6 sid)
  • Revision ID: package-import@ubuntu.com-20111220022509-cr6e714zs2ukw9t2
Tags: 3.2.1-8ubuntu1
* Sync with Debian (LP: #877135). Remaining changes:
* debian/control.in: Recommend gnome-user-guide
* debian/gnome-shell.gsettings-override: Update for Ubuntu defaults
* Watch for unstable releases

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
 
2
const ByteArray = imports.byteArray;
 
3
const DBus = imports.dbus;
 
4
const GLib = imports.gi.GLib;
 
5
const GObject = imports.gi.GObject;
 
6
const Lang = imports.lang;
 
7
const Mainloop = imports.mainloop;
 
8
const NetworkManager = imports.gi.NetworkManager;
 
9
const NMClient = imports.gi.NMClient;
 
10
const Shell = imports.gi.Shell;
 
11
const Signals = imports.signals;
 
12
const St = imports.gi.St;
 
13
 
 
14
const Main = imports.ui.main;
 
15
const PanelMenu = imports.ui.panelMenu;
 
16
const PopupMenu = imports.ui.popupMenu;
 
17
const MessageTray = imports.ui.messageTray;
 
18
const ModemManager = imports.misc.modemManager;
 
19
const Util = imports.misc.util;
 
20
 
 
21
const NMConnectionCategory = {
 
22
    INVALID: 'invalid',
 
23
    WIRED: 'wired',
 
24
    WIRELESS: 'wireless',
 
25
    WWAN: 'wwan',
 
26
    VPN: 'vpn'
 
27
};
 
28
 
 
29
const NMAccessPointSecurity = {
 
30
    UNKNOWN: 0,
 
31
    NONE: 1,
 
32
    WEP: 2,
 
33
    WPA_PSK: 3,
 
34
    WPA2_PSK: 4,
 
35
    WPA_ENT: 5,
 
36
    WPA2_ENT: 6
 
37
};
 
38
 
 
39
// small optimization, to avoid using [] all the time
 
40
const NM80211Mode = NetworkManager['80211Mode'];
 
41
const NM80211ApFlags = NetworkManager['80211ApFlags'];
 
42
const NM80211ApSecurityFlags = NetworkManager['80211ApSecurityFlags'];
 
43
 
 
44
// number of wireless networks that should be visible
 
45
// (the remaining are placed into More...)
 
46
const NUM_VISIBLE_NETWORKS = 5;
 
47
 
 
48
function macToArray(string) {
 
49
    return string.split(':').map(function(el) {
 
50
        return parseInt(el, 16);
 
51
    });
 
52
}
 
53
 
 
54
function macCompare(one, two) {
 
55
    for (let i = 0; i < 6; i++) {
 
56
        if (one[i] != two[i])
 
57
            return false;
 
58
    }
 
59
    return true;
 
60
}
 
61
 
 
62
function ssidCompare(one, two) {
 
63
    if (!one || !two)
 
64
        return false;
 
65
    if (one.length != two.length)
 
66
        return false;
 
67
    for (let i = 0; i < one.length; i++) {
 
68
        if (one[i] != two[i])
 
69
            return false;
 
70
    }
 
71
    return true;
 
72
}
 
73
 
 
74
// shared between NMNetworkMenuItem and NMDeviceWWAN
 
75
function signalToIcon(value) {
 
76
    if (value > 80)
 
77
        return 'excellent';
 
78
    if (value > 55)
 
79
        return 'good';
 
80
    if (value > 30)
 
81
        return 'ok';
 
82
    if (value > 5)
 
83
        return 'weak';
 
84
    return 'none';
 
85
}
 
86
 
 
87
// shared between NMNetworkMenuItem and NMDeviceWireless
 
88
function sortAccessPoints(accessPoints) {
 
89
    return accessPoints.sort(function (one, two) {
 
90
        return two.strength - one.strength;
 
91
    });
 
92
}
 
93
 
 
94
function ssidToLabel(ssid) {
 
95
    let label = NetworkManager.utils_ssid_to_utf8(ssid);
 
96
    if (!label)
 
97
        label = _("<unknown>");
 
98
    return label;
 
99
}
 
100
 
 
101
function NMNetworkMenuItem() {
 
102
    this._init.apply(this, arguments);
 
103
}
 
104
 
 
105
NMNetworkMenuItem.prototype = {
 
106
    __proto__: PopupMenu.PopupBaseMenuItem.prototype,
 
107
 
 
108
    _init: function(accessPoints, title, params) {
 
109
        PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params);
 
110
 
 
111
        accessPoints = sortAccessPoints(accessPoints);
 
112
        this.bestAP = accessPoints[0];
 
113
 
 
114
        if (!title) {
 
115
            let ssid = this.bestAP.get_ssid();
 
116
            title = ssidToLabel(ssid);
 
117
        }
 
118
 
 
119
        this._label = new St.Label({ text: title });
 
120
        this.addActor(this._label);
 
121
        this._icons = new St.BoxLayout({ style_class: 'nm-menu-item-icons' });
 
122
        this.addActor(this._icons, { align: St.Align.END });
 
123
 
 
124
        this._signalIcon = new St.Icon({ icon_name: this._getIcon(),
 
125
                                         style_class: 'popup-menu-icon' });
 
126
        this._icons.add_actor(this._signalIcon);
 
127
 
 
128
        this._secureIcon = new St.Icon({ style_class: 'popup-menu-icon' });
 
129
        if (this.bestAP._secType != NMAccessPointSecurity.UNKNOWN &&
 
130
            this.bestAP._secType != NMAccessPointSecurity.NONE)
 
131
            this._secureIcon.icon_name = 'network-wireless-encrypted';
 
132
        this._icons.add_actor(this._secureIcon);
 
133
 
 
134
        this._accessPoints = [ ];
 
135
        for (let i = 0; i < accessPoints.length; i++) {
 
136
            let ap = accessPoints[i];
 
137
            // need a wrapper object here, because the access points can be shared
 
138
            // between many NMNetworkMenuItems
 
139
            let apObj = {
 
140
                ap: ap,
 
141
                updateId: ap.connect('notify::strength', Lang.bind(this, this._updated))
 
142
            };
 
143
            this._accessPoints.push(apObj);
 
144
        }
 
145
    },
 
146
 
 
147
    _updated: function(ap) {
 
148
        if (ap.strength > this.bestAP.strength)
 
149
            this.bestAP = ap;
 
150
 
 
151
        this._signalIcon.icon_name = this._getIcon();
 
152
    },
 
153
 
 
154
    _getIcon: function() {
 
155
        if (this.bestAP.mode == NM80211Mode.ADHOC)
 
156
            return 'network-workgroup';
 
157
        else
 
158
            return 'network-wireless-signal-' + signalToIcon(this.bestAP.strength);
 
159
    },
 
160
 
 
161
    updateAccessPoints: function(accessPoints) {
 
162
        for (let i = 0; i < this._accessPoints.length; i++) {
 
163
            let apObj = this._accessPoints[i];
 
164
            apObj.ap.disconnect(apObj.updateId);
 
165
            apObj.updateId = 0;
 
166
        }
 
167
 
 
168
        accessPoints = sortAccessPoints(accessPoints);
 
169
        this.bestAP = accessPoints[0];
 
170
        this._accessPoints = [ ];
 
171
        for (let i = 0; i < accessPoints; i++) {
 
172
            let ap = accessPoints[i];
 
173
            let apObj = {
 
174
                ap: ap,
 
175
                updateId: ap.connect('notify::strength', Lang.bind(this, this._updated))
 
176
            };
 
177
            this._accessPoints.push(apObj);
 
178
        }
 
179
    },
 
180
 
 
181
    destroy: function() {
 
182
        for (let i = 0; i < this._accessPoints.length; i++) {
 
183
            let apObj = this._accessPoints[i];
 
184
            apObj.ap.disconnect(apObj.updateId);
 
185
            apObj.updateId = 0;
 
186
        }
 
187
 
 
188
        PopupMenu.PopupBaseMenuItem.prototype.destroy.call(this);
 
189
    }
 
190
};
 
191
 
 
192
function NMWiredSectionTitleMenuItem() {
 
193
    this._init.apply(this, arguments);
 
194
}
 
195
 
 
196
NMWiredSectionTitleMenuItem.prototype = {
 
197
    __proto__: PopupMenu.PopupSwitchMenuItem.prototype,
 
198
 
 
199
    _init: function(label, params) {
 
200
        params = params || { };
 
201
        params.style_class = 'popup-subtitle-menu-item';
 
202
        PopupMenu.PopupSwitchMenuItem.prototype._init.call(this, label, false, params);
 
203
    },
 
204
 
 
205
    updateForDevice: function(device) {
 
206
        if (device) {
 
207
            this._device = device;
 
208
            this.setStatus(device.getStatusLabel());
 
209
            this.setToggleState(device.connected);
 
210
        } else
 
211
            this.setStatus('');
 
212
    },
 
213
 
 
214
    activate: function(event) {
 
215
        PopupMenu.PopupSwitchMenuItem.prototype.activate.call(this, event);
 
216
 
 
217
        if (!this._device) {
 
218
            log('Section title activated when there is more than one device, should be non reactive');
 
219
            return;
 
220
        }
 
221
 
 
222
        let newState = this._switch.state;
 
223
 
 
224
        // Immediately reset the switch to false, it will be updated appropriately
 
225
        // by state-changed signals in devices (but fixes the VPN not being in sync
 
226
        // if the ActiveConnection object is never seen by libnm-glib)
 
227
        this._switch.setToggleState(false);
 
228
 
 
229
        if (newState)
 
230
            this._device.activate();
 
231
        else
 
232
            this._device.deactivate();
 
233
    }
 
234
};
 
235
 
 
236
function NMWirelessSectionTitleMenuItem() {
 
237
    this._init.apply(this, arguments);
 
238
}
 
239
 
 
240
NMWirelessSectionTitleMenuItem.prototype = {
 
241
    __proto__: PopupMenu.PopupSwitchMenuItem.prototype,
 
242
 
 
243
    _init: function(client, property, title, params) {
 
244
        params = params || { };
 
245
        params.style_class = 'popup-subtitle-menu-item';
 
246
        PopupMenu.PopupSwitchMenuItem.prototype._init.call(this, title, false, params);
 
247
 
 
248
        this._client = client;
 
249
        this._property = property + '_enabled';
 
250
        this._propertyHardware = property + '_hardware_enabled';
 
251
        this._setEnabledFunc = property + '_set_enabled';
 
252
 
 
253
        this._client.connect('notify::' + property + '-enabled', Lang.bind(this, this._propertyChanged));
 
254
        this._client.connect('notify::' + property + '-hardware-enabled', Lang.bind(this, this._propertyChanged));
 
255
 
 
256
        this._propertyChanged();
 
257
    },
 
258
 
 
259
    updateForDevice: function(device) {
 
260
        // we show the switch
 
261
        // - if there not just one device
 
262
        // - if the switch is off
 
263
        // - if the device is activated or disconnected
 
264
        if (device && this._softwareEnabled && this._hardwareEnabled) {
 
265
            let text = device.getStatusLabel();
 
266
            this.setStatus(text);
 
267
        } else
 
268
            this.setStatus(null);
 
269
    },
 
270
 
 
271
    activate: function(event) {
 
272
        PopupMenu.PopupSwitchMenuItem.prototype.activate.call(this, event);
 
273
 
 
274
        this._client[this._setEnabledFunc](this._switch.state);
 
275
    },
 
276
 
 
277
    _propertyChanged: function() {
 
278
        this._softwareEnabled = this._client[this._property];
 
279
        this._hardwareEnabled = this._client[this._propertyHardware];
 
280
 
 
281
        let enabled = this._softwareEnabled && this._hardwareEnabled;
 
282
        this.setToggleState(enabled);
 
283
        if (!this._hardwareEnabled)
 
284
            /* Translators: this indicates that wireless or wwan is disabled by hardware killswitch */
 
285
            this.setStatus(_("disabled"));
 
286
 
 
287
        this.emit('enabled-changed', enabled);
 
288
    }
 
289
};
 
290
 
 
291
function NMDevice() {
 
292
    throw new TypeError('Instantanting abstract class NMDevice');
 
293
}
 
294
 
 
295
NMDevice.prototype = {
 
296
    _init: function(client, device, connections) {
 
297
        this.device = device;
 
298
        if (device) {
 
299
            this.device._delegate = this;
 
300
            this._stateChangedId = this.device.connect('state-changed', Lang.bind(this, this._deviceStateChanged));
 
301
        } else
 
302
            this._stateChangedId = 0;
 
303
 
 
304
        // protected
 
305
        this._client = client;
 
306
        this._connections = [ ];
 
307
        for (let i = 0; i < connections.length; i++) {
 
308
            if (!connections[i]._uuid)
 
309
                continue;
 
310
            if (!this.connectionValid(connections[i]))
 
311
                continue;
 
312
            // record the connection
 
313
            let obj = {
 
314
                connection: connections[i],
 
315
                name: connections[i]._name,
 
316
                uuid: connections[i]._uuid,
 
317
                timestamp: connections[i]._timestamp,
 
318
            };
 
319
            this._connections.push(obj);
 
320
        }
 
321
        this._connections.sort(this._connectionSortFunction);
 
322
        this._activeConnection = null;
 
323
        this._activeConnectionItem = null;
 
324
        this._autoConnectionItem = null;
 
325
        this._overflowItem = null;
 
326
 
 
327
        if (this.device) {
 
328
            this.statusItem = new PopupMenu.PopupSwitchMenuItem(this._getDescription(), this.connected, { style_class: 'popup-subtitle-menu-item' });
 
329
            this._statusChanged = this.statusItem.connect('toggled', Lang.bind(this, function(item, state) {
 
330
                if (state)
 
331
                    this.activate();
 
332
                else
 
333
                    this.deactivate();
 
334
                this.emit('enabled-changed');
 
335
            }));
 
336
 
 
337
            this._updateStatusItem();
 
338
        }
 
339
        this.section = new PopupMenu.PopupMenuSection();
 
340
 
 
341
        this._createSection();
 
342
    },
 
343
 
 
344
    destroy: function() {
 
345
        if (this.device)
 
346
            this.device._delegate = null;
 
347
 
 
348
        if (this._stateChangedId) {
 
349
            // Need to go through GObject.Object.prototype because
 
350
            // nm_device_disconnect conflicts with g_signal_disconnect
 
351
            GObject.Object.prototype.disconnect.call(this.device, this._stateChangedId);
 
352
            this._stateChangedId = 0;
 
353
        }
 
354
        if (this._carrierChangedId) {
 
355
            // see above for why this is needed
 
356
            GObject.Object.prototype.disconnect.call(this.device, this._carrierChangedId);
 
357
            this._carrierChangedId = 0;
 
358
        }
 
359
        if (this._firmwareChangedId) {
 
360
            GObject.Object.prototype.disconnect.call(this.device, this._firmwareChangedId);
 
361
            this._firmwareChangedId = 0;
 
362
        }
 
363
 
 
364
        this._clearSection();
 
365
        if (this.statusItem)
 
366
            this.statusItem.destroy();
 
367
        this.section.destroy();
 
368
    },
 
369
 
 
370
    deactivate: function() {
 
371
        this.device.disconnect(null);
 
372
    },
 
373
 
 
374
    activate: function() {
 
375
        if (this._activeConnection)
 
376
            // nothing to do
 
377
            return;
 
378
 
 
379
        // pick the most recently used connection and connect to that
 
380
        // or if no connections ever set, create an automatic one
 
381
        if (this._connections.length > 0) {
 
382
            this._client.activate_connection(this._connections[0].connection, this.device, null, null);
 
383
        } else if (this._autoConnectionName) {
 
384
            let connection = this._createAutomaticConnection();
 
385
            if (connection)
 
386
                this._client.add_and_activate_connection(connection, this.device, null, null);
 
387
        }
 
388
    },
 
389
 
 
390
    get connected() {
 
391
        return this.device.state == NetworkManager.DeviceState.ACTIVATED;
 
392
    },
 
393
 
 
394
    setActiveConnection: function(activeConnection) {
 
395
        if (activeConnection == this._activeConnection)
 
396
            // nothing to do
 
397
            return;
 
398
 
 
399
        // remove any UI
 
400
        if (this._activeConnectionItem) {
 
401
            this._activeConnectionItem.destroy();
 
402
            this._activeConnectionItem = null;
 
403
        }
 
404
 
 
405
        this._activeConnection = activeConnection;
 
406
 
 
407
        this._clearSection();
 
408
        this._createSection();
 
409
    },
 
410
 
 
411
    checkConnection: function(connection) {
 
412
        let exists = this._findConnection(connection._uuid) != -1;
 
413
        let valid = this.connectionValid(connection);
 
414
        if (exists && !valid)
 
415
            this.removeConnection(connection);
 
416
        else if (!exists && valid)
 
417
            this.addConnection(connection);
 
418
    },
 
419
 
 
420
    addConnection: function(connection) {
 
421
        // record the connection
 
422
        let obj = {
 
423
            connection: connection,
 
424
            name: connection._name,
 
425
            uuid: connection._uuid,
 
426
            timestamp: connection._timestamp,
 
427
        };
 
428
        this._connections.push(obj);
 
429
        this._connections.sort(this._connectionSortFunction);
 
430
 
 
431
        this._clearSection();
 
432
        this._createSection();
 
433
    },
 
434
 
 
435
    removeConnection: function(connection) {
 
436
        if (!connection._uuid) {
 
437
            log('Cannot remove a connection without an UUID');
 
438
            return;
 
439
        }
 
440
        let pos = this._findConnection(connection._uuid);
 
441
        if (pos == -1) {
 
442
            // this connection was never added, nothing to do here
 
443
            return;
 
444
        }
 
445
 
 
446
        let obj = this._connections[pos];
 
447
        if (obj.item)
 
448
            obj.item.destroy();
 
449
        this._connections.splice(pos, 1);
 
450
 
 
451
        if (this._connections.length <= 1) {
 
452
            // We need to show the automatic connection again
 
453
            // (or in the case of NMDeviceWired, we want to hide
 
454
            // the only explicit connection)
 
455
            this._clearSection();
 
456
            this._createSection();
 
457
        }
 
458
    },
 
459
 
 
460
    connectionValid: function(connection) {
 
461
        return this.device.connection_valid(connection);
 
462
    },
 
463
 
 
464
    _connectionSortFunction: function(one, two) {
 
465
        if (one.timestamp == two.timestamp)
 
466
            return GLib.utf8_collate(one.name, two.name);
 
467
 
 
468
        return two.timestamp - one.timestamp;
 
469
    },
 
470
 
 
471
    setEnabled: function(enabled) {
 
472
        // do nothing by default, we want to keep the conneciton list visible
 
473
        // in the majority of cases (wired, wwan, vpn)
 
474
    },
 
475
 
 
476
    getStatusLabel: function() {
 
477
        switch(this.device.state) {
 
478
        case NetworkManager.DeviceState.DISCONNECTED:
 
479
        case NetworkManager.DeviceState.ACTIVATED:
 
480
            return null;
 
481
        case NetworkManager.DeviceState.UNMANAGED:
 
482
            /* Translators: this is for network devices that are physically present but are not
 
483
               under NetworkManager's control (and thus cannot be used in the menu) */
 
484
            return _("unmanaged");
 
485
        case NetworkManager.DeviceState.DEACTIVATING:
 
486
            return _("disconnecting...");
 
487
        case NetworkManager.DeviceState.PREPARE:
 
488
        case NetworkManager.DeviceState.CONFIG:
 
489
        case NetworkManager.DeviceState.IP_CONFIG:
 
490
        case NetworkManager.DeviceState.IP_CHECK:
 
491
        case NetworkManager.DeviceState.SECONDARIES:
 
492
            return _("connecting...");
 
493
        case NetworkManager.DeviceState.NEED_AUTH:
 
494
            /* Translators: this is for network connections that require some kind of key or password */
 
495
            return _("authentication required");
 
496
        case NetworkManager.DeviceState.UNAVAILABLE:
 
497
            // This state is actually a compound of various states (generically unavailable,
 
498
            // firmware missing, carrier not available), that are exposed by different properties
 
499
            // (whose state may or may not updated when we receive state-changed).
 
500
            if (!this._firmwareMissingId)
 
501
                this._firmwareMissingId = this.device.connect('notify::firmware-missing', Lang.bind(this, this._substateChanged));
 
502
            if (this.device.firmware_missing) {
 
503
                /* Translators: this is for devices that require some kind of firmware or kernel
 
504
                   module, which is missing */
 
505
                return _("firmware missing");
 
506
            }
 
507
            if (this.device.capabilities & NetworkManager.DeviceCapabilities.CARRIER_DETECT) {
 
508
                if (!this._carrierChangedId)
 
509
                    this._carrierChangedId = this.device.connect('notify::carrier', Lang.bind(this, this._substateChanged));
 
510
                if (!this.carrier) {
 
511
                    /* Translators: this is for wired network devices that are physically disconnected */
 
512
                    return _("cable unplugged");
 
513
                }
 
514
            }
 
515
            /* Translators: this is for a network device that cannot be activated (for example it
 
516
               is disabled by rfkill, or it has no coverage */
 
517
            return _("unavailable");
 
518
        case NetworkManager.DeviceState.FAILED:
 
519
            return _("connection failed");
 
520
        default:
 
521
            log('Device state invalid, is %d'.format(this.device.state));
 
522
            return 'invalid';
 
523
        }
 
524
    },
 
525
 
 
526
    // protected
 
527
    _createAutomaticConnection: function() {
 
528
        throw new TypeError('Invoking pure virtual function NMDevice.createAutomaticConnection');
 
529
    },
 
530
 
 
531
    _findConnection: function(uuid) {
 
532
        for (let i = 0; i < this._connections.length; i++) {
 
533
            let obj = this._connections[i];
 
534
            if (obj.uuid == uuid)
 
535
                return i;
 
536
        }
 
537
        return -1;
 
538
    },
 
539
 
 
540
    _clearSection: function() {
 
541
        // Clear everything
 
542
        this.section.removeAll();
 
543
        this._autoConnectionItem = null;
 
544
        this._activeConnectionItem = null;
 
545
        this._overflowItem = null;
 
546
        for (let i = 0; i < this._connections.length; i++) {
 
547
            this._connections[i].item = null;
 
548
        }
 
549
    },
 
550
 
 
551
    _shouldShowConnectionList: function() {
 
552
        return (this.device.state >= NetworkManager.DeviceState.DISCONNECTED);
 
553
    },
 
554
 
 
555
    _createSection: function() {
 
556
        if (!this._shouldShowConnectionList())
 
557
            return;
 
558
 
 
559
        if (this._activeConnection) {
 
560
            this._createActiveConnectionItem();
 
561
            this.section.addMenuItem(this._activeConnectionItem);
 
562
        }
 
563
        if (this._connections.length > 0) {
 
564
            let activeOffset = this._activeConnectionItem ? 1 : 0;
 
565
 
 
566
            for(let j = 0; j < this._connections.length; ++j) {
 
567
                let obj = this._connections[j];
 
568
                if (this._activeConnection &&
 
569
                    obj.connection == this._activeConnection._connection)
 
570
                    continue;
 
571
                obj.item = this._createConnectionItem(obj);
 
572
 
 
573
                if (j + activeOffset >= NUM_VISIBLE_NETWORKS) {
 
574
                    if (!this._overflowItem) {
 
575
                        this._overflowItem = new PopupMenu.PopupSubMenuMenuItem(_("More..."));
 
576
                        this.section.addMenuItem(this._overflowItem);
 
577
                    }
 
578
                    this._overflowItem.menu.addMenuItem(obj.item);
 
579
                } else
 
580
                    this.section.addMenuItem(obj.item);
 
581
            }
 
582
        } else if (this._autoConnectionName) {
 
583
            this._autoConnectionItem = new PopupMenu.PopupMenuItem(this._autoConnectionName);
 
584
            this._autoConnectionItem.connect('activate', Lang.bind(this, function() {
 
585
                let connection = this._createAutomaticConnection();
 
586
                if (connection)
 
587
                    this._client.add_and_activate_connection(connection, this.device, null, null);
 
588
            }));
 
589
            this.section.addMenuItem(this._autoConnectionItem);
 
590
        }
 
591
    },
 
592
 
 
593
    _createConnectionItem: function(obj) {
 
594
        let connection = obj.connection;
 
595
        let item = new PopupMenu.PopupMenuItem(obj.name);
 
596
 
 
597
        item.connect('activate', Lang.bind(this, function() {
 
598
            this._client.activate_connection(connection, this.device, null, null);
 
599
        }));
 
600
        return item;
 
601
    },
 
602
 
 
603
    _createActiveConnectionItem: function() {
 
604
        let title;
 
605
        let active = this._activeConnection._connection;
 
606
        if (active) {
 
607
            title = active._name;
 
608
        } else {
 
609
            /* TRANSLATORS: this is the indication that a connection for another logged in user is active,
 
610
               and we cannot access its settings (including the name) */
 
611
            title = _("Connected (private)");
 
612
        }
 
613
        this._activeConnectionItem = new PopupMenu.PopupMenuItem(title, { reactive: false });
 
614
        this._activeConnectionItem.setShowDot(true);
 
615
    },
 
616
 
 
617
    _deviceStateChanged: function(device, newstate, oldstate, reason) {
 
618
        if (newstate == oldstate) {
 
619
            log('device emitted state-changed without actually changing state');
 
620
            return;
 
621
        }
 
622
 
 
623
        if (oldstate == NetworkManager.DeviceState.ACTIVATED) {
 
624
            this.emit('network-lost');
 
625
        }
 
626
 
 
627
        if (newstate == NetworkManager.DeviceState.FAILED) {
 
628
            this.emit('activation-failed', reason);
 
629
        }
 
630
 
 
631
        this._updateStatusItem();
 
632
 
 
633
        this._clearSection();
 
634
        this._createSection();
 
635
        this.emit('state-changed');
 
636
    },
 
637
 
 
638
    _updateStatusItem: function() {
 
639
        if (this._carrierChangedId) {
 
640
            // see above for why this is needed
 
641
            GObject.Object.prototype.disconnect.call(this.device, this._carrierChangedId);
 
642
            this._carrierChangedId = 0;
 
643
        }
 
644
        if (this._firmwareChangedId) {
 
645
            GObject.Object.prototype.disconnect.call(this.device, this._firmwareChangedId);
 
646
            this._firmwareChangedId = 0;
 
647
        }
 
648
 
 
649
        this.statusItem.setStatus(this.getStatusLabel());
 
650
        this.statusItem.setToggleState(this.connected);
 
651
    },
 
652
 
 
653
    _substateChanged: function() {
 
654
        this.statusItem.setStatus(this.getStatusLabel());
 
655
 
 
656
        this.emit('state-changed');
 
657
    },
 
658
 
 
659
    _getDescription: function() {
 
660
        let dev_product = this.device.get_product();
 
661
        let dev_vendor = this.device.get_vendor();
 
662
        if (!dev_product || !dev_vendor)
 
663
            return '';
 
664
 
 
665
        let product = Util.fixupPCIDescription(dev_product);
 
666
        let vendor = Util.fixupPCIDescription(dev_vendor);
 
667
        let out = '';
 
668
 
 
669
        // Another quick hack; if all of the fixed up vendor string
 
670
        // is found in product, ignore the vendor.
 
671
        if (product.indexOf(vendor) == -1)
 
672
            out += vendor + ' ';
 
673
        out += product;
 
674
 
 
675
        return out;
 
676
    }
 
677
};
 
678
Signals.addSignalMethods(NMDevice.prototype);
 
679
 
 
680
 
 
681
function NMDeviceWired() {
 
682
    this._init.apply(this, arguments);
 
683
}
 
684
 
 
685
NMDeviceWired.prototype = {
 
686
    __proto__: NMDevice.prototype,
 
687
 
 
688
    _init: function(client, device, connections) {
 
689
        this._autoConnectionName = _("Auto Ethernet");
 
690
        this.category = NMConnectionCategory.WIRED;
 
691
 
 
692
        NMDevice.prototype._init.call(this, client, device, connections);
 
693
    },
 
694
 
 
695
    _createSection: function() {
 
696
        NMDevice.prototype._createSection.call(this);
 
697
 
 
698
        // if we have only one connection (normal or automatic)
 
699
        // we hide the connection list, and use the switch to control
 
700
        // the device
 
701
        // we can do it here because addConnection and removeConnection
 
702
        // both call _createSection at some point
 
703
        if (this._connections.length <= 1)
 
704
            this.section.actor.hide();
 
705
        else
 
706
            this.section.actor.show();
 
707
    },
 
708
 
 
709
    _createAutomaticConnection: function() {
 
710
        let connection = new NetworkManager.Connection();
 
711
        connection._uuid = NetworkManager.utils_uuid_generate();
 
712
        connection.add_setting(new NetworkManager.SettingWired());
 
713
        connection.add_setting(new NetworkManager.SettingConnection({
 
714
            uuid: connection._uuid,
 
715
            id: this._autoConnectionName,
 
716
            type: NetworkManager.SETTING_WIRED_SETTING_NAME,
 
717
            autoconnect: true
 
718
        }));
 
719
        return connection;
 
720
    }
 
721
};
 
722
 
 
723
function NMDeviceModem() {
 
724
    this._init.apply(this, arguments);
 
725
}
 
726
 
 
727
NMDeviceModem.prototype = {
 
728
    __proto__: NMDevice.prototype,
 
729
 
 
730
    _init: function(client, device, connections) {
 
731
        let is_wwan = false;
 
732
 
 
733
        this._enabled = true;
 
734
        this.mobileDevice = null;
 
735
        this._connectionType = 'ppp';
 
736
 
 
737
        this._capabilities = device.current_capabilities;
 
738
        if (this._capabilities & NetworkManager.DeviceModemCapabilities.GSM_UMTS) {
 
739
            is_wwan = true;
 
740
            this.mobileDevice = new ModemManager.ModemGsm(device.udi);
 
741
            this._connectionType = NetworkManager.SETTING_GSM_SETTING_NAME;
 
742
        } else if (this._capabilities & NetworkManager.DeviceModemCapabilities.CDMA_EVDO) {
 
743
            is_wwan = true;
 
744
            this.mobileDevice = new ModemManager.ModemCdma(device.udi);
 
745
            this._connectionType = NetworkManager.SETTING_CDMA_SETTING_NAME;
 
746
        } else if (this._capabilities & NetworkManager.DeviceModemCapabilities.LTE) {
 
747
            is_wwan = true;
 
748
            // FIXME: support signal quality
 
749
        }
 
750
 
 
751
        if (is_wwan) {
 
752
            this.category = NMConnectionCategory.WWAN;
 
753
            this._autoConnectionName = _("Auto broadband");
 
754
        } else {
 
755
            this.category = NMConnectionCategory.WIRED;
 
756
            this._autoConnectionName = _("Auto dial-up");
 
757
        }
 
758
 
 
759
        if (this.mobileDevice) {
 
760
            this._operatorNameId = this.mobileDevice.connect('notify::operator-name', Lang.bind(this, function() {
 
761
                if (this._operatorItem) {
 
762
                    let name = this.mobileDevice.operator_name;
 
763
                    if (name) {
 
764
                        this._operatorItem.label.text = name;
 
765
                        this._operatorItem.actor.show();
 
766
                    } else
 
767
                        this._operatorItem.actor.hide();
 
768
                }
 
769
            }));
 
770
            this._signalQualityId = this.mobileDevice.connect('notify::signal-quality', Lang.bind(this, function() {
 
771
                if (this._operatorItem) {
 
772
                    this._operatorItem.setIcon(this._getSignalIcon());
 
773
                }
 
774
            }));
 
775
        }
 
776
 
 
777
        NMDevice.prototype._init.call(this, client, device, connections);
 
778
    },
 
779
 
 
780
    setEnabled: function(enabled) {
 
781
        this._enabled = enabled;
 
782
        if (this.category == NMConnectionCategory.WWAN) {
 
783
            if (enabled) {
 
784
                // prevent "network unavailable" statuses
 
785
                this.statusItem.setStatus(null);
 
786
            } else
 
787
                this.statusItem.setStatus(this.getStatusLabel());
 
788
        }
 
789
 
 
790
        NMDevice.prototype.setEnabled.call(this, enabled);
 
791
    },
 
792
 
 
793
    get connected() {
 
794
        return this._enabled && this.device.state == NetworkManager.DeviceState.ACTIVATED;
 
795
    },
 
796
 
 
797
    destroy: function() {
 
798
        if (this._operatorNameId) {
 
799
            this.mobileDevice.disconnect(this._operatorNameId);
 
800
            this._operatorNameId = 0;
 
801
        }
 
802
        if (this._signalQualityId) {
 
803
            this.mobileDevice.disconnect(this._signalQualityId);
 
804
            this._signalQualityId = 0;
 
805
        }
 
806
 
 
807
        NMDevice.prototype.destroy.call(this);
 
808
    },
 
809
 
 
810
    _getSignalIcon: function() {
 
811
        return 'network-cellular-signal-' + signalToIcon(this.mobileDevice.signal_quality);
 
812
    },
 
813
 
 
814
    _createSection: function() {
 
815
        if (!this._shouldShowConnectionList())
 
816
            return;
 
817
 
 
818
        if (this.mobileDevice) {
 
819
            // If operator_name is null, just pass the empty string, as the item is hidden anyway
 
820
            this._operatorItem = new PopupMenu.PopupImageMenuItem(this.mobileDevice.operator_name || '',
 
821
                                                                  this._getSignalIcon(),
 
822
                                                                  { reactive: false });
 
823
            if (!this.mobileDevice.operator_name)
 
824
                this._operatorItem.actor.hide();
 
825
            this.section.addMenuItem(this._operatorItem);
 
826
        }
 
827
 
 
828
        NMDevice.prototype._createSection.call(this);
 
829
    },
 
830
 
 
831
    _clearSection: function() {
 
832
        this._operatorItem = null;
 
833
 
 
834
        NMDevice.prototype._clearSection.call(this);
 
835
    },
 
836
 
 
837
    _createAutomaticConnection: function() {
 
838
        // Mobile wizard is too complex for the shell UI and
 
839
        // is handled by the network panel
 
840
        Util.spawn(['gnome-control-center', 'network',
 
841
                    'connect-3g', this.device.get_path()]);
 
842
        return null;
 
843
    }
 
844
};
 
845
 
 
846
function NMDeviceBluetooth() {
 
847
    this._init.apply(this, arguments);
 
848
}
 
849
 
 
850
NMDeviceBluetooth.prototype = {
 
851
    __proto__: NMDevice.prototype,
 
852
 
 
853
    _init: function(client, device, connections) {
 
854
        this._autoConnectionName = this._makeConnectionName(device);
 
855
        device.connect('notify::name', Lang.bind(this, this._updateAutoConnectionName));
 
856
 
 
857
        this.category = NMConnectionCategory.WWAN;
 
858
 
 
859
        NMDevice.prototype._init.call(this, client, device, connections);
 
860
    },
 
861
 
 
862
    _createAutomaticConnection: function() {
 
863
        let connection = new NetworkManager.Connection;
 
864
        connection._uuid = NetworkManager.utils_uuid_generate();
 
865
        connection.add_setting(new NetworkManager.SettingBluetooth);
 
866
        connection.add_setting(new NetworkManager.SettingConnection({
 
867
            uuid: connection._uuid,
 
868
            id: this._autoConnectionName,
 
869
            type: NetworkManager.SETTING_BLUETOOTH_SETTING_NAME,
 
870
            autoconnect: false
 
871
        }));
 
872
        return connection;
 
873
    },
 
874
 
 
875
    _makeConnectionName: function(device) {
 
876
        let name = device.name;
 
877
        if (name)
 
878
            return _("Auto %s").format(name);
 
879
        else
 
880
            return _("Auto bluetooth");
 
881
    },
 
882
 
 
883
    _updateAutoConnectionName: function() {
 
884
        this._autoConnectionName = this._makeConnectionName(this.device);
 
885
 
 
886
        this._clearSection();
 
887
        this._createSection();
 
888
    }
 
889
};
 
890
 
 
891
 
 
892
// Not a real device, but I save a lot code this way
 
893
function NMDeviceVPN() {
 
894
    this._init.apply(this, arguments);
 
895
}
 
896
 
 
897
NMDeviceVPN.prototype = {
 
898
    __proto__: NMDevice.prototype,
 
899
 
 
900
    _init: function(client) {
 
901
        // Disable autoconnections
 
902
        this._autoConnectionName = null;
 
903
        this.category = NMConnectionCategory.VPN;
 
904
 
 
905
        NMDevice.prototype._init.call(this, client, null, [ ]);
 
906
    },
 
907
 
 
908
    connectionValid: function(connection) {
 
909
        return connection._type == NetworkManager.SETTING_VPN_SETTING_NAME;
 
910
    },
 
911
 
 
912
    get empty() {
 
913
        return this._connections.length == 0;
 
914
    },
 
915
 
 
916
    get connected() {
 
917
        return !!this._activeConnection;
 
918
    },
 
919
 
 
920
    setActiveConnection: function(activeConnection) {
 
921
        NMDevice.prototype.setActiveConnection.call(this, activeConnection);
 
922
 
 
923
        this.emit('active-connection-changed');
 
924
    },
 
925
 
 
926
    _shouldShowConnectionList: function() {
 
927
        return true;
 
928
    },
 
929
 
 
930
    deactivate: function() {
 
931
        if (this._activeConnection)
 
932
            this._client.deactivate_connection(this._activeConnection);
 
933
    },
 
934
 
 
935
    getStatusLabel: function() {
 
936
        return null;
 
937
    }
 
938
};
 
939
 
 
940
function NMDeviceWireless() {
 
941
    this._init.apply(this, arguments);
 
942
}
 
943
 
 
944
NMDeviceWireless.prototype = {
 
945
    __proto__: NMDevice.prototype,
 
946
 
 
947
    _init: function(client, device, connections) {
 
948
        this.category = NMConnectionCategory.WIRELESS;
 
949
 
 
950
        this._overflowItem = null;
 
951
        this._networks = [ ];
 
952
 
 
953
        // breaking the layers with this, but cannot call
 
954
        // this.connectionValid until I have a device
 
955
        this.device = device;
 
956
 
 
957
        let validConnections = connections.filter(Lang.bind(this, function(connection) {
 
958
            return this.connectionValid(connection);
 
959
        }));
 
960
        let accessPoints = device.get_access_points() || [ ];
 
961
        for (let i = 0; i < accessPoints.length; i++) {
 
962
            // Access points are grouped by network
 
963
            let ap = accessPoints[i];
 
964
 
 
965
            if (ap.get_ssid() == null) {
 
966
                // hidden access point cannot be added, we need to know
 
967
                // the SSID and security details to connect
 
968
                // nevertheless, the access point can acquire a SSID when
 
969
                // NetworkManager connects to it (via nmcli or the control-center)
 
970
                ap._notifySsidId = ap.connect('notify::ssid', Lang.bind(this, this._notifySsidCb));
 
971
                continue;
 
972
            }
 
973
 
 
974
            let pos = this._findNetwork(ap);
 
975
            let obj;
 
976
            if (pos != -1) {
 
977
                obj = this._networks[pos];
 
978
                obj.accessPoints.push(ap);
 
979
            } else {
 
980
                obj = { ssid: ap.get_ssid(),
 
981
                        mode: ap.mode,
 
982
                        security: this._getApSecurityType(ap),
 
983
                        connections: [ ],
 
984
                        item: null,
 
985
                        accessPoints: [ ap ]
 
986
                      };
 
987
                obj.ssidText = ssidToLabel(obj.ssid);
 
988
                this._networks.push(obj);
 
989
            }
 
990
 
 
991
            // Check if some connection is valid for this AP
 
992
            for (let j = 0; j < validConnections.length; j++) {
 
993
                let connection = validConnections[j];
 
994
                if (ap.connection_valid(connection) &&
 
995
                    obj.connections.indexOf(connection) == -1) {
 
996
                    obj.connections.push(connection);
 
997
                }
 
998
            }
 
999
        }
 
1000
 
 
1001
        if (this.device.active_access_point) {
 
1002
            let networkPos = this._findNetwork(this.device.active_access_point);
 
1003
 
 
1004
            if (networkPos == -1) // the connected access point is invisible
 
1005
                this._activeNetwork = null;
 
1006
            else
 
1007
                this._activeNetwork = this._networks[networkPos];
 
1008
        } else {
 
1009
            this._activeNetwork = null;
 
1010
        }
 
1011
        this._networks.sort(this._networkSortFunction);
 
1012
 
 
1013
        this._apChangedId = device.connect('notify::active-access-point', Lang.bind(this, this._activeApChanged));
 
1014
        this._apAddedId = device.connect('access-point-added', Lang.bind(this, this._accessPointAdded));
 
1015
        this._apRemovedId = device.connect('access-point-removed', Lang.bind(this, this._accessPointRemoved));
 
1016
 
 
1017
        NMDevice.prototype._init.call(this, client, device, validConnections);
 
1018
    },
 
1019
 
 
1020
    destroy: function() {
 
1021
        if (this._apChangedId) {
 
1022
            // see above for this HACK
 
1023
            GObject.Object.prototype.disconnect.call(this.device, this._apChangedId);
 
1024
            this._apChangedId = 0;
 
1025
        }
 
1026
 
 
1027
        if (this._apAddedId) {
 
1028
            GObject.Object.prototype.disconnect.call(this.device, this._apAddedId);
 
1029
            this._apAddedId = 0;
 
1030
        }
 
1031
 
 
1032
        if (this._apRemovedId) {
 
1033
            GObject.Object.prototype.disconnect.call(this.device, this._apRemovedId);
 
1034
            this._apRemovedId = 0;
 
1035
        }
 
1036
 
 
1037
        NMDevice.prototype.destroy.call(this);
 
1038
    },
 
1039
 
 
1040
    setEnabled: function(enabled) {
 
1041
        if (enabled) {
 
1042
            this.statusItem.actor.show();
 
1043
            this.section.actor.show();
 
1044
        } else {
 
1045
            this.statusItem.actor.hide();
 
1046
            this.section.actor.hide();
 
1047
        }
 
1048
    },
 
1049
 
 
1050
    activate: function() {
 
1051
        if (this._activeConnection)
 
1052
            // nothing to do
 
1053
            return;
 
1054
 
 
1055
        // among all visible networks, pick the last recently used connection
 
1056
        let best = null;
 
1057
        let bestApObj = null;
 
1058
        let bestTime = 0;
 
1059
        for (let i = 0; i < this._networks.length; i++) {
 
1060
            let apObj = this._networks[i];
 
1061
            for (let j = 0; j < apObj.connections.length; j++) {
 
1062
                let connection = apObj.connections[j];
 
1063
                if (connection._timestamp > bestTime) {
 
1064
                    best = connection;
 
1065
                    bestTime = connection._timestamp;
 
1066
                    bestApObj = apObj;
 
1067
                }
 
1068
            }
 
1069
        }
 
1070
 
 
1071
        if (best) {
 
1072
            for (let i = 0; i < bestApObj.accessPoints.length; i++) {
 
1073
                let ap = bestApObj.accessPoints[i];
 
1074
                if (ap.connection_valid(best)) {
 
1075
                    this._client.activate_connection(best, this.device, ap.dbus_path, null);
 
1076
                    break;
 
1077
                }
 
1078
            }
 
1079
            return;
 
1080
        }
 
1081
 
 
1082
        // XXX: what else to do?
 
1083
        // for now, just pick a random network
 
1084
        // (this function is called in a corner case anyway, that is, only when
 
1085
        // the user toggles the switch and has more than one wireless device)
 
1086
        if (this._networks.length > 0) {
 
1087
            let connection = this._createAutomaticConnection(this._networks[0]);
 
1088
            let accessPoints = sortAccessPoints(this._networks[0].accessPoints);
 
1089
            this._client.add_and_activate_connection(connection, this.device, accessPoints[0].dbus_path, null);
 
1090
        }
 
1091
    },
 
1092
 
 
1093
    _notifySsidCb: function(accessPoint) {
 
1094
        if (accessPoint.get_ssid() != null) {
 
1095
            accessPoint.disconnect(accessPoint._notifySsidId);
 
1096
            accessPoint._notifySsidId = 0;
 
1097
            this._accessPointAdded(this.device, accessPoint);
 
1098
        }
 
1099
    },
 
1100
 
 
1101
    _activeApChanged: function() {
 
1102
        this._activeNetwork = null;
 
1103
 
 
1104
        let activeAp = this.device.active_access_point;
 
1105
 
 
1106
        if (activeAp) {
 
1107
            let res = this._findExistingNetwork(activeAp);
 
1108
 
 
1109
            if (res != null)
 
1110
                this._activeNetwork = this._networks[res.network];
 
1111
        }
 
1112
 
 
1113
        // we don't refresh the view here, setActiveConnection will
 
1114
    },
 
1115
 
 
1116
    _getApSecurityType: function(accessPoint) {
 
1117
        if (accessPoint._secType)
 
1118
            return accessPoint._secType;
 
1119
 
 
1120
        let flags = accessPoint.flags;
 
1121
        let wpa_flags = accessPoint.wpa_flags;
 
1122
        let rsn_flags = accessPoint.rsn_flags;
 
1123
        let type;
 
1124
        if (rsn_flags != NM80211ApSecurityFlags.NONE) {
 
1125
            /* RSN check first so that WPA+WPA2 APs are treated as RSN/WPA2 */
 
1126
            if (rsn_flags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
 
1127
                type = NMAccessPointSecurity.WPA2_ENT;
 
1128
            else if (rsn_flags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
 
1129
                type = NMAccessPointSecurity.WPA2_PSK;
 
1130
        } else if (wpa_flags != NM80211ApSecurityFlags.NONE) {
 
1131
            if (wpa_flags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
 
1132
                type = NMAccessPointSecurity.WPA_ENT;
 
1133
            else if (wpa_flags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
 
1134
                type = NMAccessPointSecurity.WPA_PSK;
 
1135
        } else {
 
1136
            if (flags & NM80211ApFlags.PRIVACY)
 
1137
                type = NMAccessPointSecurity.WEP;
 
1138
            else
 
1139
                type = NMAccessPointSecurity.NONE;
 
1140
        }
 
1141
 
 
1142
        // cache the found value to avoid checking flags all the time
 
1143
        accessPoint._secType = type;
 
1144
        return type;
 
1145
    },
 
1146
 
 
1147
    _networkSortFunction: function(one, two) {
 
1148
        let oneHasConnection = one.connections.length != 0;
 
1149
        let twoHasConnection = two.connections.length != 0;
 
1150
 
 
1151
        // place known connections first
 
1152
        // (-1 = good order, 1 = wrong order)
 
1153
        if (oneHasConnection && !twoHasConnection)
 
1154
            return -1;
 
1155
        else if (!oneHasConnection && twoHasConnection)
 
1156
            return 1;
 
1157
 
 
1158
        let oneHasSecurity = one.security != NMAccessPointSecurity.NONE;
 
1159
        let twoHasSecurity = two.security != NMAccessPointSecurity.NONE;
 
1160
 
 
1161
        // place secure connections first
 
1162
        // (we treat WEP/WPA/WPA2 the same as there is no way to
 
1163
        // take them apart from the UI)
 
1164
        if (oneHasSecurity && !twoHasSecurity)
 
1165
            return -1;
 
1166
        else if (!oneHasSecurity && twoHasSecurity)
 
1167
            return 1;
 
1168
 
 
1169
        // sort alphabetically
 
1170
        return GLib.utf8_collate(one.ssidText, two.ssidText);
 
1171
    },
 
1172
 
 
1173
    _networkCompare: function(network, accessPoint) {
 
1174
        if (!ssidCompare(network.ssid, accessPoint.get_ssid()))
 
1175
            return false;
 
1176
        if (network.mode != accessPoint.mode)
 
1177
            return false;
 
1178
        if (network.security != this._getApSecurityType(accessPoint))
 
1179
            return false;
 
1180
 
 
1181
        return true;
 
1182
    },
 
1183
 
 
1184
    _findExistingNetwork: function(accessPoint) {
 
1185
        for (let i = 0; i < this._networks.length; i++) {
 
1186
            let apObj = this._networks[i];
 
1187
            for (let j = 0; j < apObj.accessPoints.length; j++) {
 
1188
                if (apObj.accessPoints[j] == accessPoint)
 
1189
                    return { network: i, ap: j };
 
1190
            }
 
1191
        }
 
1192
 
 
1193
        return null;
 
1194
    },
 
1195
 
 
1196
    _findNetwork: function(accessPoint) {
 
1197
        if (accessPoint.get_ssid() == null)
 
1198
            return -1;
 
1199
 
 
1200
        for (let i = 0; i < this._networks.length; i++) {
 
1201
            if (this._networkCompare(this._networks[i], accessPoint))
 
1202
                return i;
 
1203
        }
 
1204
        return -1;
 
1205
    },
 
1206
 
 
1207
    _accessPointAdded: function(device, accessPoint) {
 
1208
        if (accessPoint.get_ssid() == null) {
 
1209
            // This access point is not visible yet
 
1210
            // Wait for it to get a ssid
 
1211
            accessPoint._notifySsidId = accessPoint.connect('notify::ssid', Lang.bind(this, this._notifySsidCb));
 
1212
            return;
 
1213
        }
 
1214
 
 
1215
        let pos = this._findNetwork(accessPoint);
 
1216
        let apObj;
 
1217
        let needsupdate = false;
 
1218
 
 
1219
        if (pos != -1) {
 
1220
            apObj = this._networks[pos];
 
1221
            if (apObj.accessPoints.indexOf(accessPoint) != -1) {
 
1222
                log('Access point was already seen, not adding again');
 
1223
                return;
 
1224
            }
 
1225
 
 
1226
            apObj.accessPoints.push(accessPoint);
 
1227
            if (apObj.item)
 
1228
                apObj.item.updateAccessPoints(apObj.accessPoints);
 
1229
        } else {
 
1230
            apObj = { ssid: accessPoint.get_ssid(),
 
1231
                      mode: accessPoint.mode,
 
1232
                      security: this._getApSecurityType(accessPoint),
 
1233
                      connections: [ ],
 
1234
                      item: null,
 
1235
                      accessPoints: [ accessPoint ]
 
1236
                    };
 
1237
            apObj.ssidText = ssidToLabel(apObj.ssid);
 
1238
            needsupdate = true;
 
1239
        }
 
1240
 
 
1241
        // check if this enables new connections for this group
 
1242
        for (let i = 0; i < this._connections.length; i++) {
 
1243
            let connection = this._connections[i].connection;
 
1244
            if (accessPoint.connection_valid(connection) &&
 
1245
                apObj.connections.indexOf(connection) == -1) {
 
1246
                apObj.connections.push(connection);
 
1247
 
 
1248
                // this potentially changes the order
 
1249
                needsupdate = true;
 
1250
            }
 
1251
        }
 
1252
 
 
1253
        if (needsupdate) {
 
1254
            if (apObj.item)
 
1255
                apObj.item.destroy();
 
1256
 
 
1257
            if (pos != -1)
 
1258
                this._networks.splice(pos, 1);
 
1259
 
 
1260
            if (this._networks.length == 0) {
 
1261
                // only network in the list
 
1262
                this._networks.push(apObj);
 
1263
                this._clearSection();
 
1264
                this._createSection();
 
1265
                return;
 
1266
            }
 
1267
 
 
1268
            // skip networks that should appear earlier
 
1269
            let menuPos = 0;
 
1270
            for (pos = 0;
 
1271
                 pos < this._networks.length &&
 
1272
                 this._networkSortFunction(this._networks[pos], apObj) < 0; ++pos) {
 
1273
                if (this._networks[pos] != this._activeNetwork)
 
1274
                    menuPos++;
 
1275
            }
 
1276
 
 
1277
            // (re-)add the network
 
1278
            this._networks.splice(pos, 0, apObj);
 
1279
 
 
1280
            if (this._shouldShowConnectionList()) {
 
1281
                menuPos += (this._activeConnectionItem ? 1 : 0);
 
1282
                this._createNetworkItem(apObj, menuPos);
 
1283
            }
 
1284
        }
 
1285
    },
 
1286
 
 
1287
    _accessPointRemoved: function(device, accessPoint) {
 
1288
        let res = this._findExistingNetwork(accessPoint);
 
1289
 
 
1290
        if (res == null) {
 
1291
            log('Removing an access point that was never added');
 
1292
            return;
 
1293
        }
 
1294
 
 
1295
        let apObj = this._networks[res.network];
 
1296
        apObj.accessPoints.splice(res.ap, 1);
 
1297
 
 
1298
        if (apObj.accessPoints.length == 0) {
 
1299
            if (this._activeNetwork == apObj)
 
1300
                this._activeNetwork = null;
 
1301
 
 
1302
            if (apObj.item)
 
1303
                apObj.item.destroy();
 
1304
 
 
1305
            if (this._overflowItem) {
 
1306
                if (!apObj.isMore) {
 
1307
                    // we removed an item in the main menu, and we have a more submenu
 
1308
                    // we need to extract the first item in more and move it to the submenu
 
1309
 
 
1310
                    let apObj = this._overflowItem.menu.firstMenuItem;
 
1311
                    if (apObj.item) {
 
1312
                        apObj.item.destroy();
 
1313
 
 
1314
                        this._createNetworkItem(apObj, NUM_VISIBLE_NETWORKS-1);
 
1315
                    }
 
1316
                }
 
1317
 
 
1318
                // This can happen if the removed connection is from the overflow
 
1319
                // menu, or if we just moved the last connection out from the menu
 
1320
                if (this._overflowItem.menu.length == 0) {
 
1321
                    this._overflowItem.destroy();
 
1322
                    this._overflowItem = null;
 
1323
                }
 
1324
            }
 
1325
            this._networks.splice(res.network, 1);
 
1326
 
 
1327
        } else if (apObj.item)
 
1328
            apObj.item.updateAccessPoints(apObj.accessPoints);
 
1329
    },
 
1330
 
 
1331
    _createAPItem: function(connection, accessPointObj, useConnectionName) {
 
1332
        let item = new NMNetworkMenuItem(accessPointObj.accessPoints, useConnectionName ? connection._name : undefined);
 
1333
        item._connection = connection;
 
1334
        item.connect('activate', Lang.bind(this, function() {
 
1335
            let accessPoints = sortAccessPoints(accessPointObj.accessPoints);
 
1336
            for (let i = 0; i < accessPoints.length; i++) {
 
1337
                if (accessPoints[i].connection_valid(connection)) {
 
1338
                    this._client.activate_connection(connection, this.device, accessPoints[i].dbus_path, null);
 
1339
                    break;
 
1340
                }
 
1341
            }
 
1342
        }));
 
1343
        return item;
 
1344
    },
 
1345
 
 
1346
    _clearSection: function() {
 
1347
        NMDevice.prototype._clearSection.call(this);
 
1348
 
 
1349
        for (let i = 0; i < this._networks.length; i++)
 
1350
            this._networks[i].item = null;
 
1351
        this._overflowItem = null;
 
1352
    },
 
1353
 
 
1354
    removeConnection: function(connection) {
 
1355
        if (!connection._uuid)
 
1356
            return;
 
1357
        let pos = this._findConnection(connection._uuid);
 
1358
        if (pos == -1) {
 
1359
            // removing connection that was never added
 
1360
            return;
 
1361
        }
 
1362
 
 
1363
        let obj = this._connections[pos];
 
1364
        this._connections.splice(pos, 1);
 
1365
 
 
1366
        let anyauto = false, forceupdate = false;
 
1367
        for (let i = 0; i < this._networks.length; i++) {
 
1368
            let apObj = this._networks[i];
 
1369
            let connections = apObj.connections;
 
1370
            for (let k = 0; k < connections.length; k++) {
 
1371
                if (connections[k]._uuid == connection._uuid) {
 
1372
                    // remove the connection from the access point group
 
1373
                    connections.splice(k);
 
1374
                    anyauto = connections.length == 0;
 
1375
 
 
1376
                    if (anyauto) {
 
1377
                        // this potentially changes the sorting order
 
1378
                        forceupdate = true;
 
1379
                        break;
 
1380
                    }
 
1381
                    if (apObj.item) {
 
1382
                        if (apObj.item instanceof PopupMenu.PopupSubMenuMenuItem) {
 
1383
                            let items = apObj.item.menu.getMenuItems();
 
1384
                            if (items.length == 2) {
 
1385
                                // we need to update the connection list to convert this to a normal item
 
1386
                                forceupdate = true;
 
1387
                            } else {
 
1388
                                for (let j = 0; j < items.length; j++) {
 
1389
                                    if (items[j]._connection._uuid == connection._uuid) {
 
1390
                                        items[j].destroy();
 
1391
                                        break;
 
1392
                                    }
 
1393
                                }
 
1394
                            }
 
1395
                        } else {
 
1396
                            apObj.item.destroy();
 
1397
                            apObj.item = null;
 
1398
                        }
 
1399
                    }
 
1400
                    break;
 
1401
                }
 
1402
            }
 
1403
        }
 
1404
 
 
1405
        if (forceupdate || anyauto) {
 
1406
            this._networks.sort(this._networkSortFunction);
 
1407
            this._clearSection();
 
1408
            this._createSection();
 
1409
        }
 
1410
    },
 
1411
 
 
1412
    addConnection: function(connection) {
 
1413
        // record the connection
 
1414
        let obj = {
 
1415
            connection: connection,
 
1416
            name: connection._name,
 
1417
            uuid: connection._uuid,
 
1418
        };
 
1419
        this._connections.push(obj);
 
1420
 
 
1421
        // find an appropriate access point
 
1422
        let forceupdate = false;
 
1423
        for (let i = 0; i < this._networks.length; i++) {
 
1424
            let apObj = this._networks[i];
 
1425
 
 
1426
            // Check if connection is valid for any of these access points
 
1427
            for (let k = 0; k < apObj.accessPoints.length; k++) {
 
1428
                let ap = apObj.accessPoints[k];
 
1429
                if (ap.connection_valid(connection)) {
 
1430
                    apObj.connections.push(connection);
 
1431
                    // this potentially changes the sorting order
 
1432
                    forceupdate = true;
 
1433
                    break;
 
1434
                }
 
1435
            }
 
1436
        }
 
1437
 
 
1438
        if (forceupdate) {
 
1439
            this._networks.sort(this._networkSortFunction);
 
1440
            this._clearSection();
 
1441
            this._createSection();
 
1442
        }
 
1443
    },
 
1444
 
 
1445
    _createActiveConnectionItem: function() {
 
1446
        let icon, title;
 
1447
        if (this._activeConnection._connection) {
 
1448
            let connection = this._activeConnection._connection;
 
1449
            if (this._activeNetwork)
 
1450
                this._activeConnectionItem = new NMNetworkMenuItem(this._activeNetwork.accessPoints, undefined,
 
1451
                                                                   { reactive: false });
 
1452
            else
 
1453
                this._activeConnectionItem = new PopupMenu.PopupImageMenuItem(connection._name,
 
1454
                                                                              'network-wireless-connected',
 
1455
                                                                              { reactive: false });
 
1456
        } else {
 
1457
            // We cannot read the connection (due to ACL, or API incompatibility), but we still show signal if we have it
 
1458
            let menuItem;
 
1459
            if (this._activeNetwork)
 
1460
                this._activeConnectionItem = new NMNetworkMenuItem(this._activeNetwork.accessPoints, undefined,
 
1461
                                                                   { reactive: false });
 
1462
            else
 
1463
                this._activeConnectionItem = new PopupMenu.PopupImageMenuItem(_("Connected (private)"),
 
1464
                                                                              'network-wireless-connected',
 
1465
                                                                              { reactive: false });
 
1466
        }
 
1467
        this._activeConnectionItem.setShowDot(true);
 
1468
    },
 
1469
 
 
1470
    _createAutomaticConnection: function(apObj) {
 
1471
        let name;
 
1472
        let ssid = NetworkManager.utils_ssid_to_utf8(apObj.ssid);
 
1473
        if (ssid) {
 
1474
            /* TRANSLATORS: this the automatic wireless connection name (including the network name) */
 
1475
            name = _("Auto %s").format(ssid);
 
1476
        } else
 
1477
            name = _("Auto wireless");
 
1478
 
 
1479
        let connection = new NetworkManager.Connection();
 
1480
        connection.add_setting(new NetworkManager.SettingWireless());
 
1481
        connection.add_setting(new NetworkManager.SettingConnection({
 
1482
            id: name,
 
1483
            autoconnect: true, // NetworkManager will know to ignore this if appropriate
 
1484
            uuid: NetworkManager.utils_uuid_generate(),
 
1485
            type: NetworkManager.SETTING_WIRELESS_SETTING_NAME
 
1486
        }));
 
1487
        return connection;
 
1488
    },
 
1489
 
 
1490
    _createNetworkItem: function(apObj, position) {
 
1491
        if(!apObj.accessPoints || apObj.accessPoints.length == 0) {
 
1492
            // this should not happen, but I have no idea why it happens
 
1493
            return;
 
1494
        }
 
1495
 
 
1496
        if(apObj.connections.length > 0) {
 
1497
            if (apObj.connections.length == 1)
 
1498
                apObj.item = this._createAPItem(apObj.connections[0], apObj, false);
 
1499
            else {
 
1500
                let title = apObj.ssidText;
 
1501
                apObj.item = new PopupMenu.PopupSubMenuMenuItem(title);
 
1502
                apObj.item._apObj = apObj;
 
1503
                for (let i = 0; i < apObj.connections.length; i++)
 
1504
                    apObj.item.menu.addMenuItem(this._createAPItem(apObj.connections[i], apObj, true));
 
1505
            }
 
1506
        } else {
 
1507
            apObj.item = new NMNetworkMenuItem(apObj.accessPoints);
 
1508
            apObj.item._apObj = apObj;
 
1509
            apObj.item.connect('activate', Lang.bind(this, function() {
 
1510
                let accessPoints = sortAccessPoints(apObj.accessPoints);
 
1511
                if (   (accessPoints[0]._secType == NMAccessPointSecurity.WPA2_ENT)
 
1512
                    || (accessPoints[0]._secType == NMAccessPointSecurity.WPA_ENT)) {
 
1513
                    // 802.1x-enabled APs require further configuration, so they're
 
1514
                    // handled in gnome-control-center
 
1515
                    Util.spawn(['gnome-control-center', 'network', 'connect-8021x-wifi',
 
1516
                                this.device.get_path(), accessPoints[0].dbus_path]);
 
1517
                } else {
 
1518
                    let connection = this._createAutomaticConnection(apObj);
 
1519
                    this._client.add_and_activate_connection(connection, this.device, accessPoints[0].dbus_path, null)
 
1520
                }
 
1521
            }));
 
1522
        }
 
1523
        if (position < NUM_VISIBLE_NETWORKS) {
 
1524
            apObj.isMore = false;
 
1525
            this.section.addMenuItem(apObj.item, position);
 
1526
        } else {
 
1527
            if (!this._overflowItem) {
 
1528
                this._overflowItem = new PopupMenu.PopupSubMenuMenuItem(_("More..."));
 
1529
                this.section.addMenuItem(this._overflowItem);
 
1530
            }
 
1531
            this._overflowItem.menu.addMenuItem(apObj.item, position - NUM_VISIBLE_NETWORKS);
 
1532
            apObj.isMore = true;
 
1533
        }
 
1534
    },
 
1535
 
 
1536
    _createSection: function() {
 
1537
        if (!this._shouldShowConnectionList())
 
1538
            return;
 
1539
 
 
1540
        if(this._activeConnection) {
 
1541
            this._createActiveConnectionItem();
 
1542
            this.section.addMenuItem(this._activeConnectionItem);
 
1543
        }
 
1544
 
 
1545
        let activeOffset = this._activeConnectionItem ? 1 : 0;
 
1546
 
 
1547
        for(let j = 0; j < this._networks.length; j++) {
 
1548
            let apObj = this._networks[j];
 
1549
            if (apObj == this._activeNetwork)
 
1550
                continue;
 
1551
 
 
1552
            this._createNetworkItem(apObj, j + activeOffset);
 
1553
        }
 
1554
    },
 
1555
};
 
1556
 
 
1557
function NMApplet() {
 
1558
    this._init.apply(this, arguments);
 
1559
}
 
1560
NMApplet.prototype = {
 
1561
    __proto__: PanelMenu.SystemStatusButton.prototype,
 
1562
 
 
1563
    _init: function() {
 
1564
        PanelMenu.SystemStatusButton.prototype._init.call(this, 'network-error');
 
1565
 
 
1566
        this._client = NMClient.Client.new();
 
1567
 
 
1568
        this._statusSection = new PopupMenu.PopupMenuSection();
 
1569
        this._statusItem = new PopupMenu.PopupMenuItem('', { style_class: 'popup-inactive-menu-item', reactive: false });
 
1570
        this._statusSection.addMenuItem(this._statusItem);
 
1571
        this._statusSection.addAction(_("Enable networking"), Lang.bind(this, function() {
 
1572
            this._client.networking_enabled = true;
 
1573
        }));
 
1574
        this._statusSection.actor.hide();
 
1575
        this.menu.addMenuItem(this._statusSection);
 
1576
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
1577
 
 
1578
        this._devices = { };
 
1579
 
 
1580
        this._devices.wired = {
 
1581
            section: new PopupMenu.PopupMenuSection(),
 
1582
            devices: [ ],
 
1583
            item: new NMWiredSectionTitleMenuItem(_("Wired"))
 
1584
        };
 
1585
 
 
1586
        this._devices.wired.section.addMenuItem(this._devices.wired.item);
 
1587
        this._devices.wired.section.actor.hide();
 
1588
        this.menu.addMenuItem(this._devices.wired.section);
 
1589
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
1590
 
 
1591
        this._devices.wireless = {
 
1592
            section: new PopupMenu.PopupMenuSection(),
 
1593
            devices: [ ],
 
1594
            item: this._makeToggleItem('wireless', _("Wireless"))
 
1595
        };
 
1596
        this._devices.wireless.section.addMenuItem(this._devices.wireless.item);
 
1597
        this._devices.wireless.section.actor.hide();
 
1598
        this.menu.addMenuItem(this._devices.wireless.section);
 
1599
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
1600
 
 
1601
        this._devices.wwan = {
 
1602
            section: new PopupMenu.PopupMenuSection(),
 
1603
            devices: [ ],
 
1604
            item: this._makeToggleItem('wwan', _("Mobile broadband"))
 
1605
        };
 
1606
        this._devices.wwan.section.addMenuItem(this._devices.wwan.item);
 
1607
        this._devices.wwan.section.actor.hide();
 
1608
        this.menu.addMenuItem(this._devices.wwan.section);
 
1609
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
1610
 
 
1611
        this._devices.vpn = {
 
1612
            section: new PopupMenu.PopupMenuSection(),
 
1613
            device: new NMDeviceVPN(this._client),
 
1614
            item: new NMWiredSectionTitleMenuItem(_("VPN Connections"))
 
1615
        };
 
1616
        this._devices.vpn.device.connect('active-connection-changed', Lang.bind(this, function() {
 
1617
            this._devices.vpn.item.updateForDevice(this._devices.vpn.device);
 
1618
        }));
 
1619
        this._devices.vpn.item.updateForDevice(this._devices.vpn.device);
 
1620
        this._devices.vpn.section.addMenuItem(this._devices.vpn.item);
 
1621
        this._devices.vpn.section.addMenuItem(this._devices.vpn.device.section);
 
1622
        this._devices.vpn.section.actor.hide();
 
1623
        this.menu.addMenuItem(this._devices.vpn.section);
 
1624
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
 
1625
        this.menu.addSettingsAction(_("Network Settings"), 'gnome-network-panel.desktop');
 
1626
 
 
1627
        this._activeConnections = [ ];
 
1628
        this._connections = [ ];
 
1629
 
 
1630
        this._mainConnection = null;
 
1631
        this._activeAccessPointUpdateId = 0;
 
1632
        this._activeAccessPoint = null;
 
1633
        this._mobileUpdateId = 0;
 
1634
        this._mobileUpdateDevice = null;
 
1635
 
 
1636
        // Device types
 
1637
        this._dtypes = { };
 
1638
        this._dtypes[NetworkManager.DeviceType.ETHERNET] = NMDeviceWired;
 
1639
        this._dtypes[NetworkManager.DeviceType.WIFI] = NMDeviceWireless;
 
1640
        this._dtypes[NetworkManager.DeviceType.MODEM] = NMDeviceModem;
 
1641
        this._dtypes[NetworkManager.DeviceType.BT] = NMDeviceBluetooth;
 
1642
        // TODO: WiMax support
 
1643
 
 
1644
        // Connection types
 
1645
        this._ctypes = { };
 
1646
        this._ctypes[NetworkManager.SETTING_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS;
 
1647
        this._ctypes[NetworkManager.SETTING_WIRED_SETTING_NAME] = NMConnectionCategory.WIRED;
 
1648
        this._ctypes[NetworkManager.SETTING_PPPOE_SETTING_NAME] = NMConnectionCategory.WIRED;
 
1649
        this._ctypes[NetworkManager.SETTING_PPP_SETTING_NAME] = NMConnectionCategory.WIRED;
 
1650
        this._ctypes[NetworkManager.SETTING_BLUETOOTH_SETTING_NAME] = NMConnectionCategory.WWAN;
 
1651
        this._ctypes[NetworkManager.SETTING_CDMA_SETTING_NAME] = NMConnectionCategory.WWAN;
 
1652
        this._ctypes[NetworkManager.SETTING_GSM_SETTING_NAME] = NMConnectionCategory.WWAN;
 
1653
        this._ctypes[NetworkManager.SETTING_VPN_SETTING_NAME] = NMConnectionCategory.VPN;
 
1654
 
 
1655
        this._settings = NMClient.RemoteSettings.new(null);
 
1656
        this._connectionsReadId = this._settings.connect('connections-read', Lang.bind(this, function() {
 
1657
            this._readConnections();
 
1658
            this._readDevices();
 
1659
            this._syncNMState();
 
1660
 
 
1661
            // Connect to signals late so that early signals don't find in inconsistent state
 
1662
            // and connect only once (this signal handler can be called again if NetworkManager goes up and down)
 
1663
            if (!this._inited) {
 
1664
                this._inited = true;
 
1665
                this._client.connect('notify::manager-running', Lang.bind(this, this._syncNMState));
 
1666
                this._client.connect('notify::networking-enabled', Lang.bind(this, this._syncNMState));
 
1667
                this._client.connect('notify::state', Lang.bind(this, this._syncNMState));
 
1668
                this._client.connect('notify::active-connections', Lang.bind(this, this._updateIcon));
 
1669
                this._client.connect('device-added', Lang.bind(this, this._deviceAdded));
 
1670
                this._client.connect('device-removed', Lang.bind(this, this._deviceRemoved));
 
1671
                this._settings.connect('new-connection', Lang.bind(this, this._newConnection));
 
1672
            }
 
1673
        }));
 
1674
    },
 
1675
 
 
1676
    _ensureSource: function() {
 
1677
        if (!this._source) {
 
1678
            this._source = new NMMessageTraySource();
 
1679
            this._source.connect('destroy', Lang.bind(this, function() {
 
1680
                this._source = null;
 
1681
            }));
 
1682
            Main.messageTray.add(this._source);
 
1683
        }
 
1684
    },
 
1685
 
 
1686
    _makeToggleItem: function(type, title) {
 
1687
        let item = new NMWirelessSectionTitleMenuItem(this._client, type, title);
 
1688
        item.connect('enabled-changed', Lang.bind(this, function(item, enabled) {
 
1689
            let devices = this._devices[type].devices;
 
1690
            devices.forEach(function(dev) {
 
1691
                dev.setEnabled(enabled);
 
1692
            });
 
1693
            this._syncSectionTitle(type);
 
1694
        }));
 
1695
        return item;
 
1696
    },
 
1697
 
 
1698
    _syncSectionTitle: function(category) {
 
1699
        let devices = this._devices[category].devices;
 
1700
        let item = this._devices[category].item;
 
1701
        let section = this._devices[category].section;
 
1702
        if (devices.length == 0)
 
1703
            section.actor.hide();
 
1704
        else {
 
1705
            section.actor.show();
 
1706
            if (devices.length == 1) {
 
1707
                let dev = devices[0];
 
1708
                dev.statusItem.actor.hide();
 
1709
                item.updateForDevice(dev);
 
1710
            } else {
 
1711
                devices.forEach(function(dev) {
 
1712
                    dev.statusItem.actor.show();
 
1713
                });
 
1714
                // remove status text from the section title item
 
1715
                item.updateForDevice(null);
 
1716
            }
 
1717
        }
 
1718
    },
 
1719
 
 
1720
    _readDevices: function() {
 
1721
        let devices = this._client.get_devices() || [ ];
 
1722
        for (let i = 0; i < devices.length; ++i) {
 
1723
            this._deviceAdded(this._client, devices[i]);
 
1724
        }
 
1725
    },
 
1726
 
 
1727
    _notifyForDevice: function(device, iconName, title, text, urgency) {
 
1728
        if (device._notification)
 
1729
            device._notification.destroy();
 
1730
 
 
1731
        /* must call after destroying previous notification,
 
1732
           or this._source will be cleared */
 
1733
        this._ensureSource();
 
1734
 
 
1735
        let icon = new St.Icon({ icon_name: iconName,
 
1736
                                 icon_type: St.IconType.SYMBOLIC,
 
1737
                                 icon_size: this._source.ICON_SIZE
 
1738
                               });
 
1739
        device._notification = new MessageTray.Notification(this._source, title, text,
 
1740
                                                            { icon: icon });
 
1741
        device._notification.setUrgency(urgency);
 
1742
        device._notification.setTransient(true);
 
1743
        device._notification.connect('destroy', function() {
 
1744
            device._notification = null;
 
1745
        });
 
1746
        this._source.notify(device._notification);
 
1747
    },
 
1748
 
 
1749
    _deviceAdded: function(client, device) {
 
1750
        if (device._delegate) {
 
1751
            // already seen, not adding again
 
1752
            return;
 
1753
        }
 
1754
        let wrapperClass = this._dtypes[device.get_device_type()];
 
1755
        if (wrapperClass) {
 
1756
            let wrapper = new wrapperClass(this._client, device, this._connections);
 
1757
 
 
1758
            wrapper._activationFailedId = wrapper.connect('activation-failed', Lang.bind(this, function(device, reason) {
 
1759
                // XXX: nm-applet has no special text depending on reason
 
1760
                // but I'm not sure of this generic message
 
1761
                this._notifyForDevice(device, 'network-error',
 
1762
                                      _("Connection failed"),
 
1763
                                      _("Activation of network connection failed"),
 
1764
                                     MessageTray.Urgency.HIGH);
 
1765
            }));
 
1766
            wrapper._deviceStateChangedId = wrapper.connect('state-changed', Lang.bind(this, function(dev) {
 
1767
                this._syncSectionTitle(dev.category);
 
1768
            }));
 
1769
            wrapper._destroyId = wrapper.connect('destroy', function(wrapper) {
 
1770
                wrapper.disconnect(wrapper._activationFailedId);
 
1771
                wrapper.disconnect(wrapper._deviceStateChangedId);
 
1772
                wrapper.disconnect(wrapper._destroyId);
 
1773
            });
 
1774
            let section = this._devices[wrapper.category].section;
 
1775
            let devices = this._devices[wrapper.category].devices;
 
1776
 
 
1777
            section.addMenuItem(wrapper.section, 1);
 
1778
            section.addMenuItem(wrapper.statusItem, 1);
 
1779
            devices.push(wrapper);
 
1780
 
 
1781
            this._syncSectionTitle(wrapper.category);
 
1782
        } else
 
1783
            log('Invalid network device type, is ' + device.get_device_type());
 
1784
    },
 
1785
 
 
1786
    _deviceRemoved: function(client, device) {
 
1787
        if (!device._delegate) {
 
1788
            log('Removing a network device that was not added');
 
1789
            return;
 
1790
        }
 
1791
 
 
1792
        let wrapper = device._delegate;
 
1793
        wrapper.destroy();
 
1794
 
 
1795
        let devices = this._devices[wrapper.category].devices;
 
1796
        let pos = devices.indexOf(wrapper);
 
1797
        devices.splice(pos, 1);
 
1798
 
 
1799
        this._syncSectionTitle(wrapper.category)
 
1800
    },
 
1801
 
 
1802
    _syncActiveConnections: function() {
 
1803
        let closedConnections = [ ];
 
1804
        let newActiveConnections = this._client.get_active_connections() || [ ];
 
1805
        for (let i = 0; i < this._activeConnections.length; i++) {
 
1806
            let a = this._activeConnections[i];
 
1807
            if (newActiveConnections.indexOf(a) == -1) // connection is removed
 
1808
                closedConnections.push(a);
 
1809
        }
 
1810
 
 
1811
        for (let i = 0; i < closedConnections.length; i++) {
 
1812
            let active = closedConnections[i];
 
1813
            if (active._primaryDevice) {
 
1814
                active._primaryDevice.setActiveConnection(null);
 
1815
                active._primaryDevice = null;
 
1816
            }
 
1817
            if (active._inited) {
 
1818
                active.disconnect(active._notifyStateId);
 
1819
                active.disconnect(active._notifyDefaultId);
 
1820
                active.disconnect(active._notifyDefault6Id);
 
1821
                active._inited = false;
 
1822
            }
 
1823
        }
 
1824
 
 
1825
        this._activeConnections = newActiveConnections;
 
1826
        this._mainConnection = null;
 
1827
        let activating = null;
 
1828
        let default_ip4 = null;
 
1829
        let default_ip6 = null;
 
1830
        for (let i = 0; i < this._activeConnections.length; i++) {
 
1831
            let a = this._activeConnections[i];
 
1832
 
 
1833
            if (!a._inited) {
 
1834
                a._notifyDefaultId = a.connect('notify::default', Lang.bind(this, this._updateIcon));
 
1835
                a._notifyDefault6Id = a.connect('notify::default6', Lang.bind(this, this._updateIcon));
 
1836
                a._notifyStateId = a.connect('notify::state', Lang.bind(this, this._notifyActivated));
 
1837
 
 
1838
                a._inited = true;
 
1839
            }
 
1840
 
 
1841
            if (!a._connection) {
 
1842
                a._connection = this._settings.get_connection_by_path(a.connection);
 
1843
 
 
1844
                if (a._connection) {
 
1845
                    a._type = a._connection._type;
 
1846
                    a._section = this._ctypes[a._type];
 
1847
                } else {
 
1848
                    a._connection = null;
 
1849
                    a._type = null;
 
1850
                    a._section = null;
 
1851
                    log('Cannot find connection for active (or connection cannot be read)');
 
1852
                }
 
1853
            }
 
1854
 
 
1855
            if (a['default'])
 
1856
                default_ip4 = a;
 
1857
            if (a.default6)
 
1858
                default_ip6 = a;
 
1859
 
 
1860
            if (a.state == NetworkManager.ActiveConnectionState.ACTIVATING)
 
1861
                activating = a;
 
1862
 
 
1863
            if (!a._primaryDevice) {
 
1864
                if (a._type != NetworkManager.SETTING_VPN_SETTING_NAME) {
 
1865
                    // find a good device to be considered primary
 
1866
                    a._primaryDevice = null;
 
1867
                    let devices = a.get_devices();
 
1868
                    for (let j = 0; j < devices.length; j++) {
 
1869
                        let d = devices[j];
 
1870
                        if (d._delegate) {
 
1871
                            a._primaryDevice = d._delegate;
 
1872
                            break;
 
1873
                        }
 
1874
                    }
 
1875
                } else
 
1876
                    a._primaryDevice = this._devices.vpn.device
 
1877
 
 
1878
                if (a._primaryDevice)
 
1879
                    a._primaryDevice.setActiveConnection(a);
 
1880
 
 
1881
                if (a.state == NetworkManager.ActiveConnectionState.ACTIVATED
 
1882
                    && a._primaryDevice && a._primaryDevice._notification) {
 
1883
                    a._primaryDevice._notification.destroy();
 
1884
                    a._primaryDevice._notification = null;
 
1885
                }
 
1886
            }
 
1887
        }
 
1888
 
 
1889
        this._mainConnection = activating || default_ip4 || default_ip6 || this._activeConnections[0] || null;
 
1890
    },
 
1891
 
 
1892
    _notifyActivated: function(activeConnection) {
 
1893
        if (activeConnection.state == NetworkManager.ActiveConnectionState.ACTIVATED
 
1894
            && activeConnection._primaryDevice && activeConnection._primaryDevice._notification) {
 
1895
            activeConnection._primaryDevice._notification.destroy();
 
1896
            activeConnection._primaryDevice._notification = null;
 
1897
        }
 
1898
 
 
1899
        this._updateIcon();
 
1900
    },
 
1901
 
 
1902
    _readConnections: function() {
 
1903
        let connections = this._settings.list_connections();
 
1904
        for (let i = 0; i < connections.length; i++) {
 
1905
            let connection = connections[i];
 
1906
            if (connection._uuid) {
 
1907
                // connection was already seen (for example because NetworkManager was restarted)
 
1908
                continue;
 
1909
            }
 
1910
            connection._removedId = connection.connect('removed', Lang.bind(this, this._connectionRemoved));
 
1911
            connection._updatedId = connection.connect('updated', Lang.bind(this, this._updateConnection));
 
1912
 
 
1913
            this._updateConnection(connection);
 
1914
            this._connections.push(connection);
 
1915
        }
 
1916
    },
 
1917
 
 
1918
    _newConnection: function(settings, connection) {
 
1919
        if (connection._uuid) {
 
1920
            // connection was already seen
 
1921
            return;
 
1922
        }
 
1923
 
 
1924
        connection._removedId = connection.connect('removed', Lang.bind(this, this._connectionRemoved));
 
1925
        connection._updatedId = connection.connect('updated', Lang.bind(this, this._updateConnection));
 
1926
 
 
1927
        this._updateConnection(connection);
 
1928
        this._connections.push(connection);
 
1929
 
 
1930
        this._updateIcon();
 
1931
    },
 
1932
 
 
1933
    _connectionRemoved: function(connection) {
 
1934
        let pos = this._connections.indexOf(connection);
 
1935
        if (pos != -1)
 
1936
            this._connections.splice(connection);
 
1937
 
 
1938
        let section = connection._section;
 
1939
 
 
1940
        if (section == NMConnectionCategory.VPN) {
 
1941
            this._devices.vpn.device.removeConnection(connection);
 
1942
            if (this._devices.vpn.device.empty)
 
1943
                this._devices.vpn.section.actor.hide();
 
1944
        } else if (section != NMConnectionCategory.INVALID) {
 
1945
            let devices = this._devices[section].devices;
 
1946
            for (let i = 0; i < devices.length; i++)
 
1947
                devices[i].removeConnection(connection);
 
1948
        }
 
1949
 
 
1950
        connection._uuid = null;
 
1951
        connection.disconnect(connection._removedId);
 
1952
        connection.disconnect(connection._updatedId);
 
1953
    },
 
1954
 
 
1955
    _updateConnection: function(connection) {
 
1956
        let connectionSettings = connection.get_setting_by_name(NetworkManager.SETTING_CONNECTION_SETTING_NAME);
 
1957
        connection._type = connectionSettings.type;
 
1958
 
 
1959
        connection._section = this._ctypes[connection._type] || NMConnectionCategory.INVALID;
 
1960
        connection._name = connectionSettings.id;
 
1961
        connection._uuid = connectionSettings.uuid;
 
1962
        connection._timestamp = connectionSettings.timestamp;
 
1963
 
 
1964
        let section = connection._section;
 
1965
 
 
1966
        if (connection._section == NMConnectionCategory.INVALID)
 
1967
            return;
 
1968
        if (section == NMConnectionCategory.VPN) {
 
1969
            this._devices.vpn.device.checkConnection(connection);
 
1970
            this._devices.vpn.section.actor.show();
 
1971
        } else {
 
1972
            let devices = this._devices[section].devices;
 
1973
            for (let i = 0; i < devices.length; i++) {
 
1974
                devices[i].checkConnection(connection);
 
1975
            }
 
1976
        }
 
1977
    },
 
1978
 
 
1979
    _hideDevices: function() {
 
1980
        this._devicesHidden = true;
 
1981
 
 
1982
        for (let category in this._devices)
 
1983
            this._devices[category].section.actor.hide();
 
1984
    },
 
1985
 
 
1986
    _showNormal: function() {
 
1987
        if (!this._devicesHidden) // nothing to do
 
1988
            return;
 
1989
        this._devicesHidden = false;
 
1990
 
 
1991
        this._statusSection.actor.hide();
 
1992
 
 
1993
        this._syncSectionTitle('wired');
 
1994
        this._syncSectionTitle('wireless');
 
1995
        this._syncSectionTitle('wwan');
 
1996
 
 
1997
        if (!this._devices.vpn.device.empty)
 
1998
            this._devices.vpn.section.actor.show();
 
1999
    },
 
2000
 
 
2001
    _syncNMState: function() {
 
2002
        if (!this._client.manager_running) {
 
2003
            log('NetworkManager is not running, hiding...');
 
2004
            this.menu.close();
 
2005
            this.actor.hide();
 
2006
            return;
 
2007
        } else
 
2008
            this.actor.show();
 
2009
 
 
2010
        if (!this._client.networking_enabled) {
 
2011
            this.setIcon('network-offline');
 
2012
            this._hideDevices();
 
2013
            this._statusItem.label.text = _("Networking is disabled");
 
2014
            this._statusSection.actor.show();
 
2015
            return;
 
2016
        }
 
2017
 
 
2018
        this._showNormal();
 
2019
        this._updateIcon();
 
2020
    },
 
2021
 
 
2022
    _updateIcon: function() {
 
2023
        this._syncActiveConnections();
 
2024
        let mc = this._mainConnection;
 
2025
        let hasApIcon = false;
 
2026
        let hasMobileIcon = false;
 
2027
 
 
2028
        if (!mc) {
 
2029
            this.setIcon('network-offline');
 
2030
        } else if (mc.state == NetworkManager.ActiveConnectionState.ACTIVATING) {
 
2031
            switch (mc._section) {
 
2032
            case NMConnectionCategory.WWAN:
 
2033
                this.setIcon('network-cellular-acquiring');
 
2034
                break;
 
2035
            case NMConnectionCategory.WIRELESS:
 
2036
                this.setIcon('network-wireless-acquiring');
 
2037
                break;
 
2038
            case NMConnectionCategory.WIRED:
 
2039
                this.setIcon('network-wired-acquiring');
 
2040
                break;
 
2041
            case NMConnectionCategory.VPN:
 
2042
                this.setIcon('network-vpn-acquiring');
 
2043
                break;
 
2044
            default:
 
2045
                // fallback to a generic connected icon
 
2046
                // (it could be a private connection of some other user)
 
2047
                this.setIcon('network-wired-acquiring');
 
2048
            }
 
2049
        } else {
 
2050
            let dev;
 
2051
            switch (mc._section) {
 
2052
            case NMConnectionCategory.WIRELESS:
 
2053
                dev = mc._primaryDevice;
 
2054
                if (dev) {
 
2055
                    let ap = dev.device.active_access_point;
 
2056
                    let mode = dev.device.mode;
 
2057
                    if (!ap) {
 
2058
                        if (mode != NM80211Mode.ADHOC) {
 
2059
                            log('An active wireless connection, in infrastructure mode, involves no access point?');
 
2060
                            break;
 
2061
                        }
 
2062
                        this.setIcon('network-wireless-connected');
 
2063
                    } else {
 
2064
                        if (this._accessPointUpdateId && this._activeAccessPoint != ap) {
 
2065
                            this._activeAccessPoint.disconnect(this._accessPointUpdateId);
 
2066
                            this._activeAccessPoint = ap;
 
2067
                            this._activeAccessPointUpdateId = ap.connect('notify::strength', Lang.bind(function() {
 
2068
                                this.setIcon('network-wireless-signal-' + signalToIcon(ap.strength));
 
2069
                            }));
 
2070
                        }
 
2071
                        this.setIcon('network-wireless-signal-' + signalToIcon(ap.strength));
 
2072
                        hasApIcon = true;
 
2073
                    }
 
2074
                    break;
 
2075
                } else {
 
2076
                    log('Active connection with no primary device?');
 
2077
                    break;
 
2078
                }
 
2079
            case NMConnectionCategory.WIRED:
 
2080
                this.setIcon('network-wired');
 
2081
                break;
 
2082
            case NMConnectionCategory.WWAN:
 
2083
                dev = mc._primaryDevice;
 
2084
                if (!dev) {
 
2085
                    log('Active connection with no primary device?');
 
2086
                    break;
 
2087
                }
 
2088
                if (!dev.mobileDevice) {
 
2089
                    // this can happen for bluetooth in PAN mode
 
2090
                    this.setIcon('network-cellular-connected');
 
2091
                    break;
 
2092
                }
 
2093
 
 
2094
                if (this._mobileUpdateId && this._mobileUpdateDevice != dev) {
 
2095
                    this._mobileUpdateDevice.disconnect(this._mobileUpdateId);
 
2096
                    this._mobileUpdateDevice = dev.mobileDevice;
 
2097
                    this._mobileUpdateId = dev.mobileDevice.connect('notify::signal-quality', Lang.bind(this, function() {
 
2098
                        this.setIcon('network-cellular-signal-' + signalToIcon(dev.mobileDevice.signal_quality));
 
2099
                    }));
 
2100
                }
 
2101
                this.setIcon('network-cellular-signal-' + signalToIcon(dev.mobileDevice.signal_quality));
 
2102
                hasMobileIcon = true;
 
2103
                break;
 
2104
            case NMConnectionCategory.VPN:
 
2105
                this.setIcon('network-vpn');
 
2106
                break;
 
2107
            default:
 
2108
                // fallback to a generic connected icon
 
2109
                // (it could be a private connection of some other user)
 
2110
                this.setIcon('network-wired');
 
2111
                break;
 
2112
            }
 
2113
        }
 
2114
 
 
2115
        // cleanup stale signal connections
 
2116
 
 
2117
        if (!hasApIcon && this._activeAccessPointUpdateId) {
 
2118
            this._activeAccessPoint.disconnect(this._activeAccessPointUpdateId);
 
2119
            this._activeAccessPoint = null;
 
2120
            this._activeAccessPointUpdateId = 0;
 
2121
        }
 
2122
        if (!hasMobileIcon && this._mobileUpdateId) {
 
2123
            this._mobileUpdateDevice.disconnect(this._mobileUpdateId);
 
2124
            this._mobileUpdateDevice = null;
 
2125
            this._mobileUpdateId = 0;
 
2126
        }
 
2127
    }
 
2128
};
 
2129
 
 
2130
function NMMessageTraySource() {
 
2131
    this._init();
 
2132
}
 
2133
 
 
2134
NMMessageTraySource.prototype = {
 
2135
    __proto__: MessageTray.Source.prototype,
 
2136
 
 
2137
    _init: function() {
 
2138
        MessageTray.Source.prototype._init.call(this, _("Network Manager"));
 
2139
 
 
2140
        let icon = new St.Icon({ icon_name: 'network-transmit-receive',
 
2141
                                 icon_type: St.IconType.SYMBOLIC,
 
2142
                                 icon_size: this.ICON_SIZE
 
2143
                               });
 
2144
        this._setSummaryIcon(icon);
 
2145
    }
 
2146
};