v0.1.10 — Settings TUI layout polish
Banner, state panel, menu header, and menu items now share a single centred panel column rather than each block centring itself independently. The TUI feels visibly aligned in a Walker floating window of any width — no more drifting elements off to the left while the menu floats to the right. - Adaptive panel width: min(COLS − 6, 60), floored at 40. - Terminal-width detection: `stty size </dev/tty` first (kernel-reported, always reflects the live window) with `tput cols`/80 fallback. Fixes off-centre rendering in freshly-spawned floating terminals whose terminfo hasn't caught up to the compositor's actual size yet. - Config-file path renders as `~/...` instead of `/home/<user>/...` so it fits the panel. - Unset resolution shows `<auto>` instead of `?x?` (matches the other unset placeholders). Internally: `pad_block` replaces `center_block`'s widest-line-centres-block behaviour with a single shared left margin; `cmenu` left-aligns the menu at the panel edge so the `> ` cursor sits in the same column as the state panel's section headers above. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3131dcab8f
commit
aaa2f3d768
3 changed files with 120 additions and 19 deletions
|
|
@ -10,6 +10,14 @@ Lineage: forked from [Super-Shift-S-Omarchy-Deck-Mode](https://git.no-signal.uk/
|
||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
|
|
||||||
|
### v0.1.10 — Settings TUI layout polish
|
||||||
|
|
||||||
|
- Banner, state panel, menu header, and menu items now share a single centred panel column rather than each block centring itself independently. The TUI feels visibly aligned in a Walker floating window of any width — no more drifting elements off to the left while the menu floats to the right.
|
||||||
|
- Panel width is adaptive (`min(terminal − 6, 60)`, floored at 40) so the layout looks right from narrow ttys up to fullscreen.
|
||||||
|
- Terminal-width detection now reads `stty size </dev/tty` first (kernel-reported, always reflects the live window) and only falls back to `tput cols` / `80`. Fixes off-centre rendering in freshly-spawned floating terminals whose terminfo hasn't caught up yet.
|
||||||
|
- Config-file path now renders with `~` instead of `/home/<user>/…` so it fits the panel.
|
||||||
|
- Unset resolution shows `<auto>` (matching the other unset placeholders) instead of `?x?`.
|
||||||
|
|
||||||
### v0.1.9 — Auto-migrate legacy refresh-rate values
|
### v0.1.9 — Auto-migrate legacy refresh-rate values
|
||||||
|
|
||||||
- Installer now detects pre-v0.1.8 scalar `CUSTOM_REFRESH_RATES` values (e.g. `165`) and rewrites them to the v0.1.8 comma format (`60,165`), then imports the new value into the running systemd user environment. Re-running `./deckshift.sh` is enough to fix Gaming Mode for users hit by the 60 Hz bug — no need to re-open the Settings TUI and re-pick the rate.
|
- Installer now detects pre-v0.1.8 scalar `CUSTOM_REFRESH_RATES` values (e.g. `165`) and rewrites them to the v0.1.8 comma format (`60,165`), then imports the new value into the running systemd user environment. Re-running `./deckshift.sh` is enough to fix Gaming Mode for users hit by the 60 Hz bug — no need to re-open the Settings TUI and re-pick the rate.
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,80 @@ command -v lspci >/dev/null || die "lspci is required"
|
||||||
command -v jq >/dev/null || die "jq is required (install with: omarchy-pkg-add jq)"
|
command -v jq >/dev/null || die "jq is required (install with: omarchy-pkg-add jq)"
|
||||||
command -v hyprctl >/dev/null || die "hyprctl is required (this TUI is for Hyprland sessions)"
|
command -v hyprctl >/dev/null || die "hyprctl is required (this TUI is for Hyprland sessions)"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Layout — everything renders inside a single centred "panel" column so the
|
||||||
|
# banner, state, menu, and toasts all share a left edge regardless of terminal
|
||||||
|
# width. PANEL_WIDTH is adaptive: target 60 cols, capped at the terminal width
|
||||||
|
# minus margin, floored so it stays usable on narrow windows.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Live terminal width — refreshed each main loop iteration so a window resize
|
||||||
|
# is picked up between menus. Reads via `stty size` first (kernel-reported,
|
||||||
|
# always reflects the live window) because `tput cols` in a freshly-spawned
|
||||||
|
# floating terminal sometimes returns the terminfo default (80) before the
|
||||||
|
# compositor has applied its size.
|
||||||
|
#
|
||||||
|
# Every `$(...)` here ends with `|| true` because under `set -eo pipefail` a
|
||||||
|
# failing pipeline inside a command substitution propagates out and trips -e
|
||||||
|
# on the enclosing assignment.
|
||||||
|
COLS=80
|
||||||
|
PANEL_WIDTH=60
|
||||||
|
LEFT_MARGIN=0
|
||||||
|
LEFT_PAD=""
|
||||||
|
|
||||||
|
refresh_cols() {
|
||||||
|
local raw
|
||||||
|
raw=$(stty size 2>/dev/null </dev/tty || true)
|
||||||
|
if [[ "$raw" =~ ^[0-9]+[[:space:]]+([0-9]+) ]] && (( BASH_REMATCH[1] > 0 )); then
|
||||||
|
COLS=${BASH_REMATCH[1]}
|
||||||
|
else
|
||||||
|
raw=$(tput cols 2>/dev/null || true)
|
||||||
|
if [[ "$raw" =~ ^[0-9]+$ ]] && (( raw > 0 )); then
|
||||||
|
COLS=$raw
|
||||||
|
else
|
||||||
|
COLS=80
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
PANEL_WIDTH=60
|
||||||
|
(( PANEL_WIDTH > COLS - 6 )) && PANEL_WIDTH=$(( COLS - 6 ))
|
||||||
|
(( PANEL_WIDTH < 40 )) && PANEL_WIDTH=40
|
||||||
|
LEFT_MARGIN=$(( (COLS - PANEL_WIDTH) / 2 ))
|
||||||
|
(( LEFT_MARGIN < 0 )) && LEFT_MARGIN=0
|
||||||
|
LEFT_PAD=""
|
||||||
|
(( LEFT_MARGIN > 0 )) && printf -v LEFT_PAD '%*s' "$LEFT_MARGIN" ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepend N spaces to every line of stdin. Used to position blocks at the
|
||||||
|
# panel's left edge so banner, state, menu, and toasts share one column.
|
||||||
|
pad_block() {
|
||||||
|
local n="${1:-$LEFT_MARGIN}"
|
||||||
|
local pad=""
|
||||||
|
(( n > 0 )) && printf -v pad '%*s' "$n" ''
|
||||||
|
local line
|
||||||
|
while IFS= read -r line; do
|
||||||
|
printf '%s%s\n' "$pad" "$line"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Centred wrapper around gum choose: positions the menu horizontally so it
|
||||||
|
# sits inside the panel column. gum draws at column 0 with no alignment flag,
|
||||||
|
# but it reserves the cursor string's width as the gutter for unselected rows,
|
||||||
|
# so baking padding into the cursor prefix shifts the whole rendered block.
|
||||||
|
# Header is padded separately so it sits above the items at the same offset.
|
||||||
|
#
|
||||||
|
# Usage: cmenu "Header text" "Item 1" "Item 2" ...
|
||||||
|
# Cancellation (Esc/Ctrl-C) returns empty just like gchoose.
|
||||||
|
cmenu() {
|
||||||
|
refresh_cols
|
||||||
|
local header="$1"; shift
|
||||||
|
# Left-align the menu at the panel's left edge so the "> " cursor and item
|
||||||
|
# labels sit in the same column as the state panel's section headers and
|
||||||
|
# rows above. Centering items within the panel would put them in the middle
|
||||||
|
# of the panel — visually disconnected from the state panel column.
|
||||||
|
local pad="$LEFT_PAD"
|
||||||
|
gchoose --cursor "${pad}> " --header "${pad}${header}" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Conf helpers — read straight from disk; writes go through pending_* below so
|
# Conf helpers — read straight from disk; writes go through pending_* below so
|
||||||
# nothing hits the file until the user explicitly saves.
|
# nothing hits the file until the user explicitly saves.
|
||||||
|
|
@ -272,15 +346,25 @@ show_state() {
|
||||||
monitor_label="${monitor_label} (max ${HYPR_NATIVE} @ ${HYPR_MAX_REFRESH:-?}Hz)"
|
monitor_label="${monitor_label} (max ${HYPR_NATIVE} @ ${HYPR_MAX_REFRESH:-?}Hz)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Build resolution label so an unset value renders as "<auto>" (matching the
|
||||||
|
# other unset placeholders) rather than "?x?".
|
||||||
|
local resolution_label="<auto>"
|
||||||
|
if [[ -n "$width" && -n "$height" ]]; then
|
||||||
|
resolution_label="${width}x${height}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace $HOME with ~ so the config path fits the panel column.
|
||||||
|
local conf_display="${CONF/#$HOME/~}"
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
Gaming Mode display settings${pending_label}:
|
Gaming Mode display settings${pending_label}
|
||||||
|
|
||||||
Monitor : ${monitor_label}
|
Monitor : ${monitor_label}
|
||||||
Resolution : ${width:-?}x${height:-?}
|
Resolution : ${resolution_label}
|
||||||
Refresh rate : ${refresh:-<auto>} Hz
|
Refresh rate : ${refresh:-<auto>} Hz
|
||||||
GPU mode : ${gpu_mode}
|
GPU mode : ${gpu_mode}
|
||||||
|
|
||||||
Config file : ${CONF}
|
Config file : ${conf_display}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +387,7 @@ choose_monitor() {
|
||||||
pending_set OUTPUT_CONNECTOR "$connector"
|
pending_set OUTPUT_CONNECTOR "$connector"
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
choice=$(printf '%s\n' "${labels[@]}" | gchoose --header "Select monitor for Gaming Mode")
|
choice=$(cmenu "Select monitor for Gaming Mode" "${labels[@]}")
|
||||||
[[ -z "$choice" ]] && return 0
|
[[ -z "$choice" ]] && return 0
|
||||||
if [[ "$choice" == "(clear"* ]]; then
|
if [[ "$choice" == "(clear"* ]]; then
|
||||||
pending_unset OUTPUT_CONNECTOR
|
pending_unset OUTPUT_CONNECTOR
|
||||||
|
|
@ -341,7 +425,7 @@ choose_resolution() {
|
||||||
|
|
||||||
options+=("Custom…")
|
options+=("Custom…")
|
||||||
|
|
||||||
choice=$(printf '%s\n' "${options[@]}" | gchoose --header "Select launch resolution (max: ${HYPR_NATIVE:-unknown})")
|
choice=$(cmenu "Select launch resolution (max: ${HYPR_NATIVE:-unknown})" "${options[@]}")
|
||||||
[[ -z "$choice" ]] && return 0
|
[[ -z "$choice" ]] && return 0
|
||||||
if [[ "$choice" == "Custom…" ]]; then
|
if [[ "$choice" == "Custom…" ]]; then
|
||||||
w=$(ginput --prompt "Width: " --placeholder "2560")
|
w=$(ginput --prompt "Width: " --placeholder "2560")
|
||||||
|
|
@ -396,7 +480,7 @@ choose_refresh_rate() {
|
||||||
|
|
||||||
options+=("Custom…")
|
options+=("Custom…")
|
||||||
|
|
||||||
choice=$(printf '%s\n' "${options[@]}" | gchoose --header "Select refresh rate (Hz, max: ${HYPR_MAX_REFRESH:-unknown})")
|
choice=$(cmenu "Select refresh rate (Hz, max: ${HYPR_MAX_REFRESH:-unknown})" "${options[@]}")
|
||||||
[[ -z "$choice" ]] && return 0
|
[[ -z "$choice" ]] && return 0
|
||||||
if [[ "$choice" == "Custom…" ]]; then
|
if [[ "$choice" == "Custom…" ]]; then
|
||||||
rate=$(ginput --prompt "Rate (Hz): " --placeholder "144")
|
rate=$(ginput --prompt "Rate (Hz): " --placeholder "144")
|
||||||
|
|
@ -473,7 +557,7 @@ choose_gpu() {
|
||||||
done
|
done
|
||||||
labels+=("(clear GPU override — let system decide)")
|
labels+=("(clear GPU override — let system decide)")
|
||||||
|
|
||||||
choice=$(printf '%s\n' "${labels[@]}" | gchoose --header "Select GPU for Gaming Mode")
|
choice=$(cmenu "Select GPU for Gaming Mode" "${labels[@]}")
|
||||||
[[ -z "$choice" ]] && return 0
|
[[ -z "$choice" ]] && return 0
|
||||||
|
|
||||||
# Wipe every GPU-mode key first; each branch sets only what it needs.
|
# Wipe every GPU-mode key first; each branch sets only what it needs.
|
||||||
|
|
@ -500,7 +584,7 @@ choose_gpu() {
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
local t_choice t_entry=""
|
local t_choice t_entry=""
|
||||||
t_choice=$(printf '%s\n' "${t_labels[@]}" | gchoose --header "Pick dGPU (render target) — usually the discrete one")
|
t_choice=$(cmenu "Pick dGPU (render target) — usually the discrete one" "${t_labels[@]}")
|
||||||
[[ -z "$t_choice" ]] && return 0
|
[[ -z "$t_choice" ]] && return 0
|
||||||
local i
|
local i
|
||||||
for ((i=0; i<${#t_labels[@]}; i++)); do
|
for ((i=0; i<${#t_labels[@]}; i++)); do
|
||||||
|
|
@ -571,12 +655,14 @@ confirm_risky_save() {
|
||||||
|
|
||||||
(( ${#warnings[@]} == 0 )) && return 0
|
(( ${#warnings[@]} == 0 )) && return 0
|
||||||
|
|
||||||
|
refresh_cols
|
||||||
clear
|
clear
|
||||||
gum style --foreground 196 --bold "Warning — selected values may not work:"
|
|
||||||
echo ""
|
echo ""
|
||||||
printf ' %s\n' "${warnings[@]}"
|
gum style --foreground 196 --bold "Warning — selected values may not work:" | pad_block
|
||||||
echo ""
|
echo ""
|
||||||
gum style --foreground 244 "If Gaming Mode shows a black screen, press Super+Shift+R to return to desktop."
|
printf ' %s\n' "${warnings[@]}" | pad_block
|
||||||
|
echo ""
|
||||||
|
gum style --foreground 244 "If Gaming Mode shows a black screen, press Super+Shift+R to return to desktop." | pad_block
|
||||||
echo ""
|
echo ""
|
||||||
gum confirm "Save anyway?" --default=false
|
gum confirm "Save anyway?" --default=false
|
||||||
}
|
}
|
||||||
|
|
@ -587,12 +673,17 @@ confirm_risky_save() {
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
while true; do
|
while true; do
|
||||||
|
refresh_cols
|
||||||
refresh_monitor_data
|
refresh_monitor_data
|
||||||
clear
|
clear
|
||||||
|
echo ""
|
||||||
gum style \
|
gum style \
|
||||||
--border double --margin "1" --padding "1 4" --border-foreground 212 \
|
--border double --padding "1 0" --border-foreground 212 \
|
||||||
"DECKSHIFT — Gaming Mode Settings"
|
--width "$PANEL_WIDTH" --align center \
|
||||||
show_state
|
"DECKSHIFT — Gaming Mode Settings" \
|
||||||
|
| pad_block
|
||||||
|
echo ""
|
||||||
|
show_state | pad_block
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local save_label="Save and exit"
|
local save_label="Save and exit"
|
||||||
|
|
@ -603,7 +694,7 @@ main() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local action
|
local action
|
||||||
action=$(gchoose --header "What do you want to change?" \
|
action=$(cmenu "What do you want to change?" \
|
||||||
"Monitor" \
|
"Monitor" \
|
||||||
"Resolution" \
|
"Resolution" \
|
||||||
"Refresh rate" \
|
"Refresh rate" \
|
||||||
|
|
@ -621,8 +712,9 @@ main() {
|
||||||
fi
|
fi
|
||||||
flush_pending
|
flush_pending
|
||||||
clear
|
clear
|
||||||
gum style --foreground 212 "Settings saved to $CONF"
|
echo ""
|
||||||
gum style --foreground 244 "Changes apply next time you enter Gaming Mode (Super+Shift+S)."
|
gum style --foreground 212 "Settings saved to ${CONF/#$HOME/~}" | pad_block
|
||||||
|
gum style --foreground 244 "Changes apply next time you enter Gaming Mode (Super+Shift+S)." | pad_block
|
||||||
sleep 1
|
sleep 1
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
|
@ -631,7 +723,8 @@ main() {
|
||||||
gum confirm "Discard unsaved changes?" --default=true || continue
|
gum confirm "Discard unsaved changes?" --default=true || continue
|
||||||
fi
|
fi
|
||||||
clear
|
clear
|
||||||
gum style --foreground 244 "No changes saved."
|
echo ""
|
||||||
|
gum style --foreground 244 "No changes saved." | pad_block
|
||||||
sleep 1
|
sleep 1
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ set -Euo pipefail
|
||||||
# -u: Treat unset variables as errors (catches typos in variable names)
|
# -u: Treat unset variables as errors (catches typos in variable names)
|
||||||
# -o pipefail: A pipeline fails if ANY command in it fails, not just the last one
|
# -o pipefail: A pipeline fails if ANY command in it fails, not just the last one
|
||||||
|
|
||||||
DECKSHIFT_VERSION="0.1.9"
|
DECKSHIFT_VERSION="0.1.10"
|
||||||
|
|
||||||
# Resolve the directory this script lives in so we can find sibling files like
|
# Resolve the directory this script lives in so we can find sibling files like
|
||||||
# bin/deckshift-settings and applications/deckshift-settings.desktop when
|
# bin/deckshift-settings and applications/deckshift-settings.desktop when
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue