pax_global_header00006660000000000000000000000064152011407240014506gustar00rootroot0000000000000052 comment=de47eb76d48097b652ce3f60899b46e30be01ec4 nwm-1.5/000077500000000000000000000000001520114072400121545ustar00rootroot00000000000000nwm-1.5/LICENSE000077500000000000000000000020521520114072400131630ustar00rootroot00000000000000MIT License Copyright (c) 2026 tinyopsec 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. nwm-1.5/Makefile000066400000000000000000000021711520114072400136150ustar00rootroot00000000000000# nwm - Nano window manager # See LICENSE file for copyright and license details. VERSION = 1.5 PREFIX = /usr/local BINDIR = ${PREFIX}/bin 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 -Os ${INCS} ${CPPFLAGS} #CFLAGS = -g -std=c99 -pedantic -Wall -Wextra -O0 ${INCS} ${CPPFLAGS} LDFLAGS = ${LIBS} # Solaris #CFLAGS = -fast ${INCS} -DVERSION=\"${VERSION}\" #LDFLAGS = ${LIBS} 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} clean: rm -f nwm ${OBJ} install: all mkdir -p ${DESTDIR}${BINDIR} cp -f nwm ${DESTDIR}${BINDIR}/nwm chmod 755 ${DESTDIR}${BINDIR}/nwm uninstall: rm -f ${DESTDIR}${BINDIR}/nwm .PHONY: all clean install uninstall nwm-1.5/README.md000066400000000000000000000407351520114072400134440ustar00rootroot00000000000000
[![Capsule Render](https://capsule-render.vercel.app/api?type=waving&color=0E2426&height=80§ion=header&text=nwm&fontSize=52&fontColor=59C6B5&fontAlignY=72&animation=fadeIn)](https://github.com/tinyopsec/nwm) [![Typing SVG](https://readme-typing-svg.demolab.com?font=JetBrains+Mono&size=14&pause=1200&color=59C6B5¢er=true&vCenter=true&width=600&lines=Minimal+tiling+X11+window+manager;%7E1000+lines.+No+runtime+dependencies;Compile-time+configuration+only)](https://git.io/typing-svg) nwm: tiling layout with master/stack on Alpine Linux

   

## Contents - [Quick Start](#quick-start) - [vs dwm](#vs-dwm) - [Features](#features) - [Screenshots](#screenshots) - [Installation](#installation) - [Usage](#usage) - [Key Bindings](#key-bindings) - [Configuration](#configuration) - [How It Works](#how-it-works) - [Contributing](#contributing) - [Star History](#star-history) - [Related](#related) - [License](#license) --- ## 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 | ~2000 | ~900 - fits in one reading session | | RAM at idle | ~2-3 MB | ~1 MB - leaner process image | | Tiling arithmetic | Can accumulate pixel remainder | Integer division, no drift | | Gap support | Requires patching | Built in via `gappx` | | `Mod+Tab` behavior | Inconsistent across patches | Deterministic XOR for both tags and layouts | | OpenBSD `pledge(2)` | Not supported | Supported natively | | POSIX compliance | Uses GNU extensions in places | Strict POSIX C99 throughout | | Status bar | Built-in bar, requires patching to remove | No bar - use any external panel or none | | Config complexity | ~100 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 the gaps and pertag 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 | | Gaps | Configurable `gappx` on all sides | | 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 | | Auto-float | `_NET_WM_WINDOW_TYPE_DIALOG` windows float automatically | | EWMH | `_NET_WM_STATE`, `_NET_ACTIVE_WINDOW`, `_NET_CLIENT_LIST`, `_NET_SUPPORTING_WM_CHECK` | | ICCCM | `WM_DELETE_WINDOW`, `WM_TAKE_FOCUS`, `WM_NORMAL_HINTS`, `WM_HINTS` | | OpenBSD | `pledge(2)` support; also builds on FreeBSD | | Compilation | Clean under `gcc -std=c99 -pedantic -Wall -Wextra` | --- ## Screenshots nwm on Alpine Linux: three terminals tiled in master/stack layout nwm master/stack with browser in master and two terminals in stack --- ## 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. ### 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, then edit the top of the `Makefile` to point at your system's X11 paths. The relevant lines are already present but commented out: ```makefile # Uncomment for OpenBSD / FreeBSD: # INCS = -I/usr/X11R6/include # LIBS = -L/usr/X11R6/lib -lX11 ``` 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` has no built-in autostart. Launch background processes from `.xinitrc` or a wrapper script before the `exec` line. 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) | | `Mod + j` | Focus next window in stack | | `Mod + k` | Focus previous window in stack | | `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 | | `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 the same two-slot XOR mechanism as layout switching - it always restores the exact tag bitmask that was active before the last view change, including combined multi-tag views. ### 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`. 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. | 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 bottom of stack | | `focusonopen` | `1` | Set to `0` to keep focus on the current window when a new one opens | ### 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 */ ``` --- ## 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 - no floating-point accumulation, no pixel drift across redraws. 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. Layout and tag history both use a two-slot XOR system. `nwm` keeps the current and previous values in a two-element array and flips an index bit on each change. `Mod+Tab` flips the index back - always returning to whatever was active before, whether that was a layout or a tag view. ```mermaid flowchart TD start([Start]) --> chkwm[check for another WM] chkwm --> setup[setup: init display, atoms, colors,\ngrab keys, scan existing windows] setup --> run[run: main event loop] run --> next{XNextEvent} next -->|KeyPress| kp[KeyPress handler\nkp] next -->|ButtonPress| bp[ButtonPress handler\nbp] next -->|MapRequest| mapreq[MapRequest handler\nmapreq] next -->|ConfigureRequest| cfgreq[ConfigureRequest handler\ncfgreq] next -->|ClientMessage| cmsg[ClientMessage handler\ncmsg] next -->|DestroyNotify| destnot[DestroyNotify handler\ndestnot] next -->|EnterNotify| entnot[EnterNotify handler\nentnot] next -->|PropertyNotify| propnot[PropertyNotify handler\npropnot] next -->|UnmapNotify| unmapnt[UnmapNotify handler\nunmapnt] next -->|FocusIn| fcin[FocusIn handler\nfcin] next -->|MappingNotify| mapnot[MappingNotify handler\nmapnot] next -->|Other| run kp --> keyfn{execute key action} keyfn --> spawn(spawn process) keyfn --> killcl(kill client) keyfn --> quit(quit WM) keyfn --> view(view / toggle view) keyfn --> tag(tag client) keyfn --> tgltag(toggle tag on client) keyfn --> fcs(change focus) keyfn --> incnm(change master count) keyfn --> setmf(set master factor) keyfn --> setlt(set layout) keyfn --> zoom(zoom client) keyfn --> tglfs(toggle fullscreen) keyfn --> tglfl(toggle floating) bp --> btnfn{execute button action} btnfn --> mv(move client) btnfn --> tglfl btnfn --> rz(resize client) mapreq --> mg[mg: create Client struct,\nconfigure, add to list,\nmap window, arrange] cmsg --> cmsg_act{handle client message} cmsg_act --> setfs(set fullscreen state) cmsg_act --> seturg(set urgent state) destnot --> unmng[unmng: remove client,\nfree, update lists, arrange] entnot --> fc[fc: focus client under pointer] fcin --> setfocus[setfocus: restore input focus] propnot --> prop_act{update properties} prop_act --> updwmh(update WM hints) prop_act --> updtype(update window type) prop_act --> seturg unmapnt --> unmng mapnot --> grabkeys(grabkeys: reacquire key binds) mg --> ar unmng --> ar fc --> ar setfs --> ar fcs --> ar view --> ar tag --> ar tgltag --> ar incnm --> ar setmf --> ar setlt --> ar zoom --> ar tglfl --> ar mv --> ar rz --> ar spawn -.->|fork+exec| app([external app]) ar[ar: rearrange layout\ntile / monocle] ar --> shide[shide: hide/show/resize clients] shide --> rst[rst: restack windows] run -->|running = 0| cleanup[cleanup: free clients,\nungrab keys, destroy windows] cleanup --> stop([Stop]) ``` --- ## Contributing Bug reports and patches are welcome via [GitHub Issues](https://github.com/tinyopsec/nwm/issues) and pull requests.
Code requirements - No comments in production code - No external dependencies - No over-abstraction or wrapper layers - Compiles clean: `gcc -std=c99 -pedantic -Wall -Wextra` - Total source stays under 1000 lines Optional features behind `#ifdef` or compile-time constants are considered. Core event loop changes are reviewed carefully.
### Contributors ### Activity ![Repobeats](https://repobeats.axiom.co/api/embed/tinyopsec/nwm.svg "Repobeats analytics image") --- ## Trophies

trophies

--- ## Star History Star History Chart --- ## 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) | --- ## License MIT. See [LICENSE](LICENSE) for details. [![Capsule Render](https://capsule-render.vercel.app/api?type=waving&color=0E2426&height=80§ion=footer)](https://github.com/tinyopsec/nwm) nwm-1.5/nwm.c000066400000000000000000000777341520114072400131430ustar00rootroot00000000000000#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #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 & ts[sg]) #define W(c) ((c)->w + ((c)->bw << 1)) #define H(c) ((c)->h + ((c)->bw << 1)) #define TM ((1u << LEN(tags)) - 1) #define LEN(x) (sizeof(x)/sizeof(*(x))) #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)<(b)?(a):(b)) #define FRAMERATE 16 #define XP_ConfigureWindow 12 #define XP_GrabButton 28 #define XP_GrabKey 33 #define XP_SetInputFocus 42 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; 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; unsigned int isfixed:1, isfloating:1, isurgent:1, neverfocus:1, oldstate:1, isfullscreen:1, hintsvalid:1; C *next, *snext; Window win; }; enum { ClkClientWin, ClkRootWin }; enum { NetWMState, NetWMFullscreen, NetActiveWindow, NetWMWindowType, NetWMWindowTypeDialog, NetClientList, NetLast }; enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; static void ar(void); static void at(C*); static void bp(XEvent*); static void chkwm(void); static void cleanup(void); static void cmsg(XEvent*); static void cfgnt(C*); static void cfgreq(XEvent*); static void destnot(XEvent*); static void detach(C*); static void detachstack(C*); static void entnot(XEvent*); static void fc(C*); static void fcin(XEvent*); static void fcs(const A*); static Atom getatom(C*, Atom); static int getrootptr(int*, int*); static long getstate(Window); static void grabbuttons(C*, int); static void grabkeys(void); static void incnm(const A*); static void kp(XEvent*); static void killcl(const A*); static void mg(Window, XWindowAttributes*); static void mapnot(XEvent*); static void mapreq(XEvent*); static void monocle(void); static void mv(const A*); static C *nextt(C*); static void pop(C*); static void propnot(XEvent*); static void quit(const A*); static void rs(C*, int, int, int, int, int); static void rcl(C*, int, int, int, int); static void rz(const A*); static void rst(void); static void run(void); static void scan(void); static int sendevent(C*, Atom); static void setcs(C*, long); static void setfocus(C*); static void setfs(C*, int); static void setlt(const A*); static void setmf(const A*); static void setup(void); static void seturg(C*, int); static void shide(C*); static void spawn(const A*); static void tag(const A*); static void tile(void); static void tglfl(const A*); static void tglfs(const A*); static void tgltag(const A*); static void tglview(const A*); static void unfcs(C*); static void unmng(C*, int); static void unmapnt(XEvent*); static void updcl(void); static void updnm(void); static void updsz(C*); static void updtype(C*); static void updwmh(C*); static void view(const A*); static C *wintoc(Window); static int xerror(Display*, XErrorEvent*); static int xe0(Display*, XErrorEvent*); static int xerrorstart(Display*, XErrorEvent*); static void zoom(const A*); static Display *d; static Window r, wmcheck; static int screen, sw, sh, wx, wy, ww, wh; static int running = 1; static unsigned int numlockmask, sg, li, ts[2]; static float mf; static int nm; static Cursor cursor[3]; static Atom wmatom[WMLast], netatom[NetLast]; static C *cs, *s, *st; static const L *lt[2]; static unsigned long nborder, sborder, uborder; static int (*xerrorxlib)(Display*, XErrorEvent*); static Time ltime = CurrentTime; static void (*handler[LASTEvent])(XEvent*) = { [ButtonPress] = bp, [ClientMessage] = cmsg, [ConfigureRequest] = cfgreq, [DestroyNotify] = destnot, [EnterNotify] = entnot, [FocusIn] = fcin, [KeyPress] = kp, [MappingNotify] = mapnot, [MapRequest] = mapreq, [PropertyNotify] = propnot, [UnmapNotify] = unmapnt, }; #include "nwm.h" static void die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); fputc('\n', stderr); if (d) XCloseDisplay(d); exit(1); } static int aph(C *c, int *x, int *y, int *w, int *h, int i) { int bw2 = c->bw << 1; *w = MAX(1, *w); *h = MAX(1, *h); if (i) { if (*x > sw) *x = sw - (*w + bw2); if (*y > sh) *y = sh - (*h + bw2); if (*x + *w + bw2 < 0) *x = 0; if (*y + *h + bw2 < 0) *y = 0; } else { if (*x >= wx+ww) *x = wx+ww - (*w + bw2); if (*y >= wy+wh) *y = wy+wh - (*h + bw2); if (*x + *w + bw2 <= wx) *x = wx; if (*y + *h + bw2 <= wy) *y = wy; } if (c->isfloating || !lt[li]->ar) { if (!c->hintsvalid) updsz(c); *w -= c->basew; *h -= c->baseh; if (c->mina > 0 && c->maxa > 0 && *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 (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) { shide(st); if (lt[li]->ar) lt[li]->ar(); rst(); } static void at(C *c) { if (attachbottom) { C **tc; for (tc = &cs; *tc; tc = &(*tc)->next); c->next = NULL; *tc = c; } else { c->next = cs; cs = c; } c->snext = st; st = c; } static void bp(XEvent *e) { unsigned int i, click = ClkRootWin; XButtonPressedEvent *ev = &e->xbutton; C *c; ltime = ev->time; if ((c = wintoc(ev->window))) { fc(c); rst(); XAllowEvents(d, ReplayPointer, ev->time); click = ClkClientWin; } 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 chkwm(void) { xerrorxlib = XSetErrorHandler(xerrorstart); XSelectInput(d, DefaultRootWindow(d), SubstructureRedirectMask); XSync(d, False); XSetErrorHandler(xerror); } static void cleanup(void) { unsigned int i; C *c; while ((c = cs)) { detach(c); detachstack(c); XMoveWindow(d, c->win, c->x, c->y); setcs(c, WithdrawnState); free(c); } s = st = NULL; XUngrabKey(d, AnyKey, AnyModifier, r); for (i = 0; i < 3; i++) XFreeCursor(d, cursor[i]); XDeleteProperty(d, r, netatom[NetClientList]); XDestroyWindow(d, wmcheck); XSync(d, False); XSetInputFocus(d, PointerRoot, RevertToPointerRoot, CurrentTime); } static void cmsg(XEvent *e) { XClientMessageEvent *ev = &e->xclient; C *c = wintoc(ev->window); if (!c) return; if (ev->message_type == netatom[NetWMState] && (ev->data.l[1] == (long)netatom[NetWMFullscreen] || ev->data.l[2] == (long)netatom[NetWMFullscreen])) setfs(c, ev->data.l[0] == 1 || (ev->data.l[0] == 2 && !c->isfullscreen)); else if (ev->message_type == netatom[NetActiveWindow] && c != s && !c->isurgent) seturg(c, 1); } static void cfgnt(C *c) { XConfigureEvent ev = { .type = ConfigureNotify, .send_event = True, .display = d, .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(d, c->win, False, StructureNotifyMask, (XEvent*)&ev); } static void cfgreq(XEvent *e) { XConfigureRequestEvent *ev = &e->xconfigurerequest; XWindowChanges wc; C *c = wintoc(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(d, ev->window, ev->value_mask, &wc); return; } if (ev->value_mask & CWBorderWidth) { c->bw = ev->border_width; XSetWindowBorderWidth(d, c->win, c->bw); ar(); } if (c->isfloating || !lt[li]->ar) { if (ev->value_mask & CWX) { c->oldx = c->x; c->x = ev->x; } if (ev->value_mask & CWY) { c->oldy = c->y; c->y = ev->y; } if (ev->value_mask & CWWidth) { c->oldw = c->w; c->w = ev->width; } if (ev->value_mask & CWHeight) { c->oldh = c->h; c->h = ev->height; } if (VIS(c)) { XMoveResizeWindow(d, c->win, c->x, c->y, c->w, c->h); if (c->isfloating) XRaiseWindow(d, c->win); } else { cfgnt(c); } } else { cfgnt(c); } } static void destnot(XEvent *e) { C *c; if ((c = wintoc(e->xdestroywindow.window))) unmng(c, 1); } static void detach(C *c) { C **tc; for (tc = &cs; *tc && *tc != c; tc = &(*tc)->next); if (*tc) *tc = c->next; } static void detachstack(C *c) { C **tc, *t; for (tc = &st; *tc && *tc != c; tc = &(*tc)->snext); if (*tc) *tc = c->snext; if (c == s) { for (t = st; t && !VIS(t); t = t->snext); s = t; } } static void entnot(XEvent *e) { XCrossingEvent *ev = &e->xcrossing; C *c; if ((ev->mode != NotifyNormal || ev->detail == NotifyInferior) && ev->window != r) return; ltime = ev->time; c = wintoc(ev->window); if (!c || c == s) return; if (s && s->isfullscreen) return; if (c->isfullscreen) return; if (s && s->isfloating && !c->isfloating && lt[li]->ar) return; fc(c); rst(); } static void fc(C *c) { C **tc; if (!c || !VIS(c)) for (c = st; c && !VIS(c); c = c->snext); if (s && s != c) unfcs(s); if (c) { if (c->isurgent) seturg(c, 0); for (tc = &st; *tc && *tc != c; tc = &(*tc)->snext); if (*tc) *tc = c->snext; c->snext = st; st = c; grabbuttons(c, 1); XSetWindowBorder(d, c->win, sborder); setfocus(c); } else { XSetInputFocus(d, r, RevertToPointerRoot, ltime); XDeleteProperty(d, r, netatom[NetActiveWindow]); } s = c; } static void fcin(XEvent *e) { if (s && e->xfocus.window != s->win) setfocus(s); } static void fcs(const A *arg) { C *c = NULL, *i; int tiled = !!lt[li]->ar; if (!s || s->isfullscreen) return; if (arg->i > 0) { for (c = s->next; c && (!VIS(c) || (tiled && c->isfloating)); c = c->next); if (!c) for (c = cs; c && (!VIS(c) || (tiled && c->isfloating)); c = c->next); } else { for (i = cs; i != s; i = i->next) if (VIS(i) && (!tiled || !i->isfloating)) c = i; if (!c) for (; i; i = i->next) if (VIS(i) && (!tiled || !i->isfloating)) c = i; } if (c) { fc(c); rst(); } } static Atom getatom(C *c, Atom prop) { int fmt; unsigned long n, rem; unsigned char *p = NULL; Atom type, a = None; if (XGetWindowProperty(d, c->win, prop, 0L, 1L, False, XA_ATOM, &type, &fmt, &n, &rem, &p) == Success && p) { if (type == XA_ATOM && n > 0) memcpy(&a, p, sizeof(Atom)); XFree(p); } return a; } static int getrootptr(int *x, int *y) { int di; unsigned int dui; Window dw; return XQueryPointer(d, r, &dw, &dw, x, y, &di, &di, &dui); } static long getstate(Window w) { int fmt; long res = -1; unsigned char *p = NULL; unsigned long n, ex; Atom real; if (XGetWindowProperty(d, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], &real, &fmt, &n, &ex, &p) == Success) { if (n && fmt == 32 && p) memcpy(&res, p, sizeof(long)); if (p) XFree(p); } return res; } static void grabbuttons(C *c, int focused) { unsigned int mods[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; unsigned int i, j; XUngrabButton(d, AnyButton, AnyModifier, c->win); if (!focused) { XGrabButton(d, AnyButton, AnyModifier, c->win, False, BUTTONMASK, GrabModeSync, GrabModeSync, None, None); return; } for (i = 0; i < LEN(buttons); i++) if (buttons[i].click == ClkClientWin) for (j = 0; j < 4; j++) XGrabButton(d, buttons[i].button, buttons[i].mask | mods[j], c->win, False, BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); } static void grabkeys(void) { unsigned int mods[] = { 0, LockMask, numlockmask, numlockmask|LockMask }; unsigned int i, j; KeyCode code; updnm(); XUngrabKey(d, AnyKey, AnyModifier, r); for (i = 0; i < LEN(keys); i++) if ((code = XKeysymToKeycode(d, keys[i].key))) for (j = 0; j < 4; j++) XGrabKey(d, code, keys[i].mod | mods[j], r, True, GrabModeAsync, GrabModeAsync); } static void incnm(const A *arg) { nm = (nm + arg->i > 0) ? nm + arg->i : 0; ar(); } static void kp(XEvent *e) { unsigned int i; XKeyEvent *ev = &e->xkey; KeySym sym = XLookupKeysym(ev, 0); ltime = ev->time; for (i = 0; i < LEN(keys); i++) if (sym == keys[i].key && CLEANMASK(keys[i].mod) == CLEANMASK(ev->state) && keys[i].fn) keys[i].fn(&keys[i].arg); } static void killcl(const A *arg) { (void)arg; if (!s) return; if (!sendevent(s, wmatom[WMDelete])) { XGrabServer(d); XSetErrorHandler(xe0); XSetCloseDownMode(d, DestroyAll); XKillClient(d, s->win); XSync(d, False); XSetErrorHandler(xerror); XUngrabServer(d); } } static void mg(Window w, XWindowAttributes *wa) { C *c, *t = NULL; Window trans = None; XWindowChanges wc; 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; updsz(c); updwmh(c); c->tags = ts[sg]; if (XGetTransientForHint(d, w, &trans) && (t = wintoc(trans))) c->tags = t->tags; c->bw = borderpx; if (c->x + W(c) > wx+ww) c->x = wx+ww - W(c); if (c->y + H(c) > wy+wh) c->y = wy+wh - H(c); c->x = MAX(c->x, wx); c->y = MAX(c->y, wy); wc.border_width = c->bw; XConfigureWindow(d, w, CWBorderWidth, &wc); XSetWindowBorder(d, w, nborder); cfgnt(c); c->isfloating = c->oldstate = trans != None || c->isfixed; updtype(c); XSelectInput(d, w, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); grabbuttons(c, 0); at(c); XChangeProperty(d, r, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, (unsigned char*)&w, 1); if (c->isfloating) XMapRaised(d, c->win); else XMapWindow(d, c->win); setcs(c, NormalState); if (focusonopen) fc(c); ar(); } static void mapnot(XEvent *e) { XRefreshKeyboardMapping(&e->xmapping); if (e->xmapping.request == MappingKeyboard) grabkeys(); } static void mapreq(XEvent *e) { XWindowAttributes wa; C *c; if (!XGetWindowAttributes(d, e->xmaprequest.window, &wa) || wa.override_redirect) return; if ((c = wintoc(e->xmaprequest.window))) { XMapWindow(d, c->win); setcs(c, NormalState); ar(); return; } mg(e->xmaprequest.window, &wa); } static void monocle(void) { C *c; for (c = nextt(cs); c; c = nextt(c->next)) rs(c, wx, wy, MAX(1, ww - (c->bw << 1)), MAX(1, wh - (c->bw << 1)), 0); } static void mv(const A *arg) { (void)arg; int x, y, ocx, ocy, nx, ny, needar = 0; XEvent ev; Time last = 0; C *c = s; if (!c || c->isfullscreen) return; rst(); ocx = c->x; ocy = c->y; if (XGrabPointer(d, r, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, None, cursor[1], ltime) != GrabSuccess) return; if (!getrootptr(&x, &y)) { XUngrabPointer(d, CurrentTime); return; } do { XMaskEvent(d, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); if (ev.type == ConfigureRequest || ev.type == Expose || ev.type == MapRequest) handler[ev.type](&ev); else if (ev.type == MotionNotify) { if (ev.xmotion.time - last <= FRAMERATE) continue; last = ev.xmotion.time; nx = ocx + ev.xmotion.x - x; ny = ocy + ev.xmotion.y - y; if (abs(wx - nx) < (int)snap) nx = wx; else if (abs(wx+ww-W(c) - nx) < (int)snap) nx = wx+ww - W(c); if (abs(wy - ny) < (int)snap) ny = wy; else if (abs(wy+wh-H(c) - ny) < (int)snap) ny = wy+wh - H(c); if (!c->isfloating && lt[li]->ar && (abs(nx-ocx) > (int)snap || abs(ny-ocy) > (int)snap)) { c->isfloating = c->oldstate = 1; needar = 1; } if (!lt[li]->ar || c->isfloating) rs(c, nx, ny, c->w, c->h, 1); } } while (ev.type != ButtonRelease); XUngrabPointer(d, CurrentTime); if (needar) ar(); } static C *nextt(C *c) { for (; c && (c->isfloating || !VIS(c)); c = c->next); return c; } static void pop(C *c) { detach(c); c->next = cs; cs = c; fc(c); ar(); } static void propnot(XEvent *e) { XPropertyEvent *ev = &e->xproperty; C *c; if (ev->state == PropertyDelete || !(c = wintoc(ev->window))) return; if (ev->atom == XA_WM_HINTS) { updwmh(c); XSetWindowBorder(d, c->win, c->isurgent ? uborder : (c == s ? sborder : nborder)); } else if (ev->atom == XA_WM_NORMAL_HINTS) c->hintsvalid = 0; else if (ev->atom == netatom[NetWMWindowType]) updtype(c); } static void quit(const A *arg) { (void)arg; running = 0; } static void rs(C *c, int x, int y, int w, int h, int i) { if (i || c->isfloating || !lt[li]->ar) { if (aph(c, &x, &y, &w, &h, i)) rcl(c, x, y, w, h); } else { rcl(c, x, y, w, h); } } static void rcl(C *c, int x, int y, int w, int h) { XWindowChanges wc; c->oldx = c->x; c->x = x; c->oldy = c->y; c->y = y; c->oldw = c->w; c->w = w; c->oldh = c->h; c->h = h; wc.x = x; wc.y = y; wc.width = w; wc.height = h; wc.border_width = c->bw; XConfigureWindow(d, c->win, CWX|CWY|CWWidth|CWHeight|CWBorderWidth, &wc); } static void rz(const A *arg) { (void)arg; int ocx, ocy, ocw, och, nw, nh, needar = 0; XEvent ev; Time last = 0; C *c = s; if (!c || c->isfullscreen) return; rst(); ocx = c->x; ocy = c->y; ocw = c->w; och = c->h; if (XGrabPointer(d, r, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, None, cursor[2], ltime) != GrabSuccess) return; XWarpPointer(d, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); do { XMaskEvent(d, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); if (ev.type == ConfigureRequest || ev.type == Expose || ev.type == MapRequest) handler[ev.type](&ev); else if (ev.type == MotionNotify) { if (ev.xmotion.time - last <= FRAMERATE) 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 && lt[li]->ar && (abs(nw-ocw) > (int)snap || abs(nh-och) > (int)snap)) { c->isfloating = c->oldstate = 1; needar = 1; } if (!lt[li]->ar || c->isfloating) rs(c, c->x, c->y, nw, nh, 1); } } while (ev.type != ButtonRelease); XWarpPointer(d, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); XUngrabPointer(d, CurrentTime); if (needar) ar(); } static void rst(void) { C *c; XWindowChanges wc = { .stack_mode = Above }; if (!s) return; if (s->isfullscreen) { XRaiseWindow(d, s->win); return; } if (s->isfloating || !lt[li]->ar) XRaiseWindow(d, s->win); if (lt[li]->ar) { for (c = st; c; c = c->snext) if (!c->isfloating && VIS(c)) { XConfigureWindow(d, c->win, wc.sibling ? CWSibling|CWStackMode : CWStackMode, &wc); wc.sibling = c->win; } for (c = st; c; c = c->snext) if (c->isfloating && VIS(c)) XRaiseWindow(d, c->win); if (s->isfloating) XRaiseWindow(d, s->win); } } static void run(void) { XEvent ev; XSync(d, False); while (running && !XNextEvent(d, &ev)) if (!XFilterEvent(&ev, None) && ev.type < LASTEvent && handler[ev.type]) handler[ev.type](&ev); } static void scan(void) { unsigned int i, n; Window d1, d2, trans, *wins = NULL; XWindowAttributes wa; if (!XQueryTree(d, r, &d1, &d2, &wins, &n)) return; for (i = 0; i < n; i++) { if (!XGetWindowAttributes(d, wins[i], &wa) || wa.override_redirect || XGetTransientForHint(d, wins[i], &trans)) continue; if (wa.map_state == IsViewable || getstate(wins[i]) == IconicState) mg(wins[i], &wa); } for (i = 0; i < n; i++) { if (!XGetWindowAttributes(d, wins[i], &wa) || wa.override_redirect) continue; if (XGetTransientForHint(d, wins[i], &trans) && (wa.map_state == IsViewable || getstate(wins[i]) == IconicState)) mg(wins[i], &wa); } XFree(wins); } static int sendevent(C *c, Atom proto) { int n, exists = 0; Atom *prots; XEvent ev = {0}; if (XGetWMProtocols(d, 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] = proto; ev.xclient.data.l[1] = ltime; XSendEvent(d, c->win, False, NoEventMask, &ev); } return exists; } static void setcs(C *c, long state) { long data[] = { state, None }; XChangeProperty(d, c->win, wmatom[WMState], wmatom[WMState], 32, PropModeReplace, (unsigned char*)data, 2); } static void setfocus(C *c) { if (!c->neverfocus) { XSetInputFocus(d, c->win, RevertToPointerRoot, ltime); XChangeProperty(d, r, netatom[NetActiveWindow], XA_WINDOW, 32, PropModeReplace, (unsigned char*)&c->win, 1); } sendevent(c, wmatom[WMTakeFocus]); } static void setfs(C *c, int fs) { int fmt, i, j; unsigned long n, rem; unsigned char *p = NULL; Atom type, *atoms, *na; if (fs && !c->isfullscreen) { XChangeProperty(d, c->win, netatom[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); c->isfullscreen = 1; c->oldstate = c->isfloating; c->oldbw = c->bw; c->bw = 0; c->isfloating = 1; rcl(c, 0, 0, sw, sh); XRaiseWindow(d, c->win); } else if (!fs && c->isfullscreen) { if (XGetWindowProperty(d, c->win, netatom[NetWMState], 0L, 1024L, False, XA_ATOM, &type, &fmt, &n, &rem, &p) == Success && p) { if (n == 0) { XDeleteProperty(d, c->win, netatom[NetWMState]); } else { atoms = (Atom *)p; na = (Atom *)malloc(n * sizeof(Atom)); if (na) { for (i = 0, j = 0; i < (int)n; i++) if (atoms[i] != netatom[NetWMFullscreen]) na[j++] = atoms[i]; XChangeProperty(d, c->win, netatom[NetWMState], XA_ATOM, 32, PropModeReplace, (unsigned char*)na, j); free(na); } else { XDeleteProperty(d, c->win, netatom[NetWMState]); } } XFree(p); } else { XDeleteProperty(d, c->win, netatom[NetWMState]); } c->isfullscreen = 0; c->isfloating = c->oldstate; c->bw = c->oldbw; rcl(c, c->oldx, c->oldy, c->oldw, c->oldh); ar(); } } static void setlt(const A *arg) { if (arg->v == lt[li]) return; li ^= 1; lt[li] = (const L*)arg->v; if (s) ar(); } static void setmf(const A *arg) { float f; if (!arg || !lt[li]->ar) return; f = (arg->f < 1.0f) ? mf + arg->f : arg->f - 1.0f; if (f < 0.05f || f > 0.95f) return; mf = f; ar(); } static void setup(void) { static char *wmnames[] = { "WM_PROTOCOLS", "WM_DELETE_WINDOW", "WM_STATE", "WM_TAKE_FOCUS" }; static char *netnames[] = { "_NET_WM_STATE", "_NET_WM_STATE_FULLSCREEN", "_NET_ACTIVE_WINDOW", "_NET_WM_WINDOW_TYPE", "_NET_WM_WINDOW_TYPE_DIALOG", "_NET_CLIENT_LIST" }; static char *auxnames[] = { "_NET_SUPPORTING_WM_CHECK", "_NET_WM_NAME", "UTF8_STRING" }; struct sigaction sa = {0}; XSetWindowAttributes wa; XColor xc, exact; Colormap cmap; Atom aux[3]; sa.sa_handler = SIG_IGN; sa.sa_flags = SA_RESTART; sigemptyset(&sa.sa_mask); sigaction(SIGCHLD, &sa, NULL); screen = DefaultScreen(d); sw = DisplayWidth(d, screen); sh = DisplayHeight(d, screen); r = RootWindow(d, screen); wx = wy = 0; ww = sw; wh = sh; cmap = DefaultColormap(d, screen); if (!XAllocNamedColor(d, cmap, colnb, &xc, &exact)) die("nwm: cannot allocate color"); nborder = xc.pixel; if (!XAllocNamedColor(d, cmap, colsb, &xc, &exact)) die("nwm: cannot allocate color"); sborder = xc.pixel; if (!XAllocNamedColor(d, cmap, colub, &xc, &exact)) die("nwm: cannot allocate color"); uborder = xc.pixel; cursor[0] = XCreateFontCursor(d, XC_left_ptr); cursor[1] = XCreateFontCursor(d, XC_fleur); cursor[2] = XCreateFontCursor(d, XC_sizing); if (!cursor[0] || !cursor[1] || !cursor[2]) die("nwm: XCreateFontCursor"); if (!XInternAtoms(d, wmnames, WMLast, False, wmatom)) die("nwm: XInternAtoms"); if (!XInternAtoms(d, netnames, NetLast, False, netatom)) die("nwm: XInternAtoms"); if (!XInternAtoms(d, auxnames, 3, False, aux)) die("nwm: XInternAtoms"); wmcheck = XCreateSimpleWindow(d, r, 0, 0, 1, 1, 0, 0, 0); if (wmcheck == None) die("nwm: XCreateSimpleWindow"); XChangeProperty(d, wmcheck, aux[0], XA_WINDOW, 32, PropModeReplace, (unsigned char*)&wmcheck, 1); XChangeProperty(d, wmcheck, aux[1], aux[2], 8, PropModeReplace, (unsigned char*)"nwm", 3); XChangeProperty(d, r, aux[0], XA_WINDOW, 32, PropModeReplace, (unsigned char*)&wmcheck, 1); XDeleteProperty(d, r, netatom[NetClientList]); sg = li = 0; ts[0] = ts[1] = 1; mf = mfact; nm = nmaster; lt[0] = &layouts[0]; lt[1] = &layouts[1 % LEN(layouts)]; grabkeys(); wa.cursor = cursor[0]; wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask | ButtonPressMask|PointerMotionMask|EnterWindowMask | StructureNotifyMask|PropertyChangeMask; XChangeWindowAttributes(d, r, CWEventMask|CWCursor, &wa); fc(NULL); } static void seturg(C *c, int urg) { XWMHints *wh; c->isurgent = urg; XSetWindowBorder(d, c->win, urg ? uborder : (c == s ? sborder : nborder)); if (!(wh = XGetWMHints(d, c->win))) return; wh->flags = urg ? wh->flags | XUrgencyHint : wh->flags & ~XUrgencyHint; XSetWMHints(d, c->win, wh); XFree(wh); } static void shide(C *c) { for (; c; c = c->snext) { if (VIS(c)) { if ((!lt[li]->ar || c->isfloating) && !c->isfullscreen) rcl(c, c->x, c->y, c->w, c->h); } else { XMoveWindow(d, c->win, -(W(c) + sw), c->y); } } } static void spawn(const A *arg) { struct sigaction sa = {0}; pid_t pid = fork(); if (pid == -1) { fprintf(stderr, "nwm: fork\n"); return; } if (pid == 0) { sa.sa_handler = SIG_DFL; sigemptyset(&sa.sa_mask); sigaction(SIGCHLD, &sa, NULL); if (d) close(ConnectionNumber(d)); setsid(); execvp(((const char**)arg->v)[0], (char*const*)arg->v); die("nwm: execvp %s", ((const char**)arg->v)[0]); } } static void tag(const A *arg) { if (s && arg->ui & TM) { s->tags = arg->ui & TM; fc(NULL); ar(); } } static void tile(void) { C *c; unsigned int i, n, nmv, ns; int g = gappx, mw, mch, sch, y0; for (n = 0, c = nextt(cs); c; c = nextt(c->next), n++); if (!n) return; nmv = (unsigned)nm < n ? (unsigned)nm : n; ns = n > nmv ? n - nmv : 0; mw = nmv && ns ? (int)((ww - 3*g) * mf) : (nmv ? ww - 2*g : 0); mch = nmv ? MAX(1, (wh - (int)(nmv + 1) * g) / (int)nmv) : 0; sch = ns ? MAX(1, (wh - (int)(ns + 1) * g) / (int)ns) : 0; for (i = 0, c = nextt(cs); c; c = nextt(c->next), i++) { int bw2 = c->bw << 1; if (i < nmv) { y0 = wy + g + (int)i * (mch + g); rs(c, wx + g, y0, MAX(1, mw - bw2), MAX(1, mch - bw2), 0); } else { y0 = wy + g + (int)(i - nmv) * (sch + g); rs(c, wx + (nmv ? mw + 2*g : g), y0, MAX(1, (nmv ? ww - mw - 3*g : ww - 2*g) - bw2), MAX(1, sch - bw2), 0); } } } static void tglfl(const A *arg) { (void)arg; if (!s || s->isfullscreen) return; s->isfloating = !s->isfloating || s->isfixed; if (s->isfloating) rs(s, s->x, s->y, s->w, s->h, 0); ar(); } static void tglfs(const A *arg) { (void)arg; if (s) setfs(s, !s->isfullscreen); } static void tgltag(const A *arg) { unsigned int t; if (!s || !(t = s->tags ^ (arg->ui & TM))) return; s->tags = t; fc(NULL); ar(); } static void tglview(const A *arg) { unsigned int t = ts[sg] ^ (arg->ui & TM); if (t) { ts[sg] = t; fc(NULL); ar(); } } static void unfcs(C *c) { if (!c) return; grabbuttons(c, 0); XSetWindowBorder(d, c->win, c->isurgent ? uborder : nborder); } static void unmng(C *c, int destroyed) { XWindowChanges wc; detach(c); detachstack(c); if (!destroyed) { wc.border_width = c->oldbw; XGrabServer(d); XSetErrorHandler(xe0); XSelectInput(d, c->win, NoEventMask); XConfigureWindow(d, c->win, CWBorderWidth, &wc); XUngrabButton(d, AnyButton, AnyModifier, c->win); setcs(c, WithdrawnState); XSync(d, False); XSetErrorHandler(xerror); XUngrabServer(d); } free(c); fc(NULL); updcl(); ar(); } static void unmapnt(XEvent *e) { XUnmapEvent *ev = &e->xunmap; C *c; if ((c = wintoc(ev->window))) { if (ev->send_event) setcs(c, WithdrawnState); else unmng(c, 0); } } static void updcl(void) { C *c; XDeleteProperty(d, r, netatom[NetClientList]); for (c = cs; c; c = c->next) XChangeProperty(d, r, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, (unsigned char*)&c->win, 1); } static void updnm(void) { unsigned int i, j; KeyCode nlk; XModifierKeymap *mm = XGetModifierMapping(d); if (!mm) return; numlockmask = 0; nlk = XKeysymToKeycode(d, XK_Num_Lock); if (nlk) for (i = 0; i < 8; i++) for (j = 0; j < (unsigned)mm->max_keypermod; j++) if (mm->modifiermap[i * mm->max_keypermod + j] == nlk) { numlockmask = 1 << i; goto done; } done: XFreeModifiermap(mm); } static void updsz(C *c) { long ms; XSizeHints sz; if (!XGetWMNormalHints(d, 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 updtype(C *c) { int fmt, i, fs = 0; unsigned long n, rem; unsigned char *p = NULL; Atom type, *atoms; if (XGetWindowProperty(d, c->win, netatom[NetWMState], 0L, 1024L, False, XA_ATOM, &type, &fmt, &n, &rem, &p) == Success && p) { atoms = (Atom *)p; for (i = 0; i < (int)n; i++) if (atoms[i] == netatom[NetWMFullscreen]) { fs = 1; break; } XFree(p); } if (fs && !c->isfullscreen) setfs(c, 1); else if (!fs && c->isfullscreen) setfs(c, 0); if (getatom(c, netatom[NetWMWindowType]) == netatom[NetWMWindowTypeDialog]) c->isfloating = 1; } static void updwmh(C *c) { XWMHints *wh; if (!(wh = XGetWMHints(d, c->win))) return; if (c == s && wh->flags & XUrgencyHint) { wh->flags &= ~XUrgencyHint; XSetWMHints(d, c->win, wh); } else { c->isurgent = !!(wh->flags & XUrgencyHint); } c->neverfocus = !!(wh->flags & InputHint) && !wh->input; XFree(wh); } static void view(const A *arg) { if ((arg->ui & TM) == ts[sg]) return; if (!(arg->ui & TM) && ts[0] == ts[1]) return; sg ^= 1; if (arg->ui & TM) ts[sg] = arg->ui & TM; fc(NULL); ar(); } static C *wintoc(Window w) { C *c; for (c = cs; c; c = c->next) if (c->win == w) return c; return NULL; } static int xerror(Display *dpy, XErrorEvent *ee) { if (ee->error_code == BadWindow || (ee->request_code == XP_SetInputFocus && ee->error_code == BadMatch) || (ee->request_code == XP_ConfigureWindow && ee->error_code == BadMatch) || (ee->request_code == XP_GrabButton && ee->error_code == BadAccess) || (ee->request_code == XP_GrabKey && ee->error_code == BadAccess)) return 0; fprintf(stderr, "nwm: error req=%d code=%d\n", ee->request_code, ee->error_code); return xerrorxlib(dpy, ee); } static int xe0(Display *dpy, XErrorEvent *e) { (void)dpy; (void)e; return 0; } static int xerrorstart(Display *dpy, XErrorEvent *e) { (void)dpy; (void)e; die("nwm: another wm is running"); return 0; } static void zoom(const A *arg) { (void)arg; C *c = s; if (!lt[li]->ar || !c || c->isfloating) return; if (c == nextt(cs) && !(c = nextt(c->next))) return; pop(c); } int main(int argc, char *argv[]) { if (argc == 2 && !strcmp("-v", argv[1])) die("nwm-1.5"); else if (argc != 1) die("usage: nwm [-v]"); if (!(d = XOpenDisplay(NULL))) die("nwm: cannot open display"); chkwm(); setup(); #ifdef __OpenBSD__ pledge("stdio rpath proc exec ps", NULL); #endif scan(); run(); cleanup(); XCloseDisplay(d); return 0; } nwm-1.5/nwm.h000066400000000000000000000037541520114072400131370ustar00rootroot00000000000000#define MODKEY Mod4Mask #define TAGKEYS(KEY,TAG) \ { MODKEY, KEY, view, {.ui = 1 << TAG} }, \ { MODKEY|ControlMask, KEY, tglview, {.ui = 1 << TAG} }, \ { MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \ { MODKEY|ControlMask|ShiftMask, KEY, tgltag, {.ui = 1 << TAG} }, static const unsigned int borderpx = 2; static const unsigned int gappx = 6; static const unsigned int snap = 16; static const int attachbottom = 0; static const int focusonopen = 1; static const float mfact = 0.5; static const int nmaster = 1; static const char colnb[] = "#1e1e1e"; static const char colsb[] = "#7c9e7e"; static const char colub[] = "#c47f50"; static const char *tags[] = { "1","2","3","4","5","6","7","8","9" }; static const L layouts[] = { { tile }, { NULL }, { monocle }, }; static const char *termcmd[] = { "st", NULL }; static const char *dmenucmd[] = { "dmenu_run", NULL }; static const K keys[] = { { MODKEY, XK_Return, spawn, {.v = termcmd } }, { MODKEY, XK_d, spawn, {.v = dmenucmd } }, { MODKEY, XK_q, killcl, {0} }, { MODKEY|ShiftMask, XK_e, quit, {0} }, { MODKEY, XK_j, fcs, {.i = +1} }, { MODKEY, XK_k, fcs, {.i = -1} }, { MODKEY, XK_i, incnm, {.i = +1} }, { MODKEY, XK_o, incnm, {.i = -1} }, { MODKEY, XK_h, setmf, {.f = -0.05} }, { MODKEY, XK_l, setmf, {.f = +0.05} }, { MODKEY, XK_space, zoom, {0} }, { MODKEY, XK_Tab, view, {0} }, { MODKEY, XK_t, setlt, {.v = &layouts[0]} }, { MODKEY, XK_f, setlt, {.v = &layouts[1]} }, { MODKEY, XK_m, setlt, {.v = &layouts[2]} }, { MODKEY, XK_F11, tglfs, {0} }, { MODKEY|ShiftMask, XK_space, tglfl, {0} }, { MODKEY, XK_0, view, {.ui = ~0} }, { MODKEY|ShiftMask, XK_0, tag, {.ui = ~0} }, 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, mv, {0} }, { ClkClientWin, MODKEY, Button2, tglfl, {0} }, { ClkClientWin, MODKEY, Button3, rz, {0} }, };