Skip to content

Commit 30c4879

Browse files
committed
feat: ignore window change in the same app to reduce change event
1 parent 029807d commit 30c4879

File tree

3 files changed

+38
-139
lines changed

3 files changed

+38
-139
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ test:
5555
@go test ./...
5656

5757
# Run with development config
58-
run-dev: build
58+
run-dev: clean build
5959
@echo "Running with development config..."
6060
@./$(BUILD_DIR)/$(BINARY_NAME) --config=./$(CONFIG_DIR)/default.yaml --log-level=debug
6161

internal/app/app.go

Lines changed: 7 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ package app
22

33
import (
44
"context"
5-
"encoding/json"
65
"fmt"
76
"os"
8-
"os/exec"
97
"os/signal"
10-
"regexp"
11-
"strings"
128
"syscall"
13-
"time"
149

1510
"hypr-input-switcher/internal/config"
1611
"hypr-input-switcher/internal/inputmethod"
@@ -22,8 +17,6 @@ import (
2217
type Application struct {
2318
config *config.Config
2419
configManager *config.Manager
25-
currentClient string
26-
currentIM string
2720
switcher *inputmethod.Switcher
2821
notifier *notification.Notifier
2922
watchConfig bool
@@ -54,6 +47,9 @@ func (app *Application) Run(configPath string, watchConfig bool) error {
5447
app.switcher = inputmethod.NewSwitcher(cfg)
5548
app.notifier = notification.NewNotifier(cfg)
5649

50+
// Set notifier for switcher
51+
app.switcher.SetNotifier(app.notifier)
52+
5753
// Register config change callback
5854
app.configManager.AddCallback(app.onConfigChanged)
5955

@@ -91,8 +87,8 @@ func (app *Application) Run(configPath string, watchConfig bool) error {
9187
cancel()
9288
}()
9389

94-
// Start monitoring
95-
return app.monitorAndSwitch(ctx)
90+
// Use switcher's monitoring loop instead of our own
91+
return app.switcher.MonitorAndSwitch(ctx)
9692
}
9793

9894
// onConfigChanged handles configuration changes
@@ -108,124 +104,8 @@ func (app *Application) onConfigChanged(newConfig *config.Config) {
108104
// Recreate notifier with new config
109105
app.notifier = notification.NewNotifier(newConfig)
110106

111-
// Reset current client to force re-evaluation
112-
app.currentClient = ""
107+
// Set notifier for switcher
108+
app.switcher.SetNotifier(app.notifier)
113109

114110
logger.Info("Configuration applied successfully")
115111
}
116-
117-
// getCurrentClient gets current active window information
118-
func (app *Application) getCurrentClient() (*config.WindowInfo, error) {
119-
cmd := exec.Command("hyprctl", "activewindow", "-j")
120-
output, err := cmd.Output()
121-
if err != nil {
122-
return nil, fmt.Errorf("failed to get active window: %w", err)
123-
}
124-
125-
var windowInfo config.WindowInfo
126-
if err := json.Unmarshal(output, &windowInfo); err != nil {
127-
return nil, fmt.Errorf("failed to parse window info: %w", err)
128-
}
129-
130-
return &windowInfo, nil
131-
}
132-
133-
// getTargetInputMethod determines target input method based on client information
134-
func (app *Application) getTargetInputMethod(clientInfo *config.WindowInfo) string {
135-
// Get current config (thread-safe)
136-
currentConfig := app.configManager.GetConfig()
137-
138-
if clientInfo == nil {
139-
return currentConfig.DefaultInputMethod
140-
}
141-
142-
className := clientInfo.Class
143-
title := clientInfo.Title
144-
145-
// Check client rules
146-
for _, rule := range currentConfig.ClientRules {
147-
// Match class (required)
148-
if rule.Class == "" || !app.matchPattern(rule.Class, className) {
149-
continue
150-
}
151-
152-
// If title is empty or not specified, class match is enough
153-
if rule.Title == "" {
154-
return rule.InputMethod
155-
}
156-
157-
// If title is specified, both class and title must match
158-
if app.matchPattern(rule.Title, title) {
159-
return rule.InputMethod
160-
}
161-
}
162-
163-
return currentConfig.DefaultInputMethod
164-
}
165-
166-
// matchPattern matches pattern, supporting regex and string matching
167-
func (app *Application) matchPattern(pattern, text string) bool {
168-
if pattern == "" || text == "" {
169-
return false
170-
}
171-
172-
// Try as regex first
173-
if matched, err := regexp.MatchString(pattern, text); err == nil {
174-
return matched
175-
}
176-
177-
// Fallback to string contains matching
178-
return strings.Contains(strings.ToLower(text), strings.ToLower(pattern))
179-
}
180-
181-
// monitorAndSwitch main monitoring loop
182-
func (app *Application) monitorAndSwitch(ctx context.Context) error {
183-
ticker := time.NewTicker(200 * time.Millisecond)
184-
defer ticker.Stop()
185-
186-
for {
187-
select {
188-
case <-ctx.Done():
189-
return nil
190-
191-
case <-ticker.C:
192-
// Get current active window
193-
clientInfo, err := app.getCurrentClient()
194-
if err != nil {
195-
logger.Debugf("Failed to get current client: %v", err)
196-
continue
197-
}
198-
199-
currentClient := fmt.Sprintf("%s:%s", clientInfo.Class, clientInfo.Title)
200-
201-
// If window changed
202-
if currentClient != app.currentClient {
203-
app.currentClient = currentClient
204-
205-
// Get current input method status
206-
currentIM := app.switcher.GetCurrent()
207-
208-
// Determine target input method
209-
targetIM := app.getTargetInputMethod(clientInfo)
210-
211-
logger.Infof("Window changed: %s - %s", clientInfo.Class, clientInfo.Title)
212-
logger.Infof("Current IM: %s -> Target IM: %s", currentIM, targetIM)
213-
214-
// If input method needs to be switched
215-
if currentIM != targetIM && currentIM != "unknown" {
216-
if err := app.switcher.Switch(targetIM); err != nil {
217-
logger.Errorf("Failed to switch input method to %s: %v", targetIM, err)
218-
} else {
219-
logger.Infof("Switched input method to: %s", targetIM)
220-
221-
// Show notification (use current config)
222-
currentConfig := app.configManager.GetConfig()
223-
if currentConfig.Notifications.ShowOnSwitch {
224-
app.notifier.ShowInputMethodSwitch(targetIM, clientInfo)
225-
}
226-
}
227-
}
228-
}
229-
}
230-
}
231-
}

internal/inputmethod/switcher.go

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ import (
1414
)
1515

1616
type Switcher struct {
17-
currentClient string
17+
currentClient *ClientInfo
1818
currentIM string
1919
config *config.Config
2020
fcitx5 *Fcitx5
2121
rime *Rime
22+
notifier interface {
23+
ShowInputMethodSwitch(inputMethod string, clientInfo *config.WindowInfo)
24+
} // Add notifier interface
2225
}
2326

2427
type ClientInfo struct {
25-
Class string `json:"class"`
26-
Title string `json:"title"`
28+
Address string `json:"address"`
29+
Class string `json:"class"`
30+
Title string `json:"title"`
2731
}
2832

2933
func NewSwitcher(cfg *config.Config) *Switcher {
3034
switcher := &Switcher{
31-
currentClient: "",
35+
currentClient: &ClientInfo{},
3236
currentIM: "",
3337
config: cfg,
3438
}
@@ -42,6 +46,13 @@ func NewSwitcher(cfg *config.Config) *Switcher {
4246
return switcher
4347
}
4448

49+
// SetNotifier sets the notifier for the switcher
50+
func (s *Switcher) SetNotifier(notifier interface {
51+
ShowInputMethodSwitch(inputMethod string, clientInfo *config.WindowInfo)
52+
}) {
53+
s.notifier = notifier
54+
}
55+
4556
func (s *Switcher) MonitorAndSwitch(ctx context.Context) error {
4657
logger.Info("Starting Hyprland input method switcher...")
4758

@@ -68,19 +79,17 @@ func (s *Switcher) processCurrentWindow() error {
6879
return fmt.Errorf("failed to get current client: %w", err)
6980
}
7081

71-
currentClient := fmt.Sprintf("%s:%s", clientInfo.Class, clientInfo.Title)
72-
73-
// If window changed
74-
if currentClient != s.currentClient {
75-
s.currentClient = currentClient
82+
// If window changed (different address means different window)
83+
if clientInfo.Address != s.currentClient.Address {
84+
s.currentClient = clientInfo
7685

7786
// Get current input method status
7887
currentIM := s.GetCurrent()
7988

8089
// Determine target input method
8190
targetIM := s.getTargetInputMethod(clientInfo)
8291

83-
logger.Infof("Window changed: %s - %s", clientInfo.Class, clientInfo.Title)
92+
logger.Infof("Window changed: %s - %s (address: %s)", clientInfo.Class, clientInfo.Title, clientInfo.Address)
8493
logger.Infof("Current IM: %s -> Target IM: %s", currentIM, targetIM)
8594

8695
// If input method needs to be switched
@@ -91,6 +100,16 @@ func (s *Switcher) processCurrentWindow() error {
91100

92101
logger.Infof("Switched input method to: %s", targetIM)
93102
s.currentIM = targetIM
103+
104+
// Show notification if notifier is available and enabled
105+
if s.notifier != nil && s.config.Notifications.ShowOnSwitch {
106+
// Convert ClientInfo to config.WindowInfo
107+
windowInfo := &config.WindowInfo{
108+
Class: clientInfo.Class,
109+
Title: clientInfo.Title,
110+
}
111+
s.notifier.ShowInputMethodSwitch(targetIM, windowInfo)
112+
}
94113
}
95114
}
96115

@@ -228,7 +247,7 @@ func (s *Switcher) IsReady() bool {
228247
// GetStatus returns current status information
229248
func (s *Switcher) GetStatus() map[string]interface{} {
230249
status := map[string]interface{}{
231-
"current_client": s.currentClient,
250+
"current_client": s.currentClient, // Now contains the window address
232251
"current_im": s.currentIM,
233252
"fcitx5_enabled": s.config.Fcitx5.Enabled,
234253
"ready": s.IsReady(),

0 commit comments

Comments
 (0)