-
Notifications
You must be signed in to change notification settings - Fork 0
GNUstep windowmanager
Joe Maloney edited this page Sep 17, 2026
·
3 revisions
This is a rework of gershwin-windowmanager based on XCBKIT to get theme info dynamically
To test the POC:
git clone https://github.com/pkgdemon/gnustep-developer/
./Library/Scripts/checkout.sh
./Library/Scripts/bootstrap.sh
make install
sudo dscli init
Login as admin after reboot
admin@incubator:~$ cat .xinitrc
. /System/Library/Makefiles/GNUstep.sh
exec gnustep-session
#!/bin/sh
#
# gnustep-session - start and supervise the GNUstep desktop session
#
# gnustep-session start the session (run from ~/.xinitrc)
# gnustep-session --logout gracefully end the running session
#
# Startup order: WindowManager -> DockWM -> GWorkspace. Each step polls
# until the previous component is up (or a timeout passes) before moving on.
# WindowManager and DockWM are restarted if they crash. The session ends
# when GWorkspace quits or on --logout/SIGTERM/SIGHUP/SIGINT, shutting down
# GWorkspace, then DockWM, then WindowManager.
. /System/Library/Makefiles/GNUstep.sh
WM_APP=/System/Library/CoreServices/Applications/WindowManager.app
DOCK_APP=DockWM
SHELL_APP=GWorkspace
READY_TIMEOUT=15 # seconds to wait for each component to come up
STOP_TIMEOUT=5 # seconds to wait for a component to exit after SIGTERM
MAX_RESTARTS=5 # crash restarts allowed per component per RESTART_WINDOW
RESTART_WINDOW=60
LOG_DIR="$HOME/Library/Logs"
RUN_DIR="${XDG_RUNTIME_DIR:-$HOME/.cache}"
PID_FILE="$RUN_DIR/gnustep-session$(echo "${DISPLAY:-:0}" | tr ':/' '__').pid"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') gnustep-session: $*" >> "$LOG_DIR/session.log"
}
alive() {
[ -n "$1" ] && kill -0 "$1" 2>/dev/null
}
# launch <name> <app>: start app in background, logging to <name>.log; sets LAUNCHED_PID
launch() {
openapp "$2" >> "$LOG_DIR/$1.log" 2>&1 &
LAUNCHED_PID=$!
log "started $1 (pid $LAUNCHED_PID)"
}
wm_ready() {
xprop -root _NET_SUPPORTING_WM_CHECK 2>/dev/null | grep -q "window id"
}
pid_has_window() {
xdotool search --onlyvisible --pid "$1" >/dev/null 2>&1
}
# wait_until <pid> <check-command...>: poll until check succeeds, pid dies, or timeout
wait_until() {
_pid=$1; shift
_i=0
while [ $_i -lt $((READY_TIMEOUT * 4)) ]; do
alive "$_pid" || return 1
"$@" && return 0
sleep 0.25
_i=$((_i + 1))
done
return 2
}
# stop <name> <pid>: SIGTERM, poll for exit, then SIGKILL
stop() {
alive "$2" || return 0
log "stopping $1 (pid $2)"
kill -TERM "$2" 2>/dev/null
_i=0
while alive "$2" && [ $_i -lt $((STOP_TIMEOUT * 4)) ]; do
sleep 0.25
_i=$((_i + 1))
done
if alive "$2"; then
log "$1 did not exit, killing"
kill -KILL "$2" 2>/dev/null
fi
wait "$2" 2>/dev/null
}
if [ "$1" = "--logout" ]; then
if [ -f "$PID_FILE" ] && alive "$(cat "$PID_FILE")"; then
kill -TERM "$(cat "$PID_FILE")"
exit 0
fi
echo "gnustep-session: no running session for display ${DISPLAY:-:0}" >&2
exit 1
fi
mkdir -p "$LOG_DIR" "$RUN_DIR"
if [ -f "$PID_FILE" ] && alive "$(cat "$PID_FILE")"; then
echo "gnustep-session: session already running (pid $(cat "$PID_FILE"))" >&2
exit 1
fi
echo $$ > "$PID_FILE"
log "session starting on display $DISPLAY"
WM_PID=; DOCK_PID=; SHELL_PID=
SHUTTING_DOWN=0
shutdown() {
[ $SHUTTING_DOWN -eq 1 ] && return
SHUTTING_DOWN=1
log "session ending ($1)"
stop "$SHELL_APP" "$SHELL_PID"
stop DockWM "$DOCK_PID"
stop WindowManager "$WM_PID"
rm -f "$PID_FILE"
log "session ended"
exit 0
}
trap 'shutdown SIGTERM' TERM
trap 'shutdown SIGHUP' HUP
trap 'shutdown SIGINT' INT
start_wm() {
launch WindowManager "$WM_APP"; WM_PID=$LAUNCHED_PID
wait_until "$WM_PID" wm_ready
case $? in
0) log "WindowManager ready" ;;
1) log "WindowManager exited during startup" ;;
*) log "WindowManager not ready after ${READY_TIMEOUT}s, continuing" ;;
esac
}
start_dock() {
launch DockWM "$DOCK_APP"; DOCK_PID=$LAUNCHED_PID
wait_until "$DOCK_PID" pid_has_window "$DOCK_PID"
case $? in
0) log "DockWM ready" ;;
1) log "DockWM exited during startup" ;;
*) log "DockWM window not seen after ${READY_TIMEOUT}s, continuing" ;;
esac
}
start_wm
start_dock
launch "$SHELL_APP" "$SHELL_APP"; SHELL_PID=$LAUNCHED_PID
WM_RESTARTS=0; WM_WINDOW_START=$(date +%s)
DOCK_RESTARTS=0; DOCK_WINDOW_START=$(date +%s)
# may_restart <count-var> <window-start-var>: rate-limit crash restarts
may_restart() {
_now=$(date +%s)
eval "_start=\$$2; _count=\$$1"
if [ $((_now - _start)) -gt $RESTART_WINDOW ]; then
_start=$_now; _count=0
fi
_count=$((_count + 1))
eval "$1=$_count; $2=$_start"
[ $_count -le $MAX_RESTARTS ]
}
# Supervise. Sleep in the background so signals interrupt the wait promptly.
while :; do
if ! alive "$SHELL_PID"; then
wait "$SHELL_PID" 2>/dev/null
shutdown "$SHELL_APP exited"
fi
if [ -n "$WM_PID" ] && ! alive "$WM_PID"; then
wait "$WM_PID" 2>/dev/null
if may_restart WM_RESTARTS WM_WINDOW_START; then
log "WindowManager exited unexpectedly, restarting"
start_wm
else
log "WindowManager crashed $MAX_RESTARTS times in ${RESTART_WINDOW}s, not restarting"
WM_PID=
fi
fi
if [ -n "$DOCK_PID" ] && ! alive "$DOCK_PID"; then
wait "$DOCK_PID" 2>/dev/null
if may_restart DOCK_RESTARTS DOCK_WINDOW_START; then
log "DockWM exited unexpectedly, restarting"
start_dock
else
log "DockWM crashed $MAX_RESTARTS times in ${RESTART_WINDOW}s, not restarting"
DOCK_PID=
fi
fi
sleep 1 &
wait $!
done