Skip to content

Commit 4bef182

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents ad231bd + 950a7b1 commit 4bef182

File tree

2 files changed

+35
-30
lines changed

2 files changed

+35
-30
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
The MIT License (MIT)
22

3-
Copyright (c) 2014-2017 [Various Contributors](https://github.com/Hammerspoon/hammerspoon/graphs/contributors)
3+
Copyright (c) 2014-2025 [Various Contributors](https://github.com/Hammerspoon/hammerspoon/graphs/contributors)
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

extensions/window/window_filter.lua

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -166,9 +166,10 @@ function windowfilter._showCandidates()
166166
local t={}
167167
for _,app in ipairs(running) do
168168
local appname = app:name()
169+
local pid = app:pid()
169170
if appname and windowfilter.isGuiApp(appname) and #app:allWindows()==0
170171
and not windowfilter.ignoreInDefaultFilter[appname] and app:kind()>=0
171-
and (not apps[appname] or not next(apps[appname].windows)) then
172+
and (not apps[pid] or not next(apps[pid].windows)) then
172173
t[#t+1]=appname
173174
end
174175
end
@@ -277,17 +278,17 @@ function WF:isWindowAllowed(theWindow)
277278
--which allegedly is a window, but without id
278279
if not id then return end
279280
if activeInstances[self] then return self.windows[id] and true or false end
280-
local appname,win=theWindow:application():name()
281-
if apps[appname] then
282-
for wid,w in pairs(apps[appname].windows) do
281+
local pid,win=theWindow:application():pid()
282+
if apps[pid] then
283+
for wid,w in pairs(apps[pid].windows) do
283284
if wid==id then win=w break end
284285
end
285286
end
286287
if not win then
287288
-- hs.assert(not global.watcher,'window not being tracked')
288289
self.log.d('window is not being tracked')
289290
win=Window.new(theWindow,id) --fixme
290-
win.app={} win.app.name=appname
291+
win.app={} win.app.name=theWindow:application():name()
291292
if self.trackSpacesFilters then
292293
win.isInCurrentSpace=false
293294
if not win.isVisible then win.isInCurrentSpace=true
@@ -1205,7 +1206,7 @@ function App:getFocused()
12051206
if not self.windows[fwid] then
12061207
-- windows on a different space aren't picked up by :allWindows() at first refresh
12071208
log.df('%s (%d) was not registered',self.name,fwid)
1208-
appWindowEvent(fw,uiwatcher.windowCreated,nil,self.name)
1209+
appWindowEvent(fw,uiwatcher.windowCreated,nil,self.pid)
12091210
end
12101211
if not self.windows[fwid] then
12111212
log.wf('%s (%d) is STILL not registered',self.name,fwid)
@@ -1215,13 +1216,13 @@ function App:getFocused()
12151216
end
12161217
end
12171218

1218-
function App.new(app,appname,watcher)
1219-
local o = setmetatable({app=app,name=appname,watcher=watcher,windows={}},{__index=App})
1219+
function App.new(app,appname,pid,watcher)
1220+
local o = setmetatable({app=app,name=appname,pid=pid,watcher=watcher,windows={}},{__index=App})
12201221
if app:isHidden() then o.isHidden=true end
12211222
-- TODO if a way is found to fecth *all* windows across spaces, add it here
12221223
-- and remove .switchedToSpace, .forceRefreshOnSpaceChange
12231224
log.f('new app %s registered',appname)
1224-
apps[appname] = o
1225+
apps[pid] = o
12251226
o:getAppWindows()
12261227
end
12271228

@@ -1261,7 +1262,7 @@ function App:getCurrentSpaceAppWindows(inserted)
12611262
for _,win in ipairs(allWindows) do
12621263
local id=win:id()
12631264
if id then
1264-
if not self.windows[id] then appWindowEvent(win,uiwatcher.windowCreated,nil,self.name) end
1265+
if not self.windows[id] then appWindowEvent(win,uiwatcher.windowCreated,nil,self.pid) end
12651266
gone[id]=nil
12661267
arrived[id]=self.windows[id]
12671268
end
@@ -1310,7 +1311,7 @@ function App:focusChanged(id,win)
13101311
else
13111312
if not self.windows[id] then
13121313
log.df('%s (%d) is not registered yet',self.name,id)
1313-
appWindowEvent(win,uiwatcher.windowCreated,nil,self.name)
1314+
appWindowEvent(win,uiwatcher.windowCreated,nil,self.pid)
13141315
end
13151316
if self==active then
13161317
if not self.windows[id] then log.wf('cannot process focus changed for app %s - %s (%d) not registered',self.name,win:role(),id)
@@ -1344,11 +1345,12 @@ function App:destroyed()
13441345
for _,win in pairs(self.windows) do
13451346
win:destroyed()
13461347
end
1347-
apps[self.name]=nil
1348+
apps[self.pid]=nil
13481349
end
13491350

1350-
local function windowEvent(event,appname,id)
1351-
local app=apps[appname]
1351+
local function windowEvent(event,pid,id)
1352+
local app = apps[pid]
1353+
local appname = app.name
13521354
log.vf('%s (%s) <= %s (window event)',appname,id or '?',event)
13531355
if not id then return log.df('%s: %s cannot be processed',appname,event) end
13541356
if not app then return log.df('app %s is not registered!',appname) end
@@ -1371,8 +1373,9 @@ end
13711373
local RETRY_DELAY,MAX_RETRIES = 0.2,5
13721374
local windowWatcherDelayed={}
13731375

1374-
appWindowEvent=function(win,event,_,appname,retry)
1376+
appWindowEvent=function(win,event,_,pid,retry)
13751377
if not win:isWindow() then return end
1378+
local appname = application.applicationForPID(pid):name()
13761379
local role=win.subrole and win:subrole()
13771380
if appname=='Hammerspoon' and (not role or role=='AXUnknown') then return end
13781381
local id = win.id and win:id()
@@ -1382,34 +1385,35 @@ appWindowEvent=function(win,event,_,appname,retry)
13821385
retry=(retry or 0)+1
13831386
if not id then
13841387
if retry>MAX_RETRIES then log.wf('%s: %s has no id',appname,role or (win.role and win:role()) or 'window')
1385-
else windowWatcherDelayed[win]=timer.doAfter(retry*RETRY_DELAY,function()appWindowEvent(win,event,_,appname,retry)end) end
1388+
else windowWatcherDelayed[win]=timer.doAfter(retry*RETRY_DELAY,function()appWindowEvent(win,event,_,pid,retry)end) end
13861389
return
13871390
end
1388-
if apps[appname].windows[id] then return log.df('%s (%d) already registered',appname,id) end
1391+
if apps[pid].windows[id] then return log.df('%s (%d) already registered',appname,id) end
13891392
local watcher=win:newWatcher(function(_,watcherEvent)
1390-
windowEvent(watcherEvent,appname,id)
1391-
end, appname)
1393+
windowEvent(watcherEvent,pid,id)
1394+
end)
13921395
-- pretty sure this is a NOP now since pid capture is no longer delayed
13931396
-- if not watcher:pid() then
13941397
-- log.wf('%s: %s has no watcher pid',appname,role or (win.role and win:role()))
13951398
-- if retry>MAX_RETRIES then log.df('%s: %s has no watcher pid',appname,win.subrole and win:subrole() or (win.role and win:role()) or 'window')
13961399
-- else
1397-
-- windowWatcherDelayed[win]=timer.doAfter(retry*RETRY_DELAY,function()appWindowEvent(win,event,_,appname,retry)end) end
1400+
-- windowWatcherDelayed[win]=timer.doAfter(retry*RETRY_DELAY,function()appWindowEvent(win,event,_,pid,retry)end) end
13981401
-- return
13991402
-- end
1400-
Window.created(win,id,apps[appname],watcher)
1403+
Window.created(win,id,apps[pid],watcher)
14011404
watcher:start({uiwatcher.elementDestroyed,uiwatcher.windowMoved,uiwatcher.windowResized
14021405
,uiwatcher.windowMinimized,uiwatcher.windowUnminimized,uiwatcher.titleChanged})
14031406
elseif event==uiwatcher.focusedWindowChanged then
1404-
local app=apps[appname]
1407+
local app=apps[pid]
14051408
if not app then return log.df('app %s is not registered!',appname) end
14061409
app:focusChanged(id,win)
14071410
end
14081411
end
14091412

14101413
local function startAppWatcher(app,appname,retry,nologging,force)
14111414
if not app or not appname then log.e('called startAppWatcher with no app') return end
1412-
if apps[appname] then return not nologging and log.df('app %s already registered',appname) end
1415+
local pid = app:pid()
1416+
if apps[pid] then return not nologging and log.df('app %s already registered',appname) end
14131417
if app:kind()<0 or not windowfilter.isGuiApp(appname) then log.df('app %s has no GUI',appname) return end
14141418
if not fnutils.contains(axuielement.applicationElement(app):attributeNames() or {}, "AXFocusedWindow") then
14151419
log.df('app %s has no AXFocusedWindow element',appname)
@@ -1418,14 +1422,14 @@ local function startAppWatcher(app,appname,retry,nologging,force)
14181422
retry=(retry or 0)+1
14191423

14201424
if app:focusedWindow() or force then
1421-
pendingApps[appname]=nil --done
1422-
local watcher = app:newWatcher(appWindowEvent,appname)
1425+
pendingApps[pid]=nil --done
1426+
local watcher = app:newWatcher(appWindowEvent,pid)
14231427
watcher:start({uiwatcher.windowCreated,uiwatcher.focusedWindowChanged})
1424-
App.new(app,appname,watcher)
1428+
App.new(app,appname,pid,watcher)
14251429
else
14261430
-- apps that start with an open window will often fail to be detected by the watcher if we
14271431
-- start it too early, so we try `app:focusedWindow()` MAX_RETRIES times before giving up
1428-
pendingApps[appname] = timer.doAfter(retry*RETRY_DELAY,function()
1432+
pendingApps[pid] = timer.doAfter(retry*RETRY_DELAY,function()
14291433
startAppWatcher(app,appname,retry,nologging, retry > MAX_RETRIES)
14301434
end)
14311435
end
@@ -1438,7 +1442,8 @@ local function appEvent(appname,event,app)
14381442
if not appname then return end
14391443
if event==appwatcher.launched then return startAppWatcher(app,appname)
14401444
elseif event==appwatcher.launching then return end
1441-
local appo=apps[appname]
1445+
local pid = app:pid()
1446+
local appo=apps[pid]
14421447
if event==appwatcher.activated then
14431448
if appo then return appo:activated()
14441449
else return startAppWatcher(app,appname,0,true) end
@@ -1452,7 +1457,7 @@ local function appEvent(appname,event,app)
14521457
timer.doAfter(0.1*retry,function()appEvent(appname,event,app,retry)end)
14531458
return
14541459
--]]
1455-
elseif event==appwatcher.terminated then pendingApps[appname]=nil end
1460+
elseif event==appwatcher.terminated then pendingApps[pid]=nil end
14561461
if not appo then return log.df('app %s is not registered!',appname) end
14571462
if event==appwatcher.terminated then return appo:destroyed()
14581463
elseif event==appwatcher.deactivated then return appo:deactivated()

0 commit comments

Comments
 (0)