"""Custom kitty tab bar — Sethian status bar. Flat tab style (no powerline separators) + right-aligned status area. Shows user@ prefix for non-seth users. Status: git branch, CPU% + temp, memory, uptime, hostname, battery, clock. Temp and battery hide themselves when the sensor is missing (steel141 has no battery). """ import glob import os import subprocess from datetime import datetime from kitty.fast_data_types import add_timer, get_boss from kitty.tab_bar import ( DrawData, ExtraData, Screen, TabBarData, as_rgb, ) _user = os.environ.get("USER", "") _prefix = f"{_user}@" if _user and _user != "seth" else "" _hostname = os.uname().nodename # Nerd Font icons _ICON_BRANCH = "\ue725" _ICON_CPU = "\uf4bc" _ICON_MEM = "\uf035b" _ICON_UP = "\uf0aa" _ICON_HOST = "\uf108" _ICON_CLOCK = "\uf017" _ICON_TEMP = "\uf2c9" _ICON_BAT = "\uf240" _ICON_CHG = "\uf0e7" # Colors _ORANGE = as_rgb(0xD35400) _BLACK = as_rgb(0x000000) _DIM = as_rgb(0x666666) _RED = as_rgb(0xFF3030) _TAB_BG = as_rgb(0x1A1A1A) _BAR_BG = as_rgb(0x0D0D0D) # kitty only redraws the tab bar on tab changes; this timer keeps the clock/stats live. # ponytail: 10s = clock lags <=10s; each redraw runs git on kitty's main thread, so don't go much lower. REFRESH_SECS = 10.0 _timer_id = None def _refresh(_timer_id) -> None: for tm in get_boss().all_tab_managers: tm.mark_tab_bar_dirty() def _get_git_branch() -> str: try: r = subprocess.run( ["git", "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True, timeout=1, ) if r.returncode == 0: return r.stdout.strip() except Exception: pass return "" def _get_cpu_percent() -> str: try: with open("/proc/stat") as f: parts = f.readline().split() idle = int(parts[4]) + int(parts[5]) total = sum(int(p) for p in parts[1:]) prev_idle = getattr(_get_cpu_percent, "_i", 0) prev_total = getattr(_get_cpu_percent, "_t", 0) _get_cpu_percent._i = idle _get_cpu_percent._t = total if prev_total == 0: return "..." dt = total - prev_total di = idle - prev_idle return f"{(1.0 - di / dt) * 100:.0f}%" if dt else "0%" except Exception: return "?" # Intel = coretemp, AMD = k10temp; temp1 is the package/Tctl sensor on both. # hwmon numbers move between boots, so resolve once at kitty start. _CPU_TEMP = next( (os.path.join(os.path.dirname(n), "temp1_input") for n in glob.glob("/sys/class/hwmon/hwmon*/name") if open(n).read().strip() in ("coretemp", "k10temp")), None, ) # BAT* only: mice/keyboards also show up in power_supply as hid*/hidpp_battery*. _BAT = next(iter(sorted(glob.glob("/sys/class/power_supply/BAT*"))), None) def _get_cpu_temp() -> str: try: with open(_CPU_TEMP) as f: return f"{int(f.read()) // 1000}\u00b0" except Exception: return "" def _get_battery() -> tuple[str, str, bool]: """(icon, '60%', low) or ('', '', False) when there's no battery.""" try: with open(f"{_BAT}/capacity") as f: cap = int(f.read()) with open(f"{_BAT}/status") as f: status = f.read().strip() except Exception: return "", "", False on_ac = status in ("Charging", "Full", "Not charging") return (_ICON_CHG if on_ac else _ICON_BAT), f"{cap}%", (not on_ac and cap <= 20) def _get_memory() -> str: try: info = {} with open("/proc/meminfo") as f: for line in f: p = line.split() info[p[0].rstrip(":")] = int(p[1]) used = info["MemTotal"] - info.get("MemAvailable", info.get("MemFree", 0)) return f"{used / 1048576:.0f}/{info['MemTotal'] / 1048576:.0f}G" except Exception: return "?" def _get_uptime() -> str: try: with open("/proc/uptime") as f: secs = int(float(f.read().split()[0])) d, rem = divmod(secs, 86400) h, rem = divmod(rem, 3600) m = rem // 60 if d > 0: return f"{d}d {h}h" return f"{h}h {m}m" if h > 0 else f"{m}m" except Exception: return "?" def draw_tab( draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_tab_length: int, index: int, is_last: bool, extra_data: ExtraData, ) -> int: global _timer_id if _timer_id is None: _timer_id = add_timer(_refresh, REFRESH_SECS, True) # Build tab title title = tab.title if _prefix: title = f"{_prefix}{title}" label = f" {index - 1}: {title} " # Truncate if needed if len(label) > max_tab_length: label = label[: max_tab_length - 1] + "\u2026" # Draw the tab if tab.is_active: screen.cursor.fg = _BLACK screen.cursor.bg = _ORANGE screen.cursor.bold = True else: screen.cursor.fg = _DIM screen.cursor.bg = _TAB_BG screen.cursor.bold = False screen.draw(label) screen.cursor.bold = False # Small gap between tabs screen.cursor.fg = _DIM screen.cursor.bg = _BAR_BG screen.draw(" ") end = screen.cursor.x # Right-aligned status on last tab if is_last: sep = " " cells: list[tuple[str, int]] = [] branch = _get_git_branch() if branch: cells += [(_ICON_BRANCH + " ", _ORANGE), (branch, _DIM), (sep, _DIM)] cells += [(_ICON_CPU + " ", _ORANGE), (_get_cpu_percent(), _DIM)] temp = _get_cpu_temp() if temp: cells += [(" " + _ICON_TEMP + " ", _ORANGE), (temp, _DIM)] cells += [(sep, _DIM)] cells += [(_ICON_MEM + " ", _ORANGE), (_get_memory(), _DIM), (sep, _DIM)] cells += [(_ICON_UP + " ", _ORANGE), (_get_uptime(), _DIM), (sep, _DIM)] cells += [(_ICON_HOST + " ", _ORANGE), (_hostname, _DIM), (sep, _DIM)] bat_icon, bat, low = _get_battery() if bat: cells += [(bat_icon + " ", _RED if low else _ORANGE), (bat, _RED if low else _DIM), (sep, _DIM)] cells += [(_ICON_CLOCK + " ", _ORANGE), (datetime.now().strftime("%H:%M"), _DIM), (" ", _DIM)] total_len = sum(len(t) for t, _ in cells) right_pos = screen.columns - total_len if right_pos > end: screen.cursor.x = right_pos for text, fg in cells: screen.cursor.fg = fg screen.cursor.bg = _BAR_BG screen.draw(text) return end