# MyWidget **Repository Path**: e4glet/my-widget ## Basic Information - **Project Name**: MyWidget - **Description**: win10&win11桌面课程管理工具 - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-31 - **Last Updated**: 2026-11-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # TjuWidget · 桌面课表挂件 一个常驻在桌面顶部的小巧课表挂件:**毛玻璃工具条** + **独立悬浮面板**(左侧可编辑课程表,右侧农历月历)。 C++17 直接调 Win32 + WebView2,界面用 HTML/CSS/JS 写。不装运行时、不拖一堆 DLL,双击 exe 就能用,托盘右键退出。 > 只做本地课表:**不联网同步、不登录、不上传**。唯一的网络请求是查天气(可离线使用,失败自动降级)。 ![工具条 + 面板](docs/shot_split.png) | 工具条(收起态) | 课表面板(收起工具条后) | | --- | --- | | ![工具条](docs/shot_pill.png) | ![面板](docs/shot_panel.png) | | 上课时间(小节时间) | 学期设置 | | --- | --- | | ![上课时间](docs/shot_period.png) | ![学期设置](docs/shot_term.png) | --- ## 功能 ### 工具条 - 顶栏居中悬浮,圆角胶囊造型,3D 玻璃质感(CSS 渐变 + 内阴影高光,**不是**黑边描边)。 - `‹` `›` 翻周,中间是时间 / 今日课程铃铛 + 数量 / **课程表** / 天气 / 齿轮(更多)/ `›`。 - 点「课程表」或今日课程铃铛即可展开/收起面板,再点一次关闭。 - 点齿轮弹出菜单:**添加课表**(右侧显示已建课程数)、**学期设置**、**上课时间**、**添加提醒**。 ### 课程表(面板左侧) - 周视图,节次列显示每节的起止时间,今天所在列高亮。 - 一节课一张彩色卡片:课程名 / 地点 / 教师 / `W1-16` 周次标记。 - 支持任意周次范围(`1-8,10,12-16`),非本周的课自动隐藏。 - 点任意卡片即可编辑,或从菜单「添加课表」新建;可改名称、教师、地点、星期、开始/结束节次、周次、颜色,也可删除。 - 底部汇总「今日 N 节课」。 ### 农历月历(面板右侧) - 公历 + 农历双行显示,节气 / 传统节日高亮(如「寒露」「重阳节」)。 - 有课的日期下方带彩色圆点,今天高亮。 - 可切月、回到今天。 ### 提醒(重要事宜) - 给「领导听课 / 实验课 / 交作业」这类事项单独加提醒,**和课表互不干扰**,时间本来就可以与某节课重叠。 - 提前 10 分钟开始响铃,连续响 3 分钟;没点「确认」就停 5 分钟继续响,直到确认。 - 响铃时工具条自动撑高并临时置顶显示铃铛卡片,Web Audio 合成双音提示音(无需音频文件)。 - 支持「仅一次 / 每周重复」;有提醒的课表格子上会显示 🔔;已确认的提醒当天不再重复打扰。 ### 作息时间(小学 / 中学 / 大学) - 一个学期里每节的上下课时间都不一样,所以节次时间**不是常量**,而是可编辑的一份表。 - 内置三套模板:小学 6 节、中学 10 节、大学 10 节(8:20 上课、每节 40 分钟、课间 10 分钟、两节后大课间 30 分钟、下午 14:00 起、9-10 节为晚课)。 - 选模板后可逐节微调起止时间,课表格子里显示的就是这份表。 ### 学期设置 - 选「开学第一周的周一」(内置日历,点任意日期自动取当周周一)+ 学期总周数,周次自动推算。 ### 天气与其它 - 按 IP 定位当前城市,显示天气图标 + 温度,30 分钟刷新一次并本地缓存;断网时静默降级,不影响使用。 - 托盘右键:显示/隐藏、展开面板、始终置顶、贴到桌面层、开发者工具、开机自启、退出。 - 默认**贴桌面层**(不挡其它窗口,Win+D 也不消失),需要时再切「始终置顶」;开机自启默认开启。 - 单实例运行;两处窗口 150% / 200% 缩放下都按逻辑像素排版,不会被拉伸变形。 --- ## 技术方案 选型原则:**零安装、单文件、样式随便改**。所以没有用 Qt / Electron / WPF,而是「Win32 原生壳 + WebView2 渲染界面」。 ``` ┌──────────────── TjuWidget.exe (单个 exe, 无外部 DLL) ────────────────┐ │ │ │ Win32 宿主 (C++17) 页面 (HTML/CSS/JS) │ │ ├─ 工具条窗口 WS_POPUP + 透明背景 ├─ index.html?mode=pill │ │ ├─ 面板窗口 WS_POPUP + 透明背景 └─ index.html?mode=panel │ │ │ (两个独立窗口,各自一个 WebView2 控制器,共用同一份数据) │ │ ├─ 托盘图标 + 右键菜单 │ │ ├─ data.json 读写(原子写) │ │ ├─ 1 秒心跳 push {type:"tick"} ← 提醒调度的时基 │ │ └─ WebView2 环境(进程内共享一份,user data 在 %LOCALAPPDATA%) │ └──────────────────────────────────────────────────────────────────────┘ ``` ### 为什么是这种组合 | 方案 | 结论 | | --- | --- | | **Win32 + WebView2**(本项目) | ✅ exe 约 2 MB,系统自带渲染引擎,界面用 CSS 随便改,玻璃质感好做 | | Electron / Tauri | ❌ 包体 50~150 MB 起步,为一个挂件太重 | | Qt QML / WPF | ❌ 要带运行库或额外框架,改样式成本高;Qt 静态链接还有授权顾虑 | | 纯 Win32 GDI 自绘 | ❌ 圆角、渐变、字体排版全靠手搓,改一次样式改一天 | ### 几个关键实现点 - **两个独立窗口,一份文档。** 工具条和面板是各自独立的原生窗口(不是一个大窗口里切上下两半), 展开/收起因此可以各自做过渡动画,互不影响。同一个 `index.html` 靠 `?mode=pill|panel` 决定渲染哪一半, CSS 里另一半直接 `display:none`,不占布局也不吃点击。 - **WebView2 环境进程内只能有一份。** 同一个 user data 目录在同一进程建两个 environment 会报 `ERROR_NOT_SUPPORTED`,所以宿主启动时建好共享 environment,两个窗口各自 `CreateCoreWebView2Controller`。 - **透明 + 毛玻璃。** 窗口 `WS_POPUP` + `WS_EX_TOOLWINDOW`,`put_DefaultBackgroundColor({0,0,0,0})` 让页面透出桌面。 实测 Win10 上 `ACCENT_ENABLE_ACRYLICBLURBEHIND` 的 tint 会糊满整个窗口矩形且无视 `SetWindowRgn`, `DwmEnableBlurBehindWindow` 会让 WebView2 的 DComp 子窗口画出一块白矩形 —— 所以最终走「CSS 半透明 + 渐变 + 内阴影」的纯前端玻璃效果, **不用** `backdrop-filter`、**不用** 真实 border(圆角上会出现深色锯齿边),发丝线一律用 inset shadow。 - **DPI。** 屏幕 150% 时代码不按物理像素硬写:宿主把 `dpi/96` 交给 `put_RasterizationScale`, 页面里 1 CSS px 就等于 1 逻辑 px,两处窗口在任何缩放下排版一致。 - **不链接任何 WebView2 `.lib`。** 官方 `.lib` 是 MSVC 产物,MinGW 链接会缺 `__security_cookie`。 做法是把 `WebView2Loader.dll` 作为 RCDATA 资源编进 exe,运行时释放到 `%LOCALAPPDATA%\TjuWidget\` 再 `LoadLibrary` + `GetProcAddress`。 同时 `WebView2.h` 里的 `IID_*` 在 MinGW 下是「定义」而非「声明」,所以一律用 `IsEqualIID(riid, IID_…)`,不能用 `__uuidof`。 - **数据只有一份。** 任一窗口改了数据 → 发给宿主 → 原子写 `data.json` → 宿主把整份数据广播给两个窗口, 两边永远一致;翻周这类视图状态也走同一条通道,且带 `remote` 标记避免回显死循环。 ### JS ↔ C++ 协议 ``` JS → C++ : ready | expand | collapse | hide | show | save | quit | pin | menu{open} | remind{open,ringing} | week{week} | editor{kind} C++ → JS : {type:"init",data} | {type:"state",expanded,menu,visible,pin} {type:"data",data} | {type:"week",week} | {type:"editor",kind} | {type:"tick"} ``` `editor.kind` 为 `course | term | period | remind`,工具条只负责发命令,弹窗统一由面板窗口呈现。 --- ## 构建 ### 环境要求 - Windows 10 1809+ / Windows 11 - [MinGW-w64](https://www.mingw-w64.org/) GCC 8+(自带 `g++` 与 `windres`) - 无需 CMake、无需 Visual Studio、无需 Node.js ### 编译 ```bat build.bat ``` 脚本会自动找 `g++`(先在 `PATH` 里找,再试常见安装路径);找不到时手动指定: ```bat set "MINGW=C:\mingw64\bin" build.bat ``` 产物是同目录下的 `TjuWidget.exe`(静态链接,可直接拷到别的机器运行)。 命令行等价于: ```bat windres --codepage=65001 -I. --use-temp-file res\app.rc -O coff -o build\app.res g++ -std=c++17 -O2 -municode -mwindows -fno-rtti ^ -DUNICODE -D_UNICODE -DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0A00 -DNOMINMAX ^ -Isrc -Ivendor -Ivendor/webview2/build/native/include ^ src\main.cpp src\util.cpp src\glass.cpp src\tray.cpp src\webview_host.cpp ^ build\app.res -o TjuWidget.exe -static -static-libgcc -static-libstdc++ ^ -Wl,--allow-multiple-definition ^ -lole32 -loleaut32 -luuid -lshlwapi -ladvapi32 -luser32 -lgdi32 -lshell32 -ldwmapi -lcomctl32 ``` > 改了 `web/` 下的 HTML/CSS/JS 之后**必须重新构建**:这些文件是编进 exe 的资源, > 运行时按「文件大小变化」判断是否需要重新释放到 `%LOCALAPPDATA%\TjuWidget\web`。 ### 项目结构 ``` ├── build.bat 一键构建(找 MinGW → windres → g++) ├── res/app.rc 资源脚本:web 资源 + WebView2Loader.dll 内嵌 ├── src/ │ ├── main.cpp 窗口、几何、托盘联动、消息分发、心跳 │ ├── webview_host.cpp/.h WebView2 环境共享、控制器创建、资源释放与虚拟域名 │ ├── tray.cpp/.h 托盘图标与右键菜单 │ ├── glass.cpp/.h 圆角/毛玻璃相关 Win32 封装 │ ├── util.cpp/.h 路径、原子写文件、自启动注册表 │ └── resources.h 资源 ID ├── web/ 界面(构建时内嵌进 exe) │ ├── index.html 工具条 + 面板 + 四个弹窗 │ ├── style.css 玻璃质感、布局、动画 │ ├── app.js 渲染、编辑、提醒调度、天气、协议桥 │ └── lunar.js 农历/节气/节日(第三方,见 THIRD-PARTY-NOTICES) ├── vendor/ json.hpp、WebView2 SDK 头文件与 Loader DLL └── docs/ 截图与示例数据 ``` ## 使用 1. 双击 `TjuWidget.exe`,工具条出现在屏幕顶部居中。 2. 点齿轮 → **学期设置**,选开学第一周的周一 + 总周数。 3. 点齿轮 → **上课时间**,选小学/中学/大学模板,再逐节微调。 4. 点齿轮 → **添加课表**(或直接点课表里的空白卡片位)录入课程。 5. 点齿轮 → **添加提醒**,给重要事宜单独设一条提醒。 6. 点工具条上的「课程表」展开/收起面板;托盘图标右键可以隐藏、切换置顶层级、退出。 截图里的示例数据见 [`docs/sample_data.json`](docs/sample_data.json)。 ## 数据存放 | 内容 | 路径 | | --- | --- | | 课表、作息、学期、提醒 | `%APPDATA%\TjuWidget\data.json`(原子写,不会写坏) | | WebView2 用户数据 | `%LOCALAPPDATA%\TjuWidget\` | | 释放的网页资源 / Loader DLL | `%LOCALAPPDATA%\TjuWidget\web`、`%LOCALAPPDATA%\TjuWidget\` | | 开机自启 | `HKCU\Software\Microsoft\Windows\CurrentVersion\Run\TjuWidget` | 数据是纯 JSON,可以直接备份/编辑;删掉 `data.json` 就回到初始状态。 ## 已知限制 - 仅 Windows(依赖 WebView2 运行时;Win11 自带,Win10 一般随 Edge 已安装)。 - 提醒依赖程序在运行;工具条被托盘隐藏时仍会响(宿主有 1 秒心跳拉回窗口)。 - 月历只读公历+农历,不做节假日调休同步(不联网获取校历)。 ## 贡献 欢迎 Issue / PR。提交前请确认: - `build.bat` 能在干净的 MinGW-w64 环境编译通过; - 改动 `web/` 后重新构建再截图; - 新引入的第三方代码请补进 `THIRD-PARTY-NOTICES.md`。 ## 许可 [MIT](LICENSE) © my-widget contributors 第三方组件许可见 [THIRD-PARTY-NOTICES.md](THIRD-PARTY-NOTICES.md)。 界面风格参考了若干同类桌面课表挂件的公开截图,仅作样式借鉴,代码为独立实现。