Skip to content

Latest commit

 

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SimpleTerminalPy

English | 简体中文

A terminal emulator for embedded Linux handheld consoles, rewritten in Python + PySDL2 + PIL, ported from SimpleTerminal (C/SDL2 version).

  • Full VT100 escape sequence parser (30+ CSI commands)
  • 256 colors + SGR attributes (bold / underline / reverse / blink)
  • 24-bit truecolor (38;2/48;2) exact RGB rendering
  • Bracketed paste (DEC 2004) — safe multi-line paste in vim/bash/tmux
  • Interactive PTY shell (vim, top, htop, and more)
  • On-screen keyboard (OSK)
  • Pinyin IME on the OSK — Chinese input via pinyin composition (🌐 key, 1-9 candidate select, −/+/L1/R1 paging)
  • Scrollback buffer (256 lines)
  • CJK double-width rendering (bundled fonts, Apache 2.0)
  • Key calibration wizard — adapts to any handheld automatically

Screenshots

Screenshot 1

Screenshot 2

Screenshot 3

Screenshot 4


Running

# Via launcher script (put in APPS dir)
./SimpleTerminalPy.sh

# Directly
python3 main.py

# Force re-calibrate keys
python3 main.py -reset-keymap

# Command line options
python3 main.py [-font font.ttf] [-fontsize 12] [-rotate 0|90|180|270]
                [-term xterm-256color] [-reset-keymap] [-r "command"]

Dependencies: python3, pysdl2, Pillow (SDL2 system libraries required)

Minimum resolution: 640×480 (landscape) — constrained by the OSK width (608px) and the key guide page


Fonts

Bundled fonts (no system font dependency):

fonts/
├── DroidSansMono.ttf          # Monospace Latin (primary)
├── DroidSansFallbackFull.ttf  # CJK (Chinese/Japanese/Korean)
└── LICENSE.txt                # Apache License 2.0

Both fonts are from Google's Android Open Source Project, licensed under Apache 2.0 — safe to redistribute. The renderer falls back to system fonts (DejaVu Sans Mono, device firmware fonts, etc.) if the bundled ones are missing.

Key Calibration (first launch)

On first launch (or with -reset-keymap), the key calibration wizard appears:

Key Calibration 1

Key Calibration 2

        KEY SETUP

    Press [UP]  (1/15)
    ...
    Short press = confirm | Hold 3s = abort
Action Behavior
Short press (press & release) Confirm current key → record → next
Hold 3 seconds Abort calibration, exit without saving
Press an already-assigned key Ignored, wait for a different key

Calibration order: UP → DOWN → LEFT → RIGHT → A → B → X → Y → MENU → SELECT → START → L1 → R1 → L2 → R2

Results are saved to key_map.json (next to the program):

{
  "keys": {
    "up":    {"type": "hat", "value": 1, "device": 0},
    "x":     {"type": "key", "value": 307, "device": 1},
    ...
  }
}
  • type: btn (joystick button) / hat (D-pad) / key (keyboard channel) / cbtn (game controller)
  • device: source device ID. Handheld channels (btn/hat/cbtn) record the real joystick device ID. The key channel uses a fixed KBD_DEVICE constant — SDL2 keyboard events carry no device ID, so key-channel buttons can't be distinguished from a Bluetooth keyboard by device

Button Functions

Physical buttons are defined by the player during calibration; the table below lists logical key functions.

Global (active in any mode)

Button Function
MENU Quit program
X Show / hide OSK
Y Toggle OSK position (bottom / top)
L1 + R1 Delete key_map.json (re-calibrate on next start)

When OSK is visible

Button Function
D-Pad (UP/DOWN/LEFT/RIGHT) Move OSK cursor (hold to repeat)
A Press the selected OSK key
B Backspace
L1 Hold-shift (hold = uppercase, release = lowercase)
R1 Sticky modifier (select Ctrl/Alt/⇧ key, then press R1 to lock/unlock)
L2 Left arrow (pass-through to terminal, works in vim)
R2 Right arrow (pass-through to terminal, works in vim)
START Enter
SELECT Tab

When OSK is hidden (pass-through mode — vim/htop/top, etc.)

Button Function
D-Pad (UP/DOWN/LEFT/RIGHT) Arrow keys
A Enter
B Ctrl+C
SELECT Tab
L2 Scrollback up 3 lines
R2 Scrollback down 3 lines

Physical keyboard (USB / Bluetooth)

Key Function
Letters / digits / symbols Normal input (IME composition via SDL_TEXTINPUT)
Arrows / Home / End / PageUp / PageDown / F1-F12 Standard escape sequences
Ctrl + letter / symbol Control characters (Ctrl+C, Ctrl+D, Ctrl+\ = 0x1C, Ctrl+Space, etc.)
Tab / Shift+Tab Tab / reverse tab
Enter / Alt+Enter Return / ESC+Return
Ctrl+Shift+V Paste from clipboard

