Skip to content

SnipForge 0.2.1

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 07 Nov 07:46
· 4 commits to main since this release

[0.2.1] - 2026-11-08

变更 · 开机自启改为静默启动(重新打包)

  • 注册表 Run 项写入 "…\snipforge.exe" --silent。登录 Windows 后只驻留托盘图标,
    不再弹主界面抢焦点;主窗口照常创建,热键、托盘、剪贴板监听全都正常,
    点托盘或按热键随时亮出来
  • 手动双击 exe 不带该参数,行为不变(照常显示主界面)
  • 老版本写入的自启项在下次启动时自动校正成带 --silent 的写法,不用手动重开开关
  • 静默启动时撞上已有实例直接安静退出,不弹"已经在运行"的对话框

新增 · 开发者工具箱(插件形式)

21 个本地工具,侧边栏「开发工具」进入,手势里也能挂一条。

  • 编码转换:Base64(URL-safe / 折行)、URL、Hex、HTML 实体、Unicode 转义、JWT 解析
  • 格式化 / 校验:JSON(带行列报错、键排序)、XML、SQL、正则测试(高亮 + 分组明细 + 替换预览)
  • 生成 / 计算:UUID、时间戳互转、哈希(MD5/SHA-1/256/384/512)、随机密码、进制转换、Cron 下次触发
  • 文本处理:命名风格(camel/snake/kebab…)、去重排序、行级 diff、行处理、字数统计
  • 工具目录写在 plugins/devkit.plugin.json,算法在前端注册表 src/lib/devkit/。
    清单一改,导航和工具列表跟着变;插件页可以整体开关这个工具箱
  • MD5 是手写的(Web Crypto 不提供),已和 Node 的 crypto 对过 8 组输入;
    密码用拒绝采样 + 保证每类字符至少一个;进制转换走 BigInt,超过 2^53 不丢精度

变更 · 手势引擎换 8 方向(破坏性)

  • 方向从 4 个扩到 8 个:上下左右 + 四个斜向。位移按 45° 扇区判定,
    一个方向走够 step 才记一个 token
  • 逗号分隔 token:D,R = 先下后右,DR = 一条右下斜线 —— 不分开的话
    这两种手势会撞成同一个串
  • 容错一个 token:转折处多判/少判一个方向是常态,要求逐字相等太脆。
    DR,UR 和 DR,U,UR 都能命中 V
  • 掉头要 1.8 倍距离:上↔下、左↔右这种正相反的方向,防转折点抖动
    切出多余 token
  • 删掉形状识别:点云匹配、模板阈值、@ 前缀、自定义笔画全部去掉。
    S / H 改用方向串写(S = DL,DR,DL,H = D,R,D),行为从"黑盒猜"
    变成"画什么就是什么"
  • 默认绑定全部重配(配置迁移 v5 会重置,旧绑定在新语义下意思全变了):
    四个主方向 + DR/DL 两条斜线 + 若干组合
  • 录制框改成「画一遍 → 后端翻译成方向串」,和真手势共用同一套判定

变更 · 默认手势定稿(8 方向)

按实际使用习惯重排,配置迁移 v6 会重置绑定:

手势 动作 手势 动作
↑ 复制 ↓ 粘贴
→ 前进 ← 后退
↓→ 关闭窗口 ↓← 关闭同程序其他窗口
↗ 最大化/还原 ↙ 最小化
↖ Esc ↘ Del
←↓→↓← 搜索选中文字 ↑→↓← Ping 选中地址
↓↑→↓ 编辑 hosts ↓→↑← 贴图
↓←↑→ OCR →↙→ 排除当前程序
↙↘↙ 截图

浏览器专用集:↑ 页顶 / ↓ 页底 / ↘↗ 刷新 / ↗↘ 强制刷新 / ↓→ 关标签 /
↑→ 右侧标签 / ↑← 左侧标签 / ↓→↓ 主页。

新增三个动作:

  • 关闭同程序其他窗口:枚举同进程、可见、有标题的顶层窗口发 WM_CLOSE,
    当前窗口留着。没有标题的多是隐藏辅助窗口,关掉会伤到正在运行的程序
  • 搜索选中文字:先 Ctrl+C,180ms 后读剪贴板,开 Bing
  • Ping 选中地址:Ctrl+C 后只保留域名/IP/端口的合法字符,
    直接 spawn ping —— 不经过任何 shell,没有命令注入面

修复

  • 右键点一下菜单不弹(被"吞"了)。判定的顺序错了:先看方向串再看位移。
    手抖 40 多像素时 path_to_dir 已经能切出一个 token(R/D/…),而单 token
    手势是精确匹配的 —— 于是"点一下"变成执行动作,菜单自然不出来。
    现在位移 < 48px 一律按单击补发,不看向量算出了什么
  • 补发点击加了诊断日志(光标位置 + 光标下的窗口类名 + SendInput 的返回值和
    错误码)。以后"补发没生效"能一眼看出是没发出去,还是发到了别的窗口

审计(第二轮)

  • 方向解析(dir8 / RDP / 折线简化)搬进 snipforge_core::stroke,
    补 8 条单测。这块以前一条测试都没有 —— 而"画 3 段出 5 个 token"
    正是这里出过的 bug。现在覆盖:8 个扇区、22.5° 边界、直线单 token、
    直角两 token、↙↘↙ 三段、锯齿抖动仍是一段、相邻同向合并、过短轨迹不出 token
  • step 字段已废弃:RDP 之后"几段就是几个 token",本来按"走够多少像素
    算一次转向"的 step 不再参与任何判定。从 Snapshot 和 on_move 里删掉,
    配置字段保留但注明废弃(删了会让旧配置文件丢字段)
  • gesture_trace 去掉没用的 step 参数(前后端一起改)
  • 文档对齐现状:docs/FEATURES.md 还在写"画一个 S 截图""@S/@H
    形状命名空间""接管右键按下开关""$P 点云匹配"—— 这些都是已经删掉的东西。
    整章按 8 方向重写,含默认手势表和三条匹配规则;README.md 的
    "画一个 S 就截图"一并改掉

