~kris/suckless

dwm

9ba6c8e751a1bf236b6e3f0839121d78a4a940f7 — Kris Yotam 6 months ago 5edcc10
Rebind reading mode to Super+Q, add directional tagmondir, enable selfrestart

- Super+Q: reading mode (RRR, 3 vertical panes)
- Super+Shift+Q: killclient (close window)
- Super+Arrow: send focused window to monitor in that direction
- Remove Super+Shift+Comma/Period tagmon bindings
- Remove Super+Shift+R reading mode binding
- Enable SELFRESTART_PATCH for in-place dwm reload
- Add .claude/CLAUDE.md with development rules
3 files changed, 221 insertions(+), 6 deletions(-)

A .claude/CLAUDE.md
M config.def.h
M patches.def.h
A .claude/CLAUDE.md => .claude/CLAUDE.md +164 -0
@@ 0,0 1,164 @@
# DWM - Development Rules

## Build System: dwm-flexipatch

This is a **flexipatch** build. Patches are toggled via `#define` flags, NOT by applying `.diff` files.

### Critical File Hierarchy

| File | Role | Committed? |
|------|------|------------|
| `config.def.h` | **Source of truth** for all configuration | YES |
| `patches.def.h` | **Source of truth** for patch enable/disable flags | YES |
| `config.h` | Local working copy (generated from `config.def.h`) | NO (gitignored) |
| `patches.h` | Local working copy (generated from `patches.def.h`) | NO (gitignored) |
| `config.mk` | Compiler/linker flags, library paths | YES |
| `dwm.c` | Core window manager (includes config.h at line 878) | YES |
| `patch/*.c` `patch/*.h` | Patch implementations and headers | YES |
| `patch/include.c` `patch/include.h` | Conditional patch includes (do NOT edit by hand) | YES |

### Rules for Editing Configuration

1. **ALWAYS edit `config.def.h`** for keybindings, layouts, rules, colors, and custom functions.
2. **ALWAYS edit `patches.def.h`** to enable/disable patches (`#define PATCH_NAME 0` or `1`).
3. **ALSO update `config.h` and `patches.h`** to match, so local builds work without regenerating.
4. **NEVER edit only `config.h` or `patches.h`** — those are gitignored and will be lost.
5. When `config.h` does not exist, `make` generates it by copying `config.def.h`. Same for `patches.h`.

### Build & Deploy Process

**Local build (this desktop):**
```bash
cd /home/krisyotam/dev/dwm
sudo make clean install
```

**Deploy to laptop (khr1st):**
```bash
# 1. Commit and push from desktop
cd /home/krisyotam/dev/dwm
git add config.def.h patches.def.h
git commit -m "description"
git push

# 2. SSH to laptop, pull, rebuild
ssh khr1st
cd ~/.local/src/dwm && git pull && sudo make clean install

# 3. Self-restart dwm (preserves all windows — no session kill)
# Trigger via sysact menu or a keybinding mapped to self_restart
```

**NEVER** tell the user to "restart dwm" or "log out and back in" without mentioning self_restart. The SELFRESTART_PATCH is enabled and does an in-place `execv()` that preserves all window positions.

---

## Code Style (Suckless C Conventions)

### Formatting
- **Indentation:** Tabs (width 8). Never spaces for indentation.
- **Brace style:** K&R — opening brace on same line, closing at column 0.
- **Line length:** ~80 characters preferred, not strictly enforced.
- **No trailing whitespace.**

### Naming
- `CamelCase` for types and structs: `Client`, `Monitor`, `Layout`, `Key`, `Button`
- `lowercase` or `lowercasemultiword` for functions: `focusmon`, `tagmon`, `sendmon`, `killclient`
- `UPPERCASE` for macros and constants: `MODKEY`, `NUMTAGS`, `CLEANMASK`, `SHCMD`
- Enum values: `SchemeNorm`, `SchemeSel`, `NetSupported`

### Functions
```c
static void
functionname(const Arg *arg)
{
	/* body indented with tabs */
}
```
- Return type on its own line.
- Function name on the next line.
- Opening brace on its own line (for function definitions only — NOT for if/for/while).
- Minimize variable declarations; declare at top of scope.

### Comments
- `/* C89-style block comments */` preferred in source.
- `// C99 inline comments` acceptable in config files for brief annotations.
- Preprocessor guards: `#endif // PATCH_NAME`

### Conditional Compilation (Patch Guards)
```c
#if SOME_PATCH
/* patch-specific code */
#endif // SOME_PATCH
```
- Always include the patch name in the `#endif` comment.
- Custom (non-patch) code added to `config.def.h` does NOT need guards.

