With release 0.9.43 I am testing the application.watcher. Depending on complexity/load of the init script, the watcher stops after several executions, hammerspoon still keeps on listening to e.g. keyboard bindings.
Down, an applescript and an init.lua is appended to reproduce the issue.
With my personal configuration the watcher stops is after 10 times switching applications. The problem occurs with the appended scripts after 400 times switching applications (yes 40 seconds to reproduce the bug).
-- applescript
repeat 100 times
tell application "System Events"
delay 0.1
key code 48 using command down -- send cmd + tab
end tell
end repeat
-- init.lua
hs.application.watcher.new(function(name,event, app) appwatch(name,event,app) end):start()
local i = 1
function appwatch(name, event, app)
local win = hs.window.focusedWindow()
if not win then
print("no window focused")
return
end
local lookuptable = {
[hs.application.watcher.activated] = "act",
[hs.application.watcher.deactivated] = "deact",
[hs.application.watcher.hidden] = "hid",
[hs.application.watcher.launched] = "lnchd",
[hs.application.watcher.launching] = "lnchng",
[hs.application.watcher.terminated] = "term",
[hs.application.watcher.unhidden] = "unhid"
}
print(i .. " event: ".. lookuptable[event] .. "; app: " .. hs.window.focusedWindow():application():title())
if event == hs.application.watcher.activated then
i = i+1
end
end
I can reproduce it using your script. I did some debugging and the events are still received internally. The call to the callback function is still made but somehow does not succeed and no error is reported.
@cmsj Any idea?
hmm, struggling to reproduce this one, I've run the test script, with 500 iterations and Hammerspoon is calling the appwatch() function each time
@mgee if you can reproduce it, we could pepper the Objective C callback method with NSLog() calls to see if there is something going on there, but if it's making the call to protectedCallAndTraceback then this is most mysterious indeed.
Depending on your machine (the memory?), even more runs could be necessary.
Temporarily I proceeded as follows to solve the issue for me.
local appwatcher = application.watcher.new(function(name,event, app)
appwatch(name,event,app)
end)
appwatcher:start()
function appwatch(name, event, app)
appwatcher:stop()
appwatcher:start()
-- ...
end
@cmsj I added some NSLog calls and the call to protectedCallAndTraceback is made and returns true, hence it should have worked.
@mgee interesting, that rather suggests that Lua itself is losing its mind. hmmmm.
In the initial example the application watcher was not stored in a variable. Then as mentioned in #774 the watcher will be garbage collected at some point.
@arolle can you verify if the bug still exists if the application watcher is stored in a variable?
@mgee good catch :)
Assigning the application watcher to a local variable, as in
local appwatcher = hs.application.watcher.new(function(name,event,app) appwatch(name,event,app) end):start()
will not resolve the issue. Whereas assigning to a (unique name) global variable, makes the watcher persistent among the dozens of tests which I ran,
-- global variable, unique name
appwatcher = hs.application.watcher.new(function(name,event,app) appwatch(name,event,app) end):start()
But garbage collection will anyways remove the – further unreferenced – global variable, stopping the watcher:
-- init.lua
appwatch = hs.application.watcher.new(function(name,event, app) appwatch(name,event,app) end):start()
local i = 1
function appwatch(name, event, app)
print(i)
if i > 9 then
collectgarbage()
end
i = i+1
end
This has recently started to happen to me consistently. Any hope of a fix soon?
@junkblocker are you capturing your watchers into variables?
My structure is:
function applicationWatcher(appName, eventType, appObject)
--
end
local appWatcher = hs.application.watcher.new(applicationWatcher)
appWatcher:start()
What's the recommendation?
If I include appWatcher in reloadConfig() afterwards, the watcher does stay around and the problem doesn't happen.
function applicationWatcher(appName, eventType, appObject)
--
end
local appWatcher = hs.application.watcher.new(applicationWatcher)
appWatcher:start()
function reloadConfig(paths, force)
--
appWatcher:stop()
appWatcher = nil
--
end
That's because appWatcher is local so it goes out of scope and gets garbage collected. You can either remove local or, as you've done, reference it somewhere else like in your global reloadConfig.
@heptal Thanks, worked for me to set it as a global variable.
Perfect, thanks!
One thought would be to possibly remove the "local" keyword in the application watcher sample code in the getting started guide? This is why it was "local" for me in the first place...I simply had taken that and modified it for my own script.
(I've removed the relevant local keywords from the GSG. Thanks!)
Most helpful comment
That's because
appWatcheris local so it goes out of scope and gets garbage collected. You can either removelocalor, as you've done, reference it somewhere else like in your globalreloadConfig.