Compare commits

...

2 Commits

Author SHA1 Message Date
27517bd2ea fix messages 2026-02-08 03:39:08 +01:00
65fed65bf2 fix pairing 2026-02-08 03:35:43 +01:00
4 changed files with 148 additions and 4 deletions

2
go.mod
View File

@@ -5,6 +5,7 @@ go 1.25.6
require (
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/godbus/dbus/v5 v5.1.0
tinygo.org/x/bluetooth v0.14.0
)
@@ -19,7 +20,6 @@ require (
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect

139
internal/ble/agent.go Normal file
View File

@@ -0,0 +1,139 @@
package ble
import (
"context"
"fmt"
"os/exec"
"time"
"github.com/godbus/dbus/v5"
)
const (
agentPath = "/org/bluez/agent/noinputnooutput"
agentInterface = "org.bluez.Agent1"
agentManager = "org.bluez.AgentManager1"
bluezService = "org.bluez"
bluezPath = "/org/bluez"
)
// NoInputNoOutputAgent implements org.bluez.Agent1 with NoInputNoOutput capability.
// This disables pairing prompts and allows "Just Works" pairing.
type NoInputNoOutputAgent struct{}
// Release is called when the agent is unregistered.
func (a *NoInputNoOutputAgent) Release() *dbus.Error {
return nil
}
// RequestPinCode returns an error as PIN codes are not supported with NoInputNoOutput.
func (a *NoInputNoOutputAgent) RequestPinCode(device dbus.ObjectPath) (string, *dbus.Error) {
return "", dbus.NewError("org.bluez.Error.Rejected", []any{"NoInputNoOutput agent"})
}
// DisplayPinCode does nothing as we have no display.
func (a *NoInputNoOutputAgent) DisplayPinCode(device dbus.ObjectPath, pincode string) *dbus.Error {
return nil
}
// RequestPasskey returns an error as passkeys are not supported with NoInputNoOutput.
func (a *NoInputNoOutputAgent) RequestPasskey(device dbus.ObjectPath) (uint32, *dbus.Error) {
return 0, dbus.NewError("org.bluez.Error.Rejected", []any{"NoInputNoOutput agent"})
}
// DisplayPasskey does nothing as we have no display.
func (a *NoInputNoOutputAgent) DisplayPasskey(device dbus.ObjectPath, passkey uint32, entered uint16) *dbus.Error {
return nil
}
// RequestConfirmation auto-accepts pairing for "Just Works" mode.
func (a *NoInputNoOutputAgent) RequestConfirmation(device dbus.ObjectPath, passkey uint32) *dbus.Error {
return nil // Accept pairing silently
}
// RequestAuthorization auto-accepts authorization for "Just Works" mode.
func (a *NoInputNoOutputAgent) RequestAuthorization(device dbus.ObjectPath) *dbus.Error {
return nil // Accept authorization silently
}
// AuthorizeService auto-accepts service authorization.
func (a *NoInputNoOutputAgent) AuthorizeService(device dbus.ObjectPath, uuid string) *dbus.Error {
return nil // Accept service authorization silently
}
// Cancel is called when an operation is canceled.
func (a *NoInputNoOutputAgent) Cancel() *dbus.Error {
return nil
}
// RegisterNoInputNoOutputAgent connects to system DBus and registers a NoInputNoOutput
// agent with BlueZ. This disables pairing prompts for BLE connections.
func RegisterNoInputNoOutputAgent() error {
conn, err := dbus.SystemBus()
if err != nil {
return fmt.Errorf("failed to connect to system DBus: %w", err)
}
agent := &NoInputNoOutputAgent{}
// Export the agent object on DBus
err = conn.Export(agent, agentPath, agentInterface)
if err != nil {
return fmt.Errorf("failed to export agent: %w", err)
}
// Get the AgentManager interface
agentMgr := conn.Object(bluezService, bluezPath)
// Register our agent with NoInputNoOutput capability
call := agentMgr.Call(agentManager+".RegisterAgent", 0, dbus.ObjectPath(agentPath), "NoInputNoOutput")
if call.Err != nil {
return fmt.Errorf("failed to register agent: %w", call.Err)
}
// Request to be the default agent
call = agentMgr.Call(agentManager+".RequestDefaultAgent", 0, dbus.ObjectPath(agentPath))
if call.Err != nil {
return fmt.Errorf("failed to set default agent: %w", call.Err)
}
return nil
}
// SetAdapterNotPairable sets the Pairable property to false on the default adapter.
// This is optional and prevents the adapter from initiating pairing.
func SetAdapterNotPairable() error {
conn, err := dbus.SystemBus()
if err != nil {
return fmt.Errorf("failed to connect to system DBus: %w", err)
}
// Get the default adapter (hci0)
adapter := conn.Object(bluezService, "/org/bluez/hci0")
// Set Pairable to false using org.freedesktop.DBus.Properties interface
call := adapter.Call("org.freedesktop.DBus.Properties.Set", 0,
"org.bluez.Adapter1", "Pairable", dbus.MakeVariant(false))
if call.Err != nil {
return fmt.Errorf("failed to set Pairable: %w", call.Err)
}
return nil
}
// DisableBonding runs btmgmt to disable bonding on the default adapter.
// This prevents phones from initiating pairing requests.
func DisableBonding() error {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "btmgmt", "-i", "0", "bondable", "off")
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
return fmt.Errorf("btmgmt timed out")
}
if err != nil {
return fmt.Errorf("failed to disable bonding: %w (output: %s)", err, output)
}
return nil
}

View File

@@ -56,13 +56,13 @@ func (s *DeviceState) handleWriteCommand(cmd string) string {
return "ERRORE: I comandi 'W' sono accettati solo in modalità programmazione."
}
// Parse command format: "W<id> <value>"
parts := strings.Fields(cmd)
// Parse command format: "W<id>=<value>"
parts := strings.SplitN(cmd, "=", 2)
if len(parts) != 2 {
return "ERRORE: Formato comando non valido"
}
// Extract parameter ID
// Extract parameter ID (remove "W" prefix from first part)
idStr := strings.TrimPrefix(parts[0], "W")
id, err := strconv.Atoi(idStr)
if err != nil {

View File

@@ -70,6 +70,11 @@ func (l *LogBuffer) Err(format string, args ...any) {
l.Log("ERR", fmt.Sprintf(format, args...))
}
// Warn logs a WARN level message
func (l *LogBuffer) Warn(format string, args ...any) {
l.Log("WARN", fmt.Sprintf(format, args...))
}
// Entries returns a copy of all log entries
func (l *LogBuffer) Entries() []LogEntry {
l.mu.RLock()