174 lines
6.3 KiB
Lua
174 lines
6.3 KiB
Lua
--- === hs.uielement ===
|
|
---
|
|
--- A generalized framework for working with OSX UI elements
|
|
|
|
local uielement = require("hs.libuielement")
|
|
uielement.watcher = require("hs.libuielementwatcher")
|
|
local fnutils = require "hs.fnutils"
|
|
local appWatcher = require "hs.application.watcher"
|
|
|
|
local USERDATA_TAG = "hs.uielement"
|
|
local objectMT = hs.getObjectMetatable(USERDATA_TAG)
|
|
local watcherMT = hs.getObjectMetatable("hs.uielement.watcher")
|
|
|
|
--- hs.uielement:isApplication() -> bool
|
|
--- Method
|
|
--- Returns whether the UI element represents an application.
|
|
---
|
|
--- Parameters:
|
|
--- * None
|
|
---
|
|
--- Returns:
|
|
--- * A boolean, true if the UI element is an application
|
|
function objectMT.isApplication(self)
|
|
return self:role() == "AXApplication"
|
|
end
|
|
|
|
--- === hs.uielement.watcher ===
|
|
---
|
|
--- Watch for events on certain UI elements (including windows and applications)
|
|
---
|
|
--- You can watch the following events:
|
|
--- ### Application-level events
|
|
--- See hs.application.watcher for more events you can watch.
|
|
--- * hs.uielement.watcher.applicationActivated: The current application switched to this one.
|
|
--- * hs.uielement.watcher.applicationDeactivated: The current application is no longer this one.
|
|
--- * hs.uielement.watcher.applicationHidden: The application was hidden.
|
|
--- * hs.uielement.watcher.applicationShown: The application was shown.
|
|
---
|
|
--- #### Focus change events
|
|
--- These events are watched on the application level, but send the relevant child element to the handler.
|
|
--- * hs.uielement.watcher.mainWindowChanged: The main window of the application was changed.
|
|
--- * hs.uielement.watcher.focusedWindowChanged: The focused window of the application was changed. Note that the application may not be activated itself.
|
|
--- * hs.uielement.watcher.focusedElementChanged: The focused UI element of the application was changed.
|
|
---
|
|
--- ### Window-level events
|
|
--- * hs.uielement.watcher.windowCreated: A window was created. You should watch for this event on the application, or the parent window.
|
|
--- * hs.uielement.watcher.windowMoved: The window was moved.
|
|
--- * hs.uielement.watcher.windowResized: The window was resized.
|
|
--- * hs.uielement.watcher.windowMinimized: The window was minimized.
|
|
--- * hs.uielement.watcher.windowUnminimized: The window was unminimized.
|
|
---
|
|
--- ### Element-level events
|
|
--- These work on all UI elements, including windows.
|
|
--- * hs.uielement.watcher.elementDestroyed: The element was destroyed.
|
|
--- * hs.uielement.watcher.titleChanged: The element's title was changed.
|
|
|
|
uielement.watcher.applicationActivated = "AXApplicationActivated"
|
|
uielement.watcher.applicationDeactivated = "AXApplicationDeactivated"
|
|
uielement.watcher.applicationHidden = "AXApplicationHidden"
|
|
uielement.watcher.applicationShown = "AXApplicationShown"
|
|
|
|
uielement.watcher.mainWindowChanged = "AXMainWindowChanged"
|
|
uielement.watcher.focusedWindowChanged = "AXFocusedWindowChanged"
|
|
uielement.watcher.focusedElementChanged = "AXFocusedUIElementChanged"
|
|
|
|
uielement.watcher.windowCreated = "AXWindowCreated"
|
|
uielement.watcher.windowMoved = "AXWindowMoved"
|
|
uielement.watcher.windowResized = "AXWindowResized"
|
|
uielement.watcher.windowMinimized = "AXWindowMiniaturized"
|
|
uielement.watcher.windowUnminimized = "AXWindowDeminiaturized"
|
|
|
|
uielement.watcher.elementDestroyed = "AXUIElementDestroyed"
|
|
uielement.watcher.titleChanged = "AXTitleChanged"
|
|
|
|
|
|
-- Keep track of apps, to automatically stop watchers on apps AND their elements when apps quit.
|
|
|
|
local appWatchers = {}
|
|
|
|
local function appCallback(_, event, app)
|
|
if app and (appWatchers[app:pid()] and event == application.watcher.terminated) then
|
|
fnutils.each(appWatchers[app:pid()], function(watcher) watcher:_stop() end)
|
|
appWatchers[app:pid()] = nil
|
|
end
|
|
end
|
|
|
|
local globalAppWatcher = appWatcher.new(appCallback)
|
|
globalAppWatcher:start()
|
|
|
|
-- Keep track of all other UI elements to automatically stop their watchers.
|
|
|
|
local function handleEvent(callback, element, event, watcher, userData)
|
|
if event == watcher.elementDestroyed then
|
|
-- element is newly created from a dead UI element and may not have critical fields like pid and id.
|
|
-- Use the existing watcher element instead.
|
|
if element == watcher:element() then
|
|
element = watcher:element()
|
|
end
|
|
|
|
-- Pass along event if wanted.
|
|
if watcher:watchDestroyed() then
|
|
callback(element, event, watcher, userData)
|
|
end
|
|
|
|
-- Stop watcher.
|
|
if element == watcher:element() then
|
|
watcher:stop() -- also removes from appWatchers
|
|
end
|
|
else
|
|
callback(element, event, watcher, userData)
|
|
end
|
|
end
|
|
|
|
--- hs.uielement.watcher:start(events) -> hs.uielement.watcher
|
|
--- Method
|
|
--- Tells the watcher to start watching for the given list of events.
|
|
---
|
|
--- Parameters:
|
|
--- * An array of events to be watched for.
|
|
---
|
|
--- Returns:
|
|
--- * hs.uielement.watcher
|
|
---
|
|
--- Notes:
|
|
--- * See hs.uielement.watcher for a list of events. You may also specify arbitrary event names as strings.
|
|
--- * Does nothing if the watcher has already been started. To start with different events, stop it first.
|
|
function watcherMT.start(self, events)
|
|
-- Track all watchers in appWatchers.
|
|
local pid = self:pid()
|
|
if not appWatchers[pid] then appWatchers[pid] = {} end
|
|
table.insert(appWatchers[pid], self)
|
|
|
|
-- For normal elements, listen for elementDestroyed events.
|
|
if not self:element():isApplication() then
|
|
if fnutils.contains(events, self.elementDestroyed) then
|
|
self:watchDestroyed(true)
|
|
else
|
|
self:watchDestroyed(false)
|
|
events = fnutils.copy(events)
|
|
table.insert(events, self.elementDestroyed)
|
|
end
|
|
end
|
|
|
|
-- Actually start the watcher.
|
|
return self:_start(events)
|
|
end
|
|
|
|
--- hs.uielement.watcher:stop() -> hs.uielement.watcher
|
|
--- Method
|
|
--- Tells the watcher to stop listening for events.
|
|
---
|
|
--- Parameters:
|
|
--- * None
|
|
---
|
|
--- Returns:
|
|
--- * hs.uielement.watcher
|
|
---
|
|
--- Notes:
|
|
--- * This is automatically called if the element is destroyed.
|
|
function watcherMT.stop(self)
|
|
-- Remove self from appWatchers.
|
|
local pid = self:pid()
|
|
if appWatchers[pid] then
|
|
local idx = fnutils.indexOf(appWatchers[pid], self)
|
|
if idx then
|
|
table.remove(appWatchers[pid], idx)
|
|
end
|
|
end
|
|
|
|
return self:_stop()
|
|
end
|
|
|
|
return uielement
|