### Example: Adding a Custom Function in config.def.h
Custom functions go **before** the `static const Key keys[]` array. They can reference any forward-declared function from `dwm.c` (`sendmon`, `focusmon`, `arrange`, `focus`, `selmon`, `mons`, etc.) because `config.h` is included after all declarations in `dwm.c`.

---

## Layout Array Reference

The layout array uses **flextile-deluxe**. Index matters for keybindings.

| Index | Symbol | Layout | Notes |
|-------|--------|--------|-------|
| 0 | `[]=` | Tile | Default |
| 1 | `><>` | Floating | |
| 2 | `[M]` | Monocle | |
| 3 | `\|\|\|` | Columns | |
| 4 | `>M>` | Floating master | |
| 5 | `[D]` | Deck | |
| 6 | `TTT` | Bottom stack | |
| 7 | `===` | Bottom stack horiz | |
| 8 | `\|M\|` | Centered master | |
| 9 | `-M-` | Centered master horiz | |
| 10 | `:::` | Gappless grid | |
| 11 | `[\\]` | Fibonacci dwindle | |
| 12 | `(@)` | Fibonacci spiral | |
| 13 | `[T]` | Tatami mats | |
| 14 | `RRR` | Reading mode (3 vertical panes) | Custom: nmaster=3 |

**When referencing layouts in keybindings, ALWAYS verify the index by counting from 0 in the `layouts[]` array.** Off-by-one errors here cause the wrong layout to activate with no obvious error.

---

## Currently Enabled Patches

Patches set to `1` in `patches.def.h`:

**Bar:** BAR_DWMBLOCKS, BAR_LTSYMBOL, BAR_STATUS, BAR_STATUSCMD, BAR_TAGS, BAR_WINTITLE, BAR_HIDEVACANTTAGS

**Core:** CFACTS, COOL_AUTOSTART, CYCLELAYOUTS, PERTAG, RESTARTSIG, SCRATCHPADS, SEAMLESS_RESTART, SELFRESTART, SHIFTTAG, SHIFTVIEW, STACKER, STICKY, SWALLOW, TOGGLEFULLSCREEN, VANITYGAPS, XRESOURCES

**Layouts:** BSTACK, CENTEREDMASTER, CENTEREDFLOATINGMASTER, COLUMNS, DECK, FIBONACCI_DWINDLE, FIBONACCI_SPIRAL, NROWGRID, TILE, MONOCLE

---

## Current Custom Keybindings

| Binding | Action |
|---------|--------|
| Super+Q | Reading mode (RRR layout) |
| Super+Shift+Q | Kill client (close window) |
| Super+Backspace | sysact (system actions menu) |
| Super+Left/Right/Up/Down | Send window to monitor in that direction |
| Super+Comma/Period | Focus previous/next monitor |
| Super+T | Tile layout |
| Super+F | Floating layout |

---

## Rules for Making Changes

1. **Read before writing.** Always read the relevant section of `config.def.h` before modifying it. Understand the surrounding `#if` guards.
2. **Count layout indices.** Never assume a layout index. Count from 0 in the `layouts[]` array every time.
3. **Check for keybinding conflicts.** Before adding a new binding, grep for the key symbol (e.g., `XK_w`) across `config.def.h` to find all uses and check which patches guard them.
4. **Test compilation.** Clang diagnostics on `config.h` standalone are ALWAYS false positives (missing types like `Arg`, `Client`, `Monitor`). The only valid test is `make` in the repo root.
5. **Keep both files in sync.** Every edit to `config.def.h` must also be applied to `config.h` (and vice versa for patches).
6. **Preserve removed bindings as comments.** When removing a keybinding, comment it out with a `// removed: reason` note rather than deleting the line, so the history is visible.
7. **Do not modify `dwm.c` unless absolutely necessary.** Configuration belongs in `config.def.h`. New functions go in `config.def.h` (before the keys array) or as a new file in `patch/`.
8. **Do not modify `patch/include.c` or `patch/include.h`** unless adding a completely new patch file to the `patch/` directory.
9. **Commit messages:** Imperative mood, concise. Example: `"Rebind reading mode to Super+Q, add directional tagmon"`. No "Co-Authored-By" lines.

M config.def.h => config.def.h +56 -5
@@ 1055,6 1055,53 @@ ResourcePref resources[] = {
};
#endif // XRESOURCES_PATCH

/* Send focused window to the monitor in the given direction (0=left, 1=right, 2=up, 3=down).
 * Does nothing if no monitor exists in that direction. */
