功能定制
如何自定义键盘功能
对于许多人来说,定制键盘所能做的不仅仅是向计算机发送按键代码信号。大多数用户希望完成比简单的键和宏更复杂的事情。Qmk附带hook,它允许您在不同情况下插入代码、重写函数或自定义键盘的性能。
一句话概述核心&键盘&键盘映射
一般来说,我们将QMK按照如下的一个结构构建:
·Core (_quantum
)
·Keyboard/Revision (_kb
)
·Keymap (_user
)
上述的每个函数都可以用_kb()或_user()后缀定义。我们希望您在Keyboard/Revision级别时_kb()后缀,而_user()后缀应该在Keymap级别使用。
当定义Keyboard/Revision级别的函数时,注意你的_kb()在实现执行其他任何东西之前要调用_user()——否则keymap级别的函数将永远不会被调用。
自定义键码
到目前为止,最常见的操作是更改现有的键码或创建一个新的键码。从代码的角度来看,这个操作的每个机制都非常相似。
定义一个新的键码
创建您自己的自定义键码的第一步应该是去枚举它们。这意味着对它们进行命名并为该键码分配一个唯一的数字。在这里,QMK提供了名为SAFE_RANGE的宏,而不是将自定义键码限制为固定范围的数字。在枚举自定义键码时,可以使用SAFE_RANGE宏来确保获得唯一的数字。下面是一个包含2个键码的例子。在将这个块添加到你的keymap.c之后,你将能够在你的keymap中使用FOO和BAR:
enum my_keycodes {
FOO = SAFE_RANGE,
BAR
};
键码编程
当您想要覆盖现有键的行为,或为新键定义行为时,您应该使用process_record_kb()和process_record_user()函数。QMK将在键处理期间并在处理实际的键事件之前调用这些函数。如果这些函数返回true, QMK将像往常一样处理键码。这可以方便地扩展键的功能,而不是替换它。如果这些函数返回false, QMK将跳过正常的键处理步骤,并由您发送任何可供使用的键的向上或向下事件。当您每次按下或释放键时,QMK都会调用这些函数。
例子:process_record_user()的实现
这个例子做了两件事。它定义了名为FOO的自定义键码的行为,并通过在按下Enter键时播放一种音调来提示Enter键的触发:
例子:process_record_user()的实现:
这个例子做了两件事。它定义了名为FOO的自定义键码的行为,并通过在按下Enter键时播放一种音调来补充Enter键:
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case FOO:
if (record->event.pressed) {
// Do something when pressed
} else {
// Do something else when release
}
return false; // Skip all further processing of this key
case KC_ENTER:
// Play a tone when enter is pressed
if (record->event.pressed) {
PLAY_SONG(tone_qwerty);
}
return true; // Let QMK send the enter press/release events
default:
return true; // Process all other keycodes normally
}
}
process_record_* 函数文档
·Keyboard/Revision:bool process_record_kb(uint16_t keycode, keyrecord_t *record)
·Keymap: bool process_record_user(uint16_t keycode, keyrecord_t *record)
键值参数是在你的keymap中定义的,例如MO(1), KC_L等。你应该用switch…case语句来处理这些事件。
record参数包含了关于实际按下键所记录的信息,如:
keyrecord_t record {
keyevent_t event {
keypos_t key {
uint8_t col
uint8_t row
}
bool pressed
uint16_t time
}
}
键盘初始化代码
键盘初始化过程有几个步骤。这取决于您想要做什么,它将决定您应该使用哪个函数。
这是三个主要的初始化函数,按它们被调用的顺序列出:
·keyboard_pre_init_* -发生在大多数事情被启动之前。适合你想要尽早运行的硬件设置的情况。
·matrix_init_* -发生在固件启动过程的中途。硬件已经初始化,但特性可能还没有初始化。
·keyboard_post_init_* -发生在固件启动过程的末尾。在大多数情况下,这是您想要放置“定制”代码的地方。
对于大多数人来说,keyboard_post_init_user函数是最常被调用的。例如,在你想设置RGB背光的地方。
键盘预初始化码
这运行在启动的很早之前,甚至在USB已经启动之前。
在这之后不久,矩阵将被初始化。
对于大多数用户来说并不常用,因为它主要用于面向硬件的初始化。
然而,如果你有需要初始化的硬件,这是最适合初始化的地方(如初始化LED引脚)。
例子:keyboard_pre_init_user()的实现
本例中,在键盘级中,将B0、B1、B2、B3和B4设置为LED引脚。
void keyboard_pre_init_user(void) {
// Call the keyboard pre init code.
// Set our LED pins as output
setPinOutput(B0);
setPinOutput(B1);
setPinOutput(B2);
setPinOutput(B3);
setPinOutput(B4);
}
keyboard_pre_init_ *函数文档
·Keyboard/Revision:void keyboard_pre_init_kb(void)
·Keymap:void keyboard_pre_init_user(void)
矩阵初始化代码
这是在矩阵初始化时调用的,在一些硬件已经建立之后,但在许多特性已经被初始化之前。
这对于您可能在其他地方的设置所需要的东西很有用,但与硬件无关,也不依赖于它的起始位置。
matrix_init_ *功能文档
·Keyboard/Revision:void matrix_init_kb(void)
·Keymap:void matrix_init_user(void)
键盘后初始化代码
这是作为键盘初始化过程中的最后一个任务运行的。如果您想对某些特性进行更改,这是非常有用的,因为它们应该在此时初始化。
例子:keyboard_post_init_user()实现
在这个例子中,在所有东西都在后初始化运行,此为设置rgb底灯的配置。
void keyboard_post_init_user(void) {
// Call the post init code.
rgblight_enable_noeeprom(); // enables Rgb, without saving settings
rgblight_sethsv_noeeprom(180, 255, 255); // sets the color to teal/cyan without saving
rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING + 3); // sets mode to Fast breathing without saving
}
keyboard_post_init_ *函数文档
·Keyboard/Revision:void keyboard_post_init_kb(void)
·Keymap:void keyboard_post_init_user(void)
矩阵扫描代码
只要有可能,您就应该使用process_record_*()函数来客制化的键盘功能,并以这种方式连接到事件中,以确保您的代码不会对键盘产生负面的性能影响。然而,在少数情况下,功能有必要挂钩到矩阵扫描下。请特别注意这些函数中的代码性能,因为它每秒至少会被调用10次。
例子:matrix_scan_ *的实现
这个例子故意被省略了。涉及这样一个性能敏感的领域之前,您需要对QMK内部结构有足够的了解,并能够在不用示例的情况下编写这些内容。如果您需要帮助,请联系作者Discord官方或者发布提问
matrix_scan_ *函数文档
·Keyboard/Revision:void matrix_scan_kb(void)
·Keymap:void matrix_scan_user(void)
这个函数在每次矩阵扫描时都会被调用,这基本上同步于MCU能够处理的频率。请务必确认你放在这里的东西足够安全,因为它会经常运行。
如果你需要自定义矩阵扫描代码,你应该使用这个函数。它还可以用于自定义状态输出(如led或显示)或其他您希望在用户不打字时定期触发的功能。
键盘管家
·Keyboard/Revision:void housekeeping_task_kb(void)
·Keymap:void housekeeping_task_user(void)
您可以在下一个迭代开始之前,在所有QMK处理结束时调用此函数。你可以假设QMK已经安全地处理了最后的矩阵扫描的时候,这些函数的被调用层的状态已经更新,USB报告已经发送,led已经更新,并且显示已经输出。
与matrix_scan_*类似,这些函数在MCU能够处理的情况下被频繁调用。为了保持您系统能够正确响应,建议在这些函数调用期间尽可能少做一些事情,如果您确实需要实现一些特殊的东西,可能会阻碍它们的行为。
键盘空转/唤醒代码
如果您的PCB支持这个功能,它可以“空闲”,表现为停止一些功能。一个很好的例子是RGB灯或背光。这可以节省电力消耗,或者减少你的键盘的寿命损耗。
这是由两个函数控制的:susend_power_down_ *和susend_wakeup_init_ *,它们分别在系统板空闲和唤醒时被调用。
例子:susend_power_down_user()和susend_wakeup_init_user()的实现
void suspend_power_down_user(void) {
rgb_matrix_set_suspend_state(true);
}
void suspend_wakeup_init_user(void) {
rgb_matrix_set_suspend_state(false);
}
键盘挂起/唤醒功能文档
·Keyboard/Revision:
void suspend_power_down_kb(void)
and
void suspend_wakeup_init_user(void)
·Keymap:void housekeeping_task_user(void)
void suspend_power_down_kb(void)
and
void suspend_wakeup_init_user(void)
换层代码
每当层被更改时,它都会运行此代码。这对于层指示或自定义层结构很有用。
例子:layer_state_set_ *的实现
这个例子展示了如何设置基于图层的RGB下位灯光,以Plank为例:
layer_state_t layer_state_set_user(layer_state_t state) {
switch (get_highest_layer(state)) {
case _RAISE:
rgblight_setrgb (0x00, 0x00, 0xFF);
break;
case _LOWER:
rgblight_setrgb (0xFF, 0x00, 0x00);
break;
case _PLOVER:
rgblight_setrgb (0x00, 0xFF, 0x00);
break;
case _ADJUST:
rgblight_setrgb (0x7A, 0x00, 0xFF);
break;
default: // for any other layers, or the default layer
rgblight_setrgb (0x00, 0xFF, 0xFF);
break;
}
return state;
}
在此使用了使用IS_LAYER_ON_STATE(state, layer)和IS_LAYER_OFF_STATE(state, layer)宏来检查特定层的状态。
在layer_state_set_*函数之外,可以使用IS_LAYER_ON(layer)和IS_LAYER_OFF(layer)宏来检查全局层状态。
layer_state_set_ *功能文档
·Keyboard/Revision:layer_state_t layer_state_set_kb(layer_state_t state)
·Keymap:layer_state_t layer_state_set_user(layer_state_t state)
状态(state)是活动层的位掩码,在Keymap的概述中将会解释
长期设置(EEPROM Electrically Erasable Programmable read only memory 带电可擦可编程只读存储器)
这允许您为键盘配置长期的设置。这些设置存储在您的控制器的EEPROM中,并在断电后仍然保留。您可以使用eeconfig_read_kb和eeconfig_read_user函数读取设置,也可以使用eeconfig_update_kb和eeconfig_update_user写入设置。这对于你想要切换的特性是很有用的(比如切换rgb层指示)。另外,您可以使用eeconfig_init_kb和eeconfig_init_user函数为EEPROM设置默认值。
这里复杂的部分是,有很多的方法可以实现这一点,比如说,你可以存储和访问数据通过EEPROM,但是并没有绝对“正确的”方法来做到这一点。但是共通的是,每个函数只有一个DWORD(4字节)。
请记住,EEPROM的写入次数是有限的。虽然这是一个非常高的数字,但此类设置不是唯一写入EEPROM的东西,如果您写入地太频繁,MCU的寿命可能会大幅度缩短。
如果您不理解这个示例,那么请尽可能地不去使用这个特性,因为它确实相当复杂。
例子:这是一个如何添加设置和读写设置的示例。我们在这里的例子中使用用户键的映射。这是一个复函数,它包含了很多内容。事实上,它使用了上面的很多函数来工作:
*在你的keymap.c文件中,请把这个添加到顶部:
typedef union {
uint32_t raw;
struct {
bool rgb_layer_change :1;
};
} user_config_t;
user_config_t user_config;
这个代码块建立了一个32位的结构,我们可以在内存中存储设置,并写入EEPROM。使用这个方法就不需要定义变量,因为它们是在这个结构中定义的。记住bool (boolean)值使用1位,uint8_t使用8位,uint16_t使用16位。您可以混合和匹配它们,但更改顺序可能会导致问题,因为它将更改读取和写入的值。
对于layer_state_set_*函数,我们使用rgb_layer_change,并使用keyboard_post_init_user和process_record_user函数来配置所有内容。
现在,使用上面的keyboard_post_init_user代码,您希望将eeconfig_read_user()添加到其中,以填充刚才创建的结构。然后你可以立即使用这个结构来控制你的keymap中的功能。它应该是这样的:
void keyboard_post_init_user(void) {
// Call the keymap level matrix init.
// Read the user config from EEPROM
user_config.raw = eeconfig_read_user();
// Set default layer, if enabled
if (user_config.rgb_layer_change) {
rgblight_enable_noeeprom();
rgblight_sethsv_noeeprom_cyan();
rgblight_mode_noeeprom(1);
}
}
上面的代码块会在读取后立即使用EEPROM配置,来设置默认层的RGB颜色。它的“原始”值会根据您上面创建的“联合”来转换为一个可用的结构。
layer_state_t layer_state_set_user(layer_state_t state) {
switch (get_highest_layer(state)) {
case _RAISE:
if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_magenta(); rgblight_mode_noeeprom(1); }
break;
case _LOWER:
if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_red(); rgblight_mode_noeeprom(1); }
break;
case _PLOVER:
if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_green(); rgblight_mode_noeeprom(1); }
break;
case _ADJUST:
if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_white(); rgblight_mode_noeeprom(1); }
break;
default: // for any other layers, or the default layer
if (user_config.rgb_layer_change) { rgblight_sethsv_noeeprom_cyan(); rgblight_mode_noeeprom(1); }
break;
}
return state;
}
这个代码块将导致RGB underglow只在启用时改变。现在要配置这个值,请为process_record_user创建一个名为RGB_LYR的新键码。另外,我们想确保当你使用普通的RGB代码时,上面的操作会主动关闭它,让它看起来像这样:
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case FOO:
if (record->event.pressed) {
// Do something when pressed
} else {
// Do something else when release
}
return false; // Skip all further processing of this key
case KC_ENTER:
// Play a tone when enter is pressed
if (record->event.pressed) {
PLAY_SONG(tone_qwerty);
}
return true; // Let QMK send the enter press/release events
case RGB_LYR: // This allows me to use underglow as layer indication, or as normal
if (record->event.pressed) {
user_config.rgb_layer_change ^= 1; // Toggles the status
eeconfig_update_user(user_config.raw); // Writes the new status to EEPROM
if (user_config.rgb_layer_change) { // if layer state indication is enabled,
layer_state_set(layer_state); // then immediately update the layer color
}
}
return false;
case RGB_MODE_FORWARD … RGB_MODE_GRADIENT: // For any of the RGB codes (see quantum_keycodes.h, L400 for reference)
if (record->event.pressed) { //This disables layer indication, as it’s assumed that if you’re changing this … you want that disabled
if (user_config.rgb_layer_change) { // only if this is enabled
user_config.rgb_layer_change = false; // disable it, and
eeconfig_update_user(user_config.raw); // write the setings to EEPROM
}
}
return true; break;
default:
return true; // Process all other keycodes normally
}
}
最后,您需要添加eeconfig_init_user函数,以便在重置EEPROM时,可以指定默认值,甚至自定义操作。为了强制EEPROM复位,请使用EEP_RST键码或Bootmagic功能。例如,如果要设置rgb层指示为默认值,并保存为默认值:
void eeconfig_init_user(void) { // EEPROM is getting reset!
user_config.raw = 0;
user_config.rgb_layer_change = true; // We want this enabled by default
eeconfig_update_user(user_config.raw); // Write default value to EEPROM now
// use the non noeeprom versions, to write these values to EEPROM too
rgblight_enable(); // Enable RGB by default
rgblight_sethsv_cyan(); // Set it to CYAN by default
rgblight_mode(1); // set to solid by default
}
OK,到这里你已经做完了所有操作。RGB图层指示将只在你想要的时候工作。而且即使拔下键盘,它也会被保存。如果你使用任何其他的RGB代码,它会禁用层指示,这样它就会存储在你设置的模式和颜色中。
“EECONFIG”功能的文档
·Keyboard/Revision:void eeconfig_init_kb(void), uint32_t eeconfig_read_kb(void)和void eeconfig_update_kb(uint32_t val)
·Keymap: void eeconfig_init_user(void), uint32_t eeconfig_read_user(void,void eeconfig_update_user(uint32_t val)
val值是要写入EEPROM的数据的值。eeconfig_read_*函数用来从EEPROM返回一个32位(DWORD)值。
翻译:AIALRA