aboutsummaryrefslogtreecommitdiffstats
path: root/config/hypr/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'config/hypr/scripts')
-rwxr-xr-xconfig/hypr/scripts/ChangeLayout.sh120
-rwxr-xr-xconfig/hypr/scripts/DisableWaybarService.sh265
-rwxr-xr-xconfig/hypr/scripts/Dropterminal.sh630
-rwxr-xr-xconfig/hypr/scripts/ExternalBrightness.sh304
-rwxr-xr-xconfig/hypr/scripts/Float-all-Windows.sh25
-rwxr-xr-xconfig/hypr/scripts/GameMode.sh96
-rwxr-xr-xconfig/hypr/scripts/Ghostty_themes.sh120
-rwxr-xr-xconfig/hypr/scripts/HyprLayoutModule.sh131
-rwxr-xr-xconfig/hypr/scripts/JavaManager.sh245
-rwxr-xr-xconfig/hypr/scripts/KeyBinds.sh39
-rwxr-xr-xconfig/hypr/scripts/KeybindsLayoutInit.sh7
-rwxr-xr-xconfig/hypr/scripts/Kitty_themes.sh67
-rwxr-xr-xconfig/hypr/scripts/Kool_Quick_Settings.sh158
-rwxr-xr-xconfig/hypr/scripts/Logout.sh180
-rwxr-xr-xconfig/hypr/scripts/LuaAutoReload.sh47
-rwxr-xr-xconfig/hypr/scripts/LuaCycleWindow.sh73
-rwxr-xr-xconfig/hypr/scripts/LuaFocusWorkspaceRelative.sh44
-rwxr-xr-xconfig/hypr/scripts/LuaFullscreenMaximized.sh11
-rwxr-xr-xconfig/hypr/scripts/LuaMoveWindowDirectional.sh51
-rwxr-xr-xconfig/hypr/scripts/LuaMoveWindowWorkspaceRelative.sh51
-rwxr-xr-xconfig/hypr/scripts/LuaSwapWindow.sh56
-rwxr-xr-xconfig/hypr/scripts/MonitorProfiles.sh53
-rwxr-xr-xconfig/hypr/scripts/OverviewToggle.sh17
-rwxr-xr-xconfig/hypr/scripts/Polkit-Diag.sh102
-rwxr-xr-xconfig/hypr/scripts/PortalHyprland.sh21
-rwxr-xr-xconfig/hypr/scripts/Refresh.sh39
-rwxr-xr-xconfig/hypr/scripts/ResizeActive.sh21
-rwxr-xr-xconfig/hypr/scripts/Tak0-Autodispatch.sh69
-rwxr-xr-xconfig/hypr/scripts/Tak0-Per-Window-Switch.sh105
-rwxr-xr-xconfig/hypr/scripts/ThemeChanger.sh156
-rwxr-xr-xconfig/hypr/scripts/Toggle-Active-Window-Audio.sh19
-rwxr-xr-xconfig/hypr/scripts/WallpaperCmd.sh91
-rwxr-xr-xconfig/hypr/scripts/WallpaperDaemon.sh10
-rwxr-xr-xconfig/hypr/scripts/WallustConfig.sh46
-rwxr-xr-xconfig/hypr/scripts/WallustSwww.sh158
-rwxr-xr-xconfig/hypr/scripts/WaybarStartup.sh88
-rwxr-xr-xconfig/hypr/scripts/keybinds_parser.py270
-rwxr-xr-xconfig/hypr/scripts/update_WindowRules.sh14
38 files changed, 3472 insertions, 527 deletions
diff --git a/config/hypr/scripts/ChangeLayout.sh b/config/hypr/scripts/ChangeLayout.sh
index f9f6b52b..3c81bcbe 100755
--- a/config/hypr/scripts/ChangeLayout.sh
+++ b/config/hypr/scripts/ChangeLayout.sh
@@ -10,11 +10,31 @@
notif="$HOME/.config/swaync/images/ja.png"
layouts=(master dwindle scrolling monocle)
+quiet_mode=0
get_layout() {
hyprctl -j getoption general:layout | jq -r '.str'
}
+set_hypr_layout() {
+ local target="$1"
+ local output
+
+ output="$(hyprctl keyword general:layout "$target" 2>&1)"
+ if grep -q "keyword can't work with non-legacy parsers" <<<"$output"; then
+ output="$(hyprctl eval "hl.config({ general = { layout = \"${target}\" } })" 2>&1)"
+ fi
+
+ if ! grep -q "^ok$" <<<"$output"; then
+ echo "$output" >&2
+ return 1
+ fi
+}
+
+hypr_keyword() {
+ hyprctl keyword "$@" >/dev/null 2>&1 || true
+}
+
next_layout() {
local current="$1"
local i
@@ -30,66 +50,78 @@ next_layout() {
set_layout() {
local target="$1"
- hyprctl keyword general:layout "$target"
- hyprctl keyword unbind SUPER,j
- hyprctl keyword unbind SUPER,k
- hyprctl keyword unbind SUPER,left
- hyprctl keyword unbind SUPER,right
- hyprctl keyword unbind SUPER,up
- hyprctl keyword unbind SUPER,down
- hyprctl keyword unbind SUPER,O
+ if ! set_hypr_layout "$target"; then
+ if [[ "$quiet_mode" -eq 0 ]]; then
+ notify-send -e -u critical -i "$notif" " Layout switch failed: $target"
+ fi
+ return 1
+ fi
+
+ hypr_keyword unbind SUPER,j
+ hypr_keyword unbind SUPER,k
+ hypr_keyword bind SUPER,j,cyclenext
+ hypr_keyword bind SUPER,k,cyclenext,prev
+ hypr_keyword unbind SUPER,left
+ hypr_keyword unbind SUPER,right
+ hypr_keyword unbind SUPER,up
+ hypr_keyword unbind SUPER,down
+ hypr_keyword unbind SUPER,O
case "$target" in
"dwindle")
- hyprctl keyword bind SUPER,j,cyclenext
- hyprctl keyword bind SUPER,k,cyclenext,prev
- hyprctl keyword bind SUPER,left,cyclenext,prev
- hyprctl keyword bind SUPER,up,cyclenext,prev
- hyprctl keyword bind SUPER,right,cyclenext
- hyprctl keyword bind SUPER,down,cyclenext
- hyprctl keyword bind SUPER,O,layoutmsg,togglesplit
- notify-send -e -u low -i "$notif" " Dwindle Layout"
+ hypr_keyword bind SUPER,left,cyclenext,prev
+ hypr_keyword bind SUPER,up,cyclenext,prev
+ hypr_keyword bind SUPER,right,cyclenext
+ hypr_keyword bind SUPER,down,cyclenext
+ hypr_keyword bind SUPER,O,layoutmsg,togglesplit
;;
"scrolling")
- hyprctl keyword bind SUPER,j,cyclenext
- hyprctl keyword bind SUPER,k,cyclenext,prev
- hyprctl keyword bind SUPER,left,cyclenext,prev
- hyprctl keyword bind SUPER,up,cyclenext,prev
- hyprctl keyword bind SUPER,right,cyclenext
- hyprctl keyword bind SUPER,down,cyclenext
- notify-send -e -u low -i "$notif" " Scrolling Layout"
+ hypr_keyword bind SUPER,left,cyclenext,prev
+ hypr_keyword bind SUPER,up,cyclenext,prev
+ hypr_keyword bind SUPER,right,cyclenext
+ hypr_keyword bind SUPER,down,cyclenext
;;
"monocle")
- hyprctl keyword bind SUPER,j,layoutmsg,cyclenext
- hyprctl keyword bind SUPER,k,layoutmsg,cycleprev
- hyprctl keyword bind SUPER,left,layoutmsg,cycleprev
- hyprctl keyword bind SUPER,up,layoutmsg,cycleprev
- hyprctl keyword bind SUPER,right,layoutmsg,cyclenext
- hyprctl keyword bind SUPER,down,layoutmsg,cyclenext
- notify-send -e -u low -i "$notif" " Monocle Layout"
+ hypr_keyword bind SUPER,left,layoutmsg,cycleprev
+ hypr_keyword bind SUPER,up,layoutmsg,cycleprev
+ hypr_keyword bind SUPER,right,layoutmsg,cyclenext
+ hypr_keyword bind SUPER,down,layoutmsg,cyclenext
;;
"master")
- hyprctl keyword bind SUPER,j,layoutmsg,cyclenext
- hyprctl keyword bind SUPER,k,layoutmsg,cycleprev
- hyprctl keyword bind SUPER,left,movefocus,l
- hyprctl keyword bind SUPER,right,movefocus,r
- hyprctl keyword bind SUPER,up,movefocus,u
- hyprctl keyword bind SUPER,down,movefocus,d
- notify-send -e -u low -i "$notif" " Master Layout"
+ hypr_keyword bind SUPER,left,movefocus,l
+ hypr_keyword bind SUPER,right,movefocus,r
+ hypr_keyword bind SUPER,up,movefocus,u
+ hypr_keyword bind SUPER,down,movefocus,d
;;
*)
- hyprctl keyword bind SUPER,j,layoutmsg,cyclenext
- hyprctl keyword bind SUPER,k,layoutmsg,cycleprev
- hyprctl keyword bind SUPER,left,movefocus,l
- hyprctl keyword bind SUPER,right,movefocus,r
- hyprctl keyword bind SUPER,up,movefocus,u
- hyprctl keyword bind SUPER,down,movefocus,d
+ hypr_keyword bind SUPER,left,movefocus,l
+ hypr_keyword bind SUPER,right,movefocus,r
+ hypr_keyword bind SUPER,up,movefocus,u
+ hypr_keyword bind SUPER,down,movefocus,d
echo "Unknown layout: $target" >&2
return 1
;;
esac
+
+ local actual
+ actual="$(get_layout)"
+ if [[ "$actual" == "$target" ]]; then
+ if [[ "$quiet_mode" -eq 0 ]]; then
+ notify-send -e -u low -i "$notif" " ${actual^} Layout"
+ fi
+ else
+ if [[ "$quiet_mode" -eq 0 ]]; then
+ notify-send -e -u critical -i "$notif" " Layout switch failed: still ${actual}"
+ fi
+ return 1
+ fi
}
+if [[ "${1:-}" == "--quiet" || "${1:-}" == "--no-notify" ]]; then
+ quiet_mode=1
+ shift
+fi
+
current="$(get_layout)"
arg="${1:-toggle}"
@@ -104,7 +136,7 @@ master|dwindle|scrolling|monocle)
set_layout "$arg"
;;
*)
- echo "Usage: $(basename "$0") [toggle|next|init|master|dwindle|scrolling|monocle]" >&2
+ echo "Usage: $(basename "$0") [--quiet|--no-notify] [toggle|next|init|master|dwindle|scrolling|monocle]" >&2
exit 1
;;
esac
diff --git a/config/hypr/scripts/DisableWaybarService.sh b/config/hypr/scripts/DisableWaybarService.sh
new file mode 100755
index 00000000..7750fe4a
--- /dev/null
+++ b/config/hypr/scripts/DisableWaybarService.sh
@@ -0,0 +1,265 @@
+#!/usr/bin/env bash
+set -u
+set -o pipefail
+
+SERVICE_NAME="waybar.service"
+ACTION=""
+DRY_RUN=false
+OPERATION_RESULT="not-run"
+
+RED="\033[0;31m"
+GREEN="\033[0;32m"
+YELLOW="\033[1;33m"
+BLUE="\033[0;34m"
+NC="\033[0m"
+
+ICON_OK="✅"
+ICON_WARN="⚠️"
+ICON_ERR="❌"
+ICON_INFO="ℹ️"
+
+usage() {
+ cat <<EOF
+Usage: $(basename "$0") [OPTIONS]
+
+Disable --user ${SERVICE_NAME} (default action), revert it, or check status.
+
+Options:
+ -h, --help Show this help message and exit
+ -r, --revert Revert by enabling + starting --user ${SERVICE_NAME}
+ -d, --dry-run Print actions without changing anything
+ -s, --status Show current status of --user ${SERVICE_NAME}
+EOF
+}
+
+info() {
+ printf "${BLUE}${ICON_INFO} %s${NC}\n" "$1"
+}
+
+ok() {
+ printf "${GREEN}${ICON_OK} %s${NC}\n" "$1"
+}
+
+warn() {
+ printf "${YELLOW}${ICON_WARN} %s${NC}\n" "$1"
+}
+
+error() {
+ printf "${RED}${ICON_ERR} %s${NC}\n" "$1" >&2
+}
+
+fail() {
+ error "$1"
+ exit 1
+}
+
+format_command() {
+ printf "%q " "$@"
+}
+
+run_cmd() {
+ if $DRY_RUN; then
+ info "[dry-run] $(format_command "$@")"
+ return 0
+ fi
+ "$@"
+}
+
+set_action() {
+ local new_action="$1"
+ if [[ -n "$ACTION" && "$ACTION" != "$new_action" ]]; then
+ fail "Conflicting options: cannot combine '${ACTION}' with '${new_action}'."
+ fi
+ ACTION="$new_action"
+}
+
+require_systemctl() {
+ command -v systemctl >/dev/null 2>&1 || fail "systemctl is required but was not found."
+}
+
+require_user_manager() {
+ if ! systemctl --user show-environment >/dev/null 2>&1; then
+ fail "Cannot contact systemd user manager. Ensure your user systemd session is running."
+ fi
+}
+
+service_load_state() {
+ systemctl --user show --property=LoadState --value "${SERVICE_NAME}" 2>/dev/null || true
+}
+
+service_exists() {
+ local load_state
+ load_state="$(service_load_state)"
+ [[ -n "${load_state}" && "${load_state}" != "not-found" ]]
+}
+
+require_service_exists() {
+ if ! service_exists; then
+ fail "--user ${SERVICE_NAME} was not found. Exiting."
+ fi
+}
+
+get_enabled_state() {
+ local state
+ state="$(systemctl --user is-enabled "${SERVICE_NAME}" 2>/dev/null || true)"
+ [[ -n "${state}" ]] || state="unknown"
+ printf "%s" "${state}"
+}
+
+get_active_state() {
+ local state
+ state="$(systemctl --user is-active "${SERVICE_NAME}" 2>/dev/null || true)"
+ [[ -n "${state}" ]] || state="unknown"
+ printf "%s" "${state}"
+}
+
+print_state_line() {
+ local label="$1"
+ local state="$2"
+ local color="${BLUE}"
+ local icon="${ICON_INFO}"
+
+ case "${state}" in
+ loaded|enabled|active)
+ color="${GREEN}"
+ icon="${ICON_OK}"
+ ;;
+ activating|deactivating|reloading|inactive|disabled|static|indirect|masked)
+ color="${YELLOW}"
+ icon="${ICON_WARN}"
+ ;;
+ failed|not-found|unknown)
+ color="${RED}"
+ icon="${ICON_ERR}"
+ ;;
+ esac
+
+ printf " %-9s: %b%s %s%b\n" "${label}" "${color}" "${icon}" "${state}" "${NC}"
+}
+
+print_report() {
+ local load_state enabled_state active_state dry_run_state result_color result_icon result_text
+
+ load_state="$(service_load_state)"
+ [[ -n "${load_state}" ]] || load_state="unknown"
+ enabled_state="$(get_enabled_state)"
+ active_state="$(get_active_state)"
+ dry_run_state="no"
+ $DRY_RUN && dry_run_state="yes"
+
+ case "${OPERATION_RESULT}" in
+ success)
+ result_color="${GREEN}"
+ result_icon="${ICON_OK}"
+ result_text="success"
+ ;;
+ dry-run)
+ result_color="${YELLOW}"
+ result_icon="${ICON_WARN}"
+ result_text="dry-run"
+ ;;
+ failed)
+ result_color="${RED}"
+ result_icon="${ICON_ERR}"
+ result_text="failed"
+ ;;
+ *)
+ result_color="${BLUE}"
+ result_icon="${ICON_INFO}"
+ result_text="not-run"
+ ;;
+ esac
+
+ printf "\n${BLUE}${ICON_INFO} Report for --user %s${NC}\n" "${SERVICE_NAME}"
+ print_state_line "LoadState" "${load_state}"
+ print_state_line "Enabled" "${enabled_state}"
+ print_state_line "Active" "${active_state}"
+ printf " %-9s: %s\n" "Dry-run" "${dry_run_state}"
+ printf " %-9s: %b%s %s%b\n" "Result" "${result_color}" "${result_icon}" "${result_text}" "${NC}"
+}
+
+perform_disable() {
+ info "Disabling and stopping --user ${SERVICE_NAME}..."
+ if run_cmd systemctl --user disable --now "${SERVICE_NAME}"; then
+ if $DRY_RUN; then
+ OPERATION_RESULT="dry-run"
+ warn "Dry-run complete. No changes were made."
+ else
+ OPERATION_RESULT="success"
+ ok "Disabled and stopped --user ${SERVICE_NAME}."
+ fi
+ return 0
+ fi
+
+ OPERATION_RESULT="failed"
+ error "Failed to disable --user ${SERVICE_NAME}."
+ return 1
+}
+
+perform_revert() {
+ info "Reverting --user ${SERVICE_NAME} (unmask + enable + start)..."
+ if ! run_cmd systemctl --user unmask "${SERVICE_NAME}"; then
+ OPERATION_RESULT="failed"
+ error "Failed to unmask --user ${SERVICE_NAME}."
+ return 1
+ fi
+ if ! run_cmd systemctl --user enable --now "${SERVICE_NAME}"; then
+ OPERATION_RESULT="failed"
+ error "Failed to enable/start --user ${SERVICE_NAME}."
+ return 1
+ fi
+
+ if $DRY_RUN; then
+ OPERATION_RESULT="dry-run"
+ warn "Dry-run complete. No changes were made."
+ else
+ OPERATION_RESULT="success"
+ ok "Reverted successfully. --user ${SERVICE_NAME} is enabled and started."
+ fi
+ return 0
+}
+
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h|--help)
+ usage
+ exit 0
+ ;;
+ -r|--revert)
+ set_action "revert"
+ ;;
+ -d|--dry-run)
+ DRY_RUN=true
+ ;;
+ -s|--status)
+ set_action "status"
+ ;;
+ *)
+ fail "Unknown option: $1 (use -h or --help)"
+ ;;
+ esac
+ shift
+done
+
+[[ -n "${ACTION}" ]] || ACTION="disable"
+
+require_systemctl
+require_user_manager
+require_service_exists
+
+exit_code=0
+case "${ACTION}" in
+ disable)
+ perform_disable || exit_code=1
+ ;;
+ revert)
+ perform_revert || exit_code=1
+ ;;
+ status)
+ OPERATION_RESULT="success"
+ info "Status requested for --user ${SERVICE_NAME}."
+ ;;
+esac
+
+print_report
+exit "${exit_code}" \ No newline at end of file
diff --git a/config/hypr/scripts/Dropterminal.sh b/config/hypr/scripts/Dropterminal.sh
index 81c0f157..21349d4a 100755
--- a/config/hypr/scripts/Dropterminal.sh
+++ b/config/hypr/scripts/Dropterminal.sh
@@ -24,6 +24,106 @@ LOCK_FILE="/tmp/dropdown_terminal_lock"
LAST_TOGGLE_FILE="/tmp/dropdown_terminal_last_toggle"
MIN_TOGGLE_INTERVAL_MS=250
DROPDOWN_KITTY_CLASS="kitty-dropterm"
+CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
+HYPR_DIR="$CONFIG_HOME/hypr"
+LUA_ENTRY="$HYPR_DIR/hyprland.lua"
+LEGACY_LUA_ENTRY="$CONFIG_HOME/hyprland.lua"
+
+if [[ -f "$LUA_ENTRY" || -f "$LEGACY_LUA_ENTRY" ]]; then
+ HYPR_CONFIG_MODE="lua"
+else
+ HYPR_CONFIG_MODE="conf"
+fi
+lua_escape() {
+ local value="$1"
+ value=${value//\\/\\\\}
+ value=${value//\"/\\\"}
+ value=${value//$'\n'/\\n}
+ printf '%s' "$value"
+}
+
+
+hypr_dispatch() {
+ local dispatcher="$1"
+ shift
+ local payload="$*"
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ local command="$dispatcher"
+ if [ -n "$payload" ]; then
+ command="$dispatcher $payload"
+ fi
+ local escaped
+ escaped="$(lua_escape "$command")"
+ hyprctl dispatch "hl.dsp.exec_raw(\"$escaped\")"
+ else
+ hyprctl dispatch "$dispatcher" "$payload"
+ fi
+}
+
+hypr_exec_cmd() {
+ local command="$*"
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ local escaped
+ escaped="$(lua_escape "$command")"
+ hyprctl dispatch "hl.dsp.exec_cmd(\"$escaped\")"
+ else
+ hyprctl dispatch exec "$command"
+ fi
+}
+
+lua_workspace_expr() {
+ local workspace="$1"
+ if [[ "$workspace" =~ ^-?[0-9]+$ ]]; then
+ printf '%s' "$workspace"
+ else
+ local escaped
+ escaped="$(lua_escape "$workspace")"
+ printf '"%s"' "$escaped"
+ fi
+}
+
+focus_window() {
+ local addr="$1"
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ local selector escaped
+ selector="address:$addr"
+ escaped="$(lua_escape "$selector")"
+ hyprctl dispatch "hl.dsp.focus({ window = \"$escaped\" })"
+ else
+ hypr_dispatch focuswindow "address:$addr"
+ fi
+}
+
+set_window_floating() {
+ local addr="$1"
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ hyprctl dispatch "hl.dsp.window.float({ window = 'address:$addr', action = 'on' })"
+ else
+ hypr_dispatch setfloating "address:$addr"
+ fi
+}
+
+resize_window_exact() {
+ local addr="$1"
+ local width="$2"
+ local height="$3"
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ hyprctl dispatch "hl.dsp.window.resize({ window = 'address:$addr', x = $width, y = $height, exact = true })"
+ else
+ hypr_dispatch resizewindowpixel "exact $width $height,address:$addr"
+ fi
+}
+
+move_window_exact() {
+ local addr="$1"
+ local x="$2"
+ local y="$3"
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ hyprctl dispatch "hl.dsp.window.move({ window = 'address:$addr', x = $x, y = $y, exact = true })"
+ else
+ hypr_dispatch movewindowpixel "exact $x $y,address:$addr"
+ fi
+}
# Dropdown size and position configuration (percentages)
WIDTH_PERCENT=65 # Width as percentage of screen width
@@ -31,19 +131,42 @@ HEIGHT_PERCENT=65 # Height as percentage of screen height
Y_PERCENT=10 # Y position as percentage from top (X is auto-centered)
# Animation settings
-ANIMATION_DURATION=100 # milliseconds
-SLIDE_STEPS=5
-SLIDE_DELAY=5 # milliseconds between steps
+ANIMATION_DURATION=220 # total animation time in milliseconds
+SLIDE_STEPS=12
+SLIDE_DELAY=$((ANIMATION_DURATION / SLIDE_STEPS))
+if [ "$SLIDE_DELAY" -lt 8 ]; then
+ SLIDE_DELAY=8
+fi
# Parse arguments
-if [ "$1" = "-d" ]; then
- DEBUG=true
- shift
-fi
+STARTUP_MODE=false
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -d | --debug)
+ DEBUG=true
+ shift
+ ;;
+ --startup | --init)
+ STARTUP_MODE=true
+ shift
+ ;;
+ -h | --help)
+ echo "Usage: $0 [-d|--debug] [--startup|--init] <terminal_command>"
+ echo "Examples:"
+ echo " $0 kitty"
+ echo " $0 --startup kitty"
+ echo " $0 -d \"kitty -e zsh\""
+ exit 0
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
-TERMINAL_CMD="$1"
+TERMINAL_CMD="$*"
if [[ "$TERMINAL_CMD" == kitty* ]] && [[ "$TERMINAL_CMD" != *"--class"* ]] && [[ "$TERMINAL_CMD" != *"--name"* ]] && [[ "$TERMINAL_CMD" != *"--app-id"* ]]; then
- TERMINAL_CMD="$TERMINAL_CMD --class $DROPDOWN_KITTY_CLASS"
+ TERMINAL_CMD="$TERMINAL_CMD --class $DROPDOWN_KITTY_CLASS --app-id $DROPDOWN_KITTY_CLASS"
fi
# Ensure only one instance runs at a time (prevents overlapping animations)
@@ -51,25 +174,27 @@ exec 9>"$LOCK_FILE"
flock -n 9 || exit 0
# Debounce rapid toggles
-now_ms=""
-if date +%s%3N >/dev/null 2>&1; then
- now_ms=$(date +%s%3N)
-else
- now_ms=$(( $(date +%s) * 1000 ))
-fi
-if [ -f "$LAST_TOGGLE_FILE" ]; then
- last_ms=$(cat "$LAST_TOGGLE_FILE" 2>/dev/null || echo 0)
- if [ -n "$last_ms" ] && [ "$last_ms" -ge 0 ] 2>/dev/null; then
- delta_ms=$((now_ms - last_ms))
- if [ "$delta_ms" -lt "$MIN_TOGGLE_INTERVAL_MS" ] 2>/dev/null; then
- if [ "$DEBUG" = true ]; then
- echo "Toggle debounced (${delta_ms}ms < ${MIN_TOGGLE_INTERVAL_MS}ms)" >&2
+if [ "$STARTUP_MODE" != true ]; then
+ now_ms=""
+ if date +%s%3N >/dev/null 2>&1; then
+ now_ms=$(date +%s%3N)
+ else
+ now_ms=$(( $(date +%s) * 1000 ))
+ fi
+ if [ -f "$LAST_TOGGLE_FILE" ]; then
+ last_ms=$(cat "$LAST_TOGGLE_FILE" 2>/dev/null || echo 0)
+ if [ -n "$last_ms" ] && [ "$last_ms" -ge 0 ] 2>/dev/null; then
+ delta_ms=$((now_ms - last_ms))
+ if [ "$delta_ms" -lt "$MIN_TOGGLE_INTERVAL_MS" ] 2>/dev/null; then
+ if [ "$DEBUG" = true ]; then
+ echo "Toggle debounced (${delta_ms}ms < ${MIN_TOGGLE_INTERVAL_MS}ms)" >&2
+ fi
+ exit 0
fi
- exit 0
fi
fi
+ echo "$now_ms" >"$LAST_TOGGLE_FILE"
fi
-echo "$now_ms" >"$LAST_TOGGLE_FILE"
# Debug echo function
debug_echo() {
@@ -103,10 +228,11 @@ resolve_terminal_address() {
# Validate input
if [ -z "$TERMINAL_CMD" ]; then
- echo "Missing terminal command. Usage: $0 [-d] <terminal_command>"
+ echo "Missing terminal command. Usage: $0 [-d|--debug] [--startup|--init] <terminal_command>"
echo "Examples:"
- echo " $0 foot"
- echo " $0 -d foot (with debug output)"
+ echo " $0 kitty"
+ echo " $0 --startup kitty"
+ echo " $0 -d kitty (with debug output)"
echo " $0 'kitty -e zsh'"
echo " $0 'alacritty --working-directory /home/user'"
echo ""
@@ -127,14 +253,57 @@ get_window_geometry() {
# Function to check if window is currently hidden off-screen
window_is_hidden() {
local addr="$1"
- local y
- y=$(hyprctl clients -j 2>/dev/null | jq -r --arg ADDR "$addr" '.[] | select(.address == $ADDR) | .at[1]' 2>/dev/null)
- if [[ "$y" =~ ^-?[0-9]+$ ]] && [ "$y" -lt 0 ]; then
+ local geometry y height monitor_id monitor_y
+ geometry=$(hyprctl clients -j 2>/dev/null | jq -r --arg ADDR "$addr" '.[] | select(.address == $ADDR) | "\(.at[1]) \(.size[1]) \(.monitor)"' 2>/dev/null)
+ y=$(echo "$geometry" | awk '{print $1}')
+ height=$(echo "$geometry" | awk '{print $2}')
+ monitor_id=$(echo "$geometry" | awk '{print $3}')
+
+ if ! [[ "$y" =~ ^-?[0-9]+$ && "$height" =~ ^[0-9]+$ ]]; then
+ return 1
+ fi
+
+ if [[ "$monitor_id" =~ ^-?[0-9]+$ ]]; then
+ monitor_y=$(hyprctl monitors -j 2>/dev/null | jq -r --argjson MID "$monitor_id" '.[] | select(.id == $MID) | .y' 2>/dev/null | head -1)
+ fi
+ if ! [[ "$monitor_y" =~ ^-?[0-9]+$ ]]; then
+ monitor_y=0
+ fi
+
+ if [ $((y + height)) -le "$monitor_y" ]; then
return 0
fi
return 1
}
+get_window_monitor_top() {
+ local addr="$1"
+ local monitor_id monitor_y
+ monitor_id=$(hyprctl clients -j 2>/dev/null | jq -r --arg ADDR "$addr" '.[] | select(.address == $ADDR) | .monitor' 2>/dev/null | head -1)
+ if [[ "$monitor_id" =~ ^-?[0-9]+$ ]]; then
+ monitor_y=$(hyprctl monitors -j 2>/dev/null | jq -r --argjson MID "$monitor_id" '.[] | select(.id == $MID) | .y' 2>/dev/null | head -1)
+ if [[ "$monitor_y" =~ ^-?[0-9]+$ ]]; then
+ echo "$monitor_y"
+ return 0
+ fi
+ fi
+ echo 0
+}
+
+get_hidden_y_for_window() {
+ local addr="$1"
+ local height="$2"
+ local monitor_top
+ if ! [[ "$height" =~ ^[0-9]+$ ]]; then
+ height=702
+ fi
+ monitor_top=$(get_window_monitor_top "$addr")
+ if ! [[ "$monitor_top" =~ ^-?[0-9]+$ ]]; then
+ monitor_top=0
+ fi
+ echo $((monitor_top - height - 80))
+}
+
# State helpers
get_hidden_state() {
if [ -f "$STATE_FILE" ]; then
@@ -146,6 +315,15 @@ set_hidden_state() {
echo "$1" >"$STATE_FILE"
}
+sleep_ms() {
+ local ms="$1"
+ if command -v awk >/dev/null 2>&1; then
+ sleep "$(awk -v value="$ms" 'BEGIN { printf "%.3f", value / 1000 }')"
+ else
+ sleep 0.01
+ fi
+}
+
# Function to animate window slide down (show)
animate_slide_down() {
local addr="$1"
@@ -153,28 +331,28 @@ animate_slide_down() {
local target_y="$3"
local width="$4"
local height="$5"
+ local start_y="$6"
debug_echo "Animating slide down for window $addr to position $target_x,$target_y"
- # Start position (above screen)
- local start_y=$((target_y - height - 50))
-
- # Calculate step size
- local step_y=$(((target_y - start_y) / SLIDE_STEPS))
+ if ! [[ "$start_y" =~ ^-?[0-9]+$ ]]; then
+ start_y=$((target_y - height - 50))
+ fi
+ local total_delta=$((target_y - start_y))
# Move window to start position instantly (off-screen)
- hyprctl dispatch movewindowpixel "exact $target_x $start_y,address:$addr" >/dev/null 2>&1
- sleep 0.05
+ move_window_exact "$addr" "$target_x" "$start_y" >/dev/null 2>&1 || return 1
+ sleep_ms "$SLIDE_DELAY"
# Animate slide down
for i in $(seq 1 $SLIDE_STEPS); do
- local current_y=$((start_y + (step_y * i)))
- hyprctl dispatch movewindowpixel "exact $target_x $current_y,address:$addr" >/dev/null 2>&1
- sleep 0.03
+ local current_y=$((start_y + (total_delta * i / SLIDE_STEPS)))
+ move_window_exact "$addr" "$target_x" "$current_y" >/dev/null 2>&1 || return 1
+ sleep_ms "$SLIDE_DELAY"
done
# Ensure final position is exact
- hyprctl dispatch movewindowpixel "exact $target_x $target_y,address:$addr" >/dev/null 2>&1
+ move_window_exact "$addr" "$target_x" "$target_y" >/dev/null 2>&1
}
# Function to animate window slide up (hide)
@@ -184,21 +362,22 @@ animate_slide_up() {
local start_y="$3"
local width="$4"
local height="$5"
+ local end_y="$6"
debug_echo "Animating slide up for window $addr from position $start_x,$start_y"
- # End position (above screen)
- local end_y=$((start_y - height - 50))
-
- # Calculate step size
- local step_y=$(((start_y - end_y) / SLIDE_STEPS))
+ if ! [[ "$end_y" =~ ^-?[0-9]+$ ]]; then
+ end_y=$((start_y - height - 50))
+ fi
+ local total_delta=$((start_y - end_y))
# Animate slide up
for i in $(seq 1 $SLIDE_STEPS); do
- local current_y=$((start_y - (step_y * i)))
- hyprctl dispatch movewindowpixel "exact $start_x $current_y,address:$addr" >/dev/null 2>&1
- sleep 0.03
+ local current_y=$((start_y - (total_delta * i / SLIDE_STEPS)))
+ move_window_exact "$addr" "$start_x" "$current_y" >/dev/null 2>&1 || return 1
+ sleep_ms "$SLIDE_DELAY"
done
+ move_window_exact "$addr" "$start_x" "$end_y" >/dev/null 2>&1
debug_echo "Slide up animation completed"
}
@@ -312,8 +491,9 @@ calculate_dropdown_position() {
echo "$final_x $final_y $width $height $mon_name"
}
-# Get the current workspace
-CURRENT_WS=$(hyprctl activeworkspace -j | jq -r '.id')
+get_current_workspace() {
+ hyprctl activeworkspace -j 2>/dev/null | jq -r '.id // empty'
+}
# Function to get stored terminal address
get_terminal_address() {
@@ -325,7 +505,7 @@ get_terminal_address() {
# Try to find an existing dropdown terminal by class (kitty only)
find_terminal_by_class() {
hyprctl clients -j 2>/dev/null | jq -r --arg CLASS "$DROPDOWN_KITTY_CLASS" \
- '.[] | select(.class == $CLASS) | .address' | head -1
+ '.[] | select((.class == $CLASS) or (.initialClass == $CLASS)) | .address' | head -1
}
# Function to get stored monitor name
@@ -356,29 +536,197 @@ window_exists() {
}
-# Function to check if window is pinned
-window_is_pinned() {
+window_workspace_name() {
local addr="$1"
- if [ -n "$addr" ]; then
- hyprctl clients -j 2>/dev/null | jq -e --arg ADDR "$addr" '.[] | select(.address == $ADDR) | .pinned == true' >/dev/null 2>&1
+ hyprctl clients -j 2>/dev/null | jq -r --arg ADDR "$addr" '.[] | select(.address == $ADDR) | .workspace.name // empty'
+}
+
+window_is_on_special_workspace() {
+ local addr="$1"
+ local workspace_name
+ workspace_name=$(window_workspace_name "$addr")
+ [ "$workspace_name" = "$SPECIAL_WS" ] || [ "$workspace_name" = "$SPECIAL_NAME" ]
+}
+
+workspace_matches_target() {
+ local target_ws="$1"
+ local current_ws="$2"
+ if [ "$target_ws" = "$SPECIAL_WS" ] || [ "$target_ws" = "$SPECIAL_NAME" ]; then
+ [ "$current_ws" = "$SPECIAL_WS" ] || [ "$current_ws" = "$SPECIAL_NAME" ]
else
+ [ "$current_ws" = "$target_ws" ]
+ fi
+}
+
+move_window_to_workspace_silent() {
+ local target_ws="$1"
+ local addr="$2"
+ local post_ws=""
+
+ if [[ "$HYPR_CONFIG_MODE" == "lua" ]]; then
+ local ws_expr
+ ws_expr=$(lua_workspace_expr "$target_ws")
+ hyprctl dispatch "hl.dsp.window.move({ window = 'address:$addr', workspace = $ws_expr, follow = false })" >/dev/null 2>&1 || return 1
+ sleep 0.03
+ post_ws=$(window_workspace_name "$addr")
+ workspace_matches_target "$target_ws" "$post_ws"
+ return $?
+ fi
+
+ # Preferred syntax on newer Hyprland builds (target a specific window by selector).
+ if hypr_dispatch movetoworkspacesilent "$target_ws,address:$addr" >/dev/null 2>&1; then
+ sleep 0.03
+ post_ws=$(window_workspace_name "$addr")
+ if workspace_matches_target "$target_ws" "$post_ws"; then
+ return 0
+ fi
+ fi
+
+ # Compatibility fallback for builds where selector syntax is ignored.
+ hypr_dispatch focuswindow "address:$addr" >/dev/null 2>&1 || return 1
+ hypr_dispatch movetoworkspacesilent "$target_ws" >/dev/null 2>&1 || return 1
+ sleep 0.03
+ post_ws=$(window_workspace_name "$addr")
+ workspace_matches_target "$target_ws" "$post_ws"
+}
+
+infer_hidden_state() {
+ local addr="$1"
+ if window_is_hidden "$addr"; then
+ echo "hidden"
+ elif window_is_on_special_workspace "$addr"; then
+ echo "hidden"
+ else
+ echo "shown"
+ fi
+}
+
+apply_dropdown_layout() {
+ local addr="$1"
+ local pos_info
+ pos_info=$(calculate_dropdown_position)
+ if [ $? -ne 0 ]; then
+ debug_echo "Warning: Failed to calculate dropdown position, layout update skipped"
return 1
fi
+
+ local target_x=$(echo "$pos_info" | cut -d' ' -f1)
+ local target_y=$(echo "$pos_info" | cut -d' ' -f2)
+ local width=$(echo "$pos_info" | cut -d' ' -f3)
+ local height=$(echo "$pos_info" | cut -d' ' -f4)
+ set_window_floating "$addr" >/dev/null 2>&1
+ resize_window_exact "$addr" "$width" "$height" >/dev/null 2>&1
+ move_window_exact "$addr" "$target_x" "$target_y" >/dev/null 2>&1
+ return 0
}
-# Ensure pin state without toggling unexpectedly
-ensure_pinned() {
+show_terminal() {
local addr="$1"
- if ! window_is_pinned "$addr"; then
- hyprctl dispatch pin "address:$addr" >/dev/null 2>&1
+ local current_ws
+ local pos_info target_x target_y width height hidden_y
+ current_ws=$(get_current_workspace)
+ if ! [[ "$current_ws" =~ ^-?[0-9]+$ ]]; then
+ current_ws=1
+ fi
+ pos_info=$(calculate_dropdown_position)
+ if [ $? -ne 0 ] || [ -z "$pos_info" ]; then
+ debug_echo "Failed to calculate dropdown position for show; falling back to direct layout"
+ move_window_to_workspace_silent "$current_ws" "$addr" >/dev/null 2>&1 || debug_echo "Failed to move dropdown terminal to workspace $current_ws"
+ apply_dropdown_layout "$addr" || debug_echo "Dropdown layout update returned non-zero"
+ focus_window "$addr" >/dev/null 2>&1 || true
+ set_hidden_state "shown"
+ debug_echo "Dropdown terminal shown"
+ return 0
+ fi
+
+ target_x=$(echo "$pos_info" | cut -d' ' -f1)
+ target_y=$(echo "$pos_info" | cut -d' ' -f2)
+ width=$(echo "$pos_info" | cut -d' ' -f3)
+ height=$(echo "$pos_info" | cut -d' ' -f4)
+ hidden_y=$(get_hidden_y_for_window "$addr" "$height")
+ if window_is_on_special_workspace "$addr"; then
+ move_window_to_workspace_silent "$current_ws" "$addr" >/dev/null 2>&1 || debug_echo "Failed to move dropdown terminal to workspace $current_ws"
+ fi
+ set_window_floating "$addr" >/dev/null 2>&1 || true
+ resize_window_exact "$addr" "$width" "$height" >/dev/null 2>&1 || true
+ animate_slide_down "$addr" "$target_x" "$target_y" "$width" "$height" "$hidden_y" || move_window_exact "$addr" "$target_x" "$target_y" >/dev/null 2>&1
+ focus_window "$addr" >/dev/null 2>&1 || true
+ set_hidden_state "shown"
+ debug_echo "Dropdown terminal shown"
+ return 0
+}
+
+hide_terminal() {
+ local addr="$1"
+ local geometry start_x start_y width height hidden_y
+ if window_is_hidden "$addr" || window_is_on_special_workspace "$addr"; then
+ set_hidden_state "hidden"
+ debug_echo "Dropdown terminal already hidden"
+ return 0
+ fi
+
+ geometry=$(get_window_geometry "$addr")
+ if [ -n "$geometry" ]; then
+ start_x=$(echo "$geometry" | awk '{print $1}')
+ start_y=$(echo "$geometry" | awk '{print $2}')
+ width=$(echo "$geometry" | awk '{print $3}')
+ height=$(echo "$geometry" | awk '{print $4}')
+ else
+ local pos_info
+ pos_info=$(calculate_dropdown_position)
+ start_x=$(echo "$pos_info" | cut -d' ' -f1)
+ start_y=$(echo "$pos_info" | cut -d' ' -f2)
+ width=$(echo "$pos_info" | cut -d' ' -f3)
+ height=$(echo "$pos_info" | cut -d' ' -f4)
+ fi
+
+ if ! [[ "$height" =~ ^[0-9]+$ ]]; then
+ height=702
+ fi
+ hidden_y=$(get_hidden_y_for_window "$addr" "$height")
+ if ! [[ "$start_x" =~ ^-?[0-9]+$ && "$start_y" =~ ^-?[0-9]+$ ]]; then
+ debug_echo "Missing geometry for slide-up animation; moving off-screen directly"
+ move_window_exact "$addr" "$start_x" "$hidden_y" >/dev/null 2>&1 || true
+ else
+ animate_slide_up "$addr" "$start_x" "$start_y" "$width" "$height" "$hidden_y" || true
+ fi
+
+ if ! window_is_hidden "$addr"; then
+ debug_echo "Dropdown not off-screen after slide; trying direct off-screen move"
+ move_window_exact "$addr" "$start_x" "$hidden_y" >/dev/null 2>&1 || true
+ fi
+ if ! window_is_hidden "$addr"; then
+ debug_echo "Off-screen hide failed, falling back to $SPECIAL_WS"
+ if ! move_window_to_workspace_silent "$SPECIAL_WS" "$addr"; then
+ debug_echo "Failed to move dropdown terminal to $SPECIAL_WS"
+ return 1
+ fi
fi
+ set_hidden_state "hidden"
+ debug_echo "Dropdown terminal hidden"
+ return 0
}
-ensure_unpinned() {
+hide_terminal_silent() {
local addr="$1"
- if window_is_pinned "$addr"; then
- hyprctl dispatch pin "address:$addr" >/dev/null 2>&1
+ local geometry start_x height hidden_y
+ geometry=$(get_window_geometry "$addr")
+ if [ -n "$geometry" ]; then
+ start_x=$(echo "$geometry" | awk '{print $1}')
+ height=$(echo "$geometry" | awk '{print $4}')
fi
+ if ! [[ "$height" =~ ^[0-9]+$ ]]; then
+ height=702
+ fi
+ hidden_y=$(get_hidden_y_for_window "$addr" "$height")
+ if ! [[ "$start_x" =~ ^-?[0-9]+$ ]]; then
+ start_x=0
+ fi
+ move_window_exact "$addr" "$start_x" "$hidden_y" >/dev/null 2>&1 || true
+ move_window_to_workspace_silent "$SPECIAL_WS" "$addr" >/dev/null 2>&1 || true
+ set_hidden_state "hidden"
+ debug_echo "Dropdown terminal hidden (silent)"
+ return 0
}
# Function to spawn terminal and capture its address
@@ -403,45 +751,48 @@ spawn_terminal() {
local windows_before=$(hyprctl clients -j)
local count_before=$(echo "$windows_before" | jq 'length')
- # Launch terminal directly in special workspace to avoid visible spawn
- hyprctl dispatch exec "[float; size $width $height; workspace special:scratchpad silent] $TERMINAL_CMD"
-
- # Wait for window to appear
- sleep 0.1
-
- # Get windows after spawning
- local windows_after=$(hyprctl clients -j)
- local count_after=$(echo "$windows_after" | jq 'length')
+ # Launch terminal with pre-applied workspace/geometry hints to avoid visible zigzag.
+ local launch_cmd="[workspace $SPECIAL_WS silent;float;size $width $height;move $target_x $target_y] $TERMINAL_CMD"
+ hypr_exec_cmd "$launch_cmd"
local new_addr=""
+ for _ in $(seq 1 20); do
+ local windows_after=$(hyprctl clients -j)
+ local recovered
+ recovered=$(echo "$windows_after" | jq -r --arg CLASS "$DROPDOWN_KITTY_CLASS" \
+ '.[] | select((.class == $CLASS) or (.initialClass == $CLASS)) | .address' | head -1)
+ if [ -n "$recovered" ] && [ "$recovered" != "null" ]; then
+ new_addr="$recovered"
+ break
+ fi
- if [ "$count_after" -gt "$count_before" ]; then
- # Find the new window by comparing before/after lists
- new_addr=$(comm -13 \
- <(echo "$windows_before" | jq -r '.[].address' | sort) \
- <(echo "$windows_after" | jq -r '.[].address' | sort) |
- head -1)
- fi
-
- # Fallback: try to find by the most recently mapped window
- if [ -z "$new_addr" ] || [ "$new_addr" = "null" ]; then
- new_addr=$(hyprctl clients -j | jq -r 'sort_by(.focusHistoryID) | .[-1] | .address')
- fi
+ local count_after=$(echo "$windows_after" | jq 'length')
+ if [ "$count_after" -gt "$count_before" ]; then
+ new_addr=$(comm -13 \
+ <(echo "$windows_before" | jq -r '.[].address' | sort) \
+ <(echo "$windows_after" | jq -r '.[].address' | sort) |
+ head -1)
+ if [ -n "$new_addr" ] && [ "$new_addr" != "null" ]; then
+ break
+ fi
+ fi
+ sleep 0.1
+ done
if [ -n "$new_addr" ] && [ "$new_addr" != "null" ]; then
# Store the address and monitor name
echo "$new_addr $monitor_name" >"$ADDR_FILE"
debug_echo "Terminal created with address: $new_addr in special workspace on monitor $monitor_name"
- # Small delay to ensure it's properly in special workspace
- sleep 0.2
- # Move to current workspace but start hidden off-screen
- hyprctl dispatch movetoworkspacesilent "$CURRENT_WS,address:$new_addr"
- ensure_pinned "$new_addr"
- hyprctl dispatch resizewindowpixel "exact $width $height,address:$new_addr" >/dev/null 2>&1
- local off_y=$((target_y - height - 200))
- hyprctl dispatch movewindowpixel "exact $target_x $off_y,address:$new_addr" >/dev/null 2>&1
- set_hidden_state "hidden"
+ # Configure in special workspace and keep hidden until explicitly toggled.
+ set_window_floating "$new_addr" >/dev/null 2>&1
+ resize_window_exact "$new_addr" "$width" "$height" >/dev/null 2>&1
+ move_window_exact "$new_addr" "$target_x" "$target_y" >/dev/null 2>&1
+ if [ "$STARTUP_MODE" = true ]; then
+ hide_terminal_silent "$new_addr" || debug_echo "Failed to hide new dropdown terminal after spawn (silent)"
+ else
+ hide_terminal "$new_addr" || debug_echo "Failed to hide new dropdown terminal after spawn"
+ fi
return 0
fi
@@ -452,80 +803,31 @@ spawn_terminal() {
# Main logic
TERMINAL_ADDR=$(resolve_terminal_address)
-
-if [ -n "$TERMINAL_ADDR" ]; then
- debug_echo "Found existing terminal: $TERMINAL_ADDR"
- focused_monitor=$(get_monitor_info | awk '{print $6}')
- dropdown_monitor=$(get_terminal_monitor)
- if [ "$focused_monitor" != "$dropdown_monitor" ]; then
- debug_echo "Monitor focus changed: moving dropdown to $focused_monitor"
- # Calculate new position for focused monitor
- pos_info=$(calculate_dropdown_position)
- target_x=$(echo $pos_info | cut -d' ' -f1)
- target_y=$(echo $pos_info | cut -d' ' -f2)
- width=$(echo $pos_info | cut -d' ' -f3)
- height=$(echo $pos_info | cut -d' ' -f4)
- monitor_name=$(echo $pos_info | cut -d' ' -f5)
- # Move and resize window
- hyprctl dispatch movewindowpixel "exact $target_x $target_y,address:$TERMINAL_ADDR"
- hyprctl dispatch resizewindowpixel "exact $width $height,address:$TERMINAL_ADDR"
- # Update ADDR_FILE
- echo "$TERMINAL_ADDR $monitor_name" >"$ADDR_FILE"
+if [ -z "$TERMINAL_ADDR" ]; then
+ debug_echo "No existing terminal found, creating new one"
+ if spawn_terminal; then
+ TERMINAL_ADDR=$(get_terminal_address)
fi
+fi
- hidden_state=$(get_hidden_state)
- if [ "$hidden_state" = "hidden" ] || [ -z "$hidden_state" ] || window_is_hidden "$TERMINAL_ADDR"; then
- debug_echo "Bringing terminal from hidden position with slide down animation"
-
- # Calculate target position
- pos_info=$(calculate_dropdown_position)
- target_x=$(echo $pos_info | cut -d' ' -f1)
- target_y=$(echo $pos_info | cut -d' ' -f2)
- width=$(echo $pos_info | cut -d' ' -f3)
- height=$(echo $pos_info | cut -d' ' -f4)
-
- ensure_pinned "$TERMINAL_ADDR"
-
- # Set size and animate slide down
- hyprctl dispatch resizewindowpixel "exact $width $height,address:$TERMINAL_ADDR"
- animate_slide_down "$TERMINAL_ADDR" "$target_x" "$target_y" "$width" "$height"
-
- hyprctl dispatch focuswindow "address:$TERMINAL_ADDR"
- set_hidden_state "shown"
- else
- debug_echo "Hiding terminal off-screen with slide up animation"
-
- # Get current geometry for animation
- geometry=$(get_window_geometry "$TERMINAL_ADDR")
- if [ -n "$geometry" ]; then
- curr_x=$(echo $geometry | cut -d' ' -f1)
- curr_y=$(echo $geometry | cut -d' ' -f2)
- curr_width=$(echo $geometry | cut -d' ' -f3)
- curr_height=$(echo $geometry | cut -d' ' -f4)
-
- debug_echo "Current geometry: ${curr_x},${curr_y} ${curr_width}x${curr_height}"
+if [ -z "$TERMINAL_ADDR" ]; then
+ debug_echo "No dropdown terminal instance is available"
+ exit 1
+fi
- # Animate slide up first
- animate_slide_up "$TERMINAL_ADDR" "$curr_x" "$curr_y" "$curr_width" "$curr_height"
+if [ "$STARTUP_MODE" = true ]; then
+ debug_echo "Startup mode requested: ensuring dropdown terminal exists and stays hidden"
+ hide_terminal_silent "$TERMINAL_ADDR"
+ exit 0
+fi
+HIDDEN_STATE=$(get_hidden_state)
+if [ "$HIDDEN_STATE" != "hidden" ] && [ "$HIDDEN_STATE" != "shown" ]; then
+ HIDDEN_STATE=$(infer_hidden_state "$TERMINAL_ADDR")
+ set_hidden_state "$HIDDEN_STATE"
+fi
- # Move off-screen after animation
- off_y=$((curr_y - curr_height - 200))
- hyprctl dispatch movewindowpixel "exact $curr_x $off_y,address:$TERMINAL_ADDR" >/dev/null 2>&1
- ensure_unpinned "$TERMINAL_ADDR"
- set_hidden_state "hidden"
- else
- debug_echo "Could not get window geometry, moving off-screen without animation"
- hyprctl dispatch movewindowpixel "exact 0 -1000,address:$TERMINAL_ADDR" >/dev/null 2>&1
- ensure_unpinned "$TERMINAL_ADDR"
- set_hidden_state "hidden"
- fi
- fi
+if [ "$HIDDEN_STATE" = "hidden" ]; then
+ show_terminal "$TERMINAL_ADDR"
else
- debug_echo "No existing terminal found, creating new one"
- if spawn_terminal; then
- TERMINAL_ADDR=$(get_terminal_address)
- if [ -n "$TERMINAL_ADDR" ]; then
- hyprctl dispatch focuswindow "address:$TERMINAL_ADDR"
- fi
- fi
+ hide_terminal "$TERMINAL_ADDR"
fi
diff --git a/config/hypr/scripts/ExternalBrightness.sh b/config/hypr/scripts/ExternalBrightness.sh
index 29c1b238..59081e1d 100755
--- a/config/hypr/scripts/ExternalBrightness.sh
+++ b/config/hypr/scripts/ExternalBrightness.sh
@@ -5,99 +5,253 @@
# License: GNU GPLv3
# SPDX-License-Identifier: GPL-3.0-or-later
# ==================================================
-# External monitor brightness via ddcutil
+# External monitor brightness via ddcutil with dynamic monitor detection
set -u
+# Configuration
step=10
-vcp_code=10
+min=5
+vcp_code=10 # MCCS VCP feature 0x10: Luminance (brightness)
+state_file="/tmp/external_brightness_bus"
+cache_file="/tmp/external_brightness_displays.cache"
+cache_ttl=300 # 5 minutes
-usage() {
- cat <<'EOF'
-Usage: ExternalBrightness.sh [--get|--inc|--dec|--set N] [--display N]
-Env:
- DDCUTIL_DISPLAY Optional display number passed to ddcutil --display
- DDCUTIL_OPTS Extra options passed to ddcutil (e.g. "--sleep-multiplier 0.2")
-EOF
+# Detect active Hyprland config mode (Lua entrypoint vs legacy .conf includes)
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+
+if [[ -n "${HYPR_CONFIG_MODE:-}" ]]; then
+ case "${HYPR_CONFIG_MODE,,}" in
+ lua) hypr_config_mode="lua" ;;
+ conf|hyprlang) hypr_config_mode="conf" ;;
+ auto) hypr_config_mode="" ;;
+ *) hypr_config_mode="" ;;
+ esac
+fi
+
+if [[ -z "${hypr_config_mode:-}" ]]; then
+ if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+ else
+ hypr_config_mode="conf"
+ fi
+fi
+
+# Get list of displays: bus model index
+# Format: BUS|MODEL|INDEX
+get_displays() {
+ if [[ -f "$cache_file" ]]; then
+ local now mtime
+ now=$(date +%s)
+ mtime=$(stat -c %Y "$cache_file")
+ if (( now - mtime < cache_ttl )); then
+ cat "$cache_file"
+ return
+ fi
+ fi
+
+ local res
+ res=$(ddcutil detect --terse 2>/dev/null | awk '
+ /^Display/ {
+ if (bus) {
+ count[model]++
+ print bus "|" model "|" count[model]
+ }
+ bus=""; model="Unknown"
+ }
+ /I2C bus:/ { bus=$0; sub(/.*\/dev\/i2c-/, "", bus) }
+ /Model:/ { model=$0; sub(/.*Model:[[:space:]]*/, "", model) }
+ END {
+ if (bus) {
+ count[model]++
+ print bus "|" model "|" count[model]
+ }
+ }
+ ')
+
+ if [[ -n "$res" ]]; then
+ echo "$res" > "$cache_file"
+ echo "$res"
+ fi
+}
+
+get_active_bus() {
+ local displays
+ displays=$(get_displays)
+ if [[ -z "$displays" ]]; then
+ return 1
+ fi
+
+ local saved
+ if [[ -f "$state_file" ]]; then
+ saved=$(cat "$state_file")
+ # Check if saved bus still exists in current displays
+ if echo "$displays" | grep -q "^$saved|"; then
+ echo "$saved"
+ return 0
+ fi
+ fi
+
+ # Default to first display's bus
+ echo "$displays" | head -n 1 | cut -d'|' -f1
+}
+
+set_active_bus() {
+ echo "$1" > "$state_file"
+}
+
+cycle_display() {
+ local displays
+ displays=$(get_displays)
+ [[ -z "$displays" ]] && return 1
+
+ local current
+ current=$(get_active_bus) || return 1
+
+ local next
+ next=$(echo "$displays" | awk -v current="$current" -F'|' '
+ {
+ a[n++] = $1
+ }
+ END {
+ for (i=0; i<n; i++) {
+ if (a[i] == current) {
+ print a[(i+1)%n]
+ exit
+ }
+ }
+ print a[0]
+ }
+ ')
+ set_active_bus "$next"
}
ddcutil_cmd() {
- local display_arg=()
- local display="${DDCUTIL_DISPLAY:-}"
- if [[ -n "${display}" ]]; then
- display_arg+=(--display "${display}")
- fi
- ddcutil ${DDCUTIL_OPTS:-} "${display_arg[@]}" "$@"
+ local bus="$1"
+ shift
+ ddcutil --bus "$bus" ${DDCUTIL_OPTS:-} "$@"
}
get_brightness() {
- # Example output: "VCP code 0x10 (Brightness): current value = 50, max value = 100"
- local line
- if ! line="$(ddcutil_cmd getvcp "${vcp_code}" 2>/dev/null | tail -n 1)"; then
- return 1
- fi
- local current max
- current="$(printf "%s" "${line}" | sed -n 's/.*current value = \([0-9]\+\).*/\1/p')"
- max="$(printf "%s" "${line}" | sed -n 's/.*max value = \([0-9]\+\).*/\1/p')"
- [[ -n "${current}" && -n "${max}" ]] || return 1
- printf "%s %s\n" "${current}" "${max}"
+ local bus="$1"
+ local line
+ if ! line="$(ddcutil_cmd "$bus" getvcp "$vcp_code" 2>/dev/null | tail -n 1)"; then
+ return 1
+ fi
+
+ local current max
+ current="$(sed -n 's/.*current value = *\([0-9]\+\).*/\1/p' <<< "$line")"
+ max="$(sed -n 's/.*max value = *\([0-9]\+\).*/\1/p' <<< "$line")"
+
+ [[ -n "$current" && -n "$max" ]] || return 1
+ printf "%s %s\n" "$current" "$max"
}
set_brightness() {
- local value="$1"
- ddcutil_cmd setvcp "${vcp_code}" "${value}" >/dev/null 2>&1
+ local bus="$1"
+ local value="$2"
+ ddcutil_cmd "$bus" setvcp "$vcp_code" "$value" >/dev/null 2>&1
}
json_output() {
- local current max percent icon
- if ! read -r current max < <(get_brightness); then
- printf '{"text":"󰃜 N/A","tooltip":"External brightness unavailable (load i2c-dev, allow i2c access)","class":"brightness-external-off"}\n'
- return 0
- fi
- percent=$(( current * 100 / max ))
- if (( percent >= 80 )); then
- icon="󰃠"
- elif (( percent >= 60 )); then
- icon="󰃟"
- elif (( percent >= 40 )); then
- icon="󰃞"
- elif (( percent >= 20 )); then
- icon="󰃝"
- else
- icon=""
- fi
- printf '{"text":"%s %s%%","tooltip":"External display brightness: %s%%","class":"brightness-external"}\n' "${icon}" "${percent}" "${percent}"
+ local displays
+ displays=$(get_displays)
+ if [[ -z "$displays" ]]; then
+ printf '{"text":"󰃜 N/A","tooltip":"No DDC/CI displays detected","class":"brightness-external-off"}\n'
+ return 0
+ fi
+
+ local active_bus
+ active_bus=$(get_active_bus) || return 1
+
+ local current max percent
+ if ! read -r current max < <(get_brightness "$active_bus"); then
+ current="N/A"
+ percent="N/A"
+ else
+ percent=$(( current * 100 / max ))
+ fi
+
+ # Build tooltip
+ local tooltip=""
+ local active_name="Unknown"
+
+ while IFS='|' read -r bus model index; do
+ local name="${model}"
+ [[ $index -gt 1 ]] && name="${name} #${index}"
+
+ if [[ "$bus" == "$active_bus" ]]; then
+ tooltip="${tooltip}[ ${name} ]\\n"
+ active_name="$name"
+ else
+ tooltip="${tooltip}${name}\\n"
+ fi
+ done <<< "$displays"
+
+ tooltip="${tooltip}\\nBrightness: ${percent}%\\nClick to switch display"
+
+ local icon="󰃟"
+ if [[ "$percent" != "N/A" ]]; then
+ if (( percent >= 80 )); then icon="󰃠";
+ elif (( percent >= 60 )); then icon="󰃟";
+ elif (( percent >= 40 )); then icon="󰃞";
+ elif (( percent >= 20 )); then icon="󰃝";
+ else icon=""; fi
+ printf '{"text":"%s %s%%","tooltip":"%s","class":"brightness-external"}\n' "$icon" "$percent" "$tooltip"
+ else
+ printf '{"text":"󰃜 %s","tooltip":"%s","class":"brightness-external-off"}\n' "$current" "$tooltip"
+ fi
}
case "${1:-}" in
- --get|"")
- json_output
- ;;
- --inc|--dec)
- read -r current max < <(get_brightness) || exit 1
- delta=$step
- [[ "$1" == "--dec" ]] && delta=$(( -step ))
- new=$(( current + delta ))
- (( new < 5 )) && new=5
- (( new > max )) && new="${max}"
- set_brightness "${new}"
- json_output
- ;;
- --set)
- [[ -n "${2:-}" ]] || { usage; exit 1; }
- set_brightness "${2}"
- json_output
- ;;
- --display)
- [[ -n "${2:-}" ]] || { usage; exit 1; }
- DDCUTIL_DISPLAY="${2}" shift 2
- "${0}" "${@:-"--get"}"
- ;;
- -h|--help)
- usage
- ;;
- *)
- usage
- exit 1
- ;;
+ --get|"")
+ json_output
+ ;;
+ --inc|--dec)
+ bus=$(get_active_bus) || exit 0
+ read -r current max < <(get_brightness "$bus") || { json_output; exit 0; }
+ delta=$step
+ [[ "$1" == "--dec" ]] && delta=$(( -step ))
+ new=$(( current + delta ))
+ (( new < min )) && new=$min
+ (( new > max )) && new="$max"
+ set_brightness "$bus" "$new"
+ json_output
+ ;;
+ --set)
+ [[ -n "${2:-}" ]] || exit 1
+ bus=$(get_active_bus) || exit 1
+ set_brightness "$bus" "$2"
+ json_output
+ ;;
+ --cycle)
+ cycle_display
+ json_output
+ ;;
+ --bus)
+ [[ -n "${2:-}" ]] || exit 1
+ set_active_bus "$2"
+ shift 2
+ "${0}" "${@:-"--get"}"
+ ;;
+ --display)
+ # Compatibility with old --display N but now it expects a bus number
+ # Or we could map display number to bus number
+ [[ -n "${2:-}" ]] || exit 1
+ bus_target=$(get_displays | awk -v target="$2" -F'|' 'NR==target {print $1}')
+ if [[ -n "$bus_target" ]]; then
+ set_active_bus "$bus_target"
+ fi
+ shift 2
+ "${0}" "${@:-"--get"}"
+ ;;
+ -h|--help)
+ echo "Usage: $0 [--get|--inc|--dec|--set N|--cycle|--bus N|--display N]"
+ ;;
+ *)
+ exit 1
+ ;;
esac
diff --git a/config/hypr/scripts/Float-all-Windows.sh b/config/hypr/scripts/Float-all-Windows.sh
index e658cabb..25fdc465 100755
--- a/config/hypr/scripts/Float-all-Windows.sh
+++ b/config/hypr/scripts/Float-all-Windows.sh
@@ -6,5 +6,28 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# ==================================================
+# Detect active Hyprland config mode (Lua entrypoint vs legacy .conf includes)
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+
+if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+else
+ hypr_config_mode="conf"
+fi
+
+# Get current workspace ID
ws=$(hyprctl activeworkspace -j | jq -r .id)
-hyprctl clients -j | jq -r --arg ws "$ws" '.[] | select(.workspace.id == ($ws|tonumber)) | .address' | xargs -r -I {} hyprctl dispatch togglefloating address:{}
+
+# Process all windows on the current workspace
+if [[ "$hypr_config_mode" == "lua" ]]; then
+ # In Lua mode, use the native Lua API via hl.dispatch to ensure compatibility
+ hyprctl clients -j | jq -r --arg ws "$ws" '.[] | select(.workspace.id == ($ws|tonumber)) | .address' | while read -r addr; do
+ hyprctl dispatch "hl.dispatch(hl.dsp.window.float({ window = \"address:${addr}\", action = \"toggle\" }))" >/dev/null 2>&1
+ done
+else
+ # Legacy Hyprlang mode
+ hyprctl clients -j | jq -r --arg ws "$ws" '.[] | select(.workspace.id == ($ws|tonumber)) | .address' | xargs -r -I {} hyprctl dispatch togglefloating address:{}
+fi
diff --git a/config/hypr/scripts/GameMode.sh b/config/hypr/scripts/GameMode.sh
index 9c5b8264..b104179e 100755
--- a/config/hypr/scripts/GameMode.sh
+++ b/config/hypr/scripts/GameMode.sh
@@ -12,31 +12,89 @@ SCRIPTSDIR="$HOME/.config/hypr/scripts"
# shellcheck source=/dev/null
. "$SCRIPTSDIR/WallpaperCmd.sh"
+# Detect active Hyprland config mode (Lua entrypoint vs legacy .conf includes)
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
-HYPRGAMEMODE=$(hyprctl getoption animations:enabled | awk 'NR==1{print $2}')
-if [ "$HYPRGAMEMODE" = 1 ] ; then
- hyprctl --batch "\
- keyword animations:enabled 0;\
- keyword decoration:shadow:enabled 0;\
- keyword decoration:blur:enabled 0;\
- keyword general:gaps_in 0;\
- keyword general:gaps_out 0;\
- keyword general:border_size 1;\
- keyword decoration:rounding 0"
-
-\thyprctl keyword "windowrule opacity 1 override 1 override 1 override, ^(.*)$"
+if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+else
+ hypr_config_mode="conf"
+fi
+
+# Check if animations are currently enabled
+HYPRGAMEMODE=$(hyprctl getoption animations:enabled -j | jq -r '.bool' 2>/dev/null)
+if [[ "$HYPRGAMEMODE" == "null" || -z "$HYPRGAMEMODE" ]]; then
+ HYPRGAMEMODE=$(hyprctl getoption animations:enabled | awk 'NR==1{print $2}')
+fi
+
+if [ "$HYPRGAMEMODE" = "true" ] || [ "$HYPRGAMEMODE" = "1" ] ; then
+ # ENABLE Game Mode (Disable animations/decorations)
+ if [[ "$hypr_config_mode" == "lua" ]]; then
+ hyprctl eval "hl.config({
+ animations = { enabled = false },
+ decoration = { shadow = { enabled = false }, blur = { enabled = false }, rounding = 0 },
+ general = { gaps_in = 0, gaps_out = 0, border_size = 1 }
+ })"
+ hyprctl eval "hl.window_rule({ name = 'gamemode-opacity', match = { class = '.*' }, opacity = 1.0 })"
+ else
+ hyprctl --batch "\
+ keyword animations:enabled 0;\
+ keyword decoration:shadow:enabled 0;\
+ keyword decoration:blur:enabled 0;\
+ keyword general:gaps_in 0;\
+ keyword general:gaps_out 0;\
+ keyword general:border_size 1;\
+ keyword decoration:rounding 0"
+ hyprctl keyword "windowrule opacity 1 override 1 override 1 override, ^(.*)$"
+ fi
+
"$WWW_CMD" kill
notify-send -e -u low -i "$notif" " Gamemode:" " enabled"
sleep 0.1
exit
else
-\t"$WWW_DAEMON" "${WWW_DAEMON_ARGS[@]}" && "$WWW_CMD" img "$HOME/.config/rofi/.current_wallpaper" &
- sleep 0.1
- ${SCRIPTSDIR}/WallustSwww.sh
- sleep 0.5
- hyprctl reload
- ${SCRIPTSDIR}/Refresh.sh
+ # DISABLE Game Mode (Restore animations/decorations)
+ if [[ "$hypr_config_mode" == "lua" ]]; then
+ # Explicitly restore to defaults (matching settings.lua where possible)
+ hyprctl eval "hl.config({
+ animations = { enabled = true },
+ decoration = { shadow = { enabled = true }, blur = { enabled = true }, rounding = 10 },
+ general = { gaps_in = 2, gaps_out = 4, border_size = 2 }
+ })"
+ # Removing rule in Lua mode might require a different approach if no 'remove' exists
+ # We'll reload the config as a fallback or try to nullify it
+ hyprctl eval "hl.window_rule({ name = 'gamemode-opacity', match = { class = 'NONE' }, opacity = 1.0 })"
+ else
+ hyprctl --batch "\
+ keyword animations:enabled 1;\
+ keyword decoration:shadow:enabled 1;\
+ keyword decoration:blur:enabled 1;\
+ keyword general:gaps_in 2;\
+ keyword general:gaps_out 4;\
+ keyword general:border_size 2;\
+ keyword decoration:rounding 10"
+ hyprctl keyword "windowrule opacity 1 override 1 override 1 override, ^(NONE)$"
+ fi
+
+ # Restore wallpaper using the official daemon script
+ if [[ -x "${SCRIPTSDIR}/WallpaperDaemon.sh" ]]; then
+ "${SCRIPTSDIR}/WallpaperDaemon.sh" &
+ fi
+
+ sleep 0.1
+ ${SCRIPTSDIR}/WallustSwww.sh
+ sleep 0.5
+
+ # Refresh UI components
+ if [[ -x "${SCRIPTSDIR}/Refresh.sh" ]]; then
+ "${SCRIPTSDIR}/Refresh.sh"
+ else
+ hyprctl reload
+ fi
+
notify-send -e -u normal -i "$notif" " Gamemode:" " disabled"
exit
fi
-hyprctl reload
diff --git a/config/hypr/scripts/Ghostty_themes.sh b/config/hypr/scripts/Ghostty_themes.sh
index a69f246e..c3fcd792 100755
--- a/config/hypr/scripts/Ghostty_themes.sh
+++ b/config/hypr/scripts/Ghostty_themes.sh
@@ -11,6 +11,10 @@ config_file="$HOME/.config/ghostty/config"
iDIR="$HOME/.config/swaync/images"
rofi_theme_primary="$HOME/.config/rofi/config-ghostty-theme.rasi"
rofi_theme_fallback="$HOME/.config/rofi/config-edit.rasi"
+wallust_include_path="$HOME/.config/ghostty/wallust.conf"
+wallust_option_label="Set by wallpaper"
+default_option_label="Default - no color"
+wallust_refresh_script="$HOME/.config/hypr/scripts/WallustSwww.sh"
notify_user() {
local icon="$1"
@@ -23,6 +27,13 @@ notify_user() {
fi
}
+refresh_wallpaper_theme() {
+ if [[ -x "$wallust_refresh_script" ]]; then
+ "$wallust_refresh_script" >/dev/null 2>&1 || true
+ fi
+ pkill -SIGUSR2 ghostty >/dev/null 2>&1 || true
+}
+
if [[ ! -f "$config_file" ]]; then
notify_user "$iDIR/error.png" "Ghostty Theme" "Config not found: $config_file"
exit 1
@@ -46,6 +57,11 @@ current_theme=$(
}' "$config_file"
)
+wallust_enabled=$(
+ awk '/^[[:space:]]*config-file[[:space:]]*=/ && $0 !~ /^[[:space:]]*#/ && /wallust\.conf/ {print "1"; exit}' "$config_file"
+)
+[[ "$wallust_enabled" != "1" ]] && wallust_enabled="0"
+
mapfile -t available_theme_names < <(
awk -F'=' '/^[[:space:]]*#[[:space:]]*theme[[:space:]]*=/ {
val=$2
@@ -60,29 +76,103 @@ if [[ ${#available_theme_names[@]} -eq 0 ]]; then
notify_user "$iDIR/error.png" "Ghostty Theme" "No commented themes found in $config_file"
exit 1
fi
-
-menu_entries=()
-if [[ -n "$current_theme" ]]; then
- menu_entries+=("Current: $current_theme")
-fi
+menu_entries=("$wallust_option_label" "$default_option_label")
for t in "${available_theme_names[@]}"; do
menu_entries+=("$t")
done
+current_selection_index=0
+if [[ "$wallust_enabled" == "1" ]]; then
+ current_selection_index=0
+elif [[ -z "$current_theme" ]]; then
+ current_selection_index=1
+else
+ current_selection_index=1
+ for i in "${!available_theme_names[@]}"; do
+ if [[ "${available_theme_names[$i]}" == "$current_theme" ]]; then
+ current_selection_index=$((i + 2))
+ break
+ fi
+ done
+fi
choice=$(
printf "%s\n" "${menu_entries[@]}" |
- rofi -i -dmenu -p "Ghostty Theme" "${rofi_config_args[@]}" -mesg "Select a theme to apply"
+ rofi -i -dmenu -p "Ghostty Theme" "${rofi_config_args[@]}" -mesg "Select a theme to apply" -selected-row "$current_selection_index"
)
[[ -z "$choice" ]] && exit 0
-if [[ "$choice" == "Current: "* ]]; then
+
+selected_theme="$choice"
+
+if [[ "$selected_theme" == "$wallust_option_label" ]]; then
+ if [[ "$wallust_enabled" == "1" ]]; then
+ exit 0
+ fi
+
+ tmp_file=$(mktemp)
+ awk -v wallust_include_path="$wallust_include_path" '
+function trim(s) { sub(/^[[:space:]]+/, "", s); sub(/[[:space:]]+$/, "", s); return s }
+{
+ line=$0
+ if ($0 ~ /^[[:space:]]*theme[[:space:]]*=/) {
+ sub(/^[[:space:]]*theme[[:space:]]*=/, "#theme =", line)
+ print line
+ next
+ }
+ if ($0 ~ /^[[:space:]]*#?[[:space:]]*config-file[[:space:]]*=/ && $0 ~ /wallust\.conf/) {
+ print "config-file = " wallust_include_path
+ wallust_set=1
+ next
+ }
+ print $0
+}
+END {
+ if (!wallust_set) {
+ print "config-file = " wallust_include_path
+ }
+}
+' "$config_file" > "$tmp_file"
+ mv "$tmp_file" "$config_file"
+ refresh_wallpaper_theme
+ notify_user "$iDIR/ja.png" "Ghostty Theme Applied" "$wallust_option_label"
exit 0
fi
+if [[ "$selected_theme" == "$default_option_label" ]]; then
+ if [[ "$wallust_enabled" != "1" && -z "$current_theme" ]]; then
+ exit 0
+ fi
-selected_theme="$choice"
+ tmp_file=$(mktemp)
+ awk -v wallust_include_path="$wallust_include_path" '
+{
+ line=$0
+ if ($0 ~ /^[[:space:]]*theme[[:space:]]*=/) {
+ sub(/^[[:space:]]*theme[[:space:]]*=/, "#theme =", line)
+ print line
+ next
+ }
+ if ($0 ~ /^[[:space:]]*#?[[:space:]]*config-file[[:space:]]*=/ && $0 ~ /wallust\.conf/) {
+ print "#config-file = " wallust_include_path
+ wallust_seen=1
+ next
+ }
+ print $0
+}
+END {
+ if (!wallust_seen) {
+ print "#config-file = " wallust_include_path
+ }
+}
+' "$config_file" > "$tmp_file"
+ mv "$tmp_file" "$config_file"
+
+ pkill -SIGUSR2 ghostty >/dev/null 2>&1 || true
+ notify_user "$iDIR/ja.png" "Ghostty Theme Applied" "$default_option_label"
+ exit 0
+fi
-if [[ -n "$current_theme" && "$selected_theme" == "$current_theme" ]]; then
+if [[ "$wallust_enabled" != "1" && -n "$current_theme" && "$selected_theme" == "$current_theme" ]]; then
exit 0
fi
@@ -97,11 +187,16 @@ format_theme_value() {
selected_formatted=$(format_theme_value "$selected_theme")
tmp_file=$(mktemp)
-awk -v selected="$selected_theme" -v selected_formatted="$selected_formatted" '
+awk -v selected="$selected_theme" -v selected_formatted="$selected_formatted" -v wallust_include_path="$wallust_include_path" '
function trim(s) { sub(/^[[:space:]]+/, "", s); sub(/[[:space:]]+$/, "", s); return s }
function strip_quotes(s) { gsub(/^"|"$/, "", s); return s }
{
line=$0
+ if ($0 ~ /^[[:space:]]*#?[[:space:]]*config-file[[:space:]]*=/ && $0 ~ /wallust\.conf/) {
+ print "#config-file = " wallust_include_path
+ wallust_seen=1
+ next
+ }
if ($0 ~ /^[[:space:]]*theme[[:space:]]*=/) {
sub(/^[[:space:]]*theme[[:space:]]*=/, "#theme =", line)
print line
@@ -118,6 +213,11 @@ function strip_quotes(s) { gsub(/^"|"$/, "", s); return s }
}
}
print $0
+}
+END {
+ if (!wallust_seen) {
+ print "#config-file = " wallust_include_path
+ }
}' "$config_file" > "$tmp_file"
mv "$tmp_file" "$config_file"
diff --git a/config/hypr/scripts/HyprLayoutModule.sh b/config/hypr/scripts/HyprLayoutModule.sh
new file mode 100755
index 00000000..22f12920
--- /dev/null
+++ b/config/hypr/scripts/HyprLayoutModule.sh
@@ -0,0 +1,131 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Waybar module for Hyprland layouts
+
+IFS=$'\n\t'
+
+SCRIPTSDIR="$HOME/.config/hypr/scripts"
+rofi_config="$HOME/.config/rofi/config-waybar-layout.rasi"
+change_layout="${SCRIPTSDIR}/ChangeLayout.sh"
+layouts=(dwindle scrolling monocle master)
+
+layout_icon() {
+ case "$1" in
+ dwindle) echo "🄳" ;;
+ scrolling) echo "🅂" ;;
+ monocle) echo "🄼" ;;
+ master) echo "ⓜ" ;;
+ *) echo "󰹑" ;;
+ esac
+}
+
+layout_name() {
+ case "$1" in
+ dwindle) echo "Dwindle" ;;
+ scrolling) echo "Scrolling" ;;
+ monocle) echo "Monocle" ;;
+ master) echo "Master" ;;
+ *) echo "Unknown" ;;
+ esac
+}
+
+get_layout() {
+ hyprctl -j getoption general:layout 2>/dev/null | jq -r '.str // "unknown"' 2>/dev/null
+}
+
+next_layout() {
+ local current="$1"
+ local i
+
+ for i in "${!layouts[@]}"; do
+ if [[ "${layouts[i]}" == "$current" ]]; then
+ echo "${layouts[((i + 1) % ${#layouts[@]})]}"
+ return
+ fi
+ done
+
+ echo "${layouts[0]}"
+}
+
+refresh_waybar() {
+ pkill -RTMIN+8 waybar 2>/dev/null || true
+}
+
+set_layout() {
+ local target="$1"
+
+ "$change_layout" "$target" && refresh_waybar
+}
+
+show_status() {
+ local current icon name tooltip
+
+ current="$(get_layout)"
+ icon="$(layout_icon "$current")"
+ name="$(layout_name "$current")"
+ tooltip="Hyprland layout: ${name} (${icon})\n\nLeft click: Select layout\nRight click: Cycle layout\n\nOptions:\n🄳 Dwindle\n🅂 Scrolling\n🄼 Monocle\nⓜ Master"
+
+ printf '{"text":"%s","tooltip":"%s","class":"%s"}\n' "$icon" "$tooltip" "$current"
+}
+
+show_menu() {
+ local current default_row choice target i
+ local options=()
+
+ current="$(get_layout)"
+ default_row=0
+
+ for i in "${!layouts[@]}"; do
+ local layout="${layouts[i]}"
+ local prefix=" "
+
+ if [[ "$layout" == "$current" ]]; then
+ prefix="● "
+ default_row="$i"
+ fi
+
+ options+=("${prefix}$(layout_icon "$layout") $(layout_name "$layout")")
+ done
+
+ if pgrep -x rofi >/dev/null; then
+ pkill rofi
+ return 0
+ fi
+
+ choice="$(printf '%s\n' "${options[@]}" | rofi -i -dmenu -p "Layout" -mesg "Left click selects • Right click cycles" -selected-row "$default_row" -config "$rofi_config")"
+ [[ -z "$choice" ]] && exit 0
+
+ case "$choice" in
+ *Dwindle*) target="dwindle" ;;
+ *Scrolling*) target="scrolling" ;;
+ *Monocle*) target="monocle" ;;
+ *Master*) target="master" ;;
+ *) exit 1 ;;
+ esac
+
+ set_layout "$target"
+}
+
+case "${1:-status}" in
+status)
+ show_status
+ ;;
+menu)
+ show_menu
+ ;;
+next|toggle)
+ set_layout "$(next_layout "$(get_layout)")"
+ ;;
+dwindle|scrolling|monocle|master)
+ set_layout "$1"
+ ;;
+*)
+ echo "Usage: $(basename "$0") [status|menu|next|dwindle|scrolling|monocle|master]" >&2
+ exit 1
+ ;;
+esac
diff --git a/config/hypr/scripts/JavaManager.sh b/config/hypr/scripts/JavaManager.sh
new file mode 100755
index 00000000..728c69a3
--- /dev/null
+++ b/config/hypr/scripts/JavaManager.sh
@@ -0,0 +1,245 @@
+#!/usr/bin/env bash
+
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Script to manage Java runtimes
+# Based on script from https://github.com/jmhorcas
+# Submitted to KoolDots >https://github.com/LinuxBeginnings/Hyprland-Dots/issues/49
+
+# --- 1. Icons (Nerd Fonts) ---
+ICON_JAVA=""
+ICON_ACTIVE="󰄬"
+ICON_INSTALLED=""
+ICON_REMOTE=" "
+
+# --- 2. Distro detection ---
+if [ -r /etc/os-release ]; then
+ . /etc/os-release
+fi
+
+DISTRO="unknown"
+case "${ID:-}" in
+arch) DISTRO="arch" ;;
+debian | ubuntu | linuxmint) DISTRO="debian" ;;
+fedora) DISTRO="fedora" ;;
+opensuse* | sles) DISTRO="opensuse" ;;
+esac
+
+if [ "$DISTRO" = "unknown" ] && [ -n "${ID_LIKE:-}" ]; then
+ case "$ID_LIKE" in
+ *arch*) DISTRO="arch" ;;
+ *debian* | *ubuntu*) DISTRO="debian" ;;
+ *fedora*) DISTRO="fedora" ;;
+ *suse*) DISTRO="opensuse" ;;
+ esac
+fi
+
+if [ "$DISTRO" = "unknown" ]; then
+ notify-send "Error" "Unsupported distro for now."
+ exit 1
+fi
+
+# --- 3. Package helpers ---
+list_installed_pkgs() {
+ case "$DISTRO" in
+ arch) pacman -Qq | grep -E '^jdk.*-openjdk$' ;;
+ debian) dpkg -l 'openjdk-*-jdk' 2>/dev/null | awk '/^ii/ {print $2}' ;;
+ fedora | opensuse) rpm -qa | grep -E '^java-[0-9]+-openjdk' ;;
+ esac
+}
+
+list_available_pkgs() {
+ case "$DISTRO" in
+ arch) pacman -Ssq '^jdk.*-openjdk$' | sort -u ;;
+ debian) apt-cache search openjdk- | awk '{print $1}' | grep -E '^openjdk-[0-9]+-jdk$' | sort -u ;;
+ fedora) dnf -q repoquery --qf '%{name}' 'java-*-openjdk' 2>/dev/null | grep -E '^java-[0-9]+-openjdk' | sort -u ;;
+ opensuse) zypper -n search -s 'java-*-openjdk' 2>/dev/null | awk -F'|' '{print $2}' | xargs -n1 | grep -E '^java-[0-9]+-openjdk' | sort -u ;;
+ esac
+}
+
+pkg_description() {
+ local pkg="$1"
+ case "$DISTRO" in
+ arch) pacman -Si "$pkg" | grep "Description" | cut -d ':' -f2- | head -n 1 | xargs ;;
+ debian) apt-cache show "$pkg" 2>/dev/null | awk -F': ' '/^Description/ {print $2; exit}' ;;
+ fedora) dnf info -q "$pkg" 2>/dev/null | awk -F': ' '/^Summary/ {print $2; exit}' ;;
+ opensuse) zypper -n info "$pkg" 2>/dev/null | awk -F': ' '/^Summary/ {print $2; exit}' ;;
+ esac
+}
+
+get_active_version_raw() {
+ case "$DISTRO" in
+ arch) archlinux-java get ;;
+ *) java -version 2>&1 | awk -F\" '/version/ {print $2; exit}' ;;
+ esac
+}
+
+get_active_version_num() {
+ local raw="$1"
+ echo "$raw" | grep -oE '[0-9]+' | head -n1
+}
+
+list_java_alternatives() {
+ case "$DISTRO" in
+ debian) update-alternatives --list java 2>/dev/null ;;
+ fedora | opensuse) alternatives --list java 2>/dev/null ;;
+ *) echo "" ;;
+ esac
+}
+
+set_default_java() {
+ local sel_num="$1"
+ case "$DISTRO" in
+ arch)
+ local java_env
+ java_env=$(archlinux-java status | grep "java-$sel_num" | awk '{print $1}' | head -n 1)
+ if [ -n "$java_env" ]; then
+ notify-send "Java" "Setting $java_env as default..."
+ pkexec archlinux-java set "$java_env"
+ else
+ notify-send "Error" "Version $sel_num is not installed. Install it first."
+ fi
+ ;;
+ debian | fedora | opensuse)
+ local alt_path
+ alt_path=$(list_java_alternatives | grep -E "java-$sel_num|jdk-$sel_num|openjdk-$sel_num" | head -n1)
+ if [ -n "$alt_path" ]; then
+ notify-send "Java" "Setting Java $sel_num as default..."
+ if [ "$DISTRO" = "debian" ]; then
+ pkexec update-alternatives --set java "$alt_path"
+ else
+ pkexec alternatives --set java "$alt_path"
+ fi
+ else
+ notify-send "Error" "Version $sel_num is not installed. Install it first."
+ fi
+ ;;
+ esac
+}
+
+install_pkg() {
+ local pkg="$1"
+ case "$DISTRO" in
+ arch) kitty -e sudo pacman -S "$pkg" ;;
+ debian) kitty -e sudo apt install "$pkg" ;;
+ fedora) kitty -e sudo dnf install "$pkg" ;;
+ opensuse) kitty -e sudo zypper install "$pkg" ;;
+ esac
+}
+
+remove_pkg() {
+ local pkg="$1"
+ case "$DISTRO" in
+ arch) kitty -e sudo pacman -Rs "$pkg" ;;
+ debian) kitty -e sudo apt remove "$pkg" ;;
+ fedora) kitty -e sudo dnf remove "$pkg" ;;
+ opensuse) kitty -e sudo zypper remove "$pkg" ;;
+ esac
+}
+
+# --- 4. Initial data ---
+INSTALLED_PKGS=$(list_installed_pkgs)
+TEMP_ALL=$(list_available_pkgs)
+
+ACTIVE_VERSION_RAW=$(get_active_version_raw)
+ACTIVE_VERSION_NUM=$(get_active_version_num "$ACTIVE_VERSION_RAW")
+
+if [ "$DISTRO" = "arch" ]; then
+ TEMP_ALL_SYSTEM=$(archlinux-java status)
+ LATEST_VERSION=$(echo "$TEMP_ALL" "$TEMP_ALL_SYSTEM" | grep -oE '[0-9]+' | sort -nr | head -n1)
+else
+ LATEST_VERSION=$(echo "$TEMP_ALL" | grep -oE '[0-9]+' | sort -nr | head -n1)
+fi
+
+ALL_AVAILABLE=$(echo "$TEMP_ALL" | awk -v lv="$LATEST_VERSION" '{
+ v=$0; gsub(/[^0-9]/,"",v);
+ if(v=="") v=lv+1;
+ print v " " $0
+}' | sort -nr | cut -d' ' -f2-)
+
+# --- 5. Functions ---
+get_version_num() {
+ local pkg="$1"
+ local v=$(echo "$pkg" | grep -oE '[0-9]+')
+ # If there is not a number, it is the "latest"
+ if [ -z "$v" ]; then
+ echo "$LATEST_VERSION"
+ else
+ echo "$v"
+ fi
+}
+
+generate_list() {
+ for pkg in $ALL_AVAILABLE; do
+ DESC=$(pkg_description "$pkg")
+
+ VERSION=$(get_version_num "$pkg")
+ ICON="$ICON_REMOTE"
+ # We use a temporary variable for the icon to avoid overwriting ICON_REMOTE
+ DISPLAY_ICON="$ICON_REMOTE"
+
+ # 1. Is it installed?
+ if echo "$INSTALLED_PKGS" | grep -q "^$pkg$"; then
+ DISPLAY_ICON="$ICON_INSTALLED"
+ fi
+
+ # 2. Is it the active one?
+ # We compare if the number matches OR if the raw name matches
+ if [[ "$VERSION" == "$ACTIVE_VERSION_NUM" ]] || [[ "$pkg" == "$ACTIVE_VERSION_RAW" ]]; then
+ DISPLAY_ICON="$ICON_ACTIVE"
+ fi
+
+ # Ensure that if the icon is empty (remote) it maintains the space
+ [ -z "$DISPLAY_ICON" ] && DISPLAY_ICON=" "
+
+ printf "%s %s %s │ %s\n" "$ICON_JAVA" "$DISPLAY_ICON" "$DESC" "$pkg"
+ done
+}
+
+# --- 6. rofi ---
+THEME="$HOME/.config/rofi/config.rasi"
+
+SELECTION=$(generate_list | rofi -dmenu -i \
+ -p "󰒓 Java" \
+ -config "$THEME" \
+ -theme-str '
+ window { width: 1400px; }
+ listview { scrollbar: true; }
+ inputbar { children: [ "prompt", "textbox-prompt-colon", "entry" ]; }
+ prompt { background-color: @selected; text-color: @background; padding: 4px 8px; border-radius: 4px; }
+ ' \
+ -kb-custom-1 "Alt+i" \
+ -kb-custom-2 "Alt+r" \
+ -mesg "<b>$ICON_ACTIVE</b> Default | <b>$ICON_INSTALLED</b> Installed | <b>Enter:</b> Set default | <b>Alt+I:</b> Install | <b>Alt+R:</b> Remove")
+
+EXIT_CODE=$?
+[ -z "$SELECTION" ] && exit 0
+
+# --- 7. Clean selection ---
+# 1. Extract the package name from the right side of the │ separator
+PKG_NAME=$(echo "$SELECTION" | awk -F'│' '{print $2}' | xargs | awk '{print $1}')
+
+# 2. Get the real number contained in the name (if it exists)
+SEL_RAW_NUM=$(echo "$PKG_NAME" | grep -oE '[0-9]+')
+
+# If it doesn't have a value (it's the latest package), assign LATEST_VERSION
+if [ -z "$SEL_RAW_NUM" ]; then
+ SEL_RAW_NUM="$LATEST_VERSION"
+fi
+
+# --- 8. Actions ---
+case $EXIT_CODE in
+0) # SET DEFAULT
+ set_default_java "$SEL_RAW_NUM"
+ ;;
+10) # INSTALL
+ install_pkg "$PKG_NAME"
+ ;;
+11) # REMOVE
+ remove_pkg "$PKG_NAME"
+ ;;
+esac
diff --git a/config/hypr/scripts/KeyBinds.sh b/config/hypr/scripts/KeyBinds.sh
index 69111047..1005e797 100755
--- a/config/hypr/scripts/KeyBinds.sh
+++ b/config/hypr/scripts/KeyBinds.sh
@@ -16,15 +16,42 @@ if pidof rofi > /dev/null; then
fi
# define the config files
-keybinds_conf="$HOME/.config/hypr/configs/Keybinds.conf"
-user_keybinds_conf="$HOME/.config/hypr/UserConfigs/UserKeybinds.conf"
-laptop_conf="$HOME/.config/hypr/UserConfigs/Laptops.conf"
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+keybinds_conf="$hypr_dir/configs/Keybinds.conf"
+user_keybinds_conf="$hypr_dir/UserConfigs/UserKeybinds.conf"
+laptop_conf="$hypr_dir/UserConfigs/Laptops.conf"
+lua_keybinds_conf="$hypr_dir/lua/keybinds.lua"
+lua_user_keybinds="$hypr_dir/UserConfigs/user_keybinds.lua"
+lua_system_keybinds="$hypr_dir/configs/system_keybinds.lua"
+lua_legacy_system_keybinds="$hypr_dir/UserConfigs/system_keybinds.lua"
+lua_overrides="$hypr_dir/UserConfigs/user_overrides.lua"
rofi_theme="$HOME/.config/rofi/config-keybinds.rasi"
msg='☣️ NOTE ☣️: Clicking with Mouse or Pressing ENTER will have NO function'
-# collect raw bind lines (strip end-of-line comments) from available files
-files=("$keybinds_conf" "$user_keybinds_conf")
-[[ -f "$laptop_conf" ]] && files+=("$laptop_conf")
+# detect active Hyprland config mode (Lua entrypoint vs legacy .conf includes)
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+else
+ hypr_config_mode="conf"
+fi
+
+# collect raw bind lines from available files
+if [[ "$hypr_config_mode" == "lua" ]]; then
+ files=("$lua_keybinds_conf")
+ if [[ -f "$lua_system_keybinds" ]]; then
+ files+=("$lua_system_keybinds")
+ elif [[ -f "$lua_legacy_system_keybinds" ]]; then
+ files+=("$lua_legacy_system_keybinds")
+ fi
+ [[ -f "$lua_user_keybinds" ]] && files+=("$lua_user_keybinds")
+ [[ -f "$lua_overrides" ]] && files+=("$lua_overrides")
+else
+ files=("$keybinds_conf" "$user_keybinds_conf")
+ [[ -f "$laptop_conf" ]] && files+=("$laptop_conf")
+fi
# Parse binds using the python script for speed
# The last argument must be the user config for override logic to work correctly
diff --git a/config/hypr/scripts/KeybindsLayoutInit.sh b/config/hypr/scripts/KeybindsLayoutInit.sh
index f47197af..280e381b 100755
--- a/config/hypr/scripts/KeybindsLayoutInit.sh
+++ b/config/hypr/scripts/KeybindsLayoutInit.sh
@@ -5,8 +5,7 @@
# License: GNU GPLv3
# SPDX-License-Identifier: GPL-3.0-or-later
# ==================================================
-# Initialize J/K keybinds so they always cycle windows globally (no layout-specific behavior)
-# This avoids double-actions when layouts change.
+# Initialize J/K keybinds so they always cycle windows globally (no layout-specific behavior).
set -euo pipefail
@@ -15,5 +14,5 @@ hyprctl keyword unbind SUPER,j || true
hyprctl keyword unbind SUPER,k || true
# Cycle windows globally
-hyprctl keyword bind SUPER,j,layoutmsg,cyclenext
-hyprctl keyword bind SUPER,k,layoutmsg,cycleprev
+hyprctl keyword bind SUPER,j,cyclenext
+hyprctl keyword bind SUPER,k,cyclenext,prev
diff --git a/config/hypr/scripts/Kitty_themes.sh b/config/hypr/scripts/Kitty_themes.sh
index 3183b20b..24a4ae11 100755
--- a/config/hypr/scripts/Kitty_themes.sh
+++ b/config/hypr/scripts/Kitty_themes.sh
@@ -12,22 +12,55 @@ kitty_themes_DiR="$HOME/.config/kitty/kitty-themes" # Kitty Themes Directory
kitty_config="$HOME/.config/kitty/kitty.conf"
iDIR="$HOME/.config/swaync/images" # For notifications
rofi_theme_for_this_script="$HOME/.config/rofi/config-kitty-theme.rasi"
+wallust_refresh_script="$HOME/.config/hypr/scripts/WallustSwww.sh"
+debug_log="${XDG_CACHE_HOME:-$HOME/.cache}/kooldots-kitty-themes.log"
# --- Helper Functions ---
notify_user() {
notify-send -u low -i "$1" "$2" "$3"
}
+log_debug() {
+ mkdir -p "$(dirname "$debug_log")" 2>/dev/null || true
+ printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" >>"$debug_log" 2>/dev/null || true
+}
+
+resolve_theme_selection() {
+ local rofi_output="$1"
+ local idx
+
+ rofi_output="${rofi_output//$'\r'/}"
+ rofi_output="${rofi_output//$'\n'/}"
+
+ if [[ "$rofi_output" =~ ^[0-9]+$ ]] && [ "$rofi_output" -lt "${#available_theme_names[@]}" ]; then
+ current_selection_index="$rofi_output"
+ theme_to_preview_now="${available_theme_names[$current_selection_index]}"
+ return 0
+ fi
+
+ for idx in "${!available_theme_names[@]}"; do
+ if [[ "${available_theme_names[$idx]}" == "$rofi_output" ]]; then
+ current_selection_index="$idx"
+ theme_to_preview_now="${available_theme_names[$current_selection_index]}"
+ return 0
+ fi
+ done
+
+ return 1
+}
+
# Function to apply the selected kitty theme
apply_kitty_theme_to_config() {
local theme_name_to_apply="$1"
local apply_mode="${2:-preview}"
+ local is_wallpaper_mode=0
if [ -z "$theme_name_to_apply" ]; then
echo "Error: No theme name provided to apply_kitty_theme_to_config." >&2
return 1
fi
local theme_file_path_to_apply
if [ "$theme_name_to_apply" = "Set by wallpaper" ]; then
+ is_wallpaper_mode=1
theme_file_path_to_apply="$kitty_themes_DiR/01-Wallust.conf"
elif [ "$theme_name_to_apply" = "Default no color" ]; then
theme_file_path_to_apply="$kitty_themes_DiR/00-Default.conf"
@@ -58,10 +91,20 @@ apply_kitty_theme_to_config() {
cp "$temp_kitty_config_file" "$kitty_config"
rm "$temp_kitty_config_file"
+ local trigger_wallust_refresh=0
+ if [ "$theme_name_to_apply" = "Set by wallpaper" ] && [ -x "$wallust_refresh_script" ]; then
+ trigger_wallust_refresh=1
+ fi
+ if [ "$trigger_wallust_refresh" -eq 1 ]; then
+ "$wallust_refresh_script" >/dev/null 2>&1 &
+ log_debug "wallust_refresh_background_started"
+ fi
if pidof kitty >/dev/null 2>&1; then
- if [ "$apply_mode" = "apply" ] && command -v kitty >/dev/null 2>&1; then
- kitty @ load-config >/dev/null 2>&1
- kitty @ set-colors --all --configured "$theme_file_path_to_apply" >/dev/null 2>&1
+ if [ "$apply_mode" = "apply" ] && [ "$is_wallpaper_mode" -eq 0 ] && command -v kitty >/dev/null 2>&1; then
+ (
+ kitty @ load-config >/dev/null 2>&1 || true
+ kitty @ set-colors --all --configured "$theme_file_path_to_apply" >/dev/null 2>&1 || true
+ ) &
fi
for pid_kitty in $(pidof kitty); do
if [ -n "$pid_kitty" ]; then
@@ -110,6 +153,7 @@ if [ -n "$current_active_theme_name" ]; then
fi
done
fi
+theme_to_preview_now="${available_theme_names[$current_selection_index]}"
while true; do
@@ -119,21 +163,22 @@ while true; do
done
rofi_input_list_trimmed="${rofi_input_list%\\n}"
- chosen_index_from_rofi=$(echo -e "$rofi_input_list_trimmed" |
+ chosen_selection_from_rofi=$(echo -e "$rofi_input_list_trimmed" |
rofi -dmenu -i \
+ -no-custom \
-format 'i' \
-p "Kitty Theme" \
- -mesg "Enter: Preview | Ctrl+S: Apply & Exit | Esc: Cancel" \
+ -mesg "Enter: Preview | Ctrl+S (or Alt+1): Apply & Exit | Esc: Cancel" \
-config "$rofi_theme_for_this_script" \
-selected-row "$current_selection_index" \
- -kb-custom-1 "Control+s")
+ -kb-custom-1 "Control+s,Control+S,Alt+1")
rofi_exit_code=$?
+ log_debug "rofi_exit=$rofi_exit_code rofi_output='${chosen_selection_from_rofi}' current_index=$current_selection_index current_theme='${available_theme_names[$current_selection_index]}'"
if [ $rofi_exit_code -eq 0 ]; then
- if [[ "$chosen_index_from_rofi" =~ ^[0-9]+$ ]] && [ "$chosen_index_from_rofi" -lt "${#available_theme_names[@]}" ]; then
- current_selection_index="$chosen_index_from_rofi"
- theme_to_preview_now="${available_theme_names[$current_selection_index]}"
+ if resolve_theme_selection "$chosen_selection_from_rofi"; then
+ log_debug "resolved_enter index=$current_selection_index theme='${theme_to_preview_now}'"
if ! apply_kitty_theme_to_config "$theme_to_preview_now" "preview"; then
echo "$original_kitty_config_content_backup" >"$kitty_config"
for pid_kitty in $(pidof kitty); do if [ -n "$pid_kitty" ]; then kill -SIGUSR1 "$pid_kitty"; fi; done
@@ -149,7 +194,9 @@ while true; do
echo "$original_kitty_config_content_backup" >"$kitty_config"
for pid_kitty in $(pidof kitty); do if [ -n "$pid_kitty" ]; then kill -SIGUSR1 "$pid_kitty"; fi; done
break
- elif [ $rofi_exit_code -eq 10 ]; then # This is the exit code for -kb-custom-1
+ elif [ $rofi_exit_code -ge 10 ] && [ $rofi_exit_code -le 28 ]; then # custom keybindings
+ resolve_theme_selection "$chosen_selection_from_rofi" || true
+ log_debug "resolved_custom index=$current_selection_index theme='${theme_to_preview_now}' exit=$rofi_exit_code"
apply_kitty_theme_to_config "$theme_to_preview_now" "apply"
notify_user "$iDIR/ja.png" "Kitty Theme Applied" "$theme_to_preview_now"
break
diff --git a/config/hypr/scripts/Kool_Quick_Settings.sh b/config/hypr/scripts/Kool_Quick_Settings.sh
index fcd04998..6ec02bb8 100755
--- a/config/hypr/scripts/Kool_Quick_Settings.sh
+++ b/config/hypr/scripts/Kool_Quick_Settings.sh
@@ -8,22 +8,52 @@
# Rofi menu for KooL Hyprland Quick Settings (SUPER SHIFT E)
# Updated for UserConfigs/configs separation
-# Modify this config file for default terminal and EDITOR
-config_file="$HOME/.config/hypr/UserConfigs/01-UserDefaults.conf"
+# Detect active Hyprland config mode (Lua entrypoint vs legacy .conf includes)
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+else
+ hypr_config_mode="conf"
+fi
+
+# Resolve defaults file used to get terminal/editor values
+config_file="$hypr_dir/UserConfigs/01-UserDefaults.conf"
+lua_defaults_file="$hypr_dir/UserConfigs/user_defaults.lua"
+term="${term:-${TERM:-kitty}}"
+edit="${edit:-${EDITOR:-nano}}"
+visual="${visual:-${VISUAL:-}}"
-tmp_config_file=$(mktemp)
-sed 's/^\$//g; s/ = /=/g' "$config_file" > "$tmp_config_file"
-source "$tmp_config_file"
+if [[ "$hypr_config_mode" == "conf" && -f "$config_file" ]]; then
+ tmp_config_file=$(mktemp)
+ sed 's/^\$//g; s/ = /=/g' "$config_file" > "$tmp_config_file"
+ source "$tmp_config_file"
+elif [[ "$hypr_config_mode" == "lua" ]]; then
+ defaults_source=""
+ if [[ -f "$lua_defaults_file" ]]; then
+ defaults_source="$lua_defaults_file"
+ fi
+ if [[ -n "$defaults_source" ]]; then
+ lua_term=$(sed -n 's/^[[:space:]]*KOOLDOTS_DEFAULTS\.term[[:space:]]*=[[:space:]]*"\(.*\)"[[:space:]]*$/\1/p' "$defaults_source" | tail -n1)
+ lua_edit=$(sed -n 's/^[[:space:]]*KOOLDOTS_DEFAULTS\.edit[[:space:]]*=[[:space:]]*"\(.*\)"[[:space:]]*$/\1/p' "$defaults_source" | tail -n1)
+ lua_visual=$(sed -n 's/^[[:space:]]*KOOLDOTS_DEFAULTS\.visual[[:space:]]*=[[:space:]]*"\(.*\)"[[:space:]]*$/\1/p' "$defaults_source" | tail -n1)
+ [[ -n "$lua_term" ]] && term="$lua_term"
+ [[ -n "$lua_edit" ]] && edit="$lua_edit"
+ [[ -n "$lua_visual" ]] && visual="$lua_visual"
+ fi
+fi
# ##################################### #
# variables
-configs="$HOME/.config/hypr/configs"
-UserConfigs="$HOME/.config/hypr/UserConfigs"
+configs="$hypr_dir/configs"
+UserConfigs="$hypr_dir/UserConfigs"
rofi_theme="$HOME/.config/rofi/config-edit.rasi"
msg=' ⁉️ Choose what to do ⁉️'
iDIR="$HOME/.config/swaync/images"
-scriptsDir="$HOME/.config/hypr/scripts"
-UserScripts="$HOME/.config/hypr/UserScripts"
+scriptsDir="$hypr_dir/scripts"
+UserScripts="$hypr_dir/UserScripts"
# Function to show info notification
show_info() {
@@ -33,6 +63,50 @@ show_info() {
notify-send "Info" "$1"
fi
}
+
+# Determine whether an editor command is terminal-based (TUI)
+is_tui_editor() {
+ local -a cmd=("$@")
+ local bin base arg
+ [[ ${#cmd[@]} -eq 0 ]] && return 1
+
+ bin="${cmd[0]}"
+ base="$(basename "$bin")"
+
+ case "$base" in
+ vi|vim|nvim|nano|hx|helix|kak|micro|emacs-nox)
+ return 0
+ ;;
+ emacs|emacsclient)
+ for arg in "${cmd[@]:1}"; do
+ case "$arg" in
+ -nw|--no-window-system|-t|--tty)
+ return 0
+ ;;
+ esac
+ done
+ return 1
+ ;;
+ esac
+
+ return 1
+}
+
+resolve_system_lua_file() {
+ local file_name="$1"
+ local preferred="$configs/$file_name"
+ local legacy="$UserConfigs/$file_name"
+ if [[ -f "$preferred" || ! -f "$legacy" ]]; then
+ printf '%s' "$preferred"
+ else
+ printf '%s' "$legacy"
+ fi
+}
+
+resolve_user_defaults_lua_file() {
+ local preferred="$UserConfigs/user_defaults.lua"
+ printf '%s' "$preferred"
+}
# Function to toggle Rainbow Borders script availability and refresh UI components
toggle_rainbow_borders() {
local rainbow_script="$UserScripts/RainbowBorders.sh"
@@ -186,6 +260,7 @@ Edit User Keybinds
Edit User ENV variables
Edit User Startup Apps (overlay)
Edit User Window Rules (overlay)
+Edit User Layer Rules (overlay)
Edit User Settings
Edit User Decorations
Edit User Animations
@@ -194,6 +269,7 @@ Edit User Laptop Settings
Edit System Default Keybinds
Edit System Default Startup Apps
Edit System Default Window Rules
+Edit System Default Layer Rules
Edit System Default Settings
--- UTILITIES ---
Set SDDM Wallpaper
@@ -221,19 +297,42 @@ main() {
# Map choices to corresponding files
case "$choice" in
- "Edit User Defaults") file="$UserConfigs/01-UserDefaults.conf" ;;
- "Edit User ENV variables") file="$UserConfigs/ENVariables.conf" ;;
- "Edit User Keybinds") file="$UserConfigs/UserKeybinds.conf" ;;
- "Edit User Startup Apps (overlay)") file="$UserConfigs/Startup_Apps.conf" ;;
- "Edit User Window Rules (overlay)") file="$UserConfigs/WindowRules.conf" ;;
- "Edit User Settings") file="$configs/SystemSettings.conf"; show_info "Editing default settings. Copy to UserConfigs/UserSettings.conf to override." ;;
- "Edit User Decorations") file="$UserConfigs/UserDecorations.conf" ;;
- "Edit User Animations") file="$UserConfigs/UserAnimations.conf" ;;
- "Edit User Laptop Settings") file="$UserConfigs/Laptops.conf" ;;
- "Edit System Default Keybinds") file="$configs/Keybinds.conf" ;;
- "Edit System Default Startup Apps") file="$configs/Startup_Apps.conf" ;;
- "Edit System Default Window Rules") file="$configs/WindowRules.conf" ;;
- "Edit System Default Settings") file="$configs/SystemSettings.conf" ;;
+ "Edit User Defaults")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$(resolve_user_defaults_lua_file)"; else file="$UserConfigs/01-UserDefaults.conf"; fi ;;
+ "Edit User ENV variables")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_env.lua"; else file="$UserConfigs/ENVariables.conf"; fi ;;
+ "Edit User Keybinds")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_keybinds.lua"; else file="$UserConfigs/UserKeybinds.conf"; fi ;;
+ "Edit User Startup Apps (overlay)")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_startup.lua"; else file="$UserConfigs/Startup_Apps.conf"; fi ;;
+ "Edit User Window Rules (overlay)")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_window_rules.lua"; else file="$UserConfigs/WindowRules.conf"; fi ;;
+ "Edit User Layer Rules (overlay)")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_layer_rules.lua"; else file="$UserConfigs/LayerRules.conf"; fi ;;
+ "Edit User Settings")
+ if [[ "$hypr_config_mode" == "lua" ]]; then
+ file="$UserConfigs/user_settings.lua"
+ show_info "Lua mode detected. Edit UserConfigs/user_settings.lua for user settings."
+ else
+ file="$configs/SystemSettings.conf"
+ show_info "Editing default settings. Copy to UserConfigs/UserSettings.conf to override."
+ fi ;;
+ "Edit User Decorations")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_decorations.lua"; else file="$UserConfigs/UserDecorations.conf"; fi ;;
+ "Edit User Animations")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_animations.lua"; else file="$UserConfigs/UserAnimations.conf"; fi ;;
+ "Edit User Laptop Settings")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$UserConfigs/user_laptops.lua"; else file="$UserConfigs/Laptops.conf"; fi ;;
+ "Edit System Default Keybinds")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$(resolve_system_lua_file system_keybinds.lua)"; else file="$configs/Keybinds.conf"; fi ;;
+ "Edit System Default Startup Apps")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$(resolve_system_lua_file system_startup.lua)"; else file="$configs/Startup_Apps.conf"; fi ;;
+ "Edit System Default Window Rules")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$(resolve_system_lua_file system_window_rules.lua)"; else file="$configs/WindowRules.conf"; fi ;;
+ "Edit System Default Layer Rules")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$(resolve_system_lua_file system_layer_rules.lua)"; else file="$configs/LayerRules.conf"; fi ;;
+ "Edit System Default Settings")
+ if [[ "$hypr_config_mode" == "lua" ]]; then file="$(resolve_system_lua_file system_settings.lua)"; else file="$configs/SystemSettings.conf"; fi ;;
"Set SDDM Wallpaper") $scriptsDir/sddm_wallpaper.sh --normal ;;
"Choose Kitty Terminal Theme") $scriptsDir/Kitty_themes.sh ;;
"Choose Ghostty Terminal Theme") $scriptsDir/Ghostty_themes.sh ;;
@@ -278,9 +377,20 @@ main() {
*) return ;; # Do nothing for invalid choices
esac
- # Open the selected file in the terminal with the text editor
+ # Open selected file using configured editor
if [ -n "$file" ]; then
- $term -e $edit "$file"
+ local -a edit_cmd term_cmd visual_cmd selected_cmd
+ read -r -a edit_cmd <<< "$edit"
+ read -r -a term_cmd <<< "$term"
+ [[ -n "$visual" ]] && read -r -a visual_cmd <<< "$visual"
+ selected_cmd=("${edit_cmd[@]}")
+ [[ ${#visual_cmd[@]} -gt 0 ]] && selected_cmd=("${visual_cmd[@]}")
+
+ if is_tui_editor "${selected_cmd[@]}"; then
+ "${term_cmd[@]}" -e "${selected_cmd[@]}" "$file"
+ else
+ "${selected_cmd[@]}" "$file" >/dev/null 2>&1 &
+ fi
fi
}
diff --git a/config/hypr/scripts/Logout.sh b/config/hypr/scripts/Logout.sh
new file mode 100755
index 00000000..2f173ffe
--- /dev/null
+++ b/config/hypr/scripts/Logout.sh
@@ -0,0 +1,180 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Logout helper for wlogout and keybind callers.
+LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/hypr"
+LOG_FILE="${LOG_DIR}/hypr-logout.log"
+if ! mkdir -p "$LOG_DIR" >/dev/null 2>&1; then
+ LOG_FILE="${XDG_RUNTIME_DIR:-/tmp}/hypr-logout.log"
+fi
+
+log_msg() {
+ printf "[%s] %s\n" "$(date +"%F %T")" "$1" >>"$LOG_FILE"
+}
+
+run_logged() {
+ local label="$1"
+ shift
+ log_msg "RUN ${label}: $*"
+ "$@" >>"$LOG_FILE" 2>&1
+ local rc=$?
+ log_msg "RC ${label}: ${rc}"
+ return "$rc"
+}
+SESSION_USER="${USER:-$(id -un)}"
+SESSION_HYPRLAND_PIDS="$(pgrep -xu "$SESSION_USER" -x Hyprland || true)"
+SESSION_DM_SERVICE=""
+IS_SDDM_SESSION=0
+
+session_hyprland_running() {
+ local pid
+ if [ -n "$SESSION_HYPRLAND_PIDS" ]; then
+ for pid in $SESSION_HYPRLAND_PIDS; do
+ if kill -0 "$pid" >/dev/null 2>&1; then
+ return 0
+ fi
+ done
+ return 1
+ fi
+ pgrep -xu "$SESSION_USER" -x Hyprland >/dev/null 2>&1
+}
+logout_completed() {
+ # Give the session up to 2 seconds to terminate after a successful command.
+ for _ in {1..20}; do
+ session_hyprland_running || return 0
+ sleep 0.1
+ done
+ return 1
+}
+stop_proc() {
+ local name="$1"
+ pkill -u "$SESSION_USER" -x -TERM "$name" >/dev/null 2>&1 || true
+
+ # Wait up to 1 second for graceful shutdown.
+ for _ in {1..10}; do
+ pgrep -xu "$SESSION_USER" -x "$name" >/dev/null 2>&1 || return 0
+ sleep 0.1
+ done
+ pkill -u "$SESSION_USER" -x -KILL "$name" >/dev/null 2>&1 || true
+}
+
+# Close wlogout if it is still visible.
+stop_proc "wlogout"
+HYPRCTL_BIN="$(command -v hyprctl || true)"
+HYPRSHUTDOWN_BIN="$(command -v hyprshutdown || true)"
+UWSM_BIN="$(command -v uwsm || true)"
+LOGINCTL_BIN="$(command -v loginctl || true)"
+if [ -n "$LOGINCTL_BIN" ] && [ -n "${XDG_SESSION_ID:-}" ]; then
+ SESSION_DM_SERVICE="$("$LOGINCTL_BIN" show-session "$XDG_SESSION_ID" -p Service --value 2>/dev/null || true)"
+ if [ "$SESSION_DM_SERVICE" = "sddm" ] || [ "$SESSION_DM_SERVICE" = "sddm-autologin" ]; then
+ IS_SDDM_SESSION=1
+ fi
+fi
+
+# Preferred path: synchronous hyprshutdown, so script does not silently succeed.
+if [ -n "$HYPRSHUTDOWN_BIN" ]; then
+ if run_logged "hyprshutdown-no-fork" "$HYPRSHUTDOWN_BIN" --no-fork; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprshutdown returned success but Hyprland is still running"
+ fi
+fi
+# systemd session fallback.
+if [ -n "$LOGINCTL_BIN" ] && [ -n "${XDG_SESSION_ID:-}" ]; then
+ if [ "$IS_SDDM_SESSION" -eq 1 ]; then
+ log_msg "Skipping loginctl terminate-session for SDDM-managed session (${SESSION_DM_SERVICE})"
+ elif run_logged "loginctl-terminate-session" "$LOGINCTL_BIN" terminate-session "$XDG_SESSION_ID"; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "loginctl terminate-session returned success but Hyprland is still running"
+ fi
+fi
+
+# Fallback: ask Hyprland to spawn hyprshutdown via a normal dispatch exec call.
+if [ -n "$HYPRCTL_BIN" ] && [ -n "$HYPRSHUTDOWN_BIN" ]; then
+ if run_logged \
+ "hyprctl-dispatch-exec-hyprshutdown" \
+ "$HYPRCTL_BIN" dispatch exec "hyprshutdown --no-fork"; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatched hyprshutdown but Hyprland is still running"
+ fi
+fi
+
+# UWSM-managed session fallback (common on NixOS).
+if [ -n "$UWSM_BIN" ] && [ "$IS_SDDM_SESSION" -eq 0 ]; then
+ if run_logged "uwsm-stop" "$UWSM_BIN" stop; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "uwsm stop returned success but Hyprland is still running"
+ fi
+elif [ -n "$UWSM_BIN" ] && [ "$IS_SDDM_SESSION" -eq 1 ]; then
+ log_msg "Skipping uwsm stop on SDDM-managed session to avoid delayed logout"
+fi
+
+
+# Last-resort Hyprland exit fallbacks.
+if [ -n "$HYPRCTL_BIN" ]; then
+ if [ "$IS_SDDM_SESSION" -eq 1 ]; then
+ if run_logged "hyprctl-exit-1-sddm" "$HYPRCTL_BIN" dispatch exit 1; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatch exit 1 (sddm) returned success but Hyprland is still running"
+ fi
+ if run_logged "hyprctl-exit-x-sddm" "$HYPRCTL_BIN" dispatch exit x; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatch exit x (sddm) returned success but Hyprland is still running"
+ fi
+ else
+ if run_logged "hyprctl-exit-1" "$HYPRCTL_BIN" dispatch exit 1; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatch exit 1 returned success but Hyprland is still running"
+ fi
+ if run_logged "hyprctl-exit-0" "$HYPRCTL_BIN" dispatch exit 0; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatch exit 0 returned success but Hyprland is still running"
+ fi
+ if run_logged "hyprctl-exit-x" "$HYPRCTL_BIN" dispatch exit x; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatch exit x returned success but Hyprland is still running"
+ fi
+ if run_logged "hyprctl-exit-noarg" "$HYPRCTL_BIN" dispatch exit; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "hyprctl dispatch exit (no arg) returned success but Hyprland is still running"
+ fi
+ fi
+fi
+
+# Final process-level fallback.
+if run_logged "pkill-hyprland-term" pkill -u "$SESSION_USER" -x -TERM Hyprland; then
+ if logout_completed; then
+ exit 0
+ fi
+ log_msg "SIGTERM sent to Hyprland but process is still running"
+fi
+if run_logged "pkill-hyprland-kill" pkill -u "$SESSION_USER" -x -KILL Hyprland; then
+ exit 0
+fi
+
+log_msg "Logout failed: no method succeeded"
+
+exit 1
diff --git a/config/hypr/scripts/LuaAutoReload.sh b/config/hypr/scripts/LuaAutoReload.sh
new file mode 100755
index 00000000..4a9d5040
--- /dev/null
+++ b/config/hypr/scripts/LuaAutoReload.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Auto-reload Hyprland when Lua config files change.
+
+set -euo pipefail
+
+watch_root="$HOME/.config/hypr"
+[ -d "$watch_root" ] || exit 0
+
+reload_hypr() {
+ hyprctl reload >/dev/null 2>&1 || true
+}
+
+if command -v inotifywait >/dev/null 2>&1; then
+ debounce_and_reload() {
+ sleep 0.2
+ while inotifywait -q -t 0.2 -e close_write,create,move,delete -r --include '(^|/)[^/]+\.lua$' "$watch_root" >/dev/null 2>&1; do
+ :
+ done
+ reload_hypr
+ }
+
+ inotifywait -m -q -r -e close_write,create,move,delete --include '(^|/)[^/]+\.lua$' "$watch_root" | while read -r _; do
+ debounce_and_reload
+ done
+ exit 0
+fi
+
+# Fallback polling path when inotify-tools isn't installed.
+snapshot() {
+ find "$watch_root" -type f -name '*.lua' -printf '%p:%T@\n' 2>/dev/null | LC_ALL=C sort
+}
+
+previous_state="$(snapshot)"
+while true; do
+ sleep 1
+ current_state="$(snapshot)"
+ if [ "$current_state" != "$previous_state" ]; then
+ previous_state="$current_state"
+ reload_hypr
+ fi
+done
diff --git a/config/hypr/scripts/LuaCycleWindow.sh b/config/hypr/scripts/LuaCycleWindow.sh
new file mode 100755
index 00000000..ee64e67a
--- /dev/null
+++ b/config/hypr/scripts/LuaCycleWindow.sh
@@ -0,0 +1,73 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Cycle focus through visible windows on the active workspace using the
+# Hyprland Lua dispatcher API. This avoids cyclenext/focuswindow no-ops on
+# Lua config builds.
+
+set -u
+
+direction="${1:-next}"
+case "$direction" in
+ next|forward|f)
+ direction="next"
+ ;;
+ previous|prev|back|b)
+ direction="previous"
+ ;;
+ *)
+ exit 0
+ ;;
+esac
+
+active_window="$(hyprctl activewindow -j 2>/dev/null || printf '{}')"
+active_address="$(jq -r '.address // empty' <<<"$active_window")"
+active_workspace="$(jq -r '.workspace.id // empty' <<<"$active_window")"
+
+if [[ -z "$active_address" ]] || ! [[ "$active_workspace" =~ ^-?[0-9]+$ ]]; then
+ exit 0
+fi
+
+clients="$(hyprctl clients -j 2>/dev/null || printf '[]')"
+target_address="$(
+ jq -r \
+ --arg active_address "$active_address" \
+ --argjson active_workspace "$active_workspace" \
+ --arg direction "$direction" '
+ [
+ .[]
+ | select((.mapped // false) == true)
+ | select((.hidden // false) == false)
+ | select(.workspace.id == $active_workspace)
+ | {
+ address,
+ x: (.at[0] // 0),
+ y: (.at[1] // 0)
+ }
+ ]
+ | sort_by(.y, .x, .address)
+ | if length < 2 then
+ empty
+ else
+ . as $windows
+ | ($windows | map(.address) | index($active_address)) as $index
+ | if $index == null then
+ empty
+ elif $direction == "previous" then
+ $windows[(($index - 1 + length) % length)].address
+ else
+ $windows[(($index + 1) % length)].address
+ end
+ end
+ ' <<<"$clients"
+)"
+
+if [[ -z "$target_address" ]]; then
+ exit 0
+fi
+
+hyprctl dispatch "hl.dsp.focus({ window = \"address:${target_address}\" })" >/dev/null 2>&1 || true
diff --git a/config/hypr/scripts/LuaFocusWorkspaceRelative.sh b/config/hypr/scripts/LuaFocusWorkspaceRelative.sh
new file mode 100755
index 00000000..20668110
--- /dev/null
+++ b/config/hypr/scripts/LuaFocusWorkspaceRelative.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Focus the previous/next numeric workspace through the Lua dispatcher.
+# This allows moving into empty workspaces while avoiding invalid workspace 0.
+
+set -u
+
+direction="${1:-}"
+case "$direction" in
+ next|right|m+1|+1)
+ direction="next"
+ ;;
+ previous|prev|left|m-1|-1)
+ direction="previous"
+ ;;
+ *)
+ exit 0
+ ;;
+esac
+
+active_workspace="$(hyprctl activeworkspace -j 2>/dev/null || printf '{}')"
+active_id="$(jq -r '.id // empty' <<<"$active_workspace")"
+if ! [[ "$active_id" =~ ^-?[0-9]+$ ]]; then
+ exit 0
+fi
+
+case "$direction" in
+ next)
+ target_id=$((active_id + 1))
+ ;;
+ previous)
+ target_id=$((active_id - 1))
+ ;;
+esac
+if ! [[ "${target_id:-}" =~ ^-?[0-9]+$ ]] || [ "$target_id" -lt 1 ]; then
+ exit 0
+fi
+
+hyprctl dispatch "hl.dispatch(hl.dsp.focus({ workspace = ${target_id} }))" >/dev/null 2>&1 || true
diff --git a/config/hypr/scripts/LuaFullscreenMaximized.sh b/config/hypr/scripts/LuaFullscreenMaximized.sh
new file mode 100755
index 00000000..fccc577b
--- /dev/null
+++ b/config/hypr/scripts/LuaFullscreenMaximized.sh
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Helper for Lua-config sessions where legacy `hyprctl dispatch fullscreen 1`
+# is parsed as Lua and fails.
+
+setsid -f sh -c 'sleep 0.2; hyprctl dispatch "hl.dsp.window.fullscreen({ mode = 1 })"' >/dev/null 2>&1
diff --git a/config/hypr/scripts/LuaMoveWindowDirectional.sh b/config/hypr/scripts/LuaMoveWindowDirectional.sh
new file mode 100755
index 00000000..ea21196e
--- /dev/null
+++ b/config/hypr/scripts/LuaMoveWindowDirectional.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Guard directional Lua window movement against Hyprland Lua's no-target
+# "invalid workspace" runtime error.
+
+set -u
+
+direction="${1:-}"
+case "$direction" in
+ l|left)
+ direction="left"
+ ;;
+ r|right)
+ direction="right"
+ ;;
+ *)
+ exit 0
+ ;;
+esac
+
+active_window="$(hyprctl activewindow -j 2>/dev/null || printf '{}')"
+if ! jq -e '.address? and .address != ""' >/dev/null 2>&1 <<<"$active_window"; then
+ exit 0
+fi
+
+active_workspace="$(hyprctl activeworkspace -j 2>/dev/null || printf '{}')"
+active_id="$(jq -r '.id // empty' <<<"$active_workspace")"
+if ! [[ "$active_id" =~ ^-?[0-9]+$ ]]; then
+ exit 0
+fi
+
+workspaces="$(hyprctl workspaces -j 2>/dev/null || printf '[]')"
+case "$direction" in
+ left)
+ target_id="$(jq -r --argjson active "$active_id" '[.[] | select((.id < $active) and ((.windows // 0) > 0)) | .id] | max // empty' <<<"$workspaces")"
+ ;;
+ right)
+ target_id="$(jq -r --argjson active "$active_id" '[.[] | select((.id > $active) and ((.windows // 0) > 0)) | .id] | min // empty' <<<"$workspaces")"
+ ;;
+esac
+
+if ! [[ "${target_id:-}" =~ ^-?[0-9]+$ ]]; then
+ exit 0
+fi
+
+hyprctl dispatch "hl.dispatch(hl.dsp.window.move({ direction = \"${direction}\" }))" >/dev/null 2>&1 || true
diff --git a/config/hypr/scripts/LuaMoveWindowWorkspaceRelative.sh b/config/hypr/scripts/LuaMoveWindowWorkspaceRelative.sh
new file mode 100755
index 00000000..f225d4a2
--- /dev/null
+++ b/config/hypr/scripts/LuaMoveWindowWorkspaceRelative.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Move the active window to the previous/next numeric workspace through
+# the Lua dispatcher. This allows moving into empty workspaces while
+# avoiding invalid workspace 0.
+
+set -u
+
+direction="${1:-}"
+case "$direction" in
+ next|right|+1)
+ direction="next"
+ ;;
+ previous|prev|left|-1)
+ direction="previous"
+ ;;
+ *)
+ exit 0
+ ;;
+esac
+
+active_window="$(hyprctl activewindow -j 2>/dev/null || printf '{}')"
+if ! jq -e '.address? and .address != ""' >/dev/null 2>&1 <<<"$active_window"; then
+ exit 0
+fi
+
+active_workspace="$(hyprctl activeworkspace -j 2>/dev/null || printf '{}')"
+active_id="$(jq -r '.id // empty' <<<"$active_workspace")"
+if ! [[ "$active_id" =~ ^-?[0-9]+$ ]]; then
+ exit 0
+fi
+
+case "$direction" in
+ next)
+ target_id=$((active_id + 1))
+ ;;
+ previous)
+ target_id=$((active_id - 1))
+ ;;
+esac
+
+if ! [[ "${target_id:-}" =~ ^-?[0-9]+$ ]] || [ "$target_id" -lt 1 ]; then
+ exit 0
+fi
+
+hyprctl dispatch "hl.dispatch(hl.dsp.window.move({ workspace = ${target_id} }))" >/dev/null 2>&1 || true
diff --git a/config/hypr/scripts/LuaSwapWindow.sh b/config/hypr/scripts/LuaSwapWindow.sh
new file mode 100755
index 00000000..a1ec38fd
--- /dev/null
+++ b/config/hypr/scripts/LuaSwapWindow.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Safely swap the active window in a direction from Lua keybinds.
+
+arg="${1:-}"
+arg="${arg//\'/}"
+arg="${arg//\"/}"
+
+case "$arg" in
+left|l) direction="left" ;;
+right|r) direction="right" ;;
+up|u) direction="up" ;;
+down|d) direction="down" ;;
+*) exit 0 ;;
+esac
+
+active="$(hyprctl -j activewindow 2>/dev/null)"
+clients="$(hyprctl -j clients 2>/dev/null)"
+
+has_target="$(
+ jq -en --argjson active "$active" --argjson clients "$clients" --arg direction "$direction" '
+ def overlaps(a1; a2; b1; b2): (a1 < b2 and b1 < a2);
+ ($active.address // "") as $active_address
+ | ($active.workspace.id // null) as $workspace_id
+ | ($active.at[0] // 0) as $ax
+ | ($active.at[1] // 0) as $ay
+ | ($active.size[0] // 0) as $aw
+ | ($active.size[1] // 0) as $ah
+ | any($clients[];
+ (.address != $active_address)
+ and ((.workspace.id // null) == $workspace_id)
+ and (
+ if $direction == "left" then
+ ((.at[0] + .size[0]) <= $ax) and overlaps(.at[1]; (.at[1] + .size[1]); $ay; ($ay + $ah))
+ elif $direction == "right" then
+ (.at[0] >= ($ax + $aw)) and overlaps(.at[1]; (.at[1] + .size[1]); $ay; ($ay + $ah))
+ elif $direction == "up" then
+ ((.at[1] + .size[1]) <= $ay) and overlaps(.at[0]; (.at[0] + .size[0]); $ax; ($ax + $aw))
+ elif $direction == "down" then
+ (.at[1] >= ($ay + $ah)) and overlaps(.at[0]; (.at[0] + .size[0]); $ax; ($ax + $aw))
+ else
+ false
+ end
+ )
+ )
+ ' 2>/dev/null
+)"
+
+[[ "$has_target" == "true" ]] || exit 0
+
+hyprctl dispatch "hl.dsp.window.swap({ direction = \"${direction}\" })" >/dev/null 2>&1 || true
diff --git a/config/hypr/scripts/MonitorProfiles.sh b/config/hypr/scripts/MonitorProfiles.sh
index 78825986..6330d45c 100755
--- a/config/hypr/scripts/MonitorProfiles.sh
+++ b/config/hypr/scripts/MonitorProfiles.sh
@@ -12,13 +12,46 @@ if pidof rofi > /dev/null; then
pkill rofi
fi
+# Detect active Hyprland config mode (Lua entrypoint vs legacy .conf includes)
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+if [[ -n "$HYPR_CONFIG_MODE" ]]; then
+ case "${HYPR_CONFIG_MODE,,}" in
+ lua) hypr_config_mode="lua" ;;
+ conf|hyprlang) hypr_config_mode="conf" ;;
+ auto) hypr_config_mode="" ;;
+ *) hypr_config_mode="" ;;
+ esac
+fi
+
+if [[ -z "$hypr_config_mode" ]]; then
+ if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+ else
+ hypr_config_mode="conf"
+ fi
+fi
+
# Variables
iDIR="$HOME/.config/swaync/images"
SCRIPTSDIR="$HOME/.config/hypr/scripts"
monitor_dir="$HOME/.config/hypr/Monitor_Profiles"
-target="$HOME/.config/hypr/monitors.conf"
+target_conf="$HOME/.config/hypr/monitors.conf"
+target_lua_user="$HOME/.config/hypr/UserConfigs/monitors.lua"
+target_lua_legacy="$HOME/.config/hypr/lua/monitors.lua"
rofi_theme="$HOME/.config/rofi/config-Monitors.rasi"
-msg='❗NOTE:❗ This will overwrite $HOME/.config/hypr/monitors.conf'
+
+if [[ "$hypr_config_mode" == "lua" ]]; then
+ profile_ext="lua"
+ target="$target_lua_user"
+ msg="❗NOTE:❗ This will overwrite $HOME/.config/hypr/UserConfigs/monitors.lua"
+else
+ profile_ext="conf"
+ target="$target_conf"
+ msg="❗NOTE:❗ This will overwrite $HOME/.config/hypr/monitors.conf"
+fi
# Define the list of files to ignore
ignore_files=(
@@ -26,22 +59,30 @@ ignore_files=(
)
# list of Monitor Profiles, sorted alphabetically with numbers first
-mon_profiles_list=$(find -L "$monitor_dir" -maxdepth 1 -type f | sed 's/.*\///' | sed 's/\.conf$//' | sort -V)
+mon_profiles_list=$(find -L "$monitor_dir" -maxdepth 1 -type f -name "*.${profile_ext}" | sed 's/.*\///' | sed "s/\.${profile_ext}$//" | sort -V)
# Remove ignored files from the list
for ignored_file in "${ignore_files[@]}"; do
mon_profiles_list=$(echo "$mon_profiles_list" | grep -v -E "^$ignored_file$")
done
+if [[ -z "$mon_profiles_list" ]]; then
+ notify-send -u low -i "$iDIR/ja.png" "Monitor Profiles" "No .${profile_ext} profiles found in $monitor_dir"
+ exit 1
+fi
# Rofi Menu
-chosen_file=$(echo "$mon_profiles_list" | rofi -i -dmenu -config $rofi_theme -mesg "$msg")
+chosen_file=$(echo "$mon_profiles_list" | rofi -i -dmenu -config "$rofi_theme" -mesg "$msg")
if [[ -n "$chosen_file" ]]; then
- full_path="$monitor_dir/$chosen_file.conf"
+ full_path="$monitor_dir/$chosen_file.$profile_ext"
+ mkdir -p "$(dirname "$target")"
cp "$full_path" "$target"
+ if [[ "$hypr_config_mode" == "lua" && -f "$target_lua_legacy" ]]; then
+ cp "$full_path" "$target_lua_legacy"
+ fi
notify-send -u low -i "$iDIR/ja.png" "$chosen_file" "Monitor Profile Loaded"
fi
sleep 1
-${SCRIPTSDIR}/RefreshNoWaybar.sh & \ No newline at end of file
+"${SCRIPTSDIR}/RefreshNoWaybar.sh" &
diff --git a/config/hypr/scripts/OverviewToggle.sh b/config/hypr/scripts/OverviewToggle.sh
index 14470eb0..456ccef1 100755
--- a/config/hypr/scripts/OverviewToggle.sh
+++ b/config/hypr/scripts/OverviewToggle.sh
@@ -9,15 +9,18 @@
set -euo pipefail
-# 1) Try Quickshell via IPC (works if QS is running and listening)
-if pgrep -x qs >/dev/null 2>&1; then
- if qs ipc -c overview call overview toggle >/dev/null 2>&1; then
- exit 0
+QS_OVERVIEW_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/quickshell/overview"
+
+# 1) Prefer Quickshell when installed and configured
+if command -v qs >/dev/null 2>&1 && [ -d "$QS_OVERVIEW_DIR" ]; then
+ # Try Quickshell via IPC (works if QS is running and listening)
+ if pgrep -x qs >/dev/null 2>&1; then
+ if qs ipc -c overview call overview toggle >/dev/null 2>&1; then
+ exit 0
+ fi
fi
-fi
-# If QS isn't running, but the CLI exists, try starting it and retry once
-if command -v qs >/dev/null 2>&1; then
+ # If QS isn't running, try starting it and retry once
qs -c overview >/dev/null 2>&1 &
sleep 0.6
if qs ipc -c overview call overview toggle >/dev/null 2>&1; then
diff --git a/config/hypr/scripts/Polkit-Diag.sh b/config/hypr/scripts/Polkit-Diag.sh
index 0f32b640..aa4e69a8 100755
--- a/config/hypr/scripts/Polkit-Diag.sh
+++ b/config/hypr/scripts/Polkit-Diag.sh
@@ -50,20 +50,20 @@ setup_output() {
else
local outdir
outdir=$(dirname "$OUTFILE")
-
+
# Check and create directory if it doesn't exist
if [[ ! -d "$outdir" ]]; then
echo "Directory $outdir does not exist. Creating..."
mkdir -p "$outdir"
fi
-
+
# Backup existing file
if [[ -f "$OUTFILE" ]]; then
local backup_file="${OUTFILE}.bak.$(date +%Y%m%d%H%M%S)"
echo "Existing output file found. Backing up to: $backup_file"
mv "$OUTFILE" "$backup_file"
fi
-
+
echo "Diagnostics will be saved to: $OUTFILE"
exec 3> "$OUTFILE"
fi
@@ -74,7 +74,7 @@ apply_override() {
local msg="\n=== Systemd Override for hyprpolkitagent ==="
[[ $DRY_RUN -eq 0 ]] && echo -e "$msg" >&3
echo -e "$msg"
-
+
local out
if systemctl --user is-enabled hyprpolkitagent.service >/dev/null 2>&1; then
msg="[STATUS] hyprpolkitagent.service is currently enabled."
@@ -94,10 +94,10 @@ apply_override() {
msg="[CONFIRM] Force override requested. Overwriting existing override..."
[[ $DRY_RUN -eq 0 ]] && echo "$msg" >&3; echo "$msg"
fi
-
+
msg="[ACTION] Installing override to $OVERRIDE_FILE..."
[[ $DRY_RUN -eq 0 ]] && echo "$msg" >&3; echo "$msg"
-
+
if [[ $DRY_RUN -eq 0 ]]; then
# Capture dir creation
if out=$(mkdir -p "$OVERRIDE_DIR" 2>&1); then
@@ -106,7 +106,7 @@ apply_override() {
msg=" [ERROR] Failed to create directory $OVERRIDE_DIR.\n Details: $out"
fi
echo -e "$msg" >&3; echo -e "$msg"
-
+
# Capture file write
if out=$(echo "$OVERRIDE_CONTENT" > "$OVERRIDE_FILE" 2>&1); then
msg=" [OK] Successfully wrote override file."
@@ -114,7 +114,7 @@ apply_override() {
msg=" [ERROR] Failed to write override file.\n Details: $out"
fi
echo -e "$msg" >&3; echo -e "$msg"
-
+
# Capture daemon-reload
if out=$(systemctl --user daemon-reload 2>&1); then
msg=" [OK] Systemd daemon reloaded."
@@ -122,12 +122,12 @@ apply_override() {
msg=" [ERROR] Failed to reload systemd daemon.\n Details: $out"
fi
echo -e "$msg" >&3; echo -e "$msg"
-
+
# Capture restart
if systemctl --user is-active --quiet hyprpolkitagent.service; then
msg="[ACTION] Restarting hyprpolkitagent.service..."
[[ $DRY_RUN -eq 0 ]] && echo "$msg" >&3; echo "$msg"
-
+
if out=$(systemctl --user restart hyprpolkitagent.service 2>&1); then
msg=" [OK] Service restarted successfully."
else
@@ -153,31 +153,31 @@ gather_general_info() {
echo "Date: $(date)" >&3
echo -e "\n--- Kernel ---" >&3
uname -a >&3
-
+
echo -e "\n--- OS Release ---" >&3
cat /etc/os-release >&3
-
+
echo -e "\n=======================================" >&3
echo -e " Polkit Service Status" >&3
echo -e "=======================================" >&3
-
+
echo -e "\n--- System Polkit Service ---" >&3
systemctl status polkit.service --no-pager >&3 2>&1 || true
-
+
echo -e "\n--- User Hyprpolkitagent Service ---" >&3
systemctl --user status hyprpolkitagent.service --no-pager >&3 2>&1 || true
-
+
echo -e "\n--- Running Polkit Processes ---" >&3
local polkit_procs
polkit_procs=$(ps aux | grep -i '[p]olkit')
if [[ -n "$polkit_procs" ]]; then
echo "$polkit_procs" >&3
-
+
# Check for conflicting agents
local kde_agent_running=0
local gnome_agent_running=0
local hypr_agent_running=0
-
+
if echo "$polkit_procs" | grep -q "polkit-kde-authentication-agent-1"; then
kde_agent_running=1
fi
@@ -187,7 +187,7 @@ gather_general_info() {
if echo "$polkit_procs" | grep -q "hyprpolkitagent"; then
hypr_agent_running=1
fi
-
+
if [[ $hypr_agent_running -eq 1 && ($kde_agent_running -eq 1 || $gnome_agent_running -eq 1) ]]; then
echo -e "\n[!] CONFLICT DETECTED: Multiple polkit agents are running!" >&3
echo " Hyprpolkitagent is running alongside another desktop environment's agent." >&3
@@ -205,14 +205,14 @@ gather_general_info() {
else
echo "No polkit processes found running." >&3
fi
-
+
echo -e "\n=======================================" >&3
echo -e " Recent Logs" >&3
echo -e "=======================================" >&3
-
+
echo -e "\n--- Journalctl (polkit.service) [Last 50 entries, warnings/errors] ---" >&3
journalctl -u polkit.service -n 50 --no-pager -p 4 >&3 2>&1 || echo "Could not fetch system polkit logs." >&3
-
+
echo -e "\n--- Journalctl (hyprpolkitagent.service) [Last 50 entries] ---" >&3
journalctl --user -u hyprpolkitagent.service -n 50 --no-pager >&3 2>&1 || echo "Could not fetch user hyprpolkitagent logs." >&3
}
@@ -223,7 +223,7 @@ check_packages() {
local install_msg="$2"
shift 2
local pkgs=("$@")
-
+
local missing_pkgs=()
for pkg in "${pkgs[@]}"; do
local out
@@ -235,7 +235,7 @@ check_packages() {
missing_pkgs+=("$pkg")
fi
done
-
+
if [[ ${#missing_pkgs[@]} -gt 0 ]]; then
echo -e "\nWARNING: The following packages are missing:" >&3
for mpkg in "${missing_pkgs[@]}"; do
@@ -251,7 +251,7 @@ check_source_binaries() {
shift
local bins=("$@")
local found_any=0
-
+
echo -e "\n--- Source Builds (${bin_dir}) ---" >&3
for bin in "${bins[@]}"; do
if [[ -x "${bin_dir}/${bin}" ]]; then
@@ -261,7 +261,7 @@ check_source_binaries() {
echo "[MISSING] ${bin_dir}/${bin}" >&3
fi
done
-
+
return $found_any
}
@@ -269,7 +269,7 @@ gather_arch_info() {
echo -e "\n=======================================" >&3
echo -e " Package Info (Arch Linux)" >&3
echo -e "=======================================" >&3
-
+
# Essential packages required for polkit & related UI
local pkgs=(
"qt5-declarative"
@@ -278,19 +278,19 @@ gather_arch_info() {
"hyprpolkitagent"
"polkit"
)
-
+
local aur_pkgs=(
"xfce-polkit"
)
-
+
local missing_any=0
-
+
echo -e "\n--- Official Repositories ---" >&3
check_packages "pacman -Q" "Install official packages by running: sudo pacman -S" "${pkgs[@]}" || missing_any=1
-
+
echo -e "\n--- AUR ---" >&3
check_packages "pacman -Q" "Install AUR packages by running: yay -S" "${aur_pkgs[@]}" || missing_any=1
-
+
if [[ $missing_any -eq 0 ]]; then
echo -e "\nSUCCESS: All expected packages are installed." >&3
fi
@@ -299,7 +299,7 @@ gather_ubuntu_info() {
echo -e "\n=======================================" >&3
echo -e " Package Info (Ubuntu/PPA)" >&3
echo -e "=======================================" >&3
-
+
# Essential packages required for polkit & related UI
local pkgs=(
"qml-module-qtqml"
@@ -313,22 +313,22 @@ gather_ubuntu_info() {
"hyprpolkitagent"
"polkit"
)
-
+
local extra_pkgs=(
"xfce-polkit"
"polkit-kde-agent-1"
"mate-polkit"
)
-
+
local missing_any=0
-
+
echo -e "\n--- Official Repositories / PPA ---" >&3
check_packages "dpkg -s" "Install packages by running: sudo apt install" "${pkgs[@]}" || missing_any=1
-
+
echo -e "\n--- Extra/Alternative ---" >&3
check_packages "dpkg -s" "Install extra packages by running: sudo apt install" "${extra_pkgs[@]}" || missing_any=1
echo "[INFO] lxqt-polkit (optional) — large dependency set." >&3
-
+
if [[ $missing_any -eq 0 ]]; then
echo -e "\nSUCCESS: All expected packages are installed." >&3
fi
@@ -337,16 +337,16 @@ gather_debian_info() {
echo -e "\n=======================================" >&3
echo -e " Package Info (Debian/Ubuntu)" >&3
echo -e "=======================================" >&3
-
+
local source_bins=(
"hyprpolkitagent"
)
-
+
local source_found=0
if check_source_binaries "/usr/local/bin" "${source_bins[@]}"; then
source_found=1
fi
-
+
# Essential packages required for polkit & related UI
local pkgs=(
"qml-module-qtqml"
@@ -359,16 +359,16 @@ gather_debian_info() {
"qml6-module-qtquick-controls"
"polkit"
)
-
+
local extra_pkgs=(
"xfce4-polkit"
"lxqt-policykit"
"polkit-kde-agent-1"
"mate-polkit"
)
-
+
local missing_any=0
-
+
echo -e "\n--- Official Repositories ---" >&3
if [[ $source_found -eq 0 ]]; then
pkgs+=("hyprpolkitagent")
@@ -376,10 +376,10 @@ gather_debian_info() {
echo "[INFO] hyprpolkitagent found in /usr/local/bin (source build). Skipping dpkg check for it." >&3
fi
check_packages "dpkg -s" "Install packages by running: sudo apt install" "${pkgs[@]}" || missing_any=1
-
+
echo -e "\n--- Extra/Alternative ---" >&3
check_packages "dpkg -s" "Install extra packages by running: sudo apt install" "${extra_pkgs[@]}" || missing_any=1
-
+
if [[ $missing_any -eq 0 ]]; then
echo -e "\nSUCCESS: All expected packages are installed." >&3
fi
@@ -389,7 +389,7 @@ gather_fedora_info() {
echo -e "\n=======================================" >&3
echo -e " Package Info (Fedora)" >&3
echo -e "=======================================" >&3
-
+
# Essential packages required for polkit & related UI
local pkgs=(
"qt5-qtdeclarative"
@@ -398,19 +398,19 @@ gather_fedora_info() {
"hyprpolkitagent"
"polkit"
)
-
+
local extra_pkgs=(
"xfce-polkit"
)
-
+
local missing_any=0
-
+
echo -e "\n--- Official Repositories ---" >&3
check_packages "rpm -q" "Install packages by running: sudo dnf install" "${pkgs[@]}" || missing_any=1
-
+
echo -e "\n--- Extra/Alternative ---" >&3
check_packages "rpm -q" "Install extra packages by running: sudo dnf install" "${extra_pkgs[@]}" || missing_any=1
-
+
if [[ $missing_any -eq 0 ]]; then
echo -e "\nSUCCESS: All expected packages are installed." >&3
fi
diff --git a/config/hypr/scripts/PortalHyprland.sh b/config/hypr/scripts/PortalHyprland.sh
index 243ee2e8..3d3dc410 100755
--- a/config/hypr/scripts/PortalHyprland.sh
+++ b/config/hypr/scripts/PortalHyprland.sh
@@ -8,6 +8,19 @@
# For manually starting xdg-desktop-portal-hyprland
set -euo pipefail
+is_ubuntu_family() {
+ if [[ ! -r /etc/os-release ]]; then
+ return 1
+ fi
+
+ # shellcheck disable=SC1091
+ . /etc/os-release
+ [[ "${ID:-}" == "ubuntu" \
+ || "${ID:-}" == "linuxmint" \
+ || "${ID:-}" == "zorin" \
+ || "${ID:-}" == "rhino" \
+ || "${ID_LIKE:-}" == *ubuntu* ]]
+}
kill_quietly() {
killall -q "$1" 2>/dev/null || true
@@ -25,11 +38,15 @@ start_portal_binary() {
echo "Warning: no $description binary found (checked: $*)" >&2
return 1
}
+if ! is_ubuntu_family; then
+ exit 0
+fi
sleep 1
kill_quietly xdg-desktop-portal-hyprland
kill_quietly xdg-desktop-portal-wlr
kill_quietly xdg-desktop-portal-gnome
+kill_quietly xdg-desktop-portal-gtk
kill_quietly xdg-desktop-portal
sleep 1
@@ -39,6 +56,10 @@ start_portal_binary "xdg-desktop-portal-hyprland" \
sleep 2
+start_portal_binary "xdg-desktop-portal-gtk" \
+ /usr/lib/xdg-desktop-portal-gtk \
+ /usr/libexec/xdg-desktop-portal-gtk
+
start_portal_binary "xdg-desktop-portal" \
/usr/lib/xdg-desktop-portal \
/usr/libexec/xdg-desktop-portal
diff --git a/config/hypr/scripts/Refresh.sh b/config/hypr/scripts/Refresh.sh
index 03f55fa0..947b6bfc 100755
--- a/config/hypr/scripts/Refresh.sh
+++ b/config/hypr/scripts/Refresh.sh
@@ -43,16 +43,39 @@ for pid in $(pidof rofi swaync ags swaybg); do
sleep 0.1
done
-# Reload or start waybar once
-if pidof waybar >/dev/null; then
- if command -v waybar-msg >/dev/null 2>&1; then
- waybar-msg cmd reload >/dev/null 2>&1 || true
+# Restart waybar once (works with systemd user unit or manual launch setups)
+restart_waybar() {
+ local manage_with_systemd=0
+
+ if command -v systemctl >/dev/null 2>&1; then
+ if systemctl --user --quiet is-active waybar.service 2>/dev/null || systemctl --user --quiet is-enabled waybar.service 2>/dev/null; then
+ manage_with_systemd=1
+ fi
+ fi
+
+ if [ "$manage_with_systemd" -eq 1 ]; then
+ systemctl --user stop waybar.service >/dev/null 2>&1 || true
+ fi
+
+ pkill -x waybar >/dev/null 2>&1 || true
+ pkill -x '.waybar-wrapped' >/dev/null 2>&1 || true
+ sleep 0.2
+ if pgrep -x waybar >/dev/null 2>&1 || pgrep -x '.waybar-wrapped' >/dev/null 2>&1; then
+ pkill -9 -x waybar >/dev/null 2>&1 || true
+ pkill -9 -x '.waybar-wrapped' >/dev/null 2>&1 || true
+ fi
+ sleep 0.2
+
+ if [ "$manage_with_systemd" -eq 1 ]; then
+ if ! systemctl --user start waybar.service >/dev/null 2>&1; then
+ waybar >/dev/null 2>&1 &
+ fi
else
- killall -SIGUSR2 waybar 2>/dev/null || true
+ waybar >/dev/null 2>&1 &
fi
-else
- waybar &
-fi
+}
+
+restart_waybar
# relaunch swaync
sleep 0.3
diff --git a/config/hypr/scripts/ResizeActive.sh b/config/hypr/scripts/ResizeActive.sh
new file mode 100755
index 00000000..afcdeb85
--- /dev/null
+++ b/config/hypr/scripts/ResizeActive.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+# Resize the active window by delta values (dx, dy) using Lua dispatch.
+
+dx="${1:-0}"
+dy="${2:-0}"
+
+window_json="$(hyprctl activewindow -j)"
+width="$(jq -r '.size[0]' <<<"$window_json")"
+height="$(jq -r '.size[1]' <<<"$window_json")"
+
+new_width=$((width + dx))
+new_height=$((height + dy))
+
+if ((new_width < 100)); then
+ new_width=100
+fi
+if ((new_height < 100)); then
+ new_height=100
+fi
+
+hyprctl dispatch "hl.dsp.window.resize({ x = ${new_width}, y = ${new_height} })"
diff --git a/config/hypr/scripts/Tak0-Autodispatch.sh b/config/hypr/scripts/Tak0-Autodispatch.sh
index 034a6402..cabe1672 100755
--- a/config/hypr/scripts/Tak0-Autodispatch.sh
+++ b/config/hypr/scripts/Tak0-Autodispatch.sh
@@ -71,6 +71,18 @@ fi
echo "=== Deploy '$CMD' → WS $TARGET_WS @ $(date) ===" >>"$LOGFILE"
+# Detect active Hyprland config mode
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+
+if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+else
+ hypr_config_mode="conf"
+fi
+
# ─────────────────────────────────────────────────────────────────────────────
# 1️⃣ HYPRLAND READINESS GATE
# ─────────────────────────────────────────────────────────────────────────────
@@ -91,11 +103,17 @@ done
cleanup() {
echo "Cleanup: removing temporary capture rules and initialWorkspace at $(date)" >>"$LOGFILE"
- hyprctl keyword windowrulev2 "unset, initialClass:.*" >>"$LOGFILE" 2>&1 || true
- for RULE in "${CAPTURE_RULES[@]}"; do
- echo "Cleanup: removing temporary capture rule: $RULE" >>"$LOGFILE"
- hyprctl keyword windowrulev2 "unset, $RULE" >>"$LOGFILE" 2>&1 || true
- done
+ if [[ "$hypr_config_mode" == "lua" ]]; then
+ # In Lua mode, we try to nullify the temporary rules or trigger a reload
+ # Since we don't have a reliable 'unset' API via eval yet, we reload
+ hyprctl reload >>"$LOGFILE" 2>&1 || true
+ else
+ hyprctl keyword windowrulev2 "unset, initialClass:.*" >>"$LOGFILE" 2>&1 || true
+ for RULE in "${CAPTURE_RULES[@]}"; do
+ echo "Cleanup: removing temporary capture rule: $RULE" >>"$LOGFILE"
+ hyprctl keyword windowrulev2 "unset, $RULE" >>"$LOGFILE" 2>&1 || true
+ done
+ fi
}
trap cleanup EXIT INT TERM ERR
@@ -105,28 +123,35 @@ trap cleanup EXIT INT TERM ERR
# ─────────────────────────────────────────────────────────────────────────────
# Temporarily forces ALL windows (initialClass:.*)
# onto the target workspace.
-#
-# Protects against ultra-fast helpers:
-# • gpu-process
-# • renderer
-# • steamwebhelper
echo "Applying temporary initialWorkspace capture (initialClass:.*)" >>"$LOGFILE"
-hyprctl keyword windowrulev2 \
- "initialWorkspace $TARGET_WS silent, initialClass:.*" \
- >>"$LOGFILE" 2>&1 || true
+if [[ "$hypr_config_mode" == "lua" ]]; then
+ # Note: Using workspace property in hl.window_rule as a best-effort equivalent
+ hyprctl eval "hl.window_rule({ name = 'autodispatch-nuclear', match = { class = '.*' }, workspace = '$TARGET_WS' })" >>"$LOGFILE" 2>&1 || true
+else
+ hyprctl keyword windowrulev2 \
+ "initialWorkspace $TARGET_WS silent, initialClass:.*" \
+ >>"$LOGFILE" 2>&1 || true
+fi
# ─────────────────────────────────────────────────────────────────────────────
# 3️⃣.1 OPTIONAL CLASS-BASED PRE-CAPTURE
# ─────────────────────────────────────────────────────────────────────────────
# Additional precision rules.
-# Useful for Electron / Steam multi-process hell.
for RULE in "${CAPTURE_RULES[@]}"; do
echo "Applying temporary capture rule: $RULE" >>"$LOGFILE"
- hyprctl keyword windowrulev2 \
- "initialWorkspace $TARGET_WS silent, $RULE" \
- >>"$LOGFILE" 2>&1 || true
+ if [[ "$hypr_config_mode" == "lua" ]]; then
+ # Attempt to parse rule string into Lua table if it matches class:pattern
+ if [[ "$RULE" =~ class:\^\((.*)\)\$ ]]; then
+ local class_pat="${BASH_REMATCH[1]}"
+ hyprctl eval "hl.window_rule({ name = 'autodispatch-$class_pat', match = { class = '$class_pat' }, workspace = '$TARGET_WS' })" >>"$LOGFILE" 2>&1 || true
+ fi
+ else
+ hyprctl keyword windowrulev2 \
+ "initialWorkspace $TARGET_WS silent, $RULE" \
+ >>"$LOGFILE" 2>&1 || true
+ fi
done
# ─────────────────────────────────────────────────────────────────────────────
@@ -158,9 +183,14 @@ echo "App gate name: $APP_NAME" >>"$LOGFILE"
sleep 1.5
-#!TO-DO: Release the nuclear option ASAP
echo "Releasing ultra-early wide capture" >>"$LOGFILE"
-hyprctl keyword windowrulev2 "unset, initialClass:.*" >>"$LOGFILE" 2>&1 || true
+if [[ "$hypr_config_mode" == "lua" ]]; then
+ # In Lua mode, we rely on the supervision loop for precision after launch
+ # and cleanup will handle the rest.
+ :
+else
+ hyprctl keyword windowrulev2 "unset, initialClass:.*" >>"$LOGFILE" 2>&1 || true
+fi
# ─────────────────────────────────────────────────────────────────────────────
# 5️⃣ SUPERVISION LOOP (AUTHORITATIVE PHASE)
@@ -221,6 +251,7 @@ while ((SECONDS < END_TIME)); do
if ((MATCH)) && [[ -z "${SEEN[$ADDR]-}" ]]; then
echo "Placing window $ADDR (pid $PID, class $CLASS) → WS $TARGET_WS" >>"$LOGFILE"
+ # Dispatch works the same in both modes
hyprctl dispatch movetoworkspacesilent \
"$TARGET_WS,address:$ADDR" >>"$LOGFILE" 2>&1 || true
SEEN[$ADDR]=1
diff --git a/config/hypr/scripts/Tak0-Per-Window-Switch.sh b/config/hypr/scripts/Tak0-Per-Window-Switch.sh
index 04baebe0..eba3b380 100755
--- a/config/hypr/scripts/Tak0-Per-Window-Switch.sh
+++ b/config/hypr/scripts/Tak0-Per-Window-Switch.sh
@@ -1,3 +1,4 @@
+#!/usr/bin/env bash
# ==================================================
# KoolDots (2026)
# Project URL: https://github.com/LinuxBeginnings
@@ -16,28 +17,65 @@
# smooth and comfortable workflow. #
# #
##################################################################
-# This is for changing kb_layouts. Set kb_layouts in
MAP_FILE="$HOME/.cache/kb_layout_per_window"
-USER_CFG="$HOME/.config/hypr/UserConfigs/UserSettings.conf"
-SYS_CFG="$HOME/.config/hypr/configs/SystemSettings.conf"
ICON="$HOME/.config/swaync/images/ja.png"
SCRIPT_NAME="$(basename "$0")"
+SCRIPT_PATH="$(readlink -f "$0")"
+
+# Detect active Hyprland config mode
+config_home="${XDG_CONFIG_HOME:-$HOME/.config}"
+hypr_dir="$config_home/hypr"
+lua_entry="$hypr_dir/hyprland.lua"
+legacy_lua_entry="$config_home/hyprland.lua"
+
+if [[ -f "$lua_entry" || -f "$legacy_lua_entry" ]]; then
+ hypr_config_mode="lua"
+else
+ hypr_config_mode="conf"
+fi
# Ensure map file exists
touch "$MAP_FILE"
-# Read layouts from config
-if grep -q 'kb_layout' "$USER_CFG" 2>/dev/null; then
- CFG_FILE="$USER_CFG"
-elif grep -q 'kb_layout' "$SYS_CFG" 2>/dev/null; then
- CFG_FILE="$SYS_CFG"
-else
- echo "Error: cannot find kb_layout in UserSettings.conf nor SystemSettings.conf" >&2
- exit 1
+# Function to get layouts from config files
+get_layouts() {
+ local layouts=""
+ if [[ "$hypr_config_mode" == "lua" ]]; then
+ local lua_user="$hypr_dir/UserConfigs/user_settings.lua"
+ local lua_sys="$hypr_dir/configs/system_settings.lua"
+ local lua_legacy_sys="$hypr_dir/UserConfigs/system_settings.lua"
+ local lua_pristine_sys="$hypr_dir/lua/settings.lua"
+ if [[ -f "$lua_user" ]] && grep -q 'kb_layout' "$lua_user" 2>/dev/null; then
+ layouts=$(grep 'kb_layout' "$lua_user" | sed -n 's/.*kb_layout\s*=\s*"\([^"]*\)".*/\1/p' | head -n1)
+ elif [[ -f "$lua_sys" ]] && grep -q 'kb_layout' "$lua_sys" 2>/dev/null; then
+ layouts=$(grep 'kb_layout' "$lua_sys" | sed -n 's/.*kb_layout\s*=\s*"\([^"]*\)".*/\1/p' | head -n1)
+ elif [[ -f "$lua_legacy_sys" ]] && grep -q 'kb_layout' "$lua_legacy_sys" 2>/dev/null; then
+ layouts=$(grep 'kb_layout' "$lua_legacy_sys" | sed -n 's/.*kb_layout\s*=\s*"\([^"]*\)".*/\1/p' | head -n1)
+ elif [[ -f "$lua_pristine_sys" ]] && grep -q 'kb_layout' "$lua_pristine_sys" 2>/dev/null; then
+ layouts=$(grep 'kb_layout' "$lua_pristine_sys" | sed -n 's/.*kb_layout\s*=\s*"\([^"]*\)".*/\1/p' | head -n1)
+ fi
+ else
+ local conf_user="$hypr_dir/UserConfigs/UserSettings.conf"
+ local conf_sys="$hypr_dir/configs/SystemSettings.conf"
+ if [[ -f "$conf_user" ]] && grep -q 'kb_layout' "$conf_user" 2>/dev/null; then
+ layouts=$(grep 'kb_layout' "$conf_user" | cut -d '=' -f2 | tr -d '[:space:]' | head -n1)
+ elif [[ -f "$conf_sys" ]] && grep -q 'kb_layout' "$conf_sys" 2>/dev/null; then
+ layouts=$(grep 'kb_layout' "$conf_sys" | cut -d '=' -f2 | tr -d '[:space:]' | head -n1)
+ fi
+ fi
+ echo "$layouts" | tr ',' ' '
+}
+
+raw_layouts=$(get_layouts)
+if [[ -z "$raw_layouts" ]]; then
+ echo "Error: cannot find kb_layout in configuration files." >&2
+ exit 1
fi
-kb_layouts=($(grep 'kb_layout' "$CFG_FILE" | cut -d '=' -f2 | tr -d '[:space:]' | tr ',' ' '))
+
+kb_layouts=($raw_layouts)
count=${#kb_layouts[@]}
+
# Get current active window ID
get_win() {
hyprctl activewindow -j | jq -r '.address // .id'
@@ -51,7 +89,7 @@ get_keyboards() {
# Save window-specific layout
save_map() {
local W=$1 L=$2
- grep -v "^${W}:" "$MAP_FILE" > "$MAP_FILE.tmp"
+ grep -v "^${W}:" "$MAP_FILE" > "$MAP_FILE.tmp" 2>/dev/null
echo "${W}:${L}" >> "$MAP_FILE.tmp"
mv "$MAP_FILE.tmp" "$MAP_FILE"
}
@@ -60,7 +98,7 @@ save_map() {
load_map() {
local W=$1
local E
- E=$(grep "^${W}:" "$MAP_FILE")
+ E=$(grep "^${W}:" "$MAP_FILE" 2>/dev/null)
[[ -n "$E" ]] && echo "${E#*:}" || echo "${kb_layouts[0]}"
}
@@ -68,16 +106,17 @@ load_map() {
do_switch() {
local IDX=$1
for kb in $(get_keyboards); do
- hyprctl switchxkblayout "$kb" "$IDX" 2>/dev/null
+ hyprctl switchxkblayout "$kb" "$IDX" >/dev/null 2>&1
done
}
# Toggle layout for current window only
cmd_toggle() {
local W=$(get_win)
- [[ -z "$W" ]] && return
+ [[ -z "$W" || "$W" == "null" ]] && return
local CUR=$(load_map "$W")
- local i NEXT
+ local i=0
+ local NEXT
for idx in "${!kb_layouts[@]}"; do
if [[ "${kb_layouts[idx]}" == "$CUR" ]]; then
i=$idx
@@ -93,7 +132,7 @@ cmd_toggle() {
# Restore layout on focus
cmd_restore() {
local W=$(get_win)
- [[ -z "$W" ]] && return
+ [[ -z "$W" || "$W" == "null" ]] && return
local LAY=$(load_map "$W")
for idx in "${!kb_layouts[@]}"; do
if [[ "${kb_layouts[idx]}" == "$LAY" ]]; then
@@ -106,23 +145,37 @@ cmd_restore() {
# Listen to focus events and restore window-specific layouts
subscribe() {
local SOCKET2="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"
+ if [[ ! -S "$SOCKET2" ]]; then
+ # Fallback if HYPRLAND_INSTANCE_SIGNATURE is not set correctly in this subshell
+ local sig=$(hyprctl instances -j | jq -r '.[0].instance' 2>/dev/null)
+ SOCKET2="$XDG_RUNTIME_DIR/hypr/$sig/.socket2.sock"
+ fi
+
[[ -S "$SOCKET2" ]] || {
echo "Error: Hyprland socket not found." >&2
exit 1
}
socat -u UNIX-CONNECT:"$SOCKET2" - | while read -r line; do
- [[ "$line" =~ ^activewindow ]] && cmd_restore
+ if [[ "$line" =~ ^activewindow ]]; then
+ cmd_restore
+ fi
done
}
-# Ensure only one listener
-if ! pgrep -f "$SCRIPT_NAME.*--listener" >/dev/null; then
- subscribe --listener &
-fi
-
# CLI
case "$1" in
- toggle|"") cmd_toggle ;;
- *) echo "Usage: $SCRIPT_NAME [toggle]" >&2; exit 1 ;;
+ --listener)
+ subscribe
+ ;;
+ toggle|"")
+ # Ensure only one listener
+ if ! pgrep -f "$SCRIPT_NAME.*--listener" >/dev/null; then
+ "$SCRIPT_PATH" --listener &
+ fi
+ cmd_toggle
+ ;;
+ *)
+ echo "Usage: $SCRIPT_NAME [toggle]" >&2; exit 1
+ ;;
esac
diff --git a/config/hypr/scripts/ThemeChanger.sh b/config/hypr/scripts/ThemeChanger.sh
index b41738f6..c19908e4 100755
--- a/config/hypr/scripts/ThemeChanger.sh
+++ b/config/hypr/scripts/ThemeChanger.sh
@@ -6,6 +6,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
# ==================================================
set -euo pipefail
+# Wallust v3/v4 compatibility
+wallust_args=()
+# shellcheck source=/dev/null
+if [ -f "$HOME/.config/hypr/scripts/WallustConfig.sh" ]; then
+ . "$HOME/.config/hypr/scripts/WallustConfig.sh"
+fi
# SPDX-FileCopyrightText: 2025-present Ahum Maitra theahummaitra@gmail.com
#
@@ -25,12 +31,130 @@ require rofi
# notify-send is optional
have_notify() { command -v notify-send >/dev/null 2>&1; }
+capture_current_layout() {
+ if command -v jq >/dev/null 2>&1; then
+ hyprctl -j getoption general:layout 2>/dev/null | jq -r '.str // empty'
+ else
+ hyprctl getoption general:layout 2>/dev/null | awk 'NR==1 {print $2}'
+ fi
+}
+restore_layout_after_reload() {
+ local layout="$1"
+ [ -n "$layout" ] || return 0
+
+ if [ -x "$HOME/.config/hypr/scripts/ChangeLayout.sh" ]; then
+ if "$HOME/.config/hypr/scripts/ChangeLayout.sh" --no-notify "$layout" >/dev/null 2>&1; then
+ return 0
+ fi
+ fi
+
+ hyprctl keyword general:layout "$layout" >/dev/null 2>&1 || true
+}
+reload_hypr_preserve_layout() {
+ command -v hyprctl >/dev/null 2>&1 || return 0
+
+ local active_layout
+ active_layout="$(capture_current_layout || true)"
+
+ hyprctl reload config-only >/dev/null 2>&1 || true
+ sleep 0.1
+ restore_layout_after_reload "$active_layout"
+}
+# Cache theme list to avoid slow re-enumeration on every invocation
+cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}"
+theme_cache="${cache_dir}/wallust_theme_list.txt"
+cache_max_age=86400 # seconds
+
+build_theme_list() {
+ wallust "${wallust_args[@]}" theme list \
+ | awk '/^- /{sub(/^- /,""); sub(/ \(.*/, ""); print}'
+}
+
+update_theme_cache() {
+ mkdir -p "$cache_dir"
+ local tmp
+ tmp="$(mktemp "${cache_dir}/wallust-theme-list.XXXXXX")"
+ if build_theme_list > "$tmp"; then
+ if [ -s "$tmp" ]; then
+ mv "$tmp" "$theme_cache"
+ return 0
+ fi
+ fi
+ rm -f "$tmp"
+ return 1
+}
+
+cache_mtime=$(stat -c %Y "$theme_cache" 2>/dev/null || echo 0)
+cache_age=$(( $(date +%s) - cache_mtime ))
+if [ ! -s "$theme_cache" ] || [ "$cache_age" -gt "$cache_max_age" ]; then
+ update_theme_cache || true
+fi
+
+ensure_wallust_waybar_style() {
+ local waybar_style="$HOME/.config/waybar/style.css"
+ local colors_file="$HOME/.config/waybar/wallust/colors-waybar.css"
+ local styles_dir="$HOME/.config/waybar/style"
+ [ -f "$colors_file" ] || return 0
+ if [ -f "$waybar_style" ] && grep -q 'colors-waybar.css' "$waybar_style"; then
+ return 0
+ fi
+ local candidates=(
+ "Wallust-Chroma-Fusion.css"
+ "Wallust-ML4W-modern.css"
+ "Wallust-Colored.css"
+ "Wallust-Box-type.css"
+ "Wallust-Simple.css"
+ )
+ for candidate in "${candidates[@]}"; do
+ if [ -f "$styles_dir/$candidate" ]; then
+ ln -sf "$styles_dir/$candidate" "$waybar_style"
+ break
+ fi
+ done
+}
+reload_running_cava_colors() {
+ # CAVA supports SIGUSR2 to reload colors without full audio reinitialization.
+ if pgrep -x cava >/dev/null 2>&1; then
+ pkill -USR2 -x cava >/dev/null 2>&1 || true
+ fi
+}
+
+wallust_hypr_colors="$HOME/.config/hypr/wallust/wallust-hyprland.conf"
+extract_wallust_hex() {
+ local key="$1"
+ awk -v key="$key" '
+ $1 == "$" key && $2 == "=" {
+ if (match($3, /^rgb\(([0-9A-Fa-f]{6})\)$/, m)) {
+ print toupper(m[1])
+ exit
+ }
+ }
+ ' "$wallust_hypr_colors"
+}
+
+apply_hypr_border_fallback() {
+ [ -s "$wallust_hypr_colors" ] || return 0
+ local color12 color10 color15 color0
+ color12="$(extract_wallust_hex color12)"
+ color10="$(extract_wallust_hex color10)"
+ color15="$(extract_wallust_hex color15)"
+ color0="$(extract_wallust_hex color0)"
+
+ [ -n "$color12" ] && hyprctl keyword general:col.active_border "rgb($color12)" >/dev/null 2>&1 || true
+ [ -n "$color10" ] && hyprctl keyword general:col.inactive_border "rgb($color10)" >/dev/null 2>&1 || true
+ [ -n "$color12" ] && hyprctl keyword decoration:shadow:color "rgb($color12)" >/dev/null 2>&1 || true
+ [ -n "$color10" ] && hyprctl keyword decoration:shadow:color_inactive "rgb($color10)" >/dev/null 2>&1 || true
+ [ -n "$color15" ] && hyprctl keyword group:col.border_active "rgb($color15)" >/dev/null 2>&1 || true
+ [ -n "$color0" ] && hyprctl keyword group:groupbar:col.active "rgb($color0)" >/dev/null 2>&1 || true
+}
# Prompt for theme; guard -e on cancel
set +e
-choice="$(wallust theme list \
- | sed -e '1d' -e 's/^- //' \
- | rofi -dmenu -i -p 'Select Global Theme')"
+if [ -s "$theme_cache" ]; then
+ choice="$(rofi -dmenu -i -p 'Select Global Theme' < "$theme_cache")"
+else
+ choice="$(build_theme_list | rofi -dmenu -i -p 'Select Global Theme')"
+fi
prompt_status=$?
set -e
@@ -41,9 +165,15 @@ fi
# Record time before applying so we can wait for fresh template outputs
start_ts=$(date +%s)
+# Notify quickly so users get feedback immediately
+have_notify && notify-send -a ThemeChanger \
+ -h string:x-dunst-stack-tag:themechanger \
+ "Applying theme" "Selected: ${choice}"
# Apply the theme and report result
-if wallust theme -- "${choice}"; then
+wallust_log="${XDG_CACHE_HOME:-$HOME/.cache}/wallust/themechanger.log"
+mkdir -p "$(dirname "$wallust_log")"
+if wallust "${wallust_args[@]}" theme -- "${choice}" >"$wallust_log" 2>&1; then
have_notify && notify-send -a ThemeChanger \
-h string:x-dunst-stack-tag:themechanger \
"Global theme changed" "Selected: ${choice}"
@@ -55,9 +185,7 @@ if wallust theme -- "${choice}"; then
targets=(
"$HOME/.config/waybar/wallust/colors-waybar.css"
"$HOME/.config/rofi/wallust/colors-rofi.rasi"
- "$HOME/.config/kitty/kitty-themes/01-Wallust.conf"
"$HOME/.config/hypr/wallust/wallust-hyprland.conf"
- "$HOME/.config/ghostty/wallust.conf"
)
# Normalize Ghostty palette syntax in case upstream templates or older targets used ':'
@@ -101,6 +229,13 @@ if wallust theme -- "${choice}"; then
sleep 0.5
fi
+ if [ "${ok:-0}" -ne 1 ]; then
+ have_notify && notify-send -u critical -a ThemeChanger \
+ -h string:x-dunst-stack-tag:themechanger \
+ "Theme files not updated" "See: $wallust_log"
+ exit 1
+ fi
+
# Small cushion before refresh to mirror wallpaper flow
sleep 0.2
# Normalize Rofi selection colors to use the palette's accent (color12)
@@ -118,10 +253,9 @@ if wallust theme -- "${choice}"; then
fi
fi
- # Reload Hyprland so new border colors from wallust-hyprland.conf take effect
- if command -v hyprctl >/dev/null 2>&1; then
- hyprctl reload >/dev/null 2>&1 || true
- fi
+ reload_hypr_preserve_layout
+ ensure_wallust_waybar_style
+ reload_running_cava_colors
# Refresh bars/menus after files are ready
if [ -x "$HOME/.config/hypr/scripts/Refresh.sh" ]; then
@@ -146,6 +280,6 @@ if wallust theme -- "${choice}"; then
else
have_notify && notify-send -u critical -a ThemeChanger \
-h string:x-dunst-stack-tag:themechanger \
- "Failed to apply theme" "${choice}"
+ "Failed to apply theme" "See: $wallust_log"
exit 1
fi
diff --git a/config/hypr/scripts/Toggle-Active-Window-Audio.sh b/config/hypr/scripts/Toggle-Active-Window-Audio.sh
index f32ded0e..c954541f 100755
--- a/config/hypr/scripts/Toggle-Active-Window-Audio.sh
+++ b/config/hypr/scripts/Toggle-Active-Window-Audio.sh
@@ -54,18 +54,18 @@ pidsJson="$(printf '%s\n' "${all_pids[@]}" | jq -s 'map(tonumber)')"
#// Check if any descendant PID matches application.process.id or else verify other statements.
mapfile -t sink_ids < <(jq -r --argjson pids "${pidsJson}" --arg class "${__class}" --arg title "${__title}" '
.[] |
- def lc(x): (x // "" | ascii_downcase);
- def normalize(x): x | gsub("[-_~.]+";" ") ;
+ def norm(x): (x // "" | ascii_downcase | gsub("[-_~.]+";" ") | gsub("[^a-z0-9 ]+";" ") | gsub("[[:space:]]+";" ") | gsub("^ +| +$";""));
+ def to_num(x): (try (x | tostring | tonumber) catch null);
select(
- (.properties["application.process.id"] | tostring | tonumber? as $p | $p != null and ($pids | index($p)))
+ (to_num(.properties["application.process.id"]) as $p | $p != null and ($pids | index($p)))
or
- (lc(.properties["application.name"]) | contains(lc($class)))
+ (norm(.properties["application.name"]) | contains(norm($class)))
or
- (lc(.properties["application.id"]) | contains(lc($class)))
+ (norm(.properties["application.id"]) | contains(norm($class)))
or
- (lc(.properties["application.process.binary"]) | contains(lc($class)))
+ (norm(.properties["application.process.binary"]) | contains(norm($class)))
or
- (normalize(lc(.properties["media.name"])) | contains(normalize(lc($title))))
+ (norm(.properties["media.name"]) | contains(norm($title)))
) | .index' <<< "${sink_json}"
)
@@ -87,8 +87,9 @@ if [[ "${#sink_ids[@]}" -eq 0 ]]; then
done
done
fallbackJson="$(printf '%s\n' "${all_fallback[@]}" | jq -s 'map(tonumber)')"
- mapfile -t sink_ids < <( jq -r --argjson pids "${fallbackJson}" '.[] |
- select((.properties["application.process.id"] | tostring | tonumber? as $p | $p != null and ($pids | index($p)))) | .index' <<< "${sink_json}" )
+ mapfile -t sink_ids < <( jq -r --argjson pids "${fallbackJson}" '.[] |
+ def to_num(x): (try (x | tostring | tonumber) catch null);
+ select((to_num(.properties["application.process.id"]) as $p | $p != null and ($pids | index($p)))) | .index' <<< "${sink_json}" )
fi
fi
diff --git a/config/hypr/scripts/WallpaperCmd.sh b/config/hypr/scripts/WallpaperCmd.sh
index 0191ee14..90a88fd9 100755
--- a/config/hypr/scripts/WallpaperCmd.sh
+++ b/config/hypr/scripts/WallpaperCmd.sh
@@ -24,8 +24,99 @@ if [ "$WWW_CMD" = "awww" ]; then
mkdir -p "$WWW_CACHE_DIR"
if [ ! -f "$WWW_MIGRATION_MARKER" ]; then
awww clear-cache >/dev/null 2>&1 || true
+ mkdir -p "$WWW_CACHE_DIR"
touch "$WWW_MIGRATION_MARKER"
fi
fi
+wallpaper_monitor_dimensions() {
+ local monitor="${1:-}"
+ local dims=""
+
+ command -v hyprctl >/dev/null 2>&1 || return 1
+
+ if command -v jq >/dev/null 2>&1; then
+ if [ -n "$monitor" ]; then
+ dims="$(hyprctl monitors -j 2>/dev/null | jq -r --arg mon "$monitor" '.[] | select(.name == $mon) | "\(.width) \(.height)"' | awk 'NF == 2 {print; exit}')"
+ else
+ dims="$(hyprctl monitors -j 2>/dev/null | jq -r '.[] | select(.focused == true) | "\(.width) \(.height)"' | awk 'NF == 2 {print; exit}')"
+ if [ -z "$dims" ]; then
+ dims="$(hyprctl monitors -j 2>/dev/null | jq -r '.[0] | "\(.width) \(.height)"' | awk 'NF == 2 {print; exit}')"
+ fi
+ fi
+ fi
+
+ if [ -z "$dims" ]; then
+ dims="$(hyprctl monitors 2>/dev/null | awk -v mon="$monitor" '
+ $1=="Monitor" {current=$2; target=(mon=="" || current==mon)}
+ target {
+ if (match($0, /[0-9]+x[0-9]+@/)) {
+ res = substr($0, RSTART, RLENGTH)
+ sub(/@.*/, "", res)
+ split(res, xy, "x")
+ print xy[1], xy[2]
+ exit
+ }
+ }
+ ')"
+ fi
+
+ [ -n "$dims" ] || return 1
+ printf '%s\n' "$dims"
+}
+
+wallpaper_image_dimensions() {
+ local image_path="$1"
+ local dims=""
+
+ [ -n "$image_path" ] && [ -f "$image_path" ] || return 1
+
+ if command -v magick >/dev/null 2>&1; then
+ dims="$(magick identify -ping -format '%w %h' "${image_path}[0]" 2>/dev/null || true)"
+ [ -n "$dims" ] || dims="$(magick identify -ping -format '%w %h' "$image_path" 2>/dev/null || true)"
+ elif command -v identify >/dev/null 2>&1; then
+ dims="$(identify -ping -format '%w %h' "${image_path}[0]" 2>/dev/null || true)"
+ [ -n "$dims" ] || dims="$(identify -ping -format '%w %h' "$image_path" 2>/dev/null || true)"
+ fi
+
+ [ -n "$dims" ] || return 1
+ printf '%s\n' "$dims"
+}
+
+wallpaper_resize_mode() {
+ local image_path="$1"
+ local monitor="${2:-}"
+ local mode="${WALLPAPER_RESIZE_MODE:-auto}"
+ local mon_w mon_h img_w img_h
+
+ mode="${mode,,}"
+ case "$mode" in
+ fit|crop)
+ printf '%s\n' "$mode"
+ return 0
+ ;;
+ auto|"")
+ ;;
+ *)
+ mode="auto"
+ ;;
+ esac
+
+ if ! read -r mon_w mon_h < <(wallpaper_monitor_dimensions "$monitor"); then
+ printf '%s\n' "crop"
+ return 0
+ fi
+ if ! read -r img_w img_h < <(wallpaper_image_dimensions "$image_path"); then
+ printf '%s\n' "crop"
+ return 0
+ fi
+
+ if [ "$img_w" -lt "$mon_w" ] || [ "$img_h" -lt "$mon_h" ]; then
+ printf '%s\n' "crop"
+ return 0
+ fi
+
+ # Auto mode prefers full-screen fill; set WALLPAPER_RESIZE_MODE=fit to preserve full image.
+ printf '%s\n' "crop"
+}
export WWW_CMD WWW_DAEMON WWW_CACHE_DIR WWW_DAEMON_ARGS WWW_MIGRATION_MARKER
diff --git a/config/hypr/scripts/WallpaperDaemon.sh b/config/hypr/scripts/WallpaperDaemon.sh
index 4d1c6e86..60c35a6d 100755
--- a/config/hypr/scripts/WallpaperDaemon.sh
+++ b/config/hypr/scripts/WallpaperDaemon.sh
@@ -11,12 +11,12 @@ SCRIPTSDIR="$HOME/.config/hypr/scripts"
# shellcheck source=/dev/null
. "$SCRIPTSDIR/WallpaperCmd.sh"
-if command -v "$WWW_DAEMON" >/dev/null 2>&1 && command -v "$WWW_CMD" >/dev/null 2>&1; then
+if command -v "$WWW_DAEMON" >/dev/null 2>&1 && command -v "$WWW_CMD" >/dev/null 2>&1 && ! pgrep -x "$WWW_DAEMON" >/dev/null 2>&1; then
"$WWW_DAEMON" "${WWW_DAEMON_ARGS[@]}" &
fi
# Give the daemon a moment to become ready
-for _ in {1..20}; do
+for _ in {1..50}; do
"$WWW_CMD" query >/dev/null 2>&1 && break
sleep 0.1
done
@@ -92,9 +92,11 @@ apply_wallpaper_for_monitor() {
fi
if [ -n "$wallpaper_path" ] && [ -f "$wallpaper_path" ]; then
- if ! "$WWW_CMD" img -o "$monitor" "$wallpaper_path" >/dev/null 2>&1; then
+ local resize_mode
+ resize_mode="$(wallpaper_resize_mode "$wallpaper_path" "$monitor")"
+ if ! "$WWW_CMD" img -o "$monitor" --resize "$resize_mode" "$wallpaper_path" >/dev/null 2>&1; then
sleep 0.3
- "$WWW_CMD" img -o "$monitor" "$wallpaper_path" >/dev/null 2>&1 &
+ "$WWW_CMD" img -o "$monitor" --resize "$resize_mode" "$wallpaper_path" >/dev/null 2>&1 &
fi
fi
}
diff --git a/config/hypr/scripts/WallustConfig.sh b/config/hypr/scripts/WallustConfig.sh
new file mode 100755
index 00000000..44ddff0b
--- /dev/null
+++ b/config/hypr/scripts/WallustConfig.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Wallust version compatibility helpers
+#
+# Purpose:
+# - Wallust v3 reads ~/.config/wallust/wallust.toml (this repo ships a v3 config)
+# - Wallust v4 alpha uses a different config schema; users frequently install it
+# via wallust-git, which will fail to parse the v3 config.
+#
+# This file detects Wallust major version and sets arrays used by scripts:
+# - wallust_args: args to pass to wallust for wallpaper-derived palette generation
+# - wallust_kitty_args: args to pass to wallust for kitty-only palette generation
+
+wallust_args=()
+wallust_kitty_args=()
+
+wallust_prepare_args() {
+ wallust_args=()
+ wallust_kitty_args=()
+
+ command -v wallust >/dev/null 2>&1 || return 0
+
+ local version major
+ version=$(wallust --version 2>/dev/null | awk '{print $2}')
+ major=${version%%.*}
+
+ # Wallust v4 supports -C/--config-file.
+ if [ -n "$major" ] && [ "$major" -ge 4 ]; then
+ local v4_cfg="$HOME/.config/wallust/wallust-v4.toml"
+ local v4_kitty_cfg="$HOME/.config/wallust/wallust-kitty-v4.toml"
+
+ if [ -f "$v4_cfg" ]; then
+ wallust_args=(-C "$v4_cfg")
+ fi
+ if [ -f "$v4_kitty_cfg" ]; then
+ wallust_kitty_args=(-C "$v4_kitty_cfg")
+ fi
+ fi
+}
+
+wallust_prepare_args
diff --git a/config/hypr/scripts/WallustSwww.sh b/config/hypr/scripts/WallustSwww.sh
index 9df3ed61..3472d519 100755
--- a/config/hypr/scripts/WallustSwww.sh
+++ b/config/hypr/scripts/WallustSwww.sh
@@ -9,6 +9,73 @@
# Usage: WallustSwww.sh [absolute_path_to_wallpaper]
set -euo pipefail
+# Wallust v3/v4 compatibility
+wallust_args=()
+wallust_kitty_args=()
+# shellcheck source=/dev/null
+if [ -f "$HOME/.config/hypr/scripts/WallustConfig.sh" ]; then
+ . "$HOME/.config/hypr/scripts/WallustConfig.sh"
+fi
+have_notify() { command -v notify-send >/dev/null 2>&1; }
+wallust_log="${XDG_CACHE_HOME:-$HOME/.cache}/wallust/wallust-swww.log"
+mkdir -p "$(dirname "$wallust_log")"
+capture_current_layout() {
+ if command -v jq >/dev/null 2>&1; then
+ hyprctl -j getoption general:layout 2>/dev/null | jq -r '.str // empty'
+ else
+ hyprctl getoption general:layout 2>/dev/null | awk 'NR==1 {print $2}'
+ fi
+}
+restore_layout_after_reload() {
+ local layout="$1"
+ [ -n "$layout" ] || return 0
+
+ if [ -x "$HOME/.config/hypr/scripts/ChangeLayout.sh" ]; then
+ if "$HOME/.config/hypr/scripts/ChangeLayout.sh" --no-notify "$layout" >/dev/null 2>&1; then
+ return 0
+ fi
+ fi
+
+ hyprctl keyword general:layout "$layout" >/dev/null 2>&1 || true
+}
+reload_hypr_preserve_layout() {
+ command -v hyprctl >/dev/null 2>&1 || return 0
+
+ local active_layout
+ active_layout="$(capture_current_layout || true)"
+
+ hyprctl reload config-only >/dev/null 2>&1 || true
+ sleep 0.1
+ restore_layout_after_reload "$active_layout"
+}
+ensure_wallust_waybar_style() {
+ local waybar_style="$HOME/.config/waybar/style.css"
+ local colors_file="$HOME/.config/waybar/wallust/colors-waybar.css"
+ local styles_dir="$HOME/.config/waybar/style"
+ [ -f "$colors_file" ] || return 0
+ if [ -f "$waybar_style" ] && grep -q 'colors-waybar.css' "$waybar_style"; then
+ return 0
+ fi
+ local candidates=(
+ "Wallust-Chroma-Fusion.css"
+ "Wallust-ML4W-modern.css"
+ "Wallust-Colored.css"
+ "Wallust-Box-type.css"
+ "Wallust-Simple.css"
+ )
+ for candidate in "${candidates[@]}"; do
+ if [ -f "$styles_dir/$candidate" ]; then
+ ln -sf "$styles_dir/$candidate" "$waybar_style"
+ break
+ fi
+ done
+}
+reload_running_cava_colors() {
+ # CAVA supports SIGUSR2 to reload colors without full audio reinitialization.
+ if pgrep -x cava >/dev/null 2>&1; then
+ pkill -USR2 -x cava >/dev/null 2>&1 || true
+ fi
+}
# Inputs and paths
passed_path="${1:-}"
@@ -124,13 +191,23 @@ wait_for_templates() {
# Run wallust (silent) to regenerate templates defined in ~/.config/wallust/wallust.toml
# -s is used in this repo to keep things quiet and avoid extra prompts
start_ts=$(date +%s)
-wallust run -s "$wallpaper_path" || true
+if ! wallust "${wallust_args[@]}" run -s "$wallpaper_path" >"$wallust_log" 2>&1; then
+ have_notify && notify-send -u critical -a WallustSwww \
+ "Wallust failed" "See: $wallust_log"
+ exit 1
+fi
wallust_targets=(
"$HOME/.config/waybar/wallust/colors-waybar.css"
"$HOME/.config/rofi/wallust/colors-rofi.rasi"
"$HOME/.config/hypr/wallust/wallust-hyprland.conf"
)
-wait_for_templates "$start_ts" "${wallust_targets[@]}" || true
+if ! wait_for_templates "$start_ts" "${wallust_targets[@]}"; then
+ have_notify && notify-send -u critical -a WallustSwww \
+ "Wallust templates not updated" "See: $wallust_log"
+ exit 1
+fi
+ensure_wallust_waybar_style
+reload_running_cava_colors
# Normalize Rofi selection colors to a brighter accent and readable foreground
rofi_colors="$HOME/.config/rofi/wallust/colors-rofi.rasi"
@@ -153,28 +230,78 @@ fi
# Run kitty-only wallust config to keep terminal palette separate
run_wallust_with_config() {
local cfg="$1"
- if wallust run --help 2>&1 | grep -q -E '(^|[[:space:]])-c([,[:space:]]|$)|--config'; then
- wallust run -s -c "$cfg" "$wallpaper_path" || true
- else
- WALLUST_CONFIG="$cfg" wallust run -s "$wallpaper_path" || true
+ # Wallust v4: prefer config-file flag via WallustConfig.sh
+ if [ "${#wallust_kitty_args[@]}" -gt 0 ]; then
+ wallust "${wallust_kitty_args[@]}" run -s "$wallpaper_path" || true
+ return
fi
+ # Wallust v3+: prefer config-file flag when available.
+ # NOTE: Do not use -c here; on wallust 3.x it means colorspace, not config file.
+ if wallust run --help 2>&1 | grep -q -E -- '(^|[[:space:]])-C([,[:space:]]|$)|--config-file'; then
+ wallust run -s -C "$cfg" "$wallpaper_path" || true
+ return
+ fi
+ # Legacy fallback for builds that still honor env-based config override.
+ WALLUST_CONFIG="$cfg" wallust run -s "$wallpaper_path" || true
+}
+wallust_hypr_colors="$HOME/.config/hypr/wallust/wallust-hyprland.conf"
+extract_wallust_hex() {
+ local key="$1"
+ awk -v key="$key" '
+ $1 == "$" key && $2 == "=" {
+ if (match($3, /^rgb\(([0-9A-Fa-f]{6})\)$/, m)) {
+ print toupper(m[1])
+ exit
+ }
+ }
+ ' "$wallust_hypr_colors"
+}
+
+apply_hypr_border_fallback() {
+ [ -s "$wallust_hypr_colors" ] || return 0
+ local color12 color10 color15 color0
+ color12="$(extract_wallust_hex color12)"
+ color10="$(extract_wallust_hex color10)"
+ color15="$(extract_wallust_hex color15)"
+ color0="$(extract_wallust_hex color0)"
+
+ [ -n "$color12" ] && hyprctl keyword general:col.active_border "rgb($color12)" >/dev/null 2>&1 || true
+ [ -n "$color10" ] && hyprctl keyword general:col.inactive_border "rgb($color10)" >/dev/null 2>&1 || true
+ [ -n "$color12" ] && hyprctl keyword decoration:shadow:color "rgb($color12)" >/dev/null 2>&1 || true
+ [ -n "$color10" ] && hyprctl keyword decoration:shadow:color_inactive "rgb($color10)" >/dev/null 2>&1 || true
+ [ -n "$color15" ] && hyprctl keyword group:col.border_active "rgb($color15)" >/dev/null 2>&1 || true
+ [ -n "$color0" ] && hyprctl keyword group:groupbar:col.active "rgb($color0)" >/dev/null 2>&1 || true
+}
+
+apply_hypr_gap_fallback() {
+ local decorations_lua="$HOME/.config/hypr/UserConfigs/user_decorations.lua"
+ [ -s "$decorations_lua" ] || return 0
+ local gaps_in gaps_out border_size
+ gaps_in="$(sed -n 's/^[[:space:]]*gaps_in[[:space:]]*=[[:space:]]*\([0-9]\+\).*/\1/p' "$decorations_lua" | head -n1)"
+ gaps_out="$(sed -n 's/^[[:space:]]*gaps_out[[:space:]]*=[[:space:]]*\([0-9]\+\).*/\1/p' "$decorations_lua" | head -n1)"
+ border_size="$(sed -n 's/^[[:space:]]*border_size[[:space:]]*=[[:space:]]*\([0-9]\+\).*/\1/p' "$decorations_lua" | head -n1)"
+
+ [ -n "$gaps_in" ] && hyprctl keyword general:gaps_in "$gaps_in" >/dev/null 2>&1 || true
+ [ -n "$gaps_out" ] && hyprctl keyword general:gaps_out "$gaps_out" >/dev/null 2>&1 || true
+ [ -n "$border_size" ] && hyprctl keyword general:border_size "$border_size" >/dev/null 2>&1 || true
}
+# Apply Hyprland updates immediately to avoid delayed border/gap changes.
+reload_hypr_preserve_layout
+
kitty_cfg="$HOME/.config/wallust/wallust-kitty.toml"
+if [ "${#wallust_kitty_args[@]}" -gt 0 ]; then
+ kitty_cfg="$HOME/.config/wallust/wallust-kitty-v4.toml"
+fi
(
if [ -f "$kitty_cfg" ]; then
- kitty_ts=$(date +%s)
run_wallust_with_config "$kitty_cfg"
- wait_for_templates "$kitty_ts" "$HOME/.config/kitty/kitty-themes/01-Wallust.conf" || true
fi
- # Reload kitty colors when wallpaper-based theme is active
+ # Reload kitty colors when wallpaper-based theme is active.
+ # Use SIGUSR1 directly to avoid extra latency from kitty remote-control calls.
kitty_wallust_theme="$HOME/.config/kitty/kitty-themes/01-Wallust.conf"
if [ -s "$kitty_wallust_theme" ]; then
- if command -v kitty >/dev/null 2>&1; then
- kitty @ load-config >/dev/null 2>&1 || true
- kitty @ set-colors --all --configured "$kitty_wallust_theme" >/dev/null 2>&1 || true
- fi
if pidof kitty >/dev/null 2>&1; then
for pid in $(pidof kitty); do
kill -SIGUSR1 "$pid" 2>/dev/null || true
@@ -195,8 +322,5 @@ kitty_cfg="$HOME/.config/wallust/wallust-kitty.toml"
if pidof ghostty >/dev/null; then
for pid in $(pidof ghostty); do kill -SIGUSR2 "$pid" 2>/dev/null || true; done
fi
- # Reload Hyprland so new border colors from wallust-hyprland.conf take effect
- if command -v hyprctl >/dev/null 2>&1; then
- hyprctl reload >/dev/null 2>&1 || true
- fi
+ # Hyprland reload/keyword updates are applied above to avoid delayed color/gap updates.
) >/dev/null 2>&1 &
diff --git a/config/hypr/scripts/WaybarStartup.sh b/config/hypr/scripts/WaybarStartup.sh
new file mode 100755
index 00000000..ac269600
--- /dev/null
+++ b/config/hypr/scripts/WaybarStartup.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env bash
+# ==================================================
+# KoolDots (2026)
+# Project URL: https://github.com/LinuxBeginnings
+# License: GNU GPLv3
+# SPDX-License-Identifier: GPL-3.0-or-later
+# ==================================================
+# Dedicated startup helper for Waybar.
+# Handles both systemd user service setups and direct Waybar launch.
+
+runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
+export XDG_RUNTIME_DIR="$runtime_dir"
+
+is_waybar_running() {
+ pgrep -x "waybar" >/dev/null 2>&1 || pgrep -x '\.waybar-wrapped' >/dev/null 2>&1
+}
+
+wait_for_wayland() {
+ # If WAYLAND_DISPLAY is already valid, use it.
+ if [ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then
+ return 0
+ fi
+
+ # Otherwise wait briefly for an available Wayland socket.
+ for _ in $(seq 1 120); do
+ if [ -n "${WAYLAND_DISPLAY:-}" ] && [ -S "$runtime_dir/$WAYLAND_DISPLAY" ]; then
+ return 0
+ fi
+
+ for socket in "$runtime_dir"/wayland-[0-9]*; do
+ [ -S "$socket" ] || continue
+ case "$(basename "$socket")" in
+ *awww*) continue ;;
+ esac
+ export WAYLAND_DISPLAY="$(basename "$socket")"
+ return 0
+ done
+ sleep 0.1
+ done
+
+ return 1
+}
+
+start_waybar_direct() {
+ if command -v waybar >/dev/null 2>&1; then
+ waybar >/dev/null 2>&1 &
+ return 0
+ fi
+
+ if command -v .waybar-wrapped >/dev/null 2>&1; then
+ .waybar-wrapped >/dev/null 2>&1 &
+ return 0
+ fi
+
+ return 1
+}
+
+start_waybar_via_systemd() {
+ [ -x "$(command -v systemctl)" ] || return 1
+
+ local load_state
+ load_state="$(systemctl --user show waybar.service --property=LoadState --value 2>/dev/null || true)"
+ [ -n "$load_state" ] && [ "$load_state" != "not-found" ] || return 1
+
+ systemctl --user start waybar.service >/dev/null 2>&1 || return 1
+ sleep 0.4
+ is_waybar_running
+}
+
+main() {
+ # Allow key startup services to settle before launching Waybar.
+ sleep 1
+ wait_for_wayland || true
+
+ is_waybar_running && exit 0
+
+ if start_waybar_via_systemd; then
+ exit 0
+ fi
+
+ if is_waybar_running; then
+ exit 0
+ fi
+
+ start_waybar_direct || exit 1
+}
+
+main
diff --git a/config/hypr/scripts/keybinds_parser.py b/config/hypr/scripts/keybinds_parser.py
index 5b75d81c..bd6142d9 100755
--- a/config/hypr/scripts/keybinds_parser.py
+++ b/config/hypr/scripts/keybinds_parser.py
@@ -8,10 +8,30 @@
import sys
import re
import os
+CODE_KEY_MAP = {
+ 10: "1",
+ 11: "2",
+ 12: "3",
+ 13: "4",
+ 14: "5",
+ 15: "6",
+ 16: "7",
+ 17: "8",
+ 18: "9",
+ 19: "0",
+}
def normalize_combo(combo):
return combo.replace(" ", "").replace("\t", "")
+def humanize_key_token(mods, key):
+ key = key.strip()
+ code_match = re.match(r'(?i)^code:(\d+)$', key)
+ if code_match:
+ code_num = int(code_match.group(1))
+ return CODE_KEY_MAP.get(code_num, key)
+ return key
+
def extract_combo(line):
# Remove comments and whitespace
line = re.sub(r'\s*#.*$', '', line).strip()
@@ -128,6 +148,244 @@ def parse_files(files):
missing_unbind_suggestions.append(suggest)
return raw_keybinds, missing_unbind_suggestions
+def _parse_lua_string(value):
+ value = value.strip()
+ if len(value) < 2:
+ return None
+ if (value[0] == value[-1]) and value[0] in ("'", '"'):
+ quote = value[0]
+ body = value[1:-1]
+ if quote == '"':
+ body = body.replace('\\"', '"')
+ else:
+ body = body.replace("\\'", "'")
+ body = body.replace("\\\\", "\\")
+ return body
+ return None
+
+def _split_lua_args(arg_text):
+ args = []
+ current = []
+ depth = 0
+ in_string = None
+ escape = False
+ for ch in arg_text:
+ if in_string:
+ current.append(ch)
+ if escape:
+ escape = False
+ elif ch == '\\':
+ escape = True
+ elif ch == in_string:
+ in_string = None
+ continue
+ if ch in ("'", '"'):
+ in_string = ch
+ current.append(ch)
+ continue
+ if ch in "([{":
+ depth += 1
+ current.append(ch)
+ continue
+ if ch in ")]}":
+ depth = max(depth - 1, 0)
+ current.append(ch)
+ continue
+ if ch == ',' and depth == 0:
+ args.append("".join(current).strip())
+ current = []
+ continue
+ current.append(ch)
+ if current:
+ args.append("".join(current).strip())
+ return args
+
+def _find_lua_calls(text, function_names):
+ calls = []
+ pattern = re.compile(r'\b(' + "|".join(re.escape(name) for name in function_names) + r')\s*\(')
+ pos = 0
+ while True:
+ match = pattern.search(text, pos)
+ if not match:
+ break
+ fn = match.group(1)
+ start = match.end()
+ idx = start
+ depth = 1
+ in_string = None
+ escape = False
+ while idx < len(text):
+ ch = text[idx]
+ if in_string:
+ if escape:
+ escape = False
+ elif ch == '\\':
+ escape = True
+ elif ch == in_string:
+ in_string = None
+ else:
+ if ch in ("'", '"'):
+ in_string = ch
+ elif ch == '(':
+ depth += 1
+ elif ch == ')':
+ depth -= 1
+ if depth == 0:
+ calls.append((fn, text[start:idx]))
+ pos = idx + 1
+ break
+ idx += 1
+ else:
+ break
+ return calls
+
+def _find_lua_block(text, start_idx, open_char="{", close_char="}"):
+ depth = 0
+ in_string = None
+ escape = False
+ for idx in range(start_idx, len(text)):
+ ch = text[idx]
+ if in_string:
+ if escape:
+ escape = False
+ elif ch == "\\":
+ escape = True
+ elif ch == in_string:
+ in_string = None
+ continue
+ if ch in ("'", '"'):
+ in_string = ch
+ continue
+ if ch == open_char:
+ depth += 1
+ elif ch == close_char:
+ depth -= 1
+ if depth == 0:
+ return text[start_idx + 1:idx], idx + 1
+ return None, None
+
+def _extract_lua_bind_calls(text):
+ binds = []
+ calls = _find_lua_calls(text, ["bind", "bindm", "hl.bind"])
+ for fn, args_text in calls:
+ args = _split_lua_args(args_text)
+ if len(args) < 2:
+ continue
+ mods = _parse_lua_string(args[0])
+ key = _parse_lua_string(args[1])
+ if mods is None or key is None:
+ continue
+ description = None
+ desc_match = re.search(r'description\s*=\s*(\"(?:\\.|[^\"])*\"|\'(?:\\.|[^\'])*\')', args_text, re.DOTALL)
+ if desc_match:
+ description = _parse_lua_string(desc_match.group(1))
+ elif fn == "bindm" and len(args) >= 4:
+ description = _parse_lua_string(args[3])
+ binds.append({
+ "mods": mods,
+ "key": key,
+ "description": description or "",
+ })
+ return binds
+
+def _extract_lua_bind_tables(text):
+ binds = []
+ pattern = re.compile(r'\bapp_binds\s*=\s*\{', re.MULTILINE)
+ for match in pattern.finditer(text):
+ block, end_idx = _find_lua_block(text, match.end() - 1)
+ if block is None:
+ continue
+ idx = 0
+ depth = 0
+ in_string = None
+ escape = False
+ entry_start = None
+ while idx < len(block):
+ ch = block[idx]
+ if in_string:
+ if escape:
+ escape = False
+ elif ch == "\\":
+ escape = True
+ elif ch == in_string:
+ in_string = None
+ idx += 1
+ continue
+ if ch in ("'", '"'):
+ in_string = ch
+ idx += 1
+ continue
+ if ch == "{":
+ depth += 1
+ if depth == 1:
+ entry_start = idx + 1
+ elif ch == "}":
+ if depth == 1 and entry_start is not None:
+ entry_text = block[entry_start:idx]
+ args = _split_lua_args(entry_text)
+ if len(args) >= 4:
+ mods = _parse_lua_string(args[0])
+ key = _parse_lua_string(args[1])
+ description = _parse_lua_string(args[3])
+ if mods is not None and key is not None:
+ binds.append({
+ "mods": mods,
+ "key": key,
+ "description": description or "",
+ })
+ entry_start = None
+ depth = max(depth - 1, 0)
+ idx += 1
+ return binds
+
+def _extract_lua_binds(text):
+ binds = []
+ binds.extend(_extract_lua_bind_calls(text))
+ binds.extend(_extract_lua_bind_tables(text))
+ return binds
+
+def _format_lua_binds(binds):
+ formatted_lines = []
+ for bind in binds:
+ mods = bind["mods"].replace("$mainMod", "SUPER")
+ mods = re.sub(r'[ \t]+', '+', mods.strip())
+ key = humanize_key_token(mods, bind["key"])
+ if mods and key:
+ combo_str = f"{mods}+{key}"
+ elif key:
+ combo_str = key
+ else:
+ combo_str = mods
+ desc = (bind.get("description") or "").strip()
+ if desc:
+ formatted_lines.append(f"{combo_str} — {desc}")
+ else:
+ formatted_lines.append(combo_str)
+ return formatted_lines
+
+def parse_lua_files(files):
+ order = []
+ bind_map = {}
+ for file_path in files:
+ if not os.path.exists(file_path):
+ continue
+ try:
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
+ binds = _extract_lua_binds(f.read())
+ except Exception as e:
+ sys.stderr.write(f"Error reading {file_path}: {e}\n")
+ continue
+ for bind in binds:
+ combo_key = normalize_combo(f"{bind['mods']},{bind['key']}")
+ if combo_key in bind_map:
+ try:
+ order.remove(combo_key)
+ except ValueError:
+ pass
+ bind_map[combo_key] = bind
+ order.append(combo_key)
+ effective_binds = [bind_map[key] for key in order if key in bind_map]
+ return _format_lua_binds(effective_binds)
def format_for_rofi(raw_binds):
formatted_lines = []
@@ -186,6 +444,7 @@ def format_for_rofi(raw_binds):
# Formatting mods
mods = mods.replace("$mainMod", "SUPER")
mods = re.sub(r'[ \t]+', '+', mods)
+ key = humanize_key_token(mods, key)
# Build combo string
if mods and key:
@@ -214,7 +473,16 @@ def main():
sys.exit(0)
config_files = sys.argv[1:]
-
+ has_lua = any(path.endswith(".lua") for path in config_files)
+ if has_lua:
+ formatted = parse_lua_files(config_files)
+ if not formatted:
+ print("no keybinds found.")
+ sys.exit(1)
+ for line in formatted:
+ print(line)
+ return
+
binds, suggestions = parse_files(config_files)
if not binds:
diff --git a/config/hypr/scripts/update_WindowRules.sh b/config/hypr/scripts/update_WindowRules.sh
index c8d236c4..293b14e9 100755
--- a/config/hypr/scripts/update_WindowRules.sh
+++ b/config/hypr/scripts/update_WindowRules.sh
@@ -9,12 +9,6 @@
CONFIGS_DIR="$HOME/.config/hypr/configs"
TARGET_FILE="$CONFIGS_DIR/WindowRules.conf"
-V3_FILE="$CONFIGS_DIR/WindowRules-config-v3.conf"
-
-if [[ ! -f "$V3_FILE" ]]; then
- echo "Error: Source configuration file not found: $V3_FILE"
- exit 1
-fi
get_hyprland_version() {
local ver="0.0.0"
@@ -46,13 +40,11 @@ REQUIRED_VER="0.53"
SMALLEST=$(printf '%s\n' "$REQUIRED_VER" "$VERSION" | sort -V | head -n1)
if [ "$SMALLEST" = "$REQUIRED_VER" ]; then
- echo "Version $VERSION >= $REQUIRED_VER. Updating WindowRules config..."
- # Backup existing config if it exists
if [ -f "$TARGET_FILE" ]; then
- echo "Backing up existing WindowRules.conf to WindowRules.conf.bak"
- mv "$TARGET_FILE" "$TARGET_FILE.bak"
+ echo "Version $VERSION >= $REQUIRED_VER. Using WindowRules.conf directly (no -config-v3 migration file)."
+ else
+ echo "Warning: WindowRules.conf not found at $TARGET_FILE"
fi
- cp "$V3_FILE" "$TARGET_FILE"
if command -v hyprctl &>/dev/null; then
if hyprctl instances &>/dev/null; then
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage