added wayshell framework
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# Wayshell Module System Agent Instructions
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides instructions for the Wayshell Module System agent. The agent is responsible for managing the different module types and handling events.
|
||||
|
||||
## Module Types
|
||||
|
||||
### Zone Module
|
||||
|
||||
- **Initialization**: Initialize the Zone module by parsing the configuration files.
|
||||
- **Event Handling**: Handle events for entering and exiting zones.
|
||||
|
||||
### Layout Module
|
||||
|
||||
- **Initialization**: Initialize the Layout module by parsing the configuration files.
|
||||
- **Event Handling**: Handle events for activating and deactivating layouts.
|
||||
|
||||
### Focused Module
|
||||
|
||||
- **Initialization**: Initialize the Focused module by parsing the configuration files.
|
||||
- **Event Handling**: Handle events for focusing and unfocusing clients.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- **wayshell.conf**: General configuration file.
|
||||
- **wayshell.modules**: Defines the modules.
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
- **Efficient Parsing**: Optimize the parsing of configuration files to minimize CPU usage.
|
||||
- **Event Debouncing**: Implement debouncing for events to avoid unnecessary triggers.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Unit Tests**: Write unit tests for each module type.
|
||||
- **Integration Tests**: Write integration tests to ensure the modules work together correctly.
|
||||
- **Performance Tests**: Conduct performance tests to ensure the system meets the CPU usage constraints.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Wayshell Module System Design Guidelines
|
||||
|
||||
## Overview
|
||||
|
||||
The Wayshell Module System is designed to be flexible, efficient, and easy to use. This document outlines the design guidelines for the system.
|
||||
|
||||
## Module Types
|
||||
|
||||
### Zone Module
|
||||
|
||||
- **Definition**: Allows defining zones and triggers for entering and exiting these zones.
|
||||
- **Implementation**: Use efficient data structures to manage zones and triggers.
|
||||
|
||||
### Layout Module
|
||||
|
||||
- **Definition**: Allows triggering actions based on the activation or deactivation of specific layout types.
|
||||
- **Implementation**: Use event-driven architecture to handle layout changes.
|
||||
|
||||
### Focused Module
|
||||
|
||||
- **Definition**: Allows triggering actions based on focusing or unfocusing specific client types.
|
||||
- **Implementation**: Use efficient event handling to manage focus changes.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- **wayshell.conf**: General configuration file.
|
||||
- **wayshell.modules**: Defines the modules.
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
- **Efficient Parsing**: Optimize the parsing of configuration files to minimize CPU usage.
|
||||
- **Event Debouncing**: Implement debouncing for events to avoid unnecessary triggers.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Unit Tests**: Write unit tests for each module type.
|
||||
- **Integration Tests**: Write integration tests to ensure the modules work together correctly.
|
||||
- **Performance Tests**: Conduct performance tests to ensure the system meets the CPU usage constraints.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Wayshell Module System Plan
|
||||
|
||||
## Overview
|
||||
|
||||
The Wayshell Module System is designed to provide a flexible and efficient way to manage different types of modules in a Wayland environment. The system will parse configuration files and trigger actions based on specific events.
|
||||
|
||||
## Module Types
|
||||
|
||||
1. **Zone Module**: Allows defining zones and triggers for entering and exiting these zones.
|
||||
2. **Layout Module**: Allows triggering actions based on the activation or deactivation of specific layout types.
|
||||
3. **Focused Module**: Allows triggering actions based on focusing or unfocusing specific client types.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
- **wayshell.conf**: General configuration file.
|
||||
- **wayshell.modules**: Defines the modules.
|
||||
|
||||
## Design Constraints
|
||||
|
||||
- Minimize CPU usage to less than 3% when running as a daemon.
|
||||
- Avoid constantly running expensive commands.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Initial Setup
|
||||
|
||||
1. **Parse Configuration Files**: Implement parsers for `wayshell.conf` and `wayshell.modules`.
|
||||
2. **Module Initialization**: Initialize the different module types (Zone, Layout, Focused).
|
||||
|
||||
### Phase 2: Event Handling
|
||||
|
||||
1. **Zone Module**: Implement event triggers for entering and exiting zones.
|
||||
2. **Layout Module**: Implement event triggers for activating and deactivating layouts.
|
||||
3. **Focused Module**: Implement event triggers for focusing and unfocusing clients.
|
||||
|
||||
### Phase 3: Performance Optimization
|
||||
|
||||
1. **Efficient Parsing**: Optimize the parsing of configuration files to minimize CPU usage.
|
||||
2. **Event Debouncing**: Implement debouncing for events to avoid unnecessary triggers.
|
||||
|
||||
### Phase 4: Testing
|
||||
|
||||
1. **Unit Tests**: Write unit tests for each module type.
|
||||
2. **Integration Tests**: Write integration tests to ensure the modules work together correctly.
|
||||
3. **Performance Tests**: Conduct performance tests to ensure the system meets the CPU usage constraints.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Review the plan and provide feedback.
|
||||
2. Proceed with the implementation based on the approved plan.
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Css Colors
|
||||
* Generated with Matugen
|
||||
*/
|
||||
|
||||
@define-color background #0d141c;
|
||||
|
||||
@define-color error #ffb4ab;
|
||||
|
||||
@define-color error_container #93000a;
|
||||
|
||||
@define-color inverse_on_surface #2a3139;
|
||||
|
||||
@define-color inverse_primary #0062a0;
|
||||
|
||||
@define-color inverse_surface #dde3ee;
|
||||
|
||||
@define-color on_background #dde3ee;
|
||||
|
||||
@define-color on_error #690005;
|
||||
|
||||
@define-color on_error_container #ffdad6;
|
||||
|
||||
@define-color on_primary #003256;
|
||||
|
||||
@define-color on_primary_container #d0e4ff;
|
||||
|
||||
@define-color on_primary_fixed #001d35;
|
||||
|
||||
@define-color on_primary_fixed_variant #00497a;
|
||||
|
||||
@define-color on_secondary #22304c;
|
||||
|
||||
@define-color on_secondary_container #d8e2ff;
|
||||
|
||||
@define-color on_secondary_fixed #0c1b36;
|
||||
|
||||
@define-color on_secondary_fixed_variant #394664;
|
||||
|
||||
@define-color on_surface #dde3ee;
|
||||
|
||||
@define-color on_surface_variant #c0c7d2;
|
||||
|
||||
@define-color on_tertiary #282c5a;
|
||||
|
||||
@define-color on_tertiary_container #e0e0ff;
|
||||
|
||||
@define-color on_tertiary_fixed #121644;
|
||||
|
||||
@define-color on_tertiary_fixed_variant #3e4372;
|
||||
|
||||
@define-color outline #8b919b;
|
||||
|
||||
@define-color outline_variant #414750;
|
||||
|
||||
@define-color primary #9bcaff;
|
||||
|
||||
@define-color primary_container #00497a;
|
||||
|
||||
@define-color primary_fixed #d0e4ff;
|
||||
|
||||
@define-color primary_fixed_dim #9bcaff;
|
||||
|
||||
@define-color scrim #000000;
|
||||
|
||||
@define-color secondary #b8c6ea;
|
||||
|
||||
@define-color secondary_container #394664;
|
||||
|
||||
@define-color secondary_fixed #d8e2ff;
|
||||
|
||||
@define-color secondary_fixed_dim #b8c6ea;
|
||||
|
||||
@define-color shadow #000000;
|
||||
|
||||
@define-color source_color #6c8cb0;
|
||||
|
||||
@define-color surface #0d141c;
|
||||
|
||||
@define-color surface_bright #333a42;
|
||||
|
||||
@define-color surface_container #192028;
|
||||
|
||||
@define-color surface_container_high #242b33;
|
||||
|
||||
@define-color surface_container_highest #2f353e;
|
||||
|
||||
@define-color surface_container_low #151c24;
|
||||
|
||||
@define-color surface_container_lowest #080f16;
|
||||
|
||||
@define-color surface_dim #0d141c;
|
||||
|
||||
@define-color surface_tint #9bcaff;
|
||||
|
||||
@define-color surface_variant #414750;
|
||||
|
||||
@define-color tertiary #bfc2fa;
|
||||
|
||||
@define-color tertiary_container #3e4372;
|
||||
|
||||
@define-color tertiary_fixed #e0e0ff;
|
||||
|
||||
@define-color tertiary_fixed_dim #bfc2fa;
|
||||
|
||||
Executable
+45
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Focused Module — Detect client focus/unfocus events
|
||||
#===============================================================================
|
||||
# Description:
|
||||
# Subscribes to `mmsg watch focusing-client` and emits JSON when the focused
|
||||
# client changes. Tracks the previously focused app_id to emit unfocused
|
||||
# events.
|
||||
#
|
||||
# Output format (JSON lines):
|
||||
# {"app_id":"firefox","state":"focused"}
|
||||
# {"app_id":"firefox","state":"unfocused"}
|
||||
# {"app_id":"com.mitchellh.ghostty","state":"focused"}
|
||||
#
|
||||
# Dependencies:
|
||||
# - jq
|
||||
# - mmsg
|
||||
#===============================================================================
|
||||
|
||||
PREV_APP_ID=""
|
||||
|
||||
if ! command -v mmsg &>/dev/null || ! command -v jq &>/dev/null; then
|
||||
echo "focused.sh: mmsg and jq are required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while read -r line; do
|
||||
app_id=$(jq -r '.appid // empty' <<< "$line" 2>/dev/null)
|
||||
|
||||
if [[ -z "$app_id" || "$app_id" == "null" ]]; then
|
||||
if [[ -n "$PREV_APP_ID" ]]; then
|
||||
echo "{\"app_id\":\"$PREV_APP_ID\",\"state\":\"unfocused\"}"
|
||||
PREV_APP_ID=""
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "$app_id" != "$PREV_APP_ID" ]]; then
|
||||
if [[ -n "$PREV_APP_ID" ]]; then
|
||||
echo "{\"app_id\":\"$PREV_APP_ID\",\"state\":\"unfocused\"}"
|
||||
fi
|
||||
echo "{\"app_id\":\"$app_id\",\"state\":\"focused\"}"
|
||||
PREV_APP_ID="$app_id"
|
||||
fi
|
||||
done < <(mmsg watch focusing-client 2>/dev/null)
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Layout Module — Detect layout activation/deactivation events
|
||||
#===============================================================================
|
||||
# Description:
|
||||
# Subscribes to `mmsg watch all-tags` and emits JSON when any tag's layout
|
||||
# becomes active or inactive on any monitor.
|
||||
#
|
||||
# Output format (JSON lines):
|
||||
# {"layout":"M","state":"active","monitor":"DP-1","tag":1}
|
||||
# {"layout":"M","state":"inactive","monitor":"DP-1","tag":1}
|
||||
#
|
||||
# Dependencies:
|
||||
# - jq
|
||||
# - mmsg
|
||||
#===============================================================================
|
||||
|
||||
declare -A ACTIVE_LAYOUTS
|
||||
|
||||
make_key() {
|
||||
echo "${1}:${2}:${3}"
|
||||
}
|
||||
|
||||
process_tags_update() {
|
||||
local json="$1"
|
||||
local monitors monitor tag layout is_active key new_keys=""
|
||||
|
||||
monitors=$(jq -c '.all_tags[]' <<< "$json" 2>/dev/null)
|
||||
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
monitor=$(jq -r '.monitor' <<< "$entry" 2>/dev/null)
|
||||
[[ -z "$monitor" ]] && continue
|
||||
|
||||
while IFS= read -r tag_entry; do
|
||||
[[ -z "$tag_entry" ]] && continue
|
||||
tag=$(jq -r '.index' <<< "$tag_entry" 2>/dev/null)
|
||||
layout=$(jq -r '.layout' <<< "$tag_entry" 2>/dev/null)
|
||||
is_active=$(jq -r '.is_active' <<< "$tag_entry" 2>/dev/null)
|
||||
key=$(make_key "$monitor" "$tag" "$layout")
|
||||
new_keys="$new_keys $key"
|
||||
|
||||
if [[ "$is_active" == "true" ]]; then
|
||||
if [[ -z "${ACTIVE_LAYOUTS[$key]}" ]]; then
|
||||
echo "{\"layout\":\"$layout\",\"state\":\"active\",\"monitor\":\"$monitor\",\"tag\":$tag}"
|
||||
fi
|
||||
ACTIVE_LAYOUTS["$key"]="active"
|
||||
fi
|
||||
done < <(jq -c '.tags[]' <<< "$entry" 2>/dev/null)
|
||||
done <<< "$monitors"
|
||||
|
||||
for key in "${!ACTIVE_LAYOUTS[@]}"; do
|
||||
if [[ ! " $new_keys " =~ $key ]]; then
|
||||
IFS=':' read -r monitor tag layout <<< "$key"
|
||||
echo "{\"layout\":\"$layout\",\"state\":\"inactive\",\"monitor\":\"$monitor\",\"tag\":$tag}"
|
||||
unset "ACTIVE_LAYOUTS[$key]"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
if ! command -v mmsg &>/dev/null || ! command -v jq &>/dev/null; then
|
||||
echo "layout.sh: mmsg and jq are required" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while read -r line; do
|
||||
process_tags_update "$line"
|
||||
done < <(mmsg watch all-tags 2>/dev/null)
|
||||
Executable
+103
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Zone Detection Daemon
|
||||
#===============================================================================
|
||||
# Description:
|
||||
# Polls cursor position via `mmsg get cursorpos` and outputs JSON when the
|
||||
# cursor is within ZONE_BUFFER pixels of any screen edge. Emits an exit
|
||||
# event when the cursor leaves the edge zone, so the daemon knows the
|
||||
# exact departure time for proper trailing-edge debounce.
|
||||
#
|
||||
# Uses monitor-relative coordinates so edge detection works on any monitor.
|
||||
#
|
||||
# Dependencies: jq, mmsg
|
||||
#
|
||||
# Output format (JSON lines):
|
||||
# In zone: {"x":<float>,"y":<float>,"monitor":"<name>"}
|
||||
# On exit: {"state":"exit","x":<float>,"y":<float>,"monitor":"<name>"}
|
||||
#===============================================================================
|
||||
|
||||
CONFIG_FILE="${HOME}/.config/sdgos/wayshell/wayshell.conf"
|
||||
|
||||
ZONE_BUFFER=$(grep -oP 'zone_buffer=\K[0-9]+' "$CONFIG_FILE" 2>/dev/null || echo "10")
|
||||
ZONE_BUFFER=${ZONE_BUFFER:-10}
|
||||
POLL_INTERVAL=0.1
|
||||
|
||||
was_in_zone=false
|
||||
last_x=""
|
||||
last_y=""
|
||||
last_monitor=""
|
||||
|
||||
declare -A MONITOR_CACHE
|
||||
MONITOR_CACHE_AGE=0
|
||||
|
||||
get_monitor_info() {
|
||||
local mon="$1"
|
||||
local now
|
||||
now=$(date +%s)
|
||||
|
||||
if (( now - MONITOR_CACHE_AGE > 5 )) || [[ -z "${MONITOR_CACHE[$mon]}" ]]; then
|
||||
local json entry name w h 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)
|
||||
w=$(jq -r '.width' <<< "$entry" 2>/dev/null)
|
||||
h=$(jq -r '.height' <<< "$entry" 2>/dev/null)
|
||||
ox=$(jq -r '.x' <<< "$entry" 2>/dev/null)
|
||||
oy=$(jq -r '.y' <<< "$entry" 2>/dev/null)
|
||||
if [[ -n "$name" && "$w" != "null" && "$h" != "null" ]]; then
|
||||
MONITOR_CACHE["$name"]="$w $h ${ox:-0} ${oy:-0}"
|
||||
fi
|
||||
done < <(jq -c '.monitors[]' <<< "$json" 2>/dev/null)
|
||||
fi
|
||||
MONITOR_CACHE_AGE=$now
|
||||
fi
|
||||
|
||||
echo "${MONITOR_CACHE[$mon]:-1920 1080 0 0}"
|
||||
}
|
||||
|
||||
while true; do
|
||||
cursor_info=$(mmsg get cursorpos 2>/dev/null)
|
||||
if [[ -z "$cursor_info" ]]; then
|
||||
sleep "$POLL_INTERVAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
x=$(jq -r '.x' <<< "$cursor_info" 2>/dev/null)
|
||||
y=$(jq -r '.y' <<< "$cursor_info" 2>/dev/null)
|
||||
monitor=$(jq -r '.monitor' <<< "$cursor_info" 2>/dev/null)
|
||||
|
||||
if [[ -z "$x" || -z "$y" || -z "$monitor" ]]; then
|
||||
sleep "$POLL_INTERVAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
read -r width height m_x m_y <<< "$(get_monitor_info "$monitor")"
|
||||
if [[ -z "$width" || -z "$height" ]]; then
|
||||
sleep "$POLL_INTERVAL"
|
||||
continue
|
||||
fi
|
||||
|
||||
local_x=$(( ${x%.*} - m_x ))
|
||||
local_y=$(( ${y%.*} - m_y ))
|
||||
|
||||
in_zone=false
|
||||
(( local_x < ZONE_BUFFER )) && in_zone=true
|
||||
(( local_x > width - ZONE_BUFFER )) && in_zone=true
|
||||
(( local_y < ZONE_BUFFER )) && in_zone=true
|
||||
(( local_y > height - ZONE_BUFFER )) && in_zone=true
|
||||
|
||||
if $in_zone && ! $was_in_zone; then
|
||||
echo "$cursor_info"
|
||||
elif ! $in_zone && $was_in_zone; then
|
||||
echo "{\"state\":\"exit\",\"x\":$last_x,\"y\":$last_y,\"monitor\":\"$last_monitor\"}"
|
||||
fi
|
||||
|
||||
was_in_zone=$in_zone
|
||||
last_x=$x
|
||||
last_y=$y
|
||||
last_monitor=$monitor
|
||||
|
||||
sleep "$POLL_INTERVAL"
|
||||
done
|
||||
@@ -0,0 +1,52 @@
|
||||
@import "./colors.css";
|
||||
|
||||
* {
|
||||
font-family: "JetBrainsMono Nerd Font";
|
||||
font-weight: bold;
|
||||
min-height: 0;
|
||||
/* set font-size to 100% if font scaling is set to 1.00 using nwg-look */
|
||||
font-size: 97%;
|
||||
font-feature-settings: '"zero", "ss01", "ss02", "ss03", "ss04", "ss05", "cv31"';
|
||||
border: 0px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 2px;
|
||||
padding-left: 4px;
|
||||
background-color: @surface;
|
||||
animation: gradient_f 20s ease-in infinite;
|
||||
transition: all 0.3s cubic-bezier(.55,-0.68,.48,1.682);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#custom-daemon,
|
||||
#custom-window1,
|
||||
#custom-window2,
|
||||
#custom-window3,
|
||||
#custom-window4,
|
||||
#custom-window5,
|
||||
#custom-window6,
|
||||
#custom-window7,
|
||||
#custom-window8,
|
||||
#custom-window9,
|
||||
#custom-window10,
|
||||
#custom-button-term,
|
||||
#custom-button-files,
|
||||
#custom-button-next,
|
||||
#custom-button-prev,
|
||||
#custom-button-zoomin,
|
||||
#custom-button-zoomout,
|
||||
#custom-button-fit {
|
||||
background-color: @surface_container;
|
||||
color: @primary;
|
||||
border: 0px;
|
||||
border-style: solid;
|
||||
border-color: white;
|
||||
border-radius: 10px;
|
||||
padding-top: 4px;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
modules:
|
||||
- zone
|
||||
- layout
|
||||
-
|
||||
|
||||
|
||||
format:
|
||||
|
||||
name=exec,module,moduleargs
|
||||
|
||||
eg
|
||||
|
||||
monocle=waybar -c blah -s blah,layout,m
|
||||
|
||||
leftzone=waybar -c blah2 -s blah2,zone,0|330|40|660
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Test runner — runs all Wayshell tests
|
||||
#===============================================================================
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ALL_PASS=0
|
||||
ALL_FAIL=0
|
||||
|
||||
for test_script in "$DIR"/test_*.sh; do
|
||||
[[ "$(basename "$test_script")" == "run_all.sh" ]] && continue
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Running: $(basename "$test_script")"
|
||||
echo "=========================================="
|
||||
if bash "$test_script"; then
|
||||
echo "SUITE PASSED"
|
||||
else
|
||||
echo "SUITE FAILED"
|
||||
((ALL_FAIL++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Summary: $ALL_PASS suites passed, $ALL_FAIL suites failed"
|
||||
echo "=========================================="
|
||||
exit $ALL_FAIL
|
||||
Executable
+63
@@ -0,0 +1,63 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Unit tests: Focused module event processing logic
|
||||
#===============================================================================
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
# Simulated focused module matching logic (same as wayshell.sh)
|
||||
check_focused() {
|
||||
local event_app="$1" event_state="$2" expected_app="$3"
|
||||
[[ "$event_app" == "$expected_app" ]] && echo "$event_state"
|
||||
}
|
||||
|
||||
test_focused_match() {
|
||||
local result
|
||||
result=$(check_focused "firefox" "focused" "firefox")
|
||||
[[ "$result" == "focused" ]] || { echo "FAIL: focused match"; return 1; }
|
||||
echo "PASS: focused match"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_unfocused_match() {
|
||||
local result
|
||||
result=$(check_focused "firefox" "unfocused" "firefox")
|
||||
[[ "$result" == "unfocused" ]] || { echo "FAIL: unfocused match"; return 1; }
|
||||
echo "PASS: unfocused match"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_app_id_mismatch() {
|
||||
local result
|
||||
result=$(check_focused "firefox" "focused" "com.mitchellh.ghostty")
|
||||
[[ -z "$result" ]] || { echo "FAIL: mismatch should be empty"; return 1; }
|
||||
echo "PASS: no match when app_id differs"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_different_apps() {
|
||||
local firefox_result ghostty_result
|
||||
firefox_result=$(check_focused "firefox" "focused" "firefox")
|
||||
ghostty_result=$(check_focused "firefox" "focused" "ghostty")
|
||||
[[ -n "$firefox_result" && -z "$ghostty_result" ]] || { echo "FAIL: different apps"; return 1; }
|
||||
echo "PASS: different apps match correctly"
|
||||
return 0
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
if "$test_name"; then
|
||||
((PASS++))
|
||||
else
|
||||
((FAIL++))
|
||||
fi
|
||||
}
|
||||
|
||||
run_test test_focused_match
|
||||
run_test test_unfocused_match
|
||||
run_test test_app_id_mismatch
|
||||
run_test test_different_apps
|
||||
|
||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||
exit $FAIL
|
||||
Executable
+67
@@ -0,0 +1,67 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Unit tests: Layout module event processing logic
|
||||
#===============================================================================
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
# Simulated layout module matching logic (same as wayshell.sh)
|
||||
check_layout() {
|
||||
local event_layout="$1" event_state="$2" expected_layout="$3"
|
||||
[[ "$event_layout" == "$expected_layout" ]] && echo "$event_state"
|
||||
}
|
||||
|
||||
test_active_match() {
|
||||
local result
|
||||
result=$(check_layout "M" "active" "M")
|
||||
[[ "$result" == "active" ]] || { echo "FAIL: active match"; return 1; }
|
||||
echo "PASS: active match"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_inactive_match() {
|
||||
local result
|
||||
result=$(check_layout "M" "inactive" "M")
|
||||
[[ "$result" == "inactive" ]] || { echo "FAIL: inactive match"; return 1; }
|
||||
echo "PASS: inactive match"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_no_match() {
|
||||
local result
|
||||
result=$(check_layout "DW" "active" "M")
|
||||
[[ -z "$result" ]] || { echo "FAIL: no match should be empty"; return 1; }
|
||||
echo "PASS: no match when layout differs"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_multiple_layouts() {
|
||||
local matched=""
|
||||
for pair in "M:active" "DW:inactive"; do
|
||||
local layout="${pair%%:*}" state="${pair##*:}"
|
||||
local result
|
||||
result=$(check_layout "$layout" "$state" "M")
|
||||
[[ -n "$result" ]] && matched="$matched $layout=$result"
|
||||
done
|
||||
[[ "$matched" == " M=active" ]] || { echo "FAIL: multiple layouts (got: $matched)"; return 1; }
|
||||
echo "PASS: multiple layouts only match correct one"
|
||||
return 0
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
if "$test_name"; then
|
||||
((PASS++))
|
||||
else
|
||||
((FAIL++))
|
||||
fi
|
||||
}
|
||||
|
||||
run_test test_active_match
|
||||
run_test test_inactive_match
|
||||
run_test test_no_match
|
||||
run_test test_multiple_layouts
|
||||
|
||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||
exit $FAIL
|
||||
Executable
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Unit tests: Module parsing
|
||||
#===============================================================================
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
test_parse_zone_module() {
|
||||
local line="test_zone,notify-send on,notify-send off,zone,0,0,100,100"
|
||||
IFS=',' read -r name onexec offexec type args <<< "$line"
|
||||
[[ "$name" == "test_zone" ]] || { echo "FAIL: name"; return 1; }
|
||||
[[ "$onexec" == "notify-send on" ]] || { echo "FAIL: onexec"; return 1; }
|
||||
[[ "$offexec" == "notify-send off" ]] || { echo "FAIL: offexec"; return 1; }
|
||||
[[ "$type" == "zone" ]] || { echo "FAIL: type=$type"; return 1; }
|
||||
[[ "$args" == "0,0,100,100" ]] || { echo "FAIL: args=$args"; return 1; }
|
||||
echo "PASS: parse zone module"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_parse_layout_module() {
|
||||
local line="monocle_mod,echo on,echo off,layout,M"
|
||||
IFS=',' read -r name onexec offexec type args <<< "$line"
|
||||
[[ "$name" == "monocle_mod" ]] || { echo "FAIL: name"; return 1; }
|
||||
[[ "$type" == "layout" ]] || { echo "FAIL: type=$type"; return 1; }
|
||||
[[ "$args" == "M" ]] || { echo "FAIL: args=$args"; return 1; }
|
||||
echo "PASS: parse layout module"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_parse_focused_module() {
|
||||
local line="browser_focus,echo on,echo off,focused,firefox"
|
||||
IFS=',' read -r name onexec offexec type args <<< "$line"
|
||||
[[ "$name" == "browser_focus" ]] || { echo "FAIL: name"; return 1; }
|
||||
[[ "$type" == "focused" ]] || { echo "FAIL: type=$type"; return 1; }
|
||||
[[ "$args" == "firefox" ]] || { echo "FAIL: args=$args"; return 1; }
|
||||
echo "PASS: parse focused module"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_skip_comments() {
|
||||
local line="# this is a comment"
|
||||
local name
|
||||
name=$(echo "$line" | grep -oP '^[^#,]+' || echo "")
|
||||
[[ -z "$name" ]] || { echo "FAIL: comment not skipped"; return 1; }
|
||||
echo "PASS: skip comments"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_skip_empty() {
|
||||
local line=""
|
||||
[[ -z "$line" ]] || { echo "FAIL: empty not skipped"; return 1; }
|
||||
echo "PASS: skip empty lines"
|
||||
return 0
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
if "$test_name"; then
|
||||
((PASS++))
|
||||
else
|
||||
((FAIL++))
|
||||
fi
|
||||
}
|
||||
|
||||
run_test test_parse_zone_module
|
||||
run_test test_parse_layout_module
|
||||
run_test test_parse_focused_module
|
||||
run_test test_skip_comments
|
||||
run_test test_skip_empty
|
||||
|
||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||
exit $FAIL
|
||||
Executable
+65
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
#===============================================================================
|
||||
# Unit tests: Zone module event processing logic
|
||||
#===============================================================================
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
# Simulated zone check logic (same as wayshell.sh)
|
||||
check_zone() {
|
||||
local x="$1" y="$2" x1="$3" y1="$4" x2="$5" y2="$6"
|
||||
(( x >= x1 && x <= x2 && y >= y1 && y <= y2 ))
|
||||
}
|
||||
|
||||
test_within_zone() {
|
||||
check_zone 5 5 0 0 30 1080 && echo "PASS: within zone" && return 0
|
||||
echo "FAIL: within zone"
|
||||
return 1
|
||||
}
|
||||
|
||||
test_outside_zone() {
|
||||
check_zone 50 50 0 0 30 1080 && echo "FAIL: outside zone" && return 1
|
||||
echo "PASS: outside zone"
|
||||
return 0
|
||||
}
|
||||
|
||||
test_boundary_top_left() {
|
||||
check_zone 0 0 0 0 30 1080 && echo "PASS: boundary top-left" && return 0
|
||||
echo "FAIL: boundary top-left"
|
||||
return 1
|
||||
}
|
||||
|
||||
test_boundary_bottom_right() {
|
||||
check_zone 30 1080 0 0 30 1080 && echo "PASS: boundary bottom-right" && return 0
|
||||
echo "FAIL: boundary bottom-right"
|
||||
return 1
|
||||
}
|
||||
|
||||
test_multiple_zones() {
|
||||
# Only left zone should match
|
||||
local in_left=false in_right=false
|
||||
check_zone 5 500 0 0 30 1080 && in_left=true
|
||||
check_zone 5 500 1890 0 1920 1080 && in_right=true
|
||||
$in_left && ! $in_right && echo "PASS: multiple zones" && return 0
|
||||
echo "FAIL: multiple zones"
|
||||
return 1
|
||||
}
|
||||
|
||||
run_test() {
|
||||
local test_name="$1"
|
||||
if "$test_name"; then
|
||||
((PASS++))
|
||||
else
|
||||
((FAIL++))
|
||||
fi
|
||||
}
|
||||
|
||||
run_test test_within_zone
|
||||
run_test test_outside_zone
|
||||
run_test test_boundary_top_left
|
||||
run_test test_boundary_bottom_right
|
||||
run_test test_multiple_zones
|
||||
|
||||
echo "=== Results: $PASS passed, $FAIL failed ==="
|
||||
exit $FAIL
|
||||
@@ -0,0 +1,8 @@
|
||||
# Wayshell daemon configuration
|
||||
# zone_buffer — distance in pixels from screen edge that defines a "zone" (default: 10)
|
||||
# on_delay — debounce delay in ms before triggering ON action (default: 100)
|
||||
# off_delay — debounce delay in ms before triggering OFF action (default: 100)
|
||||
|
||||
zone_buffer=30
|
||||
on_delay=500
|
||||
off_delay=500
|
||||
@@ -0,0 +1,30 @@
|
||||
# Wayshell module definitions
|
||||
# Format: name,on_exec,off_exec,type,args...
|
||||
# name — unique identifier for the module
|
||||
# on_exec — command executed when the condition becomes true
|
||||
# off_exec — command executed when the condition becomes false
|
||||
# type — module type: zone | layout | focused
|
||||
#
|
||||
# Zone args: x1,y1,x2,y2 (screen-space bounding box)
|
||||
# Layout args: layout_name[,monitor]
|
||||
# layout_name: M for monocle, DW for dwindle, etc.
|
||||
# monitor: optional — if set, only triggers for that display
|
||||
# Focused args: app_id (e.g. firefox, com.mitchellh.ghostty)
|
||||
|
||||
# Zone modules — trigger when cursor enters/exits screen edges
|
||||
zone_left,notify-send "left show",notify-send "left hide",zone,0,0,30,1080
|
||||
zone_right,notify-send "right show",notify-send "right hide",zone,1890,0,1920,1080
|
||||
zone_top,notify-send "top show",notify-send "top hide",zone,0,0,1920,30
|
||||
zone_bottom,notify-send "bottom show",notify-send "bottom hide",zone,0,1050,1920,1080
|
||||
|
||||
# Layout modules — trigger when a layout is activated/deactivated on any tag
|
||||
# Append a monitor name after the layout to restrict to one display:
|
||||
# monocle_dp1,...,layout,M,DP-1
|
||||
#monocle,notify-send "monocle on HDMI",notify-send "monocle off HDMI",layout,M,HDMI-A-1
|
||||
#monocle_dp1,notify-send "monocle on DP-1",notify-send "monocle off DP-1",layout,M,DP-1
|
||||
#monocle_dp3,notify-send "monocle on DP-3",notify-send "monocle off DP-2",layout,M,DP-3
|
||||
#dwindle,notify-send "dwindle on",notify-send "dwindle off",layout,DW,HDMI-A-1
|
||||
|
||||
# Focused modules — trigger when specific app gains/loses focus
|
||||
#term_focused,notify-send "terminal focused",notify-send "terminal unfocused",focused,com.mitchellh.ghostty
|
||||
#browser_focused,notify-send "browser focused",notify-send "browser unfocused",focused,firefox
|
||||
Executable
+266
@@ -0,0 +1,266 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user