From 72c0ccfdff393f3c27389539130dc0999d7fdfd0 Mon Sep 17 00:00:00 2001 From: Jialei Date: Fri, 11 Apr 2025 14:30:17 +0800 Subject: [PATCH 1/3] feat: support starting before the desktop session --- cmd/keyswift/main.go | 25 ++++++++++ pkg/bus/impl.go | 10 ++++ pkg/wininfo/dbus/dbus.go | 101 ++++++++++++++++++++++++++++++++++++++- pkg/wininfo/wininfo.go | 4 +- 4 files changed, 137 insertions(+), 3 deletions(-) diff --git a/cmd/keyswift/main.go b/cmd/keyswift/main.go index 69cff4f..810fc3c 100644 --- a/cmd/keyswift/main.go +++ b/cmd/keyswift/main.go @@ -8,6 +8,7 @@ import ( "os/signal" "strings" "syscall" + "time" "github.com/jialeicui/golibevdev" "github.com/samber/lo" @@ -128,6 +129,30 @@ func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + // Try to reconnect DBus in the background + go func() { + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + if _, ok := windowMonitor.(*dbus.DegradedReceiver); ok { + slog.Info("Attempting to reconnect to DBus...") + newMonitor, err := dbus.New() + if err == nil { + slog.Info("Successfully reconnected to DBus") + windowMonitor = newMonitor + // Update bus manager's window monitor + busMgr.UpdateWindowMonitor(windowMonitor) + } + } + case <-sigChan: + return + } + } + }() + go func() { <-sigChan slog.Info("Shutting down...") diff --git a/pkg/bus/impl.go b/pkg/bus/impl.go index 706694d..496cef4 100644 --- a/pkg/bus/impl.go +++ b/pkg/bus/impl.go @@ -111,3 +111,13 @@ func (m *Impl) SendKeys(keyCodes []keys.Key) { slog.Debug("SendKeys done", "input", keyCodes) } + +func (m *Impl) UpdateWindowMonitor(windowInfo wininfo.WinGetter) { + m.windowInfo = windowInfo + if windowInfo != nil { + err := windowInfo.OnActiveWindowChange(m.handleWindowFocus) + if err != nil { + slog.Error("failed to register window focus handler", "error", err) + } + } +} diff --git a/pkg/wininfo/dbus/dbus.go b/pkg/wininfo/dbus/dbus.go index d363234..55326fc 100644 --- a/pkg/wininfo/dbus/dbus.go +++ b/pkg/wininfo/dbus/dbus.go @@ -3,6 +3,10 @@ package dbus import ( "encoding/json" "fmt" + "log/slog" + "os" + "path/filepath" + "strings" "github.com/godbus/dbus/v5" "github.com/godbus/dbus/v5/introspect" @@ -61,7 +65,71 @@ func (r *Receiver) Close() { r.conn.Close() } +// getDBusAddress attempts to get the latest DBus address +func getDBusAddress() (string, error) { + // 1. First try to get from environment variable + if addr := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); addr != "" { + return addr, nil + } + + // 2. Try to get from systemd user session + uid := os.Getuid() + systemdSocket := fmt.Sprintf("/run/user/%d/bus", uid) + if _, err := os.Stat(systemdSocket); err == nil { + return fmt.Sprintf("unix:path=%s", systemdSocket), nil + } + + // 3. Try to get from session file + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("failed to get home directory: %w", err) + } + + // Get DISPLAY environment variable, use ":0" as default if not set + display := os.Getenv("DISPLAY") + if display == "" { + display = ":0" + } + + // Try to get from session file + sessionFile := filepath.Join(home, ".dbus", "session-bus", display) + if _, err := os.Stat(sessionFile); err == nil { + content, err := os.ReadFile(sessionFile) + if err != nil { + return "", fmt.Errorf("failed to read session file: %w", err) + } + + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "DBUS_SESSION_BUS_ADDRESS=") { + addr := strings.TrimPrefix(line, "DBUS_SESSION_BUS_ADDRESS=") + addr = strings.Trim(addr, "'\"") + return addr, nil + } + } + } + + // 4. Try to get from XDG_RUNTIME_DIR + if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" { + busPath := filepath.Join(runtimeDir, "bus") + if _, err := os.Stat(busPath); err == nil { + return fmt.Sprintf("unix:path=%s", busPath), nil + } + } + + return "", fmt.Errorf("failed to find DBus address") +} + func (r *Receiver) setupDBus() error { + // Get the latest DBus address + addr, err := getDBusAddress() + if err != nil { + return fmt.Errorf("failed to get DBus address: %w", err) + } + + // Set environment variable + os.Setenv("DBUS_SESSION_BUS_ADDRESS", addr) + conn, err := dbus.ConnectSessionBus() if err != nil { return err @@ -109,7 +177,32 @@ func WithActiveWindowChangeCallback(callback wininfo.ActiveWindowChangeCallback) } } -func New(opt ...Option) (*Receiver, error) { +// DegradedReceiver is a degraded mode implementation, used when DBus is unavailable +type DegradedReceiver struct { + *options + current *wininfo.WinInfo +} + +func (r *DegradedReceiver) UpdateActiveWindow(in string) *dbus.Error { + return nil +} + +func (r *DegradedReceiver) GetActiveWindow() (*wininfo.WinInfo, error) { + if r.current == nil { + return nil, fmt.Errorf("no active window") + } + return r.current, nil +} + +func (r *DegradedReceiver) Close() { +} + +func (r *DegradedReceiver) OnActiveWindowChange(callback wininfo.ActiveWindowChangeCallback) error { + r.options.onChange = callback + return nil +} + +func New(opt ...Option) (wininfo.WinGetter, error) { r := &Receiver{ options: &options{}, } @@ -120,7 +213,11 @@ func New(opt ...Option) (*Receiver, error) { err := r.setupDBus() if err != nil { - return nil, err + // If DBus connection fails, return degraded mode implementation + slog.Warn("Failed to connect to DBus, running in degraded mode", "error", err) + return &DegradedReceiver{ + options: r.options, + }, nil } return r, nil diff --git a/pkg/wininfo/wininfo.go b/pkg/wininfo/wininfo.go index 07285ec..3752428 100644 --- a/pkg/wininfo/wininfo.go +++ b/pkg/wininfo/wininfo.go @@ -7,6 +7,8 @@ type WinGetter interface { // The callback function will be called with the new active window information // The function should return an error if it fails to register the callback OnActiveWindowChange(ActiveWindowChangeCallback) error + // Close closes the window info service + Close() } type WinInfo struct { @@ -14,4 +16,4 @@ type WinInfo struct { Class string } -type ActiveWindowChangeCallback func(*WinInfo) \ No newline at end of file +type ActiveWindowChangeCallback func(*WinInfo) From f771a76f07c43e2d2269c731e6753d369327336c Mon Sep 17 00:00:00 2001 From: Jialei Date: Fri, 11 Apr 2025 21:30:12 +0800 Subject: [PATCH 2/3] break --- cmd/keyswift/main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/keyswift/main.go b/cmd/keyswift/main.go index 810fc3c..ad0c337 100644 --- a/cmd/keyswift/main.go +++ b/cmd/keyswift/main.go @@ -145,6 +145,8 @@ func main() { windowMonitor = newMonitor // Update bus manager's window monitor busMgr.UpdateWindowMonitor(windowMonitor) + // success, break + return } } case <-sigChan: From 31f94b169f5b942792db12e023338997d701ca43 Mon Sep 17 00:00:00 2001 From: Jialei Date: Fri, 11 Apr 2025 21:54:04 +0800 Subject: [PATCH 3/3] Update pkg/wininfo/dbus/dbus.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/wininfo/dbus/dbus.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/wininfo/dbus/dbus.go b/pkg/wininfo/dbus/dbus.go index 55326fc..6649ad2 100644 --- a/pkg/wininfo/dbus/dbus.go +++ b/pkg/wininfo/dbus/dbus.go @@ -195,6 +195,9 @@ func (r *DegradedReceiver) GetActiveWindow() (*wininfo.WinInfo, error) { } func (r *DegradedReceiver) Close() { + // Perform cleanup for DegradedReceiver + r.options = nil // Reset options to prevent further use + slog.Info("DegradedReceiver closed") } func (r *DegradedReceiver) OnActiveWindowChange(callback wininfo.ActiveWindowChangeCallback) error {