diff options
Diffstat (limited to 'plugins/rfkill')
-rw-r--r-- | plugins/rfkill/61-gnome-settings-daemon-rfkill.rules | 8 | ||||
-rw-r--r-- | plugins/rfkill/gsd-rfkill-manager.c | 902 | ||||
-rw-r--r-- | plugins/rfkill/gsd-rfkill-manager.h | 39 | ||||
-rw-r--r-- | plugins/rfkill/main.c | 7 | ||||
-rw-r--r-- | plugins/rfkill/meson.build | 28 | ||||
-rw-r--r-- | plugins/rfkill/rfkill-glib.c | 680 | ||||
-rw-r--r-- | plugins/rfkill/rfkill-glib.h | 57 | ||||
-rw-r--r-- | plugins/rfkill/rfkill.h | 107 |
8 files changed, 1828 insertions, 0 deletions
diff --git a/plugins/rfkill/61-gnome-settings-daemon-rfkill.rules b/plugins/rfkill/61-gnome-settings-daemon-rfkill.rules new file mode 100644 index 0000000..87eabff --- /dev/null +++ b/plugins/rfkill/61-gnome-settings-daemon-rfkill.rules @@ -0,0 +1,8 @@ +# Get access to /dev/rfkill for users +# See https://bugzilla.redhat.com/show_bug.cgi?id=514798 +# +# Simplified by Kay Sievers +# https://bugzilla.redhat.com/show_bug.cgi?id=733326 +# See also https://bugzilla.gnome.org/show_bug.cgi?id=711373 + +KERNEL=="rfkill", SUBSYSTEM=="misc", TAG+="uaccess" diff --git a/plugins/rfkill/gsd-rfkill-manager.c b/plugins/rfkill/gsd-rfkill-manager.c new file mode 100644 index 0000000..5c8b690 --- /dev/null +++ b/plugins/rfkill/gsd-rfkill-manager.c @@ -0,0 +1,902 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * Copyright (C) 2010,2011 Red Hat, Inc. + * + * Author: 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, see <http://www.gnu.org/licenses/>. + * + */ + +/* Test with: + * gdbus call \ + * --session \ + * --dest org.gnome.SettingsDaemon.Rfkill \ + * --object-path /org/gnome/SettingsDaemon/Rfkill \ + * --method org.freedesktop.DBus.Properties.Set \ + * "org.gnome.SettingsDaemon.Rfkill" \ + * "AirplaneMode" \ + * "<true|false>" + * and + * gdbus call \ + * --session \ + * --dest org.gnome.SettingsDaemon.Rfkill \ + * --object-path /org/gnome/SettingsDaemon/Rfkill \ + * --method org.freedesktop.DBus.Properties.Set \ + * "org.gnome.SettingsDaemon.Rfkill" \ + * "BluetoothAirplaneMode" \ + * "<true|false>" + * + * and + * gdbus call \ + * --session \ + * --dest org.gnome.SettingsDaemon.Rfkill \ + * --object-path /org/gnome/SettingsDaemon/Rfkill \ + * --method org.freedesktop.DBus.Properties.Set \ + * "org.gnome.SettingsDaemon.Rfkill" \ + * "WwanAirplaneMode" \ + * "<true|false>" + */ + +#include "config.h" + +#include <gio/gio.h> +#include <string.h> + +#include "gnome-settings-profile.h" +#include "gsd-rfkill-manager.h" +#include "rfkill-glib.h" +#include "gnome-settings-bus.h" + +struct _GsdRfkillManager +{ + GObject parent; + + GDBusNodeInfo *introspection_data; + guint name_id; + GDBusConnection *connection; + GCancellable *cancellable; + + CcRfkillGlib *rfkill; + GHashTable *killswitches; + GHashTable *bt_killswitches; + GHashTable *wwan_killswitches; + + /* In addition to using the rfkill kernel subsystem + (which is exposed by wlan, wimax, bluetooth, nfc, + some platform drivers and some usb modems), we + need to go through NetworkManager, which in turn + will tell ModemManager to write the right commands + in the USB bus to take external modems down, all + from userspace. + */ + GDBusProxy *nm_client; + gboolean wwan_enabled; + GDBusObjectManager *mm_client; + gboolean wwan_interesting; + + GsdSessionManager *session; + GBinding *rfkill_input_inhibit_binding; + + gchar *chassis_type; +}; + +#define GSD_DBUS_NAME "org.gnome.SettingsDaemon" +#define GSD_DBUS_PATH "/org/gnome/SettingsDaemon" +#define GSD_DBUS_BASE_INTERFACE "org.gnome.SettingsDaemon" + +#define GSD_RFKILL_DBUS_NAME GSD_DBUS_NAME ".Rfkill" +#define GSD_RFKILL_DBUS_PATH GSD_DBUS_PATH "/Rfkill" + +static const gchar introspection_xml[] = +"<node>" +" <interface name='org.gnome.SettingsDaemon.Rfkill'>" +" <annotation name='org.freedesktop.DBus.GLib.CSymbol' value='gsd_rfkill_manager'/>" +" <property name='AirplaneMode' type='b' access='readwrite'/>" +" <property name='HardwareAirplaneMode' type='b' access='read'/>" +" <property name='HasAirplaneMode' type='b' access='read'/>" +" <property name='ShouldShowAirplaneMode' type='b' access='read'/>" +" <property name='BluetoothAirplaneMode' type='b' access='readwrite'/>" +" <property name='BluetoothHardwareAirplaneMode' type='b' access='read'/>" +" <property name='BluetoothHasAirplaneMode' type='b' access='read'/>" +" <property name='WwanAirplaneMode' type='b' access='readwrite'/>" +" <property name='WwanHardwareAirplaneMode' type='b' access='read'/>" +" <property name='WwanHasAirplaneMode' type='b' access='read'/>" +" </interface>" +"</node>"; + +static void gsd_rfkill_manager_class_init (GsdRfkillManagerClass *klass); +static void gsd_rfkill_manager_init (GsdRfkillManager *rfkill_manager); +static void gsd_rfkill_manager_finalize (GObject *object); + +G_DEFINE_TYPE (GsdRfkillManager, gsd_rfkill_manager, G_TYPE_OBJECT) + +static gpointer manager_object = NULL; + +static void +gsd_rfkill_manager_class_init (GsdRfkillManagerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsd_rfkill_manager_finalize; +} + +static void +gsd_rfkill_manager_init (GsdRfkillManager *manager) +{ +} + +static gboolean +engine_get_airplane_mode_helper (GHashTable *killswitches) +{ + GHashTableIter iter; + gpointer key, value; + + if (g_hash_table_size (killswitches) == 0) + return FALSE; + + g_hash_table_iter_init (&iter, killswitches); + while (g_hash_table_iter_next (&iter, &key, &value)) { + int state; + + state = GPOINTER_TO_INT (value); + + /* A single rfkill switch that's unblocked? Airplane mode is off */ + if (state == RFKILL_STATE_UNBLOCKED) + return FALSE; + } + + return TRUE; +} + +static gboolean +engine_get_bluetooth_airplane_mode (GsdRfkillManager *manager) +{ + return engine_get_airplane_mode_helper (manager->bt_killswitches); +} + +static gboolean +engine_get_bluetooth_hardware_airplane_mode (GsdRfkillManager *manager) +{ + GHashTableIter iter; + gpointer key, value; + + /* If we have no killswitches, hw airplane mode is off. */ + if (g_hash_table_size (manager->bt_killswitches) == 0) + return FALSE; + + g_hash_table_iter_init (&iter, manager->bt_killswitches); + while (g_hash_table_iter_next (&iter, &key, &value)) { + int state; + + state = GPOINTER_TO_INT (value); + + /* A single rfkill switch that's not hw blocked? Hw airplane mode is off */ + if (state != RFKILL_STATE_HARD_BLOCKED) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +engine_get_has_bluetooth_airplane_mode (GsdRfkillManager *manager) +{ + return (g_hash_table_size (manager->bt_killswitches) > 0); +} + +static gboolean +engine_get_wwan_airplane_mode (GsdRfkillManager *manager) +{ + gboolean is_airplane; + + is_airplane = engine_get_airplane_mode_helper (manager->wwan_killswitches); + + /* Try our luck with Modem Manager too. Check only if no rfkill + * devices found, or if rfkill reports all devices to be down. + * (Airplane mode will be disabled if at least one device is up, + * so if rfkill says no device is up, check any device is up via + * Network Manager (which in turn, is handled via Modem Manager)) + */ + if (g_hash_table_size (manager->wwan_killswitches) == 0 || is_airplane) + if (manager->wwan_interesting) + is_airplane = !manager->wwan_enabled; + + return is_airplane; +} + +static gboolean +engine_get_wwan_hardware_airplane_mode (GsdRfkillManager *manager) +{ + GHashTableIter iter; + gpointer key, value; + + /* If we have no killswitches, hw airplane mode is off. */ + if (g_hash_table_size (manager->wwan_killswitches) == 0) + return FALSE; + + g_hash_table_iter_init (&iter, manager->wwan_killswitches); + while (g_hash_table_iter_next (&iter, &key, &value)) { + int state; + + state = GPOINTER_TO_INT (value); + + /* A single rfkill switch that's not hw blocked? Hw airplane mode is off */ + if (state != RFKILL_STATE_HARD_BLOCKED) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +engine_get_has_wwan_airplane_mode (GsdRfkillManager *manager) +{ + return (g_hash_table_size (manager->wwan_killswitches) > 0 || + manager->wwan_interesting); +} + +static gboolean +engine_get_airplane_mode (GsdRfkillManager *manager) +{ + if (!manager->wwan_interesting) + return engine_get_airplane_mode_helper (manager->killswitches); + /* wwan enabled? then airplane mode is off (because an USB modem + could be on in this state) */ + return engine_get_airplane_mode_helper (manager->killswitches) && !manager->wwan_enabled; +} + +static gboolean +engine_get_hardware_airplane_mode (GsdRfkillManager *manager) +{ + GHashTableIter iter; + gpointer key, value; + + /* If we have no killswitches, hw airplane mode is off. */ + if (g_hash_table_size (manager->killswitches) == 0) + return FALSE; + + g_hash_table_iter_init (&iter, manager->killswitches); + while (g_hash_table_iter_next (&iter, &key, &value)) { + int state; + + state = GPOINTER_TO_INT (value); + + /* A single rfkill switch that's not hw blocked? Hw airplane mode is off */ + if (state != RFKILL_STATE_HARD_BLOCKED) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +engine_get_has_airplane_mode (GsdRfkillManager *manager) +{ + return (g_hash_table_size (manager->killswitches) > 0) || + manager->wwan_interesting; +} + +static gboolean +engine_get_should_show_airplane_mode (GsdRfkillManager *manager) +{ + return (g_strcmp0 (manager->chassis_type, "desktop") != 0) && + (g_strcmp0 (manager->chassis_type, "server") != 0) && + (g_strcmp0 (manager->chassis_type, "vm") != 0) && + (g_strcmp0 (manager->chassis_type, "container") != 0); +} + +static void +engine_properties_changed (GsdRfkillManager *manager) +{ + GVariantBuilder props_builder; + GVariant *props_changed = NULL; + + /* not yet connected to the session bus */ + if (manager->connection == NULL) + return; + + g_variant_builder_init (&props_builder, G_VARIANT_TYPE ("a{sv}")); + + g_variant_builder_add (&props_builder, "{sv}", "AirplaneMode", + g_variant_new_boolean (engine_get_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "HardwareAirplaneMode", + g_variant_new_boolean (engine_get_hardware_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "HasAirplaneMode", + g_variant_new_boolean (engine_get_has_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "ShouldShowAirplaneMode", + g_variant_new_boolean (engine_get_should_show_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "BluetoothAirplaneMode", + g_variant_new_boolean (engine_get_bluetooth_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "BluetoothHardwareAirplaneMode", + g_variant_new_boolean (engine_get_bluetooth_hardware_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "BluetoothHasAirplaneMode", + g_variant_new_boolean (engine_get_has_bluetooth_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "WwanAirplaneMode", + g_variant_new_boolean (engine_get_wwan_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "WwanHardwareAirplaneMode", + g_variant_new_boolean (engine_get_wwan_hardware_airplane_mode (manager))); + g_variant_builder_add (&props_builder, "{sv}", "WwanHasAirplaneMode", + g_variant_new_boolean (engine_get_has_wwan_airplane_mode (manager))); + + props_changed = g_variant_new ("(s@a{sv}@as)", GSD_RFKILL_DBUS_NAME, + g_variant_builder_end (&props_builder), + g_variant_new_strv (NULL, 0)); + + g_dbus_connection_emit_signal (manager->connection, + NULL, + GSD_RFKILL_DBUS_PATH, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + props_changed, NULL); +} + +static void +rfkill_changed (CcRfkillGlib *rfkill, + GList *events, + GsdRfkillManager *manager) +{ + GList *l; + int value; + + for (l = events; l != NULL; l = l->next) { + struct rfkill_event *event = l->data; + const gchar *type = ""; + + if (event->type == RFKILL_TYPE_BLUETOOTH) + type = "Bluetooth "; + else if (event->type == RFKILL_TYPE_WWAN) + type = "WWAN "; + + switch (event->op) { + case RFKILL_OP_ADD: + case RFKILL_OP_CHANGE: + if (event->hard) + value = RFKILL_STATE_HARD_BLOCKED; + else if (event->soft) + value = RFKILL_STATE_SOFT_BLOCKED; + else + value = RFKILL_STATE_UNBLOCKED; + + g_hash_table_insert (manager->killswitches, + GINT_TO_POINTER (event->idx), + GINT_TO_POINTER (value)); + if (event->type == RFKILL_TYPE_BLUETOOTH) + g_hash_table_insert (manager->bt_killswitches, + GINT_TO_POINTER (event->idx), + GINT_TO_POINTER (value)); + else if (event->type == RFKILL_TYPE_WWAN) + g_hash_table_insert (manager->wwan_killswitches, + GINT_TO_POINTER (event->idx), + GINT_TO_POINTER (value)); + g_debug ("%s %srfkill with ID %d", + event->op == RFKILL_OP_ADD ? "Added" : "Changed", + type, event->idx); + break; + case RFKILL_OP_DEL: + g_hash_table_remove (manager->killswitches, + GINT_TO_POINTER (event->idx)); + if (event->type == RFKILL_TYPE_BLUETOOTH) + g_hash_table_remove (manager->bt_killswitches, + GINT_TO_POINTER (event->idx)); + else if (event->type == RFKILL_TYPE_WWAN) + g_hash_table_remove (manager->wwan_killswitches, + GINT_TO_POINTER (event->idx)); + g_debug ("Removed %srfkill with ID %d", type, event->idx); + break; + } + } + + engine_properties_changed (manager); +} + +static void +rfkill_set_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + gboolean ret; + GError *error = NULL; + + ret = cc_rfkill_glib_send_change_all_event_finish (CC_RFKILL_GLIB (source_object), res, &error); + if (!ret) { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT)) + g_debug ("Timed out waiting for blocked rfkills"); + else if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to set RFKill: %s", error->message); + g_error_free (error); + } +} + +static void +set_wwan_complete (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GError *error; + GVariant *variant; + + error = NULL; + variant = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), result, &error); + + if (variant == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to set WWAN power status: %s", error->message); + + g_error_free (error); + } else { + g_variant_unref (variant); + } +} + +static gboolean +engine_set_bluetooth_airplane_mode (GsdRfkillManager *manager, + gboolean enable) +{ + cc_rfkill_glib_send_change_all_event (manager->rfkill, RFKILL_TYPE_BLUETOOTH, + enable, manager->cancellable, rfkill_set_cb, manager); + + return TRUE; +} + +static gboolean +engine_set_wwan_airplane_mode (GsdRfkillManager *manager, + gboolean enable) +{ + cc_rfkill_glib_send_change_all_event (manager->rfkill, RFKILL_TYPE_WWAN, + enable, manager->cancellable, rfkill_set_cb, manager); + + if (manager->nm_client) { + g_dbus_proxy_call (manager->nm_client, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + "org.freedesktop.NetworkManager", + "WwanEnabled", + g_variant_new_boolean (!enable)), + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + manager->cancellable, + set_wwan_complete, NULL); + } + + return TRUE; +} + +static gboolean +engine_set_airplane_mode (GsdRfkillManager *manager, + gboolean enable) +{ + cc_rfkill_glib_send_change_all_event (manager->rfkill, RFKILL_TYPE_ALL, + enable, manager->cancellable, rfkill_set_cb, manager); + + /* Note: we set the the NM property even if there are no modems, so we don't + need to resync when one is plugged in */ + if (manager->nm_client) { + g_dbus_proxy_call (manager->nm_client, + "org.freedesktop.DBus.Properties.Set", + g_variant_new ("(ssv)", + "org.freedesktop.NetworkManager", + "WwanEnabled", + g_variant_new_boolean (!enable)), + G_DBUS_CALL_FLAGS_NONE, + -1, /* timeout */ + manager->cancellable, + set_wwan_complete, NULL); + } + + return TRUE; +} + +static gboolean +handle_set_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GVariant *value, + GError **error, + gpointer user_data) +{ + GsdRfkillManager *manager = GSD_RFKILL_MANAGER (user_data); + + if (g_strcmp0 (property_name, "AirplaneMode") == 0) { + gboolean airplane_mode; + g_variant_get (value, "b", &airplane_mode); + return engine_set_airplane_mode (manager, airplane_mode); + } else if (g_strcmp0 (property_name, "BluetoothAirplaneMode") == 0) { + gboolean airplane_mode; + g_variant_get (value, "b", &airplane_mode); + return engine_set_bluetooth_airplane_mode (manager, airplane_mode); + } + + if (g_strcmp0 (property_name, "WwanAirplaneMode") == 0) { + gboolean airplane_mode; + g_variant_get (value, "b", &airplane_mode); + return engine_set_wwan_airplane_mode (manager, airplane_mode); + } + + return FALSE; +} + +static GVariant * +handle_get_property (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *property_name, + GError **error, + gpointer user_data) +{ + GsdRfkillManager *manager = GSD_RFKILL_MANAGER (user_data); + + /* Check session pointer as a proxy for whether the manager is in the + start or stop state */ + if (manager->connection == NULL) { + return NULL; + } + + if (g_strcmp0 (property_name, "AirplaneMode") == 0) { + gboolean airplane_mode; + airplane_mode = engine_get_airplane_mode (manager); + return g_variant_new_boolean (airplane_mode); + } + + if (g_strcmp0 (property_name, "HardwareAirplaneMode") == 0) { + gboolean hw_airplane_mode; + hw_airplane_mode = engine_get_hardware_airplane_mode (manager); + return g_variant_new_boolean (hw_airplane_mode); + } + + if (g_strcmp0 (property_name, "ShouldShowAirplaneMode") == 0) { + gboolean should_show_airplane_mode; + should_show_airplane_mode = engine_get_should_show_airplane_mode (manager); + return g_variant_new_boolean (should_show_airplane_mode); + } + + if (g_strcmp0 (property_name, "HasAirplaneMode") == 0) { + gboolean has_airplane_mode; + has_airplane_mode = engine_get_has_airplane_mode (manager); + return g_variant_new_boolean (has_airplane_mode); + } + + if (g_strcmp0 (property_name, "BluetoothAirplaneMode") == 0) { + gboolean airplane_mode; + airplane_mode = engine_get_bluetooth_airplane_mode (manager); + return g_variant_new_boolean (airplane_mode); + } + + if (g_strcmp0 (property_name, "BluetoothHardwareAirplaneMode") == 0) { + gboolean hw_airplane_mode; + hw_airplane_mode = engine_get_bluetooth_hardware_airplane_mode (manager); + return g_variant_new_boolean (hw_airplane_mode); + } + + if (g_strcmp0 (property_name, "BluetoothHasAirplaneMode") == 0) { + gboolean has_airplane_mode; + has_airplane_mode = engine_get_has_bluetooth_airplane_mode (manager); + return g_variant_new_boolean (has_airplane_mode); + } + + if (g_strcmp0 (property_name, "WwanAirplaneMode") == 0) { + gboolean airplane_mode; + airplane_mode = engine_get_wwan_airplane_mode (manager); + return g_variant_new_boolean (airplane_mode); + } + + if (g_strcmp0 (property_name, "WwanHardwareAirplaneMode") == 0) { + gboolean hw_airplane_mode; + hw_airplane_mode = engine_get_wwan_hardware_airplane_mode (manager); + return g_variant_new_boolean (hw_airplane_mode); + } + + if (g_strcmp0 (property_name, "WwanHasAirplaneMode") == 0) { + gboolean has_airplane_mode; + has_airplane_mode = engine_get_has_wwan_airplane_mode (manager); + return g_variant_new_boolean (has_airplane_mode); + } + + return NULL; +} + +static const GDBusInterfaceVTable interface_vtable = +{ + NULL, + handle_get_property, + handle_set_property +}; + +static void +on_bus_gotten (GObject *source_object, + GAsyncResult *res, + GsdRfkillManager *manager) +{ + GDBusConnection *connection; + GError *error = NULL; + + connection = g_bus_get_finish (res, &error); + if (connection == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Could not get session bus: %s", error->message); + g_error_free (error); + return; + } + manager->connection = connection; + + g_dbus_connection_register_object (connection, + GSD_RFKILL_DBUS_PATH, + manager->introspection_data->interfaces[0], + &interface_vtable, + manager, + NULL, + NULL); + + manager->name_id = g_bus_own_name_on_connection (connection, + GSD_RFKILL_DBUS_NAME, + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, + NULL, + NULL, + NULL); + + manager->session = gnome_settings_bus_get_session_proxy (); + manager->rfkill_input_inhibit_binding = g_object_bind_property (manager->session, "session-is-active", + manager->rfkill, "rfkill-input-inhibited", + G_BINDING_SYNC_CREATE); +} + +static void +sync_wwan_enabled (GsdRfkillManager *manager) +{ + GVariant *property; + + property = g_dbus_proxy_get_cached_property (manager->nm_client, + "WwanEnabled"); + + if (property == NULL) { + /* GDBus telling us NM went down */ + return; + } + + manager->wwan_enabled = g_variant_get_boolean (property); + engine_properties_changed (manager); + + g_variant_unref (property); +} + +static void +nm_signal (GDBusProxy *proxy, + char *sender_name, + char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + GsdRfkillManager *manager = user_data; + GVariant *changed; + GVariant *property; + + if (g_strcmp0 (signal_name, "PropertiesChanged") == 0) { + changed = g_variant_get_child_value (parameters, 0); + property = g_variant_lookup_value (changed, "WwanEnabled", G_VARIANT_TYPE ("b")); + g_dbus_proxy_set_cached_property (proxy, "WwanEnabled", property); + + if (property != NULL) { + sync_wwan_enabled (manager); + g_variant_unref (property); + } + + g_variant_unref (changed); + } +} + +static void +on_nm_proxy_gotten (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GsdRfkillManager *manager = user_data; + GDBusProxy *proxy; + GError *error; + + error = NULL; + proxy = g_dbus_proxy_new_for_bus_finish (result, &error); + + if (proxy == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) + g_warning ("Failed to acquire NetworkManager proxy: %s", error->message); + + g_error_free (error); + goto out; + } + + manager->nm_client = proxy; + + g_signal_connect (manager->nm_client, "g-signal", + G_CALLBACK (nm_signal), manager); + sync_wwan_enabled (manager); + + out: + g_object_unref (manager); +} + +static void +sync_wwan_interesting (GDBusObjectManager *object_manager, + GDBusObject *object, + GDBusInterface *interface, + gpointer user_data) +{ + GsdRfkillManager *manager = user_data; + GList *objects; + + objects = g_dbus_object_manager_get_objects (object_manager); + manager->wwan_interesting = (objects != NULL); + engine_properties_changed (manager); + + g_list_free_full (objects, g_object_unref); +} + +static void +on_mm_proxy_gotten (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + GsdRfkillManager *manager = user_data; + GDBusObjectManager *proxy; + GError *error; + + error = NULL; + proxy = g_dbus_object_manager_client_new_for_bus_finish (result, &error); + + if (proxy == NULL) { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) && + !g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) + g_warning ("Failed to acquire ModemManager proxy: %s", error->message); + + g_error_free (error); + goto out; + } + + manager->mm_client = proxy; + + g_signal_connect (manager->mm_client, "interface-added", + G_CALLBACK (sync_wwan_interesting), manager); + g_signal_connect (manager->mm_client, "interface-removed", + G_CALLBACK (sync_wwan_interesting), manager); + sync_wwan_interesting (manager->mm_client, NULL, NULL, manager); + + out: + g_object_unref (manager); +} + +gboolean +gsd_rfkill_manager_start (GsdRfkillManager *manager, + GError **error) +{ + g_autoptr(GError) local_error = NULL; + + gnome_settings_profile_start (NULL); + + manager->introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL); + g_assert (manager->introspection_data != NULL); + + manager->killswitches = g_hash_table_new (g_direct_hash, g_direct_equal); + manager->bt_killswitches = g_hash_table_new (g_direct_hash, g_direct_equal); + manager->wwan_killswitches = g_hash_table_new (g_direct_hash, g_direct_equal); + manager->rfkill = cc_rfkill_glib_new (); + g_signal_connect (G_OBJECT (manager->rfkill), "changed", + G_CALLBACK (rfkill_changed), manager); + + if (!cc_rfkill_glib_open (manager->rfkill, &local_error)) { + g_warning ("Error setting up rfkill: %s", local_error->message); + g_clear_error (&local_error); + } + + manager->cancellable = g_cancellable_new (); + + manager->chassis_type = gnome_settings_get_chassis_type (); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, /* g-interface-info */ + "org.freedesktop.NetworkManager", + "/org/freedesktop/NetworkManager", + "org.freedesktop.NetworkManager", + manager->cancellable, + on_nm_proxy_gotten, g_object_ref (manager)); + + g_dbus_object_manager_client_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_OBJECT_MANAGER_CLIENT_FLAGS_DO_NOT_AUTO_START, + "org.freedesktop.ModemManager1", + "/org/freedesktop/ModemManager1", + NULL, NULL, NULL, /* get_proxy_type and closure */ + manager->cancellable, + on_mm_proxy_gotten, g_object_ref (manager)); + + /* Start process of owning a D-Bus name */ + g_bus_get (G_BUS_TYPE_SESSION, + manager->cancellable, + (GAsyncReadyCallback) on_bus_gotten, + manager); + + gnome_settings_profile_end (NULL); + + return TRUE; +} + +void +gsd_rfkill_manager_stop (GsdRfkillManager *manager) +{ + g_debug ("Stopping rfkill manager"); + + if (manager->name_id != 0) { + g_bus_unown_name (manager->name_id); + manager->name_id = 0; + } + + g_clear_pointer (&manager->introspection_data, g_dbus_node_info_unref); + g_clear_object (&manager->connection); + g_clear_object (&manager->rfkill_input_inhibit_binding); + g_clear_object (&manager->session); + g_clear_object (&manager->rfkill); + g_clear_pointer (&manager->killswitches, g_hash_table_destroy); + g_clear_pointer (&manager->bt_killswitches, g_hash_table_destroy); + g_clear_pointer (&manager->wwan_killswitches, g_hash_table_destroy); + + if (manager->cancellable) { + g_cancellable_cancel (manager->cancellable); + g_clear_object (&manager->cancellable); + } + + g_clear_object (&manager->nm_client); + g_clear_object (&manager->mm_client); + manager->wwan_enabled = FALSE; + manager->wwan_interesting = FALSE; + + g_clear_pointer (&manager->chassis_type, g_free); +} + +static void +gsd_rfkill_manager_finalize (GObject *object) +{ + GsdRfkillManager *manager; + + g_return_if_fail (object != NULL); + g_return_if_fail (GSD_IS_RFKILL_MANAGER (object)); + + manager = GSD_RFKILL_MANAGER (object); + + g_return_if_fail (manager != NULL); + + gsd_rfkill_manager_stop (manager); + + G_OBJECT_CLASS (gsd_rfkill_manager_parent_class)->finalize (object); +} + +GsdRfkillManager * +gsd_rfkill_manager_new (void) +{ + if (manager_object != NULL) { + g_object_ref (manager_object); + } else { + manager_object = g_object_new (GSD_TYPE_RFKILL_MANAGER, NULL); + g_object_add_weak_pointer (manager_object, + (gpointer *) &manager_object); + } + + return GSD_RFKILL_MANAGER (manager_object); +} diff --git a/plugins/rfkill/gsd-rfkill-manager.h b/plugins/rfkill/gsd-rfkill-manager.h new file mode 100644 index 0000000..025a7d8 --- /dev/null +++ b/plugins/rfkill/gsd-rfkill-manager.h @@ -0,0 +1,39 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * Copyright (C) 2010 Red Hat, Inc. + * + * 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/>. + * + */ + +#ifndef __GSD_RFKILL_MANAGER_H +#define __GSD_RFKILL_MANAGER_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define GSD_TYPE_RFKILL_MANAGER (gsd_rfkill_manager_get_type ()) + +G_DECLARE_FINAL_TYPE (GsdRfkillManager, gsd_rfkill_manager, GSD, RFKILL_MANAGER, GObject) + +GsdRfkillManager * gsd_rfkill_manager_new (void); +gboolean gsd_rfkill_manager_start (GsdRfkillManager *manager, + GError **error); +void gsd_rfkill_manager_stop (GsdRfkillManager *manager); + +G_END_DECLS + +#endif /* __GSD_RFKILL_MANAGER_H */ diff --git a/plugins/rfkill/main.c b/plugins/rfkill/main.c new file mode 100644 index 0000000..4f19f5d --- /dev/null +++ b/plugins/rfkill/main.c @@ -0,0 +1,7 @@ +#define NEW gsd_rfkill_manager_new +#define START gsd_rfkill_manager_start +#define STOP gsd_rfkill_manager_stop +#define MANAGER GsdRfkillManager +#include "gsd-rfkill-manager.h" + +#include "daemon-skeleton.h" diff --git a/plugins/rfkill/meson.build b/plugins/rfkill/meson.build new file mode 100644 index 0000000..4d70352 --- /dev/null +++ b/plugins/rfkill/meson.build @@ -0,0 +1,28 @@ +install_data( + '61-gnome-settings-daemon-rfkill.rules', + install_dir: join_paths(udev_dir, 'rules.d') +) + +sources = files( + 'gsd-rfkill-manager.c', + 'rfkill-glib.c', + 'main.c' +) + +deps = plugins_deps +deps += [ + gio_unix_dep, + gudev_dep, + m_dep +] + +executable( + 'gsd-' + plugin_name, + sources, + include_directories: [top_inc, common_inc], + dependencies: deps, + c_args: cflags, + install: true, + install_rpath: gsd_pkglibdir, + install_dir: gsd_libexecdir +) diff --git a/plugins/rfkill/rfkill-glib.c b/plugins/rfkill/rfkill-glib.c new file mode 100644 index 0000000..22309dc --- /dev/null +++ b/plugins/rfkill/rfkill-glib.c @@ -0,0 +1,680 @@ +/* + * + * gnome-bluetooth - Bluetooth integration for GNOME + * + * Copyright (C) 2012 Bastien Nocera <hadess@hadess.net> + * Copyright © 2017 Endless Mobile, Inc. + * + * 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 <errno.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include <sys/ioctl.h> + +#include <glib.h> +#include <gio/gio.h> +#include <gio/gunixoutputstream.h> + +#include "rfkill-glib.h" +#include <gudev/gudev.h> + +enum { + CHANGED, + LAST_SIGNAL +}; + +enum { + PROP_RFKILL_INPUT_INHIBITED = 1 +}; + +static int signals[LAST_SIGNAL] = { 0 }; + +struct _CcRfkillGlib { + GObject parent; + + GUdevClient *udev; + gchar *device_file; + + GOutputStream *stream; + GIOChannel *channel; + guint watch_id; + + /* rfkill-input inhibitor */ + gboolean noinput; + int noinput_fd; + + /* Pending Bluetooth enablement. + * If (@change_all_timeout_id != 0), then (task != NULL). The converse + * does not necessarily hold. */ + guint change_all_timeout_id; + GTask *task; +}; + +G_DEFINE_TYPE (CcRfkillGlib, cc_rfkill_glib, G_TYPE_OBJECT) + +#define CHANGE_ALL_TIMEOUT 500 + +static const char *type_to_string (unsigned int type); + +static void +cancel_current_task (CcRfkillGlib *rfkill) +{ + if (rfkill->task != NULL) { + g_cancellable_cancel (g_task_get_cancellable (rfkill->task)); + g_clear_object (&rfkill->task); + } + + if (rfkill->change_all_timeout_id != 0) { + g_source_remove (rfkill->change_all_timeout_id); + rfkill->change_all_timeout_id = 0; + } +} + +/* Note that this can return %FALSE without setting @error. */ +gboolean +cc_rfkill_glib_send_change_all_event_finish (CcRfkillGlib *rfkill, + GAsyncResult *res, + GError **error) +{ + g_return_val_if_fail (CC_RFKILL_IS_GLIB (rfkill), FALSE); + g_return_val_if_fail (g_task_is_valid (res, rfkill), FALSE); + g_return_val_if_fail (g_async_result_is_tagged (res, cc_rfkill_glib_send_change_all_event), FALSE); + + return g_task_propagate_boolean (G_TASK (res), error); +} + +static void +write_change_all_again_done_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (user_data); + CcRfkillGlib *rfkill = g_task_get_source_object (task); + g_autoptr(GError) error = NULL; + gssize ret; + + g_debug ("Finished writing second RFKILL_OP_CHANGE_ALL event"); + + ret = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), res, &error); + if (ret < 0) + g_task_return_error (task, g_steal_pointer (&error)); + else + g_task_return_boolean (task, ret >= 0); + + /* If this @task has been cancelled, it may have been superceded. */ + if (rfkill->task == task) + g_clear_object (&rfkill->task); +} + +static gboolean +write_change_all_timeout_cb (CcRfkillGlib *rfkill) +{ + struct rfkill_event *event; + + g_assert (rfkill->task != NULL); + + g_debug ("Sending second RFKILL_OP_CHANGE_ALL timed out"); + + event = g_task_get_task_data (rfkill->task); + g_task_return_new_error (rfkill->task, + G_IO_ERROR, G_IO_ERROR_TIMED_OUT, + "Enabling rfkill for %s timed out", + type_to_string (event->type)); + + g_clear_object (&rfkill->task); + rfkill->change_all_timeout_id = 0; + + return G_SOURCE_REMOVE; +} + +static void +write_change_all_done_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (user_data); + CcRfkillGlib *rfkill = g_task_get_source_object (task); + g_autoptr(GError) error = NULL; + gssize ret; + struct rfkill_event *event; + + g_debug ("Sending original RFKILL_OP_CHANGE_ALL event done"); + + event = g_task_get_task_data (task); + ret = g_output_stream_write_finish (G_OUTPUT_STREAM (source_object), res, &error); + if (ret < 0) { + g_task_return_error (task, g_steal_pointer (&error)); + goto bail; + } else if (event->soft == 1 || + event->type != RFKILL_TYPE_BLUETOOTH) { + g_task_return_boolean (task, ret >= 0); + goto bail; + } + + g_assert (rfkill->change_all_timeout_id == 0); + rfkill->change_all_timeout_id = g_timeout_add (CHANGE_ALL_TIMEOUT, + (GSourceFunc) write_change_all_timeout_cb, + rfkill); + + return; + +bail: + /* If this @task has been cancelled, it may have been superceded. */ + if (rfkill->task == task) + g_clear_object (&rfkill->task); +} + +void +cc_rfkill_glib_send_change_all_event (CcRfkillGlib *rfkill, + guint rfkill_type, + gboolean enable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + struct rfkill_event *event; + g_autoptr(GCancellable) task_cancellable = NULL; + + g_return_if_fail (CC_RFKILL_IS_GLIB (rfkill)); + g_return_if_fail (rfkill->stream); + + task_cancellable = g_cancellable_new (); + g_signal_connect_object (cancellable, "cancelled", + (GCallback) g_cancellable_cancel, + task_cancellable, + G_CONNECT_SWAPPED); + /* Now check if it is cancelled already */ + if (g_cancellable_is_cancelled (cancellable)) + g_cancellable_cancel (task_cancellable); + + task = g_task_new (rfkill, task_cancellable, callback, user_data); + g_task_set_source_tag (task, cc_rfkill_glib_send_change_all_event); + + /* Clear any previous task. */ + cancel_current_task (rfkill); + g_assert (rfkill->task == NULL); + + /* Start writing out a new event. */ + event = g_new0 (struct rfkill_event, 1); + event->op = RFKILL_OP_CHANGE_ALL; + event->type = rfkill_type; + event->soft = enable ? 1 : 0; + + g_task_set_task_data (task, event, g_free); + rfkill->task = g_object_ref (task); + rfkill->change_all_timeout_id = 0; + + g_output_stream_write_async (rfkill->stream, + event, sizeof(struct rfkill_event), + G_PRIORITY_DEFAULT, + task_cancellable, write_change_all_done_cb, + g_object_ref (task)); +} + +static const char * +type_to_string (unsigned int type) +{ + switch (type) { + case RFKILL_TYPE_ALL: + return "ALL"; + case RFKILL_TYPE_WLAN: + return "WLAN"; + case RFKILL_TYPE_BLUETOOTH: + return "BLUETOOTH"; + case RFKILL_TYPE_UWB: + return "UWB"; + case RFKILL_TYPE_WIMAX: + return "WIMAX"; + case RFKILL_TYPE_WWAN: + return "WWAN"; + default: + return "UNKNOWN"; + } +} + +static const char * +op_to_string (unsigned int op) +{ + switch (op) { + case RFKILL_OP_ADD: + return "ADD"; + case RFKILL_OP_DEL: + return "DEL"; + case RFKILL_OP_CHANGE: + return "CHANGE"; + case RFKILL_OP_CHANGE_ALL: + return "CHANGE_ALL"; + default: + g_assert_not_reached (); + } +} + +static void +print_event (struct rfkill_event *event) +{ + g_debug ("RFKILL event: idx %u type %u (%s) op %u (%s) soft %u hard %u", + event->idx, + event->type, type_to_string (event->type), + event->op, op_to_string (event->op), + event->soft, event->hard); +} + +static gboolean +got_change_event (GList *events) +{ + GList *l; + + g_assert (events != NULL); + + for (l = events ; l != NULL; l = l->next) { + struct rfkill_event *event = l->data; + + if (event->op == RFKILL_OP_CHANGE) + return TRUE; + } + + return FALSE; +} + +static void +emit_changed_signal_and_free (CcRfkillGlib *rfkill, + GList *events) +{ + if (events == NULL) + return; + + g_signal_emit (G_OBJECT (rfkill), + signals[CHANGED], + 0, events); + + if (rfkill->change_all_timeout_id > 0 && + got_change_event (events)) { + struct rfkill_event *event; + + g_debug ("Received a change event after a RFKILL_OP_CHANGE_ALL event, re-sending RFKILL_OP_CHANGE_ALL"); + + event = g_task_get_task_data (rfkill->task); + g_output_stream_write_async (rfkill->stream, + event, sizeof(struct rfkill_event), + G_PRIORITY_DEFAULT, + g_task_get_cancellable (rfkill->task), + write_change_all_again_done_cb, + g_object_ref (rfkill->task)); + + g_source_remove (rfkill->change_all_timeout_id); + rfkill->change_all_timeout_id = 0; + } + + g_list_free_full (events, g_free); +} + +static gboolean +event_cb (GIOChannel *source, + GIOCondition condition, + CcRfkillGlib *rfkill) +{ + GList *events; + + events = NULL; + + if (condition & G_IO_IN) { + GIOStatus status; + struct rfkill_event event; + gsize read; + + status = g_io_channel_read_chars (source, + (char *) &event, + sizeof(event), + &read, + NULL); + + while (status == G_IO_STATUS_NORMAL && read == sizeof(event)) { + struct rfkill_event *event_ptr; + + print_event (&event); + + event_ptr = g_memdup (&event, sizeof(event)); + events = g_list_prepend (events, event_ptr); + + status = g_io_channel_read_chars (source, + (char *) &event, + sizeof(event), + &read, + NULL); + } + events = g_list_reverse (events); + } else { + g_debug ("Something unexpected happened on rfkill fd"); + return FALSE; + } + + emit_changed_signal_and_free (rfkill, events); + + return TRUE; +} + +static void +cc_rfkill_glib_init (CcRfkillGlib *rfkill) +{ + rfkill->device_file = NULL; + rfkill->noinput_fd = -1; +} + +static gboolean +_cc_rfkill_glib_open (CcRfkillGlib *rfkill, + GError **error) +{ + int fd; + int ret; + GList *events; + + g_return_val_if_fail (CC_RFKILL_IS_GLIB (rfkill), FALSE); + g_return_val_if_fail (rfkill->stream == NULL, FALSE); + g_assert (rfkill->device_file); + + fd = open (rfkill->device_file, O_RDWR); + + if (fd < 0) { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Could not open RFKILL control device, please verify your installation"); + return FALSE; + } + + ret = fcntl(fd, F_SETFL, O_NONBLOCK); + if (ret < 0) { + g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), + "Can't set RFKILL control device to non-blocking"); + close(fd); + return FALSE; + } + + events = NULL; + + while (1) { + struct rfkill_event event; + struct rfkill_event *event_ptr; + ssize_t len; + + len = read(fd, &event, sizeof(event)); + if (len < 0) { + if (errno == EAGAIN) + break; + g_debug ("Reading of RFKILL events failed"); + break; + } + + if (len != RFKILL_EVENT_SIZE_V1) { + g_warning ("Wrong size of RFKILL event\n"); + continue; + } + + if (event.op != RFKILL_OP_ADD) + continue; + + g_debug ("Read killswitch of type '%s' (idx=%d): soft %d hard %d", + type_to_string (event.type), + event.idx, event.soft, event.hard); + + event_ptr = g_memdup (&event, sizeof(event)); + events = g_list_prepend (events, event_ptr); + } + + /* Setup monitoring */ + rfkill->channel = g_io_channel_unix_new (fd); + g_io_channel_set_encoding (rfkill->channel, NULL, NULL); + g_io_channel_set_buffered (rfkill->channel, FALSE); + rfkill->watch_id = g_io_add_watch (rfkill->channel, + G_IO_IN | G_IO_HUP | G_IO_ERR, + (GIOFunc) event_cb, + rfkill); + + if (events) { + events = g_list_reverse (events); + emit_changed_signal_and_free (rfkill, events); + } else { + g_debug ("No rfkill device available on startup"); + } + + /* Setup write stream */ + rfkill->stream = g_unix_output_stream_new (fd, TRUE); + + return TRUE; +} + +static void +uevent_cb (GUdevClient *client, + gchar *action, + GUdevDevice *device, + gpointer user_data) +{ + CcRfkillGlib *rfkill = CC_RFKILL_GLIB (user_data); + + if (g_strcmp0 (action, "add") != 0) + return; + + if (g_strcmp0 (g_udev_device_get_name (device), "rfkill") == 0) { + g_autoptr(GError) error = NULL; + + g_debug ("Rfkill device has been created"); + + if (g_udev_device_get_device_file (device)) { + g_clear_pointer (&rfkill->device_file, g_free); + rfkill->device_file = g_strdup (g_udev_device_get_device_file (device)); + } else { + g_warning ("rfkill udev device does not have a device file!"); + } + + if (!_cc_rfkill_glib_open (rfkill, &error)) + g_warning ("Could not open rfkill device: %s", error->message); + else + g_debug ("Opened rfkill device after uevent"); + + g_clear_object (&rfkill->udev); + + /* Sync rfkill input inhibition state*/ + cc_rfkill_glib_set_rfkill_input_inhibited (rfkill, rfkill->noinput); + } +} + +gboolean +cc_rfkill_glib_open (CcRfkillGlib *rfkill, + GError **error) +{ + const char * const subsystems[] = { "misc", NULL }; + GUdevDevice *device; + + rfkill->udev = g_udev_client_new (subsystems); + g_debug ("Setting up uevent listener"); + g_signal_connect (rfkill->udev, "uevent", G_CALLBACK (uevent_cb), rfkill); + + /* Simulate uevent if device already exists. */ + device = g_udev_client_query_by_subsystem_and_name (rfkill->udev, "misc", "rfkill"); + if (device) + uevent_cb (rfkill->udev, "add", device, rfkill); + + return TRUE; +} + +#define RFKILL_INPUT_INHIBITED(rfkill) (rfkill->noinput_fd >= 0) + +gboolean +cc_rfkill_glib_get_rfkill_input_inhibited (CcRfkillGlib *rfkill) +{ + g_return_val_if_fail (CC_RFKILL_IS_GLIB (rfkill), FALSE); + + return rfkill->noinput; +} + +void +cc_rfkill_glib_set_rfkill_input_inhibited (CcRfkillGlib *rfkill, + gboolean inhibit) +{ + g_return_if_fail (CC_RFKILL_IS_GLIB (rfkill)); + + /* Shortcut in case we don't have an rfkill device */ + if (!rfkill->stream) { + if (rfkill->noinput == inhibit) + return; + + rfkill->noinput = inhibit; + g_object_notify (G_OBJECT (rfkill), "rfkill-input-inhibited"); + + return; + } + + if (!inhibit && RFKILL_INPUT_INHIBITED(rfkill)) { + close (rfkill->noinput_fd); + g_debug ("Closed rfkill noinput FD."); + + rfkill->noinput_fd = -1; + } + + if (inhibit && !RFKILL_INPUT_INHIBITED(rfkill)) { + int fd, res; + /* Open write only as we don't want to do any IO to it ever. */ + fd = open (rfkill->device_file, O_WRONLY); + if (fd < 0) { + if (errno == EACCES) + g_warning ("Could not open RFKILL control device, please verify your installation"); + else + g_debug ("Could not open RFKILL control device: %s", g_strerror (errno)); + return; + } + + res = ioctl (fd, RFKILL_IOCTL_NOINPUT, (long) 0); + if (res != 0) { + g_warning ("Could not disable kernel handling of RFKILL related keys: %s", g_strerror (errno)); + close (fd); + return; + } + + g_debug ("Opened rfkill-input inhibitor."); + + rfkill->noinput_fd = fd; + } + + if (rfkill->noinput != RFKILL_INPUT_INHIBITED(rfkill)) { + rfkill->noinput = RFKILL_INPUT_INHIBITED(rfkill); + g_object_notify (G_OBJECT (rfkill), "rfkill-input-inhibited"); + } +} + +static void +cc_rfkill_glib_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcRfkillGlib *rfkill = CC_RFKILL_GLIB (object); + + switch (prop_id) { + case PROP_RFKILL_INPUT_INHIBITED: + cc_rfkill_glib_set_rfkill_input_inhibited (rfkill, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_rfkill_glib_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcRfkillGlib *rfkill = CC_RFKILL_GLIB (object); + + switch (prop_id) { + case PROP_RFKILL_INPUT_INHIBITED: + g_value_set_boolean (value, rfkill->noinput); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_rfkill_glib_finalize (GObject *object) +{ + CcRfkillGlib *rfkill = CC_RFKILL_GLIB (object); + + cancel_current_task (rfkill); + + /* cleanup monitoring */ + if (rfkill->watch_id > 0) { + g_source_remove (rfkill->watch_id); + rfkill->watch_id = 0; + g_io_channel_shutdown (rfkill->channel, FALSE, NULL); + g_io_channel_unref (rfkill->channel); + } + g_clear_object (&rfkill->stream); + + if (RFKILL_INPUT_INHIBITED(rfkill)) { + close (rfkill->noinput_fd); + rfkill->noinput_fd = -1; + } + + g_clear_pointer (&rfkill->device_file, g_free); + g_clear_object (&rfkill->udev); + + G_OBJECT_CLASS(cc_rfkill_glib_parent_class)->finalize(object); +} + +static void +cc_rfkill_glib_class_init(CcRfkillGlibClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + object_class->set_property = cc_rfkill_glib_set_property; + object_class->get_property = cc_rfkill_glib_get_property; + object_class->finalize = cc_rfkill_glib_finalize; + + g_object_class_install_property (object_class, + PROP_RFKILL_INPUT_INHIBITED, + g_param_spec_boolean ("rfkill-input-inhibited", + "Rfkill input inhibited", + "Whether to prevent the kernel from handling RFKILL related key events.", + FALSE, + G_PARAM_READWRITE)); + + signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 1, G_TYPE_POINTER); + +} + +CcRfkillGlib * +cc_rfkill_glib_new (void) +{ + return CC_RFKILL_GLIB (g_object_new (CC_RFKILL_TYPE_GLIB, NULL)); +} diff --git a/plugins/rfkill/rfkill-glib.h b/plugins/rfkill/rfkill-glib.h new file mode 100644 index 0000000..0655eb4 --- /dev/null +++ b/plugins/rfkill/rfkill-glib.h @@ -0,0 +1,57 @@ +/* + * + * gnome-bluetooth - Bluetooth integration for GNOME + * + * Copyright (C) 2012 Bastien Nocera <hadess@hadess.net> + * Copyright © 2017 Endless Mobile, Inc. + * + * 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 + * + */ + +#ifndef __CC_RFKILL_GLIB_H +#define __CC_RFKILL_GLIB_H + +#include <glib-object.h> +#include <gio/gio.h> +#include "rfkill.h" + +G_BEGIN_DECLS + +#define CC_RFKILL_TYPE_GLIB cc_rfkill_glib_get_type () +G_DECLARE_FINAL_TYPE (CcRfkillGlib, cc_rfkill_glib, CC_RFKILL, GLIB, GObject) + +CcRfkillGlib *cc_rfkill_glib_new (void); +gboolean cc_rfkill_glib_open (CcRfkillGlib *rfkill, + GError **error); + +gboolean cc_rfkill_glib_get_rfkill_input_inhibited (CcRfkillGlib *rfkill); +void cc_rfkill_glib_set_rfkill_input_inhibited (CcRfkillGlib *rfkill, + gboolean noinput); + +void cc_rfkill_glib_send_change_all_event (CcRfkillGlib *rfkill, + guint rfkill_type, + gboolean enable, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean cc_rfkill_glib_send_change_all_event_finish (CcRfkillGlib *rfkill, + GAsyncResult *res, + GError **error); + +G_END_DECLS + +#endif /* __CC_RFKILL_GLIB_H */ diff --git a/plugins/rfkill/rfkill.h b/plugins/rfkill/rfkill.h new file mode 100644 index 0000000..abb2c66 --- /dev/null +++ b/plugins/rfkill/rfkill.h @@ -0,0 +1,107 @@ +#ifndef __RFKILL_H +#define __RFKILL_H + +/* + * Copyright (C) 2006 - 2007 Ivo van Doorn + * Copyright (C) 2007 Dmitry Torokhov + * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <linux/types.h> + +/* define userspace visible states */ +#define RFKILL_STATE_SOFT_BLOCKED 0 +#define RFKILL_STATE_UNBLOCKED 1 +#define RFKILL_STATE_HARD_BLOCKED 2 + +/** + * enum rfkill_type - type of rfkill switch. + * + * @RFKILL_TYPE_ALL: toggles all switches (requests only - not a switch type) + * @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device. + * @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device. + * @RFKILL_TYPE_UWB: switch is on a ultra wideband device. + * @RFKILL_TYPE_WIMAX: switch is on a WiMAX device. + * @RFKILL_TYPE_WWAN: switch is on a wireless WAN device. + * @RFKILL_TYPE_GPS: switch is on a GPS device. + * @RFKILL_TYPE_FM: switch is on a FM radio device. + * @NUM_RFKILL_TYPES: number of defined rfkill types + */ +enum rfkill_type { + RFKILL_TYPE_ALL = 0, + RFKILL_TYPE_WLAN, + RFKILL_TYPE_BLUETOOTH, + RFKILL_TYPE_UWB, + RFKILL_TYPE_WIMAX, + RFKILL_TYPE_WWAN, + RFKILL_TYPE_GPS, + RFKILL_TYPE_FM, + NUM_RFKILL_TYPES, +}; + +/** + * enum rfkill_operation - operation types + * @RFKILL_OP_ADD: a device was added + * @RFKILL_OP_DEL: a device was removed + * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device + * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all) + */ +enum rfkill_operation { + RFKILL_OP_ADD = 0, + RFKILL_OP_DEL, + RFKILL_OP_CHANGE, + RFKILL_OP_CHANGE_ALL, +}; + +/** + * struct rfkill_event - events for userspace on /dev/rfkill + * @idx: index of dev rfkill + * @type: type of the rfkill struct + * @op: operation code + * @hard: hard state (0/1) + * @soft: soft state (0/1) + * + * Structure used for userspace communication on /dev/rfkill, + * used for events from the kernel and control to the kernel. + */ +struct rfkill_event { + __u32 idx; + __u8 type; + __u8 op; + __u8 soft, hard; +} __attribute__((packed)); + +/* + * We are planning to be backward and forward compatible with changes + * to the event struct, by adding new, optional, members at the end. + * When reading an event (whether the kernel from userspace or vice + * versa) we need to accept anything that's at least as large as the + * version 1 event size, but might be able to accept other sizes in + * the future. + * + * One exception is the kernel -- we already have two event sizes in + * that we've made the 'hard' member optional since our only option + * is to ignore it anyway. + */ +#define RFKILL_EVENT_SIZE_V1 8 + +/* ioctl for turning off rfkill-input (if present) */ +#define RFKILL_IOC_MAGIC 'R' +#define RFKILL_IOC_NOINPUT 1 +#define RFKILL_IOCTL_NOINPUT _IO(RFKILL_IOC_MAGIC, RFKILL_IOC_NOINPUT) + +/* and that's all userspace gets */ + +#endif /* RFKILL_H */ |