Dwmac is an dwm-like tiling window manager for macOS

300

This Guide is designed to be read from top to bottom as a whole. You can skip parts that are obvious.

1. Installation

1.1. Homebrew installation (Preferred)

Homebrew is a package manager for macOS

brew install --cask hillyu/tap/dwmac

(Optional) You might need to configure your shell to enable completion provided by homebrew packages: https://docs.brew.sh/Shell-Completion Dwmac provides bash, fish and zsh completions.

You can also install specific previous pinned homebrew Casks versions:

brew install --cask hillyu/tap/dwmac@0.12.0

1.2. Manual installation

  1. Download the latest available zip from releases page

  2. Unpack zip

  3. Put unpacked Dwmac-v$VERSION/Dwmac.app to /Applications

  4. Put unpacked Dwmac-v$VERSION/bin/dwmac anywhere to $PATH (The step is optional. It is only needed if you want to be able to interact with Dwmac from CLI)

If you see this message

"Dwmac.app" can't be opened because Apple cannot check it for malicious software.

Option 1 to resolve the problem

xattr -d com.apple.quarantine /Applications/Dwmac.app

Option 2 to resolve the problem

  1. navigate in Finder to /Applications/Dwmac.app

  2. Right mouse click

  3. Open (yes, it’s that stupid)

2. Configuring Dwmac

2.1. Custom config location

Dwmac tries to find the custom config in two locations:

  1. ~/.dwmac.toml

  2. ${XDG_CONFIG_HOME}/dwmac/dwmac.toml (environment variable XDG_CONFIG_HOME fallbacks to ~/.config if the variable is not presented)

If the config is found in more than one location then the ambiguity is reported.

2.2. Config samples

Please see the following config samples:

Dwmac uses TOML format for the config. TOML is easy to read, and it supports comments. See TOML spec for more info

2.3. Default config

The default config is part of the documentation, it contains all trivial configuration keys with comments. Please read the default config! Non-trivial configuration options are mentioned further in this guide. If no custom config is found, Dwmac will load the default config.

If the key is omitted in the custom config, it falls back to the value in the default config, unless it’s stated otherwise for the specific keys. Namely:

  • mode.*.binding. It falls back to the empty TOML table. Your config is the source of truth for keyboard bindings. You must explicitly mention all the keyboard bindings and binding modes in your config.

  • on-focused-monitor-changed. It falls back to the empty TOML array.

  • exec TOML table. See: exec-* Environment Variables (It’s so boring and verbose, I don’t even want to mention it in the default-config.toml)

Rule of thumb: all the "scalar like" values always fall back to the default config. All the "vector like" values fall back to the empty TOML array or table.

That allows you to keep your config tidy and clean from trivial config keys for which you like the default values. You can bootstrap your custom config by copying the default config from the app installation -

cp /Applications/Dwmac.app/Contents/Resources/default-config.toml ~/.dwmac.toml
# Place a copy of this config to ~/.dwmac.toml
# After that, you can edit ~/.dwmac.toml to your liking

# Config version for compatibility and deprecations
# Fallback value (if you omit the key): config-version = 1
config-version = 2

# You can use it to add commands that run after Dwmac startup.
# Available commands : https://hillyu.github.io/Dwmac/commands
after-startup-command = []

# Start Dwmac at login
start-at-login = false

# Possible values: master-stack
default-root-container-layout = 'master-stack'

# mfact for master-stack
default-mfact = 0.5

# Possible values: left|right
# Default value: left
master-position = 'left'

# Whether new windows should be attached to the master position (index 0) or the last position.
# Fallback value (if you omit the key): attach-below = false
attach-below = false

# Possible values: horizontal|vertical|auto
# 'auto' means: wide monitor (anything wider than high) gets horizontal orientation,
#               tall monitor (anything higher than wide) gets vertical orientation
default-root-container-orientation = 'auto'

# Mouse follows focus when focused monitor changes
# Drop it from your config, if you don't like this behavior
# See https://hillyu.github.io/Dwmac/guide#on-focus-changed-callbacks
# See https://hillyu.github.io/Dwmac/commands#move-mouse
# Fallback value (if you omit the key): on-focused-monitor-changed = []
on-focused-monitor-changed = ['move-mouse monitor-lazy-center']

