package freedesktop import ( "context" "fmt" "os" "sync" "github.com/godbus/dbus/v5" ) func NewManager() (*Manager, error) { systemConn, err := dbus.ConnectSystemBus() if err != nil { return nil, fmt.Errorf("failed to connect to system bus: %w", err) } sessionConn, err := dbus.ConnectSessionBus() if err != nil { sessionConn = nil } m := &Manager{ state: &FreedeskState{ Accounts: AccountsState{}, Settings: SettingsState{}, }, stateMutex: sync.RWMutex{}, systemConn: systemConn, sessionConn: sessionConn, currentUID: uint64(os.Getuid()), subscribers: make(map[string]chan FreedeskState), subMutex: sync.RWMutex{}, } m.initializeAccounts() m.initializeSettings() return m, nil } func (m *Manager) initializeAccounts() error { accountsManager := m.systemConn.Object(dbusAccountsDest, dbus.ObjectPath(dbusAccountsPath)) var userPath dbus.ObjectPath err := accountsManager.Call(dbusAccountsInterface+".FindUserById", 0, int64(m.currentUID)).Store(&userPath) if err != nil { m.stateMutex.Lock() m.state.Accounts.Available = false m.stateMutex.Unlock() return err } m.accountsObj = m.systemConn.Object(dbusAccountsDest, userPath) m.stateMutex.Lock() m.state.Accounts.Available = true m.state.Accounts.UserPath = string(userPath) m.state.Accounts.UID = m.currentUID m.stateMutex.Unlock() if err := m.updateAccountsState(); err != nil { return fmt.Errorf("failed to update accounts state: %w", err) } return nil } func (m *Manager) initializeSettings() error { if m.sessionConn == nil { m.stateMutex.Lock() m.state.Settings.Available = false m.stateMutex.Unlock() return fmt.Errorf("no session bus connection") } m.settingsObj = m.sessionConn.Object(dbusPortalDest, dbus.ObjectPath(dbusPortalPath)) var variant dbus.Variant err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant) if err != nil { m.stateMutex.Lock() m.state.Settings.Available = false m.stateMutex.Unlock() return err } m.stateMutex.Lock() m.state.Settings.Available = true m.stateMutex.Unlock() if err := m.updateSettingsState(); err != nil { return fmt.Errorf("failed to update settings state: %w", err) } return nil } func (m *Manager) updateAccountsState() error { if !m.state.Accounts.Available || m.accountsObj == nil { return fmt.Errorf("accounts service not available") } ctx := context.Background() props, err := m.getAccountProperties(ctx) if err != nil { return err } m.stateMutex.Lock() defer m.stateMutex.Unlock() if v, ok := props["IconFile"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.IconFile = val } } if v, ok := props["RealName"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.RealName = val } } if v, ok := props["UserName"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.UserName = val } } if v, ok := props["AccountType"]; ok { if val, ok := v.Value().(int32); ok { m.state.Accounts.AccountType = val } } if v, ok := props["HomeDirectory"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.HomeDirectory = val } } if v, ok := props["Shell"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.Shell = val } } if v, ok := props["Email"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.Email = val } } if v, ok := props["Language"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.Language = val } } if v, ok := props["Location"]; ok { if val, ok := v.Value().(string); ok { m.state.Accounts.Location = val } } if v, ok := props["Locked"]; ok { if val, ok := v.Value().(bool); ok { m.state.Accounts.Locked = val } } if v, ok := props["PasswordMode"]; ok { if val, ok := v.Value().(int32); ok { m.state.Accounts.PasswordMode = val } } return nil } func (m *Manager) updateSettingsState() error { if !m.state.Settings.Available || m.settingsObj == nil { return fmt.Errorf("settings portal not available") } var variant dbus.Variant err := m.settingsObj.Call(dbusPortalSettingsInterface+".ReadOne", 0, "org.freedesktop.appearance", "color-scheme").Store(&variant) if err != nil { return err } if colorScheme, ok := variant.Value().(uint32); ok { m.stateMutex.Lock() m.state.Settings.ColorScheme = colorScheme m.stateMutex.Unlock() } return nil } func (m *Manager) getAccountProperties(ctx context.Context) (map[string]dbus.Variant, error) { var props map[string]dbus.Variant err := m.accountsObj.CallWithContext(ctx, dbusPropsInterface+".GetAll", 0, dbusAccountsUserInterface).Store(&props) if err != nil { return nil, err } return props, nil } func (m *Manager) GetState() FreedeskState { m.stateMutex.RLock() defer m.stateMutex.RUnlock() return *m.state } func (m *Manager) Subscribe(id string) chan FreedeskState { ch := make(chan FreedeskState, 64) m.subMutex.Lock() m.subscribers[id] = ch m.subMutex.Unlock() return ch } func (m *Manager) Unsubscribe(id string) { m.subMutex.Lock() if ch, ok := m.subscribers[id]; ok { close(ch) delete(m.subscribers, id) } m.subMutex.Unlock() } func (m *Manager) NotifySubscribers() { m.subMutex.RLock() defer m.subMutex.RUnlock() state := m.GetState() for _, ch := range m.subscribers { select { case ch <- state: default: } } } func (m *Manager) Close() { m.subMutex.Lock() for id, ch := range m.subscribers { close(ch) delete(m.subscribers, id) } m.subMutex.Unlock() if m.systemConn != nil { m.systemConn.Close() } if m.sessionConn != nil { m.sessionConn.Close() } }