#!/usr/bin/env bash # Start a disposable nested X11 server plus Lime's safe live-development # runtime. Run this from the normal graphical session, never from inside the # Xephyr session it creates. # # Usage: # run-xephyr-dev [--config PATH | --no-config] [--display :99] # [--screen 1600x1000] [--repl-port 4005] set -euo pipefail script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" repo_dir="$(cd -- "$script_dir/.." && pwd)" config_home="${XDG_CONFIG_HOME:-${HOME}/.config}" default_config_file="$config_home/lime/config.lisp" display_name=":99" screen_size="1600x1000" repl_port="4005" config_file="" session_mode="default" session_file="" if [[ -f "$default_config_file" ]]; then config_file="$default_config_file" fi usage() { cat <&2; exit 2; } config_file="$2" shift 2 ;; --no-config) config_file="" shift ;; --session-file) [[ $# -ge 2 ]] || { printf '%s needs a path.\n' "$1" >&2; exit 2; } session_mode="file" session_file="$2" [[ "$session_file" = /* ]] || session_file="$PWD/$session_file" shift 2 ;; --no-session) session_mode="disabled" shift ;; -d|--display) [[ $# -ge 2 ]] || { printf '%s needs a display name.\n' "$1" >&2; exit 2; } display_name="$2" shift 2 ;; -s|--screen) [[ $# -ge 2 ]] || { printf '%s needs a size.\n' "$1" >&2; exit 2; } screen_size="$2" shift 2 ;; -p|--repl-port) [[ $# -ge 2 ]] || { printf '%s needs a port number.\n' "$1" >&2; exit 2; } repl_port="$2" shift 2 ;; -h|--help) usage exit 0 ;; *) printf 'Unknown option: %s\n\n' "$1" >&2 usage >&2 exit 2 ;; esac done if ! [[ "$display_name" =~ ^:[0-9]+$ ]]; then printf '%s\n' "--display must look like :99, got $display_name" >&2 exit 2 fi display_number="${display_name#:}" socket_path="/tmp/.X11-unix/X${display_number}" if [[ -S "$socket_path" ]]; then printf 'Display %s is already in use (%s). Choose another with --display.\n' \ "$display_name" "$socket_path" >&2 exit 2 fi if ! [[ "$repl_port" =~ ^[1-9][0-9]*$ ]] || ((repl_port > 65535)); then printf 'Slynk port must be an integer from 1 through 65535, got %s\n' "$repl_port" >&2 exit 2 fi if [[ -n "$config_file" ]]; then if [[ ! -f "$config_file" ]]; then printf 'Lime config does not exist: %s\n' "$config_file" >&2 exit 2 fi # Resolve before changing into the repository so an explicit relative path # still means the path relative to the shell that launched this runner. config_file="$(cd -- "$(dirname -- "$config_file")" && pwd)/$(basename -- "$config_file")" fi command -v Xephyr >/dev/null || { printf 'Xephyr is required but was not found in PATH.\n' >&2 exit 127 } command -v sbcl >/dev/null || { printf 'SBCL is required but was not found in PATH.\n' >&2 exit 127 } cleanup() { if kill -0 "$xephyr_pid" 2>/dev/null; then kill -TERM "$xephyr_pid" 2>/dev/null || true wait "$xephyr_pid" 2>/dev/null || true fi } Xephyr "$display_name" -ac -screen "$screen_size" -br -nolisten tcp & xephyr_pid=$! trap cleanup EXIT INT TERM for attempt in {1..50}; do if [[ -S "$socket_path" ]]; then break fi if ! kill -0 "$xephyr_pid" 2>/dev/null; then wait "$xephyr_pid" fi sleep 0.1 done if [[ ! -S "$socket_path" ]]; then printf 'Xephyr did not make display %s ready.\n' "$display_name" >&2 exit 1 fi # DISPLAY is exported only to this script and its children. Your normal dwm # terminal/session keeps its original display and its existing applications. export DISPLAY="$display_name" export LIME_XEPHYR_INTERNAL_CONFIG_FILE="$config_file" export LIME_XEPHYR_INTERNAL_REPL_PORT="$repl_port" export LIME_XEPHYR_INTERNAL_SESSION_MODE="$session_mode" export LIME_XEPHYR_INTERNAL_SESSION_FILE="$session_file" printf 'Lime test session: Xephyr %s (%s), Slynk 127.0.0.1:%s\n' \ "$display_name" "$screen_size" "$repl_port" if [[ -n "$config_file" ]]; then printf 'Configuration: %s\n' "$config_file" else printf 'Configuration: starter bindings (no config file)\n' fi printf 'Close Lime or press Ctrl-C here to tear down the nested session.\n' cd "$repo_dir" sbcl --noinform --non-interactive \ --eval '(require :asdf)' \ --eval '(asdf:load-asd (merge-pathnames "lime.asd" (uiop:getcwd)))' \ --eval '(asdf:load-system "lime/dev")' \ --eval '(lime.dev:run-development-session :display-name (uiop:getenv "DISPLAY") :session-file (let ((mode (uiop:getenv "LIME_XEPHYR_INTERNAL_SESSION_MODE"))) (cond ((string= mode "disabled") nil) ((string= mode "file") (uiop:getenv "LIME_XEPHYR_INTERNAL_SESSION_FILE")) (t :default))) :config-file (let ((path (uiop:getenv "LIME_XEPHYR_INTERNAL_CONFIG_FILE"))) (and (plusp (length path)) path)) :repl-port (parse-integer (uiop:getenv "LIME_XEPHYR_INTERNAL_REPL_PORT")))'