pax_global_header00006660000000000000000000000064152626301620014515gustar00rootroot0000000000000052 comment=46fe416e9d2cf51592121aa89009339acfd8af8b tinyopsec-nwm-67f5c08/000077500000000000000000000000001526263016200146755ustar00rootroot00000000000000tinyopsec-nwm-67f5c08/.gitignore000066400000000000000000000000311526263016200166570ustar00rootroot00000000000000nwm nwm_dbg *.o *.tar.gz tinyopsec-nwm-67f5c08/LICENSE000066400000000000000000000023001526263016200156750ustar00rootroot00000000000000MIT License Copyright (c) 2026 tinyopsec Portions of this software are derived from dwm (https://dwm.suckless.org) by the suckless.org contributors, used under the MIT/X Consortium License. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tinyopsec-nwm-67f5c08/Makefile000066400000000000000000000033451526263016200163420ustar00rootroot00000000000000# nwm - Nano window manager # See LICENSE file for copyright and license details. VERSION = 1.6.5 PREFIX = /usr/local BINDIR = ${PREFIX}/bin MANPREFIX = ${PREFIX}/share/man MANDIR = ${MANPREFIX}/man1 X11INC = /usr/X11R6/include X11LIB = /usr/X11R6/lib # Linux #X11INC = /usr/include/X11 #X11LIB = /usr/lib # FreeBSD / DragonFly #X11INC = /usr/local/include #X11LIB = /usr/local/lib # NetBSD (pkgsrc) #X11INC = /usr/pkg/include #X11LIB = /usr/pkg/lib INCS = -I${X11INC} LIBS = -L${X11LIB} -lX11 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L \ -DVERSION=\"${VERSION}\" CFLAGS = -std=c99 -pedantic -Wall -Wextra \ -Wstrict-prototypes -Wmissing-prototypes -Wshadow \ -fno-common -O2 -flto ${INCS} ${CPPFLAGS} LDFLAGS = -flto ${LIBS} # Debug: make debug DBGFLAGS = -std=c99 -pedantic -Wall -Wextra \ -Wstrict-prototypes -Wmissing-prototypes -Wshadow \ -fno-common -g -O0 ${INCS} ${CPPFLAGS} CC ?= cc SRC = nwm.c OBJ = ${SRC:.c=.o} all: nwm .c.o: ${CC} -c ${CFLAGS} $< ${OBJ}: nwm.h nwm: ${OBJ} ${CC} -o $@ ${OBJ} ${LDFLAGS} debug: ${SRC} nwm.h ${CC} ${DBGFLAGS} -o nwm_dbg ${SRC} ${LIBS} dist: clean mkdir -p nwm-${VERSION} cp -R nwm.c nwm.h nwm.1 Makefile LICENSE README.md nwm-${VERSION} tar -czf nwm-${VERSION}.tar.gz nwm-${VERSION} rm -rf nwm-${VERSION} clean: rm -f nwm nwm_dbg ${OBJ} nwm-${VERSION}.tar.gz install: all mkdir -p ${DESTDIR}${BINDIR} install -m 755 nwm ${DESTDIR}${BINDIR}/nwm mkdir -p ${DESTDIR}${MANDIR} sed "s/VERSION/${VERSION}/g" < nwm.1 > ${DESTDIR}${MANDIR}/nwm.1 chmod 644 ${DESTDIR}${MANDIR}/nwm.1 uninstall: rm -f ${DESTDIR}${BINDIR}/nwm ${DESTDIR}${MANDIR}/nwm.1 .PHONY: all clean debug dist install uninstall tinyopsec-nwm-67f5c08/README.md000066400000000000000000000535771526263016200161750ustar00rootroot00000000000000
nwm header
nwm typing
logo

license MIT C99 / POSIX about 2000 lines version 1.6.5   Linux OpenBSD FreeBSD   X11 / Xlib make AUR about 3 MB RAM EWMH / ICCCM

> [!TIP] > **Want a status bar? You can have one.** nwm draws nothing itself, but it speaks EWMH, so an external bar works out of the box: > a docked bar is left alone (no border, no tag, no focus), tiling avoids the space it reserves (`_NET_WM_STRUT_PARTIAL`), and your tags are published as EWMH desktops. > [polybar](https://github.com/polybar/polybar), [tint2](https://gitlab.com/o9000/tint2) or [lemonbar](https://github.com/LemonBoy/bar) can show tag names, the current tag, occupied tags and the focused window title, and clicking a tag switches to it. > Details and a ready-to-use polybar config: [External Bars](#external-bars). ## Contents - [Quick Start](#quick-start) - [vs dwm](#vs-dwm) - [Features](#features) - [Screenshots](#screenshots) - [Installation](#installation) - [Usage](#usage) - [Key Bindings](#key-bindings) - [Configuration](#configuration) - [Window Rules](#window-rules) - [External Bars](#external-bars) - [How It Works](#how-it-works) - [Contributing](#contributing) - [Star History](#star-history) - [Related](#related) --- ## Quick Start ```sh git clone https://github.com/tinyopsec/nwm cd nwm make && sudo make install echo "exec nwm" >> ~/.xinitrc startx ``` > [!NOTE] > The default terminal is `st` and the default launcher is `dmenu_run`. Change them in `nwm.h` before compiling if you use something else (see [Configuration](#configuration)). > > For display managers, place a session file at `/usr/share/xsessions/nwm.desktop` (see [Usage](#usage)). --- ## vs dwm `nwm` takes direct inspiration from dwm but diverges in a few concrete ways: | Area | dwm | nwm | |---|---|---| | Lines of code | ~2901 total (.h / .c) | ~2000 (fits in one reading session) | | RAM at idle | ~5–15 MB | ~3–8 MB on my PC (leaner process image) | | Tiling arithmetic | Height of each window computed from the space left | Integer division, remainder assigned to the last window | | Gap support | Requires patching | Built in via `gappx` | | Mod+Tab behavior | Inconsistent across patches | Deterministic XOR two-slot history | | POSIX compliance | Uses GNU extensions in places | Strict C99 / POSIX orientation | | Status bar | Built-in bar, requires patching to remove | No built-in bar; any EWMH-aware external bar works (polybar, tint2, lemonbar), see [External Bars](#external-bars) | | Config complexity | ~100–150 lines of config + patch management | Single flat `nwm.h`, no patch stack | | Audit surface | Large: bar, fonts, drawing code | Minimal: window management only | dwm's bar and font rendering alone account for a significant portion of its codebase. `nwm` drops all of that. No drawing, no text, no color schemes beyond three border hex values. If you already run a patched dwm, `nwm` is roughly what you end up with after applying gaps and pertag-like patches, except the behavior is defined once, not assembled from diffs. --- ## Features | Category | Details | |---|---| | Layouts | Tiling (master/stack), floating, monocle | | Workspaces | 9 tags via bitmasks; windows may carry multiple tags | | Mouse support | Move, resize, toggle floating via modifier + button; carry a dragged window to another tag with the tag keys | | Gaps | Configurable `gappx`; tiling and monocle respect gaps, floating does not | | Borders | Inactive, focused, and urgent colors at compile time | | Fullscreen | Toggle via keybind or `_NET_WM_STATE_FULLSCREEN` | | Urgent hints | `XUrgencyHint` and `_NET_ACTIVE_WINDOW` handled as urgency | | Auto-float | `_NET_WM_WINDOW_TYPE_DIALOG` windows float automatically | | EWMH | `_NET_SUPPORTED`, `_NET_WM_STATE`, `_NET_WM_STATE_FULLSCREEN`, `_NET_ACTIVE_WINDOW`, `_NET_CLIENT_LIST`, `_NET_SUPPORTING_WM_CHECK`, `_NET_WM_WINDOW_TYPE`, `_NET_WM_WINDOW_TYPE_DIALOG`, `_NET_WM_WINDOW_TYPE_DOCK`, `_NET_WM_STRUT_PARTIAL`, `_NET_WM_STRUT`, `_NET_WORKAREA`, `_NET_NUMBER_OF_DESKTOPS`, `_NET_CURRENT_DESKTOP`, `_NET_DESKTOP_NAMES`, `_NET_DESKTOP_GEOMETRY`, `_NET_DESKTOP_VIEWPORT`, `_NET_WM_DESKTOP` | | External bars | Docks are not managed; their struts shrink the tiling area; tags are exposed as EWMH desktops; a bar can switch tags | | ICCCM | `WM_DELETE_WINDOW`, `WM_TAKE_FOCUS`, `WM_NORMAL_HINTS`, `WM_HINTS`, `WM_STATE` | | OpenBSD | `pledge(2)` support; FreeBSD support is expected but not actively tested | | Compilation | Clean target under `gcc` or `clang` with `-std=c99 -pedantic -Wall -Wextra` | --- ## Screenshots nwm on Alpine Linux rice, my pc btw: three terminals tiled in master/stack layout nwm master/stack with browser in master and two terminals in stack These are my screenshots. --- ## Installation ### Requirements | Dependency | Arch | Debian / Ubuntu | Void | Alpine | |---|---|---|---|---| | Xlib | libx11 | libx11-dev | libX11-devel | libx11-dev | | C compiler | `gcc` or `clang` | build-essential | gcc | build-base | No runtime dependencies beyond Xlib. ### From Source ```sh git clone --depth 1 https://github.com/tinyopsec/nwm cd nwm make sudo make install # installs to /usr/local/bin/nwm ``` Change `PREFIX` in the `Makefile` to install elsewhere. To stage the install (packaging), use `make DESTDIR=/tmp/stage install`. ### AUR (Arch Linux) ```sh yay -S nwm ``` Package: [aur.archlinux.org/packages/nwm](https://aur.archlinux.org/packages/nwm)
FreeBSD / OpenBSD / NetBSD / DragonFly Install Xlib via the system package manager. The `Makefile` defaults to the OpenBSD paths (`/usr/X11R6`). For other systems, edit `X11INC` and `X11LIB` at the top of the `Makefile`; the alternatives are already present but commented out, for example FreeBSD / DragonFly: ```make X11INC = /usr/local/include X11LIB = /usr/local/lib ``` NetBSD (pkgsrc) uses `/usr/pkg/include` and `/usr/pkg/lib`. Then build normally: ```sh make && sudo make install ``` `nwm` uses `pledge(2)` on OpenBSD automatically. No extra steps needed.
### Uninstall ```sh sudo make uninstall ``` --- ## Usage ### Starting nwm Add to `~/.xinitrc`: ```sh picom & feh --bg-scale ~/wallpaper.png & exec nwm ``` `nwm -v` prints the version. `nwm` has no built-in autostart. Launch background processes from `.xinitrc` or a wrapper script before the `exec` line. ### Display Managers For display managers: ```ini # /usr/share/xsessions/nwm.desktop [Desktop Entry] Name=nwm Comment=Minimal tiling X11 window manager Exec=nwm Type=Application ``` ### Terminal and Launcher Defaults in `nwm.h`: ```c static const char *termcmd[] = { "st", NULL }; static const char *dmenucmd[] = { "dmenu_run", NULL }; ``` To use `alacritty` and `rofi`: ```c static const char *termcmd[] = { "alacritty", NULL }; static const char *dmenucmd[] = { "rofi", "-show", "run", NULL }; ``` > [!IMPORTANT] > Recompile after any change to `nwm.h`: `make && sudo make install` --- ## Key Bindings The default modifier is Super (Win). To use Alt instead, change `#define MODKEY Mod4Mask` to `Mod1Mask` in `nwm.h`. ### Windows and Layouts | Key | Action | |---|---| | Mod + Return | Spawn terminal | | Mod + d | Spawn launcher (dmenu or whatever you chose) | | Mod + j | Focus next window in the client list (floating windows are skipped in tiling layouts) | | Mod + k | Focus previous window in the client list (floating windows are skipped in tiling layouts) | | Mod + h | Shrink master area by 5% | | Mod + l | Grow master area by 5% | | Mod + i | Increase master window count | | Mod + o | Decrease master window count | | Mod + Space | Promote focused window to master (if it is already master, the next tiled window is promoted) | | Mod + t | Tiling layout | | Mod + f | Floating layout | | Mod + m | Monocle layout | | Mod + F11 | Toggle fullscreen | | Mod + Shift + Space | Toggle floating for focused window | | Mod + q | Kill focused window | | Mod + Shift + e | Quit nwm | ### Tags | Key | Action | |---|---| | Mod + 1-9 | Switch to tag | | Mod + Ctrl + 1-9 | Toggle tag view (show alongside current) | | Mod + Shift + 1-9 | Move focused window to tag | | Mod + Ctrl + Shift + 1-9 | Toggle tag assignment on focused window | | Mod + 0 | View all tags | | Mod + Shift + 0 | Assign focused window to all tags | | Mod + Tab | Return to previous tag view (XOR two-slot) | > [!NOTE] > `Mod+Tab` is not a simple "previous tag" shortcut. It uses a two-slot XOR mechanism. It restores the previous tag bitmask saved by the last view change, including combined multi-tag views. The number of tags is the length of `tags[]` in `nwm.h`, from 1 to 31 (checked at compile time). `TAGKEYS` entries beyond that number do nothing. ### Mouse (modifier held over a client window) | Button | Action | |---|---| | Mod + Button1 | Move window | | Mod + Button2 | Toggle floating | | Mod + Button3 | Resize window | Dragging or resizing a tiled window beyond `snap` pixels from its position automatically makes it floating. The `snap` threshold is configurable in `nwm.h`. While dragging with Mod + Button1, press Mod + 1-9 to carry the window to that tag and switch the view to it. The drag continues, so you can drop the window in place or press another tag key. This reuses the `view` bindings from `keys[]`, so rebinding them in `nwm.h` rebinds this too; Mod + 0 sends the window to all tags, other bindings are ignored during a drag. All bindings are defined in the `keys[]` and `buttons[]` arrays in `nwm.h`. --- ## Configuration `nwm` is configured at compile time by editing `nwm.h`. There is no config file, no IPC, no reload mechanism. > [!IMPORTANT] > After every change to `nwm.h`, run `make && sudo make install` and restart nwm. ### Options | Option | Default | Description | |---|---:|---| | `borderpx` | `2` | Border width in pixels | | `gappx` | `6` | Gap size between windows and screen edges | | `col_nborder` | `#0E2426` | Inactive border color | | `col_sborder` | `#59C6B5` | Focused border color | | `col_uborder` | `#c47f50` | Urgent window border color | | `mfact` | `0.5` | Master area ratio (0.05–0.95) | | `nmaster` | `1` | Initial number of master windows | | `snap` | `16` | Edge snap / float-on-drag threshold in pixels | | `attachbottom` | `0` | Set to `1` to append new windows at the end of the client list (tiling order). A new window still goes to the top of the focus stack | | `focusonopen` | `1` | Set to `0` to keep focus on the current window when a new one opens | | `NWM_WITH_BORDERS` | `1` | `0` removes border colors; new windows get border width `0` | | `NWM_WITH_GAPS` | `1` | `0` removes `gappx`; tiling and monocle use zero gaps | | `NWM_WITH_PERTAG` | `1` | `0` removes per-tag state: one layout, `mfact` and `nmaster` for all tags | ### Modifier Key ```c #define MODKEY Mod4Mask /* Super / Win key */ // #define MODKEY Mod1Mask /* Alt key */ ``` ### Border Colors ```c static const char col_nborder[] = "#0E2426"; /* inactive */ static const char col_sborder[] = "#59C6B5"; /* focused */ static const char col_uborder[] = "#c47f50"; /* urgent */ ``` --- ## Window Rules Window rules are defined in `nwm.h`: ```c static const Rule rules[] = { /* class instance title tags isfloating */ { "Gimp", NULL, NULL, 0, 1 }, { "MPlayer", NULL, NULL, 0, 1 }, }; ``` Fields: | Field | Meaning | |---|---| | `class` | `WM_CLASS` class | | `instance` | `WM_CLASS` instance | | `title` | substring of the window title (`WM_NAME`) | | `tags` | tag bitmask, or `0` to keep current tags | | `isfloating` | `1` floating, `0` tiled, `-1` keep default decision | Only the first matching rule is applied. If no rule matches, `nwm` keeps its default floating decision. Dialog windows and windows that request fullscreen are made floating after the rules are applied, regardless of `isfloating`. A `tags` value with no valid tag bits is ignored. --- ## External Bars nwm has no bar and never will: drawing text is outside its scope. It does cooperate with external bars through standard EWMH hints, so you get a working bar without any code in nwm. What nwm provides to a bar: | Hint | Meaning | |---|---| | `_NET_WM_WINDOW_TYPE_DOCK`, `_NET_WM_STRUT_PARTIAL`, `_NET_WM_STRUT` | Read from the bar: it is not tiled, and the screen edge it reserves is kept free | | `_NET_WORKAREA` | The area left for windows | | `_NET_NUMBER_OF_DESKTOPS`, `_NET_DESKTOP_NAMES`, `_NET_DESKTOP_GEOMETRY`, `_NET_DESKTOP_VIEWPORT` | One desktop per entry of `tags[]`, named like the tags | | `_NET_CURRENT_DESKTOP` | Current tag (lowest tag of the view); a bar can change it by sending the same message | | `_NET_WM_DESKTOP`, `_NET_CLIENT_LIST`, `_NET_ACTIVE_WINDOW` | Where each window lives, which windows exist, which one has focus | Typical [polybar](https://github.com/polybar/polybar) setup (`~/.config/polybar/config.ini`). The bar must be a normal window, not override-redirect, otherwise nwm never sees it and nothing is reserved: ```ini [bar/main] width = 100% height = 24 override-redirect = false modules-left = xworkspaces modules-center = xwindow [module/xworkspaces] type = internal/xworkspaces enable-click = true [module/xwindow] type = internal/xwindow ``` Start the bar from `~/.xinitrc` (nwm has no autostart). Before or after nwm both work: ```sh polybar main & exec nwm ``` For [lemonbar](https://github.com/LemonBoy/bar) pass `-d` so the bar is a docked window instead of override-redirect. [tint2](https://gitlab.com/o9000/tint2) normally sets the dock type itself and needs no special option. Notes: - Tag names come from `tags[]` in `nwm.h`. Recompile to rename them, for example `"web", "dev", "mail"`. - A bar that is override-redirect is simply ignored by nwm and may overlap windows; use a non-override-redirect bar or leave a margin yourself. - Multi-monitor is not supported (see [How It Works](#how-it-works)): the bar's strut applies to the whole X screen. --- ## How It Works `nwm` manages windows through a flat client list and a parallel focus stack. The tiling algorithm divides the screen into a master area and a stack area, computing tile sizes with integer arithmetic, so there is no pixel drift across redraws. The remaining pixels are assigned to the last window in each column. Only the master width is derived from the floating-point `mfact`, which is clamped to 0.05–0.95. Tags are bitmasks. Each client carries a tag bitmask; the active view is a bitmask. A client is visible when the bitwise AND of its tags and the current view is nonzero. This means one window can appear on multiple tags simultaneously. Tag history uses a two-slot XOR system. `nwm` keeps the current and previous tag masks in a two-element array and flips an index bit on each change. `Mod+Tab` flips the index back, returning to the previous saved tag mask. Layouts are stored the same way, but only the current slot is used: there is no binding to return to the previous layout. `nwm` also keeps minimal per-tag state: - current layout - master factor - master window count When multiple tags are visible, per-tag state is restored from the lowest numbered visible tag. Some behavior is intentionally explicit: - `_NET_ACTIVE_WINDOW` requests are not used to force focus; they mark the window as urgent instead. - Tiled windows ignore size hints where needed to keep tiling deterministic. - Floating windows respect ICCCM size hints, including geometry requested by the client itself. - Border width is always `borderpx`; clients cannot change it. A fullscreen window ignores geometry requests from its client. - Dialog windows (`_NET_WM_WINDOW_TYPE_DIALOG`) float automatically. - Monocle respects `gappx`; the floating layout does not. - In tiling layouts `Mod+j` / `Mod+k` skip floating windows; focus those with the mouse. - A focused floating window keeps focus when the pointer moves over tiled windows. - While a fullscreen window is focused, `Mod+j` / `Mod+k` and focus-follows-mouse do not change focus. - Mouse resize moves the pointer to the bottom-right corner of the window. - The screen size is read once at startup. RandR, multi-monitor layouts and resolution changes are not handled; the whole X screen is treated as one monitor. - There is no minimize/iconify support. Windows left iconic by a previous window manager are mapped on startup. - `_NET_WM_STATE` (only `_NET_WM_STATE_FULLSCREEN`) is read when a window is mapped and honored via client messages; later changes to the property itself are not tracked. - Every tag is an EWMH desktop. `_NET_CURRENT_DESKTOP` is the lowest tag of the current view (when several tags are shown), `_NET_WM_DESKTOP` of a window is its lowest tag (`0xFFFFFFFF` if it is on all tags), and `_NET_DESKTOP_NAMES` comes from `tags[]`. These properties are only written when something changed. - Windows of type `_NET_WM_WINDOW_TYPE_DOCK` are not clients: nwm maps them as they are, with no border, tag or focus handling. Their `_NET_WM_STRUT_PARTIAL` (or the older `_NET_WM_STRUT`) reserves screen edges; tiling, monocle, new floating windows and drag snapping use the area that is left, and `_NET_WORKAREA` is published. Per edge the largest strut wins (struts are not added up), strut start/end ranges are ignored because the whole X screen is one monitor, and at most 8 docks are tracked (`docks[8]` in `nwm.c`). Fullscreen windows still cover the whole screen, bar included. A panel that does not set the dock type is managed as an ordinary window. - A panel can switch tags by sending `_NET_CURRENT_DESKTOP`. Clicking a window in a taskbar sends `_NET_ACTIVE_WINDOW`, which only marks that window urgent (see above). The `_NET_WM_DESKTOP` client message is not handled, so a panel cannot move windows between tags. - Not implemented: `_NET_CLIENT_LIST_STACKING`, `_NET_SHOWING_DESKTOP`. It’s hard to explain in words, but you can grasp the entire code structure if you spend an hour reading the code. --- ## Contributing Bug reports and patches are welcome via [GitHub Issues](https://github.com/tinyopsec/nwm/issues) and pull requests.
Code requirements - No external dependencies - No over-abstraction or wrapper layers - Compiles clean: `gcc -std=c99 -pedantic -Wall -Wextra` - Optional features behind `#ifdef` or compile-time constants are considered. Core event loop changes are reviewed carefully.
--- ## Star History [![Star History Chart](https://api.star-history.com/svg?repos=tinyopsec/nwm&type=Date)](https://star-history.com/#tinyopsec/nwm&Date) --- ## Related ### Suckless ecosystem | Project | Link | |---|---| | dwm | [dwm.suckless.org](https://dwm.suckless.org) | | st terminal | [st.suckless.org](https://st.suckless.org) | | dmenu | [tools.suckless.org/dmenu](https://tools.suckless.org/dmenu/) | | suckless.org | [suckless.org](https://suckless.org) | ### Specifications | Spec | Link | |---|---| | EWMH | [freedesktop.org](https://specifications.freedesktop.org/wm-spec/latest/) | | ICCCM | [x.org](https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html) | | Xlib manual | [x.org](https://www.x.org/releases/current/doc/libX11/libX11/libX11.html) | ### Packages | Distro | Link | |---|---| | AUR (Arch) | [aur.archlinux.org/packages/nwm](https://aur.archlinux.org/packages/nwm) |
Thanks / Resources used Thanks to my friends for their help, and I also thank these resources: - shields.io - capsule-render from vercel (Very beautiful) - demolab.com - star-history.com - Ross Maloney for Fundamentals of Xlib Programming by Examples: https://www.linux.co.cr/desktops/review/acrobat/030103.pdf Sorry for the English; I'm using a translator. I removed the original Russian comments from the code; the remaining comments are short and in English.
nwm footer tinyopsec-nwm-67f5c08/nwm.1000066400000000000000000000247411526263016200155700ustar00rootroot00000000000000.TH NWM 1 "2026-10-10" "nwm\-VERSION" .SH NAME nwm \- nano window manager .SH SYNOPSIS .B nwm .RB [ \-v ] .SH DESCRIPTION nwm is a minimal tiling window manager for X11. It manages windows in three layouts (tiling, floating and monocle), groups them with tags and draws nothing except window borders. It has no built-in bar; an external EWMH-aware bar can be used instead (see .BR "EXTERNAL BARS" ). .P nwm is configured at compile time only. There is no configuration file, no IPC and no reload mechanism. See .BR CUSTOMIZATION . .P nwm manages the whole X screen as a single monitor. It does not start any other programs by itself; start background programs from .I ~/.xinitrc before the .B exec nwm line. .SH OPTIONS .TP .B \-v Print version information to standard output and exit. .P Any other argument prints a usage message to standard error and exits. .SH USAGE Add the following line as the last command of .IR ~/.xinitrc : .P .RS .EX exec nwm .EE .RE .P For a display manager, install a session file such as .IR /usr/share/xsessions/nwm.desktop . .SS Tags nwm has one tag per entry of .B tags[] (nine by default, at most 31). Every window carries a tag bitmask and the current view is a bitmask. A window is visible when its tags and the current view have at least one tag in common, so one window can be shown on several tags, and several tags can be viewed at once. .P .B Mod+Tab returns to the previously saved view, including combined multi-tag views. It is not simply "the previous tag": nwm keeps two view slots and flips between them on each view change. .SS Layouts .TP .B tiling Master and stack areas. The number of master windows and the master area size can be changed at run time. Floating windows are not tiled. .TP .B floating Windows keep their own geometry. Gaps are not applied. .TP .B monocle Every visible window fills the work area; the focused one is on top. Gaps are applied. .P The layout, master factor and master count are stored per tag unless .B NWM_WITH_PERTAG is set to 0. When several tags are viewed, the state of the lowest numbered visible tag is used. .SS Floating windows Dialog windows (\fB_NET_WM_WINDOW_TYPE_DIALOG\fR), windows that request fullscreen and windows matched by a floating rule are floating. A floating window respects ICCCM size hints. A focused floating window keeps focus when the pointer moves over tiled windows. Moving or resizing a tiled window by more than .B snap pixels makes it floating. .SS Fullscreen A fullscreen window covers the whole screen, including a bar. While it is focused, .B Mod+j and .B Mod+k and focus-follows-mouse do not change focus. .SH KEY BINDINGS The default modifier .B Mod is the Super (Windows) key. Change .B MODKEY in .I nwm.h to use another one, for example .B Mod1Mask for Alt. .SS Windows and layouts .TP .B Mod+Return Spawn a terminal (default: .BR st (1)). .TP .B Mod+d Spawn a launcher (default: .BR dmenu_run (1)). .TP .B Mod+j Focus the next window. In tiling layouts floating windows are skipped. .TP .B Mod+k Focus the previous window. In tiling layouts floating windows are skipped. .TP .B Mod+h Shrink the master area by 5%. .TP .B Mod+l Grow the master area by 5%. .TP .B Mod+i Increase the number of master windows. .TP .B Mod+o Decrease the number of master windows. .TP .B Mod+Space Promote the focused window to master. If it already is the master, the next tiled window is promoted instead. .TP .B Mod+t Select the tiling layout. .TP .B Mod+f Select the floating layout. .TP .B Mod+m Select the monocle layout. .TP .B Mod+F11 Toggle fullscreen for the focused window. .TP .B Mod+Shift+Space Toggle floating for the focused window. .TP .B Mod+q Close the focused window. nwm sends .B WM_DELETE_WINDOW if the client supports it and kills the client otherwise. .TP .B Mod+Shift+e Quit nwm. .SS Tags .TP .B Mod+1\-9 View tag .IR N . .TP .B Mod+Ctrl+1\-9 Toggle tag .I N in the current view. .TP .B Mod+Shift+1\-9 Move the focused window to tag .IR N . .TP .B Mod+Ctrl+Shift+1\-9 Toggle tag .I N on the focused window. .TP .B Mod+0 View all tags. .TP .B Mod+Shift+0 Put the focused window on all tags. .TP .B Mod+Tab Return to the previous view. .SS Mouse These apply when the modifier is held over a client window. .TP .B Mod+Button1 Move the window. .TP .B Mod+Button2 Toggle floating. .TP .B Mod+Button3 Resize the window. The pointer is moved to the bottom-right corner of the window. .P While moving a window with .BR Mod+Button1 , pressing .B Mod+1\-9 carries the window to that tag and switches the view to it; the drag continues, so the window can be dropped there or carried further. .B Mod+0 sends the window to all tags. Other bindings are ignored during a drag. This reuses the .B view bindings from .BR keys[] , so rebinding them rebinds this too. .P All bindings are defined in the .B keys[] and .B buttons[] arrays in .IR nwm.h . .SH EXTERNAL BARS nwm draws no bar and has no status text. It cooperates with external bars (for example .BR polybar (1), .BR tint2 (1) or .BR lemonbar (1)) through standard EWMH hints, so a bar needs no support code in nwm and can be started before or after nwm. .SS Docks and reserved space A window of type .B _NET_WM_WINDOW_TYPE_DOCK is not a client: nwm maps it as it is and gives it no border, tag or focus handling. The screen edges it reserves with .B _NET_WM_STRUT_PARTIAL (or the older .BR _NET_WM_STRUT ) are removed from the work area. Tiling, monocle, new floating windows and drag snapping use the area that is left, and nwm publishes it as .BR _NET_WORKAREA . .P The work area is recalculated when a dock appears or disappears and when its strut property changes. Details: .IP \(bu 2 Per edge the largest strut wins; struts of several docks are not added up. .IP \(bu 2 Strut start and end ranges are ignored, because the whole X screen is one monitor. .IP \(bu 2 At most 8 docks are tracked (\fBdocks[8]\fR in .IR nwm.c ). Further docks are mapped but reserve no space. .IP \(bu 2 Fullscreen windows still cover the whole screen, bar included. .IP \(bu 2 A panel that does not set the dock type is managed as an ordinary window. A bar that is override-redirect is never seen by nwm: nothing is reserved and it may overlap windows. .SS Tags as desktops Every tag is an EWMH desktop, so a bar can show tag names, the current tag and occupied tags: .TP .B _NET_NUMBER_OF_DESKTOPS The number of entries in .BR tags[] . .TP .B _NET_DESKTOP_NAMES The names from .BR tags[] . .TP .B _NET_DESKTOP_GEOMETRY, _NET_DESKTOP_VIEWPORT Published for completeness; desktops are not larger than the screen and the viewport is always 0,0. .TP .B _NET_CURRENT_DESKTOP The lowest tag of the current view. .TP .B _NET_WM_DESKTOP Set on every client: the lowest tag of the window, or 0xFFFFFFFF if the window is on all tags. .TP .B _NET_CLIENT_LIST, _NET_ACTIVE_WINDOW The managed windows and the focused one, for taskbars and title widgets. .P These properties are written only when their value changes, and are removed when nwm exits. .SS What a bar can do .IP \(bu 2 Switch tags: send .B _NET_CURRENT_DESKTOP to the root window. The view becomes that single tag; an index outside the range of .B tags[] is ignored. .IP \(bu 2 Activate a window (for example a click in a taskbar): this only marks the window urgent, so its border gets the urgent color. nwm does not move focus on such requests, to avoid focus stealing. .IP \(bu 2 It cannot move windows between tags: the .B _NET_WM_DESKTOP client message is not handled. .SS Setting up a bar Start the bar from .I ~/.xinitrc (nwm has no autostart): .P .RS .EX polybar main & exec nwm .EE .RE .P For .BR polybar (1) use a normal window, .B override-redirect = false in the bar section, and the .B internal/xworkspaces and .B internal/xwindow modules with .BR "enable-click = true" . For .BR lemonbar (1) pass .B \-d so that the bar is a docked window. Tag names come from .BR tags[] ; recompile to rename them, for example to .IR web , .IR dev and .IR mail . .SH CUSTOMIZATION nwm is customized by editing .I nwm.h and recompiling: .P .RS .EX $ $EDITOR nwm.h $ make clean install .EE .RE .P Then restart nwm. The most important options are: .TP .B MODKEY Modifier key used by all default bindings. .TP .B borderpx Border width in pixels. .TP .B col_nborder, col_sborder, col_uborder Border colors of inactive, focused and urgent windows. .TP .B gappx Gap between windows and screen edges. .TP .B mfact, nmaster Initial master area ratio (0.05\-0.95) and number of master windows. .TP .B snap Snap and float-on-drag threshold in pixels. .TP .B attachbottom If 1, new windows are appended to the end of the client list. .TP .B focusonopen If 0, a new window does not take focus. .TP .B tags[] Tag names; the number of entries is the number of tags. .TP .B termcmd, dmenucmd Commands started by .B Mod+Return and .BR Mod+d . .TP .B NWM_WITH_BORDERS, NWM_WITH_GAPS, NWM_WITH_PERTAG Set to 0 to remove the corresponding code and data from the build. .SS Window rules Rules are listed in the .B rules[] array in .IR nwm.h . Each rule has the fields .IR class , .IR instance , .I title (a substring of the window title), .I tags (a tag bitmask, 0 keeps the current tags) and .I isfloating (1 floating, 0 tiled, \-1 default). Only the first matching rule is applied. Dialog and fullscreen windows are made floating after the rules are applied. .SH ENVIRONMENT .TP .B DISPLAY The X server to connect to. .SH EXIT STATUS nwm exits with 0 when it is quit normally (for example with .BR Mod+Shift+e ). It exits with a non-zero status if it cannot open the display, if another window manager is already running, or on a fatal error. .SH STANDARDS nwm implements parts of the ICCCM and EWMH specifications: .BR WM_DELETE_WINDOW , .BR WM_TAKE_FOCUS , .BR WM_NORMAL_HINTS , .BR WM_HINTS , .BR WM_STATE , .B _NET_SUPPORTED and the other hints named above. On OpenBSD nwm uses .BR pledge (2). .SH BUGS AND LIMITATIONS .IP \(bu 2 The screen size is read once at startup. RandR, multiple monitors and resolution changes are not handled. .IP \(bu 2 There is no minimize or iconify support. .IP \(bu 2 Changes to .B _NET_WM_STATE after a window is mapped are not tracked; state changes made through client messages are honored. .IP \(bu 2 A request to activate a window .RB ( _NET_ACTIVE_WINDOW ) only marks it urgent and does not move focus. The .B _NET_WM_DESKTOP client message is not handled, so a bar cannot move windows between tags. .IP \(bu 2 .B _NET_CLIENT_LIST_STACKING and .B _NET_SHOWING_DESKTOP are not implemented. .P Report bugs at .IR https://github.com/tinyopsec/nwm/issues . .SH SEE ALSO .BR dwm (1), .BR dmenu (1), .BR st (1), .BR startx (1), .BR xinit (1) .SH AUTHORS See the LICENSE file for copyright details (MIT). tinyopsec-nwm-67f5c08/nwm.c000066400000000000000000001326661526263016200156600ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __OpenBSD__ int pledge(const char *, const char *); #endif #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) #define MOUSEMASK (BUTTONMASK|PointerMotionMask) #define CLEANMASK(m) ((m) & ~(numlockmask|LockMask) & \ (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) #define VIS(c) ((c)->tags & tagsel[sel_group]) #define W(c) ((c)->w + ((c)->bw << 1)) #define H(c) ((c)->h + ((c)->bw << 1)) #define LEN(x) (sizeof(x)/sizeof(*(x))) #define TM ((1u << LEN(tags)) - 1) #define MAX(a,b) ((a) > (b) ? (a) : (b)) #define MIN(a,b) ((a) < (b) ? (a) : (b)) #define ARR (cur.lt[cur.li]->ar) /* current layout, NULL = floating */ #define FREE(c) ((c)->isfloating || !ARR) /* positioned by the user, not by the layout */ #define CYCLE(c) (VIS(c) && !(ARR && (c)->isfloating)) #define MOTION_THROTTLE_MS 16 typedef union { int i; unsigned int ui; float f; const void *v; } A; typedef struct { unsigned int click, mask, button; void (*fn)(const A *); A arg; } B; typedef struct { unsigned int mod; KeySym key; void (*fn)(const A *); A arg; } K; typedef struct { void (*ar)(void); } L; typedef struct C C; typedef struct { const char *class, *instance, *title; unsigned int tags; int isfloating; } Rule; /* Per-tag state: two-slot layout history, master factor, master count. */ typedef struct { const L *lt[2]; unsigned int li; float mf; int nm; } PT; struct C { float mina, maxa; int x, y, w, h, oldx, oldy, oldw, oldh; int basew, baseh, incw, inch, maxw, maxh, minw, minh; int bw, oldbw; unsigned int tags, pubtags; unsigned int isfixed:1, isfloating:1, isurgent:1, neverfocus:1, oldstate:1, isfullscreen:1, hintsvalid:1, hidden:1; C *next, *snext; Window win; }; enum { ClkClientWin, ClkRootWin }; enum { NetSupported, NetWMState, NetWMFullscreen, NetActiveWindow, NetWMWindowType, NetWMWindowTypeDialog, NetClientList, NetWMCheck, NetWMWindowTypeDock, NetWMStrut, NetWMStrutPartial, NetWorkarea, NetNumberOfDesktops, NetCurrentDesktop, NetDesktopNames, NetWMDesktop, NetDesktopGeometry, NetDesktopViewport, NetLast }; enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; static int applyrules(C *); static int applysizehints(C *, int *, int *, int *, int *, int); static void ar(void); static void at(C *); static void bp(XEvent *); static void checkwm(void); static void cleanup(void); static void clientmsg(XEvent *); static void configurenotify(C *); static void configurerequest(XEvent *); static void destroynotify(XEvent *); static void detach(C *); static void detachstack(C *); static unsigned int dockidx(Window); static void enternotify(XEvent *); static void focus(C *); static void focusin(XEvent *); static void focusnext(const A *); static long getstate(Window); static void grabbuttons(C *, int); static void grabkeys(void); static void handleterm(int); static int hasatom(Window, Atom, Atom); static void incnmaster(const A *); static void keypress(XEvent *); static void killclient(const A *); static unsigned int lowtag(unsigned int); static void manage(Window, XWindowAttributes *); static void mappingnotify(XEvent *); static void maprequest(XEvent *); static void monocle(void); static void movemouse(const A *); static C *nexttiled(C *); static void pop(C *); static void propertynotify(XEvent *); static void quit(const A *); static int resize(C *, int, int, int, int, int); static int resizeclient(C *, int, int, int, int); static void resizemouse(const A *); static void restack(void); static void run(void); static void scan(void); static int sendevent(C *, Atom); static void setclientstate(C *, long); static void setfocus(C *); static void setfullscreen(C *, int); static void setlayout(const A *); static void setmasterfact(const A *); static void setup(void); static void seturgency(C *, int); static void showhide(C *); static void spawn(const A *); static void tag(const A *); static void tile(void); static void togglefloating(const A *); static void togglefullscreen(const A *); static void toggletag(const A *); static void toggleview(const A *); static void unfocus(C *); static void undock(Window); static void unmanage(C *, int); static void unmapnotify(XEvent *); static void updateclientlist(void); static void updatedesktops(void); static void updatenumlockmask(void); static void updatesizehints(C *); static void updatewindowtype(C *); static void updateworkarea(void); static void updatewmhints(C *); static void view(const A *); static C *wintoclient(Window); static int xerror(Display *, XErrorEvent *); static int xerrorstart(Display *, XErrorEvent *); static void zoom(const A *); #include "nwm.h" #ifndef VERSION #define VERSION "unknown" #endif typedef char nwm_tags_range[(LEN(tags) >= 1 && LEN(tags) < 32) ? 1 : -1]; typedef char nwm_layouts_any[(LEN(layouts) >= 1) ? 1 : -1]; #if NWM_WITH_GAPS #define GAP() (gappx) #else #define GAP() 0 #endif #if NWM_WITH_BORDERS #define DEFAULT_BORDERPX borderpx #define BORDER(c, pix) XSetWindowBorder(display, (c)->win, (pix)) #else #define DEFAULT_BORDERPX 0 #define BORDER(c, pix) ((void)0) #endif static Display *display; static Window root, wmcheck_win; static int screen_w, screen_h, wx, wy, ww, wh; /* wx..wh: area not covered by docks */ static volatile sig_atomic_t running = 1; static unsigned int numlockmask, sel_group, tagsel[2]; static Cursor cursor[3]; static Atom wmatom[WMLast], netatom[NetLast]; static C *clients, *sel, *stack; static Window docks[8]; static unsigned int ndock; static PT cur; static void (*handler[LASTEvent])(XEvent *) = { [ButtonPress] = bp, [ClientMessage] = clientmsg, [ConfigureRequest] = configurerequest, [DestroyNotify] = destroynotify, [EnterNotify] = enternotify, [FocusIn] = focusin, [KeyPress] = keypress, [MappingNotify] = mappingnotify, [MapRequest] = maprequest, [PropertyNotify] = propertynotify, [UnmapNotify] = unmapnotify, }; static void die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); exit(1); } static unsigned int lowtag(unsigned int m) { unsigned int i; for (i = 0; i < LEN(tags) - 1 && !(m & (1u << i)); i++) ; return i; } #if NWM_WITH_PERTAG static PT per_tag[LEN(tags)]; /* Per-tag state lives under the lowest tag of the current view. */ static unsigned int curtag(void) { return lowtag(tagsel[sel_group]); } static void ptinit(void) { unsigned int i; for (i = 0; i < LEN(tags); i++) per_tag[i] = cur; } static void ptsave(void) { per_tag[curtag()] = cur; } static void ptload(void) { cur = per_tag[curtag()]; } #else #define ptinit() ((void)0) #define ptsave() ((void)0) #define ptload() ((void)0) #endif #if NWM_WITH_BORDERS static unsigned long color_norm, color_sel, color_urg; static unsigned long getcolor(const char *name) { XColor xc, exact; if (!XAllocNamedColor(display, DefaultColormap(display, DefaultScreen(display)), name, &xc, &exact)) die("nwm: cannot allocate color %s", name); return xc.pixel; } #endif static int applyrules(C *c) { const Rule *r; XClassHint ch = { NULL, NULL }; char *name = NULL; unsigned int i; int floating = -1; XGetClassHint(display, c->win, &ch); XFetchName(display, c->win, &name); for (i = 0; i < LEN(rules); i++) { r = &rules[i]; if ((!r->class || (ch.res_class && !strcmp(ch.res_class, r->class))) && (!r->instance || (ch.res_name && !strcmp(ch.res_name, r->instance))) && (!r->title || (name && strstr(name, r->title)))) { floating = r->isfloating; if (r->tags & TM) c->tags = r->tags & TM; break; } } XFree(ch.res_class); XFree(ch.res_name); XFree(name); return floating; } static int applysizehints(C *c, int *x, int *y, int *w, int *h, int interact) { int bw2 = c->bw << 1, bim; *w = MAX(1, *w); *h = MAX(1, *h); if (interact) { if (*x > screen_w) *x = screen_w - (*w + bw2); if (*x + *w + bw2 < 0) *x = 0; if (*y + *h + bw2 < 0) *y = 0; } else { if (*x >= screen_w) *x = screen_w - (*w + bw2); if (*y >= screen_h) *y = screen_h - (*h + bw2); if (*x + *w + bw2 <= 0) *x = 0; if (*y + *h + bw2 <= 0) *y = 0; } if (FREE(c)) { if (!c->hintsvalid) updatesizehints(c); bim = c->basew == c->minw && c->baseh == c->minh; if (!bim) { *w -= c->basew; *h -= c->baseh; } if (c->mina > 0.0f && c->maxa > 0.0f && *w > 0 && *h > 0) { if (c->maxa < (float)*w / *h) *w = (int)(*h * c->maxa + 0.5f); else if (c->mina < (float)*h / *w) *h = (int)(*w * c->mina + 0.5f); } if (bim) { /* the increment calculation needs it removed */ *w -= c->basew; *h -= c->baseh; } if (c->incw) *w -= *w % c->incw; if (c->inch) *h -= *h % c->inch; *w = MAX(*w + c->basew, c->minw); *h = MAX(*h + c->baseh, c->minh); if (c->maxw) *w = MIN(*w, c->maxw); if (c->maxh) *h = MIN(*h, c->maxh); } *w = MAX(1, *w); *h = MAX(1, *h); return *x != c->x || *y != c->y || *w != c->w || *h != c->h; } static void ar(void) { showhide(stack); if (ARR) ARR(); restack(); updatedesktops(); } static void at(C *c) { C **pp = &clients; if (attachbottom) while (*pp) pp = &(*pp)->next; c->next = *pp; *pp = c; c->snext = stack; stack = c; } static void bp(XEvent *e) { unsigned int i, click = ClkRootWin; XButtonPressedEvent *ev = &e->xbutton; C *c = wintoclient(ev->window); if (c) { focus(c); restack(); click = ClkClientWin; } /* * Unfocused clients carry a synchronous grab that freezes pointer and * keyboard until released. Release it even if the client is already * gone, otherwise all input stays frozen. */ XAllowEvents(display, ReplayPointer, CurrentTime); for (i = 0; i < LEN(buttons); i++) { if (click == buttons[i].click && buttons[i].fn && buttons[i].button == ev->button && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) buttons[i].fn(&buttons[i].arg); } } static void checkwm(void) { XSetErrorHandler(xerrorstart); XSelectInput(display, DefaultRootWindow(display), SubstructureRedirectMask); XSync(display, False); XSetErrorHandler(xerror); } static void cleanup(void) { unsigned int i; C *c; XWindowChanges wc; /* Bring every window back on screen, including hidden ones. */ while ((c = clients)) { clients = c->next; if (c->isfullscreen) { /* undo setfullscreen(): geometry, border, state */ XDeleteProperty(display, c->win, netatom[NetWMState]); c->bw = c->oldbw; resizeclient(c, c->oldx, c->oldy, c->oldw, c->oldh); } else { wc.border_width = c->oldbw; XConfigureWindow(display, c->win, CWBorderWidth, &wc); XMoveWindow(display, c->win, c->x, c->y); } setclientstate(c, WithdrawnState); free(c); } sel = stack = NULL; XUngrabKey(display, AnyKey, AnyModifier, root); for (i = 0; i < 3; i++) XFreeCursor(display, cursor[i]); XDeleteProperty(display, root, netatom[NetClientList]); XDeleteProperty(display, root, netatom[NetActiveWindow]); XDeleteProperty(display, root, netatom[NetSupported]); XDeleteProperty(display, root, netatom[NetWMCheck]); XDeleteProperty(display, root, netatom[NetNumberOfDesktops]); XDeleteProperty(display, root, netatom[NetCurrentDesktop]); XDeleteProperty(display, root, netatom[NetDesktopNames]); XDeleteProperty(display, root, netatom[NetWorkarea]); XDeleteProperty(display, root, netatom[NetDesktopGeometry]); XDeleteProperty(display, root, netatom[NetDesktopViewport]); XDestroyWindow(display, wmcheck_win); XSync(display, False); XSetInputFocus(display, PointerRoot, RevertToPointerRoot, CurrentTime); } static void clientmsg(XEvent *e) { XClientMessageEvent *ev = &e->xclient; C *c = wintoclient(ev->window); A a; /* A panel switches desktops by messaging the root window. */ if (ev->window == root && ev->message_type == netatom[NetCurrentDesktop]) { if ((unsigned long)ev->data.l[0] < LEN(tags)) { a.ui = 1u << ev->data.l[0]; view(&a); } return; } if (!c) return; if (ev->message_type == netatom[NetWMState] && (ev->data.l[1] == (long)netatom[NetWMFullscreen] || ev->data.l[2] == (long)netatom[NetWMFullscreen])) { if (ev->data.l[0] == 0 || ev->data.l[0] == 1 || ev->data.l[0] == 2) setfullscreen(c, ev->data.l[0] == 1 || (ev->data.l[0] == 2 && !c->isfullscreen)); } else if (ev->message_type == netatom[NetActiveWindow] && c != sel && !c->isurgent) { /* Avoid focus stealing: activation requests become urgency hints. */ seturgency(c, 1); } } static void configurenotify(C *c) { XConfigureEvent ce = { .type = ConfigureNotify, .send_event = True, .display = display, .event = c->win, .window = c->win, .x = c->x, .y = c->y, .width = c->w, .height = c->h, .border_width = c->bw, .above = None, .override_redirect = False }; XSendEvent(display, c->win, False, StructureNotifyMask, (XEvent *)&ce); } static void configurerequest(XEvent *e) { XConfigureRequestEvent *ev = &e->xconfigurerequest; XWindowChanges wc; C *c = wintoclient(ev->window); if (!c) { wc.x = ev->x; wc.y = ev->y; wc.width = ev->width; wc.height = ev->height; wc.border_width = ev->border_width; wc.sibling = ev->above; wc.stack_mode = ev->detail; XConfigureWindow(display, ev->window, ev->value_mask, &wc); return; } /* Border width and fullscreen geometry belong to nwm, not to the client. */ if (!c->isfullscreen && FREE(c)) { int x = c->x, y = c->y, w = c->w, h = c->h; if (ev->value_mask & CWX) x = ev->x; if (ev->value_mask & CWY) y = ev->y; if (ev->value_mask & CWWidth) w = ev->width; if (ev->value_mask & CWHeight) h = ev->height; if (VIS(c)) { /* No XConfigureWindow on redundant requests, but a denied * request still needs a synthetic ack: without it the client * keeps re-sending the same request (retry storm = flicker * and freeze until it gives up). The real ConfigureNotify * from the server already acks a granted request. */ if (!(applysizehints(c, &x, &y, &w, &h, 0) && resizeclient(c, x, y, w, h))) configurenotify(c); return; } applysizehints(c, &x, &y, &w, &h, 0); c->x = x; c->y = y; c->w = w; c->h = h; } configurenotify(c); } static void destroynotify(XEvent *e) { C *c; if ((c = wintoclient(e->xdestroywindow.window))) unmanage(c, 1); else undock(e->xdestroywindow.window); } static void detach(C *c) { C **pp; for (pp = &clients; *pp && *pp != c; pp = &(*pp)->next) ; if (*pp) *pp = c->next; } static void detachstack(C *c) { C **pp, *t; for (pp = &stack; *pp && *pp != c; pp = &(*pp)->snext) ; if (*pp) *pp = c->snext; if (c == sel) { for (t = stack; t && !VIS(t); t = t->snext) ; sel = t; } } /* Position of w in docks[]; ndock if it is not a known dock. */ static unsigned int dockidx(Window w) { unsigned int i; for (i = 0; i < ndock && docks[i] != w; i++) ; return i; } static void enternotify(XEvent *e) { XCrossingEvent *ev = &e->xcrossing; C *c; if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != root) return; c = wintoclient(ev->window); if (!c || c == sel || c->isfullscreen || (sel && sel->isfullscreen)) return; /* A floating window keeps focus against pointer motion over tiled ones. */ if (sel && sel->isfloating && !c->isfloating && ARR) return; focus(c); restack(); } static void focus(C *c) { if (!c || !VIS(c)) for (c = stack; c && !VIS(c); c = c->snext) ; if (sel && sel != c) unfocus(sel); if (c) { if (c->isurgent) seturgency(c, 0); detachstack(c); c->snext = stack; stack = c; grabbuttons(c, 1); BORDER(c, color_sel); setfocus(c); } else { XSetInputFocus(display, root, RevertToPointerRoot, CurrentTime); XDeleteProperty(display, root, netatom[NetActiveWindow]); } sel = c; } static void focusin(XEvent *e) { C *c; /* * A visible managed client taking focus (a modal dialog * re-asserting it) only resyncs local selection: answering * with XSetInputFocus fights the client at full speed * (focus ping-pong = flicker), the server echoes each * assertion back and the loop never ends. Only focus lost * to unmanaged windows is reclaimed. */ if (sel && e->xfocus.window != sel->win) { c = wintoclient(e->xfocus.window); if (c && VIS(c)) { /* Adopt like focus(), minus XSetInputFocus/_NET_ACTIVE_WINDOW: * border, grabs and urgency never echo back (no ping-pong). * ACTIVE stays stale until the next real focus(). */ unfocus(sel); if (c->isurgent) seturgency(c, 0); detachstack(c); c->snext = stack; stack = c; sel = c; grabbuttons(c, 1); BORDER(c, color_sel); } else { setfocus(sel); } } } static void focusnext(const A *arg) { C *c = NULL, *i; if (!sel || sel->isfullscreen) return; if (arg->i > 0) { for (c = sel->next; c && !CYCLE(c); c = c->next) ; if (!c) for (c = clients; c && !CYCLE(c); c = c->next) ; } else { for (i = clients; i != sel; i = i->next) if (CYCLE(i)) c = i; if (!c) for (i = sel->next; i; i = i->next) if (CYCLE(i)) c = i; } if (c && c != sel) { focus(c); restack(); } } static long getstate(Window w) { int fmt; long res = -1; unsigned char *p = NULL; unsigned long n, ex; Atom real; if (XGetWindowProperty(display, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], &real, &fmt, &n, &ex, &p) == Success) { if (n && fmt == 32 && p) memcpy(&res, p, sizeof(long)); XFree(p); } return res; } static void grabbuttons(C *c, int focused) { unsigned int i, j, mods[] = { 0, LockMask, numlockmask, numlockmask | LockMask }; XUngrabButton(display, AnyButton, AnyModifier, c->win); if (!focused) { XGrabButton(display, AnyButton, AnyModifier, c->win, False, BUTTONMASK, GrabModeSync, GrabModeSync, None, None); return; } for (i = 0; i < LEN(buttons); i++) { if (buttons[i].click != ClkClientWin) continue; for (j = 0; j < LEN(mods); j++) XGrabButton(display, buttons[i].button, buttons[i].mask | mods[j], c->win, False, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); } } static void grabkeys(void) { unsigned int i, j; unsigned int mods[4]; KeyCode code; updatenumlockmask(); mods[0] = 0; mods[1] = LockMask; mods[2] = numlockmask; mods[3] = numlockmask | LockMask; XUngrabKey(display, AnyKey, AnyModifier, root); for (i = 0; i < LEN(keys); i++) { if (!(code = XKeysymToKeycode(display, keys[i].key))) continue; for (j = 0; j < LEN(mods); j++) XGrabKey(display, code, keys[i].mod | mods[j], root, True, GrabModeAsync, GrabModeAsync); } } /* True if the atom list property `prop` of w contains `want`. */ static int hasatom(Window w, Atom prop, Atom want) { int fmt, found = 0; unsigned long n, rem, i; unsigned char *p = NULL; Atom type; if (XGetWindowProperty(display, w, prop, 0L, 1024L, False, XA_ATOM, &type, &fmt, &n, &rem, &p) == Success && p) { if (type == XA_ATOM && fmt == 32) for (i = 0; i < n; i++) found |= ((Atom *)p)[i] == want; XFree(p); } return found; } static void incnmaster(const A *arg) { cur.nm = MAX(cur.nm + arg->i, 0); ptsave(); ar(); } static void keypress(XEvent *e) { unsigned int i; XKeyEvent *ev = &e->xkey; KeySym sym = XkbKeycodeToKeysym(display, ev->keycode, 0, 0); for (i = 0; i < LEN(keys); i++) { if (keys[i].fn && sym == keys[i].key && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state)) keys[i].fn(&keys[i].arg); } } static void killclient(const A *arg) { (void)arg; if (!sel || sendevent(sel, wmatom[WMDelete])) return; XGrabServer(display); XSetCloseDownMode(display, DestroyAll); XKillClient(display, sel->win); XSync(display, False); XUngrabServer(display); } static void manage(Window w, XWindowAttributes *wa) { C *c, *t; Window trans = None; XWindowChanges wc; int rule_floating; /* Docks (panels) are not clients: map them, their struts shrink the work area. */ if (hasatom(w, netatom[NetWMWindowType], netatom[NetWMWindowTypeDock])) { if (dockidx(w) == ndock && ndock < LEN(docks)) { docks[ndock++] = w; XSelectInput(display, w, PropertyChangeMask); } XMapWindow(display, w); updateworkarea(); return; } if (!(c = calloc(1, sizeof(C)))) die("nwm: calloc"); c->win = w; c->x = c->oldx = wa->x; c->y = c->oldy = wa->y; c->w = c->oldw = wa->width; c->h = c->oldh = wa->height; c->oldbw = wa->border_width; updatesizehints(c); updatewmhints(c); c->tags = tagsel[sel_group] & TM; rule_floating = applyrules(c); if (XGetTransientForHint(display, w, &trans) && (t = wintoclient(trans))) c->tags = t->tags; c->bw = DEFAULT_BORDERPX; c->x = MAX(MIN(c->x, wx + ww - W(c)), wx); c->y = MAX(MIN(c->y, wy + wh - H(c)), wy); wc.border_width = c->bw; XConfigureWindow(display, w, CWBorderWidth, &wc); BORDER(c, color_norm); c->isfloating = c->oldstate = (trans != None) || c->isfixed; if (rule_floating >= 0) c->isfloating = c->oldstate = rule_floating; updatewindowtype(c); /* Floating windows are normalized now (real ConfigureNotify). * Fullscreen was already configured by setfullscreen(). * Tiled windows are configured by the pre-tile below. */ if (!c->isfullscreen && FREE(c)) resize(c, c->x, c->y, c->w, c->h, 0); XSelectInput(display, w, EnterWindowMask | FocusChangeMask | PropertyChangeMask | StructureNotifyMask); grabbuttons(c, 0); at(c); updateclientlist(); /* Map at final geometry: tiling after Map flashes a small window, * stuck for many frames on slow machines under spawn flood. */ if (!VIS(c)) { c->hidden = 1; XMoveWindow(display, w, -(W(c) + screen_w), c->y); } else if (!c->isfullscreen && !FREE(c) && ARR) { ARR(); } if (c->isfloating) XMapRaised(display, w); else XMapWindow(display, w); setclientstate(c, NormalState); if (focusonopen || !sel) focus(c); ar(); } static void mappingnotify(XEvent *e) { XRefreshKeyboardMapping(&e->xmapping); if (e->xmapping.request == MappingKeyboard || e->xmapping.request == MappingModifier) grabkeys(); } static void maprequest(XEvent *e) { XWindowAttributes wa; C *c; if (!XGetWindowAttributes(display, e->xmaprequest.window, &wa) || wa.override_redirect) return; if ((c = wintoclient(e->xmaprequest.window))) { XMapWindow(display, c->win); setclientstate(c, NormalState); ar(); } else { manage(e->xmaprequest.window, &wa); } } static void monocle(void) { C *c; int gap = MIN((int)GAP(), MIN(ww, wh) / 2); for (c = nexttiled(clients); c; c = nexttiled(c->next)) { /* No notify without a change: the client already got the real * ConfigureNotify when this geometry was set. */ if (resize(c, wx + gap, wy + gap, MAX(1, ww - 2 * gap - (c->bw << 1)), MAX(1, wh - 2 * gap - (c->bw << 1)), 0)) configurenotify(c); } } static void movemouse(const A *arg) { int x, y, ocx, ocy, nx, ny, di, needar = 0; unsigned int dui, i; KeySym sym; Window dw; XEvent ev; Time last = 0; C *c = sel; (void)arg; if (!c || c->isfullscreen) return; restack(); ocx = c->x; ocy = c->y; if (!XQueryPointer(display, root, &dw, &dw, &x, &y, &di, &di, &dui) || XGrabPointer(display, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, None, cursor[1], CurrentTime) != GrabSuccess) return; do { XMaskEvent(display, MOUSEMASK | KeyPressMask | ExposureMask | SubstructureRedirectMask, &ev); if (ev.type == ConfigureRequest || ev.type == MapRequest) { handler[ev.type](&ev); } else if (ev.type == KeyPress) { /* A tag key mid-drag carries the window to that tag; the drag goes on. */ sym = XkbKeycodeToKeysym(display, ev.xkey.keycode, 0, 0); for (i = 0; i < LEN(keys); i++) { if (keys[i].fn != view || !(keys[i].arg.ui & TM) || sym != keys[i].key || CLEANMASK(keys[i].mod) != CLEANMASK(ev.xkey.state)) continue; c->tags = keys[i].arg.ui & TM; view(&keys[i].arg); if (XQueryPointer(display, root, &dw, &dw, &x, &y, &di, &di, &dui)) { ocx = c->x; ocy = c->y; } break; } } else if (ev.type == MotionNotify) { if (ev.xmotion.time - last <= MOTION_THROTTLE_MS) continue; last = ev.xmotion.time; nx = ocx + ev.xmotion.x - x; ny = ocy + ev.xmotion.y - y; if (abs(nx - wx) < (int)snap) nx = wx; else if (abs(wx + ww - W(c) - nx) < (int)snap) nx = wx + ww - W(c); if (abs(ny - wy) < (int)snap) ny = wy; else if (abs(wy + wh - H(c) - ny) < (int)snap) ny = wy + wh - H(c); if (!c->isfloating && ARR && (abs(nx - ocx) > (int)snap || abs(ny - ocy) > (int)snap)) { c->isfloating = c->oldstate = 1; needar = 1; } if (FREE(c)) resize(c, nx, ny, c->w, c->h, 1); } } while (ev.type != ButtonRelease); XUngrabPointer(display, CurrentTime); if (needar) ar(); } static C * nexttiled(C *c) { for (; c && (c->isfloating || !VIS(c)); c = c->next) ; return c; } static void pop(C *c) { detach(c); c->next = clients; clients = c; focus(c); ar(); } static void propertynotify(XEvent *e) { XPropertyEvent *ev = &e->xproperty; C *c; if (ev->atom == netatom[NetWMStrut] || ev->atom == netatom[NetWMStrutPartial]) { if (dockidx(ev->window) < ndock) updateworkarea(); return; } if (ev->state == PropertyDelete || !(c = wintoclient(ev->window))) return; if (ev->atom == XA_WM_HINTS) { updatewmhints(c); BORDER(c, c->isurgent ? color_urg : (c == sel ? color_sel : color_norm)); } else if (ev->atom == XA_WM_NORMAL_HINTS) { c->hintsvalid = 0; } else if (ev->atom == netatom[NetWMWindowType]) { updatewindowtype(c); ar(); } } static void quit(const A *arg) { (void)arg; running = 0; } static void handleterm(int sig) { (void)sig; running = 0; } /* * Tiled windows intentionally ignore size hints to keep the layout * deterministic. Floating windows respect ICCCM size hints. */ static int resize(C *c, int x, int y, int w, int h, int interact) { if (!(interact || FREE(c)) || applysizehints(c, &x, &y, &w, &h, interact)) return resizeclient(c, x, y, w, h); return 0; } static int resizeclient(C *c, int x, int y, int w, int h) { XWindowChanges wc; /* No X call means no ConfigureNotify storm. The hidden check forces * the return from off-screen: hide moves the window without touching * c->x, so coordinates alone look unchanged. Returns 1 when the * server was touched, so callers send ConfigureNotify only then. */ if (c->x == x && c->y == y && c->w == w && c->h == h && !c->hidden) return 0; c->hidden = 0; c->oldx = c->x; c->x = wc.x = x; c->oldy = c->y; c->y = wc.y = y; c->oldw = c->w; c->w = wc.width = w; c->oldh = c->h; c->h = wc.height = h; wc.border_width = c->bw; XConfigureWindow(display, c->win, CWX | CWY | CWWidth | CWHeight | CWBorderWidth, &wc); return 1; } static void resizemouse(const A *arg) { int ocx, ocy, ocw, och, nw, nh, needar = 0; XEvent ev; Time last = 0; C *c = sel; (void)arg; if (!c || c->isfullscreen) return; restack(); ocx = c->x; ocy = c->y; ocw = c->w; och = c->h; if (XGrabPointer(display, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, None, cursor[2], CurrentTime) != GrabSuccess) return; XWarpPointer(display, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); do { XMaskEvent(display, MOUSEMASK | ExposureMask | SubstructureRedirectMask, &ev); if (ev.type == ConfigureRequest || ev.type == MapRequest) { handler[ev.type](&ev); } else if (ev.type == MotionNotify) { if (ev.xmotion.time - last <= MOTION_THROTTLE_MS) continue; last = ev.xmotion.time; nw = MAX(ev.xmotion.x - ocx - (c->bw << 1) + 1, 1); nh = MAX(ev.xmotion.y - ocy - (c->bw << 1) + 1, 1); if (!c->isfloating && ARR && (abs(nw - ocw) > (int)snap || abs(nh - och) > (int)snap)) { c->isfloating = c->oldstate = 1; needar = 1; } if (FREE(c)) resize(c, c->x, c->y, nw, nh, 1); } } while (ev.type != ButtonRelease); XWarpPointer(display, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); XUngrabPointer(display, CurrentTime); if (needar) ar(); } static void restack(void) { C *c; XWindowChanges wc; if (!sel) return; if (sel->isfullscreen || !ARR) { XRaiseWindow(display, sel->win); return; } /* * Lowering tiled windows to the bottom already leaves floating ones above * them; do not re-raise floating windows, clients restack their own * dialogs and panels and would fight back. */ wc.stack_mode = Below; wc.sibling = None; for (c = stack; c; c = c->snext) { if (c->isfloating || !VIS(c)) continue; XConfigureWindow(display, c->win, wc.sibling ? CWSibling | CWStackMode : CWStackMode, &wc); wc.sibling = c->win; } if (sel->isfloating) XRaiseWindow(display, sel->win); } static void run(void) { XEvent ev; XSync(display, False); while (running && !XNextEvent(display, &ev)) if (!XFilterEvent(&ev, None) && ev.type < LASTEvent && handler[ev.type]) handler[ev.type](&ev); } static void scan(void) { unsigned int i, n; int pass; Window d1, d2, *wins = NULL; XWindowAttributes wa; if (!XQueryTree(display, root, &d1, &d2, &wins, &n)) return; /* Manage ordinary windows first, transients second. */ for (pass = 0; pass < 2; pass++) { for (i = 0; i < n; i++) { if (!XGetWindowAttributes(display, wins[i], &wa) || wa.override_redirect || !!XGetTransientForHint(display, wins[i], &d1) != pass) continue; if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) manage(wins[i], &wa); } } XFree(wins); } static int sendevent(C *c, Atom proto) { int n, exists = 0; Atom *prots; XEvent ev = {0}; if (XGetWMProtocols(display, c->win, &prots, &n)) { while (!exists && n--) exists = prots[n] == proto; XFree(prots); } if (exists) { ev.type = ClientMessage; ev.xclient.window = c->win; ev.xclient.message_type = wmatom[WMProtocols]; ev.xclient.format = 32; ev.xclient.data.l[0] = (long)proto; ev.xclient.data.l[1] = CurrentTime; XSendEvent(display, c->win, False, NoEventMask, &ev); } return exists; } static void setclientstate(C *c, long state) { long data[] = { state, None }; XChangeProperty(display, c->win, wmatom[WMState], wmatom[WMState], 32, PropModeReplace, (unsigned char *)data, 2); } static void setfocus(C *c) { if (!c->neverfocus) { XSetInputFocus(display, c->win, RevertToPointerRoot, CurrentTime); XChangeProperty(display, root, netatom[NetActiveWindow], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&c->win, 1); } sendevent(c, wmatom[WMTakeFocus]); } static void setfullscreen(C *c, int fs) { if (fs == c->isfullscreen) return; XChangeProperty(display, c->win, netatom[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char *)(fs ? &netatom[NetWMFullscreen] : NULL), fs); c->isfullscreen = fs; if (fs) { c->oldstate = c->isfloating; c->bw = 0; c->isfloating = 1; resizeclient(c, 0, 0, screen_w, screen_h); XRaiseWindow(display, c->win); } else { c->isfloating = c->oldstate; c->bw = DEFAULT_BORDERPX; resizeclient(c, c->oldx, c->oldy, c->oldw, c->oldh); } ar(); /* also hides the window again if its tag is not visible */ } static void setlayout(const A *arg) { if (!arg->v || arg->v == cur.lt[cur.li]) return; /* Two-slot XOR layout history. */ cur.li ^= 1; cur.lt[cur.li] = (const L *)arg->v; ptsave(); ar(); } static void setmasterfact(const A *arg) { float f; if (!ARR) return; f = (arg->f < 1.0f) ? cur.mf + arg->f : arg->f - 1.0f; cur.mf = MAX(0.05f, MIN(0.95f, f)); ptsave(); ar(); } static void setup(void) { static char *wmnames[] = { "WM_PROTOCOLS", "WM_DELETE_WINDOW", "WM_STATE", "WM_TAKE_FOCUS" }; static char *netnames[] = { "_NET_SUPPORTED", "_NET_WM_STATE", "_NET_WM_STATE_FULLSCREEN", "_NET_ACTIVE_WINDOW", "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_CLIENT_LIST", "_NET_SUPPORTING_WM_CHECK", "_NET_WM_WINDOW_TYPE_DOCK", "_NET_WM_STRUT", "_NET_WM_STRUT_PARTIAL", "_NET_WORKAREA", "_NET_NUMBER_OF_DESKTOPS", "_NET_CURRENT_DESKTOP", "_NET_DESKTOP_NAMES", "_NET_WM_DESKTOP", "_NET_DESKTOP_GEOMETRY", "_NET_DESKTOP_VIEWPORT" }; static char *auxnames[] = { "_NET_WM_NAME", "UTF8_STRING" }; XSetWindowAttributes wa; static long vp[2 * LEN(tags)]; /* all zero: no large desktops */ Atom aux[2]; unsigned int i; long nd = LEN(tags), geo[2]; int scr = DefaultScreen(display); if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) die("nwm: signal"); if (signal(SIGTERM, handleterm) == SIG_ERR || signal(SIGINT, handleterm) == SIG_ERR || signal(SIGHUP, handleterm) == SIG_ERR) die("nwm: signal"); screen_w = DisplayWidth(display, scr); screen_h = DisplayHeight(display, scr); root = RootWindow(display, scr); #if NWM_WITH_BORDERS color_norm = getcolor(col_nborder); color_sel = getcolor(col_sborder); color_urg = getcolor(col_uborder); #endif cursor[0] = XCreateFontCursor(display, XC_left_ptr); cursor[1] = XCreateFontCursor(display, XC_fleur); cursor[2] = XCreateFontCursor(display, XC_sizing); XInternAtoms(display, wmnames, WMLast, False, wmatom); XInternAtoms(display, netnames, NetLast, False, netatom); XInternAtoms(display, auxnames, 2, False, aux); wmcheck_win = XCreateSimpleWindow(display, root, 0, 0, 1, 1, 0, 0, 0); XChangeProperty(display, wmcheck_win, netatom[NetWMCheck], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheck_win, 1); XChangeProperty(display, wmcheck_win, aux[0], aux[1], 8, PropModeReplace, (unsigned char *)"nwm", 3); XChangeProperty(display, root, netatom[NetWMCheck], XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wmcheck_win, 1); XChangeProperty(display, root, netatom[NetSupported], XA_ATOM, 32, PropModeReplace, (unsigned char *)netatom, NetLast); XDeleteProperty(display, root, netatom[NetClientList]); /* One desktop per tag, so external panels can show them. */ geo[0] = screen_w; geo[1] = screen_h; XChangeProperty(display, root, netatom[NetNumberOfDesktops], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&nd, 1); XChangeProperty(display, root, netatom[NetDesktopGeometry], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)geo, 2); XChangeProperty(display, root, netatom[NetDesktopViewport], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)vp, 2 * LEN(tags)); for (i = 0; i < LEN(tags); i++) XChangeProperty(display, root, netatom[NetDesktopNames], aux[1], 8, i ? PropModeAppend : PropModeReplace, (unsigned char *)tags[i], strlen(tags[i]) + 1); tagsel[0] = tagsel[1] = 1; cur.mf = (mfact < 0.05f || mfact > 0.95f) ? 0.5f : mfact; cur.nm = MAX(nmaster, 0); cur.lt[0] = &layouts[0]; cur.lt[1] = &layouts[1 % LEN(layouts)]; ptinit(); updateworkarea(); /* publishes _NET_WORKAREA and the first desktop state */ grabkeys(); wa.cursor = cursor[0]; wa.event_mask = SubstructureRedirectMask | SubstructureNotifyMask | ButtonPressMask | EnterWindowMask | StructureNotifyMask | PropertyChangeMask; XChangeWindowAttributes(display, root, CWEventMask | CWCursor, &wa); focus(NULL); } static void seturgency(C *c, int urg) { XWMHints *wm; c->isurgent = urg; BORDER(c, urg ? color_urg : (c == sel ? color_sel : color_norm)); if (!(wm = XGetWMHints(display, c->win))) return; wm->flags = urg ? (wm->flags | XUrgencyHint) : (wm->flags & ~XUrgencyHint); XSetWMHints(display, c->win, wm); XFree(wm); } static void showhide(C *c) { for (; c; c = c->snext) { if (!VIS(c)) { if (!c->hidden) { XMoveWindow(display, c->win, -(W(c) + screen_w), c->y); c->hidden = 1; } } else if (c->hidden) { /* Back from off-screen. Floating windows return here; * tiled placement follows in ar(), and its resizeclient() * forces the return via the hidden flag. */ if (FREE(c)) /* includes fullscreen */ resizeclient(c, c->x, c->y, c->w, c->h); } } } static void spawn(const A *arg) { char *const *cmd = (char *const *)arg->v; pid_t pid; pid = fork(); if (pid == -1) { fprintf(stderr, "nwm: fork failed\n"); return; } if (pid == 0) { close(ConnectionNumber(display)); signal(SIGCHLD, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGHUP, SIG_DFL); setsid(); execvp(cmd[0], cmd); fprintf(stderr, "nwm: execvp %s failed\n", cmd[0]); _exit(1); } } static void tag(const A *arg) { if (sel && (arg->ui & TM)) { sel->tags = arg->ui & TM; focus(NULL); ar(); } } /* Lay out n tiled clients from c in one column; returns the first one left. */ static C * col(C *c, unsigned int n, int x, int w) { unsigned int i; int gap = MIN((int)GAP(), MIN(ww, wh) / 2); int y = wy + gap, h, rem = MAX(1, wh - (int)(n + 1) * gap); for (i = 0; c && i < n; c = nexttiled(c->next), i++) { h = rem / (int)n + (i == n - 1 ? rem % (int)n : 0); if (resize(c, x, y, MAX(1, w - (c->bw << 1)), MAX(1, h - (c->bw << 1)), 0)) configurenotify(c); y += h + gap; } return c; } static void tile(void) { C *c; unsigned int n, nm, ns; int gap = MIN((int)GAP(), MIN(ww, wh) / 2), mw; for (n = 0, c = nexttiled(clients); c; c = nexttiled(c->next)) n++; if (!n) return; nm = MIN((unsigned int)cur.nm, n); ns = n - nm; mw = MAX(1, (nm && ns) ? (int)((ww - 3 * gap) * cur.mf) : ww - 2 * gap); c = col(nexttiled(clients), nm, wx + gap, mw); if (ns) col(c, ns, wx + (nm ? mw + 2 * gap : gap), MAX(1, ww - (nm ? mw + 3 * gap : 2 * gap))); } static void togglefloating(const A *arg) { (void)arg; if (!sel || sel->isfullscreen) return; sel->isfloating = !sel->isfloating || sel->isfixed; sel->oldstate = sel->isfloating; if (sel->isfloating) resize(sel, sel->x, sel->y, sel->w, sel->h, 0); ar(); } static void togglefullscreen(const A *arg) { (void)arg; if (sel) setfullscreen(sel, !sel->isfullscreen); } static void toggletag(const A *arg) { unsigned int t; if (!sel || !(t = sel->tags ^ (arg->ui & TM))) return; sel->tags = t; focus(NULL); ar(); } static void toggleview(const A *arg) { unsigned int m = tagsel[sel_group] ^ (arg->ui & TM); if (!m) return; sel_group ^= 1; tagsel[sel_group] = m; ptload(); focus(NULL); ar(); } static void unfocus(C *c) { grabbuttons(c, 0); BORDER(c, c->isurgent ? color_urg : color_norm); } static void undock(Window w) { unsigned int i = dockidx(w); if (i < ndock) { docks[i] = docks[--ndock]; updateworkarea(); } } static void unmanage(C *c, int destroyed) { XWindowChanges wc; detach(c); detachstack(c); if (!destroyed) { wc.border_width = c->oldbw; XGrabServer(display); /* avoid races with the client */ XSelectInput(display, c->win, NoEventMask); XConfigureWindow(display, c->win, CWBorderWidth, &wc); XUngrabButton(display, AnyButton, AnyModifier, c->win); setclientstate(c, WithdrawnState); XSync(display, False); XUngrabServer(display); } free(c); focus(NULL); updateclientlist(); ar(); } static void unmapnotify(XEvent *e) { C *c; if ((c = wintoclient(e->xunmap.window))) { if (e->xunmap.send_event) setclientstate(c, WithdrawnState); else unmanage(c, 0); } else { undock(e->xunmap.window); } } static void updateclientlist(void) { C *c; XDeleteProperty(display, root, netatom[NetClientList]); for (c = clients; c; c = c->next) XChangeProperty(display, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, (unsigned char *)&c->win, 1); } /* Publish _NET_CURRENT_DESKTOP and _NET_WM_DESKTOP; X is only touched on change. */ static void updatedesktops(void) { static long last = -1; long d = lowtag(tagsel[sel_group]); C *c; if (d != last) { XChangeProperty(display, root, netatom[NetCurrentDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&d, 1); last = d; } for (c = clients; c; c = c->next) { if (c->tags == c->pubtags) continue; c->pubtags = c->tags; d = (c->tags == TM) ? 0xFFFFFFFFL : (long)lowtag(c->tags); /* all desktops */ XChangeProperty(display, c->win, netatom[NetWMDesktop], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&d, 1); } } static void updatenumlockmask(void) { unsigned int i, j; KeyCode nlk = XKeysymToKeycode(display, XK_Num_Lock); XModifierKeymap *mm = XGetModifierMapping(display); if (!mm) return; numlockmask = 0; for (i = 0; nlk && i < 8; i++) for (j = 0; j < (unsigned int)mm->max_keypermod; j++) if (mm->modifiermap[i * mm->max_keypermod + j] == nlk) numlockmask = 1 << i; XFreeModifiermap(mm); } static void updatesizehints(C *c) { long ms; XSizeHints sz; if (!XGetWMNormalHints(display, c->win, &sz, &ms)) sz.flags = PSize; c->basew = (sz.flags & PBaseSize) ? sz.base_width : (sz.flags & PMinSize) ? sz.min_width : 0; c->baseh = (sz.flags & PBaseSize) ? sz.base_height : (sz.flags & PMinSize) ? sz.min_height : 0; c->incw = (sz.flags & PResizeInc) ? sz.width_inc : 0; c->inch = (sz.flags & PResizeInc) ? sz.height_inc : 0; c->maxw = (sz.flags & PMaxSize) ? sz.max_width : 0; c->maxh = (sz.flags & PMaxSize) ? sz.max_height : 0; c->minw = (sz.flags & PMinSize) ? sz.min_width : c->basew; c->minh = (sz.flags & PMinSize) ? sz.min_height : c->baseh; if ((sz.flags & PAspect) && sz.min_aspect.x && sz.max_aspect.y && sz.min_aspect.y && sz.max_aspect.x) { c->mina = (float)sz.min_aspect.y / sz.min_aspect.x; c->maxa = (float)sz.max_aspect.x / sz.max_aspect.y; } else { c->maxa = c->mina = 0.0f; } c->isfixed = c->maxw && c->maxh && c->maxw == c->minw && c->maxh == c->minh; c->hintsvalid = 1; } static void updatewindowtype(C *c) { if (hasatom(c->win, netatom[NetWMState], netatom[NetWMFullscreen])) setfullscreen(c, 1); if (hasatom(c->win, netatom[NetWMWindowType], netatom[NetWMWindowTypeDialog])) c->isfloating = c->oldstate = 1; } static void updatewmhints(C *c) { XWMHints *wm; if (!(wm = XGetWMHints(display, c->win))) return; if (c == sel && (wm->flags & XUrgencyHint)) { wm->flags &= ~XUrgencyHint; XSetWMHints(display, c->win, wm); c->isurgent = 0; } else { c->isurgent = !!(wm->flags & XUrgencyHint); } c->neverfocus = !!(wm->flags & InputHint) && !wm->input; XFree(wm); } /* * Docks reserve screen edges via _NET_WM_STRUT_PARTIAL or _NET_WM_STRUT; the * biggest strut per edge wins. Strut ranges are ignored: the X screen is one * monitor. Tiling, monocle and snapping use the area that is left. */ static void updateworkarea(void) { Atom type, prop[2] = { netatom[NetWMStrutPartial], netatom[NetWMStrut] }; long wa[4 * LEN(tags)], e[4] = { 0, 0, 0, 0 }; /* left right top bottom */ unsigned long n, rem; unsigned int i, j, k; unsigned char *p; int fmt, found, x, y, w, h; for (i = 0; i < ndock; i++) for (j = 0, found = 0; j < 2 && !found; j++) { p = NULL; if (XGetWindowProperty(display, docks[i], prop[j], 0L, 4L, False, XA_CARDINAL, &type, &fmt, &n, &rem, &p) != Success || !p) continue; if ((found = type == XA_CARDINAL && fmt == 32 && n == 4)) for (k = 0; k < 4; k++) e[k] = MAX(e[k], ((long *)p)[k]); XFree(p); } for (k = 0; k < 4; k++) e[k] = MIN(e[k], k < 2 ? screen_w : screen_h); x = (int)e[0]; y = (int)e[2]; w = MAX(1, screen_w - x - (int)e[1]); h = MAX(1, screen_h - y - (int)e[3]); if (x == wx && y == wy && w == ww && h == wh) return; wx = x; wy = y; ww = w; wh = h; for (i = 0; i < LEN(tags); i++) { wa[4 * i] = x; wa[4 * i + 1] = y; wa[4 * i + 2] = w; wa[4 * i + 3] = h; } XChangeProperty(display, root, netatom[NetWorkarea], XA_CARDINAL, 32, PropModeReplace, (unsigned char *)wa, 4 * LEN(tags)); ar(); } static void view(const A *arg) { unsigned int m = arg->ui & TM; if (arg->ui && !m) /* tag out of range, not Mod+Tab */ return; /* Mod+Tab (empty mask) swaps between the two saved tag slots. */ if (m == tagsel[sel_group] || (!m && tagsel[0] == tagsel[1])) return; sel_group ^= 1; if (m) tagsel[sel_group] = m; ptload(); focus(NULL); ar(); } static C * wintoclient(Window w) { C *c; for (c = clients; c && c->win != w; c = c->next) ; return c; } /* * Windows vanish under us all the time (BadWindow) and some requests race * with that. None of it is worth killing the session over: log and go on. */ static int xerror(Display *dpy, XErrorEvent *ee) { (void)dpy; if (ee->error_code == BadWindow || (ee->error_code == BadMatch && (ee->request_code == X_SetInputFocus || ee->request_code == X_ConfigureWindow))) return 0; if (ee->error_code == BadAccess && (ee->request_code == X_GrabButton || ee->request_code == X_GrabKey)) fprintf(stderr, "nwm: warning: failed to grab key/button (BadAccess conflict)\n"); else fprintf(stderr, "nwm: error req=%d code=%d\n", ee->request_code, ee->error_code); return 0; } static int xerrorstart(Display *dpy, XErrorEvent *e) { (void)dpy; (void)e; die("nwm: another wm is running"); return -1; } static void zoom(const A *arg) { C *c = sel; (void)arg; if (!ARR || !c || c->isfloating) return; if (c == nexttiled(clients) && !(c = nexttiled(c->next))) return; pop(c); } int main(int argc, char *argv[]) { if (argc == 2 && !strcmp("-v", argv[1])) { puts("nwm-" VERSION); return 0; } if (argc != 1) die("usage: nwm [-v]"); if (!(display = XOpenDisplay(NULL))) die("nwm: cannot open display"); checkwm(); setup(); #ifdef __OpenBSD__ if (pledge("stdio rpath proc exec", NULL) == -1) die("nwm: pledge"); #endif scan(); run(); cleanup(); XCloseDisplay(display); return 0; } tinyopsec-nwm-67f5c08/nwm.h000066400000000000000000000067371526263016200156640ustar00rootroot00000000000000#define MODKEY Mod4Mask /* * Compile-time constructor. * * Set these to 0 to remove the corresponding code/data from the build. * This is not runtime configuration. Recompile after changing. * * NWM_WITH_BORDERS = 0: * - no border colors are allocated; * - new clients get border width 0; * - original client border width is still restored on exit/unmanage. * * NWM_WITH_GAPS = 0: * - no gappx; * - tile/monocle use zero gaps. * * NWM_WITH_PERTAG = 0: * - no per-tag layout/master/mfact/nmaster state; * - saves a small static array and related logic */ #define NWM_WITH_BORDERS 1 #define NWM_WITH_GAPS 1 #define NWM_WITH_PERTAG 1 #define TAGKEYS(KEY,TAG) \ { MODKEY, KEY, view, {.ui = 1u << (TAG)} }, \ { MODKEY|ControlMask, KEY, toggleview,{.ui = 1u << (TAG)} }, \ { MODKEY|ShiftMask, KEY, tag, {.ui = 1u << (TAG)} }, \ { MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1u << (TAG)} }, #if NWM_WITH_BORDERS static const unsigned int borderpx = 2; static const char col_nborder[] = "#0E2426"; static const char col_sborder[] = "#59C6B5"; static const char col_uborder[] = "#c47f50"; #endif #if NWM_WITH_GAPS static const unsigned int gappx = 6; #endif static const unsigned int snap = 16; static const int attachbottom = 0; static const int focusonopen = 1; static const float mfact = 0.5f; static const int nmaster = 1; static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; static const L layouts[] = { { tile }, /* tiling */ { NULL }, /* floating */ { monocle }, /* monocle */ }; static const char *termcmd[] = { "st", NULL }; static const char *dmenucmd[] = { "dmenu_run", NULL }; static const Rule rules[] = { /* class instance title tags isfloating */ { "Gimp", NULL, NULL, 0, 1 }, { "MPlayer", NULL, NULL, 0, 1 }, }; static const K keys[] = { { MODKEY, XK_Return, spawn, {.v = termcmd} }, { MODKEY, XK_d, spawn, {.v = dmenucmd} }, { MODKEY, XK_q, killclient, {0} }, { MODKEY|ShiftMask, XK_e, quit, {0} }, { MODKEY, XK_j, focusnext, {.i = +1} }, { MODKEY, XK_k, focusnext, {.i = -1} }, { MODKEY, XK_i, incnmaster, {.i = +1} }, { MODKEY, XK_o, incnmaster, {.i = -1} }, { MODKEY, XK_h, setmasterfact,{.f = -0.05} }, { MODKEY, XK_l, setmasterfact,{.f = +0.05} }, { MODKEY, XK_space, zoom, {0} }, { MODKEY, XK_Tab, view, {0} }, { MODKEY, XK_t, setlayout, {.v = &layouts[0]} }, { MODKEY, XK_f, setlayout, {.v = &layouts[1]} }, { MODKEY, XK_m, setlayout, {.v = &layouts[2]} }, { MODKEY, XK_F11, togglefullscreen, {0} }, { MODKEY|ShiftMask, XK_space, togglefloating, {0} }, { MODKEY, XK_0, view, {.ui = ~0u} }, { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0u} }, TAGKEYS(XK_1,0) TAGKEYS(XK_2,1) TAGKEYS(XK_3,2) TAGKEYS(XK_4,3) TAGKEYS(XK_5,4) TAGKEYS(XK_6,5) TAGKEYS(XK_7,6) TAGKEYS(XK_8,7) TAGKEYS(XK_9,8) }; static const B buttons[] = { { ClkClientWin, MODKEY, Button1, movemouse, {0} }, { ClkClientWin, MODKEY, Button2, togglefloating,{0} }, { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, };