mirror of
https://github.com/anotherhadi/settuings.git
synced 2026-10-05 16:18:24 +02:00
@@ -0,0 +1,83 @@
|
||||
package bluetooth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
agentPath = dbus.ObjectPath("/org/anotherhadi/settuings/agent")
|
||||
agentCapability = "DisplayYesNo"
|
||||
)
|
||||
|
||||
// agent implements the BlueZ org.bluez.Agent1 interface, auto-accepting
|
||||
// every pairing prompt. `bluetoothctl pair` run non-interactively never
|
||||
// registers an agent, so BlueZ has nobody to ask for PIN/passkey
|
||||
// confirmation and any device that isn't "Just Works" fails with
|
||||
// AuthenticationFailed. Registering this agent as the system default
|
||||
// fixes that even though pairing itself still happens in a separate
|
||||
// bluetoothctl process: BlueZ routes auth requests to whichever agent is
|
||||
// registered as default, not to whoever initiated the pairing.
|
||||
type agent struct{}
|
||||
|
||||
func (agent) Release() *dbus.Error { return nil }
|
||||
|
||||
func (agent) RequestPinCode(_ dbus.ObjectPath) (string, *dbus.Error) {
|
||||
return "", dbus.NewError("org.bluez.Error.Rejected", nil)
|
||||
}
|
||||
|
||||
func (agent) DisplayPinCode(_ dbus.ObjectPath, _ string) *dbus.Error { return nil }
|
||||
|
||||
func (agent) RequestPasskey(_ dbus.ObjectPath) (uint32, *dbus.Error) {
|
||||
return 0, dbus.NewError("org.bluez.Error.Rejected", nil)
|
||||
}
|
||||
|
||||
func (agent) DisplayPasskey(_ dbus.ObjectPath, _ uint32, _ uint16) *dbus.Error { return nil }
|
||||
|
||||
func (agent) RequestConfirmation(_ dbus.ObjectPath, _ uint32) *dbus.Error { return nil }
|
||||
|
||||
func (agent) RequestAuthorization(_ dbus.ObjectPath) *dbus.Error { return nil }
|
||||
|
||||
func (agent) AuthorizeService(_ dbus.ObjectPath, _ string) *dbus.Error { return nil }
|
||||
|
||||
func (agent) Cancel() *dbus.Error { return nil }
|
||||
|
||||
var (
|
||||
agentOnce sync.Once
|
||||
agentErr error
|
||||
// agentConn is kept alive for the process lifetime: BlueZ calls back
|
||||
// into our agent over this connection while pairing is in progress.
|
||||
agentConn *dbus.Conn
|
||||
)
|
||||
|
||||
// ensureAgent registers this process as BlueZ's default pairing agent, once
|
||||
// per run. Call before any Pair.
|
||||
func ensureAgent() error {
|
||||
agentOnce.Do(func() {
|
||||
conn, err := dbus.ConnectSystemBus()
|
||||
if err != nil {
|
||||
agentErr = fmt.Errorf("connect to system bus: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := conn.Export(agent{}, agentPath, "org.bluez.Agent1"); err != nil {
|
||||
agentErr = fmt.Errorf("export agent: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
manager := conn.Object("org.bluez", dbus.ObjectPath("/org/bluez"))
|
||||
if call := manager.Call("org.bluez.AgentManager1.RegisterAgent", 0, agentPath, agentCapability); call.Err != nil {
|
||||
agentErr = fmt.Errorf("register agent: %w", call.Err)
|
||||
return
|
||||
}
|
||||
if call := manager.Call("org.bluez.AgentManager1.RequestDefaultAgent", 0, agentPath); call.Err != nil {
|
||||
agentErr = fmt.Errorf("request default agent: %w", call.Err)
|
||||
return
|
||||
}
|
||||
|
||||
agentConn = conn
|
||||
})
|
||||
return agentErr
|
||||
}
|
||||
@@ -185,10 +185,13 @@ func Scan(seconds int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Pair initiates pairing with a device. Because non-interactive
|
||||
// bluetoothctl has no agent to answer PIN/confirmation prompts, this only
|
||||
// succeeds for "just works" devices (most mice, keyboards, headsets).
|
||||
// Pair initiates pairing with a device, first registering settuings as
|
||||
// BlueZ's default agent so devices that need PIN/passkey confirmation
|
||||
// (not just "just works" ones) can complete pairing.
|
||||
func Pair(address string) error {
|
||||
if err := ensureAgent(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := run(actionTimeout, "pair", address)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user