Files
dotfiles/dot_config/niri/scripts/executable_niri-floating-sidebar.sh
T
Prad NukalaandClaude Fable 5 2142bdf9cb feat(arch-layer,scripts): linux-gated desktop configs + package install scripts
niri (exact_config.d, scripts), noctalia (plugins excluded — runtime
state with embedded token), fontconfig, mimeapps.list,
niri-dynamic-workspaces, systemd user unit files (enable-state not
managed; only nirinit enabled by run_once), niri-launch vendored from
/usr/bin (unowned hand-written script, shellcheck warnings pre-existing).

Scripts: packages (pacman --needed + paru, brew on darwin, root-safe,
keyed on packages.yaml hash), gh extensions (idempotent), user services
(linux), post (tpm clone, bat cache, fc-cache). All shellcheck-clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-09 14:56:09 -04:00

368 lines
11 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
#=============================================================================
# Niri Sidebar Manager
#=============================================================================
# This script manages floating windows in the Niri window manager,
# organizing them into a toggleable sidebar.
#=============================================================================
### --- DIMENSION SETTINGS --- ###
readonly WIN_W=550 # Floating window width
readonly BASE_SLOTS=4 # Default number of slots in the sidebar
readonly GAP_BETWEEN=15 # Gap between windows
readonly BOTTOM_MARGIN=50 # Bottom screen margin
readonly TOP_MARGIN=30 # Top screen margin
readonly SIDE_MARGIN=20 # Side screen margin
readonly VISIBLE_SLIVER=70 # Pixels visible when sidebar is hidden
### --- STATE FILES --- ###
# Get current workspace ID
WORKSPACE_ID=$(niri msg --json workspaces | jq -r '.[] | select(.is_focused).id')
# File paths for state tracking
readonly FLAG_FILE="/tmp/niri.toggle.${WORKSPACE_ID}.floating"
readonly STATE_FILE="/tmp/niri.toggle.${WORKSPACE_ID}.state"
readonly ORDER_STATE_FILE="/tmp/niri.toggle.${WORKSPACE_ID}.order"
readonly HIDE_STATE_FILE="/tmp/niri.toggle.${WORKSPACE_ID}.hide_state"
### --- SCREEN INFO --- ###
readonly SCREEN_W=$(niri msg --json outputs | jq -r 'to_entries[0].value.logical.width')
readonly SCREEN_H=$(niri msg --json outputs | jq -r 'to_entries[0].value.logical.height')
#=============================================================================
# FUNCTION: get_focused_window
# Description: Returns JSON info of the currently focused window
#=============================================================================
get_focused_window() {
niri msg --json windows | jq -r '.[] | select(.is_focused)'
}
#=============================================================================
# FUNCTION: ensure_files_exist
# Description: Creates necessary state files if they don't exist
#=============================================================================
ensure_files_exist() {
touch "$FLAG_FILE" "$STATE_FILE" "$HIDE_STATE_FILE" "$ORDER_STATE_FILE"
if [ ! -s "$HIDE_STATE_FILE" ]; then
echo "visible" >"$HIDE_STATE_FILE"
fi
if [ ! -s "$ORDER_STATE_FILE" ]; then
echo "bottom-up" >"$ORDER_STATE_FILE"
fi
}
#=============================================================================
# FUNCTION: get_floating_windows
# Description: Gets a list of floating window IDs in the current workspace
#=============================================================================
get_floating_windows() {
niri msg --json windows |
jq -r ".[] | select(.is_floating==true and .workspace_id==$WORKSPACE_ID) | .id"
}
#=============================================================================
# FUNCTION: cleanup_state_file
# Description: Removes closed windows from the state file
# Parameters:
# $1 - List of currently active floating windows
#=============================================================================
cleanup_state_file() {
local actual_floating="$1"
if [ ! -s "$STATE_FILE" ]; then
return
fi
local tmp_state
tmp_state=$(mktemp)
while IFS= read -r line; do
local id_in_file
id_in_file=$(echo "$line" | cut -d: -f1)
if echo "$actual_floating" | grep -q "^${id_in_file}$"; then
echo "$line" >>"$tmp_state"
fi
done <"$STATE_FILE"
mv "$tmp_state" "$STATE_FILE"
}
#=============================================================================
# FUNCTION: calculate_window_dimensions
# Description: Calculates dynamic window height and X position
# Parameters:
# $1 - Window count
# Returns: Sets WIN_H and X global variables
#=============================================================================
calculate_window_dimensions() {
local count="$1"
local slots="$BASE_SLOTS"
# Increase slots if window count exceeds default
if [ "$count" -gt "$BASE_SLOTS" ]; then
slots="$count"
fi
# Calculate available vertical space
local total_gaps_space=$(((slots - 1) * GAP_BETWEEN))
local available_h=$((SCREEN_H - TOP_MARGIN - BOTTOM_MARGIN - total_gaps_space))
# Export calculated dimensions
WIN_H=$((available_h / slots))
X=$((SCREEN_W - WIN_W - SIDE_MARGIN))
}
#=============================================================================
# FUNCTION: position_window
# Description: Moves and resizes a specific window
# Parameters:
# $1 - Window ID
# $2 - X position
# $3 - Y position
#=============================================================================
position_window() {
local id="$1"
local x="$2"
local y="$3"
niri msg action set-window-width "$WIN_W" --id "$id" 2>/dev/null || true
niri msg action set-window-height "$WIN_H" --id "$id" 2>/dev/null || true
niri msg action move-floating-window --id "$id" --x "$x" --y "$y" 2>/dev/null || true
}
#=============================================================================
# FUNCTION: reorder_sidebar
# Description: Syncs state and updates all window positions on screen
#=============================================================================
reorder_sidebar() {
local actual_floating
actual_floating=$(get_floating_windows)
cleanup_state_file "$actual_floating"
local sorted_ids
sorted_ids=$(grep -Ff <(echo "$actual_floating") "$STATE_FILE" 2>/dev/null | cut -d: -f1 || true)
local count
count=$(echo "$sorted_ids" | grep -c . || echo 0)
if [ "$count" -eq 0 ]; then
>"$FLAG_FILE"
echo "visible" >"$HIDE_STATE_FILE"
return
fi
calculate_window_dimensions "$count"
local order
order=$(cat "$ORDER_STATE_FILE" 2>/dev/null || echo "bottom-up")
>"$FLAG_FILE"
local index=0
while IFS= read -r id; do
[ -z "$id" ] && continue
local pos_y
if [ "$order" = "top-down" ]; then
pos_y=$((TOP_MARGIN + index * (WIN_H + GAP_BETWEEN)))
else
pos_y=$((SCREEN_H - BOTTOM_MARGIN - WIN_H - index * (WIN_H + GAP_BETWEEN)))
fi
position_window "$id" "$X" "$pos_y"
echo "${id}:${X}:${pos_y}" >>"$FLAG_FILE"
index=$((index + 1))
done <<<"$sorted_ids"
echo "visible" >"$HIDE_STATE_FILE"
}
#=============================================================================
# FUNCTION: toggle_float
# Description: Toggles focused window between sidebar and tiling mode
#=============================================================================
toggle_float() {
local focused_json
focused_json=$(get_focused_window)
local focused_id
focused_id=$(echo "$focused_json" | jq -r '.id')
if [ -z "$focused_id" ] || [ "$focused_id" = "null" ]; then
echo "Error: No focused window"
return 1
fi
if grep -q "^$focused_id:" "$STATE_FILE" 2>/dev/null; then
# Restore to tiling mode
local old_dim
old_dim=$(grep "^$focused_id:" "$STATE_FILE")
local old_w old_h
old_w=$(echo "$old_dim" | cut -d: -f2)
old_h=$(echo "$old_dim" | cut -d: -f3)
# Remove from state
sed -i "/^$focused_id:/d" "$STATE_FILE"
# Action: Move to tiling
niri msg action move-window-to-tiling
# Restore original dimensions
[ "$old_w" != "null" ] && niri msg action set-column-width "$old_w" 2>/dev/null || true
[ "$old_h" != "null" ] && niri msg action set-window-height "$old_h" 2>/dev/null || true
reorder_sidebar
echo "✓ Window removed from sidebar"
else
# Add to sidebar
local orig_w orig_h
orig_w=$(echo "$focused_json" | jq -r '.layout.window_size[0] // 800')
orig_h=$(echo "$focused_json" | jq -r '.layout.window_size[1] // 600')
# Save original size
echo "$focused_id:$orig_w:$orig_h" >>"$STATE_FILE"
# Action: Move to floating
niri msg action move-window-to-floating
# Refresh sidebar
reorder_sidebar
# Keep focus on the window
niri msg action focus-window --id "$focused_id" 2>/dev/null || true
echo "✓ Window added to sidebar"
fi
}
#=============================================================================
# FUNCTION: toggle_hide
# Description: Hides/Shows the entire sidebar by pushing windows off-screen
#=============================================================================
toggle_hide() {
# Save current hide state
local previous_state
previous_state=$(cat "$HIDE_STATE_FILE" 2>/dev/null || echo "visible")
# Reorder sidebar to fix any desync without breaking hide/show state
local actual_floating
actual_floating=$(get_floating_windows)
if [ -n "$actual_floating" ]; then
reorder_sidebar
# reorder_sidebar forces visible, restore previous state
echo "$previous_state" >"$HIDE_STATE_FILE"
fi
if [ ! -s "$FLAG_FILE" ]; then
echo "No floating windows detected"
return 0
fi
local current_state
current_state=$(cat "$HIDE_STATE_FILE")
if [ "$current_state" = "visible" ]; then
# Hide: push windows off-screen
while IFS=: read -r id x y; do
[ -z "$id" ] && continue
local new_x=$((SCREEN_W - VISIBLE_SLIVER))
niri msg action move-floating-window --id "$id" --x "$new_x" --y "$y" 2>/dev/null || true
done <"$FLAG_FILE"
echo "hidden" >"$HIDE_STATE_FILE"
echo "✓ Sidebar hidden"
else
# Show: restore original positions
while IFS=: read -r id x y; do
[ -z "$id" ] && continue
niri msg action move-floating-window --id "$id" --x "$x" --y "$y" 2>/dev/null || true
done <"$FLAG_FILE"
echo "visible" >"$HIDE_STATE_FILE"
echo "✓ Sidebar visible"
fi
}
#=============================================================================
# FUNCTION: flip_sidebar_order
# Description: Flip the order of the sidebar
#=============================================================================
flip_sidebar_order() {
local current
current=$(cat "$ORDER_STATE_FILE" 2>/dev/null || echo "bottom-up")
if [ "$current" = "bottom-up" ]; then
echo "top-down" >"$ORDER_STATE_FILE"
else
echo "bottom-up" >"$ORDER_STATE_FILE"
fi
reorder_sidebar
echo "✓ Sidebar order flipped"
}
#=============================================================================
# FUNCTION: show_usage
# Description: Displays help information
#=============================================================================
show_usage() {
cat <<EOF
Usage: $0 {toggle|hide|help}
Commands:
toggle Toggle focused window between sidebar and tiling mode
hide Hide/show the entire sidebar
flip Flip sidebar order (top-down / bottom-up)
reorder Syncs state and updates all window positions on screen
help Show this help message
Examples:
$0 toggle # Add/remove current window
$0 hide # Show/hide all floating windows
EOF
}
#=============================================================================
# MAIN ENTRY POINT
#=============================================================================
main() {
ensure_files_exist
case "${1:-}" in
toggle)
toggle_float
;;
hide)
toggle_hide
;;
flip)
flip_sidebar_order
;;
reorder)
reorder_sidebar
;;
help | --help | -h)
show_usage
;;
*)
echo "❌ Invalid command"
echo ""
show_usage
exit 1
;;
esac
}
main "$@"