gnome-control-center/panels/network/cc-wifi-panel.c

729 lines
20 KiB
C
Raw Normal View History

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#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-application.h"
#include "shell/cc-debug.h"
#include "shell/cc-object-storage.h"
#include <glib/gi18n.h>
#include <NetworkManager.h>
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
};
/* Static init function */
static void
update_panel_visibility (NMClient *client)
{
const GPtrArray *devices;
CcApplication *application;
gboolean visible;
guint i;
CC_TRACE_MSG ("Updating Wi-Fi panel visibility");
devices = nm_client_get_devices (client);
visible = FALSE;
for (i = 0; devices && i < devices->len; i++)
{
NMDevice *device = g_ptr_array_index (devices, i);
visible |= NM_IS_DEVICE_WIFI (device);
if (visible)
break;
}
/* Set the new visibility */
application = CC_APPLICATION (g_application_get_default ());
cc_shell_model_set_panel_visibility (cc_application_get_model (application),
"wifi",
visible ? CC_PANEL_VISIBLE : CC_PANEL_VISIBLE_IN_SEARCH);
CC_TRACE_MSG ("Wi-Fi panel visible: %s", visible ? "yes" : "no");
}
void
cc_wifi_panel_static_init_func (void)
{
NMClient *client;
g_debug ("Monitoring NetworkManager for Wi-Fi devices");
/* Create and store a NMClient instance if it doesn't exist yet */
if (!cc_object_storage_has_object (CC_OBJECT_NMCLIENT))
{
client = nm_client_new (NULL, NULL);
cc_object_storage_add_object (CC_OBJECT_NMCLIENT, client);
g_object_unref (client);
}
client = cc_object_storage_get_object (CC_OBJECT_NMCLIENT);
/* Update the panel visibility and monitor for changes */
g_signal_connect (client, "device-added", G_CALLBACK (update_panel_visibility), NULL);
g_signal_connect (client, "device-removed", G_CALLBACK (update_panel_visibility), NULL);
update_panel_visibility (client);
g_object_unref (client);
}
/* 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);
}