From 5b45748c5dc9c2d1718b6219e30a64e07e5ca313 Mon Sep 17 00:00:00 2001 From: Muhammad Asif Date: Mon, 19 May 2025 22:22:08 +0200 Subject: ayatana-indicator-bluetooth: add initial pairing agent Co-authored-by: Robert Tari Signed-off-by: Muhammad Asif --- CMakeLists.txt | 1 + src/CMakeLists.txt | 9 +++++ src/agent.vala | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/bluetooth.vala | 4 ++ src/bluez.vala | 40 +++++++++++++++++++ src/service.vala | 22 ++++++++++ 6 files changed, 191 insertions(+) create mode 100644 src/agent.vala diff --git a/CMakeLists.txt b/CMakeLists.txt index 49c6bb3..e954ecd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,7 @@ pkg_check_modules( glib-2.0>=${GLIB_2_0_REQUIRED_VERSION} gio-unix-2.0>=${GIO_2_0_REQUIRED_VERSION} libayatana-common>=0.9.3 + libnotify ) include_directories(${BLUETOOTHSERVICE_INCLUDE_DIRS}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b0a1e2..d616baf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,7 @@ vala_init(ayatana-indicator-bluetooth-service posix gio-2.0 gio-unix-2.0 + libnotify AyatanaCommon OPTIONS --ccode @@ -94,6 +95,14 @@ vala_add(ayatana-indicator-bluetooth-service phone desktop greeter + agent +) + +vala_add(ayatana-indicator-bluetooth-service + agent.vala + DEPENDS + bluetooth + device ) vala_finish(ayatana-indicator-bluetooth-service diff --git a/src/agent.vala b/src/agent.vala new file mode 100644 index 0000000..beb8f8b --- /dev/null +++ b/src/agent.vala @@ -0,0 +1,115 @@ +[DBus (name = "org.bluez.Agent1")] +public class Agent: Object +{ + private MainLoop loop; + private Bluetooth bluetooth; + + public Agent (Bluetooth bluez) + { + loop = new MainLoop (null, false); + bluetooth = bluez; + Notify.init ("ayatana-indicator-bluetooth"); + } + + private bool sendNotification (string device_name, string body) + { + bool accepted = false; + + Notify.Notification notification = new Notify.Notification (@"Pair with $device_name?", body, "bluetooth-active"); + bool bLomiri = AyatanaCommon.utils_is_lomiri (); + + if (bLomiri) + { + notification.set_hint ("x-lomiri-snap-decisions", true); + notification.set_hint ("x-lomiri-private-affirmative-tint", "true"); + } + + notification.add_action("yes_id", "Yes", (notif, action) => { + loop.quit (); + accepted = true; + }); + notification.add_action("no_id", "No", (notif, action) => { + loop.quit (); + accepted = false; + }); + + try + { + notification.show (); + } + catch (Error pError) + { + warning ("Panic: Failed showing notification: %s", pError.message); + } + + loop.run (); + + return accepted; + } + + public void AuthorizeService (GLib.ObjectPath object, string uuid) throws GLib.DBusError, GLib.IOError + { + } + + 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); + + if (confirmed) { + return; + } else { + 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?"); + + if (authorized) { + return; + } else { + throw new RejectedError.ERROR ("Rejected by user"); + } + } + + public string RequestPinCode (GLib.ObjectPath object) throws GLib.DBusError, GLib.IOError + { + return "123456"; + } + + public void DisplayPinCode (GLib.ObjectPath object, string pincode) throws GLib.DBusError, GLib.IOError + { + } + + public uint32 RequestPasskey (GLib.ObjectPath object) throws GLib.DBusError, GLib.IOError + { + return 123456; + } + + public void DisplayPasskey (GLib.ObjectPath object, uint32 passkey, uint16 entered) throws GLib.DBusError, GLib.IOError + { + } + + public void Cancel () throws GLib.DBusError, GLib.IOError + { + if (loop.is_running ()) { + loop.quit (); + } + } + + 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 +} diff --git a/src/bluetooth.vala b/src/bluetooth.vala index 0cc5432..da7c176 100644 --- a/src/bluetooth.vala +++ b/src/bluetooth.vala @@ -52,4 +52,8 @@ public interface Bluetooth: Object /* Try to connect/disconnect a particular device. The device_key argument comes from the Device struct */ public abstract void set_device_connected (uint device_key, bool connected); + + public abstract string get_device_name (ObjectPath path); + + public abstract void add_agent (string path); } diff --git a/src/bluez.vala b/src/bluez.vala index 16e237a..fabee5f 100644 --- a/src/bluez.vala +++ b/src/bluez.vala @@ -55,6 +55,8 @@ public class Bluez: Bluetooth, Object /* maps our arbitrary unique id to a Bluetooth.Device struct for public consumption */ private HashTable id_to_device; + private BluezAgentManager agent_manager; + public Bluez (KillSwitch? killswitch) { init_bluez_state_vars (); @@ -119,6 +121,8 @@ public class Bluez: Bluetooth, Object try { manager = bus.get_proxy_sync (BLUEZ_BUSNAME, "/"); + agent_manager = bus.get_proxy_sync (BLUEZ_BUSNAME, "/org/bluez"); + add_agent ("/agent"); // Find the adapters and watch for changes manager.interfaces_added.connect ((object_path, interfaces_and_properties) => { @@ -425,6 +429,12 @@ public class Bluez: Bluetooth, Object } } + public string get_device_name (ObjectPath path) + { + var device = id_to_device.lookup(path_to_id.lookup(path)); + return device.name; + } + public List get_devices () { return id_to_device.get_values(); @@ -453,6 +463,27 @@ public class Bluez: Bluetooth, Object DBusCallFlags.NONE, -1); } } + + public void add_agent(string path) + { + try + { + agent_manager.register_agent (new GLib.ObjectPath(path), "DisplayYesNo"); + } + catch (GLib.Error pError) + { + warning ("Panic: Failed registering pairing agent: %s", pError.message); + } + + try + { + agent_manager.request_default_agent (new GLib.ObjectPath(path)); + } + catch (GLib.Error pError) + { + warning ("Panic: Failed getting default pairing agent: %s", pError.message); + } + } } [DBus (name = "org.freedesktop.DBus.ObjectManager")] @@ -479,3 +510,12 @@ private interface BluezDevice : DBusProxy { [DBus (name = "Disconnect")] public abstract void disconnect_() throws DBusError, IOError; } + +[DBus (name = "org.bluez.AgentManager1")] +private interface BluezAgentManager : DBusProxy { + [DBus (name = "RegisterAgent")] + public abstract void register_agent(GLib.ObjectPath object, string capabilities) throws DBusError, IOError; + + [DBus (name = "RequestDefaultAgent")] + public abstract void request_default_agent(GLib.ObjectPath object) throws DBusError, IOError; +} diff --git a/src/service.vala b/src/service.vala index 80ccea6..b79056e 100644 --- a/src/service.vala +++ b/src/service.vala @@ -29,6 +29,7 @@ public class Service: Object private MainLoop loop; private SimpleActionGroup actions; private HashTable profiles; + private Bluetooth bluez; private DBusConnection connection; private uint exported_action_id; private const string OBJECT_PATH = "/org/ayatana/indicator/bluetooth"; @@ -52,6 +53,7 @@ public class Service: Object public Service (Bluetooth bluetooth) { actions = new SimpleActionGroup (); + bluez = bluetooth; profiles = new HashTable (str_hash, str_equal); profiles.insert ("phone", new Phone (bluetooth, actions)); @@ -74,15 +76,35 @@ public class Service: Object null, on_name_lost); + var system_name_id = Bus.own_name (BusType.SYSTEM, + "org.ayatana.indicator.bluetooth", + BusNameOwnerFlags.NONE, + on_system_bus_acquired, + null, + null); + loop = new MainLoop (null, false); loop.run (); // cleanup unexport (); Bus.unown_name (own_name_id); + Bus.unown_name (system_name_id); return Posix.EXIT_SUCCESS; } + void on_system_bus_acquired (DBusConnection connection, string name) + { + try + { + connection.register_object ("/agent", new Agent (bluez)); + } + catch (GLib.IOError pError) + { + warning ("Panic: Failed registering pairing agent: %s", pError.message); + } + } + void on_bus_acquired (DBusConnection connection, string name) { debug (@"bus acquired: $name"); -- cgit v1.2.3 From 568f6aa32e290b4e37b61dfd9985ae81a08e2892 Mon Sep 17 00:00:00 2001 From: Muhammad Asif Date: Tue, 24 Jun 2025 00:19:48 +0500 Subject: agent: use signals to register agent to BlueZ Signed-off-by: Muhammad Asif --- src/bluetooth.vala | 2 ++ src/bluez.vala | 2 +- src/service.vala | 12 ++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/bluetooth.vala b/src/bluetooth.vala index da7c176..9b2f9d5 100644 --- a/src/bluetooth.vala +++ b/src/bluetooth.vala @@ -55,5 +55,7 @@ public interface Bluetooth: Object public abstract string get_device_name (ObjectPath path); + public signal void agent_manager_ready (); + public abstract void add_agent (string path); } diff --git a/src/bluez.vala b/src/bluez.vala index fabee5f..6d08004 100644 --- a/src/bluez.vala +++ b/src/bluez.vala @@ -122,7 +122,7 @@ public class Bluez: Bluetooth, Object { manager = bus.get_proxy_sync (BLUEZ_BUSNAME, "/"); agent_manager = bus.get_proxy_sync (BLUEZ_BUSNAME, "/org/bluez"); - add_agent ("/agent"); + agent_manager_ready (); // Find the adapters and watch for changes manager.interfaces_added.connect ((object_path, interfaces_and_properties) => { diff --git a/src/service.vala b/src/service.vala index b79056e..e651811 100644 --- a/src/service.vala +++ b/src/service.vala @@ -29,7 +29,7 @@ public class Service: Object private MainLoop loop; private SimpleActionGroup actions; private HashTable profiles; - private Bluetooth bluez; + private Bluetooth bluetooth; private DBusConnection connection; private uint exported_action_id; private const string OBJECT_PATH = "/org/ayatana/indicator/bluetooth"; @@ -50,10 +50,10 @@ public class Service: Object } } - public Service (Bluetooth bluetooth) + public Service (Bluetooth bluetooth_service) { actions = new SimpleActionGroup (); - bluez = bluetooth; + bluetooth = bluetooth_service; profiles = new HashTable (str_hash, str_equal); profiles.insert ("phone", new Phone (bluetooth, actions)); @@ -83,6 +83,10 @@ public class Service: Object null, null); + bluetooth.agent_manager_ready.connect (() => { + bluetooth.add_agent ("/agent"); + }); + loop = new MainLoop (null, false); loop.run (); @@ -97,7 +101,7 @@ public class Service: Object { try { - connection.register_object ("/agent", new Agent (bluez)); + connection.register_object ("/agent", new Agent (bluetooth)); } catch (GLib.IOError pError) { -- cgit v1.2.3 From 6c7c62d7c6d153293c8aea8dd1c71faabdef3b3e Mon Sep 17 00:00:00 2001 From: Muhammad Date: Tue, 22 Jul 2025 21:05:43 +0500 Subject: 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 --- src/agent.vala | 139 +++++++++++++++++++++++++++++++++++++++++-------------- src/bluez.vala | 2 +- src/service.vala | 30 +++++++++++- 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 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 (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) { -- cgit v1.2.3 From 36d77944f2e7707eefd60ea208c6f4bc55a3f961 Mon Sep 17 00:00:00 2001 From: Muhammad Date: Tue, 10 Mar 2026 01:37:25 +0500 Subject: agent: add proper support for AuthorizeService Signed-off-by: Muhammad --- src/agent.vala | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++- src/bluetooth.vala | 6 ++++++ src/bluez.vala | 26 ++++++++++++++++++++++ src/device.vala | 5 ++++- 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/agent.vala b/src/agent.vala index 9e568a4..16ce6a2 100644 --- a/src/agent.vala +++ b/src/agent.vala @@ -1,9 +1,12 @@ [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; @@ -102,8 +105,66 @@ public class Agent: Object return accepted; } - public void AuthorizeService (GLib.ObjectPath object, string uuid) throws GLib.DBusError, GLib.IOError + 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 diff --git a/src/bluetooth.vala b/src/bluetooth.vala index 9b2f9d5..47c6f8e 100644 --- a/src/bluetooth.vala +++ b/src/bluetooth.vala @@ -46,6 +46,9 @@ public interface Bluetooth: Object /* Get a list of the Device structs that we know about */ public abstract List get_devices (); + /* Get a Device from its DBus path */ + public abstract Device get_device (ObjectPath path); + /* Emitted when one or more of the devices is added, removed, or changed */ public signal void devices_changed (); @@ -53,6 +56,9 @@ public interface Bluetooth: Object The device_key argument comes from the Device struct */ public abstract void set_device_connected (uint device_key, bool connected); + /* Sets whether or not a device is trusted (allowed to connect without authorization) */ + public abstract void set_device_trusted (uint device_key, bool trusted); + public abstract string get_device_name (ObjectPath path); public signal void agent_manager_ready (); diff --git a/src/bluez.vala b/src/bluez.vala index 8d481f2..353b1a2 100644 --- a/src/bluez.vala +++ b/src/bluez.vala @@ -325,6 +325,10 @@ public class Bluez: Bluetooth, Object v = device_proxy.get_cached_property ("Connected"); var is_connected = (v != null) && v.get_boolean (); + // look up whether the device is trusted + v = device_proxy.get_cached_property ("Trusted"); + var is_trusted = (v != null) && v.get_boolean (); + // derive the uuid-related attributes we care about v = device_proxy.get_cached_property ("UUIDs"); uint16[] uuids = {}; @@ -344,6 +348,7 @@ public class Bluez: Bluetooth, Object icon, true, is_connected, + is_trusted, supports_browsing, supports_file_transfer)); @@ -415,6 +420,19 @@ public class Bluez: Bluetooth, Object } } + public void set_device_trusted (uint id, bool trusted) + { + var device = id_to_device.lookup (id); + var path = id_to_path.lookup (id); + var proxy = (path != null) ? path_to_device_proxy.lookup (path) : null; + + if ((device != null) + && (device.is_trusted != trusted)) + { + proxy.trusted = trusted; + } + } + public void try_set_discoverable (bool b) { if (discoverable != b) @@ -440,6 +458,11 @@ public class Bluez: Bluetooth, Object return id_to_device.get_values(); } + public Device get_device (ObjectPath path) + { + return id_to_device.lookup(path_to_id.lookup(path)); + } + public bool supported { get; protected set; default = false; } public bool discoverable { get; protected set; default = false; } public bool enabled { get; protected set; default = false; } @@ -509,6 +532,9 @@ private interface BluezDevice : DBusProxy { [DBus (name = "Disconnect")] public abstract void disconnect_() throws DBusError, IOError; + + [DBus (name = "Trusted")] + public abstract bool trusted { get; set; } } [DBus (name = "org.bluez.AgentManager1")] diff --git a/src/device.vala b/src/device.vala index 51bec03..e984a35 100644 --- a/src/device.vala +++ b/src/device.vala @@ -46,10 +46,11 @@ public class Device: Object public Icon icon { get; construct; } public bool is_connectable { get; construct; } public bool is_connected { get; construct; } + public bool is_trusted { get; construct; } public bool supports_browsing { get; construct; } public bool supports_file_transfer { get; construct; } public string print() { - return @"{id:$id, name:$name, address:$address, icon:$(icon.to_string()), device_type:$device_type, is_connectable:$is_connectable, is_connected:$is_connected, supports_browsing:$supports_browsing, supports_file_transfer:$supports_file_transfer}"; + return @"{id:$id, name:$name, address:$address, icon:$(icon.to_string()), device_type:$device_type, is_connectable:$is_connectable, is_connected:$is_connected, is_trusted:$is_trusted, supports_browsing:$supports_browsing, supports_file_transfer:$supports_file_transfer}"; } public Device (uint id, @@ -59,6 +60,7 @@ public class Device: Object Icon icon, bool is_connectable, bool is_connected, + bool is_trusted, bool supports_browsing, bool supports_file_transfer) { @@ -69,6 +71,7 @@ public class Device: Object icon: icon, is_connectable: is_connectable, is_connected: is_connected, + is_trusted: is_trusted, supports_browsing: supports_browsing, supports_file_transfer: supports_file_transfer); } -- cgit v1.2.3 From ebefa983d88a9c7c9a5b86b5ebb9419656788b37 Mon Sep 17 00:00:00 2001 From: Muhammad Date: Thu, 12 Mar 2026 23:28:23 +0500 Subject: agent: rename passkey to PIN To remove confusion between web passkeys and Bluetooth passkeys Signed-off-by: Muhammad --- src/agent.vala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agent.vala b/src/agent.vala index 16ce6a2..d8dddfa 100644 --- a/src/agent.vala +++ b/src/agent.vala @@ -169,7 +169,7 @@ 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); + 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) { @@ -204,7 +204,7 @@ public class Agent: Object public uint32 RequestPasskey (GLib.ObjectPath object) throws RejectedError, GLib.DBusError, GLib.IOError { - bool accepted = sendNotification (bluetooth.get_device_name (object), "Enter passkey for this device", true, true); + bool accepted = sendNotification (bluetooth.get_device_name (object), "Enter PIN for this device", true, true); if (!accepted) { throw new RejectedError.ERROR ("Rejected by user"); @@ -215,7 +215,7 @@ public class Agent: Object 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); + string body = "Enter the PIN %06u on the other device".printf (passkey); sendNotification (bluetooth.get_device_name (object), body, false, false); } -- cgit v1.2.3 From 8e66b3019ea5c38add6a4df2a76cccf62c19bc2a Mon Sep 17 00:00:00 2001 From: Muhammad Date: Thu, 12 Mar 2026 23:44:25 +0500 Subject: agent: translate strings Signed-off-by: Muhammad --- src/agent.vala | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/agent.vala b/src/agent.vala index d8dddfa..7f517f0 100644 --- a/src/agent.vala +++ b/src/agent.vala @@ -42,7 +42,8 @@ public class Agent: Object { bool accepted = !have_actions; - notification = new Notify.Notification (@"Pair with $device_name?", body, "bluetooth-active"); + string header = (_("Pair with %s?").printf (device_name)); + notification = new Notify.Notification (header, body, "bluetooth-active"); notification.closed.connect (() => { accepted = false; notification = null; @@ -74,12 +75,12 @@ public class Agent: Object } if (have_actions) { - notification.add_action("yes_id", "Yes", (notif, action) => { + notification.add_action("yes_id", (_("Yes")), (notif, action) => { loop.quit (); notification = null; accepted = true; }); - notification.add_action("no_id", "No", (notif, action) => { + notification.add_action("no_id", (_("No")), (notif, action) => { loop.quit (); notification = null; accepted = false; @@ -110,8 +111,8 @@ public class Agent: Object 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?"; + 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 (() => { @@ -127,7 +128,7 @@ public class Agent: Object notification.set_hint ("x-lomiri-snap-decisions", true); } - notification.add_action ("trust_and_authorize", "Trust and authorize", (notif, action) => { + notification.add_action ("trust_and_authorize", (_("Trust and authorize")), (notif, action) => { loop.quit (); notification = null; @@ -135,14 +136,14 @@ public class Agent: Object authorized = true; }); - notification.add_action ("authorize", "Authorize", (notif, action) => { + notification.add_action ("authorize", (_("Authorize")), (notif, action) => { loop.quit (); notification = null; authorized = true; }); - notification.add_action ("reject", "Do not authorize", (notif, action) => { + notification.add_action ("reject", (_("Do not authorize")), (notif, action) => { loop.quit (); notification = null; }); @@ -169,7 +170,7 @@ 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 PIN %06u?".printf (passkey); + 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) { @@ -179,7 +180,7 @@ public class Agent: Object 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); + 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"); @@ -188,7 +189,7 @@ public class Agent: Object 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); + bool accepted = sendNotification (bluetooth.get_device_name (object), (_("Enter PIN for this device")), true, true); if (!accepted) { throw new RejectedError.ERROR ("Rejected by user"); @@ -199,12 +200,13 @@ public class Agent: Object 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); + 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); + bool accepted = sendNotification (bluetooth.get_device_name (object), (_("Enter PIN for this device")), true, true); if (!accepted) { throw new RejectedError.ERROR ("Rejected by user"); @@ -215,7 +217,7 @@ public class Agent: Object 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); + string body = (_("Enter the PIN %06u on the other device").printf (passkey)); sendNotification (bluetooth.get_device_name (object), body, false, false); } -- cgit v1.2.3 From 66a3f9cf71de043ba111e5dbf2a0ac7c30ad235e Mon Sep 17 00:00:00 2001 From: Ricky Tigg Date: Fri, 18 Jul 2025 15:18:55 +0200 Subject: Translated using Weblate (Finnish) Currently translated at 15.3% (2 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/fi/ --- po/fi.po | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/po/fi.po b/po/fi.po index afd8e84..6d6a827 100644 --- a/po/fi.po +++ b/po/fi.po @@ -8,13 +8,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2025-07-18 20:08+0000\n" +"Last-Translator: Ricky Tigg \n" +"Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.13-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -38,7 +41,7 @@ msgstr "" #: src/desktop.vala:173 msgid "Keyboard Settings…" -msgstr "" +msgstr "Näppäimistön asetukset…" #: src/desktop.vala:179 msgid "Mouse and Touchpad Settings…" -- cgit v1.2.3 From e4d29696c6fb6ada45eab4cf2d258b5dfe3d2eab Mon Sep 17 00:00:00 2001 From: Ida Brenna Date: Sun, 24 Aug 2025 20:47:21 +0200 Subject: Translated using Weblate (Norwegian Bokmål) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 7.6% (1 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/nb_NO/ --- po/nb_NO.po | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/po/nb_NO.po b/po/nb_NO.po index 5cb5c6f..93c1b7d 100644 --- a/po/nb_NO.po +++ b/po/nb_NO.po @@ -8,13 +8,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2025-08-25 19:02+0000\n" +"Last-Translator: Ida Brenna \n" +"Language-Team: Norwegian Bokmål \n" "Language: nb_NO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 5.13\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -63,7 +66,7 @@ msgstr "" #: src/profile.vala:91 src/profile.vala:179 msgid "Bluetooth" -msgstr "" +msgstr "Blåtann" #: src/profile.vala:180 msgid "Bluetooth status & device connections" -- cgit v1.2.3 From d4b1797050b63604ad13e868790b0f190433a086 Mon Sep 17 00:00:00 2001 From: Priit Jõerüüt Date: Fri, 5 Sep 2025 08:32:56 +0200 Subject: Translated using Weblate (Estonian) Currently translated at 100.0% (13 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/et/ --- po/et.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/po/et.po b/po/et.po index c855c1f..35a787f 100644 --- a/po/et.po +++ b/po/et.po @@ -8,16 +8,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2022-10-20 10:03+0000\n" -"Last-Translator: Kristjan Räts \n" -"Language-Team: Estonian \n" +"PO-Revision-Date: 2025-09-06 07:01+0000\n" +"Last-Translator: Priit Jõerüüt \n" +"Language-Team: Estonian \n" "Language: et\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.2-dev\n" +"X-Generator: Weblate 5.13.1-rc\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -49,7 +49,7 @@ msgstr "Hiire ja puuteplaadi seaded…" #: src/desktop.vala:186 msgid "Sound Settings…" -msgstr "Heli seaded…" +msgstr "Heli seadistused…" #: src/desktop.vala:234 msgid "Visible" -- cgit v1.2.3 From 2528a1741d0d1b992b3e9f273c9f607cd1c3be2d Mon Sep 17 00:00:00 2001 From: Ida Brenna Date: Tue, 16 Sep 2025 18:05:46 +0200 Subject: Translated using Weblate (Norwegian Bokmål) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 23.0% (3 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/nb_NO/ --- po/nb_NO.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/nb_NO.po b/po/nb_NO.po index 93c1b7d..0d4f525 100644 --- a/po/nb_NO.po +++ b/po/nb_NO.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2025-08-25 19:02+0000\n" +"PO-Revision-Date: 2025-09-17 17:01+0000\n" "Last-Translator: Ida Brenna \n" "Language-Team: Norwegian Bokmål \n" @@ -17,15 +17,15 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.13\n" +"X-Generator: Weblate 5.14-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." -msgstr "" +msgstr "Blåtann-indikator i menyfeltet." #: data/org.ayatana.indicator.bluetooth.gschema.xml:2 msgid "Whether or not to show the Bluetooth indicator in the menu bar." -msgstr "" +msgstr "Hvorvidt man skal vise blåtannindikatoren i menyfeltet." #: src/bluez.vala:310 msgid "Unknown" -- cgit v1.2.3 From 69d7dc3aee709f595bbf051df2420ebb0322b4c1 Mon Sep 17 00:00:00 2001 From: BoneNI Date: Fri, 14 Nov 2025 04:58:37 +0100 Subject: Translated using Weblate (Lao) Currently translated at 30.7% (4 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/lo/ --- po/lo.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/lo.po b/po/lo.po index c793823..832c4c7 100644 --- a/po/lo.po +++ b/po/lo.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2023-04-21 15:52+0000\n" -"Last-Translator: Vilakone Thammavong \n" +"PO-Revision-Date: 2025-11-15 04:51+0000\n" +"Last-Translator: BoneNI \n" "Language-Team: Lao \n" "Language: lo\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.18-dev\n" +"X-Generator: Weblate 5.15-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -49,7 +49,7 @@ msgstr "" #: src/desktop.vala:186 msgid "Sound Settings…" -msgstr "" +msgstr "ຕັ້ງຄ່າສຽງ…" #: src/desktop.vala:234 msgid "Visible" -- cgit v1.2.3 From 0e55e90aaf53154d0a0c9f826b22dcee1e24cc5f Mon Sep 17 00:00:00 2001 From: BoneNI Date: Tue, 27 Jan 2026 12:04:17 +0100 Subject: Translated using Weblate (Lao) Currently translated at 100.0% (13 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/lo/ --- po/lo.po | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/po/lo.po b/po/lo.po index 832c4c7..9495c8f 100644 --- a/po/lo.po +++ b/po/lo.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2025-11-15 04:51+0000\n" +"PO-Revision-Date: 2026-01-28 00:35+0000\n" "Last-Translator: BoneNI \n" "Language-Team: Lao \n" @@ -17,15 +17,15 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.15-dev\n" +"X-Generator: Weblate 5.16-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." -msgstr "ປະເພດການຈ່າຍເງິນ" +msgstr "ຕົວຊີ້ບອກບລູທູດໃນແຖບເມນູ." #: data/org.ayatana.indicator.bluetooth.gschema.xml:2 msgid "Whether or not to show the Bluetooth indicator in the menu bar." -msgstr "" +msgstr "ຈະສະແດງ ຫຼື ບໍ່ສະແດງຕົວຊີ້ບອກບລູທູດໃນແຖບເມນູ." #: src/bluez.vala:310 msgid "Unknown" @@ -33,41 +33,41 @@ msgstr "ບໍ່ຮູ້ຈັກ" #: src/desktop.vala:114 msgid "Connection" -msgstr "ເຊື່ອມຕໍ່" +msgstr "ການເຊື່ອມຕໍ່" #: src/desktop.vala:167 msgid "Send files…" -msgstr "" +msgstr "ສົ່ງໄຟລ໌…" #: src/desktop.vala:173 msgid "Keyboard Settings…" -msgstr "" +msgstr "ການຕັ້ງຄ່າແປ້ນພິມ…" #: src/desktop.vala:179 msgid "Mouse and Touchpad Settings…" -msgstr "" +msgstr "ການຕັ້ງຄ່າເມົາສ໌ ແລະ ທັດຊ໌ແພັດ…" #: src/desktop.vala:186 msgid "Sound Settings…" -msgstr "ຕັ້ງຄ່າສຽງ…" +msgstr "ການຕັ້ງຄ່າສຽງ…" #: src/desktop.vala:234 msgid "Visible" -msgstr "" +msgstr "ສາມາດເບິ່ງເຫັນໄດ້" #. settings section #: src/desktop.vala:247 msgid "Bluetooth Settings…" -msgstr "" +msgstr "ການຕັ້ງຄ່າບລູທູດ…" #: src/phone.vala:45 msgid "Bluetooth settings…" -msgstr "" +msgstr "ການຕັ້ງຄ່າບລູທູດ…" #: src/profile.vala:91 src/profile.vala:179 msgid "Bluetooth" -msgstr "" +msgstr "ບລູທູດ" #: src/profile.vala:180 msgid "Bluetooth status & device connections" -msgstr "" +msgstr "ສະຖານະບລູທູດ ແລະ ການເຊື່ອມຕໍ່ອຸປະກອນ" -- cgit v1.2.3 From fc6e1f94dc028f27cb8ade21fd5346beb9f4dc25 Mon Sep 17 00:00:00 2001 From: Arif Budiman Date: Mon, 16 Feb 2026 12:51:34 +0100 Subject: Translated using Weblate (Indonesian) Currently translated at 76.9% (10 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/id/ --- po/id.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/id.po b/po/id.po index 12fd88c..e6186ff 100644 --- a/po/id.po +++ b/po/id.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2021-03-18 17:29+0000\n" -"Last-Translator: Reza Almanda \n" +"PO-Revision-Date: 2026-02-17 12:09+0000\n" +"Last-Translator: Arif Budiman \n" "Language-Team: Indonesian \n" "Language: id\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.5.2-dev\n" +"X-Generator: Weblate 5.16\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -49,7 +49,7 @@ msgstr "Pengaturan tetikus dan touchpad…" #: src/desktop.vala:186 msgid "Sound Settings…" -msgstr "Pengaturan suara…" +msgstr "Pengaturan Suara…" #: src/desktop.vala:234 msgid "Visible" -- cgit v1.2.3 From eb9ec32c528883f3f38792bb0e52cc49414c6ba0 Mon Sep 17 00:00:00 2001 From: Arif Budiman Date: Mon, 9 Mar 2026 14:43:53 +0100 Subject: Translated using Weblate (Indonesian) Currently translated at 76.9% (10 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/id/ --- po/id.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/id.po b/po/id.po index e6186ff..845074b 100644 --- a/po/id.po +++ b/po/id.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2026-02-17 12:09+0000\n" +"PO-Revision-Date: 2026-03-10 14:09+0000\n" "Last-Translator: Arif Budiman \n" "Language-Team: Indonesian \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.16\n" +"X-Generator: Weblate 5.17-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -41,7 +41,7 @@ msgstr "Mengirim file…" #: src/desktop.vala:173 msgid "Keyboard Settings…" -msgstr "Pengaturan Papan tik…" +msgstr "Pengaturan Papan Ketik…" #: src/desktop.vala:179 msgid "Mouse and Touchpad Settings…" -- cgit v1.2.3 From a4169c2c7d2ea8c8b21ad5492b003c296c77a463 Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Wed, 18 Mar 2026 11:46:58 +0100 Subject: Translated using Weblate (English (United Kingdom)) Currently translated at 100.0% (13 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/en_GB/ --- po/en_GB.po | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/po/en_GB.po b/po/en_GB.po index 14ae773..799f482 100644 --- a/po/en_GB.po +++ b/po/en_GB.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: 2023-09-07 05:05+0000\n" -"Last-Translator: Kieran W \n" +"PO-Revision-Date: 2026-03-18 20:11+0000\n" +"Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom) \n" "Language: en_GB\n" @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.0.1-dev\n" +"X-Generator: Weblate 5.17-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -41,7 +41,7 @@ msgstr "Send files…" #: src/desktop.vala:173 msgid "Keyboard Settings…" -msgstr "Keyboard settings…" +msgstr "Keyboard Settings…" #: src/desktop.vala:179 msgid "Mouse and Touchpad Settings…" -- cgit v1.2.3 From 8c3c7f06b3d9554599b022e7ac7227540389ba35 Mon Sep 17 00:00:00 2001 From: BackSpec Date: Tue, 17 Mar 2026 20:16:19 +0100 Subject: Translated using Weblate (Japanese) Currently translated at 7.6% (1 of 13 strings) Translation: Ayatana Indicators/Bluetooth Indicator Applet Translate-URL: https://hosted.weblate.org/projects/ayatana-indicators/bluetooth-applet/ja/ --- po/ja.po | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/po/ja.po b/po/ja.po index c9e51f9..f01c067 100644 --- a/po/ja.po +++ b/po/ja.po @@ -8,13 +8,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-10 23:51+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" +"PO-Revision-Date: 2026-03-18 20:11+0000\n" +"Last-Translator: BackSpec \n" +"Language-Team: Japanese \n" "Language: ja\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 5.17-dev\n" #: data/org.ayatana.indicator.bluetooth.gschema.xml:1 msgid "Bluetooth indicator in the menu bar." @@ -26,7 +29,7 @@ msgstr "" #: src/bluez.vala:310 msgid "Unknown" -msgstr "" +msgstr "不明" #: src/desktop.vala:114 msgid "Connection" -- cgit v1.2.3