diff options
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/bluez.vala | 42 | ||||
-rw-r--r-- | src/indicator-bluetooth.vala | 283 | ||||
-rw-r--r-- | src/rfkill.vala | 168 |
4 files changed, 385 insertions, 112 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 101f8f1..64cb42d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,9 @@ bin_PROGRAMS = indicator-bluetooth indicator_bluetooth_SOURCES = \ config.vapi \ - indicator-bluetooth.vala + bluez.vala \ + indicator-bluetooth.vala \ + rfkill.vala indicator_bluetooth_VALAFLAGS = \ --pkg posix \ diff --git a/src/bluez.vala b/src/bluez.vala new file mode 100644 index 0000000..14984bf --- /dev/null +++ b/src/bluez.vala @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2012 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.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 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. + */ + +[DBus (name = "org.bluez.Manager")] +public interface BluezManager : Object +{ + public abstract string default_adapter () throws IOError; +} + +[DBus (name = "org.bluez.Adapter")] +public interface BluezAdapter : Object +{ + public abstract string[] list_devices () throws IOError; + public abstract HashTable<string, Variant> get_properties () throws IOError; + public abstract void set_property (string name, Variant value) throws IOError; +} + +[DBus (name = "org.bluez.Device")] +public interface BluezDevice : Object +{ + public abstract HashTable<string, Variant> get_properties () throws IOError; +} + +[DBus (name = "org.bluez.Audio")] +public interface BluezAudio : Object +{ + public abstract void connect () throws IOError; +} + +[DBus (name = "org.bluez.Input")] +public interface BluezInput : Object +{ + public abstract void connect () throws IOError; +} diff --git a/src/indicator-bluetooth.vala b/src/indicator-bluetooth.vala index 88cb175..a157ab1 100644 --- a/src/indicator-bluetooth.vala +++ b/src/indicator-bluetooth.vala @@ -1,137 +1,198 @@ -[DBus (name = "org.bluez.Manager")] -interface BluezManager : Object +/* + * Copyright (C) 2012 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.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 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 class BluetoothIndicator : AppIndicator.Indicator { - public abstract string default_adapter () throws IOError; -} + private RFKillManager rfkill; + private Gtk.MenuItem status_item; + private Gtk.MenuItem enable_item; + private bool enable_value = false; + private Gtk.CheckMenuItem visible_item; + private Gtk.SeparatorMenuItem devices_separator; + private Gtk.MenuItem devices_item; + private List<Gtk.MenuItem> device_items; + private Gtk.MenuItem settings_item; + + public BluetoothIndicator () + { + Object (id: "indicator-bluetooth", icon_name: "bluetooth-active", category: "Hardware"); + + /* Monitor killswitch status */ + rfkill = new RFKillManager (); + rfkill.open (); + rfkill.device_added.connect (update_rfkill); + rfkill.device_changed.connect (update_rfkill); + rfkill.device_deleted.connect (update_rfkill); + + /* Get/control bluetooth status from Bluez */ + BluezAdapter adapter = null; + try + { + var manager = Bus.get_proxy_sync<BluezManager> (BusType.SYSTEM, "org.bluez", "/"); + var path = manager.default_adapter (); + adapter = Bus.get_proxy_sync<BluezAdapter> (BusType.SYSTEM, "org.bluez", path); + } + catch (IOError e) + { + stderr.printf ("Failed to connect to Bluez: %s", e.message); + } -[DBus (name = "org.bluez.Adapter")] -interface BluezAdapter : Object -{ - public abstract string[] list_devices () throws IOError; - public abstract HashTable<string, Variant> get_properties () throws IOError; - public abstract void set_property (string name, Variant value) throws IOError; -} + set_status (AppIndicator.IndicatorStatus.ACTIVE); -[DBus (name = "org.bluez.Device")] -interface BluezDevice : Object -{ - public abstract HashTable<string, Variant> get_properties () throws IOError; -} + var menu = new Gtk.Menu (); + set_menu (menu); -[DBus (name = "org.bluez.Audio")] -interface BluezAudio : Object -{ - public abstract void connect () throws IOError; -} + status_item = new Gtk.MenuItem (); + status_item.sensitive = false; + status_item.visible = true; + menu.append (status_item); -[DBus (name = "org.bluez.Input")] -interface BluezInput : Object -{ - public abstract void connect () throws IOError; -} + enable_item = new Gtk.MenuItem (); + enable_item.activate.connect (toggle_enabled); + menu.append (enable_item); -int main (string[] args) -{ - Intl.setlocale (LocaleCategory.ALL, ""); - Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); - Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); - Intl.textdomain (GETTEXT_PACKAGE); + visible_item = new Gtk.CheckMenuItem.with_label (_("Visible")); + visible_item.activate.connect (() => { adapter.set_property ("Discoverable", new Variant.boolean (true)); }); // FIXME: Make rw + menu.append (visible_item); + + devices_separator = new Gtk.SeparatorMenuItem (); + menu.append (devices_separator); - Gtk.init (ref args); + devices_item = new Gtk.MenuItem.with_label (_("Devices")); + devices_item.sensitive = false; + devices_item.visible = true; + menu.append (devices_item); - BluezAdapter adapter; - try - { - var manager = Bus.get_proxy_sync<BluezManager> (BusType.SYSTEM, "org.bluez", "/"); - var path = manager.default_adapter (); - adapter = Bus.get_proxy_sync<BluezAdapter> (BusType.SYSTEM, "org.bluez", path); - } - catch (IOError e) - { - return Posix.EXIT_FAILURE; - } + device_items = new List<Gtk.MenuItem> (); - var indicator = new AppIndicator.Indicator ("indicator-bluetooth", "bluetooth-active", AppIndicator.IndicatorCategory.HARDWARE); - indicator.set_status (AppIndicator.IndicatorStatus.ACTIVE); + try + { + var devices = adapter.list_devices (); + foreach (var path in devices) + { + var device = Bus.get_proxy_sync<BluezDevice> (BusType.SYSTEM, "org.bluez", path); + var properties = device.get_properties (); + var iter = HashTableIter<string, Variant> (properties); + string name; + Variant value; + //stderr.printf ("%s\n", path); + while (iter.next (out name, out value)) + { + //stderr.printf (" %s=%s\n", name, value.print (false)); + if (name == "Name" && value.is_of_type (VariantType.STRING)) + { + var item = new Gtk.MenuItem.with_label (value.get_string ()); + device_items.append (item); + menu.append (item); + + item.submenu = new Gtk.Menu (); + var i = new Gtk.MenuItem.with_label (_("Send files...")); + i.visible = true; + i.activate.connect (() => { Process.spawn_command_line_async ("bluetooth-sendto --device=DEVICE --name=NAME"); }); + item.submenu.append (i); + + //var i = new Gtk.MenuItem.with_label (_("Keyboard Settings...")); + //i.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center keyboard"); }); + //var i = new Gtk.MenuItem.with_label (_("Mouse and Touchpad Settings...")); + //i.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center mouse"); }); + //var i = new Gtk.MenuItem.with_label (_("Sound Settings...")); + //i.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center sound"); }); + } + } + } + } + catch (IOError e) + { + stderr.printf ("%s\n", e.message); + } - var menu = new Gtk.Menu (); - indicator.set_menu (menu); + var sep = new Gtk.SeparatorMenuItem (); + sep.visible = true; + menu.append (sep); - var item = new Gtk.MenuItem.with_label ("Bluetooth: On"); - item.sensitive = false; - item.show (); - menu.append (item); + settings_item = new Gtk.MenuItem.with_label (_("Bluetooth Settings...")); + settings_item.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center bluetooth"); }); + settings_item.visible = true; + menu.append (settings_item); - item = new Gtk.MenuItem.with_label ("Turn off Bluetooth"); - item.show (); - menu.append (item); + update_rfkill (); + } - item = new Gtk.CheckMenuItem.with_label (_("Visible")); - item.activate.connect (() => { adapter.set_property ("Discoverable", new Variant.boolean (true)); }); - item.show (); - menu.append (item); - - var sep = new Gtk.SeparatorMenuItem (); - sep.show (); - menu.append (sep); + private void update_rfkill () + { + var have_lock = false; + var software_locked = false; + var hardware_locked = false; - item = new Gtk.MenuItem.with_label (_("Devices")); - item.sensitive = false; - item.show (); - menu.append (item); + foreach (var device in rfkill.get_devices ()) + { + if (device.device_type != RFKillDeviceType.BLUETOOTH) + continue; + + have_lock = true; + if (device.software_lock) + software_locked = true; + if (device.hardware_lock) + hardware_locked = true; + } + var locked = hardware_locked || software_locked; - try - { - var devices = adapter.list_devices (); - foreach (var path in devices) + if (hardware_locked) { - var device = Bus.get_proxy_sync<BluezDevice> (BusType.SYSTEM, "org.bluez", path); - var properties = device.get_properties (); - var iter = HashTableIter<string, Variant> (properties); - string name; - Variant value; - //stderr.printf ("%s\n", path); - while (iter.next (out name, out value)) - { - //stderr.printf (" %s=%s\n", name, value.print (false)); - if (name == "Name" && value.is_of_type (VariantType.STRING)) - { - item = new Gtk.MenuItem.with_label (value.get_string ()); - item.show (); - menu.append (item); - - item.submenu = new Gtk.Menu (); - var i = new Gtk.MenuItem.with_label (_("Send files...")); - i.show (); - i.activate.connect (() => { Process.spawn_command_line_async ("bluetooth-sendto --device=DEVICE --name=NAME"); }); - item.submenu.append (i); - - //var i = new Gtk.MenuItem.with_label (_("Keyboard Settings...")); - //i.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center keyboard"); }); - //var i = new Gtk.MenuItem.with_label (_("Mouse and Touchpad Settings...")); - //i.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center mouse"); }); - //var i = new Gtk.MenuItem.with_label (_("Sound Settings...")); - //i.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center sound"); }); - } - } + status_item.label = _("Bluetooth: Disabled"); + enable_item.visible = false; } + else if (software_locked) + { + status_item.label = _("Bluetooth: Off"); + enable_item.label = _("Turn on Bluetooth"); + enable_item.visible = true; + enable_value = false; + } + else + { + status_item.label = _("Bluetooth: On"); + enable_item.label = _("Turn off Bluetooth"); + enable_item.visible = true; + enable_value = true; + } + + /* Disable devices when locked */ + visible_item.visible = !locked; + devices_separator.visible = !locked; + devices_item.visible = !locked; + foreach (var item in device_items) + item.visible = !locked; } - catch (IOError e) + + private void toggle_enabled () { - stderr.printf ("%s\n", e.message); - return Posix.EXIT_FAILURE; + rfkill.set_software_lock (RFKillDeviceType.BLUETOOTH, enable_value); } +} - sep = new Gtk.SeparatorMenuItem (); - sep.show (); - menu.append (sep); +public static int main (string[] args) +{ + Intl.setlocale (LocaleCategory.ALL, ""); + Intl.bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR); + Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + Intl.textdomain (GETTEXT_PACKAGE); - item = new Gtk.MenuItem.with_label (_("Bluetooth Settings...")); - item.activate.connect (() => { Process.spawn_command_line_async ("gnome-control-center bluetooth"); }); - item.show (); - menu.append (item); + Gtk.init (ref args); + var indicator = new BluetoothIndicator (); + Gtk.main (); + + indicator = null; - return 0; + return Posix.EXIT_SUCCESS; } diff --git a/src/rfkill.vala b/src/rfkill.vala new file mode 100644 index 0000000..ed7afbd --- /dev/null +++ b/src/rfkill.vala @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2012 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.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 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<RFKillDevice> (); + } + + 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<RFKillDevice> get_devices () + { + var devices = new List<RFKillDevice> (); + 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<RFKillDevice> _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; + } + stderr.printf ("idx=%u type=%d op=%d soft=%d hard=%d\n", event.idx, event.type, event.op, event.soft, event.hard); + 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 +} |