# You can effectively turn off macOS "Hide application" (cmd-h) feature by toggling this flag
# Useful if you don't use this macOS feature, but accidentally hit cmd-h or cmd-alt-h key
# Also see: https://hillyu.github.io/Dwmac/goodies#disable-hide-app
automatically-unhide-macos-hidden-apps = false

# Whether to automatically center floating windows.
# Fallback value (if you omit the key): center-floating-windows = false
center-floating-windows = false

# List of workspaces that should stay alive even when they contain no windows,
# even when they are invisible.
# This config version is only available since 'config-version = 2'
# Fallback value (if you omit the key): persistent-workspaces = []
persistent-workspaces = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
                         "C", "D", "E", "F", "G", "I", "M", "N", "O", "P", "Q",
                         "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

# A callback that runs every time binding mode changes
# See: https://hillyu.github.io/Dwmac/guide#binding-modes
# See: https://hillyu.github.io/Dwmac/commands#mode
on-mode-changed = []

# Possible values: (qwerty|dvorak|colemak)
# See https://hillyu.github.io/Dwmac/guide#key-mapping
[key-mapping]
    preset = 'qwerty'

# Gaps between windows (inner-*) and between monitor edges (outer-*).
# Possible values:
# - Constant:     gaps.outer.top = 8
# - Per monitor:  gaps.outer.top = [{ monitor.main = 16 }, { monitor."some-pattern" = 32 }, 24]
#                 In this example, 24 is a default value when there is no match.
#                 Monitor pattern is the same as for 'workspace-to-monitor-force-assignment'.
#                 See:
#                 https://hillyu.github.io/Dwmac/guide#assign-workspaces-to-monitors
[gaps]
    inner.horizontal = 0
    inner.vertical =   0
    outer.left =       0
    outer.bottom =     0
    outer.top =        0
    outer.right =      0

# 'main' binding mode declaration
# See: https://hillyu.github.io/Dwmac/guide#binding-modes
# 'main' binding mode must be always presented
# Fallback value (if you omit the key): mode.main.binding = {}
[mode.main.binding]

    # All possible keys:
    # - Letters.        a, b, c, ..., z
    # - Numbers.        0, 1, 2, ..., 9
    # - Keypad numbers. keypad0, keypad1, keypad2, ..., keypad9
    # - F-keys.         f1, f2, ..., f20
    # - Special keys.   minus, equal, period, comma, slash, backslash, quote, semicolon,
    #                   backtick, leftSquareBracket, rightSquareBracket, space, enter, esc,
    #                   backspace, tab, pageUp, pageDown, home, end, forwardDelete,
    #                   sectionSign (ISO keyboards only, european keyboards only)
    # - Keypad special. keypadClear, keypadDecimalMark, keypadDivide, keypadEnter, keypadEqual,
    #                   keypadMinus, keypadMultiply, keypadPlus
    # - Arrows.         left, down, up, right

    # All possible modifiers: cmd, alt, ctrl, shift

    # All possible commands: https://hillyu.github.io/Dwmac/commands

    # See: https://hillyu.github.io/Dwmac/commands#exec-and-forget
    # You can uncomment the following lines to open up terminal with alt + enter shortcut
    # alt-enter = '''exec-and-forget osascript -e '
    # tell application "Terminal"
    #     do script
    #     activate
    # end tell'
    # '''

    # See: https://hillyu.github.io/Dwmac/commands#layout
    cmd-alt-ctrl-slash = 'layout master-stack'

    # See: https://hillyu.github.io/Dwmac/commands#focus
    cmd-alt-ctrl-j = 'focus next'
    cmd-alt-ctrl-k = 'focus prev'
    cmd-alt-ctrl-h = 'mfact -0.1'
    cmd-alt-ctrl-l = 'mfact +0.1'

    # See: https://hillyu.github.io/Dwmac/commands#move
    cmd-alt-ctrl-space = 'move-node-to-master'
    cmd-alt-ctrl-shift-j = 'swap next'
    cmd-alt-ctrl-shift-k = 'swap prev'

    # See: https://hillyu.github.io/Dwmac/commands#resize
    cmd-alt-ctrl-minus = 'resize smart -50'
    cmd-alt-ctrl-equal = 'resize smart +50'

    # See: https://hillyu.github.io/Dwmac/commands#workspace
    cmd-alt-ctrl-1 = 'workspace 1'
    cmd-alt-ctrl-2 = 'workspace 2'
    cmd-alt-ctrl-3 = 'workspace 3'
    cmd-alt-ctrl-4 = 'workspace 4'
    cmd-alt-ctrl-5 = 'workspace 5'
    cmd-alt-ctrl-6 = 'workspace 6'
    cmd-alt-ctrl-7 = 'workspace 7'
    cmd-alt-ctrl-8 = 'workspace 8'
    cmd-alt-ctrl-9 = 'workspace 9'

    # See: https://hillyu.github.io/Dwmac/commands#move-node-to-workspace
    cmd-alt-ctrl-shift-1 = 'move-node-to-workspace 1'
    cmd-alt-ctrl-shift-2 = 'move-node-to-workspace 2'
    cmd-alt-ctrl-shift-3 = 'move-node-to-workspace 3'
    cmd-alt-ctrl-shift-4 = 'move-node-to-workspace 4'
    cmd-alt-ctrl-shift-5 = 'move-node-to-workspace 5'
    cmd-alt-ctrl-shift-6 = 'move-node-to-workspace 6'
    cmd-alt-ctrl-shift-7 = 'move-node-to-workspace 7'
    cmd-alt-ctrl-shift-8 = 'move-node-to-workspace 8'
    cmd-alt-ctrl-shift-9 = 'move-node-to-workspace 9'

    # See: https://hillyu.github.io/Dwmac/commands#workspace-back-and-forth
    cmd-alt-ctrl-tab = 'workspace-back-and-forth'
    # See: https://hillyu.github.io/Dwmac/commands#move-workspace-to-monitor
    cmd-alt-ctrl-shift-tab = 'move-workspace-to-monitor --wrap-around next'

    # See: https://hillyu.github.io/Dwmac/commands#mode
    cmd-alt-ctrl-shift-semicolon = 'mode service'

