2026-05-13 18:48:44 +02:00
TUI
2026-05-11 20:24:40 +02:00
2026-05-13 18:48:44 +02:00
2026-05-12 22:15:07 +02:00
2026-05-12 22:15:07 +02:00

rubbs

A Ruby gem for building telnet BBS servers. Three interaction layers, from simplest to richest:

Layer What it is When to use
BBS::Flow Line-oriented declarative dialogue DSL (ask / say / menu / persist) Login, registration, simple wizards
BBS::TUI Page-router with frame-buffer delta rendering Single-purpose full-screen UIs
BBS::Application Full widget framework: Menubar, Window, Button, TextInput, ListBox, … Multi-window Synchronet-style BBSes

A single session can switch between layers — typically Flow for login then Application for the main shell.

Installation

# Gemfile
gem 'bbs', git: 'https://git.teletype.hu/tools/rubbs.git'

Quick start — Flow + Application

require 'bbs'

MAIN = BBS::Application.define do
  theme BBS::Theme.synchronet

  menubar do
    menu '&File' do
      item '&About' do BBS::Dialogs.message(self, 'Hello from rubbs!') end
      item 'E&xit'  do :halt end
    end
  end

  status_hint('F1', 'Help') { BBS::Dialogs.message(self, 'No help yet.') }
  status_hint('F10', 'Menu') { menubar.open(0) }
end

BBS.configure do |c|
  c.mouse        = true
  c.idle_seconds = 600
  c.flow = BBS::Flow.define do
    big_banner 'MY BBS', style: :success
    ask :username, prompt: 'Name'
    app MAIN
    say 'Goodbye!'
  end
end

BBS.start

Connect with telnet localhost 2323.

Configuration

BBS.configure yields a BBS::Config:

Option Default Description
flow A BBS::Flow object (required)
port ENV["BBS_PORT"] || 2323 TCP port
mouse false Enable xterm SGR mouse reporting
idle_seconds nil Disconnect after N idle seconds (nil = never)
theme Default theme for Applications (overridable per app)
screens_dir nil Directory for ERB screens used by BBS::Flow#screen
on_session_end nil Lambda called when a session ends

The Application layer

BBS::Application is a widget event loop with:

  • a root container drawn behind windows;
  • an optional Menubar with Alt-letter pulldown menus and F10 access;
  • an optional Statusbar with clickable hotkey hints;
  • a stack of modal Windows (the top one receives input first);
  • xterm SGR mouse events delivered to widgets;
  • a 1 Hz internal tick for chat polling, clocks, etc.

Built-in widgets (BBS::Widgets)

Widget Purpose
Label Static text, optionally aligned, themed via style_key
Button &Save-style hotkey, fires on_click(self); mouse-clickable
TextInput Single-line input with cursor, scroll, on_submit/on_change
TextArea Multi-line editor with arrow-key navigation
Checkbox Toggle with on_change(checked, self)
ListBox Scrolling list with mouse + keyboard, wheel scrolling
ScrollView Read-only scrolling text view, with scrollbar
ProgressBar, Spinner Progress feedback
Panel, HBox, VBox Containers / layout
Window Top-level modal container with title bar, close gadget, shadow

Built-in dialogs (BBS::Dialogs)

BBS::Dialogs.message(app, "Saved!", title: ' Done ')
BBS::Dialogs.confirm(app, "Delete?", on_yes: -> { ... })
BBS::Dialogs.input(app, prompt: 'Name?', on_submit: ->(value) { ... })

Menubar DSL

menubar do
  menu '&File' do
    item '&Open', shortcut: 'Ctrl-O' do  end
    separator
    item 'E&xit' do :halt end
  end
end

The block runs in the Application's context (so you can call open_window, close_window, cols, rows, etc.). Return :halt from any handler to end the session.

Theming

BBS::Theme.synchronet (default) is a yellow/cyan Synchronet-inspired palette. BBS::Theme.mono provides a colour-free fallback. Create your own with BBS::Theme.new(:my_theme, { window_border: "\e[1;36m", … }).

The Flow layer

The DSL for line-based dialogue. Available steps:

Step Purpose
screen :name Render an ERB screen from screens/
banner, big_banner Box-drawing / FIGlet ASCII art header
say, text, section, line, body Text output
ask :field, set :field, value, persist :field, store: Input + persistence
pause, wait_enter, gate, confirm Control flow
menu, exit_menu Numbered option menus
fetch, pick Data fetch + list selection
rows, table Formatted output
call { |ctx, runner| … } Arbitrary code; return :halt to end
tui DEFINITION Hand off to a BBS::TUI page router
app DEFINITION Hand off to a BBS::Application widget app

Input model

Session#readkey(timeout:) returns one of:

  • a single-character String (printable input)
  • a Symbol: :enter, :tab, :shift_tab, :backspace, :escape, :up, :down, :left, :right, :home, :end, :page_up, :page_down, :insert, :delete, :f1:f12, :ctrl_a:ctrl_z, :alt_a … (any printable character preceded by ESC), :interrupt, :idle
  • a BBS::Telnet::MouseEvent with kind, button, x, y, mods — delivered when BBS.config.mouse = true

Stores

BBS::Store is a thread-safe upsert-by-session_id CSV file with an in-memory cache; appends are O(1), full rewrites only on update.

Architecture

File Responsibility
bbs/server.rb TCP accept loop, one thread per client
bbs/session.rb Telnet negotiation, mouse enable/disable, idle timer
bbs/telnet.rb Telnet protocol, full key parsing, xterm SGR mouse
bbs/flow.rb, bbs/flow_runner.rb Flow DSL + interpreter
bbs/tui.rb, bbs/tui_runner.rb Page-router with frame-buffer rendering
bbs/frame_buffer.rb Double-buffer, delta diff, drawing helpers
bbs/application.rb Widget event loop, window stack, ticking
bbs/widget.rb, bbs/container.rb Widget tree base
bbs/widgets.rb Built-in widgets
bbs/window.rb, bbs/menu.rb, bbs/statusbar.rb, bbs/dialogs.rb Chrome
bbs/focus_manager.rb Tab/Shift-Tab focus traversal
bbs/theme.rb Style palette (synchronet / mono)
bbs/event.rb Unified key + mouse event objects
bbs/store.rb Thread-safe cached CSV upsert
bbs/banner.rb, bbs/renderer.rb FIGlet + ERB
bbs/color.rb Simple ANSI colour helper
Description
No description provided
Readme 268 KiB
Languages
Ruby 100%