From 7e2ae535604e5052e879d806570a332572591430 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Thu, 8 Oct 2026 10:17:06 -0400 Subject: [PATCH] =?UTF-8?q?feat(config):=20add=20kitty=20pre=E2=80=91shutd?= =?UTF-8?q?own=20session=20save=20and=20config=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .chezmoiignore.tmpl | 1 + .../run_once_after_30-user-services.sh.tmpl | 7 +++ dot_config/kitty/day_session.py | 51 ++++++++++++------- dot_config/kitty/kitty.conf | 14 ++--- dot_config/starship.toml | 43 ++++++++-------- .../systemd/user/kitty-day-save.service | 13 +++++ 6 files changed, 84 insertions(+), 45 deletions(-) create mode 100644 dot_config/systemd/user/kitty-day-save.service diff --git a/.chezmoiignore.tmpl b/.chezmoiignore.tmpl index b855aa1..ec11e6b 100644 --- a/.chezmoiignore.tmpl +++ b/.chezmoiignore.tmpl @@ -46,6 +46,7 @@ television .config/mimeapps.list .config/systemd .config/systemd/** +.local/bin/kitty-day-save .local/bin/niri-launch .config/fish/auto-Niri.fish .local/bin/cp-appid diff --git a/.chezmoiscripts/run_once_after_30-user-services.sh.tmpl b/.chezmoiscripts/run_once_after_30-user-services.sh.tmpl index dbe35d5..2b366d2 100644 --- a/.chezmoiscripts/run_once_after_30-user-services.sh.tmpl +++ b/.chezmoiscripts/run_once_after_30-user-services.sh.tmpl @@ -9,6 +9,13 @@ if [ -f "$HOME/.config/systemd/user/nirinit.service" ] && command -v systemctl > systemctl --user daemon-reload || true systemctl --user enable nirinit.service || true fi + +# Pre-shutdown save of kitty's daily session (dot_config/systemd/user, only on +# linux desktop/laptop roles; see .chezmoiignore.tmpl). +if [ -f "$HOME/.config/systemd/user/kitty-day-save.service" ] && command -v systemctl >/dev/null 2>&1; then + systemctl --user daemon-reload || true + systemctl --user enable --now kitty-day-save.service || true +fi {{ else -}} #!/bin/sh exit 0 diff --git a/dot_config/kitty/day_session.py b/dot_config/kitty/day_session.py index 8fb7d6c..e6e67df 100644 --- a/dot_config/kitty/day_session.py +++ b/dot_config/kitty/day_session.py @@ -8,31 +8,34 @@ One file, three entry points (kitty runs it under a different __name__ each): startup_session at it whenever no running kitty owns today's session (an flock held for the owner's lifetime). Every launch after kitty was closed reopens the day; a second concurrent instance starts plain instead of - duplicating today's tabs. + duplicating today's tabs. `kitty --session ` (the kitty-days tv + cable) opens that day as a read-only snapshot instead. - watcher (__kitty_watcher__): SAVE_DELAY seconds after a window opens, closes, moves, gains focus or finishes a command (and every SAVE_EVERY seconds, for output from long-running programs), rewrites the session this instance started with: tabs/splits/cwds, each shell window's scrollback, and the program running in it. On restore the shell `cat`s the scrollback, then - re-runs the program. Days opened later via the picker are left as read-only - snapshots. -- kitten (map ... kitten day_session.py): picker over every archived day, - newest first; the choice is opened with goto_session. + re-runs the program. +- kitten `day_session.py flush` (sent over remote control by kitty-day-save + before shutdown/reboot): saves right away, then holds further saves for + FREEZE seconds so windows dying during shutdown can't shrink the saved day. """ import fcntl -import glob import json import os import re import shlex +import sys import tempfile from datetime import date, timedelta +from time import monotonic from kittens.tui.handler import result_handler ROOT = os.path.expanduser('~/.local/share/kitty-sessions') SAVE_DELAY = 1 # seconds; batches bursts of events, and the most change lost when kitty dies SAVE_EVERY = 30 # seconds; catches output that arrives without any window event +FREEZE = 60 # seconds without saves after a flush; long enough for any shutdown, short if it's cancelled SB_VAR = 'day_scrollback' # user var naming a window's scrollback dump; kitty saves/restores it via launch --var UNSERIALIZE = 'kitty-unserialize-data=' # kitty.constants.unserialize_launch_flag # Never replay a command that was mid-run when the session was saved if any word @@ -149,12 +152,14 @@ def on_load(boss, data) -> None: args[1] = UNSERIALIZE + json.dumps(data) return shlex.join(args) + frozen_until = 0.0 + def save(timer_id: int) -> None: nonlocal last, pending pending = False windows = [w for w in boss.all_windows if w.created_in_session_name == name] # Nothing left in the session means kitty is closing: keep the last good save. - if not windows: + if not windows or monotonic() < frozen_until: return os.makedirs(sb_dir, exist_ok=True) keep = set() @@ -185,7 +190,14 @@ def on_load(boss, data) -> None: pending = True add_timer(save, SAVE_DELAY, False) + def flush() -> None: + nonlocal frozen_until + frozen_until = 0.0 + save(0) + frozen_until = monotonic() + FREEZE + _schedule_save = schedule + boss.day_session_flush = flush # the flush kitten runs in its own module copy, so it finds this via boss add_timer(lambda timer_id: schedule(), SAVE_EVERY, True) @@ -206,23 +218,26 @@ def main(args: list[str]) -> None: @result_handler(no_ui=True) def handle_result(args: list[str], answer: str, target_window_id: int, boss) -> None: - from kitty.session import goto_session + if 'flush' in args and (flush := getattr(boss, 'day_session_flush', None)): + flush() - days = sorted(glob.glob(os.path.join(ROOT, '*', '*.kitty-session')), key=os.path.basename, reverse=True) - if not days: - boss.show_error('No day sessions', f'Nothing archived under {ROOT} yet.') - return - def label(path: str) -> str: - d = date.fromisoformat(os.path.basename(path).partition('.')[0]) - return f'{d} {d:%a} week of {os.path.basename(os.path.dirname(path))}' - - boss.choose_entry('Open a day', ((p, label(p)) for p in days), lambda p: p and goto_session(boss, (p,))) +def session_arg() -> str | None: + """`--session` from kitty's own command line (geninclude runs inside kitty).""" + argv = sys.argv[1:] + for i, a in enumerate(argv): + if a.startswith('--session='): + return a.partition('=')[2] + if a == '--session' and i + 1 < len(argv): + return argv[i + 1] + return None if __name__ == '__main__': path = today_path() - if own_day(path): + other = session_arg() + # An explicit --session for another day must not claim today's lock. + if (other is None or os.path.abspath(os.path.expanduser(other)) == path) and own_day(path): os.makedirs(os.path.dirname(path), exist_ok=True) try: open(path, 'x').close() # empty session = one tab with the default shell diff --git a/dot_config/kitty/kitty.conf b/dot_config/kitty/kitty.conf index 4f905c7..b9b16f0 100644 --- a/dot_config/kitty/kitty.conf +++ b/dot_config/kitty/kitty.conf @@ -35,7 +35,7 @@ action_alias launch_os launch --cwd=current --type=os-window action_alias launch_ov launch --cwd=current --type=overlay action_alias launch_tb launch --cwd=current --type=tab action_alias launch_hs launch --cwd=current --location=hsplit --bias=25 -action_alias launch_vs launch --cwd=current --location=vsplit --bias=30 +action_alias launch_vs launch --cwd=current --location=vsplit --bias=33 action_alias launch_vs50 launch --cwd=current --location=vsplit --bias=50 # panes = kitty windows @@ -84,12 +84,10 @@ map alt+o goto_tab 9 # reopens today's session unless another running kitty already owns it: tabs, # splits, cwds, each shell's scrollback and the program running in it. Tabs # made with *_with_cwd / --cwd=current join it and are saved ~1s after each -# change. ctrl+shift+d reopens any earlier day. +# change, and right before shutdown/reboot (kitty-day-save.service). `tv +# kitty-days` opens any earlier day in a new kitty. geninclude day_session.py watcher day_session.py -map ctrl+shift+d kitten day_session.py -# show only the active day's tabs (plus tabs outside any session) -tab_bar_filter session:~ or session:^$ # split nav (vim-aware) map ctrl+h kitten pass_keys.py neighboring_window left ctrl+h @@ -103,9 +101,11 @@ map super+up resize_window taller map super+down resize_window shorter 3 tab_bar_style powerline -tab_powerline_style slanted -tab_bar_align center +tab_powerline_style angled +tab_bar_align left tab_bar_edge top +# always shown, even with one tab: it carries the stack-layout pane indicator +tab_bar_min_tabs 1 # Titles come from ~/.config/kitty/tab_bar.py (`{custom}`): the repo the tab's # cwd belongs to, or the file nvim is editing when nvim is the active process. diff --git a/dot_config/starship.toml b/dot_config/starship.toml index fc42c54..4a6be9f 100644 --- a/dot_config/starship.toml +++ b/dot_config/starship.toml @@ -1,6 +1,7 @@ "$schema" = 'https://starship.rs/config-schema.json' palette = "cyberdream" + format = """ $username\ $hostname\ @@ -12,6 +13,8 @@ $cmd_duration\ $python\ $character""" +add_newline = true + right_format = """$time""" [directory] @@ -59,30 +62,30 @@ style = "bright-black" [palettes.cyberdream] # https://github.com/scottmckendry/cyberdream.nvim -black = "#1e2124" -red = "#ff6e5e" -green = "#5eff6c" -yellow = "#f1ff5e" -blue = "#5ea1ff" -purple = "#bd5eff" -cyan = "#5ef1ff" -white = "#ffffff" +black = "#1e2124" +red = "#ff6e5e" +green = "#5eff6c" +yellow = "#f1ff5e" +blue = "#5ea1ff" +purple = "#bd5eff" +cyan = "#5ef1ff" +white = "#ffffff" -bright-black = "#4a4d4f" -bright-red = "#ff8b7e" -bright-green = "#7eff89" +bright-black = "#4a4d4f" +bright-red = "#ff8b7e" +bright-green = "#7eff89" bright-yellow = "#f3ff7e" -bright-blue = "#7eb3ff" +bright-blue = "#7eb3ff" bright-purple = "#ff7ef3" -bright-cyan = "#7ef3ff" -bright-white = "#ffffff" +bright-cyan = "#7ef3ff" +bright-white = "#ffffff" # Dim variants (usable as named colors in styles) -dim-black = "#1c1f22" -dim-red = "#d05c50" -dim-green = "#4fd05b" +dim-black = "#1c1f22" +dim-red = "#d05c50" +dim-green = "#4fd05b" dim-yellow = "#c5d050" -dim-blue = "#4f85d1" +dim-blue = "#4f85d1" dim-purple = "#d050c6" -dim-cyan = "#4fc5d1" -dim-white = "#d0d0d1" +dim-cyan = "#4fc5d1" +dim-white = "#d0d0d1" diff --git a/dot_config/systemd/user/kitty-day-save.service b/dot_config/systemd/user/kitty-day-save.service new file mode 100644 index 0000000..87bd183 --- /dev/null +++ b/dot_config/systemd/user/kitty-day-save.service @@ -0,0 +1,13 @@ +[Unit] +Description=Save kitty daily sessions before shutdown/reboot +PartOf=graphical-session.target +After=graphical-session.target + +[Service] +ExecStart=%h/.local/bin/kitty-day-save +# Re-arm after a flush, in case the shutdown is cancelled. +Restart=always +RestartSec=10 + +[Install] +WantedBy=graphical-session.target