# 'service' binding mode declaration.
# See: https://hillyu.github.io/Dwmac/guide#binding-modes
[mode.service.binding]
    esc = ['reload-config', 'mode main']
    f = ['layout floating tiling', 'mode main'] # Toggle between floating and tiling layout
    backspace = ['close-all-windows-but-current', 'mode main']

    # sticky is not yet supported https://github.com/hillyu/Dwmac/issues/2
    #s = ['layout sticky tiling', 'mode main']

2.4. Binding modes

You can create multiple sets of bindings by creating different binding modes. When you switch to a different binding mode, all the bindings from the current mode are deactivated, and only the bindings specified in the new mode become active. The initial binding mode that Dwmac starts out with is "main".

Working with binding modes consists of two parts: 1. defining a binding to switch to the binding mode and 2. declaring the binding mode itself.

[mode.main.binding]            # Declare 'main' binding mode
    alt-r = 'mode resize'      # 1. Define a binding to switch to 'resize' mode

[mode.resize.binding]          # 2. Declare 'resize' binding mode
    minus = 'resize smart -50'
    equal = 'resize smart +50'

2.5. Commands

Commands are the thing you use to manipulate Dwmac and query its state.

There are two ways on how you can use commands:

  1. Bind keys to run Dwmac commands. Example:

    [mode.main.binding]
        # Bind alt-1 key to switch to workspace 1
        alt-1 = 'workspace 1'
        # Or bind a sequence of commands
        alt-shift-1 = ['move-node-to-workspace 1', 'workspace 1']
  2. Run commands in CLI. Open up a Terminal.app and type:

    dwmac workspace 1

For the list of available commands see: commands

2.6. Keyboard layouts and key mapping

By default, key bindings in the config are perceived as qwerty layout.

If you use different layout, different alphabet, or you just want to have a fancy alias for the existing key, you can use key-mapping.key-notation-to-key-code.

# Define my fancy unicorn key notation
[key-mapping.key-notation-to-key-code]
    unicorn = 'u'

