summaryrefslogtreecommitdiff
path: root/examples/acmefs/eventlog
blob: b7c7596aecb90ba04bdaffd5fe92d13bdc357f3d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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