diff options
Diffstat (limited to 'docs/zh-cn/custom_quantum_functions.md')
-rw-r--r-- | docs/zh-cn/custom_quantum_functions.md | 367 |
1 files changed, 177 insertions, 190 deletions
diff --git a/docs/zh-cn/custom_quantum_functions.md b/docs/zh-cn/custom_quantum_functions.md index 1ae996e39..29c508905 100644 --- a/docs/zh-cn/custom_quantum_functions.md +++ b/docs/zh-cn/custom_quantum_functions.md | |||
@@ -1,31 +1,35 @@ | |||
1 | # 如何定制你键盘的功能 | 1 | # 如何定制化键盘功能 |
2 | 2 | ||
3 | 对于很多人来说客制化键盘可不只是向你的电脑发送你按了那个件这么简单。你肯定想实现比简单按键和宏更复杂的功能。QMK有能让你注入代码的钩子, 覆盖功能, 另外,还可以自定义键盘在不同情况下的行为。 | 3 | <!--- |
4 | original document: 0.15.12:docs/custom_quantum_functions.md | ||
5 | git diff 0.15.12 HEAD -- docs/custom_quantum_functions.md | cat | ||
6 | --> | ||
4 | 7 | ||
5 | 本页不假定任何特殊的QMK知识,但阅读[理解QMK](understanding_qmk.md)将会在更基础的层面帮你理解发生了什么。 | 8 | 对于很多人来说对客制化键盘的诉求不只是向电脑输入按下的键。你肯定想实现比简单按键和宏更复杂的功能。QMK支持基于注入点的代码注入,功能重写,另外还可以自定义键盘在不同情况下的行为。 |
6 | 9 | ||
7 | ## A Word on Core vs 键盘 vs 布局 | 10 | 本页不要求任何额外的QMK知识基础,但阅读[理解QMK](zh-cn/understanding_qmk.md)将会在更基础的层面帮你理解发生了什么。 |
8 | 11 | ||
9 | 我们把qmk组织成一个层次结构: | 12 | ## 核心/键盘/键映射的概念 :id=a-word-on-core-vs-keyboards-vs-keymap |
13 | |||
14 | QMK基于如下层级组成: | ||
10 | 15 | ||
11 | * Core (`_quantum`) | 16 | * Core (`_quantum`) |
12 | * Keyboard/Revision (`_kb`) | 17 | * Keyboard/Revision (`_kb`) |
13 | * Keymap (`_user`) | 18 | * Keymap (`_user`) |
14 | 19 | ||
15 | 下面描述的每一个函数都可以在定义上加一个`_kb()`或 `_user()` 后缀。 建议在键盘/修订层使用`_kb()`后缀,在布局层使用`_user()`后缀。 | 20 | 该文后续部分所提及的函数在定义时皆可添加 `_kb()` 或 `_user()` 后缀,我们建议在键盘及其子版本中使用 `_kb()` 后缀,而在键映射中使用 `_user()` 后缀。 |
16 | 21 | ||
17 | 在键盘/修订层定义函数时,`_kb()`在执行任何代码前先调用`_user()`是必要的,不然布局层函数就不要被调用。 | 22 | 在键盘及其子版本中定义函数时,一个重要的点是在 `_kb()` 函数执行任何逻辑前,应先调用 `_user()` 函数,否则这些键映射中的函数将没有机会被执行。 |
18 | <!-- 翻译问题:上面那句翻译的不太好--> | ||
19 | # 自定义键码 | 23 | # 自定义键码 |
20 | 24 | ||
21 | 到目前为止,最常见的任务是更改现有键码的行为或创建新的键码。从代码角度来看这些操作都很相似。 | 25 | 到目前为止,最常见的任务是更改现有键码的行为或创建新的键码。从代码角度来看这些操作都很相似。 |
22 | 26 | ||
23 | ## 定义一个新键码 | 27 | ## 定义一个新键码 |
24 | 28 | ||
25 | 创建键码第一步,先枚举出它全部,也就是给键码起个名字并分配唯一数值。QMK没有直接限制最大键码值大小,而是提供了一个`SAFE_RANGE`宏。你可以在枚举时用`SAFE_RANGE`来保证你取得了唯一的键码值。 | 29 | 创建键码的第一步,是先定义其枚举值,也就是给键码起个名字并分配一个唯一值。QMK没有直接限制最大可用的键码值,而是提供了一个 `SAFE_RANGE` 宏。你可以在定义枚举时用 `SAFE_RANGE` 来保证你取得了唯一的键码值。 |
26 | 30 | ||
27 | 31 | ||
28 | 这有枚举两个键码的例子。把这块加到`keymap.c`的话你就在布局中能用`FOO`和`BAR`了。 | 32 | 这有定义两个键码的枚举值的例子。添加以下代码块至 `keymap.c` 后你就可以在布局中使用 `FOO` 和 `BAR` 了。 |
29 | 33 | ||
30 | ```c | 34 | ```c |
31 | enum my_keycodes { | 35 | enum my_keycodes { |
@@ -34,15 +38,15 @@ enum my_keycodes { | |||
34 | }; | 38 | }; |
35 | ``` | 39 | ``` |
36 | 40 | ||
37 | ## 键码的行为编程 | 41 | ## 编程设计的键码的行为 :id=programming-the-behavior-of-any-keycode |
38 | 42 | ||
39 | 当你覆盖一个已存在按键的行为时,或将这个行为赋给新键时,你要用`process_record_kb()`和`process_record_user()`函数。这俩函数在键处理中真实键事件被处理前被QMK调用。如果这俩函数返回`true`,QMK将会用正常的方式处理键码。这样可以很方便的扩展键码的功能而不是替换它。如果函数返回`false` QMK会跳过正常键处理,然后发送键子抬起还是按下事件就由你决定了。 | 43 | 当你覆盖一个已存在按键的行为时,或是给新按键设计功能时,请使用 `process_record_kb()` 和 `process_record_user()` 函数。QMK会在响应并处理按键事件前调用这些函数,如果这些函数返回值为 `true`,QMK将继续用常规的方式处理键码,这样可以很方便的扩展键码的功能而不需要替换代码实现。如果函数返回`false` QMK会跳过常规的键处理逻辑,需要发送的按键按下或抬起事件则需交由你负责完成。 |
40 | 44 | ||
41 | 当某键按下或释时这俩函会用。 | 45 | 意按键在按下或起时,次都会调用这些函数。 |
42 | 46 | ||
43 | ### process_record_user()`示例实现 | 47 | ### process_record_user()` 示例 |
44 | 48 | ||
45 | 这个例子做了两个事。自定义了一个叫做`FOO`的键码的行为,并补充了在按下回车时播放音符。 | 49 | 这个例子做了两个事。自定义了一个叫做 `FOO` 的键码的行为,并提供了在按下回车时播放音符的功能。 |
46 | 50 | ||
47 | ```c | 51 | ```c |
48 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { | 52 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { |
@@ -51,7 +55,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |||
51 | if (record->event.pressed) { | 55 | if (record->event.pressed) { |
52 | // 按下时做些什么 | 56 | // 按下时做些什么 |
53 | } else { | 57 | } else { |
54 | // 释时做些什么 | 58 | // 起时做些什么 |
55 | } | 59 | } |
56 | return false; // 跳过此键的所有进一步处理 | 60 | return false; // 跳过此键的所有进一步处理 |
57 | case KC_ENTER: | 61 | case KC_ENTER: |
@@ -59,21 +63,21 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |||
59 | if (record->event.pressed) { | 63 | if (record->event.pressed) { |
60 | PLAY_SONG(tone_qwerty); | 64 | PLAY_SONG(tone_qwerty); |
61 | } | 65 | } |
62 | return true; // 让QMK触回车按下/释事件 | 66 | return true; // 让QMK车按下/起事件 |
63 | default: | 67 | default: |
64 | return true; // 正常理其他键码 | 68 | return true; // 正常他键码 |
65 | } | 69 | } |
66 | } | 70 | } |
67 | ``` | 71 | ``` |
68 | 72 | ||
69 | ### `process_record_*` 文档 | 73 | ### `process_record_*` 示例 |
70 | 74 | ||
71 | * 键盘/修订: `bool process_record_kb(uint16_t keycode, keyrecord_t *record)` | 75 | * 键盘/各子版本:`bool process_record_kb(uint16_t keycode, keyrecord_t *record)` |
72 | * 局: `bool process_record_user(uint16_t keycode, keyrecord_t *record)` | 76 | * 键映:`bool process_record_user(uint16_t keycode, keyrecord_t *record)` |
73 | 77 | ||
74 | `keycode(键码)`参数是在布局上定义的,比如`MO(1)`, `KC_L`, 等等。 你要用 `switch...case` 块来处理这些事件。 | 78 | `keycode` 参数为键映射中形如 `MO(1)`,`KC_L` 等定义的键值项。 应使用 `switch...case` 代码块来处理这些事件。 |
75 | 79 | ||
76 | `record`参数含有实际按键的信息: | 80 | `record` 参数含有按键的真实状态信息: |
77 | 81 | ||
78 | ```c | 82 | ```c |
79 | keyrecord_t record { | 83 | keyrecord_t record { |
@@ -88,108 +92,31 @@ keyrecord_t record { | |||
88 | } | 92 | } |
89 | ``` | 93 | ``` |
90 | 94 | ||
91 | # LED控制 | ||
92 | |||
93 | qmk提供了读取HID规范包含的5个LED的方法。: | ||
94 | |||
95 | * `USB_LED_NUM_LOCK` | ||
96 | * `USB_LED_CAPS_LOCK` | ||
97 | * `USB_LED_SCROLL_LOCK` | ||
98 | * `USB_LED_COMPOSE` | ||
99 | * `USB_LED_KANA` | ||
100 | |||
101 | 这五个常量对应于主机LED状态的位置位。 | ||
102 | 有两种方法可以获得主机LED状态: | ||
103 | |||
104 | * 通过执行 `led_set_user()` | ||
105 | * 通过调用 `host_keyboard_leds()` | ||
106 | |||
107 | ## `led_set_user()` | ||
108 | |||
109 | 当5个LED中任何一个的状态需要改变时,此函数将被调用。此函数通过参数输入LED参数。 | ||
110 | 使用`IS_LED_ON(usb_led, led_name)`和`IS_LED_OFF(usb_led, led_name)`这两个宏来检查LED状态。 | ||
111 | |||
112 | !> `host_keyboard_leds()`可能会在`led_set_user()`被调用前返回新值。 | ||
113 | |||
114 | ### `led_set_user()`函数示例实现 | ||
115 | |||
116 | ```c | ||
117 | void led_set_user(uint8_t usb_led) { | ||
118 | if (IS_LED_ON(usb_led, USB_LED_NUM_LOCK)) { | ||
119 | writePinLow(B0); | ||
120 | } else { | ||
121 | writePinHigh(B0); | ||
122 | } | ||
123 | if (IS_LED_ON(usb_led, USB_LED_CAPS_LOCK)) { | ||
124 | writePinLow(B1); | ||
125 | } else { | ||
126 | writePinHigh(B1); | ||
127 | } | ||
128 | if (IS_LED_ON(usb_led, USB_LED_SCROLL_LOCK)) { | ||
129 | writePinLow(B2); | ||
130 | } else { | ||
131 | writePinHigh(B2); | ||
132 | } | ||
133 | if (IS_LED_ON(usb_led, USB_LED_COMPOSE)) { | ||
134 | writePinLow(B3); | ||
135 | } else { | ||
136 | writePinHigh(B3); | ||
137 | } | ||
138 | if (IS_LED_ON(usb_led, USB_LED_KANA)) { | ||
139 | writePinLow(B4); | ||
140 | } else { | ||
141 | writePinHigh(B4); | ||
142 | } | ||
143 | } | ||
144 | ``` | ||
145 | |||
146 | ### `led_set_*`函数文档 | ||
147 | |||
148 | * 键盘/修订: `void led_set_kb(uint8_t usb_led)` | ||
149 | * 布局: `void led_set_user(uint8_t usb_led)` | ||
150 | |||
151 | ## `host_keyboard_leds()` | ||
152 | |||
153 | 调用这个函数会返回最后收到的LED状态。这个函数在`led_set_*`之外读取LED状态时很有用,比如在[`matrix_scan_user()`](#矩阵扫描代码). | ||
154 | 为了便捷,你可以用`IS_HOST_LED_ON(led_name)`和`IS_HOST_LED_OFF(led_name)` 宏,而不直接调用和检查`host_keyboard_leds()`。 | ||
155 | |||
156 | ## 设置物理LED状态 | ||
157 | |||
158 | 一些键盘实现了为设置物理LED的状态提供了方便的方法。 | ||
159 | |||
160 | ### Ergodox Boards | ||
161 | |||
162 | Ergodox实现了提供`ergodox_right_led_1`/`2`/`3_on`/`off()`来让每个LED开或关, 也可以用 `ergodox_right_led_on`/`off(uint8_t led)` 按索引打开或关闭他们。 | ||
163 | |||
164 | 此外,还可以使用`ergodox_led_all_set(uint8_t n)`指定所有LED的亮度级别;针对每个LED用`ergodox_right_led_1`/`2`/`3_set(uint8_t n)`;使用索引的话用`ergodox_right_led_set(uint8_t led, uint8_t n)`。 | ||
165 | |||
166 | Ergodox boards 同时定义了最低亮度级别`LED_BRIGHTNESS_LO`和最高亮度级别`LED_BRIGHTNESS_HI`(默认最高). | ||
167 | |||
168 | # 键盘初始化代码 | 95 | # 键盘初始化代码 |
169 | 96 | ||
170 | 键盘初始化过程有几个步骤你是那个数取你想要么。 | 97 | 键盘初始化过程须经过几个步骤,你目的决定你需要注哪函数。 |
171 | 98 | ||
172 | 有三个主要初始化函数,按调用顺序列出。 | 99 | 有三个主要初始化函数,按调用顺序列出。 |
173 | 100 | ||
174 | * `keyboard_pre_init_*` - 会在大多数其他东西运行前运行。适用于哪些需要提前运行的硬件初始化。 | 101 | * `keyboard_pre_init_*` - 会在大多数其他功能运行前执行。适用于那些需要尽早执行的硬件初始化工作。 |
175 | * `matrix_init_*` - 在固件启动过程中间被调用。此时硬件已初始化,功能未初始化。 | 102 | * `matrix_init_*` - 在固件启动过程中被调用。此时硬件已初始化,但部能还不用。 |
176 | * `keyboard_post_init_*` - 在固件启动过程最后被调用。大多数情况下,你的“客制化”代码都可以放在这里。 | 103 | * `keyboard_post_init_*` - 在固件启动过程的最后被调用。大多数情况下,你的“客制化”代码都可以放在这里。 |
177 | 104 | ||
178 | !> 对于大多数人来说`keyboard_post_init_user`是你想要调用的函数。例如, 此时你可以设置RGB灯发光。 | 105 | !> 对于大多数人来说 `keyboard_post_init_user` 是你想要关注的函数。例如, 你可以在这里启动RGB背光灯。 |
179 | 106 | ||
180 | ## 键盘预初始化代码 | 107 | ## 键盘预初始化代码 |
181 | 108 | ||
182 | 这代码早运行,甚至都在USB前运行。 | 109 | 这部分代码行的常早,甚至是在USB通信能之前。 |
183 | 110 | ||
184 | 在这之后不久矩阵被始化了。 | 111 | 在这之后不久即会完成矩阵的初始化。 |
185 | 112 | ||
186 | 对于大多数用户来说,这用不,因为它主要是用于面向硬件的初始化。 | 113 | 对于大多数用户来说不在此处进行修改,因为它主要用于硬件初始化。 |
187 | 114 | ||
188 | 但如果你有硬件初始化的话放在这里再好不过了(比如初始化LED引脚一类的). | 115 | 但如果你有硬件须初始化的话放在这里再好不过了(比如初始化LED引脚). |
189 | 116 | ||
190 | ### `keyboard_pre_init_user()`示例实现 | 117 | ### `keyboard_pre_init_user()` 示例 |
191 | 118 | ||
192 | 本例中在键盘级,设 B0, B1, B2, B3, 和 B4 是LED引脚。 | 119 | 本例中,在键盘 B0, B1, B2, B3, 和 B4 引脚设置为LED引脚。 |
193 | 120 | ||
194 | ```c | 121 | ```c |
195 | void keyboard_pre_init_user(void) { | 122 | void keyboard_pre_init_user(void) { |
@@ -206,95 +133,110 @@ void keyboard_pre_init_user(void) { | |||
206 | 133 | ||
207 | ### `keyboard_pre_init_*` 函数文档 | 134 | ### `keyboard_pre_init_*` 函数文档 |
208 | 135 | ||
209 | * 键盘/修订: `void keyboard_pre_init_kb(void)` | 136 | * 键盘/各子版本:`void keyboard_pre_init_kb(void)` |
210 | * 局: `void keyboard_pre_init_user(void)` | 137 | * 键映:`void keyboard_pre_init_user(void)` |
211 | 138 | ||
212 | ## 矩阵初始化代码 | 139 | ## 矩阵初始化代码 |
213 | 140 | ||
214 | 这会矩阵初始化时被调用,在硬件设置,但在一些功能被初始化前。 | 141 | 在矩阵初始化后被调用。时部分硬件已设置,但一些功能未完始化。 |
215 | 142 | ||
216 | 这你设置地方会用到西的时很有用,硬件无关,的置。 | 143 | 此用来设一与硬件无关,对始没有殊要求。 |
217 | 144 | ||
218 | 145 | ||
219 | ### `matrix_init_*`函数文档 | 146 | ### `matrix_init_*` 函数文档 |
220 | 147 | ||
221 | * 键盘/修订: `void matrix_init_kb(void)` | 148 | * 键盘/各子版本:`void matrix_init_kb(void)` |
222 | * 局: `void matrix_init_user(void)` | 149 | * 键映:`void matrix_init_user(void)` |
223 | 150 | ||
151 | ### 低级矩阵函数的重写 :id=low-level-matrix-overrides | ||
224 | 152 | ||
225 | ## 键盘后初始化代码 | 153 | * GPIO引脚初始化:`void matrix_init_pins(void)` |
154 | * 此处须完成低级行列引脚的初始化。默认实现中,这里会参考可选的键盘设置项 `ROW2COL`,`COL2ROW` 及 `DIRECT_PINS` 来初始化所有 `MATRIX_ROW_PINS` 及 `MATRIX_COL_PINS` 中定义的GPIO引脚的输入/输出状态。当键盘设计者重写该函数后,QMK本身不会进行任何引脚的初始化,只会听从重写的函数的实现逻辑。 | ||
155 | * `COL2ROW`-从行中读: `void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)` | ||
156 | * `ROW2COL`-从列中读: `void matrix_read_rows_on_col(matrix_row_t current_matrix[], uint8_t current_col)` | ||
157 | * `DIRECT_PINS`-直读: `void matrix_read_cols_on_row(matrix_row_t current_matrix[], uint8_t current_row)` | ||
158 | * 以上三个函数须参考矩阵类别,从底层矩阵的相关引脚状态中获取输入信息,并且应该只需要实现三者之一。默认情况下,在遍历 `MATRIX_ROW_PINS` and `MATRIX_COL_PINS` 时,会根据是否设置了 `ROW2COL`,`COL2ROW` 或 `DIRECT_PINS` 来配置输入输出方式。当键盘设计者重写该函数后,QMK本身不会进行任何矩阵GPIO引脚状态的变更,只会听从重写的函数的实现逻辑。 | ||
226 | 159 | ||
227 | 这是键盘初始化过程中的最后一个任务。如果您想更改某些特性,这会很有用,因为此时应该对它们进行初始化。 | 160 | ## 键盘后初始化代码 |
228 | 161 | ||
162 | 这是键盘初始化过程中的最后一个任务。此时您可以配置并调整某些特性,因为此时这些特性已初始化完毕。 | ||
229 | 163 | ||
230 | ### `keyboard_post_init_user()`示例实现 | 164 | ### `keyboard_post_init_user()` 实现示例 |
231 | 165 | ||
232 | 本示例在所有初始化完成后运行,配置RGB灯。 | 166 | 本示例在所有初始化完成后运行,配置RGB背光。 |
233 | 167 | ||
234 | ```c | 168 | ```c |
235 | void keyboard_post_init_user(void) { | 169 | void keyboard_post_init_user(void) { |
236 | // 调用后初始化代码 | 170 | // 调用后初始化代码 |
237 | rgblight_enable_noeeprom(); // 使能Rgb,不保存设置 | 171 | rgblight_enable_noeeprom(); // 使能Rgb,不保存设置 |
238 | rgblight_sethsv_noeeprom(180, 255, 255); // 将颜色设置到蓝绿色(青色)不保存 | 172 | rgblight_sethsv_noeeprom(180, 255, 255); // 将颜色设置到蓝绿色(青色),不保存设置 |
239 | rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING + 3); // 设置快速呼吸模式不保存 | 173 | rgblight_mode_noeeprom(RGBLIGHT_MODE_BREATHING + 3); // 设置快速呼吸模式,不保存设置 |
240 | } | 174 | } |
241 | ``` | 175 | ``` |
242 | 176 | ||
243 | ### `keyboard_post_init_*` 函数文档 | 177 | ### `keyboard_post_init_*` 函数文档 |
244 | 178 | ||
245 | * 键盘/修订: `void keyboard_post_init_kb(void)` | 179 | * 键盘/各子版本:`void keyboard_post_init_kb(void)` |
246 | * 布局: `void keyboard_post_init_user(void)` | 180 | * 布局: `void keyboard_post_init_user(void)` |
247 | 181 | ||
248 | # 矩阵扫描代码 | 182 | # 矩阵扫描码 |
249 | 183 | ||
250 | 可能的话你要用`process_record_*()`自定义键盘,以这种方式连接到事件中,以确保代码不会对键盘产生负面的性能影响。然而,在极少数情况下,有必要进行矩阵扫描。在这些函数中要特别注意代码的性能,因为它每秒至少被调用10次。 | 184 | 应尽量使用 `process_record_*()` 实现所需的键盘自定义以及事件监听,以确保这些代码不会对键盘性能产生负面的影响。然而,在极少数情况下需要在矩阵扫描中添加监听,此时需要极端留意这些函数代码的性能表现,因为这些函数每秒可能被执行十数次。 |
251 | 185 | ||
252 | ### `matrix_scan_*`示例实现 | 186 | ### `matrix_scan_*` 实现示例 |
253 | 187 | ||
254 | 这个例子被故意省略了。在hook这样一个对性能及其敏感的区域之前,您应该足够了解qmk的内部结构,以便在没有示例的情况下编写。如果你需要帮助,请[建立一个issue](https://github.com/qmk/qmk_firmware/issues/new)或[在Discord上与我们交流](https://discord.gg/Uq7gcHh). | 188 | 这个例子被故意省略了。在监听处理这样一个对性能及其敏感的部分之前,您应该足够了解qmk的内部结构,才可以在没有示例的情况下编写。如果你需要帮助,请[新建一个issue](https://github.com/qmk/qmk_firmware/issues/new)或[在Discord上与我们交流](https://discord.gg/Uq7gcHh). |
255 | 189 | ||
256 | ### `matrix_scan_*` 函数文档 | 190 | ### `matrix_scan_*` 函数文档 |
257 | 191 | ||
258 | * 键盘/修订: `void matrix_scan_kb(void)` | 192 | * 键盘/各子版本:`void matrix_scan_kb(void)` |
259 | * 布局: `void matrix_scan_user(void)` | 193 | * 布局: `void matrix_scan_user(void)` |
260 | 194 | ||
261 | 该函数在每次矩阵扫描时被调用,这基本与MCU处理能力上限相同。在这里写代码要谨慎,因为它会运行很多次。 | 195 | 该函数在每次矩阵扫描时被调用,这基本与MCU处理能力上限相同。在这里写代码要谨慎,因为它会运行很多次。 |
262 | 196 | ||
263 | 你会在自定义矩阵扫描代码时用到这个函数。这也可以用作自定义状态输出(比如LED灯或者屏幕)或者其他即便用户不输入你也想定期运行的功能。 | 197 | 在需要自定义矩阵扫描代码时可以使用该函数。这也可以用作自定义状态输出(比如LED灯或者屏幕)或者其他即便用户没有输入时你也想定期运行的功能。 |
198 | |||
199 | # Keyboard housekeeping | ||
200 | |||
201 | * 键盘/各子版本:`void housekeeping_task_kb(void)` | ||
202 | * 键映射:`void housekeeping_task_user(void)` | ||
203 | |||
204 | 该函数在所有QMK处理工作完毕后,下一轮开始执行前被执行。可以放心地假设此时QMK已对最新的矩阵扫描结果完成了所有的处理工作 -- 更新层状态,发送USB事件,更新LED状态,刷新显示屏。 | ||
264 | 205 | ||
206 | 与 `matrix_scan_*` 类似,这些函数会频繁调用直至MCU处理能力上限。为了确保键盘的响应能力,建议在这些函数中尽量做最少的事情,在你确实需要在这里实现特别的功能时,可能会影响到其它功能的表现。 | ||
265 | 207 | ||
266 | # 键盘 空闲/唤醒 代码 | 208 | # 键盘 空闲/唤醒 代码 |
267 | 209 | ||
268 | 如果键盘支持就可以通过停止一大票功能来达到"空闲"。RGB灯和背光就是很好的例子。这可以节约能耗,也可能让你键盘风味更佳。 | 210 | 在主控板支持情况下,暂停大部分功能可以实现“空闲”状态,例如RGB灯光和背光。既可以节省电量消耗,也可能增强键盘的表现。 |
269 | 211 | ||
270 | 两个函数控制: `suspend_power_down_*`和`suspend_wakeup_init_*`, 分别在统板空闲和唤醒时调用。 | 212 | 这两个函数控制: `suspend_power_down_*` 和 `suspend_wakeup_init_*`,分别在空闲和唤醒时用。 |
271 | 213 | ||
272 | 214 | ||
273 | ### suspend_power_down_user()和suspend_wakeup_init_user()例实现 | 215 | ### suspend_power_down_user() 和 suspend_wakeup_init_user() 实现示例 |
274 | 216 | ||
275 | 217 | ||
276 | ```c | 218 | ```c |
277 | void suspend_power_down_user(void) { | 219 | void suspend_power_down_user(void) { |
278 | // code will run multiple times while keyboard is suspended | 220 | // 当键盘挂起时会被多次调用的代码 |
279 | } | 221 | } |
280 | 222 | ||
281 | void suspend_wakeup_init_user(void) { | 223 | void suspend_wakeup_init_user(void) { |
282 | // code will run on keyboard wakeup | 224 | // 键盘唤醒时被调用的代码 |
283 | } | 225 | } |
284 | ``` | 226 | ``` |
285 | 227 | ||
286 | ### 键盘 挂起/唤醒 函数文档 | 228 | ### 键盘 挂起/唤醒 函数文档 |
287 | 229 | ||
288 | * 键盘/修订: `void suspend_power_down_kb(void)` 和`void suspend_wakeup_init_user(void)` | 230 | * 键盘/各子版本:`void suspend_power_down_kb(void)` 和 `void suspend_wakeup_init_user(void)` |
289 | * 局: `void suspend_power_down_kb(void)` 和 `void suspend_wakeup_init_user(void)` | 231 | * 键映:`void suspend_power_down_kb(void)` 和 `void suspend_wakeup_init_user(void)` |
290 | 232 | ||
291 | # 层改代码 | 233 | # 层换代码 :id=layer-change-code |
292 | 234 | ||
293 | 每当层改这个行代码。这于层或自定义层处理有用。 | 235 | 每当层生换时执行,用于感知层换件,或自定义层处理逻。 |
294 | 236 | ||
295 | ### `layer_state_set_*` 示例实现 | 237 | ### `layer_state_set_*` 实现示例 |
296 | 238 | ||
297 | 本例用了Planck键盘示范了如何设置 [RGB背光灯](feature_rgblight.md)与层应 | 239 | 本例,通Planck键盘示范了如何将[RGB背光灯](zh-cn/feature_rgblight.md)设与层步。 |
298 | 240 | ||
299 | ```c | 241 | ```c |
300 | layer_state_t layer_state_set_user(layer_state_t state) { | 242 | layer_state_t layer_state_set_user(layer_state_t state) { |
@@ -311,36 +253,41 @@ layer_state_t layer_state_set_user(layer_state_t state) { | |||
311 | case _ADJUST: | 253 | case _ADJUST: |
312 | rgblight_setrgb (0x7A, 0x00, 0xFF); | 254 | rgblight_setrgb (0x7A, 0x00, 0xFF); |
313 | break; | 255 | break; |
314 | default: // for any other layers, or the default layer | 256 | default: // 默认层及其它层 |
315 | rgblight_setrgb (0x00, 0xFF, 0xFF); | 257 | rgblight_setrgb (0x00, 0xFF, 0xFF); |
316 | break; | 258 | break; |
317 | } | 259 | } |
318 | return state; | 260 | return state; |
319 | } | 261 | } |
320 | ``` | 262 | ``` |
263 | |||
264 | 可以通过 `IS_LAYER_ON_STATE(state, layer)` 和 `IS_LAYER_OFF_STATE(state, layer)` 宏来确认常规层的状态。 | ||
265 | |||
266 | 如果不在 `layer_state_set_*` 函数中,可以通过 `IS_LAYER_ON(layer)` 和 `IS_LAYER_OFF(layer)` 宏来确认全局的层状态。 | ||
267 | |||
321 | ### `layer_state_set_*` 函数文档 | 268 | ### `layer_state_set_*` 函数文档 |
322 | 269 | ||
323 | * 键盘/修订: `uint32_t layer_state_set_kb(uint32_t state)` | 270 | * 键盘/各子版本:`uint32_t layer_state_set_kb(uint32_t state)` |
324 | * 布局: `layer_state_t layer_state_set_user(layer_state_t state)` | 271 | * 布局: `layer_state_t layer_state_set_user(layer_state_t state)` |
325 | 272 | ||
326 | 273 | ||
327 | 该`状`的bitmask, 详见[局概述](keymap.md#布局的层状态) | 274 | 处的 `state` 为当前活跃层的位掩码, 详见[键映概述](zh-cn/keymap.md#keymap-layer-status) |
328 | 275 | ||
329 | 276 | ||
330 | # 掉电保存配置 (EEPROM) | 277 | # 配置的持久存储(EEPROM) |
331 | 278 | ||
332 | 这会让你的配置长期的保存在键盘中。这些配置保存在你主控的EEPROM里,掉电不会消失。 设置可以用`eeconfig_read_kb`和`eeconfig_read_user`读取,可以用`eeconfig_update_kb`和`eeconfig_update_user`写入。这对于您希望能够切换的功能很有用(比如切换RGB层指示。此外,你可以用`eeconfig_init_kb`和`eeconfig_init_user`来设置EEPROM默认值。 | 279 | 该功能可以让键盘的配置持久存储下来。这些配置存储在控制器的EEPROM中,即便掉电后依旧可以留存下来。可以通过 `eeconfig_read_kb` 和 `eeconfig_read_user` 来读取,通过 `eeconfig_update_kb` and `eeconfig_update_user` 来进行保存。该功能常用于保存一些开关状态(比如rgb层指示灯)。此外,可以通过 `eeconfig_init_kb` 和 `eeconfig_init_user` 来设置EEPROM的默认配置值。 |
333 | 280 | ||
334 | 最复杂的部分可能是,有很多方法可以通过EEPROM存储和访问数据,并且并没有用哪种方法是“政治正确”的。你每个功能只有一个双字(四字节)空间。 | 281 | 复杂的地方是,有很多方法可以存储和访问EEPROM数据,并且没有哪种方法是“正确”的。但是,每个功能只有一个双字(四字节)空间可用。 |
335 | 282 | ||
336 | 记住EEPROM是有写入寿命的。尽管写入寿命很高,但是并不是只有设置写道EEPROM中。如果你写入频繁,你的MCU寿命将会变短。 | 283 | 记住EEPROM是有写入寿命的。尽管写入寿命很高,但是并不是只有这些配置信息会写到EEPROM中。如果你写入过于频繁,你的MCU寿命将会急速减少。 |
337 | 284 | ||
338 | * 如果您不理解这个例子,那么您可能望避使用这个特性,因为它相当复杂。 | 285 | * 如果您不理解这个例子,那么您可以使用这个特性,因为它相当复杂。 |
339 | 286 | ||
340 | ### 示例实现 | 287 | ### 实现示例 |
341 | |||
342 | 本例讲解了如何添加设置,并且读写。本里使用了用户布局。这是一个复杂的函数,有很多事情要做。实际上,它使用了很多上述函数来工作! | ||
343 | 288 | ||
289 | 本例讲解了如何添加并读写设置项。本例使用用户键映射来实现。这是一个复杂的函数,有很多事情要做。实际上,它使用了很多前述的函数来工作! | ||
290 | (译注:该示例由于英文行文,可能会觉得看得稀里糊涂。实现的功能很简单,即开启了层指示功能(RGB_LYR)时,rgb背光灯会展示当前层的特定颜色用以指示层状态,而触发任何改变rgb背光颜色的键码时,rgb背光灯将回归普通的背光灯角色,不再作为层指示器) | ||
344 | 291 | ||
345 | 在你的keymap.c文件中,将以下代码添加至顶部: | 292 | 在你的keymap.c文件中,将以下代码添加至顶部: |
346 | ```c | 293 | ```c |
@@ -354,14 +301,14 @@ typedef union { | |||
354 | user_config_t user_config; | 301 | user_config_t user_config; |
355 | ``` | 302 | ``` |
356 | 303 | ||
357 | 以上代码建立了一个结构体,该结构体可以存储设置并可用于写入EEPROM。如此这般将无需定义变量,因为在结构体中已然定义。要记住`bool` (布尔)值使用1位, `uint8_t`使用8位, `uint16_t`使用16位。你可以混合搭配使用,但是顺序记错可能会招致麻烦,因为那会改变写入写出的值。 | 304 | 以上代码建立了一个32位的结构体,用于在内存及EEPROM中存储配置项。此时不再需要再单独声明变量,因为都已经在该结构体中定义了。须记住 `bool`(布尔)值占用1位,`uint8_t` 占用8位,`uint16_t` 占用16位。你可以混合搭配使用,但改变这些顺序会因为错误的读写而招致问题。 |
358 | 305 | ||
359 | `layer_state_set_*`函数中使用了`rgb_layer_change`,使用了`keyboard_post_init_user`和`process_record_user`来配置一切。 | 306 | 我们在 `layer_state_set_*` 函数中会使用 `rgb_layer_change`。通过 `keyboard_post_init_user` 和 `process_record_user` 来配置所需的一切。 |
360 | 307 | ||
361 | 首先要使用`keyboard_post_init_user,你要加入`eeconfig_read_user()`来填充你刚刚创建的结构体。然后您可以立即使用这个结构来控制您的布局中的功能。就像这样: | 308 | 在编写 `keyboard_post_init_user` 时,你需要使用 `eeconfig_read_user()` 来计算并填充你刚刚创建的结构体。然后即可以使用结构体数据来控制键映射中的功能。就像这样: |
362 | ```c | 309 | ```c |
363 | void keyboard_post_init_user(void) { | 310 | void keyboard_post_init_user(void) { |
364 | // 调用局级别的矩阵初始化 | 311 | // 调用键映级别的矩阵初始化 |
365 | 312 | ||
366 | // 从EEPROM读用户配置 | 313 | // 从EEPROM读用户配置 |
367 | user_config.raw = eeconfig_read_user(); | 314 | user_config.raw = eeconfig_read_user(); |
@@ -374,7 +321,7 @@ void keyboard_post_init_user(void) { | |||
374 | } | 321 | } |
375 | } | 322 | } |
376 | ``` | 323 | ``` |
377 | 以上函数会在读EEPROM配置后立即使用该设置来设置默认层RGB颜色。"raw"的值是从你上面基于"union"创建的结构体中转换来的。 | 324 | 以上函数会在读EEPROM配置后立即设置默认层的RGB颜色。"raw"值将被转换为上述创建的实际使用的"union"结构体。 |
378 | 325 | ||
379 | ```c | 326 | ```c |
380 | layer_state_t layer_state_set_user(layer_state_t state) { | 327 | layer_state_t layer_state_set_user(layer_state_t state) { |
@@ -398,7 +345,7 @@ layer_state_t layer_state_set_user(layer_state_t state) { | |||
398 | return state; | 345 | return state; |
399 | } | 346 | } |
400 | ``` | 347 | ``` |
401 | 这样仅在值使能时会改变RGB背光灯。现在配置这个值, 为`process_record_user`创建一个新键码叫做`RGB_LYR`。我们要确保,如果使用正常的RGB代码,使用上面的示例将其关闭,请将其设置为: | 348 | 这样仅在相关值使能时才会改变RGB背光灯。若要配置该值, 为 `process_record_user` 创建一个新键码 `RGB_LYR`。此时我们想实现的是,如果触发了常规的RGB码,以上示例中的逻辑都将不生效,形如: |
402 | ```c | 349 | ```c |
403 | 350 | ||
404 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { | 351 | bool process_record_user(uint16_t keycode, keyrecord_t *record) { |
@@ -407,7 +354,7 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |||
407 | if (record->event.pressed) { | 354 | if (record->event.pressed) { |
408 | // 按下时做点什么 | 355 | // 按下时做点什么 |
409 | } else { | 356 | } else { |
410 | // 释时做点什么 | 357 | // 起时做点什么 |
411 | } | 358 | } |
412 | return false; // 跳过此键的进一步处理 | 359 | return false; // 跳过此键的进一步处理 |
413 | case KC_ENTER: | 360 | case KC_ENTER: |
@@ -415,76 +362,116 @@ bool process_record_user(uint16_t keycode, keyrecord_t *record) { | |||
415 | if (record->event.pressed) { | 362 | if (record->event.pressed) { |
416 | PLAY_SONG(tone_qwerty); | 363 | PLAY_SONG(tone_qwerty); |
417 | } | 364 | } |
418 | return true; // 让QMK产生回车按下/释事件 | 365 | return true; // 让QMK产生回车按下/起事件 |
419 | case RGB_LYR: // underglow作为层指示,或正常使用 | 366 | case RGB_LYR: // 这允许光灯作为层指示,或正常用 |
420 | if (record->event.pressed) { | 367 | if (record->event.pressed) { |
421 | user_config.rgb_layer_change ^= 1; // 切换状态 | 368 | user_config.rgb_layer_change ^= 1; // 切换状态 |
422 | eeconfig_update_user(user_config.raw); // 向EEPROM写入新状态 | 369 | eeconfig_update_user(user_config.raw); // 向EEPROM写入新状态 |
423 | if (user_config.rgb_layer_change) { // 如果层态被使能 | 370 | if (user_config.rgb_layer_change) { // 如果层指使能 |
424 | layer_state_set(layer_state); // 那么立刻更新层颜色 | 371 | layer_state_set(layer_state); // 那么立刻更新层颜色 |
425 | } | 372 | } |
426 | } | 373 | } |
427 | return false; | 374 | return false; |
428 | case RGB_MODE_FORWARD ... RGB_MODE_GRADIENT: // 对于所有的RGB代码 (see quantum_keycodes.h, L400 可以参) | 375 | case RGB_MODE_FORWARD ... RGB_MODE_GRADIENT: // 对于所有的RGB代码 (参考 quantum_keycodes.h, 400 处) |
429 | if (record->event.pressed) { //本句失能层指示,假设你了个…你要把它禁用 | 376 | if (record->event.pressed) { // 本句失能层指示功能,假设你现在要调该…你要把它禁用 |
430 | if (user_config.rgb_layer_change) { // 仅当使能时 | 377 | if (user_config.rgb_layer_change) { // 仅当使能时 |
431 | user_config.rgb_layer_change = false; // 失能,然后 | 378 | user_config.rgb_layer_change = false; // 失能,然后 |
432 | eeconfig_update_user(user_config.raw); // 向EEPROM写入设置 | 379 | eeconfig_update_user(user_config.raw); // 向EEPROM写入设置 |
433 | } | 380 | } |
434 | } | 381 | } |
435 | return true; break; | 382 | return true; break; |
436 | default: | 383 | default: |
437 | return true; // 按其他键正常 | 384 | return true; // 其他键码正常处理 |
438 | } | 385 | } |
439 | } | 386 | } |
440 | ``` | 387 | ``` |
441 | 最后你要加入`eeconfig_init_user`函数,所以当EEPROM重置时,可以指定默认值, 甚至自定义操作。想强制重置EEPROM,请用`EEP_RST`键码或[Bootmagic](feature_bootmagic.md)函数。比如,如果要在默认情况下设置RGB层指示,并保存默认值 | 388 | 最后,须添加 `eeconfig_init_user` 函数,从而当EEPROM重置时,可以指定默认值, 甚至自定义操作。若想强制重置EEPROM,请用 `EEP_RST` 键码或[Bootmagic](zh-cn/feature_bootmagic.md) 功能。比如,在你想重置RGB层指示配置,并保存默认值时。 |
442 | 389 | ||
443 | ```c | 390 | ```c |
444 | void eeconfig_init_user(void) { // EEPROM正被重置 | 391 | void eeconfig_init_user(void) { // EEPROM被重置 |
445 | user_config.raw = 0; | 392 | user_config.raw = 0; |
446 | user_config.rgb_layer_change = true; // 我们想要默认使能 | 393 | user_config.rgb_layer_change = true; // 我们想要默认使能 |
447 | eeconfig_update_user(user_config.raw); // 向EEPROM写入默认值 | 394 | eeconfig_update_user(user_config.raw); // 向EEPROM写入默认值 |
448 | 395 | ||
449 | // use the non noeeprom versions, 还要EEPROM写入这些 | 396 | // 通过使用非'noeeprom'版本的数,可以时写入这些配置EEPROM中 |
450 | rgblight_enable(); // 默认使能RGB | 397 | rgblight_enable(); // 默认使能RGB |
451 | rgblight_sethsv_cyan(); // 默认设置青色 | 398 | rgblight_sethsv_cyan(); // 默认设置青色 |
452 | rgblight_mode(1); // 默认设置长亮 | 399 | rgblight_mode(1); // 默认设置长亮 |
453 | } | 400 | } |
454 | ``` | 401 | ``` |
455 | 402 | ||
456 | 然后就完事了。RGB层指示会在你想让它工作时工作。这个设置会一直保存,即便你拔下键盘。如果你使用其他RGB代码,层指示将失能,现在它可以做你所想了。 | 403 | 一切已就绪,RGB层指示将在需要时生效。这个设置会持久存储,即便是拔下键盘。如果你使用其他RGB码,层指示将失效,从而可以停留在期望的模式及颜色下。 |
457 | 404 | ||
458 | ### 'EECONFIG' 函数文档 | 405 | ### 'EECONFIG' 函数文档 |
459 | 406 | ||
460 | * 键盘/修订: `void eeconfig_init_kb(void)`, `uint32_t eeconfig_read_kb(void)`和`void eeconfig_update_kb(uint32_t val)` | 407 | * 键盘/各子版本:`void eeconfig_init_kb(void)`, `uint32_t eeconfig_read_kb(void)` 和 `void eeconfig_update_kb(uint32_t val)` |
461 | * 布局: `void eeconfig_init_user(void)`, `uint32_t eeconfig_read_user(void)`和`void eeconfig_update_user(uint32_t val)` | 408 | * 键映射:`void eeconfig_init_user(void)`, `uint32_t eeconfig_read_user(void)` 和 `void eeconfig_update_user(uint32_t val)` |
462 | 409 | ||
463 | `val` 是你想写入EEPROM的值,`eeconfig_read_*`函数会从EEPROM返回一个32位(双字)的值。 | 410 | `val` 是你想写入EEPROM的值,`eeconfig_read_*`函数会从EEPROM返回一个32位(双字)的值。 |
464 | 411 | ||
465 | # 自定义击键-长按临界值(TAPPING_TERM) | 412 | ### 定时执行 :id=deferred-execution |
466 | 默认情况下,击键-长按临界值是全球统一的,并且不能通过键进行配置。对于大多数用户来说这很好。但是在有些情况下,对于`LT`键来说按键延时对双功能键的提升更大,可能是因为有些键比其他的键更容易按住。为了不给每个都自定义键码,本功能可以为每个键定义`TAPPING_TERM`。 | ||
467 | |||
468 | 想使能这个功能的话, 要先在`config.h`加上`#define TAPPING_TERM_PER_KEY`。 | ||
469 | 413 | ||
414 | QMK支持在特定时间间隔后执行回调,以代替手动的计时器管理。 | ||
470 | 415 | ||
471 | ## `get_tapping_term`示例实 | 416 | #### 时函数 |
472 | 417 | ||
473 | 要修基于键`TAPPING TERM`,要`keymap.c`件如下代码: | 418 | 的 _定时回调函数_ 用的签,如下: |
474 | 419 | ||
475 | ```c | 420 | ```c |
476 | uint16_t get_tapping_term(uint16_t keycode, keyrecord_t *record) { | 421 | uint32_t my_callback(uint32_t trigger_time, void *cb_arg) { |
477 | switch (keycode) { | 422 | /* 处理了一些工作 */ |
478 | case SFT_T(KC_SPC): | 423 | bool repeat = my_deferred_functionality(); |
479 | return TAPPING_TERM + 1250; | 424 | return repeat ? 500 : 0; |
480 | case LT(1, KC_GRV): | ||
481 | return 130; | ||
482 | default: | ||
483 | return TAPPING_TERM; | ||
484 | } | ||
485 | } | 425 | } |
486 | ``` | 426 | ``` |
487 | 427 | ||
488 | ### `get_tapping_term` 函数文档 | 428 | 第一个参数 `trigger_time` 为预期的执行时间,如果因为其它事情造成了延迟未能在准确的时间点执行,可以利用这个参数“追赶”或者跳过这次间隔,取决于你的目的是什么。 |
429 | |||
430 | 第二个参数 `cb_arg` 为下述的 `defer_exec()` 传入的参数,由此可以获取调用时的状态信息。 | ||
431 | |||
432 | 返回值为该函数下一次期望被回调的时间间隔毫秒数 -- 若返回 `0` 则会自动被注销掉。上例中,通过执行假想的 `my_deferred_functionality()` 函数来决策回调是否继续下去 -- 若是,则给出一个 `500` 毫秒的延迟计划,否则,返回 `0` 来告知定时处理后台任务该计划已执行完毕。 | ||
433 | |||
434 | ?> 须留意返回的延时时间是相对原定的触发时间点的,而不是回调执行完的时间点。这样可以防止偶发的执行延迟影响稳定的定时事件计划。 | ||
435 | |||
436 | #### 注册定时回调 | ||
437 | |||
438 | 在定义好回调后,通过如下API进行定时回调注册: | ||
439 | |||
440 | ```c | ||
441 | deferred_token my_token = defer_exec(1500, my_callback, NULL); | ||
442 | ``` | ||
443 | |||
444 | 第一个参数为执行 `my_callback` 的毫秒时间延迟 -- 上例中为 `1500` 毫秒,即 1.5 秒。 | ||
445 | |||
446 | 第三个参数为回调执行时传入的 `cb_arg` 参数。须确保该值在回调时依旧有效 -- 局部函数内的变量会在回调执行前就被释放掉因此不能用。如果并不需要这个参数,可以传入 `NULL`。 | ||
447 | |||
448 | 返回值 `deferred_token` 可被用于在回调执行前取消该定时计划。如果该函数调用失败,会返回 `INVALID_DEFERRED_TOKEN`,一般错误原因是延时值被设置为 `0` 或回调函数参数为 `NULL`,还有一种可能是已有过量的回调在等待被处理 -- 可以按照下述方法修改这个阈值。 | ||
449 | |||
450 | #### 延长定时回调时间 | ||
451 | |||
452 | 由 `defer_exec()` 返回的 `deferred_token` 可以用来修改回调执行所需等待的时延值: | ||
453 | ```c | ||
454 | // 重新调整 my_token 后续的执行计划为当前时间起800ms后 | ||
455 | extend_deferred_exec(my_token, 800); | ||
456 | ``` | ||
457 | |||
458 | #### 取消定时回调 | ||
459 | |||
460 | 由 `defer_exec()` 返回的 `deferred_token` 可以用来取消掉后续的执行计划: | ||
461 | ```c | ||
462 | // 取消 my_token 的后续回调 | ||
463 | cancel_deferred_exec(my_token); | ||
464 | ``` | ||
465 | |||
466 | 一旦 token 被取消了,即视为不再可用。重新使用该 token 是不支持的。 | ||
467 | |||
468 | #### 定时回调的限制 | ||
469 | |||
470 | 可安排的定时回调计划数量是有限的,由 `MAX_DEFERRED_EXECUTORS` 定义的值确定。 | ||
471 | |||
472 | 如果定时回调注册失败了,可以在对应的键盘或键映射下的 `config.h` 文件中修改该值,比如将默认的 8 改为 16: | ||
473 | |||
474 | ```c | ||
475 | #define MAX_DEFERRED_EXECUTORS 16 | ||
476 | ``` | ||
489 | 477 | ||
490 | 不像这篇的其他功能,这个不需要quantum或者键盘级别的函数,只要用户级函数即可。 | ||