Merge lp:~naaando/wingpanel-indicator-network/vpn into lp:~wingpanel-devs/wingpanel-indicator-network/trunk
- vpn
- Merge into trunk
Proposed by
Fernando da Silva Sousa
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Danielle Foré | ||||
Approved revision: | 230 | ||||
Merged at revision: | 233 | ||||
Proposed branch: | lp:~naaando/wingpanel-indicator-network/vpn | ||||
Merge into: | lp:~wingpanel-devs/wingpanel-indicator-network/trunk | ||||
Diff against target: |
860 lines (+619/-171) 6 files modified
src/CMakeLists.txt (+3/-0) src/Widgets/PopoverWidget.vala (+7/-0) src/Widgets/VpnInterface.vala (+96/-0) src/common/Widgets/AbstractVpnInterface.vala (+206/-0) src/common/Widgets/NMVisualizer.vala (+176/-171) src/common/Widgets/VpnMenuItem.vala (+131/-0) |
||||
To merge this branch: | bzr merge lp:~naaando/wingpanel-indicator-network/vpn | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Adam Bieńkowski (community) | code | Approve | |
Danielle Foré | ux | Approve | |
Kirill Romanov (community) | test | Approve | |
Review via email: mp+321350@code.launchpad.net |
Commit message
Show configured VPNs
Description of the change
Adds VPN menu
To post a comment you must log in.
- 226. By Fernando da Silva Sousa
-
Fix small typos
- 227. By Fernando da Silva Sousa
-
* Fix wingpanel crashing when a VPN is added all being removed
* Hide VPN menu if there's no VPN setup
Revision history for this message
Fernando da Silva Sousa (naaando) wrote : | # |
I made the fixes, under the hood the menu is hidden when there are no VPN connections now.
Revision history for this message
Danielle Foré (danrabbit) wrote : | # |
I can confirm that the VPN section isn't present while I have no VPN configured :)
Revision history for this message
Kirill Romanov (djaler1) wrote : | # |
I can confirm that it works good for me!
review:
Approve
(test)
Revision history for this message
Kirill Romanov (djaler1) wrote : | # |
I have some VPN connections, and I succesfully managed to connect and switch between them
Revision history for this message
Adam Bieńkowski (donadigo) wrote : | # |
One little thing in the diff comment.
review:
Needs Fixing
(code)
Revision history for this message
Danielle Foré (danrabbit) : | # |
review:
Approve
(ux)
- 228. By Fernando da Silva Sousa
-
Rename function that create VPN
- 229. By Fernando da Silva Sousa
-
Fix coding style
- 230. By Fernando da Silva Sousa
-
Merge trunk
Revision history for this message
Adam Bieńkowski (donadigo) wrote : | # |
Thanks. I can let that go now.
review:
Approve
(code)
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/CMakeLists.txt' |
2 | --- src/CMakeLists.txt 2016-10-29 20:28:52 +0000 |
3 | +++ src/CMakeLists.txt 2017-04-23 18:39:47 +0000 |
4 | @@ -17,15 +17,18 @@ |
5 | Widgets/DisplayWidget.vala |
6 | Widgets/PopoverWidget.vala |
7 | Widgets/EtherInterface.vala |
8 | + Widgets/VpnInterface.vala |
9 | Widgets/WifiInterface.vala |
10 | common/Utils.vala |
11 | common/Widgets/WifiMenuItem.vala |
12 | + common/Widgets/VpnMenuItem.vala |
13 | common/Widgets/NMVisualizer.vala |
14 | common/Widgets/WidgetNMInterface.vala |
15 | Services/SettingsManager.vala |
16 | ${CMAKE_CURRENT_BINARY_DIR}/config.vala |
17 | common/rfkill.vala |
18 | common/Widgets/AbstractWifiInterface.vala |
19 | + common/Widgets/AbstractVpnInterface.vala |
20 | common/Widgets/AbstractEtherInterface.vala |
21 | |
22 | PACKAGES |
23 | |
24 | === modified file 'src/Widgets/PopoverWidget.vala' |
25 | --- src/Widgets/PopoverWidget.vala 2016-11-19 14:50:46 +0000 |
26 | +++ src/Widgets/PopoverWidget.vala 2017-04-23 18:39:47 +0000 |
27 | @@ -19,6 +19,7 @@ |
28 | public class Network.Widgets.PopoverWidget : Network.Widgets.NMVisualizer { |
29 | private Gtk.Box other_box; |
30 | private Gtk.Box wifi_box; |
31 | + private Gtk.Box vpn_box; |
32 | private Wingpanel.Widgets.Button show_settings_button; |
33 | private Wingpanel.Widgets.Button hidden_item; |
34 | |
35 | @@ -47,8 +48,10 @@ |
36 | |
37 | other_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); |
38 | wifi_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); |
39 | + vpn_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); |
40 | add (other_box); |
41 | add (wifi_box); |
42 | + add (vpn_box); |
43 | |
44 | if (!is_dm ()) { |
45 | hidden_item = new Wingpanel.Widgets.Button (_("Connect to Hidden Network…")); |
46 | @@ -90,6 +93,10 @@ |
47 | }); |
48 | } |
49 | |
50 | + if (widget_interface is Network.VpnInterface) { |
51 | + container_box = vpn_box; |
52 | + } |
53 | + |
54 | if (!is_dm () || get_children ().length () > 0) { |
55 | widget_interface.sep = new Wingpanel.Widgets.Separator (); |
56 | container_box.pack_end (widget_interface.sep); |
57 | |
58 | === added file 'src/Widgets/VpnInterface.vala' |
59 | --- src/Widgets/VpnInterface.vala 1970-01-01 00:00:00 +0000 |
60 | +++ src/Widgets/VpnInterface.vala 2017-04-23 18:39:47 +0000 |
61 | @@ -0,0 +1,96 @@ |
62 | +/* |
63 | + * Copyright (c) 2017 elementary LLC. (http://launchpad.net/elementary) |
64 | + * |
65 | + * This program is free software: you can redistribute it and/or modify |
66 | + * it under the terms of the GNU Library General Public License as published by |
67 | + * the Free Software Foundation, either version 2.1 of the License, or |
68 | + * (at your option) any later version. |
69 | + * |
70 | + * This program is distributed in the hope that it will be useful, |
71 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
72 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
73 | + * GNU Library General Public License for more details. |
74 | + * |
75 | + * You should have received a copy of the GNU Library General Public License |
76 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
77 | + */ |
78 | + |
79 | +public class Network.VpnInterface : Network.AbstractVpnInterface { |
80 | + private Wingpanel.Widgets.Switch vpn_item; |
81 | + Gtk.Revealer revealer; |
82 | + |
83 | + public VpnInterface (NM.Client nm_client, NM.RemoteSettings nm_settings) { |
84 | + init_vpn_interface (nm_client, nm_settings); |
85 | + vpn_item.set_caption (display_title); |
86 | + debug ("Starting VPN Interface"); |
87 | + |
88 | + vpn_item.get_style_context ().add_class ("h4"); |
89 | + vpn_item.switched.connect (() => { |
90 | + revealer.reveal_child = vpn_item.get_active (); |
91 | + if (!vpn_item.get_active ()) { |
92 | + vpn_deactivate_cb (); |
93 | + } |
94 | + }); |
95 | + |
96 | + vpn_list.add.connect (check_vpn_availability); |
97 | + vpn_list.remove.connect (check_vpn_availability); |
98 | + |
99 | + notify["vpn_state"].connect (update); |
100 | + } |
101 | + |
102 | + construct { |
103 | + orientation = Gtk.Orientation.VERTICAL; |
104 | + vpn_item = new Wingpanel.Widgets.Switch (""); |
105 | + vpn_item.get_style_context ().add_class ("h4"); |
106 | + pack_start (vpn_item); |
107 | + |
108 | + var scrolled_box = new Wingpanel.Widgets.AutomaticScrollBox (null, null); |
109 | + scrolled_box.set_policy (Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); |
110 | + scrolled_box.add (vpn_list); |
111 | + |
112 | + revealer = new Gtk.Revealer (); |
113 | + revealer.add (scrolled_box); |
114 | + pack_start (revealer); |
115 | + } |
116 | + |
117 | + public override void update () { |
118 | + base.update (); |
119 | + |
120 | + check_vpn_availability (); |
121 | + if (active_vpn_item != null) { |
122 | + vpn_item.set_active (true); |
123 | + } |
124 | + } |
125 | + |
126 | + private void check_vpn_availability () { |
127 | + var length = vpn_list.get_children ().length (); |
128 | + // The first item is the blank item |
129 | + show_vpn (length > 1); |
130 | + } |
131 | + |
132 | + private void show_vpn (bool show) { |
133 | + no_show_all = sep.no_show_all = !show; |
134 | + visible = sep.visible = show; |
135 | + } |
136 | + |
137 | + protected override void vpn_activate_cb (VpnMenuItem item) { |
138 | + warning ("Activating connection"); |
139 | + vpn_deactivate_cb (); |
140 | + |
141 | + debug ("Connecting to VPN : %s", item.connection.get_id ()); |
142 | + |
143 | + nm_client.activate_connection (item.connection, null, null, null); |
144 | + active_vpn_item = item; |
145 | + Idle.add (() => { update (); return false; }); |
146 | + } |
147 | + |
148 | + protected override void vpn_deactivate_cb () { |
149 | + if (active_vpn_connection == null) { |
150 | + update (); |
151 | + return; |
152 | + } |
153 | + debug ("Deactivating VPN : %s", active_vpn_connection.get_id ()); |
154 | + nm_client.deactivate_connection (active_vpn_connection); |
155 | + Idle.add (() => { update (); return false; }); |
156 | + } |
157 | +} |
158 | |
159 | === added file 'src/common/Widgets/AbstractVpnInterface.vala' |
160 | --- src/common/Widgets/AbstractVpnInterface.vala 1970-01-01 00:00:00 +0000 |
161 | +++ src/common/Widgets/AbstractVpnInterface.vala 2017-04-23 18:39:47 +0000 |
162 | @@ -0,0 +1,206 @@ |
163 | +/* |
164 | + * Copyright (c) 2017 elementary LLC. (http://launchpad.net/elementary) |
165 | + * |
166 | + * This program is free software: you can redistribute it and/or modify |
167 | + * it under the terms of the GNU Library General Public License as published by |
168 | + * the Free Software Foundation, either version 2.1 of the License, or |
169 | + * (at your option) any later version. |
170 | + * |
171 | + * This program is distributed in the hope that it will be useful, |
172 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
173 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
174 | + * GNU Library General Public License for more details. |
175 | + * |
176 | + * You should have received a copy of the GNU Library General Public License |
177 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
178 | + */ |
179 | + |
180 | +public abstract class Network.AbstractVpnInterface : Network.WidgetNMInterface { |
181 | + protected NM.VPNConnection? active_vpn_connection = null; |
182 | + |
183 | + protected Gtk.ListBox vpn_list; |
184 | + |
185 | + protected NM.Client nm_client; |
186 | + public NM.RemoteSettings nm_settings; |
187 | + |
188 | + protected VpnMenuItem? active_vpn_item { get; set; } |
189 | + protected VpnMenuItem? blank_item = null; |
190 | + protected Gtk.Stack placeholder; |
191 | + |
192 | + /** |
193 | + * If we want to add a visual feedback on DisplayWidget later, |
194 | + * we just need to remove vpn_state and swap it to state on the code |
195 | + **/ |
196 | + public Network.State vpn_state { get; protected set; default = Network.State.DISCONNECTED; } |
197 | + |
198 | + public void init_vpn_interface (NM.Client _nm_client, NM.RemoteSettings _nm_settings) { |
199 | + nm_client = _nm_client; |
200 | + nm_settings = _nm_settings; |
201 | + display_title = _("VPN"); |
202 | + |
203 | + blank_item = new VpnMenuItem.blank (); |
204 | + vpn_list.add (blank_item); |
205 | + active_vpn_item = null; |
206 | + |
207 | + /* Advices that no Vpn has been configured */ |
208 | + var no_vpn_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 6); |
209 | + no_vpn_box.visible = true; |
210 | + no_vpn_box.valign = Gtk.Align.CENTER; |
211 | + |
212 | + var no_vpn = construct_placeholder_label (_("No VPN Available"), true); |
213 | + no_vpn_box.add (no_vpn); |
214 | + |
215 | + placeholder.add_named (no_vpn_box, "no-vpn"); |
216 | + placeholder.visible_child_name = "no-vpn"; |
217 | + |
218 | + nm_settings.connections_read.connect (update); |
219 | + nm_client.notify["active-connections"].connect (update); |
220 | + nm_settings.new_connection.connect (vpn_added_cb); |
221 | + |
222 | + update (); |
223 | + } |
224 | + |
225 | + construct { |
226 | + placeholder = new Gtk.Stack (); |
227 | + placeholder.visible = true; |
228 | + |
229 | + vpn_list = new Gtk.ListBox (); |
230 | + // Single click is disabled because it's being handled by VpnMenuItem |
231 | + vpn_list.activate_on_single_click = false; |
232 | + vpn_list.visible = true; |
233 | + vpn_list.set_placeholder (placeholder); |
234 | + } |
235 | + |
236 | + public override void update () { |
237 | + update_active_connection (); |
238 | + |
239 | + VpnMenuItem? item = null; |
240 | + |
241 | + if (active_vpn_connection != null) { |
242 | + switch (active_vpn_connection.vpn_state) { |
243 | + case NM.VPNConnectionState.UNKNOWN: |
244 | + case NM.VPNConnectionState.DISCONNECTED: |
245 | + vpn_state = State.DISCONNECTED; |
246 | + active_vpn_item = null; |
247 | + break; |
248 | + case NM.VPNConnectionState.PREPARE: |
249 | + case NM.VPNConnectionState.IP_CONFIG_GET: |
250 | + case NM.VPNConnectionState.CONNECT: |
251 | + vpn_state = State.CONNECTING_VPN; |
252 | + item = get_item_by_uuid (active_vpn_connection.get_uuid ()); |
253 | + break; |
254 | + case NM.VPNConnectionState.FAILED: |
255 | + vpn_state = State.FAILED_VPN; |
256 | + active_vpn_item = null; |
257 | + break; |
258 | + case NM.VPNConnectionState.ACTIVATED: |
259 | + vpn_state = State.CONNECTED_VPN; |
260 | + item = get_item_by_uuid (active_vpn_connection.get_uuid ()); |
261 | + sensitive = true; |
262 | + break; |
263 | + } |
264 | + } else { |
265 | + vpn_state = State.DISCONNECTED; |
266 | + } |
267 | + |
268 | + if (item == null) { |
269 | + blank_item.set_active (true); |
270 | + |
271 | + if (active_vpn_item != null) { |
272 | + active_vpn_item.no_show_all = false; |
273 | + active_vpn_item.visible = true; |
274 | + active_vpn_item.vpn_state = vpn_state; |
275 | + } |
276 | + } |
277 | + |
278 | + base.update (); |
279 | + } |
280 | + |
281 | + protected Gtk.Label construct_placeholder_label (string text, bool title = false) { |
282 | + var label = new Gtk.Label (text); |
283 | + label.visible = true; |
284 | + label.use_markup = true; |
285 | + label.wrap = true; |
286 | + label.wrap_mode = Pango.WrapMode.WORD_CHAR; |
287 | + label.max_width_chars = 30; |
288 | + label.justify = Gtk.Justification.CENTER; |
289 | + |
290 | + if (title) { |
291 | +#if PLUG_NETWORK |
292 | + label.get_style_context ().add_class ("h2"); |
293 | +#endif |
294 | + } |
295 | + |
296 | + return label; |
297 | + } |
298 | + |
299 | + /** |
300 | + * The vpn_added_cb is called on new_connection signal, |
301 | + * (we get the vpn connections from there) |
302 | + * then we filter the connection that make sense for us. |
303 | + */ |
304 | + void vpn_added_cb (Object obj) { |
305 | + var vpn = (NM.RemoteConnection)obj; |
306 | + switch (vpn.get_connection_type ()) { |
307 | + case NM.SettingVpn.SETTING_NAME: |
308 | + // Remove vpn when it's removed in switchboard-plug-networking |
309 | + vpn.removed.connect (vpn_removed_cb); |
310 | + |
311 | + // Add the item to vpn_list |
312 | + var item = new VpnMenuItem (vpn); |
313 | + item.set_visible (true); |
314 | + item.user_action.connect (vpn_activate_cb); |
315 | + |
316 | + vpn_list.add (item); |
317 | + update (); |
318 | + break; |
319 | + default: |
320 | + break; |
321 | + } |
322 | + } |
323 | + |
324 | + // Removed vpn, from removed signal attached to connection when it get added. |
325 | + void vpn_removed_cb (NM.RemoteConnection vpn_) { |
326 | + var item = get_item_by_uuid (vpn_.get_uuid ()); |
327 | + item.destroy (); |
328 | + } |
329 | + |
330 | + private VpnMenuItem? get_item_by_uuid (string uuid) { |
331 | + VpnMenuItem? item = null; |
332 | + foreach (var child in vpn_list.get_children ()) { |
333 | + var _item = (VpnMenuItem)child; |
334 | + if (_item.connection != null && _item.connection.get_uuid () == uuid && item == null) { |
335 | + item = (VpnMenuItem)child; |
336 | + } |
337 | + } |
338 | + |
339 | + return item; |
340 | + } |
341 | + |
342 | + /** |
343 | + * Loop through each active connection to find out the vpn. |
344 | + */ |
345 | + protected void update_active_connection () { |
346 | + active_vpn_connection = null; |
347 | + |
348 | + nm_client.get_active_connections ().foreach ((ac) => { |
349 | + if (ac.get_vpn () && active_vpn_connection == null) { |
350 | + active_vpn_connection = (NM.VPNConnection)ac; |
351 | + active_vpn_connection.vpn_state_changed.connect (update); |
352 | + |
353 | + foreach (var v in vpn_list.get_children ()) { |
354 | + var menu_item = (VpnMenuItem) v; |
355 | + |
356 | + if (menu_item.connection.get_uuid () == active_vpn_connection.uuid) { |
357 | + menu_item.set_active (true); |
358 | + active_vpn_item = menu_item; |
359 | + active_vpn_item.vpn_state = vpn_state; |
360 | + } |
361 | + } |
362 | + } |
363 | + }); |
364 | + } |
365 | + |
366 | + protected abstract void vpn_activate_cb (VpnMenuItem i); |
367 | + protected abstract void vpn_deactivate_cb (); |
368 | +} |
369 | |
370 | === modified file 'src/common/Widgets/NMVisualizer.vala' |
371 | --- src/common/Widgets/NMVisualizer.vala 2017-01-06 14:22:24 +0000 |
372 | +++ src/common/Widgets/NMVisualizer.vala 2017-04-23 18:39:47 +0000 |
373 | @@ -16,175 +16,180 @@ |
374 | */ |
375 | |
376 | public abstract class Network.Widgets.NMVisualizer : Gtk.Grid { |
377 | - protected NM.Client nm_client; |
378 | - protected NM.RemoteSettings nm_settings; |
379 | - protected NM.VPNConnection? active_vpn_connection = null; |
380 | - |
381 | - protected GLib.List<WidgetNMInterface>? network_interface; |
382 | - |
383 | - public bool secure { private set; get; default = false; } |
384 | - public Network.State state { private set; get; default = Network.State.CONNECTING_WIRED; } |
385 | - |
386 | - construct { |
387 | - network_interface = new GLib.List<WidgetNMInterface>(); |
388 | - |
389 | - build_ui (); |
390 | - |
391 | - /* Monitor network manager */ |
392 | - nm_client = new NM.Client (); |
393 | - nm_settings = new NM.RemoteSettings (null); |
394 | - |
395 | - nm_client.notify["active-connections"].connect (update_vpn_connection); |
396 | - |
397 | - nm_client.device_added.connect (device_added_cb); |
398 | - nm_client.device_removed.connect (device_removed_cb); |
399 | - |
400 | - nm_client.notify["networking-enabled"].connect (update_state); |
401 | - |
402 | - var devices = nm_client.get_devices (); |
403 | - for (var i = 0; i < devices.length; i++) |
404 | - device_added_cb (devices.get (i)); |
405 | - |
406 | - show_all(); |
407 | - update_vpn_connection (); |
408 | - } |
409 | - |
410 | - protected abstract void build_ui (); |
411 | - protected abstract void add_interface (WidgetNMInterface widget_interface); |
412 | - protected abstract void remove_interface (WidgetNMInterface widget_interface); |
413 | - |
414 | - void device_removed_cb (NM.Device device) { |
415 | - foreach (var widget_interface in network_interface) { |
416 | - if (widget_interface.is_device (device)) { |
417 | - network_interface.remove (widget_interface); |
418 | - |
419 | - // Implementation call |
420 | - remove_interface (widget_interface); |
421 | - break; |
422 | - } |
423 | - } |
424 | - |
425 | - update_interfaces_names (); |
426 | - } |
427 | - |
428 | - void update_interfaces_names () { |
429 | - var count_type = new Gee.HashMap<string, int?> (); |
430 | - foreach (var iface in network_interface) { |
431 | - var type = iface.get_type ().name (); |
432 | - if (count_type.has_key (type)) { |
433 | - count_type[type] = count_type[type] + 1; |
434 | - } else { |
435 | - count_type[type] = 1; |
436 | - } |
437 | - } |
438 | - |
439 | - foreach (var iface in network_interface) { |
440 | - var type = iface.get_type ().name (); |
441 | - iface.update_name (count_type [type]); |
442 | - } |
443 | - } |
444 | - |
445 | - private void device_added_cb (NM.Device device) { |
446 | - if (device.get_iface ().has_prefix ("vmnet") || |
447 | - device.get_iface ().has_prefix ("lo") || |
448 | - device.get_iface ().has_prefix ("veth")) { |
449 | - return; |
450 | - } |
451 | - |
452 | - WidgetNMInterface? widget_interface = null; |
453 | -#if PLUG_NETWORK |
454 | - WidgetNMInterface? hotspot_interface = null; |
455 | -#endif |
456 | - |
457 | - if (device is NM.DeviceWifi) { |
458 | - widget_interface = new WifiInterface (nm_client, nm_settings, device); |
459 | -#if PLUG_NETWORK |
460 | - hotspot_interface = new HotspotInterface((WifiInterface)widget_interface); |
461 | -#endif |
462 | - |
463 | - debug ("Wifi interface added"); |
464 | - } else if (device is NM.DeviceEthernet) { |
465 | - widget_interface = new EtherInterface (nm_client, nm_settings, device); |
466 | - debug ("Wired interface added"); |
467 | - } else { |
468 | - debug ("Unknown device: %s\n", device.get_device_type().to_string()); |
469 | - } |
470 | - |
471 | - if (widget_interface != null) { |
472 | - // Implementation call |
473 | - network_interface.append (widget_interface); |
474 | - add_interface(widget_interface); |
475 | - widget_interface.notify["state"].connect(update_state); |
476 | - |
477 | - } |
478 | - |
479 | -#if PLUG_NETWORK |
480 | - if (hotspot_interface != null) { |
481 | - // Implementation call |
482 | - network_interface.append (hotspot_interface); |
483 | - add_interface(hotspot_interface); |
484 | - hotspot_interface.notify["state"].connect(update_state); |
485 | - |
486 | - } |
487 | -#endif |
488 | - |
489 | - update_interfaces_names (); |
490 | - update_all (); |
491 | - show_all (); |
492 | - } |
493 | - |
494 | - void update_all () { |
495 | - foreach(var inter in network_interface) { |
496 | - inter.update (); |
497 | - } |
498 | - } |
499 | - |
500 | - void update_state () { |
501 | - if (!nm_client.networking_get_enabled ()) { |
502 | - state = Network.State.DISCONNECTED_AIRPLANE_MODE; |
503 | - } else { |
504 | - var next_state = Network.State.DISCONNECTED; |
505 | - var best_score = int.MAX; |
506 | - |
507 | - foreach (var inter in network_interface) { |
508 | - var score = inter.state.get_priority(); |
509 | - |
510 | - if (score < best_score) { |
511 | - next_state = inter.state; |
512 | - best_score = score; |
513 | - } |
514 | - } |
515 | - |
516 | - state = next_state; |
517 | - } |
518 | - } |
519 | - |
520 | - void update_vpn_connection () { |
521 | - active_vpn_connection = null; |
522 | - |
523 | - nm_client.get_active_connections ().foreach ((ac) => { |
524 | - if (active_vpn_connection == null && ac.get_vpn ()) { |
525 | - active_vpn_connection = (NM.VPNConnection) ac; |
526 | - update_vpn_state (active_vpn_connection.get_vpn_state ()); |
527 | - active_vpn_connection.vpn_state_changed.connect (() => { |
528 | - update_vpn_state (active_vpn_connection.get_vpn_state ()); |
529 | - }); |
530 | - } |
531 | - }); |
532 | - } |
533 | - |
534 | - void update_vpn_state (NM.VPNConnectionState state) { |
535 | - switch (state) { |
536 | - case NM.VPNConnectionState.DISCONNECTED: |
537 | - case NM.VPNConnectionState.PREPARE: |
538 | - case NM.VPNConnectionState.IP_CONFIG_GET: |
539 | - case NM.VPNConnectionState.CONNECT: |
540 | - case NM.VPNConnectionState.FAILED: |
541 | - secure = false; |
542 | - break; |
543 | - case NM.VPNConnectionState.ACTIVATED: |
544 | - secure = true; |
545 | - break; |
546 | - } |
547 | - } |
548 | + protected NM.Client nm_client; |
549 | + protected NM.RemoteSettings nm_settings; |
550 | + protected NM.VPNConnection? active_vpn_connection = null; |
551 | + |
552 | + protected GLib.List<WidgetNMInterface>? network_interface; |
553 | + |
554 | + public bool secure { private set; get; default = false; } |
555 | + public Network.State state { private set; get; default = Network.State.CONNECTING_WIRED; } |
556 | + |
557 | + construct { |
558 | + network_interface = new GLib.List<WidgetNMInterface>(); |
559 | + |
560 | + build_ui (); |
561 | + |
562 | + /* Monitor network manager */ |
563 | + nm_client = new NM.Client (); |
564 | + nm_settings = new NM.RemoteSettings (null); |
565 | + |
566 | + nm_client.notify["active-connections"].connect (update_vpn_connection); |
567 | + |
568 | + nm_client.device_added.connect (device_added_cb); |
569 | + nm_client.device_removed.connect (device_removed_cb); |
570 | + |
571 | + nm_client.notify["networking-enabled"].connect (update_state); |
572 | + |
573 | + var devices = nm_client.get_devices (); |
574 | + for (var i = 0; i < devices.length; i++) |
575 | + device_added_cb (devices.get (i)); |
576 | + |
577 | + // Vpn interface |
578 | + create_vpn_interface (); |
579 | + |
580 | + show_all(); |
581 | + update_vpn_connection (); |
582 | + } |
583 | + |
584 | + protected abstract void build_ui (); |
585 | + protected abstract void add_interface (WidgetNMInterface widget_interface); |
586 | + protected abstract void remove_interface (WidgetNMInterface widget_interface); |
587 | + |
588 | + void device_removed_cb (NM.Device device) { |
589 | + foreach (var widget_interface in network_interface) { |
590 | + if (widget_interface.is_device (device)) { |
591 | + network_interface.remove (widget_interface); |
592 | + |
593 | + // Implementation call |
594 | + remove_interface (widget_interface); |
595 | + break; |
596 | + } |
597 | + } |
598 | + |
599 | + update_interfaces_names (); |
600 | + } |
601 | + |
602 | + void update_interfaces_names () { |
603 | + var count_type = new Gee.HashMap<string, int?> (); |
604 | + foreach (var iface in network_interface) { |
605 | + var type = iface.get_type ().name (); |
606 | + if (count_type.has_key (type)) { |
607 | + count_type[type] = count_type[type] + 1; |
608 | + } else { |
609 | + count_type[type] = 1; |
610 | + } |
611 | + } |
612 | + |
613 | + foreach (var iface in network_interface) { |
614 | + var type = iface.get_type ().name (); |
615 | + iface.update_name (count_type [type]); |
616 | + } |
617 | + } |
618 | + |
619 | + private void device_added_cb (NM.Device device) { |
620 | + if (device.get_iface ().has_prefix ("vmnet") || |
621 | + device.get_iface ().has_prefix ("lo") || |
622 | + device.get_iface ().has_prefix ("veth")) { |
623 | + return; |
624 | + } |
625 | + |
626 | + WidgetNMInterface? widget_interface = null; |
627 | +#if PLUG_NETWORK |
628 | + WidgetNMInterface? hotspot_interface = null; |
629 | +#endif |
630 | + |
631 | + if (device is NM.DeviceWifi) { |
632 | + widget_interface = new WifiInterface (nm_client, nm_settings, device); |
633 | +#if PLUG_NETWORK |
634 | + hotspot_interface = new HotspotInterface((WifiInterface)widget_interface); |
635 | +#endif |
636 | + |
637 | + debug ("Wifi interface added"); |
638 | + } else if (device is NM.DeviceEthernet) { |
639 | + widget_interface = new EtherInterface (nm_client, nm_settings, device); |
640 | + debug ("Wired interface added"); |
641 | + } else { |
642 | + debug ("Unknown device: %s\n", device.get_device_type().to_string()); |
643 | + } |
644 | + |
645 | + if (widget_interface != null) { |
646 | + // Implementation call |
647 | + network_interface.append (widget_interface); |
648 | + add_interface(widget_interface); |
649 | + widget_interface.notify["state"].connect(update_state); |
650 | + |
651 | + } |
652 | + |
653 | +#if PLUG_NETWORK |
654 | + if (hotspot_interface != null) { |
655 | + // Implementation call |
656 | + network_interface.append (hotspot_interface); |
657 | + add_interface(hotspot_interface); |
658 | + hotspot_interface.notify["state"].connect(update_state); |
659 | + |
660 | + } |
661 | +#endif |
662 | + |
663 | + update_interfaces_names (); |
664 | + update_all (); |
665 | + show_all (); |
666 | + } |
667 | + |
668 | + private void create_vpn_interface () { |
669 | + WidgetNMInterface widget_interface = new VpnInterface (nm_client, nm_settings); |
670 | + network_interface.append (widget_interface); |
671 | + add_interface (widget_interface); |
672 | + widget_interface.notify["state"].connect (update_state); |
673 | + } |
674 | + |
675 | + void update_all () { |
676 | + foreach(var inter in network_interface) { |
677 | + inter.update (); |
678 | + } |
679 | + } |
680 | + |
681 | + void update_state () { |
682 | + if (!nm_client.networking_get_enabled ()) { |
683 | + state = Network.State.DISCONNECTED_AIRPLANE_MODE; |
684 | + } else { |
685 | + var next_state = Network.State.DISCONNECTED; |
686 | + foreach (var inter in network_interface) { |
687 | + if (inter.state != Network.State.DISCONNECTED) { |
688 | + next_state = inter.state; |
689 | + } |
690 | + } |
691 | + |
692 | + state = next_state; |
693 | + } |
694 | + } |
695 | + |
696 | + void update_vpn_connection () { |
697 | + active_vpn_connection = null; |
698 | + |
699 | + nm_client.get_active_connections ().foreach ((ac) => { |
700 | + if (active_vpn_connection == null && ac.get_vpn ()) { |
701 | + active_vpn_connection = (NM.VPNConnection) ac; |
702 | + update_vpn_state (active_vpn_connection.get_vpn_state ()); |
703 | + active_vpn_connection.vpn_state_changed.connect (() => { |
704 | + update_vpn_state (active_vpn_connection.get_vpn_state ()); |
705 | + }); |
706 | + } |
707 | + }); |
708 | + } |
709 | + |
710 | + void update_vpn_state (NM.VPNConnectionState state) { |
711 | + switch (state) { |
712 | + case NM.VPNConnectionState.DISCONNECTED: |
713 | + case NM.VPNConnectionState.PREPARE: |
714 | + case NM.VPNConnectionState.IP_CONFIG_GET: |
715 | + case NM.VPNConnectionState.CONNECT: |
716 | + case NM.VPNConnectionState.FAILED: |
717 | + secure = false; |
718 | + break; |
719 | + case NM.VPNConnectionState.ACTIVATED: |
720 | + secure = true; |
721 | + break; |
722 | + } |
723 | + } |
724 | } |
725 | |
726 | === added file 'src/common/Widgets/VpnMenuItem.vala' |
727 | --- src/common/Widgets/VpnMenuItem.vala 1970-01-01 00:00:00 +0000 |
728 | +++ src/common/Widgets/VpnMenuItem.vala 2017-04-23 18:39:47 +0000 |
729 | @@ -0,0 +1,131 @@ |
730 | +/* |
731 | + * Copyright (c) 2017 elementary LLC. (http://launchpad.net/elementary) |
732 | + * |
733 | + * This program is free software: you can redistribute it and/or modify |
734 | + * it under the terms of the GNU Library General Public License as published by |
735 | + * the Free Software Foundation, either version 2.1 of the License, or |
736 | + * (at your option) any later version. |
737 | + * |
738 | + * This program is distributed in the hope that it will be useful, |
739 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
740 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
741 | + * GNU Library General Public License for more details. |
742 | + * |
743 | + * You should have received a copy of the GNU Library General Public License |
744 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
745 | + */ |
746 | + |
747 | +public class Network.VpnMenuItem : Gtk.ListBoxRow { |
748 | + private static unowned Gtk.RadioButton? blank_button = null; |
749 | + |
750 | + public signal void user_action (); |
751 | + public NM.RemoteConnection? connection { get; private set; } |
752 | + public string id { |
753 | + get { |
754 | + return connection.get_id (); |
755 | + } |
756 | + } |
757 | + public Network.State vpn_state { get; set; default = Network.State.DISCONNECTED; } |
758 | + |
759 | + public Gtk.RadioButton radio_button { get; private set; } |
760 | + Gtk.Spinner spinner; |
761 | + Gtk.Image error_img; |
762 | + |
763 | + public VpnMenuItem (NM.RemoteConnection? _connection) { |
764 | + connection = _connection; |
765 | + connection.changed.connect (update); |
766 | + |
767 | + var main_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 6); |
768 | + main_box.margin_start = main_box.margin_end = 6; |
769 | + |
770 | + radio_button = new Gtk.RadioButton (null); |
771 | + if (blank_button != null) { |
772 | + radio_button.join_group (blank_button); |
773 | + } |
774 | + |
775 | + radio_button.button_release_event.connect ((b, ev) => { |
776 | + user_action (); |
777 | + return false; |
778 | + }); |
779 | + |
780 | + error_img = new Gtk.Image.from_icon_name ("process-error-symbolic", Gtk.IconSize.MENU); |
781 | + error_img.margin_start = 6; |
782 | + error_img.set_tooltip_text (_("This Virtual Private Network could not be connected to.")); |
783 | + |
784 | + spinner = new Gtk.Spinner (); |
785 | + spinner.start (); |
786 | + spinner.visible = false; |
787 | + spinner.no_show_all = !spinner.visible; |
788 | + |
789 | + main_box.pack_start (radio_button, true, true); |
790 | + main_box.pack_start (spinner, false, false); |
791 | + main_box.pack_start (error_img, false, false); |
792 | + |
793 | + notify["vpn_state"].connect (update); |
794 | + radio_button.notify["active"].connect (update); |
795 | + |
796 | + add (main_box); |
797 | + get_style_context ().add_class ("menuitem"); |
798 | + |
799 | + update (); |
800 | + } |
801 | + |
802 | + /** |
803 | + * Only used for an item which is not displayed: hacky way to have no radio button selected. |
804 | + **/ |
805 | + public VpnMenuItem.blank () { |
806 | + radio_button = new Gtk.RadioButton (null); |
807 | + blank_button = radio_button; |
808 | + } |
809 | + |
810 | + private void update () { |
811 | + radio_button.label = connection.get_id (); |
812 | + |
813 | +#if PLUG_NETWORK |
814 | + if (show_icons) { |
815 | +#endif |
816 | + hide_item (error_img); |
817 | + hide_item (spinner); |
818 | + |
819 | +#if PLUG_NETWORK |
820 | + } |
821 | +#endif |
822 | + switch (vpn_state) { |
823 | + case State.FAILED_VPN: |
824 | + show_item (error_img); |
825 | + break; |
826 | + case State.CONNECTING_VPN: |
827 | + show_item (spinner); |
828 | + if (!radio_button.active) { |
829 | + critical ("An VPN is being connected but not active."); |
830 | + } |
831 | + break; |
832 | + } |
833 | + } |
834 | + |
835 | + public void set_active (bool active) { |
836 | + radio_button.set_active (active); |
837 | + } |
838 | + |
839 | + public void hide_icons (bool show_remove_button = true) { |
840 | +#if PLUG_NETWORK |
841 | + show_icons = false; |
842 | + hide_item (error_img); |
843 | + hide_item (spinner); |
844 | + if (!show_remove_button) { |
845 | + hide_item (remove_button); |
846 | + } |
847 | +#endif |
848 | + } |
849 | + |
850 | + void show_item (Gtk.Widget w) { |
851 | + w.visible = true; |
852 | + w.no_show_all = w.visible; |
853 | + } |
854 | + |
855 | + void hide_item (Gtk.Widget w) { |
856 | + w.visible = false; |
857 | + w.no_show_all = !w.visible; |
858 | + w.hide (); |
859 | + } |
860 | +} |
I don't have any VPN connections set up, but I still get the header in the menu. It should probably only show if you actually have VPN connections configured. Especially since toggling VPN to on does nothing. This is kind of dangerous honestly because it could make people think they have a VPN just by hitting this switch.
It also shows as "Vpn" instead of "VPN"