Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion Services/BluetoothService.qml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ pragma Singleton

import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Bluetooth
import qs.Common

Singleton {
id: root
Expand Down Expand Up @@ -535,7 +537,18 @@ Singleton {
onTriggered: root._finishOperationFailed(qsTr("蓝牙操作超时;当前 Quickshell API 未提供更详细的 BlueZ 错误"))
}

Component.onCompleted: root._applyDiscovery()
Process {
id: agentProcess
command: ["python3", Paths.scriptPath("system", "bluetooth-agent.py")]
running: true
}

Component.onCompleted: {
root._applyDiscovery();
if (root.adapter && !root.adapter.pairable) {
root.adapter.pairable = true;
}
}
Component.onDestruction: {
for (const nativeAdapter of root._nativeAdapters) {
if (nativeAdapter && nativeAdapter.discovering)
Expand Down
108 changes: 108 additions & 0 deletions scripts/system/bluetooth-agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
Clavis Bluetooth Pairing Agent (BlueZ D-Bus Agent)
Provides automatic authorization and confirmation for Bluetooth pairing operations.
"""

import sys
import os
import signal
import dbus
import dbus.service
import dbus.mainloop.glib
from gi.repository import GLib

AGENT_PATH = "/org/clavis/bluetooth/agent"
CAPABILITY = "DisplayYesNo"

class BluetoothAgent(dbus.service.Object):
def __init__(self, bus, path):
super().__init__(bus, path)

@dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="")
def Release(self):
print("[BluetoothAgent] Released by BlueZ", flush=True)

@dbus.service.method("org.bluez.Agent1", in_signature="os", out_signature="")
def AuthorizeService(self, device, uuid):
print(f"[BluetoothAgent] AuthorizeService: {device} {uuid}", flush=True)
return

@dbus.service.method("org.bluez.Agent1", in_signature="o", out_signature="s")
def RequestPinCode(self, device):
print(f"[BluetoothAgent] RequestPinCode for {device}", flush=True)
return "0000"

@dbus.service.method("org.bluez.Agent1", in_signature="o", out_signature="u")
def RequestPasskey(self, device):
print(f"[BluetoothAgent] RequestPasskey for {device}", flush=True)
return dbus.UInt32(0)

@dbus.service.method("org.bluez.Agent1", in_signature="ouq", out_signature="")
def DisplayPasskey(self, device, passkey, entered):
print(f"[BluetoothAgent] DisplayPasskey: device={device} passkey={passkey:06d} entered={entered}", flush=True)

@dbus.service.method("org.bluez.Agent1", in_signature="os", out_signature="")
def DisplayPinCode(self, device, pincode):
print(f"[BluetoothAgent] DisplayPinCode: device={device} pin={pincode}", flush=True)

@dbus.service.method("org.bluez.Agent1", in_signature="ou", out_signature="")
def RequestConfirmation(self, device, passkey):
print(f"[BluetoothAgent] RequestConfirmation: device={device} passkey={passkey:06d} -> auto confirmed", flush=True)
return

@dbus.service.method("org.bluez.Agent1", in_signature="o", out_signature="")
def RequestAuthorization(self, device):
print(f"[BluetoothAgent] RequestAuthorization for {device} -> authorized", flush=True)
return

@dbus.service.method("org.bluez.Agent1", in_signature="", out_signature="")
def Cancel(self):
print("[BluetoothAgent] Pairing request cancelled", flush=True)

def main():
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
loop = GLib.MainLoop()

try:
bus = dbus.SystemBus()
except Exception as e:
print(f"[BluetoothAgent] Failed to connect to SystemBus: {e}", file=sys.stderr, flush=True)
sys.exit(1)

agent = BluetoothAgent(bus, AGENT_PATH)

def register():
try:
manager = dbus.Interface(bus.get_object("org.bluez", "/org/bluez"), "org.bluez.AgentManager1")
manager.RegisterAgent(AGENT_PATH, CAPABILITY)
manager.RequestDefaultAgent(AGENT_PATH)
print(f"[BluetoothAgent] Registered as default agent with capability '{CAPABILITY}'", flush=True)
return True
except dbus.DBusException as e:
print(f"[BluetoothAgent] Registration error: {e}", file=sys.stderr, flush=True)
return False

register()

def name_owner_changed(name, old_owner, new_owner):
if name == "org.bluez" and new_owner != "":
print("[BluetoothAgent] BlueZ daemon restarted, re-registering agent...", flush=True)
register()

bus.add_signal_receiver(
name_owner_changed,
signal_name="NameOwnerChanged",
dbus_interface="org.freedesktop.DBus"
)

def sig_handler(sig, frame):
loop.quit()

signal.signal(signal.SIGINT, sig_handler)
signal.signal(signal.SIGTERM, sig_handler)

loop.run()

if __name__ == "__main__":
main()