Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cmd/keyswift/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os/signal"
"strings"
"syscall"
"time"

"github.com/jialeicui/golibevdev"
"github.com/samber/lo"
Expand Down Expand Up @@ -128,6 +129,32 @@ 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)
// success, break
return
}
}
case <-sigChan:
return
}
}
}()

go func() {
<-sigChan
slog.Info("Shutting down...")
Expand Down
10 changes: 10 additions & 0 deletions pkg/bus/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
104 changes: 102 additions & 2 deletions pkg/wininfo/dbus/dbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -109,7 +177,35 @@ 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() {
// 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 {
r.options.onChange = callback
return nil
}

func New(opt ...Option) (wininfo.WinGetter, error) {
r := &Receiver{
options: &options{},
}
Expand All @@ -120,7 +216,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
Expand Down
4 changes: 3 additions & 1 deletion pkg/wininfo/wininfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ 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 {
Title string
Class string
}

type ActiveWindowChangeCallback func(*WinInfo)
type ActiveWindowChangeCallback func(*WinInfo)