#
hs.window.filter
Filter windows by application, title, location on screen and more, and easily subscribe to events on these windows
Warning: this module is still somewhat experimental. Should you encounter any issues, please feel free to report them on https://github.com/Hammerspoon/hammerspoon/issues or #hammerspoon on irc.libera.chat.
Windowfilters monitor all windows as they're created, closed, moved etc., and select some (or none) among these windows
according to specific filtering rules. These filtering rules are app-specific, i.e. they start off by selecting all windows
belonging to a certain application (but you can also define default and override filters - see :setAppFilter()
,
:setDefaultFilter()
, :setOverrideFilter()
) and they can allow or reject windows based on:
- visibility, focused and/or fullscreen status
- title length or patterns in the title
- position on screen (inside or outside a certain region or screen)
- accessibility role (standard window, dialog, etc.)
- whether they're in the current Mission Control Space or not
The filtering happens automatically in the background; windowfilters then:
- generate a dynamic list of the windows that currently satisfy the filtering rules (see
:getWindows()
) - sanitize and expose all pertinent events on these windows (see
:subscribe()
and the module constants with all the events)
A default windowfilter (not to be confused with the default filter within a windowfilter) is provided as convenience;
it excludes some known apps and windows that are transient in nature, therefore unlikely to be "interesting" for e.g. window management.
hs.window.filter.new()
(with no arguments) returns a copy of the default windowfilter that you can further tailor
to your needs - see hs.window.filter.default
and hs.window.filter.new()
for more information.
Usage examples:
local wf=hs.window.filter
-- alter the default windowfilter
wf.default:setAppFilter('My IDE',{allowTitles=1}) -- ignore no-title windows (e.g. transient autocomplete suggestions) in My IDE
-- set the exact scope of what you're interested in - see hs.window.filter:setAppFilter()
wf_terminal = wf.new{'Terminal','iTerm2'} -- all visible terminal windows
wf_timewaster = wf.new(false):setAppFilter('Safari',{allowTitles='reddit'}) -- any Safari windows with "reddit" anywhere in the title
wf_leftscreen = wf.new{override={visible=true,fullscreen=false,allowScreens='-1,0',currentSpace=true}}
-- all visible and non-fullscreen windows that are on the screen to the left of the primary screen in the current Space
wf_editors_righthalf = wf.new{'TextEdit','Sublime Text','BBEdit'}:setRegions(hs.screen.primaryScreen():fromUnitRect'0.5,0/1,1')
-- text editor windows that are on the right half of the primary screen
wf_bigwindows = wf.new(function(w)return w:frame().area>3000000 end) -- only very large windows
wf_notif = wf.new{['Notification Center']={allowRoles='AXNotificationCenterAlert'}} -- notification center alerts
-- subscribe to events
wf_terminal:subscribe(wf.windowFocused,some_fn) -- run a function whenever a terminal window is focused
wf_timewaster:subscribe(wf.hasWindow,startAnnoyingMe):subscribe(wf.hasNoWindows,stopAnnoyingMe) -- fight procrastination :)
#
API Overview
Constants - Useful values which cannot be changed
default defaultCurrentSpace hasNoWindows hasWindow sortByCreated sortByCreatedLast sortByFocused sortByFocusedLast windowAllowed windowCreated windowDestroyed windowFocused windowFullscreened windowHidden windowInCurrentSpace windowMinimized windowMoved windowNotInCurrentSpace windowNotOnScreen windowNotVisible windowOnScreen windowRejected windowsChanged windowTitleChanged windowUnfocused windowUnfullscreened windowUnhidden windowUnminimized windowVisible
Variables - Configurable values
allowedWindowRoles forceRefreshOnSpaceChange ignoreAlways
Functions - API calls offered directly by the extension
focusEast focusNorth focusSouth focusWest isGuiApp switchedToSpace
Constructors - API calls which return an object, typically one that offers API methods
copy new
Methods - API calls which can only be made on an object returned by a constructor
allowApp focusWindowEast focusWindowNorth focusWindowSouth focusWindowWest getFilters getWindows isAppAllowed isWindowAllowed pause rejectApp resume setAppFilter setCurrentSpace setDefaultFilter setFilters setOverrideFilter setRegions setScreens setSortOrder subscribe unsubscribe unsubscribeAll windowsToEast windowsToNorth windowsToSouth windowsToWest
#
API Documentation
#
Constants
#
default
#
defaultCurrentSpace
#
hasNoWindows
#
hasWindow
#
sortByCreated
#
sortByCreatedLast
#
sortByFocused
#
sortByFocusedLast
#
windowAllowed
#
windowCreated
#
windowDestroyed
#
windowFocused
#
windowFullscreened
#
windowInCurrentSpace
#
windowMinimized
#
windowMoved
#
windowNotInCurrentSpace
#
windowNotOnScreen
#
windowNotVisible
#
windowOnScreen
#
windowRejected
#
windowsChanged
#
windowTitleChanged
#
windowUnfocused
#
windowUnfullscreened
#
windowUnminimized
#
windowVisible
#
Variables
#
allowedWindowRoles
#
forceRefreshOnSpaceChange
#
ignoreAlways
#
Functions
#
focusEast
#
focusNorth
#
focusSouth
#
focusWest
#
isGuiApp
#
switchedToSpace
#
Constructors
#
copy
#
new
| | |
| --------------------------------------------|-------------------------------------------------------------------------------------|
| Signature | hs.window.filter.new(fn[,logname[,loglevel]]) -> hs.window.filter object
|
| Type | Constructor |
| Description | Creates a new hs.window.filter instance |
| Parameters |
- fn
if
nil
, returns a copy of the default windowfilter, including any customizations you might have applied to it so far; you can then further restrict or expand it iftrue
, returns an empty windowfilter that allows every window iffalse
, returns a windowfilter with a default rule to reject every window if a string or table of strings, returns a windowfilter that only allows visible windows of the specified apps as perhs.application:name()
if a table, you can fully define a windowfilter without having to call any methods after construction; the table must be structured as perhs.window.filter:setFilters()
; if not specified in the table, the default filter in the new windowfilter will reject all windows otherwise it must be a function that accepts anhs.window
object and returnstrue
if the window is allowed orfalse
otherwise; this way you can define a fully custom windowfilter - logname - (optional) name of the
hs.logger
instance for the new windowfilter; if omitted, the class logger will be used - loglevel - (optional) log level for the
hs.logger
instance for the new windowfilter
- a new windowfilter instance
#
Methods
#
allowApp
#
focusWindowEast
#
focusWindowNorth
#
focusWindowSouth
#
focusWindowWest
#
getFilters
#
getWindows
#
isAppAllowed
#
isWindowAllowed
#
pause
#
rejectApp
#
resume
#
setAppFilter
| | |
| --------------------------------------------|-------------------------------------------------------------------------------------|
| Signature | hs.window.filter:setAppFilter(appname, filter) -> hs.window.filter object
|
| Type | Method |
| Description | Sets the detailed filtering rules for the windows of a specific app |
| Parameters |
- appname - app name as per
hs.application:name()
- filter - if
false
, reject the app; iftrue
,nil
, or omitted, allow all visible windows (in any Space) for the app; otherwise it must be a table describing the filtering rules for the app, via the following fields: visible - iftrue
, only allow visible windows (in any Space); iffalse
, reject visible windows; if omitted, this rule is ignored currentSpace - iftrue
, only allow windows in the current Mission Control Space (minimized and hidden windows are included, as they're considered to belong to all Spaces); iffalse
, reject windows in the current Space (including all minimized and hidden windows); if omitted, this rule is ignored fullscreen - iftrue
, only allow fullscreen windows; iffalse
, reject fullscreen windows; if omitted, this rule is ignored hasTitlebar - iftrue
, only allow windows with titlebar; iffalse
, reject window with titlebar; if omitted, this rule is ignored focused - iftrue
, only allow a window while focused; iffalse
, reject the focused window; if omitted, this rule is ignored activeApplication - only allow any of this app's windows while it is (iftrue
) or it's not (iffalse
) the active application; if omitted, this rule is ignored allowTitlesif a number, only allow windows whose title is at least as many characters long; e.g. pass1
to filter windows with an empty titleif a string or table of strings, only allow windows whose title matches (one of) the pattern(s) as perstring.match
if omitted, this rule is ignored rejectTitles - if a string or table of strings, reject windows whose titles matches (one of) the pattern(s) as perstring.match
; if omitted, this rule is ignored allowRegions - anhs.geometry
rect or constructor argument, or a list of them, designating (a) screen "region(s)" in absolute coordinates: only allow windows that "cover" at least 50% of (one of) the region(s), and/or windows that have at least 50% of their surface inside (one of) the region(s); if omitted, this rule is ignored rejectRegions - anhs.geometry
rect or constructor argument, or a list of them, designating (a) screen "region(s)" in absolute coordinates: reject windows that "cover" at least 50% of (one of) the region(s), and/or windows that have at least 50% of their surface inside (one of) the region(s); if omitted, this rule is ignored allowScreens - a valid argument forhs.screen.find()
, or a list of them, indicating one (or more) screen(s): only allow windows that (mostly) lie on (one of) the screen(s); if omitted, this rule is ignored rejectScreens - a valid argument forhs.screen.find()
, or a list of them, indicating one (or more) screen(s): reject windows that (mostly) lie on (one of) the screen(s); if omitted, this rule is ignored allowRolesif a string or table of strings, only allow these window roles as perhs.window:subrole()
if the special string'*'
, this rule is ignored (i.e. all window roles, including empty ones, are allowed)if omitted, use the default allowed roles (defined inhs.window.filter.allowedWindowRoles
)
- the
hs.window.filter
object for method chaining
- Passing
focused=true
infilter
will (naturally) result in the windowfilter ever allowing 1 window at most - If you want to allow all windows for an app, including invisible ones, pass an empty table for
filter
- Spaces-aware windowfilters might experience a (sometimes significant) delay after every Space switch, since (due to OS X limitations) they must re-query for the list of all windows in the current Space every time.
- If System Preferences>Mission Control>Displays have separate Spaces is on, the current Space is defined as the union of all the Spaces that are currently visible
- This table explains the effects of different combinations of
visible
andcurrentSpace
, showing which windows will be allowed: </li><li> |visible= nil | true | false |</li><li>|currentSpace|------------------------------------------|------------------------------|--------------|</li><li>| nil |all |visible in ANY space |min and hidden|</li><li>| true |visible in CURRENT space+min and hidden |visible in CURRENT space |min and hidden|</li><li>| false |visible in OTHER space only+min and hidden|visible in OTHER space only |none |</li><li>
#
setCurrentSpace
#
setDefaultFilter
#
setFilters
#
setOverrideFilter
#
setRegions
#
setScreens
#
setSortOrder
#
subscribe
| | |
| --------------------------------------------|-------------------------------------------------------------------------------------|
| Signature | hs.window.filter:subscribe(event, fn[, immediate]) -> hs.window.filter object
|
| Type | Method |
| Description | Subscribe to one or more events on the allowed windows |
| Parameters |
- event - string or list of strings, the event(s) to subscribe to (see the
hs.window.filter
constants); alternatively, this can be a map{event1=fn1,event2=fn2,...}
: fnN will be subscribed to eventN, and the parameterfn
will be ignored - fn - function or list of functions, the callback(s) to add for the event(s); each will be passed 3 parameters
a
hs.window
object referring to the event's window a string containing the application name (window:application():name()
) for convenience a string containing the event that caused the callback, i.e. (one of) the event(s) you subscribed to - immediate - (optional) if
true
, also call all the callbacks immediately for windows that satisfy the event(s) criteria
- the
hs.window.filter
object for method chaining
- Passing lists means that all the
fn
s will be called when any of theevent
s fires, so it's not a shortcut for subscribing distinct callbacks to distinct events; use a map or chained:subscribe
calls for that. - Use caution with
immediate
: if for example you're subscribing tohs.window.filter.windowUnfocused
,fn
(s) will be called for all the windows except the currently focused one. - If the windowfilter was paused with
hs.window.filter:pause()
, calling this will resume it.