From b0f2a4055dcc715949fa6ccf77e159bb1b491a34 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 16:24:40 +0200 Subject: [PATCH 01/47] fixes Signed-off-by: h8d13 --- api/lib/api.o | Bin 15720 -> 15720 bytes api/lib/libragnar.a | Bin 16166 -> 16166 bytes src/config.c | 22 ++++++++++++++-------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/api/lib/api.o b/api/lib/api.o index cc54e974b77567ec6a2ce7b02de8922c826f022e..5b2768f76e6f6b11b693df2406545b7df54bb2f5 100644 GIT binary patch delta 2749 zcmZ{lYiv|S6vuZi+imyJc6TXzxxiw#X^W)T?k=#kK%$M37Mm#bOQVRgNFomlG`uQQ zYGVp*2uS@0gBC(-6F(G4yEYI+d=!mnqL3yHR=?0DCRRb|Ld!d>XYS734wpE|-Av{; z=lst(GiPUX=jhJf<5u^xE$=3>PcKchq*-VjT!~!!e0B+A)_P^%16;IEM`N}dt2}n4 z=OLJ*SZRESkmE^Aq?NN4l-m5tvHSYAjW!?q0nN62HU-_b9c(W0(o2*TcM)<;$A)yD z^QQ-i#2H2+SE~@R&ahYRU;18U>xI!H=tu3SaCsk=Uz>2vNS zJIQsbSg#_US>fL^@tD_i;k6^_qc+Q@T$s?krr*%Lev{>MZX-L%YbxN%2_s;(6;;_Y zlmkhd#%$jcr^XGN`pSKW>Jv%j%GS%gFbm_Uh}Wj}>BUGzyclkGjZ$+{KR3IGawvbB_Fx%Vi~W!jeatAo} zLkJbB>_FeL!s}^l8{$PO3!{3Ts+H+|Z?Pz3$92brVHKK)u9Yb7Ici~jY(b3ZEWRgqdCqJH?oEA(LR-^tauW2L{AB7R{R~EqKhZ|C+dY|!{O=J%Khf_5<+gGcTM#Fj?wPdZXMI9+h=#yI zmC+MQ@kdcj^DX+yK{PE?wRV%$5^?*x5F>g~P_8IXD- zD^O1KO+n3dXh9v(=ci73mg`W3PNFLXHP>M$28cFGw!98YL{IJqD^=N0^t<8 delta 2718 zcmZ{lZA?^06oz*$$j5%b?xI{SBJ5g0Tan#G5Tzz&(;#3>?WT!Isnw!2MoTQDM2I3S ziBi$ny1r@El+corG{%px#n{@a38A&^k7{UatLcxjNq^AN2&h!O&pqIp>garnREBI~+(@#1x!PXkbg=P4uxUgy{DNv?S)Uk8wV+mUSQ}sZptVC>%AV`7xJ#@i9rR zz<}GM?0STe__wTaLL+ZnH?6Si8M1YlO!l!%_^g{1-$QPRC)Olo=su#(40voBOGT+| zu~IpvyX~`S%7t;Ra9~_7>W(cp>-2-EIxg{ce9eEZ^3L#z`rxzs*x%S<_p;~ELi3Y| z*z=V)A0Xyfn|0#;EYAet89~tDW4BP}@G2Si;J0VstRvmlN*-VFehn=}AU-ph_m2y` zf)Pjgf`enT;w6fBpG?K5Nl90(jp22>M@hJ=N4=VoZmTChDXP_oT0X|pWN%|#YNm2@ z%Dy=@J?FxxVc$sm*eLd~qU~3nN>OY&+a1$(dbZPXOGgoL`>@8jSlM`wH$6F7r0K?h zvyiPonrorbJ*;oS2A9SLu+x>tHsYA8P-(r(`@fO0p+lABp1kWaY&k;ts#dDsYl}r8Kdw6}9M_|T=uV09OEp2e&qqBXM4JTV z6)%wJ=ZF%`;isDhkIb87_~ya2Ox0Ej%6(Od)+0!?NupkfwxW*cF^MjeXcsz(Uj855 zKJ*j)Me>z(v%m^e?HPW9Xc*nF^P*`KBS3V6MCEQ&p_=GcL1%X(Omv&ra&tF!p_gcr zpyqCzLX2pau~h0}H4u4b}9!9)WDYv49oSfa9GJ6ec#32IjS5fO@?4cBt66{UFzf;6w? zD_TR!(=}#^6WPXXw9h0eE1pLk(Tjqb6@N!3#rK01ajkxuS0X_3{d`3`AYL~{jsFP6 zkGPH2Em2wV8&nhR6V$9Yh%nJJ&Y4=|x5pRgCHk$P+*iKE3^Afft{Gpx*Js5I$%MC9 zWv_A@wiO|o*Xt``v=pmayUA;{IQ?CS5WOKNSCnsAHljpx-A^kv3EvX9R;XG?qH-TL zAxQKMLCt+=Lmknww3*0qAJ(Ij=w?C9eb|S7qOFoI@55?wC*KDvQQ4&)t)!gMMpmy> zwTQS5e~Q;X-%$RXLzt-Xy~^iHGz>Zqy+n=gRjr9$L5!&Jz3Mj6D7>p?zE`6rdK;lt b^u5B)Re!KPRF*cdIP{jTVR1d|`5nrCZyyh0 diff --git a/api/lib/libragnar.a b/api/lib/libragnar.a index 93fedb66b3b17d88d0d6ade0cc432f7b466ea819..553f33b5034384a571a385288043ae7e3423c4be 100644 GIT binary patch delta 2187 zcmZXUe@s+I5XWyNisGuMZPCRTODaKMsNrdzt6Ri@hNi}LYWBt*J zVA7}4c+V0eC18MlG^!-WRu;7>+nkW%{8C*jX`g)4qi|k{J=A)2~3A_CCt0V zpvPUn8^>VKoeK$NQAX2$$>f298q<=wW{99#J>lLuxj&C3>eFad~0|MFm9}(ZXn~FqT-AafzwZkZW6{`u6h0-n5!T zZdSw8Ipjn4-}vb^W~-knf^4>8F$!Ey@Du)S=$l>3I>F2_!UJN6xU85Z7%O6eP>X7Z zJEiK9@Qze3K{Kk?z2nv6QXPOERR7gd>TQ|ZE7hAYm}9U*2_<*X&bLm5jCmm*wZp*N zP~;{0(b{YrGezYgnObEBUtWl>vx{K11?mb6zS9mhnAvWJc(K9KptZz+KJ=HQiC7Fu z78v23;+EnPgUH118D0gk_JC;`;pXSmtmo7=s5K4#MVjb|U4>@+r=O^NhC+R2iR+8Y z>eC~Bwz!HF>T{_A-SZ5XRcP?lN~%jVb3=TMgOKZ?5v83D3O?=-n~Jo_bFh&868Wiv z05{Me;Fy!@pAxqemlYh82i@f)e2{$5oW~lN4VkRHJIWUird>%Tj~Th^qB^#V4qjpbJ&&_3ECW4!|u`t=DU4f_f9QQhdE2 cr}P#+4NBQ|&Vz8eGz7=Wav4wbl)cUW2l4wOumAu6 delta 2188 zcmZWpYfMx}6y6!#z5BjqjMfOIMo5Za z6QxCK(Vo;tB&aE|X?%egtt|qE*0w)d(#E!${!o+tu%!iUQBcwCnR91Tfk|d3_k7>^ z&Np*r=Xk^MhK}_%NAuYG<>Tu4@^RQ~E8#ypfxcihq$_s5YaB8?sZgeb`ROOnk7;UP`t;C(paCn2W7m^+6pg{Y^1 zpBp359b>eqlb)>f&qmFCT*Pfyz7Rh>R{Rkg=)XsHZQc<7a}@H`D9;_C$ZHrmI10zK zS^TY0xa?b$e&GHT6`NG)G;>nb5W5D3n#odOxoWT`cvsEiuiqExX*=&tNmpe05zu@F zy8;V+ncVgdm9O`O_?CO3JX;KXzQyTpk4#Co%k({FN}1xDX?Eotaz7V68?3`F*2yj| zY5qm*xDaYIgRO(3S{`38g4Qx&Z_o>mwH&_UUvah$ht%~$o4i} zHz6mG%~uRli((G5MYz>~!B#+L;DxlEcc)l&{=d^8?KGIj58ai;E`I?BPu4OR;ucN%_F(E{e~I&xsb~7Zl|ec2!2ZnKy*OmW5tl6Mx=gTiz9$ z(J=j4iedNx}oh3E;F ziTb6&CW&5x7DNX;6VZbby#>994yh(OD044M^g0aZ=&Yqni49x%*69$P9pZDXa5Fa) zSs-pF4vte)9+9cbtl*m!;_Iz~%(g&vT<7&xcm*>*#U(eyGi*YDWy1hM^KF8TyhL0N z*NeM~L!3n>&T+gIVl;ta=;6Ijp);OB+o8(PdAF(B4=wodGpX`4g{(d!?l6v|RrfUW zyGS9c6$)8h2^sl1U#*x{aY3tV>|}KVG$K@QC;B0~phwzeGm@fLrRYgJ3H*u+3LJ2d zgEnzpaU{t>+3gMoNq+Aj2iKr_j?Qm7NWkSJfuEe@;5rN-H0UJyY!`_Z%7!OJuS(HH zE@G{5k?1C7l|Ij1zkNP0mh&gp@5s{%iJXTs(Yf-R@vsYCacj+qPw7CuFgMl zljsFpP}J=qrvW&FP?m>8D=8LPD=%Nt=_=XDT9dWUL!zxvR;crK4~c%~funJqc}X)1 zjv!L(CCwU&MQXhhji$*aH=C?ayrg*=j3S+%_mbvsxS-K|l|+l71)-%XiEg7!i$5d5zjNDm|dl8TqM7mVaWEddV&JQ Date: Thu, 23 Jul 2026 16:42:50 +0200 Subject: [PATCH 02/47] chore: untrack build artifacts, ignore *.o *.a" Signed-off-by: h8d13 --- .gitignore | 3 ++- api/lib/api.o | Bin 15720 -> 0 bytes api/lib/libragnar.a | Bin 16166 -> 0 bytes 3 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 api/lib/api.o delete mode 100644 api/lib/libragnar.a diff --git a/.gitignore b/.gitignore index 6d1c2bb..226f4ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ -ragnar.o +*.o +*.a ragnarstart *.patch .cache/ diff --git a/api/lib/api.o b/api/lib/api.o deleted file mode 100644 index 5b2768f76e6f6b11b693df2406545b7df54bb2f5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15720 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 diff --git a/api/lib/libragnar.a b/api/lib/libragnar.a deleted file mode 100644 index 553f33b5034384a571a385288043ae7e3423c4be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16166 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 AyZ`_I From e9245a983c1b4e4ab29bb9ef6dde616096b4d7a1 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 16:44:52 +0200 Subject: [PATCH 03/47] add small tooltip about default terminal/menu Signed-off-by: h8d13 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a497814..0623617 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,8 @@ Ragnar uses `libconfig` to load an external configuration file: The configuration is loaded on startup and can be reloaded while the window manager is running, typically through a keybinding. +> By default it uses `alacritty` (Super+Return) and `bmenu` (Super+S), if you haven't edited these yet. + --- ## Code Structure From 4008cc0a192399a956f2c899953e699b41d070da Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 17:29:37 +0200 Subject: [PATCH 04/47] attempt to fix `PKGBUILD` * temp point to fork * ign `pkg/` staging dir Signed-off-by: h8d13 --- .gitignore | 1 + PKGBUILD | 36 ++++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 226f4ea..474f7d2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ ragnarstart *.patch .cache/ bin/ +pkg/ diff --git a/PKGBUILD b/PKGBUILD index 30a5ad4..7254e09 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,18 +1,32 @@ # Contributor: Luxzi pkgname=ragnarwm -_pkgname="Ragnar" +_pkgname="ragnar" pkgver='1.3' -pkgrel=1 +pkgrel=2 pkgdesc="Minimal, flexible & user-friendly X tiling window manager" arch=('x86_64') -url="https://github.com/cococry/Ragnar" +url="https://github.com/h8d13/ragnar" license=('GPL') groups=() -depends=('xcb' 'xcb-util' '') +depends=( + 'xcb-util' + 'xcb-proto' + 'xcb-util-keysyms' + 'xcb-util-cursor' + 'xcb-util-wm' + 'libconfig' + 'xorg-server' + 'xorg-xinit' + 'mesa' +) makedepends=('git' 'make' 'gcc') +optdepends=( + 'xorg-server-xephyr: nested test session via ragnarstart' + 'alacritty: default terminal keybind' +) provides=('ragnarwm') -source=("${_pkgname}::git+https://github.com/cococry/${_pkgname}.git") +source=("${_pkgname}::git+https://github.com/h8d13/ragnar.git") sha256sums=('SKIP') pkgver() { @@ -22,12 +36,14 @@ pkgver() { build() { cd $_pkgname - make ragnar + cd api && make || exit 1 + cd .. || exit 1 && make } package() { - cd $_pkgname - install -D -m777 ./ragnar "$pkgdir/usr/bin/ragnar" - install -D -m777 ./ragnar.desktop "$pkgdir/usr/share/applications/ragnar.desktop" - install -D -m777 ./ragnarstart "$pkgdir/usr/bin/ragnarstart" + cd $_pkgname + install -Dm755 bin/ragnar "$pkgdir/usr/bin/ragnar" + install -Dm755 ragnarstart "$pkgdir/usr/bin/ragnarstart" + install -Dm644 ragnar.desktop "$pkgdir/usr/share/xsessions/ragnar.desktop" + install -Dm644 cfg/ragnar.cfg "$pkgdir/etc/ragnarwm/ragnar.cfg" } From 72140e442cddfc109958c3e306754b87f0a390a2 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 17:51:52 +0200 Subject: [PATCH 05/47] feat(build): PKGBUILD + Makefiles * build: honor external CFLAGS, make api lib incremental * api target renamed to lib/libragnar.a, the file ar actually writes * pkg: bump pkgrel, disable debug split, drop ragnarstart install * pkg: add contributor line * chore: add git clean helper * chore: ignore makepkg source mirror and package tarballs Signed-off-by: h8d13 --- .gitignore | 2 ++ Makefile | 10 ++++++---- PKGBUILD | 6 ++++-- api/Makefile | 8 ++++---- clean.sh | 22 ++++++++++++++++++++++ 5 files changed, 38 insertions(+), 10 deletions(-) create mode 100755 clean.sh diff --git a/.gitignore b/.gitignore index 474f7d2..353234b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ ragnarstart .cache/ bin/ pkg/ +ragnar/ +**.tar.zst diff --git a/Makefile b/Makefile index aa0301d..01c1848 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ CC = cc -CFLAGS = -O3 -ffast-math -Wall -Wextra -pedantic -CFLAGS += -isystem api/include +CFLAGS ?= -O3 -ffast-math +ALL_CFLAGS = $(CFLAGS) -Wall -Wextra -pedantic -isystem api/include LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-composite -lxcb-ewmh -lX11 -lX11-xcb -lGL -lm -lconfig -lxcb-util SRC = ./src/*.c ./src/ipc/*.c BIN = ragnar -RAGNAR_API = api/lib/ragnar.a +RAGNAR_API = api/lib/libragnar.a PREFIX = /usr BINDIR = $(PREFIX)/bin @@ -15,8 +15,10 @@ BINDIR = $(PREFIX)/bin .PHONY: all all: $(RAGNAR_API) mkdir -p ./bin - $(CC) -o bin/$(BIN) $(CFLAGS) $(SRC) $(LDLIBS) + $(CC) -o bin/$(BIN) $(ALL_CFLAGS) $(SRC) $(LDLIBS) +# always recurse; sub-make decides if anything is stale +.PHONY: $(RAGNAR_API) $(RAGNAR_API): $(MAKE) -C api diff --git a/PKGBUILD b/PKGBUILD index 7254e09..5562907 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -1,9 +1,10 @@ # Contributor: Luxzi +# Contributor: h8d13 pkgname=ragnarwm _pkgname="ragnar" pkgver='1.3' -pkgrel=2 +pkgrel=3 pkgdesc="Minimal, flexible & user-friendly X tiling window manager" arch=('x86_64') url="https://github.com/h8d13/ragnar" @@ -26,6 +27,7 @@ optdepends=( 'alacritty: default terminal keybind' ) provides=('ragnarwm') +options=('!debug') source=("${_pkgname}::git+https://github.com/h8d13/ragnar.git") sha256sums=('SKIP') @@ -36,6 +38,7 @@ pkgver() { build() { cd $_pkgname + # mirrors what happens with install.sh cd api && make || exit 1 cd .. || exit 1 && make } @@ -43,7 +46,6 @@ build() { package() { cd $_pkgname install -Dm755 bin/ragnar "$pkgdir/usr/bin/ragnar" - install -Dm755 ragnarstart "$pkgdir/usr/bin/ragnarstart" install -Dm644 ragnar.desktop "$pkgdir/usr/share/xsessions/ragnar.desktop" install -Dm644 cfg/ragnar.cfg "$pkgdir/etc/ragnarwm/ragnar.cfg" } diff --git a/api/Makefile b/api/Makefile index 7136040..ac862d4 100644 --- a/api/Makefile +++ b/api/Makefile @@ -1,9 +1,9 @@ CC=gcc -CFLAGS= -all: lib/ragnar.a -lib/ragnar.a: lib/api.o +CFLAGS ?= +all: lib/libragnar.a +lib/libragnar.a: lib/api.o ar cr lib/libragnar.a lib/*.o -lib/api.o: lib +lib/api.o: api.c include/ragnar/api.h | lib ${CC} -c api.c -o lib/api.o ${CFLAGS} lib: diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..827101c --- /dev/null +++ b/clean.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# stupid helper to clean artifacts + +# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +# ⠀⠀⠀⠀⠀⠀⠀⠀⠐⢿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀ +# ⠀⠀⠀⠀⠀⢀⣴⣿⣦⡀⠙⠉⠻⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀ +# ⠀⠀⠀⢀⣴⣿⣿⣿⣿⣇⠀⠀⠀⢽⣿⣿⣿⣿⣦⡀⠀⠀⠀ +# ⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⡇⠀⣦⡀⠙⢿⢿⣿⣿⣿⣦⡀⠀ +# ⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⣿⣿⡆⠀⠀⠈⢻⣿⣿⣿⣆ +# ⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⣿⣿⣷⣄⣀⣠⣿⣿⣿⠟⠁ +# ⠀⠀⠙⢿⣿⣿⣿⣿⣿⡿⠃⠀⠻⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀ +# ⠀⠀⠀⠀⠙⢿⣿⣿⣿⣇⠀⠀⠀⣽⣿⣿⣿⠟⠁⠀⠀⠀⠀ +# ⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣷⣶⣾⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀ +# ⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀ +# ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + +git clean -Xnd + +read -rp "Proceed? [y/N] " reply +[[ "${reply}" == [yY] ]] || { echo "aborted"; exit 0; } + +git clean -Xfd From a120fd9b238cdf059793dd3e0e38d4ab2d2e9717 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 17:58:01 +0200 Subject: [PATCH 06/47] `_FORTIFY_SOURCE` warning + free Signed-off-by: h8d13 --- src/config.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/config.c b/src/config.c index f007df4..629570f 100644 --- a/src/config.c +++ b/src/config.c @@ -598,14 +598,18 @@ initconfig(state_t* s) { logmsg(s, LogLevelError, "cannot read config file because HOME is not defined."); } - char *cfg_path; + char *cfg_path = NULL; char const cfg_path_global[] = "/etc/ragnarwm/ragnar.cfg"; - asprintf(&cfg_path, "%s/.config/ragnarwm/ragnar.cfg", home); + // on failure asprintf leaves cfg_path undefined, reset to NULL + if (home && asprintf(&cfg_path, "%s/.config/ragnarwm/ragnar.cfg", home) < 0) { + cfg_path = NULL; + } - printf("ragnar: attempting to read config at %s or %s\n", cfg_path, cfg_path_global); + printf("ragnar: attempting to read config at %s or %s\n", + cfg_path ? cfg_path : "(no HOME)", cfg_path_global); if ( - !config_read_file(&cfghndl, cfg_path) + !(cfg_path && config_read_file(&cfghndl, cfg_path)) && !config_read_file(&cfghndl, cfg_path_global) ) { logmsg(s, LogLevelError, "%s:%d - %s\n", config_error_file(&cfghndl), @@ -616,7 +620,7 @@ initconfig(state_t* s) { destroyconfig(); terminate(s, EXIT_FAILURE); } - + free(cfg_path); } void From 8a066e88b212777e1721acd834066808b21c257c Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 17:59:26 +0200 Subject: [PATCH 07/47] bumpv now that no more errors Signed-off-by: h8d13 --- PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PKGBUILD b/PKGBUILD index 5562907..cb90aed 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -4,7 +4,7 @@ pkgname=ragnarwm _pkgname="ragnar" pkgver='1.3' -pkgrel=3 +pkgrel=4 pkgdesc="Minimal, flexible & user-friendly X tiling window manager" arch=('x86_64') url="https://github.com/h8d13/ragnar" From 4f9edd2dcdf77de130a0cd04675f4c23059b8a5e Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 18:02:16 +0200 Subject: [PATCH 08/47] chore(build): use one `url` source Signed-off-by: h8d13 --- PKGBUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PKGBUILD b/PKGBUILD index cb90aed..82f608e 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -28,7 +28,7 @@ optdepends=( ) provides=('ragnarwm') options=('!debug') -source=("${_pkgname}::git+https://github.com/h8d13/ragnar.git") +source=("${_pkgname}::git+${url}.git") sha256sums=('SKIP') pkgver() { From cc3e109b713853eb4a54b11801e0e5bd634781cb Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 18:07:12 +0200 Subject: [PATCH 09/47] indents + comment Signed-off-by: h8d13 --- PKGBUILD | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 82f608e..0daea63 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -32,20 +32,21 @@ source=("${_pkgname}::git+${url}.git") sha256sums=('SKIP') pkgver() { - cd $_pkgname - echo $pkgver + cd $_pkgname || exit 1 + echo $pkgver } build() { - cd $_pkgname - # mirrors what happens with install.sh - cd api && make || exit 1 - cd .. || exit 1 && make + cd $_pkgname || exit 1 + # mirrors what happens with install.sh + cd api && make || exit 1 + cd .. || exit 1 && make } package() { - cd $_pkgname - install -Dm755 bin/ragnar "$pkgdir/usr/bin/ragnar" - install -Dm644 ragnar.desktop "$pkgdir/usr/share/xsessions/ragnar.desktop" - install -Dm644 cfg/ragnar.cfg "$pkgdir/etc/ragnarwm/ragnar.cfg" + cd $_pkgname || exit 1 + install -Dm755 bin/ragnar "$pkgdir/usr/bin/ragnar" + install -Dm644 ragnar.desktop "$pkgdir/usr/share/xsessions/ragnar.desktop" + install -Dm644 cfg/ragnar.cfg "$pkgdir/etc/ragnarwm/ragnar.cfg" + # we only install sytemwide default cfg, user cfg has to be done manually } From fb37d9364cc3f6416af6fe4efe3cadf15b3fd5e7 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 18:19:59 +0200 Subject: [PATCH 10/47] docs Signed-off-by: h8d13 --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 0623617..bc1e934 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,10 @@ Ragnar uses `libconfig` to load an external configuration file: The configuration is loaded on startup and can be reloaded while the window manager is running, typically through a keybinding. +You can add to `.xinitrc`: + +`exec path/to/ragnar` Then simply; `startx` + > By default it uses `alacritty` (Super+Return) and `bmenu` (Super+S), if you haven't edited these yet. --- From d2e6ec1007669b159263d4b618d545e575f88396 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 19:06:46 +0200 Subject: [PATCH 11/47] add support for `_DOCK`, testing polybar Signed-off-by: h8d13 --- src/funcs.h | 4 +++- src/ragnar.c | 59 +++++++++++++++++++++++++++++++++++++++------------ src/structs.h | 5 +++-- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/funcs.h b/src/funcs.h index 6ddc9c4..50c1f58 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -504,7 +504,9 @@ void grabkeybinds(state_t* s); * */ void loaddefaultcursor(state_t* s); -bool iswindowpopup(state_t* s, xcb_window_t win); +bool haswindowtype(state_t* s, xcb_window_t win, xcb_atom_t type); +bool iswindowpopup(state_t* s, xcb_window_t win); +bool iswindowdock(state_t* s, xcb_window_t win); /** * @brief Takes in a size for a client window and adjusts it diff --git a/src/ragnar.c b/src/ragnar.c index 0cb3ebb..e65ae42 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -383,6 +383,12 @@ void managewins(state_t* s) { if (transient_for) continue; + // docks stay unmanaged; struts are gathered after the scan + if (iswindowdock(s, wins[i])) { + free(attr_reply); + continue; + } + if (wait_for_mapped(s, wins[i]) || getstate(s, wins[i]) == 3) { client_t* cl = makeclient(s, wins[i]); xcb_flush(s->con); @@ -2068,6 +2074,7 @@ setupatoms(state_t* s) { s->ewmh_atoms[EWMHwindowType] = getatom(s, "_NET_WM_WINDOW_TYPE"); s->ewmh_atoms[EWMHwindowTypeDialog] = getatom(s, "_NET_WM_WINDOW_TYPE_DIALOG"); s->ewmh_atoms[EWMHwindowTypePopup] = getatom(s, "_NET_WM_WINDOW_TYPE_POPUP_MENU"); + s->ewmh_atoms[EWMHwindowTypeDock] = getatom(s, "_NET_WM_WINDOW_TYPE_DOCK"); s->ewmh_atoms[EWMHclientList] = getatom(s, "_NET_CLIENT_LIST"); s->ewmh_atoms[EWMHcurrentDesktop] = getatom(s, "_NET_CURRENT_DESKTOP"); s->ewmh_atoms[EWMHnumberOfDesktops] = getatom(s, "_NET_NUMBER_OF_DESKTOPS"); @@ -2176,12 +2183,11 @@ loaddefaultcursor(state_t* s) { logmsg(s, LogLevelTrace, "loaded cursor image '%s'.", s->config.cursorimage); } -bool -iswindowpopup(state_t* s, xcb_window_t win) { - xcb_atom_t typeatom = s->ewmh_atoms[EWMHwindowType]; - xcb_atom_t popupatom = s->ewmh_atoms[EWMHwindowTypePopup]; +bool +haswindowtype(state_t* s, xcb_window_t win, xcb_atom_t type) { + xcb_atom_t typeatom = s->ewmh_atoms[EWMHwindowType]; - if (typeatom == XCB_ATOM_NONE || popupatom == XCB_ATOM_NONE) + if (typeatom == XCB_ATOM_NONE || type == XCB_ATOM_NONE) return false; xcb_get_property_cookie_t prop_cookie = xcb_get_property( @@ -2190,20 +2196,30 @@ iswindowpopup(state_t* s, xcb_window_t win) { if (!prop_reply) return false; - bool ispopup = false; + bool hastype = false; if (xcb_get_property_value_length(prop_reply) > 0) { xcb_atom_t* atoms = (xcb_atom_t*) xcb_get_property_value(prop_reply); int len = xcb_get_property_value_length(prop_reply) / sizeof(xcb_atom_t); for (int i = 0; i < len; ++i) { - if (atoms[i] == popupatom) { - ispopup = true; + if (atoms[i] == type) { + hastype = true; break; } } } free(prop_reply); - return ispopup; + return hastype; +} + +bool +iswindowpopup(state_t* s, xcb_window_t win) { + return haswindowtype(s, win, s->ewmh_atoms[EWMHwindowTypePopup]); +} + +bool +iswindowdock(state_t* s, xcb_window_t win) { + return haswindowtype(s, win, s->ewmh_atoms[EWMHwindowTypeDock]); } /** @@ -2418,18 +2434,28 @@ evmaprequest(state_t* s, xcb_generic_event_t* ev) { return; } - if (wa_reply->override_redirect) { + bool override_redirect = wa_reply->override_redirect; + free(wa_reply); + if (override_redirect) { return; } - // Free the reply after checking - free(wa_reply); - // Don't handle already managed clients if (clientfromwin(s, map_ev->window) != NULL) { return; } + // Docks (status bars etc.) stay unmanaged: map them as-is and + // reserve the space their struts request + if (iswindowdock(s, map_ev->window)) { + xcb_map_window(s->con, map_ev->window); + s->nwinstruts = 0; + getwinstruts(s, s->root); + makelayout(s, s->monfocus); + xcb_flush(s->con); + return; + } + // Handle new client client_t* cl = makeclient(s, map_ev->window); @@ -2554,6 +2580,13 @@ void evunmapnotify(state_t* s, xcb_generic_event_t* ev) { // Retrieve the event xcb_unmap_notify_event_t* unmap_ev = (xcb_unmap_notify_event_t*)ev; + // A dock going away frees the space its struts reserved + if(iswindowdock(s, unmap_ev->window)) { + s->nwinstruts = 0; + getwinstruts(s, s->root); + makelayout(s, s->monfocus); + return; + } if(iswindowpopup(s, unmap_ev->window)) { logmsg(s, LogLevelTrace, "remoed popup window. ", unmap_ev->window); int32_t idx = -1; diff --git a/src/structs.h b/src/structs.h index 858e0a8..5d59efc 100644 --- a/src/structs.h +++ b/src/structs.h @@ -142,8 +142,9 @@ typedef enum { EWMHfullscreen, EWMHactiveWindow, EWMHwindowType, - EWMHwindowTypeDialog, - EWMHwindowTypePopup, + EWMHwindowTypeDialog, + EWMHwindowTypePopup, + EWMHwindowTypeDock, EWMHclientList, EWMHcurrentDesktop, EWMHnumberOfDesktops, From 96acbcd509a9c46800d5a70756e832a1ee5ff2d4 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 19:25:23 +0200 Subject: [PATCH 12/47] add `movefocus` Signed-off-by: h8d13 --- cfg/ragnar.cfg | 20 +++++++----- src/config.c | 4 +++ src/keycallbacks.h | 81 ++++++++++++++++++++++++++++++++++++++++------ src/structs.h | 4 +++ 4 files changed, 91 insertions(+), 18 deletions(-) diff --git a/cfg/ragnar.cfg b/cfg/ragnar.cfg index ccbd392..6bbf1b2 100644 --- a/cfg/ragnar.cfg +++ b/cfg/ragnar.cfg @@ -65,6 +65,10 @@ # - movefocusdown # - movefocusleft # - movefocusright +# - focusup +# - focusdown +# - focusleft +# - focusright # - cyclefocusmonitordown # - cyclefocusmonitorup # - togglescratchpad @@ -77,11 +81,11 @@ # Specifies the width of the border around client # windows -win_border_width = 0; -# Specifies the color of the border around client +win_border_width = 2; +# Specifies the color of the border around client # windows -win_border_color = 0xE6DFDC; -# Specifies the color of the border around +win_border_color = 0x3B3B3B; +# Specifies the color of the border around # selected/focused client windows win_border_color_selected = 0xE6DFDC; @@ -261,22 +265,22 @@ keybinds = ( { mod = "%mod_key | Shift"; key = "KeyW"; - do = "movefocusup"; + do = "focusup"; }, { mod = "%mod_key | Shift"; key = "KeyA"; - do = "movefocusleft"; + do = "focusleft"; }, { mod = "%mod_key | Shift"; key = "KeyS"; - do = "movefocusdown"; + do = "focusdown"; }, { mod = "%mod_key | Shift"; key = "KeyD"; - do = "movefocusright"; + do = "focusright"; }, { mod = "%mod_key"; diff --git a/src/config.c b/src/config.c index 629570f..2c3cf37 100644 --- a/src/config.c +++ b/src/config.c @@ -260,6 +260,10 @@ const key_cb_mapping_t keycbmappings[] = { {"movefocusdown", movefocusdown}, {"movefocusleft", movefocusleft}, {"movefocusright", movefocusright}, + {"focusup", focusup}, + {"focusdown", focusdown}, + {"focusleft", focusleft}, + {"focusright", focusright}, {"cyclefocusmonitordown", cyclefocusmonitordown}, {"cyclefocusmonitorup", cyclefocusmonitorup}, {"togglescratchpad", togglescratchpad}, diff --git a/src/keycallbacks.h b/src/keycallbacks.h index e00d189..2b6e114 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -3,6 +3,7 @@ #include "funcs.h" #include "structs.h" +#include #include #include #include @@ -691,9 +692,9 @@ inline void decgapsizelayout(state_t* s, passthrough_data_t data) { * @param s The window manager's state * @param data The data to use for the function (unused here) */ -inline void movefocusup(state_t* s, passthrough_data_t data) { +inline void movefocusup(state_t* s, passthrough_data_t data) { (void)data; - if(!s->focus->floating) return; + if(!s->focus || !s->focus->floating) return; v2_t pos = s->focus->area.pos; v2_t dest = (v2_t){pos.x, @@ -711,9 +712,9 @@ inline void movefocusup(state_t* s, passthrough_data_t data) { * @param s The window manager's state * @param data The data to use for the function (unused here) */ -inline void movefocusdown(state_t* s, passthrough_data_t data) { +inline void movefocusdown(state_t* s, passthrough_data_t data) { (void)data; - if(!s->focus->floating) return; + if(!s->focus || !s->focus->floating) return; v2_t pos = s->focus->area.pos; v2_t dest = (v2_t){pos.x, @@ -731,9 +732,9 @@ inline void movefocusdown(state_t* s, passthrough_data_t data) { * @param s The window manager's state * @param data The data to use for the function (unused here) */ -inline void movefocusleft(state_t* s, passthrough_data_t data) { +inline void movefocusleft(state_t* s, passthrough_data_t data) { (void)data; - if(!s->focus->floating) return; + if(!s->focus || !s->focus->floating) return; v2_t pos = s->focus->area.pos; v2_t dest = (v2_t){MIN(MAX(pos.x - s->config.keywinmove_step, s->monfocus->area.pos.x), @@ -750,18 +751,78 @@ inline void movefocusleft(state_t* s, passthrough_data_t data) { * @param s The window manager's state * @param data The data to use for the function (unused here) */ -inline void movefocusright(state_t* s, passthrough_data_t data) { +inline void movefocusright(state_t* s, passthrough_data_t data) { (void)data; - if(!s->focus->floating) return; + if(!s->focus || !s->focus->floating) return; v2_t pos = s->focus->area.pos; - v2_t dest = (v2_t){MIN(MAX(pos.x + s->config.keywinmove_step, s->monfocus->area.pos.x), - s->monfocus->area.pos.x + s->monfocus->area.size.x + v2_t dest = (v2_t){MIN(MAX(pos.x + s->config.keywinmove_step, s->monfocus->area.pos.x), + s->monfocus->area.pos.x + s->monfocus->area.size.x - s->focus->area.size.x), pos.y}; moveclient(s, s->focus, dest, true); s->ignore_enter_layout = true; } +/** + * @brief Focuses the client geometrically nearest to the focused + * one in the given direction on the current desktop. Candidates + * behind the focused client's center are skipped; among the rest, + * off-axis distance is penalized so straight neighbors win. + * + * @param s The window manager's state + * @param dir Unit direction to search in + */ +inline void focusdir(state_t* s, v2_t dir) { + if(!s->focus || !s->monfocus) return; + + v2_t fc = (v2_t){ + s->focus->area.pos.x + s->focus->area.size.x / 2, + s->focus->area.pos.y + s->focus->area.size.y / 2}; + + client_t* best = NULL; + float bestscore = 0; + for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + if(cl == s->focus) continue; + if(cl->desktop != mondesktop(s, cl->mon)->idx) continue; + + v2_t cc = (v2_t){ + cl->area.pos.x + cl->area.size.x / 2, + cl->area.pos.y + cl->area.size.y / 2}; + float along = (cc.x - fc.x) * dir.x + (cc.y - fc.y) * dir.y; + if(along <= 0) continue; + float ortho = fabsf((cc.x - fc.x) * dir.y) + fabsf((cc.y - fc.y) * dir.x); + float score = along + 2 * ortho; + if(!best || score < bestscore) { + best = cl; + bestscore = score; + } + } + if(!best) return; + focusclient(s, best, false); + raiseclient(s, best); + s->ignore_enter_layout = true; +} + +inline void focusup(state_t* s, passthrough_data_t data) { + (void)data; + focusdir(s, (v2_t){0, -1}); +} + +inline void focusdown(state_t* s, passthrough_data_t data) { + (void)data; + focusdir(s, (v2_t){0, 1}); +} + +inline void focusleft(state_t* s, passthrough_data_t data) { + (void)data; + focusdir(s, (v2_t){-1, 0}); +} + +inline void focusright(state_t* s, passthrough_data_t data) { + (void)data; + focusdir(s, (v2_t){1, 0}); +} + /** diff --git a/src/structs.h b/src/structs.h index 5d59efc..07a8300 100644 --- a/src/structs.h +++ b/src/structs.h @@ -50,6 +50,10 @@ void movefocusup(state_t* s, passthrough_data_t data); void movefocusdown(state_t* s, passthrough_data_t data); void movefocusleft(state_t* s, passthrough_data_t data); void movefocusright(state_t* s, passthrough_data_t data); +void focusup(state_t* s, passthrough_data_t data); +void focusdown(state_t* s, passthrough_data_t data); +void focusleft(state_t* s, passthrough_data_t data); +void focusright(state_t* s, passthrough_data_t data); void cyclefocusmonitordown(state_t* s, passthrough_data_t data); void cyclefocusmonitorup(state_t* s, passthrough_data_t data); void togglescratchpad(state_t* s, passthrough_data_t data); From 6400f158453db31dde3e9244faf10c4c0fd00877 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 19:34:08 +0200 Subject: [PATCH 13/47] partial revert, only focus pointer if it had event Signed-off-by: h8d13 --- cfg/ragnar.cfg | 12 +++------ src/config.c | 4 --- src/keycallbacks.h | 61 ---------------------------------------------- src/ragnar.c | 15 +++++------- src/structs.h | 4 --- 5 files changed, 10 insertions(+), 86 deletions(-) diff --git a/cfg/ragnar.cfg b/cfg/ragnar.cfg index 6bbf1b2..4222eae 100644 --- a/cfg/ragnar.cfg +++ b/cfg/ragnar.cfg @@ -65,10 +65,6 @@ # - movefocusdown # - movefocusleft # - movefocusright -# - focusup -# - focusdown -# - focusleft -# - focusright # - cyclefocusmonitordown # - cyclefocusmonitorup # - togglescratchpad @@ -265,22 +261,22 @@ keybinds = ( { mod = "%mod_key | Shift"; key = "KeyW"; - do = "focusup"; + do = "movefocusup"; }, { mod = "%mod_key | Shift"; key = "KeyA"; - do = "focusleft"; + do = "movefocusleft"; }, { mod = "%mod_key | Shift"; key = "KeyS"; - do = "focusdown"; + do = "movefocusdown"; }, { mod = "%mod_key | Shift"; key = "KeyD"; - do = "focusright"; + do = "movefocusright"; }, { mod = "%mod_key"; diff --git a/src/config.c b/src/config.c index 2c3cf37..629570f 100644 --- a/src/config.c +++ b/src/config.c @@ -260,10 +260,6 @@ const key_cb_mapping_t keycbmappings[] = { {"movefocusdown", movefocusdown}, {"movefocusleft", movefocusleft}, {"movefocusright", movefocusright}, - {"focusup", focusup}, - {"focusdown", focusdown}, - {"focusleft", focusleft}, - {"focusright", focusright}, {"cyclefocusmonitordown", cyclefocusmonitordown}, {"cyclefocusmonitorup", cyclefocusmonitorup}, {"togglescratchpad", togglescratchpad}, diff --git a/src/keycallbacks.h b/src/keycallbacks.h index 2b6e114..b6e846f 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -3,7 +3,6 @@ #include "funcs.h" #include "structs.h" -#include #include #include #include @@ -763,66 +762,6 @@ inline void movefocusright(state_t* s, passthrough_data_t data) { s->ignore_enter_layout = true; } -/** - * @brief Focuses the client geometrically nearest to the focused - * one in the given direction on the current desktop. Candidates - * behind the focused client's center are skipped; among the rest, - * off-axis distance is penalized so straight neighbors win. - * - * @param s The window manager's state - * @param dir Unit direction to search in - */ -inline void focusdir(state_t* s, v2_t dir) { - if(!s->focus || !s->monfocus) return; - - v2_t fc = (v2_t){ - s->focus->area.pos.x + s->focus->area.size.x / 2, - s->focus->area.pos.y + s->focus->area.size.y / 2}; - - client_t* best = NULL; - float bestscore = 0; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { - if(cl == s->focus) continue; - if(cl->desktop != mondesktop(s, cl->mon)->idx) continue; - - v2_t cc = (v2_t){ - cl->area.pos.x + cl->area.size.x / 2, - cl->area.pos.y + cl->area.size.y / 2}; - float along = (cc.x - fc.x) * dir.x + (cc.y - fc.y) * dir.y; - if(along <= 0) continue; - float ortho = fabsf((cc.x - fc.x) * dir.y) + fabsf((cc.y - fc.y) * dir.x); - float score = along + 2 * ortho; - if(!best || score < bestscore) { - best = cl; - bestscore = score; - } - } - if(!best) return; - focusclient(s, best, false); - raiseclient(s, best); - s->ignore_enter_layout = true; -} - -inline void focusup(state_t* s, passthrough_data_t data) { - (void)data; - focusdir(s, (v2_t){0, -1}); -} - -inline void focusdown(state_t* s, passthrough_data_t data) { - (void)data; - focusdir(s, (v2_t){0, 1}); -} - -inline void focusleft(state_t* s, passthrough_data_t data) { - (void)data; - focusdir(s, (v2_t){-1, 0}); -} - -inline void focusright(state_t* s, passthrough_data_t data) { - (void)data; - focusdir(s, (v2_t){1, 0}); -} - /** diff --git a/src/ragnar.c b/src/ragnar.c index e65ae42..2c882db 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -1566,21 +1566,18 @@ switchmonitordesktop(state_t* s, int32_t desktop) { logmsg(s, LogLevelTrace, "Switched virtual desktop on monitor %i to %i", s->monfocus->idx, desktop); - s->ignore_enter_layout = false; + // Swallow the enter events generated by hiding/showing clients; + // the pointer only decides focus again once it actually moves + // (reset in evmotionnotify) + s->ignore_enter_layout = true; - // Retrieving cursor position - bool cursor_success; - v2_t cursor = cursorpos(s, &cursor_success); - if(!cursor_success) return; - - // Focusing the client on the other desktop that is hovered + // Focus the first client on the switched-to desktop for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { - if(pointinarea(cursor, cl->area)) { + if(cl->desktop == mondesktop(s, s->monfocus)->idx) { focusclient(s, cl, false); break; } } - } /** diff --git a/src/structs.h b/src/structs.h index 07a8300..5d59efc 100644 --- a/src/structs.h +++ b/src/structs.h @@ -50,10 +50,6 @@ void movefocusup(state_t* s, passthrough_data_t data); void movefocusdown(state_t* s, passthrough_data_t data); void movefocusleft(state_t* s, passthrough_data_t data); void movefocusright(state_t* s, passthrough_data_t data); -void focusup(state_t* s, passthrough_data_t data); -void focusdown(state_t* s, passthrough_data_t data); -void focusleft(state_t* s, passthrough_data_t data); -void focusright(state_t* s, passthrough_data_t data); void cyclefocusmonitordown(state_t* s, passthrough_data_t data); void cyclefocusmonitorup(state_t* s, passthrough_data_t data); void togglescratchpad(state_t* s, passthrough_data_t data); From b65224542c816873fc78611f0f997784426276b4 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 19:43:23 +0200 Subject: [PATCH 14/47] hide cursor when no movement has been registered yet. Signed-off-by: h8d13 --- Makefile | 2 +- src/funcs.h | 1 + src/ragnar.c | 31 +++++++++++++++++++++++++++++++ src/structs.h | 3 +++ 4 files changed, 36 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 01c1848..140c0a8 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC = cc CFLAGS ?= -O3 -ffast-math ALL_CFLAGS = $(CFLAGS) -Wall -Wextra -pedantic -isystem api/include -LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-composite -lxcb-ewmh -lX11 -lX11-xcb -lGL -lm -lconfig -lxcb-util +LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-composite -lxcb-ewmh -lxcb-xfixes -lX11 -lX11-xcb -lGL -lm -lconfig -lxcb-util SRC = ./src/*.c ./src/ipc/*.c BIN = ragnar diff --git a/src/funcs.h b/src/funcs.h index 50c1f58..66e7996 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -504,6 +504,7 @@ void grabkeybinds(state_t* s); * */ void loaddefaultcursor(state_t* s); +void setcursorhidden(state_t* s, bool hidden); bool haswindowtype(state_t* s, xcb_window_t win, xcb_atom_t type); bool iswindowpopup(state_t* s, xcb_window_t win); bool iswindowdock(state_t* s, xcb_window_t win); diff --git a/src/ragnar.c b/src/ragnar.c index 2c882db..6e368f7 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -2178,6 +2179,34 @@ loaddefaultcursor(state_t* s) { xcb_cursor_context_free(context); logmsg(s, LogLevelTrace, "loaded cursor image '%s'.", s->config.cursorimage); + + // XFixes lets us hide the cursor until the pointer actually moves; + // the version handshake is required before any other xfixes request + xcb_xfixes_query_version_reply_t* xfixes_reply = xcb_xfixes_query_version_reply( + s->con, xcb_xfixes_query_version(s->con, XCB_XFIXES_MAJOR_VERSION, + XCB_XFIXES_MINOR_VERSION), NULL); + s->xfixes_ok = xfixes_reply != NULL; + free(xfixes_reply); + if(!s->xfixes_ok) { + logmsg(s, LogLevelError, "XFixes unavailable, cursor auto-hide disabled."); + } + setcursorhidden(s, true); +} + +/** + * @brief Hides or shows the cursor (no-op without XFixes). The cursor + * is hidden on keybind use and shown again on real pointer motion. + * */ +void +setcursorhidden(state_t* s, bool hidden) { + if(!s->xfixes_ok || s->cursorhidden == hidden) return; + if(hidden) { + xcb_xfixes_hide_cursor(s->con, s->root); + } else { + xcb_xfixes_show_cursor(s->con, s->root); + } + s->cursorhidden = hidden; + xcb_flush(s->con); } bool @@ -2714,6 +2743,7 @@ evkeypress(state_t* s, xcb_generic_event_t* ev) { // If it was pressed, call the callback of the keybind if ((keysym == s->config.keybinds[i].key) && (e->state == s->config.keybinds[i].modmask)) { if(s->config.keybinds[i].cb) { + setcursorhidden(s, true); s->config.keybinds[i].cb(s, s->config.keybinds[i].data); } } @@ -2832,6 +2862,7 @@ evmotionnotify(state_t* s, xcb_generic_event_t* ev) { } s->lastmotiontime = curtime; s->ignore_enter_layout = false; + setcursorhidden(s, false); if (motion_ev->event == s->root) { monitor_t* mon = cursormon(s); diff --git a/src/structs.h b/src/structs.h index 5d59efc..8e4d0ee 100644 --- a/src/structs.h +++ b/src/structs.h @@ -521,6 +521,9 @@ struct state_t { bool ignore_enter_layout; + bool xfixes_ok; + bool cursorhidden; + client_t* focus; popup_list_t popups; From a89e28e500be2799422db04beae5ef0bc0eea81b Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:06:54 +0200 Subject: [PATCH 15/47] single monitor wrapping Signed-off-by: h8d13 --- src/keycallbacks.h | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/keycallbacks.h b/src/keycallbacks.h index b6e846f..bf66a3f 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -793,6 +793,14 @@ inline void cyclefocusmonitordown(state_t* s, passthrough_data_t data) { if(!prevmon) return; + // single monitor: wrap-around lands on the same monitor; move the + // client one slot towards the master instead (symmetric to the + // in-layout move, unlike the head-insert the monitor switch does) + if(prevmon == s->focus->mon) { + cycleuplayout(s, data); + return; + } + area_t afocusmon = s->focus->mon->area; bool fs = s->focus->fullscreen; @@ -872,10 +880,18 @@ inline void cyclefocusmonitorup(state_t* s, passthrough_data_t data) { monitor_t* nextmon = s->focus->mon->next; if(!nextmon) { - nextmon = s->monitors; + nextmon = s->monitors; } if(!nextmon) return; + // single monitor: wrap-around lands on the same monitor; move the + // client one slot away from the master instead (symmetric to the + // in-layout move, unlike the head-insert the monitor switch does) + if(nextmon == s->focus->mon) { + cycledownlayout(s, data); + return; + } + area_t afocusmon = s->focus->mon->area; bool fs = s->focus->fullscreen; From 48238009aa5bb1fe4d00c811f52d959e4dee02da Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:11:58 +0200 Subject: [PATCH 16/47] when moving a window out of current desktop, refocus master Signed-off-by: h8d13 --- src/ragnar.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/ragnar.c b/src/ragnar.c index 6e368f7..072f85a 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -1504,6 +1504,17 @@ switchclientdesktop(state_t* s, client_t* cl, int32_t desktop) { } hideclient(s, cl); makelayout(s, s->monfocus); + + // Sent-away client took focus with it; hand it to the first client + // (master slot) still on the visible desktop + if(!s->focus) { + for(client_t* it = s->monfocus->clients; it != NULL; it = it->next) { + if(it->desktop == mondesktop(s, s->monfocus)->idx) { + focusclient(s, it, false); + break; + } + } + } } /** From 9e7248259d1bebbd9bc6fa910de990c1f7a8c859 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:15:10 +0200 Subject: [PATCH 17/47] newly spawned window should have focus Signed-off-by: h8d13 --- src/ragnar.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ragnar.c b/src/ragnar.c index 072f85a..007be78 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -2525,6 +2525,10 @@ evmaprequest(state_t* s, xcb_generic_event_t* ev) { s->mapping_scratchpad_index = -1; } + // Newly spawned windows take focus (and the active border) right + // away instead of waiting for the pointer to wander into them + focusclient(s, cl, true); + xcb_flush(s->con); } void From 68194bacfcfcc61c6dd992ec16c87157122543f3 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:17:43 +0200 Subject: [PATCH 18/47] continue aprent Signed-off-by: h8d13 --- src/ragnar.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ragnar.c b/src/ragnar.c index 007be78..88763a5 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -2526,7 +2526,10 @@ evmaprequest(state_t* s, xcb_generic_event_t* ev) { } // Newly spawned windows take focus (and the active border) right - // away instead of waiting for the pointer to wander into them + // away instead of waiting for the pointer to wander into them. + // The relayout shifts windows under the stationary pointer; swallow + // the resulting enter events so they can't steal the focus back. + s->ignore_enter_layout = true; focusclient(s, cl, true); xcb_flush(s->con); From d988872514d29821bfc3c099765fed3dc73ed152 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:21:10 +0200 Subject: [PATCH 19/47] continue(4823800): refocus on close Signed-off-by: h8d13 --- src/ragnar.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/ragnar.c b/src/ragnar.c index 88763a5..3ef854c 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -3308,12 +3308,26 @@ releaseclient(state_t* s, xcb_window_t win) { * associated with the window */ while(cl) { if(cl->win == win) { - /* Setting the pointer to previous client to the next client + /* Setting the pointer to previous client to the next client * after the client we want to release, effectivly removing it - * from our list of clients*/ + * from our list of clients*/ *prev = cl->next; + bool wasfocus = (cl == s->focus); + if(wasfocus) { + s->focus = NULL; + } // Freeing memory allocated for client free(cl); + // Released client held focus; hand it to the first client + // (master slot) still on the visible desktop + if(wasfocus && s->monfocus) { + for(client_t* it = s->monfocus->clients; it != NULL; it = it->next) { + if(it->desktop == mondesktop(s, s->monfocus)->idx) { + focusclient(s, it, false); + break; + } + } + } return; } // Advancing the client From a707e6c4ff407fb9873caeb2d324409ea110ff28 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:30:06 +0200 Subject: [PATCH 20/47] publish newdesktop on move, not on switch Signed-off-by: h8d13 --- src/funcs.h | 1 + src/keycallbacks.h | 4 ++-- src/ragnar.c | 21 +++++++++++++++++---- src/structs.h | 1 + 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/funcs.h b/src/funcs.h index 66e7996..4e23e26 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -472,6 +472,7 @@ void uploaddesktopnames(state_t* s, monitor_t* mon); * and sends them to EWMH * @param s The window manager's state */ +void setclientdesktop(state_t* s, client_t* cl, uint32_t desktop); void updateewmhdesktops(state_t* s, monitor_t* mon); /** diff --git a/src/keycallbacks.h b/src/keycallbacks.h index bf66a3f..e4a7a3b 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -842,7 +842,7 @@ inline void cyclefocusmonitordown(state_t* s, passthrough_data_t data) { s->focus->mon = prevmon; updateewmhdesktops(s, s->focus->mon); s->monfocus = s->focus->mon; - s->focus->desktop = mondesktop(s, s->focus->mon)->idx; + setclientdesktop(s, s->focus, mondesktop(s, s->focus->mon)->idx); } // Resizing @@ -933,7 +933,7 @@ inline void cyclefocusmonitorup(state_t* s, passthrough_data_t data) { s->focus->mon = nextmon; updateewmhdesktops(s, s->focus->mon); s->monfocus = s->focus->mon; - s->focus->desktop = mondesktop(s, s->focus->mon)->idx; + setclientdesktop(s, s->focus, mondesktop(s, s->focus->mon)->idx); } // Resizing { diff --git a/src/ragnar.c b/src/ragnar.c index 3ef854c..40c9cec 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -555,8 +555,8 @@ makeclient(state_t* s, xcb_window_t win) { updateclienthints(s, cl); // Set client's monitor - cl->mon = clmon; - cl->desktop = mondesktop(s, s->monfocus)->idx; + cl->mon = clmon; + setclientdesktop(s, cl, mondesktop(s, s->monfocus)->idx); logmsg(s, LogLevelTrace,"Added client on desktop %i", cl->desktop); // Update the EWMH client list @@ -821,7 +821,7 @@ moveclient(state_t* s, client_t* cl, v2_t pos, bool manage_mons) { updateewmhdesktops(s, cl->mon); } s->monfocus = cl->mon; - cl->desktop = mondesktop(s, cl->mon)->idx; + setclientdesktop(s, cl, mondesktop(s, cl->mon)->idx); } updateedgewindows(s, cl); } @@ -1498,13 +1498,17 @@ switchclientdesktop(state_t* s, client_t* cl, int32_t desktop) { } free(names); - cl->desktop = desktop; + setclientdesktop(s, cl, desktop); if(cl == s->focus) { unfocusclient(s, cl); } hideclient(s, cl); makelayout(s, s->monfocus); + // Publish desktop count/names right away (the target desktop may + // have just been created); bars otherwise lag until a view switch + updateewmhdesktops(s, s->monfocus); + // Sent-away client took focus with it; hand it to the first client // (master slot) still on the visible desktop if(!s->focus) { @@ -2021,6 +2025,14 @@ uploaddesktopnames(state_t* s, monitor_t* mon) { } void +setclientdesktop(state_t* s, client_t* cl, uint32_t desktop) { + cl->desktop = desktop; + // publish per-window desktop so bars can show occupancy + xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, cl->win, + s->ewmh_atoms[EWMHdesktop], XCB_ATOM_CARDINAL, 32, 1, &desktop); +} + +void updateewmhdesktops(state_t* s, monitor_t* mon) { s->monfocus = mon; uint32_t desktopcount = 0; @@ -2088,6 +2100,7 @@ setupatoms(state_t* s) { s->ewmh_atoms[EWMHcurrentDesktop] = getatom(s, "_NET_CURRENT_DESKTOP"); s->ewmh_atoms[EWMHnumberOfDesktops] = getatom(s, "_NET_NUMBER_OF_DESKTOPS"); s->ewmh_atoms[EWMHdesktopNames] = getatom(s, "_NET_DESKTOP_NAMES"); + s->ewmh_atoms[EWMHdesktop] = getatom(s, "_NET_WM_DESKTOP"); xcb_atom_t utf8str = getatom(s, "UTF8_STRING"); diff --git a/src/structs.h b/src/structs.h index 8e4d0ee..75d29b9 100644 --- a/src/structs.h +++ b/src/structs.h @@ -149,6 +149,7 @@ typedef enum { EWMHcurrentDesktop, EWMHnumberOfDesktops, EWMHdesktopNames, + EWMHdesktop, EWMHcount } ewmh_atom_t; From fd637fae2be316e33887e5faceec032372013114 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:35:02 +0200 Subject: [PATCH 21/47] partial revert parent Signed-off-by: h8d13 --- src/funcs.h | 1 - src/keycallbacks.h | 4 ++-- src/ragnar.c | 19 +++++++------------ src/structs.h | 1 - 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/funcs.h b/src/funcs.h index 4e23e26..66e7996 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -472,7 +472,6 @@ void uploaddesktopnames(state_t* s, monitor_t* mon); * and sends them to EWMH * @param s The window manager's state */ -void setclientdesktop(state_t* s, client_t* cl, uint32_t desktop); void updateewmhdesktops(state_t* s, monitor_t* mon); /** diff --git a/src/keycallbacks.h b/src/keycallbacks.h index e4a7a3b..bf66a3f 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -842,7 +842,7 @@ inline void cyclefocusmonitordown(state_t* s, passthrough_data_t data) { s->focus->mon = prevmon; updateewmhdesktops(s, s->focus->mon); s->monfocus = s->focus->mon; - setclientdesktop(s, s->focus, mondesktop(s, s->focus->mon)->idx); + s->focus->desktop = mondesktop(s, s->focus->mon)->idx; } // Resizing @@ -933,7 +933,7 @@ inline void cyclefocusmonitorup(state_t* s, passthrough_data_t data) { s->focus->mon = nextmon; updateewmhdesktops(s, s->focus->mon); s->monfocus = s->focus->mon; - setclientdesktop(s, s->focus, mondesktop(s, s->focus->mon)->idx); + s->focus->desktop = mondesktop(s, s->focus->mon)->idx; } // Resizing { diff --git a/src/ragnar.c b/src/ragnar.c index 40c9cec..7422c53 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -556,7 +556,7 @@ makeclient(state_t* s, xcb_window_t win) { // Set client's monitor cl->mon = clmon; - setclientdesktop(s, cl, mondesktop(s, s->monfocus)->idx); + cl->desktop = mondesktop(s, s->monfocus)->idx; logmsg(s, LogLevelTrace,"Added client on desktop %i", cl->desktop); // Update the EWMH client list @@ -821,7 +821,7 @@ moveclient(state_t* s, client_t* cl, v2_t pos, bool manage_mons) { updateewmhdesktops(s, cl->mon); } s->monfocus = cl->mon; - setclientdesktop(s, cl, mondesktop(s, cl->mon)->idx); + cl->desktop = mondesktop(s, cl->mon)->idx; } updateedgewindows(s, cl); } @@ -1498,13 +1498,17 @@ switchclientdesktop(state_t* s, client_t* cl, int32_t desktop) { } free(names); - setclientdesktop(s, cl, desktop); + cl->desktop = desktop; if(cl == s->focus) { unfocusclient(s, cl); } hideclient(s, cl); makelayout(s, s->monfocus); + // Sending a client somewhere makes that desktop active; only view + // switches set this otherwise, leaving the EWMH count stale + s->monfocus->activedesktops[desktop].init = true; + // Publish desktop count/names right away (the target desktop may // have just been created); bars otherwise lag until a view switch updateewmhdesktops(s, s->monfocus); @@ -2025,14 +2029,6 @@ uploaddesktopnames(state_t* s, monitor_t* mon) { } void -setclientdesktop(state_t* s, client_t* cl, uint32_t desktop) { - cl->desktop = desktop; - // publish per-window desktop so bars can show occupancy - xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, cl->win, - s->ewmh_atoms[EWMHdesktop], XCB_ATOM_CARDINAL, 32, 1, &desktop); -} - -void updateewmhdesktops(state_t* s, monitor_t* mon) { s->monfocus = mon; uint32_t desktopcount = 0; @@ -2100,7 +2096,6 @@ setupatoms(state_t* s) { s->ewmh_atoms[EWMHcurrentDesktop] = getatom(s, "_NET_CURRENT_DESKTOP"); s->ewmh_atoms[EWMHnumberOfDesktops] = getatom(s, "_NET_NUMBER_OF_DESKTOPS"); s->ewmh_atoms[EWMHdesktopNames] = getatom(s, "_NET_DESKTOP_NAMES"); - s->ewmh_atoms[EWMHdesktop] = getatom(s, "_NET_WM_DESKTOP"); xcb_atom_t utf8str = getatom(s, "UTF8_STRING"); diff --git a/src/structs.h b/src/structs.h index 75d29b9..8e4d0ee 100644 --- a/src/structs.h +++ b/src/structs.h @@ -149,7 +149,6 @@ typedef enum { EWMHcurrentDesktop, EWMHnumberOfDesktops, EWMHdesktopNames, - EWMHdesktop, EWMHcount } ewmh_atom_t; From fafdaaaacd19b94b56b48428e3a72bcf47652b02 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 20:44:32 +0200 Subject: [PATCH 22/47] fix default cmd for dmenu Signed-off-by: h8d13 --- cfg/ragnar.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/ragnar.cfg b/cfg/ragnar.cfg index 4222eae..553e3f5 100644 --- a/cfg/ragnar.cfg +++ b/cfg/ragnar.cfg @@ -506,7 +506,7 @@ keybinds = ( mod = "%mod_key"; key = "KeyS"; do = "runcmd"; - cmd = "dmenu &"; + cmd = "dmenu_run &"; }, { mod = "%mod_key"; From 15a88e79ca5024b5cfae5cb5c8d29a23aeb36cb5 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 21:27:17 +0200 Subject: [PATCH 23/47] idek Signed-off-by: h8d13 --- PKGBUILD | 1 + README.md | 2 ++ src/funcs.h | 10 ---------- src/keycallbacks.h | 2 +- src/ragnar.c | 37 ++----------------------------------- 5 files changed, 6 insertions(+), 46 deletions(-) diff --git a/PKGBUILD b/PKGBUILD index 0daea63..57070f2 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -25,6 +25,7 @@ makedepends=('git' 'make' 'gcc') optdepends=( 'xorg-server-xephyr: nested test session via ragnarstart' 'alacritty: default terminal keybind' + 'polybar: optional status/desktops bars' ) provides=('ragnarwm') options=('!debug') diff --git a/README.md b/README.md index bc1e934..c82d11a 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ You can add to `.xinitrc`: > By default it uses `alacritty` (Super+Return) and `bmenu` (Super+S), if you haven't edited these yet. +You can also for example add: `polybar &` before the `exec ragnar` line. + --- ## Code Structure diff --git a/src/funcs.h b/src/funcs.h index 66e7996..6003361 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -338,16 +338,6 @@ void setwintype(state_t* s, client_t* cl); */ void seturgent(state_t* s, client_t* cl, bool urgent); - -/** - * @brief Returns the next on-screen client after the - * given client. - * - * @param s The window manager's state - * @param skip_floating Whether or not to skip floating clients - */ -client_t* nextvisible(state_t* s, bool skip_floating); - /** * @brief Gets the value of a given property on a * window of a given client. diff --git a/src/keycallbacks.h b/src/keycallbacks.h index bf66a3f..2801481 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -261,7 +261,7 @@ inline void runcmd(state_t* s, passthrough_data_t data) { inline void addfocustolayout(state_t* s, passthrough_data_t data) { (void)data; - if(s->focus->is_scratchpad) return; + if(!s->focus || s->focus->is_scratchpad) return; s->focus->floating = false; diff --git a/src/ragnar.c b/src/ragnar.c index 7422c53..703f5fd 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -248,7 +248,7 @@ loop(state_t* s) { // Poll for events without blocking while ((ev = xcb_wait_for_event(s->con))) { uint8_t evcode = ev->response_type & ~0x80; - /* If the event we receive is listened for by our + /* If the event we receive is listened for by our * event listeners, call the callback for the event. */ if (evcode < ARRLEN(evhandlers) && evhandlers[evcode]) { evhandlers[evcode](s, ev); @@ -1349,40 +1349,7 @@ seturgent(state_t* s, client_t* cl, bool urgent) { } /** - * @brief Returns the next on-screen client after the - * focused client. - * - * @param s The window manager's state - * @param skip_floating Whether or not to skip floating clients - */ -client_t* -nextvisible(state_t* s, bool skip_floating) { - client_t* next = NULL; - // Find the next client on the current monitor & desktop - for(client_t* cl = s->focus->next; cl != NULL; cl = cl->next) { - bool checktiled = (skip_floating) ? !cl->floating : true; - if(checktiled && clientonscreen(s, cl, s->monfocus)) { - next = cl; - break; - } - } - - // If there is no next client, cycle back to the first client on the - // current monitor & desktop - if(!next) { - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { - bool checktiled = (skip_floating) ? !cl->floating : true; - if(checktiled) { - next = cl; - break; - } - } - } - return next; - } - -/** - * @brief Gets the value of a given property on a + * @brief Gets the value of a given property on a * window of a given client. * * @param s The window manager's state From f0720fff6e07e4c941711915f77b4bd4b9a265bb Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 21:44:20 +0200 Subject: [PATCH 24/47] add debug wrapper Signed-off-by: h8d13 --- .gitignore | 1 + debug.sh | 9 +++++++++ src/ragnar.c | 12 ++++++++++++ 3 files changed, 22 insertions(+) create mode 100755 debug.sh diff --git a/.gitignore b/.gitignore index 353234b..5071b1a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ bin/ pkg/ ragnar/ **.tar.zst +.gdb.log diff --git a/debug.sh b/debug.sh new file mode 100755 index 0000000..84cd837 --- /dev/null +++ b/debug.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# run ragnar under gdb; on crash the backtrace lands in .gdb.log +# next to this script. use `exec ~/ragnar/debug.sh` in .xinitrc. + +cd "$(dirname "$0")" || exit 1 + +make CFLAGS="-O0 -g" || exit 1 + +exec gdb -q -batch -ex run -ex 'bt full' --args ./bin/ragnar > .gdb.log 2>&1 diff --git a/src/ragnar.c b/src/ragnar.c index 703f5fd..31ad97a 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -3585,6 +3585,18 @@ updatemons(state_t* s) { } free(res_reply); + // No RandR outputs with a CRTC (nested Xephyr, some VMs): treat the + // whole screen as one monitor instead of crashing on mon == NULL + if(registered_count == 0) { + area_t screenarea = (area_t){ + .pos = (v2_t){0, 0}, + .size = (v2_t){s->screen->width_in_pixels, s->screen->height_in_pixels} + }; + addmon(s, screenarea, registered_count++); + logmsg(s, LogLevelError, + "no RandR monitors found, falling back to the X screen as one monitor."); + } + return registered_count; } From 3d040f38c35301138f673e98420f54c3ab3ad9e4 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 22:09:22 +0200 Subject: [PATCH 25/47] giveup Signed-off-by: h8d13 --- debug.sh | 9 --------- 1 file changed, 9 deletions(-) delete mode 100755 debug.sh diff --git a/debug.sh b/debug.sh deleted file mode 100755 index 84cd837..0000000 --- a/debug.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# run ragnar under gdb; on crash the backtrace lands in .gdb.log -# next to this script. use `exec ~/ragnar/debug.sh` in .xinitrc. - -cd "$(dirname "$0")" || exit 1 - -make CFLAGS="-O0 -g" || exit 1 - -exec gdb -q -batch -ex run -ex 'bt full' --args ./bin/ragnar > .gdb.log 2>&1 From 1f34581770c757de8b080bc61dfb4acd8075ee0d Mon Sep 17 00:00:00 2001 From: h8d13 Date: Thu, 23 Jul 2026 22:16:55 +0200 Subject: [PATCH 26/47] rem hardcoded string always logs to $HOME/.ragnarwm.log Signed-off-by: h8d13 --- cfg/ragnar.cfg | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cfg/ragnar.cfg b/cfg/ragnar.cfg index 553e3f5..fdca225 100644 --- a/cfg/ragnar.cfg +++ b/cfg/ragnar.cfg @@ -198,12 +198,11 @@ max_struts = 8; # use Vsync gl_vsync = false; -# Specifies the file where to log messages to. -# (Ignored if 'log_messages' or 'should_log_to_file' +# Messages are logged to ~/.ragnarwm.log +# (Ignored if 'log_messages' or 'should_log_to_file' # are disabled.) -log_file = "/home/cococry/ragnarwm.log"; -# Specifies the cursor image to use for the root window +# Specifies the cursor image to use for the root window cursor_image = "arrow"; # Specifies whether or not to log messages From 70982a870141cc07f681df79be7eea54c6aab093 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 13:28:57 +0200 Subject: [PATCH 27/47] make deps copy-pastable. Signed-off-by: h8d13 --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c82d11a..91b4579 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,14 @@ Installing Ragnar involves two steps. Install the following dependencies: ```console -xcb-util -xcb-proto -xcb-util-keysyms -xcb-util-cursor -xcb-util-wm -xorg-server -xorg-xinit -mesa +xcb-util \ +xcb-proto \ +xcb-util-keysyms \ +xcb-util-cursor \ +xcb-util-wm \ +xorg-server \ +xorg-xinit \ +mesa \ libconfig ``` From 8a2153a33a1f0afcd3bad4f8ee7d4d36ccfdca3b Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 13:47:16 +0200 Subject: [PATCH 28/47] minor readme corrections Signed-off-by: h8d13 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 91b4579..a1c2ed9 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ The IPC system communicates through a socket using binary data. This allows exte Ragnar uses `libconfig` to load an external configuration file: ```console -~/.config/ragnarwm.cfg +~/.config/ragnarwm/ragnar.cfg ``` The configuration is loaded on startup and can be reloaded while the window manager is running, typically through a keybinding. @@ -83,7 +83,7 @@ You can add to `.xinitrc`: `exec path/to/ragnar` Then simply; `startx` -> By default it uses `alacritty` (Super+Return) and `bmenu` (Super+S), if you haven't edited these yet. +> By default it uses `alacritty` (Super+Return) and `dmenu` (Super+S), if you haven't edited these yet. You can also for example add: `polybar &` before the `exec ragnar` line. From b836416f5346358633c5800d6de72f9c2ec6eaec Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 14:21:01 +0200 Subject: [PATCH 29/47] cfg: trailing whitespaces note about fonts in readme Signed-off-by: h8d13 --- README.md | 1 + cfg/ragnar.cfg | 84 +++++++++++++++++++++++++------------------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index a1c2ed9..1f7a966 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ You can add to `.xinitrc`: `exec path/to/ragnar` Then simply; `startx` > By default it uses `alacritty` (Super+Return) and `dmenu` (Super+S), if you haven't edited these yet. +> Both of these need fonts file; for instance `ttf-dejavu` You can also for example add: `polybar &` before the `exec ragnar` line. diff --git a/cfg/ragnar.cfg b/cfg/ragnar.cfg index fdca225..7482ee8 100644 --- a/cfg/ragnar.cfg +++ b/cfg/ragnar.cfg @@ -1,8 +1,8 @@ -# ██████ █████ ██████ ███ ██ █████ ██████ ██████ ██████ ███ ██ ███████ ██ ██████ -# ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ -# ██████ ███████ ██ ███ ██ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ █████ ██ ██ ███ -# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -# ██ ██ ██ ██ ██████ ██ ████ ██ ██ ██ ██ ██████ ██████ ██ ████ ██ ██ ██████ +# ██████ █████ ██████ ███ ██ █████ ██████ ██████ ██████ ███ ██ ███████ ██ ██████ +# ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ +# ██████ ███████ ██ ███ ██ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ █████ ██ ██ ███ +# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +# ██ ██ ██ ██ ██████ ██ ████ ██ ██ ██ ██ ██████ ██████ ██ ████ ██ ██ ██████ # Options: # ---------------------- @@ -70,12 +70,12 @@ # - togglescratchpad # # ---------------------- -# NOTE: For all key options, see: +# NOTE: For all key options, see: # https://github.com/cococry/ragnar/blob/main/src/structs.h#L89 # ---------------------- # -# Specifies the width of the border around client +# Specifies the width of the border around client # windows win_border_width = 2; # Specifies the color of the border around client @@ -85,21 +85,21 @@ win_border_color = 0x3B3B3B; # selected/focused client windows win_border_color_selected = 0xE6DFDC; -# Specifies the main modifier key that is +# Specifies the main modifier key that is # used to execute window manager shortcuts mod_key = "Super"; -# Specifies the modifier key that is used +# Specifies the modifier key that is used # to interact with clients windows win_mod = "Super"; -# Specfies the mouse button that needs to be +# Specfies the mouse button that needs to be # held in order to move client windows move_button = "LeftMouse"; -# Specfies the mouse button that needs to be +# Specfies the mouse button that needs to be # held in order to resize client windows resize_button = "RightMouse"; -# Specifies the desktop index that is initially +# Specifies the desktop index that is initially # selected on every monitor initial_desktop = 0; # Specfies the number of allocated virtual @@ -109,92 +109,92 @@ num_desktops = 9; # in order. desktop_names = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; -# Specifies whether or not server-side window +# Specifies whether or not server-side window # decorations should be enabled. use_decoration = false; -# Specifies whether or not server-side titlebars -# should be shown on startup. (Ignored when +# Specifies whether or not server-side titlebars +# should be shown on startup. (Ignored when # use_decoration is set to false.) show_titlebars_init = false; -# Specifies the height (in pixels) of the +# Specifies the height (in pixels) of the # titlebar of client windows. titlebar_height = 30 -# Specifies the color of titlebars of +# Specifies the color of titlebars of # client windows titlebar_color = 0xffffff; -# Specifies the color of the font that is used +# Specifies the color of the font that is used # across the window manager's UI font_color = 0xff0000ff; -# Specifies the path to the font file to use as +# Specifies the path to the font file to use as # the window manager's font font_path = "/usr/share/fonts/TTF/JetBrainsMonoNerdFont-Bold.ttf"; -# Specifies the area that the master window takes -# up in master-slave layouts initially. +# Specifies the area that the master window takes +# up in master-slave layouts initially. # (in 0.0-1.0 %) layout_master_area = 0.5; # Specifies the minimum area that master windows -# need to take up in master-slave layouts +# need to take up in master-slave layouts # (in 0.0-1.0 %) layout_master_area_min = 0.1; # Specifies the maximum a2rea that master windows -# can take up in master-slave layouts +# can take up in master-slave layouts # (in 0.0-1.0 %) layout_master_area_max = 0.9; -# Specifies the amount that the master area changes/steps +# Specifies the amount that the master area changes/steps # when it is decreased/increased. # (in 0.0-1.0 %) layout_master_area_step = 0.1; -# Specifies the amount that areas of windows +# Specifies the amount that areas of windows # within layouts change when they are increased/decreased # (in px) layout_size_step = 100.0; -# Specifies the minimum area that windows within -# layouts need to take up +# Specifies the minimum area that windows within +# layouts need to take up # (in px) layout_size_min = 150.0; -# Specifies the amount that windows are moved +# Specifies the amount that windows are moved # by when using 'move' shortcuts for floating windows # (in px) key_win_move_step = 100.0; -# Specifies the initial gap between windows +# Specifies the initial gap between windows # within layouts (in px) win_layout_gap = 5; # Specifies the maximum gap that windows within # layouts can have around each other (in px) win_layout_gap_max = 150; -# Specifies the amount that the gap between -# non-floating windows changes when it's +# Specifies the amount that the gap between +# non-floating windows changes when it's # decreased/increased (in px) # (in px) win_layout_gap_step = 5; -# Specifies the layout that is initially used +# Specifies the layout that is initially used # for every virtual desktop initial_layout = "LayoutTiledMaster"; # Advanced Configuration # -------------------------------- -# Specifies the framerate at which motion notify -# events are captured. This is used to streamline +# Specifies the framerate at which motion notify +# events are captured. This is used to streamline # performance. Especially on high polling rate mouses, -# lag can be very noticable when not throtteling motion +# lag can be very noticable when not throtteling motion # notify events. -motion_notify_debounce_fps = 60; +motion_notify_debounce_fps = 60; -# Specifies the maximum number of 'strut'-window that -# the window manager can capture. Struts are information -# about window positions and sizes that are used to correctly +# Specifies the maximum number of 'strut'-window that +# the window manager can capture. Struts are information +# about window positions and sizes that are used to correctly # establish window layouts with bars or other status windows. max_struts = 8; -# Specifies if decoration that is rendered with OpenGL should +# Specifies if decoration that is rendered with OpenGL should # use Vsync gl_vsync = false; @@ -213,7 +213,7 @@ log_messages = true; # to the log file should_log_to_file = true; -# Specifies the maximum number of scratchpads +# Specifies the maximum number of scratchpads # that can be allocated max_scratchpads = 10; @@ -449,7 +449,7 @@ keybinds = ( mod = "%mod_key"; key = "KeyJ"; do = "cycledownlayout"; - }, + }, { mod = "%mod_key"; key = "KeyK"; From 1cb1209a883fd20d03c609fd4320a8dea575207a Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 14:53:19 +0200 Subject: [PATCH 30/47] solidify logic Signed-off-by: h8d13 --- src/ragnar.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/ragnar.c b/src/ragnar.c index 31ad97a..3989e55 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -1577,7 +1577,7 @@ switchmonitordesktop(state_t* s, int32_t desktop) { uint32_t numinlayout(state_t* s, monitor_t* mon) { uint32_t nlayout = 0; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(clientonscreen(s, cl, mon) && !cl->floating) { nlayout++; } @@ -1607,7 +1607,7 @@ makelayout(state_t* s, monitor_t* mon) { if(curlayout == LayoutFloating) return; /* Make sure that there is always at least one slave window */ - uint32_t nlayout = numinlayout(s, s->monfocus); + uint32_t nlayout = numinlayout(s, mon); uint32_t deskidx = mondesktop(s, mon)->idx; while(nlayout - mon->layouts[deskidx].nmaster == 0 && nlayout != 1) { mon->layouts[deskidx].nmaster--; @@ -1642,7 +1642,7 @@ makelayout(state_t* s, monitor_t* mon) { */ void resetlayoutsizes(state_t* s, monitor_t* mon) { - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || !clientonscreen(s, cl, mon)) continue; @@ -1703,9 +1703,9 @@ tiledmaster(state_t* s, monitor_t* mon) { mon->layouts[deskidx].mastermaxed = false; uint32_t i = 0; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { - if(cl->floating || - cl->desktop != mondesktop(s, cl->mon)->idx || + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { + if(cl->floating || + cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; if(i >= nmaster) break; @@ -1719,9 +1719,9 @@ tiledmaster(state_t* s, monitor_t* mon) { i = 0; float lastadd = 0.0f; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || - cl->desktop != mondesktop(s, cl->mon)->idx || + cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; bool ismaster = (i < nmaster); @@ -1778,7 +1778,7 @@ verticalstripes(state_t* s, monitor_t* mon) { int32_t gapsize = mon->layouts[deskidx].gapsize; { - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; nwins++; @@ -1792,8 +1792,8 @@ verticalstripes(state_t* s, monitor_t* mon) { // Apply strut information to the layout for(uint32_t i = 0; i < s->nwinstruts; i++) { - bool onmonitor = - s->winstruts[i].startx >= mon->area.pos.x + bool onmonitor = + s->winstruts[i].startx >= mon->area.pos.x && s->winstruts[i].endx <= mon->area.pos.x + mon->area.size.x; if(!onmonitor) continue; @@ -1813,10 +1813,10 @@ verticalstripes(state_t* s, monitor_t* mon) { h -= s->winstruts[i].bottom; } } - + float lastadd = 0.0f; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; float winw = (float)w / nwins + cl->layoutsizeadd - lastadd; @@ -1849,7 +1849,7 @@ horizontalstripes(state_t* s, monitor_t* mon) { int32_t gapsize = mon->layouts[deskidx].gapsize; { - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; nwins++; @@ -1887,7 +1887,7 @@ horizontalstripes(state_t* s, monitor_t* mon) { float lastadd = 0.0f; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; float winh = (float)h / nwins + cl->layoutsizeadd - lastadd; @@ -2361,7 +2361,7 @@ enumartelayout(state_t* s, monitor_t* mon, uint32_t* nmaster, uint32_t* nslaves) } uint32_t i = 0; - for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx || cl->mon != mon) continue; if(i >= *nmaster) { @@ -2391,7 +2391,7 @@ isclientmaster(state_t* s, client_t* cl, monitor_t* mon) { uint32_t deskidx = mondesktop(s, mon)->idx; uint32_t nmaster = mon->layouts[deskidx].nmaster; - for(client_t* iter = s->monfocus->clients; iter != NULL; iter = iter->next) { + for(client_t* iter = mon->clients; iter != NULL; iter = iter->next) { if(iter->floating || iter->desktop != mondesktop(s, iter->mon)->idx || iter->mon != mon) continue; From 3942ed656e0a431b86c08f91a86bbf68f41c99fb Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 14:54:59 +0200 Subject: [PATCH 31/47] double free Signed-off-by: h8d13 --- src/ragnar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ragnar.c b/src/ragnar.c index 3989e55..8bfa586 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -288,10 +288,10 @@ terminate(state_t* s, int32_t exitcode) { } } + // s->con is owned by s->dsp (XGetXCBConnection); XCloseDisplay tears the + // xcb connection down with it, a second xcb_disconnect double-frees if (s->dsp != NULL) XCloseDisplay(s->dsp); - // Give up the X connection - xcb_disconnect(s->con); logmsg(s, LogLevelTrace, "terminated with exit code %i.", exitcode); From b9150c3db9aae8907e6d74678f0a650b62fb4425 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 15:58:17 +0200 Subject: [PATCH 32/47] pointer resizing; only active in floating mode. do not advertise a non-functional feature Signed-off-by: h8d13 --- CHEATSHEET_DEFAULTS.md | 1 + src/ragnar.c | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 CHEATSHEET_DEFAULTS.md diff --git a/CHEATSHEET_DEFAULTS.md b/CHEATSHEET_DEFAULTS.md new file mode 100644 index 0000000..7d025cc --- /dev/null +++ b/CHEATSHEET_DEFAULTS.md @@ -0,0 +1 @@ +`Super+Shift+R` : bound to `setfloatingmode` diff --git a/src/ragnar.c b/src/ragnar.c index 8bfa586..413216e 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -440,6 +440,13 @@ void managewins(state_t* s) { void updateedgewindows(state_t* s, client_t* cl) { if (!cl->edges) return; + // pointer resize handles are floating-window UX; tiled windows + // resize through layout keybinds + bool show = cl->floating && !cl->fullscreen; + if (cl->showedgewindows != show) + toggleedgewindows(s, cl, show); + if (!show) return; + int w = cl->area.size.x; int h = cl->area.size.y; int b = EDGE_WIDTH; From c73019f28213cc50d904ef28d03f884a4ab265b2 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 15:59:01 +0200 Subject: [PATCH 33/47] oops Signed-off-by: h8d13 --- CHEATSHEET_DEFAULTS.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CHEATSHEET_DEFAULTS.md diff --git a/CHEATSHEET_DEFAULTS.md b/CHEATSHEET_DEFAULTS.md deleted file mode 100644 index 7d025cc..0000000 --- a/CHEATSHEET_DEFAULTS.md +++ /dev/null @@ -1 +0,0 @@ -`Super+Shift+R` : bound to `setfloatingmode` From 212fbaee8d35e32754645c075f758c72e14cbe5d Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 16:04:44 +0200 Subject: [PATCH 34/47] continue paretn Signed-off-by: h8d13 --- src/keycallbacks.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/keycallbacks.h b/src/keycallbacks.h index 2801481..6f859a0 100644 --- a/src/keycallbacks.h +++ b/src/keycallbacks.h @@ -355,6 +355,9 @@ inline void setfloatingmode(state_t* s, passthrough_data_t data) { for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { if(clientonscreen(s, cl, s->monfocus)) { cl->floating = true; + // floating layout skips makelayout, so no geometry op will + // refresh the edge handles; do it here + updateedgewindows(s, cl); } } uint32_t deskidx = mondesktop(s, s->monfocus)->idx; From b8f8efa950a9a423eda0ae63dd71b5060b7460e1 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 16:18:08 +0200 Subject: [PATCH 35/47] rem some stuff; unpublish on last Signed-off-by: h8d13 --- src/funcs.h | 1 + src/ragnar.c | 63 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/funcs.h b/src/funcs.h index 6003361..f99aa06 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -471,6 +471,7 @@ void updateewmhdesktops(state_t* s, monitor_t* mon); * @param mon The monitor that the virtual desktop is on */ void createdesktop(state_t* s, uint32_t idx, monitor_t* mon); +void prunedesktops(state_t* s, monitor_t* mon); /** * @brief Initializes all important atoms for EWMH & diff --git a/src/ragnar.c b/src/ragnar.c index 413216e..60532ee 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -1497,6 +1497,9 @@ switchclientdesktop(state_t* s, client_t* cl, int32_t desktop) { } } } + + // IPC/EWMH moves can drain a background desktop + prunedesktops(s, s->monfocus); } /** @@ -1572,6 +1575,9 @@ switchmonitordesktop(state_t* s, int32_t desktop) { break; } } + + // Leaving an emptied desktop unpublishes it + prunedesktops(s, s->monfocus); } /** @@ -2021,9 +2027,43 @@ updateewmhdesktops(state_t* s, monitor_t* mon) { } } +/** + * @brief Unpublishes active desktops that hold no client anymore and + * notifies EWMH if any were removed. The currently viewed desktop and + * the default desktop stay published even when empty. + * + * @param s The window manager's state + * @param mon The monitor whose desktops are pruned + */ +void +prunedesktops(state_t* s, monitor_t* mon) { + desktop_t* curdesk = mondesktop(s, mon); + bool changed = false; + for(uint32_t i = 0; i < mon->desktopcount; i++) { + if(!mon->activedesktops[i].init) continue; + if(i == s->config.desktopinit) continue; + if(curdesk && i == curdesk->idx) continue; + + bool occupied = false; + for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { + if(cl->desktop == i) { + occupied = true; + break; + } + } + if(!occupied) { + mon->activedesktops[i].init = false; + changed = true; + } + } + if(changed) { + updateewmhdesktops(s, mon); + } +} + /** * @brief Creates a new virtual desktop and notifies EWMH about it. - * @param s The window manager's state + * @param s The window manager's state * @param idx The index of the virtual desktop * @param mon The monitor that the virtual desktop is on */ @@ -2632,6 +2672,8 @@ evunmapnotify(state_t* s, xcb_generic_event_t* ev) { cl->ignoreunmap = false; return; } + // cl is freed by releaseclient below; monitors outlive it + monitor_t* clmon = cl ? cl->mon : NULL; if(cl) { if(cl->is_scratchpad) { @@ -2652,6 +2694,11 @@ evunmapnotify(state_t* s, xcb_generic_event_t* ev) { // Re-establish the window layout makelayout(s, s->monfocus); + // Client may have been the last one on a background desktop + if(clmon) { + prunedesktops(s, clmon); + } + xcb_flush(s->con); } @@ -2673,8 +2720,10 @@ evdestroynotify(state_t* s, xcb_generic_event_t* ev) { if(cl->is_scratchpad) { removescratchpad(s, cl->scratchpad_index); } + monitor_t* clmon = cl->mon; unframeclient(s, cl); releaseclient(s, destroy_ev->window); + prunedesktops(s, clmon); } /** @@ -3592,18 +3641,6 @@ updatemons(state_t* s) { } free(res_reply); - // No RandR outputs with a CRTC (nested Xephyr, some VMs): treat the - // whole screen as one monitor instead of crashing on mon == NULL - if(registered_count == 0) { - area_t screenarea = (area_t){ - .pos = (v2_t){0, 0}, - .size = (v2_t){s->screen->width_in_pixels, s->screen->height_in_pixels} - }; - addmon(s, screenarea, registered_count++); - logmsg(s, LogLevelError, - "no RandR monitors found, falling back to the X screen as one monitor."); - } - return registered_count; } From 5d4c259dd07fd5cdb88d35566beed4931133ae05 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 16:21:12 +0200 Subject: [PATCH 36/47] fix paretn Signed-off-by: h8d13 --- src/ragnar.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/ragnar.c b/src/ragnar.c index 60532ee..977bdab 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -2022,8 +2022,17 @@ updateewmhdesktops(state_t* s, monitor_t* mon) { uploaddesktopnames(s, s->monfocus); desktop_t* desk = mondesktop(s, s->monfocus); if(desk) { + // _NET_DESKTOP_NAMES only lists published desktops, so the + // current desktop must be its position in that list; the raw + // idx drifts as soon as pruning leaves a gap + uint32_t ewmhidx = 0; + for(uint32_t i = 0; i < desk->idx && i < s->monfocus->desktopcount; i++) { + if(s->monfocus->activedesktops[i].init) { + ewmhidx++; + } + } xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, s->root, s->ewmh_atoms[EWMHcurrentDesktop], - XCB_ATOM_CARDINAL, 32, 1, &desk->idx); + XCB_ATOM_CARDINAL, 32, 1, &ewmhidx); } } From 0a859f40314bbb8daa82b92aa5353f2e79fe26c2 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 16:38:46 +0200 Subject: [PATCH 37/47] continue parent Signed-off-by: h8d13 --- src/ragnar.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ragnar.c b/src/ragnar.c index 977bdab..d132f4e 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -1526,9 +1526,9 @@ switchmonitordesktop(state_t* s, int32_t desktop) { } init_i++; } - // Notify EWMH for desktop change - xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, s->root, s->ewmh_atoms[EWMHcurrentDesktop], - XCB_ATOM_CARDINAL, 32, 1, &desktopidx); + // Names and count go out before current: pagers resolve the + // current index against names, stale ones render the wrong label + uploaddesktopnames(s, s->monfocus); uint32_t desktopcount = 0; for(uint32_t i = 0; i < s->monfocus->desktopcount; i++) { @@ -1538,7 +1538,10 @@ switchmonitordesktop(state_t* s, int32_t desktop) { } xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, s->root, s->ewmh_atoms[EWMHnumberOfDesktops], XCB_ATOM_CARDINAL, 32, 1, &desktopcount); - uploaddesktopnames(s, s->monfocus); + + // Notify EWMH for desktop change + xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, s->root, s->ewmh_atoms[EWMHcurrentDesktop], + XCB_ATOM_CARDINAL, 32, 1, &desktopidx); for (client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { @@ -2011,6 +2014,9 @@ uploaddesktopnames(state_t* s, monitor_t* mon) { void updateewmhdesktops(state_t* s, monitor_t* mon) { s->monfocus = mon; + // Names and count go out before current: pagers resolve the + // current index against names, stale ones render the wrong label + uploaddesktopnames(s, s->monfocus); uint32_t desktopcount = 0; for(uint32_t i = 0; i < s->monfocus->desktopcount; i++) { if(s->monfocus->activedesktops[i].init) { @@ -2019,7 +2025,6 @@ updateewmhdesktops(state_t* s, monitor_t* mon) { } xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, s->root, s->ewmh_atoms[EWMHnumberOfDesktops], XCB_ATOM_CARDINAL, 32, 1, &desktopcount); - uploaddesktopnames(s, s->monfocus); desktop_t* desk = mondesktop(s, s->monfocus); if(desk) { // _NET_DESKTOP_NAMES only lists published desktops, so the From afcb8389b5020410e5be0eb8d3119b2035728fc2 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 16:47:05 +0200 Subject: [PATCH 38/47] idek Signed-off-by: h8d13 --- src/ragnar.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/ragnar.c b/src/ragnar.c index d132f4e..d7feb06 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -1999,12 +1999,13 @@ uploaddesktopnames(state_t* s, monitor_t* mon) { xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(s->con)).data; xcb_window_t root_window = screen->root; - // Set the _NET_DESKTOP_NAMES property + // Set the _NET_DESKTOP_NAMES property; EWMH mandates UTF8_STRING, + // pagers reject a STRING-typed value and fall back to numbering xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, root_window, s->ewmh_atoms[EWMHdesktopNames], - XCB_ATOM_STRING, + getatom(s, "UTF8_STRING"), 8, total_length, data); From 609be2736ef8d2110a13be63860b0f7777683e1e Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 16:52:06 +0200 Subject: [PATCH 39/47] EWMH standard receive switch Signed-off-by: h8d13 --- src/ragnar.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/ragnar.c b/src/ragnar.c index d7feb06..1fb673d 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -3225,6 +3225,25 @@ evpropertynotify(state_t* s, xcb_generic_event_t* ev) { void evclientmessage(state_t* s, xcb_generic_event_t* ev) { xcb_client_message_event_t* msg_ev = (xcb_client_message_event_t*)ev; + + // Pagers request desktop switches with an index into the published + // (compressed) desktop list; map it back to the raw desktop index + if(msg_ev->type == s->ewmh_atoms[EWMHcurrentDesktop]) { + if(!s->monfocus) return; + uint32_t target = msg_ev->data.data32[0]; + uint32_t pos = 0; + for(uint32_t i = 0; i < s->monfocus->desktopcount; i++) { + if(!s->monfocus->activedesktops[i].init) continue; + if(pos == target) { + switchmonitordesktop(s, i); + xcb_flush(s->con); + break; + } + pos++; + } + return; + } + client_t* cl = clientfromwin(s, msg_ev->window); if(!cl) { From 55b6828f3d9c0ddbb316d1453e6e62b911858b87 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 17:12:08 +0200 Subject: [PATCH 40/47] nuke ragnarstart belongs in `.xinit` or /etc/environment or `.bashrc`/`.bash_profile` nuke xephyr optional dep. QEMU testing instead Signed-off-by: h8d13 --- .gitignore | 1 - Makefile | 2 -- PKGBUILD | 5 ++--- ragnarstart | 1 - src/ragnar.c | 3 --- 5 files changed, 2 insertions(+), 10 deletions(-) delete mode 100755 ragnarstart diff --git a/.gitignore b/.gitignore index 5071b1a..86d3f83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ *.o *.a -ragnarstart *.patch .cache/ bin/ diff --git a/Makefile b/Makefile index 140c0a8..cef3ed9 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,6 @@ config: .PHONY: install install: install -Dm755 bin/$(BIN) -t $(BINDIR) - install -Dm755 ragnarstart -t $(BINDIR) cp -f ragnar.desktop $(PREFIX)/share/xsessions/ .PHONY: clean @@ -50,4 +49,3 @@ clean: uninstall: $(RM) $(BINDIR)/ragnar $(RM) $(PREFIX)/share/xsessions/ragnar.desktop - $(RM) $(BINDIR)/ragnarstart diff --git a/PKGBUILD b/PKGBUILD index 57070f2..519c152 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -3,8 +3,8 @@ pkgname=ragnarwm _pkgname="ragnar" -pkgver='1.3' -pkgrel=4 +pkgver='2' +pkgrel=1 pkgdesc="Minimal, flexible & user-friendly X tiling window manager" arch=('x86_64') url="https://github.com/h8d13/ragnar" @@ -23,7 +23,6 @@ depends=( ) makedepends=('git' 'make' 'gcc') optdepends=( - 'xorg-server-xephyr: nested test session via ragnarstart' 'alacritty: default terminal keybind' 'polybar: optional status/desktops bars' ) diff --git a/ragnarstart b/ragnarstart deleted file mode 100755 index 8b13789..0000000 --- a/ragnarstart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/ragnar.c b/src/ragnar.c index 1fb673d..efe5140 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -149,9 +149,6 @@ setup(state_t* s) { XSetErrorHandler(xerror); XSync(s->dsp, False); } - // Run the startup script - runcmd(NULL, (passthrough_data_t){.cmd = "ragnarstart"}); - // Setting up xcb connection s->con = XGetXCBConnection(s->dsp); // Checking for errors From e2d8f665dafb61772bd2449f479cf588084caa13 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Fri, 24 Jul 2026 18:33:41 +0200 Subject: [PATCH 41/47] misc docs Signed-off-by: h8d13 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1f7a966..6d5be7f 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ Ragnar uses `libconfig` to load an external configuration file: ```console ~/.config/ragnarwm/ragnar.cfg +# or system-wide +/etc/ragnarwm/ragnar.cfg ``` The configuration is loaded on startup and can be reloaded while the window manager is running, typically through a keybinding. From 6e4b1e7d7c488cd17d92df8108e19f26c544ab86 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 25 Jul 2026 17:03:37 +0200 Subject: [PATCH 42/47] more pkging: * `make api` separate * always install `.cfg` to `/etc/` * PKGBUILD `backup=` for config migrations * `install.sh` should work regardless if you are `UID` 0 or 1000+ Signed-off-by: h8d13 --- Makefile | 44 ++++++++++++++++++++------------------------ PKGBUILD | 14 +++++++------- install.sh | 24 +++++++++++++++++++----- 3 files changed, 46 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index cef3ed9..0e08567 100644 --- a/Makefile +++ b/Makefile @@ -7,39 +7,34 @@ LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-composit SRC = ./src/*.c ./src/ipc/*.c BIN = ragnar -RAGNAR_API = api/lib/libragnar.a - PREFIX = /usr BINDIR = $(PREFIX)/bin +SYSCONFDIR = /etc .PHONY: all -all: $(RAGNAR_API) +all: mkdir -p ./bin $(CC) -o bin/$(BIN) $(ALL_CFLAGS) $(SRC) $(LDLIBS) -# always recurse; sub-make decides if anything is stale -.PHONY: $(RAGNAR_API) -$(RAGNAR_API): +# client-side IPC library. the WM links none of it, only the headers under +# api/include are needed to build. opt-in, for writing external clients. +.PHONY: api +api: $(MAKE) -C api +.PHONY: install-api +install-api: + $(MAKE) -C api install -DEST_DIR := $(HOME)/.config/ragnarwm -CONFIG_FILE := $(DEST_DIR)/ragnar.cfg - -.PHONY: config -config: - @if [ ! -f "$(CONFIG_FILE)" ]; then \ - echo "Config file does not exist. Copying default config..."; \ - mkdir -p "$(DEST_DIR)"; \ - cp -r "./cfg/ragnar.cfg" "$(CONFIG_FILE)"; \ - else \ - echo "Config file already exists. Skipping copy $(CONFIG_FILE)."; \ - fi - +# no user config target: ragnar reads $HOME/.config/ragnarwm/ragnar.cfg first +# and falls back to the copy installed below. an override is a plain cp, and +# writing $HOME from a root install lands in the wrong home. +# mirrors PKGBUILD logic, and is "standard practice" for config files. .PHONY: install -install: - install -Dm755 bin/$(BIN) -t $(BINDIR) - cp -f ragnar.desktop $(PREFIX)/share/xsessions/ +install: + install -Dm755 bin/$(BIN) -t $(DESTDIR)$(BINDIR) + install -Dm644 ragnar.desktop -t $(DESTDIR)$(PREFIX)/share/xsessions + install -Dm644 cfg/ragnar.cfg -t $(DESTDIR)$(SYSCONFDIR)/ragnarwm .PHONY: clean clean: @@ -47,5 +42,6 @@ clean: .PHONY: uninstall uninstall: - $(RM) $(BINDIR)/ragnar - $(RM) $(PREFIX)/share/xsessions/ragnar.desktop + $(RM) $(DESTDIR)$(BINDIR)/$(BIN) + $(RM) $(DESTDIR)$(PREFIX)/share/xsessions/ragnar.desktop + $(RM) -r $(DESTDIR)$(SYSCONFDIR)/ragnarwm diff --git a/PKGBUILD b/PKGBUILD index 519c152..e6e3c40 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -25,9 +25,11 @@ makedepends=('git' 'make' 'gcc') optdepends=( 'alacritty: default terminal keybind' 'polybar: optional status/desktops bars' + 'ttf-dejavu: fonts for both the above' ) provides=('ragnarwm') options=('!debug') +backup=('etc/ragnarwm/ragnar.cfg') source=("${_pkgname}::git+${url}.git") sha256sums=('SKIP') @@ -38,15 +40,13 @@ pkgver() { build() { cd $_pkgname || exit 1 - # mirrors what happens with install.sh - cd api && make || exit 1 - cd .. || exit 1 && make + make } package() { cd $_pkgname || exit 1 - install -Dm755 bin/ragnar "$pkgdir/usr/bin/ragnar" - install -Dm644 ragnar.desktop "$pkgdir/usr/share/xsessions/ragnar.desktop" - install -Dm644 cfg/ragnar.cfg "$pkgdir/etc/ragnarwm/ragnar.cfg" - # we only install sytemwide default cfg, user cfg has to be done manually + # single source of truth: same install target ./install.sh drives. + # only the systemwide default cfg is installed, user cfg is a manual cp. + # this is usually convention for most WM configs, template in /etc + make DESTDIR="$pkgdir" PREFIX=/usr install } diff --git a/install.sh b/install.sh index 1e3027c..023538e 100755 --- a/install.sh +++ b/install.sh @@ -1,7 +1,21 @@ -cd api -make && sudo make install -cd .. -make config -make && sudo make install +#!/usr/bin/env bash +# api/ only needs building when you iterate on the IPC client lib: +# make -C api && sudo make -C api install +set -e +# we also want this to work regardless if the user is root or noot +as_root() { + if [ "$(id -u)" -eq 0 ]; then + "$@" + elif esc=$(command -v sudo) || esc=$(command -v doas); then + "$esc" "$@" + else + echo "no sudo or doas, trying su" >&2 + su root -c "$*" + fi +} +make +as_root make install + +# we do not need the make config anymore; logic unified. echo "Successfully installed ragnarwm." From 7a06d2f3d634a3e5fc35b56d5b6bc3122093e1e5 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 25 Jul 2026 17:25:32 +0200 Subject: [PATCH 43/47] remove dead deps; respect host linking `--as-needed` Signed-off-by: h8d13 --- Makefile | 12 ++++++++---- PKGBUILD | 7 ++++--- README.md | 10 +++++----- src/ragnar.c | 9 --------- src/structs.h | 6 +----- 5 files changed, 18 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 0e08567..974bc2c 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ CC = cc -CFLAGS ?= -O3 -ffast-math -ALL_CFLAGS = $(CFLAGS) -Wall -Wextra -pedantic -isystem api/include +# ?= so an exported CFLAGS (makepkg, distro build) replaces this wholesale. +# geometry here is ratios and pixel rects cast back to ints, so -ffast-math +# bought nothing and -ffinite-math-only would let divisions by a zero-sized +# monitor fold away instead of showing up. +CFLAGS ?= -O2 +ALL_CFLAGS = $(CFLAGS) $(CPPFLAGS) -Wall -Wextra -pedantic -isystem api/include -LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-composite -lxcb-ewmh -lxcb-xfixes -lX11 -lX11-xcb -lGL -lm -lconfig -lxcb-util +LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-xfixes -lX11 -lX11-xcb -lconfig SRC = ./src/*.c ./src/ipc/*.c BIN = ragnar @@ -14,7 +18,7 @@ SYSCONFDIR = /etc .PHONY: all all: mkdir -p ./bin - $(CC) -o bin/$(BIN) $(ALL_CFLAGS) $(SRC) $(LDLIBS) + $(CC) -o bin/$(BIN) $(ALL_CFLAGS) $(LDFLAGS) $(SRC) $(LDLIBS) # client-side IPC library. the WM links none of it, only the headers under # api/include are needed to build. opt-in, for writing external clients. diff --git a/PKGBUILD b/PKGBUILD index e6e3c40..704d34c 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -10,16 +10,17 @@ arch=('x86_64') url="https://github.com/h8d13/ragnar" license=('GPL') groups=() +# mirrors the NEEDED entries of bin/ragnar, nothing more: +# libxcb covers xcb + randr + xfixes, libx11 covers X11 + X11-xcb. depends=( - 'xcb-util' - 'xcb-proto' + 'libxcb' + 'libx11' 'xcb-util-keysyms' 'xcb-util-cursor' 'xcb-util-wm' 'libconfig' 'xorg-server' 'xorg-xinit' - 'mesa' ) makedepends=('git' 'make' 'gcc') optdepends=( diff --git a/README.md b/README.md index 6d5be7f..da0d26f 100644 --- a/README.md +++ b/README.md @@ -32,14 +32,13 @@ Installing Ragnar involves two steps. Install the following dependencies: ```console -xcb-util \ -xcb-proto \ +libxcb \ +libx11 \ xcb-util-keysyms \ xcb-util-cursor \ xcb-util-wm \ xorg-server \ xorg-xinit \ -mesa \ libconfig ``` @@ -74,9 +73,10 @@ The IPC system communicates through a socket using binary data. This allows exte Ragnar uses `libconfig` to load an external configuration file: ```console -~/.config/ragnarwm/ragnar.cfg -# or system-wide +# by default, system-wide: /etc/ragnarwm/ragnar.cfg +# or mkdir -p and cp the default per user. +# ~/.config/ragnarwm/ragnar.cfg ``` The configuration is loaded on startup and can be reloaded while the window manager is running, typically through a keybinding. diff --git a/src/ragnar.c b/src/ragnar.c index efe5140..5d9c101 100644 --- a/src/ragnar.c +++ b/src/ragnar.c @@ -14,15 +14,9 @@ #include #include #include -#include #include #include -#include -#include -#include -#include #include -#include #include #include @@ -35,9 +29,6 @@ #include #include -#include -#include - #include "config.h" #include "ipc/sockets.h" #include "structs.h" diff --git a/src/structs.h b/src/structs.h index 8e4d0ee..fe5ca6a 100644 --- a/src/structs.h +++ b/src/structs.h @@ -5,11 +5,8 @@ #include #include +#include #include -#include - -#include -#include #include #define EDGE_WIDTH 5 @@ -511,7 +508,6 @@ typedef struct { struct state_t { window_edge_t grabedge; xcb_connection_t* con; - xcb_ewmh_connection_t ewmh; xcb_window_t root; xcb_screen_t* screen; From cbfc4a4130e6453801a9747631c46e3e479fd95e Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 25 Jul 2026 17:41:17 +0200 Subject: [PATCH 44/47] docs Signed-off-by: h8d13 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da0d26f..15142e6 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Ragnar uses `libconfig` to load an external configuration file: # by default, system-wide: /etc/ragnarwm/ragnar.cfg # or mkdir -p and cp the default per user. -# ~/.config/ragnarwm/ragnar.cfg +~/.config/ragnarwm/ragnar.cfg ``` The configuration is loaded on startup and can be reloaded while the window manager is running, typically through a keybinding. From 1a7a798b140fb4c4f5efce92100cdb8f67726c13 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 25 Jul 2026 18:17:57 +0200 Subject: [PATCH 45/47] rename run -> debug Signed-off-by: h8d13 --- run => debug | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename run => debug (100%) diff --git a/run b/debug similarity index 100% rename from run rename to debug From c7c8e70410a635fba3c74048db221ebbd10e38f8 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 25 Jul 2026 18:22:56 +0200 Subject: [PATCH 46/47] chore(git): add attribs Signed-off-by: h8d13 --- .gitattributes | 8 ++++++++ CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md | 0 2 files changed, 8 insertions(+) create mode 100644 .gitattributes rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..66c1575 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# keep CRLF out of the tree +* text=auto eol=lf + +# build plumbing, not part of the language breakdown. +# a pattern without a slash matches the basename at any depth, so this +# covers ./Makefile and ./api/Makefile both. +Makefile linguist-detectable=false +flake.nix linguist-detectable=false diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md From 5f300278a74927c810a1fe5a06e6ad075fbdc945 Mon Sep 17 00:00:00 2001 From: h8d13 Date: Sat, 25 Jul 2026 18:33:34 +0200 Subject: [PATCH 47/47] chore(git): rem `clang` local artifacts Signed-off-by: h8d13 --- .gitignore | 2 ++ compile_commands.json | 70 ------------------------------------------- 2 files changed, 2 insertions(+), 70 deletions(-) delete mode 100644 compile_commands.json diff --git a/.gitignore b/.gitignore index 86d3f83..65254e1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ pkg/ ragnar/ **.tar.zst .gdb.log +compile_commands.json +compile_flags.txt diff --git a/compile_commands.json b/compile_commands.json deleted file mode 100644 index 8d05530..0000000 --- a/compile_commands.json +++ /dev/null @@ -1,70 +0,0 @@ -[ - { - "arguments": [ - "/usr/bin/cc", - "-c", - "-O3", - "-ffast-math", - "-Wall", - "-Wextra", - "-pedantic", - "-o", - "bin/ragnar", - "./src/config.c" - ], - "directory": "/home/cococry/dev/ragnar", - "file": "/home/cococry/dev/ragnar/src/config.c", - "output": "/home/cococry/dev/ragnar/bin/ragnar" - }, - { - "arguments": [ - "/usr/bin/cc", - "-c", - "-O3", - "-ffast-math", - "-Wall", - "-Wextra", - "-pedantic", - "-o", - "bin/ragnar", - "./src/ragnar.c" - ], - "directory": "/home/cococry/dev/ragnar", - "file": "/home/cococry/dev/ragnar/src/ragnar.c", - "output": "/home/cococry/dev/ragnar/bin/ragnar" - }, - { - "arguments": [ - "/usr/bin/cc", - "-c", - "-O3", - "-ffast-math", - "-Wall", - "-Wextra", - "-pedantic", - "-o", - "bin/ragnar", - "./src/ipc/shm.c" - ], - "directory": "/home/cococry/dev/ragnar", - "file": "/home/cococry/dev/ragnar/src/ipc/shm.c", - "output": "/home/cococry/dev/ragnar/bin/ragnar" - }, - { - "arguments": [ - "/usr/bin/cc", - "-c", - "-O3", - "-ffast-math", - "-Wall", - "-Wextra", - "-pedantic", - "-o", - "bin/ragnar", - "./src/ipc/sockets.c" - ], - "directory": "/home/cococry/dev/ragnar", - "file": "/home/cococry/dev/ragnar/src/ipc/sockets.c", - "output": "/home/cococry/dev/ragnar/bin/ragnar" - } -]