diff --git a/data/gschema.xml b/data/gschema.xml index ff531a5..f220b7a 100644 --- a/data/gschema.xml +++ b/data/gschema.xml @@ -1,6 +1,11 @@ + + true + Sets the last bluetooth state to re-apply on reboot + + true Sets registered agent obex diff --git a/meson.build b/meson.build index 5142ead..cd36e5a 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ i18n = import('i18n') granite_dep = dependency ('granite') gtk_dep = dependency ('gtk+-3.0') +posix_dep = meson.get_compiler('vala').find_library('posix') subdir('data') subdir('po') diff --git a/src/Services/Manager.vala b/src/Services/Manager.vala index 5725a34..fab1884 100644 --- a/src/Services/Manager.vala +++ b/src/Services/Manager.vala @@ -21,15 +21,23 @@ public class Bluetooth.ObjectManager : Object { public signal void status_discovering (); public bool has_adapter { get; private set; default = false; } + private bool is_powered { get; set; default = false; } private Settings settings; private GLib.DBusObjectManagerClient object_manager; + private RFKillManager rfkill; construct { settings = new Settings ("io.elementary.desktop.bluetooth"); create_manager.begin (); + rfkill = new RFKillManager (); + rfkill.open (); + rfkill.device_added.connect (check_global_state); + rfkill.device_changed.connect (check_global_state); + rfkill.device_deleted.connect (check_global_state); + settings.changed ["sharing"].connect (() => { if (settings.get_boolean ("sharing")) { register_agent (); @@ -38,6 +46,10 @@ public class Bluetooth.ObjectManager : Object { } }); + settings.changed["enabled"].connect (() => { + set_global_state.begin (); + }); + register_agent (); } @@ -137,7 +149,14 @@ public class Bluetooth.ObjectManager : Object { if (discovering != null) { status_discovering (); } + + var powered = changed.lookup_value ("Powered", new VariantType ("b")); + if (powered != null) { + check_global_state (); + } }); + + check_global_state (); } } @@ -147,6 +166,8 @@ public class Bluetooth.ObjectManager : Object { } else if (iface is Bluetooth.Adapter) { has_adapter = !get_adapters ().is_empty; } + + check_global_state (); } public Gee.LinkedList get_adapters () requires (object_manager != null) { @@ -234,4 +255,54 @@ public class Bluetooth.ObjectManager : Object { return null; } + + private void check_global_state () { + var active = false; + var adapters = get_adapters (); + foreach (var adapter in adapters) { + if (adapter.powered) { + active = true; + break; + } + } + + if (active != is_powered) { + is_powered = active; + } + + if (settings.get_boolean ("enabled") != is_powered) { + settings.set_boolean ("enabled", is_powered); + } + } + + private async void set_global_state () { + var state = settings.get_boolean ("enabled"); + if (is_powered == state) { + return; + } + + /* `is_powered` and `connected` properties will be set by the check_global state () callback when adapter or device + * properties change. Do not set now so that global_state_changed signal will be emitted. */ + var adapters = get_adapters (); + foreach (var adapter in adapters) { + adapter.powered = state; + } + + if (state == false) { + var devices = get_devices (); + foreach (var device in devices) { + if (device.connected) { + try { + yield device.disconnect (); + } catch (Error e) { + critical (e.message); + } + } + } + } + + rfkill.set_software_lock (RFKillDeviceType.BLUETOOTH, !state); + + check_global_state (); + } } diff --git a/src/Services/RFKill.vala b/src/Services/RFKill.vala new file mode 100644 index 0000000..935892f --- /dev/null +++ b/src/Services/RFKill.vala @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2012 Canonical Ltd. + * Author: Robert Ancell + * + * 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 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +public enum RFKillDeviceType { + ALL = 0, + WLAN, + BLUETOOTH, + UWB, + WIMAX, + WMAN +} + +public class RFKillDevice { + public signal void changed (); + + public bool software_lock { + get { return _software_lock; } + set { + var event = RFKillEvent (); + event.idx = idx; + event.op = RFKillOperation.CHANGE; + event.soft = value ? 1 : 0; + if (Posix.write (manager.fd, &event, 8) != 8) + return; + } + } + + public bool hardware_lock { get { return _hardware_lock; } } + + public RFKillDeviceType device_type { get { return _device_type; } } + + internal RFKillManager manager; + internal uint32 idx; + internal RFKillDeviceType _device_type; + internal bool _software_lock; + internal bool _hardware_lock; + + internal RFKillDevice (RFKillManager manager, uint32 idx, RFKillDeviceType device_type, bool software_lock, bool hardware_lock) { + this.manager = manager; + this.idx = idx; + _device_type = device_type; + _software_lock = software_lock; + _hardware_lock = hardware_lock; + } +} + +public class RFKillManager : Object { + public signal void device_added (RFKillDevice device); + public signal void device_changed (RFKillDevice device); + public signal void device_deleted (RFKillDevice device); + + public RFKillManager () { + _devices = new List (); + } + + public void open () { + fd = Posix.open ("/dev/rfkill", Posix.O_RDWR); + Posix.fcntl (fd, Posix.F_SETFL, Posix.O_NONBLOCK); + + /* Read initial state */ + while (read_event ()); + + /* Monitor for events */ + var channel = new IOChannel.unix_new (fd); + channel.add_watch (IOCondition.IN | IOCondition.HUP | IOCondition.ERR, () => { return read_event (); }); + } + + public List get_devices () { + var devices = new List (); + foreach (var device in _devices) + devices.append (device); + return devices; + } + + public void set_software_lock (RFKillDeviceType type, bool lock_enabled) { + var event = RFKillEvent (); + event.type = type; + event.op = RFKillOperation.CHANGE_ALL; + event.soft = lock_enabled ? 1 : 0; + if (Posix.write (fd, &event, 8) != 8) + return; + } + + internal int fd = -1; + private List _devices; + + private bool read_event () { + var event = RFKillEvent (); + if (Posix.read (fd, &event, 8) != 8) + return false; + + switch (event.op) { + case RFKillOperation.ADD: + var device = new RFKillDevice (this, event.idx, (RFKillDeviceType) event.type, event.soft != 0, event.hard != 0); + _devices.append (device); + device_added (device); + break; + case RFKillOperation.DELETE: + var device = get_device (event.idx); + if (device != null) { + _devices.remove (device); + device_deleted (device); + } + break; + case RFKillOperation.CHANGE: + var device = get_device (event.idx); + if (device != null) { + device._software_lock = event.soft != 0; + device._hardware_lock = event.hard != 0; + device.changed (); + device_changed (device); + } + break; + } + return true; + } + + private RFKillDevice? get_device (uint32 idx) { + foreach (var device in _devices) { + if (device.idx == idx) + return device; + } + + return null; + } +} + +private struct RFKillEvent { + uint32 idx; + uint8 type; + uint8 op; + uint8 soft; + uint8 hard; +} + +private enum RFKillOperation { + ADD = 0, + DELETE, + CHANGE, + CHANGE_ALL +} diff --git a/src/meson.build b/src/meson.build index 552b9ef..9d6f351 100644 --- a/src/meson.build +++ b/src/meson.build @@ -5,6 +5,7 @@ executable( 'Services' / 'Device.vala', 'Services' / 'Manager.vala', 'Services' / 'ObexAgent.vala', + 'Services' / 'RFKill.vala', 'Services' / 'Session.vala', 'Services' / 'Transfer.vala', 'Dialog' / 'BtReceiver.vala', @@ -14,6 +15,7 @@ executable( dependencies : [ granite_dep, gtk_dep, + posix_dep ], install : true )