half-baked re-implementation of the major parts of sdorfehs in Hammerspoon
1-- callback for watch_app() when a window has been created
2spoonfish.ignore_events = false
3spoonfish.app_event = function(element, event)
4 if spoonfish.ignore_events then
5 return
6 end
7
8 if element == nil then
9 spoonfish.log.e("app_event got nil element for " .. hs.inspect(event))
10 return
11 end
12
13 if event == spoonfish.events.windowCreated then
14 spoonfish.watch_hswindow(element)
15 elseif event == spoonfish.events.focusedWindowChanged then
16 local win = spoonfish.window_find_by_id(element:id())
17 if win then
18 -- TODO: don't do this when it's in response to a window destroying
19 spoonfish.frame_focus(win["space"], win["frame"], false)
20 end
21 elseif event == spoonfish.events.windowResized then
22 local win = spoonfish.window_find_by_id(element:id())
23 if win then
24 spoonfish.window_reframe(win)
25 end
26 end
27end
28
29-- callback for .app_watcher, informing about a new or closed app
30spoonfish.app_meta_event = function(name, event, hsapp)
31 if event == hs.application.watcher.launched then
32 spoonfish.watch_app(hsapp)
33 elseif event == hs.application.watcher.terminated then
34 spoonfish.log.i("app " .. hsapp:pid() .. " terminated")
35
36 local app = spoonfish.apps[hsapp:pid()]
37 if app == nil then
38 return
39 end
40
41 app["watcher"]:stop()
42
43 -- checking w["win"]:application() will probably fail by this point
44 for _, w in ipairs(spoonfish.windows) do
45 if w["app_pid"] == hsapp:pid() then
46 spoonfish.log.i("cleaning up window " .. w["win"]:title())
47 spoonfish.window_remove(w)
48 end
49 end
50
51 spoonfish.apps[hsapp:pid()] = nil
52 end
53end
54
55-- watch an application to be notififed when it creates a new window
56spoonfish.watch_app = function(hsapp)
57 if spoonfish.apps[hsapp:pid()] then
58 return
59 end
60
61 local matched = false
62 for _, p in pairs(spoonfish.apps_to_watch) do
63 if p == "" then
64 matched = true
65 break
66 end
67 if not p:find("^%^") then
68 p = spoonfish.escape_pattern(p)
69 end
70 if hsapp:title():find(p) then
71 matched = true
72 break
73 end
74 end
75 for _, p in pairs(spoonfish.apps_to_ignore) do
76 if p == "" then
77 matched = false
78 break
79 end
80 if not p:find("^%^") then
81 p = spoonfish.escape_pattern(p)
82 end
83 if hsapp:title():find(p) then
84 matched = false
85 break
86 end
87 end
88 if not matched then
89 spoonfish.log.i("ignoring app[" .. hsapp:pid() .. "] " .. hsapp:title() ..
90 " (" .. hsapp:name() .. ")")
91 return
92 end
93
94 spoonfish.log.i("watching app[" .. hsapp:pid() .. "] " .. hsapp:title() ..
95 " (" .. hsapp:name() .. ")")
96
97 local watcher = hsapp:newWatcher(spoonfish.app_event)
98 spoonfish.apps[hsapp:pid()] = {
99 watcher = watcher,
100 }
101 watcher:start({
102 spoonfish.events.windowCreated,
103 spoonfish.events.windowMoved,
104 spoonfish.events.windowResized,
105 spoonfish.events.focusedWindowChanged,
106 })
107
108 -- watch windows that already exist
109 local wf = hs.window.filter.new(hsapp:name())
110 for _, w in pairs(wf:getWindows()) do
111 spoonfish.watch_hswindow(w)
112 end
113end
114
115-- watch a hs.window object to be notified when it is closed or moved
116spoonfish.watch_hswindow = function(hswin)
117 if hswin == nil or not hswin:isStandard() then
118 return
119 end
120
121 -- if the window looks like a dialog, ignore it
122 if hswin:size().w < (hs.screen.mainScreen():frame().w / 3) and
123 hswin:size().h < (hs.screen.mainScreen():frame().h / 2) then
124 spoonfish.log.i(" ignoring dialog-looking window " .. hswin:title())
125 -- but we can at least try to help
126 hswin:centerOnScreen()
127 return
128 end
129
130 for _, p in pairs(spoonfish.windows_to_ignore) do
131 if not p:find("^%^") then
132 p = spoonfish.escape_pattern(p)
133 end
134 if hswin:title():find(p) then
135 spoonfish.log.i(" ignoring window " .. hswin:title() .. ", matches " .. p)
136 return
137 end
138 end
139
140 -- this is unfortunate but there's no space info in the window object
141 local w_space = hs.spaces.activeSpaceOnScreen()
142 for _, space_id in
143 pairs(hs.spaces.spacesForScreen(hs.screen.mainScreen():getUUID())) do
144 local wins = hs.spaces.windowsForSpace(space_id)
145
146 for _, w in pairs(wins) do
147 if w == hswin:id() then
148 w_space = space_id
149 break
150 end
151 end
152 end
153
154 spoonfish.log.i(" space[" .. w_space .. "]: watching window " .. hswin:id() ..
155 ": " .. hswin:title())
156 local watcher = hswin:newWatcher(spoonfish.window_event, { id = hswin:id() })
157 watcher:start({
158 spoonfish.events.elementDestroyed,
159 spoonfish.events.windowResized,
160 spoonfish.events.windowMoved,
161 })
162
163 spoonfish.frame_capture(w_space, spoonfish.spaces[w_space].frame_current, hswin)
164end
165
166-- callback for watch_hswindow() when a window has been closed or moved
167spoonfish.window_event = function(hswin, event, watcher, info)
168 if not spoonfish.initialized then
169 return
170 end
171
172 if event == spoonfish.events.elementDestroyed then
173 local win = spoonfish.window_find_by_id(info["id"])
174 watcher:stop()
175 if win ~= nil then
176 spoonfish.window_remove(win)
177
178 -- stay on this frame
179 spoonfish.frame_focus(win["space"], win["frame"], false)
180 end
181 end
182end
183
184-- callback from hs.spaces.watcher
185spoonfish.spaces_event = function(new_space)
186 if new_space == -1 then
187 new_space = hs.spaces.activeSpaceOnScreen()
188 end
189
190 if spoonfish.spaces[new_space] then
191 spoonfish.frame_focus(new_space, spoonfish.spaces[new_space].frame_current,
192 true)
193 end
194end