half-baked re-implementation of the major parts of sdorfehs in Hammerspoon
at main 194 lines 5.5 kB view raw
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