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_) {