diff --git a/data/css/default.css b/data/css/default.css index eed40763..3dfa00cf 100644 --- a/data/css/default.css +++ b/data/css/default.css @@ -72,28 +72,37 @@ border-radius: 3px; color: #41A6B5; } -.excellent { +.excellent>label +{ color: #00ff00; } -.good { +.good>label +{ color: #88ff00; } -.ok { +.ok>label +{ color: #ffff00; } -.weak { +.weak>label +{ color: #ff8800; } -.none { +.none>label +{ color: #ff0000; } +.bluetooth>label +{ + color: #8888ff; +} .wf-dock, -.wf-dock .out-box { +.wf-dock>.out-box { background: transparent; } @@ -104,18 +113,18 @@ border-radius: 1em; } -.wf-dock button { +.wf-dock button.toplevel-icon { background: transparent; padding-left: 0rem; padding-right: 0rem; } -.wf-dock button.activated { +.wf-dock .box button.activated { border-bottom: 3px solid white; margin-bottom: 5px; } -.wf-dock image { +.wf-dock .box image { background: transparent; padding-left: 0rem; padding-right: 0rem; @@ -123,7 +132,7 @@ -gtk-icon-transform: scale(0.7); } -.wf-dock image:hover { +.wf-dock .box image:hover { background: transparent; padding-left: 0rem; padding-right: 0rem; @@ -131,14 +140,14 @@ -gtk-icon-transform: scale(1.0); } -.wf-dock image { +.wf-dock .box image.toplevel-icon { animation-name: embiggen; animation-duration: 1000ms; animation-timing-function: linear; animation-iteration-count: 1; } -.wf-dock .closing image { +.wf-dock .box .closing image.toplevel-icon { animation-name: kromulent; animation-duration: 1000ms; animation-timing-function: linear; @@ -146,6 +155,38 @@ animation-fill-mode: forwards; } +.wf-dock .network .image-button{ + -gtk-icon-size:64px; +} + +.wf-dock .network-control-center { + -gtk-icon-size: 48px; +} + +.network-control-center image.access-point { + -gtk-icon-size: 40px; +} + +.network-control-center .access-point.secure .security { + -gtk-icon-size:20px; + color:red; +} + +.network-control-center .access-point.secure.has-password .security { + color:green; +} + +.network-control-center .access-point.has-password{ + font-weight: bold; + background: #8f83; +} + + +.network-control-center .access-point .band { + font-size: 1em; + font-weight: bolder; +} + @keyframes embiggen { to { -gtk-icon-size: 64px; diff --git a/metadata/dock.xml b/metadata/dock.xml index 3451c111..43aee187 100644 --- a/metadata/dock.xml +++ b/metadata/dock.xml @@ -45,5 +45,9 @@ <_long>Keep all window icons for up to 2 seconds to allow CSS animations. true + diff --git a/metadata/panel.xml b/metadata/panel.xml index b59900e7..451e9f96 100644 --- a/metadata/panel.xml +++ b/metadata/panel.xml @@ -165,6 +165,10 @@ <_short>On Click Command default + <_short>Menu diff --git a/src/dock/dock.cpp b/src/dock/dock.cpp index 763cbfdb..9c0a695c 100644 --- a/src/dock/dock.cpp +++ b/src/dock/dock.cpp @@ -4,12 +4,16 @@ #include #include +#include #include #include #include #include "dock.hpp" #include "../util/gtk-utils.hpp" +#include "network/manager.hpp" +#include "network/network-widget.hpp" +#include "network/network.hpp" #include @@ -20,13 +24,19 @@ class WfDock::impl wl_surface *_wl_surface; Gtk::Box out_box; Gtk::Box box; + std::unique_ptr network_image; + std::unique_ptr network_control; + std::shared_ptr network_manager; WfOption css_path{"dock/css_path"}; WfOption dock_height{"dock/dock_height"}; + WfOption network{"dock/show_network_status"}; public: impl(WayfireOutput *output) { + network_image = std::make_unique("dock"); + this->output = output; window = std::unique_ptr( new WayfireAutohidingWindow(output, "dock")); @@ -55,6 +65,34 @@ class WfDock::impl } } + if (network) + { + network_manager = NetworkManager::getInstance(); + add_child(*network_image); + network_control = std::make_unique(); + network_image->get_popover()->set_child(*network_control); + network_image->set_has_frame(false); + network_manager->signal_default_changed().connect( + [this] (std::shared_ptr network) + { + network_image->set_icon_name(network->get_icon_name()); + for (auto clas : network_image->get_css_classes()) + { + network_image->remove_css_class(clas); + } + + for (auto clas : network->get_css_classes()) + { + network_image->add_css_class(clas); + } + + network_image->set_tooltip_text(network->get_name()); + + network_image->add_css_class("network"); + network_image->add_css_class("flat"); + }); + } + window->present(); _wl_surface = gdk_wayland_surface_get_wl_surface( window->get_surface()->gobj()); diff --git a/src/dock/dock.hpp b/src/dock/dock.hpp index d4db695c..ea5b449c 100644 --- a/src/dock/dock.hpp +++ b/src/dock/dock.hpp @@ -1,10 +1,12 @@ #pragma once #include +#include #include #include #include +#include "network/manager.hpp" #include "wf-shell-app.hpp" class WfDock diff --git a/src/panel/panel.cpp b/src/panel/panel.cpp index 0864fb1f..852e65e3 100644 --- a/src/panel/panel.cpp +++ b/src/panel/panel.cpp @@ -14,6 +14,7 @@ #include #include "panel.hpp" +#include "network/manager.hpp" #include "widgets/battery.hpp" #include "widgets/command-output.hpp" #include "widgets/language.hpp" @@ -243,6 +244,7 @@ class WayfirePanel::impl { const auto lock_sn_watcher = Watcher::Instance(); const auto lock_notification_daemon = Daemon::Instance(); + const auto lock_network_manager = NetworkManager::getInstanceIfExists(); for (auto child : box.get_children()) { box.remove(*child); diff --git a/src/panel/widgets/network.cpp b/src/panel/widgets/network.cpp index 788a6745..a74fa7dc 100644 --- a/src/panel/widgets/network.cpp +++ b/src/panel/widgets/network.cpp @@ -1,367 +1,98 @@ #include "network.hpp" +#include "gtkmm/gesture.h" +#include "gtkmm/gestureclick.h" +#include "gtkmm/gesturelongpress.h" +#include "network/network.hpp" #include #include -#include #include +#include -#define NM_DBUS_NAME "org.freedesktop.NetworkManager" -#define ACTIVE_CONNECTION "PrimaryConnection" -#define STRENGTH "Strength" +WayfireNetworkInfo::WayfireNetworkInfo() +{} -std::string WfNetworkConnectionInfo::get_control_center_section(DBusProxy& nm) -{ - Glib::Variant wifi; - nm->get_cached_property(wifi, "WirelessEnabled"); - - return wifi.get() ? "wifi" : "network"; -} - -void WfNetworkConnectionInfo::spawn_control_center(DBusProxy& nm) -{ - std::string command = "env XDG_CURRENT_DESKTOP=GNOME gnome-control-center "; - command += get_control_center_section(nm); - - Glib::spawn_command_line_async(command); -} - -struct NoConnectionInfo : public WfNetworkConnectionInfo -{ - std::string get_icon_name(WfConnectionState state) - { - return "network-offline-symbolic"; - } - - int get_connection_strength() - { - return 0; - } - - std::string get_strength_str() - { - return "none"; - } - - std::string get_ip() - { - return "127.0.0.1"; - } - - virtual ~NoConnectionInfo() - {} -}; - -struct WifiConnectionInfo : public WfNetworkConnectionInfo +void WayfireNetworkInfo::init(Gtk::Box *container) { - WayfireNetworkInfo *widget; - DBusProxy ap; - sigc::connection ap_sig; - - WifiConnectionInfo(const DBusConnection& connection, std::string path, - WayfireNetworkInfo *widget) - { - this->widget = widget; - - ap = Gio::DBus::Proxy::create_sync(connection, NM_DBUS_NAME, path, - "org.freedesktop.NetworkManager.AccessPoint"); - - if (ap) - { - ap_sig = ap->signal_properties_changed().connect( - sigc::mem_fun(*this, &WifiConnectionInfo::on_properties_changed)); - } - } - - void on_properties_changed(DBusPropMap changed, DBusPropList invalid) - { - bool needs_refresh = false; - for (auto& prop : changed) - { - if (prop.first == STRENGTH) - { - needs_refresh = true; - } - } - - if (needs_refresh) - { - widget->update_icon(); - widget->update_status(); - } - } - - int get_strength() - { - assert(ap); - - Glib::Variant vstr; - ap->get_cached_property(vstr, STRENGTH); - - return vstr.get(); - } - - std::string get_strength_str() - { - int value = get_strength(); - - if (value > 80) - { - return "excellent"; - } - - if (value > 55) - { - return "good"; - } - - if (value > 30) - { - return "ok"; - } - - if (value > 5) - { - return "weak"; - } + network_manager = NetworkManager::getInstance(); + button = std::make_unique("panel"); + button->add_css_class("widget-icon"); + button->add_css_class("flat"); + button->add_css_class("network"); - return "none"; - } + container->append(*button); + button->set_child(button_content); + button->add_css_class("flat"); - virtual std::string get_icon_name(WfConnectionState state) - { - if ((state <= CSTATE_ACTIVATING) || (state == CSTATE_DEACTIVATING)) - { - return "network-wireless-acquiring-symbolic"; - } + button->get_popover()->set_child(control); - if (state == CSTATE_DEACTIVATED) - { - return "network-wireless-disconnected-symbolic"; - } + button_content.set_valign(Gtk::Align::CENTER); + button_content.append(icon); + button_content.append(status); + button_content.set_spacing(6); - if (ap) - { - return "network-wireless-signal-" + get_strength_str() + "-symbolic"; - } else - { - return "network-wireless-no-route-symbolic"; - } - } + icon.set_valign(Gtk::Align::CENTER); - virtual int get_connection_strength() + auto show_hide_label = [this] () { - if (ap) + if (no_label) { - return get_strength(); + status.hide(); } else { - return 100; - } - } - - virtual std::string get_ip() - { - return "0.0.0.0"; - } - - virtual ~WifiConnectionInfo() - {} -}; - -struct EthernetConnectionInfo : public WfNetworkConnectionInfo -{ - DBusProxy ap; - EthernetConnectionInfo(const DBusConnection& connection, std::string path) - {} - - virtual std::string get_icon_name(WfConnectionState state) - { - if ((state <= CSTATE_ACTIVATING) || (state == CSTATE_DEACTIVATING)) - { - return "network-wired-acquiring-symbolic"; + status.show(); } - - if (state == CSTATE_DEACTIVATED) - { - return "network-wired-disconnected-symbolic"; - } - - return "network-wired-symbolic"; - } - - std::string get_connection_name() - { - return "Ethernet - " + connection_name; - } - - std::string get_strength_str() - { - return "excellent"; - } - - virtual int get_connection_strength() - { - return 100; - } - - virtual std::string get_ip() - { - return "0.0.0.0"; - } - - virtual ~EthernetConnectionInfo() - {} -}; - - -/* TODO: handle Connectivity */ - -static WfConnectionState get_connection_state(DBusProxy connection) -{ - if (!connection) - { - return CSTATE_DEACTIVATED; - } - - Glib::Variant state; - connection->get_cached_property(state, "State"); - return (WfConnectionState)state.get(); -} - -void WayfireNetworkInfo::update_icon() -{ - auto icon_name = info->get_icon_name( - get_connection_state(active_connection_proxy)); - icon.set_from_icon_name(icon_name); -} - -struct status_color -{ - int point; - Gdk::RGBA rgba; -} status_colors[] = { - {0, Gdk::RGBA{"#ff0000"}}, - {25, Gdk::RGBA{"#ff0000"}}, - {40, Gdk::RGBA{"#ffff55"}}, - {100, Gdk::RGBA{"#00ff00"}}, -}; - -#define MAX_COLORS (sizeof(status_colors) / sizeof(status_color)) - -void WayfireNetworkInfo::update_status() -{ - std::string description = info->get_connection_name(); - - status.set_text(description); - button.set_tooltip_text(description); - - status.remove_css_class("excellent"); - status.remove_css_class("good"); - status.remove_css_class("weak"); - status.remove_css_class("none"); - if (status_color_opt) - { - status.add_css_class(info->get_strength_str()); - } -} - -void WayfireNetworkInfo::update_active_connection() -{ - Glib::Variant active_conn_path; - nm_proxy->get_cached_property(active_conn_path, ACTIVE_CONNECTION); - - if (active_conn_path && (active_conn_path.get() != "/")) - { - active_connection_proxy = Gio::DBus::Proxy::create_sync( - connection, NM_DBUS_NAME, active_conn_path.get(), - "org.freedesktop.NetworkManager.Connection.Active"); - } else - { - active_connection_proxy = DBusProxy(); - } - - auto set_no_connection = [=] () - { - info = std::unique_ptr(new NoConnectionInfo()); - info->connection_name = "No connection"; }; + no_label.set_callback(show_hide_label); + show_hide_label(); - if (!active_connection_proxy) + auto click = Gtk::GestureClick::create(); + click->set_button(3); + signals.push_back(click->signal_released().connect( + [this] (int, double, double) { - set_no_connection(); - } else + on_click(); + })); + signals.push_back(click->signal_pressed().connect( + [click] (int, double, double) { - Glib::Variant vtype, vobject; - active_connection_proxy->get_cached_property(vtype, "Type"); - active_connection_proxy->get_cached_property(vobject, "SpecificObject"); - auto type = vtype.get(); - auto object = vobject.get(); + click->set_state(Gtk::EventSequenceState::CLAIMED); + })); - if (type.find("wireless") != type.npos) - { - info = std::unique_ptr( - new WifiConnectionInfo(connection, object, this)); - } else if (type.find("ethernet") != type.npos) - { - info = std::unique_ptr( - new EthernetConnectionInfo(connection, object)); - } else if (type.find("bluetooth")) - { - std::cout << "Unimplemented: bluetooth connection" << std::endl; - set_no_connection(); - // TODO - } else - { - std::cout << "Unimplemented: unknown connection type" << std::endl; - set_no_connection(); - // TODO: implement Unknown connection - } + auto touch = Gtk::GestureLongPress::create(); + touch->set_touch_only(true); + signals.push_back(touch->signal_pressed().connect( + [this] (double, double) + { + on_click(); + })); - Glib::Variant vname; - active_connection_proxy->get_cached_property(vname, "Id"); - info->connection_name = vname.get(); - } + button->add_controller(touch); + button->add_controller(click); - update_icon(); - update_status(); + signals.push_back(network_manager->signal_default_changed().connect( + sigc::mem_fun(*this, &WayfireNetworkInfo::set_connection))); + set_connection(network_manager->get_primary_network()); } -void WayfireNetworkInfo::on_nm_properties_changed( - const Gio::DBus::Proxy::MapChangedProperties& properties, - const std::vector& invalidated) +void WayfireNetworkInfo::set_connection(std::shared_ptr network) { - for (auto & prop : properties) + for (auto clas : button_content.get_css_classes()) { - if (prop.first == ACTIVE_CONNECTION) + if ((clas == "flat") || (clas == "network") || (clas == "widget-icon")) { - update_active_connection(); + continue; } - } -} -bool WayfireNetworkInfo::setup_dbus() -{ - auto cancellable = Gio::Cancellable::create(); - connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM, cancellable); - if (!connection) - { - std::cerr << "Failed to connect to dbus" << std::endl; - return false; + button_content.remove_css_class(clas); } - nm_proxy = Gio::DBus::Proxy::create_sync(connection, NM_DBUS_NAME, - "/org/freedesktop/NetworkManager", - "org.freedesktop.NetworkManager"); - if (!nm_proxy) + for (auto clas : network->get_css_classes()) { - std::cerr << "Failed to connect to network manager, " << - "are you sure it is running?" << std::endl; - return false; + button_content.add_css_class(clas); } - signals.push_back(nm_proxy->signal_properties_changed().connect( - sigc::mem_fun(*this, &WayfireNetworkInfo::on_nm_properties_changed))); - - return true; + status.set_label(network->get_name()); + icon.set_from_icon_name(network->get_icon_symbolic()); } void WayfireNetworkInfo::on_click() @@ -371,63 +102,9 @@ void WayfireNetworkInfo::on_click() Glib::spawn_command_line_async((std::string)click_command_opt); } else { - info->spawn_control_center(nm_proxy); - } -} - -void WayfireNetworkInfo::init(Gtk::Box *container) -{ - if (!setup_dbus()) - { - enabled = false; - return; - } - - button.add_css_class("widget-icon"); - button.add_css_class("flat"); - button.add_css_class("network"); - - container->append(button); - button.set_child(button_content); - button.add_css_class("flat"); - - signals.push_back(button.signal_clicked().connect( - sigc::mem_fun(*this, &WayfireNetworkInfo::on_click))); - - button_content.set_valign(Gtk::Align::CENTER); - button_content.append(icon); - button_content.append(status); - button_content.set_spacing(6); - - icon.set_valign(Gtk::Align::CENTER); - signals.push_back(icon.property_scale_factor().signal_changed().connect( - sigc::mem_fun(*this, &WayfireNetworkInfo::update_icon))); - icon.add_css_class("network-icon"); - - update_active_connection(); - handle_config_reload(); -} - -void WayfireNetworkInfo::handle_config_reload() -{ - if (status_opt.value() == NETWORK_STATUS_ICON) - { - if (status.get_parent()) - { - button_content.remove(status); - } - } else - { - if (!status.get_parent()) - { - button_content.append(status); - } + std::string command = "env XDG_CURRENT_DESKTOP=GNOME gnome-control-center"; + Glib::spawn_command_line_async(command); } - - // TODO: show IP for "full" status - - update_icon(); - update_status(); } WayfireNetworkInfo::~WayfireNetworkInfo() diff --git a/src/panel/widgets/network.hpp b/src/panel/widgets/network.hpp index e6ab1d90..271ac444 100644 --- a/src/panel/widgets/network.hpp +++ b/src/panel/widgets/network.hpp @@ -5,82 +5,34 @@ #include #include #include +#include #include +#include "wf-popover.hpp" #include "../widget.hpp" - -using DBusConnection = Glib::RefPtr; -using DBusProxy = Glib::RefPtr; - -using DBusPropMap = const Gio::DBus::Proxy::MapChangedProperties&; -using DBusPropList = const std::vector&; - -enum WfConnectionState // NmActiveConnectionState -{ - CSTATE_UNKNOWN = 0, - CSTATE_ACTIVATING = 1, - CSTATE_ACTIVATED = 2, - CSTATE_DEACTIVATING = 3, - CSTATE_DEACTIVATED = 4, -}; - -struct WfNetworkConnectionInfo -{ - std::string connection_name; - - virtual void spawn_control_center(DBusProxy& nm); - virtual std::string get_control_center_section(DBusProxy& nm); - - virtual std::string get_connection_name() - { - return connection_name; - } - - virtual std::string get_icon_name(WfConnectionState state) = 0; - virtual int get_connection_strength() = 0; - virtual std::string get_ip() = 0; - virtual std::string get_strength_str() = 0; - - virtual ~WfNetworkConnectionInfo() - {} -}; - -static const std::string NETWORK_STATUS_ICON = "none"; -static const std::string NETWORK_STATUS_CONN_NAME = "connection"; -static const std::string NETWORK_STATUS_NAME_IP = "full"; +#include "../../util/network/manager.hpp" +#include "network/network-widget.hpp" class WayfireNetworkInfo : public WayfireWidget { - DBusConnection connection; - DBusProxy nm_proxy, active_connection_proxy; - - std::unique_ptr info; - + void on_click(); std::vector signals; - - Gtk::Button button; + std::shared_ptr network_manager; + std::unique_ptr button; Gtk::Box button_content; Gtk::Image icon; Gtk::Label status; - - bool enabled = true; WfOption status_opt{"panel/network_status"}; WfOption status_color_opt{"panel/network_status_use_color"}; WfOption status_font_opt{"panel/network_status_font"}; WfOption click_command_opt{"panel/network_onclick_command"}; + WfOption no_label{"panel/network_no_label"}; - bool setup_dbus(); - void update_active_connection(); - void on_nm_properties_changed(DBusPropMap properties, - DBusPropList invalidated); - - void on_click(); + NetworkControlWidget control; public: - void update_icon(); - void update_status(); - + WayfireNetworkInfo(); + ~WayfireNetworkInfo(); void init(Gtk::Box *container); - void handle_config_reload(); - virtual ~WayfireNetworkInfo(); + void set_connection(std::shared_ptr network); }; diff --git a/src/util/meson.build b/src/util/meson.build index 5621206e..d835637f 100644 --- a/src/util/meson.build +++ b/src/util/meson.build @@ -8,9 +8,26 @@ util = static_library( 'css-config.cpp', 'wf-ipc.cpp', 'animated-scale.cpp', + 'network/manager.cpp', + 'network/wifi.cpp', + 'network/wired.cpp', + 'network/network-widget.cpp', + 'network/wifi-ap.cpp', + 'network/network.cpp', + 'network/modem.cpp', + 'network/settings.cpp', + 'network/connection.cpp', + ], + dependencies: [ + wf_protos, + gtklayershell, + wayland_client, + gtkmm, + wfconfig, + libinotify, + json, ], - dependencies: [wf_protos, gtklayershell, wayland_client, gtkmm, wfconfig, libinotify, json], ) util_includes = include_directories('.') -libutil = declare_dependency(link_with: util, include_directories: util_includes) +libutil = declare_dependency(link_with: util, include_directories: util_includes) \ No newline at end of file diff --git a/src/util/network/bluetooth.hpp b/src/util/network/bluetooth.hpp new file mode 100644 index 00000000..b0bf2ecc --- /dev/null +++ b/src/util/network/bluetooth.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "network.hpp" +class BluetoothNetwork : public Network +{ + public: + std::shared_ptr bluetooth_proxy; + BluetoothNetwork(std::string path, std::shared_ptr device_proxy, + std::shared_ptr bluetooth_proxy) : + Network(path, device_proxy), bluetooth_proxy(bluetooth_proxy) + {} + + std::string get_name() override + { + if (bluetooth_proxy) + { + Glib::Variant name; + bluetooth_proxy->get_cached_property(name, "Name"); + return name.get(); + } + + return "Bluetooth"; + } + + std::string get_icon_name() override + { + if (is_active()) + { + return "network-bluetooth-activated"; + } + + return "network-bluetooth-inactive"; + } + + std::vector get_css_classes() override + { + return {"bluetooth"}; + } + + std::string get_friendly_name() override + { + return "Bluetooth"; + } +}; diff --git a/src/util/network/connection.cpp b/src/util/network/connection.cpp new file mode 100644 index 00000000..0b3c5c15 --- /dev/null +++ b/src/util/network/connection.cpp @@ -0,0 +1,89 @@ +#include "connection.hpp" +#include "network/manager.hpp" + +Connection::Connection() : + Network("/", nullptr), connection_proxy(nullptr), devices({}) +{} + +Connection::Connection(std::string path, std::shared_ptr connection_proxy, + std::vector> devices) : + Network(path, nullptr), connection_proxy(connection_proxy), devices(devices) +{ + /* Bubble up emits from any device here */ + for (auto & it : devices) + { + signals.push_back(it->signal_network_altered().connect( + [this] () + { + network_altered.emit(); + })); + } + + Glib::Variant vpn_data; + connection_proxy->get_cached_property(vpn_data, "Vpn"); + has_vpn = vpn_data.get(); + + if (has_vpn) + { + Glib::Variant vpn_path_start; + connection_proxy->get_cached_property(vpn_path_start, "Connection"); + vpn_path = vpn_path_start.get(); + } +} + +Connection::~Connection() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +std::string Connection::get_name() +{ + if (devices.size() == 0) + { + return "No connection"; + } + + std::string secure = ""; + if (has_vpn) + { + auto settings = NetworkManager::getInstance()->get_vpn(vpn_path); + if (settings) + { + secure = " with " + settings->name; + } else + { + secure = " with VPN"; + } + } + + return devices[0]->get_name() + secure; +} + +std::string Connection::get_icon_name() +{ + if (devices.size() == 0) + { + return "network-disconnected"; + } + + std::string secure = ""; + if (has_vpn) + { + secure = devices[0]->get_secure_variant(); + } + + return devices[0]->get_icon_name() + secure; +} + +std::vector Connection::get_css_classes() +{ + if (devices.size() == 0) + { + return {"none"}; + } + + return devices[0]->get_css_classes(); +} diff --git a/src/util/network/connection.hpp b/src/util/network/connection.hpp new file mode 100644 index 00000000..b42d72f3 --- /dev/null +++ b/src/util/network/connection.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include +#include + +#include "network.hpp" + +/* Information about an active connection */ +class Connection : public Network +{ + public: + bool has_vpn; + std::vector signals; + std::shared_ptr connection_proxy, vpn_proxy; + std::vector> devices; + std::string vpn_path = ""; + + Connection(); + Connection(std::string path, std::shared_ptr connection_proxy, + std::vector> devices); + ~Connection(); + std::string get_name(); + std::string get_icon_name(); + std::vector get_css_classes(); +}; diff --git a/src/util/network/manager.cpp b/src/util/network/manager.cpp new file mode 100644 index 00000000..8a9ac238 --- /dev/null +++ b/src/util/network/manager.cpp @@ -0,0 +1,714 @@ +#include +#include +#include +#include +#include + +#include "manager.hpp" +#include "bluetooth.hpp" +#include "connection.hpp" +#include "gtkmm/enums.h" +#include "network.hpp" +#include "network/settings.hpp" +#include "sigc++/functors/mem_fun.h" +#include "vpn.hpp" +#include "wifi.hpp" +#include "modem.hpp" +#include "wired.hpp" +#include "null.hpp" +#define NM_DBUS_NAME "org.freedesktop.NetworkManager" +#define MM_DBUS_NAME "org.freedesktop.ModemManager1" +#define ACTIVE_CONNECTION "PrimaryConnection" +#define STRENGTH "Strength" + +#define NM_PATH "/org/freedesktop/NetworkManager" + +#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" +#define NM_INTERFACE "org.freedesktop.NetworkManager" + +#define ETHERNET_TYPE 1 +#define WIFI_TYPE 2 +#define MODEM_TYPE 8 +#define GENERIC_TYPE 14 +#define BLUETOOTH_TYPE 5 + +NetworkManager::NetworkManager() +{ + popup_window.set_child(popup_box); + popup_box.append(popup_label); + popup_box.append(popup_entry); + popup_box.set_orientation(Gtk::Orientation::VERTICAL); + popup_entry.set_visibility(false); + our_signals.push_back(popup_entry.signal_activate().connect( + sigc::mem_fun(*this, &NetworkManager::submit_password))); + Gio::DBus::Proxy::create_for_bus(Gio::DBus::BusType::SYSTEM, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + [this] (const Glib::RefPtr & result) + { + // Got a dbus proxy + manager_proxy = Gio::DBus::Proxy::create_finish(result); + auto val = manager_proxy->call_sync("ListNames"); + Glib::Variant> list; + val.get_child(list, 0); + auto l2 = list.get(); + for (auto t : l2) + { + if (t == NM_DBUS_NAME) + { + connect_nm(); + } + + if (t == MM_DBUS_NAME) + { + mm_start.emit(); + } + } + + /* https://dbus.freedesktop.org/doc/dbus-java/api/org/freedesktop/DBus.NameOwnerChanged.html */ + our_signals.push_back(manager_proxy->signal_signal().connect( + [this] (const Glib::ustring & sender_name, + const Glib::ustring & signal_name, + const Glib::VariantContainerBase & params) + { + if (signal_name == "NameOwnerChanged") + { + Glib::Variant to, from, name; + params.get_child(name, 0); + params.get_child(from, 1); + params.get_child(to, 2); + if (name.get() == NM_DBUS_NAME) + { + if (from.get() == "") + { + connect_nm(); + } else if (to.get() == "") + { + lost_nm(); + } + } else if (name.get() == MM_DBUS_NAME) + { + if (from.get() == "") + { + mm_start.emit(); + } else if (to.get() == "") + { + for_each(all_devices.cbegin(), all_devices.cend(), + [this] (std::map>::const_reference it) + { + if (std::dynamic_pointer_cast(it.second) != nullptr) + { + device_removed.emit(it.second); + all_devices.erase(it.first); + } + }); + mm_stop.emit(); + } + } + } + })); + }); +} + +void NetworkManager::setting_added(std::string path) +{ + auto proxy = Gio::DBus::Proxy::create_sync( + connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Settings.Connection"); + + all_settings.emplace(path, + new NetworkSettings(path, proxy)); + auto setting = all_settings[path]; + + if (setting->get_ssid() != "") + { + for (auto & device : all_devices) + { + auto wifi = std::dynamic_pointer_cast(device.second); + if (wifi) + { + for (auto & ap : wifi->all_access_points) + { + if (ap.second && (ap.second->get_ssid() == setting->get_ssid())) + { + ap.second->set_has_saved_password(true); + } + } + } + } + } +} + +void NetworkManager::setting_removed(std::string path) +{ + auto setting = all_settings[path]; + if (setting) + { + for (auto & device : all_devices) + { + auto wifi = std::dynamic_pointer_cast(device.second); + if (wifi) + { + for (auto & ap : wifi->all_access_points) + { + if (ap.second->get_ssid() == setting->get_ssid()) + { + ap.second->set_has_saved_password(false); + } + } + } + } + } + + all_settings.erase(path); +} + +void NetworkManager::lost_nm() +{ + if (primary_signal) + { + primary_signal.disconnect(); + } + + if (debounce) + { + debounce.disconnect(); + } + + std::cout << "NetworkManager Lost" << std::endl; + connection = nullptr; + settings_proxy = nullptr; + nm_proxy = nullptr; + all_devices.clear(); + all_vpns.clear(); + primary_connection = "/"; + primary_connection_obj = std::make_shared(); + for (auto signal : nm_dbus_signals) + { + signal.disconnect(); + } + + nm_stop.emit(); +} + +void NetworkManager::connect_nm() +{ + std::cout << "NetworkManager Found" << std::endl; + all_devices.emplace("/", new NullNetwork()); + connection = Gio::DBus::Connection::get_sync(Gio::DBus::BusType::SYSTEM); + if (!connection) + { + std::cerr << "Failed to connect to dbus" << std::endl; + return; + } + + /* Get known VPNs */ + settings_proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + "/org/freedesktop/NetworkManager/Settings", + "org.freedesktop.NetworkManager.Settings"); + if (!settings_proxy) + { + std::cerr << "No NM Settings proxy" << std::endl; + return; + } + + auto ret1 = settings_proxy->call_sync("ListConnections").get_child(); + Glib::Variant> ret = + Glib::VariantBase::cast_dynamic>>(ret1); + for (auto & it : ret.get()) + { + setting_added(it); + check_add_vpn(it); + vpn_added.emit(it); + } + + nm_dbus_signals.push_back(settings_proxy->signal_signal().connect( + [this] (const Glib::ustring& sender, const Glib::ustring& signal, + const Glib::VariantContainerBase& container) + { + if (signal == "ConnectionRemoved") + { + auto var = + Glib::VariantBase::cast_dynamic>(container.get_child()).get(); + all_settings.erase(var); + all_vpns.erase(var); + vpn_removed.emit(var); + } else if (signal == "NewConnection") + { + auto var = + Glib::VariantBase::cast_dynamic>(container.get_child()).get(); + setting_added(var); + check_add_vpn(var); + vpn_added.emit(var); + } + })); + + nm_proxy = Gio::DBus::Proxy::create_sync(connection, NM_DBUS_NAME, + NM_PATH, + NM_INTERFACE); + if (!nm_proxy) + { + std::cerr << "Failed to connect to network manager, " << + "are you sure it is running?" << std::endl; + return; + } + + nm_dbus_signals.push_back(nm_proxy->signal_properties_changed().connect( + sigc::mem_fun(*this, &NetworkManager::on_nm_properties_changed))); + + nm_dbus_signals.push_back(nm_proxy->signal_signal().connect( + sigc::mem_fun(*this, &NetworkManager::on_nm_signal))); + + /* Fill Initial List*/ + + nm_proxy->call("GetAllDevices", sigc::mem_fun(*this, &NetworkManager::get_all_devices_cb)); +} + +void NetworkManager::check_add_vpn(std::string path) +{ + auto proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Settings.Connection"); + + auto values = Glib::VariantBase::cast_dynamic>>>(proxy->call_sync("GetSettings").get_child()); + + auto hash = values.get(); + if ((hash.count("connection") == 1) && (hash["connection"].count("id") == 1) && + (hash["connection"].count("type") == 1)) + { + auto contype = hash["connection"]["type"]; + auto conname = hash["connection"]["id"]; + auto strtype = Glib::VariantType("s"); + if (contype.is_of_type(strtype) && conname.is_of_type(strtype)) + { + auto name = Glib::VariantBase::cast_dynamic>(conname).get(); + auto contype_str = Glib::VariantBase::cast_dynamic>(contype).get(); + if (contype_str == "vpn") + { + all_vpns[path] = std::make_shared(path, proxy, name); + } + } else + { + std::cerr << "INVALID TYPES " << conname.get_type_string() << " " << contype.get_type_string() << + std::endl; + } + } +} + +void NetworkManager::get_all_devices_cb(std::shared_ptr async) +{ + auto list = Glib::VariantBase::cast_dynamic>>(nm_proxy->call_finish( + async).get_child()).get(); + for (auto & val : list) + { + add_network(val); + } + + /* Now get the current connection */ + nm_dbus_signals.push_back(Glib::signal_idle().connect([this] () + { + Glib::Variant path_read; + nm_proxy->get_cached_property(path_read, "PrimaryConnection"); + changed_primary(path_read.get()); + return G_SOURCE_REMOVE; + })); + /* And emit a start event */ + nm_start.emit(); +} + +void NetworkManager::add_network(std::string path) +{ + Glib::RefPtr device_proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Device"); + Glib::Variant type; + device_proxy->get_cached_property(type, "DeviceType"); + uint connection_type = type.get(); + if (connection_type == WIFI_TYPE) + { + auto wifi_proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Device.Wireless"); + all_devices.emplace(path, new WifiNetwork(path, device_proxy, wifi_proxy)); + device_added.emit(all_devices[path]); + } else if (connection_type == MODEM_TYPE) + { + auto modem_proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Device.Modem"); + all_devices.emplace(path, new ModemNetwork(path, device_proxy, modem_proxy)); + device_added.emit(all_devices[path]); + } else if (connection_type == BLUETOOTH_TYPE) + { + auto bluetooth_proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Device.Bluetooth"); + all_devices.emplace(path, new BluetoothNetwork(path, device_proxy, bluetooth_proxy)); + device_added.emit(all_devices[path]); + } else if (connection_type == ETHERNET_TYPE) + { + all_devices.emplace(path, new WiredNetwork(path, device_proxy)); + device_added.emit(all_devices[path]); + } +} + +void NetworkManager::on_nm_properties_changed(const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) +{ + for (auto & it : properties) + { + if (it.first == "PrimaryConnection") + { + auto value = Glib::VariantBase::cast_dynamic>(it.second).get(); + changed_primary(value); + } else if ((it.first == "NetworkingEnabled") || (it.first == "WirelessEnabled") || + (it.first == "WirelessHardwareEnabled") || + (it.first == "WwanEnabled") || (it.first == "WwanHardwareEnabled")) + { + global_change.emit(); + } + } +} + +void NetworkManager::changed_primary(std::string value) +{ + if (value != primary_connection) + { + if (primary_signal) + { + primary_signal.disconnect(); + } + + primary_connection = value; + auto network = get_connection(value); + primary_connection_obj = network; + + /* Any change inside the primary connection also called default_changed */ + primary_signal = network->signal_network_altered().connect( + [this, network] () + { + default_changed.emit(network); + }); + /* Tell clients */ + default_changed.emit(network); + } +} + +std::shared_ptr NetworkManager::get_connection(std::string path) +{ + auto connection_proxy = Gio::DBus::Proxy::create_sync(connection, + NM_DBUS_NAME, + path, + "org.freedesktop.NetworkManager.Connection.Active"); + + std::vector> list; + Glib::Variant> paths; + connection_proxy->get_cached_property(paths, "Devices"); + if (paths) + { + for (auto & it : paths.get()) + { + list.push_back(all_devices[it]); + } + } + + return std::make_shared(path, connection_proxy, list); +} + +void NetworkManager::on_nm_signal(const Glib::ustring& sender, const Glib::ustring& signal, + const Glib::VariantContainerBase& container) +{ + if (signal == "DeviceAdded") + { + auto val = Glib::VariantBase::cast_dynamic>(container.get_child()).get(); + add_network(val); + } else if (signal == "DeviceRemoved") + { + auto val = Glib::VariantBase::cast_dynamic>(container.get_child()).get(); + all_devices.erase(val); + } else + { + return; + } + + /* NM list changed, but let's not send instantly */ + if (debounce) + { + debounce.disconnect(); + } + + debounce = Glib::signal_timeout().connect([this] () + { + signal_device_list_changed().emit(all_devices); + return G_SOURCE_REMOVE; + }, 100); +} + +std::shared_ptr NetworkManager::get_primary_network() +{ + return primary_connection_obj; +} + +void NetworkManager::activate_connection(std::string p1, std::string p2, std::string p3) +{ + Glib::VariantStringBase path1, path2, path3; + Glib::VariantStringBase::create_object_path(path1, p1); + Glib::VariantStringBase::create_object_path(path2, p2); + Glib::VariantStringBase::create_object_path(path3, p3); + auto paths = Glib::VariantContainerBase::create_tuple({path1, path2, path3}); + // auto data = Glib::VariantContainerBase::create_tuple(paths); + + try { + nm_proxy->call_sync("ActivateConnection", paths); + } catch (...) + { + /* It's most likely a WIFI AP with no password set. Let's ask */ + std::cout << p2 << std::endl; + if (p2.find("/Devices/") != std::string::npos) + { + auto device = std::dynamic_pointer_cast(all_devices[p2]); + if (device) + { + popup_cache_p2 = p2; + popup_cache_p3 = p3; + auto ap = device->get_access_points()[p3]; + popup_label.set_label("Preshared Key required for Access Point '" + ap->get_ssid() + "'"); + popup_window.present(); + popup_window.get_focus(); + } + } + } +} + +void NetworkManager::deactivate_connection(std::string p1) +{ + auto manager = NetworkManager::getInstance()->get_nm_proxy(); + Glib::VariantStringBase path1; + Glib::VariantStringBase::create_object_path(path1, p1); + auto paths = Glib::VariantContainerBase::create_tuple({path1}); + // auto data = Glib::VariantContainerBase::create_tuple(paths); + + try { + manager->call_sync("DeactivateConnection", paths); + } catch (...) + {} +} + +/* Is Wifi Enabled, in software and in rfkill */ +std::tuple NetworkManager::wifi_global_enabled() +{ + if (!nm_proxy) + { + return {false, false}; + } + + Glib::Variant wifisoft, wifihard; + nm_proxy->get_cached_property(wifisoft, "WirelessEnabled"); + nm_proxy->get_cached_property(wifihard, "WirelessHardwareEnabled"); + return {wifisoft.get(), wifihard.get()}; +} + +/* Is Mobile Data Enabled, in software and in rfkill */ +std::tuple NetworkManager::mobile_global_enabled() +{ + if (!nm_proxy) + { + return {false, false}; + } + + Glib::Variant modemsoft, modemhard; + nm_proxy->get_cached_property(modemsoft, "WwanEnabled"); + nm_proxy->get_cached_property(modemhard, "WwanHardwareEnabled"); + return {modemsoft.get(), modemhard.get()}; +} + +/* Is Networking enabled */ +bool NetworkManager::networking_global_enabled() +{ + if (!nm_proxy) + { + return false; + } + + Glib::Variant enabled; + nm_proxy->get_cached_property(enabled, "NetworkingEnabled"); + return enabled.get(); +} + +void NetworkManager::wifi_global_set(bool value) +{ + auto another_proxy = Gio::DBus::Proxy::create_sync(nm_proxy->get_connection(), + NM_DBUS_NAME, + NM_PATH, + DBUS_PROPERTIES_INTERFACE); + + auto params = Glib::VariantContainerBase::create_tuple( + { + Glib::Variant::create(NM_INTERFACE), + Glib::Variant::create("WirelessEnabled"), + Glib::Variant::create(Glib::Variant::create(value)) + }); + another_proxy->call_sync("Set", params); +} + +void NetworkManager::mobile_global_set(bool value) +{ + auto another_proxy = Gio::DBus::Proxy::create_sync(nm_proxy->get_connection(), + NM_DBUS_NAME, + NM_PATH, + DBUS_PROPERTIES_INTERFACE); + + auto params = Glib::VariantContainerBase::create_tuple( + { + Glib::Variant::create(NM_INTERFACE), + Glib::Variant::create("WwanEnabled"), + + Glib::Variant::create(Glib::Variant::create(value)) + }); + another_proxy->call_sync("Set", params); +} + +std::shared_ptr NetworkManager::get_setting_for_ssid(std::string ssid) +{ + if (ssid == "") + { + return nullptr; + } + + for (auto setting : all_settings) + { + if (setting.second->get_ssid() == ssid) + { + return setting.second; + } + } + + return nullptr; +} + +std::shared_ptr NetworkManager::get_vpn(std::string path) +{ + return all_vpns[path]; +} + +void NetworkManager::submit_password() +{ + auto password = popup_entry.get_text(); + if (password.length() == 0) + { + return; + } + + popup_entry.set_text(""); + auto wifi = std::dynamic_pointer_cast(all_devices[popup_cache_p2]); + if (!wifi) + { + return; + } + + auto ap = wifi->get_access_points()[popup_cache_p3]; + if (!ap) + { + return; + } + + auto ssid = ap->get_ssid(); + if (ssid.length() == 0) + { + return; + } + + popup_window.hide(); + + // --- Build settings using glibmm (correct types) --- + + // SSID as byte array 'ay' + std::vector ssid_bytes(ssid.begin(), ssid.end()); + auto ssid_variant = Glib::Variant>::create(ssid_bytes); + + // UUID + gchar *uuid_c = g_uuid_string_random(); + Glib::ustring uuid(uuid_c); + g_free(uuid_c); + + // ----- connection (a{sv}) + std::map connection_map; + connection_map["type"] = + Glib::Variant::create("802-11-wireless"); + connection_map["id"] = Glib::Variant::create(ssid); + connection_map["uuid"] = Glib::Variant::create(uuid); + + // ----- 802-11-wireless (a{sv}) + std::map wifi_map; + wifi_map["ssid"] = ssid_variant; + wifi_map["mode"] = Glib::Variant::create("infrastructure"); + + // ----- 802-11-wireless-security (a{sv}) + std::map sec_map; + bool use_security = !password.empty(); + if (use_security) + { + sec_map["key-mgmt"] = Glib::Variant::create("wpa-psk"); + sec_map["psk"] = Glib::Variant::create(password); + } + + // ------------------------ + // TOP-LEVEL SETTINGS (a{sa{sv}}) + // ------------------------ + std::map> + settings_map; + + settings_map["connection"] = connection_map; + settings_map["802-11-wireless"] = wifi_map; + if (use_security) + { + settings_map["802-11-wireless-security"] = sec_map; + } + + auto settings = Glib::Variant< + std::map>>:: + create(settings_map); + // ------------------------ + // Object paths (o) + // ------------------------ + auto device_path = + Glib::Variant::create(popup_cache_p2); + // Access point path is "/" → NM autoselects AP matching SSID + auto ap_path = Glib::Variant::create("/"); + // ------------------------ + // FINAL TUPLE (a{sa{sv}}, o, o) + // ------------------------ + std::vector args_vec = {settings, device_path, ap_path}; + auto args = Glib::VariantContainerBase::create_tuple(args_vec); + + // ------------------------ + // CALL NetworkManager + // ------------------------ + nm_proxy->call_sync("AddAndActivateConnection", args); +} + +void NetworkManager::networking_global_set(bool value) +{ + auto params = Glib::VariantContainerBase::create_tuple(Glib::Variant::create(value)); + nm_proxy->call_sync("Enable", params); +} + +NetworkManager::~NetworkManager() +{ + lost_nm(); + /* Signals that outlast each NetworkManager start/stop but need to clear on widget reload */ + for (auto signal : our_signals) + { + signal.disconnect(); + } +} diff --git a/src/util/network/manager.hpp b/src/util/network/manager.hpp new file mode 100644 index 00000000..17e4c21b --- /dev/null +++ b/src/util/network/manager.hpp @@ -0,0 +1,182 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#include "network.hpp" +#include "connection.hpp" +#include "vpn.hpp" +#include "settings.hpp" + +using type_signal_network = sigc::signal)>; +using type_signal_device_list_changed = sigc::signal>)>; +using type_signal_simple = sigc::signal; +using type_signal_path = sigc::signal; + +class NetworkManager +{ + private: + type_signal_network default_changed, device_added, device_removed; + type_signal_device_list_changed device_list_changed; + type_signal_simple global_change, nm_start, nm_stop, mm_start, mm_stop; + type_signal_path vpn_added, vpn_removed; + + + Glib::RefPtr connection; + Glib::RefPtr nm_proxy, settings_proxy, manager_proxy; + + /* Connections that must be severed after NM Dbus is lost, and this NM is deconstructed*/ + std::vector nm_dbus_signals, our_signals; + sigc::connection debounce, primary_signal; + + std::string primary_connection = ""; + + std::shared_ptr primary_connection_obj = std::make_shared(); + + std::map> all_devices; + std::map> all_vpns; + std::map> all_settings; + + void on_nm_properties_changed(const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated); + void on_nm_signal(const Glib::ustring&, const Glib::ustring&, const Glib::VariantContainerBase&); + void get_all_devices_cb(std::shared_ptr); + void add_network(std::string path); + void check_add_vpn(std::string path); + void changed_primary(std::string path); + void connect_nm(); + void lost_nm(); + + void setting_added(std::string path); + void setting_removed(std::string path); + + Gtk::Window popup_window; + Gtk::Box popup_box; + Gtk::Label popup_label; + Gtk::Entry popup_entry; + + std::string popup_cache_p2 = "", popup_cache_p3 = ""; + inline static std::weak_ptr instance; + + public: + /* Emitted when the default connection or it's properties change */ + type_signal_network signal_default_changed() + { + return default_changed; + } + + /* Emitted when any network device is connected or disconnected */ + type_signal_device_list_changed signal_device_list_changed() + { + return device_list_changed; + } + + /* Emitted when a networking device is added */ + type_signal_network signal_device_added() + { + return device_added; + } + + /* Emitted when a networking device is removed */ + type_signal_network signal_device_removed() + { + return device_removed; + } + + /* Emitted when any of the global enable toggles is changed */ + type_signal_simple signal_global_toggle() + { + return global_change; + } + + type_signal_path signal_vpn_added() + { + return vpn_added; + } + + type_signal_path signal_vpn_removed() + { + return vpn_removed; + } + + type_signal_simple signal_nm_start() + { + return nm_start; + } + + type_signal_simple signal_nm_stop() + { + return nm_stop; + } + + type_signal_simple signal_mm_start() + { + return mm_start; + } + + type_signal_simple signal_mm_stop() + { + return mm_stop; + } + + std::shared_ptr get_nm_proxy() + { + return nm_proxy; + } + + /* A list of current networks. */ + + std::map> get_all_vpns() + { + return all_vpns; + } + + std::map> get_all_devices() + { + return all_devices; + } + + static std::shared_ptr getInstance() + { + if (instance.expired()) + { + auto instance_now = std::make_shared(); + instance = instance_now; + return instance_now; + } + + return instance.lock(); + } + + static std::shared_ptr getInstanceIfExists() + { + return instance.lock(); + } + + NetworkManager(); + ~NetworkManager(); + + std::shared_ptr get_primary_network(); + std::shared_ptr get_connection(std::string path); + void activate_connection(std::string connection_path, std::string device_path, std::string details_path); + void deactivate_connection(std::string connection_path); + + + std::tuple wifi_global_enabled(); + std::tuple mobile_global_enabled(); + bool networking_global_enabled(); + + std::shared_ptr get_vpn(std::string path); + + void wifi_global_set(bool value); + void mobile_global_set(bool value); + void networking_global_set(bool value); + void submit_password(); + + std::shared_ptr get_setting_for_ssid(std::string ssid); +}; diff --git a/src/util/network/modem.cpp b/src/util/network/modem.cpp new file mode 100644 index 00000000..2a2d7d2b --- /dev/null +++ b/src/util/network/modem.cpp @@ -0,0 +1,219 @@ +#include "modem.hpp" +#include "giomm/dbusproxy.h" +#include "glibmm/variant.h" +#include +ModemNetwork::ModemNetwork(std::string path, std::shared_ptr device_proxy, + std::shared_ptr modem_proxy) : + Network(path, device_proxy), modem_proxy(modem_proxy) +{ + Glib::Variant device_data; + modem_proxy->get_cached_property(device_data, "DeviceId"); + + find_mm_proxy(device_data.get()); +} + +void ModemNetwork::find_mm_proxy(std::string dev_id) +{ + auto mm_om_proxy = Gio::DBus::Proxy::create_sync(modem_proxy->get_connection(), + "org.freedesktop.ModemManager1", + "/org/freedesktop/ModemManager1", + "org.freedesktop.DBus.ObjectManager"); + + auto ret1 = mm_om_proxy->call_sync("GetManagedObjects").get_child(); + auto ret = Glib::VariantBase::cast_dynamic>>>>(ret1); + + for (auto & it : ret.get()) + { + std::string modem_path = it.first; + for (auto & next : it.second) + { + if (next.first == "org.freedesktop.ModemManager1.Modem") + { + for (auto & why : next.second) + { + if (why.first == "DeviceIdentifier") + { + auto devid = Glib::VariantBase::cast_dynamic>(why.second); + if (devid.get() == dev_id) + { + mm_proxy = Gio::DBus::Proxy::create_sync(device_proxy->get_connection(), + "org.freedesktop.ModemManager1", + modem_path, + "org.freedesktop.ModemManager1.Modem"); + + modem_3gpp_proxy = Gio::DBus::Proxy::create_sync(device_proxy->get_connection(), + "org.freedesktop.ModemManager1", + modem_path, + "org.freedesktop.ModemManager1.Modem.Modem3gpp"); + } + } + } + } + } + } + + if (mm_proxy) + { + /* Get initial values */ + + Glib::VariantBase signal_pair; + mm_proxy->get_cached_property(signal_pair, "SignalQuality"); + + auto [a, b] = + Glib::VariantBase::cast_dynamic>>(signal_pair).get(); + strength = a; + + signals.push_back(mm_proxy->signal_properties_changed().connect( + [this] (const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) + { + for (auto & it : properties) + { + if (it.first == "SignalQuality") + { + auto container = Glib::VariantBase::cast_dynamic(it.second); + Glib::Variant signal_data; + container.get_child(signal_data, 0); + // "(ub)" => percent 0-100, 'is recent' + strength = signal_data.get(); + } + + if (it.first == "CurrentModes") + { + auto container = Glib::VariantBase::cast_dynamic(it.second); + Glib::Variant mode_data; + container.get_child(mode_data, 1); + caps = mode_data.get(); + } + } + })); + } else + { + std::cerr << "Could not get extra modem details" << std::endl; + } +} + +ModemNetwork::~ModemNetwork() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +std::string ModemNetwork::strength_string() +{ + if (strength >= 80) + { + return "excellent"; + } + + if (strength >= 55) + { + return "good"; + } + + if (strength >= 30) + { + return "ok"; + } + + if (strength >= 5) + { + return "weak"; + } + + return "none"; +} + +std::string ModemNetwork::get_name() +{ + if (!mm_proxy) + { + return "Misconfigured Mobile"; + } + + if (modem_3gpp_proxy) + { + Glib::Variant carrier; + modem_3gpp_proxy->get_cached_property(carrier, "OperatorName"); + return carrier.get(); + } + + return "Mobile"; +} + +std::string ModemNetwork::get_signal_band() +{ + if (strength == 100) + { + return "100"; + } else if (strength >= 80) + { + return "80"; + } else if (strength >= 60) + { + return "60"; + } else if (strength >= 40) + { + return "40"; + } else if (strength >= 20) + { + return "20"; + } + + return "0"; +} + +std::string ModemNetwork::get_connection_type_string() +{ + if (caps & CAP_5G) + { + return "5g"; + } else if (caps & CAP_4G) + { + return "lte"; + } else if (caps & CAP_3G) + { + return "hspa"; + } else if (caps & CAP_2G) + { + return "gprs"; + } else if (caps & CAP_CS) + { + return "edge"; + } + + return "edge"; +} + +std::string ModemNetwork::get_icon_name() +{ + if (!mm_proxy) + { + return "network-mobile-off"; + } + + if (!is_active()) + { + return "network-mobile-off"; + } + + return "network-mobile-" + get_signal_band() + "-" + get_connection_type_string(); +} + +std::string ModemNetwork::get_secure_variant() +{ + return "-locked"; +} + +std::vector ModemNetwork::get_css_classes() +{ + return {"modem", get_connection_type_string(), strength_string(), "carrier-" + get_name()}; +} + +std::string ModemNetwork::get_friendly_name() +{ + return "Mobile Data"; +} diff --git a/src/util/network/modem.hpp b/src/util/network/modem.hpp new file mode 100644 index 00000000..64e6354d --- /dev/null +++ b/src/util/network/modem.hpp @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include +#include + +#include "network.hpp" +#include "sigc++/connection.h" + +#define CAP_5G 16 +#define CAP_4G 8 +#define CAP_3G 4 +#define CAP_2G 2 +#define CAP_CS 1 + +class ModemNetwork : public Network +{ + public: + unsigned char strength = 0; + int caps = 8; + std::shared_ptr modem_3gpp_proxy; + std::shared_ptr modem_proxy; + std::shared_ptr mm_proxy; + ModemNetwork(std::string path, std::shared_ptr device_proxy, + std::shared_ptr modem_proxy); + void find_mm_proxy(std::string dev_id); + ~ModemNetwork(); + std::string strength_string(); + std::string get_name() override; + std::string get_signal_band(); + std::string get_connection_type_string(); + std::string get_icon_name() override; + std::string get_friendly_name() override; + std::string get_secure_variant() override; + std::vector get_css_classes() override; +}; diff --git a/src/util/network/network-widget.cpp b/src/util/network/network-widget.cpp new file mode 100644 index 00000000..b7aae4a9 --- /dev/null +++ b/src/util/network/network-widget.cpp @@ -0,0 +1,564 @@ +#include +#include +#include +#include + +#include "bluetooth.hpp" +#include "gtk/gtk.h" +#include "gtkmm/enums.h" +#include "manager.hpp" +#include "modem.hpp" +#include "network.hpp" +#include "vpn.hpp" +#include "wifi-ap.hpp" +#include "wifi.hpp" +#include "wired.hpp" +#include "network-widget.hpp" + + +AccessPointWidget::AccessPointWidget(std::string path_in, std::shared_ptr ap_in) : + ap(ap_in), path(path_in) +{ + add_css_class("access-point"); + append(overlay); + append(label); + append(band); + + set_hexpand(true); + label.set_hexpand(true); + label.set_justify(Gtk::Justification::LEFT); + label.set_halign(Gtk::Align::START); + + overlay.set_child(wifi); + overlay.add_overlay(security); + + auto update_ap = [this] () + { + label.set_label(ap->get_ssid()); + wifi.set_from_icon_name(ap->get_icon_name()); + security.set_from_icon_name(ap->get_security_icon_name()); + band.set_label(ap->get_band_name()); + auto classes = get_css_classes(); + for (auto css_class : classes) + { + remove_css_class(css_class); + } + + for (auto css_class : ap->get_css_classes()) + { + add_css_class(css_class); + } + }; + + band.set_halign(Gtk::Align::END); + band.add_css_class("band"); + band.set_justify(Gtk::Justification::RIGHT); + security.set_halign(Gtk::Align::END); + security.set_valign(Gtk::Align::END); + security.add_css_class("security"); + + + signals.push_back(ap_in->signal_altered().connect(update_ap)); + update_ap(); +} + +AccessPointWidget::~AccessPointWidget() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +std::shared_ptr AccessPointWidget::get_ap() +{ + return ap; +} + +VPNControlWidget::VPNControlWidget(std::shared_ptr config) : + config(config) +{ + append(image); + append(label); + label.set_label(config->name); + image.set_from_icon_name("network-vpn-symbolic"); +} + +VPNControlWidget::~VPNControlWidget() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +DeviceControlWidget::DeviceControlWidget(std::shared_ptr network) : + network(network) +{ + add_css_class("device"); + set_halign(Gtk::Align::FILL); + set_orientation(Gtk::Orientation::VERTICAL); + append(topbox); + append(revealer); + topbox.append(image); + topbox.append(label); + revealer.set_child(revealer_box); + revealer_box.set_orientation(Gtk::Orientation::VERTICAL); + auto wifi = std::dynamic_pointer_cast(network); + auto mobile = std::dynamic_pointer_cast(network); + auto wired = std::dynamic_pointer_cast(network); + auto bt = std::dynamic_pointer_cast(network); + if (wifi) + { + type = "wifi"; + for (auto & it : wifi->get_access_points()) + { + add_access_point(it.second); + } + + sort_access_points(); + + + signals.push_back(wifi->signal_add_access_point().connect( + [this] (std::shared_ptr ap) + { + add_access_point(ap); + sort_access_points(); + })); + + signals.push_back(wifi->signal_remove_access_point().connect( + [this] (std::shared_ptr ap) + { + remove_access_point(ap->get_path()); + sort_access_points(); + })); + + auto wifi_cb = + [this, wifi] () + { + if (wifi->get_current_access_point_path() == "/") + { + sort_access_points(); + revealer.set_reveal_child(true); + topbox.hide(); + } else + { + revealer.set_reveal_child(false); + topbox.show(); + } + }; + signals.push_back(network->signal_network_altered().connect(wifi_cb)); + wifi_cb(); + } else if (wired) + { + type = "wired"; + revealer.hide(); + } else if (bt) + { + type = "bt"; + revealer.hide(); + } else if (mobile) + { + type = "mobile"; + revealer.hide(); + } else + { + type = "broken"; + revealer.hide(); + } + + /* Click toggles connection on/off */ + auto click = Gtk::GestureClick::create(); + signals.push_back(click->signal_released().connect( + [network] (int, double, double) + { + network->toggle(); + })); + topbox.add_controller(click); + + /* Set label and image based on friendly info */ + auto network_change_cb = [this, network] () + { + image.set_from_icon_name(network->get_icon_symbolic()); + label.set_label(network->get_name()); + if (network->is_active()) + { + label.add_css_class("active"); + } else + { + label.remove_css_class("active"); + } + + auto classes = get_css_classes(); + for (auto css_class : classes) + { + remove_css_class(css_class); + } + + for (auto css_class : network->get_css_classes()) + { + add_css_class(css_class); + } + }; + signals.push_back(network->signal_network_altered().connect(network_change_cb)); + network_change_cb(); +} + +void DeviceControlWidget::remove_access_point(std::string path) +{ + auto widget = access_points[path]; + if (widget) + { + revealer_box.remove(*widget); + } + + access_points.erase(path); + + sort_access_points(); +} + +void DeviceControlWidget::add_access_point(std::shared_ptr ap) +{ + if (!ap || (ap->get_ssid() == "")) + { + return; + } + + auto path = ap->get_path(); + if (path == "/") + { + return; + } + + auto success = access_points.emplace(path, new AccessPointWidget(path, ap)); + if (!success.second) + { + std::cerr << "Unable to insert Access Point " << path << success.first->first << std::endl; + exit(1); + } + + auto widget = access_points[path]; + auto click = Gtk::GestureClick::create(); + + auto sig = click->signal_released().connect( + [this, path] (int, double, double) + { + selected_access_point(path); + }); + widget->add_controller(click); + widget->signals.push_back(sig); + revealer_box.append(*access_points[path]); + + sort_access_points(); +} + +void DeviceControlWidget::selected_access_point(std::string path) +{ + auto wifi = std::dynamic_pointer_cast(network); + if (!wifi) + { + std::cerr << "Cannot select AP on non-wifi device" << std::endl; + } + + if (path == "/") + { + return; + } + + if (wifi->get_current_access_point_path() == path) + { + wifi->disconnect(); + } else + { + wifi->connect(path); + } +} + +DeviceControlWidget::~DeviceControlWidget() +{ + access_points.clear(); + network = nullptr; + for (auto signal : signals) + { + signal.disconnect(); + } +} + +bool sort_compare(std::pair> a, + std::pair> b) +{ + auto a_ap = a.second->get_ap(); + auto b_ap = b.second->get_ap(); + + /*if (!a_ap->has_saved_password && b_ap->has_saved_password) + * { + * return true; + * }*/ + + return a_ap->get_strength() > b_ap->get_strength(); +} + +void DeviceControlWidget::sort_access_points() +{ + std::vector>> sorting; + for (auto & it : access_points) + { + sorting.push_back(it); + } + + sort(sorting.begin(), sorting.end(), sort_compare); + + std::shared_ptr prev = nullptr; + for (auto & it : sorting) + { + if (!prev) + { + /* Work around not being allowed to pass NULL in gtkmm */ + gtk_box_reorder_child_after((GtkBox*)revealer_box.gobj(), (GtkWidget*)it.second->gobj(), NULL); + } else + { + revealer_box.reorder_child_after(*it.second, *prev); + } + + prev = it.second; + } +} + +NetworkControlWidget::NetworkControlWidget() +{ + append(network_manager_failed); + /* Default state is no NM found */ + network_manager_failed.set_label("Network Manager is not running"); + top.hide(); + add_css_class("network-control-center"); + set_orientation(Gtk::Orientation::VERTICAL); + network_manager = NetworkManager::getInstance(); + + signals.push_back(network_manager->signal_nm_start().connect( + sigc::mem_fun(*this, &NetworkControlWidget::nm_start))); + signals.push_back(network_manager->signal_nm_stop().connect( + sigc::mem_fun(*this, &NetworkControlWidget::nm_stop))); + signals.push_back(network_manager->signal_mm_start().connect( + sigc::mem_fun(*this, &NetworkControlWidget::mm_start))); + signals.push_back(network_manager->signal_mm_stop().connect( + sigc::mem_fun(*this, &NetworkControlWidget::mm_stop))); + signals.push_back(network_manager->signal_device_added().connect( + sigc::mem_fun(*this, &NetworkControlWidget::add_device))); + signals.push_back(network_manager->signal_device_removed().connect( + sigc::mem_fun(*this, &NetworkControlWidget::remove_device))); + + top.append(global_networking); + top.append(wifi_networking); + top.append(mobile_networking); + append(top); + append(wire_box); + append(mobile_box); + append(wifi_box); + append(bt_box); + append(vpn_box); + + mobile_networking.set_halign(Gtk::Align::END); + wifi_networking.set_halign(Gtk::Align::END); + + global_networking.set_label("Networking"); + wifi_networking.set_label("Wifi"); + mobile_networking.set_label("Mobile"); + mobile_networking.hide(); + + /* Connect to global widget cb */ + signal_network = global_networking.signal_toggled().connect( + [this] () + { + network_manager->networking_global_set(global_networking.get_active()); + }); + signal_wifi = wifi_networking.signal_toggled().connect( + [this] () + { + network_manager->wifi_global_set(wifi_networking.get_active()); + }); + signal_mobile = mobile_networking.signal_toggled().connect( + [this] () + { + network_manager->mobile_global_set(mobile_networking.get_active()); + }); + /* Connect changes to global state in NM */ + signals.push_back(network_manager->signal_global_toggle().connect( + sigc::mem_fun(*this, &NetworkControlWidget::update_globals))); +} + +void NetworkControlWidget::update_globals() +{ + auto [wifi_soft, wifi_hard] = network_manager->wifi_global_enabled(); + auto [mobile_soft, mobile_hard] = network_manager->mobile_global_enabled(); + auto global = network_manager->networking_global_enabled(); + signal_wifi.block(true); + signal_mobile.block(true); + signal_network.block(true); + if (!wifi_hard) + { + wifi_networking.set_label("Wifi ✈"); + wifi_networking.set_active(false); + } else if (!wifi_soft) + { + wifi_networking.set_label("Wifi"); + wifi_networking.set_active(false); + } else + { + wifi_networking.set_label("Wifi"); + wifi_networking.set_active(true); + } + + if (!mobile_hard) + { + mobile_networking.set_label("Mobile ✈"); + mobile_networking.set_active(false); + } else if (!mobile_soft) + { + mobile_networking.set_label("Mobile"); + mobile_networking.set_active(false); + } else + { + mobile_networking.set_label("Mobile"); + mobile_networking.set_active(true); + } + + global_networking.set_active(global); + signal_wifi.unblock(); + signal_mobile.unblock(); + signal_network.unblock(); +} + +void NetworkControlWidget::add_vpn(std::shared_ptr config) +{ + auto widget = std::make_shared(config); + vpn_widgets.emplace(config->path, widget); + + auto click = Gtk::GestureClick::create(); + auto sig = click->signal_released().connect( + [this, config] (int, double, double) + { + auto primary = network_manager->get_primary_network(); + if (primary->has_vpn) + { + network_manager->deactivate_connection(primary->get_path()); + } else + { + network_manager->activate_connection(config->path, "/", "/"); + } + }); + widget->add_controller(click); + widget->signals.push_back(sig); + vpn_box.append(*widget); +} + +void NetworkControlWidget::remove_vpn(std::string path) +{ + auto widget = vpn_widgets[path]; + vpn_box.remove(*widget); + vpn_widgets.erase(path); +} + +void NetworkControlWidget::add_device(std::shared_ptr network) +{ + /* GUI doesn't want our null-device */ + if (network->get_path() == "/") + { + return; + } + + auto new_controller = std::make_shared(network); + widgets.emplace(network->get_path(), new_controller); + auto widget = widgets[network->get_path()]; + if (widget->type == "wifi") + { + wifi_box.append(*widget); + } else if (widget->type == "mobile") + { + mobile_box.append(*widget); + } else if (widget->type == "wired") + { + wire_box.append(*widget); + } else if (widget->type == "bt") + { + bt_box.append(*widget); + } else + { + std::cout << "Unknown network type : " << widget->type << std::endl; + } +} + +void NetworkControlWidget::remove_device(std::shared_ptr network) +{ + auto widget = widgets[network->get_path()]; + if (widget->type == "wifi") + { + wifi_box.remove(*widget); + } else if (widget->type == "mobile") + { + mobile_box.remove(*widget); + } else if (widget->type == "wired") + { + wire_box.remove(*widget); + } else if (widget->type == "bt") + { + bt_box.remove(*widget); + } else + { + std::cout << "Unknown network type : " << widget->type << std::endl; + } + + widgets.erase(network->get_path()); +} + +void NetworkControlWidget::nm_start() +{ + network_manager_failed.hide(); + top.show(); + /* Fill already existing devices */ + for (auto & device : network_manager->get_all_devices()) + { + add_device(device.second); + } + + for (auto & it : network_manager->get_all_vpns()) + { + add_vpn(it.second); + } + + update_globals(); +} + +void NetworkControlWidget::nm_stop() +{ + network_manager_failed.show(); + top.hide(); + widgets.clear(); + for (auto & it : vpn_widgets) + { + vpn_box.remove(*it.second); + } + + vpn_widgets.clear(); +} + +void NetworkControlWidget::mm_start() +{ + mobile_networking.show(); +} + +void NetworkControlWidget::mm_stop() +{ + mobile_networking.hide(); +} + +NetworkControlWidget::~NetworkControlWidget() +{ + signal_network.disconnect(); + signal_mobile.disconnect(); + signal_wifi.disconnect(); + for (auto signal : signals) + { + signal.disconnect(); + } +} diff --git a/src/util/network/network-widget.hpp b/src/util/network/network-widget.hpp new file mode 100644 index 00000000..a28441af --- /dev/null +++ b/src/util/network/network-widget.hpp @@ -0,0 +1,86 @@ +#pragma once +#include +#include +#include + +#include "gtkmm/overlay.h" +#include "manager.hpp" +#include "wifi-ap.hpp" +#include "vpn.hpp" + + +class AccessPointWidget : public Gtk::Box +{ + private: + Gtk::Overlay overlay; + Gtk::Image wifi, security; + Gtk::Label label, band; + std::shared_ptr ap; + std::string path; + + public: + std::vector signals; + AccessPointWidget(std::string path, std::shared_ptr ap); + ~AccessPointWidget(); + std::shared_ptr get_ap(); +}; + +class DeviceControlWidget : public Gtk::Box +{ + private: + std::map> access_points; + std::shared_ptr network; + Gtk::Label label; + Gtk::Image image; + Gtk::Revealer revealer; + Gtk::Box revealer_box, topbox; + std::vector signals; + + public: + DeviceControlWidget(std::shared_ptr network); + ~DeviceControlWidget(); + void add_access_point(std::shared_ptr ap); + void remove_access_point(std::string path); + void selected_access_point(std::string path); + void sort_access_points(); + std::string type; +}; + +class VPNControlWidget : public Gtk::Box +{ + private: + std::shared_ptr config; + Gtk::Image image; + Gtk::Label label; + + public: + std::vector signals; + + VPNControlWidget(std::shared_ptr config); + ~VPNControlWidget(); +}; + +class NetworkControlWidget : public Gtk::Box +{ + Gtk::Label network_manager_failed; + Gtk::Box wire_box, wifi_box, mobile_box, vpn_box, bt_box, top; + Gtk::CheckButton global_networking, wifi_networking, mobile_networking; + std::map> widgets; + std::map> vpn_widgets; + sigc::connection signal_network, signal_wifi, signal_mobile; + std::vector signals; + + public: + NetworkControlWidget(); + ~NetworkControlWidget(); + std::shared_ptr network_manager; + void update_globals(); + void add_device(std::shared_ptr network); + void remove_device(std::shared_ptr network); + void add_vpn(std::shared_ptr config); + void remove_vpn(std::string path); + void nm_start(); + void nm_stop(); + void mm_start(); + void mm_stop(); +}; diff --git a/src/util/network/network.cpp b/src/util/network/network.cpp new file mode 100644 index 00000000..0da421dd --- /dev/null +++ b/src/util/network/network.cpp @@ -0,0 +1,118 @@ +#include "network.hpp" +#include "manager.hpp" + +type_signal_network_altered Network::signal_network_altered() +{ + return network_altered; +} + +Network::Network(std::string path, std::shared_ptr in_proxy) : + network_path(path), device_proxy(in_proxy) +{ + /* Allow for nullnetwork and pseudo networks */ + if (in_proxy == nullptr) + { + return; + } + + Glib::Variant val; + device_proxy->get_cached_property(val, "Interface"); + if (val.get().length() > 0) + { + interface = val.get(); + } + + /* Any change of state */ + signals.push_back(device_proxy->signal_signal().connect( + [this] (const Glib::ustring& sender, const Glib::ustring& signal, + const Glib::VariantContainerBase& container) + { + if (signal == "StateChanged") + { + if (container.is_of_type(Glib::VariantType("(uuu)"))) + { + auto value = container.get_child(0); + auto value2 = Glib::VariantBase::cast_dynamic>(value).get(); + last_state = value2; + network_altered.emit(); + } + } + })); + + signals.push_back(device_proxy->signal_properties_changed().connect( + [this] (const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) + { + for (auto & it : properties) + { + if (it.first == "ActiveConnection") + { + network_altered.emit(); + } + } + })); +} + +Network::~Network() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +std::string Network::get_friendly_name() +{ + return "Unknown Device"; +} + +std::string Network::get_interface() +{ + if (!device_proxy) + { + return "/dev/null"; + } + + Glib::Variant iface; + device_proxy->get_cached_property(iface, "Interface"); + return iface.get(); +} + +bool Network::show_spinner() +{ + return last_state == NM_DEVICE_STATE_PREPARE || + last_state == NM_DEVICE_STATE_CONFIG; +} + +std::string Network::get_path() +{ + return network_path; +} + +void Network::connect(std::string path) +{ + NetworkManager::getInstance()->activate_connection("/", network_path, path); +} + +void Network::toggle() +{ + if (is_active()) + { + disconnect(); + } else + { + connect("/"); + } +} + +void Network::disconnect() +{ + device_proxy->call_sync("Disconnect"); +} + +bool Network::is_active() +{ + Glib::Variant val; + device_proxy->get_cached_property(val, "ActiveConnection"); + return val.get() != "/"; +} diff --git a/src/util/network/network.hpp b/src/util/network/network.hpp new file mode 100644 index 00000000..b4ad8749 --- /dev/null +++ b/src/util/network/network.hpp @@ -0,0 +1,61 @@ +#pragma once +#include "sigc++/connection.h" +#include +#include +#include +#include + +#define NM_DEVICE_STATE_UNKNOWN 0 +#define NM_DEVICE_STATE_UNMANAGED 10 +#define NM_DEVICE_STATE_UNAVAILABLE 20 +#define NM_DEVICE_STATE_DISCONNECTED 30 +#define NM_DEVICE_STATE_PREPARE 40 +#define NM_DEVICE_STATE_CONFIG 50 +#define NM_DEVICE_STATE_NEED_AUTH 60 +#define NM_DEVICE_STATE_IP_CONFIG 70 +#define NM_DEVICE_STATE_IP_CHECK 80 +#define NM_DEVICE_STATE_SECONDARIES 90 +#define NM_DEVICE_STATE_ACTIVATED 100 +#define NM_DEVICE_STATE_DEACTIVATING 110 +#define NM_DEVICE_STATE_FAILED 120 + +using type_signal_network_altered = sigc::signal; + +class Network +{ + protected: + type_signal_network_altered network_altered; + std::string network_path; + std::vector signals; + + public: + type_signal_network_altered signal_network_altered(); + std::shared_ptr device_proxy; + std::string interface = ""; + int last_state = 0; + virtual std::string get_name() = 0; + virtual std::vector get_css_classes() = 0; + Network(std::string path, std::shared_ptr in_proxy); + ~Network(); + virtual std::string get_friendly_name(); + virtual std::string get_interface(); + bool show_spinner(); + virtual std::string get_icon_name() = 0; + std::string get_icon_symbolic() + { + return get_icon_name() + "-symbolic"; + } + + virtual std::string get_secure_variant() + { + return ""; + } + + std::string get_path(); + void disconnect(); + void connect(std::string path_extra); + void toggle(); + bool is_active(); + Network(const Network &) = delete; + Network& operator =(const Network&) = delete; +}; diff --git a/src/util/network/null.hpp b/src/util/network/null.hpp new file mode 100644 index 00000000..eb4acda0 --- /dev/null +++ b/src/util/network/null.hpp @@ -0,0 +1,24 @@ +#pragma once +#include "network.hpp" + +class NullNetwork : public Network +{ + public: + NullNetwork() : Network("/", nullptr) + {} + + std::string get_name() override + { + return "No connection"; + } + + std::string get_icon_name() override + { + return "network-offline"; + } + + std::vector get_css_classes() override + { + return {"none"}; + } +}; diff --git a/src/util/network/settings.cpp b/src/util/network/settings.cpp new file mode 100644 index 00000000..514a71a0 --- /dev/null +++ b/src/util/network/settings.cpp @@ -0,0 +1,50 @@ +#include "settings.hpp" +#include "glibmm/variant.h" +#include "sigc++/functors/mem_fun.h" +#include "giomm/dbusproxy.h" +#include + +NetworkSettings::NetworkSettings(std::string path, std::shared_ptr proxy) : + proxy(proxy) +{ + proxy->signal_signal().connect( + sigc::mem_fun(*this, &NetworkSettings::signal)); + read_contents(); +} + +void NetworkSettings::signal(const Glib::ustring& name, const Glib::ustring& signal, + const Glib::VariantContainerBase& variants) +{ + if (signal == "Updates") + { + read_contents(); + } +} + +void NetworkSettings::read_contents() +{ + auto contents = Glib::VariantBase::cast_dynamic>>>(proxy->call_sync("GetSettings").get_child()).get(); + + setting_name = + Glib::VariantBase::cast_dynamic>(contents["connection"]["id"]).get(); + + if (contents.count("802-11-wireless") == 1) + { + auto ssid_bytes = + Glib::VariantBase::cast_dynamic>>(contents[ + "802-11-wireless"][ + "ssid"]).get(); + ssid = std::string(ssid_bytes.begin(), ssid_bytes.end()); + } +} + +std::string NetworkSettings::get_ssid() +{ + return ssid; +} + +std::string NetworkSettings::get_name() +{ + return setting_name; +} diff --git a/src/util/network/settings.hpp b/src/util/network/settings.hpp new file mode 100644 index 00000000..f82b87ea --- /dev/null +++ b/src/util/network/settings.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +class NetworkSettings +{ + private: + std::shared_ptr proxy; + std::string setting_name = ""; + std::string ssid = ""; + + public: + NetworkSettings(std::string path, std::shared_ptr proxy); + void signal(const Glib::ustring&, const Glib::ustring&, const Glib::VariantContainerBase&); + void read_contents(); + + std::string get_ssid(); + std::string get_name(); +}; diff --git a/src/util/network/vpn.hpp b/src/util/network/vpn.hpp new file mode 100644 index 00000000..a4919bae --- /dev/null +++ b/src/util/network/vpn.hpp @@ -0,0 +1,17 @@ +#pragma once +#include +#include + +class VpnConfig +{ + private: + std::shared_ptr vpn_proxy; + + public: + std::string name; + std::string path; + + VpnConfig(std::string path, std::shared_ptr vpn_proxy, std::string name) : + vpn_proxy(vpn_proxy), name(name), path(path) + {} +}; diff --git a/src/util/network/wifi-ap.cpp b/src/util/network/wifi-ap.cpp new file mode 100644 index 00000000..deef6b1d --- /dev/null +++ b/src/util/network/wifi-ap.cpp @@ -0,0 +1,211 @@ +#include "wifi-ap.hpp" +#include "glibmm/variant.h" +#include "network.hpp" +#include + +std::string AccessPoint::get_path() +{ + return ap_path; +} + +AccessPoint::AccessPoint(std::string path, std::shared_ptr access_point_proxy) : + ap_path(path), access_point_proxy(access_point_proxy) +{ + Glib::Variant strength_start; + access_point_proxy->get_cached_property(strength_start, "Strength"); + strength = strength_start.get(); + + Glib::Variant> ssid_start; + access_point_proxy->get_cached_property(ssid_start, "Ssid"); + auto ssid_bytes = ssid_start.get(); + ssid = std::string(ssid_bytes.begin(), ssid_bytes.end()); + + Glib::Variant security_flags_start; + access_point_proxy->get_cached_property(security_flags_start, "RsnFlags"); + security_flags = security_flags_start.get(); + + Glib::Variant freq_start; + access_point_proxy->get_cached_property(freq_start, "Frequency"); + freq = freq_start.get(); + + signals.push_back(access_point_proxy->signal_properties_changed().connect( + [this] (const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) + { + for (auto & it : properties) + { + if (it.first == "Strength") + { + auto value = Glib::VariantBase::cast_dynamic>(it.second).get(); + strength = value; + access_point_altered.emit(); + } else if (it.first == "Ssid") + { + auto value = + Glib::VariantBase::cast_dynamic>>(it.second).get(); + ssid = std::string(value.begin(), value.end()); + access_point_altered.emit(); + } else if (it.first == "RsnFlags") + { + auto value = + Glib::VariantBase::cast_dynamic>(it.second).get(); + security_flags = value; + access_point_altered.emit(); + } else if (it.first == "Frequency") + { + auto value = + Glib::VariantBase::cast_dynamic>(it.second).get(); + freq = value; + access_point_altered.emit(); + } + } + })); +} + +std::string AccessPoint::get_ssid() +{ + return ssid; +} + +std::string AccessPoint::strength_string() +{ + if (strength >= 80) + { + return "excellent"; + } + + if (strength >= 55) + { + return "good"; + } + + if (strength >= 30) + { + return "ok"; + } + + if (strength >= 5) + { + return "weak"; + } + + return "none"; +} + +std::string AccessPoint::get_icon_name() +{ + return "network-wireless-signal-" + strength_string() + "-symbolic"; +} + +type_signal_network_altered AccessPoint::signal_altered() +{ + return access_point_altered; +} + +AccessPoint::~AccessPoint() +{ + for (auto signal : signals) + { + signal.disconnect(); + } +} + +std::string AccessPoint::get_security_icon_name() +{ + if (security_flags | NM_SOME_WPA_SECURITY) + { + return "channel-secure-symbolic"; + } + + return "channel-insecure-symbolic"; +} + +std::string AccessPoint::get_band_name() +{ + if ((freq > 800) && (freq < 1000)) + { + return "900Mhz"; + } else if ((freq > 2000) && (freq < 3000)) + { + return "2.4 Ghz"; + } else if ((freq >= 5000) && (freq < 6000)) + { + return "5 Ghz"; + } else if ((freq >= 6000) && (freq < 7000)) + { + return "6 Ghz"; + } else if ((freq >= 40000) && (freq < 50000)) + { + return "45 Ghz"; + } else if ((freq >= 57000) && (freq < 74000)) + { + return "60 Ghz"; + } + + return "???"; +} + +std::vector AccessPoint::get_css_classes() +{ + /* Set a bunch of AP-specific info here + * This allows theme makers to put much more detail in */ + std::vector classlist; + classlist.push_back("access-point"); + if ((freq > 800) && (freq < 1000)) + { + classlist.push_back("f900mhz"); + } else if ((freq > 2000) && (freq < 3000)) + { + classlist.push_back("f2-4ghz"); + } else if ((freq >= 5000) && (freq < 6000)) + { + classlist.push_back("f5ghz"); + } else if ((freq >= 6000) && (freq < 7000)) + { + classlist.push_back("f6ghz"); + } else if ((freq >= 40000) && (freq < 50000)) + { + classlist.push_back("f45ghz"); + } else if ((freq >= 57000) && (freq < 74000)) + { + classlist.push_back("f60ghz"); + } + + classlist.push_back(strength_string()); + + if (ssid.length() > 0) + { + classlist.push_back("ap-" + ssid); + } + + if (security_flags | NM_SOME_WPA_SECURITY) + { + classlist.push_back("secure"); + } else + { + classlist.push_back("insecure"); + } + + if (saved_password) + { + classlist.push_back("has-password"); + } + + return classlist; +} + +unsigned char AccessPoint::get_strength() +{ + return strength; +} + +void AccessPoint::set_has_saved_password(bool new_val) +{ + saved_password = new_val; + access_point_altered.emit(); +} + +bool AccessPoint::has_saved_password() +{ + return saved_password; +} diff --git a/src/util/network/wifi-ap.hpp b/src/util/network/wifi-ap.hpp new file mode 100644 index 00000000..e53ffe78 --- /dev/null +++ b/src/util/network/wifi-ap.hpp @@ -0,0 +1,41 @@ +#pragma once +#include "network/network.hpp" +#include "network/settings.hpp" +#include "sigc++/connection.h" +#include +#include + +/* 256 + 512, Has *some* kind of password security */ +#define NM_SOME_WPA_SECURITY 0x300 + + +using type_signal_access_point_altered = sigc::signal; +class AccessPoint +{ + private: + std::string ap_path; + type_signal_access_point_altered access_point_altered; + unsigned char strength; + unsigned int security_flags; + unsigned int freq; + std::string ssid = ""; + std::vector signals; + bool saved_password; + + public: + + std::string get_path(); + std::shared_ptr access_point_proxy; + AccessPoint(std::string path, std::shared_ptr access_point_proxy); + ~AccessPoint(); + std::string get_ssid(); + std::string strength_string(); + std::string get_icon_name(); + std::string get_security_icon_name(); + std::string get_band_name(); + std::vector get_css_classes(); + type_signal_access_point_altered signal_altered(); + unsigned char get_strength(); + bool has_saved_password(); + void set_has_saved_password(bool); +}; diff --git a/src/util/network/wifi.cpp b/src/util/network/wifi.cpp new file mode 100644 index 00000000..846ed781 --- /dev/null +++ b/src/util/network/wifi.cpp @@ -0,0 +1,196 @@ +#include +#include +#include +#include "wifi-ap.hpp" +#include "wifi.hpp" +#include "manager.hpp" + +type_signal_access_point WifiNetwork::signal_add_access_point() +{ + return add_ap; +} + +type_signal_access_point WifiNetwork::signal_remove_access_point() +{ + return remove_ap; +} + +std::map> WifiNetwork::get_access_points() +{ + return all_access_points; +} + +void WifiNetwork::add_access_point(std::string path) +{ + auto ap_proxy = Gio::DBus::Proxy::create_sync(wifi_proxy->get_connection(), + "org.freedesktop.NetworkManager", + path, + "org.freedesktop.NetworkManager.AccessPoint"); + all_access_points.emplace(path, std::make_shared(path, ap_proxy)); + auto ap = all_access_points[path]; + + auto setting = NetworkManager::getInstance()->get_setting_for_ssid(ap->get_ssid()); + if (setting) + { + ap->set_has_saved_password(true); + } else + { + ap->set_has_saved_password(false); + } +} + +void WifiNetwork::remove_access_point(std::string path) +{ + all_access_points.erase(path); +} + +WifiNetwork::WifiNetwork(std::string path, std::shared_ptr device_proxy, + std::shared_ptr wifi_proxy) : + Network(path, device_proxy), wifi_proxy(wifi_proxy) +{ + signals.push_back(wifi_proxy->signal_signal().connect( + [this] (const Glib::ustring& source, const Glib::ustring& signal, + const Glib::VariantContainerBase& value) + { + if (signal == "AccessPointAdded") + { + auto val = Glib::VariantBase::cast_dynamic>(value.get_child()).get(); + add_access_point(val); + add_ap.emit(all_access_points[val]); + } else if (signal == "AccessPointRemoved") + { + auto val = Glib::VariantBase::cast_dynamic>(value.get_child()).get(); + remove_ap.emit(all_access_points[val]); + all_access_points.erase(val); + } + })); + + signals.push_back(wifi_proxy->signal_properties_changed().connect( + [this] (const Gio::DBus::Proxy::MapChangedProperties& properties, + const std::vector& invalidated) + { + for (auto & it : properties) + { + if (it.first == "ActiveAccessPoint") + { + if (access_point_signal) + { + access_point_signal.disconnect(); + } + + auto access_point = get_current_access_point(); + if (access_point) + { + /* Bubble signal upwards */ + access_point_signal = access_point->signal_altered().connect( + [this] () + { + network_altered.emit(); + }); + } + + network_altered.emit(); + } + } + })); + /* Initial values */ + Glib::Variant> base; + wifi_proxy->get_cached_property(base, "AccessPoints"); + for (auto & it : base.get()) + { + add_access_point(it); + } + + auto access_point = get_current_access_point(); + if (access_point) + { + signals.push_back(access_point_signal = access_point->signal_altered().connect( + [this] () + { + network_altered.emit(); + })); + } +} + +std::string WifiNetwork::get_name() +{ + auto network = get_current_access_point(); + if (network) + { + return network->get_ssid(); + } + + return "Wifi..."; +} + +std::vector WifiNetwork::get_css_classes() +{ + std::vector list = {"wifi"}; + auto ap = get_current_access_point(); + if (ap) + { + auto aplist = ap->get_css_classes(); + list.insert(list.end(), aplist.begin(), aplist.end()); + } + + return list; +} + +std::string WifiNetwork::get_icon_name() +{ + if (show_spinner()) + { + return "network-wireless-disconnected"; + } + + auto ap = get_current_access_point(); + if (!ap) + { + return "network-wireless-disconnected"; + } + + return "network-wireless-signal-" + ap->strength_string(); +} + +std::string WifiNetwork::get_friendly_name() +{ + auto ap = get_current_access_point(); + if (ap) + { + return ap->get_ssid(); + } + + return "Not connected"; +} + +std::shared_ptr WifiNetwork::get_current_access_point() +{ + auto access_point = all_access_points[get_current_access_point_path()]; + return access_point; +} + +std::string WifiNetwork::get_current_access_point_path() +{ + Glib::Variant ap_name; + wifi_proxy->get_cached_property(ap_name, "ActiveAccessPoint"); + return ap_name.get(); +} + +WifiNetwork::~WifiNetwork() +{ + all_access_points.clear(); + if (access_point_signal) + { + access_point_signal.disconnect(); + } + + for (auto signal : signals) + { + signal.disconnect(); + } +} + +std::string WifiNetwork::get_secure_variant() +{ + return "-secure"; +} diff --git a/src/util/network/wifi.hpp b/src/util/network/wifi.hpp new file mode 100644 index 00000000..9fed88d4 --- /dev/null +++ b/src/util/network/wifi.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include +#include +#include + +#include "network.hpp" +#include "wifi-ap.hpp" + + +using type_signal_access_point = sigc::signal)>; + +class WifiNetwork : public Network +{ + private: + sigc::connection access_point_signal; + type_signal_access_point add_ap, remove_ap; + + protected: + std::shared_ptr wifi_proxy; + + public: + std::map> all_access_points; + WifiNetwork(std::string path, std::shared_ptr device_proxy, + std::shared_ptr wifi_proxy); + ~WifiNetwork(); + + type_signal_access_point signal_add_access_point(); + type_signal_access_point signal_remove_access_point(); + std::map> get_access_points(); + void add_access_point(std::string path); + void remove_access_point(std::string path); + std::string get_name() override; + std::vector get_css_classes() override; + std::string get_icon_name() override; + std::string get_friendly_name() override; + std::string get_secure_variant() override; + std::string get_current_access_point_path(); + std::shared_ptr get_current_access_point(); +}; diff --git a/src/util/network/wired.cpp b/src/util/network/wired.cpp new file mode 100644 index 00000000..93df96fc --- /dev/null +++ b/src/util/network/wired.cpp @@ -0,0 +1,35 @@ +#include "wired.hpp" +#include +WiredNetwork::WiredNetwork(std::string path, std::shared_ptr proxy) : + Network(path, proxy) +{} + +std::string WiredNetwork::get_name() +{ + return "Wired"; +} + +std::string WiredNetwork::get_icon_name() +{ + if (is_active()) + { + return "network-wired"; + } + + return "network-wired-offline"; +} + +std::vector WiredNetwork::get_css_classes() +{ + return {"ethernet", "good"}; +} + +std::string WiredNetwork::get_friendly_name() +{ + return "Ethernet"; +} + +std::string WiredNetwork::get_secure_variant() +{ + return "-secure"; +} diff --git a/src/util/network/wired.hpp b/src/util/network/wired.hpp new file mode 100644 index 00000000..81f1e256 --- /dev/null +++ b/src/util/network/wired.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include "network.hpp" + +class WiredNetwork : public Network +{ + public: + WiredNetwork(std::string path, std::shared_ptr proxy); + std::string get_name() override; + std::string get_icon_name() override; + std::string get_friendly_name() override; + std::vector get_css_classes() override; + std::string get_secure_variant() override; +};