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:
AskaEth committed 2026-11-07 09:33:21 +08:00
1 parent 7b971dc75d
commit c434dbe335
7 files changed
+209 -10

No files matched your search

+13 -2
View File
@@ -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);
}
}
+2
View File
@@ -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"));