keyboard stuff
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 "eeconfig.h"
20#include "action.h"
21#include "action_util.h"
22#include "host.h"
23#include "keycode.h"
24#include "wait.h"
25#include "send_string.h"
26#include "utf8.h"
27#include "debug.h"
28#include "quantum.h"
29
30#if defined(AUDIO_ENABLE)
31# include "audio.h"
32#endif
33
34#if defined(UNICODE_ENABLE) + defined(UNICODEMAP_ENABLE) + defined(UCIS_ENABLE) > 1
35# error "Cannot enable more than one Unicode method (UNICODE, UNICODEMAP, UCIS) at the same time"
36#endif
37
38// Keycodes used for starting Unicode input on different platforms
39#ifndef UNICODE_KEY_MAC
40# define UNICODE_KEY_MAC KC_LEFT_ALT
41#endif
42#ifndef UNICODE_KEY_LNX
43# define UNICODE_KEY_LNX LCTL(LSFT(KC_U))
44#endif
45#ifndef UNICODE_KEY_WINC
46# define UNICODE_KEY_WINC KC_RIGHT_ALT
47#endif
48
49// Comma-delimited, ordered list of input modes selected for use (e.g. in cycle)
50// Example: #define UNICODE_SELECTED_MODES UNICODE_MODE_WINCOMPOSE, UNICODE_MODE_LINUX
51#ifndef UNICODE_SELECTED_MODES
52# define UNICODE_SELECTED_MODES -1
53#endif
54
55// Whether input mode changes in cycle should be written to EEPROM
56#ifndef UNICODE_CYCLE_PERSIST
57# define UNICODE_CYCLE_PERSIST true
58#endif
59
60// Delay between starting Unicode input and sending a sequence, in ms
61#ifndef UNICODE_TYPE_DELAY
62# define UNICODE_TYPE_DELAY 10
63#endif
64
65unicode_config_t unicode_config;
66uint8_t unicode_saved_mods;
67led_t unicode_saved_led_state;
68
69#if UNICODE_SELECTED_MODES != -1
70static uint8_t selected[] = {UNICODE_SELECTED_MODES};
71static int8_t selected_count = ARRAY_SIZE(selected);
72static int8_t selected_index;
73#endif
74
75__attribute__((weak)) void unicode_input_mode_set_user(uint8_t input_mode) {}
76
77__attribute__((weak)) void unicode_input_mode_set_kb(uint8_t input_mode) {
78 unicode_input_mode_set_user(input_mode);
79}
80
81#ifdef AUDIO_ENABLE
82# ifdef UNICODE_SONG_MAC
83static float song_mac[][2] = UNICODE_SONG_MAC;
84# endif
85# ifdef UNICODE_SONG_LNX
86static float song_lnx[][2] = UNICODE_SONG_LNX;
87# endif
88# ifdef UNICODE_SONG_WIN
89static float song_win[][2] = UNICODE_SONG_WIN;
90# endif
91# ifdef UNICODE_SONG_BSD
92static float song_bsd[][2] = UNICODE_SONG_BSD;
93# endif
94# ifdef UNICODE_SONG_WINC
95static float song_winc[][2] = UNICODE_SONG_WINC;
96# endif
97# ifdef UNICODE_SONG_EMACS
98static float song_emacs[][2] = UNICODE_SONG_EMACS;
99# endif
100
101static void unicode_play_song(uint8_t mode) {
102 switch (mode) {
103# ifdef UNICODE_SONG_MAC
104 case UNICODE_MODE_MACOS:
105 PLAY_SONG(song_mac);
106 break;
107# endif
108# ifdef UNICODE_SONG_LNX
109 case UNICODE_MODE_LINUX:
110 PLAY_SONG(song_lnx);
111 break;
112# endif
113# ifdef UNICODE_SONG_WIN
114 case UNICODE_MODE_WINDOWS:
115 PLAY_SONG(song_win);
116 break;
117# endif
118# ifdef UNICODE_SONG_BSD
119 case UNICODE_MODE_BSD:
120 PLAY_SONG(song_bsd);
121 break;
122# endif
123# ifdef UNICODE_SONG_WINC
124 case UNICODE_MODE_WINCOMPOSE:
125 PLAY_SONG(song_winc);
126 break;
127# endif
128# ifdef UNICODE_SONG_EMACS
129 case UNICODE_MODE_EMACS:
130 PLAY_SONG(song_emacs);
131 break;
132# endif
133 }
134}
135#endif
136
137void unicode_input_mode_init(void) {
138 eeconfig_read_unicode_mode(&unicode_config);
139#if UNICODE_SELECTED_MODES != -1
140# if UNICODE_CYCLE_PERSIST
141 // Find input_mode in selected modes
142 int8_t i;
143 for (i = 0; i < selected_count; i++) {
144 if (selected[i] == unicode_config.input_mode) {
145 selected_index = i;
146 break;
147 }
148 }
149 if (i == selected_count) {
150 // Not found: input_mode isn't selected, change to one that is
151 unicode_config.input_mode = selected[selected_index = 0];
152 }
153# else
154 // Always change to the first selected input mode
155 unicode_config.input_mode = selected[selected_index = 0];
156# endif
157#endif
158 unicode_input_mode_set_kb(unicode_config.input_mode);
159 dprintf("Unicode input mode init to: %u\n", unicode_config.input_mode);
160}
161
162uint8_t get_unicode_input_mode(void) {
163 return unicode_config.input_mode;
164}
165
166static void persist_unicode_input_mode(void) {
167 eeconfig_update_unicode_mode(&unicode_config);
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
180static void 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
187 unicode_config.input_mode = selected[selected_index];
188
189# if UNICODE_CYCLE_PERSIST
190 persist_unicode_input_mode();
191# endif
192
193# ifdef AUDIO_ENABLE
194 unicode_play_song(unicode_config.input_mode);
195# endif
196
197 unicode_input_mode_set_kb(unicode_config.input_mode);
198 dprintf("Unicode input mode cycle to: %u\n", unicode_config.input_mode);
199#endif
200}
201
202void unicode_input_mode_step(void) {
203 cycle_unicode_input_mode(1);
204}
205
206void unicode_input_mode_step_reverse(void) {
207 cycle_unicode_input_mode(-1);
208}
209
210__attribute__((weak)) void unicode_input_start(void) {
211 unicode_saved_led_state = host_keyboard_led_state();
212
213 // Note the order matters here!
214 // Need to do this before we mess around with the mods, or else
215 // UNICODE_KEY_LNX (which is usually Ctrl-Shift-U) might not work
216 // correctly in the shifted case.
217 if (unicode_config.input_mode == UNICODE_MODE_LINUX && unicode_saved_led_state.caps_lock) {
218 tap_code(KC_CAPS_LOCK);
219 }
220
221 unicode_saved_mods = get_mods(); // Save current mods
222 clear_mods(); // Unregister mods to start from a clean state
223 clear_weak_mods();
224
225 switch (unicode_config.input_mode) {
226 case UNICODE_MODE_MACOS:
227 register_code(UNICODE_KEY_MAC);
228 break;
229 case UNICODE_MODE_LINUX:
230 tap_code16(UNICODE_KEY_LNX);
231 break;
232 case UNICODE_MODE_WINDOWS:
233 // For increased reliability, use numpad keys for inputting digits
234 if (!unicode_saved_led_state.num_lock) {
235 tap_code(KC_NUM_LOCK);
236 }
237 register_code(KC_LEFT_ALT);
238 wait_ms(UNICODE_TYPE_DELAY);
239 tap_code(KC_KP_PLUS);
240 break;
241 case UNICODE_MODE_WINCOMPOSE:
242 tap_code(UNICODE_KEY_WINC);
243 tap_code(KC_U);
244 break;
245 case UNICODE_MODE_EMACS:
246 // The usual way to type unicode in emacs is C-x-8 <RET> then the unicode number in hex
247 tap_code16(LCTL(KC_X));
248 tap_code16(KC_8);
249 tap_code16(KC_ENTER);
250 break;
251 }
252
253 wait_ms(UNICODE_TYPE_DELAY);
254}
255
256__attribute__((weak)) void unicode_input_finish(void) {
257 switch (unicode_config.input_mode) {
258 case UNICODE_MODE_MACOS:
259 unregister_code(UNICODE_KEY_MAC);
260 break;
261 case UNICODE_MODE_LINUX:
262 tap_code(KC_SPACE);
263 if (unicode_saved_led_state.caps_lock) {
264 tap_code(KC_CAPS_LOCK);
265 }
266 break;
267 case UNICODE_MODE_WINDOWS:
268 unregister_code(KC_LEFT_ALT);
269 if (!unicode_saved_led_state.num_lock) {
270 tap_code(KC_NUM_LOCK);
271 }
272 break;
273 case UNICODE_MODE_WINCOMPOSE:
274 tap_code(KC_ENTER);
275 break;
276 case UNICODE_MODE_EMACS:
277 tap_code16(KC_ENTER);
278 break;
279 }
280
281 set_mods(unicode_saved_mods); // Reregister previously set mods
282}
283
284__attribute__((weak)) void unicode_input_cancel(void) {
285 switch (unicode_config.input_mode) {
286 case UNICODE_MODE_MACOS:
287 unregister_code(UNICODE_KEY_MAC);
288 break;
289 case UNICODE_MODE_LINUX:
290 tap_code(KC_ESCAPE);
291 if (unicode_saved_led_state.caps_lock) {
292 tap_code(KC_CAPS_LOCK);
293 }
294 break;
295 case UNICODE_MODE_WINCOMPOSE:
296 tap_code(KC_ESCAPE);
297 break;
298 case UNICODE_MODE_WINDOWS:
299 unregister_code(KC_LEFT_ALT);
300 if (!unicode_saved_led_state.num_lock) {
301 tap_code(KC_NUM_LOCK);
302 }
303 break;
304 case UNICODE_MODE_EMACS:
305 tap_code16(LCTL(KC_G)); // C-g cancels
306 break;
307 }
308
309 set_mods(unicode_saved_mods); // Reregister previously set mods
310}
311
312// clang-format off
313
314static void send_nibble_wrapper(uint8_t digit) {
315 if (unicode_config.input_mode == UNICODE_MODE_WINDOWS) {
316 uint8_t kc = digit < 10
317 ? KC_KP_1 + (10 + digit - 1) % 10
318 : KC_A + (digit - 10);
319 tap_code(kc);
320 return;
321 }
322 send_nibble(digit);
323}
324
325// clang-format on
326
327void register_hex(uint16_t hex) {
328 for (int i = 3; i >= 0; i--) {
329 uint8_t digit = ((hex >> (i * 4)) & 0xF);
330 send_nibble_wrapper(digit);
331 }
332}
333
334void register_hex32(uint32_t hex) {
335 bool first_digit = true;
336 bool needs_leading_zero = (unicode_config.input_mode == UNICODE_MODE_WINCOMPOSE);
337 for (int i = 7; i >= 0; i--) {
338 // Work out the digit we're going to transmit
339 uint8_t digit = ((hex >> (i * 4)) & 0xF);
340
341 // If we're still searching for the first digit, and found one
342 // that needs a leading zero sent out, send the zero.
343 if (first_digit && needs_leading_zero && digit > 9) {
344 send_nibble_wrapper(0);
345 }
346
347 // Always send digits (including zero) if we're down to the last
348 // two bytes of nibbles.
349 bool must_send = i < 4;
350
351 // If we've found a digit worth transmitting, do so.
352 if (digit != 0 || !first_digit || must_send) {
353 send_nibble_wrapper(digit);
354 first_digit = false;
355 }
356 }
357}
358
359void register_unicode(uint32_t code_point) {
360 if (code_point > 0x10FFFF || (code_point > 0xFFFF && unicode_config.input_mode == UNICODE_MODE_WINDOWS)) {
361 // Code point out of range, do nothing
362 return;
363 }
364
365 unicode_input_start();
366 if (code_point > 0xFFFF && unicode_config.input_mode == UNICODE_MODE_MACOS) {
367 // Convert code point to UTF-16 surrogate pair on macOS
368 code_point -= 0x10000;
369 uint32_t lo = code_point & 0x3FF, hi = (code_point & 0xFFC00) >> 10;
370 register_hex32(hi + 0xD800);
371 register_hex32(lo + 0xDC00);
372 } else {
373 register_hex32(code_point);
374 }
375 unicode_input_finish();
376}
377
378void send_unicode_string(const char *str) {
379 if (!str) {
380 return;
381 }
382
383 while (*str) {
384 int32_t code_point = 0;
385 str = decode_utf8(str, &code_point);
386
387 if (code_point >= 0) {
388 register_unicode(code_point);
389 }
390 }
391}