一个用 C++23 编写的跨平台 GUI 下载器:EUI-NEO 前端 + aria2-next 外部进程引擎(分片多连接、断点续传、磁力/BT),支持 Windows / Linux / macOS, 全部通过 mcpp 包管理。
mcpp build # 编译
mcpp run # 启动 GUI 窗口下载的文件默认保存在你的系统下载目录(可在设置页修改保存路径)。
- 单实例:同一用户同一会话只运行一个 TinyNext。重复启动不会开第二个窗口, 而是把命令行参数里的下载链接转发给已运行实例(Windows 上还会把已有窗口 切到前台),由它自动添加任务。
- CLI:
tinynext <https://...>启动即添加下载;如果应用未运行,会自动 打开应用并把链接加入下载列表。可一次传多个 URL。详细用法见docs/cli.md。 - 转发走临时目录的
tinynext.inbox文件,主实例每 ~0.5s 轮询取走任务。 - 给 AI 助手的项目指南见
AGENTS.md(含构建 / CLI 用法 / 模块约定)。
岛屿卡片风布局:内容区 / 状态筛选侧边栏是浮在背景上的圆角"岛"卡(drawPanel 统一
样式:中间色调 + 细边框 + 柔和投影),顶部贴齐窗口顶(为后续自定义顶部栏预留),左缘是
透明的总侧边栏(纯图标、不铺底色),只有卡片右缘留少量间距。
- 左侧图标栏(总侧边栏):整高透明列,纯图标导航(下载列表 / 设置),底部按钮切换 深浅主题;左上角是应用 logo(项目名缩写)。
- 下载状态子侧边栏:下载页内容区左侧的 所有 / 下载中 / 已完成 筛选(独立岛卡)。
- 内容大卡:下载页的工具栏 + 任务列表 + 翻页收在同一张卡片里。
- 任务卡片:每个任务一张卡片,纵向排布文件名+状态标签、进度条、信息(百分比/速度/大小)+ 操作图标。
- 添加下载弹窗:右上角 ➕ 打开;URL 输入框多行(长链接完整可见),打开时 自动读取剪贴板(若是 http(s)/magnet 链接则预填)。弹窗里可设置分片数(每任务, 默认填配置值)、优先级(默认/高/中/低,独立一行)、重命名、限速、 下载目录。
- 顶部工具栏(内容大卡右上):全部暂停 / 全部继续、排序(最新在前 / 状态优先 / 文件名 / 大小 / 进度)、➕ 添加下载。
- 翻页:◀ 页码 ▶ [数字/页],整组收在一张小卡片里;中间只显示当前页码,分页大小是 无边框的"数字/页"文本(带小箭头,可点开选择 5/10/20/50/100)。
EUI-NEO 没有全局缩放开关(components::button 自带的 .scale() 只作用于组件
按钮),因此 TinyNext 在 src/app.cpp 里用一个统一系数 kUI(默认 1.4f)+
辅助函数 S(x) = x * kUI,把所有尺寸 / 字号 / 间距和窗口尺寸整体放大。想整体
改大改小,只调 kUI 一个数即可。布局在 EUI 的逻辑像素空间(= 窗口屏幕像素),
所以窗口与内容必须一起放大,高 DPI 屏上整体才会真正变大(本机 2560×1600
@150% 下,1.4 倍后窗口约 1568×1008)。
- 点击右上角 ➕ 打开「添加下载」弹窗,粘贴 HTTP(S) 链接或 magnet: 磁力链接,点「提交」或按回车开始。
- 弹窗里可为该任务设置:连接数(打开时自动填配置的默认值,可改)、优先级(默认/高/中/低,排队调度用)、重命名(留空=URL 文件名)、限速KB/s(0=全局)、下载目录(打开时自动填配置的默认目录,可改;磁力链接建议确认,因为种子内容名不由 URL 决定)。
- 卡片操作全部用图标,无文字:
- 复制链接、删除:所有任务都有;
- 下载中:暂停 / 取消;已暂停:继续 / 取消;
- 失败 / 已取消:重新下载(aria2 从
.aria2控制文件断点续传); - 已完成:打开 / 打开所在文件夹。
- 同名文件自动加
(1)、(2)后缀,不会互相覆盖。
暂停 / 继续走 aria2 RPC(aria2.pause / aria2.unpause):是真正的中断,
不占连接,可随时继续,进度不倒退。
- 重新下载:失败 / 已取消的卡片 ↻ 按钮用原 URL + 原路径重新入队,aria2 从同
目录的
.aria2控制文件续传(真正的断点续传)。 - 重启恢复:aria2 daemon 启动带
--save-session/--input-file,退出时先aria2.saveSession持久化未完成任务;下次启动自动载入并续传,任务列表由tellActive/tellWaiting/tellStopped枚举重建。 - 设置页「完成后移除控制文件」开启后,下载完成即删
.aria2;未完成(含取消)则 保留,供重新下载续传。
- 主题:跟随系统 / 深色 / 浅色(跟随系统时 ~2s 轮询 OS 主题,自动切换)。
- 下载路径:默认系统下载目录(Windows
%USERPROFILE%\Downloads/ macOS$HOME/Downloads/ LinuxXDG_DOWNLOAD_DIR),可「浏览」用系统选择器或手输。 - aria2 参数(仅 aria2-next):分片数、每服务器连接(默认 64,上限 64)、 最小分片(≥1M)、每任务限速(KB/s,0=不限)、最大同时下载数(队列并发上限, 默认 5,范围 1~64)、代理地址(HTTP/HTTPS,aria2 不支持 SOCKS5)、不使用 代理列表、失败重试次数 / 重试等待秒、完成后移除控制文件、完成后命令、 User-Agent / Referer / 磁盘缓存。设置项过多,设置页正文可滚动。 新下载立即生效;daemon 级参数在 aria2 daemon 已启动时需重启才生效。
- 所有设置点「保存」落盘到
tinynext.conf(JSON),「放弃」回滚;左侧栏底部 ⓘ 打开「关于」弹窗(含项目 GitHub 链接)。
UI 只面向抽象 dl::DownloadEngine 接口(src/download_engine.cppm),唯一实现是
dl::Aria2Engine(TinyHttpsEngine 已移除):
- spawn
engines/aria2-next守护进程,JSON-RPC 驱动,-x 64 -s 64分片多连接。 - 断点续传(
.aria2控制文件)、磁力/BT、重试、限速、代理等能力来自 aria2 本身。 - 本地 JSON-RPC 用自写的极简跨平台 socket(
aria2_engine.cpp里的LocalSocket), 无外部 HTTP/网络依赖。
三平台都需把对应的 aria2-next 二进制放进 engines/(已 gitignore,checksums.sha256 保留):
| 平台 | release 资产(v2.5.5) | 放置为 |
|---|---|---|
| Windows x64 | aria2-next-2.5.5-windows-x86_64.exe |
engines/aria2-next.exe |
| Linux x64 | aria2-next-2.5.5-linux-x86_64 |
engines/aria2-next |
| macOS (Apple Silicon) | aria2-next-2.5.5-macos-arm64 |
engines/aria2-next |
| macOS (Intel) | aria2-next-2.5.5-macos-x86_64 |
engines/aria2-next |
下载页:https://github.com/AnInsomniacy/aria2-next/releases
Windows 发行打包用 .\make-dist.ps1:它自动把 engines/ 里的 aria2 二进制和
checksums.sha256 一起打进 dist\ 与 tinynext-v<版本>-win64.zip(版本号从
mcpp.toml 读取)。aria2 是唯一下载引擎,engines/ 缺失时脚本会警告但继续
打包(运行时下载不可用)。
平台验证步骤:
- 各平台
mcpp build。Windows 自动加 GUI 子系统标志;Linux 用run.sh启动 (规避 mcpp 私有 glibc 与系统 Mesa 的 GLIBC 版本冲突);macOS 直接mcpp run。 - 添加一个大文件(≥128MB 才能用满 64 连接)。
- 文件夹选择器依赖:Linux 需
zenity(无则回退kdialog,都没有则手输路径); macOS 用osascript;Windows 系统自带。 - 主题跟随系统:Windows 读注册表、macOS 读
AppleInterfaceStyle、Linux 读 gtksettings.ini(均 best-effort)。
CI 工作流 .github/workflows/release.yml 在推送 v* 标签时自动在 Windows /
Linux / macOS 三平台构建并创建 Release(也可 workflow_dispatch 手动触发——只
构建并上传 artifacts、不建 Release,便于先修跨平台编译错误)。
流程:
- 把工作流推到仓库后,先用
workflow_dispatch跑一遍,按失败作业逐一修复 Linux / macOS 的编译问题(这两个平台是首次在 CI 编译 POSIX 分支)。 - 三平台都绿后打标签并推送:
git tag v0.1.0 && git push origin v0.1.0 - 到仓库 Releases 页把自动生成的 draft release 补充说明后发布。
产物(aria2-next 二进制在 CI 上按 engines/checksums.sha256 校验后随包附上):
| 平台 | 产物 | 打包脚本 |
|---|---|---|
| Windows x64 | tinynext-v*-win64.zip |
make-dist.ps1 |
| Linux x64 | tinynext-v*-linux-x86_64.tar.gz |
make-dist.sh linux x86_64 |
| macOS Apple Silicon | tinynext-v*-macos-arm64.tar.gz |
make-dist.sh macos arm64 |
(macOS Intel 暂不参与 CI 构建——官方 mcpp install.sh 只提供 macosx-arm64
二进制;等 Intel 的 mcpp 二进制或 macOS 上 subos 安装验证后再加 macos-13
runner。)
Linux 包内含 run.sh 启动脚本(走系统 loader + 系统 Mesa,原理见仓库根
run.sh),目标机器需 glibc ≥ 2.39 且有桌面 GLX。
| 组件 | 包 | 版本 |
|---|---|---|
| 工具链 | LLVM/Clang(mcpp.toml 的 [toolchain] 固定) |
22.1.8 |
| UI 框架 | compat:eui-neo |
0.5.3(feature: app-main;0.5.5 与 C++23 构建不兼容,见 roadmap) |
| 下载引擎 | aria2-next(外部进程) |
2.5.5 |
| 配置 JSON | nlohmann:json |
3.12.0 |
全模块化(import std + 各 tinynext.* 模块),按职责拆成多个模块:
| 模块 | 文件 | 职责 |
|---|---|---|
tinynext.download_engine |
src/download_engine.cppm |
引擎抽象接口 dl::DownloadEngine / TaskView |
tinynext.aria2_engine |
src/aria2_engine.cppm/.cpp |
aria2-next 进程引擎(JSON-RPC + 本地 socket) |
tinynext.config |
src/config.cppm |
JSON 配置 / 主题 / 下载目录 / aria2 参数 |
tinynext.cli |
src/cli.cppm |
单实例锁 + 命令行 URL + inbox 转发 + CliBoot 引导 |
tinynext.ui.utils |
src/ui/utils.cppm |
kUI/S() 缩放 + 格式化/解析辅助 |
tinynext.ui.theme |
src/ui/theme.cppm |
AppTheme 深浅主题 + currentTheme() |
tinynext.ui.state |
src/ui/state.cppm |
共享可变全局 + 引擎 + 添加下载流程 |
tinynext.ui.platform |
src/ui/platform.cppm |
DPI boot + 文件夹选择 + 打开文件/URL |
tinynext.ui.widgets |
src/ui/widgets.cppm |
列表选择器 + 侧栏/rail/卡片操作控件 + drawPanel 岛卡 |
tinynext.ui.cards |
src/ui/cards.cppm |
下载任务卡片 |
tinynext.ui.downloads_page |
src/ui/downloads_page.cppm |
下载页 + 添加下载弹窗 |
tinynext.ui.settings_page |
src/ui/settings_page.cppm |
设置页 |
tinynext.ui.about_dialog |
src/ui/about_dialog.cppm |
关于弹窗 |
src/app.cpp |
—(普通 TU) | 薄入口:app::dslAppConfig() + app::compose() 分发 |
页面按职责拆成独立模块(原 tinynext.ui.pages / pages.cppm 已删除)。
src/app.cpp— EUI 应用入口。启用app-main特性后,main()由包内的 GLFW 入口(core/app/glfw_app_main.cpp)提供,本项目只定义app::dslAppConfig()和app::compose()(因此任何 TU 都不能再定义main())。- EUI-NEO 是 header-only C++17 库(无模块接口)。
src/app.cpp包含完整<eui_neo.h>(提供dsl_app_impl.h里的app::update/render等机制实现); 各 UI 模块只包含精简头src/ui/eui_ui.h(去掉dsl_app_impl.h)——该头内联 lambda 若同时出现在普通 TU 和模块全局片段会 mangled name 冲突。 assets/— EUI 默认中文字体(JingNanJunJunTi)+ 图标字体,运行时按exeDir/assets/或assets/相对路径解析。
- mcpp 不会自动生成 bin 目标:
mcpp只在存在src/main.cpp时才推断 可执行目标。启用app-main后 main 在依赖包里,必须显式声明[targets.tinynext] kind = "bin"(见mcpp.toml)。 app-main与测试互斥:该特性会把glfw_app_main.o急切地链入, 与任何定义main()的测试 TU 冲突(multiple definition of 'main')。 本项目因此删除了tests/。- Winsock 必须手动初始化:Windows 上本地 JSON-RPC socket 需要
WSAStartup。aria2_engine.cpp里自写的LocalSocket::platformInit()(POSIX 是 no-op)在Aria2Engine构造 / 析构里负责WSAStartup/WSACleanup,不要漏。 import std;后禁止再#include标准头:被import std;的 TU 包含的 头文件内不能#include <mutex>等标准头,否则 "redefinition of 'defer_lock'" 报错(std 模块已声明这些实体)。- RPC 仅限本机:aria2 daemon 用
--rpc-listen-all=false只监听 127.0.0.1,--rpc-secret随机生成,本地 RPC 不会被外部访问。 - 双击不弹终端:Windows 默认把 exe 链接成控制台子系统,双击会附带一个
黑窗口。已在
mcpp.toml的[target.'cfg(windows)'.build]里加了-Wl,-subsystem:windows+-Wl,-entry:mainCRTStartup(GUI 子系统的默认 入口是 WinMainCRTStartup,而入口代码是main(),必须显式指回 mainCRTStartup),现在直接启动 GUI 窗口、无控制台。 - EUI-NEO 无全局缩放开关:只有自动 DPI 感知(
highDpi=true,逻辑坐标 = 窗口屏幕像素,渲染按dpiScale换算保证高 DPI 清晰)和组件 button 的.scale()(只作用于单个按钮)。整体放大 UI 需要自己引入系数(见上文 「UI 缩放」),并同时放大窗口尺寸,否则高 DPI 屏上控件仍然偏小。 - eui 元素 id 必须全局唯一:同一个 frame 里同名 id 会互相覆盖——例如
components::text的标签 id 若写成add.priority.label,会和buildListPicker(id="add.priority")内部的字段标签 id 撞名,导致文字不显示。 新增控件 id 要避开已有前缀。 - Windows 打开文件/文件夹不要用
std::system("explorer …"):explorer从命令行 启动会让调用进程同步等 Explorer 窗口关闭,UI 线程卡死。统一走ShellExecuteW(platform.cppm::shellExecFn(),立即返回)。 - xlings 解压含 symlink 的 tarball 会在 Windows 上中途失败:下载包里若有符号链接
(如 IXWebSocket 的
Dockerfile→docker/Dockerfile.alpine),解压器解到该条目即 中止,源码目录缺失导致 mcpp 报install_packages failed。下载/校验本身没问题; 需要手工用系统 tar 完整解压(跳过 symlink)补装 verdir,见仓库根 CLAUDE.md。
本项目源码采用 MIT 协议(见 LICENSE)。
注意:下载引擎 aria2-next(engines/ 下的二进制,GPLv2)是随发行包
单独分发的第三方程序,不改变本项目 MIT 许可的状态;其自身仍受 GPLv2 约束。