Skip to content

Commit 005e2a1

Browse files
committed
InputSourceSwitch: add application watcher support for input source switching
Some apps like kitty-quick-access cannot be captured by window filters. This adds an alternative watcher mode using hs.application.watcher that triggers on app activation events instead. Config format now supports both string (uses window filter) and table with source and watcher type specification. Fixes #351
1 parent e5b8712 commit 005e2a1

File tree

1 file changed

+56
-19
lines changed

1 file changed

+56
-19
lines changed

Source/InputSourceSwitch.spoon/init.lua

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
---
99
--- spoon.InputSourceSwitch:setApplications({
1010
--- ["WeChat"] = "Pinyin - Simplified",
11-
--- ["Mail"] = "ABC"
11+
--- ["Mail"] = "ABC",
12+
--- -- Use application watcher for apps that window filter can't capture
13+
--- ["kitty-quick-access"] = { source = "ABC", watcher = "application" }
1214
--- })
1315
---
1416
--- spoon.InputSourceSwitch:start()
@@ -21,7 +23,7 @@ obj.__index = obj
2123

2224
-- Metadata
2325
obj.name = "InputSourceSwitch"
24-
obj.version = "1.0"
26+
obj.version = "1.1"
2527
obj.author = "eks5115 <[email protected]>"
2628
obj.homepage = "https://github.com/Hammerspoon/Spoons"
2729
obj.license = "MIT - https://opensource.org/licenses/MIT"
@@ -52,43 +54,64 @@ local function isMethod(methodName)
5254
return false
5355
end
5456

55-
local function setAppInputSource(appName, sourceName, event)
57+
local function switchInputSource(sourceName, appName)
58+
local r = true
59+
60+
if (isLayout(sourceName)) then
61+
r = hs.keycodes.setLayout(sourceName)
62+
elseif isMethod(sourceName) then
63+
r = hs.keycodes.setMethod(sourceName)
64+
else
65+
hs.alert.show(string.format('sourceName: %s is not layout or method', sourceName))
66+
end
67+
68+
if (not r) then
69+
hs.alert.show(string.format('set %s to %s failure', appName, sourceName))
70+
end
71+
end
72+
73+
local function setAppInputSourceWithWindowFilter(appName, sourceName, event)
5674
event = event or hs.window.filter.windowFocused
5775

5876
hs.window.filter.new(appName):subscribe(event, function()
59-
local r = true
60-
61-
if (isLayout(sourceName)) then
62-
r = hs.keycodes.setLayout(sourceName)
63-
elseif isMethod(sourceName) then
64-
r = hs.keycodes.setMethod(sourceName)
65-
else
66-
hs.alert.show(string.format('sourceName: %s is not layout or method', sourceName))
67-
end
77+
switchInputSource(sourceName, appName)
78+
end)
79+
end
6880

69-
if (not r) then
70-
hs.alert.show(string.format('set %s to %s failure', appName, sourceName))
81+
local function setAppInputSourceWithAppWatcher(appName, sourceName)
82+
local watcher = hs.application.watcher.new(function(name, eventType, appObj)
83+
if eventType == hs.application.watcher.activated and name == appName then
84+
switchInputSource(sourceName, appName)
7185
end
72-
end)
86+
end)
87+
watcher:start()
88+
return watcher
7389
end
7490

7591
--- InputSourceSwitch.applicationMap
7692
--- Variable
7793
--- Mapping the application name to the input source
7894
obj.applicationsMap = {}
7995

96+
-- Store application watchers to prevent garbage collection
97+
obj.appWatchers = {}
98+
8099
--- InputSourceSwitch:setApplications()
81100
--- Method
82101
--- Set that mapping the application name to the input source
83102
---
84103
--- Parameters:
85104
--- * applications - A table containing that mapping the application name to the input source
86-
--- key is the application name and value is the input source name
105+
--- key is the application name
106+
--- value can be:
107+
--- - a string: the input source name (uses window filter, default)
108+
--- - a table: { source = "input source name", watcher = "window" | "application" }
87109
--- example:
88110
--- ```
89111
--- {
90112
--- ["WeChat"] = "Pinyin - Simplified",
91-
--- ["Mail"] = "ABC"
113+
--- ["Mail"] = "ABC",
114+
--- ["kitty-quick-access"] = { source = "ABC", watcher = "application" }
92115
--- }
93116
--- ```
94117
function obj:setApplications(applications)
@@ -104,8 +127,22 @@ end
104127
--- Parameters:
105128
--- * None
106129
function obj:start()
107-
for k,v in pairs(self.applicationsMap) do
108-
setAppInputSource(k, v)
130+
for appName, config in pairs(self.applicationsMap) do
131+
local sourceName, watcherType
132+
133+
if type(config) == "string" then
134+
sourceName = config
135+
watcherType = "window"
136+
else
137+
sourceName = config.source
138+
watcherType = config.watcher or "window"
139+
end
140+
141+
if watcherType == "application" then
142+
self.appWatchers[appName] = setAppInputSourceWithAppWatcher(appName, sourceName)
143+
else
144+
setAppInputSourceWithWindowFilter(appName, sourceName)
145+
end
109146
end
110147
return self
111148
end

0 commit comments

Comments
 (0)