Commit Graph

40 Commits

Author SHA1 Message Date
f62084eac7 ci: gate publish jobs on commit message deploy directives
Some checks are pending
Publish / backend (push) Waiting to run
CI / fmt (push) Successful in 33s
Publish / frontend (push) Successful in 45s
CI / clippy (push) Successful in 1m43s
CI / check (push) Successful in 1m44s
Publish jobs now only run when the commit message contains:
- "deploy: frontend" — deploys frontend only
- "deploy: backend" — deploys backend only
- "deploy: backend, frontend" or "deploy: frontend, backend" — both

Case-insensitive matching. Commits without a deploy directive
will only run CI (check/fmt/clippy), not publish.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 13:24:44 +03:00
edb6853e3a fix: switch overlay dismissal and port dropdown selection persistence
All checks were successful
CI / fmt (push) Successful in 34s
CI / check (push) Successful in 1m14s
CI / clippy (push) Successful in 1m19s
Publish / frontend (push) Successful in 52s
Publish / backend (push) Successful in 2m36s
- Overlay now reliably dismissed: use window.setTimeout and inline
  overlay hide instead of separate function that could lose DOM ref
- Minimum 1s duration to prevent flash for short hotkey sequences
- Port dropdown preserves user's selection across page refreshes
  by tracking selectedPort locally instead of always using device's
  active_port
- Prevent duplicate change listener on port select across remounts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 13:20:30 +03:00
2e6f80f9ac feat: suspend input during port switch with overlay and countdown
All checks were successful
CI / fmt (push) Successful in 34s
Publish / frontend (push) Successful in 55s
CI / clippy (push) Successful in 1m21s
CI / check (push) Successful in 1m37s
Publish / backend (push) Successful in 2m51s
When switching ports via the console dropdown:
- Input (keyboard, mouse, wheel) is suspended immediately to prevent
  stray events from interfering with the OSCAR hotkey sequence
- A semi-transparent overlay with spinner and countdown timer appears
  over the console canvas
- Duration is calculated from the actual key_pause_duration setting
  multiplied by the number of * pause tokens in the port's hotkey,
  plus a 500ms buffer
- Input resumes and overlay disappears when the timer expires

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 13:11:47 +03:00
7406b4ac02 feat: auto-resume session on page refresh
All checks were successful
CI / fmt (push) Successful in 35s
Publish / frontend (push) Successful in 48s
CI / check (push) Successful in 1m28s
CI / clippy (push) Successful in 1m25s
Publish / backend (push) Successful in 2m42s
On load, check sessionStorage for stored credentials (saved during
login). If present, auto-authenticate and skip the login form. Falls
back to showing login if the stored credentials fail.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 12:19:03 +03:00
35db634317 fix: hotkey-based port switching and HTML entity unescaping
All checks were successful
CI / fmt (push) Successful in 36s
Publish / frontend (push) Successful in 44s
CI / check (push) Successful in 1m23s
CI / clippy (push) Successful in 1m36s
Publish / backend (push) Successful in 2m45s
Three fixes for KVM port management:

1. HTML unescape scraped values — the > character in hotkey strings
   (e.g., PrintScreen>0>1) gets entity-encoded to &gt; in the device's
   HTML. Added html_unescape() to the scraper so hotkeys round-trip
   correctly.

2. Send hotkeys over WebSocket — port switching via the Belkin web form
   only changes the active port number, it doesn't send the hotkey
   sequence to the downstream KVM. Now when switching ports from the
   console dropdown, blekin parses the Belkin hotkey syntax and sends
   the key press/release sequence over the existing WebSocket connection.