[mode.main.binding]
    alt-unicorn = 'workspace wonderland' # (⁀ᗢ⁀)
  • For dvorak and colemak users, Dwmac offers preconfigured presets.

    [key-mapping]
        preset = 'dvorak'  # or 'colemak'

2.7. Modifier alias

You can define a preferred modifier key set using the mod option and use it in your key bindings with the mod- prefix. This allows you to easily change your preferred modifiers in one place.

mod = 'cmd-alt'

[mode.main.binding]
    mod-j = 'focus next'
    mod-k = 'focus prev'
    mod-shift-j = 'swap next'
    mod-shift-k = 'swap prev'

2.8. Fine-grained modifiers

Dwmac supports distinguishing between left and right modifier keys. Available modifiers are:

  • shift, lshift, rshift

  • ctrl, lctrl, rctrl

  • alt (or opt), lalt, ralt

  • cmd, lcmd, rcmd

Example:

[mode.main.binding]
    rcmd-z = 'fullscreen' # Zoom window with Right Command + Z
    lcmd-z = 'reload-config' # Reload config with Left Command + Z

2.9. exec-* Environment Variables

You can configure environment variables of exec-* commands and callbacks (such as exec-and-forget, 'exec-on-workspace-change' callback)

  • exec.inherit-env-vars = true configures whether inherit environment variables of Dwmac.app or not. (The default is true)

  • You can override env variables with the following syntax:

    [exec.env-vars]
        PATH = '${HOME}/bin:${PATH}'

    Environment variable substitution is supported in form of ${ENV_VAR}

  • You can inspect what is the end result of environment variables using list-exec-env-vars command

  • GUI apps on macOS don’t have Homebrew’s prefix in their PATH by default (docs.brew.sh). That’s why unless you override exec section in your config, Dwmac falls back to the following exec configuration:

    [exec]
        inherit-env-vars = true
    [exec.env-vars]
        PATH = '/opt/homebrew/bin:/opt/homebrew/sbin:${PATH}'

2.10. Other configuration options

  • start-at-login (default: false)

    Starts Dwmac automatically when you log in.

  • attach-below (default: false)

    Determines where new windows are inserted in the stack. false: New windows become the Master (index 0). true: New windows are added to the end of the Stack.

  • default-root-container-layout (default: master-stack)

    Sets the default layout for new workspaces.

  • default-root-container-orientation (default: auto)

    Sets the default orientation (horizontal, vertical, or auto) for new workspaces. auto chooses horizontal for wide screens and vertical for tall screens.

  • master-position (default: left)

    Determines the position of the Master window in the Master-Stack layout when the orientation is horizontal. left: Master window is on the left, Stack is on the right. right: Master window is on the right, Stack is on the left.

  • automatically-unhide-macos-hidden-apps (default: false)

    If true, Dwmac will unhide applications that were hidden using Cmd+H. This can be useful if you don’t use the macOS hiding feature and want to prevent accidental hiding.

  • center-floating-windows (default: false)

    If true, floating windows will be automatically centered on the screen.

3. Layout

Dwmac uses a flat tiling model, specifically the Master-Stack layout (inspired by dwm). Unlike tree-based window managers, there are no nested containers. Each workspace simply manages a flat list of windows.

  • The first window in the list is the Master. It occupies the main area of the screen (determined by mfact).

  • The remaining windows form the Stack. They share the remaining area.

3.1. Orientation

The Master-Stack layout can be oriented in two ways:

  • Horizontal (h_master_stack): The Master window is on the left, and the Stack is on the right. Stack windows are stacked vertically.

  • Vertical (v_master_stack): The Master window is at the top, and the Stack is at the bottom. Stack windows are stacked horizontally.

You can switch the orientation of the current workspace using the layout command.

Example 1. Example: Horizontal Layout

In a horizontal layout with 3 windows:

  • Window 1 (Master) takes the left part of the screen.

  • Window 2 and Window 3 share the right part of the screen, stacked on top of each other.

Horizontal Layout Diagram

3.2. Floating windows

Floating windows are not part of the Master-Stack tiling layout. They float above tiled windows. However, they are still part of the workspace and can be focused using the focus command.

