diff options
Diffstat (limited to 'src/agent.vala')
| -rw-r--r-- | src/agent.vala | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/src/agent.vala b/src/agent.vala new file mode 100644 index 0000000..7f517f0 --- /dev/null +++ b/src/agent.vala @@ -0,0 +1,249 @@ +[DBus (name = "org.bluez.Agent1")] +public class Agent: Object +{ + // These actions and menus are exposed on their relevant paths by service.vala + public GLib.Menu menu; + public GLib.SimpleActionGroup actions; + private GLib.SimpleAction pin_action; + + // These paths are set by service.vala + public string menu_path; + public string actions_path; + + private MainLoop loop; + private Bluetooth bluetooth; + private Notify.Notification? notification; + private string passkey; + + public Agent (Bluetooth bluez) + { + // Menu + menu = new GLib.Menu (); + GLib.MenuItem item = new GLib.MenuItem ("", "notifications.pin"); + item.set_attribute_value ("x-canonical-type", new Variant.string ("com.canonical.snapdecision.textfield")); + item.set_attribute_value ("x-echo-mode-password", new Variant.boolean (false)); + menu.append_item (item); + + // Actions + actions = new GLib.SimpleActionGroup (); + pin_action = new GLib.SimpleAction.stateful ("pin", null, new Variant.string ("")); + pin_action.change_state.connect ((value) => { + this.passkey = value.get_string (); + }); + actions.add_action (pin_action); + + loop = new MainLoop (null, false); + bluetooth = bluez; + Notify.init ("ayatana-indicator-bluetooth"); + } + + /* TODO: Add a better way to differentiate between rejected and cancelled errors, maybe with an enum */ + private bool sendNotification (string device_name, string body, bool need_input, bool have_actions) + { + bool accepted = !have_actions; + + string header = (_("Pair with %s?").printf (device_name)); + notification = new Notify.Notification (header, body, "bluetooth-active"); + notification.closed.connect (() => { + accepted = false; + notification = null; + + if (loop.is_running ()) { + loop.quit (); + } + }); + + bool is_lomiri = AyatanaCommon.utils_is_lomiri (); + + if (is_lomiri) { + if (have_actions) { + notification.set_hint ("x-lomiri-snap-decisions", true); + notification.set_hint ("x-lomiri-private-affirmative-tint", "true"); + } + + if (need_input) { + VariantBuilder actions_builder = new VariantBuilder (new VariantType ("a{sv}")); + actions_builder.add ("{sv}", "notifications", new Variant.string (actions_path)); + + VariantBuilder builder = new VariantBuilder (new VariantType ("a{sv}")); + builder.add ("{sv}", "busName", new Variant.string ("org.ayatana.indicator.bluetooth")); + builder.add ("{sv}", "menuPath", new Variant.string (menu_path)); + builder.add ("{sv}", "actions", actions_builder.end ()); + + notification.set_hint ("x-lomiri-private-menu-model", builder.end ()); + } + } + + if (have_actions) { + notification.add_action("yes_id", (_("Yes")), (notif, action) => { + loop.quit (); + notification = null; + accepted = true; + }); + notification.add_action("no_id", (_("No")), (notif, action) => { + loop.quit (); + notification = null; + accepted = false; + }); + } + + if (!have_actions && !need_input) { + // Display-only notification. Make sure we don't time out. + notification.set_hint ("urgency", 2); + } + + try { + notification.show (); + } + catch (Error e) { + warning ("Panic: Failed showing notification: %s", e.message); + } + + if (have_actions) { + loop.run (); + } + + return accepted; + } + + public void AuthorizeService (GLib.ObjectPath object, string uuid) throws RejectedError, GLib.DBusError, GLib.IOError + { + bool authorized = false; + bool trusted = false; + + string header = (_("Allow %s to connect?").printf (bluetooth.get_device_name (object))); + string body = (_("Allow the Bluetooth device to access a Bluetooth service?")); + + notification = new Notify.Notification (header, body, "bluetooth-active"); + notification.closed.connect (() => { + authorized = false; + notification = null; + + if (loop.is_running ()) { + loop.quit (); + } + }); + + if (AyatanaCommon.utils_is_lomiri ()) { + notification.set_hint ("x-lomiri-snap-decisions", true); + } + + notification.add_action ("trust_and_authorize", (_("Trust and authorize")), (notif, action) => { + loop.quit (); + notification = null; + + trusted = true; + authorized = true; + }); + + notification.add_action ("authorize", (_("Authorize")), (notif, action) => { + loop.quit (); + notification = null; + + authorized = true; + }); + + notification.add_action ("reject", (_("Do not authorize")), (notif, action) => { + loop.quit (); + notification = null; + }); + + try { + notification.show (); + } + catch (Error e) { + warning ("Panic: Failed showing notification: %s", e.message); + } + + loop.run (); + + // Once the loop quits, we can see if we want to set the 'Trusted' property in BlueZ + if (trusted) { + Device device = bluetooth.get_device (object); + bluetooth.set_device_trusted (device.id, trusted); + } + + if (!authorized) { + throw new RejectedError.ERROR ("Rejected by user"); + } + } + + public void RequestConfirmation (GLib.ObjectPath object, uint32 passkey) throws RejectedError, GLib.DBusError, GLib.IOError + { + string body = (_("Are you sure you want to pair with PIN %06u?").printf (passkey)); + bool confirmed = sendNotification (bluetooth.get_device_name (object), body, false, true); + + if (!confirmed) { + throw new RejectedError.ERROR ("Rejected by user"); + } + } + + public void RequestAuthorization (GLib.ObjectPath object) throws RejectedError, GLib.DBusError, GLib.IOError + { + bool authorized = sendNotification (bluetooth.get_device_name (object), (_("Are you sure you want to pair with this device?")), false, true); + + if (!authorized) { + throw new RejectedError.ERROR ("Rejected by user"); + } + } + + public string RequestPinCode (GLib.ObjectPath object) throws RejectedError, GLib.DBusError, GLib.IOError + { + bool accepted = sendNotification (bluetooth.get_device_name (object), (_("Enter PIN for this device")), true, true); + + if (!accepted) { + throw new RejectedError.ERROR ("Rejected by user"); + } + + return passkey; + } + + public void DisplayPinCode (GLib.ObjectPath object, string pincode) throws GLib.DBusError, GLib.IOError + { + string body = (_("Enter the PIN code %s on the other device").printf (pincode)); + sendNotification (bluetooth.get_device_name (object), body, false, false); + } + + public uint32 RequestPasskey (GLib.ObjectPath object) throws RejectedError, GLib.DBusError, GLib.IOError + { + bool accepted = sendNotification (bluetooth.get_device_name (object), (_("Enter PIN for this device")), true, true); + + if (!accepted) { + throw new RejectedError.ERROR ("Rejected by user"); + } + + return passkey.to_int (); + } + + public void DisplayPasskey (GLib.ObjectPath object, uint32 passkey, uint16 entered) throws GLib.DBusError, GLib.IOError + { + string body = (_("Enter the PIN %06u on the other device").printf (passkey)); + sendNotification (bluetooth.get_device_name (object), body, false, false); + } + + public void Cancel () throws GLib.DBusError, GLib.IOError + { + if (loop.is_running ()) { + loop.quit (); + } + + if (notification != null) { + notification.close (); + notification = null; + } + } + + public void Release () throws GLib.DBusError, GLib.IOError + { + } +} + +[DBus (name = "org.bluez.Error.Cancelled")] +public errordomain CancelledError { + ERROR +} + +[DBus (name = "org.bluez.Error.Rejected")] +public errordomain RejectedError { + ERROR +} |
