DWM Complete suckless desktop (dwm + st + dwmblocks + picom) on FreeBSD 15 — porting guide from Linux

I recently migrated from Slackware to FreeBSD 15.0-RELEASE and ported my full suckless desktop environment. Sharing the process and the gotchas for anyone wanting to run dwm + st + dwmblocks on FreeBSD.

My dotfiles are at https://github.com/cl45h/dotfiles-freebsd

What I'm running

- dwm 6.8 with patches: vanitygaps, status2d, fancybar, swallow, cfacts, scratchpad, movestack, xresources
- st 0.9.3 with patches: HarfBuzz, alpha, scrollback, externalpipe, boxdraw, xresources
- dwmblocks with 4 blocks: volume, clock, internet, cputemp
- picom (pijulius fork) for compositing with animations
- pywal for dynamic color theming
- dunst for notifications

config.mk changes for FreeBSD

The main issue with compiling suckless tools on FreeBSD is the different paths for X11 and libraries. Here's what needs to change from the Linux defaults:

dwm config.mk:
Code:
X11INC = /usr/local/include
X11LIB = /usr/local/lib
FREETYPEINC = /usr/local/include/freetype2

INCS = -I${X11INC} -I${FREETYPEINC} `pkg-config --cflags xmu`
LIBS = -L${X11LIB} -lX11 -lX11-xcb ${XINERAMALIBS} ${FREETYPELIBS} -lxcb-res -lxcb -lXmu -lkvm

Key differences from Linux:
- -lXmu and pkg-config --cflags xmu are needed for Xresources patch support
- -lkvm is needed for the swallow patch (process lookup via kvm_getprocs() instead of /proc)
- -lxcb-res -lxcb also needed for swallow
- The CPPFLAGS need -D__BSD_VISIBLE=1 for some POSIX functions

st config.mk:
Code:
X11INC = /usr/local/include
X11LIB = /usr/local/lib
LIBS = -L$(X11LIB) -lm -lX11 -lutil -lXft \
       `$(PKG_CONFIG) --libs fontconfig` \
       `$(PKG_CONFIG) --libs freetype2` \
       `$(PKG_CONFIG) --libs harfbuzz`

Key difference: remove -lrt from LIBS. FreeBSD doesn't have librt — those functions are in libc.

Compiling

Use gmake instead of make on FreeBSD. BSD make has different syntax and will fail on suckless Makefiles:

Code:
cd ~/.local/src/dwm
gmake clean && gmake && doas gmake install

Same for st and dwmblocks.

dwmblocks scripts adapted for FreeBSD

The Linux scripts that read from /proc or /sys won't work on FreeBSD. Here's what I changed:

cputemp — CPU temperature

Linux uses sensors or reads from /sys/class/thermal. On FreeBSD, use sysctl():

Code:
#!/bin/sh
CPU_TEMP="$(sysctl -n dev.cpu.0.temperature 2>/dev/null | sed 's/C$//' | awk '{printf "%d", $1}')"
printf "🌡 %s°C" "$CPU_TEMP"

Requires coretemp kernel module loaded. Add to /etc/rc.conf:
Code:
kld_list="coretemp"

internet — network status

Linux reads from /proc/net and /sys/class/net. On FreeBSD, use ifconfig():

Code:
#!/bin/sh
# Ethernet
for iface in $(ifconfig -l); do
    case "$iface" in lo*|wlan*|tun*|pflog*) continue ;; esac
    if ifconfig "$iface" 2>/dev/null | grep -q "status: active"; then
        eth="🌐"; break
    fi
done
[ -z "$eth" ] && eth="❎"

# WiFi
if ifconfig wlan0 2>/dev/null | grep -q "status: associated"; then
    ssid=$(ifconfig wlan0 | awk '/ssid/{print $2}')
    wifi="📶 $ssid"
fi

# VPN
vpn=""
ifconfig tun0 >/dev/null 2>&1 && vpn="🔒"

printf "%s %s%s" "$wifi" "$eth" "$vpn"

volume — PulseAudio volume

This one works the same on both systems if you use PulseAudio:
Code:
pactl get-sink-volume @DEFAULT_SINK@ | awk '{print $5}'

clock

Replace setsid -f (Linux-only flag) with setsid ... & for FreeBSD compatibility.

picom (pijulius fork with animations)

The x11-wm/picom package doesn't include animations. To get the pijulius fork with animations (zoom, squeeze, slide-up, etc.), compile from source:

Code:
git clone https://github.com/pijulius/picom.git
cd picom
meson setup build
ninja -C build
sudo ninja -C build install

Dependencies: pkg install meson ninja libev uthash libconfig

.xinitrc

My ~/.xinitrc:
Code:
picom &
dwmblocks &
dunst &
xrandr --output DP-0 --left-of DP-2
exec dwm

Packages I installed for the desktop

Code:
pkg install xorg xinit xrandr xclip nsxiv xwallpaper dmenu
pkg install py311-pywal dunst noto-emoji
pkg install pulseaudio cava ranger

Gotchas summary

1. Use gmake not make
2. Remove -lrt from st (FreeBSD has it in libc)
3. Add -lXmu -lkvm -lxcb -lxcb-res to dwm for swallow + xresources
4. Add -D__BSD_VISIBLE=1 to CPPFLAGS in dwm
5. Replace /proc and /sys reads with sysctl() and ifconfig() in scripts
6. Replace setsid -f with setsid ... &
7. Replace shuf with sort -R (no shuf on base FreeBSD)
8. X11 paths are /usr/local/include and /usr/local/lib, not /usr/include

Hope this helps someone making the switch. Happy to answer questions.
 
There is a dwm port. I just run make extract on /usr/ports/x11-wm/dwm then copy /usr/ports/x11-wm/dwm/work/dwm-6.8 (or whatever version) to $HOME, then create and edit config.h. The link to your dotfiles can be useful.
I have my own https://srobb.net/dwm.html page, but the main point is that it doesn't have to be compiled from scratch. The same goes for st, which is under /usr/ports/x11/sterm. Again your dot files can be really helpful to someone just starting with either dwm or sterm.

And, dwmblocks is also a pkg and port. I've not used it, I stick with dwm's built in bar, it sounds like you have it set up to give more info than I use.
 
Thanks for checking it out! You're right, the port is a great starting point - I didn't know about the make extract approach, that's a clean way to get the base source with FreeBSD paths already sorted.

In my case I went the manual route because I'm running several patches on top (vanitygaps, pertag, movestack, swallow, scratchpad, and xresources for pywal integration), so the port's vanilla source wouldn't cover it. But for someone starting fresh, your approach definitely saves the hassle of fixing includes and libs paths in config.mk.

Nice page btw, bookmarked it. And yeah, x11-wm/dwmblocks is handy if you want per-block refresh intervals and clickable status
I have it showing volume, battery, clock, and a couple of custom scripts.
 
Back
Top