hyprland: cycle workspaces per monitor
This commit is contained in:
@@ -444,8 +444,10 @@ bind = $mainMod CTRL, 8, workspace, 8
|
|||||||
bind = $mainMod CTRL, 9, movetoworkspacesilent, 9
|
bind = $mainMod CTRL, 9, movetoworkspacesilent, 9
|
||||||
bind = $mainMod CTRL, 9, workspace, 9
|
bind = $mainMod CTRL, 9, workspace, 9
|
||||||
|
|
||||||
# Workspace cycling (like XMonad's cycleWorkspaceOnCurrentScreen)
|
# Workspace cycling with monitor-local history and commit-on-release semantics.
|
||||||
bind = $mainMod, backslash, exec, ~/.config/hypr/scripts/workspace-back.sh
|
bind = $mainMod, backslash, exec, ~/.config/hypr/scripts/workspace-back.sh cycle
|
||||||
|
bindr = , SUPER_L, exec, ~/.config/hypr/scripts/workspace-back.sh finalize
|
||||||
|
bindr = , SUPER_R, exec, ~/.config/hypr/scripts/workspace-back.sh finalize
|
||||||
|
|
||||||
# Swap current workspace with another (like XMonad's swapWithCurrent)
|
# Swap current workspace with another (like XMonad's swapWithCurrent)
|
||||||
bind = $hyper, 5, exec, ~/.config/hypr/scripts/swap-workspaces.sh
|
bind = $hyper, 5, exec, ~/.config/hypr/scripts/swap-workspaces.sh
|
||||||
|
|||||||
@@ -1,19 +1,71 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=workspace-history-common.sh
|
||||||
|
# shellcheck source-path=SCRIPTDIR
|
||||||
|
source "${script_dir}/workspace-history-common.sh"
|
||||||
|
|
||||||
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
action="${1:-cycle}"
|
||||||
state_dir="${runtime_dir}/hypr"
|
|
||||||
prev_file="${state_dir}/prev-workspace"
|
|
||||||
|
|
||||||
prev="$(cat "${prev_file}" 2>/dev/null || true)"
|
exec 9>"${lock_file}"
|
||||||
if [[ -z "${prev}" ]]; then
|
flock 9
|
||||||
|
|
||||||
|
state="$(wh_load_state)"
|
||||||
|
monitors_json="$(wh_monitors_json)"
|
||||||
|
state="$(wh_refresh_state_json "${state}" "${monitors_json}")"
|
||||||
|
|
||||||
|
focused_monitor="$(
|
||||||
|
jq -r '.[] | select(.focused == true) | .name // empty' <<<"${monitors_json}" | head -n 1
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "${focused_monitor}" ]]; then
|
||||||
|
wh_save_state "${state}"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ "${prev}" =~ ^[0-9]+$ ]] && (( prev < 1 || prev > max_ws )); then
|
current_workspace="$(jq -r --arg monitor "${focused_monitor}" '.monitorCurrent[$monitor] // empty' <<<"${state}")"
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
hyprctl dispatch workspace "${prev}" >/dev/null 2>&1 || true
|
case "${action}" in
|
||||||
|
cycle)
|
||||||
|
next_workspace="$(
|
||||||
|
jq -r \
|
||||||
|
--arg monitor "${focused_monitor}" \
|
||||||
|
--arg current "${current_workspace}" \
|
||||||
|
'
|
||||||
|
(.monitorHistory[$monitor] // []) as $history
|
||||||
|
| if ($history | length) < 2 then
|
||||||
|
""
|
||||||
|
else
|
||||||
|
($history | index($current)) as $idx
|
||||||
|
| if $idx == null then
|
||||||
|
""
|
||||||
|
else
|
||||||
|
$history[(($idx + 1) % ($history | length))]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
' <<<"${state}"
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "${next_workspace}" || "${next_workspace}" == "${current_workspace}" ]]; then
|
||||||
|
wh_save_state "${state}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
state="$(
|
||||||
|
jq -c \
|
||||||
|
--arg monitor "${focused_monitor}" \
|
||||||
|
'.cycle = {"active": true, "monitor": $monitor}' <<<"${state}"
|
||||||
|
)"
|
||||||
|
wh_save_state "${state}"
|
||||||
|
hyprctl dispatch workspace "${next_workspace}" >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
finalize)
|
||||||
|
state="$(wh_finalize_cycle_json "${state}")"
|
||||||
|
wh_save_state "${state}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf 'Unknown action: %s\n' "${action}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|||||||
127
dotfiles/config/hypr/scripts/workspace-history-common.sh
Normal file
127
dotfiles/config/hypr/scripts/workspace-history-common.sh
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
||||||
|
|
||||||
|
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||||
|
state_dir="${HYPR_WORKSPACE_STATE_DIR:-${runtime_dir}/hypr}"
|
||||||
|
state_file="${state_dir}/workspace-history.json"
|
||||||
|
# shellcheck disable=SC2034 # Sourced by sibling scripts that coordinate updates.
|
||||||
|
lock_file="${state_dir}/workspace-history.lock"
|
||||||
|
|
||||||
|
mkdir -p "${state_dir}"
|
||||||
|
|
||||||
|
wh_default_state() {
|
||||||
|
cat <<'EOF'
|
||||||
|
{"monitorCurrent":{},"monitorHistory":{},"cycle":{"active":false}}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
wh_load_state() {
|
||||||
|
local state
|
||||||
|
if [[ -s "${state_file}" ]] && state="$(jq -c '.' "${state_file}" 2>/dev/null)"; then
|
||||||
|
printf '%s\n' "${state}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
wh_default_state
|
||||||
|
}
|
||||||
|
|
||||||
|
wh_save_state() {
|
||||||
|
local state="$1"
|
||||||
|
local tmp_file
|
||||||
|
|
||||||
|
tmp_file="$(mktemp "${state_file}.XXXXXX")"
|
||||||
|
printf '%s\n' "${state}" >"${tmp_file}"
|
||||||
|
mv "${tmp_file}" "${state_file}"
|
||||||
|
}
|
||||||
|
|
||||||
|
wh_normalize_workspace() {
|
||||||
|
local ws_id="${1:-}"
|
||||||
|
local ws_name="${2:-}"
|
||||||
|
|
||||||
|
if [[ -n "${ws_name}" && "${ws_name}" != "null" && "${ws_name}" != special:* ]]; then
|
||||||
|
printf '%s\n' "${ws_name}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "${ws_id}" || "${ws_id}" == "null" || "${ws_id}" =~ ^- ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${ws_id}" =~ ^[0-9]+$ ]] && ((ws_id >= 1 && ws_id <= max_ws)); then
|
||||||
|
printf '%s\n' "${ws_id}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
wh_monitors_json() {
|
||||||
|
hyprctl -j monitors 2>/dev/null || printf '[]\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
wh_refresh_state_json() {
|
||||||
|
local state="$1"
|
||||||
|
local monitors_json="$2"
|
||||||
|
local row
|
||||||
|
local monitor
|
||||||
|
local ws_id
|
||||||
|
local ws_name
|
||||||
|
local workspace
|
||||||
|
|
||||||
|
while IFS= read -r row; do
|
||||||
|
[[ -z "${row}" ]] && continue
|
||||||
|
|
||||||
|
monitor="$(jq -r '.name // empty' <<<"${row}")"
|
||||||
|
ws_id="$(jq -r '.activeWorkspace.id // empty' <<<"${row}")"
|
||||||
|
ws_name="$(jq -r '.activeWorkspace.name // empty' <<<"${row}")"
|
||||||
|
workspace="$(wh_normalize_workspace "${ws_id}" "${ws_name}")"
|
||||||
|
|
||||||
|
[[ -z "${monitor}" || -z "${workspace}" ]] && continue
|
||||||
|
|
||||||
|
state="$(
|
||||||
|
jq -c \
|
||||||
|
--arg monitor "${monitor}" \
|
||||||
|
--arg workspace "${workspace}" \
|
||||||
|
'
|
||||||
|
.monitorCurrent[$monitor] = $workspace
|
||||||
|
| if .cycle.active == true and .cycle.monitor == $monitor then
|
||||||
|
.
|
||||||
|
else
|
||||||
|
.monitorHistory[$monitor] =
|
||||||
|
([$workspace] + ((.monitorHistory[$monitor] // []) | map(select(. != $workspace))))
|
||||||
|
end
|
||||||
|
' <<<"${state}"
|
||||||
|
)"
|
||||||
|
done < <(jq -c '.[]' <<<"${monitors_json}")
|
||||||
|
|
||||||
|
printf '%s\n' "${state}"
|
||||||
|
}
|
||||||
|
|
||||||
|
wh_finalize_cycle_json() {
|
||||||
|
local state="$1"
|
||||||
|
local monitor
|
||||||
|
local current
|
||||||
|
|
||||||
|
if [[ "$(jq -r '.cycle.active // false' <<<"${state}")" != "true" ]]; then
|
||||||
|
printf '%s\n' "${state}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
monitor="$(jq -r '.cycle.monitor // empty' <<<"${state}")"
|
||||||
|
current="$(jq -r --arg monitor "${monitor}" '.monitorCurrent[$monitor] // empty' <<<"${state}")"
|
||||||
|
|
||||||
|
if [[ -n "${monitor}" && -n "${current}" ]]; then
|
||||||
|
state="$(
|
||||||
|
jq -c \
|
||||||
|
--arg monitor "${monitor}" \
|
||||||
|
--arg current "${current}" \
|
||||||
|
'
|
||||||
|
.monitorHistory[$monitor] =
|
||||||
|
([$current] + ((.monitorHistory[$monitor] // []) | map(select(. != $current))))
|
||||||
|
| .cycle = {"active": false}
|
||||||
|
' <<<"${state}"
|
||||||
|
)"
|
||||||
|
else
|
||||||
|
state="$(jq -c '.cycle = {"active": false}' <<<"${state}")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf '%s\n' "${state}"
|
||||||
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
max_ws="${HYPR_MAX_WORKSPACE:-9}"
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=workspace-history-common.sh
|
||||||
|
# shellcheck source-path=SCRIPTDIR
|
||||||
|
source "${script_dir}/workspace-history-common.sh"
|
||||||
|
|
||||||
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
runtime_dir="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||||
sig="${HYPRLAND_INSTANCE_SIGNATURE:-}"
|
sig="${HYPRLAND_INSTANCE_SIGNATURE:-}"
|
||||||
@@ -10,19 +13,24 @@ if [[ -z "$sig" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
sock="${runtime_dir}/hypr/${sig}/.socket2.sock"
|
sock="${runtime_dir}/hypr/${sig}/.socket2.sock"
|
||||||
state_dir="${runtime_dir}/hypr"
|
|
||||||
last_file="${state_dir}/last-workspace"
|
|
||||||
prev_file="${state_dir}/prev-workspace"
|
|
||||||
|
|
||||||
mkdir -p "${state_dir}"
|
with_history_lock() {
|
||||||
|
exec 9>"${lock_file}"
|
||||||
|
flock 9
|
||||||
|
"$@"
|
||||||
|
}
|
||||||
|
|
||||||
# Initialize current workspace to avoid empty state.
|
refresh_history_state() {
|
||||||
if command -v hyprctl >/dev/null 2>&1; then
|
local state
|
||||||
cur_id="$(hyprctl activeworkspace -j | jq -r '.id' 2>/dev/null || true)"
|
local monitors_json
|
||||||
if [[ -n "${cur_id}" && "${cur_id}" != "null" ]]; then
|
|
||||||
echo "${cur_id}" > "${last_file}"
|
state="$(wh_load_state)"
|
||||||
fi
|
monitors_json="$(wh_monitors_json)"
|
||||||
fi
|
state="$(wh_refresh_state_json "${state}" "${monitors_json}")"
|
||||||
|
wh_save_state "${state}"
|
||||||
|
}
|
||||||
|
|
||||||
|
with_history_lock refresh_history_state
|
||||||
|
|
||||||
# Wait for the event socket to be ready.
|
# Wait for the event socket to be ready.
|
||||||
while [[ ! -S "${sock}" ]]; do
|
while [[ ! -S "${sock}" ]]; do
|
||||||
@@ -32,36 +40,7 @@ done
|
|||||||
nc -U "${sock}" | while read -r line; do
|
nc -U "${sock}" | while read -r line; do
|
||||||
case "${line}" in
|
case "${line}" in
|
||||||
workspace*">>"*)
|
workspace*">>"*)
|
||||||
payload="${line#*>>}"
|
with_history_lock refresh_history_state
|
||||||
# Handle workspacev2 payloads: id,name
|
|
||||||
if [[ "${payload}" == *","* ]]; then
|
|
||||||
ws_id="${payload%%,*}"
|
|
||||||
ws_name="${payload#*,}"
|
|
||||||
else
|
|
||||||
ws_id="${payload}"
|
|
||||||
ws_name="${payload}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ignore special/negative workspaces.
|
|
||||||
if [[ "${ws_id}" =~ ^- ]] || [[ "${ws_name}" == special:* ]]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Ignore workspaces outside the configured cap.
|
|
||||||
if [[ "${ws_id}" =~ ^[0-9]+$ ]] && (( ws_id < 1 || ws_id > max_ws )); then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
ws_ident="${ws_name}"
|
|
||||||
if [[ -z "${ws_ident}" ]]; then
|
|
||||||
ws_ident="${ws_id}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
prev="$(cat "${last_file}" 2>/dev/null || true)"
|
|
||||||
if [[ -n "${prev}" && "${ws_ident}" != "${prev}" ]]; then
|
|
||||||
echo "${prev}" > "${prev_file}"
|
|
||||||
fi
|
|
||||||
echo "${ws_ident}" > "${last_file}"
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user