package main import ( "encoding/json" "fmt" "os" "git.zaphyra.eu/airpodsctl/types" "github.com/godbus/dbus/v5" ) type State struct { BusConn *dbus.Conn Signals chan *dbus.Signal Devices types.Devices SelectedDevice string DebugOutput bool OutputFormat types.OutputFormat SendNotifications bool LastNotificationId int } func InitState() (State, error) { dbusConn, errBusConn := dbus.ConnectSessionBus() if errBusConn != nil { return State{}, fmt.Errorf("[InitState] Failed to connect to session bus: %s", errBusConn) } // create state object state := State{ BusConn: dbusConn, Signals: make(chan *dbus.Signal, 10), Devices: types.Devices{}, DebugOutput: false, OutputFormat: types.FormatCLI, SendNotifications: false, LastNotificationId: 0, } // initial devices poll state.UpdateDevices() return state, nil } // Close cleans up and shuts down signal delivery loop. func (state *State) Close() error { // remove signal reception state.BusConn.RemoveSignal(state.Signals) // unregister on dbus: state.BusConn.RemoveMatchSignal( dbus.WithMatchObjectPath("/org/kairpods/manager"), ) // disconnect from dbus return state.BusConn.Close() } func (state *State) PrintDebug(message string) { if state.DebugOutput { fmt.Fprint(os.Stderr, message) } } func (state *State) UpdateDevices() error { var devicesStr string obj := state.BusConn.Object("org.kairpods", "/org/kairpods/manager") if err := obj.Call("org.kairpods.manager.GetDevices", 0).Store(&devicesStr); err != nil { return fmt.Errorf("[UpdateDevices] Error reading Devices from dbus: %s", err) } if err := json.Unmarshal([]byte(devicesStr), &state.Devices); err != nil { return fmt.Errorf("[UpdateDevices] Faild to unmarshal devices-JSON: %s", err) } return nil } func (state *State) RegisterSignals() error { // register for signals on dbus errRegisterSignals := state.BusConn.AddMatchSignal( dbus.WithMatchObjectPath("/org/kairpods/manager"), ) if errRegisterSignals != nil { return fmt.Errorf("[RegisterSignals] Failed to register for signals on dbus: %s", errRegisterSignals) } // connect bus to signals state.BusConn.Signal(state.Signals) return nil } func (state *State) SendNotify(body string, timeout int, subject string) error { if !state.SendNotifications { return nil } if subject != "" { subject = "airpodsctl (" + subject + ")" } else { subject = "airpodsctl" } obj := state.BusConn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications") err := obj.Call( "org.freedesktop.Notifications.Notify", 0, "", uint32(state.LastNotificationId), "", subject, body, []string{}, map[string]dbus.Variant{ "transient": dbus.MakeVariant(true), }, int32(timeout*1000), ).Store(&state.LastNotificationId) return err } func (state *State) SendRawCommand(device types.Device, command string) error { var returnVal bool obj := state.BusConn.Object("org.kairpods", "/org/kairpods/manager") err := obj.Call( "org.kairpods.manager.Passthrough", 0, device.Address, command, ).Store(&returnVal) if err != nil { return err } if returnVal != true { return fmt.Errorf("[SendRawCommand] Unable to send command '%s' to device '%s'", command, device.Address) } return nil } func (state *State) SetANCMode(device types.Device, ancMode types.ANCMode) error { var returnVal bool obj := state.BusConn.Object("org.kairpods", "/org/kairpods/manager") err := obj.Call( "org.kairpods.manager.SendCommand", 0, device.Address, "set_noise_mode", map[string]dbus.Variant{ "value": dbus.MakeVariant(ancMode.GetValue()), }, ).Store(&returnVal) if err != nil { return err } if returnVal != true { return fmt.Errorf("[SetANCMode] Unable to set ANC-Mode for device: %s", device.Address) } return nil }