4. Emulation of virtual workspaces

Native macOS Spaces have a lot of problems

  • The animation for Spaces switching is slow

    • You can’t disable animation for Spaces switching (you can only make it slightly faster by turning on Reduce motion setting, but it’s suboptimal)

  • You have a limit of Spaces (up to 16 Spaces with one monitor)

  • You can’t create/delete/reorder Space and move windows between Spaces with hotkeys (you can only switch between Spaces with hotkeys)

  • Apple doesn’t provide public API to communicate with Spaces (create/delete/reorder/switch Space and move windows between Spaces)

Since Spaces are so hard to deal with, Dwmac reimplements Spaces and calls them "Workspaces". The idea is that if the workspace isn’t active then all of its windows are placed outside the visible area of the screen, in the bottom right or left corner. Once you switch back to the workspace, (e.g. by the means of workspace command, or cmd + tab) windows are placed back to the visible area of the screen.

When you quit the Dwmac or when the Dwmac detects that it’s about to crash, Dwmac will place all windows back to the visible area of the screen.

Dwmac shows the name of currently active workspace in its tray icon (top right corner), to give users a visual feedback on what workspace is currently active.

The intended workflow of using Dwmac workspaces is to only have one macOS Space (or as many monitors you have, if Displays have separate Spaces is enabled) and don’t interact with macOS Spaces anymore.

Note

For better or worse, macOS doesn’t allow to place windows outside the visible area entirely. You will still be able to see a 1 pixel vertical line of "hidden" windows in the bottom right or left corner of your screen. That means, that if Dwmac crashes badly you will still be able to manually "unhide" the windows by dragging these few pixels to the center of the screen.

If you want to minimize the visibility of hidden windows, it’s recommended to place Dock in the bottom (and additionally turn automatic hiding on)

4.1. Proper monitor arrangement

Since Dwmac needs a free space to hide windows in, please make sure to arrange monitors in a way where every monitor has free space in the bottom right or left corner. (System Settings → Displays → Arrange…​)

If you fail to arrange your monitors properly, you will see parts of hidden windows on other monitors.

monitor arrangement 1 bad
Figure 1. Bad monitor arrangement. Monitor 2 doesn’t have free space in either of the bottom corners
monitor arrangement 1 good
Figure 2. Good monitor arrangement. Every monitor has free space in either of the bottom corners
monitor arrangement 2 bad
Figure 3. Bad monitor arrangement. Monitor 1 doesn’t have free space in either of the bottom corners
monitor arrangement 2 good
Figure 4. Good monitor arrangement. Every monitor has free space in either of the bottom corners

4.2. A note on mission control

For some reason, mission control doesn’t like that Dwmac puts a lot of windows in the bottom right corner of the screen. Mission control shows windows too small even when there is enough space to show them bigger.

There is a workaround. You can enable Group windows by application setting:

defaults write com.apple.dock expose-group-apps -bool true && killall Dock

(or in System Settings: System Settings → Desktop & Dock → Group windows by application). For whatever weird reason, it helps.

4.3. A note on ‘Displays have separate Spaces’

There is an observation that macOS works better and more stable if you disable Displays have separate Spaces. (It’s enabled by default) People report all sorts of weird issues related to focus and performance when this setting is enabled:

  • Wrong window may receive focus in multi-monitor setup: #101 (Bug in Apple API)

  • Wrong borderless Alacritty window may receive focus in single monitor setup: #247 (Bug in Apple API)

  • Performance issues: #333

  • macOS randomly switches focus back: #289

When Displays have separate Spaces is enabled, moving windows between monitors causes windows to move between different Spaces which is not correctly handled by the public APIs Dwmac uses, apparently, these APIs are not aware about Spaces existence. Spaces are just cursed in macOS. The less Spaces you have, the better macOS behaves.

‘Displays have separate Spaces’ is enabled ‘Displays have separate Spaces’ is disabled

Is it possible for window to span across several monitors?

❌ No. macOS limitation

👍 Yes

Overall stability and performance

❌ Weird focus and performance issues may happen (see the list above)

👍 Public Apple API are more stable (which in turn affects Dwmac stability)

When the first monitor is in fullscreen

