gnome-control-center/panels/bluetooth/cc-bluetooth-panel.c
Benjamin Berg 75c3e11708 bluetooth: Fix bluetooth switch transitions
This commit changes the switch to do a proper delayed state change using
the state-set signal. Also changed is that we always update the switch
state rather than avoiding an update when it is not powered.

Avoiding this update was introduced in commit 4a009da483 (bluetooth:
Don't change the switch status when transitioning), however, the current
implementation causes us to get stuck in the wrong visual state
sometimes. Also, with this patch I am unable to see any visual glitch on
hardware that should be affected, and even if there was a glitch,
getting the final state right is more important.

Closes: #607, #1272
2021-07-29 23:10:46 +00:00

274 lines
9.4 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 <shell/cc-shell.h>
#include <shell/cc-object-storage.h>
#include <bluetooth-settings-widget.h>
#include "cc-bluetooth-panel.h"
#include "cc-bluetooth-resources.h"
struct _CcBluetoothPanel {
CcPanel parent_instance;
GtkBox *airplane_box;
GtkBox *disabled_box;
GtkSwitch *enable_switch;
GtkBox *header_box;
GtkBox *hw_airplane_box;
GtkBox *no_devices_box;
BluetoothSettingsWidget *settings_widget;
GtkStack *stack;
/* Killswitch */
GDBusProxy *rfkill;
GDBusProxy *properties;
gboolean airplane_mode;
gboolean bt_airplane_mode;
gboolean hardware_airplane_mode;
gboolean has_airplane_mode;
};
CC_PANEL_REGISTER (CcBluetoothPanel, cc_bluetooth_panel)
static const char *
cc_bluetooth_panel_get_help_uri (CcPanel *panel)
{
return "help:gnome-help/bluetooth";
}
static void
cc_bluetooth_panel_finalize (GObject *object)
{
CcBluetoothPanel *self;
self = CC_BLUETOOTH_PANEL (object);
g_clear_object (&self->properties);
g_clear_object (&self->rfkill);
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 */
cc_shell_embed_widget_in_header (cc_panel_get_shell (CC_PANEL (self)),
GTK_WIDGET (self->header_box), GTK_POS_RIGHT);
}
static void
airplane_mode_changed_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GVariant) ret = NULL;
g_autoptr(GError) error = NULL;
gboolean state = GPOINTER_TO_UINT (user_data);
if (!g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object),
res, &error)) {
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to change Bluetooth killswitch state to %s: %s",
state ? "on" : "off", error->message);
} else {
g_debug ("Changed Bluetooth killswitch state to %s",
state ? "on" : "off");
}
}
static void
enable_switch_state_set_cb (CcBluetoothPanel *self, gboolean state)
{
g_debug ("Power switched to %s", state ? "on" : "off");
g_dbus_proxy_call (self->properties,
"Set",
g_variant_new_parsed ("('org.gnome.SettingsDaemon.Rfkill', 'BluetoothAirplaneMode', %v)",
g_variant_new_boolean (!state)),
G_DBUS_CALL_FLAGS_NONE,
-1,
cc_panel_get_cancellable (CC_PANEL (self)),
airplane_mode_changed_cb, self);
}
static void
adapter_status_changed_cb (CcBluetoothPanel *self)
{
GtkAlign valign;
gboolean sensitive, powered;
GtkWidget *page;
g_debug ("Updating airplane mode: BluetoothHasAirplaneMode %d, BluetoothHardwareAirplaneMode %d, BluetoothAirplaneMode %d, AirplaneMode %d",
self->has_airplane_mode, self->hardware_airplane_mode, self->bt_airplane_mode, self->airplane_mode);
valign = GTK_ALIGN_CENTER;
if (self->has_airplane_mode == FALSE) {
g_debug ("No Bluetooth available");
sensitive = FALSE;
powered = FALSE;
page = GTK_WIDGET (self->no_devices_box);
} else if (self->hardware_airplane_mode) {
g_debug ("Bluetooth is Hard blocked");
sensitive = FALSE;
powered = FALSE;
page = GTK_WIDGET (self->hw_airplane_box);
} else if (self->airplane_mode) {
g_debug ("Airplane mode is on, Wi-Fi and Bluetooth are disabled");
sensitive = FALSE;
powered = FALSE;
page = GTK_WIDGET (self->airplane_box);
} else if (self->bt_airplane_mode ||
!bluetooth_settings_widget_get_default_adapter_powered (self->settings_widget)) {
g_debug ("Default adapter is unpowered");
sensitive = TRUE;
powered = FALSE;
page = GTK_WIDGET (self->disabled_box);
} else {
g_debug ("Bluetooth is available and powered");
sensitive = TRUE;
powered = TRUE;
page = GTK_WIDGET (self->settings_widget);
valign = GTK_ALIGN_FILL;
}
gtk_widget_set_valign (GTK_WIDGET (self->stack), valign);
gtk_widget_set_sensitive (GTK_WIDGET (self->header_box), sensitive);
g_signal_handlers_block_by_func (self->enable_switch, enable_switch_state_set_cb, self);
gtk_switch_set_state (self->enable_switch, powered);
g_signal_handlers_unblock_by_func (self->enable_switch, enable_switch_state_set_cb, self);
gtk_stack_set_visible_child (self->stack, page);
}
static void
airplane_mode_changed (CcBluetoothPanel *self)
{
g_autoptr(GVariant) airplane_mode = NULL;
g_autoptr(GVariant) bluetooth_airplane_mode = NULL;
g_autoptr(GVariant) bluetooth_hardware_airplane_mode = NULL;
g_autoptr(GVariant) bluetooth_has_airplane_mode = NULL;
airplane_mode = g_dbus_proxy_get_cached_property (self->rfkill, "AirplaneMode");
self->airplane_mode = g_variant_get_boolean (airplane_mode);
bluetooth_airplane_mode = g_dbus_proxy_get_cached_property (self->rfkill, "BluetoothAirplaneMode");
self->bt_airplane_mode = g_variant_get_boolean (bluetooth_airplane_mode);
bluetooth_hardware_airplane_mode = g_dbus_proxy_get_cached_property (self->rfkill, "BluetoothHardwareAirplaneMode");
self->hardware_airplane_mode = g_variant_get_boolean (bluetooth_hardware_airplane_mode);
bluetooth_has_airplane_mode = g_dbus_proxy_get_cached_property (self->rfkill, "BluetoothHasAirplaneMode");
self->has_airplane_mode = g_variant_get_boolean (bluetooth_has_airplane_mode);
adapter_status_changed_cb (self);
}
static void
airplane_mode_off_button_clicked_cb (CcBluetoothPanel *self)
{
g_debug ("Airplane Mode Off clicked, disabling airplane mode");
g_dbus_proxy_call (self->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,
cc_panel_get_cancellable (CC_PANEL (self)),
NULL, NULL);
}
static void
panel_changed_cb (CcBluetoothPanel *self,
const char *panel)
{
CcShell *shell;
g_autoptr(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);
}
static void
cc_bluetooth_panel_class_init (CcBluetoothPanelClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_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;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/bluetooth/cc-bluetooth-panel.ui");
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, airplane_box);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, disabled_box);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, enable_switch);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, header_box);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, no_devices_box);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, hw_airplane_box);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, settings_widget);
gtk_widget_class_bind_template_child (widget_class, CcBluetoothPanel, stack);
gtk_widget_class_bind_template_callback (widget_class, adapter_status_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, airplane_mode_off_button_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, enable_switch_state_set_cb);
gtk_widget_class_bind_template_callback (widget_class, panel_changed_cb);
}
static void
cc_bluetooth_panel_init (CcBluetoothPanel *self)
{
bluetooth_settings_widget_get_type ();
g_resources_register (cc_bluetooth_get_resource ());
gtk_widget_init_template (GTK_WIDGET (self));
/* RFKill */
self->rfkill = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
"org.gnome.SettingsDaemon.Rfkill",
"/org/gnome/SettingsDaemon/Rfkill",
"org.gnome.SettingsDaemon.Rfkill",
NULL, NULL);
self->properties = cc_object_storage_create_dbus_proxy_sync (G_BUS_TYPE_SESSION,
G_DBUS_PROXY_FLAGS_NONE,
"org.gnome.SettingsDaemon.Rfkill",
"/org/gnome/SettingsDaemon/Rfkill",
"org.freedesktop.DBus.Properties",
NULL, NULL);
airplane_mode_changed (self);
g_signal_connect_object (self->rfkill, "g-properties-changed",
G_CALLBACK (airplane_mode_changed), self, G_CONNECT_SWAPPED);
}