aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Makefile.am4
-rw-r--r--src/bluez.vala42
-rw-r--r--src/indicator-bluetooth.vala283
-rw-r--r--src/rfkill.vala168
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
+}