aboutsummaryrefslogtreecommitdiffstats
path: root/idatui/kittygfx.py
diff options
context:
space:
mode:
authorblasty <peter@haxx.in>2026-08-21 12:08:42 +0200
committerblasty <peter@haxx.in>2026-08-21 12:08:42 +0200
commit7a3b198a904da186cc2860df059b063db24282ea (patch)
treefd5a754169522c0ccd8b7e0d70b2238395d81b09 /idatui/kittygfx.py
parentdocs: be honest that the triskel fork is unpublished (diff)
downloadida-tui-7a3b198a904da186cc2860df059b063db24282ea.tar.gz
ida-tui-7a3b198a904da186cc2860df059b063db24282ea.tar.xz
ida-tui-7a3b198a904da186cc2860df059b063db24282ea.zip
splash: give the logo a placement id so re-anchoring replaces, not stacks
A kitty placement is identified by (image id, placement id); an a=p with no p= key is anonymous and every one stacks another copy. The splash re-anchors on every progress note (~5/s), so a 60s load ended with ~300 placements of an RGBA logo alpha-compositing over each other -- soft edges creeping to solid, and the terminal re-rendering all of them per frame. Same pair every time (LOGO_PLACEMENT) = the terminal REPLACES the placement, so re-anchoring is free and atomic. That is also why on_resize no longer clear()s first (that showed a hole for a frame), and why a shrunk-to-nothing region now drops the placement instead of leaving a stale one anchored. tests/test_kittygfx.py pins the escapes (pure, stdlib); experiments/splash_place_count.py counts what a real splash sends.
Diffstat (limited to 'idatui/kittygfx.py')
-rw-r--r--idatui/kittygfx.py19
1 files changed, 16 insertions, 3 deletions
diff --git a/idatui/kittygfx.py b/idatui/kittygfx.py
index e53fba3..2f6766d 100644
--- a/idatui/kittygfx.py
+++ b/idatui/kittygfx.py
@@ -45,6 +45,14 @@ import time
#: One id for the splash. Ids are a terminal-wide namespace shared with whatever
#: else the user is running, so this is deliberately not 1.
LOGO_ID = 0x1DA7
+#: Placement id for the splash. A placement is identified by the PAIR (image id,
+#: placement id): re-placing with the same pair REPLACES the placement, while a
+#: placement with no ``p`` key is anonymous and every one of those stacks a new
+#: copy on the screen. The splash re-anchors itself on every progress note, so
+#: without this the terminal ends a long load holding hundreds of placements of
+#: the same image at the same cell -- alpha-compositing the (RGBA) logo over
+#: itself until its soft edges go solid, and re-rendering all of them per frame.
+LOGO_PLACEMENT = 1
_supported: bool | None = None
_uploaded: dict[int, tuple[int, int]] = {} # image id -> (pixel w, pixel h)
@@ -210,21 +218,26 @@ def is_uploaded(image_id: int = LOGO_ID) -> bool:
def place(row: int, col: int, cols: int, rows: int,
- image_id: int = LOGO_ID) -> bool:
+ image_id: int = LOGO_ID, placement_id: int = LOGO_PLACEMENT) -> bool:
"""Draw the uploaded image at (``row``, ``col``), 0-based, sized in cells.
Saves and restores the cursor, and asks the terminal not to move it
(``C=1``), so Textual's idea of where the cursor is stays true.
+
+ Always carries a placement id (``p``), so calling this again REPLACES the
+ previous placement instead of adding another one underneath it -- see
+ ``LOGO_PLACEMENT``. Callers re-anchor freely; the screen holds exactly one.
"""
size = _uploaded.get(image_id)
if size is None or cols <= 0 or rows <= 0:
log(f"place: refused size={size} cols={cols} rows={rows}")
return False
w, h = size
- log(f"place row={row} col={col} c={cols} r={rows}")
+ log(f"place row={row} col={col} c={cols} r={rows} p={placement_id}")
return _write(
f"\033[s\033[{row + 1};{col + 1}H"
- f"\033_Ga=p,i={image_id},s={w},v={h},c={cols},r={rows},C=1,q=2\033\\"
+ f"\033_Ga=p,i={image_id},p={placement_id},"
+ f"s={w},v={h},c={cols},r={rows},C=1,q=2\033\\"
f"\033[u")