console-window
console-window is a stdlib-only, single-dependency-free curses framework that is not a general widget toolkit. It is best understood as a keyboard-driven, full-screen "pager + selection engine + screen-stack router" built on raw curses pads. The genuinely novel ideas are:
- a separate head pad / body pad split with a static header band;
- a scroll position and an independent pick (cursor) position that stay synchronized;
- a
Contextmetadata object per line with anabutconcept (keep N lines around the picked row visible — perfect for expandable/transient detail rows); OptionSpinnerscoped key bindings with last-writer-wins scope subtraction per screen.
The package abstracts away the complexities of managing curses pads, dynamic screen resizing, custom scroll/pick synchronization, and keyboard handling. Its strengths include:
- Zero runtime dependencies. The only requirements are the standard-library
cursesandcurses.textpadmodules — no third-party dependency tree and no associated supply-chain risk. - Efficient rendering. Uses accumulated refreshes on the screen and pads with a single terminal update per frame, rather than flicker-prone full refreshes. A redraw path can recover the display from terminal corruption.
- Two-axis scroll/pick model. The scroll position and the pick (cursor) position are tracked independently and reconciled each frame. Most simple pagers conflate these; separating them is what enables kept-visible selection with context rows above and below.
- Per-line metadata plus an
abutconcept. Because context is attached to each line rather than parsed from text, and transient child rows auto-abut to their parent, expandable/transient detail rows stay attached to the row that produced them. "After trumps before" is a thoughtful touch. - Non-pickable rows are first-class. Decorative separator and header lines are skipped by navigation and page movement, so they do not produce "dead" cursor stops; scrolling still reaches them, including a trailing block of them past the last pickable row.
- Scoped, self-subtracting key bindings. A later, narrower binding (e.g.
ESCto clear a filter on one screen only) subtracts that screen from an earlier global binding. This is a real router, not a flat key map, and it actively rejects collisions with navigation keys. - Convention-based dispatch. Actions map to matching handler methods on screen classes, keeping them free of central
if key == ...towers. - Resize resilience. Rendering retries during terminal resizes, dialogs drain resize events, and the window degrades gracefully below its minimum size instead of crashing.
- Inline confirmation is unusually complete. Tiered matching (exact, then case-insensitive, then abbreviation), an auto-calculated minimum abbreviation length, and six modes including identity matching and free text.
- Real, dogfooded apps. The API is validated by several downstream projects (listed below), not by theory.
- Headless, testable core. The layout math and a headless render backend let the whole engine be exercised without a TTY.
- Display-cell correctness. A small standard-library width engine accounts for wide CJK/emoji and zero-width combining marks, so layout and highlighting stay correct across the body, header, dialogs and the emoji-heavy sample app.
Installation
Install the package via pip:
pip install console-window
Running the Sample App (from source)
For development, run the bundled sample app directly against your working tree — no install or build step required:
./runner
(runner executes SampleApp as __main__, so live edits are picked
up immediately. The equivalent one-off is python3 SampleApp.py.)
Core Features (The "Secret Sauce")
The wrapper handles several advanced terminal interaction patterns automatically:
- Viewport-Sized Pads: Separate
headandbodypads give the static header band and the independently scrolling body, but the pads are blit buffers sized to the terminal rather than content stores.add_body()/add_header()/draw()write the model, andrender()paints the visible page into pad row 0, so pad memory and per-frameclear()cost stay constant no matter how many rows an app adds. Row counts need no configuration at all:opts.body_rows/opts.head_rowsare advisory hints only, andopts.max_rows(default32767,Nonefor unlimited) is the single content ceiling. - Loud Content Limits: A row added past
opts.max_rowsis discarded, but no longer quietly: the first discarded row of each fill episode emits aUserWarningnaming the namespace and the option to change, and every discarded row is counted inConsoleWindow.rows_dropped. It stays non-fatal — runpython -W error::UserWarningif you would rather it killed the app. - Seamless Resizing: Automatically detects terminal size changes (
KEY_RESIZE) and recalculates layout dimensions, with robust error handling to prevent commoncurses.errorcrashes during resizing. - Scroll & Pick Synchronization: The
Windowclass manages scroll position and a separate pick position (highlighted row) simultaneously.- When using pick mode, scrolling automatically tracks the highlighted element to keep it visible in
.pick_pos. - To use that value, you must separately be able to associate
.pick_posto its value by, say, keeping an array of values for every added line to thebody.
- When using pick mode, scrolling automatically tracks the highlighted element to keep it visible in
- Intuitive Navigation: Provides built-in handling for common navigation keys (
j/k,UP/DOWN, Page Up/Down,H/M/Lfor home/middle/last viewable line, etc.). - Dynamic Options Management (
OptionSpinner): The companion class simplifies creation of application settings, allowing users to toggle options or input strings using single keypresses (e.g.,pto toggle pick mode). - Blocking Popups: Includes fully implemented, blocking pop-up methods for user input (
.answer()) and alerts (.alert()), handling the necessary temporary screen takeover and cursor management.
Worked Example: SampleApp.py
The canonical example is the bundled sample app:
https://github.com/joedefen/console-window/blob/main/SampleApp.py
It is a read-only /etc/passwd browser, and it is deliberately more than a list:
it exercises the whole Screen paradigm the library is built around — one
window, a ScreenStack of screens, and key bindings scoped per screen. What it
demonstrates:
ScreenStack+ four screens (MAIN,HELP,HISTORY,THEMES) pushed and popped with?,h,tandESC/ENTER, dispatched throughScreenStack.perform_actions().- Convention-based actions — each key maps to an
<attr>_ACTIONmethod on the current screen (filter_ACTION,select_ACTION,help_screen_ACTION, …), so there is no centralif key == ...tower. - Scoped, self-subtracting keys —
topens the theme screen onMAINbut cycles themes onTHEMES/HELP; the narrower binding subtracts its screen from the wider one automatically. - Per-line
Contextmetadata — rows carryContext(genre='item'), read back withget_picked_context(), and decorative rows (Context(genre='DECOR')) are skipped by navigation. IncrementalSearchBar—/filters the list as you type, usingpassthrough_modeto hand keys to the bar.InlineConfirmation—SPACEasks you to confirm withybefore "processing" the picked row.- Themes — a
THEMESscreen of colour samples backed byTheme.list_all()/get_current()/set(). The active theme is now per-window state:ConsoleWindowOpts(theme=...)seedswindow.theme,window.set_theme()switches it (and re-initialises the terminal's colour pairs, which curses keeps per terminal rather than per window), andTheme.set()remains the process-wide switch. - Dialogs —
acallsanswer()for text input,lcallsalert(), andndrivesanswer()from anOptionSpinneroption (add_key(..., prompt=...)).answer()returnsNoneonly when the user aborts; if the terminal cannot fit the dialog it raisesTerminalTooSmallErrorwith the required and actual sizes. flash()— transient, non-destructive status messages.- Token-based headers —
HeaderLine(text,bracket_key,key,extend) rendered withadd_header_line(), rather than string markup. Ctrl-Cas an application key —ctrl_c_terminates=Falseandkeys={0x3, ord('q')}on the quit action. This works only while no live window in the process has requested terminating Ctrl-C: once any window uses the default (ctrl_c_terminates=True), terminating Ctrl-C is latched for as long as a window depends on it. The handler that was installed before the first window is captured and restored when the last window stops, andreset_ctrl_c()does the same deliberately for an embedding application that manages SIGINT itself.- Full-repaint recovery —
rtogglesrender(redraw=True), the path that repaints after terminal corruption. - Multi-row picking —
psetspick_size(1–3) viaset_pick_mode(True, size). - Large-body stress —
zappends 2000 non-pickable filler rows to exercise long bodies and the scroll indicator. No option is needed for them: the pads stay viewport-sized, memory andclear()cost do not grow with the row count, and the defaultmax_rowsceiling is far above 2000.
Running it
See Running the Sample App (from source)
above. The sample app is interactive, so it needs a real terminal (any POSIX
box). It is a repo-level script and is not part of the installed package:
after pip install console-window you write your own main() — start from the
sample app above, or from the shape below.
The shape of every app
from console_window import ConsoleWindow, ConsoleWindowOpts
def main() -> None:
# ConsoleWindow.__init__ performs the curses setup (no curses.wrapper needed).
# No row sizing is required; max_rows is only a safety ceiling.
win = ConsoleWindow(opts=ConsoleWindowOpts(head_line=True))
try:
while True:
win.clear()
win.add_header('hello - q or ESC to quit')
for i in range(100):
win.add_body(f'row {i}')
win.render()
key = win.prompt(seconds=0.5) # key code, or None on timeout
if key in (ord('q'), 27):
break
finally:
win.stop_curses()
if __name__ == '__main__':
main()
For anything beyond a single list, subclass Screen and drive it with
ScreenStack — that is what the sample app does, and it is the paradigm the
library is designed around.
ScreenStack.perform_actions() is the normal router: it walks the spinner
options, honours their scopes, and calls the matching <attr>_ACTION method.
For apps that dispatch an action themselves, ScreenStack.act_in(action, screens=None) is the low-level single-flag variant: it clears that flag from
stack.obj and returns whether it was set and the current screen matched.
ScreenStack.is_curr() answers the screen test on its own. A missing flag reads
as False, so an options object need not pre-seed every action.
Singleton vs. per-instance windows
ConsoleWindow supports two process-wide models, selected by the constructor's
singleton argument:
singleton=True(default) — the historical process-wide model. Every window draws through one shared screen,ConsoleWindow.static_scrandConsoleWindow.static_winare populated, and the unboundConsoleWindow.stop_curses()tears that screen down. Callingwin.stop_curses()on any singleton window does the same. A second window reuses the first window's screen and backend.singleton=False— per-instance windows. Each window owns its screen, pads and teardown;win.stop_curses()stops only that window,ConsoleWindow.stop_all()stops every live window, and the unboundConsoleWindow.stop_curses()is an error.
The first successful construction latches the mode for the whole process.
Mixing the two values raises AssertionError rather than silently changing what
teardown means. Use singleton=False in tests and in applications that create
independent windows (for example, one real window plus a headless one).
Versioning and Deprecation
console-window follows semantic versioning: patch releases fix behaviour
without changing the public API, minor releases add functionality, and major
releases may make incompatible changes.
Deprecated APIs remain supported indefinitely. A deprecation is a request to
migrate, not a scheduled removal: deprecated functions and parameters keep
working, and one is removed only when there is significant value in doing so
(for example, the API is actively harmful or blocks a necessary redesign).
Deprecated paths emit a DeprecationWarning where practical, and every removal
is recorded in CHANGELOG.md.
License
MIT
Comparable and Competing Projects
console-window is a niche tool. For almost everyone, one of the projects below is the better choice — reach for console-window specifically when you want a dependency-free, keyboard-driven list/detail viewer in the "pager meets selection engine" style and you are comfortable building your own widgets on top of it.
| Framework | Model | Widgets | Dependencies | Async/threads | Wide chars | Testing | Learning curve | Best for |
|---|---|---|---|---|---|---|---|---|
| console-window | head/body pads + scroll/pick + screen stack | you build views from your own classes; options, search bar, inline confirmation, dialogs | none (stdlib) | sync polling | yes (stdlib cell width) | pytest, TTY-free, render via a fake backend |
low | keyboard list/detail viewers, "less-like" browsers |
| urwid | canvas + widget tree, event loop | rich (ListBox, Tree, Edit, Overlay, Columns/Pile) | wcwidth |
event loops | yes (via wcwidth) | good (examples, docs) | medium | general full-screen apps |
| Textual | DOM + CSS, async, reactive | very rich; mouse, animations, web target | rich, markdown-it, typing-extensions, … |
asyncio-first | yes | strong (pytest harness) | medium–high | modern, styled, ambitious TUIs |
| prompt_toolkit | input-centric full-screen | editing, completion, dialogs; limited layout widgets | wcwidth |
yes | yes | strong | medium | REPLs, shells, rich input |
| npyscreen | forms/fields | form widgets (menus, multisets) | stdlib curses | no | partial | sparse | low | classic form-style admin TUIs |
| py_cui | widget grid | menus, scroll, forms | stdlib curses | no | partial | some | low | quick dashboards |
| asciimatics | screens + effects | widgets + animation engine | pyfiglet, pillow |
no | partial | some | low–medium | animated/demo screens |
| blessed | raw terminfo control | none (primitives) | jinxed (Windows) |
no | partial | some | low | custom low-level drawing |
| Rich | text/print styling (+Live/Layout) | none (not a TUI toolkit) | markdown-it-py, pygments |
partial | yes | strong | low | pretty output/logs, not navigation |
Where console-window wins: zero dependencies; a tiny conceptual surface; excellent selection/paging semantics (abut, pick vs. scroll, decorative-row skipping); a scoped key router; efficient pad blitting; and a model already proven across several real apps.
Where it loses to the big two (urwid / Textual): no widget library, no mouse, no CSS/layout language, no async, no documentation site, single-window only. Textual in particular offers a testing framework and a modern developer experience a solo project cannot match — at the cost of a dependency tree and a very different paradigm.
Closest functional peers: npyscreen (form/list admin TUIs) and py_cui (dashboard grids) — both stdlib-curses, both simpler and less maintained than Textual/urwid. console-window is differentiated by its pager-centric selection model, which those two lack.
Projects Using console-window
For more extensive examples, you can look at some projects using console-window:
- efibootdude
- simple wrapper for
efibootmgr - one of the smaller projects
- demonstrates a context sensitive available keys lines (i.e., first line) where the actions depend on current line.
- simple wrapper for
- dwipe
- disk cleaner with parallel jobs and stamps wiped partitions
- pmemstat
- system/process memory monitor with rollups and hooks for zRAM
- memfo
- viewer of /proc/meminfo that shows historical values
- vappman
- convenince wrapper to mostly hide CLI of AppMan, a life-cycle manager for 2000+ AppImages
Metadata
Release files for console-window 2.0.0.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| console_window-2.0.0.4.tar.gz | 119.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| console_window-2.0.0.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 185.5 kB
Release files / console_window-2.0.0.4.tar.gz
| Download URL | console_window-2.0.0.4.tar.gz |
|---|---|
| Size | 119.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b133777c028e249854a0ad4b78882a2b0aa3c739f8202d85529243c15033f5b1
|
|
BLAKE2b-256 checksum How to use checksums |
d18b1b4ccd23d15a167a19d842c65c2af27439e7d7da574d142d8227856c661b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-requests/2.32.5
|
Release files / console_window-2.0.0.4-py3-none-any.whl
| Download URL | console_window-2.0.0.4-py3-none-any.whl |
|---|---|
| Size | 65.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
59d3c4a852516b5943c7a044b72bad0b4daba0810cbea4ce42da6e30b556325f
|
|
BLAKE2b-256 checksum How to use checksums |
937a0fecf9bdeff9c6df54f76deb5e5de3f5d19d068258f3444b328ba7bea421
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
python-requests/2.32.5
|
Security for console-window 2.0.0.4
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.