👍 Second monitor operates independently

❌ Second monitor is unusable black screen

macOS status bar …​

…​ is displayed on both monitors

…​ is displayed only on main monitor

If you don’t care about macOS native fullscreen in multi-monitor setup (which is itself clunky anyway, since it creates a separate Space instance), I recommend disabling Displays have separate Spaces.

You can disable the setting by running:

defaults write com.apple.spaces spans-displays -bool true && killall SystemUIServer

(or in System Settings: System Settings → Desktop & Dock → Displays have separate Spaces). Logout is required for the setting to take effect.

5. Callbacks

5.1. 'on-window-detected' callback

You can use on-window-detected callback to run commands every time a new window is detected.

Here is a showcase example that uses all the possible configurations:

[[on-window-detected]]
    if.app-id = 'com.apple.systempreferences'
    if.app-name-regex-substring = 'settings'
    if.window-title-regex-substring = 'substring'
    if.workspace = 'workspace-name'
    if.during-dwmac-startup = true
    check-further-callbacks = true
    run = ['layout floating', 'move-node-to-workspace S']  # The callback itself

run commands are run only if the detected window matches all the specified conditions. If no conditions are specified then run is run every time a new window is detected.

Several callbacks can be declared in the config. The callbacks are processed in the order they are declared. By default, the first callback that matches the criteria is run, and further callbacks are not considered. (The behavior can be overridden with check-further-callbacks option)

Available window conditions are:

Condition TOML key Condition description

if.app-id

Application ID exact match of the detected window

if.app-name-regex-substring

Application name case insensitive regex substring of the detected window

if.window-title-regex-substring

Window title case insensitive regex substring of the detected window

if.during-dwmac-startup

  • If true then run the callback only during Dwmac startup.

  • If false then run callback only NOT during Dwmac startup.

  • If not specified then the condition isn’t checked

if.workspace

Window’s workspace name exact match

  • if.during-dwmac-startup = true is useful if you want to do the initial app arrangement only on startup.

  • if.during-dwmac-startup = false is useful if you want to relaunch Dwmac, but the callback has side effects that you don’t want to run on every relaunch. (e.g. the callback opens new windows)

There are several ways to know app-id:

Important
Some windows initialize their title after the window appears. window-title-regex-substring may not work as expected for such windows

Examples of automations:

  • Assign apps on particular workspaces

    [[on-window-detected]]
        if.app-id = 'org.alacritty'
        run = 'move-node-to-workspace T' # mnemonics T - Terminal
    
    [[on-window-detected]]
        if.app-id = 'com.google.Chrome'
        run = 'move-node-to-workspace W' # mnemonics W - Web browser
    
    [[on-window-detected]]
        if.app-id = 'com.jetbrains.intellij'
        run = 'move-node-to-workspace I' # mnemonics I - IDE
  • Make all windows float by default

    [[on-window-detected]]
        check-further-callbacks = true
        run = 'layout floating'

5.2. 'on-focus-changed' callbacks

You can track focus changes using the following callbacks: on-focus-changed and on-focused-monitor-changed.

  • on-focus-changed is called every time focused window or workspace changes.

  • on-focused-monitor-changed is called every time focused monitor changes.

A common use case for the callbacks is to implement "mouse follows focus" behavior. All you need is to combine the callback of your choice with move-mouse command:

on-focused-monitor-changed = ['move-mouse monitor-lazy-center'] # Mouse lazily follows focused monitor
# or
on-focus-changed = ['move-mouse window-lazy-center'] # Mouse lazily follows any focus (window or workspace)

You shouldn’t rely on the order callbacks are called, since it’s an implementation detail and can change from version to version.

The callbacks are "recursion resistant", which means that any focus change within the callback won’t retrigger the callback. Changing the focus within these callbacks is a bad idea anyway, and the way it’s handled will probably change in future versions.

5.3. 'exec-on-workspace-change' callback

exec-on-workspace-change callback allows to run arbitrary process when focused workspace changes. It may be useful for integrating with bars.

# Notify Sketchybar about workspace change
exec-on-workspace-change = ['/bin/bash', '-c',
    'sketchybar --trigger dwmac_workspace_change FOCUSED=$DWMAC_FOCUSED_WORKSPACE'
]

