Compare commits

..

No commits in common. "d680730d305c0b647e072addb5e2ecc149dc352a" and "964ce103a051b44feb436211bf2d9513595b1e0f" have entirely different histories.

3 changed files with 15 additions and 235 deletions

View File

@ -279,13 +279,9 @@ class Taskbar(Gtk.Box):
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
row.append(Gtk.Label(label="center-focused", xalign=0.0, hexpand=True))
sw = Gtk.Switch(valign=Gtk.Align.CENTER)
if ly["name"] == "columns":
sw.set_tooltip_text("Keep the focused window centred in its column as "
"it scrolls, instead of the minimal-movement default")
else:
sw.set_tooltip_text("Keep the focused column centred so the prev/next "
"columns stay on-screen and tappable")
sw.connect("state-set", lambda s, state, name=ly["name"]: self._on_fit_toggle(s, state, name))
sw.connect("state-set", self._on_fit_toggle)
row.append(sw)
page.append(row)
self._fit_sws[ly["name"]] = sw
@ -329,15 +325,6 @@ class Taskbar(Gtk.Box):
self._cols_lbl.set_label(str(new))
_eval(f'hl.dispatch(hl.dsp.layout("cols {"+1" if delta > 0 else "-1"}"))')
def _read_center(self) -> bool:
# columns.lua's own "center-focused" switch — global, published in the same
# cache file as the columns stepper's count (see columns.lua's publish()).
try:
data = json.loads(_COLUMNS_STATE.read_text())
return bool(data.get("_center", False))
except (FileNotFoundError, json.JSONDecodeError, ValueError, TypeError):
return False
# -- sync + handlers ---------------------------------------------------
def _sync_layout_controls(self) -> None:
# reflect the current per-ws layout / direction / focus_fit in the tabs+options
@ -345,11 +332,6 @@ class Taskbar(Gtk.Box):
run_json(["hyprctl", "getoption", "general:layout", "-j"], self._apply_tab_sel)
run_json(["hyprctl", "getoption", "scrolling:direction", "-j"], self._apply_dir_sel)
run_json(["hyprctl", "getoption", "scrolling:focus_fit_method", "-j"], self._apply_fit_sel)
# columns' own center-focused state lives in columns-state.json, not a hyprctl
# option — read it synchronously rather than round-tripping a subprocess.
columns_sw = self._fit_sws.get("columns")
if columns_sw is not None:
columns_sw.set_active(self._read_center())
def _apply_tab_sel(self, ok, data) -> None:
cur = data.get("str") if ok and isinstance(data, dict) else None
@ -374,13 +356,9 @@ class Taskbar(Gtk.Box):
self._syncing = False
def _apply_fit_sel(self, ok, data) -> None:
# scrolling:focus_fit_method only describes the scrolling layout's own switch;
# columns' switch is synced separately from columns-state.json (see
# _sync_layout_controls) since it isn't backed by a hyprctl option at all.
val = data.get("int") if ok and isinstance(data, dict) else 0
sw = self._fit_sws.get("scrolling")
if sw is not None:
self._syncing = True
for sw in self._fit_sws.values():
sw.set_active(val == 1)
self._syncing = False
@ -406,11 +384,8 @@ class Taskbar(Gtk.Box):
if name:
_eval(f'layouts.set("{self._active_ws}", "{name}", "{self._cur_dir()}")')
def _on_fit_toggle(self, _sw, state, name) -> bool:
def _on_fit_toggle(self, _sw, state) -> bool:
if not getattr(self, "_syncing", False):
if name == "columns":
_eval(f'hl.dispatch(hl.dsp.layout("center {"on" if state else "off"}"))')
else:
_eval(f"layouts.set_fit({1 if state else 0})")
return False

View File

@ -35,15 +35,6 @@
-- vertical ws: k/j = / columns, h/l = / per-view.
-- cols +/-/N — direct column-count set (the menu's stepper).
-- orient h/v — orientation (set from the menu dropdown).
-- center on/off/toggle — "center-focused" switch (menu): when a column is overfull
-- and scrolls, keep the focused window centred in the column
-- instead of the minimal-movement default (whichever edge of the
-- focused window would otherwise go off-screen just barely comes
-- back into view). Global (mirrors the scrolling layout's
-- "Centered follow" toggle), persisted across reloads, OFF by
-- default. Only ever repositions the column holding the focused
-- window — every other column's pan is untouched, since there is
-- only one focused window at a time.
--
-- State is per workspace; a window's column assignment, order, both weight tables and
-- each column's pan offset persist across relayouts.
@ -57,22 +48,8 @@ local function clampw(v) return math.max(RMIN, math.min(RMAX, v)) end
-- Publish per-workspace column count so the astal-menu can show a live stepper.
local STATE_FILE = (os.getenv("HOME") or "") .. "/.cache/astal-menu/columns-state.json"
-- "center-focused" switch — global (like the scrolling layout's own fit-method
-- toggle), so a single state, not per-workspace. Seeded once at module load from
-- the last-published state, since a reload wipes this module's Lua state (same
-- reason ncols/rows/orient are seeded — see seed() below).
local center_focus = false
do
local f = io.open(STATE_FILE, "r")
if f then
local data = f:read("*a") or ""; f:close()
if data:match('"_center"%s*:%s*true') then center_focus = true end
end
end
local function publish()
local parts = { string.format('"_center":%s', tostring(center_focus)) }
local parts = {}
for wsid, s in pairs(state) do
parts[#parts + 1] = string.format('"%s":{"cols":%d,"rows":%d,"orient":"%s"}',
tostring(wsid), s.ncols, s.rows, s.orient)
@ -281,24 +258,15 @@ local function recalculate(ctx)
for r = 1, #rowlist do rh[r] = rh[r] * scale end
pan = 0
else
-- overfull: keep natural heights and PAN so the focused row stays in view
-- (center_focus: centred in the viewport instead of minimal movement).
-- Only the column holding the focused window (frow ~= nil) is touched here;
-- every other column keeps whatever pan it already had.
-- overfull: keep natural heights and PAN so the focused row stays in view.
pan = s.pan[c] or 0
if frow then
local top = 0
for r = 1, frow - 1 do top = top + rh[r] end
local bottom = top + rh[frow]
if center_focus then
pan = top + (rh[frow] - secLen) / 2
else
if top - pan < 0 then pan = top end
if bottom - pan > secLen then pan = bottom - secLen end
end
end
-- clamp to the column's actual scroll range either way — this is also what
-- keeps "centred" from showing blank space past the first/last row.
pan = math.max(0, math.min(pan, total - secLen))
end
s.pan[c] = pan
@ -332,23 +300,11 @@ local function recalculate(ctx)
end
local function layout_msg(ctx, cmd)
local verb, arg = tostring(cmd or ""):match("^(%S+)%s*(%S*)$")
if not verb then return end
-- "center-focused" switch: global, doesn't touch any window, so handled before the
-- empty-workspace bail-out below (toggling it from the menu shouldn't require a
-- window to be focused/present).
if verb == "center" then
if arg == "on" then center_focus = true
elseif arg == "off" then center_focus = false
else center_focus = not center_focus end
publish()
return
end
local t = ctx.targets
if type(t) ~= "table" or not t[1] then return end
local s = ws_state(win_ws_id(t[1].window))
local verb, arg = tostring(cmd or ""):match("^(%S+)%s*(%S*)$")
if not verb then return end
local cols, _, foc = columnize(s, t)
local horizontal = (s.orient ~= "v")
@ -470,6 +426,6 @@ return {
dirs = { "h", "v" },
dir_labels = { "Horizontal", "Vertical" },
default_dir = "h",
fit_method = true, -- menu shows the "center-focused" switch (routed to "center on/off")
fit_method = false,
stepper = true, -- menu shows a "Columns: (-)[N](+)" count stepper
}

View File