static void
tagmondir(const Arg *arg)
{
	Client *c = selmon->sel;
	Monitor *m, *best = NULL;
	int dist, bestdist = 99999;

	if (!c || !mons->next)
		return;

	for (m = mons; m; m = m->next) {
		if (m == selmon)
			continue;
		switch (arg->i) {
		case 0: /* left */
			if (m->mx + m->mw <= selmon->mx) {
				dist = selmon->mx - m->mx;
				if (dist < bestdist) { bestdist = dist; best = m; }
			}
			break;
		case 1: /* right */
			if (m->mx >= selmon->mx + selmon->mw) {
				dist = m->mx - selmon->mx;
				if (dist < bestdist) { bestdist = dist; best = m; }
			}
			break;
		case 2: /* up */
			if (m->my + m->mh <= selmon->my) {
				dist = selmon->my - m->my;
				if (dist < bestdist) { bestdist = dist; best = m; }
			}
			break;
		case 3: /* down */
			if (m->my >= selmon->my + selmon->mh) {
				dist = m->my - selmon->my;
				if (dist < bestdist) { bestdist = dist; best = m; }
			}
			break;
		}
	}

	if (best)
		sendmon(c, best);
}

static const Key keys[] = {
	/* modifier                     key            function                argument */



@@ 1090,8 1137,8 @@ static const Key keys[] = {

	/* System */
	{ MODKEY,                       XK_BackSpace,  spawn,                  {.v = (const char*[]){ "sysact", NULL } } },
	{ MODKEY|ShiftMask,             XK_q,          spawn,                  {.v = (const char*[]){ "sysact", NULL } } },
	{ MODKEY,                       XK_q,          killclient,             {0} },
	{ MODKEY|ShiftMask,             XK_q,          killclient,             {0} }, // close window
	{ MODKEY,                       XK_q,          setlayout,              {.v = &layouts[14]} }, // reading mode (RRR)

	#if KEYMODES_PATCH
	{ MODKEY,                       XK_Escape,     setkeymode,             {.ui = COMMANDMODE} },


@@ 1275,7 1322,7 @@ static const Key keys[] = {
	#endif // XRDB_PATCH | XRESOURCES_PATCH
	{ MODKEY,                       XK_t,          setlayout,              {.v = &layouts[0]} },
	{ MODKEY,                       XK_f,          setlayout,              {.v = &layouts[1]} },
	{ MODKEY|ShiftMask,             XK_r,          setlayout,              {.v = &layouts[14]} }, // reading mode
	/* { MODKEY|ShiftMask,             XK_r,          setlayout,              {.v = &layouts[14]} }, // removed: reading mode moved to Super+q */
	#if COLUMNS_LAYOUT
	/* { MODKEY,                       XK_c,          setlayout,              {.v = &layouts[3]} }, // removed: Super+c now dcreate */
	#endif // COLUMNS_LAYOUT


@@ 1343,8 1390,12 @@ static const Key keys[] = {
	#endif // SCRATCHPAD_ALT_1_PATCH
	{ MODKEY,                       XK_comma,      focusmon,               {.i = -1 } },
	{ MODKEY,                       XK_period,     focusmon,               {.i = +1 } },
	{ MODKEY|ShiftMask,             XK_comma,      tagmon,                 {.i = -1 } },
	{ MODKEY|ShiftMask,             XK_period,     tagmon,                 {.i = +1 } },
	/* { MODKEY|ShiftMask,             XK_comma,      tagmon,                 {.i = -1 } }, // removed: send window via Super+Arrow instead */
	/* { MODKEY|ShiftMask,             XK_period,     tagmon,                 {.i = +1 } }, // removed: send window via Super+Arrow instead */
	{ MODKEY,                       XK_Left,       tagmondir,              {.i = 0 } }, // send window to left monitor
	{ MODKEY,                       XK_Right,      tagmondir,              {.i = 1 } }, // send window to right monitor
	{ MODKEY,                       XK_Up,         tagmondir,              {.i = 2 } }, // send window to upper monitor
	{ MODKEY,                       XK_Down,       tagmondir,              {.i = 3 } }, // send window to lower monitor
	#if FOCUSADJACENTTAG_PATCH
	{ MODKEY,                       XK_Left,       viewtoleft,             {0} }, // note keybinding conflict with focusdir
	{ MODKEY,                       XK_Right,      viewtoright,            {0} }, // note keybinding conflict with focusdir

M patches.def.h => patches.def.h +1 -1
@@ 1118,7 1118,7 @@
/* Allows restarting dwm without the dependency of an external script.
 * https://dwm.suckless.org/patches/selfrestart/
 */
#define SELFRESTART_PATCH 0
#define SELFRESTART_PATCH 1

/* Floating windows being sent to another monitor will be centered.
 * https://dwm.suckless.org/patches/sendmoncenter/