diff --git a/data/16/network-wireless-no-route-symbolic.svg b/data/16/network-wireless-no-route-symbolic.svg
new file mode 100644
index 00000000..cedb47e7
--- /dev/null
+++ b/data/16/network-wireless-no-route-symbolic.svg
@@ -0,0 +1,5 @@
+
+
diff --git a/data/16/network-wireless-offline-symbolic.svg b/data/16/network-wireless-offline-symbolic.svg
new file mode 100644
index 00000000..8f1e7fd5
--- /dev/null
+++ b/data/16/network-wireless-offline-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/data/Indicator.css b/data/Indicator.css
index 6b64bd3b..fc1f2d94 100644
--- a/data/Indicator.css
+++ b/data/Indicator.css
@@ -3,6 +3,10 @@
* SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io)
*/
+settings-toggle {
+ font-size: 0.85rem;
+}
+
network .image-button.toggle,
settings-toggle .image-button.toggle {
border-radius: 1em;
@@ -14,6 +18,7 @@ settings-toggle .image-button {
box-shadow: none;
min-height: 2.1666rem; /* 26px */
min-width: 2.1666rem; /* 26px */
+ margin-bottom: 0.3rem;
}
network .image-button,
diff --git a/data/network.gresource.xml b/data/network.gresource.xml
index 0a433259..592c62fc 100644
--- a/data/network.gresource.xml
+++ b/data/network.gresource.xml
@@ -64,6 +64,8 @@
24/network-wireless-no-route-symbolic.svg
24/network-wireless-offline-symbolic.svg
+ 16/network-wireless-no-route-symbolic.svg
+ 16/network-wireless-offline-symbolic.svg
16/network-wireless-signal-excellent-symbolic.svg
16/network-wireless-signal-good-symbolic.svg
16/network-wireless-signal-ok-symbolic.svg
diff --git a/src/Widgets/PopoverWidget.vala b/src/Widgets/PopoverWidget.vala
index f77e4836..99e3271e 100644
--- a/src/Widgets/PopoverWidget.vala
+++ b/src/Widgets/PopoverWidget.vala
@@ -13,9 +13,7 @@ public class Network.Widgets.PopoverWidget : Gtk.Box {
public Network.State state { private set; get; default = Network.State.CONNECTING_WIRED; }
private Gtk.FlowBox other_box;
- private Gtk.Box wifi_box;
private Gtk.Box vpn_box;
- private Gtk.ModelButton hidden_item;
private Gtk.Revealer toggle_revealer;
public bool is_in_session { get; construct; }
@@ -46,7 +44,6 @@ public class Network.Widgets.PopoverWidget : Gtk.Box {
max_children_per_line = 3,
selection_mode = Gtk.SelectionMode.NONE
};
- wifi_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
vpn_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
try {
@@ -101,17 +98,11 @@ public class Network.Widgets.PopoverWidget : Gtk.Box {
add (toggle_revealer);
add (vpn_box);
- add (wifi_box);
if (is_in_session) {
- hidden_item = new Gtk.ModelButton ();
- hidden_item.text = _("Connect to Hidden Network…");
- hidden_item.no_show_all = true;
-
var show_settings_button = new Gtk.ModelButton ();
show_settings_button.text = _("Network Settings…");
- add (hidden_item);
add (show_settings_button);
show_settings_button.clicked.connect (show_settings);
@@ -130,16 +121,6 @@ public class Network.Widgets.PopoverWidget : Gtk.Box {
show_all ();
update_vpn_connection ();
- hidden_item.clicked.connect (() => {
- bool found = false;
- foreach (unowned var iface in network_interface) {
- if (iface is WifiInterface && ((WifiInterface) iface).hidden_sensitivity && !found) {
- ((WifiInterface) iface).connect_to_hidden ();
- found = true;
- }
- }
- });
-
/* Monitor network manager */
nm_client.device_added.connect (device_added_cb);
nm_client.device_removed.connect (device_removed_cb);
@@ -150,8 +131,7 @@ public class Network.Widgets.PopoverWidget : Gtk.Box {
}
private void add_interface (WidgetNMInterface widget_interface) {
- if (widget_interface is EtherInterface || widget_interface is ModemInterface) {
-
+ if (!(widget_interface is Network.VpnInterface)) {
var flowboxchild = new Gtk.FlowBoxChild () {
// Prevent weird double focus border
can_focus = false,
@@ -162,35 +142,11 @@ public class Network.Widgets.PopoverWidget : Gtk.Box {
return;
}
- var container_box = wifi_box;
-
- if (widget_interface is Network.WifiInterface) {
- container_box = wifi_box;
- hidden_item.no_show_all = false;
- hidden_item.show_all ();
-
- ((Network.WifiInterface) widget_interface).notify["hidden-sensitivity"].connect (() => {
- bool hidden_sensitivity = false;
-
- foreach (unowned var iface in network_interface) {
- if (iface is WifiInterface) {
- hidden_sensitivity = hidden_sensitivity || ((WifiInterface) iface ).hidden_sensitivity;
- }
-
- hidden_item.sensitive = hidden_sensitivity;
- }
- });
- }
-
- if (widget_interface is Network.VpnInterface) {
- container_box = vpn_box;
- }
-
if (is_in_session && get_children ().length () > 0) {
- container_box.pack_end (widget_interface.sep);
+ vpn_box.pack_end (widget_interface.sep);
}
- container_box.pack_end (widget_interface);
+ vpn_box.pack_end (widget_interface);
}
public void opened () {
diff --git a/src/Widgets/SettingsToggle.vala b/src/Widgets/SettingsToggle.vala
index 8be10f3b..18bbd7e8 100644
--- a/src/Widgets/SettingsToggle.vala
+++ b/src/Widgets/SettingsToggle.vala
@@ -8,8 +8,15 @@ public class Network.SettingsToggle : Gtk.Box {
public string icon_name { get; set; }
public string text { get; set; }
public string settings_uri { get; set; default = "settings://"; }
+ public string subtitle { get; set; default = ""; }
+ public Gtk.Popover? popover { get; set; default = null; }
- private Gtk.GestureMultiPress middle_click_gesture;
+ private Gtk.Label subtitle_label;
+ private Gtk.Revealer subtitle_revealer;
+ private Gtk.ToggleButton button;
+ private Gtk.GestureMultiPress click_controller;
+ private Gtk.GestureLongPress long_press_controller;
+ private Gtk.EventControllerKey menu_key_controller;
class construct {
set_css_name ("settings-toggle");
@@ -18,7 +25,7 @@ public class Network.SettingsToggle : Gtk.Box {
construct {
var image = new Gtk.Image ();
- var button = new Gtk.ToggleButton () {
+ button = new Gtk.ToggleButton () {
halign = CENTER,
image = image
};
@@ -30,30 +37,97 @@ public class Network.SettingsToggle : Gtk.Box {
max_width_chars = 13,
mnemonic_widget = button
};
- label.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL);
- halign = CENTER;
+ subtitle_label = new Gtk.Label (null) {
+ ellipsize = MIDDLE,
+ justify = CENTER,
+ lines = 1,
+ max_width_chars = 13
+ };
+ subtitle_label.get_style_context ().add_class (Granite.STYLE_CLASS_SMALL_LABEL);
+ subtitle_label.get_style_context ().add_class (Gtk.STYLE_CLASS_DIM_LABEL);
+
+ subtitle_revealer = new Gtk.Revealer () {
+ child = subtitle_label
+ };
+
+ hexpand = true;
orientation = VERTICAL;
- spacing = 3;
add (button);
add (label);
+ add (subtitle_revealer);
bind_property ("action-name", button, "action-name");
bind_property ("icon-name", image, "icon-name");
bind_property ("text", label, "label");
- middle_click_gesture = new Gtk.GestureMultiPress (this) {
- button = Gdk.BUTTON_MIDDLE
+ click_controller = new Gtk.GestureMultiPress (this) {
+ button = 0,
+ exclusive = true
};
- middle_click_gesture.pressed.connect (() => {
- try {
- AppInfo.launch_default_for_uri (settings_uri, null);
-
- var popover = (Gtk.Popover) get_ancestor (typeof (Gtk.Popover));
- popover.popdown ();
- } catch (Error e) {
- critical ("Failed to open system settings: %s", e.message);
+ click_controller.pressed.connect (() => {
+ if (click_controller.get_current_button () == Gdk.BUTTON_MIDDLE) {
+ try {
+ AppInfo.launch_default_for_uri (settings_uri, null);
+
+ click_controller.set_state (CLAIMED);
+ click_controller.reset ();
+
+ var popover = (Gtk.Popover) get_ancestor (typeof (Gtk.Popover));
+ popover.popdown ();
+ } catch (Error e) {
+ critical ("Failed to open system settings: %s", e.message);
+ }
+ }
+
+ if (popover != null) {
+ var sequence = click_controller.get_current_sequence ();
+ var event = click_controller.get_last_event (sequence);
+
+ if (event.triggers_context_menu ()) {
+ popover.popup ();
+
+ click_controller.set_state (CLAIMED);
+ click_controller.reset ();
+ }
}
});
+
+ notify["popover"].connect (construct_menu);
+ notify["subtitle"].connect (construct_subtitle);
+ }
+
+ private void construct_menu () {
+ popover.position = RIGHT;
+ popover.relative_to = button;
+
+ long_press_controller = new Gtk.GestureLongPress (this);
+ long_press_controller.pressed.connect (() => {
+ popover.popup ();
+ });
+
+ menu_key_controller = new Gtk.EventControllerKey (this);
+ menu_key_controller.key_released.connect ((keyval, keycode, state) => {
+ var mods = state & Gtk.accelerator_get_default_mod_mask ();
+ switch (keyval) {
+ case Gdk.Key.F10:
+ if (mods == Gdk.ModifierType.SHIFT_MASK) {
+ popover.popup ();
+ }
+ break;
+ case Gdk.Key.Menu:
+ case Gdk.Key.MenuKB:
+ popover.popup ();
+ break;
+ default:
+ return;
+ }
+ });
+ }
+
+ private void construct_subtitle () {
+ subtitle_label.label = subtitle;
+ subtitle_revealer.reveal_child = subtitle != "";
+ button.get_accessible ().accessible_description = subtitle;
}
}
diff --git a/src/Widgets/WifiInterface.vala b/src/Widgets/WifiInterface.vala
index 263438ff..2eade9a8 100644
--- a/src/Widgets/WifiInterface.vala
+++ b/src/Widgets/WifiInterface.vala
@@ -9,13 +9,12 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
public NM.Client nm_client { get; construct; }
public NM.DeviceWifi? wifi_device;
- public bool hidden_sensitivity { get; set; default = true; }
public string active_ap_name { get; private set; }
- private Granite.SwitchModelButton wifi_item;
- private Gtk.Revealer revealer;
+ private SettingsToggle wifi_toggle;
+ private SimpleAction toggle_action;
private RFKillManager rfkill;
private NM.AccessPoint? active_ap;
private Gtk.ListBox wifi_list;
@@ -27,6 +26,7 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
private bool software_locked;
private bool hardware_locked;
+ private uint wifi_animation_timeout;
private uint timeout_scan = 0;
private Cancellable wifi_scan_cancellable = new Cancellable ();
@@ -105,9 +105,6 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
wifi_list.set_sort_func (sort_func);
wifi_list.set_placeholder (placeholder);
- wifi_item = new Granite.SwitchModelButton (display_title);
- wifi_item.get_style_context ().add_class (Granite.STYLE_CLASS_H4_LABEL);
-
var scrolled_box = new Gtk.ScrolledWindow (null, null) {
child = wifi_list,
hscrollbar_policy = NEVER,
@@ -115,15 +112,34 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
propagate_natural_height = true
};
- revealer = new Gtk.Revealer () {
- child = scrolled_box
+ var hidden_item = new Gtk.ModelButton () {
+ text = _("Connect to Hidden Network…")
+ };
+
+ var menu_box = new Gtk.Box (VERTICAL, 3) {
+ margin_top = 3,
+ margin_bottom = 3
+ };
+ menu_box.add (scrolled_box);
+ menu_box.add (new Gtk.Separator (HORIZONTAL));
+ menu_box.add (hidden_item);
+ menu_box.show_all ();
+
+ var context_menu = new Gtk.Popover (null) {
+ child = menu_box
+ };
+
+ wifi_toggle = new SettingsToggle () {
+ action_name = "wifi.toggle",
+ icon_name = "network-wireless-no-route-symbolic",
+ settings_uri = "settings://network/wifi",
+ text = display_title,
+ popover = context_menu
};
- orientation = Gtk.Orientation.VERTICAL;
- pack_start (wifi_item);
- pack_start (revealer);
+ add (wifi_toggle);
- bind_property ("display-title", wifi_item, "text");
+ bind_property ("display-title", wifi_toggle, "text");
wifi_list.row_activated.connect ((row) => {
if (row is WifiMenuItem) {
@@ -131,31 +147,49 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
}
});
- wifi_item.notify["active"].connect (() => {
- var active = wifi_item.active;
- if (active != !software_locked) {
- rfkill.set_software_lock (RFKillDeviceType.WLAN, !active);
- nm_client.dbus_set_property.begin (
- NM.DBUS_PATH, NM.DBUS_INTERFACE,
- "WirelessEnabled", active,
- -1, null, (obj, res) => {
- try {
- ((NM.Client) obj).dbus_set_property.end (res);
- } catch (Error e) {
- warning ("Error activating wifi item: %s", e.message);
- }
+ hidden_item.clicked.connect (connect_to_hidden);
+
+ toggle_action = new SimpleAction.stateful ("toggle", null, new Variant.boolean (false));
+ toggle_action.activate.connect (() => {
+ var active = toggle_action.get_state ().get_boolean ();
+ rfkill.set_software_lock (RFKillDeviceType.WLAN, !software_locked);
+ nm_client.dbus_set_property.begin (
+ NM.DBUS_PATH, NM.DBUS_INTERFACE,
+ "WirelessEnabled", !active,
+ -1, null, (obj, res) => {
+ try {
+ ((NM.Client) obj).dbus_set_property.end (res);
+ } catch (Error e) {
+ warning ("Error activating wifi item: %s", e.message);
}
- );
- }
+ }
+ );
+ });
+
+ hidden_item.sensitive = toggle_action.get_state ().get_boolean ();
+ toggle_action.notify["state"].connect (() => {
+ hidden_item.sensitive = toggle_action.get_state ().get_boolean ();
});
+
+ var action_group = new SimpleActionGroup ();
+ action_group.add_action (toggle_action);
+
+ insert_action_group ("wifi", action_group);
}
private void update () {
+ if (wifi_animation_timeout > 0) {
+ Source.remove (wifi_animation_timeout);
+ wifi_animation_timeout = 0;
+ }
+
switch (wifi_device.state) {
case NM.DeviceState.UNKNOWN:
case NM.DeviceState.UNMANAGED:
case NM.DeviceState.FAILED:
state = State.FAILED_WIFI;
+ wifi_toggle.icon_name = "panel-network-wireless-offline-symbolic";
+
if (active_wifi_item != null) {
active_wifi_item.state = wifi_device.state;
}
@@ -166,10 +200,12 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
case NM.DeviceState.UNAVAILABLE:
cancel_scan ();
state = State.DISCONNECTED;
+ wifi_toggle.icon_name = "panel-network-wireless-offline-symbolic";
break;
case NM.DeviceState.DISCONNECTED:
set_scan_placeholder ();
state = State.DISCONNECTED;
+ wifi_toggle.icon_name = "panel-network-wireless-no-route-symbolic";
break;
case NM.DeviceState.PREPARE:
@@ -180,6 +216,28 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
case NM.DeviceState.SECONDARIES:
set_scan_placeholder ();
state = State.CONNECTING_WIFI;
+
+ var wifi_animation_state = 0;
+ wifi_animation_timeout = Timeout.add (300, () => {
+ wifi_animation_state = (wifi_animation_state + 1) % 4;
+ string strength = "";
+ switch (wifi_animation_state) {
+ case 0:
+ strength = "weak";
+ break;
+ case 1:
+ strength = "ok";
+ break;
+ case 2:
+ strength = "good";
+ break;
+ case 3:
+ strength = "excellent";
+ break;
+ }
+ wifi_toggle.icon_name = "panel-network-wireless-signal-" + strength + "-symbolic";
+ return true;
+ });
break;
case NM.DeviceState.ACTIVATED:
@@ -188,8 +246,27 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
/* That can happen if active_ap has not been added yet, at startup. */
if (active_ap != null) {
state = strength_to_state (active_ap.get_strength ());
+
+ switch (state) {
+ case Network.State.CONNECTED_WIFI_WEAK:
+ wifi_toggle.icon_name = "panel-network-wireless-signal-weak-symbolic";
+ break;
+ case Network.State.CONNECTED_WIFI_OK:
+ wifi_toggle.icon_name = "panel-network-wireless-signal-ok-symbolic";
+ break;
+ case Network.State.CONNECTED_WIFI_GOOD:
+ wifi_toggle.icon_name = "panel-network-wireless-signal-good-symbolic";
+ break;
+ case Network.State.CONNECTED_WIFI_EXCELLENT:
+ wifi_toggle.icon_name = "panel-network-wireless-signal-excellent-symbolic";
+ break;
+ default:
+ wifi_toggle.icon_name = "panel-network-wireless-no-route-symbolic";
+ break;
+ }
} else {
state = State.CONNECTED_WIFI_WEAK;
+ wifi_toggle.icon_name = "network-wireless-signal-weak-symbolic";
}
break;
}
@@ -217,18 +294,10 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
update_active_ap ();
- wifi_item.set_sensitive (!hardware_locked);
- wifi_item.active = !locked;
+ toggle_action.set_enabled (!hardware_locked);
+ toggle_action.set_state (new Variant.boolean (!locked));
active_ap = wifi_device.get_active_access_point ();
-
- if (wifi_device.state == NM.DeviceState.UNAVAILABLE || state == Network.State.FAILED_WIFI) {
- revealer.reveal_child = false;
- hidden_sensitivity = false;
- } else {
- revealer.reveal_child = true;
- hidden_sensitivity = true;
- }
}
private void wifi_activate_cb (WifiMenuItem i) {
@@ -365,7 +434,7 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
return null;
}
- public void connect_to_hidden () {
+ private void connect_to_hidden () {
var hidden_dialog = new NMA.WifiDialog.for_other (nm_client) {
deletable = false
};
@@ -472,6 +541,7 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
if (active_ap == null) {
debug ("No active AP");
+ active_ap_name = "";
blank_item.active = true;
} else {
unowned GLib.Bytes active_ap_ssid = active_ap.ssid;
@@ -496,6 +566,8 @@ public class Network.WifiInterface : Network.WidgetNMInterface {
debug ("Active AP not added");
}
}
+
+ wifi_toggle.subtitle = active_ap_name;
}
private void access_point_removed_cb (Object ap_) {