feat(termux): 横屏自适应窗口、安装后清理 apt 工作区、补分支 README
- app/boot/termux_window:Termux 上默认窗口按 X 屏幕等比自适应 (横屏铺满、竖屏不溢出,最大 2×;日志 source=termux-screen), ULL_WINDOW_SIZE 覆盖与 1180×760 验收基准不变;5 条单测 - termux::install 成功后清理 $PREFIX/tmp/ull-java-termux(一次安装约 100 MB) - 构建文档补 clippy/rustfmt 组件、cargo-clippy 目标被带成 android 的坑、 门禁命令、横屏行为与 openjdk-17 实测 - 新增 README.termux.md(分支定位/已验证矩阵/快速开始/边界/路线图), 两份主 README 的 Termux 条目链过去
This commit is contained in:
1 parent
7b971dc75d
commit
c434dbe335
7 files changed
+209
-10
No files matched your search
@@ -195,8 +195,8 @@ This list is the reason to believe the rest. None of it is hidden behind marketi
|
||||
- **Install progress is stage-level**, not byte-level, on the content-library install path.
|
||||
- **No native file chooser.** Import takes drag-and-drop or a pasted path.
|
||||
- **Android/Termux is experimental and not promised**: no released asset targets it. A GUI build is
|
||||
possible from source via the glibc toolchain (`packaging/docs/termux-glibc-build.md`); it was
|
||||
verified once on CPU rendering only.
|
||||
possible from source via the glibc toolchain (`packaging/docs/termux-glibc-build.md`, branch notes:
|
||||
[`README.termux.md`](README.termux.md)); it was verified once on CPU rendering only.
|
||||
|
||||
## Download and run
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Unlucky Launcher · Termux 适配(`termux-glibc` 分支)
|
||||
|
||||
这条分支把启动器带到 **Termux(Android)**:用 glibc 工具链编出 `aarch64-unknown-linux-gnu`
|
||||
二进制,在 **termux-x11** 里跑 GUI;同时补上 Termux 平台识别、系统 JDK 安装与中文字体。
|
||||
|
||||
## 已验证
|
||||
|
||||
| 项 | 状态 | 证据 |
|
||||
| --- | --- | --- |
|
||||
| 源码构建(GUI + CLI) | ✅ | Rust 1.99.1 gnu + clang/lld + glibc 运行时;CPU-only(tiny-skia) |
|
||||
| 窗口显示 | ✅ | termux-x11 `:0`,截图 1180×760,首帧 ~330 ms |
|
||||
| 中文 | ✅ | 启动时注册 `/system/fonts/NotoSansCJK-Regular.ttc` |
|
||||
| 微软登录 id | ✅ | 构建期注入,日志 `client_id=configured source=build` |
|
||||
| 横屏适配 | ✅ | 默认窗口按 X 屏幕等比自适应(`source=termux-screen`) |
|
||||
| Java 发现 | ✅ | `JAVA_HOME`/PATH + `$PREFIX/lib/jvm` 扫描 |
|
||||
| Java 自动安装 | ✅ | `ull java install 17` 由内置镜像装完 openjdk-17(约 20 s) |
|
||||
| GPU(wgpu) | ⏳ | bionic 侧 Vulkan/turnip 已验证;glibc 侧变体待构建验证 |
|
||||
| Minecraft 运行 | ⏳ | bionic JVM 加载不了 glibc natives;计划走 glibc JVM |
|
||||
|
||||
## 快速开始
|
||||
|
||||
完整配方(工具链、patchelf、linker、门禁)见
|
||||
[`packaging/docs/termux-glibc-build.md`](packaging/docs/termux-glibc-build.md)。最短路径:
|
||||
|
||||
```sh
|
||||
termux-x11 :0 -ac &
|
||||
DISPLAY=:0 ./unlucky-launcher-iced --software-renderer
|
||||
```
|
||||
|
||||
- 没有 GPU 栈时**必须**加 `--software-renderer`(CPU 渲染)。
|
||||
- 窗口尺寸默认按屏幕自适应;`ULL_WINDOW_SIZE=1520x900` 可覆盖。
|
||||
- Java:`ull java install 17|21|25` 在 Termux 上默认用系统包(内置镜像 + 隔离 apt 状态)。
|
||||
|
||||
## 已知边界
|
||||
|
||||
- 仅单机(OnePlus 15 / Android 17)验证,试验级、不在发布矩阵内。
|
||||
- GUI 只验证了 CPU 渲染;GPU 变体需要 glibc 侧 Vulkan 栈。
|
||||
- 装好 JDK ≠ 能跑 MC:MC 的 LWJGL natives 是 glibc `.so`,bionic、JVM 加载不了。
|
||||
- Termux 上三条测试失败是环境差异(`/proc/*/children`、长路径脱敏、mtime flaky),
|
||||
PC 的 Linux CI 不受影响。
|
||||
|
||||
## 后续
|
||||
|
||||
1. glibc 侧 GPU(wgpu)变体与取证
|
||||
2. MC 运行链:glibc JVM(Temurin linux-aarch64 + glibc Mesa)或 proot 方案
|
||||
3. 剪贴板/IME 在 termux-x11 下的行为验证
|
||||
+1
-1
@@ -158,7 +158,7 @@ unlucky-launcher --version # CLI,版本 + 系统摘要
|
||||
文件是被静默覆盖的。
|
||||
- **内容库的安装进度是阶段级**,不是字节级。
|
||||
- **没有原生文件选择框。** 导入靠拖拽或粘贴路径。
|
||||
- **Android/Termux 是试验级、不承诺**:发布矩阵里没有面向它的资产。GUI 可以经 glibc 工具链源码自建(见 `packaging/docs/termux-glibc-build.md`),目前只在 CPU 渲染下验证过一次。
|
||||
- **Android/Termux 是试验级、不承诺**:发布矩阵里没有面向它的资产。GUI 可以经 glibc 工具链源码自建(见 `packaging/docs/termux-glibc-build.md`,分支说明见 [`README.termux.md`](README.termux.md)),目前只在 CPU 渲染下验证过一次。
|
||||
|
||||
## 下载与运行
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ use super::{MIN_SIZE, WINDOW_SIZE};
|
||||
|
||||
mod diagnostics;
|
||||
mod fallback;
|
||||
mod termux_window;
|
||||
|
||||
use fallback::{on_failure, respawn, OnFailure};
|
||||
|
||||
@@ -131,10 +132,20 @@ fn launch(
|
||||
|
||||
// M98:`ULL_WINDOW_SIZE=1520x900` 覆盖尺寸(矩阵的「最小 / 宽屏」两档靠它,且必须走**真外壳**
|
||||
// ——`pages_demo` example 的标题栏是留白带,出不了「三键位置」那张图)。
|
||||
let (size, size_source) = acceptance::window_size(
|
||||
// Termux:没有环境覆盖时,默认尺寸按 X 屏幕等比自适应(横屏优先),
|
||||
// 失败就退回 1180×760 的验收尺寸。
|
||||
let (default_size, default_source) = termux_window::default_size(
|
||||
(WINDOW_SIZE.width, WINDOW_SIZE.height),
|
||||
(MIN_SIZE.width, MIN_SIZE.height),
|
||||
);
|
||||
)
|
||||
.unwrap_or(((WINDOW_SIZE.width, WINDOW_SIZE.height), "default"));
|
||||
let (size, size_source) =
|
||||
acceptance::window_size(default_size, (MIN_SIZE.width, MIN_SIZE.height));
|
||||
let size_source = if size_source == "default" {
|
||||
default_source
|
||||
} else {
|
||||
size_source
|
||||
};
|
||||
let settings = diagnostics::window_settings(size);
|
||||
// 两行日志与开窗用的是**同一个** settings(见模块文档纪律 2)
|
||||
diagnostics::report_window(&settings, options.scale_factor, size_source);
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
//! Termux / termux-x11 下的窗口尺寸自适应(横屏优先)。
|
||||
//!
|
||||
//! 验收尺寸(1180×760)是 D30 的硬要求,不能改;但手机显示器可能横屏远大于它
|
||||
//! (例如 2066×1272)或竖屏略小。这里只在**运行时**把「默认尺寸」换成按当前 X
|
||||
//! 屏幕算出的最大等比矩形;`ULL_WINDOW_SIZE` 覆盖仍然优先(见 `acceptance::window_size`)。
|
||||
//!
|
||||
//! 只在 Termux + 有 `DISPLAY` 时探测;`xdpyinfo` 缺失/解析失败一律回默认,不影响其它平台。
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
use unlucky_core::platform::paths::ProcessEnv;
|
||||
use unlucky_core::platform::termux;
|
||||
|
||||
/// 屏幕四周留白(像素)。
|
||||
const MARGIN: f32 = 32.0;
|
||||
/// 最大放大倍数:大屏上也不把窗口撑成巨屏。
|
||||
const MAX_SCALE: f32 = 2.0;
|
||||
/// `xdpyinfo` 里尺寸那一行的前缀。
|
||||
const DIMENSIONS_PREFIX: &str = "dimensions:";
|
||||
|
||||
/// Termux 上的默认窗口尺寸:按 X 屏幕等比放大/缩小,失败返回 `None`(用调用方默认)。
|
||||
///
|
||||
/// `default` / `min` 由调用方传入([`super::WINDOW_SIZE`] / [`super::MIN_SIZE`]),
|
||||
/// 这里不复制常量。
|
||||
#[must_use]
|
||||
pub(super) fn default_size(
|
||||
default: (f32, f32),
|
||||
min: (f32, f32),
|
||||
) -> Option<((f32, f32), &'static str)> {
|
||||
if !termux::is_termux(&ProcessEnv) || std::env::var_os("DISPLAY").is_none() {
|
||||
return None;
|
||||
}
|
||||
let screen = x_screen_size()?;
|
||||
Some((fit_screen(default, min, screen), "termux-screen"))
|
||||
}
|
||||
|
||||
/// 在 `screen` 里放一个保持 `default` 宽高比的最大矩形(保留留白,不突破 `min`)。
|
||||
#[must_use]
|
||||
pub(super) fn fit_screen(default: (f32, f32), min: (f32, f32), screen: (f32, f32)) -> (f32, f32) {
|
||||
if screen.0 <= 0.0 || screen.1 <= 0.0 || default.0 <= 0.0 || default.1 <= 0.0 {
|
||||
return default;
|
||||
}
|
||||
let available = (
|
||||
(screen.0 - MARGIN * 2.0).max(1.0),
|
||||
(screen.1 - MARGIN * 2.0).max(1.0),
|
||||
);
|
||||
let scale = (available.0 / default.0)
|
||||
.min(available.1 / default.1)
|
||||
.min(MAX_SCALE);
|
||||
let width = (default.0 * scale).max(min.0).min(screen.0);
|
||||
let height = (default.1 * scale).max(min.1).min(screen.1);
|
||||
(width, height)
|
||||
}
|
||||
|
||||
/// 当前 X 屏幕尺寸(`xdpyinfo` 的 `dimensions: WxH pixels`)。
|
||||
fn x_screen_size() -> Option<(f32, f32)> {
|
||||
let output = Command::new("xdpyinfo").output().ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
let text = String::from_utf8_lossy(&output.stdout);
|
||||
let line = text
|
||||
.lines()
|
||||
.find(|line| line.trim_start().starts_with(DIMENSIONS_PREFIX))?;
|
||||
let value = line.split_whitespace().nth(1)?;
|
||||
let (width, height) = value.split_once('x')?;
|
||||
Some((width.parse().ok()?, height.parse().ok()?))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::fit_screen;
|
||||
|
||||
const DEFAULT: (f32, f32) = (1180.0, 760.0);
|
||||
const MIN: (f32, f32) = (880.0, 560.0);
|
||||
|
||||
fn close(actual: (f32, f32), expected: (f32, f32)) {
|
||||
assert!(
|
||||
(actual.0 - expected.0).abs() < 0.5 && (actual.1 - expected.1).abs() < 0.5,
|
||||
"expected {expected:?}, got {actual:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn landscape_screen_scales_up_keeping_aspect() {
|
||||
// 2066×1272:留白后 2002×1208,高度是瓶颈 → 1875.6×1208。
|
||||
close(
|
||||
fit_screen(DEFAULT, MIN, (2066.0, 1272.0)),
|
||||
(1875.58, 1208.0),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portrait_screen_still_fits_width() {
|
||||
// 1272×2066:宽度是瓶颈 → 1208×778。
|
||||
close(fit_screen(DEFAULT, MIN, (1272.0, 2066.0)), (1208.0, 778.03));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_screen_is_capped_at_two_times() {
|
||||
close(fit_screen(DEFAULT, MIN, (4000.0, 3000.0)), (2360.0, 1520.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiny_screen_never_goes_below_min_or_overflow() {
|
||||
let (width, height) = fit_screen(DEFAULT, MIN, (800.0, 600.0));
|
||||
assert_eq!(width, 800.0, "宽度受屏幕限制");
|
||||
assert_eq!(height, 560.0, "高度不小于最小尺寸");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_screen_falls_back_to_default() {
|
||||
assert_eq!(fit_screen(DEFAULT, MIN, (0.0, 0.0)), DEFAULT);
|
||||
}
|
||||
}
|
||||
@@ -127,6 +127,8 @@ fn run_install(
|
||||
match apt_install(prefix, workspace, mirror, package) {
|
||||
Ok(()) => {
|
||||
if let Some(home) = installed_home(prefix, major) {
|
||||
// 装完把隔离的 lists/cache/archives 清掉(几十到上百 MB,别留在 $PREFIX/tmp)。
|
||||
let _ = std::fs::remove_dir_all(workspace);
|
||||
return Ok(home);
|
||||
}
|
||||
failures.push(format!("{mirror}:装完找不到 bin/java"));
|
||||
|
||||
@@ -35,12 +35,13 @@ G="$PWD/.gnu"
|
||||
|
||||
### 2.2 Rust gnu 工具链
|
||||
|
||||
下载 Rust 1.99.1(或与 `rust-version` 兼容的 stable)的 `rustc` / `rust-std` / `cargo` 三个
|
||||
`aarch64-unknown-linux-gnu` 组件。官方源在国内很慢,用 USTC 镜像:
|
||||
下载 Rust 1.99.1(或与 `rust-version` 兼容的 stable)的 `rustc` / `rust-std` / `cargo`,
|
||||
以及跑门禁需要的 `clippy` / `rustfmt` 五个 `aarch64-unknown-linux-gnu` 组件。官方源在国内很慢,
|
||||
用 USTC 镜像:
|
||||
|
||||
```sh
|
||||
mkdir -p "$TMPDIR/ull-gnu" && cd "$TMPDIR/ull-gnu"
|
||||
for c in rustc rust-std cargo; do
|
||||
for c in rustc rust-std cargo clippy rustfmt; do
|
||||
curl -fLO "https://mirrors.ustc.edu.cn/rust-static/dist/$c-1.99.1-aarch64-unknown-linux-gnu.tar.xz"
|
||||
done
|
||||
```
|
||||
@@ -56,11 +57,17 @@ done
|
||||
|
||||
```sh
|
||||
LD="$PREFIX/glibc/lib/ld-linux-aarch64.so.1"
|
||||
for f in "$G/toolchain/bin/rustc" "$G/toolchain/bin/cargo" "$G/toolchain/bin/rustdoc"; do
|
||||
patchelf --set-interpreter "$LD" --set-rpath "$PREFIX/glibc/lib:$G/toolchain/lib" "$f"
|
||||
for f in rustc cargo rustdoc clippy-driver cargo-clippy rustfmt cargo-fmt; do
|
||||
patchelf --set-interpreter "$LD" --set-rpath "$PREFIX/glibc/lib:$G/toolchain/lib" \
|
||||
"$G/toolchain/bin/$f"
|
||||
done
|
||||
```
|
||||
|
||||
> **坑(实测)**:`cargo clippy` 会在 PATH 里找 `cargo-clippy`。如果只装了 Termux 的 Rust,
|
||||
> 找到的是 android 版 `cargo-clippy`,`aws-lc-sys` 等构建脚本会被当成
|
||||
> `--target=aarch64-linux-android` 编译并失败。把 gnu 工具链的 `bin` 放在 PATH 最前面即可;
|
||||
> `rustc`/`rustfmt` 同理。
|
||||
|
||||
### 2.4 gcc 运行时片段
|
||||
|
||||
`clang -fuse-ld=lld` 仍然需要 `crtbeginS.o` / `crtendS.o` / `libgcc.a`。从 Debian 的
|
||||
@@ -112,6 +119,18 @@ cargo --config 'source.crates-io.replace-with="ustc"' \
|
||||
|
||||
`--no-default-features` 关掉 `gpu`,只编 tiny-skia(CPU)。产物:`.gnu/target/release/unlucky-launcher-iced`。
|
||||
|
||||
门禁(与 CI 同一组命令,环境同上):
|
||||
|
||||
```sh
|
||||
cargo fmt --all --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace --all-targets --no-fail-fast
|
||||
```
|
||||
|
||||
Termux 上有三条**环境差异**失败(不是回归):`launch` 测试读
|
||||
`/proc/<pid>/task/<pid>/children`(Android 内核没有)、natives 事件的长路径被脱敏成
|
||||
`<opaque>`、`settings_bridge` 的 mtime 热重载偶发(flaky)。PC 的 Linux CI 不受影响。
|
||||
|
||||
## 4. 运行(termux-x11)
|
||||
|
||||
```sh
|
||||
@@ -134,6 +153,9 @@ DISPLAY=:0 .gnu/target/release/unlucky-launcher-iced \
|
||||
- 中文:GUI 在 Termux 上会把 `/system/fonts/NotoSansCJK-Regular.ttc` 注册进 cosmic-text
|
||||
(fontdb 不扫 Android 的字体目录),无需额外环境变量。
|
||||
- 启动日志里 `[auth] msa client_id=configured source=build` 表示构建期 id 已烘入。
|
||||
- **横屏优先**:Termux 上默认窗口尺寸会按当前 X 屏幕等比自适应(日志
|
||||
`[window] ... source=termux-screen`),横屏铺满、竖屏也不溢出;验收尺寸 1180×760 仍是
|
||||
默认基准,`ULL_WINDOW_SIZE=1520x900` 这类覆盖仍然优先。
|
||||
|
||||
### 4.1 Termux 上的 Java
|
||||
|
||||
@@ -141,8 +163,11 @@ DISPLAY=:0 .gnu/target/release/unlucky-launcher-iced \
|
||||
|
||||
- 内置 SAU / TUNA / USTC / 官方四个 termux-main 镜像,按顺序尝试;
|
||||
- `apt-get` 的 lists/cache 隔离在 `$PREFIX/tmp/ull-java-termux/`,不改用户的 apt 配置;
|
||||
装完自动清理该目录(一次安装约 100 MB 临时占用);
|
||||
- 装完从 `$PREFIX/lib/jvm/java-<major>-openjdk` 发现,`ull java list` 直接可见;
|
||||
- `--vendor termux` 只走系统包;`temurin|oracle|microsoft|zulu` 仍走归档下载链。
|
||||
- 已实测:`ull java install 17` 从 SAU 镜像装完 `openjdk-17`(含 `openjdk-17-x`),
|
||||
约 20 秒;`ull java list` 随后列出 `[system]` 的 17。
|
||||
|
||||
## 5. GPU(未在 glibc 侧验证)
|
||||
|
||||
|
||||
Reference in new issue
Block a user