审计(手势链路)

一轮完整代码审计,按"会不会在钩子线程里出事"这条线逐层过:

  • handle_event 每个 mousemove 都拿配置锁 + clone() 一个 String。
    这个函数就在钩子线程里同步跑,等于持续分配内存。改为配置变更时写进
    AtomicBool + RwLock<String>,热路径只做一次原子读

  • on_move 在轨迹点数封顶后每个事件都做全量 RDP。pts 到 1500 后长度
    不再变,1500 % 6 == 0 让重算条件恒成立 —— 越划越卡。改成只有真的压入
    新点时才重算

  • match_action 每次方向变化都整体 clone 专用手势集(连所有绑定的轨迹点)。
    改成按引用索引

  • inject_click 在钩子回调里调 SendInput。整理成整对发送都在工作线程
    里做,回调只负责"决定要补点一下"

  • "关闭同程序其他窗口"会把自己也关掉:枚举时没有排除当前窗口,
    说是"其他窗口",实际连用户正在用的那个一起 WM_CLOSE

  • path_to_dir 里有两处 unwrap()(包围盒)。虽然前面对点数有保护,
    但这段跑在钩子线程,panic 会连带整个输入链。改成单次遍历算包围盒

  • on_move 的兜底之外,on_down 也会处理"上一次抬起丢了"的残留状态,
    先把旧轨迹收掉再开新的

  • 修正 gesture.rs 顶部过时的模块文档(还在描述已删除的点云形状识别、
    四个方向)和 window.rs 里一段粘错位置的注释

  • 在浏览器里画 ↓↑→↓ 打开的是主页,而不是编辑 hosts。原因是匹配顺序:
    先查浏览器专用集,里面 ↓→↓(主页)只要"差一个 token"就算命中,
    于是把通用集里精确命中的 ↓↑→↓ 挡住了。现在改成:

    • 先比距离,精确匹配永远赢过模糊匹配;距离相同才让专用集优先
      (专用集的意义是"在这个程序里覆盖",不是"随便糊一个")
    • 容错按长度收紧:两段以内只认精确(↓→ 是关窗口,↓→→ 不该命中它);
      三段以上才允许差一个
    • 只容忍"多画",不容忍"少画":转折处手抖多切一个 token 可以,
      但 3 段手势不会去命中 4 段绑定 —— 否则画 ↓→↓ 会误触 ↓↑→↓
  • 匹配规则搬到 snipforge_core::pattern。主 crate 的测试 harness 在本机
    起不来(STATUS_ENTRYPOINT_NOT_FOUND),所以 gesture.rs 里的测试从来没跑过,
    其中两条断言一直是错的(normalize("D-X,R") 和一处编辑距离)。搬进 core 后
    36 项单测全绿,这两条也一并修正

  • 吞一半按下/抬起会让系统按键状态卡死(这是这一轮所有怪现象的根)。
    鼠标钩子吞掉一个事件,系统那颗键的状态就不会跟着变 —— 实测:放行
    RButtondown、吞掉 RButtonup 之后,GetAsyncKeyState(VK_RBUTTON)
    会一直返回 DOWN。后果是目标窗口一直以为右键按着:捕获不放、
    菜单卡住、左键跟着失灵,得再点一次右键才恢复。
    规则:按下和抬起必须成对处理,要么都放行,要么都吞掉

  • 桌面右键菜单跳两下。整对吞掉后必须补发一次点击,而补发时按下和抬起
    之间的间隔太短(35ms),桌面/任务栏的菜单还没建完,合成的抬起落在了底下
    的桌面上,于是又开一次菜单。间隔改成 100ms(接近真人点击),抬起就能落到
    已经建好的菜单窗口上

  • 松手后手势不结束、轨迹一直跟着鼠标。根因是同时跑了两个实例:
    两套钩子互相截事件,一边拿了按下、另一边拿了抬起,前者状态永远不清。
    启动时用会话内命名互斥体挡掉第二个实例(弹提示后退出);另外加 20 秒
    超时兜底,任何情况下都不会再"永久跟随"

  • 删掉 swallowDown 开关(配置项和界面都去掉):两种模式只有一种是对的,
    留个开关只会让人选到错的那个

  • 手势只认 S 和 H,其余字母不再内置识别。一笔画的折线本来就是方向串
    (↓→ 既是「向下再向右」,又恰好是字母 L 的写法),全字母参与识别时
    每次「下再右」都被判成画字母。更要紧的是 RLR(画 Z 形)是「排除当前
    程序」的默认绑定,会被 @Z 吃掉,排除功能直接失效。
    现在只有 S / H 参与内置识别;其他字母在「鼠标手势」页画一遍存成自定义
    手势即可 —— 只认你加的那一个,不干扰任何方向手势

  • 画 ↓→ 时浮标报「未绑定」,但松手却执行了动作。原因:↓→ 的形状正好
    等于字母 L 的模板(竖下 + 横右),被形状识别抢成 @L。执行层有回落
    (@L 没绑就按方向串 DR 找),浮标层却只查 @L —— 显示和执行用了两套规则。
    现在两处共用同一套回落,浮标显示的永远是真正会执行的那个动作