KennethCheng 1df072356f feat(windows): Phase 6 C3 — 系统托盘 + URL 打开
- 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 校验逻辑的拒绝路径
2026-09-19 07:28:49 +08:00

Windows-Gotify-Client-Rust

轻量、现代、Windows 原生体验良好的 Gotify 桌面客户端,使用 Rust + Slint 构建。

详细的产品定位、技术栈约束、架构、UI 规范与分阶段实施计划全部记录在 AGENTS.md。本文档只做项目导览。

状态

🚧 从 0 起步 —— 当前仓库只有目录骨架与占位 Cargo.toml,尚未进入 Phase 1(Bootstrap)。 完整 8 阶段路线图见 AGENTS.md §57。

目标平台

  • Windows 10 x64
  • Windows 11 x64(ARM64 条件允许时支持)
  • 后台常驻、托盘型客户端

技术栈

层 选型
语言 Rust stable
UI Slint 1.17.x
HTTP reqwest(blocking + rustls)
WebSocket tungstenite
Windows 集成 windows-rs focused crates(按需引入)
配置 / 状态 JSON 文件(%APPDATA%/WindowsGotify/)
日志 tracing + tracing-subscriber

显式禁用:Tauri / Wails / Electron / WebView2 / WPF / WinUI 3 / Node 运行时。 理由见 AGENTS.md §2。

仓库布局

Windows-Gotify-Client-Rust/
├── Cargo.toml          # 占位 — 见 AGENTS.md §44
├── AGENTS.md           # AI Agent 实施计划(1363 行)
├── README.md
├── LICENSE             # MIT(占位)
├── .gitignore
├── assets/             # 图标、字体等
├── ui/                 # Slint 视图
├── src/                # Rust 业务代码
│   ├── gotify/         # HTTP + WebSocket
│   ├── storage/        # config.json / state.json
│   ├── windows/        # 托盘 / Toast / 启动 / 单实例 / URL
│   └── services/       # 生命周期
├── tests/              # 集成测试
└── scripts/            # PowerShell 构建脚本

开发环境

本仓库目前还没有源码,本节将在 Phase 1 完成后补全具体的 cargo run / cargo build --release 步骤。

隐私与安全

  • App Token 默认以 JSON 明文存储在 %APPDATA%/WindowsGotify/config.json,仅当前用户可访问
  • 日志永不打印完整 Authorization 头
  • 通知点击的 click.url 仅允许 http:// / https://,拒绝 file:// / javascript: / 自定义 scheme
  • 详细策略见 AGENTS.md §13 / §20 / §38 / §51

License

MIT(见 LICENSE)。

S
Description
No description provided
Readme MIT
637 KiB
2026-09-19 20:40:55 +08:00
Languages
Rust 73.7%
Slint 26.3%