quick-settings: add WiFi and VPN tiles with NetworkManager integration

parent 56d36009
...@@ -62,8 +62,17 @@ ...@@ -62,8 +62,17 @@
.quick-settings-submenu { .quick-settings-submenu {
border-radius: var(--border-radius); border-radius: var(--border-radius);
margin: 5px 0; margin: 5px 0;
padding: 4px; padding: 4px 0;
background: var(--dialog-bg-color); background: var(--dialog-bg-color);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
box-shadow: 0px 0px 10px var(--headerbar-shade-color); box-shadow: 0px 0px 10px var(--headerbar-shade-color);
button {
margin: 2px 4px;
}
separator {
margin-top: 4px;
margin-bottom: 4px;
}
} }
...@@ -11,4 +11,6 @@ src/controlCenter/widgets/quickSettings/tiles/powerProfilesTile.vala ...@@ -11,4 +11,6 @@ src/controlCenter/widgets/quickSettings/tiles/powerProfilesTile.vala
src/controlCenter/widgets/quickSettings/tiles/commandTile.vala src/controlCenter/widgets/quickSettings/tiles/commandTile.vala
src/controlCenter/widgets/quickSettings/tiles/caffeineTile.vala src/controlCenter/widgets/quickSettings/tiles/caffeineTile.vala
src/controlCenter/widgets/quickSettings/tiles/nightLightTile.vala src/controlCenter/widgets/quickSettings/tiles/nightLightTile.vala
src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
src/controlCenter/widgets/quickSettings/tiles/vpnTile.vala
data/ui/notifications_widget.blp data/ui/notifications_widget.blp
...@@ -118,3 +118,43 @@ msgstr "Баланс" ...@@ -118,3 +118,43 @@ msgstr "Баланс"
#: src/controlCenter/widgets/quickSettings/tiles/powerProfilesTile.vala:99 #: src/controlCenter/widgets/quickSettings/tiles/powerProfilesTile.vala:99
msgid "Power Saver" msgid "Power Saver"
msgstr "Энергосбережение" msgstr "Энергосбережение"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Wi-Fi"
msgstr "Wi-Fi"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Disabled"
msgstr "Отключён"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Not Connected"
msgstr "Не подключён"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Connecting…"
msgstr "Подключение…"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Password"
msgstr "Пароль"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Connect"
msgstr "Подключить"
#: src/controlCenter/widgets/quickSettings/tiles/wifiTile.vala
msgid "Network Settings"
msgstr "Настройки сети"
#: src/controlCenter/widgets/quickSettings/tiles/vpnTile.vala
msgid "VPN"
msgstr "VPN"
#: src/controlCenter/widgets/quickSettings/tiles/vpnTile.vala
msgid "Disconnected"
msgstr "Отключён"
#: src/controlCenter/widgets/quickSettings/tiles/vpnTile.vala
msgid "VPN Settings"
msgstr "Настройки VPN"
...@@ -37,6 +37,8 @@ ...@@ -37,6 +37,8 @@
"widget-config": { "widget-config": {
"quick-settings": { "quick-settings": {
"tiles": [ "tiles": [
{ "type": "wifi" },
{ "type": "vpn" },
{ "type": "power-profiles" }, { "type": "power-profiles" },
{ "type": "dnd" }, { "type": "dnd" },
{ "type": "dark-mode" }, { "type": "dark-mode" },
......
...@@ -459,7 +459,7 @@ ...@@ -459,7 +459,7 @@
"type": { "type": {
"type": "string", "type": "string",
"description": "The tile type", "description": "The tile type",
"enum": ["dnd", "dark-mode", "power-profiles", "caffeine", "night-light", "command"] "enum": ["dnd", "dark-mode", "power-profiles", "caffeine", "night-light", "command", "wifi", "vpn"]
}, },
"label": { "label": {
"type": "string", "type": "string",
......
namespace XimperShellNotificationCenter.Widgets {
[DBus (name = "org.freedesktop.NetworkManager")]
public interface NMDBus : Object {
public abstract bool wireless_enabled { get; set; }
public abstract ObjectPath[] devices { owned get; }
public abstract ObjectPath[] active_connections {
owned get;
}
}
[DBus (name = "org.freedesktop.NetworkManager.Device")]
public interface NMDeviceDBus : Object {
public abstract uint device_type { get; }
}
[DBus (name = "org.freedesktop.NetworkManager.Device.Wireless")]
public interface NMWirelessDBus : Object {
public abstract ObjectPath active_access_point {
owned get;
}
public abstract ObjectPath[] get_all_access_points ()
throws GLib.Error;
public abstract void request_scan (
HashTable<string, Variant> options)
throws GLib.Error;
}
[DBus (name = "org.freedesktop.NetworkManager.AccessPoint")]
public interface NMAccessPointDBus : Object {
public abstract uint8[] ssid { owned get; }
public abstract uint8 strength { get; }
public abstract uint wpa_flags { get; }
public abstract uint rsn_flags { get; }
}
[DBus (name = "org.freedesktop.NetworkManager.Connection.Active")]
public interface NMActiveConnDBus : Object {
public abstract bool vpn { get; }
public abstract uint state { get; }
public abstract ObjectPath connection { owned get; }
}
[DBus (name = "org.freedesktop.NetworkManager.Settings")]
public interface NMSettingsDBus : Object {
public abstract ObjectPath[] list_connections ()
throws GLib.Error;
}
[DBus (name = "org.freedesktop.NetworkManager.Settings.Connection")]
public interface NMSettingsConnDBus : Object {
public abstract HashTable<string, HashTable<string, Variant>>
get_settings () throws GLib.Error;
}
public class NetworkManagerProxy : Object {
public signal void state_changed ();
private NMDBus ?nm = null;
private DBusProxy ?nm_raw = null;
private bool _available = false;
private bool _has_wifi = false;
private static NetworkManagerProxy ?_instance = null;
public static NetworkManagerProxy get_instance () {
if (_instance == null) {
_instance = new NetworkManagerProxy ();
}
return _instance;
}
public bool available {
get { return _available; }
}
public bool has_wifi {
get { return _has_wifi; }
}
private NetworkManagerProxy () {
connect_dbus.begin ();
}
private async void connect_dbus () {
try {
nm = yield Bus.get_proxy (BusType.SYSTEM,
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager");
nm_raw = nm as DBusProxy;
_available = true;
nm_raw.g_properties_changed.connect (
on_props_changed);
yield detect_wifi ();
} catch (Error e) {
warning ("NetworkManager not available: %s",
e.message);
}
}
private void on_props_changed (
Variant changed, string[] invalidated) {
state_changed ();
}
private async void detect_wifi () {
if (nm == null) return;
try {
foreach (var dev_path in nm.devices) {
var dev = yield Bus.get_proxy<NMDeviceDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
dev_path);
if (dev.device_type == 2) {
_has_wifi = true;
return;
}
}
} catch (Error e) {
warning ("Failed to detect WiFi: %s",
e.message);
}
}
public bool get_wireless_enabled () {
if (nm == null) return false;
return nm.wireless_enabled;
}
public void set_wireless_enabled (bool val) {
if (nm == null) return;
nm.wireless_enabled = val;
}
public async ObjectPath ?get_wifi_device () {
if (nm == null) return null;
try {
foreach (var dev_path in nm.devices) {
var dev = yield Bus.get_proxy<NMDeviceDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
dev_path);
if (dev.device_type == 2) {
return dev_path;
}
}
} catch (Error e) {
warning ("get_wifi_device: %s", e.message);
}
return null;
}
public unowned NMDBus ?get_nm () {
return nm;
}
public async Variant ?call_nm (string method,
Variant ?args)
throws GLib.Error {
var proxy = yield new DBusProxy.for_bus (
BusType.SYSTEM,
DBusProxyFlags.NONE, null,
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager",
"org.freedesktop.NetworkManager",
null);
return yield proxy.call (method, args,
DBusCallFlags.NONE, 30000, null);
}
public static string ssid_to_string (uint8[] ssid) {
return (string) ssid;
}
public static string get_signal_icon (uint8 strength) {
if (strength >= 80) {
return "network-wireless-signal-excellent-symbolic";
} else if (strength >= 60) {
return "network-wireless-signal-good-symbolic";
} else if (strength >= 40) {
return "network-wireless-signal-ok-symbolic";
} else if (strength >= 20) {
return "network-wireless-signal-weak-symbolic";
}
return "network-wireless-signal-none-symbolic";
}
}
}
...@@ -28,6 +28,12 @@ namespace XimperShellNotificationCenter.Widgets { ...@@ -28,6 +28,12 @@ namespace XimperShellNotificationCenter.Widgets {
case "night-light": case "night-light":
tile = new NightLightTile (cfg); tile = new NightLightTile (cfg);
break; break;
case "wifi":
tile = new WifiTile (cfg);
break;
case "vpn":
tile = new VpnTile (cfg);
break;
default: default:
warning ("Unknown quick-settings tile: %s", warning ("Unknown quick-settings tile: %s",
type_id); type_id);
......
namespace XimperShellNotificationCenter.Widgets {
private struct VpnInfo {
string name;
ObjectPath conn_path;
bool is_active;
ObjectPath ?active_path;
}
public class VpnTile : QuickSettingsTile {
private NetworkManagerProxy nm_proxy;
private Gee.ArrayList<VpnInfo?> vpn_list =
new Gee.ArrayList<VpnInfo?> ();
private uint sync_timeout = 0;
private Gtk.Box ?current_submenu = null;
private bool updating_submenu = false;
private bool connecting = false;
public VpnTile (Json.Object ?cfg) {
base ("network-vpn-symbolic", _("VPN"));
setup_arrow ();
nm_proxy = NetworkManagerProxy.get_instance ();
nm_proxy.state_changed.connect (schedule_sync);
}
public override bool is_available () {
return nm_proxy.available;
}
public override void on_toggle () {
toggle_vpn.begin ();
}
public override void on_cc_visibility_change (
bool visible) {
if (visible) {
sync_state.begin ();
} else {
current_submenu = null;
connecting = false;
}
}
private void schedule_sync () {
if (sync_timeout != 0) {
Source.remove (sync_timeout);
}
sync_timeout = Timeout.add (500, () => {
sync_timeout = 0;
on_debounced_update.begin ();
return Source.REMOVE;
});
}
private async void on_debounced_update () {
bool was_active = active;
yield sync_state ();
if (was_active != active) {
connecting = false;
refresh_submenu.begin ();
} else if (!connecting) {
refresh_submenu.begin ();
}
}
private async void sync_state () {
yield load_vpn_list ();
bool any_active = false;
string ?active_name = null;
foreach (var vpn in vpn_list) {
if (vpn.is_active) {
any_active = true;
active_name = vpn.name;
break;
}
}
active = any_active;
if (any_active && active_name != null) {
icon.set_from_icon_name (
"network-vpn-symbolic");
set_subtitle (active_name);
} else {
icon.set_from_icon_name (
"network-vpn-symbolic");
set_subtitle (_("Disconnected"));
}
}
private async void load_vpn_list () {
var new_list = new Gee.ArrayList<VpnInfo?> ();
var seen = new Gee.HashSet<string> ();
// Get active VPN connections
var active_vpns =
new Gee.HashMap<string, ObjectPath> ();
try {
var nm = nm_proxy.get_nm ();
if (nm != null) {
foreach (var ac_path
in nm.active_connections) {
try {
var ac = yield Bus.get_proxy
<NMActiveConnDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
ac_path);
if (ac.vpn && ac.state == 2) {
active_vpns.set (
(string) ac.connection,
ac_path);
}
} catch (Error e) {
continue;
}
}
}
} catch (Error e) {}
// Get saved VPN connections
try {
var settings =
yield Bus.get_proxy<NMSettingsDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager/"
+ "Settings");
foreach (var conn_path
in settings.list_connections ()) {
try {
var conn = yield Bus.get_proxy
<NMSettingsConnDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
conn_path);
var s = conn.get_settings ();
var conn_s = s.lookup ("connection");
if (conn_s == null) continue;
var type_var = conn_s.lookup ("type");
if (type_var == null
|| type_var.get_string () != "vpn") {
continue;
}
var id_var = conn_s.lookup ("id");
string name = id_var != null
? id_var.get_string () : "VPN";
string key = (string) conn_path;
if (seen.contains (key)) continue;
seen.add (key);
bool is_act =
active_vpns.has_key (key);
ObjectPath ?act_path = is_act
? active_vpns.get (key) : null;
new_list.add (VpnInfo () {
name = name,
conn_path = conn_path,
is_active = is_act,
active_path = act_path,
});
} catch (Error e) {
continue;
}
}
} catch (Error e) {
warning ("load_vpn_list: %s", e.message);
}
vpn_list = new_list;
}
private async void toggle_vpn () {
// Find first active VPN to deactivate
foreach (var vpn in vpn_list) {
if (vpn.is_active && vpn.active_path != null) {
yield deactivate_vpn (vpn.active_path);
return;
}
}
// No active VPN — activate first in list
if (vpn_list.size > 0) {
yield activate_vpn (vpn_list.get (0).conn_path);
}
}
private async void activate_vpn (
ObjectPath conn_path) {
try {
yield nm_proxy.call_nm (
"ActivateConnection",
new Variant ("(ooo)",
conn_path,
new ObjectPath ("/"),
new ObjectPath ("/")));
} catch (Error e) {
warning ("activate_vpn: %s", e.message);
}
}
private async void deactivate_vpn (
ObjectPath active_path) {
try {
yield nm_proxy.call_nm (
"DeactivateConnection",
new Variant ("(o)", active_path));
} catch (Error e) {
warning ("deactivate_vpn: %s", e.message);
}
}
private async void deactivate_by_conn (
ObjectPath conn_path) {
try {
var nm = nm_proxy.get_nm ();
if (nm == null) return;
foreach (var ac_path
in nm.active_connections) {
try {
var ac = yield Bus.get_proxy
<NMActiveConnDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
ac_path);
if (ac.vpn
&& (string) ac.connection
== (string) conn_path) {
yield deactivate_vpn (ac_path);
return;
}
} catch (Error e) {
continue;
}
}
} catch (Error e) {
warning ("deactivate_by_conn: %s",
e.message);
}
}
// Submenu
public override Gtk.Widget ?create_submenu () {
var box = new Gtk.Box (
Gtk.Orientation.VERTICAL, 0);
box.add_css_class ("quick-settings-submenu");
current_submenu = box;
refresh_submenu.begin ();
return box;
}
private async void refresh_submenu () {
if (current_submenu == null) return;
if (updating_submenu) return;
updating_submenu = true;
yield load_vpn_list ();
if (current_submenu != null) {
for (var child =
current_submenu.get_first_child ();
child != null;) {
var next = child.get_next_sibling ();
current_submenu.remove (child);
child = next;
}
build_vpn_list (current_submenu);
}
updating_submenu = false;
}
private void build_vpn_list (Gtk.Box box) {
foreach (var vpn in vpn_list) {
var row = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 8);
row.add_css_class ("submenu-row");
var row_icon = new Gtk.Image.from_icon_name (
"network-vpn-symbolic");
var row_label = new Gtk.Label (vpn.name);
row_label.set_ellipsize (
Pango.EllipsizeMode.END);
row_label.set_hexpand (true);
row_label.set_halign (Gtk.Align.START);
var vpn_switch = new Gtk.Switch ();
vpn_switch.set_valign (Gtk.Align.CENTER);
vpn_switch.set_active (vpn.is_active);
ObjectPath conn_path = vpn.conn_path;
// Connect AFTER initial set_active
Idle.add_once (() => {
vpn_switch.state_set.connect ((state) => {
connecting = true;
if (state) {
activate_vpn.begin (conn_path);
} else {
deactivate_by_conn.begin (
conn_path);
}
return true;
});
});
row.append (row_icon);
row.append (row_label);
row.append (vpn_switch);
var btn = new Gtk.Button ();
btn.set_child (row);
btn.add_css_class ("flat");
btn.clicked.connect (() => {
vpn_switch.set_active (
!vpn_switch.active);
});
box.append (btn);
}
// Settings button
build_settings_button (box);
}
private void build_settings_button (Gtk.Box box) {
string ?cmd = get_settings_command ();
if (cmd == null) return;
if (vpn_list.size > 0) {
box.append (new Gtk.Separator (
Gtk.Orientation.HORIZONTAL));
}
var row = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 8);
row.add_css_class ("submenu-row");
row.append (new Gtk.Image.from_icon_name (
"emblem-system-symbolic"));
var label = new Gtk.Label (
_("VPN Settings"));
label.set_hexpand (true);
label.set_halign (Gtk.Align.START);
row.append (label);
var btn = new Gtk.Button ();
btn.set_child (row);
btn.add_css_class ("flat");
btn.clicked.connect (() => {
try {
Process.spawn_command_line_async (cmd);
ximper_shell_notification_center_daemon
.set_visibility (false);
} catch (Error e) {
warning ("Settings: %s", e.message);
}
});
box.append (btn);
}
private static string ?get_settings_command () {
try {
string output;
Process.spawn_command_line_sync (
"which nm-connection-editor",
out output, null, null);
if (output.strip ().length > 0) {
return "nm-connection-editor";
}
} catch (SpawnError e) {}
return null;
}
}
}
namespace XimperShellNotificationCenter.Widgets {
private struct ApInfo {
string ssid;
uint8 strength;
bool secured;
bool is_active;
ObjectPath ap_path;
bool has_saved;
ObjectPath ?saved_path;
}
public class WifiTile : QuickSettingsTile {
private NetworkManagerProxy nm_proxy;
private ObjectPath ?wifi_dev_path = null;
private string ?active_ssid = null;
private uint8 active_strength = 0;
private Gee.HashSet<string> ?saved_ssids = null;
private Gee.HashMap<string, ObjectPath> ?saved_map = null;
private uint sync_timeout = 0;
private Gtk.Box ?current_submenu = null;
private bool connecting = false;
private bool updating_submenu = false;
public WifiTile (Json.Object ?cfg) {
base ("network-wireless-offline-symbolic",
_("Wi-Fi"));
setup_arrow ();
nm_proxy = NetworkManagerProxy.get_instance ();
nm_proxy.state_changed.connect (schedule_sync);
init_wifi.begin ();
}
private void schedule_sync () {
if (sync_timeout != 0) {
Source.remove (sync_timeout);
}
sync_timeout = Timeout.add (1000, () => {
sync_timeout = 0;
on_debounced_update.begin ();
return Source.REMOVE;
});
}
private async void on_debounced_update () {
string ?old_ssid = active_ssid;
yield sync_state ();
if (old_ssid != active_ssid) {
connecting = false;
refresh_submenu.begin ();
} else if (!connecting) {
refresh_submenu.begin ();
}
}
private async void init_wifi () {
wifi_dev_path =
yield nm_proxy.get_wifi_device ();
yield sync_state ();
}
public override bool is_available () {
return nm_proxy.available && nm_proxy.has_wifi;
}
public override void on_toggle () {
nm_proxy.set_wireless_enabled (
!nm_proxy.get_wireless_enabled ());
}
public override void on_cc_visibility_change (
bool visible) {
if (visible) {
sync_state.begin ();
scan.begin ();
} else {
current_submenu = null;
connecting = false;
}
}
private async void sync_state () {
if (wifi_dev_path == null && nm_proxy.available) {
wifi_dev_path =
yield nm_proxy.get_wifi_device ();
}
bool enabled = nm_proxy.get_wireless_enabled ();
active = enabled;
if (!enabled) {
icon.set_from_icon_name (
"network-wireless-disabled-symbolic");
set_subtitle (_("Disabled"));
active_ssid = null;
return;
}
active_ssid = null;
active_strength = 0;
if (wifi_dev_path != null) {
try {
var wireless =
yield Bus.get_proxy<NMWirelessDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
wifi_dev_path);
ObjectPath ap_path =
wireless.active_access_point;
if (ap_path != null
&& ap_path != "/"
&& ap_path != "") {
var ap =
yield Bus.get_proxy<NMAccessPointDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
ap_path);
active_ssid =
NetworkManagerProxy.ssid_to_string (
ap.ssid);
active_strength = ap.strength;
}
} catch (Error e) {}
}
if (active_ssid != null && active_ssid.length > 0) {
icon.set_from_icon_name (
NetworkManagerProxy.get_signal_icon (
active_strength));
set_subtitle (active_ssid);
connecting = false;
} else if (!connecting) {
icon.set_from_icon_name (
"network-wireless-offline-symbolic");
set_subtitle (_("Not Connected"));
}
}
private async void scan () {
if (wifi_dev_path == null) return;
try {
var wireless =
yield Bus.get_proxy<NMWirelessDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
wifi_dev_path);
wireless.request_scan (
new HashTable<string, Variant> (
str_hash, str_equal));
} catch (Error e) {}
}
private async ApInfo[] get_access_points () {
ApInfo[] result = {};
if (wifi_dev_path == null) return result;
yield load_saved_connections ();
try {
var wireless =
yield Bus.get_proxy<NMWirelessDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
wifi_dev_path);
var ap_paths =
wireless.get_all_access_points ();
var best = new Gee.HashMap<string, ApInfo?> ();
foreach (var ap_path in ap_paths) {
try {
var ap =
yield Bus.get_proxy<NMAccessPointDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
ap_path);
string ssid =
NetworkManagerProxy.ssid_to_string (
ap.ssid);
if (ssid == null || ssid.length == 0) {
continue;
}
bool secured =
ap.wpa_flags != 0
|| ap.rsn_flags != 0;
bool is_act = active_ssid != null
&& ssid == active_ssid;
bool has_saved =
saved_ssids != null
&& saved_ssids.contains (ssid);
ObjectPath ?sp = has_saved
&& saved_map != null
? saved_map.get (ssid) : null;
var info = ApInfo () {
ssid = ssid,
strength = ap.strength,
secured = secured,
is_active = is_act,
ap_path = ap_path,
has_saved = has_saved,
saved_path = sp,
};
if (!best.has_key (ssid)
|| best.get (ssid).strength
< ap.strength) {
best.set (ssid, info);
}
} catch (Error e) {
continue;
}
}
var sorted = new Gee.ArrayList<ApInfo?> ();
foreach (var v in best.values) {
sorted.add (v);
}
sorted.sort ((a, b) => {
if (a.is_active != b.is_active) {
return a.is_active ? -1 : 1;
}
return (int) b.strength
- (int) a.strength;
});
foreach (var info in sorted) {
result += info;
}
} catch (Error e) {
warning ("get_access_points: %s",
e.message);
}
return result;
}
private async void load_saved_connections () {
saved_ssids = new Gee.HashSet<string> ();
saved_map =
new Gee.HashMap<string, ObjectPath> ();
try {
var settings =
yield Bus.get_proxy<NMSettingsDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
"/org/freedesktop/NetworkManager/"
+ "Settings");
foreach (var conn_path
in settings.list_connections ()) {
try {
var conn =
yield Bus.get_proxy<NMSettingsConnDBus> (
BusType.SYSTEM,
"org.freedesktop.NetworkManager",
conn_path);
var s = conn.get_settings ();
var wifi_s = s.lookup (
"802-11-wireless");
if (wifi_s == null) continue;
var ssid_var = wifi_s.lookup ("ssid");
if (ssid_var == null) continue;
string ssid =
NetworkManagerProxy.ssid_to_string (
ssid_var.get_data_as_bytes ()
.get_data ());
if (ssid.length > 0) {
saved_ssids.add (ssid);
saved_map.set (ssid, conn_path);
}
} catch (Error e) {
continue;
}
}
} catch (Error e) {
warning ("load_saved: %s", e.message);
}
}
// Submenu
public override Gtk.Widget ?create_submenu () {
if (!nm_proxy.get_wireless_enabled ()) {
current_submenu = null;
return null;
}
var box = new Gtk.Box (
Gtk.Orientation.VERTICAL, 0);
box.add_css_class ("quick-settings-submenu");
current_submenu = box;
refresh_submenu.begin ();
return box;
}
private async void refresh_submenu () {
if (current_submenu == null) return;
if (updating_submenu) return;
updating_submenu = true;
var aps = yield get_access_points ();
if (current_submenu != null) {
for (var child =
current_submenu.get_first_child ();
child != null;) {
var next = child.get_next_sibling ();
current_submenu.remove (child);
child = next;
}
build_ap_list (current_submenu, aps);
}
updating_submenu = false;
}
private void build_ap_list (
Gtk.Box box, ApInfo[] aps) {
foreach (var ap in aps) {
var row = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 8);
row.add_css_class ("submenu-row");
var row_icon = new Gtk.Image.from_icon_name (
NetworkManagerProxy.get_signal_icon (
ap.strength));
var row_label = new Gtk.Label (ap.ssid);
row_label.set_ellipsize (
Pango.EllipsizeMode.END);
row_label.set_hexpand (true);
row_label.set_halign (Gtk.Align.START);
var status_stack = new Gtk.Stack ();
status_stack.set_transition_type (
Gtk.StackTransitionType.CROSSFADE);
var check_img = new Gtk.Image.from_icon_name (
"object-select-symbolic");
var lock_img = new Gtk.Image.from_icon_name (
"network-wireless-encrypted-symbolic");
var spinner = new Gtk.Spinner ();
var empty = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 0);
status_stack.add_named (check_img, "check");
status_stack.add_named (lock_img, "lock");
status_stack.add_named (spinner, "spinner");
status_stack.add_named (empty, "empty");
if (ap.is_active) {
status_stack.set_visible_child_name (
"check");
} else if (ap.secured && !ap.has_saved) {
status_stack.set_visible_child_name (
"lock");
} else {
status_stack.set_visible_child_name (
"empty");
}
row.append (row_icon);
row.append (row_label);
row.append (status_stack);
var item_box = new Gtk.Box (
Gtk.Orientation.VERTICAL, 0);
var btn = new Gtk.Button ();
btn.set_child (row);
btn.add_css_class ("flat");
item_box.append (btn);
if (ap.secured && !ap.has_saved
&& !ap.is_active) {
build_password_row (item_box, btn,
status_stack, spinner, ap);
} else if (!ap.is_active) {
build_connect_handler (btn,
status_stack, spinner, ap);
}
box.append (item_box);
}
// Settings button
build_settings_button (box);
}
private void build_password_row (
Gtk.Box item_box, Gtk.Button btn,
Gtk.Stack status_stack, Gtk.Spinner spinner,
ApInfo ap) {
var pwd_revealer = new Gtk.Revealer ();
pwd_revealer.set_transition_type (
Gtk.RevealerTransitionType.SLIDE_DOWN);
pwd_revealer.set_transition_duration (150);
var pwd_box = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 4);
pwd_box.set_margin_start (8);
pwd_box.set_margin_end (8);
pwd_box.set_margin_bottom (4);
var pwd_entry = new Gtk.PasswordEntry ();
pwd_entry.set_hexpand (true);
pwd_entry.placeholder_text = _("Password");
pwd_entry.set_show_peek_icon (true);
var connect_btn = new Gtk.Button.with_label (
_("Connect"));
connect_btn.set_sensitive (false);
pwd_entry.notify["text"].connect (() => {
connect_btn.set_sensitive (
pwd_entry.get_text ().strip ().length >= 8);
});
pwd_box.append (pwd_entry);
pwd_box.append (connect_btn);
pwd_revealer.set_child (pwd_box);
item_box.append (pwd_revealer);
btn.clicked.connect (() => {
pwd_revealer.set_reveal_child (
!pwd_revealer.get_reveal_child ());
if (pwd_revealer.get_reveal_child ()) {
pwd_entry.grab_focus ();
}
});
string ssid = ap.ssid;
connect_btn.clicked.connect (() => {
string pwd =
pwd_entry.get_text ().strip ();
if (pwd.length > 0) {
pwd_revealer.set_reveal_child (false);
start_connecting (status_stack, spinner);
connect_new.begin (ssid, pwd);
}
});
pwd_entry.activate.connect (() => {
connect_btn.clicked ();
});
}
private void build_connect_handler (
Gtk.Button btn, Gtk.Stack status_stack,
Gtk.Spinner spinner, ApInfo ap) {
ObjectPath ?sp = ap.saved_path;
ObjectPath ap_path = ap.ap_path;
string ssid = ap.ssid;
bool secured = ap.secured;
btn.clicked.connect (() => {
start_connecting (status_stack, spinner);
if (sp != null) {
activate_saved.begin (sp, ap_path);
} else if (!secured) {
connect_new.begin (ssid, "");
}
});
}
private void start_connecting (
Gtk.Stack stack, Gtk.Spinner spinner) {
stack.set_visible_child_name ("spinner");
spinner.start ();
connecting = true;
set_subtitle (_("Connecting\u2026"));
}
private void build_settings_button (Gtk.Box box) {
string ?cmd = get_settings_command ();
if (cmd == null) return;
box.append (new Gtk.Separator (
Gtk.Orientation.HORIZONTAL));
var row = new Gtk.Box (
Gtk.Orientation.HORIZONTAL, 8);
row.add_css_class ("submenu-row");
row.append (new Gtk.Image.from_icon_name (
"emblem-system-symbolic"));
var label = new Gtk.Label (
_("Network Settings"));
label.set_hexpand (true);
label.set_halign (Gtk.Align.START);
row.append (label);
var btn = new Gtk.Button ();
btn.set_child (row);
btn.add_css_class ("flat");
btn.clicked.connect (() => {
try {
Process.spawn_command_line_async (cmd);
ximper_shell_notification_center_daemon
.set_visibility (false);
} catch (Error e) {
warning ("Settings: %s", e.message);
}
});
box.append (btn);
}
private static string ?get_settings_command () {
try {
string output;
Process.spawn_command_line_sync (
"which nm-connection-editor",
out output, null, null);
if (output.strip ().length > 0) {
return "nm-connection-editor";
}
} catch (SpawnError e) {}
return null;
}
// Connection methods
private async void activate_saved (
ObjectPath conn_path,
ObjectPath ap_path) {
if (wifi_dev_path == null) return;
try {
yield nm_proxy.call_nm (
"ActivateConnection",
new Variant ("(ooo)",
conn_path, wifi_dev_path, ap_path));
} catch (Error e) {
warning ("activate_saved: %s", e.message);
connecting = false;
}
}
private async void connect_new (
string ssid, string psk) {
if (wifi_dev_path == null) return;
try {
yield nm_proxy.call_nm (
"AddAndActivateConnection",
new Variant ("(a{sa{sv}}oo)",
build_wifi_settings (ssid, psk),
wifi_dev_path,
new ObjectPath ("/")));
} catch (Error e) {
warning ("connect_new: %s", e.message);
connecting = false;
}
}
private HashTable<string, HashTable<string, Variant>>
build_wifi_settings (
string ssid, string psk) {
var result =
new HashTable<string, HashTable<string, Variant>> (
str_hash, str_equal);
var conn = new HashTable<string, Variant> (
str_hash, str_equal);
conn.insert ("type",
new Variant.string ("802-11-wireless"));
conn.insert ("id",
new Variant.string (ssid));
string user = Environment.get_user_name ();
var perms = new VariantBuilder (
new VariantType ("as"));
perms.add ("s", "user:%s:".printf (user));
conn.insert ("permissions", perms.end ());
result.insert ("connection", conn);
var wifi = new HashTable<string, Variant> (
str_hash, str_equal);
var ssid_builder = new VariantBuilder (
new VariantType ("ay"));
for (int i = 0; i < ssid.data.length; i++) {
ssid_builder.add ("y", ssid.data[i]);
}
wifi.insert ("ssid", ssid_builder.end ());
result.insert ("802-11-wireless", wifi);
if (psk.length > 0) {
var sec = new HashTable<string, Variant> (
str_hash, str_equal);
sec.insert ("key-mgmt",
new Variant.string ("wpa-psk"));
sec.insert ("psk",
new Variant.string (psk));
result.insert (
"802-11-wireless-security", sec);
}
return result;
}
}
}
...@@ -52,6 +52,9 @@ widget_sources = [ ...@@ -52,6 +52,9 @@ widget_sources = [
'controlCenter/widgets/quickSettings/tiles/commandTile.vala', 'controlCenter/widgets/quickSettings/tiles/commandTile.vala',
'controlCenter/widgets/quickSettings/tiles/caffeineTile.vala', 'controlCenter/widgets/quickSettings/tiles/caffeineTile.vala',
'controlCenter/widgets/quickSettings/tiles/nightLightTile.vala', 'controlCenter/widgets/quickSettings/tiles/nightLightTile.vala',
'controlCenter/widgets/quickSettings/networkManagerProxy.vala',
'controlCenter/widgets/quickSettings/tiles/wifiTile.vala',
'controlCenter/widgets/quickSettings/tiles/vpnTile.vala',
] ]
app_sources = [ app_sources = [
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment