sane-input-handler: port to oil shell
This commit is contained in:
@@ -73,18 +73,17 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
packageUnwrapped = pkgs.static-nix-shell.mkBash {
|
packageUnwrapped = pkgs.static-nix-shell.mkYsh {
|
||||||
pname = "sane-input-handler";
|
pname = "sane-input-handler";
|
||||||
srcRoot = ./.;
|
srcRoot = ./.;
|
||||||
pkgs = {
|
pkgs = {
|
||||||
inherit (pkgs) coreutils jq killall playerctl procps sane-open util-linux wireplumber;
|
inherit (pkgs) coreutils killall playerctl procps sane-open util-linux wireplumber;
|
||||||
sway = config.sane.programs.sway.package;
|
sway = config.sane.programs.sway.package;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
suggestedPrograms = [
|
suggestedPrograms = [
|
||||||
"bonsai"
|
"bonsai"
|
||||||
# dependencies which get pulled in unconditionally:
|
# dependencies which get pulled in unconditionally:
|
||||||
"jq"
|
|
||||||
"killall"
|
"killall"
|
||||||
"playerctl"
|
"playerctl"
|
||||||
"procps" #< TODO: reduce to just those parts of procps which are really needed
|
"procps" #< TODO: reduce to just those parts of procps which are really needed
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env nix-shell
|
#!/usr/bin/env nix-shell
|
||||||
#!nix-shell -i bash -p bash -p coreutils -p jq -p killall -p playerctl -p procps -p sane-open -p sway -p util-linux -p wireplumber
|
#!nix-shell -i ysh -p coreutils -p killall -p oils-for-unix -p playerctl -p procps -p sane-open -p sway -p util-linux -p wireplumber
|
||||||
# vim: set filetype=bash :
|
# vim: set filetype=bash :
|
||||||
|
|
||||||
# input map considerations
|
# input map considerations
|
||||||
@@ -56,12 +56,14 @@
|
|||||||
|
|
||||||
|
|
||||||
# increments to use for volume adjustment (in %)
|
# increments to use for volume adjustment (in %)
|
||||||
VOL_INCR=5
|
var VOL_INCR = 5
|
||||||
KEYBOARD="${KEYBOARD:-wvkbd-mobintl}"
|
var KEYBOARD = "${KEYBOARD:-wvkbd-mobintl}"
|
||||||
CAMERA="${CAMERA:-org.postmarketos.Megapixels.desktop}"
|
var CAMERA = "${CAMERA:-org.postmarketos.Megapixels.desktop}"
|
||||||
|
var VERBOSITY = 0
|
||||||
|
var DRY_RUN = false
|
||||||
|
|
||||||
showHelp() {
|
proc showHelp {
|
||||||
echo "usage: sane-input-handler [--verbose] [--dry-run] <action>"
|
echo "usage: sane-input-handler [--verbose [--verbose]] [--dry-run] <action>"
|
||||||
echo ""
|
echo ""
|
||||||
echo "where action is one of:"
|
echo "where action is one of:"
|
||||||
echo "- power_tap_{1,2}"
|
echo "- power_tap_{1,2}"
|
||||||
@@ -78,280 +80,354 @@ showHelp() {
|
|||||||
echo "- voldown_start"
|
echo "- voldown_start"
|
||||||
}
|
}
|
||||||
|
|
||||||
log() {
|
proc log (; ...stmts; level, context) {
|
||||||
printf "sane-input-handler: %s\n" "$1" >&2
|
var prefix = " $context:" if context else ""
|
||||||
|
var formatted = "$(pp value (stmts))"
|
||||||
|
echo "[$level] sane-input-handler:$prefix $formatted" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
VERBOSE=
|
proc info (context=""; ...stmts) {
|
||||||
debug() {
|
log (level="INFO", context=context, ...stmts)
|
||||||
if [ -n "$VERBOSE" ]; then
|
|
||||||
log "$@"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace() {
|
proc debug (context=""; ...stmts) {
|
||||||
debug "$*"
|
if (VERBOSITY >= 1) {
|
||||||
"$@"
|
log (level="DEBG", context=context, ...stmts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DRY_RUN=
|
proc verbose (context=""; ...stmts) {
|
||||||
effect() {
|
if (VERBOSITY >= 2) {
|
||||||
if [ -n "$DRY_RUN" ]; then
|
log (level="VERB", context=context, ...stmts)
|
||||||
log "SKIP(dry run): $*"
|
}
|
||||||
else
|
}
|
||||||
trace "$@"
|
|
||||||
fi
|
proc trace (...args) {
|
||||||
|
debug (...args)
|
||||||
|
@[args]
|
||||||
|
}
|
||||||
|
|
||||||
|
proc effect (...args) {
|
||||||
|
if (DRY_RUN) {
|
||||||
|
info "SKIP(dry run)" (...args)
|
||||||
|
} else {
|
||||||
|
trace @[args]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## HELPERS
|
## HELPERS
|
||||||
|
|
||||||
# swaySetOutput true|false
|
# swaySetOutput true|false
|
||||||
# turns the display on or off
|
# turns the display on or off
|
||||||
swaySetOutput() {
|
proc swaySetOutput (value) {
|
||||||
effect swaymsg -- output '*' power "$1"
|
effect swaymsg -- output '*' power "$value"
|
||||||
}
|
}
|
||||||
|
|
||||||
# swaySetTouch enabled|disabled
|
# swaySetTouch enabled|disabled
|
||||||
# turns touch input on or off
|
# turns touch input on or off
|
||||||
swaySetTouch() {
|
proc swaySetTouch (value) {
|
||||||
# XXX(2024/06/09): `type:touch` method is documented, but now silently fails
|
# XXX(2024/06/09): `type:touch` method is documented, but now silently fails
|
||||||
# swaymsg -- input type:touch events "$1"
|
# swaymsg -- input type:touch events "$1"
|
||||||
|
|
||||||
local inputs=($(swaymsg -t get_inputs --raw | jq '. | map(select(.type == "touch")) | map(.identifier) | join(" ")' --raw-output))
|
var inputs = null
|
||||||
debug "detected ${#inputs[@]} sway inputs"
|
swaymsg -t get_inputs --raw | json read (&inputs)
|
||||||
for id in "${inputs[@]}"; do
|
for input in (inputs) {
|
||||||
effect swaymsg -- input "$id" events "$1"
|
if (input.type === "touch") {
|
||||||
done
|
effect swaymsg -- input "$[input.identifier]" events "$value"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# success if all touch inputs have their events enabled
|
# true if all touch inputs have their events enabled
|
||||||
swayGetTouch() {
|
func swayGetTouch () {
|
||||||
swaymsg -t get_inputs --raw \
|
var inputs = null
|
||||||
| jq --exit-status '. | map(select(.type == "touch")) | all(.libinput.send_events == "enabled")' \
|
swaymsg -t get_inputs --raw | json read (&inputs)
|
||||||
> /dev/null
|
|
||||||
}
|
var num_touch_enabled = 0
|
||||||
# success if all outputs have power
|
var num_touch_disabled = 0
|
||||||
swayGetOutput() {
|
for input in (inputs) {
|
||||||
swaymsg -t get_outputs --raw \
|
if (input.type === "touch") {
|
||||||
| jq --exit-status '. | all(.power)' \
|
var send_events = input.libinput.send_events
|
||||||
> /dev/null
|
case (send_events) {
|
||||||
|
("enabled") {
|
||||||
|
setvar num_touch_enabled += 1
|
||||||
|
}
|
||||||
|
("disabled") {
|
||||||
|
setvar num_touch_disabled += 1
|
||||||
|
}
|
||||||
|
(else) {
|
||||||
|
info "swayGetTouch" ("unknown 'libinput.send_events' value:", send_events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (num_touch_disabled === 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
isAllOn() {
|
# true if all outputs have power
|
||||||
swayGetOutput && swayGetTouch
|
func swayGetOutput () {
|
||||||
|
var outputs = null
|
||||||
|
swaymsg -t get_outputs --raw | json read (&outputs)
|
||||||
|
|
||||||
|
var num_power_true = 0
|
||||||
|
var num_power_false = 0
|
||||||
|
for output in (outputs) {
|
||||||
|
case (output.power) {
|
||||||
|
(true) {
|
||||||
|
setvar num_power_true += 1
|
||||||
|
}
|
||||||
|
(false) {
|
||||||
|
setvar num_power_false += 1
|
||||||
|
}
|
||||||
|
(else) {
|
||||||
|
info "swayGetOutput" ("unknown 'power' value:", output.power)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (num_power_false === 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
isInhibited() {
|
# true if rofi is visible
|
||||||
pidof rofi
|
func rofiGet () {
|
||||||
|
if pidof rofi {
|
||||||
|
return (true)
|
||||||
|
} else {
|
||||||
|
return (false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWith() {
|
var MEMOIZED = {}
|
||||||
local state=
|
func memoize (name, f) {
|
||||||
if [ -n "$_isInhibited" ]; then
|
var expr = null
|
||||||
state="inhibited+"
|
if (name in MEMOIZED) {
|
||||||
fi
|
setvar expr = MEMOIZED[name]
|
||||||
if [ -n "$_isAllOn" ]; then
|
verbose "memoize(cached)" (name, expr)
|
||||||
state="${state}on"
|
} else {
|
||||||
else
|
verbose "memoize(uncached)" (name)
|
||||||
state="${state}off"
|
# setvar expr = f()
|
||||||
fi
|
setvar expr = io->evalExpr (f);
|
||||||
log "state=$state action=$action: handleWith: $*"
|
verbose "memoize(uncached -> cached)" (name, expr)
|
||||||
"$@"
|
setglobal MEMOIZED[name] = expr
|
||||||
exit $?
|
}
|
||||||
|
return (expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAllOn () {
|
||||||
|
return (memoize ("isAllOn", ^[swayGetOutput() and swayGetTouch()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInhibited () {
|
||||||
|
return (memoize ("rofiGet", ^[rofiGet()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
proc handleWith (...args) {
|
||||||
|
var state = ""
|
||||||
|
if (isInhibited()) {
|
||||||
|
setvar state = "inhibited+"
|
||||||
|
}
|
||||||
|
if (isAllOn()) {
|
||||||
|
setvar state = "${state}on"
|
||||||
|
} else {
|
||||||
|
setvar state = "${state}off"
|
||||||
|
}
|
||||||
|
|
||||||
|
info "handleWith" ("state=$state", "action=$action", ...args)
|
||||||
|
@[args]
|
||||||
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
## HANDLERS
|
## HANDLERS
|
||||||
ignore() {
|
proc ignore {
|
||||||
true
|
:
|
||||||
}
|
}
|
||||||
inhibited() {
|
proc inhibited {
|
||||||
true
|
:
|
||||||
}
|
}
|
||||||
unmapped() {
|
proc unmapped {
|
||||||
true
|
:
|
||||||
}
|
}
|
||||||
|
|
||||||
allOn() {
|
proc allOn {
|
||||||
swaySetOutput true
|
swaySetOutput true
|
||||||
swaySetTouch enabled
|
swaySetTouch enabled
|
||||||
}
|
}
|
||||||
allOff() {
|
proc allOff {
|
||||||
swaySetOutput false
|
swaySetOutput false
|
||||||
swaySetTouch disabled
|
swaySetTouch disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleKeyboard() {
|
proc toggleKeyboard {
|
||||||
local keyboardPid=$(pidof "$KEYBOARD")
|
var keyboardPids = $(pidof "$KEYBOARD") => split(" ")
|
||||||
if [ -z "$keyboardPid" ]; then
|
if (not keyboardPids) {
|
||||||
log "cannot find $KEYBOARD"
|
log ("cannot find $KEYBOARD")
|
||||||
return
|
return
|
||||||
fi
|
}
|
||||||
|
|
||||||
for p in $keyboardPid; do
|
for p in (keyboardPids) {
|
||||||
# `env` so that we get the right `kill` binary instead of bash's builtin
|
# `env` so that we get the right `kill` binary instead of bash's builtin
|
||||||
# `kill` only one keyboard process. in the case of e.g. sandboxing,
|
# `kill` only one keyboard process. in the case of e.g. sandboxing,
|
||||||
# the keyboard might consist of multiple processes and each one we signal would cause a toggle
|
# the keyboard might consist of multiple processes and each one we signal would cause a toggle
|
||||||
if effect env kill -s RTMIN+0 "$p"; then
|
if effect env kill -s RTMIN+0 "$p" {
|
||||||
break
|
break
|
||||||
fi
|
}
|
||||||
done
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
## DISPATCHERS
|
## DISPATCHERS
|
||||||
|
|
||||||
dispatchDefault() {
|
proc dispatchDefault (action) {
|
||||||
case "$action" in
|
case (action) {
|
||||||
"power_tap_2")
|
("power_tap_2") {
|
||||||
# power twice => screenoff
|
# power twice => screenoff
|
||||||
handleWith allOff
|
handleWith allOff
|
||||||
;;
|
}
|
||||||
"power_hold")
|
("power_hold") {
|
||||||
# power twice => toggle media player
|
# power twice => toggle media player
|
||||||
handleWith effect playerctl play-pause
|
handleWith effect playerctl play-pause
|
||||||
;;
|
}
|
||||||
|
|
||||||
volup_tap*)
|
/ ^ 'volup_tap_' d+ $ / {
|
||||||
handleWith effect wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%+
|
handleWith effect wpctl set-volume '@DEFAULT_AUDIO_SINK@' "$VOL_INCR"%+
|
||||||
;;
|
}
|
||||||
voldown_tap*)
|
/ ^ 'voldown_tap_' d+ $ / {
|
||||||
handleWith effect wpctl set-volume @DEFAULT_AUDIO_SINK@ "$VOL_INCR"%-
|
handleWith effect wpctl set-volume '@DEFAULT_AUDIO_SINK@' "$VOL_INCR"%-
|
||||||
;;
|
}
|
||||||
esac
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchOff() {
|
proc dispatchOff (action) {
|
||||||
case "$action" in
|
case (action) {
|
||||||
"power_tap_1")
|
("power_tap_1") {
|
||||||
# power once => unlock
|
# power once => unlock
|
||||||
handleWith allOn
|
handleWith allOn
|
||||||
;;
|
}
|
||||||
"power_tap_1_hold")
|
("power_tap_1_hold") {
|
||||||
# power tap->hold: escape hatch for when bonsaid locks up
|
# power tap->hold: escape hatch for when bonsaid locks up
|
||||||
handleWith effect systemctl restart bonsaid
|
handleWith effect systemctl restart bonsaid
|
||||||
;;
|
}
|
||||||
volup_hold*)
|
/ ^ 'volup_hold_' d+ $ / {
|
||||||
handleWith effect playerctl position 30+
|
handleWith effect playerctl position 30+
|
||||||
;;
|
}
|
||||||
voldown_hold*)
|
/ ^ 'voldown_hold_' d+ $ / {
|
||||||
handleWith effect playerctl position 10-
|
handleWith effect playerctl position 10-
|
||||||
;;
|
}
|
||||||
esac
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchOn() {
|
proc dispatchOn (action) {
|
||||||
case "$action" in
|
case (action) {
|
||||||
# power_tap_1: intentional default to no-op (it's important this be unmapped, because events can be misordered with power_tap_1 arriving *after* power_tap_2)
|
# power_tap_1: intentional default to no-op (it's important this be unmapped, because events can be misordered with power_tap_1 arriving *after* power_tap_2)
|
||||||
# power_tap_2: intentional default to screenoff
|
# power_tap_2: intentional default to screenoff
|
||||||
"power_tap_1_hold")
|
("power_tap_1_hold") {
|
||||||
# power tap->hold: kill active window
|
# power tap->hold: kill active window
|
||||||
# TODO: disable this if locked (with e.g. schlock, swaylock, etc)
|
# TODO: disable this if locked (with e.g. schlock, swaylock, etc)
|
||||||
handleWith effect swaymsg kill
|
handleWith effect swaymsg kill
|
||||||
;;
|
}
|
||||||
"power_and_volup")
|
("power_and_volup") {
|
||||||
# power (hold) -> volup: take screenshot
|
# power (hold) -> volup: take screenshot
|
||||||
handleWith effect sane-open --application sane-screenshot.desktop
|
handleWith effect sane-open --application sane-screenshot.desktop
|
||||||
;;
|
}
|
||||||
"power_and_voldown")
|
("power_and_voldown") {
|
||||||
# power (hold) -> voldown: open camera
|
# power (hold) -> voldown: open camera
|
||||||
handleWith effect sane-open --auto-keyboard --application "$CAMERA"
|
handleWith effect sane-open --auto-keyboard --application "$CAMERA"
|
||||||
;;
|
}
|
||||||
"power_then_volup")
|
("power_then_volup") {
|
||||||
# power (tap) -> volup: rotate CCW
|
# power (tap) -> volup: rotate CCW
|
||||||
handleWith effect swaymsg -- output '-' transform 90 anticlockwise
|
handleWith effect swaymsg -- output '-' transform 90 anticlockwise
|
||||||
;;
|
}
|
||||||
"power_then_voldown")
|
("power_then_voldown") {
|
||||||
# power (tap) -> voldown: rotate CW
|
# power (tap) -> voldown: rotate CW
|
||||||
handleWith effect swaymsg -- output '-' transform 90 clockwise
|
handleWith effect swaymsg -- output '-' transform 90 clockwise
|
||||||
;;
|
}
|
||||||
|
|
||||||
"volup_tap_1")
|
("volup_tap_1") {
|
||||||
# volume up once: filesystem browser
|
# volume up once: filesystem browser
|
||||||
handleWith effect sane-open --auto-keyboard --application rofi-filebrowser.desktop
|
handleWith effect sane-open --auto-keyboard --application rofi-filebrowser.desktop
|
||||||
;;
|
}
|
||||||
"volup_hold_1")
|
("volup_hold_1") {
|
||||||
# volume up hold: browse files and apps
|
# volume up hold: browse files and apps
|
||||||
# reset fs directory: useful in case you get stuck in broken directory (e.g. one which lacks a `..` entry)
|
# reset fs directory: useful in case you get stuck in broken directory (e.g. one which lacks a `..` entry)
|
||||||
rm -f ~/.cache/rofi/rofi3.filebrowsercache
|
rm -f ~/.cache/rofi/rofi3.filebrowsercache
|
||||||
handleWith effect sane-open --auto-keyboard --application rofi.desktop
|
handleWith effect sane-open --auto-keyboard --application rofi.desktop
|
||||||
;;
|
}
|
||||||
|
|
||||||
"voldown_start")
|
("voldown_start") {
|
||||||
# volume down once: toggle keyboard
|
# volume down once: toggle keyboard
|
||||||
handleWith toggleKeyboard
|
handleWith toggleKeyboard
|
||||||
;;
|
}
|
||||||
"voldown_hold_1")
|
("voldown_hold_1") {
|
||||||
# hold voldown to launch terminal
|
# hold voldown to launch terminal
|
||||||
# note we already triggered the keyboard; that's fine: usually keyboard + terminal go together :)
|
# note we already triggered the keyboard; that's fine: usually keyboard + terminal go together :)
|
||||||
handleWith effect sane-open --auto-keyboard --application xdg-terminal-exec.desktop
|
handleWith effect sane-open --auto-keyboard --application xdg-terminal-exec.desktop
|
||||||
;;
|
}
|
||||||
"voldown_tap_1")
|
("voldown_tap_1") {
|
||||||
# swallow, to prevent keyboard from also triggering media controls
|
# swallow, to prevent keyboard from also triggering media controls
|
||||||
handleWith ignore
|
handleWith ignore
|
||||||
;;
|
}
|
||||||
voldown_hold_*)
|
/ ^ 'voldown_hold_' d+ $ / {
|
||||||
# swallow, to prevent terminal from also triggering media controls
|
# swallow, to prevent terminal from also triggering media controls
|
||||||
handleWith ignore
|
handleWith ignore
|
||||||
;;
|
}
|
||||||
esac
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchInhibited() {
|
proc dispatchInhibited (action) {
|
||||||
case "$action" in
|
case (action) {
|
||||||
"power_tap_1_hold")
|
("power_tap_1_hold") {
|
||||||
# power hold: escape hatch in case rofi has hung
|
# power hold: escape hatch in case rofi has hung
|
||||||
handleWith effect killall -9 rofi
|
handleWith effect killall -9 rofi
|
||||||
;;
|
}
|
||||||
*)
|
(else) {
|
||||||
# eat everything else (and let rofi consume it)
|
# eat everything else (and let rofi consume it)
|
||||||
handleWith inhibited
|
handleWith inhibited
|
||||||
;;
|
}
|
||||||
esac
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatchToplevel() {
|
proc dispatchToplevel (action) {
|
||||||
_isAllOn="$(isAllOn && echo 1 || true)"
|
if (not isAllOn()) {
|
||||||
|
trace dispatchOff "$action"
|
||||||
|
} else {
|
||||||
|
if (isInhibited()) {
|
||||||
|
trace dispatchInhibited "$action"
|
||||||
|
} else {
|
||||||
|
trace dispatchOn "$action"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if [ -z "$_isAllOn" ]; then
|
trace dispatchDefault "$action"
|
||||||
trace dispatchOff
|
|
||||||
else
|
|
||||||
_isInhibited="$(isInhibited && echo 1 || true)"
|
|
||||||
if [ -n "$_isInhibited" ]; then
|
|
||||||
trace dispatchInhibited
|
|
||||||
else
|
|
||||||
trace dispatchOn
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
trace dispatchDefault
|
|
||||||
}
|
}
|
||||||
|
|
||||||
action=
|
var action = null
|
||||||
doShowHelp=
|
var doShowHelp = false
|
||||||
parseArgs() {
|
proc parseArgs (; ...args) {
|
||||||
for arg in "$@"; do
|
for arg in (args) {
|
||||||
case "$arg" in
|
case (arg) {
|
||||||
(--dry-run)
|
("--dry-run") {
|
||||||
DRY_RUN=1
|
setglobal DRY_RUN = true
|
||||||
;;
|
}
|
||||||
(-h|--help)
|
("--help") {
|
||||||
doShowHelp=1
|
setglobal doShowHelp = true
|
||||||
;;
|
}
|
||||||
(-v|--verbose)
|
("--verbose") {
|
||||||
VERBOSE=1
|
setglobal VERBOSITY += 1
|
||||||
;;
|
}
|
||||||
(*)
|
(else) {
|
||||||
action="$arg"
|
setglobal action = "$arg"
|
||||||
;;
|
}
|
||||||
esac
|
}
|
||||||
done
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseArgs "$@"
|
if is-main {
|
||||||
|
parseArgs (...ARGV)
|
||||||
|
|
||||||
if [ -n "$doShowHelp" ]; then
|
if (doShowHelp) {
|
||||||
showHelp
|
showHelp
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
}
|
||||||
|
|
||||||
trace dispatchToplevel
|
trace dispatchToplevel "$action"
|
||||||
handleWith unmapped
|
handleWith unmapped
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user