3. New hotkey.ts parser — converts Belkin hotkey syntax to scancode
   sequences:
   - > and -> = sequential (tap each key)
   - + = simultaneous (hold all, release in reverse)
   - Supports all key names: PrintScreen, Ctrl, Alt, Shift, F1-F12,
     letters, digits, navigation keys, numpad

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 12:13:57 +03:00
9bd215356b fix: add __templates__ and image button coords to KVM form submissions
All checks were successful
CI / fmt (push) Successful in 34s
Publish / frontend (push) Successful in 46s
CI / clippy (push) Successful in 1m21s
CI / check (push) Successful in 1m32s
Publish / backend (push) Successful in 2m44s
The Belkin firmware requires:
1. A hidden __templates__ field in all form POSTs
2. Image button actions submitted as name.x=0&name.y=0 (not name=value)
3. ECG_kvm_powerport_cnt hidden field

Without these, the device returns "Permission denied" on port config
saves. Also fixed the switch endpoint with the same pattern.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 11:50:38 +03:00
d503742542 fix: port count change immediately updates table rows
Some checks failed
CI / fmt (push) Failing after 39s
Publish / frontend (push) Successful in 47s
CI / clippy (push) Successful in 1m44s
CI / check (push) Successful in 1m47s
Publish / backend (push) Successful in 2m50s
When the port count dropdown changes, the table now re-renders with
the new number of rows, preserving existing values for ports that
were already visible. This allows configuring ports > 16 after
switching to a higher port count.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 11:38:52 +03:00
63aa9a400f feat: Send Key dropdown for browser-intercepted keys
Some checks failed
CI / fmt (push) Failing after 40s
Publish / frontend (push) Successful in 44s
CI / check (push) Successful in 1m21s
CI / clippy (push) Successful in 1m45s
Publish / backend (push) Successful in 2m51s
Adds a "Send Key" dropdown menu to the console toolbar for keys that
browsers intercept before JavaScript can capture them (Print Screen,
Scroll Lock, Pause/Break). Also includes Escape, Tab, Caps Lock,
Num Lock, and Ctrl+Alt+Del.

Each menu item sends a press+release scancode pair directly over the
WebSocket, bypassing browser key capture entirely. This enables
activating downstream KVM switch menus (e.g., Avocent OSCAR via
Print Screen) from the blekin interface.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 11:32:10 +03:00
ea18d97aa6 feat: KVM port management — configuration, switching, and navigation shell
Some checks failed
CI / fmt (push) Failing after 43s
Publish / frontend (push) Successful in 44s
CI / check (push) Successful in 1m20s
CI / clippy (push) Successful in 1m41s
Publish / backend (push) Successful in 2m49s
Backend (crates/ericrfb-proxy):
- Session cookie now persisted in AppState for device API calls
- New kvm.rs with three REST endpoints:
  GET /api/kvm/ports — scrapes kvm.asp, returns port config as JSON
  PUT /api/kvm/ports — saves port names, hotkeys, visibility, count
  POST /api/kvm/switch — switches active KVM port via home2.asp
- HTML scraping extracts form values from predictable firmware HTML

Frontend (crates/ericrfb-frontend):
- New shell.ts: sidebar navigation with page routing pattern
  (Console, Ports — extensible for Virtual Media, Users, etc.)
- Console refactored into pages/console.ts with mount/unmount lifecycle
  - Port switcher dropdown in toolbar (fetches port list, switches on change)
  - WebSocket auto-reconnects after port switch
- New pages/ports.ts: editable port configuration table
  - Port count, key pause duration, per-port name/hotkey/show-in-console
  - Save, reload, and per-port switch buttons
  - Active port highlighted
- Dark theme sidebar with active state indicators

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 11:18:36 +03:00
dd029c7f93 doc: port mapping implementation planning 2026-05-07 11:13:36 +03:00
75a51def79 fix: correct keyboard scancode mapping from KeyTranslator.java
All checks were successful
CI / fmt (push) Successful in 39s
Publish / frontend (push) Successful in 45s
CI / check (push) Successful in 1m22s
CI / clippy (push) Successful in 1m38s
Publish / backend (push) Successful in 2m48s
The mapping was built assuming keynr followed physical keyboard order
with Escape=0. In reality, KeyTranslator.java maps Java VK_* codes to
keynr values with a different layout:

- keynr 0 = Backquote (not Escape)
- keynr 59 = Escape
- keynr 27 = Enter (not 40)
- keynr 40 = Backslash (not 42)

This caused the number row and QWERTY row to be off by 1 (Escape
was inserted at position 0, pushing everything). The home row
(CapsLock=28, A=29...L=37) happened to align by coincidence, which
is why 'g'(33) worked but 'r'(should be 18, was 19=T) didn't.

Also fixes: F1-F12 off by 1, PrintScreen/ScrollLock/Pause off by 1,
numpad operator keys swapped with numpad 7/8/9.

Corrected both Rust (input.rs) and TypeScript (input.ts) mappings
to match the authoritative KeyTranslator.java VK→keynr table.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 10:42:02 +03:00
4f7d69c75a fix: Tight palette selector 0 reads two separate color bytes
All checks were successful
CI / fmt (push) Successful in 46s
Publish / frontend (push) Successful in 51s
CI / check (push) Successful in 1m28s
CI / clippy (push) Successful in 1m35s
Publish / backend (push) Successful in 2m45s
When pal_selector=0 (no predefined palette), the Java code reads two
separate full bytes for the 2-color palette (line 421-422 of
ByteColorRFBRenderer.java). We were reading one packed byte and
splitting into nibbles, consuming 1 byte too few and misaligning all
subsequent reads including the zlib compressed data.

This caused "deflate decompression error" on stream 1 because the
zlib header (78 da) was offset by 1 byte.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 09:55:21 +03:00
acf99f849b feat: phase 9 — encoding 9 (IIP) with tile-versioned delta cache
All checks were successful
CI / check (push) Successful in 1m22s
Publish / frontend (push) Successful in 43s
CI / fmt (push) Successful in 54s
CI / clippy (push) Successful in 1m40s
Publish / backend (push) Successful in 2m27s
codec/iip.rs:
- TileCache: (fb_width/16 × fb_height/16) tiles, 8 versions × 256 bytes
  each, matching t.java's (8, 16*16) allocation
- TileEntry: versioned read/write at byte offsets within tile data
- decode_iip() handles all 4 modes from the control byte:
  - Mode 0/12 (cache-read): tile control bytes select which cached
    version of each 16x16 tile to display, no new pixel data on wire
  - Mode 4 (write-only): Tight-decoded pixel data written to cache
  - Mode 8 (update+read): conditionally writes new data to cache
    (bit 7 of control byte = 0 means update), then reads from cache
- Tile control bytes compressed via zlib (varint length) when >= 12
- Sub-types 1-4/8 map to bit-depths 1/2/4/4/8 bpp
- Cache resized on framebuffer resize (ModeChange msg 128)

Wired into session dispatch for encoding 9. Not advertised in default
encoding list — only active if explicitly requested.

codec/tight.rs: made get_or_init() pub for IIP's zlib access.

39 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 09:39:49 +03:00
e39555196d chore: deployment todos 2026-05-07 09:23:39 +03:00
c31508f138 feat: phase 10 — reconnection, encoding 10, disconnect button
All checks were successful
Publish / frontend (push) Successful in 42s
CI / fmt (push) Successful in 44s
CI / check (push) Successful in 1m43s
CI / clippy (push) Successful in 1m43s
Publish / backend (push) Successful in 2m47s
Frontend reconnection:
- WebSocket auto-reconnects with exponential backoff (1s → 30s)
- Re-authenticates with OmniView to get fresh APPLET_ID on reconnect
- Credentials stored in sessionStorage for automatic re-login
- Status bar shows connection state and reconnect countdown
- Disconnect button returns to login screen

Encoding 10 (Raw with tile interleave):
- codec/raw_tile.rs: decodes encoding 10 per ByteColorRFBRenderer.for()
- Flag byte bit 0 selects plain raw vs 16x16 tile-interleaved data
- Deinterleave handles edge tiles smaller than 16x16
- Wired into session dispatch
- 2 unit tests

