Repository navigation
v3.1.2
✨ 更新 / What's new
桌面点不动、鼠标能动但点不动
用户报的「偶尔手势操作会造成无法点击,关掉桌面窗口管理器才正常」和「鼠标能动但点不动」有两条独立的根因,这次一起治掉。
- 关掉系统的幽灵化。 本程序有一扇铺满整屏的置顶覆盖窗(手势笔迹)。UI 线程只要卡住超过 5 秒,DWM 就会在它上面盖一块点得中的幽灵窗——那块窗属于
dwm.exe,本程序既摘不掉也关不掉,用户唯一能试出来的解法就是杀dwm.exe。现在在进程级关掉这条路径(Win32.DisableWindowGhosting),它整个不存在了。 - 把三处会阻塞 UI 线程的活搬走。 抢前台(改用专属泵线程 + 超时;原先为了让
AttachThreadInput有泵可消化同步消息而切到 UI 线程,结果目标程序一忙就把 UI 线程按住,SetForegroundWindow不返回时更是永久挂住);端口扫描与杀进程(原先对每个占用者WaitForExit(2000),三个占用者就是 6 秒);以及配置与提醒快照的写盘重试。 - 钩子被摘掉不再静默。 Windows 的
LowLevelHooksTimeout(本机实测 300ms)一超时就摘钩子,不报错,句柄照样有效。现在把「钩子还活着吗」抽成纯函数Core.HookWatch,并装上三个成对的数:死寂时长、上游延迟(MSLLHOOKSTRUCT.time与TickCount相减)、回调耗时。三者合读才分得开「别人堵了链 / 我们自己超了时 / 钩子已被摘」——这三种病修法相反,而只报时长时它们长得一模一样。自愈那一刻还会采一份在场候选名单(HookSuspects),只证明在场、不证明作案。 - 卡顿与旧帧的仪表。 记录卡了多久,并带上 gc2 / 分配 / 距上次心跳 / 上游延迟 / 回调耗时 / 笔迹更新,可疑调用各自带名字。笔迹窗收笔改走「推迟隐藏」:先让清空后的画面合成出去再藏,治「画手势还出现上一次的轨迹」。
快捷面板
- 救活「从未弹出成功」的面板。 中键长按唤面板这条路拿不到前台时(那次按下被钩子吞了,系统不算本程序响应过输入),面板会挂成一块收不到键盘的浮窗,还占着槽位——于是下一次唤出变成了「关闭」,正是「长按时灵时不灵」的来源。补抢窗口从一次延长到约 2 秒;额度用尽后不再抢,但面板留在屏幕上、可见可点,下一次长按救活它,而不是替用户关掉一块他以为没出现的面板。
- 格子点击统计。 面板管理器里每个格子带着点击数(角标 + 悬停提示),页脚的**「清空点击统计」**只在真有数据时出现。计数认的是步骤自己的身份,所以改名、拖到另一页、甚至删掉再建一个同名的,都跟着走。它单独存在
clockwork.usage.json——点击发生的那一刻就得记下,而保存设置要重绑全部热键、重装鼠标钩子,一个计数器不该付那笔代价。清空会先问一句:那份历史是这个文件里唯一没有第二份的东西;它只动计数,不碰设置也不碰格子。 - 计数不会因为点击数改变面板显示什么:没有任何格子会被重排、隐藏或顶到前面。这是「你常按什么」的记录,不是「你该按什么」的建议。
语言资源
- 4 条新文案(点击统计的角标、清空按钮、它的悬停提示与确认框)已进全部 17 种语言,连同中性资源文件共 18 个
.resx一起补齐。
Desktop stops responding, or the mouse moves but nothing clicks
Two independent root causes behind "gestures sometimes make the desktop unclickable, and closing the Desktop Window Manager fixes it" and "the mouse moves but nothing clicks":
- System ghosting is off for this process. The app keeps a full-screen topmost overlay (the gesture trail). Once the UI thread stalls for over 5 seconds, DWM drops a clickable ghost window on top of it — that ghost belongs to
dwm.exe, which the app can neither remove nor close, and killingdwm.exewas the only workaround users could find. That path is now disabled at process level (Win32.DisableWindowGhosting) and simply does not exist. - Three blocking paths moved off the UI thread. Foreground acquisition now runs on a dedicated pump thread with a timeout — the old code switched to the UI thread so
AttachThreadInputhad a pump to drain synchronous messages, which meant a busy target app pinned the UI thread, and a non-returningSetForegroundWindowhung it forever. Port scanning and process killing (previouslyWaitForExit(2000)per occupant — three occupants, six seconds). And the settings / reminder-snapshot write retry. - Hook removal is no longer silent. Windows silently removes the hook once
LowLevelHooksTimeoutis exceeded (measured 300 ms locally) — no error, and the handle still looks valid. "Is the hook alive?" is now a pure function,Core.HookWatch, over three paired numbers: time silent, upstream latency (MSLLHOOKSTRUCT.timeminusTickCount), and callback time. Only read together do they separate "someone else blocked the chain" from "we timed out ourselves" from "the hook was already removed" — those three need opposite fixes, and by duration alone they look identical. A list of candidates present at that moment (HookSuspects) is captured too, proving presence rather than guilt. - Stall and stale-frame instrumentation. How long it stalled, plus gc2 / allocations / since-last-beat / upstream / callback / trail-update times, with a name on each suspect call. The trail window now hides after the cleared frame has composited, fixing "the previous gesture's trail shows while drawing a new one".
Quick panel
- Never-activated panels are revived instead of dismissed. When middle-button summon fails to take foreground (the press was swallowed by the hook, so Windows never counts the app as responding to input), the panel would hang around as a float that never receives keyboard, still holding its slot — so the next summon toggled it away. That is where "long press sometimes does nothing" came from. Retries now run for about 2 seconds; once the budget is spent the panel stays on screen, still visible and clickable, and the next long press revives it rather than closing a panel the user thought had never appeared.
- Per-tile click statistics. Each tile in the panel manager carries its count as a badge and in its tooltip, and Reset click stats in the footer appears only when there is something to clear. The tally is keyed on the step's own identity rather than on the slot, so renaming, moving to another page, or deleting and re-making a tile under the same name keeps the same count. It lives in
clockwork.usage.json, separate from your settings: a click has to be recorded on the click, and saving settings re-binds every hotkey and re-installs the mouse hook — a price a counter should not be paying. Reset asks first, because that history is the one thing in the file with no second copy; it touches the counts only. - Statistics never change what the panel shows: no tile is reordered, hidden or promoted by its count. This is a record of what you reach for, not a suggestion about what you should.
Locales
- Four new strings (the count badge, the reset button, its tooltip, and the confirmation) added to all 17 languages — 18 resource files counting the neutral one.
Which package? / 下载哪个?
Clockwork-v3.1.2-win-x64.zip— .NET runtime included; unzip and run on any Windows 10/11. Take this if you're unsure. / 内含 .NET 运行时,解压即用;拿不准、或电脑装不了东西就选它。Clockwork-v3.1.2-win-x64-needs-dotnet10.zip— same app without the bundled runtime; needs the .NET 10 Desktop Runtime installed. / 同一个程序,只是不带运行时;需要电脑上已装 .NET 10 桌面运行时。Clockwork.exe— the same build as the zip above, unzipped: click, run, or drop it over your existing Clockwork.exe to update. Windows will offer the runtime download if it is missing. / 就是上面那个不含运行时的版本,只是没打包:点开即用,也可直接覆盖现有的 Clockwork.exe 完成更新。缺运行时时 Windows 会自己提示下载。
Full Changelog: v3.1.1...v3.1.2