267 lines
9.6 KiB
Bash
Executable File
267 lines
9.6 KiB
Bash
Executable File
#!/bin/bash
|
|
#===============================================================================
|
|
# Wayshell Daemon — Module Event Manager
|
|
#===============================================================================
|
|
# Description:
|
|
# Reads module configuration, spawns zone/layout/focused event sources,
|
|
# and dispatches ON/OFF actions with configurable trailing-edge debounce.
|
|
#
|
|
# Dependencies: jq, mmsg
|
|
# Config: wayshell.conf (zone_buffer, on_delay, off_delay)
|
|
# Modules: wayshell.modules (name,on_exec,off_exec,type,args...)
|
|
#===============================================================================
|
|
|
|
CONFIG_DIR="${HOME}/.config/sdgos/wayshell"
|
|
CONFIG_FILE="${CONFIG_DIR}/wayshell.conf"
|
|
MODULES_FILE="${CONFIG_DIR}/wayshell.modules"
|
|
MODULE_DIR="${CONFIG_DIR}/modules"
|
|
|
|
exec 1>>/tmp/wayshell_daemon.log 2>&1
|
|
echo "=== Starting Wayshell Daemon ==="
|
|
|
|
CLEANED_UP=false
|
|
cleanup() {
|
|
$CLEANED_UP && return
|
|
CLEANED_UP=true
|
|
echo "Shutting down Wayshell Daemon..."
|
|
kill -- -$$ 2>/dev/null
|
|
exit 0
|
|
}
|
|
trap cleanup SIGTERM SIGINT EXIT
|
|
|
|
# --- Config ---
|
|
ON_DELAY=$(grep -oP 'on_delay=\K[0-9]+' "$CONFIG_FILE" 2>/dev/null || echo "100")
|
|
OFF_DELAY=$(grep -oP 'off_delay=\K[0-9]+' "$CONFIG_FILE" 2>/dev/null || echo "100")
|
|
echo "Config: on_delay=$ON_DELAY, off_delay=$OFF_DELAY"
|
|
|
|
# --- Storage ---
|
|
declare -A MODULES
|
|
declare -A MODULE_STATES
|
|
declare -A MODULE_ENTER_TS
|
|
declare -A MODULE_EXIT_TS
|
|
|
|
# --- Module parsing ---
|
|
parse_modules() {
|
|
echo "Parsing modules from $MODULES_FILE"
|
|
[[ -f "$MODULES_FILE" ]] || { echo "ERROR: Modules file not found"; exit 1; }
|
|
local count=0
|
|
while IFS=',' read -r name onexec offexec type args; do
|
|
name="${name// /}"
|
|
[[ "$name" =~ ^#.*$ || -z "$name" ]] && continue
|
|
MODULES["$name"]="$onexec,$offexec,$type,$args"
|
|
MODULE_STATES["$name"]="disabled"
|
|
((count++))
|
|
echo " [$count] $name ($type)"
|
|
done < "$MODULES_FILE"
|
|
echo "Total modules loaded: $count"
|
|
}
|
|
parse_modules
|
|
|
|
# --- Filter ---
|
|
modules_by_type() {
|
|
local t="$1"
|
|
for n in "${!MODULES[@]}"; do
|
|
IFS=',' read -r _ _ mt _ <<< "${MODULES[$n]}"
|
|
[[ "$mt" == "$t" ]] && echo "$n"
|
|
done
|
|
}
|
|
|
|
# --- Monitor offset cache ---
|
|
declare -A MONITOR_OFFSETS
|
|
MONITOR_CACHE_TS=0
|
|
get_monitor_offset() {
|
|
local mon="$1"; local now; now=$(date +%s)
|
|
if (( now - MONITOR_CACHE_TS > 5 )); then
|
|
local json entry name ox oy
|
|
json=$(mmsg get all-monitors 2>/dev/null)
|
|
if [[ -n "$json" ]]; then
|
|
while IFS= read -r entry; do
|
|
name=$(jq -r '.name' <<< "$entry" 2>/dev/null)
|
|
ox=$(jq -r '.x' <<< "$entry" 2>/dev/null); oy=$(jq -r '.y' <<< "$entry" 2>/dev/null)
|
|
[[ -n "$name" && "$ox" != "null" ]] && MONITOR_OFFSETS["$name"]="$ox,$oy"
|
|
done < <(jq -c '.monitors[]' <<< "$json" 2>/dev/null)
|
|
fi
|
|
MONITOR_CACHE_TS=$now
|
|
fi
|
|
echo "${MONITOR_OFFSETS[$mon]:-0,0}"
|
|
}
|
|
|
|
# --- Check debounce timers and fire actions ---
|
|
# Called every ~50ms from the main event loop.
|
|
check_fires() {
|
|
local now name onexec offexec
|
|
now=$(date +%s%3N)
|
|
|
|
# Pending ON fires
|
|
for name in "${!MODULE_ENTER_TS[@]}"; do
|
|
local st=${MODULE_STATES[$name]}
|
|
[[ "$st" != "pending_on" && "$st" != "pending_off" ]] && continue
|
|
if (( now - MODULE_ENTER_TS[$name] >= ON_DELAY )); then
|
|
if [[ "$st" == "pending_on" ]]; then
|
|
MODULE_STATES["$name"]="enabled"
|
|
IFS=',' read -r onexec _ _ _ <<< "${MODULES[$name]}"
|
|
echo "ON $name"
|
|
eval "$onexec" &
|
|
fi
|
|
unset "MODULE_ENTER_TS[$name]"
|
|
fi
|
|
done
|
|
|
|
# Pending OFF fires
|
|
for name in "${!MODULE_EXIT_TS[@]}"; do
|
|
local st=${MODULE_STATES[$name]}
|
|
[[ "$st" != "pending_off" && "$st" != "enabled" ]] && continue
|
|
if (( now - MODULE_EXIT_TS[$name] >= OFF_DELAY )); then
|
|
MODULE_STATES["$name"]="disabled"
|
|
IFS=',' read -r _ offexec _ _ <<< "${MODULES[$name]}"
|
|
echo "OFF $name"
|
|
eval "$offexec" &
|
|
unset "MODULE_EXIT_TS[$name]"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# --- Process zone events ---
|
|
process_zone_event() {
|
|
local data="$1"
|
|
|
|
# Exit event — cursor left the edge zone entirely
|
|
local state
|
|
state=$(jq -r '.state // "enter"' <<< "$data" 2>/dev/null)
|
|
if [[ "$state" == "exit" ]]; then
|
|
local now; now=$(date +%s%3N)
|
|
while IFS= read -r module_name; do
|
|
[[ -z "$module_name" ]] && continue
|
|
local st=${MODULE_STATES[$module_name]}
|
|
if [[ "$st" == "enabled" || "$st" == "pending_on" ]]; then
|
|
MODULE_EXIT_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_off"
|
|
unset "MODULE_ENTER_TS[$module_name]"
|
|
fi
|
|
done < <(modules_by_type "zone")
|
|
return
|
|
fi
|
|
|
|
# Enter event — check bounding boxes
|
|
local x y monitor
|
|
x=$(jq -r '.x' <<< "$data" 2>/dev/null)
|
|
y=$(jq -r '.y' <<< "$data" 2>/dev/null)
|
|
monitor=$(jq -r '.monitor' <<< "$data" 2>/dev/null)
|
|
[[ -z "$x" || -z "$y" || -z "$monitor" ]] && return
|
|
|
|
local x_int=${x%.*}
|
|
local y_int=${y%.*}
|
|
local offset mx my
|
|
offset=$(get_monitor_offset "$monitor")
|
|
mx="${offset%,*}"; my="${offset#*,}"
|
|
|
|
local now; now=$(date +%s%3N)
|
|
|
|
while IFS= read -r module_name; do
|
|
[[ -z "$module_name" ]] && continue
|
|
IFS=',' read -r onexec offexec _ args <<< "${MODULES[$module_name]}"
|
|
IFS=',' read -r x1 y1 x2 y2 <<< "$args"
|
|
local ax1=$(( x1 + mx )) ay1=$(( y1 + my ))
|
|
local ax2=$(( x2 + mx )) ay2=$(( y2 + my ))
|
|
local in=$(( x_int >= ax1 && x_int <= ax2 && y_int >= ay1 && y_int <= ay2 ? 1 : 0 ))
|
|
local st=${MODULE_STATES[$module_name]}
|
|
|
|
if (( in )); then
|
|
if [[ "$st" == "disabled" ]]; then
|
|
MODULE_ENTER_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_on"
|
|
elif [[ "$st" == "pending_off" ]]; then
|
|
# Re-entered before off delay — cancel OFF, stay enabled
|
|
unset "MODULE_EXIT_TS[$module_name]"
|
|
MODULE_STATES["$module_name"]="enabled"
|
|
fi
|
|
else
|
|
if [[ "$st" == "enabled" || "$st" == "pending_on" ]]; then
|
|
MODULE_EXIT_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_off"
|
|
unset "MODULE_ENTER_TS[$module_name]"
|
|
fi
|
|
fi
|
|
done < <(modules_by_type "zone")
|
|
}
|
|
|
|
# --- Layout event processing ---
|
|
process_layout_event() {
|
|
local event="$1"; local layout state event_monitor
|
|
layout=$(jq -r '.layout' <<< "$event" 2>/dev/null)
|
|
state=$(jq -r '.state' <<< "$event" 2>/dev/null)
|
|
event_monitor=$(jq -r '.monitor // empty' <<< "$event" 2>/dev/null)
|
|
[[ -z "$layout" || -z "$state" ]] && return
|
|
local now; now=$(date +%s%3N)
|
|
while IFS= read -r module_name; do
|
|
[[ -z "$module_name" ]] && continue
|
|
IFS=',' read -r onexec offexec _ args <<< "${MODULES[$module_name]}"
|
|
# args = "layout_name" or "layout_name,monitor_name"
|
|
local expected_layout="${args%%,*}"
|
|
local expected_monitor=""
|
|
[[ "$args" == *","* ]] && expected_monitor="${args#*,}"
|
|
[[ "$layout" != "$expected_layout" ]] && continue
|
|
[[ -n "$expected_monitor" && "$event_monitor" != "$expected_monitor" ]] && continue
|
|
local st=${MODULE_STATES[$module_name]}
|
|
if [[ "$state" == "active" && "$st" == "disabled" ]]; then
|
|
MODULE_ENTER_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_on"
|
|
elif [[ "$state" == "inactive" && ( "$st" == "enabled" || "$st" == "pending_on" ) ]]; then
|
|
MODULE_EXIT_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_off"
|
|
unset "MODULE_ENTER_TS[$module_name]"
|
|
fi
|
|
done < <(modules_by_type "layout")
|
|
}
|
|
|
|
# --- Focused event processing ---
|
|
process_focused_event() {
|
|
local event="$1"; local app_id state
|
|
app_id=$(jq -r '.app_id' <<< "$event" 2>/dev/null)
|
|
state=$(jq -r '.state' <<< "$event" 2>/dev/null)
|
|
[[ -z "$app_id" || -z "$state" ]] && return
|
|
local now; now=$(date +%s%3N)
|
|
while IFS= read -r module_name; do
|
|
[[ -z "$module_name" ]] && continue
|
|
IFS=',' read -r onexec offexec _ expected <<< "${MODULES[$module_name]}"
|
|
[[ "$app_id" != "$expected" ]] && continue
|
|
local st=${MODULE_STATES[$module_name]}
|
|
if [[ "$state" == "focused" && "$st" == "disabled" ]]; then
|
|
MODULE_ENTER_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_on"
|
|
elif [[ "$state" == "unfocused" && ( "$st" == "enabled" || "$st" == "pending_on" ) ]]; then
|
|
MODULE_EXIT_TS[$module_name]=$now
|
|
MODULE_STATES["$module_name"]="pending_off"
|
|
unset "MODULE_ENTER_TS[$module_name]"
|
|
fi
|
|
done < <(modules_by_type "focused")
|
|
}
|
|
|
|
# --- Start module subprocesses (auto-restarting) ---
|
|
echo "Starting modules..."
|
|
|
|
(
|
|
start_src() {
|
|
local script="$1" label="$2"
|
|
[[ -x "$script" ]] || { echo "WARNING: $script not found — $label disabled" >&2; return; }
|
|
(
|
|
while true; do "$script"; sleep 0.5; done
|
|
) | while IFS= read -r line; do echo "${label}:$line"; done &
|
|
}
|
|
start_src "$MODULE_DIR/zone.sh" "zone"
|
|
start_src "$MODULE_DIR/layout.sh" "layout"
|
|
start_src "$MODULE_DIR/focused.sh" "focused"
|
|
wait
|
|
) | while true; do
|
|
if IFS= read -t 0.05 -r line; then
|
|
source="${line%%:*}"
|
|
data="${line#*:}"
|
|
case "$source" in
|
|
zone) process_zone_event "$data" ;;
|
|
layout) process_layout_event "$data" ;;
|
|
focused) process_focused_event "$data" ;;
|
|
esac
|
|
fi
|
|
check_fires
|
|
done
|