Skip to content

Latest commit

 

History

17 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NetHarness

Remote-control a real Amiga over TCP/IP. Run commands, capture the screen, inject mouse and keyboard input, and reboot it — all from another machine, with nobody sitting at the Amiga.

$ nhctl.py --host 192.168.1.32 EXEC version
rc=0
Kickstart 47.115, Workbench 47.5

$ nhctl.py --host 192.168.1.32 SCREENSHOT desktop.png
OK desktop.png 1024x768x24

It exists because testing Amiga software normally means physically sitting at the machine: clicking through a GUI, squinting at a screen, and power-cycling by hand. NetHarness turns that into something a script can drive.

What it does

Command
EXEC <command> Run any AmigaDOS command; returns its exit code and its output
SCREENSHOT [file] Capture the front screen as a PNG
CLICK x y, MOVETO, MOVE, BUTTON Mouse, injected at hardware level
TYPE <text>, PRESSKEY, KEY, CLEARFIELD Keyboard, with shift handled
RESETINPUT Release any stuck buttons or qualifiers
REBOOT Cold-reboot the machine (flushes disks first)
PING Liveness check

Input is injected through input.device (IND_WRITEEVENT), the same path real hardware uses, so it exercises window activation, GadTools, menus — everything a genuine click would.

New in 1.3 — stop guessing pixels

Command
UITREE The front screen's windows and gadgets: id, kind, bounds, label, string contents
UICLICK <text> / UICLICKID <id> Click a gadget by identity, not coordinates
MENUS / MENUSEL <menu> <item> Enumerate the menu strip; pick an item by name
POINTER Where the pointer actually is (it's a hardware sprite — invisible in screenshots)
SHOTREGION x y w h Capture just a region — far cheaper than a full frame
REGIONSUM x y w h 4-byte checksum: "has this redrawn yet?"
WAITCHANGE x y w h Block until a region actually changes, instead of guessing a delay
GETFILE / PUTFILE Binary-safe file transfer — deploy a build through the harness itself
SCREENS List open screens, front to back
netharness [port] Listen port is an argument, so a new build can be tested beside the running one

New in 1.4:

Command
RELOAD Apply a staged C:netharness.new and restart the harness in place - no machine reboot

New in 1.5:

  • Plays fair with the TCP/IP stack's own shutdown: the harness blocks in WaitSelect on socket and break signal, so a stack shutdown (Roadshow's NetShutdown) sees it release bsdsocket.library immediately instead of timing out against a blocked accept()/recv() and deferring the teardown.

New in 1.6:

  • Send-stall guard: a controller that vanishes mid-transfer no longer freezes the harness in a blocking send() - every send waits for writability first (20 s cap), then the dead client is dropped.
  • Idle-client reaper: a silent client is disconnected after 600 s, so a controller that died without closing can't hold the single client slot.

New in 1.7:

  • Bring-up retries forever instead of giving up after 60 s: 2 s apart for the first minute, then every 10 s (CTRL-C aborts the wait). The harness now self-connects whenever its network path appears late - a companion Pi still booting, WiFi rejoining, or the stack restarting - with no manual run needed at the machine.

UITREE + UICLICK are the headline: drive the GUI by what things are.

$ nhctl.py UITREE
W 0 0 11 332 199 "Time Preferences"
G 16 STRING 58 29 44 8 "" "2026"
G 15 GADGET 128 146 192 10 "Hours" ""

Honest limitation: some gadgets expose no text to Intuition — OS 3.2 Prefs' Save/Use/Cancel buttons report empty labels because those apps draw the text themselves. Slider labels and string-gadget contents do come through. Use UICLICKID <id> for such buttons, UICLICK <text> everywhere else.

Every input command is acknowledged after injection, so OK means the Amiga really did it — not merely that a packet was sent.

Why EXEC matters

Driving a GUI blind is miserable: you guess coordinates, click, screenshot, and hope. EXEC avoids most of that — anything expressible as a Shell command is one call, with output returned to you. Combined with ARexx it reaches inside applications too:

nhctl.py EXEC 'rx "address IBROWSE; ''GOTOURL https://aminet.net''"'

Getting started

On the Amiga (needs a TCP/IP stack — Roadshow, AmiTCP, Miami, a314bsd…):

Execute Install
Run >NIL: C:netharness

It listens on TCP port 7800. To start it at every boot, add that Run line to the end of S:User-Startup, after your TCP/IP stack comes up.

One addressing nuance: with a proxy-style stack such as a314bsd (where the sockets actually live on a Raspberry Pi), connect to the proxy host's IP — the Amiga itself has no address of its own.

Use the installer rather than copying by hand, especially if you unpacked the .zip: ZIP archives cannot carry AmigaDOS protection bits, so netharness arrives without its e (executable) flag and the Shell refuses to run it. The installer sets the flag for you; to do it manually:

Copy netharness C:
Protect C:netharness +e

The .lha archive preserves the flag, so it needs no such fixup.

On the controlling machine (Python 3, plus Pillow for screenshots):

python3 nhctl.py --host <amiga-ip> PING
python3 nhctl.py --host <amiga-ip> EXEC list SYS:
python3 nhctl.py --host <amiga-ip> --batch < commands.txt

--batch sends many commands over one connection, which is much quicker than one invocation each.

Screenshots

Screen capture handles the awkward cases real Amigas actually present:

  • Interleaved bitmaps, which AGA Workbench screens normally use
  • True-colour RTG screens (Picasso96/CyberGraphX) via ReadPixelArray
  • Planar screens of any depth, with the screen's real palette

The mouse pointer is a hardware sprite and never appears in a capture — verify mouse actions by their effect (a window activating, a gadget highlighting), not by looking for the cursor.

Notes worth knowing

  • Every input command is acknowledged by the Amiga after injection, so OK means delivered and injected, not merely sent. Without that, a command that lands on nothing looks exactly like one that worked.
  • Prefer RELOAD over REBOOT for updates. REBOOT calls ColdReboot(), and not every machine survives a warm CPU reset - one of the test machines here (an A2000) lands on a grey screen and needs a power cycle. RELOAD applies a staged C:netharness.new and restarts the harness in place instead, which is what rebooting was being used for anyway. It applies the update while still serving, so a failed copy leaves the working build running rather than bricking your remote access.
  • REBOOT flushes filesystems first. ColdReboot() resets instantly, and without an explicit flush any file written moments earlier is quietly lost — which looks uncannily like the file "reverting" after a reboot.
  • DOS requesters are suppressed (pr_WindowPtr = -1). A command touching a missing volume would otherwise raise "Please insert volume…" and block the single-threaded harness with no way to dismiss it remotely.
  • EXEC is synchronous. A command that never returns holds the connection, so start long-lived programs with EXEC run >NIL: <program>.
  • There is no authentication. It executes commands as sent — use it on a network you trust, not a public one.

Which version is installed?

The binary carries a standard AmigaDOS version cookie:

$ nhctl.py --host <ip> EXEC "version C:netharness full"
netharness 1.4 (08/12/26)

Building

cd amiga && make        # needs the bebbo amiga-gcc cross-compiler

The Amiga side is a single C file using only standard bsdsocket.library calls, built -m68020 — change that to -m68000 in amiga/Makefile if you need it to run on a stock 68000 machine.

Licence

MIT — see LICENSE.

About

Remote-control a real Amiga over TCP/IP - run commands, capture the screen, inject input

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages