summaryrefslogtreecommitdiff
path: root/examples/acmefs/eventlog
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 02:07:23 -0300
committerGabriel Schneider <[email protected]>2026-08-25 09:42:07 -0300
commit6f48508aa08396bcf9dd4da2cab1d221bcc53f78 (patch)
tree83daec3db5ac27ea3172651df2b3e9cb62eddb4a /examples/acmefs/eventlog
parent28c70cabb6ceb7e5fecfd74f6984f5a995269f01 (diff)
downloadpardes-6f48508aa08396bcf9dd4da2cab1d221bcc53f78.tar.gz
pardes-6f48508aa08396bcf9dd4da2cab1d221bcc53f78.zip
acmefs: pardes --fs serves acme's control filesystem over raw Linux FUSE
Diffstat (limited to 'examples/acmefs/eventlog')
-rwxr-xr-xexamples/acmefs/eventlog161
1 files changed, 161 insertions, 0 deletions
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: <id>/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 <<EOF
+usage: $self [-m mountdir] [pane-id]
+
+Prints one line per event record: origin, type, target, q0, q1, flag, text.
+The mount comes from \$PARDES_FS and the pane id from \$PARDES_PANE, both of
+which pardes sets in every pane shell when started with --fs.
+
+Opening a pane's event file suppresses that pane's Look and Exec while this
+runs, and consumes records any other client of the same pane needs.
+EOF
+ exit 2
+fi
+case $pane in
+*[!0-9]*) echo "$self: '$pane' is not a pane id" >&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