]> git.gir.st - tmk_keyboard.git/blob - tmk_core/protocol/lufa/lufa.c
core: Fix Oneshot mods key repeat problem #474
[tmk_keyboard.git] / tmk_core / protocol / lufa / lufa.c
1 /*
2 * Copyright 2012 Jun Wako <wakojun@gmail.com>
3 * This file is based on:
4 * LUFA-120219/Demos/Device/Lowlevel/KeyboardMouse
5 * LUFA-120219/Demos/Device/Lowlevel/GenericHID
6 */
7
8 /*
9 LUFA Library
10 Copyright (C) Dean Camera, 2012.
11
12 dean [at] fourwalledcubicle [dot] com
13 www.lufa-lib.org
14 */
15
16 /*
17 Copyright 2012 Dean Camera (dean [at] fourwalledcubicle [dot] com)
18 Copyright 2010 Denver Gingerich (denver [at] ossguy [dot] com)
19
20 Permission to use, copy, modify, distribute, and sell this
21 software and its documentation for any purpose is hereby granted
22 without fee, provided that the above copyright notice appear in
23 all copies and that both that the copyright notice and this
24 permission notice and warranty disclaimer appear in supporting
25 documentation, and that the name of the author not be used in
26 advertising or publicity pertaining to distribution of the
27 software without specific, written prior permission.
28
29 The author disclaim all warranties with regard to this
30 software, including all implied warranties of merchantability
31 and fitness. In no event shall the author be liable for any
32 special, indirect or consequential damages or any damages
33 whatsoever resulting from loss of use, data or profits, whether
34 in an action of contract, negligence or other tortious action,
35 arising out of or in connection with the use or performance of
36 this software.
37 */
38
39 #include "report.h"
40 #include "host.h"
41 #include "host_driver.h"
42 #include "keyboard.h"
43 #include "action.h"
44 #include "led.h"
45 #include "sendchar.h"
46 #include "debug.h"
47 #ifdef SLEEP_LED_ENABLE
48 #include "sleep_led.h"
49 #endif
50 #include "suspend.h"
51 #include "hook.h"
52
53 #ifdef LUFA_DEBUG_SUART
54 #include "avr/suart.h"
55 #endif
56
57 #include "matrix.h"
58 #include "descriptor.h"
59 #include "lufa.h"
60
61
62 //#define LUFA_DEBUG
63
64
65 uint8_t keyboard_idle = 0;
66 /* 0: Boot Protocol, 1: Report Protocol(default) */
67 uint8_t keyboard_protocol = 1;
68 static uint8_t keyboard_led_stats = 0;
69
70 static report_keyboard_t keyboard_report_sent;
71
72
73 /* Host driver */
74 static uint8_t keyboard_leds(void);
75 static void send_keyboard(report_keyboard_t *report);
76 static void send_mouse(report_mouse_t *report);
77 static void send_system(uint16_t data);
78 static void send_consumer(uint16_t data);
79 host_driver_t lufa_driver = {
80 keyboard_leds,
81 send_keyboard,
82 send_mouse,
83 send_system,
84 send_consumer
85 };
86
87
88 /*******************************************************************************
89 * Console
90 ******************************************************************************/
91 #ifdef CONSOLE_ENABLE
92 static void Console_Task(void)
93 {
94 /* Device must be connected and configured for the task to run */
95 if (USB_DeviceState != DEVICE_STATE_Configured)
96 return;
97
98 uint8_t ep = Endpoint_GetCurrentEndpoint();
99
100 #if 0
101 // TODO: impl receivechar()/recvchar()
102 Endpoint_SelectEndpoint(CONSOLE_OUT_EPNUM);
103
104 /* Check to see if a packet has been sent from the host */
105 if (Endpoint_IsOUTReceived())
106 {
107 /* Check to see if the packet contains data */
108 if (Endpoint_IsReadWriteAllowed())
109 {
110 /* Create a temporary buffer to hold the read in report from the host */
111 uint8_t ConsoleData[CONSOLE_EPSIZE];
112
113 /* Read Console Report Data */
114 Endpoint_Read_Stream_LE(&ConsoleData, sizeof(ConsoleData), NULL);
115
116 /* Process Console Report Data */
117 //ProcessConsoleHIDReport(ConsoleData);
118 }
119
120 /* Finalize the stream transfer to send the last packet */
121 Endpoint_ClearOUT();
122 }
123 #endif
124
125 /* IN packet */
126 Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM);
127 if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) {
128 Endpoint_SelectEndpoint(ep);
129 return;
130 }
131
132 // fill empty bank
133 while (Endpoint_IsReadWriteAllowed())
134 Endpoint_Write_8(0);
135
136 // flash senchar packet
137 if (Endpoint_IsINReady()) {
138 Endpoint_ClearIN();
139 }
140
141 Endpoint_SelectEndpoint(ep);
142 }
143 #else
144 static void Console_Task(void)
145 {
146 }
147 #endif
148
149
150 /*******************************************************************************
151 * USB Events
152 ******************************************************************************/
153 /*
154 * Event Order of Plug in:
155 * 0) EVENT_USB_Device_Connect
156 * 1) EVENT_USB_Device_Suspend
157 * 2) EVENT_USB_Device_Reset
158 * 3) EVENT_USB_Device_Wake
159 */
160 void EVENT_USB_Device_Connect(void)
161 {
162 print("[C]");
163 /* For battery powered device */
164 if (!USB_IsInitialized) {
165 USB_Disable();
166 USB_Init();
167 USB_Device_EnableSOFEvents();
168 }
169 }
170
171 void EVENT_USB_Device_Disconnect(void)
172 {
173 print("[D]");
174 /* For battery powered device */
175 USB_IsInitialized = false;
176 /* TODO: This doesn't work. After several plug in/outs can not be enumerated.
177 if (USB_IsInitialized) {
178 USB_Disable(); // Disable all interrupts
179 USB_Controller_Enable();
180 USB_INT_Enable(USB_INT_VBUSTI);
181 }
182 */
183 }
184
185 void EVENT_USB_Device_Reset(void)
186 {
187 #ifdef LUFA_DEBUG
188 print("[R]");
189 #endif
190 }
191
192 void EVENT_USB_Device_Suspend()
193 {
194 #ifdef LUFA_DEBUG
195 print("[S]");
196 #endif
197 hook_usb_suspend_entry();
198 }
199
200 void EVENT_USB_Device_WakeUp()
201 {
202 #ifdef LUFA_DEBUG
203 print("[W]");
204 #endif
205 hook_usb_wakeup();
206 }
207
208 #ifdef CONSOLE_ENABLE
209 static bool console_flush = false;
210 #define CONSOLE_FLUSH_SET(b) do { \
211 uint8_t sreg = SREG; cli(); console_flush = b; SREG = sreg; \
212 } while (0)
213
214 // called every 1ms
215 void EVENT_USB_Device_StartOfFrame(void)
216 {
217 static uint8_t count;
218 if (++count % 50) return;
219 count = 0;
220
221 if (!console_flush) return;
222 Console_Task();
223 console_flush = false;
224 }
225 #endif
226
227 /** Event handler for the USB_ConfigurationChanged event.
228 * This is fired when the host sets the current configuration of the USB device after enumeration.
229 *
230 * ATMega32u2 supports dual bank(ping-pong mode) only on endpoint 3 and 4,
231 * it is safe to use singl bank for all endpoints.
232 */
233 void EVENT_USB_Device_ConfigurationChanged(void)
234 {
235 #ifdef LUFA_DEBUG
236 print("[c]");
237 #endif
238 bool ConfigSuccess = true;
239
240 /* Setup Keyboard HID Report Endpoints */
241 ConfigSuccess &= ENDPOINT_CONFIG(KEYBOARD_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN,
242 KEYBOARD_EPSIZE, ENDPOINT_BANK_SINGLE);
243
244 #ifdef MOUSE_ENABLE
245 /* Setup Mouse HID Report Endpoint */
246 ConfigSuccess &= ENDPOINT_CONFIG(MOUSE_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN,
247 MOUSE_EPSIZE, ENDPOINT_BANK_SINGLE);
248 #endif
249
250 #ifdef EXTRAKEY_ENABLE
251 /* Setup Extra HID Report Endpoint */
252 ConfigSuccess &= ENDPOINT_CONFIG(EXTRAKEY_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN,
253 EXTRAKEY_EPSIZE, ENDPOINT_BANK_SINGLE);
254 #endif
255
256 #ifdef CONSOLE_ENABLE
257 /* Setup Console HID Report Endpoints */
258 ConfigSuccess &= ENDPOINT_CONFIG(CONSOLE_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN,
259 CONSOLE_EPSIZE, ENDPOINT_BANK_SINGLE);
260 #if 0
261 ConfigSuccess &= ENDPOINT_CONFIG(CONSOLE_OUT_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_OUT,
262 CONSOLE_EPSIZE, ENDPOINT_BANK_SINGLE);
263 #endif
264 #endif
265
266 #ifdef NKRO_ENABLE
267 /* Setup NKRO HID Report Endpoints */
268 ConfigSuccess &= ENDPOINT_CONFIG(NKRO_IN_EPNUM, EP_TYPE_INTERRUPT, ENDPOINT_DIR_IN,
269 NKRO_EPSIZE, ENDPOINT_BANK_SINGLE);
270 #endif
271 }
272
273 /*
274 Appendix G: HID Request Support Requirements
275
276 The following table enumerates the requests that need to be supported by various types of HID class devices.
277
278 Device type GetReport SetReport GetIdle SetIdle GetProtocol SetProtocol
279 ------------------------------------------------------------------------------------------
280 Boot Mouse Required Optional Optional Optional Required Required
281 Non-Boot Mouse Required Optional Optional Optional Optional Optional
282 Boot Keyboard Required Optional Required Required Required Required
283 Non-Boot Keybrd Required Optional Required Required Optional Optional
284 Other Device Required Optional Optional Optional Optional Optional
285 */
286 /** Event handler for the USB_ControlRequest event.
287 * This is fired before passing along unhandled control requests to the library for processing internally.
288 */
289 void EVENT_USB_Device_ControlRequest(void)
290 {
291 uint8_t* ReportData = NULL;
292 uint8_t ReportSize = 0;
293
294 /* Handle HID Class specific requests */
295 switch (USB_ControlRequest.bRequest)
296 {
297 case HID_REQ_GetReport:
298 if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
299 {
300 Endpoint_ClearSETUP();
301
302 // Interface
303 switch (USB_ControlRequest.wIndex) {
304 case KEYBOARD_INTERFACE:
305 // TODO: test/check
306 ReportData = (uint8_t*)&keyboard_report_sent;
307 ReportSize = sizeof(keyboard_report_sent);
308 break;
309 }
310
311 /* Write the report data to the control endpoint */
312 Endpoint_Write_Control_Stream_LE(ReportData, ReportSize);
313 Endpoint_ClearOUT();
314 #ifdef LUFA_DEBUG
315 xprintf("[r%d]", USB_ControlRequest.wIndex);
316 #endif
317 }
318
319 break;
320 case HID_REQ_SetReport:
321 if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
322 {
323
324 // Interface
325 switch (USB_ControlRequest.wIndex) {
326 case KEYBOARD_INTERFACE:
327 #ifdef NKRO_ENABLE
328 case NKRO_INTERFACE:
329 #endif
330 Endpoint_ClearSETUP();
331
332 while (!(Endpoint_IsOUTReceived())) {
333 if (USB_DeviceState == DEVICE_STATE_Unattached)
334 return;
335 }
336 keyboard_led_stats = Endpoint_Read_8();
337
338 Endpoint_ClearOUT();
339 Endpoint_ClearStatusStage();
340 #ifdef LUFA_DEBUG
341 xprintf("[L%d]", USB_ControlRequest.wIndex);
342 #endif
343 break;
344 }
345
346 }
347
348 break;
349
350 case HID_REQ_GetProtocol:
351 if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
352 {
353 if (USB_ControlRequest.wIndex == KEYBOARD_INTERFACE) {
354 Endpoint_ClearSETUP();
355 while (!(Endpoint_IsINReady()));
356 Endpoint_Write_8(keyboard_protocol);
357 Endpoint_ClearIN();
358 Endpoint_ClearStatusStage();
359 #ifdef LUFA_DEBUG
360 print("[p]");
361 #endif
362 }
363 }
364
365 break;
366 case HID_REQ_SetProtocol:
367 if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
368 {
369 if (USB_ControlRequest.wIndex == KEYBOARD_INTERFACE) {
370 Endpoint_ClearSETUP();
371 Endpoint_ClearStatusStage();
372
373 keyboard_protocol = (USB_ControlRequest.wValue & 0xFF);
374 clear_keyboard();
375 #ifdef LUFA_DEBUG
376 print("[P]");
377 #endif
378 }
379 }
380
381 break;
382 case HID_REQ_SetIdle:
383 if (USB_ControlRequest.bmRequestType == (REQDIR_HOSTTODEVICE | REQTYPE_CLASS | REQREC_INTERFACE))
384 {
385 Endpoint_ClearSETUP();
386 Endpoint_ClearStatusStage();
387
388 keyboard_idle = ((USB_ControlRequest.wValue & 0xFF00) >> 8);
389 #ifdef LUFA_DEBUG
390 xprintf("[I%d]%d", USB_ControlRequest.wIndex, (USB_ControlRequest.wValue & 0xFF00) >> 8);
391 #endif
392 }
393
394 break;
395 case HID_REQ_GetIdle:
396 if (USB_ControlRequest.bmRequestType == (REQDIR_DEVICETOHOST | REQTYPE_CLASS | REQREC_INTERFACE))
397 {
398 Endpoint_ClearSETUP();
399 while (!(Endpoint_IsINReady()));
400 Endpoint_Write_8(keyboard_idle);
401 Endpoint_ClearIN();
402 Endpoint_ClearStatusStage();
403 #ifdef LUFA_DEBUG
404 print("[i]");
405 #endif
406 }
407
408 break;
409 }
410 }
411
412 /*******************************************************************************
413 * Host driver
414 ******************************************************************************/
415 static uint8_t keyboard_leds(void)
416 {
417 return keyboard_led_stats;
418 }
419
420 static void send_keyboard(report_keyboard_t *report)
421 {
422 uint8_t timeout = 255;
423
424 if (USB_DeviceState != DEVICE_STATE_Configured)
425 return;
426
427 /* Select the Keyboard Report Endpoint */
428 #ifdef NKRO_ENABLE
429 if (keyboard_protocol && keyboard_nkro) {
430 /* Report protocol - NKRO */
431 Endpoint_SelectEndpoint(NKRO_IN_EPNUM);
432
433 /* Check if write ready for a polling interval around 1ms */
434 while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(8);
435 if (!Endpoint_IsReadWriteAllowed()) return;
436
437 /* Write Keyboard Report Data */
438 Endpoint_Write_Stream_LE(report, NKRO_EPSIZE, NULL);
439 }
440 else
441 #endif
442 {
443 /* Boot protocol */
444 Endpoint_SelectEndpoint(KEYBOARD_IN_EPNUM);
445
446 /* Check if write ready for a polling interval around 10ms */
447 while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40);
448 if (!Endpoint_IsReadWriteAllowed()) return;
449
450 /* Write Keyboard Report Data */
451 Endpoint_Write_Stream_LE(report, KEYBOARD_EPSIZE, NULL);
452 }
453
454 /* Finalize the stream transfer to send the last packet */
455 Endpoint_ClearIN();
456
457 keyboard_report_sent = *report;
458 }
459
460 static void send_mouse(report_mouse_t *report)
461 {
462 #ifdef MOUSE_ENABLE
463 uint8_t timeout = 255;
464
465 if (USB_DeviceState != DEVICE_STATE_Configured)
466 return;
467
468 /* Select the Mouse Report Endpoint */
469 Endpoint_SelectEndpoint(MOUSE_IN_EPNUM);
470
471 /* Check if write ready for a polling interval around 10ms */
472 while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40);
473 if (!Endpoint_IsReadWriteAllowed()) return;
474
475 /* Write Mouse Report Data */
476 Endpoint_Write_Stream_LE(report, sizeof(report_mouse_t), NULL);
477
478 /* Finalize the stream transfer to send the last packet */
479 Endpoint_ClearIN();
480 #endif
481 }
482
483 static void send_system(uint16_t data)
484 {
485 uint8_t timeout = 255;
486
487 if (USB_DeviceState != DEVICE_STATE_Configured)
488 return;
489
490 report_extra_t r = {
491 .report_id = REPORT_ID_SYSTEM,
492 .usage = data
493 };
494 Endpoint_SelectEndpoint(EXTRAKEY_IN_EPNUM);
495
496 /* Check if write ready for a polling interval around 10ms */
497 while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40);
498 if (!Endpoint_IsReadWriteAllowed()) return;
499
500 Endpoint_Write_Stream_LE(&r, sizeof(report_extra_t), NULL);
501 Endpoint_ClearIN();
502 }
503
504 static void send_consumer(uint16_t data)
505 {
506 uint8_t timeout = 255;
507
508 if (USB_DeviceState != DEVICE_STATE_Configured)
509 return;
510
511 report_extra_t r = {
512 .report_id = REPORT_ID_CONSUMER,
513 .usage = data
514 };
515 Endpoint_SelectEndpoint(EXTRAKEY_IN_EPNUM);
516
517 /* Check if write ready for a polling interval around 10ms */
518 while (timeout-- && !Endpoint_IsReadWriteAllowed()) _delay_us(40);
519 if (!Endpoint_IsReadWriteAllowed()) return;
520
521 Endpoint_Write_Stream_LE(&r, sizeof(report_extra_t), NULL);
522 Endpoint_ClearIN();
523 }
524
525
526 /*******************************************************************************
527 * sendchar
528 ******************************************************************************/
529 #ifdef CONSOLE_ENABLE
530 #define SEND_TIMEOUT 5
531 int8_t sendchar(uint8_t c)
532 {
533 #ifdef LUFA_DEBUG_SUART
534 xmit(c);
535 #endif
536 // Not wait once timeouted.
537 // Because sendchar() is called so many times, waiting each call causes big lag.
538 static bool timeouted = false;
539
540 // prevents Console_Task() from running during sendchar() runs.
541 // or char will be lost. These two function is mutually exclusive.
542 CONSOLE_FLUSH_SET(false);
543
544 if (USB_DeviceState != DEVICE_STATE_Configured)
545 return -1;
546
547 uint8_t ep = Endpoint_GetCurrentEndpoint();
548 Endpoint_SelectEndpoint(CONSOLE_IN_EPNUM);
549 if (!Endpoint_IsEnabled() || !Endpoint_IsConfigured()) {
550 goto ERROR_EXIT;
551 }
552
553 if (timeouted && !Endpoint_IsReadWriteAllowed()) {
554 goto ERROR_EXIT;
555 }
556
557 timeouted = false;
558
559 uint8_t timeout = SEND_TIMEOUT;
560 while (!Endpoint_IsReadWriteAllowed()) {
561 if (USB_DeviceState != DEVICE_STATE_Configured) {
562 goto ERROR_EXIT;
563 }
564 if (Endpoint_IsStalled()) {
565 goto ERROR_EXIT;
566 }
567 if (!(timeout--)) {
568 timeouted = true;
569 goto ERROR_EXIT;
570 }
571 _delay_ms(1);
572 }
573
574 Endpoint_Write_8(c);
575
576 // send when bank is full
577 if (!Endpoint_IsReadWriteAllowed()) {
578 while (!(Endpoint_IsINReady()));
579 Endpoint_ClearIN();
580 } else {
581 CONSOLE_FLUSH_SET(true);
582 }
583
584 Endpoint_SelectEndpoint(ep);
585 return 0;
586 ERROR_EXIT:
587 Endpoint_SelectEndpoint(ep);
588 return -1;
589 }
590 #else
591 int8_t sendchar(uint8_t c)
592 {
593 #ifdef LUFA_DEBUG_SUART
594 xmit(c);
595 #endif
596 return 0;
597 }
598 #endif
599
600
601 /*******************************************************************************
602 * main
603 ******************************************************************************/
604 static void setup_mcu(void)
605 {
606 /* Disable watchdog if enabled by bootloader/fuses */
607 MCUSR &= ~(1 << WDRF);
608 wdt_disable();
609
610 /* Disable clock division */
611 clock_prescale_set(clock_div_1);
612 }
613
614 static void setup_usb(void)
615 {
616 // Leonardo needs. Without this USB device is not recognized.
617 USB_Disable();
618
619 USB_Init();
620
621 // for Console_Task
622 USB_Device_EnableSOFEvents();
623 }
624
625 int main(void) __attribute__ ((weak));
626 int main(void)
627 {
628 setup_mcu();
629
630 #ifdef LUFA_DEBUG_SUART
631 SUART_OUT_DDR |= (1<<SUART_OUT_BIT);
632 SUART_OUT_PORT |= (1<<SUART_OUT_BIT);
633 #endif
634 print_set_sendchar(sendchar);
635 print("\r\ninit\n");
636
637 hook_early_init();
638 keyboard_setup();
639 setup_usb();
640 sei();
641
642 /* wait for USB startup & debug output */
643 while (USB_DeviceState != DEVICE_STATE_Configured) {
644 #if defined(INTERRUPT_CONTROL_ENDPOINT)
645 ;
646 #else
647 USB_USBTask();
648 #endif
649 }
650 print("USB configured.\n");
651
652 /* init modules */
653 keyboard_init();
654 host_set_driver(&lufa_driver);
655 #ifdef SLEEP_LED_ENABLE
656 sleep_led_init();
657 #endif
658
659 print("Keyboard start.\n");
660 hook_late_init();
661 while (1) {
662 while (USB_DeviceState == DEVICE_STATE_Suspended) {
663 #ifdef LUFA_DEBUG
664 print("[s]");
665 #endif
666 hook_usb_suspend_loop();
667 }
668
669 keyboard_task();
670
671 #if !defined(INTERRUPT_CONTROL_ENDPOINT)
672 USB_USBTask();
673 #endif
674 }
675 }
676
677
678 /* hooks */
679 __attribute__((weak))
680 void hook_early_init(void) {}
681
682 __attribute__((weak))
683 void hook_late_init(void) {}
684
685 static uint8_t _led_stats = 0;
686 __attribute__((weak))
687 void hook_usb_suspend_entry(void)
688 {
689 // Turn LED off to save power
690 // Set 0 with putting aside status before suspend and restore
691 // it after wakeup, then LED is updated at keyboard_task() in main loop
692 _led_stats = keyboard_led_stats;
693 keyboard_led_stats = 0;
694 led_set(keyboard_led_stats);
695
696 matrix_clear();
697 clear_keyboard();
698 #ifdef SLEEP_LED_ENABLE
699 sleep_led_enable();
700 #endif
701 }
702
703 __attribute__((weak))
704 void hook_usb_suspend_loop(void)
705 {
706 suspend_power_down();
707 if (USB_Device_RemoteWakeupEnabled && suspend_wakeup_condition()) {
708 USB_Device_SendRemoteWakeup();
709 }
710 }
711
712 __attribute__((weak))
713 void hook_usb_wakeup(void)
714 {
715 suspend_wakeup_init();
716 #ifdef SLEEP_LED_ENABLE
717 sleep_led_disable();
718 #endif
719
720 // Restore LED status
721 // BIOS/grub won't recognize/enumerate if led_set() takes long(around 40ms?)
722 // Converters fall into the case and miss wakeup event(timeout to reply?) in the end.
723 //led_set(host_keyboard_leds());
724 // Instead, restore stats and update at keyboard_task() in main loop
725 keyboard_led_stats = _led_stats;
726 }
Imprint / Impressum