/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2017 Georges Basile Stavracas Neto * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * */ #include "cc-network-resources.h" #include "cc-wifi-panel.h" #include "net-device.h" #include "net-device-wifi.h" #include "network-dialogs.h" #include "shell/cc-object-storage.h" #include "list-box-helper.h" #include #include typedef enum { OPERATION_NULL, OPERATION_SHOW_DEVICE, OPERATION_CREATE_WIFI, OPERATION_CONNECT_HIDDEN, OPERATION_CONNECT_8021X } CmdlineOperation; struct _CcWifiPanel { CcPanel parent; /* RFKill (Airplane Mode) */ GDBusProxy *rfkill_proxy; GtkSwitch *rfkill_switch; GtkWidget *rfkill_widget; /* Main widgets */ GtkStack *center_stack; GtkStack *header_stack; GtkStack *main_stack; GtkSizeGroup *sizegroup; GtkStack *stack; NMClient *client; GPtrArray *devices; GCancellable *cancellable; /* Command-line arguments */ CmdlineOperation arg_operation; gchar *arg_device; gchar *arg_access_point; }; static void rfkill_switch_notify_activate_cb (GtkSwitch *rfkill_switch, GParamSpec *pspec, CcWifiPanel *self); static void update_devices_names (CcWifiPanel *self); G_DEFINE_TYPE (CcWifiPanel, cc_wifi_panel, CC_TYPE_PANEL) enum { PROP_0, PROP_PARAMETERS, N_PROPS }; /* Auxiliary methods */ static void add_wifi_device (CcWifiPanel *self, NMDevice *device) { GtkWidget *header_widget; NetObject *net_device; /* Only manage Wi-Fi devices */ if (!NM_IS_DEVICE_WIFI (device)) return; /* Create the NetDevice */ net_device = g_object_new (NET_TYPE_DEVICE_WIFI, "panel", self, "removable", FALSE, "cancellable", self->cancellable, "client", self->client, "nm-device", device, "id", nm_device_get_udi (device), NULL); net_object_add_to_stack (net_device, self->stack, self->sizegroup); /* And add to the header widgets */ header_widget = net_device_wifi_get_header_widget (NET_DEVICE_WIFI (net_device)); gtk_stack_add_named (self->header_stack, header_widget, net_object_get_id (net_device)); /* Setup custom title properties */ g_ptr_array_add (self->devices, net_device); update_devices_names (self); } static void check_main_stack_page (CcWifiPanel *self) { const gchar *nm_version; nm_version = nm_client_get_version (self->client); if (!nm_version) gtk_stack_set_visible_child_name (self->main_stack, "nm-not-running"); else if (self->devices->len == 0 || !nm_client_wireless_get_enabled (self->client)) gtk_stack_set_visible_child_name (self->main_stack, "no-wifi-devices"); else gtk_stack_set_visible_child_name (self->main_stack, "wifi-connections"); } static void load_wifi_devices (CcWifiPanel *self) { const GPtrArray *devices; guint i; devices = nm_client_get_devices (self->client); /* Cold-plug existing devices */ if (devices) { for (i = 0; i < devices->len; i++) add_wifi_device (self, g_ptr_array_index (devices, i)); } check_main_stack_page (self); } static inline gboolean get_cached_rfkill_property (CcWifiPanel *self, const gchar *property) { g_autoptr (GVariant) result; result = g_dbus_proxy_get_cached_property (self->rfkill_proxy, property); return result ? g_variant_get_boolean (result) : FALSE; } static void sync_airplane_mode_switch (CcWifiPanel *self) { gboolean enabled, should_show, hw_enabled; enabled = get_cached_rfkill_property (self, "HasAirplaneMode"); should_show = get_cached_rfkill_property (self, "ShouldShowAirplaneMode"); gtk_widget_set_visible (GTK_WIDGET (self->rfkill_widget), enabled && should_show); if (!enabled || !should_show) return; enabled = get_cached_rfkill_property (self, "AirplaneMode"); hw_enabled = get_cached_rfkill_property (self, "HardwareAirplaneMode"); enabled |= hw_enabled; if (enabled != gtk_switch_get_active (self->rfkill_switch)) { g_signal_handlers_block_by_func (self->rfkill_switch, rfkill_switch_notify_activate_cb, self); gtk_switch_set_active (self->rfkill_switch, enabled); g_signal_handlers_unblock_by_func (self->rfkill_switch, rfkill_switch_notify_activate_cb, self); } gtk_widget_set_sensitive (GTK_WIDGET (self->rfkill_switch), !hw_enabled); check_main_stack_page (self); } static void update_devices_names (CcWifiPanel *self) { guint number_of_devices = self->devices->len; if (number_of_devices == 1) { GtkWidget *title_widget; NetObject *net_device; net_device = g_ptr_array_index (self->devices, 0); title_widget = net_device_wifi_get_title_widget (NET_DEVICE_WIFI (net_device)); gtk_stack_add_named (self->center_stack, title_widget, "single"); gtk_stack_set_visible_child_name (self->center_stack, "single"); net_object_set_title (net_device, _("Wi-Fi")); } else { GtkWidget *single_page_widget; guint i; for (i = 0; i < number_of_devices; i++) { NetObject *object; NMDevice *device; object = g_ptr_array_index (self->devices, i); device = net_device_get_nm_device (NET_DEVICE (object)); net_object_set_title (object, nm_device_get_product (device)); } /* Remove the widget at the "single" page */ single_page_widget = gtk_stack_get_child_by_name (self->center_stack, "single"); if (single_page_widget) { g_object_ref (single_page_widget); gtk_container_remove (GTK_CONTAINER (self->center_stack), single_page_widget); g_object_unref (single_page_widget); } /* Show the stack-switcher page */ gtk_stack_set_visible_child_name (self->center_stack, "many"); } } /* Command-line arguments */ static void reset_command_line_args (CcWifiPanel *self) { self->arg_operation = OPERATION_NULL; g_clear_pointer (&self->arg_device, g_free); g_clear_pointer (&self->arg_access_point, g_free); } static gboolean handle_argv_for_device (CcWifiPanel *self, NetObject *net_object) { GtkWidget *toplevel; NMDevice *device; gboolean ret; toplevel = cc_shell_get_toplevel (cc_panel_get_shell (CC_PANEL (self))); device = net_device_get_nm_device (NET_DEVICE (net_object)); ret = FALSE; if (self->arg_operation == OPERATION_CREATE_WIFI) { cc_network_panel_create_wifi_network (toplevel, self->client); ret = TRUE; } else if (self->arg_operation == OPERATION_CONNECT_HIDDEN) { cc_network_panel_connect_to_hidden_network (toplevel, self->client); ret = TRUE; } else if (g_str_equal (nm_object_get_path (NM_OBJECT (device)), self->arg_device)) { if (self->arg_operation == OPERATION_CONNECT_8021X) { cc_network_panel_connect_to_8021x_network (toplevel, self->client, device, self->arg_access_point); ret = TRUE; } else if (self->arg_operation == OPERATION_SHOW_DEVICE) { gtk_stack_set_visible_child_name (self->stack, net_object_get_id (net_object)); ret = TRUE; } } if (ret) reset_command_line_args (self); return ret; } static void handle_argv (CcWifiPanel *self) { guint i; if (self->arg_operation == OPERATION_NULL) return; for (i = 0; i < self->devices->len; i++) { if (handle_argv_for_device (self, g_ptr_array_index (self->devices, i))) break; } } static GPtrArray * variant_av_to_string_array (GVariant *array) { GVariantIter iter; GVariant *v; GPtrArray *strv; gsize count; count = g_variant_iter_init (&iter, array); strv = g_ptr_array_sized_new (count + 1); while (g_variant_iter_next (&iter, "v", &v)) { g_ptr_array_add (strv, (gpointer) g_variant_get_string (v, NULL)); g_variant_unref (v); } g_ptr_array_add (strv, NULL); /* NULL-terminate the strv data array */ return strv; } static gboolean verify_argv (CcWifiPanel *self, const char **args) { switch (self->arg_operation) { case OPERATION_CONNECT_8021X: case OPERATION_SHOW_DEVICE: if (!self->arg_device) { g_warning ("Operation %s requires an object path", args[0]); return FALSE; } default: return TRUE; } } /* Callbacks */ static void device_added_cb (NMClient *client, NMDevice *device, CcWifiPanel *self) { add_wifi_device (self, device); check_main_stack_page (self); } static void device_removed_cb (NMClient *client, NMDevice *device, CcWifiPanel *self) { GtkWidget *child; const gchar *id; guint i; if (!NM_IS_DEVICE_WIFI (device)) return; id = nm_device_get_udi (device); /* Destroy all stack pages related to this device */ child = gtk_stack_get_child_by_name (self->stack, id); gtk_widget_destroy (child); child = gtk_stack_get_child_by_name (self->header_stack, id); gtk_widget_destroy (child); /* Remove from the devices list */ for (i = 0; i < self->devices->len; i++) { NetObject *object = g_ptr_array_index (self->devices, i); if (g_strcmp0 (net_object_get_id (object), id) == 0) { g_ptr_array_remove (self->devices, object); break; } } /* Update the title widget */ update_devices_names (self); /* And check which page should be visible */ check_main_stack_page (self); } static void wireless_enabled_cb (NMClient *client, NMDevice *device, CcWifiPanel *self) { check_main_stack_page (self); } static void rfkill_proxy_acquired_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { CcWifiPanel *self; GDBusProxy *proxy; GError *error; error = NULL; proxy = cc_object_storage_create_dbus_proxy_finish (res, &error); if (error) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_printerr ("Error creating rfkill proxy: %s\n", error->message); g_error_free (error); return; } self = CC_WIFI_PANEL (user_data); self->rfkill_proxy = proxy; sync_airplane_mode_switch (self); } static void rfkill_switch_notify_activate_cb (GtkSwitch *rfkill_switch, GParamSpec *pspec, CcWifiPanel *self) { gboolean enable; enable = gtk_switch_get_active (rfkill_switch); g_dbus_proxy_call (self->rfkill_proxy, "org.freedesktop.DBus.Properties.Set", g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill'," "'AirplaneMode', %v)", g_variant_new_boolean (enable)), G_DBUS_CALL_FLAGS_NONE, -1, self->cancellable, NULL, NULL); } /* Overrides */ static const gchar * cc_wifi_panel_get_help_uri (CcPanel *panel) { return "help:gnome-help/net-wireless"; } static GtkWidget * cc_wifi_panel_get_title_widget (CcPanel *panel) { CcWifiPanel *self = CC_WIFI_PANEL (panel); return GTK_WIDGET (self->center_stack); } static void cc_wifi_panel_constructed (GObject *object) { CcWifiPanel *self; CcShell *shell; self = CC_WIFI_PANEL (object); shell = cc_panel_get_shell (CC_PANEL (object)); G_OBJECT_CLASS (cc_wifi_panel_parent_class)->constructed (object); cc_shell_embed_widget_in_header (shell, GTK_WIDGET (self->header_stack)); } static void cc_wifi_panel_finalize (GObject *object) { CcWifiPanel *self = (CcWifiPanel *)object; g_cancellable_cancel (self->cancellable); g_clear_object (&self->cancellable); g_clear_object (&self->client); g_clear_object (&self->rfkill_proxy); g_clear_pointer (&self->devices, g_ptr_array_unref); reset_command_line_args (self); G_OBJECT_CLASS (cc_wifi_panel_parent_class)->finalize (object); } static void cc_wifi_panel_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } static void cc_wifi_panel_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CcWifiPanel *self = CC_WIFI_PANEL (object); GVariant *parameters; switch (prop_id) { case PROP_PARAMETERS: reset_command_line_args (self); parameters = g_value_get_variant (value); if (parameters) { GPtrArray *array; const gchar **args; array = variant_av_to_string_array (parameters); args = (const gchar **) array->pdata; if (args[0]) { if (g_str_equal (args[0], "create-wifi")) self->arg_operation = OPERATION_CREATE_WIFI; else if (g_str_equal (args[0], "connect-hidden-wifi")) self->arg_operation = OPERATION_CONNECT_HIDDEN; else if (g_str_equal (args[0], "connect-8021x-wifi")) self->arg_operation = OPERATION_CONNECT_8021X; else if (g_str_equal (args[0], "show-device")) self->arg_operation = OPERATION_SHOW_DEVICE; else self->arg_operation = OPERATION_NULL; } if (args[0] && args[1]) self->arg_device = g_strdup (args[1]); if (args[0] && args[1] && args[2]) self->arg_access_point = g_strdup (args[2]); if (!verify_argv (self, (const char **) args)) { reset_command_line_args (self); g_ptr_array_unref (array); return; } g_ptr_array_unref (array); handle_argv (self); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void cc_wifi_panel_class_init (CcWifiPanelClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); CcPanelClass *panel_class = CC_PANEL_CLASS (klass); panel_class->get_help_uri = cc_wifi_panel_get_help_uri; panel_class->get_title_widget = cc_wifi_panel_get_title_widget; object_class->constructed = cc_wifi_panel_constructed; object_class->finalize = cc_wifi_panel_finalize; object_class->get_property = cc_wifi_panel_get_property; object_class->set_property = cc_wifi_panel_set_property; gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/network/wifi.ui"); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, center_stack); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, header_stack); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, main_stack); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, rfkill_switch); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, rfkill_widget); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, sizegroup); gtk_widget_class_bind_template_child (widget_class, CcWifiPanel, stack); gtk_widget_class_bind_template_callback (widget_class, rfkill_switch_notify_activate_cb); g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); } static void cc_wifi_panel_init (CcWifiPanel *self) { g_resources_register (cc_network_get_resource ()); gtk_widget_init_template (GTK_WIDGET (self)); self->cancellable = g_cancellable_new (); self->devices = g_ptr_array_new_with_free_func (g_object_unref); /* Create and store a NMClient instance if it doesn't exist yet */ if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT)) { NMClient *client = nm_client_new (NULL, NULL); cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client); g_object_unref (client); } /* Load NetworkManager */ self->client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT); g_signal_connect_object (self->client, "device-added", G_CALLBACK (device_added_cb), self, 0); g_signal_connect_object (self->client, "device-removed", G_CALLBACK (device_removed_cb), self, 0); g_signal_connect_object (self->client, "notify::wireless-enabled", G_CALLBACK (wireless_enabled_cb), self, 0); /* Load Wi-Fi devices */ load_wifi_devices (self); /* Acquire Airplane Mode proxy */ cc_object_storage_create_dbus_proxy (G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, "org.gnome.SettingsDaemon.Rfkill", "/org/gnome/SettingsDaemon/Rfkill", "org.gnome.SettingsDaemon.Rfkill", self->cancellable, rfkill_proxy_acquired_cb, self); /* Handle comment-line arguments after loading devices */ handle_argv (self); }