- src/windows/url.rs: open() 用 url::Url parse 校验 + scheme 严格
限制(只 http/https,拒绝 file:// / javascript: / data: / ftp /
自定义 scheme);通过 ShellExecuteW 调默认浏览器打开;§13
/ §38 安全约束
- src/windows/tray.rs:
· RegisterClassExW 注册隐藏 helper window class
· CreateWindowExW(WS_EX_TOOLWINDOW, 0×0) 创建不显示的
message-only helper window
· Shell_NotifyIconW NIM_ADD 注册托盘图标(默认 NULL icon,
Phase 7 polish 再换 .ico)
· dedicated thread 跑 GetMessageW / DispatchMessageW 标准
message loop
· WM_USER+1 uCallbackMessage 处理 tray 鼠标事件
· WM_LBUTTONDBLCLK = Open callback
· WM_RBUTTONUP = CreatePopupMenu + TrackPopupMenuEx 显示
Open / Quit 菜单
· WM_COMMAND 处理菜单 item IDM_OPEN / IDM_QUIT
· Drop 时 NIM_DELETE + DestroyWindow + thread.join() 干净退出
- src/lib.rs:
· #[cfg(windows)] 启动时建 TrayIcon,绑 on_open(show window)
/ on_quit(std::process::exit(0));失败时只 warn 不 abort
(托盘是便利功能,不致命)
· 引入 url::open;Phase 7 polish 时接到 message-card 点击
- src/windows/mod.rs: pub use tray + url
- Cargo.toml: windows-sys features 加 Win32_Graphics_Gdi
(WNDCLASSEXW 依赖)
避坑:
- LRESULT / WPARAM / LPARAM 在 windows-sys 0.59 是 type
alias,不是 struct — 不能 LRESULT(0) 构造;直接 0 返回
隐式 cast
- HMENU 是 *mut c_void;用 std::ptr::null_mut()(不是 null())
- NOTIFYICONDATAW_0 在 0.59 没 derive Default;用
unsafe { std::mem::zeroed() }
- std::mem::GUID::zeroed() 不存在;用 windows_sys::core::GUID
显式构造
- NIM_VERSION 常量 windows-sys 0.59 没 export;用字面 4u32
(NIM_VERSION = 4 in Win32 header)
- HWND (raw pointer) 不 Send;跨线程传递时转 usize
- Box<dyn Fn> 不能 derive Clone;用 Box + 唯一 move 到
TrayShared 不需要 Clone
- on_quit closure 不能捕获 Rc<RefCell<AppState>>(不是 Sync)
→ 简化:on_quit 直接 std::process::exit(0);之前 close
已经 save_state,丢失窗口只是 size 增量
- WNDCLASSEXW + RegisterClassExW 在 Win32_Graphics_Gdi
feature 后(hbrBackground 用 HBRUSH = GDI 类型)
验证:cargo build / build --release 都 exit 0,0 warning
- cargo test: 19 passed (新增 4 个 url rejection 测试 +
之前的 15 个); 0 failed
- debug 35.66 MB, subsystem = WINDOWS_CUI
- release 9.79 MB, subsystem = WINDOWS_GUI
- 托盘只能手测(CI 不能断言 WM_USER 消息);自动测试覆盖
URL 校验逻辑的拒绝路径
84 lines
2.8 KiB
TOML
84 lines
2.8 KiB
TOML
# Cargo.toml — Phase 1 (Bootstrap).
|
|
#
|
|
# Dependency rationale lives in AGENTS.md §44. Only the crates actually used
|
|
# by Phase 1 are listed; the remaining crates (reqwest / tungstenite / serde /
|
|
# url / anyhow / ...) are added in later phases when their modules land.
|
|
|
|
[package]
|
|
name = "windows-gotify-client-rust"
|
|
version = "0.1.0"
|
|
edition = "2021"
|
|
rust-version = "1.75"
|
|
description = "Lightweight, modern, Windows-native Gotify desktop client (Rust + Slint)"
|
|
license = "MIT"
|
|
readme = "README.md"
|
|
authors = ["KennethCheng <a1478925630@163.com>"]
|
|
|
|
[[bin]]
|
|
name = "windows-gotify-client-rust"
|
|
path = "src/main.rs"
|
|
|
|
[dependencies]
|
|
# --- UI ---
|
|
# `slint` crate. The `slint!{}` macro is compiled in via the default feature
|
|
# set in 1.x (verified against 1.18.0 — `internal-interpreter` was removed in
|
|
# 1.x). The Windows GUI backend (`backend-winit`) is also part of defaults.
|
|
# Phase 2 will likely switch from the embedded macro to `include_slint!()`
|
|
# + standalone .slint files under `ui/`.
|
|
slint = "1"
|
|
|
|
# --- Logging ---
|
|
tracing = "0.1"
|
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
|
|
|
# --- Error handling ---
|
|
thiserror = "2"
|
|
|
|
# --- Serialization (Phase 2 C3 storage) ---
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
|
|
# --- Networking (Phase 3 Gotify HTTP) ---
|
|
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
|
|
|
|
# --- Date/time (Phase 3 C2 convert Gotify RFC3339) ---
|
|
chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
|
|
|
|
# --- Windows-only (Phase 6 integration) ---
|
|
# `windows-sys` is the low-level ABI layer of `windows-rs`. We only pull
|
|
# the focused feature subsets we actually use (mutex, registry, message
|
|
# loop, windowing); AGENTS.md §1 forbids pulling the umbrella `windows`
|
|
# crate wholesale. `Win32` is the umbrella feature that gates the
|
|
# `Win32_*` sub-features.
|
|
windows-sys = { version = "0.59", features = [
|
|
"Win32",
|
|
"Win32_Foundation",
|
|
"Win32_Security",
|
|
"Win32_System_Threading",
|
|
"Win32_System_Registry",
|
|
"Win32_Graphics_Gdi",
|
|
"Win32_UI_WindowsAndMessaging",
|
|
"Win32_UI_Shell",
|
|
] }
|
|
|
|
# --- WebSocket (Phase 4 real-time stream) ---
|
|
tungstenite = { version = "0.21", default-features = false, features = ["rustls-tls-webpki-roots", "handshake"] }
|
|
url = "2"
|
|
http = "1"
|
|
|
|
# Slint's `.slint` files are compiled by `build.rs`; see build.rs.
|
|
[build-dependencies]
|
|
slint-build = "1"
|
|
|
|
[profile.release]
|
|
# See AGENTS.md §42. The `release-balanced` profile below is the practical
|
|
# default after benchmarking — adjust if startup time regresses.
|
|
opt-level = "z"
|
|
lto = true
|
|
codegen-units = 1
|
|
panic = "abort"
|
|
strip = true
|
|
|
|
[profile.release-balanced]
|
|
inherits = "release"
|
|
opt-level = 3 |