Mac 滑鼠滾輪一格一格地跳
macOS 把滑鼠滾輪當作一串離散的「格」交給應用程式,而觸控板拿到的是連續手勢。要讓滾輪變順暢,就得把這些格攔截下來、逐幀重新投遞——三個真實的坑就藏在這裡。
macOS 拿到滾輪後到底做了什麼
觸控板上報的是連續手勢——系統收到一串位置,並且知道手指還在動。滑鼠滾輪上報的是離散的「格」:一格就是一個事件,附帶一個行數,macOS 把它交給應用程式,然後就不管了。
所以滾輪「一格一格地跳」,不是哪裡壞了,而是它本來就被這樣要求:一格,一跳。會平滑滾輪的應用程式是自己做的;大多不做。
真正有效的機制
三步,缺一不可:
- 在應用程式看到之前攔截。用
CGEventTap先拿到滾輪事件,這樣才能把它吞掉並替換。 - 累積,而不是一次性消費。引擎保留這一格還沒用掉的餘量,逐幀一點點送出去。
- 跟著顯示器的時鐘送。用
CVDisplayLink與面板更新率同步,增量就跟着幀到達,而不是成串地衝出去。
這正是開源專案 MOS 普及開來的架構,也是正確的做法——我們的滾動引擎是照它實作的,而不是另發明一個更差的滾輪。
坑一——並不是每一格都意味著「滾動」
這一條最咬人。在「照片」「地圖」「Keynote」「Numbers」以及大多數試算表裡,一格滾輪根本不是滾動,而是縮放的一步或翻一頁。
一旦平滑化,這一格會在你鬆手很久之後還在到達:照片從 80% 跳到 200%,簡報一次翻過十幾頁。使用者於是用「按應用程式加白名單」來繞開——而這恰恰是這類功能本該替人省掉的手工活。
所以在改造一個事件之前,引擎先問一個問題:這是滾動還是縮放?縮放語意的事件原樣放行,不做任何加工。對滾輪本來就是「按行精確」的應用程式同理——再加工只會更差,不會更好。
坑二——悄悄變成殭屍的顯示時鐘
CVDisplayLink 並不總能活過一夜。顯示器喚醒之後,或者發生重新配置(換線、過 KVM、改解析度),它可能停止投遞回呼,卻仍然聲稱自己在執行。
如果沒人檢查,外面看到的現象就是滾動突然不動畫了——以及「這應用程式莫名其妙壞了」。修法並不漂亮:假定這個時鐘會死,偵測它,然後重建它。
坑三——兩個工具搶同一個滾輪
同一層上的兩個事件監聽器都會消費滾輪。每個引擎都把對方吞掉的事件當成「輸入丟了」,又把自己重新發出的事件當成「新輸入」。兩個插值器於是互相追,結果就是滾動卡頓、翻倍,或者干脆停擺。
使用者很自然地得出結論:新裝的那個工具搞壞了我的滾動。把衝突說出來(「偵測到另一個滾動工具在執行」)遠比讓人盲目排查有用。
如果現在滾動就很難受,先查這幾條
- 是不是有第二個平滑工具在跑?兩個同時跑比一個都不跑更糟。
- 那個應用程式用的是不是按行精確的增量?對它們再加工只會白白增加延遲。
- 是不是睡眠、喚醒或換線之後才開始的?那指向顯示時鐘,不指向應用程式。
- 是某一個應用程式,還是所有應用程式?只有一個通常是語意不匹配(坑一);所有應用程式都這樣,問題在監聽器本身。
誠實的邊界
平滑是改造輸入,不是創造輸入。它天然會帶來一點延遲,也無法讓一個本來就忽略精確增量的應用程式變好。當滾輪本來就已經正常時,正確答案是別去動它——這也正是上面那些語意與「按行精確」判斷存在的原因。
相關
DDC/CI 無回應 · 休眠後亮度被重置 · 文字發虛 · 色彩發灰 · 該用哪個工具?