gnome-control-center/panels/bluetooth/cc-bluetooth-panel.c
Bastien Nocera 4a009da483 bluetooth: Don't change the switch status when transitioning
When transitioning from an enabled platform rfkill to disabled, the
Bluetooth adapter's power will lag behind. Make sure to not change the
switch's status when we get into this temporary state.

https://bugzilla.gnome.org/show_bug.cgi?id=760168
2016-01-10 15:59:25 +01:00

353 lines
12 KiB
C

/*
*
* Copyright (C) 2013 Bastien Nocera <hadess@hadess.net>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib/gi18n-lib.h>
#include <shell/cc-shell.h>
#include <bluetooth-settings-widget.h>
#include "cc-bluetooth-panel.h"
#include "cc-bluetooth-resources.h"
CC_PANEL_REGISTER (CcBluetoothPanel, cc_bluetooth_panel)
#define BLUETOOTH_PANEL_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_BLUETOOTH_PANEL, CcBluetoothPanelPrivate))
#define WID(s) GTK_WIDGET (gtk_builder_get_object (self->priv->builder, s))
#define BLUETOOTH_NO_DEVICES_PAGE "no-devices-page"
#define BLUETOOTH_DISABLED_PAGE "disabled-page"
#define BLUETOOTH_AIRPLANE_PAGE "airplane-page"
#define BLUETOOTH_HW_AIRPLANE_PAGE "hw-airplane-page"
#define BLUETOOTH_WORKING_PAGE "working-page"
struct CcBluetoothPanelPrivate {
GtkBuilder *builder;
GtkWidget *stack;
GtkWidget *widget;
GCancellable *cancellable;
/* Killswitch */
GtkWidget *kill_switch_header;
GDBusProxy *rfkill, *properties;
gboolean airplane_mode;
gboolean bt_airplane_mode;
gboolean hardware_airplane_mode;
gboolean has_airplane_mode;
};
static void cc_bluetooth_panel_finalize (GObject *object);
static void cc_bluetooth_panel_constructed (GObject *object);
static const char *
cc_bluetooth_panel_get_help_uri (CcPanel *panel)
{
return "help:gnome-help/bluetooth";
}
static void
cc_bluetooth_panel_class_init (CcBluetoothPanelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
CcPanelClass *panel_class = CC_PANEL_CLASS (klass);
object_class->constructed = cc_bluetooth_panel_constructed;
object_class->finalize = cc_bluetooth_panel_finalize;
panel_class->get_help_uri = cc_bluetooth_panel_get_help_uri;
g_type_class_add_private (klass, sizeof (CcBluetoothPanelPrivate));
}
static void
cc_bluetooth_panel_finalize (GObject *object)
{
CcBluetoothPanel *self;
self = CC_BLUETOOTH_PANEL (object);
g_cancellable_cancel (self->priv->cancellable);
g_clear_object (&self->priv->cancellable);
g_clear_object (&self->priv->properties);
g_clear_object (&self->priv->rfkill);
g_clear_object (&self->priv->kill_switch_header);
G_OBJECT_CLASS (cc_bluetooth_panel_parent_class)->finalize (object);
}
static void
cc_bluetooth_panel_constructed (GObject *object)
{
CcBluetoothPanel *self = CC_BLUETOOTH_PANEL (object);
G_OBJECT_CLASS (cc_bluetooth_panel_parent_class)->constructed (object);
/* add kill switch widgets */
self->priv->kill_switch_header = g_object_ref (WID ("box_power"));
cc_shell_embed_widget_in_header (cc_panel_get_shell (CC_PANEL (self)),
self->priv->kill_switch_header);
gtk_widget_show_all (self->priv->kill_switch_header);
}
static void
power_callback (GObject *object,
GParamSpec *spec,
CcBluetoothPanel *self)
{
gboolean state;
state = gtk_switch_get_active (GTK_SWITCH (WID ("switch_bluetooth")));
g_debug ("Power switched to %s", state ? "on" : "off");
g_dbus_proxy_call (self->priv->properties,
"Set",
g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill', 'BluetoothAirplaneMode', %v)",
g_variant_new_boolean (!state)),
G_DBUS_CALL_FLAGS_NONE,
-1,
self->priv->cancellable,
NULL, NULL);
}
static void
cc_bluetooth_panel_update_power (CcBluetoothPanel *self)
{
GObject *toggle;
gboolean sensitive, powered, change_powered;
const char *page;
g_debug ("Updating airplane mode: has_airplane_mode %d, hardware_airplane_mode %d, BT airplane_mode %d, airplane_mode %d",
self->priv->has_airplane_mode, self->priv->hardware_airplane_mode, self->priv->bt_airplane_mode, self->priv->airplane_mode);
change_powered = TRUE;
if (self->priv->has_airplane_mode == FALSE) {
g_debug ("No Bluetooth available");
sensitive = FALSE;
powered = FALSE;
page = BLUETOOTH_NO_DEVICES_PAGE;
} else if (self->priv->hardware_airplane_mode) {
g_debug ("Bluetooth is Hard blocked");
sensitive = FALSE;
powered = FALSE;
page = BLUETOOTH_HW_AIRPLANE_PAGE;
} else if (self->priv->airplane_mode) {
g_debug ("Airplane mode is on, Wi-Fi and Bluetooth are disabled");
sensitive = FALSE;
powered = FALSE;
page = BLUETOOTH_AIRPLANE_PAGE;
} else if (self->priv->bt_airplane_mode ||
!bluetooth_settings_widget_get_default_adapter_powered (BLUETOOTH_SETTINGS_WIDGET (self->priv->widget))) {
g_debug ("Default adapter is unpowered, but should be available");
sensitive = TRUE;
change_powered = FALSE;
page = BLUETOOTH_DISABLED_PAGE;
} else {
g_debug ("Bluetooth is available and powered");
sensitive = TRUE;
powered = TRUE;
page = BLUETOOTH_WORKING_PAGE;
}
gtk_widget_set_sensitive (WID ("box_power") , sensitive);
toggle = G_OBJECT (WID ("switch_bluetooth"));
if (change_powered) {
g_signal_handlers_block_by_func (toggle, power_callback, self);
gtk_switch_set_active (GTK_SWITCH (toggle), powered);
g_signal_handlers_unblock_by_func (toggle, power_callback, self);
}
gtk_stack_set_visible_child_name (GTK_STACK (self->priv->stack), page);
}
static void
airplane_mode_changed (GDBusProxy *proxy,
GVariant *changed_properties,
GStrv invalidated_properties,
CcBluetoothPanel *self)
{
GVariant *v;
v = g_dbus_proxy_get_cached_property (self->priv->rfkill, "AirplaneMode");
self->priv->airplane_mode = g_variant_get_boolean (v);
g_variant_unref (v);
v = g_dbus_proxy_get_cached_property (self->priv->rfkill, "BluetoothAirplaneMode");
self->priv->bt_airplane_mode = g_variant_get_boolean (v);
g_variant_unref (v);
v = g_dbus_proxy_get_cached_property (self->priv->rfkill, "BluetoothHardwareAirplaneMode");
self->priv->hardware_airplane_mode = g_variant_get_boolean (v);
g_variant_unref (v);
v = g_dbus_proxy_get_cached_property (self->priv->rfkill, "BluetoothHasAirplaneMode");
self->priv->has_airplane_mode = g_variant_get_boolean (v);
g_variant_unref (v);
cc_bluetooth_panel_update_power (self);
}
static void
on_airplane_mode_off_clicked (GtkButton *button,
CcBluetoothPanel *self)
{
g_debug ("Airplane Mode Off clicked, disabling airplane mode");
g_dbus_proxy_call (self->priv->rfkill,
"org.freedesktop.DBus.Properties.Set",
g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill',"
"'AirplaneMode', %v)",
g_variant_new_boolean (FALSE)),
G_DBUS_CALL_FLAGS_NONE,
-1,
self->priv->cancellable,
NULL, NULL);
}
static void
add_stack_page (CcBluetoothPanel *self,
const char *message,
const char *explanation,
const char *name)
{
GtkWidget *label, *image, *box;
char *str;
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
g_object_set (G_OBJECT (box), "margin-top", 64, "margin-bottom", 64, NULL);
if (g_str_equal (name, BLUETOOTH_AIRPLANE_PAGE) ||
g_str_equal (name, BLUETOOTH_HW_AIRPLANE_PAGE)) {
image = gtk_image_new_from_icon_name ("airplane-mode-symbolic",
GTK_ICON_SIZE_DIALOG);
} else {
image = gtk_image_new_from_icon_name ("bluetooth-active-symbolic",
GTK_ICON_SIZE_DIALOG);
}
gtk_image_set_pixel_size (GTK_IMAGE (image), 192);
gtk_style_context_add_class (gtk_widget_get_style_context (image), "dim-label");
gtk_box_pack_start (GTK_BOX (box), image, FALSE, FALSE, 24);
str = g_strdup_printf ("<span size=\"larger\" weight=\"bold\">%s</span>", message);
label = gtk_label_new ("");
gtk_label_set_markup (GTK_LABEL (label), str);
g_free (str);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
label = gtk_label_new (explanation);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
if (g_str_equal (name, BLUETOOTH_AIRPLANE_PAGE)) {
GtkWidget *button, *alignment;
button = gtk_button_new_with_label (_("Turn Off Airplane Mode"));
alignment = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
gtk_container_add (GTK_CONTAINER (alignment), button);
g_signal_connect (G_OBJECT (button), "clicked",
G_CALLBACK (on_airplane_mode_off_clicked), self);
gtk_box_pack_start (GTK_BOX (box), alignment, FALSE, FALSE, 24);
}
gtk_stack_add_named (GTK_STACK (self->priv->stack), box, name);
gtk_widget_show_all (box);
}
static void
panel_changed (GtkWidget *settings_widget,
const char *panel,
CcBluetoothPanel *self)
{
CcShell *shell;
GError *error = NULL;
shell = cc_panel_get_shell (CC_PANEL (self));
if (cc_shell_set_active_panel_from_id (shell, panel, NULL, &error) == FALSE) {
g_warning ("Failed to activate '%s' panel: %s", panel, error->message);
g_error_free (error);
}
}
static void
cc_bluetooth_panel_init (CcBluetoothPanel *self)
{
GError *error = NULL;
self->priv = BLUETOOTH_PANEL_PRIVATE (self);
g_resources_register (cc_bluetooth_get_resource ());
self->priv->builder = gtk_builder_new ();
gtk_builder_set_translation_domain (self->priv->builder, GETTEXT_PACKAGE);
gtk_builder_add_from_resource (self->priv->builder,
"/org/gnome/control-center/bluetooth/bluetooth.ui",
&error);
if (error != NULL) {
g_warning ("Could not load ui: %s", error->message);
g_error_free (error);
return;
}
self->priv->cancellable = g_cancellable_new ();
/* RFKill */
self->priv->rfkill = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.gnome.SettingsDaemon.Rfkill",
"/org/gnome/SettingsDaemon/Rfkill",
"org.gnome.SettingsDaemon.Rfkill",
NULL, NULL);
self->priv->properties = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
NULL,
"org.gnome.SettingsDaemon.Rfkill",
"/org/gnome/SettingsDaemon/Rfkill",
"org.freedesktop.DBus.Properties",
NULL, NULL);
self->priv->stack = gtk_stack_new ();
gtk_stack_set_homogeneous (GTK_STACK (self->priv->stack), TRUE);
add_stack_page (self, _("No Bluetooth Found"), _("Plug in a dongle to use Bluetooth."), BLUETOOTH_NO_DEVICES_PAGE);
add_stack_page (self, _("Bluetooth Turned Off"), _("Turn on to connect devices and receive file transfers."), BLUETOOTH_DISABLED_PAGE);
add_stack_page (self, _("Airplane Mode is on"), _("Bluetooth is disabled when airplane mode is on."), BLUETOOTH_AIRPLANE_PAGE);
add_stack_page (self, _("Hardware Airplane Mode is on"), _("Turn off the Airplane mode switch to enable Bluetooth."), BLUETOOTH_HW_AIRPLANE_PAGE);
self->priv->widget = bluetooth_settings_widget_new ();
g_signal_connect (G_OBJECT (self->priv->widget), "panel-changed",
G_CALLBACK (panel_changed), self);
gtk_stack_add_named (GTK_STACK (self->priv->stack),
self->priv->widget, BLUETOOTH_WORKING_PAGE);
gtk_widget_show (self->priv->widget);
gtk_widget_show (self->priv->stack);
gtk_container_add (GTK_CONTAINER (self), self->priv->stack);
airplane_mode_changed (NULL, NULL, NULL, self);
g_signal_connect (self->priv->rfkill, "g-properties-changed",
G_CALLBACK (airplane_mode_changed), self);
g_signal_connect_swapped (G_OBJECT (self->priv->widget), "adapter-status-changed",
G_CALLBACK (cc_bluetooth_panel_update_power), self);
g_signal_connect (G_OBJECT (WID ("switch_bluetooth")), "notify::active",
G_CALLBACK (power_callback), self);
}