summaryrefslogtreecommitdiff
path: root/quantum/unicode
diff options
context:
space:
mode:
authorRyan <fauxpark@gmail.com>2022-09-13 01:49:04 +1000
committerGitHub <noreply@github.com>2022-09-12 08:49:04 -0700
commit3d667f09705fa780bd5881cfa3b3cb10fa41b1fe (patch)
treeb05d0a4cd413947c1ae09fc08c33f2411029d29e /quantum/unicode
parent4087251da6ffcbfd16a9b655b7816803a565f0a6 (diff)
Refactor Unicode feature (#18333)
Diffstat (limited to 'quantum/unicode')
-rw-r--r--quantum/unicode/unicode.c376
-rw-r--r--quantum/unicode/unicode.h165
-rw-r--r--quantum/unicode/utf8.c46
-rw-r--r--quantum/unicode/utf8.h21
4 files changed, 608 insertions, 0 deletions
diff --git a/quantum/unicode/unicode.c b/quantum/unicode/unicode.c
new file mode 100644
index 0000000000..f9f429e7af
--- /dev/null
+++ b/quantum/unicode/unicode.c
@@ -0,0 +1,376 @@
1/* Copyright 2022
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "unicode.h"
18
19#include "eeprom.h"
20#include "eeconfig.h"
21#include "action.h"
22#include "action_util.h"
23#include "host.h"
24#include "keycode.h"
25#include "wait.h"
26#include "audio.h"
27#include "send_string.h"
28#include "utf8.h"
29
30#if defined(UNICODE_ENABLE) + defined(UNICODEMAP_ENABLE) + defined(UCIS_ENABLE) > 1
31# error "Cannot enable more than one Unicode method (UNICODE, UNICODEMAP, UCIS) at the same time"
32#endif
33
34// Keycodes used for starting Unicode input on different platforms
35#ifndef UNICODE_KEY_MAC
36# define UNICODE_KEY_MAC KC_LEFT_ALT
37#endif
38#ifndef UNICODE_KEY_LNX
39# define UNICODE_KEY_LNX LCTL(LSFT(KC_U))
40#endif
41#ifndef UNICODE_KEY_WINC
42# define UNICODE_KEY_WINC KC_RIGHT_ALT
43#endif
44
45// Comma-delimited, ordered list of input modes selected for use (e.g. in cycle)
46// Example: #define UNICODE_SELECTED_MODES UC_WINC, UC_LNX
47#ifndef UNICODE_SELECTED_MODES
48# define UNICODE_SELECTED_MODES -1
49#endif
50
51// Whether input mode changes in cycle should be written to EEPROM
52#ifndef UNICODE_CYCLE_PERSIST
53# define UNICODE_CYCLE_PERSIST true
54#endif
55
56// Delay between starting Unicode input and sending a sequence, in ms
57#ifndef UNICODE_TYPE_DELAY
58# define UNICODE_TYPE_DELAY 10
59#endif
60
61unicode_config_t unicode_config;
62uint8_t unicode_saved_mods;
63led_t unicode_saved_led_state;
64
65#if UNICODE_SELECTED_MODES != -1
66static uint8_t selected[] = {UNICODE_SELECTED_MODES};
67static int8_t selected_count = ARRAY_SIZE(selected);
68static int8_t selected_index;
69#endif
70
71/** \brief unicode input mode set at user level
72 *
73 * Run user code on unicode input mode change
74 */
75__attribute__((weak)) void unicode_input_mode_set_user(uint8_t input_mode) {}
76
77/** \brief unicode input mode set at keyboard level
78 *
79 * Run keyboard code on unicode input mode change
80 */
81__attribute__((weak)) void unicode_input_mode_set_kb(uint8_t input_mode) {
82 unicode_input_mode_set_user(input_mode);
83}
84
85#ifdef AUDIO_ENABLE
86# ifdef UNICODE_SONG_MAC
87static float song_mac[][2] = UNICODE_SONG_MAC;
88# endif
89# ifdef UNICODE_SONG_LNX
90static float song_lnx[][2] = UNICODE_SONG_LNX;
91# endif
92# ifdef UNICODE_SONG_WIN
93static float song_win[][2] = UNICODE_SONG_WIN;
94# endif
95# ifdef UNICODE_SONG_BSD
96static float song_bsd[][2] = UNICODE_SONG_BSD;
97# endif
98# ifdef UNICODE_SONG_WINC
99static float song_winc[][2] = UNICODE_SONG_WINC;
100# endif
101# ifdef UNICODE_SONG_EMACS
102static float song_emacs[][2] = UNICODE_SONG_EMACS;
103# endif
104
105static void unicode_play_song(uint8_t mode) {
106 switch (mode) {
107# ifdef UNICODE_SONG_MAC
108 case UC_MAC:
109 PLAY_SONG(song_mac);
110 break;
111# endif
112# ifdef UNICODE_SONG_LNX
113 case UC_LNX:
114 PLAY_SONG(song_lnx);
115 break;
116# endif
117# ifdef UNICODE_SONG_WIN
118 case UC_WIN:
119 PLAY_SONG(song_win);
120 break;
121# endif
122# ifdef UNICODE_SONG_BSD
123 case UC_BSD:
124 PLAY_SONG(song_bsd);
125 break;
126# endif
127# ifdef UNICODE_SONG_WINC
128 case UC_WINC:
129 PLAY_SONG(song_winc);
130 break;
131# endif
132# ifdef UNICODE_SONG_EMACS
133 case UC_EMACS:
134 PLAY_SONG(song_emacs);
135 break;
136# endif
137 }
138}
139#endif
140
141void unicode_input_mode_init(void) {
142 unicode_config.raw = eeprom_read_byte(EECONFIG_UNICODEMODE);
143#if UNICODE_SELECTED_MODES != -1
144# if UNICODE_CYCLE_PERSIST
145 // Find input_mode in selected modes
146 int8_t i;
147 for (i = 0; i < selected_count; i++) {
148 if (selected[i] == unicode_config.input_mode) {
149 selected_index = i;
150 break;
151 }
152 }
153 if (i == selected_count) {
154 // Not found: input_mode isn't selected, change to one that is
155 unicode_config.input_mode = selected[selected_index = 0];
156 }
157# else
158 // Always change to the first selected input mode
159 unicode_config.input_mode = selected[selected_index = 0];
160# endif
161#endif
162 unicode_input_mode_set_kb(unicode_config.input_mode);
163 dprintf("Unicode input mode init to: %u\n", unicode_config.input_mode);
164}
165
166uint8_t get_unicode_input_mode(void) {
167 return unicode_config.input_mode;
168}
169
170void set_unicode_input_mode(uint8_t mode) {
171 unicode_config.input_mode = mode;
172 persist_unicode_input_mode();
173#ifdef AUDIO_ENABLE
174 unicode_play_song(mode);
175#endif
176 unicode_input_mode_set_kb(mode);
177 dprintf("Unicode input mode set to: %u\n", unicode_config.input_mode);
178}
179
180void cycle_unicode_input_mode(int8_t offset) {
181#if UNICODE_SELECTED_MODES != -1
182 selected_index = (selected_index + offset) % selected_count;
183 if (selected_index < 0) {
184 selected_index += selected_count;
185 }
186 unicode_config.input_mode = selected[selected_index];
187# if UNICODE_CYCLE_PERSIST
188 persist_unicode_input_mode();
189# endif
190# ifdef AUDIO_ENABLE
191 unicode_play_song(unicode_config.input_mode);
192# endif
193 unicode_input_mode_set_kb(unicode_config.input_mode);
194 dprintf("Unicode input mode cycle to: %u\n", unicode_config.input_mode);
195#endif
196}
197
198void persist_unicode_input_mode(void) {
199 eeprom_update_byte(EECONFIG_UNICODEMODE, unicode_config.input_mode);
200}
201
202__attribute__((weak)) void unicode_input_start(void) {
203 unicode_saved_led_state = host_keyboard_led_state();
204
205 // Note the order matters here!
206 // Need to do this before we mess around with the mods, or else
207 // UNICODE_KEY_LNX (which is usually Ctrl-Shift-U) might not work
208 // correctly in the shifted case.
209 if (unicode_config.input_mode == UC_LNX && unicode_saved_led_state.caps_lock) {
210 tap_code(KC_CAPS_LOCK);
211 }
212
213 unicode_saved_mods = get_mods(); // Save current mods
214 clear_mods(); // Unregister mods to start from a clean state
215 clear_weak_mods();
216
217 switch (unicode_config.input_mode) {
218 case UC_MAC:
219 register_code(UNICODE_KEY_MAC);
220 break;
221 case UC_LNX:
222 tap_code16(UNICODE_KEY_LNX);
223 break;
224 case UC_WIN:
225 // For increased reliability, use numpad keys for inputting digits
226 if (!unicode_saved_led_state.num_lock) {
227 tap_code(KC_NUM_LOCK);
228 }
229 register_code(KC_LEFT_ALT);
230 wait_ms(UNICODE_TYPE_DELAY);
231 tap_code(KC_KP_PLUS);
232 break;
233 case UC_WINC:
234 tap_code(UNICODE_KEY_WINC);
235 tap_code(KC_U);
236 break;
237 case UC_EMACS:
238 // The usual way to type unicode in emacs is C-x-8 <RET> then the unicode number in hex
239 tap_code16(LCTL(KC_X));
240 tap_code16(KC_8);
241 tap_code16(KC_ENTER);
242 break;
243 }
244
245 wait_ms(UNICODE_TYPE_DELAY);
246}
247
248__attribute__((weak)) void unicode_input_finish(void) {
249 switch (unicode_config.input_mode) {
250 case UC_MAC:
251 unregister_code(UNICODE_KEY_MAC);
252 break;
253 case UC_LNX:
254 tap_code(KC_SPACE);
255 if (unicode_saved_led_state.caps_lock) {
256 tap_code(KC_CAPS_LOCK);
257 }
258 break;
259 case UC_WIN:
260 unregister_code(KC_LEFT_ALT);
261 if (!unicode_saved_led_state.num_lock) {
262 tap_code(KC_NUM_LOCK);
263 }
264 break;
265 case UC_WINC:
266 tap_code(KC_ENTER);
267 break;
268 case UC_EMACS:
269 tap_code16(KC_ENTER);
270 break;
271 }
272
273 set_mods(unicode_saved_mods); // Reregister previously set mods
274}
275
276__attribute__((weak)) void unicode_input_cancel(void) {
277 switch (unicode_config.input_mode) {
278 case UC_MAC:
279 unregister_code(UNICODE_KEY_MAC);
280 break;
281 case UC_LNX:
282 tap_code(KC_ESCAPE);
283 if (unicode_saved_led_state.caps_lock) {
284 tap_code(KC_CAPS_LOCK);
285 }
286 break;
287 case UC_WINC:
288 tap_code(KC_ESCAPE);
289 break;
290 case UC_WIN:
291 unregister_code(KC_LEFT_ALT);
292 if (!unicode_saved_led_state.num_lock) {
293 tap_code(KC_NUM_LOCK);
294 }
295 break;
296 case UC_EMACS:
297 tap_code16(LCTL(KC_G)); // C-g cancels
298 break;
299 }
300
301 set_mods(unicode_saved_mods); // Reregister previously set mods
302}
303
304// clang-format off
305
306static void send_nibble_wrapper(uint8_t digit) {
307 if (unicode_config.input_mode == UC_WIN) {
308 uint8_t kc = digit < 10
309 ? KC_KP_1 + (10 + digit - 1) % 10
310 : KC_A + (digit - 10);
311 tap_code(kc);
312 return;
313 }
314 send_nibble(digit);
315}
316
317// clang-format on
318
319void register_hex(uint16_t hex) {
320 for (int i = 3; i >= 0; i--) {
321 uint8_t digit = ((hex >> (i * 4)) & 0xF);
322 send_nibble_wrapper(digit);
323 }
324}
325
326void register_hex32(uint32_t hex) {
327 bool onzerostart = true;
328 for (int i = 7; i >= 0; i--) {
329 if (i <= 3) {
330 onzerostart = false;
331 }
332 uint8_t digit = ((hex >> (i * 4)) & 0xF);
333 if (digit == 0) {
334 if (!onzerostart) {
335 send_nibble_wrapper(digit);
336 }
337 } else {
338 send_nibble_wrapper(digit);
339 onzerostart = false;
340 }
341 }
342}
343
344void register_unicode(uint32_t code_point) {
345 if (code_point > 0x10FFFF || (code_point > 0xFFFF && unicode_config.input_mode == UC_WIN)) {
346 // Code point out of range, do nothing
347 return;
348 }
349
350 unicode_input_start();
351 if (code_point > 0xFFFF && unicode_config.input_mode == UC_MAC) {
352 // Convert code point to UTF-16 surrogate pair on macOS
353 code_point -= 0x10000;
354 uint32_t lo = code_point & 0x3FF, hi = (code_point & 0xFFC00) >> 10;
355 register_hex32(hi + 0xD800);
356 register_hex32(lo + 0xDC00);
357 } else {
358 register_hex32(code_point);
359 }
360 unicode_input_finish();
361}
362
363void send_unicode_string(const char *str) {
364 if (!str) {
365 return;
366 }
367
368 while (*str) {
369 int32_t code_point = 0;
370 str = decode_utf8(str, &code_point);
371
372 if (code_point >= 0) {
373 register_unicode(code_point);
374 }
375 }
376}
diff --git a/quantum/unicode/unicode.h b/quantum/unicode/unicode.h
new file mode 100644
index 0000000000..b3e43799ff
--- /dev/null
+++ b/quantum/unicode/unicode.h
@@ -0,0 +1,165 @@
1/* Copyright 2022
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#pragma once
18
19#include <stdint.h>
20
21#include "quantum.h"
22
23typedef union {
24 uint32_t raw;
25 struct {
26 uint8_t input_mode : 8;
27 };
28} unicode_config_t;
29
30extern unicode_config_t unicode_config;
31
32enum unicode_input_modes {
33 UC_MAC, // macOS using Unicode Hex Input
34 UC_LNX, // Linux using IBus
35 UC_WIN, // Windows using EnableHexNumpad
36 UC_BSD, // BSD (not implemented)
37 UC_WINC, // Windows using WinCompose (https://github.com/samhocevar/wincompose)
38 UC_EMACS, // Emacs is an operating system in search of a good text editor
39 UC__COUNT // Number of available input modes (always leave at the end)
40};
41
42void unicode_input_mode_init(void);
43uint8_t get_unicode_input_mode(void);
44void set_unicode_input_mode(uint8_t mode);
45void cycle_unicode_input_mode(int8_t offset);
46void persist_unicode_input_mode(void);
47
48void unicode_input_mode_set_user(uint8_t input_mode);
49void unicode_input_mode_set_kb(uint8_t input_mode);
50
51void unicode_input_start(void);
52void unicode_input_finish(void);
53void unicode_input_cancel(void);
54
55void register_hex(uint16_t hex);
56void register_hex32(uint32_t hex);
57void register_unicode(uint32_t code_point);
58
59void send_unicode_string(const char *str);
60
61// clang-format off
62
63#define UC_BSPC UC(0x0008) // (backspace)
64
65#define UC_SPC UC(0x0020) // (space)
66#define UC_EXLM UC(0x0021) // !
67#define UC_DQUT UC(0x0022) // "
68#define UC_HASH UC(0x0023) // #
69#define UC_DLR UC(0x0024) // $
70#define UC_PERC UC(0x0025) // %
71#define UC_AMPR UC(0x0026) // &
72#define UC_QUOT UC(0x0027) // '
73#define UC_LPRN UC(0x0028) // (
74#define UC_RPRN UC(0x0029) // )
75#define UC_ASTR UC(0x002A) // *
76#define UC_PLUS UC(0x002B) // +
77#define UC_COMM UC(0x002C) // ,
78#define UC_DASH UC(0x002D) // -
79#define UC_DOT UC(0x002E) // .
80#define UC_SLSH UC(0x002F) // /
81
82#define UC_0 UC(0x0030) // 0
83#define UC_1 UC(0x0031) // 1
84#define UC_2 UC(0x0032) // 2
85#define UC_3 UC(0x0033) // 3
86#define UC_4 UC(0x0034) // 4
87#define UC_5 UC(0x0035) // 5
88#define UC_6 UC(0x0036) // 6
89#define UC_7 UC(0x0037) // 7
90#define UC_8 UC(0x0038) // 8
91#define UC_9 UC(0x0039) // 9
92#define UC_COLN UC(0x003A) // :
93#define UC_SCLN UC(0x003B) // ;
94#define UC_LT UC(0x003C) // <
95#define UC_EQL UC(0x003D) // =
96#define UC_GT UC(0x003E) // >
97#define UC_QUES UC(0x003F) // ?
98
99#define UC_AT UC(0x0040) // @
100#define UC_A UC(0x0041) // A
101#define UC_B UC(0x0042) // B
102#define UC_C UC(0x0043) // C
103#define UC_D UC(0x0044) // D
104#define UC_E UC(0x0045) // E
105#define UC_F UC(0x0046) // F
106#define UC_G UC(0x0047) // G
107#define UC_H UC(0x0048) // H
108#define UC_I UC(0x0049) // I
109#define UC_J UC(0x004A) // J
110#define UC_K UC(0x004B) // K
111#define UC_L UC(0x004C) // L
112#define UC_M UC(0x004D) // M
113#define UC_N UC(0x004E) // N
114#define UC_O UC(0x004F) // O
115
116#define UC_P UC(0x0050) // P
117#define UC_Q UC(0x0051) // Q
118#define UC_R UC(0x0052) // R
119#define UC_S UC(0x0053) // S
120#define UC_T UC(0x0054) // T
121#define UC_U UC(0x0055) // U
122#define UC_V UC(0x0056) // V
123#define UC_W UC(0x0057) // W
124#define UC_X UC(0x0058) // X
125#define UC_Y UC(0x0059) // Y
126#define UC_Z UC(0x005A) // Z
127#define UC_LBRC UC(0x005B) // [
128#define UC_BSLS UC(0x005C) // (backslash)
129#define UC_RBRC UC(0x005D) // ]
130#define UC_CIRM UC(0x005E) // ^
131#define UC_UNDR UC(0x005F) // _
132
133#define UC_GRV UC(0x0060) // `
134#define UC_a UC(0x0061) // a
135#define UC_b UC(0x0062) // b
136#define UC_c UC(0x0063) // c
137#define UC_d UC(0x0064) // d
138#define UC_e UC(0x0065) // e
139#define UC_f UC(0x0066) // f
140#define UC_g UC(0x0067) // g
141#define UC_h UC(0x0068) // h
142#define UC_i UC(0x0069) // i
143#define UC_j UC(0x006A) // j
144#define UC_k UC(0x006B) // k
145#define UC_l UC(0x006C) // l
146#define UC_m UC(0x006D) // m
147#define UC_n UC(0x006E) // n
148#define UC_o UC(0x006F) // o
149
150#define UC_p UC(0x0070) // p
151#define UC_q UC(0x0071) // q
152#define UC_r UC(0x0072) // r
153#define UC_s UC(0x0073) // s
154#define UC_t UC(0x0074) // t
155#define UC_u UC(0x0075) // u
156#define UC_v UC(0x0076) // v
157#define UC_w UC(0x0077) // w
158#define UC_x UC(0x0078) // x
159#define UC_y UC(0x0079) // y
160#define UC_z UC(0x007A) // z
161#define UC_LCBR UC(0x007B) // {
162#define UC_PIPE UC(0x007C) // |
163#define UC_RCBR UC(0x007D) // }
164#define UC_TILD UC(0x007E) // ~
165#define UC_DEL UC(0x007F) // (delete)
diff --git a/quantum/unicode/utf8.c b/quantum/unicode/utf8.c
new file mode 100644
index 0000000000..4b2cd4d8d4
--- /dev/null
+++ b/quantum/unicode/utf8.c
@@ -0,0 +1,46 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "utf8.h"
18
19// Borrowed from https://nullprogram.com/blog/2017/10/06/
20const char *decode_utf8(const char *str, int32_t *code_point) {
21 const char *next;
22
23 if (str[0] < 0x80) { // U+0000-007F
24 *code_point = str[0];
25 next = str + 1;
26 } else if ((str[0] & 0xE0) == 0xC0) { // U+0080-07FF
27 *code_point = ((int32_t)(str[0] & 0x1F) << 6) | ((int32_t)(str[1] & 0x3F) << 0);
28 next = str + 2;
29 } else if ((str[0] & 0xF0) == 0xE0) { // U+0800-FFFF
30 *code_point = ((int32_t)(str[0] & 0x0F) << 12) | ((int32_t)(str[1] & 0x3F) << 6) | ((int32_t)(str[2] & 0x3F) << 0);
31 next = str + 3;
32 } else if ((str[0] & 0xF8) == 0xF0 && (str[0] <= 0xF4)) { // U+10000-10FFFF
33 *code_point = ((int32_t)(str[0] & 0x07) << 18) | ((int32_t)(str[1] & 0x3F) << 12) | ((int32_t)(str[2] & 0x3F) << 6) | ((int32_t)(str[3] & 0x3F) << 0);
34 next = str + 4;
35 } else {
36 *code_point = -1;
37 next = str + 1;
38 }
39
40 // part of a UTF-16 surrogate pair - invalid
41 if (*code_point >= 0xD800 && *code_point <= 0xDFFF) {
42 *code_point = -1;
43 }
44
45 return next;
46}
diff --git a/quantum/unicode/utf8.h b/quantum/unicode/utf8.h
new file mode 100644
index 0000000000..521dd1918c
--- /dev/null
+++ b/quantum/unicode/utf8.h
@@ -0,0 +1,21 @@
1/* Copyright 2021 QMK
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#pragma once
18
19#include <stdint.h>
20
21const char *decode_utf8(const char *str, int32_t *code_point);