diff --git a/.gitignore b/.gitignore index eaf1b59..4c223ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,16 @@ # 构建产物。 # -# 四行都必要:根目录是 firmware crate,protocol/ 和 client/ 各自也是独立的 -# crate(各有自己的 .cargo/config.toml 与 target 目录),armor/ 是一套独立 -# workspace(协议层 + ESP32-C3 固件),同理。所以只写 `target/` 匹配不到它们。 +# 每一行都必要:根目录是 firmware crate,protocol/ 也是独立的 crate(有自己的 +# .cargo/config.toml 与 target 目录),armor/ 是一套独立 workspace(协议层 + +# ESP32-C3 固件),同理。所以只写 `target/` 匹配不到它们。 # # 全部锚定在根目录(前置 /),这是有意的:components/esp32-camera/target/ 看着 # 像构建产物,其实是上游组件的**按芯片分目录**(esp32/ esp32s2/ esp32s3/ …), # 里面是 ll_cam.c 等源码,包含本项目打的补丁。不要把它一起忽略掉。 # -# desktop/ 自己还有一份 .gitignore(node_modules、dist、src-tauri/target)。 +# desktop/ 自己还有一份 .gitignore(node_modules、dist、三个 crate 的 target)。 /target/ /protocol/target/ -/client/target/ /armor/target/ # 本机专属的 cargo 注册表缓存。 @@ -23,3 +22,6 @@ # WiFi 凭据,编译期由 env! 读取。不要提交。 .env + +# pnpm 在本机把 store 落在了工作区里(沙箱下 HOME 里的 store 不可写)。 +/.pnpm-store/ diff --git a/README.md b/README.md index 5e86387..d611c64 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,22 @@ 两块 ESP32-S3-WROOM-1-N16R8 图传板(DVP 接口接 OV5640),向队伍客户端与裁判终端推 UDP 图传。 -固件是跑在 ESP-IDF 上的 Rust,用官方 esp32-camera 组件采集;客户端是 Rust + Slint -桌面程序。 +固件是跑在 ESP-IDF 上的 Rust,用官方 esp32-camera 组件采集;桌面端是 **Tauri v2 + +React**,用户端与裁判端两套页面(见 [`desktop/README.md`](desktop/README.md))。同一套 +系统里还有一队一块的**装甲板**(ESP32-C3,一主三从),负责四路击打检测、灯带与整车血量, +并把血量广播给裁判终端。 ```text -protocol/ 线路格式 + 帧重组 两端共用,无 ESP-IDF 依赖,13 个单元测试 -. 固件 -> ESP32-S3 交叉编译,Xtensa -client/ 桌面客户端 Slint,队伍端 / 裁判端两种身份 +protocol/ 线路格式 + 帧重组 三端共用,无 ESP-IDF 依赖,14 个单元测试 +. 图传固件 -> ESP32-S3 交叉编译,Xtensa +armor/ 装甲板固件 -> ESP32-C3 独立 workspace,RISC-V;规则层 22 个单元测试 +desktop/ 桌面端 Tauri v2 + React;用户端(血条 + 瞄准器)/ 裁判端 + core/ 不含界面与 Tauri 的网络层、配置、密码(可单测) + src-tauri/ Tauri 后端:推流、命令、录制 + src/ React 前端:两套页面 + HUD ``` -三个 crate 各自独立构建,各有自己的 `.cargo/config.toml`。 +五份产物各自独立构建,各有自己的 `.cargo/config.toml`。 ## 拓扑 @@ -25,16 +31,19 @@ client/ 桌面客户端 Slint,队伍端 / 裁判端两种身份 ┌────────────────────┼────────────────────┐ │ │ │ ┌───────┴───────┐ ┌───────┴───────┐ ┌───────┴───────┐ - │ 图传板 A │ │ 图传板 B │ │ 装甲板 ×4 │ - │ (队伍一) │ │ (队伍二) │ │ (预留,未实现) │ + │ 图传板 A │ │ 图传板 B │ │ 装甲板主板 │ + │ (队伍一) │ │ (队伍二) │ │ ×2(每队一块)│ └───────────────┘ └───────────────┘ └───────────────┘ ``` -**两块板子不区分队伍。** 它们只知道自己是谁(MAC 派生的 `sender_id`),"哪一边"由 +**图传板不区分队伍。** 它们只知道自己是谁(MAC 派生的 `sender_id`),"哪一边"由 客户端决定。烧同一份固件即可,配置全在客户端一侧。 -裁判终端同时收两块板子。队伍客户端也收全部板子的视频,然后在**界面层**过滤到绑定的 -那一块。 +**装甲板反过来:队伍是编译期定的**(`ARMOR_TEAM=0/1`,同时决定灯色红/蓝),因为血量 +上报里必须带 `team` 字段,而且灯色现场不该靠按键切错。见 `armor/README.md`。 + +裁判终端同时收两块图传板,也收两块装甲板主板在 5601 上广播的血量。队伍客户端也收 +全部板子的视频,然后在**界面层**过滤到绑定的那一块。 ## 实测性能 @@ -98,6 +107,9 @@ app)、打印串口日志。ESP-IDF 位置从 `.cargo/config.toml` 读,不 WIFI_SSID=你的2.4G网络 WIFI_PASS=密码 +# 装甲板(armor/)用这两个里的第一个;队伍号决定灯色与上报里的 team 字段: +#ARMOR_TEAM=0 # 0 = 队伍一(红),1 = 队伍二(蓝) + # 以下都有默认值: #CAM_RES=vga # qqvga | qvga | cif | hvga | vga | svga | xga | sxga | uxga #CAM_QUALITY=12 # 0-63,数字越小画质越好、字节越少 @@ -106,29 +118,40 @@ WIFI_PASS=密码 ``` 凭据由 `env!` 在**编译期**读取,所以没有运行时解析路径,也不会因为忘了读 `.env` 而 -在板子上带着别人的密码。 +在板子上带着别人的密码。`ARMOR_TEAM` 同理:换队伍重刷装甲板固件即可,不用擦 NVS。 `WIFI_AUTH` 一般不用设:固件先扫描 AP,用它报告的真实认证方式去连,只有扫不到时才 需要人工指定。指定之后**只**试这一种方式——不成功就直接报错,而不是挨个试 10 秒。 挨个试等于在猜一个本该已知的值。 -### 2. 客户端 +### 2. 装甲板固件(ESP32-C3) ```sh -cd client && cargo build --release -./target/aarch64-apple-darwin/release/cam-client --list +scripts/flash_armor.sh # 编译 + 烧录 + 20 秒串口日志 +python3 scripts/armor_probe.py 15 # 不开界面,单看板子在广播什么血量 ``` -`--list` 列出发现的板子。然后: +接线表、血量规则、C3 的 RMT/ADC 踩坑都在 [`armor/README.md`](armor/README.md)。 + +### 3. 桌面端 ```sh -cam-client # 裁判端,无需配置 -cam-client --role team --team 1 # 队伍端,首次运行问绑定 +cd desktop +pnpm install +pnpm app # = tauri dev ``` -**`client/.cargo/config.toml` 里的 `target = "aarch64-apple-darwin"` 换机器必须改。** -client/ 嵌在固件的目录树里,会继承父目录那个 `xtensa-esp32s3-espidf` 的交叉编译配 -置,然后在依赖深处以 `libc` 找不到 `MAP_FAILED` 的形式失败。 +没有板子也能把整条链路跑起来:`CAM_DEMO=1 pnpm app` 会合成**协议数据报**喂进真实的接收 +路径(分片、重组、发现、掉血派生、两道闸门全在跑真代码),界面上标着"演示模式"。 + +首次启动会问这台机器是哪一端:**用户端**要绑一块图传板与一块装甲板(改动需密码), +**裁判端**不需要任何配置就能看两路。 + +**`desktop/.cargo/config.toml` 里的 `target = "aarch64-apple-darwin"` 换机器必须改。** +desktop/ 嵌在固件的目录树里,会继承父目录那个 `xtensa-esp32s3-espidf` 的交叉编译配置, +然后在依赖深处以 `libc` 找不到 `MAP_FAILED` 的形式失败。 + +界面、视频通道、密码模型、录制与排查都在 [`desktop/README.md`](desktop/README.md)。 ## 发现是双向的 @@ -303,9 +326,9 @@ OV5640 的 JPEG 编码器**在传感器内部**。DVP 接口出来的一帧要 一张 VGA 的 JPEG 解码要几毫秒。放在收包线程里会挤进数据报之间,造成**链路上本不存在 的丢帧**——不是真的丢了网络包,是应用来不及取走 socket 缓冲。 -`slint::Image` 不是 `Send`,所以 worker 线程只能处理纯数据(`Vec` + 尺寸),`Ima -ge` 的构造必须留在事件循环。这条约束决定了整个客户端的线程结构,细节在 -`client/README.md`。 +现在桌面端把 JPEG **原样**交给 WebView,由浏览器的原生解码器解——后端连解都不解, +接收线程只做一次 16 字节头的拼接与一次广播。这一条比上面那条更彻底:省掉的不是几毫秒, +而是一整个线程与一次内存拷贝。细节在 [`desktop/README.md`](desktop/README.md)。 ## 排查 @@ -315,11 +338,13 @@ ge` 的构造必须留在事件循环。这条约束决定了整个客户端的 | 几帧后停止出图,报 `NO-EOI` | `PSRAM DMA` 被打开了 | | 板子在线但 `0 clients subscribed` | 订阅包格式不对。`Subscribe` 的 `role` 字节紧跟 27 字节头,在偏移 27 | | 一直接到但画面偶尔解不出来 | 帧重组的问题。**先怀疑你的测量工具**:本项目的探测脚本就因为重组状态没按 `frame_id` 隔离,误报过 34 张坏帧 | -| `libc` 找不到 `MAP_FAILED` | `client/.cargo/config.toml` 的 target 没改成宿主三元组 | +| `libc` 找不到 `MAP_FAILED` | `desktop/.cargo/config.toml` 的 target 没改成宿主三元组 | | 串口看不到任何输出 | 非 TTY 下 `espflash monitor` 不可用,用 `/tmp/rd2.py` 裸读 | | WiFi 连不上 | SSID 必须在 2.4GHz。5G 频段看不见 | -| 裁判端两块画面宽窄不一 | 检查那两格是否都写了相同的 `preferred-width`,且占位文字是绝对定位(`x`/`y`)而不是普通布局 | -| 放大后画面没变清晰 | `image-fit` 必须跟着 zoom 在 `contain`/`fill` 之间切。只用 `contain` 的话画面不会跟着放大 | +| 裁判端两块画面宽窄不一 | 两格必须是 `flex: 1 1 0` 且 `min-width: 0`:按内容分宽度时,占位文字长的那一格会多占十几个像素 | +| 裁判端两格显示同一块板子 | 旧版槽位分配的真 bug。现在候选里要排除另一个槽位占着的 id | +| 有画面但状态栏说"离线" | 视频帧也必须算存活证据(`Discovery::touch`),不能只看 Hello | +| 改了绑定却"没生效" | 改动需要先解锁;后端每个改动命令都会校验,界面上的入口只是入口 | 独立核对线路格式: @@ -330,20 +355,38 @@ python3 scripts/udp_probe.py 10 它手写了每一个协议常量,**不依赖 `protocol` crate**——正因为如此,如果 crate 的编码 有错,脚本会立刻报出不一致。 -## 三份文档 +## 四份文档 | | | |---|---| | 根 `README.md` | 你在这里。拓扑、性能、构建烧录、硬件坑 | | `protocol/README.md` | 线路格式、帧重组的取舍、每个拒收条件 | -| `client/README.md` | 命令行、绑定流程、Slint 的硬约束、线程模型 | +| `armor/README.md` | 装甲板接线表、血量规则、上报、C3 的 RMT/ADC 踩坑 | +| `desktop/README.md` | 两套页面、视频通道路径、两道闸门、密码模型、录制与排查 | -## 装甲板(预留) +## 装甲板(已实现) -`protocol::ArmorHealth`、`ARMOR_PORT`(5601)、`MsgType` 5 号都已就位。客户端有真实的 -`ArmorSource` trait,当前实现是 `NoArmorYet`。 +`armor/` 是一套独立的 Rust workspace(目标 **ESP32-C3**,RISC-V): -**界面显示占位符而不是 0。** 0 看起来像真值——现场看到两个 0 会以为对方已经没血了。 +- `armor-protocol` —— 规则层:血量(100 起、每击 −3)、每板独立的 50 ms 受击冷却、 + 受击后**只有那一路**灯带全黑 50 ms、按键 FSM、上报编码。纯计算,主机上 22 个测试。 +- `armor-firmware` —— 固件:四路压电片 40 kHz ADC 连续采样、四路 WS2812(C3 只有一个 + RMT 通道可用,靠 `rmt_tx_switch_gpio` 轮流驱动)、NVS、BOOT 键、WiFi + UDP。 -接上以后板子向 `ARMOR_PORT` 发这个结构,客户端实现 `ArmorSource` 即可,界面和其余代 -码都不用改。 +硬件是"一主三从":三块从板里没有 MCU,各自只有一路灯带和一路压电片,直接接线到主板。 +接线表与实现细节见 [`armor/README.md`](armor/README.md)。 + +```sh +scripts/build_armor.sh # 编译(WIFI_SSID/WIFI_PASS/ARMOR_TEAM 取自 .env) +scripts/flash_armor.sh # 编译 + 烧录 + 串口日志 +python3 scripts/armor_probe.py 15 # 不开界面,单看板子在广播什么血量 +``` + +桌面端侧:`desktop/core/src/armor.rs` 的 `ArmorHealthStore` 监听 5601,按 `(team, index)` +存最后一次读数,四条都在 1.5 s 内收到才显示——**没有数据时显示占位符而不是 0**, +0 看起来像真值,现场看到会以为对方已经没血了。界面上直接显示血量数字(整车一条血, +归零显示 `0 · 已击毁` 并转红);数字变化也会打进客户端日志。 + +同一块板子还会被单独记下来(帧头里的 MAC 派生 id),所以两端都能列出"场上有哪几块装甲板" +并选一块绑定——命中反馈也是从这个 id 上归位的。**这不需要改固件**:主板一直在用 +`encode_from(sender_id)` 把自己的 id 打进帧头。 diff --git a/client/.cargo/config.toml b/client/.cargo/config.toml deleted file mode 100644 index 0580ea2..0000000 --- a/client/.cargo/config.toml +++ /dev/null @@ -1,13 +0,0 @@ -# This crate builds for the host, not the ESP32. -# -# It sits inside the firmware's directory tree, so cargo walks up and inherits -# ../.cargo/config.toml, which pins `target = "xtensa-esp32s3-espidf"` and an -# ldproxy linker that only work inside the ESP-IDF cross build. Without this file -# the desktop client is cross-compiled for Xtensa, which fails deep inside -# dependencies (libc not finding MAP_FAILED and friends, because it was being -# compiled for an embedded target that has no mmap). -# -# Overriding the target back to the host triple is the whole fix. Nothing else in -# the parent config applies to a non-ESP target. -[build] -target = "aarch64-apple-darwin" diff --git a/client/Cargo.lock b/client/Cargo.lock deleted file mode 100644 index ba9e1c9..0000000 --- a/client/Cargo.lock +++ /dev/null @@ -1,6101 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "ab_glyph" -version = "0.2.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" -dependencies = [ - "ab_glyph_rasterizer", - "owned_ttf_parser", -] - -[[package]] -name = "ab_glyph_rasterizer" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" - -[[package]] -name = "accesskit" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" -dependencies = [ - "uuid", -] - -[[package]] -name = "accesskit_atspi_common" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023da0e5097f46df7092d5280b02efb9bbf8d93298daeced42652463e357d636" -dependencies = [ - "accesskit", - "accesskit_consumer", - "atspi-common", - "phf", - "serde", - "zvariant", -] - -[[package]] -name = "accesskit_consumer" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" -dependencies = [ - "accesskit", - "hashbrown 0.16.1", -] - -[[package]] -name = "accesskit_ios" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "750c4e9f6ce888dfe8a10c0f1b5ceb646a7854fd46579d74919219d1bb314083" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown 0.16.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit 0.2.2", -] - -[[package]] -name = "accesskit_macos" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown 0.16.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "accesskit_unix" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e156ed3802e35eefe894ef2671bc6c889303d8a7e110b5e1b48f504b91362f" -dependencies = [ - "accesskit", - "accesskit_atspi_common", - "async-channel", - "async-executor", - "async-task", - "atspi", - "futures-lite", - "futures-util", - "serde", - "zbus", -] - -[[package]] -name = "accesskit_windows" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106c2b961215864d1c2e703ee63269c25c4e80a577ffb2c1017b9c17dcdf83a1" -dependencies = [ - "accesskit", - "accesskit_consumer", - "hashbrown 0.16.1", - "static_assertions", - "windows", - "windows-core", -] - -[[package]] -name = "accesskit_winit" -version = "0.33.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b41e63a69f36d9f1f41e70464c7e5f72eee485ef26aab19f0b4f86e6c0a84c" -dependencies = [ - "accesskit", - "accesskit_ios", - "accesskit_macos", - "accesskit_unix", - "accesskit_windows", - "raw-window-handle", - "winit", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "getrandom 0.3.4", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "android-activity" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" -dependencies = [ - "android-properties", - "bitflags 2.13.2", - "cc", - "jni", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "num_enum", - "thiserror 2.0.21", -] - -[[package]] -name = "android-properties" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" - -[[package]] -name = "android_system_properties" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" -dependencies = [ - "libc", -] - -[[package]] -name = "annotate-snippets" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f211a51805bc641f3ad5b7664c77d2547af685cc33b4cd8d31964027a46f13f1" -dependencies = [ - "anstyle", - "memchr", - "unicode-width", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arboard" -version = "3.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" -dependencies = [ - "clipboard-win", - "log", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "parking_lot", - "percent-encoding", - "windows-sys 0.60.2", - "wl-clipboard-rs", - "x11rb", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" - -[[package]] -name = "as-raw-xcb-connection" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" - -[[package]] -name = "ash" -version = "0.38.0+1.3.281" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" -dependencies = [ - "libloading", -] - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix 1.1.5", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix 1.1.5", -] - -[[package]] -name = "async-recursion" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "async-signal" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 1.1.5", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "atspi" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" -dependencies = [ - "atspi-common", - "atspi-proxies", -] - -[[package]] -name = "atspi-common" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" -dependencies = [ - "enumflags2", - "serde", - "static_assertions", - "zbus", - "zbus-lockstep", - "zbus-lockstep-macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "atspi-proxies" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" -dependencies = [ - "atspi-common", - "serde", - "zbus", -] - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "base64" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" - -[[package]] -name = "bincode" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" -dependencies = [ - "serde", - "unty", -] - -[[package]] -name = "bindgen" -version = "0.72.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" -dependencies = [ - "bitflags 2.13.2", - "cexpr", - "clang-sys", - "itertools 0.13.0", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash 2.1.3", - "shlex 1.3.0", - "syn 2.0.119", -] - -[[package]] -name = "bit-set" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block2" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" -dependencies = [ - "objc2 0.5.2", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2 0.6.4", -] - -[[package]] -name = "blocking" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "borsh" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" -dependencies = [ - "bytes", - "cfg_aliases", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "by_address" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "byteorder-lite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "calloop" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" -dependencies = [ - "bitflags 2.13.2", - "log", - "polling", - "rustix 0.38.44", - "slab", - "thiserror 1.0.69", -] - -[[package]] -name = "calloop" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" -dependencies = [ - "bitflags 2.13.2", - "polling", - "rustix 1.1.5", - "tracing", -] - -[[package]] -name = "calloop-wayland-source" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" -dependencies = [ - "calloop 0.13.0", - "rustix 0.38.44", - "wayland-backend", - "wayland-client", -] - -[[package]] -name = "cam-client" -version = "0.1.0" -dependencies = [ - "anyhow", - "env_logger", - "image", - "log", - "protocol", - "slint", - "slint-build", -] - -[[package]] -name = "cc" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f74872d07caf508b30a21f6836e7d7016a2eaf7d9ff4f48deaa58cd8a0407630" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex 2.0.1", -] - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom 7.1.3", -] - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "cgl" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" -dependencies = [ - "libc", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "clang-sys" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "clipboard-win" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" -dependencies = [ - "error-code", -] - -[[package]] -name = "clru" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5" -dependencies = [ - "hashbrown 0.16.1", -] - -[[package]] -name = "codespan-reporting" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "combine" -version = "4.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-field-offset" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d535c69e75b4e3c69e17205b5b37a91936c54086cecd339b70071500786acb9c" -dependencies = [ - "const-field-offset-macro", - "field-offset", -] - -[[package]] -name = "const-field-offset-macro" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d5848843a221ac268cfdd6bfd14e68d5d3dfa79d52c880aafab93f9517fb74" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - -[[package]] -name = "countme" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" - -[[package]] -name = "crc32fast" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "critical-section" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" - -[[package]] -name = "crossbeam-channel" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "ctor" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83cf0d42651b16c6dfe68685716d18480d18a9c39c62d76e8cf3eb6ed5d8bcbf" -dependencies = [ - "dtor", -] - -[[package]] -name = "cursor-icon" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" - -[[package]] -name = "data-url" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" - -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror 2.0.21", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.119", - "unicode-xid", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags 2.13.2", - "objc2 0.6.4", -] - -[[package]] -name = "displaydoc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "dlib" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" -dependencies = [ - "libloading", -] - -[[package]] -name = "document-features" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" -dependencies = [ - "litrs", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" - -[[package]] -name = "drm" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" -dependencies = [ - "bitflags 2.13.2", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "libc", - "rustix 0.38.44", -] - -[[package]] -name = "drm-ffi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" -dependencies = [ - "drm-sys", - "rustix 1.1.5", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" -dependencies = [ - "libc", - "linux-raw-sys 0.9.4", -] - -[[package]] -name = "dtor" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edf234dd1594d6dd434a8fb8cada51ddbbc593e40e4a01556a0b31c62da2775b" - -[[package]] -name = "either" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "env_filter" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "error-code" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" - -[[package]] -name = "euclid" -version = "0.22.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" -dependencies = [ - "num-traits", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "femtovg" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31eebf3ab1b76359ccd5f850c07a7c6b2722c64d9127c40e02358e83f3ff9b1a" -dependencies = [ - "bitflags 2.13.2", - "bytemuck", - "fnv", - "glow", - "imgref", - "itertools 0.15.0", - "log", - "rgb", - "slotmap", - "swash", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if", - "libc", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" - -[[package]] -name = "fixed_decimal" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c3c892f121fff406e5dd6b28c1b30096b95111c30701a899d4f2b18da6d1bd" -dependencies = [ - "displaydoc", - "smallvec", - "writeable", -] - -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - -[[package]] -name = "flate2" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" -dependencies = [ - "crc32fast", - "miniz_oxide 0.9.1", - "zlib-rs", -] - -[[package]] -name = "float-cmp" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "font-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "fontdb" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2660c5e9157bf76d2db1294e4a9feba604ef610819a3b591088d0d8392a3290f" -dependencies = [ - "log", - "slotmap", - "tinyvec", -] - -[[package]] -name = "fontique" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6688bc1294fe7117d788937b6c53480169b29c566954af490830d4c09da9516a" -dependencies = [ - "hashbrown 0.17.1", - "linebender_resource_handle", - "memmap2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-text", - "objc2-foundation 0.3.2", - "parlance", - "read-fonts", - "roxmltree", - "smallvec", - "windows", - "windows-core", - "yeslogic-fontconfig-sys", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futures" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" - -[[package]] -name = "futures-executor" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "futures-sink" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" - -[[package]] -name = "futures-task" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" - -[[package]] -name = "futures-util" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "gbm" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" -dependencies = [ - "bitflags 2.13.2", - "drm", - "drm-fourcc", - "gbm-sys", - "libc", -] - -[[package]] -name = "gbm-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" -dependencies = [ - "libc", -] - -[[package]] -name = "gethostname" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" -dependencies = [ - "rustix 1.1.5", - "windows-link", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", -] - -[[package]] -name = "gif" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "glob" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" - -[[package]] -name = "glow" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "263218b0ba2b0715c3370fb04674f5f8aa331594b521c94ff0a8e4a8472d1386" -dependencies = [ - "js-sys", - "slotmap", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "glutin" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" -dependencies = [ - "bitflags 2.13.2", - "cfg_aliases", - "cgl", - "dispatch2", - "glutin_egl_sys", - "glutin_glx_sys", - "glutin_wgl_sys", - "libloading", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "raw-window-handle", - "wayland-sys", - "windows-sys 0.52.0", - "x11-dl", -] - -[[package]] -name = "glutin-winit" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" -dependencies = [ - "cfg_aliases", - "glutin", - "raw-window-handle", - "winit", -] - -[[package]] -name = "glutin_egl_sys" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" -dependencies = [ - "gl_generator", - "windows-sys 0.52.0", -] - -[[package]] -name = "glutin_glx_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" -dependencies = [ - "gl_generator", - "x11-dl", -] - -[[package]] -name = "glutin_wgl_sys" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" -dependencies = [ - "gl_generator", -] - -[[package]] -name = "gpu-allocator" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795" -dependencies = [ - "ash", - "hashbrown 0.16.1", - "log", - "presser", - "thiserror 2.0.21", - "windows", -] - -[[package]] -name = "grid" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5" - -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "num-traits", - "zerocopy", -] - -[[package]] -name = "harfrust" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" -dependencies = [ - "bitflags 2.13.2", - "bytemuck", - "read-fonts", - "smallvec", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash 0.1.5", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash 0.2.0", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -dependencies = [ - "foldhash 0.2.0", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "htmlparser" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ce8546b993eaf241d69ded33b1be6d205dd9857ec879d9d18bd05d3676e144" - -[[package]] -name = "i-slint-backend-linuxkms" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f519a8611a4982921e46e391015e5e40ceac5cc63813333ef91ada77df180d8" -dependencies = [ - "bytemuck", - "calloop 0.14.5", - "cfg_aliases", - "drm", - "gbm", - "glutin", - "i-slint-common", - "i-slint-core", - "i-slint-renderer-femtovg", - "i-slint-renderer-software", - "input", - "libseat", - "memmap2", - "nix", - "raw-window-handle", - "xkbcommon", -] - -[[package]] -name = "i-slint-backend-selector" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb974705d61ba56034f6129ff61f3e6282a2c0c07752f25e6cb417d81c0f0958" -dependencies = [ - "cfg-if", - "cfg_aliases", - "i-slint-backend-linuxkms", - "i-slint-backend-testing", - "i-slint-backend-winit", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "i-slint-renderer-femtovg", -] - -[[package]] -name = "i-slint-backend-testing" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01943d6c5ea5bc913d497bdaa423c309e7627afff47a6794bfd13be5c564209f" -dependencies = [ - "cfg_aliases", - "i-slint-common", - "i-slint-core", - "i-slint-renderer-software", - "vtable", -] - -[[package]] -name = "i-slint-backend-winit" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae4fcf1b19e9cb109984d2af87d2b9485388c619265bff71dd523133888bee2" -dependencies = [ - "accesskit", - "accesskit_winit", - "arboard", - "block2 0.6.2", - "bytemuck", - "cfg-if", - "cfg_aliases", - "futures", - "glutin", - "glutin-winit", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "i-slint-renderer-femtovg", - "i-slint-renderer-skia", - "i-slint-renderer-software", - "imgref", - "lyon_path", - "muda", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", - "objc2-ui-kit 0.3.2", - "pin-weak", - "raw-window-handle", - "rgb", - "scoped-tls-hkt", - "scopeguard", - "softbuffer", - "strum", - "vtable", - "wasm-bindgen", - "web-sys", - "webbrowser", - "windows", - "winit", - "zbus", -] - -[[package]] -name = "i-slint-common" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a578f3e4b681b2b9cbd326d3be19716e2fac51d00a8a5b00c0ef4ae923b5e6" -dependencies = [ - "derive_more", - "fontique", - "htmlparser", - "icu_decimal", - "icu_locale_core", - "icu_provider", - "pulldown-cmark", - "resvg", - "skrifa", -] - -[[package]] -name = "i-slint-compiler" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55d48ebef05e2bf09ba5a1fc67bcb11033b9e12a2a50ed3105c7d25e2b539d0" -dependencies = [ - "annotate-snippets", - "by_address", - "data-url", - "derive_more", - "i-slint-common", - "icu_normalizer", - "icu_properties", - "image", - "indexmap", - "itertools 0.15.0", - "lyon_extra", - "lyon_path", - "num_enum", - "proc-macro2", - "quote", - "rayon", - "resvg", - "rowan", - "rspolib", - "skrifa", - "smol_str 0.3.6", - "strum", - "swash", - "typed-index-collections", - "unicode-segmentation", - "url", -] - -[[package]] -name = "i-slint-core" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2ea15059d70bc31b1a8b1eb4c34f2c5df04cb7e53f36179b43c0261231d646" -dependencies = [ - "accesskit", - "async-channel", - "bitflags 2.13.2", - "cfg-if", - "chrono", - "clru", - "const-field-offset", - "derive_more", - "euclid", - "i-slint-common", - "i-slint-core-macros", - "icu_normalizer", - "image", - "js-sys", - "ksni", - "lyon_algorithms", - "lyon_extra", - "lyon_geom", - "lyon_path", - "num-traits", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "once_cell", - "parley", - "pin-project", - "pin-weak", - "portable-atomic", - "raw-window-handle", - "resvg", - "rgb", - "scopeguard", - "skrifa", - "slab", - "strum", - "swash", - "sys-locale", - "taffy", - "unicode-linebreak", - "unicode-script", - "unicode-segmentation", - "vtable", - "wasm-bindgen", - "web-sys", - "web-time", - "wgpu", - "windows", -] - -[[package]] -name = "i-slint-core-macros" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f5a8a076de52f53be80caa826599ed2107b54c9d4a421e66dc06cf893a984c" -dependencies = [ - "quote", - "serde_json", - "syn 3.0.6", -] - -[[package]] -name = "i-slint-renderer-femtovg" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb0de504d57cdadf80c2abf7bcb8b7135a3716aee1f41b6ebb90ea93e3649d7c" -dependencies = [ - "cfg-if", - "const-field-offset", - "femtovg", - "glow", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "imgref", - "lyon_path", - "pin-weak", - "rgb", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "i-slint-renderer-skia" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc9c1cf02385f1d26fc1f423544e52390a8e5c2d89420a8042aa94b4fe109d1f" -dependencies = [ - "bytemuck", - "cfg-if", - "cfg_aliases", - "clru", - "const-field-offset", - "glow", - "glutin", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "lyon_path", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", - "objc2-quartz-core 0.3.2", - "pin-weak", - "raw-window-handle", - "read-fonts", - "scoped-tls-hkt", - "skia-safe", - "softbuffer", - "spin_on", - "unicode-segmentation", - "vtable", - "wgpu", - "wgpu-hal", - "windows", - "windows-core", - "write-fonts", -] - -[[package]] -name = "i-slint-renderer-software" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f8c641e138a2cc96537cac4481f5c7c8bb8dcc66d09acaed269329c8738628a" -dependencies = [ - "bytemuck", - "clru", - "derive_more", - "euclid", - "i-slint-common", - "i-slint-core", - "lyon_path", - "num-traits", - "skrifa", - "swash", - "zeno", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_decimal" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eb8f655bba2d0c0459e43a5b0e6cd3cd388109898fb3d85d048165e8b0abd08" -dependencies = [ - "displaydoc", - "fixed_decimal", - "icu_decimal_data", - "icu_locale_core", - "icu_locale_fallback", - "icu_plurals", - "icu_provider", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_decimal_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c887fc7d4c297cf3f9436864af9e3dba7f8be9415a6c2a7f392f3b1636298c" - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "serde", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locale_fallback" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "251af8e57c9400e3eb58242fe5b8b1152b2a64fdf4cf632f923c38ccee6f2fa9" -dependencies = [ - "icu_locale_core", - "icu_locale_fallback_data", - "icu_provider", - "potential_utf", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locale_fallback_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "decf2a22ec8fa68f1a0c1129a3f8583f8f8bc24e8b9ccbe98ead99f62a4dc3a8" - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_plurals" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e475e6766ef87b1d3c1f97be6363995b0bfaeddbc789671615df598a97ff0593" -dependencies = [ - "fixed_decimal", - "icu_locale_fallback", - "icu_plurals_data", - "icu_provider", - "zerovec", -] - -[[package]] -name = "icu_plurals_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1251aa39a95e1333888e499b1263e1c196447a28948acf5bdabd82c046f153bf" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -dependencies = [ - "displaydoc", - "icu_locale_core", - "serde", - "stable_deref_trait", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_segmenter" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d07aafccd67af15d02512a6adf5896fbc5ed00f2e99b471d2efa14016db3db" -dependencies = [ - "icu_collections", - "icu_locale_fallback", - "icu_provider", - "icu_segmenter_data", - "potential_utf", - "smallvec", - "utf8_iter", - "zerovec", -] - -[[package]] -name = "icu_segmenter_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae293c039020f9ec10710af98d29ce6aa2051486638b49c9a6409f3b4a9e98ad" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.25.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" -dependencies = [ - "bytemuck", - "byteorder-lite", - "moxcms", - "num-traits", - "png", - "zune-core", - "zune-jpeg", -] - -[[package]] -name = "image-webp" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" -dependencies = [ - "byteorder-lite", - "quick-error", -] - -[[package]] -name = "imagesize" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b27460c2c92b037f3f94c538ed9a3342f3fdf923606781629ccb35f82d042a" - -[[package]] -name = "imgref" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e44b0a4eaa4c82f441d50a963f2d5f05a787240aeee097597033e72accfd22f" - -[[package]] -name = "indexmap" -version = "2.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", -] - -[[package]] -name = "input" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9793345a65d71317763a33066b5d8351f8760dde8d4930fe9e39b5f14a7959d" -dependencies = [ - "bitflags 2.13.2", - "input-sys", - "libc", - "log", - "udev", -] - -[[package]] -name = "input-sys" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36eee07d8e02bd95bf52b2e642cf13d33701b94c6e4b04fbf1d1fb07e9cb19e7" - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jiff" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-core" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" -dependencies = [ - "defmt", - "log", -] - -[[package]] -name = "jiff-static" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys 0.4.1", - "log", - "simd_cesu8", - "thiserror 2.0.21", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" -dependencies = [ - "cfg-if", - "futures-util", - "wasm-bindgen", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.13.2", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "ksni" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "814b44c24cd2cb236c3b8a41c7f08237b452a8e76ecaa81f1cec40b5b678215b" -dependencies = [ - "async-executor", - "async-io", - "async-lock", - "futures-channel", - "futures-lite", - "futures-util", - "pastey", - "serde", - "task-local", - "zbus", -] - -[[package]] -name = "kurbo" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" -dependencies = [ - "arrayvec", - "euclid", - "polycool", - "smallvec", -] - -[[package]] -name = "lazy_static" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" - -[[package]] -name = "libc" -version = "0.2.190" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" - -[[package]] -name = "libloading" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" -dependencies = [ - "cfg-if", - "windows-link", -] - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "libredox" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" -dependencies = [ - "bitflags 2.13.2", - "libc", - "plain", - "redox_syscall 0.9.4", -] - -[[package]] -name = "libseat" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6656c69b6e0366ffb83faa232f3e4fda5fc91161722d140f2c95ce2e90b1ef31" -dependencies = [ - "errno", - "libseat-sys", - "log", -] - -[[package]] -name = "libseat-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a484fa48be5f62a8d3ffed767779d0ab808cfead91de98ef4362d469097dfb" -dependencies = [ - "pkg-config", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "linebender_resource_handle" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" - -[[package]] -name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[package]] -name = "litrs" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "lyon_algorithms" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009" -dependencies = [ - "lyon_path", - "num-traits", -] - -[[package]] -name = "lyon_extra" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7755f08423275157ad1680aaecc9ccb7e0cc633da3240fea2d1522935cc15c72" -dependencies = [ - "lyon_path", - "thiserror 2.0.21", -] - -[[package]] -name = "lyon_geom" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92" -dependencies = [ - "arrayvec", - "euclid", - "num-traits", -] - -[[package]] -name = "lyon_path" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e" -dependencies = [ - "lyon_geom", - "num-traits", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "memmap2" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "moxcms" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" -dependencies = [ - "num-traits", - "pxfm", -] - -[[package]] -name = "muda" -version = "0.19.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" -dependencies = [ - "crossbeam-channel", - "dpi", - "keyboard-types", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "once_cell", - "png", - "thiserror 2.0.21", - "windows-sys 0.61.2", -] - -[[package]] -name = "naga" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130" -dependencies = [ - "arrayvec", - "bit-set", - "bitflags 2.13.2", - "cfg-if", - "cfg_aliases", - "codespan-reporting", - "half", - "hashbrown 0.17.1", - "indexmap", - "libm", - "log", - "naga-types", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "spirv", - "thiserror 2.0.21", - "unicode-ident", -] - -[[package]] -name = "naga-types" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49" -dependencies = [ - "hashbrown 0.17.1", - "indexmap", - "rustc-hash 1.1.0", - "thiserror 2.0.21", -] - -[[package]] -name = "natord" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308d96db8debc727c3fd9744aac51751243420e46edf401010908da7f8d5e57c" - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.13.2", - "jni-sys 0.3.1", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys 0.3.1", -] - -[[package]] -name = "nix" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" -dependencies = [ - "bitflags 2.13.2", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "nom" -version = "8.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" -dependencies = [ - "memchr", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_enum" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "objc-sys" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" - -[[package]] -name = "objc2" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "libc", - "objc2 0.5.2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation 0.2.2", - "objc2-quartz-core 0.2.2", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.13.2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-data" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2 0.6.4", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.13.2", - "dispatch2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-contacts", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.13.2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "dispatch", - "libc", - "objc2 0.5.2", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.13.2", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.13.2", - "objc2 0.6.4", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-metal" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" -dependencies = [ - "bitflags 2.13.2", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-foundation 0.3.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", - "objc2-metal 0.2.2", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.13.2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation 0.2.2", - "objc2-link-presentation", - "objc2-quartz-core 0.2.2", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.13.2", - "block2 0.6.2", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2 0.5.1", - "objc2 0.5.2", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" -dependencies = [ - "bitflags 2.13.2", - "block2 0.5.1", - "objc2 0.5.2", - "objc2-core-location", - "objc2-foundation 0.2.2", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -dependencies = [ - "critical-section", - "portable-atomic", -] - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "orbclient" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" -dependencies = [ - "libc", - "libredox", -] - -[[package]] -name = "ordered-float" -version = "5.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c9e0d9b23589f26070720bac724174bfec1083e82f7854cdd0267518343c0" -dependencies = [ - "num-traits", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "os_pipe" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "owned_ttf_parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" -dependencies = [ - "ttf-parser", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - -[[package]] -name = "parlance" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6937eda350acc1a5d05872c3cbf99fe78619c269096e2be3d4a350058639d5" - -[[package]] -name = "parley" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22d2ff88bd3f7d68d1d9b09c7e6209f9a8e8c05088295140a2bcf2e9b17038c5" -dependencies = [ - "accesskit", - "fontique", - "harfrust", - "hashbrown 0.17.1", - "icu_normalizer", - "icu_properties", - "icu_segmenter", - "linebender_resource_handle", - "parlance", - "parley_data", - "skrifa", -] - -[[package]] -name = "parley_data" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1567535334d6ba2d3cde19221ba9a7bd0fabb3cbd99046ddfb10ae061cfcc889" -dependencies = [ - "icu_properties", -] - -[[package]] -name = "pastey" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "petgraph" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" -dependencies = [ - "fixedbitset", - "hashbrown 0.15.5", - "indexmap", -] - -[[package]] -name = "phf" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" -dependencies = [ - "phf_macros", - "phf_shared", - "serde", -] - -[[package]] -name = "phf_generator" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" -dependencies = [ - "fastrand", - "phf_shared", -] - -[[package]] -name = "phf_macros" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" -dependencies = [ - "phf_generator", - "phf_shared", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "phf_shared" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pico-args" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pin-utils" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bee6c73da26345c729282832b60b0363cf3dd9f4bfd81d8551b7a1c889a113" - -[[package]] -name = "pin-weak" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b330c9d1b92dfe68442ca20b009c717d5f0b1e3cf4965e62f704c3c6e95a1305" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "png" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" -dependencies = [ - "bitflags 2.13.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.8.9", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.5.3", - "pin-project-lite", - "rustix 1.1.5", - "windows-sys 0.61.2", -] - -[[package]] -name = "polycool" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" -dependencies = [ - "arrayvec", -] - -[[package]] -name = "portable-atomic" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" -dependencies = [ - "critical-section", -] - -[[package]] -name = "portable-atomic-util" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "serde_core", - "writeable", - "zerovec", -] - -[[package]] -name = "presser" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.119", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "profiling" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" - -[[package]] -name = "protocol" -version = "0.1.0" - -[[package]] -name = "pulldown-cmark" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f068eba8e7071c5f9511831b44f32c740d5adf574e990f946ddb53db2f314e" -dependencies = [ - "bitflags 2.13.2", - "memchr", - "unicase", -] - -[[package]] -name = "pxfm" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" - -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - -[[package]] -name = "quick-xml" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "range-alloc" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca45419789ae5a7899559e9512e58ca889e41f04f1f2445e9f4b290ceccd1d08" - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "raw-window-metal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" -dependencies = [ - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", -] - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "read-fonts" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" -dependencies = [ - "bytemuck", - "font-types", - "once_cell", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "redox_syscall" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "renderdoc-sys" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" - -[[package]] -name = "resvg" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e3803f97b999e80cbf7c6ecdd07a8102204d92e1633cf48783720c521196bd" -dependencies = [ - "bytemuck", - "gif", - "image-webp", - "log", - "pico-args", - "rgb", - "svgtypes", - "tiny-skia 0.12.0", - "usvg", - "zune-jpeg", -] - -[[package]] -name = "rgb" -version = "0.8.53" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "rowan" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b574c58582fa59fa43a2feb6608b8744184659f08a2e0117e4b8224d95ed61" -dependencies = [ - "countme", - "hashbrown 0.14.5", - "memoffset", - "rustc-hash 1.1.0", - "text-size", -] - -[[package]] -name = "roxmltree" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb" -dependencies = [ - "memchr", -] - -[[package]] -name = "rspolib" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fda9a7796aff63a7b1b39ccc93fffaaf65e20042984b4843041a49ca4677535" -dependencies = [ - "lazy_static", - "natord", - "snafu", - "unicode-linebreak", - "unicode-width", -] - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.13.2", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", -] - -[[package]] -name = "rustix" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" -dependencies = [ - "bitflags 2.13.2", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scoped-tls-hkt" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9603871ffe5df3ac39cb624790c296dbd47a400d202f56bf3e414045099524d" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sctk-adwaita" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" -dependencies = [ - "ab_glyph", - "log", - "memmap2", - "smithay-client-toolkit", - "tiny-skia 0.11.4", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "simplecss" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" -dependencies = [ - "log", -] - -[[package]] -name = "siphasher" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" - -[[package]] -name = "skia-bindings" -version = "0.153.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb325787c91b3f6c34a7c6f2b8a2297b20292d733ca53a08c233e5028aec49af" -dependencies = [ - "bindgen", - "cc", - "flate2", - "heck", - "pkg-config", - "regex", - "serde_json", - "tar", - "toml", -] - -[[package]] -name = "skia-safe" -version = "0.153.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61cb643dac43067f3eb76c866635a74377ac0abca0ff2590674e1e295514c3d" -dependencies = [ - "bitflags 2.13.2", - "skia-bindings", - "windows", -] - -[[package]] -name = "skrifa" -version = "0.44.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" -dependencies = [ - "bytemuck", - "read-fonts", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "slint" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e477d5ff6fec20909100ff8f7062432ebfbda7a62a0f37d0252aa126f38c83" -dependencies = [ - "const-field-offset", - "i-slint-backend-linuxkms", - "i-slint-backend-selector", - "i-slint-common", - "i-slint-core", - "i-slint-core-macros", - "i-slint-renderer-femtovg", - "i-slint-renderer-software", - "num-traits", - "once_cell", - "pin-weak", - "slint-macros", - "unicode-segmentation", - "vtable", -] - -[[package]] -name = "slint-build" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ea28d4e1ad47d829b91e227f17037fac07a8001668a93fee47ac04f88718ebc" -dependencies = [ - "derive_more", - "fontique", - "i-slint-compiler", - "spin_on", - "toml_edit", -] - -[[package]] -name = "slint-macros" -version = "1.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e74b13d20884dc480ce37d0fabbf02cb4bc0df2a595434feac96ca7c2a0f15" -dependencies = [ - "i-slint-compiler", - "proc-macro2", - "quote", - "spin_on", -] - -[[package]] -name = "slotmap" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e" - -[[package]] -name = "smithay-client-toolkit" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" -dependencies = [ - "bitflags 2.13.2", - "calloop 0.13.0", - "calloop-wayland-source", - "cursor-icon", - "libc", - "log", - "memmap2", - "rustix 0.38.44", - "thiserror 1.0.69", - "wayland-backend", - "wayland-client", - "wayland-csd-frame", - "wayland-cursor", - "wayland-protocols", - "wayland-protocols-wlr", - "wayland-scanner", - "xkeysym", -] - -[[package]] -name = "smol_str" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" -dependencies = [ - "serde", -] - -[[package]] -name = "smol_str" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" -dependencies = [ - "borsh", - "serde_core", -] - -[[package]] -name = "snafu" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e84b3f4eacbf3a1ce05eac6763b4d629d60cbc94d632e4092c54ade71f1e1a2" -dependencies = [ - "snafu-derive", -] - -[[package]] -name = "snafu-derive" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "as-raw-xcb-connection", - "bytemuck", - "fastrand", - "js-sys", - "memmap2", - "ndk", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-quartz-core 0.3.2", - "raw-window-handle", - "redox_syscall 0.5.18", - "rustix 1.1.5", - "tiny-xlib", - "tracing", - "wasm-bindgen", - "wayland-backend", - "wayland-client", - "wayland-sys", - "web-sys", - "windows-sys 0.61.2", - "x11rb", -] - -[[package]] -name = "spin_on" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076e103ed41b9864aa838287efe5f4e3a7a0362dd00671ae62a212e5e4612da2" -dependencies = [ - "pin-utils", -] - -[[package]] -name = "spirv" -version = "0.4.0+sdk-1.4.341.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" -dependencies = [ - "bitflags 2.13.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strict-num" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" -dependencies = [ - "float-cmp", -] - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "svgtypes" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d" -dependencies = [ - "kurbo", - "siphasher", -] - -[[package]] -name = "swash" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c2499c2d826531388872b2268718aed907a39bd785ab0dcfe57fab26283f92e" -dependencies = [ - "skrifa", - "yazi", - "zeno", -] - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "sys-locale" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" -dependencies = [ - "js-sys", - "libc", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "taffy" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aea22054047c16c3f34d3ac473a2170be1424b1115b2a3adcf28cfb067c88859" -dependencies = [ - "arrayvec", - "grid", - "serde", - "slotmap", -] - -[[package]] -name = "tar" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "task-local" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2972044a9e5e448a506a7ff6f0d03b566d8ef4cd6918a58fc59835a0f8666626" -dependencies = [ - "pin-project-lite", -] - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix 1.1.5", - "windows-sys 0.61.2", -] - -[[package]] -name = "text-size" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" -dependencies = [ - "thiserror-impl 2.0.21", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "tiny-skia" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83d13394d44dae3207b52a326c0c85a8bf87f1541f23b0d143811088497b09ab" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "tiny-skia-path 0.11.4", -] - -[[package]] -name = "tiny-skia" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea" -dependencies = [ - "arrayref", - "arrayvec", - "bytemuck", - "cfg-if", - "log", - "png", - "tiny-skia-path 0.12.0", -] - -[[package]] -name = "tiny-skia-path" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e7fc0c2e86a30b117d0462aa261b72b7a99b7ebd7deb3a14ceda95c5bdc93" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-skia-path" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9" -dependencies = [ - "arrayref", - "bytemuck", - "strict-num", -] - -[[package]] -name = "tiny-xlib" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90a0ca3ee6a69f2ad28fd11621a4c3f03b371f366be500b64df260c4ffbafb4" -dependencies = [ - "as-raw-xcb-connection", - "ctor", - "libloading", - "pkg-config", - "tracing", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "serde_core", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" - -[[package]] -name = "tokio" -version = "1.53.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" -dependencies = [ - "pin-project-lite", -] - -[[package]] -name = "toml" -version = "1.1.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.25.15+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tree_magic_mini" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6" -dependencies = [ - "memchr", - "nom 8.0.0", - "petgraph", -] - -[[package]] -name = "ttf-parser" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" - -[[package]] -name = "typed-index-collections" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898160f1dfd383b4e92e17f0512a7d62f3c51c44937b23b6ffc3a1614a8eaccd" -dependencies = [ - "bincode", - "serde", -] - -[[package]] -name = "udev" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" -dependencies = [ - "io-lifetimes", - "libc", - "libudev-sys", - "pkg-config", -] - -[[package]] -name = "uds_windows" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" -dependencies = [ - "memoffset", - "tempfile", - "windows-sys 0.61.2", -] - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - -[[package]] -name = "unicode-ident" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" - -[[package]] -name = "unicode-linebreak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" - -[[package]] -name = "unicode-script" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee" - -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-vo" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "unty" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "usvg" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977d0a4abdef933f424a99fe09f95576e089b90aebc6f016a3bc813762493e91" -dependencies = [ - "base64", - "data-url", - "flate2", - "fontdb", - "harfrust", - "imagesize", - "kurbo", - "log", - "pico-args", - "roxmltree", - "simplecss", - "siphasher", - "skrifa", - "strict-num", - "svgtypes", - "tiny-skia-path 0.12.0", - "unicode-bidi", - "unicode-script", - "unicode-vo", - "xmlwriter", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a" -dependencies = [ - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vtable" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "515a4b5b1399cc46fb0dc4081436cb40a3852a2d3e94fd1fa50bb68a334896eb" -dependencies = [ - "const-field-offset", - "portable-atomic", - "stable_deref_trait", - "vtable-macro", -] - -[[package]] -name = "vtable-macro" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8191d73ececf73fc94e65809bf0e667525a0bcff9aeeb8d042a4173a508332a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" -dependencies = [ - "js-sys", - "tokio", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 3.0.6", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.129" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wayland-backend" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" -dependencies = [ - "cc", - "downcast-rs", - "rustix 1.1.5", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" -dependencies = [ - "bitflags 2.13.2", - "rustix 1.1.5", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-csd-frame" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" -dependencies = [ - "bitflags 2.13.2", - "cursor-icon", - "wayland-backend", -] - -[[package]] -name = "wayland-cursor" -version = "0.31.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" -dependencies = [ - "rustix 1.1.5", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-protocols" -version = "0.32.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" -dependencies = [ - "bitflags 2.13.2", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-plasma" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" -dependencies = [ - "bitflags 2.13.2", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" -dependencies = [ - "bitflags 2.13.2", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" -dependencies = [ - "dlib", - "log", - "once_cell", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webbrowser" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c35be770821a214dbc362fc26908c853e776c0004294d0b10b8a6bad582f94" -dependencies = [ - "jni", - "log", - "ndk-context", - "objc2 0.6.4", - "objc2-app-kit 0.3.2", - "objc2-foundation 0.3.2", - "url", - "web-sys", -] - -[[package]] -name = "weezl" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" - -[[package]] -name = "wgpu" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e" -dependencies = [ - "arrayvec", - "bitflags 2.13.2", - "bytemuck", - "cfg-if", - "cfg_aliases", - "document-features", - "hashbrown 0.17.1", - "log", - "portable-atomic", - "profiling", - "raw-window-handle", - "smallvec", - "static_assertions", - "wgpu-core", - "wgpu-hal", - "wgpu-types", -] - -[[package]] -name = "wgpu-core" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14" -dependencies = [ - "arrayvec", - "bit-set", - "bit-vec", - "bitflags 2.13.2", - "bytemuck", - "cfg_aliases", - "document-features", - "hashbrown 0.17.1", - "indexmap", - "log", - "naga", - "naga-types", - "once_cell", - "parking_lot", - "portable-atomic", - "profiling", - "raw-window-handle", - "rustc-hash 1.1.0", - "smallvec", - "thiserror 2.0.21", - "wgpu-core-deps-apple", - "wgpu-core-deps-windows-linux-android", - "wgpu-hal", - "wgpu-naga-bridge", - "wgpu-types", -] - -[[package]] -name = "wgpu-core-deps-apple" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061f3d319a40d39d00b1ecc2c33b89fe21d4e6fe01859df3500a3a8ecccd6b68" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-core-deps-windows-linux-android" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f" -dependencies = [ - "wgpu-hal", -] - -[[package]] -name = "wgpu-hal" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58" -dependencies = [ - "android_system_properties", - "arrayvec", - "ash", - "bit-set", - "bitflags 2.13.2", - "block2 0.6.2", - "bytemuck", - "cfg-if", - "cfg_aliases", - "gpu-allocator", - "hashbrown 0.17.1", - "libc", - "libloading", - "log", - "naga", - "naga-types", - "objc2 0.6.4", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation 0.3.2", - "objc2-metal 0.3.2", - "objc2-quartz-core 0.3.2", - "once_cell", - "ordered-float", - "parking_lot", - "portable-atomic", - "portable-atomic-util", - "profiling", - "range-alloc", - "raw-window-handle", - "raw-window-metal", - "renderdoc-sys", - "smallvec", - "static_assertions", - "thiserror 2.0.21", - "wgpu-naga-bridge", - "wgpu-types", - "windows", - "windows-core", -] - -[[package]] -name = "wgpu-naga-bridge" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081" -dependencies = [ - "naga", - "wgpu-types", -] - -[[package]] -name = "wgpu-types" -version = "30.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286" -dependencies = [ - "bitflags 2.13.2", - "bytemuck", - "log", - "naga-types", - "raw-window-handle", - "static_assertions", -] - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winit" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" -dependencies = [ - "ahash", - "android-activity", - "atomic-waker", - "bitflags 2.13.2", - "block2 0.5.1", - "bytemuck", - "calloop 0.13.0", - "cfg_aliases", - "concurrent-queue", - "core-foundation", - "core-graphics", - "cursor-icon", - "dpi", - "js-sys", - "libc", - "memmap2", - "ndk", - "objc2 0.5.2", - "objc2-app-kit 0.2.2", - "objc2-foundation 0.2.2", - "objc2-ui-kit 0.2.2", - "orbclient", - "percent-encoding", - "pin-project", - "raw-window-handle", - "redox_syscall 0.4.1", - "rustix 0.38.44", - "sctk-adwaita", - "smithay-client-toolkit", - "smol_str 0.2.2", - "tracing", - "unicode-segmentation", - "wasm-bindgen", - "wasm-bindgen-futures", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-plasma", - "web-sys", - "web-time", - "windows-sys 0.52.0", - "x11-dl", - "x11rb", - "xkbcommon-dl", -] - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wl-clipboard-rs" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d7888ccd4896447b2d14d3a9350a85df2aeb6f181e2e7a31349d104ac46cac1" -dependencies = [ - "libc", - "log", - "os_pipe", - "rustix 1.1.5", - "thiserror 2.0.21", - "tree_magic_mini", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-wlr", -] - -[[package]] -name = "write-fonts" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae90d1d9f6d1d36ef116b0b1e651a6d7c12ba925439d4d06ff9adc94662a3d8" -dependencies = [ - "font-types", - "indexmap", - "kurbo", - "log", - "read-fonts", -] - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" -dependencies = [ - "as-raw-xcb-connection", - "gethostname", - "libc", - "libloading", - "once_cell", - "rustix 1.1.5", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" - -[[package]] -name = "xattr" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" -dependencies = [ - "libc", - "rustix 1.1.5", -] - -[[package]] -name = "xcursor" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" - -[[package]] -name = "xkbcommon" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" -dependencies = [ - "libc", - "memmap2", - "xkeysym", -] - -[[package]] -name = "xkbcommon-dl" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" -dependencies = [ - "bitflags 2.13.2", - "dlib", - "log", - "once_cell", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" - -[[package]] -name = "xmlwriter" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9" - -[[package]] -name = "yazi" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" - -[[package]] -name = "yeslogic-fontconfig-sys" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8b8abf912b9a29ff112e1671c97c33636903d13a69712037190e6805af4f76" -dependencies = [ - "dlib", - "once_cell", - "pkg-config", -] - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix 1.1.5", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus-lockstep" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" -dependencies = [ - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus-lockstep-macros" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "zbus-lockstep", - "zbus_xml", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 3.0.6", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" -dependencies = [ - "serde", - "winnow", - "zvariant", -] - -[[package]] -name = "zbus_xml" -version = "5.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" -dependencies = [ - "serde", - "winnow", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zcheapstr" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" -dependencies = [ - "serde", -] - -[[package]] -name = "zeno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" - -[[package]] -name = "zerocopy" -version = "0.8.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6df92bf3d9227be3d53173901ddbffac2babc27ae50f397776ffd6dc33f800cb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4f328cf2f05d084e496c3e9c3f33ed0a183656a16e1fcec4d464d8373aec82" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "serde", - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.6", -] - -[[package]] -name = "zlib-rs" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zune-core" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" - -[[package]] -name = "zune-jpeg" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" -dependencies = [ - "zune-core", -] - -[[package]] -name = "zvariant" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow", - "zcheapstr", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 3.0.6", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 3.0.6", - "winnow", -] diff --git a/client/Cargo.toml b/client/Cargo.toml deleted file mode 100644 index 7bd98cd..0000000 --- a/client/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "cam-client" -version = "0.1.0" -edition = "2021" - -[build-dependencies] -# Compiles ui/camera.slint into Rust before the crate itself builds. Kept separate -# from [dependencies] because it only runs on the build host, never on the target. -slint-build = "1" - -[dependencies] -# Shared wire format with the firmware. Kept as a path dependency rather than -# copied, so the two sides cannot drift apart silently. -protocol = { path = "../protocol" } - -# Slint for the UI. The default renderer on macOS is femtovg, which needs no -# extra system libraries; `backend-winit` provides the window integration. -slint = "1" - -# JPEG decoding for display. Slint's Image can take raw RGBA, so the JPEG has to -# be decoded somewhere - `image` does that, and nothing on this side re-encodes. -image = { version = "0.25", default-features = false, features = ["jpeg"] } - -anyhow = "1" -log = "0.4" -env_logger = "0.11" - -[profile.release] -opt-level = 2 diff --git a/client/README.md b/client/README.md deleted file mode 100644 index 595bbff..0000000 --- a/client/README.md +++ /dev/null @@ -1,365 +0,0 @@ -# client - -Slint 桌面客户端。**两种身份,界面对应两个不同的组件。** - -| 身份 | 画面 | 血量 | 需要绑定 | -|---|---|---|---| -| `--role referee`(默认) | 两块板左右并排 | 双方 | 否 | -| `--role team` | 一块画面 | 自己那一边 | **是,选定后不可更改** | - -```sh -cd client && cargo build --release -./target/aarch64-apple-darwin/release/cam-client -``` - -产物路径带三重名(`target/aarch64-apple-darwin/release/`)是因为下面那节解释了原因。 - -## 命令行 - -```text - --role 身份。默认 referee(无需配置即可看两路) - --team <0|1> 自己属于哪一边,仅 team 端用 - --camera-id <0xID> 绑定到某块板子(MAC 派生,重启不变) - --camera 向哪块板子发订阅(默认 255.255.255.255 广播) - --config <路径> 配置文件位置 - --list 列出发现的摄像头后退出 - -h, --help 显示这段帮助 -``` - -常用: - -```sh -cam-client --list # 先看看场上有几块板子 -cam-client --role team --team 1 # 队伍一(首次运行会问绑定) -cam-client --role team --team 1 --camera-id 0x3D4D8A9A # 跳过选择直接绑 -``` - -`--list` 会等 5 秒收广播周期再打印,所以它不是即时的。 - -## 配置文件 - -默认 `~/.config/cam-client/config.toml`,`--config` 可改。 - -```toml -role = "team" -team = 1 -camera_id = 0x3D4D8A9A -``` - -**没有配置文件也能跑**——裁判端就是无配置启动的,直接显示两路。队伍端缺配置时进入 -摄像头选择界面。 - -命令行覆盖配置文件,反过来不成立(只在内存里改)。这样现场可以把一台机器临时当另一 -台用,不动文件。 - -TOML 的序列化是手写的(`config.rs`),没有引 `toml` crate。配置只有四个标量字段, -引一个解析器不值得;而手写解析换来的是零依赖,以及 `save` 与 `parse` 严格对称这一点 -可以被一个测试覆盖。**忘记同步这两个方向的改动是这类代码最典型的 bug**,所以它们挨着 -放,并且有 `parses_a_team_config` 盯着。 - -## 绑定流程 - -队伍端首次运行: - -1. 接收线程开始收广播,板子每 2 秒发一次 Hello -2. 界面列出发现的所有板子(id + IP + 是否在线) -3. 点一个 → 写进配置文件 -4. 此后配置里已有 `camera_id`,界面**不再出现选择页** - -"选定不可更改"就落在这条路径上:没有 unbind 命令,`--camera-id` 是命令行覆盖而不是改 -文件。 - -改绑的办法只有两种:手改配置文件,或 `rm ~/.config/cam-client/config.toml`。 - -### `Box::leak` 不是疏忽 - -绑定回调拿到的 `CallbackHandle` **必须活到窗口关闭**。它是 `Copy`,但 `Drop` 会注销回 -调。 - -这里踩过一个坑:一开始用 `mem::forget`,编译器提示它对 `Copy` 类型无效——这个提示是 -对的,`mem::forget` 传的是值,句柄被复制进闭包捕获后原值立刻 drop,回调在第一次点击 -后就注销了,**第二次点击什么也不会发生**。`Box::leak` 让所有权真的离开作用域。 - -窗口里的 `Timer` 同理:被 drop 即停止,也泄漏掉。 - -## Slint 的三条硬约束 - -这三条都不是文档里写的,是撞出来的。 - -**`Weak` 和 `slint::Image` 都不是 `Send`。** - -`slint::Image` 尤其致命:JPEG 必须在别的线程解码(一张 VGA 的 JPEG 要几毫秒,放在收包 -线程里会挤进数据报之间,造成链路上本不存在的丢帧),但解码出来的 RGBA 无法跨线程交给 -UI。 - -解法是分两段:worker 线程只处理纯数据(`Vec` + 尺寸),`slint::Image` 的构造留在 -事件循环里。`main.rs` 的 `decoded` 槽位就是这个用途,`take_decoded()` 是那段跨界。 - -**`ModelRc` 没有 `From>`。** - -只有 `From>`、`From<&[T]>`、`From<[T; N]>`。所以要写: - -```rust -std::rc::Rc::new(slint::VecModel::from(v)).into() -``` - -而不是 `v.into()`。这个编译错误会在每个第一次用它的人身上重现一次。 - -**`.slint` 的语法限制。** - -- `Rectangle` 这类基础元素**不能**挂 `callback`,要用 `TouchArea` -- `in` 属性不能从内部赋值 -- 没有 `RowLayout`,用 `HorizontalLayout` -- 窗口不能同时给 `width`/`height` 和 `min-*` - -## 线程 - -三个,加上 Slint 自己的: - -| 线程 | 做什么 | 为什么单独 | -|---|---|---| -| `udp-reader` | 收包、解析、重组 | 不能让解码挤进数据报之间 | -| `jpeg-decode` | JPEG → RGBA | 几毫秒的活 | -| 事件循环 | 取 RGBA、造 `Image`、刷新界面 | `Image` 只能在拥有它的线程构造 | -| `slint::Timer` 回调(33ms) | 推进上面那条流水线 | 显示不需要 22fps 重绘 | - -33ms 的轮询间隔不是每帧一次:更快只是让事件循环和接收路径抢 CPU。 - -### 槽位一旦占定就不换人 - -裁判端有两个槽位,但**不要按 id 顺序重新对号入座**。 - -原来那版是: - -```rust -let ids: Vec = cameras.iter().map(|c| c.id).take(2).collect(); -let wanted = ids.get(i).copied(); -``` - -看起来稳定,其实不是:低 id 的板子一离线,`ids` 就缩短成 `[B]`,B 落到**左**槽 —— -画面左右互换。现场看非常像"两路串了",而实际上只是排序。 - -现在的规则:还**在线**的槽位保持不动;空槽或已掉线的槽位才从还没被占用的板子里按 id -顺序取第一个。 - -```rust -let still_here = view.sender_id.is_some_and(|id| cameras.iter().any(|c| c.id == id)); -if still_here { continue; } -``` - -换人时优先挑一个和当前不同的板子,否则会出现"板子短暂掉线又回来,槽位原地打转", -看起来像卡住。 - -`view.sender_id` 变了才重建 `View`(清零帧率窗口),否则每 33ms 重置一次统计,帧率 -永远算不出来。 - -## 帧率:按计数除以墙钟 - -```rust -self.fps = (stats.frames_shown - self.window_frames) as f32 / window.as_secs_f32(); -``` - -不是对帧间隔取平均。 - -一次 UI 轮询(33ms)之间可能完成多帧,平均间隔量的是**显示**的刷新快慢而不是**相机** -的出帧快慢——那会把上限 22fps 的传感器报成 36fps。**一个笃定的错误数字比没有数字更糟**, -现场你会以为还有性能余量。 - -## 血量:接口就位,数据源没有 - -```rust -let armor: Arc = Arc::new(NoArmorYet); -``` - -`ArmorSource` 是真的 trait,`protocol::ArmorHealth` 和 `ARMOR_PORT` 都已就位。装甲板接上 -后只需换掉这一行,`render_health()` 会自动走 `Some(hps)` 分支,界面和其余代码都不用改。 - -现在没有数据源,所以**显示占位符而不是 0**。0 看起来像真值——现场看到两个 0 会以为 -对方已经没血了,然后开始找裁判理论。 - -占位分支依然给出 4 个血格(值全 0),因为界面用 `available` 决定颜色:空数组会让整个 -占位框消失,看起来像布局出错,而不是像"还没有数据"。 - -## 防作弊:release 队伍端只接收绑定的那一块 - -对抗赛里两队都用这套系统看自己小车的图传,所以要防止对手开第二个窗口连到另一队 -的板子。 - -**关键是这件事必须做在接收端,而不是界面里。** - -一开始我按"release 下不显示 id 和 IP"理解,那样等于没做: - -- 广播的 Hello 里就有 MAC 派生的 `sender_id` 和 IP。对手 `tcpdump` 一下全都知道, - 界面藏不藏毫无关系。 -- 数据本来已经收下并解码了。`.slint` 只控制显示,隐藏一行文字不阻止任何事。 - -所以是 `Receiver::restrict_to`:队伍端 release 且已绑定时,只重组、只解码、只统计 -绑定的那一块。对手的板子即使把流推到了本机端口(IP 已泄露,`DEFAULT_PORT` 是公开 -常量),数据也不进入任何管线。 - -```rust -let restrict = if config.role == Role::Team && !TEAM_DIAGNOSTICS { - config.camera_id.filter(|_| args.camera_id.is_none()) -} else { - None -}; -``` - -四种情况不限制,各有理由: - -| 情况 | 为什么不限制 | -|---|---| -| 裁判端 | 中立第三方,本来就要同时看两块板子 | -| debug 构建 | 开发时需要看到全场 | -| 队伍端未绑定 | 选择界面需要列出候选,否则无从选起 | -| `--camera-id` 覆盖了配置 | 显式指定了要看哪块,是有意的 | - -### 遥测按编译模式门控 - -```rust -const TEAM_DIAGNOSTICS: bool = cfg!(debug_assertions); -``` - -fps / 丢帧率 / 数据报数 / 帧间隔这些在队伍端 release 下**不产出**(空字符串,界面 -也不显示),stdout 的诊断日志同样门控。 - -由编译模式决定而不是命令行参数:命令行开关意味着二进制里带着那套东西,忘开一次 -就泄露了。 - -裁判端不受影响——双方遥测对判罚有用。 - -### `--list` 在 release 队伍端直接拒绝 - -它会打印场上所有板子的 id 和 IP,正是要防的那件事。与其列出来再叮嘱"别看",不如 -不给: - -``` -Error: "--list 需要 debug 构建:它会列出场上所有板子的 id 和 IP。" -``` - -### 选择界面保留 - -现场换机器时还要用它重新绑定。数据侧的闸门已经足够,界面列出候选不构成额外泄露 -(对手需要的是实时画面,不是 id 字符串)。 - -### 这挡不住什么 - -诚实地说:**IP 仍然通过 Hello 广播泄露。** 对手知道你的 IP 之后,可以自己发一个 -`Subscribe` 过来——固件会向任何订阅者推流,那道闸门在他的客户端上,不在你的。 - -真要挡住需要固件侧做配对(首次绑定发一个 token,固件只接受带 token 的订阅), -代价是固件重启就失配。这次没做,因为需求定的是"避免一个队伍通过开其他窗口连接到 -其他队伍的图传",而这需要对手主动在自己机器上装这套客户端——纯 UI 或客户端侧的 -措施挡不住它。**这一条如果要真正封住,得回固件加 token。** - -## 缩放与自适应布局 - -### 滚轮缩放 + 拖动平移 - -Slint 的 `Image` 没有缩放因子属性,所以实现是:把 `Image` 的 `width`/`height` 按 -`zoom` 放大后放进 `clip: true` 的容器,靠 `offset-x`/`offset-y` 平移。 - -- 滚轮改 `zoom`(每次 ×1.25,上限 8 倍) -- 拖动改 `offset`(只在 `zoom > 1` 时生效——此时才有可移动的空间) -- 双击复位 -- 缩回 1.0 时自动归位 - -最后一条容易漏:缩回 1.0 后若保留偏移,下一次放大会从偏移位置开始,看起来像 -"跳了一下"。 - -`image-fit` 必须跟着 `zoom` 切: - -``` -image-fit: root.zoom <= 1.0 ? contain : fill; -``` - -`contain` 在 `zoom = 1` 时保证完整可见且不变形,`fill` 才让画面跟着放大。**只用 -`contain` 的话放大不会有任何视觉变化**——这个 bug 很容易写出来,因为界面上确实 -变大了,只是画面没跟上。 - -裁判端两路的缩放**互相独立**:滚轮只作用于鼠标所在的那一路。 - -### 为什么两格必须等宽 - -裁判端曾经放大时左边占 55%、右边 45%;空的那一侧更极端时会被压成一条缝。 - -原因不是缺 `stretch`。`HorizontalLayout` 先按内容的**最大**需求分配,剩下的才按 -stretch 分。左格写"已连接,等待画面…"(8 字),右格"未收到画面"(5 字),左格就 -多要十几个像素。而没有画面的一格最小宽度接近 0,可以被压没。 - -三处一起改: - -1. **占位文字改成绝对定位**(给出 `x`/`y`),不参与父级尺寸计算。文字长短本不该 - 决定画面大小。 -2. **两格统一 `preferred-width` + `horizontal-stretch: 1`**,需求变成相同,于是精确 - 平分。`min-width: 320px` 兜底,保证任何一侧至少有一个能看画的宽度。 -3. **缩放指示与复位按钮也绝对定位**。否则它们一出现就改变这一格的尺寸需求,把不 - 等宽重新引进来——而且是在用户放大之后才出现,最难排查。 - -`preferred-width` 只接受长度或字面量 `100%`,不接受 `50%`。 - -### 启动即最大化 - -```rust -ui.window().set_maximized(true); -ui.show()?; -``` - -**必须在 `show()` 之前**——之后调用会被后端当成"用户点了最大化按钮",而不是初始 -状态。 - -取 `set_maximized` 而不是 `set_fullscreen`:全屏会盖掉菜单栏和 Dock,切出去要按快 -捷键。裁判终端偶尔要切到别的窗口核对数据。 - -## 内存:解码只留最新一张 - -解码线程在写入前先 `retain` 掉同一路的旧图(`main.rs`)。 - -一张 VGA 的 RGBA 是 1.2 MB。两路各留两张就是 5 MB,而延迟换清晰度在这里没有意义—— -观战的人要的是最新的画面,不是最完整的那张已经过时的。 - -## 为什么客户端收全部板子的视频 - -队伍端也订阅了所有板子,然后在**界面层**过滤到自己那一个。 - -反过来的做法(板子按订阅者的 role 决定推给谁)会引出一个不对称:板子得维护"谁是队伍 -一谁是队伍二"的认知,但那块知识本来就在客户端。裁判端同时要两路的事实说明这个 -认知不能放在板子上。 - -代价是队伍端浪费一份几乎相同的图像数据——实测 264 kB/s,占链路 6–9%,可以接受。 - -## 换机器要改 `client/.cargo/config.toml` - -```toml -[build] -target = "aarch64-apple-darwin" -``` - -**这行必须改**,它是本文件唯一的存在理由。 - -client/ 嵌在固件的目录树里,cargo 会向上找到 `../.cargo/config.toml`,那里面 -`target = "xtensa-esp32s3-espidf"` 加一个 ldproxy linker,只有在 ESP-IDF 交叉编译里才成立。 -没有这个覆盖,桌面客户端会被交叉编译到 Xtensa,然后在依赖深处失败——`libc` 找不到 -`MAP_FAILED` 之类的东西,因为它正在为一个没有 `mmap` 的嵌入式目标编译。 - -父配置的其它设置对非 ESP 目标都不适用,所以只需覆盖 target 一项。 - -## 线程安全清单 - -- 接收线程 → 其它:`Arc` / `Arc`,内部 `Mutex` -- 解码线程 → 事件循环:`Arc>>` -- 事件循环 → 界面:直接调用(同一个线程) -- 绝不跨线程:`Weak`、`slint::Image`、`ModelRc` - -## 依赖 - -| crate | 为什么 | -|---|---| -| `slint` / `slint-build` | 界面。macOS 默认渲染器是 femtovg,不需要额外系统库 | -| `image` | 只为 `jpeg` feature,解码用 | -| `protocol`(path) | 共享线路格式。用 path 而不是复制,两边不可能悄悄分叉 | - -`image` 关掉了 default features 只开 jpeg:这个客户端不读 PNG/TIFF/webp 以外的任何 -东西,也不写任何文件。 - -这一侧**没有任何东西重新编码**。板子出的就是传感器里的 JPEG,客户端只解码。 diff --git a/client/build.rs b/client/build.rs deleted file mode 100644 index 872acc1..0000000 --- a/client/build.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Compiles the Slint markup and writes the generated Rust. -//! -//! Without this, `slint::include_modules!` has nothing to expand to: it reads the -//! path to the generated file from an environment variable that only this build -//! script sets. - -fn main() { - let config = slint_build::CompilerConfiguration::new().with_style("fluent".to_owned()); - slint_build::compile_with_config("ui/camera.slint", config).expect("compiling ui/camera.slint"); -} diff --git a/client/src/config.rs b/client/src/config.rs deleted file mode 100644 index 2b2d010..0000000 --- a/client/src/config.rs +++ /dev/null @@ -1,310 +0,0 @@ -//! 绑定配置:这个客户端是什么身份,以及看哪块板子。 -//! -//! 队伍的"选定后不可更改"落在 [`Config::camera_id`] 上:首次运行时从发现列表里选, -//! 结果写进配置文件,之后直接读,不再询问。 -//! -//! 用 TOML 而不是 JSON,因为这个文件是**给人看的**——现场排查时要能一眼看懂改了什么。 -//! 而它只有十来个字段,不值得为它引入整个序列化框架。 - -use std::path::{Path, PathBuf}; - -use anyhow::{Context, Result}; - -/// 客户端身份。 -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Role { - /// 队伍终端:一块画面 + 自己那一边的血量。 - Team, - /// 裁判终端:两块画面 + 双方血量。 - Referee, -} - -impl Role { - pub fn as_str(self) -> &'static str { - match self { - Role::Team => "team", - Role::Referee => "referee", - } - } - - pub fn parse(s: &str) -> Option { - match s { - "team" => Some(Role::Team), - "referee" => Some(Role::Referee), - _ => None, - } - } - - pub fn to_protocol(self) -> protocol::SubscribeRole { - match self { - Role::Team => protocol::SubscribeRole::Team, - Role::Referee => protocol::SubscribeRole::Referee, - } - } - -} - -/// 磁盘上的配置。 -#[derive(Debug, Clone)] -pub struct Config { - /// 客户端身份。 - pub role: Role, - /// 队伍端:自己属于哪一边,0 = 队伍一,1 = 队伍二。 - /// - /// 决定血量栏显示哪一边。裁判端两个都要,所以忽略这个值。 - pub team: u8, - /// 绑定的板子 id(MAC 派生,重启不变)。 - /// - /// `None` 表示还没选过,首次运行会在界面上询问。 - pub camera_id: Option, - /// 板子的 IP。DHCP 可能变,所以这只是发现阶段的一个提示。 - pub camera_addr: Option, -} - -impl Default for Config { - /// 默认当裁判机:它不需要任何配置就能用,是现场最省事的起点。 - fn default() -> Self { - Self { - role: Role::Referee, - team: 0, - camera_id: None, - camera_addr: None, - } - } -} - -impl Config { - /// 从磁盘读取。 - /// - /// 文件不存在时返回默认配置,这是正常路径——首次运行本来就没有配置。 - pub fn load(path: &Path) -> Result { - if !path.exists() { - log::info!("no config at {}; starting with defaults", path.display()); - return Ok(Self::default()); - } - - let text = std::fs::read_to_string(path) - .with_context(|| format!("reading {}", path.display()))?; - - Self::parse(&text).with_context(|| format!("parsing {}", path.display())) - } - - /// 写入磁盘。 - /// - /// 手写序列化而不是引入 toml crate:这个文件只有十来个字段,且格式必须与 - /// [`Config::parse`] 严格对称——两份代码比一个依赖更容易在 review 时看出不匹配。 - pub fn save(&self, path: &Path) -> Result<()> { - if let Some(dir) = path.parent() { - std::fs::create_dir_all(dir) - .with_context(|| format!("creating {}", dir.display()))?; - } - - let mut out = String::new(); - out.push_str("# cam-client 配置。\n"); - out.push_str("#\n"); - out.push_str("# role : team | referee\n"); - out.push_str("# team = 一路画面 + 自己那一边的血量,需要绑定一块板子\n"); - out.push_str("# referee = 两路画面 + 双方血量,不需要绑定\n"); - out.push_str("# team : 0 = 队伍一, 1 = 队伍二(仅 team 端用)\n"); - out.push_str("#\n"); - out.push_str("# 绑定之后不要再改 camera_id:队伍端按它选画面,改了就换了视角。\n"); - out.push_str("# camera_id 由板子的 MAC 派生,重启后不变,所以这个绑定是稳的。\n\n"); - out.push_str(&format!("role = \"{}\"\n", self.role.as_str())); - out.push_str(&format!("team = {}\n", self.team)); - - if let Some(id) = self.camera_id { - out.push_str(&format!("camera_id = \"0x{id:08X}\"\n")); - } - if let Some(addr) = self.camera_addr { - out.push_str(&format!("camera_addr = \"{addr}\"\n")); - } - - std::fs::write(path, out).with_context(|| format!("writing {}", path.display()))?; - - log::info!("saved config to {}", path.display()); - Ok(()) - } - - /// 解析 TOML 的一个子集。 - /// - /// 只支持 `key = value`、字符串、`整数`、十六进制字符串和注释——恰好是这个文件 - /// 需要的全部,遇到不认识的形式会明确报错而不是静默忽略,因为静默忽略会让一个 - /// 打错的配置看起来像是生效了。 - fn parse(text: &str) -> Result { - let mut config = Config::default(); - let mut role_seen = false; - - for (lineno, raw) in text.lines().enumerate() { - let line = strip_comment(raw).trim(); - if line.is_empty() { - continue; - } - - let Some((key, value)) = line.split_once('=') else { - anyhow::bail!("line {}: expected `key = value`, got {line:?}", lineno + 1); - }; - - let key = key.trim(); - let value = value.trim().trim_matches('"'); - - match key { - "role" => { - config.role = Role::parse(value).ok_or_else(|| { - anyhow::anyhow!( - "line {}: role must be `team` or `referee`, got {value:?}", - lineno + 1 - ) - })?; - role_seen = true; - } - "team" => { - config.team = value - .parse::() - .with_context(|| format!("line {}: team must be 0 or 1", lineno + 1))?; - } - "camera_id" => { - config.camera_id = Some(parse_id(value).with_context(|| { - format!("line {}: camera_id must look like 0x1A2B3C4D", lineno + 1) - })?); - } - "camera_addr" => { - config.camera_addr = Some(value.parse().with_context(|| { - format!("line {}: camera_addr must be an IPv4 address", lineno + 1) - })?); - } - _ => { - anyhow::bail!( - "line {}: unknown key {key:?}; refusing to guess", - lineno + 1 - ); - } - } - } - - if !role_seen { - // 缺 role 时按裁判端处理:它是唯一无需配置就能工作的模式。 - config.role = Role::Referee; - } - - Ok(config) - } -} - -/// 去掉 `#` 之后的内容。 -fn strip_comment(line: &str) -> &str { - match line.find('#') { - Some(at) => &line[..at], - None => line, - } -} - -/// 解析 `0x1A2B3C4D` 或十进制形式的板子 id。 -fn parse_id(text: &str) -> Result { - let text = text.trim(); - - if let Some(hex) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) { - u32::from_str_radix(hex, 16).with_context(|| format!("{text:?} is not hex")) - } else { - text.parse::() - .with_context(|| format!("{text:?} is not a number")) - } -} - -/// 默认配置文件位置。 -/// -/// 优先 `$XDG_CONFIG_HOME` 或 `~/.config`,与 Linux 上其它工具一致;macOS 上用同一个 -/// 路径而不是 `~/Library/Application Support`,是为了让两台机器(裁判机和队伍机可能 -/// 是不同系统)的配置结构一致。 -pub fn default_config_path() -> PathBuf { - let base = std::env::var_os("XDG_CONFIG_HOME") - .map(PathBuf::from) - .filter(|p| !p.as_os_str().is_empty()) - .unwrap_or_else(|| { - let home = std::env::var_os("HOME").map(PathBuf::from).unwrap_or_else(|| { - PathBuf::from(".") - }); - home.join(".config") - }); - - base.join("cam-client").join("config.toml") -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn parses_a_team_config() { - let config = Config::parse( - r#" - # 队伍一的机器 - role = "team" - team = 1 - camera_id = "0x3D4D8A9A" - "#, - ) - .expect("parses"); - - assert_eq!(config.role, Role::Team); - assert_eq!(config.team, 1); - assert_eq!(config.camera_id, Some(0x3D4D_8A9A)); - assert!(config.is_bound()); - } - - #[test] - fn referee_needs_no_binding() { - let config = Config::parse("role = \"referee\"").expect("parses"); - - assert_eq!(config.role, Role::Referee); - assert!(config.is_bound(), "the referee watches everything"); - } - - #[test] - fn a_team_without_a_camera_is_unbound() { - let config = Config::parse("role = \"team\"\nteam = 0").expect("parses"); - - assert_eq!(config.role, Role::Team); - assert!(!config.is_bound(), "must ask which camera to watch"); - } - - #[test] - fn roundtrips_through_save_and_load() { - let mut dir = std::env::temp_dir(); - dir.push(format!("cam-client-test-{}", std::process::id())); - let path = dir.join("nested").join("config.toml"); - - let config = Config { - role: Role::Team, - team: 1, - camera_id: Some(0x1234_5678), - camera_addr: Some("192.168.1.50".parse().unwrap()), - }; - - config.save(&path).expect("saves"); - let loaded = Config::load(&path).expect("loads"); - - assert_eq!(loaded.role, config.role); - assert_eq!(loaded.team, config.team); - assert_eq!(loaded.camera_id, config.camera_id); - assert_eq!(loaded.camera_addr, config.camera_addr); - - let _ = std::fs::remove_dir_all(&dir); - } - - #[test] - fn a_missing_file_is_the_default_not_an_error() { - let config = Config::load(Path::new("/nonexistent/cam-client/config.toml")) - .expect("a missing config is a normal first run"); - - assert_eq!(config.role, Role::Referee); - } - - #[test] - fn refuses_garbage_instead_of_ignoring_it() { - // 静默忽略一个打错的 key,会让配置看起来生效了。 - assert!(Config::parse("rol = \"team\"").is_err(), "unknown key"); - assert!(Config::parse("role = \"camera\"").is_err(), "bad role value"); - assert!(Config::parse("role = \"team\"\nteam = nine").is_err(), "bad team"); - assert!(Config::parse("just some text").is_err(), "not key = value"); - } -} \ No newline at end of file diff --git a/client/src/main.rs b/client/src/main.rs deleted file mode 100644 index 8272c6d..0000000 --- a/client/src/main.rs +++ /dev/null @@ -1,757 +0,0 @@ -//! ESP32 图传查看器。 -//! -//! 两种身份,由 [`Role`] 决定: -//! -//! - **队伍端** —— 一块画面 + 自己那一边的血量。需要绑定一块板子(选定后不可更改)。 -//! - **裁判端** —— 两块画面左右并排 + 双方血量。不需要绑定。 -//! -//! 用法: -//! -//! ```text -//! cam-client 默认裁判端 -//! cam-client --role team --team 1 队伍一 -//! cam-client --camera-id 0x3D4D8A9A 绑定到某块板子 -//! ``` -//! -//! 绑定与身份存在配置文件里(默认 `~/.config/cam-client/config.toml`)。首次运行 -//! 队伍端会列出发现的摄像头,选定后写入,之后不再询问。 -//! -//! # 为什么血量是占位符 -//! -//! 装甲板接口已经预留([`receiver::ArmorSource`] 与 `protocol::ArmorHealth`),但 -//! 现在没有实现也没有数据源。界面显示占位符而不是 0——0 看起来像真值,现场看到会 -//! 以为对方已经没血了。 - -mod config; -mod receiver; - -use std::error::Error; -use std::net::Ipv4Addr; -use std::path::PathBuf; -use std::sync::{Arc, Mutex}; -use std::time::{Duration, Instant}; - -use config::{Config, Role}; -use receiver::{ - decode_frame, ArmorSource, CameraInfo, DecodedFrame, NoArmorYet, Receiver, - SourceStats, ARMOR_BOARDS, -}; - -slint::include_modules!(); - -const USAGE: &str = "\ -用法: cam-client [选项] - - --role 身份。默认 referee(无需配置即可看两路) - --team <0|1> 自己属于哪一边,仅 team 端用 - --camera-id <0xID> 绑定到某块板子(MAC 派生,重启不变) - --camera 向哪块板子发订阅(默认 255.255.255.255 广播) - --config <路径> 配置文件位置 - --list 列出发现的摄像头后退出 - -h, --help 显示这段帮助 -"; - -/// 事件循环轮询新数据的间隔。 -/// -/// 不是每帧一次:显示不需要每秒重绘 22 次,更快只是让事件循环和接收路径抢 CPU。 -const UI_POLL: Duration = Duration::from_millis(33); - -/// 队伍端在 release 下是否显示技术诊断信息。 -/// -/// 对抗赛里两队都用这套系统看自己小车的图传,所以 release 给现场用、debug 给开发 -/// 用。这个开关由**编译模式**决定而不是命令行参数——命令行开关意味着二进制里带着 -/// 那套东西,忘开一次就泄露了。 -/// -/// 裁判端不受它影响:裁判是中立方,双方遥测对判罚有用。 -const TEAM_DIAGNOSTICS: bool = cfg!(debug_assertions); - -/// 帧率统计窗口。 -/// -/// 按计数除以墙钟时间,而不是对帧间隔取平均。一次轮询之间可能完成多帧,所以平均间隔 -/// 量的是**显示**的刷新快慢而不是**相机**的出帧快慢——那会把上限 22fps 的传感器 -/// 报成 36fps。一个笃定的错误数字比没有数字更糟。 -const FPS_WINDOW: Duration = Duration::from_secs(2); - -/// 某一路的显示状态。 -struct View { - /// 显示哪块板子(`None` = 还没有画面)。 - sender_id: Option, - fps: f32, - window_started: Instant, - window_frames: u64, - reported: bool, -} - -impl View { - fn new() -> Self { - Self { - sender_id: None, - fps: 0.0, - window_started: Instant::now(), - window_frames: 0, - reported: false, - } - } - - /// 累计窗口内的帧数,算出帧率。 - fn update_fps(&mut self, stats: &SourceStats) { - let window = self.window_started.elapsed(); - if window < FPS_WINDOW { - return; - } - - self.fps = (stats.frames_shown - self.window_frames) as f32 / window.as_secs_f32(); - self.window_frames = stats.frames_shown; - self.window_started = Instant::now(); - } -} - -/// 解码后的画面缓存,供事件循环取用。 -/// -/// 解码线程只处理纯数据(`Vec` + 尺寸),`slint::Image` 的构造留在事件循环, -/// 因为 `slint::Image` 不是 `Send`。 -type DecodedSlots = Arc>>; - -fn main() -> Result<(), Box> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - - let args = Args::parse(std::env::args().skip(1))?; - - if args.help { - print!("{USAGE}"); - return Ok(()); - } - - let config_path = args.config.clone().unwrap_or_else(config::default_config_path); - - // 命令行覆盖配置文件,这样现场可以临时当另一台机器用而不用改文件。 - let mut config = Config::load(&config_path)?; - - if let Some(role) = args.role { - config.role = role; - } - if let Some(team) = args.team { - config.team = team; - } - if let Some(id) = args.camera_id { - config.camera_id = Some(id); - } - - log::info!( - "role={} team={} camera_id={:?} config={}", - config.role.as_str(), - config.team, - config.camera_id, - config_path.display() - ); - - // 队伍端在 release 下已经绑定时,把接收端限制到那一块。 - // - // 这是本次改动里唯一有实际防护的一处:`.slint` 里不显示 id 只是藏起文字,而 - // 广播的 Hello 早就把 MAC 派生的 id 和 IP 送出去了(对手 tcpdump 一下就有)。 - // 在**接收端**拒绝才是真的:不重组、不解码、不进 discovery。 - // - // 四种情况不限制: - // - 裁判端(中立第三方,本来就要两块板子) - // - debug 构建(开发时要能看到全场) - // - 队伍端还没绑定(选择界面需要列出候选) - // - 命令行临时用别的板子跑(--camera-id 覆盖配置) - let restrict = if config.role == Role::Team && !TEAM_DIAGNOSTICS { - config.camera_id.filter(|_| args.camera_id.is_none()) - } else { - None - }; - - let rx = Arc::new(Receiver::start( - args.camera, - config.role.to_protocol(), - restrict, - )?); - let discovery = rx.discovery(); - let frames = rx.frames(); - - if args.list { - // 队伍端 release 下 `--list` 会列出场上所有板子(包括对手的 id 和 IP), - // 正是要防的那件事。所以这里直接拒绝,而不是列出来再让人别看。 - if config.role == Role::Team && !TEAM_DIAGNOSTICS { - return Err( - "--list 需要 debug 构建:它会列出场上所有板子的 id 和 IP。\ - release 的队伍端请直接用已绑定的配置运行。" - .into(), - ); - } - // 等几个广播周期,让列表有内容。 - let socket = bind_socket()?; - let for_list = Arc::clone(&rx); - let handle = std::thread::spawn(move || for_list.run(socket)); - - std::thread::sleep(Duration::from_secs(5)); - - let cameras = discovery.online_cameras(); - if cameras.is_empty() { - println!("没有发现摄像头。板子每 2 秒广播一次,再等等或检查网络。"); - } else { - println!("发现 {} 块板子:", cameras.len()); - for c in cameras { - println!(" 0x{:08X} {} (id 与 MAC 派生,重启不变)", c.id, c.addr); - } - } - - rx.shutdown(); - let _ = handle.join(); - return Ok(()); - } - - // 血量目前没有数据源。接口已经就位,装甲板接上后换掉这一行即可。 - let armor: Arc = Arc::new(NoArmorYet); - - let decoded: DecodedSlots = Arc::new(Mutex::new(Vec::new())); - - // 接收线程。 - { - let rx = Arc::clone(&rx); - std::thread::Builder::new() - .name("udp-reader".into()) - .spawn(move || { - let Ok(socket) = bind_socket() else { - log::error!("could not bind the socket for the receive loop"); - return; - }; - rx.run(socket); - })?; - } - - // 解码线程:一张 VGA 的 JPEG 要几毫秒,放在收包线程里会挤进数据报之间,造成 - // 链路上本不存在的丢帧。 - { - let frames = Arc::clone(&frames); - let decoded = Arc::clone(&decoded); - - std::thread::Builder::new() - .name("jpeg-decode".into()) - .spawn(move || { - loop { - if frames.pending_senders().is_empty() { - std::thread::sleep(Duration::from_millis(5)); - continue; - } - - for sender_id in frames.pending_senders() { - let Some(frame) = frames.take(sender_id) else { - continue; - }; - - match decode_frame(&frame.data) { - Some(pixels) => { - let mut slot = decoded.lock().expect("decoded mutex poisoned"); - - // 同一路只保留最新一张,避免解码已经过时的画面。 - slot.retain(|(id, _)| *id != sender_id); - slot.push((sender_id, pixels)); - } - None => log::warn!( - "frame {} from 0x{:08X} is not a decodable JPEG ({} bytes)", - frame.frame_id, - frame.sender_id, - frame.data.len() - ), - } - } - } - })?; - } - - let timer = slint::Timer::default(); - - // 每个身份一个闭包,而不是一个闭包里再 match 身份。 - // - // 两种身份是两个不同的组件类型(TeamApp / RefereeApp),没有共同的超类可以 - // 在一个闭包里统一调用 set_frame/set_frame_a。写成两个分支后每边都拿到具体 - // 类型,属性赋值直接调用,不再需要 match 身份——那种 match 会让编译器在每个 - // 赋值点都检查一遍身份,而它本来就是编译期就定死的。 - match config.role { - Role::Team => { - let ui = TeamApp::new()?; - - // 启动即最大化。必须在 show() 之前:之后调用会被后端当成"用户点了最大化 - // 按钮"。取 set_maximized 而不是 set_fullscreen,因为全屏会盖掉菜单栏和 - // Dock,裁判终端要切去别的窗口核对数据。 - ui.window().set_maximized(true); - ui.show()?; - - // 接收端的限制在构造 Receiver 时已经按配置设好了(见上面 `restrict`), - // 这里不重复设:只有选择界面上新选定的那一次需要改它。 - let rx_for_pick = Arc::clone(&rx); - - let weak = ui.as_weak(); - let discovery = Arc::clone(&discovery); - let decoded = Arc::clone(&decoded); - let armor = Arc::clone(&armor); - let config_path = config_path.clone(); - let team = config.team; - let bound = config.camera_id; - - let mut view = View::new(); - - // 绑定回调与刷新定时器都要读这份列表,所以用锁共享,而不是让其中一个 - // 把 `choices` 移走。 - let choices: Arc>> = Arc::new(Mutex::new(Vec::new())); - - // 绑定完成时写配置。"选定不可更改"就落在这里:此后配置里已有 - // camera_id,界面不再出现选择页。 - let choices_for_pick = Arc::clone(&choices); - - let pick_handle = ui.on_pick(move |index| { - let Ok(index) = usize::try_from(index) else { - return; - }; - - let picked = choices_for_pick - .lock() - .expect("choices mutex poisoned") - .get(index) - .map(|c| c.id_text.to_string()); - - let Some(id_text) = picked else { - return; - }; - let Some(id) = parse_id(&id_text).ok() else { - log::error!("could not read back the selected id {id_text:?}"); - return; - }; - - let saved = Config { - role: Role::Team, - team, - camera_id: Some(id), - camera_addr: None, - }; - - if let Err(err) = saved.save(&config_path) { - log::error!("could not save the binding: {err}"); - log::error!("画面仍会显示,但下次启动要重新选择"); - } else { - log::info!("bound to camera 0x{id:08X}; saved in the config"); - } - - // 选定之后把接收端也限制到这一块。此后场上别人的板子即使把流推到 - // 本机端口,也不会被重组或解码。 - // - // 解除限制(传 None)要等到重启:配置里有了 camera_id 就会走绑定 - // 分支,限制随之恢复,两者不会不一致。 - rx_for_pick.restrict_to(Some(id)); - }); - - let choices_for_poll = Arc::clone(&choices); - - timer.start(slint::TimerMode::Repeated, UI_POLL, move || { - let Some(ui) = weak.upgrade() else { - return; - }; - - let cameras = discovery.online_cameras(); - - // 未绑定:只显示选择界面。 - let Some(bound) = bound else { - let updated: Vec = cameras - .iter() - .map(|c| CameraChoice { - id_text: format!("0x{:08X}", c.id).into(), - addr_text: format!("{} ({:?})", c.addr, c.silence()).into(), - online: c.is_online(), - }) - .collect(); - - *choices_for_poll.lock().expect("choices mutex poisoned") = updated.clone(); - - ui.set_choices(std::rc::Rc::new(slint::VecModel::from(updated)).into()); - ui.set_choosing(true); - return; - }; - - ui.set_choosing(false); - - if view.sender_id != Some(bound) { - view = View::new(); - view.sender_id = Some(bound); - } - - if let Some(image) = take_decoded(&decoded, bound) { - ui.set_frame(image); - } - - let stats = discovery.stats(bound); - view.update_fps(&stats); - - // 遥测日志同样按编译模式门控:release 的队伍端不往 stdout 打帧率/丢帧这些 - // 能被对手读到的数字。 - if TEAM_DIAGNOSTICS && !view.reported && stats.frames_shown > 0 { - log_diagnostics(&stats, bound); - view.reported = true; - } - - ui.set_stats(render_camera_stats( - &cameras, - bound, - &stats, - view.fps, - TEAM_DIAGNOSTICS, - )); - ui.set_health(render_health(team, armor.as_ref())); - }); - - // 绑定回调必须活到窗口关闭。CallbackHandle 是 Copy,但 Drop 会注销 - // 回调,所以必须真的把它泄漏掉——之前用 `mem::forget` 编译器提示它对 - // Copy 类型无效,那确实无效:第一次点击后回调就被注销了,第二次点击 - // 什么也不会发生。 - std::boxed::Box::leak(Box::new(pick_handle)); - } - - Role::Referee => { - let ui = RefereeApp::new()?; - ui.window().set_maximized(true); - ui.show()?; - - let weak = ui.as_weak(); - let discovery = Arc::clone(&discovery); - let decoded = Arc::clone(&decoded); - let armor = Arc::clone(&armor); - - let mut views: Vec = (0..2).map(|_| View::new()).collect(); - - timer.start(slint::TimerMode::Repeated, UI_POLL, move || { - let Some(ui) = weak.upgrade() else { - return; - }; - - let cameras = discovery.online_cameras(); - - // 槽位对应**队伍**,不是"第 N 块发现的板子"。 - // - // 两条规则: - // - // 1. 一次只填一个空槽,按 id 顺序。之前两块都空时每轮都从 - // `online_cameras()` 取前两个,于是只有一块板在场时两块槽位显示 - // 同一块画面——右半边不再是"队伍二",而是队伍一的复制。裁判看到 - // 两边一模一样,会以为两队的图传串了。 - // - // 2. 已填的槽位不换人。原先是 `ids.take(2)` 后按下标对号入座,低 id - // 的板子一离线,后面那块就跳到前面,画面左右互换,看起来同样 - // 像串线。掉线的槽位保留它的 id 并显示"离线",等它回来。 - // - // 所以每轮最多消费一个候选 id。 - let mut next_candidate = cameras.iter().map(|c| c.id); - - for view in views.iter_mut() { - let still_here = view - .sender_id - .is_some_and(|id| cameras.iter().any(|c| c.id == id)); - - if still_here { - continue; - } - - // 槽位已占且板子还在场上 → 保留(上面已 continue)。到这里说明 - // 槽位是空的,或板子掉线了。 - let candidate = next_candidate - .next() - // 别把刚掉线的这块又立刻选回同一个槽位:它短暂离线又回来时, - // 画面会原地卡一下才恢复。 - .filter(|id| Some(*id) != view.sender_id); - - match candidate { - Some(id) => { - *view = View::new(); - view.sender_id = Some(id); - } - // 没有可分配的了。掉线的槽位**保留 id**(显示"离线"而不是 - // 变回空槽),这样它回来时还在原位。 - None => {} - } - } - - for (i, view) in views.iter_mut().enumerate() { - let Some(sender_id) = view.sender_id else { - continue; - }; - - if let Some(image) = take_decoded(&decoded, sender_id) { - if i == 0 { - ui.set_frame_a(image); - } else { - ui.set_frame_b(image); - } - } - - let stats = discovery.stats(sender_id); - view.update_fps(&stats); - - if !view.reported && stats.frames_shown > 0 { - log_diagnostics(&stats, sender_id); - view.reported = true; - } - - // 裁判端始终显示诊断信息:它是中立方,双方遥测对判罚有用。 - let view_stats = - render_camera_stats(&cameras, sender_id, &stats, view.fps, true); - - if i == 0 { - ui.set_stats_a(view_stats); - } else { - ui.set_stats_b(view_stats); - } - } - - ui.set_health_a(render_health(0, armor.as_ref())); - ui.set_health_b(render_health(1, armor.as_ref())); - }); - } - } - - // Timer 一旦被 drop 就停止,所以要活到进程结束。 - let _timer = Box::leak(Box::new(timer)); - - slint::run_event_loop()?; - - rx.shutdown(); - Ok(()) -} - -/// 从解码缓存里取出某一路的最新画面。 -/// -/// 解码线程只处理纯数据,`slint::Image` 的构造留在这里——`slint::Image` 不是 -/// `Send`,跨不了线程。 -fn take_decoded( - decoded: &DecodedSlots, - sender_id: u32, -) -> Option { - let mut slot = decoded.lock().expect("decoded mutex poisoned"); - - let pos = slot.iter().position(|(id, _)| *id == sender_id)?; - let (_, pixels) = slot.remove(pos); - - let buffer = slint::SharedPixelBuffer::::clone_from_slice( - &pixels.rgba, - pixels.width, - pixels.height, - ); - - Some(slint::Image::from_rgba8(buffer)) -} - -/// 命令行参数。 -struct Args { - role: Option, - team: Option, - camera_id: Option, - camera: Ipv4Addr, - config: Option, - list: bool, - help: bool, -} - -impl Args { - fn parse(argv: impl Iterator) -> Result> { - let mut args = Args { - role: None, - team: None, - camera_id: None, - camera: protocol::BROADCAST_ADDR, - config: None, - list: false, - help: false, - }; - - let mut it = argv.peekable(); - - while let Some(arg) = it.next() { - match arg.as_str() { - "-h" | "--help" => args.help = true, - "--list" => args.list = true, - "--role" => { - let v = it.next().ok_or("--role needs a value")?; - args.role = - Some(Role::parse(&v).ok_or("role must be `team` or `referee`")?); - } - "--team" => { - let v = it.next().ok_or("--team needs a value")?; - args.team = Some(v.parse().map_err(|_| "--team must be 0 or 1")?); - } - "--camera-id" => { - let v = it.next().ok_or("--camera-id needs a value")?; - args.camera_id = Some(parse_id(&v)?); - } - "--camera" => { - let v = it.next().ok_or("--camera needs a value")?; - args.camera = v - .parse() - .map_err(|_| "--camera must be an IPv4 address")?; - } - "--config" => { - args.config = Some(PathBuf::from(it.next().ok_or("--config needs a path")?)); - } - other => { - return Err(format!("unknown argument {other:?}\n\n{USAGE}").into()); - } - } - } - - Ok(args) - } -} - -/// 绑定 UDP 接收 socket。 -/// -/// 固定端口(而非临时端口)是必须的:板子从订阅的**源端口**学到推流目标,而这个 -/// socket 同时承担收包。 -fn bind_socket() -> std::io::Result { - let socket = std::net::UdpSocket::bind(std::net::SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, - protocol::DEFAULT_PORT, - ))?; - socket.set_read_timeout(Some(Duration::from_millis(100)))?; - - // 广播是发现的前提:板子靠它宣告存在,客户端靠它发起订阅。 - let _ = socket.set_broadcast(true); - Ok(socket) -} - -/// 解析 `0x1A2B3C4D` 或十进制形式的板子 id。 -fn parse_id(text: &str) -> Result> { - let text = text.trim(); - - if let Some(hex) = text.strip_prefix("0x").or_else(|| text.strip_prefix("0X")) { - Ok(u32::from_str_radix(hex, 16)?) - } else { - Ok(text.parse()?) - } -} - -/// 一次性诊断,在某一路第一次出画面时记录。 -/// -/// 不再单列"无法解码"计数:那是链路正常但内容坏了的情形,会在解码线程里逐帧告警, -/// 汇总在这里反而会把它和普通的丢帧混在一起。 -fn log_diagnostics(stats: &SourceStats, sender_id: u32) { - log::info!( - "camera 0x{sender_id:08X}: {} frames shown | {} dropped ({:.1}%) | \ - {} datagrams | {}x{} {} bytes", - stats.frames_shown, - stats.frames_dropped, - stats.loss_percent(), - stats.datagrams, - stats.width, - stats.height, - stats.last_frame_len, - ); -} - -/// 把某一路的统计渲染成界面结构。 -/// -/// `diagnostics` 为 false 时不产出技术字段——它们留空字符串,界面也不显示。留空而 -/// 不是填 0 或假值:0 会被读成"丢帧 0"这种看起来像真值的东西。 -fn render_camera_stats( - cameras: &[CameraInfo], - sender_id: u32, - stats: &SourceStats, - fps: f32, - diagnostics: bool, -) -> CameraStats { - let online = cameras - .iter() - .find(|c| c.id == sender_id) - .map(CameraInfo::is_online) - .unwrap_or(false); - - // 从没出过画面时区分"在线但还没收到"和"离线":前者只是刚连上,显示"等待画面" - // 比让人以为板子挂了诚实。 - // - // 有画面时显示 id —— 这对调试有用。裁判端和队伍端 debug 都显示;队伍端 release - // 下这一路只有自己绑定的板子,显示它不构成泄露(界面上根本没有别人的板子可选, - // 见 Receiver::restrict_to)。 - let status = if stats.frames_shown == 0 { - if online { - "已连接,等待画面…".to_string() - } else { - "离线".to_string() - } - } else { - format!("0x{:08X}", sender_id) - }; - - CameraStats { - online, - status_text: status.into(), - // 同理:没出过帧时 fps 是 0.0,读起来像"板子卡死了",而真相是"还没收到"。 - // 状态行已经写了"已连接,等待画面…",这里不该再补一个矛盾的数字。 - fps: (diagnostics && stats.frames_shown > 0) - .then(|| format!("{fps:.1}")) - .unwrap_or_default() - .into(), - // 没出过一个字节的源,`stats.width`/`height` 还是默认值 0,于是显示成 - // "0x0"——一个看起来像分辨率的假分辨率。用 frames_shown 把它挡掉。 - resolution: (diagnostics && stats.frames_shown > 0) - .then(|| format!("{}x{}", stats.width, stats.height)) - .unwrap_or_default() - .into(), - frame_bytes: (diagnostics && stats.frames_shown > 0) - .then(|| format!("{:.1} kB", stats.last_frame_len as f32 / 1024.0)) - .unwrap_or_default() - .into(), - frames_shown: diagnostics - .then(|| stats.frames_shown.to_string()) - .unwrap_or_default() - .into(), - frames_dropped: diagnostics - .then(|| stats.frames_dropped.to_string()) - .unwrap_or_default() - .into(), - loss_percent: diagnostics - .then(|| format!("{:.1}%", stats.loss_percent())) - .unwrap_or_default() - .into(), - datagrams: diagnostics - .then(|| stats.datagrams.to_string()) - .unwrap_or_default() - .into(), - gap_ms: stats - .last_interval - .filter(|_| diagnostics) - .map(|d| format!("{:.0} ms", d.as_secs_f32() * 1000.0)) - .unwrap_or_default() - .into(), - } -} - -/// 渲染一边的血量。 -/// -/// 现在永远走 `available == false` 的分支,因为还没有数据源。装甲板接上后 -/// [`ArmorSource::health`] 返回 `Some`,界面自动显示真实数值,这里不用改。 -/// -/// 没有数据时用占位符而不是 0:0 看起来像真值,现场看到会以为对方已经没血了。 -fn render_health(team: u8, armor: &dyn ArmorSource) -> TeamHealth { - let name = match team { - 0 => "队伍一", - _ => "队伍二", - }; - - match armor.health(team) { - Some(hps) => TeamHealth { - team_name: format!("{name} · 装甲板").into(), - available: true, - placeholder: String::new().into(), - hps: std::rc::Rc::new(slint::VecModel::from( - hps.iter().map(|hp| *hp as i32).collect::>(), - )) - .into(), - }, - None => TeamHealth { - team_name: name.into(), - available: false, - placeholder: - "装甲板未接入:血量接口已预留(protocol::ArmorHealth),当前无数据源".into(), - // 依然给出 4 个格子:界面用 `available` 决定颜色,所以空数组会让占位 - // 框整个消失,看起来像布局出错,而不是像"还没有数据"。 - hps: std::rc::Rc::new(slint::VecModel::from(vec![0i32; ARMOR_BOARDS])).into(), - }, - } -} diff --git a/client/src/receiver.rs b/client/src/receiver.rs deleted file mode 100644 index 9d4cd6d..0000000 --- a/client/src/receiver.rs +++ /dev/null @@ -1,530 +0,0 @@ -//! UDP 接收、发现、多路帧重组。 -//! -//! # 多路 -//! -//! 裁判终端同时收两块图传板,队伍终端也会收到两块(只是界面上只显示一块)。 -//! 所以重组状态**必须按发送方分开**——把两块板的帧塞进同一个重组器,会让它们 -//! 的 `frame_id` 互相顶掉,两路都出不来。 -//! -//! # 发现与订阅 -//! -//! 两个方向各司其职: -//! -//! - 板子每 2 秒广播一次 [`protocol::MsgType::Hello`],客户端被动收到就知道有谁在线。 -//! - 客户端广播 [`protocol::MsgType::Subscribe`],板子收到后开始定向推流。 -//! -//! 板子靠**源地址**记住客户端,所以客户端只要周期性续订就能维持推流,不存在 -//! 任何超时淘汰的竞态。 -//! -//! # 丢帧策略 -//! -//! 缺了分片的帧整个丢弃、下一帧顶上,不重传也不尝试补洞。对实时画面这是对的: -//! 等重传补齐时,那一帧早就不新鲜了。这个取舍的代价是丢帧率必须摆在界面上,否则 -//! "画面卡"没法区分是链路问题还是传感器问题。 - -use std::collections::HashMap; -use std::io::ErrorKind; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4, UdpSocket}; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::{Arc, Mutex}; -use std::thread; -use std::time::{Duration, Instant}; - -use protocol::{Fragment, FrameAssembler, Subscribe, SubscribeRole, DEFAULT_PORT, HEADER_LEN}; - -/// 接受的最大数据报。高于协议的理论上限(1427),留出余量,这样一个被截断的读只可能 -/// 来自网络,而不是来自过小的缓冲。 -const RECV_BUFFER: usize = 2048; - -/// 重新广播订阅的间隔。 -/// -/// 比板子判定"客户端掉线"的超时(6 秒)短得多,所以偶发丢一两个包不会中断推流。 -const SUBSCRIBE_INTERVAL: Duration = Duration::from_millis(800); - -/// 一张帧没收齐就放弃的时限。 -/// -/// 一帧在 22fps 下约 45ms 到,所以几百毫秒还没齐的永远不会齐了。 -const FRAME_TIMEOUT: Duration = Duration::from_millis(500); - -/// 一块已知的图传板。 -#[derive(Debug, Clone)] -pub struct CameraInfo { - pub id: u32, - pub addr: SocketAddrV4, - pub firmware: u16, - /// 最近一次收到它广播的时刻。 - pub last_seen: Instant, -} - -impl CameraInfo { - /// 多久没有广播了。 - pub fn silence(&self) -> Duration { - self.last_seen.elapsed() - } - - /// 板子在线吗? - /// - /// 阈值是广播间隔的数倍,所以偶发丢包不会让界面上的在线状态闪��。 - pub fn is_online(&self) -> bool { - self.silence() < Duration::from_secs(7) - } -} - -/// 一块板的运行时统计。 -#[derive(Debug, Clone, Copy, Default)] -pub struct SourceStats { - pub frames_shown: u64, - /// 因分片缺失被放弃的帧。 - pub frames_dropped: u64, - pub datagrams: u64, - pub last_frame_len: usize, - pub width: u16, - pub height: u16, - /// 上一帧到这一帧之间的间隔。 - pub last_interval: Option, - last_shown_at: Option, -} - -impl SourceStats { - /// 丢弃的帧占已收到帧的比例。 - pub fn loss_percent(&self) -> f32 { - let total = self.frames_shown + self.frames_dropped; - if total == 0 { - 0.0 - } else { - self.frames_dropped as f32 * 100.0 / total as f32 - } - } - - fn on_frame(&mut self, width: u16, height: u16, len: usize) { - self.last_interval = self.last_shown_at.map(|t| t.elapsed()); - self.last_shown_at = Some(Instant::now()); - self.frames_shown += 1; - self.width = width; - self.height = height; - self.last_frame_len = len; - } -} - -/// 每一路的最新帧,等着被解码。 -/// -/// 每路只保留最新的一张:在 22fps 下把每一帧都排队意味着内存里压着几十张 JPEG, -/// 然后解码那些几毫秒后就被下一帧顶掉的画面——而这正是这条链路要消除的延迟。 -#[derive(Default)] -pub struct LatestFrames { - inner: Mutex>, -} - -impl LatestFrames { - pub fn put(&self, frame: protocol::Frame) { - self.inner - .lock() - .expect("latest-frames mutex poisoned") - .insert(frame.sender_id, frame); - } - - /// 取走某一路的待处理帧。 - pub fn take(&self, sender_id: u32) -> Option { - self.inner - .lock() - .expect("latest-frames mutex poisoned") - .remove(&sender_id) - } - - /// 列出所有有待处理帧的板子 id。 - pub fn pending_senders(&self) -> Vec { - self.inner - .lock() - .expect("latest-frames mutex poisoned") - .keys() - .copied() - .collect() - } -} - -/// 所有板子的共享状态,跨线程只读。 -pub struct Discovery { - cameras: Mutex>, - stats: Mutex>, -} - -impl Discovery { - fn new() -> Self { - Self { - cameras: Mutex::new(HashMap::new()), - stats: Mutex::new(HashMap::new()), - } - } - - /// 当前已知的板子,按 id 排序,让界面上���列表顺序稳定。 - pub fn cameras(&self) -> Vec { - let mut list: Vec = self - .cameras - .lock() - .expect("cameras mutex poisoned") - .values() - .cloned() - .collect(); - - list.sort_by_key(|c| c.id); - list - } - - /// 在线且刚刚广播过的板子。 - pub fn online_cameras(&self) -> Vec { - self.cameras().into_iter().filter(CameraInfo::is_online).collect() - } - - /// 某一路的统计。 - pub fn stats(&self, sender_id: u32) -> SourceStats { - self.stats - .lock() - .expect("stats mutex poisoned") - .get(&sender_id) - .copied() - .unwrap_or_default() - } - - /// 只刷新存活时间戳,不改地址或固件版本。 - /// - /// 收到视频帧时调用:正在推流本身就是最可靠的存活证据。 - fn touch(&self, id: u32) { - let mut cameras = self.cameras.lock().expect("cameras mutex poisoned"); - - if let Some(info) = cameras.get_mut(&id) { - info.last_seen = Instant::now(); - } - } - - fn note_hello(&self, id: u32, addr: SocketAddrV4, firmware: u16) { - let mut cameras = self.cameras.lock().expect("cameras mutex poisoned"); - - cameras - .entry(id) - .and_modify(|c| { - c.addr = addr; - c.last_seen = Instant::now(); - c.firmware = firmware; - }) - .or_insert(CameraInfo { - id, - addr, - firmware, - last_seen: Instant::now(), - }); - } - - fn note_frame(&self, id: u32, width: u16, height: u16, len: usize) { - let mut stats = self.stats.lock().expect("stats mutex poisoned"); - stats.entry(id).or_default().on_frame(width, height, len); - } - - fn count(&self, id: u32, f: impl FnOnce(&mut SourceStats)) { - let mut stats = self.stats.lock().expect("stats mutex poisoned"); - f(stats.entry(id).or_default()); - } - - /// 超过 `max_idle` 没有广播的板子从列表里移除。 - /// - /// 界面上不会留下一堆永远离线的板子。 - pub fn forget_silent_cameras(&self, max_idle: Duration) { - self.cameras - .lock() - .expect("cameras mutex poisoned") - .retain(|_, c| c.silence() < max_idle); - } -} - -/// 客户端身份。 -/// -/// 常量而非 MAC 派生:队伍客户端和裁判终端各自唯一,进程内也不需要区分自己, -/// 而一个确定的常量让日志更好读。 -pub const CLIENT_ID: u32 = 0x0C11_0001; - -/// 接收器。 -/// -/// socket 在这里创建而不是在接收线程里,这样"端口被占用"会立刻报错,而不是被线程 -/// 吞掉、表现为一个永远不出画面的空窗口。 -pub struct Receiver { - discovery: Arc, - frames: Arc, - role: SubscribeRole, - camera: Ipv4Addr, - /// 只处理这一块板子的数据,`None` 表示全都要(裁判端)。 - /// - /// 队伍端在 release 下设成 `Some(bound)`,这是一道**接收端**的闸门而不只是界面 - /// 的:对手的板子即使把流推到了本机端口,也不会被重组、不会进解码队列、不会 - /// 出现在任何统计里。 - /// - /// 为什么必须在这一层而不是靠界面隐藏:`.slint` 只控制显示,而数据已经收下并 - /// 解码了。隐藏一行文字不阻止任何事。 - /// - /// 用 `Mutex` 而不是 `Cell`/`AtomicU32`:接收线程要读它,而绑定回调在主线程上 - /// 写它。订阅循环本身已经是串行的,所以锁的持有时间极短。 - only_sender: Mutex>, - stop: Arc, -} - -impl Receiver { - /// 绑定视频端口并准备好发现。 - /// - /// 绑定固定端口(而非临时端口)是必须的:板子是从订阅的**源端口**学到推流目标的, - /// 临时端口也能工作,但固定端口让三台客户端行为一致,也方便抓包排查。 - /// - /// `only_sender` 为 `Some` 时只接受那一块板子的数据,见 [`Receiver`] 的字段说明。 - pub fn start( - camera: Ipv4Addr, - role: SubscribeRole, - only_sender: Option, - ) -> std::io::Result { - let socket = - UdpSocket::bind(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_PORT))?; - socket.set_read_timeout(Some(Duration::from_millis(100)))?; - - let broadcast = socket.set_broadcast(true).is_ok(); - if !broadcast { - log::warn!("could not enable broadcast on the socket"); - } - - Ok(Self { - discovery: Arc::new(Discovery::new()), - frames: Arc::new(LatestFrames::default()), - role, - camera, - only_sender: Mutex::new(only_sender), - stop: Arc::new(AtomicBool::new(false)), - }) - } - - /// 绑定之后切换限制目标,或解除限制。 - /// - /// 队伍端在选择界面里点定一块板子时调用:此前要能看到所有候选(否则无从选 - /// 起),此后只看这一块。 - pub fn restrict_to(&self, only_sender: Option) { - *self.only_sender.lock().expect("only_sender mutex poisoned") = only_sender; - } - - /// 当前限制到哪一块板子。 - fn sender_limit(&self) -> Option { - *self.only_sender.lock().expect("only_sender mutex poisoned") - } - - pub fn discovery(&self) -> Arc { - Arc::clone(&self.discovery) - } - - pub fn frames(&self) -> Arc { - Arc::clone(&self.frames) - } - - /// 运行接收循环,直到 [`Receiver::shutdown`]。 - /// - /// 阻塞,所以放在单独的线程上。 - pub fn run(&self, socket: UdpSocket) { - let target = SocketAddrV4::new(self.camera, DEFAULT_PORT); - let subscribe = Subscribe { - role: self.role, - client_id: CLIENT_ID, - }; - let subscribe_bytes = subscribe.encode(); - - // 每个发送方一个重组器:两块板的 frame_id 各自独立,混在一起会互相顶掉。 - let mut assemblers: HashMap = HashMap::new(); - let mut buf = [0u8; RECV_BUFFER]; - let mut last_subscribe = Instant::now() - SUBSCRIBE_INTERVAL; - let mut last_sweep = Instant::now(); - - log::info!( - "listening on UDP {DEFAULT_PORT}, announcing {:?} to {target}", - self.role - ); - - while !self.stop.load(Ordering::Relaxed) { - // 周期性续订就是保活:板子在 6 秒收不到就认为我们走了。 - if last_subscribe.elapsed() >= SUBSCRIBE_INTERVAL { - if let Err(err) = socket.send_to(&subscribe_bytes, target) { - log::warn!("could not subscribe at {target}: {err}"); - } - last_subscribe = Instant::now(); - } - - match socket.recv_from(&mut buf) { - Ok((n, from)) => { - self.handle_datagram(&buf[..n], from, &mut assemblers); - } - Err(err) - if err.kind() == ErrorKind::WouldBlock - || err.kind() == ErrorKind::TimedOut => - { - // 帧之间的常态。读超时正是让退出能及时响应的原因。 - } - Err(err) => { - log::warn!("recv failed: {err}"); - thread::sleep(Duration::from_millis(100)); - } - } - - // 放弃等太久的半截帧。否则板子在帧中途死掉时,重组器会一直留着它。 - for (id, asm) in assemblers.iter_mut() { - if asm.expire().is_some() { - self.discovery.count(*id, |s| s.frames_dropped += 1); - } - } - - if last_sweep.elapsed() >= Duration::from_secs(5) { - self.discovery.forget_silent_cameras(Duration::from_secs(60)); - last_sweep = Instant::now(); - } - } - - log::info!("receive loop stopped"); - } - - /// 处理一个数据报。 - fn handle_datagram( - &self, - bytes: &[u8], - from: std::net::SocketAddr, - assemblers: &mut HashMap, - ) { - if bytes.len() < HEADER_LEN { - return; - } - - let limit = self.sender_limit(); - - // 板子的在线通告。注意这里不看来源端口:广播来的源端口没有意义,身份由 - // 头里的 sender_id 决定。 - if let Some(hello) = protocol::Hello::decode(bytes) { - // 限制生效时不记录其他板子。理由不只是"不显示":如果把它们记进 - // discovery,`--list` 式的输出、槽位排序、界面上任何一处遍历 cameras() - // 的地方都会间接泄露场上还有谁。 - if let (Some(only), SocketAddr::V4(v4)) = (limit, from) { - if hello.sender_id != only { - return; - } - self.discovery.note_hello(hello.sender_id, v4, hello.firmware); - return; - } - - if let SocketAddr::V4(v4) = from { - let is_new = !self.discovery.cameras().iter().any(|c| c.id == hello.sender_id); - self.discovery.note_hello(hello.sender_id, v4, hello.firmware); - - if is_new { - log::info!( - "found camera 0x{:08X} at {v4}", - hello.sender_id - ); - } - } - return; - } - - let Some(frag) = Fragment::decode(bytes) else { - // 既不是 Hello 也不是合法分片:将来的其它消息,或一次损坏。 - return; - }; - - let id = frag.header.sender_id; - - // 视频同样要过闸门。对手的板子一旦把流推到这个端口(IP 已被广播泄露, - // 端口是公开常量),它的数据不会进任何重组器。 - if let Some(only) = limit { - if id != only { - return; - } - } - - // 视频帧也算存活证据。 - // - // `is_online` 看的是 `last_seen`,而它此前只由 Hello 更新。于是出现一个 - // 荒唐的状态:画面在每秒刷 22 帧,状态栏却显示"离线"(橙色)。真板子每 2 - // 秒广播一次所以不易撞上,但它本身就是设计错误——正在推流的板子比广播更 - // 可靠地证明自己活着。 - self.discovery.touch(id); - - match assemblers - .entry(id) - .or_insert_with(|| FrameAssembler::new(FRAME_TIMEOUT)) - .push(frag) - { - Ok(frame) => { - self.discovery.count(id, |s| s.datagrams += 1); - self.discovery - .note_frame(id, frame.width, frame.height, frame.data.len()); - self.frames.put(frame); - } - Err(protocol::DropReason::Superseded) => { - // 被更新的帧追上了。这不是丢帧:半截的那张只是被超越,在健康链路上 - // 这是帧被放弃的**正常**方式。 - self.discovery.count(id, |s| s.datagrams += 1); - } - Err(protocol::DropReason::TimedOut) => { - self.discovery.count(id, |s| { - s.datagrams += 1; - s.frames_dropped += 1; - }); - } - } - } - - pub fn shutdown(&self) { - self.stop.store(true, Ordering::Relaxed); - } - -} - -/// 解码后的画面。 -pub struct DecodedFrame { - pub rgba: Vec, - pub width: u32, - pub height: u32, -} - -/// 解码一张 JPEG。 -/// -/// 返回 `None` 表示字节不是可解码的图像——协议层挡不住这种:它只检查分片结构, -/// 不检查 JPEG 本身是否有效。 -pub fn decode_frame(jpeg: &[u8]) -> Option { - let img = image::load_from_memory_with_format(jpeg, image::ImageFormat::Jpeg).ok()?; - let rgba = img.to_rgba8(); - - Some(DecodedFrame { - width: rgba.width(), - height: rgba.height(), - rgba: rgba.into_raw(), - }) -} - -/// 装甲板血量的来源。 -/// -/// **预留,当前没有实现也没有数据源。** 血量显示为占位符。 -/// -/// 接口和端口都已就位([`protocol::ARMOR_PORT`]、[`protocol::ArmorHealth`]):装甲板 -/// 接上以后往那个端口发消息即可,裁判界面与其它代码都不用改。 -pub trait ArmorSource: Send + Sync { - /// 某一边的血量。 - /// - /// 返回 4 个装甲板的当前血量,按 index 排列。没有数据时返回 `None`,界面据此 - /// 显示占位符而不是 0——0 是一个看起来像真值的假值。 - fn health(&self, team: u8) -> Option<[u16; 4]>; -} - -/// 一边的装甲板数量。 -/// -/// RoboMaster 规则是每队 4 块。界面按这个数量画格子,所以数据还没接上时也能看到 -/// 位置——空着比画 0 个格子更让人以为布局坏了。 -pub const ARMOR_BOARDS: usize = 4; - -/// 目前唯一的实现:什么都不提供。 -/// -/// 存在的意义是让界面代码从一开始就对着一个真实接口开发,而不是等装甲板就绪后再改 -/// 一次结构。 -pub struct NoArmorYet; - -impl ArmorSource for NoArmorYet { - fn health(&self, _team: u8) -> Option<[u16; 4]> { - None - } -} \ No newline at end of file diff --git a/client/ui/camera.slint b/client/ui/camera.slint deleted file mode 100644 index 32b3ae9..0000000 --- a/client/ui/camera.slint +++ /dev/null @@ -1,550 +0,0 @@ -// Rectangle、VerticalLayout、HorizontalLayout 是语言内置的,无需 import。 - -/* 一块画面的读数。 - * - * 技术字段(fps / 丢帧 / 数据报数等)在队伍端 release 下**不产出**:Rust 侧 - * `render_camera_stats(..., TEAM_DIAGNOSTICS)` 传 false 时这些字段是空字符串, - * 界面也不显示它们。裁判端始终显示。 */ -export struct CameraStats { - // 在线状态 - online: bool, - status-text: string, - - // 实时测量 - fps: string, - resolution: string, - frame-bytes: string, - - // 累计 - frames-shown: string, - frames-dropped: string, - loss-percent: string, - datagrams: string, - - // 帧间隔,用于把"卡"和"帧率低"区分开 - gap-ms: string, -} - -/* 一边的血量(4 块装甲板)。 - * - * `available` 为 false 时界面显示占位符而不是 0:0 是一个看起来像真值的假值, - * 现场看到 0 会以为对方已经没血了。 */ -export struct TeamHealth { - team-name: string, - available: bool, - // 占位符文案 - placeholder: string, - // 每块装甲板的血量;available 时有效 - hps: [int], -} - -/* 首次运行时的摄像头选择。 - * - * 队伍端需要绑定一块板子(选定后不可更改),裁判端不需要。 */ -export struct CameraChoice { - id-text: string, - addr-text: string, - online: bool, -} - -/* ------------------------------------------------------------------ 视频窗格 - * - * 一路画面 + 滚轮缩放 + 拖动平移。两种身份共用这个组件。 - * - * 缩放的实现方式:把 `Image` 的 `width`/`height` 按 `zoom` 放大后放进一个 - * `clip: true` 的容器里,靠 `offset-x`/`offset-y` 平移。Slint 的 `Image` 没有 - * 缩放因子属性,所以只能用这个办法——它也顺带让"放大到超出窗口"成为可能。 - * - * `image-fit: contain` 让缩放到 1.0 时画面完整可见且不变形;放大后 - * `image-fit: fill` 才会跟着缩放,否则画面尺寸固定在 contain 算出的尺寸上, - * 放大看起来毫无反应。 - */ -component VideoPane inherits Rectangle { - // 画面 - in property frame; - // 没有画面时显示的提示(例如槽位空着) - in property empty-text; - - background: #000000; - // 关键:不 clip 的话放大后的画面会溢出到界面其它地方去。 - clip: true; - - // 缩放与平移。zoom = 1.0 是"完整可见"。 - in-out property zoom: 1.0; - in-out property offset-x: 0px; - in-out property offset-y: 0px; - - // 放大到几倍为止。再多也看不清了,只是把像素拉大。 - in property max-zoom: 8.0; - in property step: 1.25; - - // 拖动时的起点,由 TouchArea 记录。 - private property drag-start-x: 0px; - private property drag-start-y: 0px; - private property drag-origin-x: 0px; - private property drag-origin-y: 0px; - - public function reset-view() { - root.zoom = 1.0; - root.offset-x = 0px; - root.offset-y = 0px; - } - - Image { - source: root.frame; - // contain:zoom=1 时完整可见且不变形。 - // fill:放大后跟着容器缩放——只用 contain 的话放大不会有任何视觉变化。 - image-fit: root.zoom <= 1.0 ? contain : fill; - width: parent.width * root.zoom; - height: parent.height * root.zoom; - x: (parent.width - self.width) / 2 + root.offset-x; - y: (parent.height - self.height) / 2 + root.offset-y; - horizontal-alignment: center; - vertical-alignment: center; - } - - // 没有画面时的占位。裁判端两块板子通常只到一块,空槽位要能说清原因, - // 而不是留一块黑。 - // - // `absolute` 布局:它不参与父级的尺寸需求计算。之前它不是,于是"已连接, - // 等待画面…"这行字的长度决定了这一格要多少宽度,裁判端两格就永远不等宽 - // (实测 55% / 45%)——文字长短本不该影响画面大小。 - if root.empty-text != "": Text { - text: root.empty-text; - color: #5a5a64; - font-size: 14px; - horizontal-alignment: center; - vertical-alignment: center; - // 占满整个窗格,文字居中,但不参与布局。 - x: 0; - y: parent.height / 2 - self.height / 2; - width: parent.width; - } - - // 缩放指示:只在放大时出现,否则一直占着位置。 - // - // 同样是绝对定位(给出 x/y),否则它一出现就会改变这一格的尺寸需求,把两路 - // 不等宽的问题重新引进来——而且是在用户放大之后才出现,最难排查。 - if root.zoom > 1.05: Rectangle { - background: #000000aa; - border-radius: 4px; - width: 92px; - height: 26px; - x: parent.width - self.width - 10px; - y: 10px; - - Text { - text: "x" + root.zoom; - color: #d0d0d8; - font-size: 13px; - horizontal-alignment: center; - vertical-alignment: center; - } - } - - // reset-view 按钮:只在放大/平移后出现,给一个回到全景的出口。 - if root.zoom > 1.05 || root.offset-x != 0px || root.offset-y != 0px: Rectangle { - background: #000000aa; - border-radius: 4px; - width: 76px; - height: 26px; - x: 10px; - y: 10px; - - TouchArea { - clicked => { - root.reset-view(); - } - } - - Text { - text: "复位"; - color: #d0d0d8; - font-size: 13px; - horizontal-alignment: center; - vertical-alignment: center; - } - } - - // 滚轮与拖动都在最上层。 - TouchArea { - scroll-event(event) => { - // delta-y 为正表示向上滚(放大)。 - if event.delta-y > 0px { - root.zoom = Math.min(root.zoom * root.step, root.max-zoom); - } else if event.delta-y < 0px { - root.zoom = Math.max(root.zoom / root.step, 1.0); - } - // 缩回 1.0 时顺便归位:此时画面完整可见,平移没有意义, - // 留着会让下一次放大从偏移位置开始,看起来像"跳了一下"。 - if root.zoom <= 1.0 { - root.offset-x = 0px; - root.offset-y = 0px; - } - return EventResult.accept; - } - - // 拖动平移。 - // - // 用 `pointer-event` 而不是 `pressed` / `moved`:TouchArea 的 `pressed` 是 - // **属性**(可以读 `self.pressed`)而不是回调,写成 `pressed => {...}` 编译 - // 不过。按下(Down)时记起点和当前偏移,移动(Move)时按增量更新。 - pointer-event(event) => { - if event.kind == PointerEventKind.down { - root.drag-start-x = self.mouse-x; - root.drag-start-y = self.mouse-y; - root.drag-origin-x = root.offset-x; - root.drag-origin-y = root.offset-y; - } else if event.kind == PointerEventKind.move && self.pressed { - // 只在放大后生效——zoom=1 时画面完整可见,没有可移动的空间。 - if root.zoom > 1.0 { - root.offset-x = root.drag-origin-x - + (self.mouse-x - root.drag-start-x); - root.offset-y = root.drag-origin-y - + (self.mouse-y - root.drag-start-y); - } - } - return EventResult.accept; - } - - double-clicked => { - root.reset-view(); - } - - // 放大后用抓取光标提示可拖动。 - mouse-cursor: root.zoom > 1.0 ? move : default; - } -} - -/* 一路画面的下方信息条。两种身份共用。 - * - * 技术读数(fps / shown / dropped)在队伍端 release 下为空字符串,`visible` - * 让整块消失而不是留一条空行。 */ -component StatusBar inherits Rectangle { - in property stats; - in property health; - // 血格子比状态栏里的字宽,裁判端并排时用 - in property cell-width: 34px; - in property cell-height: 12px; - // 队伍端那一条更高一些,因为它独占一行血量 - in property wide: false; - - background: #1a1a20; - height: wide ? 72px : 78px; - - HorizontalLayout { - padding: 12px; - spacing: 16px; - - // 左:队伍名 + 血量格子 - VerticalLayout { - padding: 0; - spacing: 5px; - horizontal-stretch: 1; - - Text { - text: root.health.team-name; - color: #e8e8ec; - font-size: 14px; - font-weight: 600; - vertical-alignment: center; - } - - HorizontalLayout { - padding: 0; - spacing: 5px; - - for hp[index] in root.health.hps: Rectangle { - width: root.cell-width; - height: root.cell-height; - border-width: 1px; - border-color: root.health.available ? #4a4a55 : #2f2f38; - background: root.health.available ? #6cc96c : #17171c; - } - } - } - - // 右:状态 + 技术读数 - // - // VerticalLayout 没有 horizontal-alignment 属性(右对齐要落在里面的 Text - // 上)。给它 preferred-width 而不是 stretch,让它按内容宽度占位。 - VerticalLayout { - padding: 0; - spacing: 3px; - preferred-width: root.wide ? 340px : 300px; - - Text { - text: root.stats.status-text - + (root.stats.fps != "" ? " " + root.stats.fps + " fps" : "") - + (root.stats.resolution != "" ? " " + root.stats.resolution : ""); - color: root.stats.online ? #e8e8ec : #e0a060; - font-size: 13px; - horizontal-alignment: right; - } - - // 技术读数整行在 release 下消失,不留空行。 - if root.stats.frames-shown != "": Text { - text: "shown " + root.stats.frames-shown - + (root.stats.frames-dropped != "" - ? " dropped " + root.stats.frames-dropped - + " (" + root.stats.loss-percent + ")" - : "") - + (root.stats.gap-ms != "" ? " gap " + root.stats.gap-ms : "") - + (root.stats.frame-bytes != "" ? " " + root.stats.frame-bytes : ""); - color: root.stats.frames-dropped == "0" ? #7e7e89 : #e0a060; - font-size: 12px; - horizontal-alignment: right; - } - } - } -} - -/* ---------------------------------------------------------------- 队伍端 - * - * 队伍终端:一块画面 + 自己那一边的血量。 - * - * 与裁判端是两套独立布局而不是一套加条件显示:两种场景的布局诉求不同(一边要省 - * 画面空间给视频,一边要均分),共用一套再用 visible 切换会让每处改动都要考虑两种 - * 情况。 */ -export component TeamApp inherits Window { - title: "cam-client — 队伍"; - // 初始尺寸只作为"用户什么都不做时"的兜底;Rust 侧在 show() 之前按屏幕工作区 - // 覆盖它,所以这里的数字只是无屏幕信息时的退路。 - preferred-width: 1280px; - preferred-height: 800px; - // min-* 保证拖到很小时仍然可用:两路并排时没有下限,空的那格会被压没。 - min-width: 900px; - min-height: 600px; - - in property frame; - in property stats; - in property health; - - // 选择绑定时显示,其余时候隐藏 - in property <[CameraChoice]> choices; - in property choosing; - - callback pick(int); - - background: #101014; - - if !root.choosing: VerticalLayout { - spacing: 0; - - // 画面占满其余全部空间。0 的 padding/spacing 是"贴边",让画面尽可能大—— - // 视频终端里每一像素都可能是有用信息。 - VideoPane { - frame: root.frame; - empty-text: root.stats.online ? "已连接,等待画面…" : "离线"; - vertical-stretch: 1; - horizontal-stretch: 1; - } - - StatusBar { - stats: root.stats; - health: root.health; - wide: true; - } - - // 血量还没有数据源:把这件事写出来,而不是让一排灰块让人猜。 - // release 下这行也保留——它不泄露任何对手信息。 - Rectangle { - background: #14141a; - height: 24px; - - Text { - text: root.health.placeholder; - color: #6a6a75; - font-size: 12px; - horizontal-alignment: center; - vertical-alignment: center; - } - } - } - - // 选择界面 - if root.choosing: VerticalLayout { - padding: 32px; - spacing: 14px; - - Text { - text: "选择要观看的摄像头"; - color: #e8e8ec; - font-size: 20px; - font-weight: 600; - } - - Text { - text: "选定后不能更改。这里的选择会写入配置文件,重启后仍然生效。"; - color: #8a8a95; - font-size: 13px; - wrap: word-wrap; - } - - Rectangle { height: 6px; background: transparent; } - - // TouchArea 而不是给 Rectangle 挂 callback:Rectangle 是基础元素,没有 - // 点击回调;TouchArea 才是带交互的元素。 - for choice[index] in root.choices: Rectangle { - background: choice.online ? #1c1c22 : #16161b; - border-width: 1px; - border-color: #2a2a33; - height: 56px; - - TouchArea { - clicked => { - root.pick(index); - } - } - - HorizontalLayout { - padding: 14px; - spacing: 16px; - - Text { - text: choice.id-text; - color: #e8e8ec; - font-size: 14px; - font-weight: 600; - vertical-alignment: center; - horizontal-alignment: left; - } - Text { - text: choice.addr-text; - color: #8a8a95; - font-size: 12px; - vertical-alignment: center; - horizontal-alignment: left; - horizontal-stretch: 1; - } - Text { - text: choice.online ? "在线" : "离线"; - color: choice.online ? #7ddc7d : #8a8a95; - font-size: 12px; - vertical-alignment: center; - } - } - } - - // 一块都没发现时给出可操作的提示,而不是一个空列表。 - if root.choices.length == 0: Rectangle { - background: #1a1418; - border-width: 1px; - border-color: #4a2a35; - height: 64px; - - Text { - text: "还没有发现任何摄像头。请确认板子已上电、已连上这个网络,\n然后稍候——板子每 2 秒广播一次在线状态。"; - color: #e0a060; - font-size: 13px; - wrap: word-wrap; - vertical-alignment: center; - } - } - } -} - -/* ---------------------------------------------------------------- 裁判端 - * - * 裁判终端:两块画面左右并排 + 双方血量。 - * - * 与队伍端分开:这里的核心诉求是"一眼比出两边的状态",所以两块画面等宽、血量 - * 并排对称。 */ -export component RefereeApp inherits Window { - title: "cam-client — 裁判系统"; - preferred-width: 1600px; - preferred-height: 900px; - min-width: 1000px; - min-height: 640px; - - in property frame-a; - in property frame-b; - in property stats-a; - in property stats-b; - in property health-a; - in property health-b; - - // 两路的缩放互相独立:滚轮只作用于鼠标所在的一路(见各自窗格上的 TouchArea)。 - // 裁判要并排比出两边,所以默认都是 1.0,且不提供联动。 - - background: #101014; - - VerticalLayout { - spacing: 0; - - // 两路画面等宽并排。 - // - // 之前只写 `horizontal-stretch: 1`,于是**内容多的一格会占更多宽度**: - // HorizontalLayout 先按内容的最大需求分配,剩下的才按 stretch 分。队伍一 - // 那格写着"已连接,等待画面…",队伍二只有"未收到画面",于是左边多要了 - // 十几个像素,右边就少十几个——实测 55% / 45%。 - // - // 空的那一侧被压成一条缝是更极端的同一类问题:没有图 ⇒ 最小宽度接近 0。 - // - // 解法是让两格的需求**只由画面决定**,与文字无关: - // preferred-width: 50% 声明"我想要一半",配合 stretch 1 精确平分 - // min-width: 320px 兜底,保证任何一侧至少有一个能看画的宽度 - // 文字的溢出由 VideoPane 的 clip 裁掉,不参与布局。 - HorizontalLayout { - padding: 0; - spacing: 2px; - - VideoPane { - frame: root.frame-a; - empty-text: root.stats-a.online ? "已连接,等待画面…" : "等待队伍一的图传"; - vertical-stretch: 1; - horizontal-stretch: 1; - preferred-width: 480px; - min-width: 320px; - } - - VideoPane { - frame: root.frame-b; - // 槽位对应队伍,所以空槽要说清在等谁,而不是笼统的"未收到画面"。 - empty-text: root.stats-b.online ? "已连接,等待画面…" : "等待队伍二的图传"; - vertical-stretch: 1; - horizontal-stretch: 1; - preferred-width: 480px; - min-width: 320px; - } - } - - // 每路画面下方的队伍条:名字、血量、统计。 - // - // 用与画面列完全相同的宽度策略,所以拖动窗口时文字块不会跟着画面块错位。 - HorizontalLayout { - padding: 0; - spacing: 2px; - - StatusBar { - stats: root.stats-a; - health: root.health-a; - horizontal-stretch: 1; - preferred-width: 480px; - min-width: 320px; - } - - StatusBar { - stats: root.stats-b; - health: root.health-b; - horizontal-stretch: 1; - preferred-width: 480px; - min-width: 320px; - } - } - - // 装甲板还没接上:说清楚,而不是留一排看不懂的灰块。 - Rectangle { - background: #14141a; - height: 24px; - - Text { - text: root.health-a.placeholder; - color: #6a6a75; - font-size: 12px; - horizontal-alignment: center; - vertical-alignment: center; - } - } - } -} diff --git a/desktop/.gitignore b/desktop/.gitignore index 2f8f754..88f22c9 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -10,3 +10,7 @@ dist/ # Tauri 生成的权限 schema(构建产物,随 tauri-build 版本变化)。 /src-tauri/gen/schemas/ + +# 本机开发用的状态:dev 配置(里面有密码哈希)与演示/录制的输出、界面截图。 +/.dev/ +/.shots/ diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 0000000..0ae1d6b --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,418 @@ +# desktop + +桌面端。**两种身份,两套页面,一个进程。** + +| 身份 | 画面 | 血量 | 装甲板 | 绑定 | +|---|---|---|---|---| +| 用户端(`team`) | 一路 | 自己那边 + 血条 | 自己那块 | **要,改动需密码** | +| 裁判端(`referee`) | 两路并排 | 双方 | 发现列表 + 槽位 | 不需要 | + +界面是 WebView 里的 React(画面画在 canvas 上),后端是 Rust。**它不是把旧客户端套进 +一个窗口**:网络、重组、发现、配置、密码、录制全部重写成了可单测的模块。 + +```text +desktop/ + core/ cam-core:不含任何界面与 Tauri 的东西(69 个测试) + src-tauri/ Tauri 后端:推流、命令、录制(30 个测试) + src/ React 前端:两套页面 + HUD + src/lib/ IPC 封装、WebSocket 客户端、状态存储、格式化 + src/components/ 视频窗格、血条、瞄准器、装甲板表、录制面板、设置抽屉 + src/pages/ 用户端 / 裁判端 +``` + +## 快速开始 + +```sh +cd desktop +pnpm install +pnpm app # = tauri dev:起 vite + 编 Rust + 开窗口 +pnpm build # 只构建前端(tsc --noEmit && vite build) +pnpm tauri build # 出 .app 与 .dmg +``` + +下面两条是 Rust 侧,用它验证不依赖窗口的部分: + +```sh +cd desktop/core && cargo test # 69 +cd desktop/src-tauri && cargo test # 30,其中几条真的会起 ffmpeg +``` + +### 演示模式:没有板子也能把整条链路跑起来 + +```sh +cd desktop +CAM_DEMO=1 pnpm app +``` + +它**不伪造数据给界面**,而是合成**协议数据报**,从 `Receiver::datagram` 与 +`ArmorHealthStore::datagram` 进去。也就是说演示模式跑的是同一条真实路径: + +```text +合成 JPEG ──分片──▶ Receiver::datagram ──重组──▶ 帧 sink ──▶ WebSocket ──▶ canvas +合成血量 ─────────▶ ArmorHealthStore ──▶ HealthTracker ──▶ 命中反馈 +``` + +分片、重组、发现、统计、掉血派生、两道闸门全在跑真代码,截图与录制也不需要特殊分支。 +界面上会有一条 **演示模式** 标记,板子 id 也刻意取成 `0x0DE00001` 这种一眼能看出是人造 +的形状——**别把它当成现场数据**。 + +### 命令行与环境变量 + +```text + --role 身份。默认看配置文件 + --config <路径> 配置文件位置 + --demo 演示模式 + --list 打印发现的摄像头与装甲板后退出 + -h, --help 帮助 + +CAM_DEMO=1 等价于 --demo +CAM_CONFIG=<路径> 等价于 --config <路径> +``` + +两个环境变量存在的理由很实际:`tauri dev` 要用两个 `--` 才能把参数递给应用 +(`tauri dev -- -- --demo`),写在一行环境变量里不会有人记错。 + +**未知参数只记一条 debug 日志,不报错。** macOS 从访达双击 .app 时系统会塞进来一个 +`-psn_0_123456`;把它当错误处理的表现是"双击图标什么都没发生"。 + +## 为什么换成 Tauri,代价是什么 + +旧客户端是 Rust + Slint 单二进制,能用。换掉它的理由是**界面这一侧的生产力**:血条、 +瞄准器、可拖拽的偏移校准、下拉绑定、设置抽屉、录制列表与回放——这些用 CSS/HTML 写是 +几十分钟的事,用即时模式 UI 工具写要自己排版、自己画控件、自己处理焦点。这个项目的重心 +在链路(UDP、重组、发现、防作弊),界面应当越快改完越好。 + +代价说清楚: + +- **多一层**。WebView + Node 工具链 + Tauri 的权限系统,比一个 Slint 二进制重得多。 +- **要联网装依赖**(前端 npm、Rust crates)。 +- **画面要过一条本地通道**才能到界面(见下一节)。Slint 那版是把 RGBA 直接交给渲染器, + 路径更短。 + +换来的另一样东西是**可测性**:`core/` 不认识界面,所以重组、发现、帧率窗口、槽位分配、 +配置与密码全都能用单元测试盯住——而它们恰好是最容易错、错了又最难在现场看出来的部分。 + +## 视频怎么进 WebView + +**一个只监听回环地址的 WebSocket,推二进制 JPEG。** 界面从 `stream_info` 命令拿到 +`ws://127.0.0.1:<随机端口>/?token=...`。 + +### 为什么不是 Tauri 的 IPC + +两路 22fps、每帧约 22 kB,每秒约 1 MB 的裸 JPEG。走 IPC 要序列化成 JSON(base64 再涨 +1/3),前端再解回来——CPU 花在没人需要的编解码上,延迟白涨一截。二进制 WebSocket 帧把 +原始字节直接交给前端,浏览器用**原生解码器**解,**后端连 JPEG 都不用解**。 + +### 二进制帧的布局 + +```text + 0 4 8 10 12 16 + +--------------+--------------+------+------+--------------+---------+ + | sender_id u32| frame_id u32 | w u16| h u16| jpeg_len u32 | JPEG... | + +--------------+--------------+------+------+--------------+---------+ +``` + +小端,16 字节头。前后端对这段布局的约定由一个测试盯着(`server.rs` 与 `stream.ts` +两侧都有),长度不一致的帧宁可丢掉,也不要让前端拿到一段不完整的 JPEG。 + +### 前端侧的两个陷阱 + +**解码是异步的,所以帧号要看着用。** `createImageBitmap` 返回 Promise,两个相邻帧的解码 +可能乱序完成。所以每路记住最后画过的帧号,只有更新的那帧才采用——没有这一步,画面会偶尔 +往回跳一帧,看起来像"卡了一下",极难归因。 + +**`ImageBitmap` 必须显式 `close()`。** 它持有解码后的像素,不吃 GC 的账。每收到新帧就 +替换旧的那张并不够,必须把旧的那张关掉,否则两路 22fps 跑几分钟就是几百 MB。 + +## 防作弊:两道闸门,不是一个 + +对抗赛里两队都用这套系统看自己小车的图传,要防止对手开第二个窗口连到另一队的板子。 + +**关键是这件事做在接收端,不是界面里。** 广播的 Hello 里就有 MAC 派生的 `sender_id` 与 +IP,藏起来毫无意义;数据也可能已经收下并解码了,`.slint`/CSS 只控制显示。 + +| 闸门 | 管什么 | 什么时候开 | +|---|---|---| +| `only_sender` | 帧的重组与投递 | 用户端**始终**是绑定的那一块 | +| `discovery_all` | 是否记录**其它**板子的 Hello | 只在密码解锁后的设置界面里打开 | + +旧客户端把两件事绑在同一道闸门上,于是只能在"能列出候选"和"挡住对手"之间二选一,改绑 +只能靠手改配置文件。现在分开了:设置界面能列出候选摄像头(否则无从选起),但**画面不进 +来**——对手的板子即使把流推到本机端口、即使被记进发现列表,它的帧也进不了重组器。 + +四种情况不限制:裁判端(中立方)、debug 构建、用户端还没绑定、`--camera-id` 显式覆盖。 + +### 遥测按编译模式门控 + +`TEAM_DIAGNOSTICS = cfg!(debug_assertions)`。用户端 release 下**不产出**帧率/丢帧这些 +数字——不是"界面藏起来",是 DTO 里就不存在(`CameraStatsDto::build` 直接把它们置零)。 +由编译模式决定而不是命令行开关:开关意味着二进制里带着那套东西,忘开一次就泄露了。 + +### 这挡不住什么 + +诚实地说:**IP 仍然通过 Hello 广播泄露。** 对手知道你的 IP 之后可以自己发一个 +`Subscribe` 过来——固件会向任何订阅者推流,那道闸门在他的客户端上,不在你的。真要挡住 +需要固件侧做配对 token(首次绑定发一个令牌,固件只接受带令牌的订阅),代价是固件重启就 +失配。 + +回环通道上那个 token 也不是防对手的:它只让"本机另一个程序顺手连上来看画面"变成一件需要 +先读到那个 token 的事。 + +## 密码:它挡的是谁 + +必须说清楚,否则会被当成一把它并不是的锁。 + +- **挡得住**:现场随手改绑定。选手想把自己的画面换到对手那块板子上,得先知道密码;围观 + 的人翻不到明文(只有 Argon2id 哈希),也不能靠改配置文件把绑定改掉——改了之后界面会 + 要求先解锁。**退出/关闭程序**同样要密码,因为"随手关掉"也是一种要挡的误操作。 +- **挡不住**:一个愿意在自己机器上重编这个客户端的人。配置与二进制都在他手上,删掉哈希 + 或者直接改源码都行。真要封住得回固件加配对 token。 + +所以它的定位是**防误触与防随手改**,不是密码学意义上的对抗。 + +实现上: + +- Argon2id(`argon2` crate,默认参数 19 MiB / t=2),存标准的 PHC 字符串,参数跟着哈希 + 一起躺在配置里,以后调参不需要迁移。用快哈希(SHA-256 之类)对 4 位数字密码等于没有 + 保护。 +- 首次启动**强制**设置密码(`Config::needs_password_setup`),最短 4 位。长度下限只要 + 4 位是刻意的:一个 8 位强密码在这里换不来任何实际安全增益,却会让现场每次改绑都要掏 + 手机查一遍。 +- 解锁一次管到进程结束(会话内)。用户端可以手动"上锁"。 +- **裁判端不要密码**:它没有"绑定到自己那块板子"的动作可被滥用,而现场裁判要能最快地改 + 槽位、开录。 +- **离开用户端要密码**:否则切到裁判端就能看两路画面、还能随便改配置,闸门形同虚设。 + +执行点在 Rust 侧:每个改变配置的命令都过 `AppState::require_unlocked`。界面上的解锁 +对话框只是入口——即使有人从控制台直接调 `invoke("bind_camera")`,一样要过这一关。 + +**故意没有 `--camera-id` 这类命令行覆盖。** 旧客户端有一个,用来"临时把一台机器当另一台 +用";在这里它等于一条不需要密码就能改绑的路,所以去掉。要临时换一块板子,就在设置里改 +(要密码),或者用 `--config` 指一份另外的配置文件。 + +忘了密码:删掉配置文件里的 `password` 一行(或整个文件)。旧客户端的配置会在首次启动时 +导入并备份成 `.bak`。 + +## 用户端 HUD + +### 血条 + +画面**下面**一条窄带里的胶囊条,不挡画面。宽度、是否显示数字/队名、低血阈值与脉动都可配。 + +**未接入时显示占位说明而不是 0**:0 是一个看起来像真值的假值,现场看到 0 会以为对方已经 +没血了,然后开始找裁判理论。这一条在旧客户端上是踩过坑的。 + +### 瞄准器 + +画在 canvas 上(不是叠一层 DOM),因为**它必须跟着画面走**:它标的是相机光轴上的一个点。 +用 DOM 摆一个 `` 再叠一层 `transform` 的话,"图像落在哪、瞄准点落在哪"要算两遍,而 +两份实现迟早会不一致——表现是放大之后瞄准器偏了,恰恰发生在这件事最要紧的时候。所以变换 +只算一次,图像、网格、瞄准器、伤害数字全都用同一组数画出来。 + +尺寸与缩放的关系也是刻意分开的: + +| | 跟着缩放走吗 | 为什么 | +|---|---|---| +| 位置 | **是** | 它指的是画面里的一个点 | +| 大小 | 否 | 像真瞄具一样,放大之后准星覆盖画面里更小的一块 | +| 网格 | 是 | 它标的是画面里的比例,用来估距离 | + +**偏移校准存的是画面比例,不是像素。** 摄像头与枪管不同轴时要把瞄点挪到弹着点上,而像素 +偏移在分辨率或窗口大小一变就全错了。校准可以拖着画面调,也可以直接填数值。 + +### 命中反馈 + +主板**不报**"被打了一下",只报"现在是多少血"。所以命中是靠相邻两次读数之差还原的 +(`HealthTracker`): + +- 血量减少 ⇒ 一次命中,伤害是两个读数之差; +- 血量增加 ⇒ 不是命中,是长按 BOOT 的重置; +- 第一次见到一块板子不产生命中——没有基线,此时 `hp = 0` 只是"已经没血了"。 + +按**板子**记基线而不是按 `(板子, index)`:主板一次广播四条、`hp` 相同,按四条各记一份 +基线会让一次掉血被报成四次命中。 + +已知精度上限:两次读数之间发生两次伤害时只能看到净差。心跳 200 ms,所以这段窗口里的两次 +打击会被合并成一次——对"闪一下红"足够,对判定不够,判定看的是界面上的绝对血量。 + +反馈有三样:整屏闪红、画面抖动、准星旁的伤害数字。**大号数字不放在画面正中**——第一版 +放了,结果它正好压在瞄准点上,而被打中的那一下恰恰是最需要看着准星的时候。 + +## 裁判端 + +### 两格等宽 + +`flex: 1 1 0` + `min-width: 0`,两格的宽度只由容器决定。 + +旧客户端在这里翻过车:`HorizontalLayout` 先按内容的**最大**需求分配,剩下的才按 stretch +分。一格写着"已连接,等待画面…"(8 字),另一格只有"未收到画面"(5 字),于是左边多要 +十几个像素——实测 55% / 45%。现在占位文字绝对定位、两格需求相同。 + +### 槽位分配:修掉了一个真 bug + +规则: + +1. 还在线的槽位不换人; +2. **候选里排除已经被另一个槽位占着的 id**; +3. 掉线的槽位保留它的 id(显示"离线"),这样它回来时还在原位; +4. 没有候选就不动——只有一块板在场时另一个槽位空着,空着比显示一份复制品诚实。 + +规则 2 是修 bug。旧实现只排除"这个槽位自己原来那个 id",于是**队伍一的板子一离线,它的 +槽位就会去拿队伍二那块,两格显示同一块板子**——看起来像图传串了,现场的排查方向会是网线、 +AP、板子,而不是二十行分配代码。现在它有一个直接对着这个场景写的测试。 + +有烧错固件的板子(`team` 字段是反的)时可以在设置里改成手动指定槽位。 + +### 装甲板发现列表 + +场上连上了几块板子、各自自报哪一队、现在多少血、多久没广播、四条上报齐不齐、分到了哪个 +槽位——"裁判端没显示血量"时第一眼看的就是这张表。它能区分"板子根本没上线"、"上线了但队 +伍号烧反了"和"只有一条上报在发"。 + +**这不需要改固件**:主板一直用 `encode_from(sender_id)` 把自己的 MAC 派生 id 打进帧头, +只是旧的解码路径把它丢了。现在 `ArmorHealth::decode_from` 把它交出来。 + +### 截图与录制 + +- 截图存 PNG(`shots/` 下),文件名带板子 id 与毫秒时间戳。 +- 录制存 mp4,走 ffmpeg 子进程:`-f image2pipe -c:v mjpeg -framerate -i pipe:0` + 喂帧,x264 编码,`+faststart`。 +- **帧率要显式告诉 ffmpeg**。`image2pipe` 里的 JPEG 序列没有时间戳,一串帧在 ffmpeg 看来 + 就是"以固定帧率播放的图片"。所以这个数只能来自实测,它会写进日志与 DTO——回放快放/慢放 + 时第一个要看的就是它。 +- 喂帧经由一条长度 64 的队列与一个写盘线程。**接收线程绝不能被写盘拖慢**:那一天的表现是 + 链路上本不存在的丢帧。写不进去就丢帧并计数,丢帧数会出现在界面上。 +- 文件名会查重:同一秒停一次再开一次是很自然的操作("这局重录"),而重名意味着 ffmpeg 的 + `-y` 把上一段**静默覆盖**掉。所以先收尾上一段再开新的,重名往后排 `-2`、`-3`。 + +界面内回放走 Tauri 的资源协议,只对 `tauri.conf.json` 里 `assetProtocol.scope` 白名单的 +目录生效。默认录制目录在名单里;改到别处时 `