Handheld buttons are isolated from Bluetooth keyboards by event type: btn/hat/cbtn come from the joystick device and never collide with keyboard events. The key channel shares SDL keyboard events with any attached keyboard (SDL2 provides no per-keyboard device ID), which only matters if a handheld button was calibrated on the key channel.

In pinyin mode: L1 = candidate previous page, R1 = candidate next page (same as − / +).


Using the OSK

  • A presses the highlighted key
  • L1 hold for uppercase, release to return to lowercase
  • R1 sticky modifiers: move cursor to Ctrl / Alt / and press R1 to lock, then press letters for combos (e.g. Ctrl+A); press R1 again to unlock
  • R1-locked Shift acts as Caps Lock (L1 does not affect a locked shift)
  • Symbol layer via the #+= key on the OSK, ABC to return
  • X shows / hides the OSK anytime

Pinyin input (Chinese)

  • Press the 🌐 key (labeled ) at the bottom-left to enter pinyin mode (EN label), press again to return
  • Type pinyin letters → candidates appear above the number row; 1-9 selects, − / + / L1 / R1 pages
  • ⌫ / B smart backspace: deletes the composition first, passes to the terminal when empty
  • Enter / START commits the raw pinyin when nothing matched
  • Space / comma / period are swallowed while composing (backspace would otherwise be unable to delete them)
  • Candidate frequency ordering from Jun Da's character frequency list; the dictionary (pinyin_dict.json) is generated by generate_pinyin_dict.py (pypinyin + Jun Da list)

Scrollback

  • L2 / R2: scroll up / down 3 lines (when OSK is hidden)
  • [N]^ indicator in the top-right corner while scrolled
  • Pressing any other key returns to the bottom

File Structure

SimpleTerminalPy/
├── main.py              # Main loop + event dispatch + startup flow
├── config.py            # Color palette (259 colors) + defaults
├── terminal.py          # Glyph / Term / Cursor data model
├── vt100.py             # VT100 state machine (30+ CSI commands)
├── pty_handler.py       # PTY creation + select reader thread
├── renderer.py          # PIL dirty-line incremental renderer + glyph LRU cache
├── osk_mgr.py           # OSK manager facade (language switch + press dispatch)
├── osk/                 # Language keyboards
│   ├── osk_base.py      #   Shared UI core (navigation / rendering / cache)
│   ├── osk_en.py        #   English keyboard (layouts + modifier semantics)
│   └── osk_pinyin.py    #   Pinyin keyboard (composition / candidates / IME)
├── pinyin_ime.py        # Pinyin dictionary lookup (prefix match + paging)
├── pinyin_dict.json     # Pinyin → hanzi dictionary (generated, ~650KB)
├── generate_pinyin_dict.py  # Dictionary generator (pypinyin + Jun Da freq list)
├── input_handler.py     # Event (type, value, device) → logical key
├── key_calibrate.py     # Key calibration wizard + key guide screen
├── wcwidth.py           # Unicode East Asian Width
├── fonts/               # Bundled fonts (Apache 2.0)
│   ├── DejaVuSansMono.ttf        # Monospace primary (symbols)
│   ├── DroidSansMono.ttf         # Monospace fallback
│   ├── DroidSansFallbackFull.ttf # CJK
│   └── LICENSE.txt
├── screenshots/         # README screenshots
├── tests/               # unittest suite (93 tests)
│   └── test_simple_terminal_py.py
├── key_map.json         # Player calibration result (generated at runtime)
├── SimpleTerminalPy-Raw.sh  # Source launcher (rename to SimpleTerminalPy.sh in APPS)
├── SimpleTerminalPy-Bin.sh  # Binary launcher (bundled into release package)
├── build_release.sh     # PyInstaller onedir packaging script
├── sync_raw_to_app.sh   # Sync source code to SD card
└── sync_bin_to_app.sh   # Sync binary package to SD card

Syncing to the Console

# Sync source code to /mnt/sdcard/Roms/APPS/SimpleTerminalPy
# Automatically clears __pycache__ and key_map.json (re-calibrate on next start)
bash sync_raw_to_app.sh

# Sync the packaged binary instead (requires build_release.sh first)
bash sync_bin_to_app.sh

Known Limitations

  • Color emoji not supported (reasonable trade-off for embedded terminals)
  • Possible brief frame drops on extreme full-screen refreshes (e.g. vim initial startup)
  • Bold/italic SGR attributes render without glyph variation (bold colors are brightened, matching the C version)
  • Pinyin IME: single-character candidates only (no multi-syllable word matching yet; type ni → select → hao → select)

License

MIT License — based on SimpleTerminal (MIT)

About

A simple terminal emulator that can run on modern Linux handheld gaming devices, based on the SimpleTerminal project, rewritten in Python and PySDL2.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages