- 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 条目链过去
116 lines
4.0 KiB
Rust
116 lines
4.0 KiB
Rust
//! 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);
|
||
}
|
||
}
|