Device & Cursor¶
Device and Cursor sit on the host, not on any grid. They control the window/tab chrome and the terminal caret.
You do not construct them. Get them from:
ctx.device/ctx.cursorinside a hook (Context)terminal.device/terminal.cursor(or the underlyingruntime) when you hold a Terminal
Title, mode flags, caret position, visibility, and style are always tracked locally on the device/cursor objects (so offscreen tests and web sessions still report consistent values). Only a live terminal session issues the real escape sequences. Offscreen and web runtimes keep the Python state; live side effects that need a real TTY are skipped.
Device¶
Device covers title, viewport size,
clear/scroll, clipboard, and terminal mode flags.
Title and size¶
from xnano import Context, on_state
@on_state("unread > 0")
def flash_title(self, ctx: Context) -> None:
ctx.device.title = f"({ctx.state.unread}) inbox" # (1)!
- Live terminal: window title. Web host: document title when the server applies it. Offscreen: stored on the device only.
size = ctx.device.size # Size(width=…, height=…) in cells
width, height = size.width, size.height
size mirrors the runtime viewport. It is read-only from the device side.
Clear and scroll¶
ctx.device.clear() # kind="all" by default
ctx.device.clear("purge") # screen + scrollback
ctx.device.clear("current_line")
ctx.device.scroll_up(3)
ctx.device.scroll_down(1)
clear accepts:
| Kind | Region |
|---|---|
"all" |
Entire visible screen |
"purge" |
Screen and scrollback |
"from_cursor_down" |
Caret to bottom |
"from_cursor_up" |
Top to caret |
"current_line" |
Current line only |
"until_new_line" |
Caret to end of line |
These only emit terminal commands when the runtime is live.
Clipboard¶
copy_to_clipboard(text) is the device method for putting text on the system
clipboard when the host supports it.
Mode flags¶
Boolean properties toggle terminal features. State is always kept in Python; live sessions apply the corresponding native enable/disable calls.
| Property | Default | Role |
|---|---|---|
raw_mode |
False |
Raw input (no line buffering / echo) |
alternate_screen |
False |
Alternate screen buffer (typical full-screen TUI) |
line_wrap |
True |
Automatic line wrap |
mouse_capture |
False |
Capture mouse for the session |
bracketed_paste |
False |
Bracketed paste sequences |
focus_change |
False |
OS-level terminal focus gained/lost events |
synchronized_updates |
False |
Batch output updates |
# Prefer Terminal(mouse_events=True) for app-level mouse hooks.
# Device flag is the lower-level session switch:
ctx.device.mouse_capture = True
For click/hover hooks on grids, pass mouse_events=True to
Terminal when you construct it — that wires mouse into the
event loop. Setting device.mouse_capture alone is useful when you already
hold a live session (for example the markdown pager enables it
for wheel and hover).
if ctx.device.alternate_screen:
...
ctx.device.raw_mode = True # live only; needs a real TTY
Cursor¶
Cursor is the host caret: show/hide,
style, position, save/restore, blinking.
Most apps leave the caret alone and let focused Input fields own editing.
Use cursor controls when you draw selection yourself, drive a custom caret, or
hide the system caret over a full-screen paint.
Visibility and style¶
from xnano import Context, on_focus
@on_focus("search", kind="gained")
def hide_system_caret(self, ctx: Context) -> None:
ctx.cursor.visible = False
ctx.cursor.style = "blinking_bar"
ctx.cursor.enable_blinking()
ctx.cursor.disable_blinking() # maps blinking_* styles to steady_*
| Style | Appearance |
|---|---|
"default" |
Terminal default |
"blinking_block" / "steady_block" |
Block |
"blinking_underline" / "steady_underline" |
Underline |
"blinking_bar" / "steady_bar" |
Vertical bar |
Position¶
Position is tracked in cells as (x, y). Live terminals move the real caret;
offscreen/web keep the local coordinates for tests and frame inspection.
ctx.cursor.move(4, 2)
ctx.cursor.position = (0, 0)
x, y = ctx.cursor.position
# same as: ctx.cursor.get_position()
ctx.cursor.move_up()
ctx.cursor.move_down(2)
ctx.cursor.move_left()
ctx.cursor.move_right(3)
ctx.cursor.save()
ctx.cursor.move(10, 5)
ctx.cursor.restore()
Live vs offscreen vs web¶
| Concern | Live terminal | Offscreen / tests | Web host |
|---|---|---|---|
title, flags, size |
Applied + tracked | Tracked locally | Tracked; title may map to the page |
clear / scroll_* |
Escape sequences | No-op on the wire | No-op on the wire |
cursor position |
Real caret moves | Local coords only | Local coords only |
cursor visibility / style |
Applied when live | Tracked locally | Tracked; no browser caret |
Offscreen is intentional: a headless or web server must not dump terminal control sequences onto the process that owns stdout.
from xnano import Terminal
terminal = Terminal(title="My App")
# after the session is live / after render attaches a runtime:
terminal.device.title = "My App"
terminal.cursor.visible = True
Next¶
- Context —
ctx.device/ctx.cursoron every hook - Terminal —
mouse_events, live session - Markdown — uses mouse capture for wheel / image hover
- Effects — field paint transitions (not the system caret)
API
Device ·
Cursor ·
ClearType ·
CursorStyle ·
Context