gnome-control-center/panels/network/cc-wifi-panel.c
Georges Basile Stavracas Neto 016efdfac8 wifi: Introduce Wi-Fi panel
The glory moment has come. The new Wi-Fi panel is finally
introduced using a different code style from the rest of
the Network panel, since Control Center itself is written
using the GTK+ C code style.

The Wi-Fi panel uses modern GTK+ features like template
classes and new widgets. The files are stored together
with the Network panel so that we can reuse the abstraction
layer that the Network panel has to manage devices.

https://bugzilla.gnome.org/show_bug.cgi?id=784818
2017-07-18 13:30:46 -03:00

632 lines
18 KiB
C

/* -*- 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/list-box-helper.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
};
/* 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)
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 */
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, 0);
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
rfkill_proxy_acquired_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
CcWifiPanel *self = CC_WIFI_PANEL (user_data);
GError *error = NULL;
self->rfkill_proxy = g_dbus_proxy_new_for_bus_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;
}
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/wifi";
}
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);
/* Load NetworkManager */
self->client = nm_client_new (NULL, NULL);
g_signal_connect (self->client,
"device-added",
G_CALLBACK (device_added_cb),
self);
g_signal_connect (self->client,
"device-removed",
G_CALLBACK (device_removed_cb),
self);
/* Load Wi-Fi devices */
load_wifi_devices (self);
/* Acquire Airplane Mode proxy */
g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"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);
}