前两天手里转着一支笔,屏幕上弹了个确认框,我顺手拿笔尾往 MacBook 的触控板上按了一下。
没按动,也没声音。
换回手指按了一下,「咔哒」一声,很清脆,手指肚也能感觉到面板往下陷了一点。再用笔按,还是死硬一块,半点动静都没有。
如果你手边有一台 2015 年之后的 MacBook,现在就可以拿支笔试一下——或者把电脑关机再去按它。
你会发现这块触控板根本按不下去。
更让人意外的是,这块看起来跟机身铝合金颜色一模一样、摸起来冰凉顺滑的触控板,表面其实是一层 0.5 毫米厚的磨砂强化玻璃。金属会屏蔽电容信号,要是真做成铝合金,底下的电容传感器就彻底瞎了。所以苹果只能在玻璃背面喷上和机身同色的涂层,伪装成金属的样子。
从 2015 年苹果推出 Force Touch 开始,老式触控板底下的弹簧和跷跷板铰链就被拆掉了,只靠四角四片极薄的金属悬臂把这块玻璃托在机身里,垂直方向几乎没有下陷空间。
平时你用手指按下去听到的那声「咔哒」,还有按到底的手感,都是机器模拟出来的。
我一开始以为声音是从扬声器里放出来的录音。后来把电脑调成静音,又插上耳机试了一次,触控板底下还是能传出「咔哒」声。去查了苹果的官方结构图才知道,发声的是一颗藏在触控板正下方的线性电磁马达,苹果叫它 Taptic Engine。

但这又引出另一个问题。
触控板四个角底下垫着四个应变片压力传感器,用来测量你施加了多大力气。按理说拿笔戳也会有压力,为什么手指按就响,拿笔按就没反应?
因为苹果在芯片里设了一道门槛。
如果只要有压力就触发点击,把电脑合上塞进书包,或者往触控板上放一本书,它在包里就会一直误触。所以工程师规定:必须先由玻璃表面的电容传感器检测到人体皮肤,确认按上来的是手指,底下的压力传感器才会开始工作。
塑料笔壳不导电,过不了电容传感器这一关。不管你用多大力气去按,触控板都当无事发生。
搞明白这点之后,我试了一下。先把左手小拇指搭在触控板边角上,不用力,只靠皮肤把电容传感器激活,右手再拿刚才那支圆珠笔去按触控板中间。
「咔哒」。
那支笔立马就能按响了。要是换成火腿肠或者剥了皮的橘子,连手都不用搭,直接就能按响。
声音的来源搞清楚了,可手感是怎么回事?
就算已经知道这块玻璃板是固定的,闭上眼睛用手指去按,还是觉得它实实在在地往下陷了一毫米。
按理说,想让人感觉面板往下陷,底下的马达应该上下震动才对。但笔记本机身太薄,垂直方向根本塞不下,所以苹果把这颗电磁马达做成了水平左右运动的结构。

当手指往下压、压力超过阈值的那一毫秒,电磁线圈会推着里面的金属块,在水平方向撞一下底座。这一下撞击干了两件事:
- 第一,震动玻璃面板,向空气发出一道很短的声波,也就是你听到的「咔哒」声。
- 第二,玻璃表面在几毫秒内横向蹭了你的指尖一下。
人手指尖负责感知高频震动的感受器叫环层小体(Pacinian corpuscles)。这个器官有个盲区:它能敏锐地察觉到皮肤发生了形变,但分不清这股力是垂直顶上来的,还是横向搓过去的。
你的手指正在往下用力,指尖收到一次横向震动,耳朵也跟着听到一声「咔哒」。大脑把这三个信号拼在一起,自动得出了一个结论:我把按钮按下去了。
既然整个过程都是软件算好之后发指令让电磁铁敲出来的,我就想试一件事。
能不能手不碰触控板,直接写段程序去调那个电磁铁,让触控板自己在那儿响?
翻了苹果的官方开发文档,公开的 AppKit 里确实有个叫 NSHapticFeedbackManager 的接口,但苹果做了限制:只有当手指真的放在触控板上滑动时,代码发出的震动指令才会生效。手一离开,调用就失效。
公开接口走不通,只能去系统底层找私有框架。
在 macOS 的 /System/Library/PrivateFrameworks 目录下,有个未公开的底层库叫 MultitouchSupport.framework。用工具把它的导出符号表拉出来看,能找到两个 C 语言函数:_MTDeviceGetMTActuator 和 _MTActuatorActuate。Actuator 就是触控板底下的那颗电磁马达。
我用 Python 的 ctypes 写了十几行代码,直接加载这个私有库,拿到默认触控板的马达句柄,往 _MTActuatorActuate 里传入动作编号 6(代表按下),隔 60 毫秒再传一个 3(代表松开回弹),外面套个循环跑五次:
python3 - << 'EOF'
import ctypes, time
mt = ctypes.CDLL('/System/Library/PrivateFrameworks/MultitouchSupport.framework/MultitouchSupport')
cf = ctypes.CDLL('/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation')
mt.MTDeviceCreateList.restype = ctypes.c_void_p
cf.CFArrayGetValueAtIndex.restype = ctypes.c_void_p
mt.MTDeviceGetMTActuator.restype = ctypes.c_void_p
dev = ctypes.c_void_p(cf.CFArrayGetValueAtIndex(ctypes.c_void_p(mt.MTDeviceCreateList()), 0))
act = ctypes.c_void_p(mt.MTDeviceGetMTActuator(dev))
mt.MTActuatorOpen(act)
for _ in range(5):
mt.MTActuatorActuate(act, 6, 0, ctypes.c_float(0), ctypes.c_float(0))
time.sleep(0.06)
mt.MTActuatorActuate(act, 3, 0, ctypes.c_float(0), ctypes.c_float(0))
time.sleep(0.4)
mt.MTActuatorClose(act)
EOF
手完全离开电脑,在终端里敲下回车。
桌上的触控板自己「咔哒、咔哒、咔哒」连响了五下,跟人手按下去的声音一模一样。 如果你现在用的就是 Mac,把这段代码粘进终端跑一下就能听到。
顺着这个私有库的名字去 GitHub 上搜,我发现早就有人把这套硬件玩出了别的用处。
一个叫 Kris Shah 的开发者写了个开源项目叫 TrackWeight。他用同一个私有库反过来读取触控板底下那四个压力传感器的原始数据,把 MacBook 变成了一台精确到克的电子秤。
唯一需要注意的是,往触控板上放苹果或者咖啡豆称重的时候,你得把一根手指搭在触控板边角上——不然过不了电容传感器那一关,电子秤不读数。
这类底层硬件玩法在云栈社区的开发者讨论里也偶尔有人分享,感兴趣的可以留意一下。