Besides the exec.env-vars, the process has access to the following environment variables:

  • DWMAC_FOCUSED_WORKSPACE - the workspace user switched to

  • DWMAC_PREV_WORKSPACE - the workspace user switched from

For a more elaborate example on how to integrate with Sketchybar see ./goodies

6. Multiple monitors

  • The pool of workspaces is shared between monitors

  • Each monitor shows its own workspace. The showed workspaces are called "visible" workspaces

  • Different monitors can’t show the same workspace at the same time

  • Each workspace (even invisible, even empty) has a monitor assigned to it

  • By default, all workspaces are assigned to the "main" monitor ("main" as in System → Displays → Use as)

When you switch to a workspace:

  1. Dwmac takes the assigned monitor of the workspace and makes the workspace visible on the monitor

  2. Dwmac focuses the workspace

You can move workspace to a different monitor with move-workspace-to-monitor command.

The idea of making pool of workspaces shared is based on the observation that most users have a limited set of workspaces on their secondary monitors. Secondary monitors are frequently dedicated to specific tasks (browser, shell), or for monitoring various activities such as logs and dashboards. Thus, using one workspace per secondary monitor and "the rest" on the main monitor often makes sense.

Note

When you switch to an empty workspace, Dwmac puts the workspace on an assigned monitor.

  • I find that Dwmac model works better with the observation listed above.

  • Dwmac model is more consistent (it works the same for empty workspaces and non-empty workspaces)

6.1. Assign workspaces to monitors

You can use workspace-to-monitor-force-assignment syntax to assign workspaces to always appear on particular monitors

[workspace-to-monitor-force-assignment]
    1 = 1                            # Monitor sequence number from left to right. 1-based indexing
    2 = 'main'                       # Main monitor
    3 = 'secondary'                  # Non-main monitor in case when there are only two monitors
    4 = 'built-in'                   # Case insensitive regex substring
    5 = '^built-in retina display$'  # Case insensitive regex match
    6 = ['secondary', 'dell']        # You can specify multiple patterns.
                                     #   The first matching pattern will be used
  • Left hand side of the assignment is the workspace name

  • Right hand side of the assignment is the monitor pattern

Supported monitor patterns:

  • main - "Main" monitor ("main" as in System Settings → Displays → Use as)

  • secondary - Non-main monitor in case when there are only two monitors

  • <number> (e.g. 1, 2) - Sequence number of the monitor from left to right. 1-based indexing

  • <regex-pattern> (e.g. dell.*, built-in.*) - Case insensitive regex substring pattern

You can specify multiple patterns as an array. The first matching pattern will be used

move-workspace-to-monitor command has no effect for workspaces that have monitor assignment

7. Dialog heuristics

  • Apple provides accessibility API for apps to let others know which of their windows are dialogs

  • A lot of apps don’t implement this API or implement it improperly

    Even some Apple dialogs don’t implement the API properly. (E.g. Finder "Copy" progress window doesn’t let others know that it’s a dialog)

Dwmac uses the API to gently ask windows whether they are dialogs, but Dwmac also applies some heuristics.

For example, windows without a fullscreen button (NB! fullscreen button and maximize button are different buttons) are generally considered dialogs, excluding terminal apps (WezTerm, Alacritty, iTerm2, etc.).

Windows that are recognized as dialogs are floated by default.

If you find that some windows are not handled properly, you’re welcome to create a PR that improves the heuristic. It’s fine to hardcode special handling for popular applications, Dwmac already does it. Please see isDialogHeuristic function in Dwmac sources.

You can also use on-window-detected to force tile or force float all windows of a particular application:

  1. Force tile all the windows (or windows of a particular app)

    [[on-window-detected]]
        if.app-id = '...'
        run = 'layout tiling'
  2. Force float all the windows (or windows of a particular app)

    [[on-window-detected]]
        if.app-id = '...'
        run = 'layout floating'

8. Common pitfall: keyboard keys handling

If you can’t make Dwmac handle some keys in your config, please make sure that you don’t have keys conflict with other software that might listen to global keys (e.g. skhd, Karabiner-Elements, Raycast)