@ -597,75 +597,6 @@ _fix_hypr_usr() {
fi
}
# Offer to redeploy hypr/usr/ (device-specific lua) from the dotfiles template,
# file by file. hypr/usr/ is excluded from the normal config-updater sync to
# preserve per-device customisation (monitors.lua, wallpaper.conf), but feature/
# bind updates in autostart.lua, binds.lua, windowrules.lua etc. still land in
# the dotfiles template and need an explicit, reviewable opt-in to reach a
# machine that already has its own hypr/usr/. Skipped entirely on first-time
# setup — _migrate_hypr_usr already seeds usr/ from the template in that case.
_redeploy_usr_configs() {
local de_dir; de_dir=$(_de_source_dir)
local src_usr="${de_dir:+$de_dir/hypr/usr}"
local usr_dir="${XDG_CONFIG_HOME:-$HOME/.config}/hypr/usr"
[[ -n "${src_usr:-}" && -d "$src_usr" ]] || return 0
[[ -d "$usr_dir" ]] || return 0
ask "Redeploy user-local configs (hypr/usr/)?" || { log "Skipped user-local config redeploy"; return 0; }
local -a files=()
while IFS= read -r -d '' item; do
files+=("$(basename "$item")")
done < <(find "$src_usr" -maxdepth 1 -mindepth 1 -type f ! -name '*.old' -print0 | sort -z)
if [[ ${#files[@]} -eq 0 ]]; then
warn "No template files found in $src_usr"
return 0
fi
local -a selected=()
if command -v dialog &>/dev/null; then
local items=()
for f in "${files[@]}"; do
local tag="on"
[[ "$f" == "monitors.lua" || "$f" == "wallpaper.conf" ]] && tag="off"
local status="new"
[[ -f "$usr_dir/$f" ]] && { diff -q "$usr_dir/$f" "$src_usr/$f" &>/dev/null && status="unchanged" || status="differs"; }
items+=("$f" "($status)" "$tag")
done
local chosen
chosen=$(dialog --stdout --separate-output \
--title " Redeploy hypr/usr/ " \
--checklist " SPACE = toggle | ENTER = confirm | ESC = cancel (monitors.lua / wallpaper.conf default OFF — device-specific) " \
0 70 0 "${items[@]}") || { warn "Cancelled — no user-local configs redeployed."; return 0; }
while IFS= read -r line; do
[[ -n "$line" ]] && selected+=("$line")
done <<< "$chosen"
else
warn "'dialog' not installed — redeploying all except monitors.lua/wallpaper.conf"
for f in "${files[@]}"; do
[[ "$f" == "monitors.lua" || "$f" == "wallpaper.conf" ]] && continue
selected+=("$f")
done
fi
if [[ ${#selected[@]} -eq 0 ]]; then
warn "Nothing selected — no user-local configs redeployed."
return 0
fi
for f in "${selected[@]}"; do
if cp -p "$src_usr/$f" "$usr_dir/$f"; then
ok "redeployed hypr/usr/$f"
else
err "failed hypr/usr/$f"
fi
done
_fix_hypr_usr
}
# Deploy root-owned greeter config (/etc/greetd) that config-updater can't reach.
# Only when greetd is the active greeter on this machine. Uses the sudo credential
# primed once at startup, so it doesn't re-prompt. PAM is intentionally left to the
@ -682,95 +613,13 @@ _deploy_greetd() {
ok "greetd config deployed" || warn "greetd config deploy had errors"
}
# Ensure this machine has finished the Plymouth + greetd/ReGreet boot/login
# migration. Delegates entirely to setup/tools/migrate-to-greetd.sh (idempotent,
# already handles old-DM detection/teardown, PAM/FIDO, wallpaper sync, initramfs
# + GRUB) rather than duplicating that logic here. Skips silently once the
# machine is already on the target stack.
_ensure_plymouth_regreet() {
local migrate_tool="$DOTFILES/setup/tools/migrate-to-greetd.sh"
[[ -x "$migrate_tool" ]] || return 0
local greetd_state; greetd_state=$(systemctl is-enabled greetd.service 2>/dev/null || true)
local using_regreet=false
grep -q 'regreet' /etc/greetd/config.toml 2>/dev/null && using_regreet=true
local plymouth_ok=false
pacman -Qq plymouth &>/dev/null && [[ -d /usr/share/plymouth/themes/m-archy ]] && plymouth_ok=true
if [[ "$greetd_state" == "enabled" && "$using_regreet" == true && "$plymouth_ok" == true ]]; then
ok "Plymouth + greetd/ReGreet already active"
return 0
fi
warn "Not yet on Plymouth + greetd/ReGreet (currently: greetd=${greetd_state}, regreet=${using_regreet}, plymouth=${plymouth_ok})"
if ask "Migrate now? (installs plymouth+regreet+cage, rebuilds initramfs; reboot needed after)"; then
"$migrate_tool" --yes
fi
}
# Ensure the Astal GObject typelibs astal-menu's main.py imports at startup are
# installed. config-updater only copies the astal-menu/ config directory — it
# never installs packages — so a machine that's never run the AUR install is
# missing these even after a full config sync.
_ensure_astal_menu_deps() {
local de_dir; de_dir=$(_de_source_dir)
[[ "${de_dir:-}" == */hyprlua ]] || return 0
[[ -d "$de_dir/astal-menu" ]] || return 0
local -a needed=(libastal-network-git libastal-bluetooth-git libastal-apps-git)
local -a missing=()
for pkg in "${needed[@]}"; do
pacman -Qq "$pkg" &>/dev/null || missing+=("$pkg")
done
if [[ ${#missing[@]} -eq 0 ]]; then
ok "astal-menu AUR deps already installed"
return 0
fi
warn "astal-menu is missing AUR deps: ${missing[*]}"
if ask "Install them now?"; then
if yay -S --noconfirm --needed "${missing[@]}"; then
ok "astal-menu deps installed"
else
warn "Failed to install some astal-menu deps"
fi
fi
}
# Ensure hyprshutdown is installed. hypr/usr/binds.lua's power binds
# (Super+Shift/Ctrl/Ctrl+Shift+O) call it directly; without it those binds
# silently no-op. It's in the official extra repo, not AUR.
_ensure_hyprshutdown() {
local de_dir; de_dir=$(_de_source_dir)
[[ "${de_dir:-}" == */hyprlua ]] || return 0
if pacman -Qq hyprshutdown &>/dev/null; then
ok "hyprshutdown already installed"
return 0
fi
warn "hyprshutdown is missing — the power binds in hypr/usr/binds.lua will no-op without it"
if ask "Install it now?"; then
if sudo pacman -S --noconfirm --needed hyprshutdown; then
ok "hyprshutdown installed"
else
warn "Failed to install hyprshutdown"
fi
fi
}
sync_configs() {
section "Config Sync"
# ── Migration: old flat layout → hypr/usr/ ───────────────────────────────
_migrate_hypr_usr
_fix_hypr_usr
_redeploy_usr_configs
_deploy_greetd
_ensure_plymouth_regreet
_ensure_astal_menu_deps
_ensure_hyprshutdown
# ── Preferred: use the installed update-configs.sh ───────────────────────
local cfg_script