39 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 09:21:21 +03:00
865a08da17 chore: setup backend environment 2026-05-07 09:08:28 +03:00
3ba05bcb05 chore: setup hosting environment 2026-05-07 08:41:16 +03:00
2539a1fd06 ci: ssh resolved
All checks were successful
CI / fmt (push) Successful in 43s
Publish / frontend (push) Successful in 58s
CI / clippy (push) Successful in 1m31s
CI / check (push) Successful in 1m34s
Publish / backend (push) Successful in 2m36s
2026-05-06 18:50:33 +03:00
ee4b0a2124 ci: debug ssh
All checks were successful
CI / fmt (push) Successful in 38s
CI / check (push) Successful in 1m52s
CI / clippy (push) Successful in 1m51s
Publish / frontend (push) Successful in 37s
Publish / backend (push) Successful in 2m27s
2026-05-06 18:38:50 +03:00
8440d653b3 ci: debug ssh
Some checks are pending
Publish / backend (push) Waiting to run
CI / fmt (push) Successful in 37s
Publish / frontend (push) Successful in 46s
CI / check (push) Successful in 1m46s
CI / clippy (push) Successful in 1m51s
2026-05-06 18:31:01 +03:00
022c38bdc2 fix(ci): frontend SSH init should test UI_HOST not WS_HOST
Some checks failed
CI / fmt (push) Successful in 44s
CI / clippy (push) Successful in 1m19s
CI / check (push) Successful in 1m34s
Publish / backend (push) Waiting to run
Publish / frontend (push) Failing after 51s
Also bump ConnectTimeout to 5s for the frontend SSH init.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 18:08:36 +03:00
3b9ef6407c ci: debug ssh
Some checks are pending
CI / fmt (push) Waiting to run
Publish / frontend (push) Waiting to run
Publish / backend (push) Waiting to run
CI / check (push) Successful in 1m34s
CI / clippy (push) Successful in 1m26s
2026-05-06 18:01:34 +03:00
99e337d387 ci: debug ssh
Some checks failed
Publish / backend (push) Waiting to run
CI / fmt (push) Successful in 36s
Publish / frontend (push) Failing after 39s
CI / check (push) Successful in 1m31s
CI / clippy (push) Successful in 1m36s
2026-05-06 17:35:30 +03:00
5e5908804a fix(ci): use --rsync-path 'sudo rsync' for privileged deploys
Some checks failed
CI / fmt (push) Successful in 43s
Publish / frontend (push) Failing after 44s
CI / clippy (push) Successful in 1m20s
CI / check (push) Successful in 1m31s
Publish / backend (push) Failing after 22m42s
Simplified deploy steps — rsync directly to final paths using
sudo rsync on the remote instead of temp-file staging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 16:27:49 +03:00
075fef0ea9 fix(ci): drop Node.js install step — already on runner
Some checks failed
CI / check (push) Waiting to run
CI / fmt (push) Successful in 49s
CI / clippy (push) Successful in 1m37s
Publish / backend (push) Waiting to run
Publish / frontend (push) Failing after 28s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 16:16:07 +03:00
2700821559 fix(ci): use Node.js tarball instead of fnm (runner lacks unzip)
Some checks failed
CI / check (push) Waiting to run
CI / fmt (push) Waiting to run
CI / clippy (push) Waiting to run
Publish / frontend (push) Waiting to run
Publish / backend (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 16:15:43 +03:00
2627bab72a ci: add publish workflow with frontend and backend deploy
Some checks failed
Publish / backend (push) Waiting to run
Publish / frontend (push) Failing after 36s
CI / fmt (push) Successful in 53s
CI / clippy (push) Successful in 1m20s
CI / check (push) Successful in 1m28s
publish.yml — triggered on push to main, two parallel jobs:

frontend:
- Builds Vite frontend (fnm + npm ci + npm run build)
- Rsyncs dist/ to gitea_ci@UI_HOST:UI_PATH/
- Rsyncs nginx config to UI_HOST, creates sites-enabled symlink,
  runs nginx -t && systemctl reload nginx

backend:
- Builds release binary (cargo build --release -p ericrfb-proxy)
- Stops blekin.service on WS_HOST
- Rsyncs binary to WS_HOST:/usr/local/bin/ericrfb-proxy via sudo rsync
- Rsyncs systemd unit to WS_HOST:/etc/systemd/system/blekin.service
- Enables and starts the service

asset/nginx/blekin.kosherinata.internal.conf:
- Serves static frontend from UI_PATH
- Reverse proxies /api/ to frootmig:3000 with WebSocket upgrade
- 24h read/send timeouts for long-lived KVM sessions

asset/systemd/blekin.service:
- Runs ericrfb-proxy with BLEKIN_HOST=10.3.0.130
- Restart on failure with 5s backoff

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 16:12:46 +03:00
8692c0e46a feat: phase 8 — Vite/TS canvas-based KVM console frontend
All checks were successful
CI / fmt (push) Successful in 28s
CI / check (push) Successful in 1m31s
CI / clippy (push) Successful in 1m29s
crates/ericrfb-frontend — vanilla TypeScript + Vite:

login.ts:
- Login form POSTs to /api/login, receives applet_id
- Error display, auto-transitions to console view on success

console.ts:
- Canvas-based renderer sized to framebuffer dimensions
- WebSocket binary protocol decoder: TAG_BLIT → putImageData,
  TAG_RESIZE → canvas resize
- Keyboard capture: keydown/keyup → JS code → e-RIC scancode → WS
- Mouse capture: move/click/wheel → scaled coords + button mask → WS
- Right-click and context menu suppressed for pass-through

input.ts:
- Full 104-key JS KeyboardEvent.code → scancode mapping table

protocol.ts:
- Binary message builders matching proxy WS protocol tags

Toolbar: Ctrl+Alt+Del button, Fullscreen toggle.
Dark theme, pixelated canvas rendering, cursor hidden over console.

Vite config proxies /api to localhost:3000 for dev mode.
Build outputs to ../../dist for proxy static serving.

Builds to 5.8KB JS + 1.4KB CSS gzipped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 15:19:53 +03:00
3bd7ee8eac feat: phase 7 — proxy daemon with HTTP login and WebSocket bridge
All checks were successful
CI / fmt (push) Successful in 38s
CI / check (push) Successful in 1m20s
CI / clippy (push) Successful in 1m19s
ericrfb-proxy axum server with three endpoints:

POST /api/login:
- Proxies credentials to OmniView auth.asp
- Extracts session cookie, fetches title_app.asp
- Returns JSON with applet_id, port, protocol_version, board_name

GET /api/ws?applet_id=...&port=...:
- WebSocket upgrade, connects to OmniView via e-RIC RFB
- Bidirectional pump: OmniView frames → RGBA blits over WS,
  browser input events → key/mouse/hotkey to OmniView
- Binary protocol: TAG_BLIT(0x01), TAG_RESIZE(0x03) server→client;
  TAG_KEY_PRESS(0x10), TAG_KEY_RELEASE(0x11), TAG_POINTER(0x12),
  TAG_CTRL_ALT_DEL(0x13) client→server

Static file fallback via tower-http ServeDir.

Config via config.toml or BLEKIN_HOST env var.

Tested against real OmniView:
- Login endpoint returns valid APPLET_ID
- WebSocket upgrade succeeds (HTTP 101)
- Session connects and pumps frames

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 15:11:50 +03:00
ab74f607e8 feat: phase 6 — keyboard/mouse input + fix Tight zlib init
All checks were successful
CI / fmt (push) Successful in 30s
CI / check (push) Successful in 1m9s
CI / clippy (push) Successful in 1m9s
input.rs:
- write_key_press/release/tap: scancode | 0x80 = press, bare = release
- JavaScript KeyboardEvent.code → e-RIC scancode mapping (104pc layout)
- Hotkey sequence sender (raw hex byte strings from applet params)
- write_ctrl_alt_del() using HOTKEYCODE_0 "36 f0 37 f0 4e"
- PointerEvent writer already in msg.rs (8 bytes, absolute mode)
- 5 unit tests

Tight zlib fix:
- Changed Decompress::new(true) for zlib-wrapped format (matching
  Java's Inflater() which expects zlib header 78 9C)
- Added output length verification after decompression
- Tested: Tight-encoded frames now decode correctly from real device

37 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:51:50 +03:00
c8f981f045 feat: phase 5 — Tight decoder with zlib streams and sub-palettes
All checks were successful
CI / fmt (push) Successful in 36s
CI / check (push) Successful in 1m1s
CI / clippy (push) Successful in 1m4s
codec/tight.rs:
- Full Tight (encoding 7) decoder per ByteColorRFBRenderer.a() line 324
- Control byte: bottom 4 bits = zlib stream reset flags,
  top 4 bits = subencoding (0-15)
- Subencoding 8: solid fill (1-byte color)
- Subencoding 15: palette-indexed fill (selector + index)
- Subencodings 4-7: filtered data with optional 2-color palette
- Subencodings 10-13: reduced bit-depth packed (1/2/4 bpp)
- Subencodings 0-3: raw 8bpp data
- Data >= 12 bytes uses zlib compression with varint length
- 4 persistent zlib streams with reset-on-flag logic
- All 4 hardcoded sub-palettes ported as RGB332 indices:
  PALETTE_BW (2), PALETTE_GRAY4 (4), PALETTE_GRAY16 (16),
  PALETTE_COLOR16 (16 EGA-like colors)
- Bit-depth unpackers: 1bpp, 2bpp, 4bpp (MSB-first)
- 5 unit tests

Updated examples to request [7, 5, 1, 0, -250] (Tight preferred).
Tested against real OmniView: correct rendering with Tight encoding.
32 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:44:06 +03:00
21ed797302 feat: phase 4 — Hextile decoder and recording example
All checks were successful
CI / fmt (push) Successful in 28s
CI / check (push) Successful in 1m12s
CI / clippy (push) Successful in 1m12s
codec/hextile.rs:
- Full Hextile (encoding 5) decoder per ByteColorRFBRenderer.int()
- Handles: Raw tiles, BackgroundSpecified, ForegroundSpecified,
  AnySubrects, SubrectsColoured flags
- Background/foreground colors persist across tiles
- 4 unit tests covering all subencoding paths

framebuffer.rs:
- Added fill_rect() for Hextile background/subrect fills

session.rs:
- Wired Hextile encoding 5 into the rect dispatch

examples/record.rs:
- 30-second (configurable) recording session
- Saves 1 PNG per second to out/ directory
- Requests encodings [5, 1, 0] (Hextile, CopyRect, Raw)
- Tested against real OmniView: 10 frames in 10s, no errors

27 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:35:20 +03:00
1164ffdd98 fix: send SetPixelFormat to request 8bpp RGB332
All checks were successful
CI / fmt (push) Successful in 35s
CI / clippy (push) Successful in 57s
CI / check (push) Successful in 1m0s
The OmniView defaults to 16bpp when no SetPixelFormat is sent.
The Java applet sends SetPixelFormat (msg type 0) at
ByteColorRFBRenderer.new() line 76 to request 8bpp RGB332
(bpp=8, depth=8, true_color, red_max=7, green_max=7, blue_max=3,
shifts 0/3/6). Without this, pixel data arrives as 16bpp pairs
that produce green stripe artifacts when interpreted as 8bpp.

Verified: snapshot now matches the reference screenshot from
http://10.3.0.130/screenshot.jpg (dark screen with "Free" label).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:31:37 +03:00
e9823aff03 feat: phase 3 — framebuffer, raw decoder, session pump, snapshot
All checks were successful
CI / fmt (push) Successful in 37s
CI / check (push) Successful in 1m0s
CI / clippy (push) Successful in 1m4s
framebuffer.rs:
- Framebuffer struct (8bpp RGB332, row-major)
- apply_raw() blit, copy_rect() with overlap-safe logic
- to_rgba() via compile-time RGB332 LUT

session.rs:
- ActiveSession: connect + SetEncodings + initial FBUpdateRequest
- Full server message dispatch loop (all 15 message types)
- Raw (encoding 0) and CopyRect (encoding 1) decoders
- Ping response, bandwidth probe bookends, mode change resize

examples/snapshot.rs:
- Connects, waits for first FramebufferUpdate, saves PNG
- Tested against real OmniView at 10.3.0.130:443
- Successfully captured 640x480 frame

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:23:43 +03:00
1bd43fc1f9 feat: phase 2 — handshake, message writers, and server message dispatch
All checks were successful
CI / fmt (push) Successful in 30s
CI / check (push) Successful in 1m1s
CI / clippy (push) Successful in 1m4s
handshake.rs:
- Config, PixelFormat, ServerInit, Session types
- connect() walks all 11 handshake steps per aw.g() line 226
- Auth error mapping from aw.a(int) line 350 (7 error codes)

msg.rs — client-to-server writers:
- SetEncodings (type 2), FramebufferUpdateRequest (type 3)
- KeyEvent (type 4), PointerEvent (type 5, 8 bytes)
- PingResponse (type 149), BandwidthMarker (type 151)

msg.rs — server message dispatch:
- ServerMsg enum covering all 15 message types
- Readers for ping, bandwidth probe, ack, debug string,
  RFB command, server cut text, server name update,
  layout/locale, RDP event, FB update header

examples/handshake.rs: connects and prints session info.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 14:11:31 +03:00
07db90094d style: apply rustfmt to proto.rs
All checks were successful
CI / fmt (push) Successful in 32s
CI / check (push) Successful in 55s
CI / clippy (push) Successful in 59s
Fixes CI fmt check failure — rustfmt wants multi-line assert_eq! for
long struct literals and the varint roundtrip assertion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 13:59:54 +03:00
c4e3df5a44 ci: add Gitea Actions workflow for check, fmt, clippy
Some checks failed
CI / fmt (push) Failing after 40s
CI / check (push) Successful in 1m2s
CI / clippy (push) Successful in 1m10s
Three parallel jobs on the rust runner:
- cargo check --workspace
- rustfmt --check on all .rs files
- cargo clippy --workspace -- -D warnings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 13:47:58 +03:00
3db2927add feat: phase 0+1 — workspace scaffold and protocol primitives
Phase 0: Cargo workspace with ericrfb (lib) and ericrfb-proxy (bin)
crates, .envrc for RUST_LOG, workspace dependency pins.

Phase 1: ericrfb/src/proto.rs implements all wire primitives:
- read/write helpers matching h.java (u8, i8, u16-BE, i16-BE, i32-BE)
- varint reader/writer matching aw.int() (1-3 bytes, Tight lengths)
- modified-UTF-8 string reader matching h.byte()
- RectHeader { x: u16, y: u16, w: u16, h: u16, encoding: i32 }
- RGB332 → RGBA compile-time LUT (256 entries)

20 tests including proptest varint roundtrip over [0, 2^22).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 13:44:24 +03:00
a60cee3f23 fix: reconcile implementation plan with decompiled Java source
Cross-referenced every wire-format claim against rc-src and corrected
several errors that would have caused stream desync during implementation:

- rect header is 4×u16 + i32 (12 bytes), not varints + u8
- ping payload is i32 (4 bytes), not 1 byte
- PointerEvent is 8 bytes (includes trailing extra_u16)
- ServerInit is 19 bytes, not 16
- pixel-format struct is variable-length, not fixed 25 bytes
- string primitive uses modified-UTF-8, not ISO-8859-1
- bandwidth probe is read-only (no echo response)
- Phase 1 uses sync Read traits (async deferred to Phase 2)
- corrected all stale line-number references in ac.java/aw.java

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-06 13:40:40 +03:00
6e1a6fc29d doc: implementation plan 2026-05-06 13:36:07 +03:00