Skip to content

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.cursor inside a hook (Context)
  • terminal.device / terminal.cursor (or the underlying runtime) 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

Title
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)!
  1. Live terminal: window title. Web host: document title when the server applies it. Offscreen: stored on the device only.
Viewport size
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

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

Clipboard
ctx.device.copy_to_clipboard(self.selected_text)

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
Mouse capture on the device
# 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).

Reading flags
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

Visibility
from xnano import Context, on_focus

@on_focus("search", kind="gained")
def hide_system_caret(self, ctx: Context) -> None:
    ctx.cursor.visible = False
Style
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.

Position
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 a Terminal you own
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

  • Contextctx.device / ctx.cursor on every hook
  • Terminalmouse_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