network: add support for virtual device types (vlan, bond, bridge)

Virtual devices may not actually exist until their corresponding
connections are brought up. So we need new code to create fake device
wrapper objects for them based on the corresponding NMConnections.

https://bugzilla.gnome.org/show_bug.cgi?id=677144
https://bugzilla.gnome.org/show_bug.cgi?id=677146
https://bugzilla.gnome.org/show_bug.cgi?id=677148
This commit is contained in:
Dan Winship 2012-12-04 08:49:34 -05:00
parent 427750d6af
commit c49bb5aa03

View File

@ -26,6 +26,7 @@ const Util = imports.misc.util;
const NMConnectionCategory = { const NMConnectionCategory = {
INVALID: 'invalid', INVALID: 'invalid',
WIRED: 'wired', WIRED: 'wired',
VIRTUAL: 'virtual',
WIRELESS: 'wireless', WIRELESS: 'wireless',
WWAN: 'wwan', WWAN: 'wwan',
VPN: 'vpn' VPN: 'vpn'
@ -307,13 +308,10 @@ const NMDevice = new Lang.Class({
Extends: NMConnectionBased, Extends: NMConnectionBased,
_init: function(client, device, connections) { _init: function(client, device, connections) {
this.device = device;
this.device._delegate = this;
this._stateChangedId = this.device.connect('state-changed', Lang.bind(this, this._deviceStateChanged));
// protected
this._client = client; this._client = client;
this._setDevice(device);
this.parent(connections); this.parent(connections);
this._activeConnection = null; this._activeConnection = null;
this._activeConnectionItem = null; this._activeConnectionItem = null;
this._autoConnectionItem = null; this._autoConnectionItem = null;
@ -338,24 +336,7 @@ const NMDevice = new Lang.Class({
}, },
destroy: function() { destroy: function() {
if (this.device) this._setDevice(null);
this.device._delegate = null;
if (this._stateChangedId) {
// Need to go through GObject.Object.prototype because
// nm_device_disconnect conflicts with g_signal_disconnect
GObject.Object.prototype.disconnect.call(this.device, this._stateChangedId);
this._stateChangedId = 0;
}
if (this._carrierChangedId) {
// see above for why this is needed
GObject.Object.prototype.disconnect.call(this.device, this._carrierChangedId);
this._carrierChangedId = 0;
}
if (this._firmwareChangedId) {
GObject.Object.prototype.disconnect.call(this.device, this._firmwareChangedId);
this._firmwareChangedId = 0;
}
if (this._deferredWorkId) { if (this._deferredWorkId) {
// Just clear out, the actual removal is handled when the // Just clear out, the actual removal is handled when the
@ -369,6 +350,33 @@ const NMDevice = new Lang.Class({
this.section.destroy(); this.section.destroy();
}, },
_setDevice: function(device) {
if (device) {
this.device = device;
this.device._delegate = this;
this._stateChangedId = this.device.connect('state-changed', Lang.bind(this, this._deviceStateChanged));
} else if (this.device) {
this.device._delegate = null;
if (this._stateChangedId) {
// Need to go through GObject.Object.prototype because
// nm_device_disconnect conflicts with g_signal_disconnect
GObject.Object.prototype.disconnect.call(this.device, this._stateChangedId);
this._stateChangedId = 0;
}
if (this._carrierChangedId) {
GObject.Object.prototype.disconnect.call(this.device, this._carrierChangedId);
this._carrierChangedId = 0;
}
if (this._firmwareChangedId) {
GObject.Object.prototype.disconnect.call(this.device, this._firmwareChangedId);
this._firmwareChangedId = 0;
}
this.device = null;
}
},
deactivate: function() { deactivate: function() {
this.device.disconnect(null); this.device.disconnect(null);
return true; return true;
@ -383,7 +391,7 @@ const NMDevice = new Lang.Class({
// Otherwise, if no connection is currently configured, // Otherwise, if no connection is currently configured,
// try automatic configuration (or summon the config dialog) // try automatic configuration (or summon the config dialog)
if (this._connections.length == 1) { if (this._connections.length == 1) {
this._client.activate_connection(this._connections[0].connection, this.device, null, null); this._client.activate_connection(this._connections[0].connection, this.device || null, null, null);
return true; return true;
} else if (this._connections.length == 0) { } else if (this._connections.length == 0) {
return this._activateAutomaticConnection(); return this._activateAutomaticConnection();
@ -403,7 +411,7 @@ const NMDevice = new Lang.Class({
}, },
get connected() { get connected() {
return this.device.state == NetworkManager.DeviceState.ACTIVATED; return this.device && this.device.state == NetworkManager.DeviceState.ACTIVATED;
}, },
clearActiveConnection: function(activeConnection) { clearActiveConnection: function(activeConnection) {
@ -436,6 +444,9 @@ const NMDevice = new Lang.Class({
}, },
getStatusLabel: function() { getStatusLabel: function() {
if (!this.device)
return null;
switch(this.device.state) { switch(this.device.state) {
case NetworkManager.DeviceState.DISCONNECTED: case NetworkManager.DeviceState.DISCONNECTED:
case NetworkManager.DeviceState.ACTIVATED: case NetworkManager.DeviceState.ACTIVATED:
@ -486,7 +497,7 @@ const NMDevice = new Lang.Class({
}, },
syncDescription: function() { syncDescription: function() {
if (this.device._description) if (this.device && this.device._description)
this.statusItem.label.text = this.device._description; this.statusItem.label.text = this.device._description;
}, },
@ -1439,6 +1450,56 @@ const NMDeviceWireless = new Lang.Class({
}, },
}); });
const NMDeviceVirtual = new Lang.Class({
Name: 'NMDeviceVirtual',
Extends: NMDeviceSimple,
_init: function(client, iface, connections) {
this.iface = iface;
this.parent(client, null, connections);
this.category = NMConnectionCategory.VIRTUAL;
},
_shouldShowConnectionList: function() {
return this.hasConnections();
},
connectionValid: function(connection) {
return connection.get_virtual_iface_name() == this.iface;
},
addConnection: function(connection) {
if (!this.device && !this.hasConnections())
this.statusItem.label.text = NMGtk.utils_get_connection_device_name(connection);
this.parent(connection);
},
adoptDevice: function(device) {
if (device.get_iface() == this.iface) {
this._setDevice(device);
if (device._description)
this.syncDescription();
this._updateStatusItem();
this.emit('state-changed');
return true;
} else
return false;
},
removeDevice: function(device) {
if (device == this.device) {
this._setDevice(null);
this._updateStatusItem();
this.emit('state-changed');
}
},
hasConnections: function() {
return this._connections.length != 0;
}
});
const NMVPNSection = new Lang.Class({ const NMVPNSection = new Lang.Class({
Name: 'NMVPNSection', Name: 'NMVPNSection',
Extends: NMConnectionBased, Extends: NMConnectionBased,
@ -1619,6 +1680,7 @@ const NMApplet = new Lang.Class({
this._nmDevices = []; this._nmDevices = [];
this._devices = { }; this._devices = { };
this._virtualDevices = [ ];
this._devices.wired = { this._devices.wired = {
section: new PopupMenu.PopupMenuSection(), section: new PopupMenu.PopupMenuSection(),
@ -1629,6 +1691,15 @@ const NMApplet = new Lang.Class({
this.menu.addMenuItem(this._devices.wired.section); this.menu.addMenuItem(this._devices.wired.section);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._devices.virtual = {
section: new PopupMenu.PopupMenuSection(),
devices: [ ],
};
this._devices.virtual.section.actor.hide();
this.menu.addMenuItem(this._devices.virtual.section);
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
this._devices.wireless = { this._devices.wireless = {
section: new PopupMenu.PopupMenuSection(), section: new PopupMenu.PopupMenuSection(),
devices: [ ], devices: [ ],
@ -1662,6 +1733,12 @@ const NMApplet = new Lang.Class({
this._dtypes[NetworkManager.DeviceType.INFINIBAND] = NMDeviceSimple; this._dtypes[NetworkManager.DeviceType.INFINIBAND] = NMDeviceSimple;
// TODO: WiMax support // TODO: WiMax support
// Virtual device types
this._vtypes = { };
this._vtypes[NetworkManager.SETTING_VLAN_SETTING_NAME] = NMDeviceVirtual;
this._vtypes[NetworkManager.SETTING_BOND_SETTING_NAME] = NMDeviceVirtual;
this._vtypes[NetworkManager.SETTING_BRIDGE_SETTING_NAME] = NMDeviceVirtual;
// Connection types // Connection types
this._ctypes = { }; this._ctypes = { };
this._ctypes[NetworkManager.SETTING_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS; this._ctypes[NetworkManager.SETTING_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS;
@ -1672,6 +1749,9 @@ const NMApplet = new Lang.Class({
this._ctypes[NetworkManager.SETTING_CDMA_SETTING_NAME] = NMConnectionCategory.WWAN; this._ctypes[NetworkManager.SETTING_CDMA_SETTING_NAME] = NMConnectionCategory.WWAN;
this._ctypes[NetworkManager.SETTING_GSM_SETTING_NAME] = NMConnectionCategory.WWAN; this._ctypes[NetworkManager.SETTING_GSM_SETTING_NAME] = NMConnectionCategory.WWAN;
this._ctypes[NetworkManager.SETTING_INFINIBAND_SETTING_NAME] = NMConnectionCategory.WIRED; this._ctypes[NetworkManager.SETTING_INFINIBAND_SETTING_NAME] = NMConnectionCategory.WIRED;
this._ctypes[NetworkManager.SETTING_VLAN_SETTING_NAME] = NMConnectionCategory.VIRTUAL;
this._ctypes[NetworkManager.SETTING_BOND_SETTING_NAME] = NMConnectionCategory.VIRTUAL;
this._ctypes[NetworkManager.SETTING_BRIDGE_SETTING_NAME] = NMConnectionCategory.VIRTUAL;
this._ctypes[NetworkManager.SETTING_VPN_SETTING_NAME] = NMConnectionCategory.VPN; this._ctypes[NetworkManager.SETTING_VPN_SETTING_NAME] = NMConnectionCategory.VPN;
this._settings = NMClient.RemoteSettings.new(null); this._settings = NMClient.RemoteSettings.new(null);
@ -1723,7 +1803,14 @@ const NMApplet = new Lang.Class({
let devices = this._devices[category].devices; let devices = this._devices[category].devices;
let item = this._devices[category].item; let item = this._devices[category].item;
let section = this._devices[category].section; let section = this._devices[category].section;
if (devices.length == 0)
let visible;
if (category == NMConnectionCategory.VIRTUAL)
visible = !section.isEmpty();
else
visible = devices.length > 0;
if (!visible)
section.actor.hide(); section.actor.hide();
else { else {
section.actor.show(); section.actor.show();
@ -1784,30 +1871,14 @@ const NMApplet = new Lang.Class({
MessageTray.Urgency.HIGH); MessageTray.Urgency.HIGH);
}, },
_makeWrapperDevice: function(wrapperClass, device) {
let wrapper = new wrapperClass(this._client, device, this._connections);
wrapper._activationFailedId = wrapper.connect('activation-failed',
Lang.bind(this, this._onActivationFailed));
wrapper._deviceStateChangedId = wrapper.connect('state-changed', Lang.bind(this, function(dev) {
this._syncSectionTitle(dev.category);
}));
wrapper._destroyId = wrapper.connect('destroy', function(wrapper) {
wrapper.disconnect(wrapper._activationFailedId);
wrapper.disconnect(wrapper._deviceStateChangedId);
wrapper.disconnect(wrapper._destroyId);
});
return wrapper;
},
_syncDeviceNames: function() { _syncDeviceNames: function() {
if (NMGtk) { if (NMGtk) {
let names = NMGtk.utils_disambiguate_device_names(this._nmDevices); let names = NMGtk.utils_disambiguate_device_names(this._nmDevices);
for (let i = 0; i < this._nmDevices.length; i++) { for (let i = 0; i < this._nmDevices.length; i++) {
let device = this._nmDevices[i]; let device = this._nmDevices[i];
device._description = names[i]; device._description = names[i];
device._delegate.syncDescription(); if (device._delegate)
device._delegate.syncDescription();
} }
} else { } else {
for (let i = 0; i < this._nmDevices.length; i++) { for (let i = 0; i < this._nmDevices.length; i++) {
@ -1822,41 +1893,75 @@ const NMApplet = new Lang.Class({
// already seen, not adding again // already seen, not adding again
return; return;
} }
for (let i = 0; i < this._virtualDevices.length; i++) {
if (this._virtualDevices[i].adoptDevice(device)) {
this._nmDevices.push(device);
if (!skipSyncDeviceNames)
this._syncDeviceNames();
return;
}
}
let wrapperClass = this._dtypes[device.get_device_type()]; let wrapperClass = this._dtypes[device.get_device_type()];
if (wrapperClass) { if (wrapperClass) {
let wrapper = this._makeWrapperDevice(wrapperClass, device); let wrapper = new wrapperClass(this._client, device, this._connections);
let section = this._devices[wrapper.category].section; this._addDeviceWrapper(wrapper);
let devices = this._devices[wrapper.category].devices;
section.addMenuItem(wrapper.statusItem);
section.addMenuItem(wrapper.section);
devices.push(wrapper);
this._nmDevices.push(device); this._nmDevices.push(device);
if (!skipSyncDeviceNames) if (!skipSyncDeviceNames)
this._syncDeviceNames(); this._syncDeviceNames();
this._syncSectionTitle(wrapper.category);
} }
}, },
_addDeviceWrapper: function(wrapper) {
wrapper._activationFailedId = wrapper.connect('activation-failed',
Lang.bind(this, this._onActivationFailed));
wrapper._deviceStateChangedId = wrapper.connect('state-changed', Lang.bind(this, function(dev) {
this._syncSectionTitle(dev.category);
}));
wrapper._destroyId = wrapper.connect('destroy', function(wrapper) {
wrapper.disconnect(wrapper._activationFailedId);
wrapper.disconnect(wrapper._deviceStateChangedId);
wrapper.disconnect(wrapper._destroyId);
});
let section = this._devices[wrapper.category].section;
section.addMenuItem(wrapper.statusItem);
section.addMenuItem(wrapper.section);
this._syncSectionTitle(wrapper.category);
let devices = this._devices[wrapper.category].devices;
devices.push(wrapper);
},
_deviceRemoved: function(client, device) { _deviceRemoved: function(client, device) {
if (!device._delegate) { let pos = this._nmDevices.indexOf(device);
if (pos != -1) {
this._nmDevices.splice(pos, 1);
this._syncDeviceNames();
}
let wrapper = device._delegate;
if (!wrapper) {
log('Removing a network device that was not added'); log('Removing a network device that was not added');
return; return;
} }
let wrapper = device._delegate; if (wrapper instanceof NMDeviceVirtual)
wrapper.removeDevice(device);
else
this._removeDeviceWrapper(wrapper);
},
_removeDeviceWrapper: function(wrapper) {
wrapper.destroy(); wrapper.destroy();
let devices = this._devices[wrapper.category].devices; let devices = this._devices[wrapper.category].devices;
let pos = devices.indexOf(wrapper); let pos = devices.indexOf(wrapper);
devices.splice(pos, 1); devices.splice(pos, 1);
pos = this._nmDevices.indexOf(device);
this._nmDevices.splice(pos, 1);
this._syncDeviceNames();
this._syncSectionTitle(wrapper.category) this._syncSectionTitle(wrapper.category)
}, },
@ -2051,6 +2156,13 @@ const NMApplet = new Lang.Class({
devices[i].removeConnection(connection); devices[i].removeConnection(connection);
} }
if (section == NMConnectionCategory.VIRTUAL) {
let iface = connection.get_virtual_iface_name();
let wrapper = this._findVirtualDevice(iface);
if (wrapper && !wrapper.hasConnections())
this._removeDeviceWrapper(wrapper);
}
connection.disconnect(connection._removedId); connection.disconnect(connection._removedId);
connection.disconnect(connection._updatedId); connection.disconnect(connection._updatedId);
connection._removedId = connection._updatedId = 0; connection._removedId = connection._updatedId = 0;
@ -2064,6 +2176,27 @@ const NMApplet = new Lang.Class({
let section = connection._section; let section = connection._section;
if (section == NMConnectionCategory.VIRTUAL) {
let wrapperClass = this._vtypes[connection._type];
if (!wrapperClass)
return;
let iface = connection.get_virtual_iface_name();
let wrapper = this._findVirtualDevice(iface);
if (!wrapper) {
wrapper = new wrapperClass(this._client, iface, this._connections);
this._addDeviceWrapper(wrapper);
this._virtualDevices.push(wrapper);
// We might already have a device for this connection
for (let i = 0; i < this._nmDevices.length; i++) {
let device = this._nmDevices[i];
if (wrapper.adoptDevice(device))
break;
}
}
}
if (section == NMConnectionCategory.INVALID) if (section == NMConnectionCategory.INVALID)
return; return;
if (section == NMConnectionCategory.VPN) { if (section == NMConnectionCategory.VPN) {
@ -2076,6 +2209,15 @@ const NMApplet = new Lang.Class({
} }
}, },
_findVirtualDevice: function(iface) {
for (let i = 0; i < this._virtualDevices.length; i++) {
if (this._virtualDevices[i].iface == iface)
return this._virtualDevices[i];
}
return null;
},
_hideDevices: function() { _hideDevices: function() {
this._devicesHidden = true; this._devicesHidden = true;
@ -2091,6 +2233,7 @@ const NMApplet = new Lang.Class({
this._statusSection.actor.hide(); this._statusSection.actor.hide();
this._syncSectionTitle(NMConnectionCategory.WIRED); this._syncSectionTitle(NMConnectionCategory.WIRED);
this._syncSectionTitle(NMConnectionCategory.VIRTUAL);
this._syncSectionTitle(NMConnectionCategory.WIRELESS); this._syncSectionTitle(NMConnectionCategory.WIRELESS);
this._syncSectionTitle(NMConnectionCategory.WWAN); this._syncSectionTitle(NMConnectionCategory.WWAN);
}, },
@ -2128,6 +2271,7 @@ const NMApplet = new Lang.Class({
this.setIcon('network-wireless-acquiring-symbolic'); this.setIcon('network-wireless-acquiring-symbolic');
break; break;
case NMConnectionCategory.WIRED: case NMConnectionCategory.WIRED:
case NMConnectionCategory.VIRTUAL:
this.setIcon('network-wired-acquiring-symbolic'); this.setIcon('network-wired-acquiring-symbolic');
break; break;
default: default:
@ -2167,6 +2311,7 @@ const NMApplet = new Lang.Class({
break; break;
} }
case NMConnectionCategory.WIRED: case NMConnectionCategory.WIRED:
case NMConnectionCategory.VIRTUAL:
this.setIcon('network-wired-symbolic'); this.setIcon('network-wired-symbolic');
break; break;
case NMConnectionCategory.WWAN: case NMConnectionCategory.WWAN: