在 TCL 电视(Android 11 / 无 Google Play / 只有遥控)上远程连到开发机干活。 三套方案都已在本仓库的实机上跑通,互相补充:
| 方案 A:自建 PWA | 方案 B:Termux + SSH | 方案 C:herdr WebUI + 隧道 | |
|---|---|---|---|
| 跑什么 | ColliePWA(herdr 的手机客户端) | Termux 里的 ssh + tmux/herdr 的 TUI |
herdr-web-ui(官方生态的浏览器客户端) |
| 电视上装什么 | TV Bro(带 GeckoView 的构建) | Termux + openssh + tmux |
复用已有的 TV Bro + Termux(Termux 只用来起隧道) |
| 开发机上要什么 | 起一个 Collie 实例(自建 Web 应用) | 什么都不用装(只要有 sshd) | herdr ≥0.9 + Bun + 源码版 herdr-web-ui |
| 打字 | 网页输入框 → 搜狗 TV 输入法 | 终端 → 搜狗 TV 输入法(软键盘实测可用) | 网页输入框 / 终端(xterm.js) |
| 实体键盘打中文 | 未实测 | ❌ 不行(Termux 上游 bug #5290) | ✅ 实测通过 |
| 屏幕利用 | 卡片/按钮式 UI,为遥控优化 | 真终端,TUI 原样渲染 —— 信息密度高 | 工作区 + 聊天卡片 + 真终端,信息密度最高 |
| 装一次耗时 | ~90 s(143 MB 包) | ~330 s(32 MB 包 + apt) | 开发机上 ~5 分钟 + 电视侧 0 |
| 适合 | "看哪个 agent 在等我、回一句话" | "真的要敲命令、看日志、改代码" | 长期主力(尤其要打中文时) |
建议:A 和 C 都装,B 视需要。 A 用来快速瞥一眼 + 回话(遥控友好);C 用来长期干活(中文输入 + 字号/滚动都好); B 是"开发机上什么都不能装"时的兜底 —— 但它打不了中文,见下面的死结。 它们共用同一套安装通道、输入法和验收手段,见「对齐体验」。
| 能力 | 值 | 影响 |
|---|---|---|
| ABI | armeabi-v7a, armeabi —— abilist64 为空 |
只能装 32 位 APK,arm64-only 的直接装不上 |
| Android | 11 / API 30,user 构建 |
无 root;targetSdk 28 的老应用也能装 |
| Google Play / GMS | 没有 | 一切靠侧载,别考虑 Play |
android.software.leanback_only |
是 | 没有 LEANBACK_LAUNCHER 的不会出现在 TV 桌面(但能用 am start 拉起) |
| 输入法 | com.sohu.inputmethod.sogou.tv(搜狗 TV 输入法) |
遥控 D-pad 就能打字,应用走标准 InputConnection 就吃得到 |
| 遥控 | gIrTouch_Mouse,SOURCE_MOUSE|SOURCE_TOUCHPAD|SOURCE_POINTER |
带指针能力,标准控件可以 tap |
| 触摸屏 | 没有 | TCL 自有界面不把 touch 当 click |
| 外设 | android.hardware.usb.host ✅、蓝牙 ON ✅ |
见下面「键盘 / 鼠标」 |
实测过三种输入设备(2026-09-18)。结论:长期用选蓝牙。
坑 ①:不是每个 USB 口都是主机口。 实测把 2.4G 接收器插在
U 盘那个口上 → 立刻枚举成功;插在另一个口上 →
dumpsys usb 的 host_manager 里一个设备都没多。
怎么判断当前用的是不是主机口(不需要 root):
adb -s "$TV_ADDR" shell "dumpsys usb" | grep -A40 host_manager
# 主机口:会列出 name=/dev/bus/usb/... + 厂商/产品名
# 非主机口:列表里什么都没有判据不是 /sys/bus/usb/devices/(shell 读不了,是 Permission denied,
不是空;别把它当成"没有设备"),也不是 dumpsys usb 的
device_manager.connected(那是设备模式 = 电视当从设备接电脑,和主机模式无关)。
坑 ②:2.4G 接收器距离短。 实测离开一两米就不工作 —— 电视场景下基本不好用。 而且那个主机口就是 U 盘侧载要用的口,两个设备互斥(要 Hub,而且电视的 USB 口 供电常常带不动 Hub + U 盘,得用带供电的 Hub)。
驱动侧完全具备,不是"声称支持但没实现":
/sys/bus/usb/drivers/ 里有: usbhid(U 键盘/鼠标) hub(U 扩展坞) xpad(Xbox 手柄)
xpad 在,说明厂商确实考虑过 "USB 输入类外设" 这个场景。
已被证明可用 —— 蓝牙键盘(K380)和蓝牙鼠标(M585/M590)接上就直接出输入设备。
Profile: HidHostService ← 蓝牙 HID 主机功能在
HID_HOST=100 ← 已配对设备被当作 HID 接纳
feature:android.hardware.bluetooth_le ← BLE 鼠标也支持
配对:电视 设置 → 蓝牙 → 添加设备,鼠标先进配对模式。
# ① 内核认到设备了吗 —— 看设备名和能力
adb -s "$TV_ADDR" shell "getevent -pl" | grep -E 'add device|name:'
# 鼠标的特征:REL_X / REL_Y / WHEEL + BTN_LEFT/RIGHT/MIDDLE
# ② framework 认成鼠标了吗 —— 看 Sources 和 Cursor Input Mapper
adb -s "$TV_ADDR" shell "dumpsys input" | grep -A24 'Device [0-9]*: <设备名>'
# ★ Sources 里要含 0x00002000(SOURCE_MOUSE)
# ★ 而且应该有 `Cursor Input Mapper:` 这一段 —— 只有鼠标才会有
# ③ ★★ 最硬的一条:SurfaceFlinger 里真的有一个「光标图层」
adb -s "$TV_ADDR" shell "dumpsys SurfaceFlinger" | grep -A14 'Sprite#0' \
| grep -E 'type=CURSOR|geomLayerBounds|alpha='
# 要看到 `composition type=CURSOR` —— 这是 SurfaceFlinger 的光标层,做不了假Android 的光标会在静止几秒后自动淡出,淡到几乎全透明。实测:
* Layer 0xf2ce5000 (Sprite#0)
composition type=CURSOR (4) ← 光标层确实在
geomLayerBounds=[0 0 33 42] ← 33x42 的箭头
transform: (81.29, 775.49) ← 当前坐标
alpha=0.017197 ← 但只有 1.7% 不透明 = 已淡出
所以静止时截图里根本看不见光标,而你会误判成「鼠标没生效」。
alpha 会在鼠标移动时回到 1.0,停一会儿又掉回 ~0.02 —— 我连采 5 次
screencap + 反复读这个图层,只要没人碰鼠标,alpha 就一直是 0.017197。
判据要选不依赖「画面上看得见」的那一条:composition type=CURSOR 在,
就说明光标系统在跑、鼠标被当作指针设备。让用户自己看屏幕比截图可靠。
| 设备 | 结果 |
|---|---|
| 罗技 2.4G(B330/M330/M3) | ①②③ 全过。Sources: 0x01002313 含 0x2000;有 Cursor Input Mapper;光标层 type=CURSOR 在 (81,775)。但接收距离短,一两米就失效 |
| 罗技蓝牙(M585/M590) | ①②③ 全过,UniqueId: d2:be:b4:bd:58:0c,bus=0x0005(= 蓝牙总线),Sources: 0x00002303 含 0x2000,有 Cursor Input Mapper。推荐 |
换一个口/换一种连接方式,要重新跑一遍 ①②③ —— 我这次就是靠 「host_manager 里一个设备都没多」才发现另一个 USB 口不是主机口。
⚠️ 不能靠sendevent代你动鼠标来验。 鼠标的 event 节点是crw-rw---- root input,而 Android 11 的 SELinux 不允许 shell 写输入设备 (sendevent: Permission denied)——shell 虽然在input组里也没用。 这一步必须物理动一下鼠标。
另一个容易搞混的:系统里原本就有一个鼠标类设备
gIrTouch_Mouse(REL_X/REL_Y/WHEEL+SOURCE_MOUSE|TOUCHPAD|POINTER),那是遥控的红外指针。 所以"电视上有鼠标类设备"不等于"插的鼠标被认了",要看设备名。
adb install 被固件硬拦(OverseasAppConfig,AOSP 的 verifier 开关无效),
唯一通道是 U 盘 + TGuard 图形安装器,也就是 tools/tv-install.sh。
细节见 03-tcl-tv-sideload.md。
bash tools/tv-install.sh <apk> # 约 90 s;U 盘越干净越快💡 用蓝牙键鼠的另一个好处:U 盘那个主机口就空出来了。 如果用 2.4G 接收器, 它和 U 盘抢同一个主机口 —— 装应用时得把鼠标拔下来。蓝牙不占口。
⚠️ 安装器扫的是整个可移动卷、而且是递归的 —— 把 APK 挪到二级目录 或AndroidTV/外面都躲不掉(实测两面都验过)。唯一有效的是不让名字以.apk结尾: 用不到的改成xxx.apk.disabled,它们就不再被当成安装源。 细节和「僵尸条目」那个坑见 03。
| 项 | 值 |
|---|---|
com.tcl.browser |
3.0.880,连 CSS 都不解析(整页裸 HTML) |
com.quark.yun.tv |
夸克网盘 App 顺带的浏览器,要扫码登录 |
| 系统 WebView | com.android.webview 83.0.4103.120 = Chromium 83(2020-06) |
Chromium 83 遇到 Tailwind v4 产出的 @layer(需要 Chrome 99)会以整块为单位丢弃
—— 而 Tailwind v4 把所有样式都包在 @layer 里,于是一个字节的样式都不生效。
症状是「裸 HTML + 卡在 Loading…」,非常容易被误判成网站坏了/网络不通。
1. 装带 Gecko 的 TV Bro 构建(普通构建不含新引擎)
| 构建 | 大小 | 引擎 |
|---|---|---|
tvbro-<ver>-generic-geckoExcluded.apk |
6.8 MB | 只有系统 WebView |
tvbro-<ver>-generic-geckoIncluded-armeabi-v7a.apk |
142.9 MB | 自带 GeckoView |
bash tools/tv-install.sh tvbro-2.1.6-generic-geckoIncluded-armeabi-v7a.apk2.
TV Bro 装了 Gecko 也默认用 WebView。
打开任意网页 → 按 BACK 呼出工具栏 → 点最右的「设置」
→ 浏览器引擎 = WebView ← 默认值
→ 点它 → 选 GeckoView
→ 「…加载速度比 WebView 稍慢。你想继续吗?」→ OK
→ 「需要重启应用 / 重新启动应用以应用此更改」→ 退出
3. 打开你的 Collie 实例
TV Bro 打开 http://<开发机>:<端口>/ 或经 Tailscale 的地址
看错误页的措辞。 Chromium 报 net::ERR_ACCESS_DENIED / net::ERR_EMPTY_RESPONSE
这类 net::ERR_*;Gecko 不用这套命名。看到 net::ERR_* 就说明还在用 WebView。
电视上打开 https://colliepwa.dev/demo,GeckoView 引擎下完整渲染:
Collie shows the agents in your terminal.
6 panes, 1 needs you This device can type
WHAT YOU CAN DO HERE
Read the live pane, colour and all. / Answer a prompt by tapping its card.
Type a reply, or send Esc, Tab and Ctrl keys.
[Open the pane that needs you] 1.21.3 Demo — dummy data
引擎里 libxul.so 的 UA 版本是 Firefox/147.0。
herdr 是跑在开发机上的终端复用器/agent 编排器(TUI)。 客户端只要有 ssh + 终端 就行。
# 1. 装 Termux(必须 arm 32 位那份)
bash tools/tv-install.sh termux-app_v0.118.3+github-debug_armeabi-v7a.apkAPK 自带 bootstrap(lib/armeabi-v7a/libtermux-bootstrap.so,26.5 MB),
首次启动不需要联网。
# 2. 在电视上打开 Termux,然后(可以照抄,也可以走 run-as 自动化)
pkg install -y openssh tmux
# 3. ssh 到开发机,attach 到 herdr / tmux
ssh -t <开发机> 'herdr attach' # 或 tmux new -A -s workTermux 的 apt 源
packages-cf.termux.dev在国内可达(实测 62 kB/s), 装 openssh + tmux 大约 15 分钟(网络慢,一次性)。
三条路,按推荐顺序:
- 电视的搜狗 TV 输入法直接能打进 Termux —— 实测可行。
Termux 走标准
InputConnection,所以遥控 D-pad 在搜狗键盘上选字母就行。 慢,但能用。 - Termux 的「跨屏输入」 —— 键盘左侧有个「点击后扫码 · 手机输入更方便」, 用手机扫码变键盘。日常最舒服的一条。
- USB / 蓝牙键盘 —— 电视有
android.hardware.usb.host,蓝牙也是开的。 真要长时间敲命令,这是唯一不难受的选择。
Termux 自带的功能键行(ESC / CTRL / ALT / ←↓→ / HOME / END / PGUP / PGDN)
正好补上遥控没有的键 —— 配合第 2 条很好用。
用方案 C 不用看这节 —— 浏览器缩放就够了。下面是方案 B(Termux)的办法。
电视上默认字号很小(1920×1080、240dpi 下 stty size = 43×173)。Termux 支持改,
三条路,只有在电视上能用的那条最省事:
源码(TermuxTerminalViewClient.onKeyDown)里写得很清楚:
} else if (!...areHardwareKeyboardShortcutsDisabled() &&
e.isCtrlPressed() && e.isAltPressed()) {
int unicodeChar = e.getUnicodeChar(0);
...
} else if (unicodeChar == '+' || e.getUnicodeChar(KeyEvent.META_SHIFT_ON) == '+') {
changeFontSize(true); // 放大
} else if (unicodeChar == '-') {
changeFontSize(false); // 缩小
}步进 2、实时生效、自动持久。 有硬件键盘(USB / 蓝牙)就能用 —— 这也是
电视上唯一顺手的方式。同一段里还有 Ctrl+Alt+ 加别的键:k 开关软键盘、
m 菜单、r 重命名会话、c 新会话、v 粘贴、1~9 切会话。
onScale() 里判断缩放系数 → changeFontSize()。但这台电视没有触摸屏,
鼠标也做不了捏合。
这是最容易白费半小时的坑。 源码里是:
int fontSize = SharedPreferenceUtils.getIntStoredAsString(
mSharedPreferences, TERMUX_APP.KEY_FONTSIZE, DEFAULT_FONTSIZE);名字叫 getIntStoredAsString —— 值是以字符串形式存的。写成 <int> 会被
完全忽略,而且一点报错都没有,表现就是「改了没用」:
<!-- ✗ 无效,静默忽略 -->
<int name="fontsize" value="40" />
<!-- ✓ 正确 -->
<string name="fontsize">40</string>实测(量文字行距,同一段欢迎横幅):
| 写法 | 文字行数 | 平均行距 | 文字块总高 |
|---|---|---|---|
| 不写(默认) | 18 | 27.26 px | 464 px |
<int name="fontsize" value="40"/> |
18 | 27.26 px | 464 px |
<string name="fontsize">40</string> |
10 | 68.39 px | 616 px |
fontsize 的可用范围由 MIN_FONTSIZE/MAX_FONTSIZE 夹住(从资源读),
changeFontSize() 每次步进 2。默认大约 20(默认行距 27.26px ≈ fontsize 20)。
在电视上直接写(需要 run-as,Termux 的 GitHub 构建是 debuggable):
D=/data/data/com.termux
# 停应用 → 改 → 起应用(不停应用会被它回写覆盖)
adb -s "$TV_ADDR" shell "am force-stop com.termux"
adb -s "$TV_ADDR" shell "run-as com.termux sed -i \
's#<int name=\"fontsize\" value=\"[0-9]*\" />#<string name=\"fontsize\">24</string>#' \
$D/shared_prefs/com.termux_preferences.xml"
adb -s "$TV_ADDR" shell "am start -n com.termux/com.termux.app.TermuxActivity"更简单:先按一次
Ctrl+Alt++让 Termux 自己把<string>那条写进去, 之后再用 sed 改数字就不会踩格式的坑了。
| 尝试 | 结果 |
|---|---|
~/.termux/termux.properties 里的字号 |
❌ 支持的键里没有字号(只有 font-family / font-woff2) |
settings put system font_scale 1.5 |
❌ 行距与文字块高度完全不变(只有功能键行的字变大 —— 这点骗到过我) |
| 界面菜单 | ❌ 没有(它不进菜单,只走快捷键和捏合) |
adb -s "$TV_ADDR" shell "wm density 320" # 240 → 320
adb -s "$TV_ADDR" shell "wm density reset" # 还原实测行距 27.26 → 33.89 px,reset 后精确回到 27.26 px;写在
settings secure display_density_forced 里,重启不丢。
代价是整个电视 UI 都变大(TCL 桌面也一样)—— 只想改终端就不要用这个。
❗ 别靠肉眼判字号变化。 本机没有 PIL/numpy,而截图内容是会变的 —— 我先后被「功能键行的字变大」和「前后画面内容不同」各骗了一次。 可靠做法是先控住内容(删掉
current_session逼出固定内容的欢迎横幅) 再量文字行距:用tools/png-rowpitch.py——python3 tools/png-rowpitch.py 截图.png 0 700会给出 文字行数 / 平均行距 / 文字块总高 三个数,一样就是没变,不用猜。 (它自带--selftest,已接进verify-all.sh。)
apt update : Get https://packages-cf.termux.dev/... Fetched 572 kB in 9s
openssh : OpenSSH_10.6p1, OpenSSL 3.4.1
tmux : tmux 3.8
ssh 客户端 : CLIENT-SSH-OK / 远端看到: localhost armv8l
ssh + tmux : demo: 1 windows (created ...) ← 就是 herdr 的用法
跨机 sshd : 从 WSL 连电视的 8022 → TV-SSH-OK / uid=10092(u0_a92)
屏幕上的 TUI : [demo] 0:sleep* "localhost" 22:12 16-Oct-2026 ← 1920x1080 正常渲染
方案 A 要自建 Collie 实例;方案 B 的死结是 Termux 里实体键盘打不了中文。 方案 C 用 herdr 生态现成的 herdr-web-ui(浏览器客户端):
[电视] TV Bro (GeckoView) → http://127.0.0.1:7317
↑ SSH 隧道(Termux 里起)
[开发机] herdr-web-ui(只绑 127.0.0.1)
↓
herdr server(同一批 pane,TUI 仍可 attach)
为什么是它:① 中文输入实测可用(方案 B 的死结是 Termux 把硬件键直接喂 PTY, 不是电视的限制);② 电视上零安装(复用 TV Bro + Termux);③ 浏览器缩放顺手解决 字号/滚动/选中。
为什么用隧道而不是 HOST=0.0.0.0:隧道进来的连接服务端判成 via:"local",
不需要配对码也不需要 token;直连则必须配 HERDR_WEB_TOKEN,而明文 http 上的
token 会被同网段任何人嗅到。
前提:uname -m 为 x86_64/aarch64(armv7l 官方没构建)、bun ≥1.4、
node ≥18、herdr ≥0.9.0 且 herdr status server 为 running、git。
git clone https://github.com/devswha/herdr-web-ui.git ~/herdr-web-ui
cd ~/herdr-web-ui && bun install
export PATH="$HOME/.local/bin:$HOME/.n/bin:$HOME/.bun/bin:$PATH"
export HERDR_WEB_HERDR_BIN="$HOME/.local/bin/herdr"
HOST=127.0.0.1 PORT=7317 bun run start自检(必须看到 "ok":true):
curl -s http://127.0.0.1:7317/api/health
# {"ok":true,"herdr":{"terminal_attach":true},"auth":{"via":"local","role":"drive"},...}别用
herdr plugin install,除非 herdr 是用完整 PATH 启动的:插件的 build 跑在 herdr 自己的环境里,而 GUI 终端起的 herdr 通常看不到~/.bun/bin, 会报bun not found。源码安装只依赖你当前 shell 的 PATH。
一次性 ssh -N -L 不会自己重连(开发机一重启就静默死掉),要守护循环。
~/.termux/herdr-tunnel.sh:
#!/data/data/com.termux/files/usr/bin/sh
H=/data/data/com.termux/files/home
PREFIX=/data/data/com.termux/files/usr
PATH=$PREFIX/bin:$PATH; export PATH
PIDFILE=$H/.ssh/herdr-tunnel.pid
# 单实例:Android 的 ps -A 只显示进程名,不能用它判重 → pidfile + kill -0
if [ -f "$PIDFILE" ]; then
old=$(cat "$PIDFILE" 2>/dev/null)
[ -n "$old" ] && kill -0 "$old" 2>/dev/null && exit 0
fi
echo $$ > "$PIDFILE"; trap 'rm -f "$PIDFILE"' EXIT INT TERM
"$PREFIX/bin/termux-wake-lock" 2>/dev/null
while :; do
ssh -N -L 7317:127.0.0.1:7317 \
-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ExitOnForwardFailure=yes \
-o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 \
-i "$H/.ssh/id_ed25519" bot@192.0.2.35
echo "[$(date '+%F %T')] ssh 退出 rc=$?,5 秒后重连" >> "$H/.ssh/tunnel.log"
sleep 5
done密钥(一次):
mkdir -p ~/.ssh && chmod 700 ~/.ssh
ssh-keygen -t ed25519 -N "" -C "tv-termux@tcl" -f ~/.ssh/id_ed25519
cat ~/.ssh/id_ed25519.pub # 加到开发机的 ~/.ssh/authorized_keys挂到 ~/.bashrc —— 打开 Termux 就起隧道,顺手把页面拉起来:
PF=$HOME/.ssh/herdr-tunnel.pid
if ! { [ -f "$PF" ] && kill -0 "$(cat "$PF")" 2>/dev/null; }; then
( setsid nohup "$HOME/.termux/herdr-tunnel.sh" </dev/null >/dev/null 2>&1 & )
echo "[herdr] 隧道已启动(切到 TV Bro 即可)"
fi并在 ~/.bash_profile 里加 . ~/.bashrc —— 否则 login shell 不加载 .bashrc
(实测这台 Termux 没有 .bash_profile)。
页面交给 TV Bro 的主页,不要在
.bashrc里am start。 实测过自动打开确实能用,但有两个副作用:① 你打开 Termux 只为干别的时, TV Bro 会在 6 秒后抢焦台盖掉它;② TV Bro 已经开着页面时被 VIEW intent 重新拉起, 可能重新加载、丢掉当前 UI 状态。 改为主页:TV Bro 里打开http://127.0.0.1:7317设为主页 —— force-stop 后只启动也直奔该页(实测)。
可选:~/.termux/boot/10-herdr-tunnel.sh 里 ping 等网络后 exec 同一个脚本,
需要装 Termux:Boot(纯 Java,不受 32 位限制)。
macOS 用 launchd(用户级 ~/Library/LaunchAgents/),Linux 用 systemctl --user。
两个单元,环境里 PATH 必须写全,RunAtLoad=true +
KeepAlive { SuccessfulExit: false }。
- herdr server 的启动器:先
herdr status server,已在跑就exit 0(配合SuccessfulExit:false不会重拉)⇒ 不用立刻重启你正在跑的 herdr。 启动命令要写成exec script -q /dev/null herdr server套一层 PTY —— 否则 herdr 不报direct_terminal_attach,终端会降级成 mirror。 - web ui 的启动器:等 herdr 就绪后跑
bun run server/managed.ts。⚠️ 它的能力探测启动时只算一次,改完 herdr 那边必须重启 web ui。
两个动作:打开 Termux(起隧道)→ 打开 TV Bro(主页就是 herdr 页面)。
| 情况 | 你要做什么 |
|---|---|
| 电视开机 / 重启 | 开 Termux → 开 TV Bro |
| 从屏保回来 | 遥控唤醒 → 切回 TV Bro 刷新 |
| 开发机重启 / 网络抖动 | 什么都不用做(自动重连) |
ERROR_CONNECTION_REFUSED |
打开 Termux,等 5~10 秒 → 刷新 |
唯一软肋:Android 内存紧张时可能杀掉 Termux ⇒ 隧道断 ⇒ 再打开一次 Termux。
打字:拼音 → 空格/数字/点候选 选词 → 回车 发送;Shift 切中英。
候选栏还在时直接回车只会把拼音上屏(标准 IME 语义,PC 上一样)。
| 坑 | 现象 | 修法 |
|---|---|---|
| 插件装不上 | bun not found |
herdr 的 PATH 里没有 bun → 走源码安装 |
| 终端报「服务器无法启动 herdr」 | 聊天能看、终端连不上 | PATH 少了 ~/.local/bin,或设 HERDR_WEB_HERDR_BIN |
| launchd 起的 herdr 丢能力 | terminal_attach:false,终端变 mirror |
启动器套 script -q /dev/null;再重启 web ui |
| 隧道起不来 | 反复 Address already in use |
有残留进程:pkill -9 -f '[s]sh -N -L 7317' |
密钥在却 Permission denied |
隧道秒退 | 密钥写到了 /data/data/com.termux/.ssh/,不是 files/home/.ssh/ |
| 多开几个守护 | 每开一次 Termux 多一个 | ps -A 看不到完整命令行 → pidfile + kill -0 |
机器 x86_64 / macOS 13.7.8 · herdr 0.9.3 · node v24.18.0 · bun 1.4.3
渲染 TV Bro(GeckoView)完整渲染真实工作区
终端 terminal_attach:true,终端可交互(提示符 bot@bot:~)
中文 实体键盘 nihao → ni'hao → 候选「你好 你号 泥嚎 拟好 倪好」→ 上屏 → 回车发送
持久化 开发机 reboot 后 launchd 自动起两单元(PPID=1、PATH 完整);
电视侧守护脚本 12 秒内自动重连;重启后中文依然可用
⚠️ 重启会丢掉 herdr 的所有 pane —— 那是重启本身的后果,不是方案的问题。 要"重启也保留工作区"得另外做 session 落盘/恢复,不在本文范围。
Shift切中英 ✅- 回车要"先选词、再回车"(标准 IME 语义)
- 切输入法(多个 IME 之间):Android 没有这个快捷键 ——
Ctrl+Space在PhoneWindowManager里被吃掉,用来切的是物理键盘布局。要的话得上 Key Mapper 这类按键映射应用。
官方 Keyboard 指南第一句:
Herdr is mouse-native. You can click panes, tabs, workspaces, and agents, drag split borders, and use right-click menus without learning a single keybinding. Keyboard control is optional.
所以电视上有鼠标就已经能用了,键盘快捷键是加成。这条直接决定了电视上的体验 下限 —— 不用背键位也能干活。
默认前缀 ctrl+b。prefix+c = 按 ctrl+b 松开,再按 c。
prefix+? 随时弹出帮助面板列出所有生效键位,里面按 / 还能过滤动作 ——
这是最好的发现途径,不用记本文这张表。
官方建议先学这五个:
| 动作 | 键 |
|---|---|
| 新标签页 | prefix+c |
| 向右 / 向下分屏 | prefix+v / prefix+minus |
| 在 pane 间移动 | prefix+h/j/k/l |
| 工作区导航 | prefix+w |
| Detach(全都留着继续跑) | prefix+q |
其余按任务分:
| 分类 | 键 |
|---|---|
| Pane | prefix+z 缩放 · prefix+x 关闭 · prefix+shift+h/j/k/l 交换 · prefix+r 调大小 · prefix+[ 复制模式 |
| Tab | prefix+n / prefix+p 上/下一个 · prefix+1..9 跳转 · prefix+shift+t 重命名 · prefix+shift+x 关闭 |
| 工作区 / 会话 | prefix+shift+n 新建 · prefix+shift+w 重命名 · prefix+shift+d 关闭 · prefix+g Goto 面板 · prefix+b 侧栏 |
Goto 面板(prefix+g)在电视上特别值得记 —— 它把每个 agent/终端列成一行、
按工作区分组:/ 搜索(名字 / agent 类型 / tab / 工作区 / 分支 / 路径),
j/k 或上下移动,左右跳到上/下一个工作区分组(不绕回),
b/w/i/d 过滤 阻塞 / 工作中 / 空闲 / 完成,a 全显示。鼠标滚轮/拖滚动条也能滚。
复制模式(prefix+[):h/j/k/l + tmux 风格 w/b/e、{}、PageUp/Down、
/ ? 前后搜索、n/N 重复、v 或 Space 选区、y 或 Enter 复制、q/Esc 退出。
鼠标拖选直接复制,根本不进复制模式。
只影响方案 B(在电视的 Termux 里跑 herdr TUI)。 用方案 C 时 herdr 跑在开发机上,没这个冲突。
herdr 官方推荐的「免 prefix」方案全用 ctrl+alt 家族 —— 它把 Ghostty / iTerm2 /
kitty / WezTerm / Alacritty / GNOME Terminal / Konsole 以及 GNOME/KDE 的默认键位
都过了一遍,结论是 ctrl+alt 几乎没人占。
但 Termux 自己就占了一大片(从 TermuxTerminalViewClient.onKeyDown 抄出来的):
Termux 占用的 ctrl+alt+… |
干什么 |
|---|---|
n / ↓ |
下一个会话 |
p / ↑ |
上一个会话 |
→ / ← |
开 / 关抽屉 |
k |
开关软键盘 |
m |
菜单 |
r |
重命名会话 |
c |
新会话 |
u |
URL 选择 |
v |
粘贴 |
+ / - |
终端字号 |
1~9 |
切到第 N 个会话 |
所以 herdr 那份「免 prefix」配置里 ctrl+alt+c(新标签页)、ctrl+alt+n/p、
ctrl+alt+arrows 等等根本到不了 herdr。两条出路:
① 用 prefix 模型(推荐)。 herdr 默认走 ctrl+b,和 Termux 的 ctrl+alt 不冲突,
上面那几张表照用就行。
② 让 Termux 把 ctrl+alt 让出来。 ~/.termux/termux.properties:
disable-hardware-keyboard-shortcuts = true代价是连 ctrl+alt+= / - 调字号也一起没了(它和那些会话快捷键在源码里受
同一个开关管),那就改用 fontsize 偏好设字号(见上一节)。
shortcut.create-session / shortcut.next-session / shortcut.previous-session /
shortcut.rename-session 只能重绑那四个会话快捷键,不解决 +/-。
⚠️ 上面这两条来自源码,未在真机上实测 —— 没有能注入 Ctrl+Alt 组合键的手段 (Android 的input不支持keycombination),得用手按着验。
三套的操作习惯是同一套:电视输入法、遥控、装包/卸载全共用。差别只在“打字和字号”:
| 环节 | A 自建 PWA | B Termux | C herdr WebUI |
|---|---|---|---|
| 打字 | 网页输入框 | 终端(实体键盘打不了中文) | 网页输入框/终端(实体键盘可打中文) |
| 字号 | 网页自定 | ctrl+alt+=/-,或改 fontsize |
浏览器缩放即可 |
| 切中英 | — | — | 单按 Shift |
| 装 / 卸 | 都是 tools/tv-install.sh;卸载 adb uninstall <包名>(不受 TCL 拦截) |
三套指向同一台开发机上的同一个 herdr 会话(A/C 读 panes,B 直接 attach)—— 看到的是同一批 pane,可以混用。
Termux 的 GitHub 构建是 debuggable 的,所以 run-as 可以直接在它里面跑命令:
# 把任务写成脚本再推过去执行 —— 避免 adb shell→run-as→sh→bash 四层引号地狱
cat > /tmp/t.sh <<'EOS'
export PREFIX=/data/data/com.termux/files/usr
export HOME=/data/data/com.termux/files/home
export LD_LIBRARY_PATH="$PREFIX/lib"; export PATH="$PREFIX/bin:$PREFIX/bin/applets"
cd "$HOME"
"$PREFIX/bin/apt" update
EOS
"$ADB" -s "$TV" push "$(win_of /tmp/t.sh)" /data/local/tmp/t.sh
"$ADB" -s "$TV" shell "run-as com.termux cp /data/local/tmp/t.sh files/home/t.sh"
"$ADB" -s "$TV" shell "run-as com.termux sh files/home/t.sh"- heredoc 会失败 ——
can't create temporary file /data/local/xxx.tmp: Permission denied(它要往/data/local/写临时文件)。用上面的 push + cp,或 base64 内联:B=$(base64 -w0 file) adb shell "run-as com.termux sh -c 'export PATH=/data/data/com.termux/files/usr/bin:\$PATH; printf %s $B | base64 -d > files/home/xxx'"
PATH必须显式给,否则base64/mkdir这些外部程序找不到,管道会静默失败。 $HOME是/data/user/0/com.termux,不是 Termux 的家目录files/home/。 用$HOME拼路径会把密钥/配置写到错地方(实测把 ssh 密钥写到了/data/data/com.termux/.ssh/,导致后来隧道一直Permission denied)。 一律用绝对路径,或显式HOME=/data/data/com.termux/files/home。adb shell不转发 stdin 到run-as——adb shell 'run-as ...' < file拿不到内容。
另加一条(不属于 run-as,但同一次踩的):每条 adb shell 都要 </dev/null
(AGENTS.md §6)。非交互环境下 adb 会占住 stdin,把脚本后续命令的输入流吃掉,
现象是“第一条命令有输出、后面全部静默无输出”。
input text 'echo hi'能用(空格正常),但送不了换行 —— 写'...\n'会变成字面的\n(实测屏幕上出现B-OKn)。- 回车要单独发
input keyevent KEYCODE_ENTER,而且每次输入之间要留 1.5 s 以上, 否则几次输入会粘成一条命令(实测echo HELLO-FROM-ADBuname -aecho $SHELL; id)。 - 输入法会挡住半屏,先
input keyevent KEYCODE_BACK收掉再截图。
- 确认框的按钮
focused=false——input keyevent完全打不动,必须input tap点坐标 application-label里有空格 —— 旧的badging_field会截到第一个空格(TV Bro: …→TV), 于是列表里永远找不到;而且列表显示的是本地化名字(TV Bro: 电视网络浏览器)- TGuard 缓存以文件路径为键 —— 上一版 APK 删了,条目还在,而且永远解析不出应用名; 匹配到它就会「点了没反应」而版本号校验照样通过
前两条见 03-tcl-tv-sideload.md;第 3 条脚本里会自动把新版
改名叫缓存里的那个路径让条目重新生效,并在装完比一次落地字节数
(同一个 versionName 可以有天差地别的体积)。
| 我以为 | 实际 |
|---|---|
| 换浏览器就行 | 两个浏览器共用系统 WebView 83,表现完全一样 |
| 引擎太老 —— 对,但只对了一半 | 换了 Gecko 还是不行:TV Bro 默认仍用 WebView |
| 服务端给电视的是不同页面 | 拿电视 UA / Accept-Language 复验,字节数一模一样(155654) |
| MIME 类型不对 | 实测 text/css、application/javascript,全对 |
| CSS 取不到 | 在电视上直接打开那个 .css URL,能取到(满屏 CSS 文本) |
| 网站坏了 / 需要登录 | 同浏览器开百度完全正常 —— 「能渲染 CSS」≠「能渲染现代 CSS」 |
| Termux 要实体键盘 | 搜狗 TV 输入法能直接打进 Termux(走标准 InputConnection) |
input keyevent 送不了回车 |
能送,是我 sleep 太短导致多次输入粘在一起 |
最省时间的两个判据:
- 错误页是不是
net::ERR_*措辞 → 是则还在用 WebView(方案 A 的唯一分叉点) - 装完对不上时先比落地字节数,别只比
versionName