Finally tracked down and came up with a work around for that weird bug. Sometimes i3's input gets into a weird state, I think because some windows either don't close properly or move the focus somewhere it shouldn't be. Either way it breaks keyboard input completely even though i3 itself is fine. Added a watchdog to check for this condition and reset i3 if it happens. Sure, your desktop may pop up but it beats the hell out of a frozen GUI.
This commit is contained in:
4
i38.sh
4
i38.sh
@@ -1149,6 +1149,10 @@ $(if [[ $sounds -eq 0 ]]; then
|
|||||||
echo "exec_always --no-startup-id ${i3Path}/scripts/sound.py"
|
echo "exec_always --no-startup-id ${i3Path}/scripts/sound.py"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
# i3 watchdog - monitors i3 responsiveness and auto-recovers from freezes
|
||||||
|
if [[ $usingSway -ne 0 ]]; then
|
||||||
|
echo "exec_always --no-startup-id ${i3Path}/scripts/i3_watchdog.sh"
|
||||||
|
fi
|
||||||
# xbrlapi is X11-only, skip on Sway/Wayland
|
# xbrlapi is X11-only, skip on Sway/Wayland
|
||||||
if [[ $brlapi -eq 0 ]] && [[ $usingSway -ne 0 ]]; then
|
if [[ $brlapi -eq 0 ]] && [[ $usingSway -ne 0 ]]; then
|
||||||
echo 'exec --no-startup-id xbrlapi --quiet'
|
echo 'exec --no-startup-id xbrlapi --quiet'
|
||||||
|
|||||||
144
scripts/i3_watchdog.sh
Executable file
144
scripts/i3_watchdog.sh
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# I38 i3 Watchdog - Monitors i3 responsiveness and auto-restarts on lockup
|
||||||
|
# This script runs in the background and checks if i3 is responding to commands
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
checkInterval=3 # Check every 3 seconds
|
||||||
|
timeoutSeconds=2 # Consider i3 locked if it doesn't respond within 2 seconds
|
||||||
|
pidFile="${HOME}/.config/i3/i3_watchdog.pid"
|
||||||
|
|
||||||
|
log_message() {
|
||||||
|
echo "[i3-watchdog] $*"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Kill any existing watchdog instances
|
||||||
|
if [[ -f "$pidFile" ]]; then
|
||||||
|
oldPid=$(cat "$pidFile")
|
||||||
|
if kill -0 "$oldPid" 2>/dev/null; then
|
||||||
|
log_message "Killing old watchdog instance (PID: $oldPid)"
|
||||||
|
kill "$oldPid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Write our PID
|
||||||
|
echo "$$" > "$pidFile"
|
||||||
|
|
||||||
|
log_message "I38 Watchdog started (PID: $$)"
|
||||||
|
|
||||||
|
lastSuccessTime=$(date +%s)
|
||||||
|
consecutiveFailures=0
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sleep "$checkInterval"
|
||||||
|
|
||||||
|
# Check if i3 is still running
|
||||||
|
if ! pgrep -x i3 > /dev/null; then
|
||||||
|
log_message "i3 process not found - exiting watchdog"
|
||||||
|
rm -f "$pidFile"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to communicate with i3 IPC AND check X focus
|
||||||
|
ipcWorks=0
|
||||||
|
focusWorks=0
|
||||||
|
|
||||||
|
if timeout "$timeoutSeconds" i3-msg -t get_version > /dev/null 2>&1; then
|
||||||
|
ipcWorks=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Also check if X focus is working
|
||||||
|
if DISPLAY=:0 timeout 1 xdotool getwindowfocus > /dev/null 2>&1; then
|
||||||
|
focusWorks=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Both must work for system to be healthy
|
||||||
|
if [[ $ipcWorks -eq 1 ]] && [[ $focusWorks -eq 1 ]]; then
|
||||||
|
# Success - reset counters
|
||||||
|
lastSuccessTime=$(date +%s)
|
||||||
|
consecutiveFailures=0
|
||||||
|
else
|
||||||
|
# Failed - increment failure counter
|
||||||
|
((consecutiveFailures++))
|
||||||
|
currentTime=$(date +%s)
|
||||||
|
timeSinceSuccess=$((currentTime - lastSuccessTime))
|
||||||
|
|
||||||
|
log_message "Health check failed - IPC:$ipcWorks Focus:$focusWorks (failure #$consecutiveFailures, ${timeSinceSuccess}s since last success)"
|
||||||
|
|
||||||
|
# If we've had 2+ consecutive failures, i3 is likely frozen
|
||||||
|
if [[ $consecutiveFailures -ge 2 ]]; then
|
||||||
|
log_message "CRITICAL: i3 IPC frozen for ${timeSinceSuccess}s - running diagnostics"
|
||||||
|
|
||||||
|
# Check what processes exist
|
||||||
|
i3Pid=$(pgrep -x i3)
|
||||||
|
soundPid=$(pgrep -f "scripts/sound.py")
|
||||||
|
log_message "Process check: i3=$i3Pid sound.py=$soundPid"
|
||||||
|
|
||||||
|
# Check i3 process state
|
||||||
|
if [[ -n "$i3Pid" ]]; then
|
||||||
|
i3State=$(ps -o stat= -p "$i3Pid" 2>/dev/null)
|
||||||
|
log_message "i3 process state: $i3State"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check sound.py process state
|
||||||
|
if [[ -n "$soundPid" ]]; then
|
||||||
|
soundState=$(ps -o stat= -p "$soundPid" 2>/dev/null)
|
||||||
|
log_message "sound.py process state: $soundState"
|
||||||
|
|
||||||
|
# Check what sound.py is doing
|
||||||
|
soundStack=$(cat "/proc/$soundPid/stack" 2>/dev/null | head -5)
|
||||||
|
log_message "sound.py kernel stack: $soundStack"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to get i3 socket info
|
||||||
|
i3Socket=$(i3 --get-socketpath 2>/dev/null)
|
||||||
|
log_message "i3 socket path: $i3Socket"
|
||||||
|
|
||||||
|
if [[ -n "$i3Socket" ]] && [[ -S "$i3Socket" ]]; then
|
||||||
|
socketInfo=$(ls -l "$i3Socket" 2>/dev/null)
|
||||||
|
log_message "Socket exists: $socketInfo"
|
||||||
|
else
|
||||||
|
log_message "Socket does not exist or is not a socket!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if this is a focus issue (Wine keyboard grab bug)
|
||||||
|
focusCheck=$(DISPLAY=:0 xdotool getwindowfocus 2>&1)
|
||||||
|
log_message "Focus check result: $focusCheck"
|
||||||
|
|
||||||
|
# Try to reset focus to i3
|
||||||
|
log_message "Attempting to reset X focus..."
|
||||||
|
DISPLAY=:0 xdotool key --clearmodifiers Super_L 2>/dev/null
|
||||||
|
sleep 0.5
|
||||||
|
|
||||||
|
# Try to focus on i3's root window
|
||||||
|
i3RootWindow=$(DISPLAY=:0 xdotool search --class "i3" | head -1)
|
||||||
|
if [[ -n "$i3RootWindow" ]]; then
|
||||||
|
log_message "Focusing i3 root window: $i3RootWindow"
|
||||||
|
DISPLAY=:0 xdotool windowfocus "$i3RootWindow" 2>/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try i3-msg to focus something
|
||||||
|
log_message "Using i3-msg to focus workspace..."
|
||||||
|
DISPLAY=:0 i3-msg workspace number 1 >/dev/null 2>&1
|
||||||
|
sleep 0.5
|
||||||
|
DISPLAY=:0 i3-msg focus output primary >/dev/null 2>&1
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Check if focus is actually fixed now
|
||||||
|
if DISPLAY=:0 timeout 1 xdotool getwindowfocus >/dev/null 2>&1; then
|
||||||
|
log_message "Focus recovery successful!"
|
||||||
|
else
|
||||||
|
log_message "Focus still broken - restarting i3..."
|
||||||
|
DISPLAY=:0 i3-msg -t run_command restart
|
||||||
|
log_message "Restart command sent"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for restart to complete
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# Reset counters
|
||||||
|
lastSuccessTime=$(date +%s)
|
||||||
|
consecutiveFailures=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
@@ -50,13 +50,20 @@ def on_new_window(self,i3):
|
|||||||
|
|
||||||
def on_close_window(self,i3):
|
def on_close_window(self,i3):
|
||||||
try:
|
try:
|
||||||
windowName = getattr(i3.container, 'name', None)
|
# Get container early - if this fails, bail immediately
|
||||||
windowClass = getattr(i3.container, 'window_class', None)
|
container = getattr(i3, 'container', None)
|
||||||
|
if container is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Fast checks with immediate bailout on any issue
|
||||||
|
windowName = getattr(container, 'name', None)
|
||||||
|
windowClass = getattr(container, 'window_class', None)
|
||||||
|
|
||||||
# Skip sound only for notification daemon (it has its own sound)
|
# Skip sound only for notification daemon (it has its own sound)
|
||||||
if windowName != 'xfce4-notifyd' and windowClass != 'xfce4-notifyd':
|
if windowName != 'xfce4-notifyd' and windowClass != 'xfce4-notifyd':
|
||||||
play_sound_async('play -nqV0 synth .25 sin 880:440 sin 920:480 remix - norm -3 pitch -500')
|
play_sound_async('play -nqV0 synth .25 sin 880:440 sin 920:480 remix - norm -3 pitch -500')
|
||||||
except Exception:
|
except Exception:
|
||||||
# Silently ignore errors to prevent blocking i3
|
# Silently ignore any errors - better no sound than blocking i3
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def on_mode(self,event):
|
def on_mode(self,event):
|
||||||
|
|||||||
Reference in New Issue
Block a user