From 6f48508aa08396bcf9dd4da2cab1d221bcc53f78 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 25 Aug 2026 02:07:23 -0300 Subject: acmefs: pardes --fs serves acme's control filesystem over raw Linux FUSE --- examples/acmefs/eventlog | 161 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100755 examples/acmefs/eventlog (limited to 'examples/acmefs/eventlog') diff --git a/examples/acmefs/eventlog b/examples/acmefs/eventlog new file mode 100755 index 00000000..b7c7596a --- /dev/null +++ b/examples/acmefs/eventlog @@ -0,0 +1,161 @@ +#!/usr/bin/env bash +# Stream one pane's `event` file and print every record in words, so that the +# protocol can be watched instead of guessed at. bash and coreutils only. +# +# WARNING -- THIS IS NOT A PASSIVE OBSERVER +# +# Two things change the moment this script starts. +# +# While a pane's event file is open, that pane's button-2 (Exec) and button-3 +# (Look) actions are REPORTED and NOT PERFORMED. Middle-clicking Del in the +# tag of a watched pane will print a record here and do nothing to the pane. +# (Chorded Cut and Paste are exempt and behave normally.) That suppression is +# the feature -- it is what lets a script define its own tag commands -- but +# while you are only watching, it makes the pane feel broken. +# +# And a record is consumed by whoever reads it first. If another program is +# driving that pane through its event file, do not point this at the same pane: +# the two readers will split the stream and both will misbehave. Watch a pane +# nobody owns, or watch the script instead. +# +# WHAT IT DEMONSTRATES +# +# A record is two characters -- origin and type -- then four blank separated +# decimal numbers (q0, q1, flag, text length) and the text. Uppercase types +# refer to the body, lowercase to the tag; that single bit of case is the whole +# addressing scheme. This script spells all of it out: `M X` prints as +# "mouse exec body", and the flag bits print as the words they stand for. +# +# The event file is read through `cat` rather than opened by the shell. bash's +# `read` buffers from a seekable fd and then seeks back to correct the file +# position -- fine on a real file, silently lossy on a stream whose server +# ignores offsets. `cat` reads strictly forward, and the pipe it writes into is +# not seekable, so nothing can be skipped. +# +# FILES TOUCHED: /event (read only, but see the warning). +# +# USAGE: eventlog [-m mountdir] [pane-id] default id: $PARDES_PANE +set -u +LC_ALL=C # so ${#text} counts BYTES: event offsets are byte offsets + +self=${0##*/} +mount=${PARDES_FS:-} + +if [ "${1:-}" = "-m" ]; then + [ $# -ge 2 ] || { echo "$self: -m needs a directory" >&2; exit 2; } + mount=$2 + shift 2 +fi +pane=${1:-${PARDES_PANE:-}} + +if [ -z "$mount" ] || [ -z "$pane" ]; then + cat >&2 <&2; exit 2 ;; +esac +ev="$mount/$pane/event" +[ -r "$ev" ] || { echo "$self: cannot read $ev (no such pane?)" >&2; exit 1; } + +origin_word() { + case $1 in + E) echo "fs-write" ;; # a write to this pane's body or tag + F) echo "fs-action" ;; # an action taken through another of its files + K) echo "keyboard" ;; + M) echo "mouse" ;; + *) echo "origin?$1" ;; + esac +} + +# Uppercase = body, lowercase = tag. Nothing else distinguishes the two. +type_word() { + case $1 in + D | I | L | X) echo "body" ;; + d | i | l | x) echo "tag" ;; + *) echo "?" ;; + esac +} + +action_word() { + case $1 in + D | d) echo "delete" ;; + I | i) echo "insert" ;; + L | l) echo "look" ;; # button 3 + X | x) echo "exec" ;; # button 2 + *) echo "type?$1" ;; + esac +} + +# The flag is a bitwise OR whose meaning depends on the type. Deletes and +# inserts always carry 0, so only look and exec decode to anything. +flag_words() { + local t=$1 f=$2 out="" + case $t in + X | x) + (((f & 1) != 0)) && out="$out,builtin" + (((f & 2) != 0)) && out="$out,expanded(record follows)" + (((f & 8) != 0)) && out="$out,chorded-arg(2 records follow)" + ;; + L | l) + (((f & 1) != 0)) && out="$out,no-load-needed" + (((f & 2) != 0)) && out="$out,expanded(record follows)" + (((f & 4) != 0)) && out="$out,file-or-pane-name" + ;; + esac + [ -n "$out" ] && printf '%s' "${out#,}" || printf -- '-' +} + +printf '%-10s %-7s %-7s %8s %8s %-24s %s\n' ORIGIN ACTION WHERE Q0 Q1 FLAG TEXT +cat -- "$ev" 2>/dev/null | while IFS= read -r line; do + # Blank lines are a record terminator, not a record: skip them. This is + # also what keeps the reader in step with either text layout below. + [ -n "$line" ] || continue + o=${line:0:1} + t=${line:1:1} + rest=${line:2} + # shellcheck disable=SC2034 + read -r q0 q1 flag n text <<<"$rest" || : + q0=${q0:-0} q1=${q1:-0} flag=${flag:-0} n=${n:-0} text=${text:-} + case $n in *[!0-9]*) n=0 ;; esac + if [ "$n" -gt 0 ] && [ -z "$text" ]; then + # The counted bytes follow the newline. + IFS= read -r -N "$n" text || : + elif [ "$n" -gt 0 ] && [ "${#text}" -lt "$n" ]; then + # The text sat on the record line and contained a newline of its + # own, which the line read above swallowed. Take the remainder. + want=$((n - ${#text} - 1)) + more="" + [ "$want" -gt 0 ] && { IFS= read -r -N "$want" more || :; } + text="$text +$more" + fi + if [ -n "$text" ]; then + shown=$(printf '%q' "$text") + elif [ "$n" -gt 0 ]; then + shown="(short by $n bytes: the stream ended mid-record)" + else + # Count 0 means "no text was sent". For a delete that is the rule; + # for a look or an exec it means the text was 256 bytes or longer and + # was elided, or the selection was null and an expansion follows. + case $t in + X | x | L | l) shown="(no text: elided, or null -- read $pane/data)" ;; + *) shown="" ;; + esac + fi + printf '%-10s %-7s %-7s %8s %8s %-24s %s\n' \ + "$(origin_word "$o")" "$(action_word "$t")" "$(type_word "$t")" \ + "$q0" "$q1" "$(flag_words "$t" "$flag")" "$shown" +done +# cat ends when the pane or the whole mount goes away. That is the editor +# exiting, not a failure, so say nothing and leave with 0. +exit 0 -- cgit v1.3