flet-terminal
Native GPU-accelerated terminal control for Flet apps.
A 1:1 wrapping of the xterm.dart terminal engine, in the same architecture as flet-ads. One uv add flet-terminal covers Windows, Linux, macOS, Android and Web — no Python dependencies beyond flet (CI-verified on Flet 1.0.0 through the latest 1.0.x).
The control is view-only by design: it renders and streams bytes over a DataChannel; your app owns the child process (a local pty, an SSH/WebSocket session, anything). That separation is what makes the same package work in a desktop demo, in CollabShell (remote Colab pty), and in local-proot mobile apps.
Test it without an account
Every release builds the demo app for every platform. Sideload or unpack any of them and every button is live:
| Variant | Download | Notes |
|---|---|---|
| 📱 ARM64 (most phones/TVs) | fletterminal-arm64-v8a.apk | Modern 64-bit Android devices — grab this one unless you know otherwise |
| 📱 ARMv7 (older phones) | fletterminal-armeabi-v7a.apk | Legacy 32-bit Android devices |
| 💻 x86_64 (emulators/ChromeOS) | fletterminal-x86_64.apk | Android emulators & Chromebooks |
| 🪟 Windows | FletTerminal_windows_x64.zip | Portable zip |
| 🐧 Linux | FletTerminal_linux_x86_64.tar.gz | Universal tarball |
Plain flet run launches the prebuilt client without the extension's Dart half — the terminal needs a built app (an artifact above, or flet build <platform>).
Platform Support
| Platform | Windows | Linux | macOS | Android | iOS | Web |
|---|---|---|---|---|---|---|
| Supported | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
The control itself is pure Dart rendering (xterm.dart), so it runs wherever Flet runs. The demo app's local-pty service uses POSIX openpty (Windows demo uses WinPTY transport); mobile hosts own their own process/session layer.
Usage
uv add flet-terminal
The recommended entry point is the MobileTerminal composite — terminal + search bar + virtual extra-keys bar with sticky CTRL/ALT and zoom/settings:
import flet as ft
from flet_terminal import MobileTerminal, BUILTIN_THEMES
@ft.component
def App():
page = ft.context.page
mt, _ = ft.use_state(lambda: MobileTerminal(
show_extra_keys=True,
show_search=False,
scrollback=10000,
font_family="JetBrains Mono",
theme=BUILTIN_THEMES["JetBrains Dark"],
))
# Your process layer: forward terminal bytes to a pty/SSH/kernel here.
mt.set_on_bytes(lambda payload: mt.send_bytes(payload))
return ft.Column(controls=[mt], spacing=0, expand=True)
def main(page: ft.Page):
page.title = "My Terminal"
page.theme_mode = ft.ThemeMode.DARK
page.padding = 0
page.render(App)
ft.run(main)
MobileTerminal constructs anywhere — inside a component tree, in a plain main(), or from a background task; no renderer context needed.
For just the viewport, use Terminal directly:
import flet as ft
from flet_terminal import Terminal, BUILTIN_THEMES
def main(page: ft.Page):
term = Terminal(scrollback=10000, theme=BUILTIN_THEMES["Dracula"], expand=True)
def on_terminal_input(data: bytes):
term.send_bytes(data) # echo / forward to your process layer
term.set_on_bytes(on_terminal_input)
page.add(term)
term.write("\x1b[1;32mWelcome to FletTerminal!\x1b[0m\r\n> ")
ft.run(main)
Controls
Terminal Properties
| Property | Type | Default | Description |
|---|---|---|---|
scrollback |
int | None |
10000 |
Maximum number of scrollback lines retained in the ring buffer. |
font_family |
str | None |
"JetBrains Mono" |
Monospace font family for rendering text. |
font_size |
float | None |
11.0 |
Font point size. |
cursor_blink |
bool | None |
True |
Whether the terminal cursor blinks (real Dart-side timer). |
cursor_style |
str | None |
"block" |
Cursor shape: "block", "underline", or "bar". |
theme |
dict | None |
None |
Dictionary mapping ANSI color keys to hex colors. |
read_only |
bool | None |
False |
When True, disables user keyboard input (output still renders). |
auto_focus |
bool | None |
True |
Automatically focuses the terminal when mounted. |
ctrl_active / alt_active |
bool | None |
False |
Sticky modifier states, synced bidirectionally with Dart. |
Terminal Methods
| Method | Arguments | Description |
|---|---|---|
send_bytes(payload) |
bytes |
Sends binary data over the DataChannel to the terminal canvas (queued until the channel opens). |
send_input(payload) |
bytes |
Local loopback to the registered set_on_bytes handler (virtual-key path). |
write(data) |
str | bytes |
Writes text or escape sequences to the terminal. |
clear() |
— | Clears the scrollback and visible screen buffer. |
focus() |
— | Requests keyboard focus. |
search(query, start, direction) |
str, int, str |
Highlights/selects matches in the scrollback; total count arrives via on_selection_change. |
get_selection_async() |
— | Returns the current selection text (or None when not ready — request/response call, never queued). |
select_all() / clear_selection() |
— | Selection helpers. |
paste() / paste_async() |
— | Reads the clipboard on the Dart side and feeds it over the channel/data event. |
Event handlers
| Event | Handler | Description |
|---|---|---|
on_data |
Callable[[ft.ControlEvent], None] |
String-based terminal input (fallback path when no DataChannel handler matches). |
on_resize |
Callable[[ft.ControlEvent], None] |
Dimensions changed. Event data is JSON {"cols": int, "rows": int} — forward it as TIOCSWINSZ to your pty. |
on_modifier_reset |
Callable[[ft.ControlEvent], None] |
A sticky modifier was consumed by a hardware keystroke. |
on_title_change |
Callable[[ft.ControlEvent], None] |
OSC 0/2 title sequence received. |
on_bell |
Callable[[ft.ControlEvent], None] |
Bell character (\a / 0x07) received. |
on_selection_change |
Callable[[ft.ControlEvent], None] |
Selection updated, or search results reported ({"query","found","count","index"}). |
on_copy |
Callable[[ft.ControlEvent], None] |
Text copied (secondary-tap or copy_selection). |
on_mount |
Callable[[ft.ControlEvent], None] |
Dart view (re)created — replay your scrollback here. |
on_shortcut |
Callable[[ShortcutEvent], None] |
Built-in host shortcut consumed pre-PTY; e.shortcut carries the machine name. Active only when a handler is set. |
on_data_channel_open |
internal | Wires the pty DataChannel; self-installed. |
Built-in shortcuts (on_shortcut)
| Combination | e.shortcut value |
|---|---|
Ctrl/Cmd+Shift+T |
new_terminal |
Ctrl/Cmd+Shift+W |
close_terminal |
Ctrl/Cmd+Shift+1 .. 9 |
switch_terminal_1 .. switch_terminal_9 |
Ctrl/Cmd+Shift+F |
toggle_search |
Ctrl/Cmd+Shift+L |
clear |
Ctrl/Cmd+Shift+C / +V |
copy / paste |
Ctrl/Cmd+Shift+= / - / 0 |
zoom_in / zoom_out / zoom_reset |
Ctrl/Cmd+PageUp / PageDown |
prev_terminal / next_terminal |
F1 |
help |
MobileTerminal Additional Methods & Properties
| Member | Description |
|---|---|
set_theme(name) |
Switch to a built-in theme by name (unknown names are a no-op). |
set_cursor_style(style) / toggle_cursor_blink() |
Cursor appearance controls. |
zoom_in(step=1.0) / zoom_out(step=1.0) / reset_zoom() |
Font-size controls (floor 6.0 px). |
toggle_search() / show_search |
Built-in search bar visibility. |
copy_selection() / paste() / select_all() / clear_selection() |
Clipboard & selection helpers. |
send_bytes / send_input / write / clear / focus / search / set_on_bytes |
Forwarded from Terminal. |
ready / pending_count / clear_pending() / flush() |
DataChannel queue introspection (0 pending after reconnect? replay or drop). |
set_on_unmount(callback) |
Fired when the Dart view is torn down. |
font_size / cursor_blink / cursor_style / theme_name |
Read-only state mirrors (theme_name is None for custom palettes). |
ctrl_active / alt_active |
Sticky modifier state (settable; buttons repaint automatically). |
ExtraKeysBar Constructor
| Parameter | Type | Default | Description |
|---|---|---|---|
on_send_payload |
Callable[[bytes], None] |
required | Called with the final byte payload after modifier processing. |
on_modifier_change |
Callable[[bool, bool], None] |
required | Called with (ctrl, alt) when sticky state changes. |
show_settings |
bool |
True |
Show the gear-icon settings popup. |
on_set_theme / on_set_cursor / on_toggle_blink / on_toggle_search |
callbacks | None |
Settings-menu callbacks. |
on_copy / on_paste / on_select_all / on_clear |
callbacks | None |
Clipboard/buffer callbacks. |
keys |
list[tuple[str, bytes | None]] |
DEFAULT_EXTRA_KEYS |
Custom key layout. A None payload marks a sticky modifier (CTRL/ALT). |
The default layout: ESC, TAB, CTRL, ALT, ↑ ↓ ← →, - / | ~ ^. Sticky CTRL transforms a→\x01 (case-folded via code−96/code−64); sticky ALT prefixes ESC — the mirror of the Dart-side hardware-key transform.
Configuration
Ctrl/Alt stickiness, themes, zoom, cursor and the search bar are all reachable from the gear menu (show_settings=True) and mirrored in the corresponding Python API above — pick whichever layer your app prefers.
Limitations
- View-only by design. No spawn/pty API: the package renders bytes and reports events; your app owns the process (local
pty.openpty, remote WebSocket, kernel client…). The channel is namedptyfor transport reasons only. - Built-in themes are plain ANSI dictionaries. Dart accepts extra keys (
brightBlack…brightWhite,searchHit*) that fall back to the default theme; the fourBUILTIN_THEMESship exactly twelve keys each. get_selection_asyncnever queues (request/response): it returnsNonewhile the Dart view is not ready — retry afteron_mount.- Search is linear over the scrollback (xterm has no index); the match counter reflects the Dart-side count.
Example
The standalone demo (all artifacts above) lives at examples/flet_terminal_example — a full MobileTerminal + local-pty app, cross-platform.
Frozen-Control Support
Flet 1.0's declarative renderer stamps _frozen = True on component-rendered controls. Imperative mutations then raise RuntimeError: Frozen controls cannot be updated. flet_terminal handles this internally via thaw(); all MobileTerminal/ExtraKeysBar/TerminalSearchBar mutations already use it — no extra wrapping needed by consumers. If you build your own long-lived controls in a declarative tree, the pattern is:
from flet_terminal.frozen_support import thaw
with thaw(some_control):
some_control.style = new_style
some_control.update()
License
This project is licensed under the MIT License. See LICENSE for details.
Metadata
Release files for flet-terminal 0.4.1
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 | |
|---|---|---|---|---|
| flet_terminal-0.4.1.tar.gz | 40.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flet_terminal-0.4.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 71.8 kB
Release files / flet_terminal-0.4.1.tar.gz
| Download URL | flet_terminal-0.4.1.tar.gz |
|---|---|
| Size | 40.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e165cef16d36c5447321f009016acce677e042dea9b4f6c3670a4a37a6f30b4c
|
|
BLAKE2b-256 checksum How to use checksums |
494543b1b1ec3953e8190e054e4e4056a3d00d1c6bb913690e2496253b568a28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logRelease files / flet_terminal-0.4.1-py3-none-any.whl
| Download URL | flet_terminal-0.4.1-py3-none-any.whl |
|---|---|
| Size | 31.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3249b3a71d4c7707c8ed20edfdcbe53973e89dfe159ced576ac0fdcfced9f122
|
|
BLAKE2b-256 checksum How to use checksums |
137bf79c5e30ff74242b12d03c963df6795092325b12ab60a3bcdb4d90fd2492
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logSecurity for flet-terminal 0.4.1
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.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for flet_terminal-0.4.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.
Transparency logA 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.