aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMuhammad <thevancedgamer@mentallysanemainliners.org>2025-07-22 21:05:43 +0500
committerMuhammad <thevancedgamer@mentallysanemainliners.org>2025-07-22 21:08:52 +0500
commit6c7c62d7c6d153293c8aea8dd1c71faabdef3b3e (patch)
treef8711ae61fc52604e4cad744d35d5121b1f89494
parent568f6aa32e290b4e37b61dfd9985ae81a08e2892 (diff)
downloadayatana-indicator-bluetooth-6c7c62d7c6d153293c8aea8dd1c71faabdef3b3e.tar.gz
ayatana-indicator-bluetooth-6c7c62d7c6d153293c8aea8dd1c71faabdef3b3e.tar.bz2
ayatana-indicator-bluetooth-6c7c62d7c6d153293c8aea8dd1c71faabdef3b3e.zip
agent: Add support for PIN and passkeys on Lomiri
* Passkey/PIN display is slightly wonky right now, BlueZ doesn't call the Cancel() method once pairing is done, so you have to swipe away the notification manually. But apart from that, everything else works. Signed-off-by: Muhammad <thevancedgamer@mentallysanemainliners.org>
-rw-r--r--src/agent.vala139
-rw-r--r--src/bluez.vala2
-rw-r--r--src/service.vala30
3 files changed, 134 insertions, 37 deletions
diff --git a/src/agent.vala b/src/agent.vala
index beb8f8b..9e568a4 100644
--- a/src/agent.vala
+++ b/src/agent.vala
@@ -1,48 +1,103 @@
[DBus (name = "org.bluez.Agent1")]
public class Agent: Object
{
+ public GLib.Menu menu;
+ public GLib.SimpleActionGroup actions;
+ private GLib.SimpleAction pin_action;
+ 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");
}
- private bool sendNotification (string device_name, string body)
+ /* 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 = false;
+ bool accepted = !have_actions;
+
+ notification = new Notify.Notification (@"Pair with $device_name?", body, "bluetooth-active");
+ notification.closed.connect (() => {
+ accepted = false;
+ notification = null;
+
+ if (loop.is_running ()) {
+ loop.quit ();
+ }
+ });
+
+ bool is_lomiri = AyatanaCommon.utils_is_lomiri ();
- Notify.Notification notification = new Notify.Notification (@"Pair with $device_name?", body, "bluetooth-active");
- bool bLomiri = 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 (bLomiri)
- {
- 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 ());
+ }
}
- notification.add_action("yes_id", "Yes", (notif, action) => {
- loop.quit ();
- accepted = true;
- });
- notification.add_action("no_id", "No", (notif, action) => {
- loop.quit ();
- accepted = false;
- });
+ 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
- {
+ try {
notification.show ();
}
- catch (Error pError)
- {
- warning ("Panic: Failed showing notification: %s", pError.message);
+ catch (Error e) {
+ warning ("Panic: Failed showing notification: %s", e.message);
}
- loop.run ();
+ if (have_actions) {
+ loop.run ();
+ }
return accepted;
}
@@ -54,42 +109,53 @@ public class Agent: Object
public void RequestConfirmation (GLib.ObjectPath object, uint32 passkey) throws RejectedError, GLib.DBusError, GLib.IOError
{
string body = "Are you sure you want to pair with passkey %06u?".printf (passkey);
- bool confirmed = sendNotification (bluetooth.get_device_name (object), body);
+ bool confirmed = sendNotification (bluetooth.get_device_name (object), body, false, true);
- if (confirmed) {
- return;
- } else {
+ 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?");
+ bool authorized = sendNotification (bluetooth.get_device_name (object), "Are you sure you want to pair with this device?", false, true);
- if (authorized) {
- return;
- } else {
+ if (!authorized) {
throw new RejectedError.ERROR ("Rejected by user");
}
}
- public string RequestPinCode (GLib.ObjectPath object) throws GLib.DBusError, GLib.IOError
+ public string RequestPinCode (GLib.ObjectPath object) throws RejectedError, GLib.DBusError, GLib.IOError
{
- return "123456";
+ 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
{
+ sendNotification (bluetooth.get_device_name (object), @"Enter the PIN code $pincode on the other device", false, false);
}
- public uint32 RequestPasskey (GLib.ObjectPath object) throws GLib.DBusError, GLib.IOError
+ public uint32 RequestPasskey (GLib.ObjectPath object) throws RejectedError, GLib.DBusError, GLib.IOError
{
- return 123456;
+ bool accepted = sendNotification (bluetooth.get_device_name (object), "Enter passkey 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 passkey %06u on the other device".printf (passkey);
+ sendNotification (bluetooth.get_device_name (object), body, false, false);
}
public void Cancel () throws GLib.DBusError, GLib.IOError
@@ -97,6 +163,11 @@ public class Agent: Object
if (loop.is_running ()) {
loop.quit ();
}
+
+ if (notification != null) {
+ notification.close ();
+ notification = null;
+ }
}
public void Release () throws GLib.DBusError, GLib.IOError
diff --git a/src/bluez.vala b/src/bluez.vala
index 6d08004..8d481f2 100644
--- a/src/bluez.vala
+++ b/src/bluez.vala
@@ -468,7 +468,7 @@ public class Bluez: Bluetooth, Object
{
try
{
- agent_manager.register_agent (new GLib.ObjectPath(path), "DisplayYesNo");
+ agent_manager.register_agent (new GLib.ObjectPath(path), AyatanaCommon.utils_is_lomiri() ? "KeyboardDisplay" : "DisplayYesNo");
}
catch (GLib.Error pError)
{
diff --git a/src/service.vala b/src/service.vala
index e651811..cff90d3 100644
--- a/src/service.vala
+++ b/src/service.vala
@@ -30,9 +30,15 @@ public class Service: Object
private SimpleActionGroup actions;
private HashTable<string,Profile> profiles;
private Bluetooth bluetooth;
+ private Agent agent;
private DBusConnection connection;
private uint exported_action_id;
+
+ private uint exported_agent_action_id;
+ private uint exported_agent_menu_id;
+
private const string OBJECT_PATH = "/org/ayatana/indicator/bluetooth";
+ private const string AGENT_OBJECT_PATH = "/org/ayatana/indicator/bluetooth/agent";
private void unexport ()
{
@@ -47,6 +53,18 @@ public class Service: Object
connection.unexport_action_group (exported_action_id);
exported_action_id = 0;
}
+
+ if (exported_agent_menu_id != 0)
+ {
+ connection.unexport_menu_model (exported_agent_menu_id);
+ exported_agent_menu_id = 0;
+ }
+
+ if (exported_agent_action_id != 0)
+ {
+ connection.unexport_action_group (exported_agent_action_id);
+ exported_agent_action_id = 0;
+ }
}
}
@@ -54,6 +72,9 @@ public class Service: Object
{
actions = new SimpleActionGroup ();
bluetooth = bluetooth_service;
+ agent = new Agent (bluetooth);
+ agent.actions_path = AGENT_OBJECT_PATH;
+ agent.menu_path = AGENT_OBJECT_PATH;
profiles = new HashTable<string,Profile> (str_hash, str_equal);
profiles.insert ("phone", new Phone (bluetooth, actions));
@@ -84,7 +105,7 @@ public class Service: Object
null);
bluetooth.agent_manager_ready.connect (() => {
- bluetooth.add_agent ("/agent");
+ bluetooth.add_agent (AGENT_OBJECT_PATH);
});
loop = new MainLoop (null, false);
@@ -101,7 +122,7 @@ public class Service: Object
{
try
{
- connection.register_object ("/agent", new Agent (bluetooth));
+ connection.register_object (AGENT_OBJECT_PATH, agent);
}
catch (GLib.IOError pError)
{
@@ -119,6 +140,11 @@ public class Service: Object
debug (@"exporting action group '$(OBJECT_PATH)'");
exported_action_id = connection.export_action_group (OBJECT_PATH,
actions);
+
+ exported_agent_action_id = connection.export_action_group (AGENT_OBJECT_PATH,
+ agent.actions);
+ exported_agent_menu_id = connection.export_menu_model (AGENT_OBJECT_PATH,
+ agent.menu);
}
catch (Error e)
{