first pass of hyprland config

This commit is contained in:
2026-10-23 16:17:23 -06:00
parent 2f656c4a9e
commit 2a737ed6df
39 changed files with 1334 additions and 52 deletions
@@ -0,0 +1,40 @@
#####################################################################
DEPENDENCIES+=(hyprctl)
#####################################################################
${scwryptsmodule}() {
eval "$(utils.parse.autosetup)"
##########################################
local result="$(hyprctl eval "$(printf '%s\n' "${ARGS[@]}")")"
[[ "${result}" == ok ]] \
|| echo.error "hyprctl eval failed : ${result}" \
|| return 1
}
#####################################################################
${scwryptsmodule}.parse() { return 0; }
${scwryptsmodule}.parse.locals() {
local ARGS=()
}
${scwryptsmodule}.parse.usage() {
USAGE__description='
run hyprland lua in the current hyprland instance (one statement per
argument, evaluated together); fails unless hyprland replies "ok"
'
USAGE__args='
\$@ lua statements (e.g. "hl.dispatch(hl.dsp.window.center({}))")
'
}
${scwryptsmodule}.parse.validate() {
[[ "${#ARGS[@]}" -gt 0 ]] \
|| echo.error 'at least one lua statement is required'
}
@@ -0,0 +1,41 @@
#####################################################################
DEPENDENCIES+=(hyprctl jq)
#####################################################################
${scwryptsmodule}() {
eval "$(utils.parse.autosetup)"
##########################################
hyprctl -j clients \
| jq -r --arg class "${CLIENT_CLASS}" '.[] | select(.class | test($class; "i")) | .address' \
;
}
#####################################################################
${scwryptsmodule}.parse() { return 0; }
${scwryptsmodule}.parse.locals() {
local ARGS=()
local CLIENT_CLASS
}
${scwryptsmodule}.parse.usage() {
USAGE__description='
print the address of every hyprland window whose class matches
(one per line; case-insensitive regex)
'
USAGE__args='
\$1 window class regex
'
}
${scwryptsmodule}.parse.validate() {
CLIENT_CLASS="${ARGS[1]}"
[[ "${CLIENT_CLASS}" ]] \
|| echo.error 'window class is required'
}
@@ -0,0 +1,10 @@
#
# interact with the running hyprland instance
#
# run hyprland lua (hyprctl eval) with error checking
use desktop/wayland/hyprland/eval --group dotwryn
# list window addresses matching a class regex
use desktop/wayland/hyprland/get-window-addresses --group dotwryn
+163
View File
@@ -0,0 +1,163 @@
#!/usr/bin/env zsh
use notify
use desktop/wayland/hyprland --group dotwryn
DEPENDENCIES+=(jq)
#####################################################################
USAGE__usage+=' <path-executable> [...options...]'
USAGE__options="
-c, --client <string> if different from the executable name, the window class (regex)
-s, --scale <value> (default: 0.8 or 0.5 if screen width >3000px)
-x, --x-offset <value> (default: 0.0)
-y, --y-offset <value> (default: 0.0)
-a, --always-launch invoke executable even if a window of client-class exists
-n, --no-resize don't resize the window (ignores -sxy flags)
-l, --no-center leave the window wherever it was last positioned
--has-statusbar-icon (accepted for i3 compatibility; no effect)
"
USAGE__description="
Hyprland port of 'desktop/xorg/i3/launch-or-show'.
Makes it easy to bind appications to key shortcuts without having to
spin up redundant instances. Each application gets its own special
workspace (hyprland's scratchpad), named after the executable:
1) starts the application if it is not running
2) moves all instances of the application to its special workspace
3) (toggle) hides the special workspace if it is showing
4) (toggle) shows the special workspace (resized and centered) if it is hidden
"
#####################################################################
MAIN() {
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
local application client_class
local x_offset=0.0 y_offset=0.0 scale
local always_launch=false resize=true center=true
local _s _p=0
while [[ "${#}" -gt 0 ]]
do
_s=1
case "${1}" in
( -c | --client ) _s=2; client_class="${2}" ;;
( -x | --x-offset ) _s=2; x_offset="${2}" ;;
( -y | --y-offset ) _s=2; y_offset="${2}" ;;
( -s | --scale ) _s=2; scale="${2}" ;;
( -a | --always-launch ) always_launch=true ;;
( -n | --no-resize ) resize=false ;;
( -l | --no-center ) center=false ;;
( --has-statusbar-icon ) ;;
( -h | --help ) USAGE; return 0 ;;
( * )
case "$((_p+=1))" in
( 1 ) application="${1}" ;;
( * ) echo.error "extra positional argument '${1}'" ;;
esac
;;
esac
shift "${_s}" || echo.error "missing value for argument '${1}'" || shift "${#}"
done
unset _s _p
[[ "${application}" ]] || echo.error 'path-executable required'
[[ "${client_class}" ]] || client_class="${application}"
[[ "${application}" ]] && {
utils.dependencies.check "${application}" \
|| notify.error "${application} not found"
}
utils.check-errors --fail
##########################################
local special_workspace="special:${application:t}"
local monitor="$(hyprctl -j monitors | jq -c '.[] | select(.focused)')"
[[ "${scale}" ]] || {
[[ "$(jq -r '.width' <<< "${monitor}")" -gt 3000 ]] \
&& scale=0.5 \
|| scale=0.8 \
;
}
##########################################
local addresses=($(dotwryn.desktop.wayland.hyprland.get-window-addresses "${client_class}"))
[[ "${always_launch}" == true ]] || [[ "${#addresses[@]}" -eq 0 ]] && {
echo.status 'launching application'
hyprctl dispatch "hl.dsp.exec_cmd(\"${application}\")" >/dev/null
echo.status 'waiting for application window'
local wait_count
for wait_count in {1..50}
do
[[ "$(dotwryn.desktop.wayland.hyprland.get-window-addresses "${client_class}" | wc -l)" -gt "${#addresses[@]}" ]] && break
sleep 0.1
done
addresses=($(dotwryn.desktop.wayland.hyprland.get-window-addresses "${client_class}"))
}
[[ "${#addresses[@]}" -gt 0 ]] \
|| notify.error "no '${client_class}' window found for ${application}" \
|| return 1
##########################################
echo.status 'moving windows to special workspace'
local address lua=()
for address in "${addresses[@]}"
do
lua+=(
"hl.dispatch(hl.dsp.window.float({ window = \"address:${address}\", action = \"enable\" }))"
"hl.dispatch(hl.dsp.window.move({ window = \"address:${address}\", workspace = \"${special_workspace}\", follow = false }))"
)
done
local shown_special="$(jq -r '.specialWorkspace.name' <<< "${monitor}")"
[[ "${shown_special}" == "${special_workspace}" ]] && {
echo.status 'hiding special workspace'
lua+=("hl.dispatch(hl.dsp.workspace.toggle_special(\"${application:t}\"))")
dotwryn.desktop.wayland.hyprland.eval "${lua[@]}"
return $?
}
echo.status 'showing special workspace'
lua+=("hl.dispatch(hl.dsp.workspace.toggle_special(\"${application:t}\"))")
[[ "${resize}" == true ]] && {
local window_size=($(jq -r \
--argjson f "${scale}" --argjson x "${x_offset}" --argjson y "${y_offset}" \
'"\((.width / .scale * $f + $x) | floor) \((.height / .scale * $f + $y) | floor)"' \
<<< "${monitor}"
))
echo.debug "window size: ${window_size[*]}"
for address in "${addresses[@]}"
do
lua+=("hl.dispatch(hl.dsp.window.resize({ window = \"address:${address}\", x = ${window_size[1]}, y = ${window_size[2]} }))")
done
}
[[ "${center}" == true ]] && {
for address in "${addresses[@]}"
do
lua+=("hl.dispatch(hl.dsp.window.center({ window = \"address:${address}\" }))")
done
}
dotwryn.desktop.wayland.hyprland.eval "${lua[@]}"
}
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env zsh
use notify
DEPENDENCIES+=(hyprlock)
#####################################################################
USAGE__description='
Hyprland port of "desktop/xorg/i3/lock"; locks the session with hyprlock
(themed by ~/.config/hypr/hyprlock.conf and the active colorscheme)
'
#####################################################################
MAIN() {
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
pgrep -x hyprlock &>/dev/null && return 0
hyprlock \
|| notify.error 'something went wrong; unable to lock' \
;
}
+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env zsh
use notify
DEPENDENCIES+=(qs)
#####################################################################
USAGE__description='
Hyprland port of "desktop/xorg/i3/logout"; asks for confirmation
(quickshell LogoutPrompt) before exiting hyprland
'
#####################################################################
MAIN() {
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
qs ipc call logout show \
|| notify.error 'unable to show logout prompt (is quickshell running?)' \
;
}
+26
View File
@@ -0,0 +1,26 @@
#!/usr/bin/env zsh
use notify
DEPENDENCIES+=(flameshot hyprctl jq)
#####################################################################
USAGE__description='
Hyprland equivalent of the i3 flameshot screenshot; freezes the
focused monitor in flameshot to select / annotate / copy / save
'
#####################################################################
MAIN() {
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
# flameshot screens are numbered from 0 in output order (hyprland ids are not)
local screen_number
screen_number="$(hyprctl monitors -j | jq -r 'sort_by(.id) | to_entries[] | select(.value.focused) | .key')"
[ "${screen_number}" ] \
|| { notify.error 'unable to determine focused monitor'; return 1; }
flameshot screen --edit --number "${screen_number}" \
|| notify.error 'unable to take screenshot' \
;
}
+72
View File
@@ -0,0 +1,72 @@
#!/usr/bin/env zsh
use notify
use scwrypts/get-realpath
DEPENDENCIES+=(hyprctl)
REQUIRED_ENV+=(DESKTOP__WALLPAPER_PATH)
#####################################################################
USAGE__usage+=' [wallpaper]'
USAGE__args+='
$1 one of:
random : any wallpaper (default)
theme : the wallpaper named after the active colorscheme (e.g. main.jpg)
(optional \$2 : use this theme name instead)
<file> : a wallpaper file within DESKTOP__WALLPAPER_PATH
'
USAGE__description='
Hyprland port of "desktop/xorg/i3/set-background"; sets the wallpaper
on every monitor through hyprpaper
'
#####################################################################
MAIN() {
local SCWRYPTS_NOTIFICATION_ENGINES=(echo notify.desktop)
# env values are literal (e.g. '$HOME/Pictures/bg'); resolve before use
local wallpaper_path="$(scwrypts.get-realpath "${DESKTOP__WALLPAPER_PATH}")"
[[ -d "${wallpaper_path}" ]] \
|| echo.error "no such directory for DESKTOP__WALLPAPER_PATH='${DESKTOP__WALLPAPER_PATH}'" \
|| return 1
local selection="${1:-random}"
local wallpaper
case "${selection}" in
( random )
wallpaper="$(find "${wallpaper_path}" -type f | shuf -n1)"
;;
( theme )
# optional $2 : theme name (default: the active theme)
local theme_name="${2:-$(cat -- "${DOTWRYN}/config/colorschemes/active/name.txt" 2>/dev/null)}"
wallpaper="$(find "${wallpaper_path}" -type f -name "${theme_name}.*" | head -n1)"
[[ "${wallpaper}" ]] \
|| echo.error "no wallpaper for theme '${theme_name}' in wallpaper path" \
|| return 2
;;
( * )
wallpaper="${wallpaper_path}/${selection}"
[[ -f "${wallpaper}" ]] \
|| echo.error "no such wallpaper '${selection}' in wallpaper path" \
|| return 2
;;
esac
[[ "${wallpaper}" ]] \
|| echo.error "no wallpapers found in '${wallpaper_path}'" \
|| return 3
pgrep -x hyprpaper &>/dev/null || {
hyprctl dispatch 'hl.dsp.exec_cmd("hyprpaper")' >/dev/null
sleep 1
}
# empty monitor : fallback for any monitor without a specific wallpaper
hyprctl hyprpaper wallpaper ", ${wallpaper}, cover" >/dev/null \
|| notify.error "unable to set wallpaper '${wallpaper}'" \
;
}
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env zsh
DEPENDENCIES+=(gsettings)
#####################################################################
USAGE__description='
On X11, GTK reads its theme from ~/.config/gtk-3.0/settings.ini (written by
lxappearance). Wayland GTK apps read gsettings instead, so copy the values
across; settings.ini stays the single source of truth for both sessions.
'
#####################################################################
MAIN() {
local settings_file="${XDG_CONFIG_HOME:-${HOME}/.config}/gtk-3.0/settings.ini"
[[ -f "${settings_file}" ]] \
|| echo.error "no gtk settings found at '${settings_file}'" \
|| return 1
local ini_key gsettings_key value
for ini_key gsettings_key in \
gtk-theme-name gtk-theme \
gtk-icon-theme-name icon-theme \
gtk-font-name font-name \
gtk-cursor-theme-name cursor-theme \
;
do
value="$(sed -n "s/^${ini_key}=//p" "${settings_file}" | tail -n1)"
[[ "${value}" ]] || continue
gsettings set org.gnome.desktop.interface "${gsettings_key}" "${value}" \
&& echo.status "${gsettings_key} : ${value}" \
|| echo.error "unable to set ${gsettings_key}" \
;
done
# libadwaita / portal-aware apps (electron, gtk4) follow color-scheme rather than the theme name
local theme="$(gsettings get org.gnome.desktop.interface gtk-theme)"
case "${theme:l}" in
( *dark* ) gsettings set org.gnome.desktop.interface color-scheme prefer-dark ;;
( * ) gsettings set org.gnome.desktop.interface color-scheme default ;;
esac
[[ "${ERRORS}" -eq 0 ]]
}
+7 -14
View File
@@ -28,21 +28,14 @@ ${scwryptsmodule}.parse() {
[[ ${POSITIONAL_ARGS} -eq 0 ]] || return 0
((POSITIONAL_ARGS+=1))
case $1 in
( backlight ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-audio-volume-change.oga" ;;
( gamedock ) SFX_FILE="${DESKTOP__SFX_PATH}/gamedock.oga" ;;
( homedock ) SFX_FILE="${DESKTOP__SFX_PATH}/homedock.oga" ;;
( login ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-desktop-login.oga" ;;
( logout ) SFX_FILE="${DESKTOP__SFX_PATH}/smooth-desktop-login.oga" ;;
( mute ) SFX_FILE="${DESKTOP__SFX_PATH}/smooth-dialog-warning.oga" ;;
( notify ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-complete.oga" ;;
( undock ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-desktop-login.oga" ;;
( volume ) SFX_FILE="${DESKTOP__SFX_PATH}/yaru-message.oga" ;;
( * )
SFX_FILE="$1"
;;
esac
# mapped names live in $DOTWRYN/config/sfx.map (shared with the hyprland quickshell)
local mapped_file="$(awk -v name="${1}" '!/^#/ && $1 == name { print $2; exit }' "${DOTWRYN}/config/sfx.map" 2>/dev/null)"
[[ "${mapped_file}" ]] \
&& SFX_FILE="${DESKTOP__SFX_PATH}/${mapped_file}" \
|| SFX_FILE="${1}" \
;
return 1
}
+1
View File
@@ -3,6 +3,7 @@ set -g default-terminal "tmux-256color"
set -g mouse off
set -sg escape-time 0
set-option -g default-shell /bin/zsh
set-option -ga update-environment HYPRLAND_INSTANCE_SIGNATURE
set-option -gw xterm-keys on
set-window-option -g mode-keys vi
set-window-option -g status-keys vi