Skip to content

Commit e44f97c

Browse files
committed
Add telnet server with 60pps paced output
Add gambaterm-telnet server using telnetlib3, with a simplified architecture compared to the previous NUL-padding approach. Frame output is paced at 60 packets per second via an asyncio forwarding task combined with TCP_NODELAY, producing smooth rendering without complex bandwidth estimation. - TelnetTerminal blessed subclass (mirrors SSHTerminal pattern) - Kitty keyboard protocol required for both SSH and telnet - Per-user save state directories (SHA-256 hashed usernames) - NAWS terminal resize support - Connection stats logging and idle timeout - --max-players and --robot-check support via telnetlib3 guard shells
1 parent bf125b3 commit e44f97c

9 files changed

Lines changed: 1171 additions & 11 deletions

File tree

README.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,42 @@ Optional arguments:
9696
SSH server
9797
----------
9898

99-
It is possible to serve the emulation through SSH. Clients with terminals supporting the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) can send input directly without X11 forwarding. Otherwise, X11 forwarding (`ssh -X`) can be used as a fallback. Use `gambaterm-ssh --help` for more information. 24-bit color is always true over ssh.
99+
It is possible to serve the emulation through SSH. Clients must use a terminal that supports the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), or use X11 forwarding (`ssh -X`) as a fallback. Use `gambaterm-ssh --help` for more information. 24-bit color is always assumed over SSH. Audio is not available over SSH.
100+
101+
```shell
102+
$ gambaterm-ssh --password 123 myrom.gbc
103+
$ gambaterm-ssh --password 123 --bind 0.0.0.0 --port 8022 myrom.gbc # Listen on all interfaces
104+
```
105+
106+
Connect with ssh client:
107+
108+
``shell
109+
ssh localhost -p 8022
110+
```
111+
112+
Suggest removing keystroke timing obfuscation for better button latency:
113+
114+
```shell
115+
ssh -o ObscureKeystrokeTiming=no example.com -p 8022
116+
```
117+
118+
Telnet server
119+
-------------
120+
121+
The emulator can also be served over telnet, requiring no authentication or SSH keys:
122+
123+
```shell
124+
$ gambaterm-telnet myrom.gbc
125+
$ gambaterm-telnet --bind 0.0.0.0 --port 8023 myrom.gbc # listen on all interfaces
126+
```
127+
128+
Connect with any telnet client:
129+
130+
```shell
131+
$ telnet localhost 8023
132+
```
133+
134+
Clients must use a terminal that supports the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/) -- connections without it are rejected. Use `--max-players N` to limit concurrent connections. 24-bit color is always assumed unless the client reports `TERM=ansi` (16 colors). Audio is not available over SSH or telnet.
100135

101136

102137
Terminal support
@@ -221,6 +256,7 @@ Here is the list of the dependencies used in this project, all great open source
221256
- [xlib](https://github.com/python-xlib/python-xlib)/[pynput](https://github.com/moses-palmer/pynput) - Getting keyboard inputs
222257
- [pygame](https://github.com/pygame/pygame) - Getting game controller inputs
223258
- [asyncssh](https://github.com/ronf/asyncssh) - Running the SSH server
259+
- [telnetlib3](https://github.com/jquast/telnetlib3) - Running the telnet server
224260

225261

226262
Contact

gambaterm/colors.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,21 @@ class ColorMode(IntEnum):
2828
HAS_24_BIT_COLOR = 4
2929

3030

31+
def detect_color_mode(env: dict[str, str]) -> ColorMode:
32+
"""Detect color support from environment variables.
33+
34+
Assumes 24-bit color for all terminals except TERM='ansi' which
35+
gets 16 colors.
36+
37+
:param env: environment dict with keys like TERM
38+
:returns: detected :class:`ColorMode`
39+
"""
40+
term_val = env.get("TERM", "").lower()
41+
if term_val == "ansi":
42+
return ColorMode.HAS_4_BIT_COLOR
43+
return ColorMode.HAS_24_BIT_COLOR
44+
45+
3146
def detect_local_color_mode(term: Terminal) -> ColorMode:
3247
"""Detect the color mode of the local terminal using blessed."""
3348
n = term.number_of_colors

gambaterm/ssh.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import os
44
import time
5+
import hashlib
56
import asyncio
67
import argparse
78
import traceback
@@ -107,12 +108,13 @@ async def ssh_process_handler(process: SSHServerProcess[str]) -> int:
107108
console_cls.add_console_arguments(parser)
108109
namespace = parser.parse_args(command.split(), namespace)
109110

110-
# Manage save directory
111+
# Manage save directory — hash username to prevent path traversal
111112
if "save_directory" in namespace.__dict__:
113+
safe_name = hashlib.sha256(username.encode("utf-8")).hexdigest()[:16]
112114
save_directory = (
113115
None
114116
if getattr(namespace, "input_file", False)
115-
else Path("ssh_save") / username
117+
else Path("ssh_save") / safe_name
116118
)
117119
setattr(namespace, "save_directory", save_directory)
118120

@@ -197,13 +199,8 @@ def ssh_terminal_handler(
197199
else:
198200
assert False
199201

200-
# Default to 24-bit color since the vast majority of modern terminals
201-
# support it.
202-
color_mode = (
203-
app_config.color_mode
204-
if app_config.color_mode is not None
205-
else ColorMode.HAS_24_BIT_COLOR
206-
)
202+
# Kitty keyboard protocol implies 24-bit color support
203+
color_mode = app_config.color_mode or ColorMode.HAS_24_BIT_COLOR
207204

208205
print(
209206
f"[Terminal Info] {username}: {terminal_type}, {input_source}, {term.width}x{term.height}"

0 commit comments

Comments
 (0)