{"id":19637195,"url":"https://github.com/seeker04/plwm","last_synced_at":"2026-04-02T18:31:37.790Z","repository":{"id":206579861,"uuid":"716819971","full_name":"Seeker04/plwm","owner":"Seeker04","description":"An X11 window manager written in Prolog","archived":false,"fork":false,"pushed_at":"2026-03-29T23:11:09.000Z","size":22278,"stargazers_count":296,"open_issues_count":35,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-30T00:56:18.781Z","etag":null,"topics":["linux","prolog","scryer-prolog","swi-prolog","tiling-window-manager","x11"],"latest_commit_sha":null,"homepage":"","language":"Prolog","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Seeker04.png","metadata":{"files":{"readme":"docs/README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-11-10T00:07:41.000Z","updated_at":"2026-03-29T23:10:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"ec93d365-a1e1-4fa6-a690-610a38c1b2b1","html_url":"https://github.com/Seeker04/plwm","commit_stats":null,"previous_names":["seeker04/plwm"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/Seeker04/plwm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seeker04%2Fplwm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seeker04%2Fplwm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seeker04%2Fplwm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seeker04%2Fplwm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Seeker04","download_url":"https://codeload.github.com/Seeker04/plwm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Seeker04%2Fplwm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["linux","prolog","scryer-prolog","swi-prolog","tiling-window-manager","x11"],"created_at":"2024-11-11T12:33:35.101Z","updated_at":"2026-04-02T18:31:37.753Z","avatar_url":"https://github.com/Seeker04.png","language":"Prolog","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!-- MIT License, Copyright (c) 2023-2025 Barnabás Zahorán, see LICENSE --\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"plwm logo\" src=\"../img/logo.png\"\u003e\n\u003c/p\u003e\n\n# plwm - An X11 window manager written in Prolog\n\n**Table of Contents**\n- [About](#about)\n- [Feature highlights](#feature-highlights)\n- [Installation](#installation)\n  - [Minimal environment](#minimal-environment)\n  - [Using a display manager](#using-a-display-manager)\n- [Usage](#usage)\n  - [Basics](#basics)\n  - [Default keybindings](#default-keybindings)\n  - [Configuration](#configuration)\n  - [External bars](#external-bars)\n  - [Multi-monitor](#multi-monitor)\n  - [Layout overrides](#layout-overrides)\n  - [Rules](#rules)\n  - [Menus](#menus)\n  - [Hooks](#hooks)\n  - [Scriptability](#scriptability)\n- [Screenshots](#screenshots)\n- [Project status](#project-status)\n- [Contribution](#contribution)\n- [FAQ](#faq)\n- [Similar projects](#similar-projects)\n\n# About\n\nplwm is a highly customizable X11 dynamic tiling window manager written in [Prolog](https://en.wikipedia.org/wiki/Prolog).\n\nMain goals of the project are: high code \u0026 documentation quality; powerful yet easy customization; covering most common needs of tiling WM users; and to stay small, easy to use and hack on.\n\nPowered by [SWI-Prolog](https://www.swi-prolog.org/)\n\nPorting to [Scryer Prolog](https://www.scryer.pl/), a Rust-powered ISO Prolog implementation, is underway too! ([discussion](https://github.com/Seeker04/plwm/discussions/102), [PR](https://github.com/Seeker04/plwm/pull/114))\n\n**2025.11.14:** plwm was presented at [Scryer Prolog Meetup 2025](https://hsd-pbsa.de/veranstaltung/scryer-prolog-meetup-2025/). Slides are uploaded in [ODP](scryer-meetup-2025/plwm.odp) and [PDF](scryer-meetup-2025/plwm.pdf).\n\n# Feature highlights\n\n* Easy to hack on, great way to introduce yourself to the logic programming paradigm and Prolog\n* Easy to configure: Prolog is declarative, so even though the config is source code, it feels like a dedicated format\n* Tiling is dynamic, with various layouts included by default: monocle, vertical/horizontal stacks, grid, left/right/top/bottom/centered master-stack, nrows(N), ncols(N)\n* Floating windows are also supported (move/resize with mouse)\n* Support for external bars, e.g. polybar, lemonbar\n* Nice level of EWMH compilance - **partially still work-in-progress**\n* Performance: plwm is fast and light as a feather when it comes to resource usage (10-15 MB memory)\n* Dynamic workspace operations: create, rename, reindex or delete workspaces on the fly\n* Other features: multi-monitor support, inner/outer gaps, menu integrations with dmenu/rofi, rules, hooks, animations, command fifo and more\n* You can say: \"My window manager is a semantic consequence of a set of axioms and implications which my computer is deducing/proving from an infinitely branching proof-tree\"\n\n# Installation\n\n**From release**\n\nDownload the latest version from the [releases](https://github.com/Seeker04/plwm/releases), extract and run `./install.sh`.\n\n**From source**\n\nRun `make \u0026\u0026 sudo make install`\n\n**Dependencies:**\n\n* `xorg` with `libx11-dev`, `libxft-dev`, `libxrandr-dev`\u003cbr\u003e\n(exact package names may vary, `dev` versions are only needed when building from source)\n* [SWI-Prolog](https://www.swi-prolog.org/Download.html) (most likely also packaged by your distro)\n\nE.g. on Ubuntu 22.04, easiest way to install them is:\n\n`sudo apt install xorg-dev swi-prolog`\n\n## Minimal environment\n\nAdd the following line to the end of your `~/.xinitrc`:\n\n`exec plwm`\n\nThen simply use the command `startx` in tty.\n\nFor example, to automate this after logging in, put these lines in `~/.bash_profile` (or to whatever shell startup that matches your setup):\n\n```bash\n# Start X from login shell on tty1 if not already started\nif [ -z \"$DISPLAY\" -a $(tty) = \"/dev/tty1\" ]; then\n\texec startx\nfi\n```\n\n## Using a display manager\n\nPlease refer to the documentation of your display manager on how to set up sessions for custom WMs.\n\n[Here](https://wiki.archlinux.org/title/Display_manager) you can find good references for this.\n\nFor most modern display managers, you'll have to create a `/usr/share/xsessions/plwm.desktop` file with content like:\n\n```ini\n[Desktop Entry]\nName=plwm\nComment=This session logs you into plwm\nExec=plwm\nIcon=path-to-this-repo/plwm/img/logo.png\nType=Application\n```\n\n# Usage\n\n## Basics\n\nCommand-line options are listed by running `plwm -h`. The manual plwm(1) also contains them as well as the default keybindings.\n\nIf you have already used dynamic tiling WMs like dwm, then nothing should be too surprising in this section.\n\nAll windows are _managed_ by default. This means that whenever a window spawns, it will be added to a list. We call this list the _stack_. Also, the placement and the size of the windows are automatically calculated and set. This is determined by the currently active _layout_ (stack, horizontal stack, grid, etc.).\n\nSome layouts are called \"master-stack\" layouts (the ones which have \"master\" in their names, e.g., `lmaster`). With these layouts the first `nmaster` number of windows (1 by default) in the stack go to a dedicated area. These are the _master windows_, which usually have the most space allocated for them. All other windows go to the remaining screen space.\n\nFor example, if you start plwm with the default config, you have: `layout = lmaster`, `nmaster = 1`, `mfact = 2/3`, which means that the stack's top window will always be on the left side and occupy 2/3 of the screen width, while the other windows will be in a secondary stack on the right having 1/3 of the screen width.\n\nSome base promises of this approach:\n* Declarative window management: you only _tell_ your system _what_ window arrangement you want and don't do the movement/resizing (don't need to care about the _how_)\n* When new windows are spawned or old ones close, the layout will adapt automatically\n* Playing with the `layout`, `nmaster`, `mfact` trio, one can cook up mostly any kind of arrangement a situation may need, just with a few keystrokes.\n* 100% of the screen space is utilized at all times, 99% if you use gaps :)\n\n## Default keybindings\n\n| Keybind               | Predicate                         | Description                                                       |\n| --------------------- | --------------------------------- | ----------------------------------------------------------------- |\n| super + j             | `shift_focus(down)`               | Focus next window in stack                                        |\n| super + k             | `shift_focus(up)`                 | Focus previous window in stack                                    |\n| super + shift + j     | `move_focused(down)`              | Swap focused window with the next                                 |\n| super + shift + k     | `move_focused(up)`                | Swap focused window with the preceding                            |\n| super + Return        | `focused_to_top`                  | Move focused window to top of the stack                           |\n| super + q             | `close_focused`                   | Close focused window                                              |\n| super + shift + space | `toggle_floating`                 | Toggle between manual and automatic management of focused window  |\n| super + f             | `toggle_fullscreen`               | Toggle fullscreen of focused window                               |\n| super + shift + q     | `quit`                            | Quit plwm                                                         |\n| super + i             | `change_nmaster(+1)`              | Increase number of master windows by 1                            |\n| super + d             | `change_nmaster(-1)`              | Decrease number of master windows by 1                            |\n| super + h             | `change_mfact(-0.05)`             | Remove 5% from the space of master area                           |\n| super + l             | `change_mfact(+0.05)`             | Add 5% to the space of master area                                |\n| super + shift + f     | `layout:set_layout(floating)`     | Switch to `floating` layout (all windows unmanaged)               |\n| super + shift + m     | `layout:set_layout(monocle)`      | Switch to `monocle` layout (all maximized, one visible at a time) |\n| super + shift + s     | `layout:set_layout(stack)`        | Switch to `stack` layout                                          |\n| super + shift + h     | `layout:set_layout(hstack)`       | Switch to `hstack` (horizontal stack) layout                      |\n| super + shift + g     | `layout:set_layout(grid)`         | Switch to `grid` layout                                           |\n| super + shift + l     | `layout:set_layout(lmaster)`      | Switch to `lmaster` (left master) layout                          |\n| super + shift + r     | `layout:set_layout(rmaster)`      | Switch to `rmaster` (right master) layout                         |\n| super + shift + t     | `layout:set_layout(tmaster)`      | Switch to `tmaster` (top master) layout                           |\n| super + shift + b     | `layout:set_layout(bmaster)`      | Switch to `bmaster` (bottom master) layout                        |\n| super + shift + c     | `layout:set_layout(cmaster)`      | Switch to `cmaster` (central master) layout                       |\n| super + Tab           | `toggle_workspace`                | Switch between last two workspaces                                |\n| super + shift + Tab   | `toggle_hide_empty_workspaces`    | Toggle the `hide_empty_workspaces` setting                        |\n| super + 1             | `switch_workspace('1')`           | Go to workspace '1'                                               |\n| ...                   | ...                               | ...                                                               |\n| super + 9             | `switch_workspace('9')`           | Go to workspace '9'                                               |\n| super + p             | `switch_workspace(prev)`          | Go to previous workspace                                          |\n| super + n             | `switch_workspace(next)`          | Go to next workspace                                              |\n| super + shift + 1     | `move_focused_to_workspace('1')`  | Move focused window to workspace '1'                              |\n| ...                   | ...                               | ...                                                               |\n| super + shift + 9     | `move_focused_to_workspace('9')`  | Move focused window to workspace '9'                              |\n| super + shift + p     | `move_focused_to_workspace(prev)` | Move focused window to previous workspace                         |\n| super + shift + n     | `move_focused_to_workspace(next)` | Move focused window to next workspace                             |\n| super + ,             | `switch_monitor(prev)`            | Switch to previous monitor                                        |\n| super + .             | `switch_monitor(next)`            | Switch to next monitor                                            |\n| super + shift + ,     | `move_focused_to_monitor(prev)`   | Move focused window to previous monitor                           |\n| super + shift + .     | `move_focused_to_monitor(next)`   | Move focused window to next monitor                               |\n\n**Tip:** `change_nmaster/1` and `change_mfact/1` can not only take deltas, but also exact values to assign. Omit the the `+` and `-` prefixes for this behavior.\n\n**Tip:** For both `switch_workspace/1` and `move_focused_to_workspace/1`, you can also pass `prev_nonempty` and `next_nonempty` for switching/moving to the next _non-empty_ workspace. The former can be nice for cycling through only the relevant workspaces. The latter can also work well when using the `hide_empty_workspaces` setting. You can also pass workspace _indices_ instead of _names_. Use numbers starting from 1 **without single quotes** for this (of course, this only makes sense if you use different workspace names than the default '1'...'9').\n\n**Tip:** There are two parametric layouts which have no default keybindings: `nrows(N)` and `ncols(N)`. You can use them if you wish to have layouts with fixed number of rows or columns. For example, you can add a line like:\n\n```Prolog\nsuper + shift + \"T\" -\u003e layout:set_layout(ncols(3))\n```\n\nthen you'll have a triple stack layout where your windows will be evenly spread and sized among the three columns (can be nice with wide monitors).\n\n**Tip:** you can bind multiple (consecutive) actions to a single keystroke by writing a comma separated list of predicates enclosed in parentheses, i.e., `Keyes -\u003e (Act1, Act2,...)`\n\n## Configuration\n\n`sudo make install` installs the [default configuration](../config/config.pl) to `/etc/plwm/config.pl`. This file can be copied to user config directories.\n\nplwm attempts reading configuration when it starts from the first file among\n- `$XDG_CONFIG_HOME/plwm/config.pl`\n- `$HOME/.config/plwm/config.pl`\n- `/etc/plwm/config.pl`\n\nA custom path can be specified with the `-c` flag.\n\n**Note:** a reinstall will overwrite `/etc/plwm/config.pl`, however a backup is always created if there is any difference.\n\nWhile cooking your config, you can use the `-C` flag to quickly and easily check its validity.\n\n`config.pl` is self-documenting with lots of comments, but here is a quick reference:\n\n| Setting                    | Values _(default value)_                                        | Description                                     |\n| -------------------------- | --------------------------------------------------------------- | ----------------------------------------------- |\n| `default_nmaster`          | 0\u003c= integer\u003cbr\u003e**Default:** 1                                   | Initial number of master windows                |\n| `default_mfact`            | 0.05 .. 0.95\u003cbr\u003e**Default:** 2/3                                | Initial space percentage given to master area   |\n| `default_layout`           | floating, monocle, stack, hstack, nrows(N), ncols(N), grid, lmaster, rmaster, tmaster, bmaster, cmaster\u003cbr\u003e**Default:** lmaster | Layout to use by default |\n| `attach_bottom`            | true or false\u003cbr\u003e**Default:** false                             | Put new windows to bottom of the stack instead  |\n| `border_width`             | 0\u003c= integer\u003cbr\u003e**Default:** 1                                   | Border width in pixels                          |\n| `border_width_focused`     | 0\u003c= integer\u003cbr\u003e**Default:** 1                                   | Border width for focused window in pixels       |\n| `border_color`             | hexa code or color name\u003cbr\u003e**Default:** \"white\"                 | Border color                                    |\n| `border_color_focused`     | hexa code or color name\u003cbr\u003e**Default:** \"blue\"                  | Border color for focused window                 |\n| `snap_threshold`           | 0\u003c= integer\u003cbr\u003e**Default:** 32                                  | Snap to screen border threshold in pixels while dragging windows |\n| `outer_gaps`               | 0\u003c= integer\u003cbr\u003e**Default:** 0                                   | Space reserved around screen edge in pixels     |\n| `inner_gaps`               | 0\u003c= integer\u003cbr\u003e**Default:** 0                                   | Space between adjacent tiled windows in pixels  |\n| `workspaces`               | list of atoms in UTF-8, at least 1\u003cbr\u003e**Default:** ['1','2','3','4,'5','6','7','8','9'] | Workspace names |\n| `starting_workspace`       | an element from workspaces\u003cbr\u003e**Default:** '1'                  | Starting workspace                              |\n| `hide_empty_workspaces`    | true or false\u003cbr\u003e**Default:** false                             | Hide names of inactive and empty workspaces from bars |\n| `ws_format`                | string with a \\~w **or**\u003cbr/\u003estring with a \\~d followed by a \\~w\u003cbr\u003e**Default:** \"\\~w\"  | Format of empty workspaces on bars (~d = index, ~w = name) |\n| `ws_format_occupied`       | string with a \\~w **or**\u003cbr/\u003estring with a \\~d followed by a \\~w\u003cbr\u003e**Default:** \"▘\\~w\" | Format of occupied workspaces on bars |\n| `layout_default_overrides` | list of (Monitor, Workspace -\u003e Nmaster, Mfact, Layout)\u003cbr\u003e**Default:** [] | Overrides of the 3 values to specific monitors and/or workspaces (explained [here](#layout-overrides)) |\n| `bar_classes`              | list of string pairs from bar's WM_CLASS,\u003cbr/\u003equery with [xprop(1)](https://linux.die.net/man/1/xprop)\u003cbr\u003e**Default:** [\"polybar\"-\"Polybar\"] | Space will be reserved for matching windows and they cannot be focused, resized, etc. |\n| `bar_placement`            | follow_focus, static\u003cbr\u003e**Default:** follow_focus               | Determines placement of external bars (explained [here](#layout-overrides)) |\n| `fifo_enabled`             | true or false\u003cbr\u003e**Default:** false                             | Whether to spawn a command FIFO\u003cbr\u003e(explained [here](#scriptability)) |\n| `fifo_path`                | string\u003cbr\u003e**Default:** \"/tmp/plwm_fifo\"                         | Path of command FIFO                            |\n| `menucmd`                  | list of strings\u003cbr\u003e**Default:** [\"dmenu\", \"-i\", \"-l\", \"20\", \"-p\"] | Command and its arguments to use for menu operations |\n| `animation_enabled`        | true or false\u003cbr\u003e**Default:** false                             | Whether to animate window move/resize           |\n| `animation_time`           | 0.0\u003c float\u003cbr\u003e**Default:** 0.2                                  | Time of the animation                           |\n| `animation_granularity`    | 1\u003c= integer\u003cbr\u003e**Default:** 30                                  | Number of steps in animation interpolations     |\n| `modkey`                   | shift, lock, ctrl, alt, mod2, mod3, super, mod5\u003cbr\u003e**Default:** super | Key you must hold for mouse operations    |\n| `scroll_up_action`         | callable term or 'none'\u003cbr\u003e**Default:** switch_workspace(next)  | Action to perform on modkey + scroll up         |\n| `scroll_down_action`       | callable term or 'none'\u003cbr\u003e**Default:** switch_workspace(prev)  | Action to perform on modkey + scroll down       |\n| `keymaps`                  | list of (Modifiers + Key -\u003e Action)\u003cbr\u003e**Default:** [see here](#default-keybindings) | Modifiers: see values at `modkey`\u003cbr/\u003eKey: keycode, [usual X11 names](http://xahlee.info/linux/linux_show_keycode_keysym.html), or [special key](../src/xf86names.pl)\u003cbr/\u003eAction: callable term |\n| `rules`                    | list of (Name, Class, Title -\u003e Monitor, Workspace, Mode)\u003cbr\u003e**Default:** [] | Auto place and configure matching windows (explained [here](#rules)) |\n| `hooks`                    | list of (Event -\u003e Action)\u003cbr\u003e**Default:** `[start -\u003e writeln(\"plwm starting\"), quit -\u003e writeln(\"plwm quitting\")]` | Run custom logic on certain events (explained [here](#hooks)) |\n\n**Tips**\n\n* You can safely remove any setting from your config file, plwm will use the default value for those.\n* In `keymaps/1`, the callback predicates can be arbitrary shell commands using `shellcmd/1`, even whole commandlines (some examples are included in the [default config](../config/config.pl)).\n* If you wish to stick to default keymaps mostly, with only a few changes and feel redundant to list the whole table in your config, you can simply omit the `keymaps/1` setting and add your changes as a `start` hook like this:\n\n```Prolog\nhooks([\n  start -\u003e (\n    add(keymaps, super + g -\u003e shellcmd(\"gcolor2\")),    % add new\n    add(keymaps, super + l -\u003e switch_workspace(next)), % overwrite existing\n    add(keymaps, super + f -\u003e none)                    % remove existing\n  )\n]).\n```\n\n**Changing settings during runtime**\n\n* `set/2` and `add/2` can be used to overwrite or append to existing settings, respectively. You can invoke them via a keymap, the [command fifo](#scriptability) or the [command menu](#menus).\n* The whole configuration file can be reloaded by calling `reload_config/0`.\n* You can use `dump_settings(Path, false)` to dump all current settings and `dump_settings(Path, true)` to dump only those that differ from the defaults to a file. Both are available in the command menu.\n\n## External bars\n\nFirst, you must specify `bar_classes/1` based on the WM_CLASS properties of your bars, which you can find out using [xprop(1)](https://linux.die.net/man/1/xprop). Then you can both:\n* manually start/close bars while plwm is already running\n* automatically start bars using `hooks/1` and its `start` event in the config\n\n`bar_placement/1` can take two values:\n* `follow_focus`: space will be reserved for bars on all monitors and bars will always be moved to the focused monitor (this is the default behavior)\n* `static`: space will only be reserved for bars in their respective monitors they occupy and no bar will be moved automatically. Placing bars to desired monitors is the responsibility of the user/external bar\n\nSome bars (e.g., polybar) already support different labels for empty and occupied workspaces. The advantages of plwm's built-in `ws_format/1` and `ws_format_occupied/1` are:\n* it works on more primitive bars as well\n* it respects multi-monitor scenarios, i.e., it only considers the set of workspaces that belong to the active monitor - this is relevant only with `bar_placement(follow_focus)`\n\nYou can toggle your external bars with the following hacks (again, some bars may have their own IPC mechanizm for this, but these are bar agnostic solutions):\n\n```Prolog\nalt + b -\u003e shellcmd(\"pkill polybar || polybar\")\n```\n\nor if you use multiple bars (a top and a bottom polybar called \"top\" and \"bot\", for example), you can:\n\n```Prolog\nalt + b -\u003e shellcmd(\"pkill polybar || (polybar top \u0026 polybar bot)\")\n```\n\nor if you want the ability to separately toggle the bars, use something like:\n\n```Prolog\nalt + b         -\u003e shellcmd(\"pkill -fx 'polybar top' || polybar top\"),\nalt + shift + b -\u003e shellcmd(\"pkill -fx 'polybar bot' || polybar bot\")\n```\n\n**Note:** if you are using polybar, **do not enable** its `override-redirect = true` setting (it can even crash plwm in some cases)! Reasoning: plwm itself handles all bars (anything that matches `bar_classes`, not just polybar) the intended way: bars cannot be focused, grabbed, moved or resized; tiling windows will never cover them (but you can drag floating windows above them); fullscreen windows will always cover them.\n\n## Multi-monitor\n\nThe multi-monitor concept in plwm is similar to dwm's: the set of workspaces is cloned for each monitor. So if you're using the default config with two monitors, then you'll have two times nine unique workspaces: `M1/1`, `M1/2`,..., `M2/9`.\n\nThe `switch_monitor/1` and `move_focused_to_monitor/1` predicates can take many different values:\n\n* `prev`/`next` will go to the previous/next monitor (these will wrap) - only these have keymaps by default\n* `prev_nonempty`/`next_nonempty` will go to the previous/next _non-empty_ monitor (a monitor is considered empty if its currently displayed workspace is empty). If you use a lot of monitors, say six, it could be convenient to cycle through only the relevant, i.e., non-empty ones (these will wrap)\n* `left`/`right`/`up`/`down` will go to the specified direction relative to the active monitor calculated from x/y screen coordinates (these won't wrap)\n* An output name, shown by `xrandr(1)`, e.g. \"eDP-1\" or \"HDMI-1\". If you have a consistent [xrandr(1)](https://man.archlinux.org/man/xrandr.1) setup, then you can refer them to move to arbitrary monitors\n* Index of managed monitor\n\nYou can also switch monitors by moving with the mouse between them. Likewise, windows can be dragged and dropped between monitors using the mouse.\n\n## Layout overrides\n\nThe `default_nmaster`, `default_mfact` and `default_layout` settings can each be overridden for specific monitors and/or workspaces using `layout_default_overrides/5`.\n\nUnderscore at the monitor column means \"all monitors\" for that workspace, while underscore at the workspace column means \"all workspaces\" on that monitor.\n\nUnderscores at the nmaster, mfact and layout columns mean not to alter the default settings for those.\n\n**Note:** a later override will overrule values of previous ones if there is an overlap.\n\nSome examples:\n\n```Prolog\nlayout_default_overrides([\n%  monitor      workspace     nmaster  mfact   layout\n  ( _        ,  '2'       -\u003e  _     ,  _    ,  grid    ),\n  ( \"eDP-1\"  ,  _         -\u003e  2     ,  1/2  ,  tmaster ),\n  ( \"HDMI-1\" ,  '3'       -\u003e  _     ,  0.90 ,  _       )\n]).\n```\n\n## Rules\n\nYou can apply custom rules to newly spawned windows that match one or more criteria. You can match the window's name, class and title. These are substring matches by default, but you can wrap them in `exact()` to force an exact match, or can write any of them as `_` to ignore those particular checks.\n\nThe `monitor` column takes an output name, or you can leave it as `_`, which implies opening on the currently active monitor.\n\nThe `workspace` column takes a workspace name (use single quotes), an index (from 1) or you can leave it as `_`, which implies opening on the currently active workspace.\n\nThe `mode` column can take the following values:\n- `managed`: Window will be managed (`_` also implies this)\n- `floating`: Window will be unmanaged, i.e., floating\n- `[X, Y, W, H]`: Same as `floating`, but also apply this geometry:\n    - `X` can be `left`, `right`, `center`, an integer coordinate, a percent (0.0..1.0) of x axis\n    - `Y` can be `top`, `bottom`, `center`, an integer coordinate, a percent (0.0..1.0) of y axis\n    - `W` can be an integer size or a percent (0.0..1.0) of screen width\n    - `H` can be an integer size or a percent (0.0..1.0) of screen height\n    - also, any of them can be left as `_` to keep the value the window spawned with\n- `fullscreen`: Window will open in fullscreen mode\n\n**Note:** if a window matches multiple rules, the first will be applied.\n\nSome examples:\n\n```Prolog\nrules([\n%  name      class     title                 monitor     wspace    mode\n  (_      ,  _      ,  exact(\"gcolor2\")  -\u003e  _        ,  _      ,  [center, center, 1/3, 1/3]),\n  (_      ,  _      ,  \"Firefox\"         -\u003e  \"eDP-1\"  ,  'www'  ,  fullscreen                ),\n  (\"Bar\"  ,  \"Baz\"  ,  _                 -\u003e  \"HDMI-1\" ,  '1'    ,  [700, 250, _, _]          )\n])\n```\n\nYou can find out the `name`, `class` and `title` values of windows using [xprop(1)](https://linux.die.net/man/1/xprop):\n\nWM_CLASS(STRING) = name , class\u003cbr\u003e\nWM_NAME(STRING)  = title\n\n**Tip:** if you want to apply a rule to a terminal application, then you'll need to use a terminal emulator that allows you to predefine the window class or title (most of them do). For example, alacritty has the `--class` and st has the `-c` flag for this:\n\n```prolog\nshellcmd(\"alacritty --class cmus -e cmus\")\n```\n\nthen you can match it with with a rule like:\n\n```prolog\n(\"cmus\", \"cmus\", _ -\u003e \"eDP-1\", '9', managed)\n```\n\n## Menus\n\nThe `menu` module also has default keybindings:\n\n**Navigation/window placement**\n\n| Keybind         | Predicate                | Description                                                   |\n| --------------- | ------------------------ | ------------------------------------------------------------- |\n| alt + w         | `menu:goto_window`       | List windows from all monitors/workplaces, go to monitor/workspace of selected, then raise and focus the window |\n| alt + shift + w | `menu:goto_workspace`    | List monitors/workspaces, except current, then go to selected |\n| alt + p         | `menu:pull_from`         | List windows from all monitors/workspaces, except current, then pull selected ones to the active monitor/workspace and focus it |\n| alt + shift + p | `menu:push_to`           | List monitors/workspaces, except current, then push the focused window to the selected |\n| alt + q         | `menu:close_windows`     | Close selected windows                                        |\n| alt + shift + q | `menu:keep_windows`      | Close all windows other than the selected                     |\n\n**Dynamic workspace operations**\n\n| Keybind         | Predicate                | Description                                                   |\n| --------------- | ------------------------ | ------------------------------------------------------------- |\n| alt + c         | `menu:create_workspace`  | Prompt for a name and append it to the list of workspaces     |\n| alt + r         | `menu:rename_workspace`  | Prompt for a name and rename the active workspace to it       |\n| alt + i         | `menu:reindex_workspace` | List possible workspace indices and move the active one to the selected index |\n| alt + d         | `menu:delete_workspaces` | List workspaces and delete the selected ones (its windows, if any, will be moved to the next workspace) - **Note:** deleting is not allowed if only one workspace is left |\n\n**Extras**\n\n| Keybind         | Predicate                | Description                                                   |\n| --------------- | ------------------------ | ------------------------------------------------------------- |\n| alt + shift + k | `menu:list_keymaps`      | List all defined keymaps, their mapped actions, descriptions of said actions and execute the selected. Useful for early discoverability or running forgotten or rarely used mappings |\n| alt + shift + c | `menu:list_cmds`         | List all available commands (i.e., predicates intended to be called by the user) and their descriptions, then execute the selected. Again, adds discoverability for new users. Can also be useful to run rare commands which have no mappings defined or even custom logic hacked into plwm (you'll need to add your predicates to `menu:list_cmds/0` and `menu:cmd_desc/2`) |\n\nThese predicates need `menucmd/1` set in the config to a program like [dmenu](https://tools.suckless.org/dmenu/) or [rofi](https://davatorium.github.io/rofi/). For example:\n\n```Prolog\nmenucmd([\"dmenu\", \"-i\", \"-l\", \"20\", \"-p\"]).\n```\n\nor\n\n```Prolog\nmenucmd([\"rofi\", \"-dmenu\"]).\n```\n\n**Note:** A prompt name will be written as last argument for `menucmd/1`, so if you are using dmenu, you should add `-p` as final argument.\n\n**Note:** `pull_from`, `delete_workspaces`, `close_windows` and `keep_windows` can operate on multiple selections. Use Ctrl+Enter in dmenu, or Shift+Enter with `-dmenu -multi-select` in rofi.\n\n## Hooks\n\nYou can run custom logic on certain events with the `hooks/1` configuration. If you wish to execute multiple predicates, list them inside a parantheses, separated by commas.\n\nAn example:\n```Prolog\nhooks([\n  start -\u003e (\n    shellcmd(\"xrandr --output HDMI-1 --left-of eDP-1\"), % setup 2nd monitor\n    shellcmd(\"picom\"),                                  % compositor\n    shellcmd(\"polybar\"),                                % status bar\n    switch_monitor(\"HDMI-1\")\n  ),\n\n  switch_workspace_post -\u003e (   % display different wallpaper on each workspace\n    active_mon_ws(_, Ws),\n    (Ws = '1' -\u003e shellcmd(\"feh --bg-fill ~/pic/bg/lake.jpg\")\n    ;Ws = '2' -\u003e shellcmd(\"feh --bg-fill ~/pic/bg/forest.jpg\")\n    ;Ws = '3' -\u003e shellcmd(\"feh --bg-fill ~/pic/bg/mountain.jpg\")\n    ;true)\n  ),\n]).\n```\n\nSupported events:\n\n| Event                   | Description                                             |\n| ----------------------- | ------------------------------------------------------- |\n| `start`                 | after all initialization, but before main X event loop  |\n| `quit`                  | before quitting, also before calling `XCloseDisplay(3)` |\n| `switch_workspace_pre`  | before switching workspace                              |\n| `switch_workspace_post` | after switching workspace                               |\n| `switch_monitor_pre`    | before switching monitor                                |\n| `switch_monitor_post`   | after switching monitor                                 |\n| `window_create_pre`     | before a window is mapped (won't run for bars)          |\n| `window_create_post`    | after a window is mapped (won't run for bars)           |\n| `window_destroy_pre`    | before a window is unmapped (won't run for bars)        |\n| `window_destroy_post`   | after a window is unmapped (won't run for bars)         |\n\nIf you would like to hook to some other event, feel free to submit a [GitHub issue](https://github.com/Seeker04/plwm/issues/new) for it.\n\n## Scriptability\n\nIf `fifo_enabled/1` and `fifo_path/1` are both set (disabled by default), then a named pipe will be created (with [mkfifo(1)](https://www.man7.org/linux/man-pages/man1/mkfifo.1.html)).\n\nThe user can execute any term or list of terms by writing their code to this pipe.\n\nFor most usecases, predicates listed by `menu:list_cmds/0` are the ones users would be interested in calling, but note that this mechanizm can execute _arbitrary_ terms. Even internal ones, or custom ones the user hacks together. There is no limit.\n\nIn case of an issue (e.g. syntax error, predicate does not exist,...), the error will be written to the plwm log.\n\nExamples:\n\nSwitch to the next workspace.\n```bash\necho \"switch_workspace(next).\" \u003e /tmp/plwm_fifo\n```\n\nCreate a new workspace 'temp', switch to it and set its layout.\n```bash\necho \"create_workspace(temp),\n      switch_workspace(temp),\n      layout:set_layout(grid).\" \u003e /tmp/plwm_fifo\n```\n\nUse conjunction (comma) if you need to share variables between terms.\n```bash\necho \"Ws = temp. create_workspace(Ws). switch_workspace(Ws).\" \u003e /tmp/plwm_fifo # doesn't work\necho \"Ws = temp, create_workspace(Ws), switch_workspace(Ws).\" \u003e /tmp/plwm_fifo # works\n```\n\nYou can write reusable script files like:\n```Prolog\n% switch to other monitor\nswitch_monitor(\"HDMI-1\"),\n\n% create some workspaces\nWorkspaces = [a, b, c],\nforall(member(Ws, Workspaces), (\n    create_workspace(Ws)\n)).\n```\nand simply execute them with:\n```bash\ncat myscript.pl \u003e /tmp/plwm_fifo\n```\n\nCreate workspaces '1'..'9'.\n```bash\n# this may yield inconsistent results (terms missing or in wrong order)\nfor i in {1..9}; do\n    echo \"create_workspace($i).\" \u003e /tmp/plwm_fifo\ndone\n\n# instead send the whole input at once like before\nfor i in {1..9}; do\n    echo \"create_workspace($i).\"\ndone \u003e /tmp/plwm_fifo\n```\n\n# Screenshots\n\n| ![Screenshot 1](../img/screenshot1.png) |\n|:--:|\n| *default appearance* |\n\n| ![Screenshot 2](../img/screenshot2.png) |\n|:--:|\n| *lmaster / nmaster=1 / mfact=2/3 / 1px borders / 18px inner \u0026 outer gaps / goto window menu / polybar / picom / gruvbox colors* |\n\n| ![Screenshot 3](../img/screenshot3.png) |\n|:--:|\n| *bmaster / nmaster=1 / mfact=2/3 / 3px focused border / 26px inner \u0026 outer gaps / polybar / picom /w rounded corners \u0026 shadows* |\n\n| \u003cvideo src=\"https://github.com/user-attachments/assets/e99ada44-6aa8-48c6-80bb-4e135d6ead79\"\u003e\u003c/video\u003e |\n|:--:|\n| *layout demonstration with animations enabled / time 0.2 / granularity 30* |\n\n# Project status\n\n**!!! Disclaimer:** plwm is still in an experimental state. First stable release will be v1.0.0. While crashes or other major bugs don't really occur, it's good to keep this in mind **!!!**\n\nAlso, this means that breaking changes (e.g. renaming of settings) are to be expected before reaching v1.0.0. We plan on switching to [semantic versioning](https://semver.org/) from that point onwards.\n\nFor known problems, see [the Issues with bug labels](https://github.com/Seeker04/plwm/issues?q=is%3Aopen+is%3Aissue+label%3Abug).\n\n# Contribution\n\nFirst and foremost, if you find any bugs, please [create a GitHub issue](https://github.com/Seeker04/plwm/issues/new), preferably, with all details you can provide. (First, please check if it's not reported already).\n\nIf you have a feature request or questions, feel free to [open discussions](https://github.com/Seeker04/plwm/discussions).\n\nAny code contribution is also welcome. Especially if it solves some known issue. For brand new ideas, I recommend creating a discussion first.\n\nPlease read the [Development Guide](development_guide.md).\n\n# FAQ\n\n**Why workplaces instead of tags?**\n\nTags is a cool generalization of workspaces, but I never actually utilized them in dwm for more than simple workspace usage. That's why I went with the simpler approach.\n\n**What about performance? Isn't Prolog slow?**\n\nCompared to what? C? Yes. Does it matter? No. I've been using dwm for 6 years, so I have a good idea of its speed, and when I switch to plwm, it feels **exactly** as snappy... on my 10 years old laptop. So I don't think, anyone will see a notable difference.\n\n**Isn't SWI-Prolog non-ISO compliant?**\n\nInteroperability between different Prolog implementations was never really on the table. Their C FFIs are also different, so [plx.c](../src/plx.c) would also need to be rewritten for each kind of Prolog. SWI-Prolog is arguably one of the most popular free and community-driven Prolog implementations, is easily accessible, has good documentation, some LSP support and a lot of libraries. Though, to be honest, plwm's code mostly sticks to fundamentals, so if someone really wanted to, it shouldn't be too hard to port this to another Prolog system...\n\n**Why not Wayland?**\n\nA Wayland port some day is not out of the realm of possibilities...\n\n**Some windows display all grey without contents!**\n\nIt is a known issue for Java applications which use the XToolkit/XAWT backend (e.g., UMLet). There is an excerpt from dwm's manual about it in plwm(1). Adding\n```bash\nexport _JAVA_AWT_WM_NONREPARENTING=1\n```\nto your `.xinitrc` should solve this problem.\n\n**My configuration doesn't work!**\n\nRun `plwm --check`, then you should see the problem. Consult the [table here](#configuration) to see the proper type for each setting. E.g. make sure you use double quotes for _strings_ and single quotes for _atoms_.\n\nIf you don't see any error, then please report it as an issue by attaching your config and any message plwm dumps to stderr or to its logfile with `-l`.\n\n**Something is missing...**\n\ntl;dr plwm is a window manager, not a full-fledged desktop environment.\n\nplwm is minimal in the sense that it doesn't try to solve problems outside of a wm's domain, especially if they are easily served by other programs (see [here](https://en.wikipedia.org/wiki/Unix_philosophy)):\n\n* Don't want a status bar? You're set. Want one (or more)? Here are a few: [polybar](https://polybar.github.io/), [lemonbar](https://github.com/LemonBoy/bar), [xmobar](https://codeberg.org/xmobar/xmobar)\n* Want transparent windows or other effects? Use a compositor like [picom](https://wiki.archlinux.org/title/Picom)\n* Want tabbed windows? Use [tabbed](https://tools.suckless.org/tabbed/)\n* Program launcher? [dmenu](https://tools.suckless.org/dmenu/) or [rofi](https://davatorium.github.io/rofi/) will get you covered\n* Auto hide cursor? Try [unclutter](https://wiki.archlinux.org/title/Unclutter)\n* Wallpaper? Many image viewers can set it, [feh](https://wiki.archlinux.org/title/Feh) for example\n* Screen locker? Check out [slock](https://tools.suckless.org/slock/)\n* plwm offers some basic rule based automation, but if you want more scriptability, try [xdotool](https://man.archlinux.org/man/xdotool.1.en) and [devilspie](https://linux.die.net/man/1/devilspie)\n\n# Similar projects\n\n* [tinywm](https://github.com/mackstann/tinywm): plwm's very first working version was basically tinywm, but in Prolog.\n* [dwm](https://dwm.suckless.org/): Later, features and even implementation tricks were taken from dwm.\n\nThanks to both of these projects for inspiration and code to learn from!\n\nSome other similar projects:\n\n* [xmonad](https://xmonad.org/)\n* [i3](https://i3wm.org/)\n* [bspwm](https://github.com/baskerville/bspwm)\n* [awesomewm](https://awesomewm.org/)\n\nand here's a [longer list](https://wiki.archlinux.org/title/Comparison_of_tiling_window_managers).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseeker04%2Fplwm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseeker04%2Fplwm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseeker04%2Fplwm/lists"}