从亮度快捷键修复说起
在 OC-little 中有 ThinkPad 现成的亮度快捷键修复补丁,本质上是把 ThinkPad 的 Fn + F5 和 Fn + F6 映射到 F14 和 F15 上,而 F14 和 F15 是 macOS 中「系统偏好设置」中亮度调节的默认快捷键。
为什么这么说呢?让我们先来看一下 SSDT 补丁是怎么写的:
DefinitionBlock("", "SSDT", 2, "OCLT", "BrightFN", 0)
{
External(_SB.PCI0.LPCB.KBD, DeviceObj)
External(_SB.PCI0.LPCB.EC, DeviceObj)
External(_SB.PCI0.LPCB.EC.XQ14, MethodObj)
External(_SB.PCI0.LPCB.EC.XQ15, MethodObj)
Scope (_SB.PCI0.LPCB.EC)
{
Method (_Q14, 0, NotSerialized)//up
{
If (_OSI ("Darwin"))
{
Notify(\_SB.PCI0.LPCB.KBD, 0x0406)
}
Else
{
\_SB.PCI0.LPCB.EC.XQ14()
}
}
Method (_Q15, 0, NotSerialized)//down
{
If (_OSI ("Darwin"))
{
Notify(\_SB.PCI0.LPCB.KBD, 0x0405)
}
Else
{
\_SB.PCI0.LPCB.EC.XQ15()
}
}
}
}
把上述 SSDT 翻译成伪编程语言(人话)。为 _SB.PCI0.LPCB.EC 总线下的设备定义函数 _Q14:如果当前操作系统是 macOS(Darwin),则向 \_SB.PCI0.LPCB.KBD 设备发送 0x0406 信息;否则,就执行函数 XQ14()。函数 _Q15 同理。
需要注意的是,使用这个亮度补丁的前提是 DSDT 重命名、将 _Q14 重命名为 XQ14。也就是说在原始 DSDT 中 _Q14(也就是 Fn + F5)函数将会被重命名为 XQ14,只有在非 macOS 操作系统下才会被调用;而在 macOS 中将不会执行 XQ14(也就是原始的 _Q14)函数,而是向 \_SB.PCI0.LPCB.KBD(也就是键盘)发送 0x0406 和 0x10。
这个 0x0406 其实就是 F15 的扫描码,这个之后再说。只从上述 SSDT 中我们可以得出什么结论呢?
- Fn + F5 和 Fn + F6 对应的是 _SB.PCI0.LPCB.EC 总线下的两个函数,Q15 和 Q14。
- 在 macOS 上,亮度的增减是通过向键盘设备发送一串十六进制实现的。
- Q15 和 Q14 发送的十六进制就是 F14 和 F15,所以 Fn + F5 和 Fn + F6 其实就是 F14 和 F15。
找出键盘上所有「额外的」快捷键
如果说 _SB.PCI0.LPCB.EC 下有两个函数实现了亮度快捷键,我们完全有理由推测这个总线下的其他函数定义了其它快捷键。
反编译原始的 DSDT 信息,用 MaciASL 打开,使用 Command + F 搜索 _SB.PCI0.LPCB.EC,看看有没有别的函数。果然,可以找到许多类似的模式的函数定义:
Scope (\_SB.PCI0.LPCB.EC)
{
Method (_Q63, 0, NotSerialized) // _Qxx: EC Query, xx=0x00-0xFF
{
If (\_SB.PCI0.LPCB.EC.HKEY.MHKK (0x01, 0x00080000))
{
\_SB.PCI0.LPCB.EC.HKEY.MHKQ (0x1014)
}
\UCMS (0x0B)
}
}
那么 _Q63 就是一个快捷键函数(由于 ACPI 的命名必须是 4 位、不足的补下划线 _,所以在下文中,我都会将形如 _Q63 的函数简称为 Q63)。如法炮制,找出剩余的函数。
当然在实际操作中,我其实偷了一个懒。我已经知道了 ThinkPad 全线的键盘定义是一致的(由于 OC-little 中提供的亮度快捷键 SSDT 是 ThinkPad 通用的),所以我找了 ThinkPad 其他机型已经做好黑苹果的 EFI,去找他们的 SSDT 中有没有快捷键修复。果然我找到了 ThinkPad X1 Carbon 6th 的 SSDT-KBD.aml 文件,使用 MaciASL 反编译,可以找到 ThinkPad 快捷键有这么几个函数:Q14、Q15、Q16、Q43、Q60、Q61、Q62、Q64、Q65、Q66。
接下来的问题就是,如何找出每个实体按键和上述函数之间的关系呢?
使用 ACPIDebug 找出快捷键与 ACPI 的映射关系
Rehabman 提供了一系列 DSDT Patch 用于 Debug ACPI 函数。OC-little 将其中的 DSDT Patch 精简为通用的 SSDT 热补丁、可以直接使用。ACPIDebug 的本质是提供一组 ACPI 函数,可以在控制台中输出指定的信息,如同 printf 或 console.log。我们只需要在需要打印调试信息的地方调用相关函数输出信息即可。
安装 ACPIDebug 的方法很简单,加载 SSDT-RMDT.aml 和内核驱动 ACPIDebug.kext 即可。相对困难的地方在于编写 SSDT 进行调试。
在 OC-little 中的样例 SSDT-BKeyQxx-Debug.dsl,也给出了打印两个参数的 RMDT 函数的使用示例:
Scope (_SB.PCI0.LPCB.EC0)
{
Method (_QXX, 0, NotSerialized)
{
If (_OSI ("Darwin"))
{
//Debug...
\RMDT.P2 ("ABCD-_PTS-Arg0=", \_SB.PCI9.TPTS)
\RMDT.P2 ("ABCD-_WAK-Arg0=", \_SB.PCI9.TWAK)
//Debug...end
}
Else
{
\_SB.PCI0.LPCB.EC0.XQXX()
}
}
}
注意到 \RMDT.P2 ("ABCD-_WAK-Arg0=", \_SB.PCI9.TWAK) 没有?在 QXX 函数中,调用了 \RMDT.P2 函数打印了两个参数,第一个是 ABCD-_PTS-Arg0= 字符串,第二个是变量 \_SB.PCI9.TPTS 。按下 QXX 函数对应的快捷键、就会执行上述打印函数,就可以在 macOS 控制台 Console.app 中看到 ABCD-_PTS-Arg0= 和 \_SB.PCI9.TPTS 变量的值。
如果你能看懂一些 ACPI 的话,通过 SSDT-RMD 中定义的 \RMDT.P2 函数需要打印两个参数,而 P1 函数只打印一个参数。当然现在我们只需要这个结论就够了。
仿照 OC-little 给出的亮度快捷键补丁和 SSDT-BKeyQxx-Debug.dsl 的例子,编写如下 SSDT:
// 需要注意的是,注释里的中文只是解释说明
// 在实际编写时注释里不能有中文
DefinitionBlock("", "SSDT", 2, "OCLT", "ACPIDebug", 0) // 我们的表名是 ACPIDebug
{
External(_SB.PCI0.LPCB.KBD, DeviceObj) // 引用外部定义 KBD,以你机器中 DSDT 中的为准
External(_SB.PCI0.LPCB.EC, DeviceObj) // 引用外部定义 EC,以你机器中 DSDT 中的为准
External(_SB.PCI0.LPCB.EC.XQ14, MethodObj) // 引用外部定义的 XQ14 函数
External(RMDT.P1, MethodObj) // 引用外部定义的 RMDT.P1 函数
Scope (_SB.PCI0.LPCB.EC)
{
Method (_Q14, 0, NotSerialized)
{
If (_OSI ("Darwin"))
{
\RMDT.P1 ("SUKKA_DEBUG_KEYBOARD-Q14") // 打印一个参数:字符串 SUKKA_DEBUG_KEYBOARD-Q14
}
Else
{
\_SB.PCI0.LPCB.EC.XQ14()
}
}
}
}
然后,继续在文件头部使用 External 添加对 XQ15 函数的外部定义,并仿照 _Q14 函数,编写剩余的快捷键函数定义。当然别忘了还需要在 config.plist 中添加 ACPI 重命名,将 _Q14 等重命名为 XQ14 等、以避免冲突。最后 SSDT 类似下图所示:
重启以加载上述 SSDT,然后打开 macOS 控制台,在右上角搜索框中输入 SUKKA_DEBUG_KEYBOARD 并回车,过滤出只包含指定字符串的信息。
接着,按下 Fn + F5 ,看看控制台中是否会打印信息:
打印出 SUKKA_DEBUG_KEYBOARD-Q14 ,表示 Fn + F5 就是 Q14。继续按下其他快捷键,根据打印信息找出每一个快捷键分别对应的函数:
这里列出我用上述方法找到的 ThinkPad 键盘的函数:
- Fn + F1 = Q43
- Fn + F5 = Q15
- Fn + F6 = Q14
- Fn + F7 = Q16
- Fn + F8 = Q64
- Fn + F9 = Q66
- Fn + F10 = Q60
- Fn + F11 = Q61
- Fn + F12 = Q62
- Fn + PrtScreen = Q65
学习 PS2 和 ABD 键码
在 OC-little 中的「PS2 键盘映射」章节中指出,一个按键会产生两种扫描码,分别是 PS2 扫描码 和 ADB 扫描码。在 ApplePS2ToADBMap.h 文件中可以找到原始的 ADB 扫描码和 PS2 扫描码之间的对应关系。
如果你的键盘是使用 VoodooPS2Controller 驱动的,可以使用 Rehabman 开发的 ioio 工具获取每个按键的键码。点击下载 并解压 ioio 工具,在终端运行下述命令查看按键的扫描码:
ioio -s ApplePS2Keyboard LogScanCodes 1
回到刚才打开的 macOS 控制台,删去右上角搜素框中所有字符,输入 PS2 并回车。
Tips:如果控制台有很多信息,可以用顶部的按钮清理。
按下 F1 键,可以看到控制台打印出如下扫描码:
让我们看看 3b=7a,等于号左边的 3b 是 PS2 扫描码,等于号右边的 7a 是 ADB 扫描码。还记得前文提到的 ApplePS2ToADBMap.h 文件么?看看在其中我们能不能找到什么:
0x7a, // 3b F1
啊,0x7a 对应的 3b,按键是 F1!
还记得前文说的「 0x0406 就是 F15 的扫描码」么?让我们读读文件:
// These ADB codes are for F14/F15 (works in 10.12)
#define BRIGHTNESS_DOWN 0x6b
#define BRIGHTNESS_UP 0x71
BRIGHTNESS_DOWN, // e0 05 dell down
BRIGHTNESS_UP, // e0 06 dell up
0x6b
是 F15
是 ADB 的扫描键码,同时又和 e0 06
是对应的。因此,0x0406
对应 e0 06
、0x0405
对应 e0 05
,最后两位都是相同的。这是不是巧合呢?肯定不是。
在这里我直接说结论。0x0406
中 04
指的就是 PS2 扫描码中的 e0
(即扩展键码),06
就是后两位。除了可以取 04
以外,还可以取 0x03
,表示 PS2 扫描码只有 2 位的。比如 F1 的 PS2 扫描码 3b
,就可以表示为 0x033b
。
再让我们回头来看看 OC-little 中提供的亮度快捷键 SSDT 补丁:
Scope (_SB.PCI0.LPCB.EC)
{
Method (_Q14, 0, NotSerialized)//up
{
If (_OSI ("Darwin"))
{
Notify(\_SB.PCI0.LPCB.KBD, 0x0406)
}
Else
{
\_SB.PCI0.LPCB.EC.XQ14()
}
}
}
所以按下 Fn + F6 ,ACPI 就会执行 _Q14 函数、向键盘 KBD 发送 0x0406,翻译为 ADB 扫描码就是 e0 06,对应 PS2 扫描码中的 0x71、也就是 F15,正好是系统偏好设置中的增加显示器亮度
编写 SSDT 定义快捷键
还记得第一章节的第一句话是怎么说的么?
在 OC-little 中有 ThinkPad 现成的亮度快捷键修复补丁,本质上是把 ThinkPad 的 Fn + F5 和 Fn + F6 映射到 F14 和 F15 上,而 F14 和 F15 是 macOS 中「系统偏好设置」中亮度调节的默认快捷键。
那么,我们可以用同样的方法,将 Fn + Fx 键分别映射 F13、F14、F15 一直到 F21。然后在「系统偏好设置」或者第三方快捷键软件中为 F13、F14 等按键定义操作。
首先列一张表将每个键、以及键码都对应起来。 原始按键 - 按键图标 - ACPI 函数 - 映射按键 - PS2 扫描码(十六进制)- ADB 扫描码 Fn + F1 - 静音 - Q43 - 静音 - e020 (0x0420) - 4a Fn + F4 - 麦克风开关 - Q6A - F13 - 64 (0x0364) - d9 Fn + F5 - 亮度减 - Q15 - F14 - e005 (0x0405) - 6b Fn + F6 - 亮度加 - Q14 - F15 - e006 (0x0406) - 71 Fn + F7 - 多屏幕 - Q16 - F16 - 67 (0x0367) - 6a Fn + F8 - WIFI 开关 - Q64 - F17 - 68 (0x0368) - 40 Fn + F9 - 太阳 - Q66 - F18 - 69 (0x0369) - 4f Fn + F10 - 蓝牙开关 - Q60 - F19 - 6a (0x036A) - 50 Fn + F11 - 键盘 - Q61 - F20 - 6b (0x036B) - 5a Fn + F12 - 星星 - Q62 - F21 - 6c (0x036C) - DEADKEY PrtScr - 截图 - N/A - F22 - e037 (0x0437) - 64 Fn + PrtScr - ThinkPad 触摸板开关 - Q65 - N/A - e01e (0x041e) - N/A
接下来,模仿亮度快捷键补丁的方式,按照上述表编写 SSDT:
// 需要注意的是,注释里的中文只是解释说明
// 在实际编写时注释里不能有中文
DefinitionBlock("", "SSDT", 2, "HACK", "Keyboard", 0)
{
External(_SB.PCI0.LPCB.KBD, DeviceObj) // 对键盘设备的外部引用
External(_SB.PCI0.LPCB.EC, DeviceObj) // 对 EC 总线的外部引用
External(_SB.PCI0.LPCB.EC.XQ43, MethodObj) // 对 XQ43 函数的引用
Scope (_SB.PCI0.LPCB.EC)
{
Method (_Q43, 0, NotSerialized) // Q43 函数
{
If (_OSI ("Darwin")) // macOS
{
Notify(\_SB.PCI0.LPCB.KBD, 0x0420) // 发送 PS2 扫描码 e020
}
Else // 非 macOS
{
\_SB.PCI0.LPCB.EC.XQ43() // 执行 XQ43 函数
}
}
}
}
然后依次添加原始函数的外部引用、依次添加 Notify 函数向键盘发送 PS2 键码。
需要注意的是,像 PrtScr 这种不是额外的快捷键,是不存在对应的 ACPI 函数的。我们可以使用 Custom PS2 Map 或者 Custom ADB Map 的方式进行映射:
Name(_SB.PCI0.LPCB.KBD.RMCF, Package()
{
"Keyboard", Package()
{
"Custom PS2 Map", Package()
{
Package(){},
"e037=64", // PrtSc = F13
},
// "Custom ADB Map", Package()
// {
// Package(){},
// "1e=06", // A = Z
// },
},
})
在这里我们需要了解一下 Custom PS2/ADB Map 的规则。等于号左边的永远是按下的按钮,等于号右边永远是原始的定义。
我们来看这么个例子:
"Custom PS2 Map", Package()
{
Package(){},
"1e=2c",
"2c=1e"
}
其中,1e 是 A 的 PS2 扫描码,2c 是 Z 的 PS2 扫描码。所以 1e=2c 表示,按下 A 后会触发 2c,而 2c 原始的定义是 Z ,因此输出字母 Z;同理,按下 Z 后,2c 被映射到 1e、也就是原始的 A ,所以输出的是字母 A。
使用快捷键禁用触控板(还有 ThinkPad 小红点)
在使用上述 SSDT 将 e037(PrtSc)映射到 6d( F13)之前,e037 其实是一个特殊的键码、用来开关 Trackpad(触控板)设备(在 ThinkPad 上,小红点也属于 Trackpad 设备)。虽然很少有人会用 macOS(特别是 Hackintosh)的笔记本玩游戏、因此没有禁用触控板的必要,但是凭着「我可以不用,你不能没有」的精神,我们还是希望能有快捷键可以用于关闭触控板、只是不能是 PrtSc 罢了。
就像前文所说,我们可以把 A 映射到 Z 的同时还把 Z 映射到 A;同理也可以先将一个按键映射到 e037 用于开关触控板,再将 PrtSc (e037)映射到其它键(如 F13)。
不过现在,我想把 Fn + F11 (ThinkPad 上 F11 画了一个键盘)映射到 e037 上,而 Fn + F11 由于是额外的快捷键、是没有 PS2 扫描码的,我该怎么办呢?答案是「无中生码」。
首先需要找一个我们用不到的 PS2 扫描码。再回头去看看 ApplePS2ToADBMap.h 去选择一个不是 DEADKEY 、同时键盘上又用不到的 PS2 扫描码。在这里我选的是 e01e。我已经知道了 Fn + F11 对应的 ACPI 函数是 Q61,因此在 Q61 函数中触发 e01e 即可:
Method (_61, 0, NotSerialized)
{
If (_OSI ("Darwin"))
{
Notify(\_SB.PCI0.LPCB.KBD, 0x041e) // e01e
}
Else
{
\_SB.PCI0.LPCB.EC.XQ61()
}
}
现在,如果按下 Fn + F11 就会发送 PS2 扫描码 e01e。 接下来我们只要在 Custom PS2 Map 中分别定义两个映射即可:
Name(_SB.PCI0.LPCB.KBD.RMCF, Package()
{
"Keyboard", Package()
{
"Custom PS2 Map", Package()
{
Package(){},
"e01e=e037", // Fn + F11 = PrtSc
"e037=64", // PrtSc = F13
},
},
})
评论0