Skip to content

Commit 476b387

Browse files
jialeicuiCopilot
andauthored
feat: support starting before the desktop session (#2)
Co-authored-by: Copilot <[email protected]>
1 parent d21ee1e commit 476b387

File tree

4 files changed

+142
-3
lines changed

4 files changed

+142
-3
lines changed

cmd/keyswift/main.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os/signal"
99
"strings"
1010
"syscall"
11+
"time"
1112

1213
"github.com/jialeicui/golibevdev"
1314
"github.com/samber/lo"
@@ -128,6 +129,32 @@ func main() {
128129
sigChan := make(chan os.Signal, 1)
129130
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
130131

132+
// Try to reconnect DBus in the background
133+
go func() {
134+
ticker := time.NewTicker(5 * time.Second)
135+
defer ticker.Stop()
136+
137+
for {
138+
select {
139+
case <-ticker.C:
140+
if _, ok := windowMonitor.(*dbus.DegradedReceiver); ok {
141+
slog.Info("Attempting to reconnect to DBus...")
142+
newMonitor, err := dbus.New()
143+
if err == nil {
144+
slog.Info("Successfully reconnected to DBus")
145+
windowMonitor = newMonitor
146+
// Update bus manager's window monitor
147+
busMgr.UpdateWindowMonitor(windowMonitor)
148+
// success, break
149+
return
150+
}
151+
}
152+
case <-sigChan:
153+
return
154+
}
155+
}
156+
}()
157+
131158
go func() {
132159
<-sigChan
133160
slog.Info("Shutting down...")

pkg/bus/impl.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,13 @@ func (m *Impl) SendKeys(keyCodes []keys.Key) {
111111

112112
slog.Debug("SendKeys done", "input", keyCodes)
113113
}
114+
115+
func (m *Impl) UpdateWindowMonitor(windowInfo wininfo.WinGetter) {
116+
m.windowInfo = windowInfo
117+
if windowInfo != nil {
118+
err := windowInfo.OnActiveWindowChange(m.handleWindowFocus)
119+
if err != nil {
120+
slog.Error("failed to register window focus handler", "error", err)
121+
}
122+
}
123+
}

pkg/wininfo/dbus/dbus.go

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package dbus
33
import (
44
"encoding/json"
55
"fmt"
6+
"log/slog"
7+
"os"
8+
"path/filepath"
9+
"strings"
610

711
"github.com/godbus/dbus/v5"
812
"github.com/godbus/dbus/v5/introspect"
@@ -61,7 +65,71 @@ func (r *Receiver) Close() {
6165
r.conn.Close()
6266
}
6367

68+
// getDBusAddress attempts to get the latest DBus address
69+
func getDBusAddress() (string, error) {
70+
// 1. First try to get from environment variable
71+
if addr := os.Getenv("DBUS_SESSION_BUS_ADDRESS"); addr != "" {
72+
return addr, nil
73+
}
74+
75+
// 2. Try to get from systemd user session
76+
uid := os.Getuid()
77+
systemdSocket := fmt.Sprintf("/run/user/%d/bus", uid)
78+
if _, err := os.Stat(systemdSocket); err == nil {
79+
return fmt.Sprintf("unix:path=%s", systemdSocket), nil
80+
}
81+
82+
// 3. Try to get from session file
83+
home, err := os.UserHomeDir()
84+
if err != nil {
85+
return "", fmt.Errorf("failed to get home directory: %w", err)
86+
}
87+
88+
// Get DISPLAY environment variable, use ":0" as default if not set
89+
display := os.Getenv("DISPLAY")
90+
if display == "" {
91+
display = ":0"
92+
}
93+
94+
// Try to get from session file
95+
sessionFile := filepath.Join(home, ".dbus", "session-bus", display)
96+
if _, err := os.Stat(sessionFile); err == nil {
97+
content, err := os.ReadFile(sessionFile)
98+
if err != nil {
99+
return "", fmt.Errorf("failed to read session file: %w", err)
100+
}
101+
102+
lines := strings.Split(string(content), "\n")
103+
for _, line := range lines {
104+
if strings.HasPrefix(line, "DBUS_SESSION_BUS_ADDRESS=") {
105+
addr := strings.TrimPrefix(line, "DBUS_SESSION_BUS_ADDRESS=")
106+
addr = strings.Trim(addr, "'\"")
107+
return addr, nil
108+
}
109+
}
110+
}
111+
112+
// 4. Try to get from XDG_RUNTIME_DIR
113+
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
114+
busPath := filepath.Join(runtimeDir, "bus")
115+
if _, err := os.Stat(busPath); err == nil {
116+
return fmt.Sprintf("unix:path=%s", busPath), nil
117+
}
118+
}
119+
120+
return "", fmt.Errorf("failed to find DBus address")
121+
}
122+
64123
func (r *Receiver) setupDBus() error {
124+
// Get the latest DBus address
125+
addr, err := getDBusAddress()
126+
if err != nil {
127+
return fmt.Errorf("failed to get DBus address: %w", err)
128+
}
129+
130+
// Set environment variable
131+
os.Setenv("DBUS_SESSION_BUS_ADDRESS", addr)
132+
65133
conn, err := dbus.ConnectSessionBus()
66134
if err != nil {
67135
return err
@@ -109,7 +177,35 @@ func WithActiveWindowChangeCallback(callback wininfo.ActiveWindowChangeCallback)
109177
}
110178
}
111179

112-
func New(opt ...Option) (*Receiver, error) {
180+
// DegradedReceiver is a degraded mode implementation, used when DBus is unavailable
181+
type DegradedReceiver struct {
182+
*options
183+
current *wininfo.WinInfo
184+
}
185+
186+
func (r *DegradedReceiver) UpdateActiveWindow(in string) *dbus.Error {
187+
return nil
188+
}
189+
190+
func (r *DegradedReceiver) GetActiveWindow() (*wininfo.WinInfo, error) {
191+
if r.current == nil {
192+
return nil, fmt.Errorf("no active window")
193+
}
194+
return r.current, nil
195+
}
196+
197+
func (r *DegradedReceiver) Close() {
198+
// Perform cleanup for DegradedReceiver
199+
r.options = nil // Reset options to prevent further use
200+
slog.Info("DegradedReceiver closed")
201+
}
202+
203+
func (r *DegradedReceiver) OnActiveWindowChange(callback wininfo.ActiveWindowChangeCallback) error {
204+
r.options.onChange = callback
205+
return nil
206+
}
207+
208+
func New(opt ...Option) (wininfo.WinGetter, error) {
113209
r := &Receiver{
114210
options: &options{},
115211
}
@@ -120,7 +216,11 @@ func New(opt ...Option) (*Receiver, error) {
120216

121217
err := r.setupDBus()
122218
if err != nil {
123-
return nil, err
219+
// If DBus connection fails, return degraded mode implementation
220+
slog.Warn("Failed to connect to DBus, running in degraded mode", "error", err)
221+
return &DegradedReceiver{
222+
options: r.options,
223+
}, nil
124224
}
125225

126226
return r, nil

pkg/wininfo/wininfo.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ type WinGetter interface {
77
// The callback function will be called with the new active window information
88
// The function should return an error if it fails to register the callback
99
OnActiveWindowChange(ActiveWindowChangeCallback) error
10+
// Close closes the window info service
11+
Close()
1012
}
1113

1214
type WinInfo struct {
1315
Title string
1416
Class string
1517
}
1618

17-
type ActiveWindowChangeCallback func(*WinInfo)
19+
type ActiveWindowChangeCallback func(*WinInfo)

0 commit comments

Comments
 (0)