aboutsummaryrefslogtreecommitdiff
path: root/quantum/visualizer/visualizer.c
diff options
context:
space:
mode:
Diffstat (limited to 'quantum/visualizer/visualizer.c')
-rw-r--r--quantum/visualizer/visualizer.c502
1 files changed, 502 insertions, 0 deletions
diff --git a/quantum/visualizer/visualizer.c b/quantum/visualizer/visualizer.c
new file mode 100644
index 000000000..cc99d1e3b
--- /dev/null
+++ b/quantum/visualizer/visualizer.c
@@ -0,0 +1,502 @@
1/*
2The MIT License (MIT)
3
4Copyright (c) 2016 Fred Sundvik
5
6Permission is hereby granted, free of charge, to any person obtaining a copy
7of this software and associated documentation files (the "Software"), to deal
8in the Software without restriction, including without limitation the rights
9to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10copies of the Software, and to permit persons to whom the Software is
11furnished to do so, subject to the following conditions:
12
13The above copyright notice and this permission notice shall be included in all
14copies or substantial portions of the Software.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22SOFTWARE.
23*/
24
25#include "config.h"
26#include "visualizer.h"
27#include <string.h>
28#ifdef PROTOCOL_CHIBIOS
29#include "ch.h"
30#endif
31
32#include "gfx.h"
33
34#ifdef LCD_BACKLIGHT_ENABLE
35#include "lcd_backlight.h"
36#endif
37
38//#define DEBUG_VISUALIZER
39
40#ifdef DEBUG_VISUALIZER
41#include "debug.h"
42#else
43#include "nodebug.h"
44#endif
45
46#ifdef SERIAL_LINK_ENABLE
47#include "serial_link/protocol/transport.h"
48#include "serial_link/system/serial_link.h"
49#endif
50
51#include "action_util.h"
52
53// Define this in config.h
54#ifndef VISUALIZER_THREAD_PRIORITY
55#define "Visualizer thread priority not defined"
56#endif
57
58static visualizer_keyboard_status_t current_status = {
59 .layer = 0xFFFFFFFF,
60 .default_layer = 0xFFFFFFFF,
61 .leds = 0xFFFFFFFF,
62#ifdef BACKLIGHT_ENABLE
63 .backlight_level = 0,
64#endif
65 .mods = 0xFF,
66 .suspended = false,
67#ifdef VISUALIZER_USER_DATA_SIZE
68 .user_data = {0}
69#endif
70};
71
72static bool same_status(visualizer_keyboard_status_t* status1, visualizer_keyboard_status_t* status2) {
73 return status1->layer == status2->layer &&
74 status1->default_layer == status2->default_layer &&
75 status1->mods == status2->mods &&
76 status1->leds == status2->leds &&
77 status1->suspended == status2->suspended
78#ifdef BACKLIGHT_ENABLE
79 && status1->backlight_level == status2->backlight_level
80#endif
81#ifdef VISUALIZER_USER_DATA_SIZE
82 && memcmp(status1->user_data, status2->user_data, VISUALIZER_USER_DATA_SIZE) == 0
83#endif
84 ;
85}
86
87static bool visualizer_enabled = false;
88
89#ifdef VISUALIZER_USER_DATA_SIZE
90static uint8_t user_data[VISUALIZER_USER_DATA_SIZE];
91#endif
92
93#define MAX_SIMULTANEOUS_ANIMATIONS 4
94static keyframe_animation_t* animations[MAX_SIMULTANEOUS_ANIMATIONS] = {};
95
96#ifdef SERIAL_LINK_ENABLE
97MASTER_TO_ALL_SLAVES_OBJECT(current_status, visualizer_keyboard_status_t);
98
99static remote_object_t* remote_objects[] = {
100 REMOTE_OBJECT(current_status),
101};
102
103#endif
104
105GDisplay* LCD_DISPLAY = 0;
106GDisplay* LED_DISPLAY = 0;
107
108#ifdef LCD_DISPLAY_NUMBER
109__attribute__((weak))
110GDisplay* get_lcd_display(void) {
111 return gdispGetDisplay(LCD_DISPLAY_NUMBER);
112}
113#endif
114
115#ifdef LED_DISPLAY_NUMBER
116__attribute__((weak))
117GDisplay* get_led_display(void) {
118 return gdispGetDisplay(LED_DISPLAY_NUMBER);
119}
120#endif
121
122void start_keyframe_animation(keyframe_animation_t* animation) {
123 animation->current_frame = -1;
124 animation->time_left_in_frame = 0;
125 animation->need_update = true;
126 int free_index = -1;
127 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
128 if (animations[i] == animation) {
129 return;
130 }
131 if (free_index == -1 && animations[i] == NULL) {
132 free_index=i;
133 }
134 }
135 if (free_index!=-1) {
136 animations[free_index] = animation;
137 }
138}
139
140void stop_keyframe_animation(keyframe_animation_t* animation) {
141 animation->current_frame = animation->num_frames;
142 animation->time_left_in_frame = 0;
143 animation->need_update = true;
144 animation->first_update_of_frame = false;
145 animation->last_update_of_frame = false;
146 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
147 if (animations[i] == animation) {
148 animations[i] = NULL;
149 return;
150 }
151 }
152}
153
154void stop_all_keyframe_animations(void) {
155 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
156 if (animations[i]) {
157 animations[i]->current_frame = animations[i]->num_frames;
158 animations[i]->time_left_in_frame = 0;
159 animations[i]->need_update = true;
160 animations[i]->first_update_of_frame = false;
161 animations[i]->last_update_of_frame = false;
162 animations[i] = NULL;
163 }
164 }
165}
166
167static uint8_t get_num_running_animations(void) {
168 uint8_t count = 0;
169 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
170 count += animations[i] ? 1 : 0;
171 }
172 return count;
173}
174
175static bool update_keyframe_animation(keyframe_animation_t* animation, visualizer_state_t* state, systemticks_t delta, systemticks_t* sleep_time) {
176 // TODO: Clean up this messy code
177 dprintf("Animation frame%d, left %d, delta %d\n", animation->current_frame,
178 animation->time_left_in_frame, delta);
179 if (animation->current_frame == animation->num_frames) {
180 animation->need_update = false;
181 return false;
182 }
183 if (animation->current_frame == -1) {
184 animation->current_frame = 0;
185 animation->time_left_in_frame = animation->frame_lengths[0];
186 animation->need_update = true;
187 animation->first_update_of_frame = true;
188 } else {
189 animation->time_left_in_frame -= delta;
190 while (animation->time_left_in_frame <= 0) {
191 int left = animation->time_left_in_frame;
192 if (animation->need_update) {
193 animation->time_left_in_frame = 0;
194 animation->last_update_of_frame = true;
195 (*animation->frame_functions[animation->current_frame])(animation, state);
196 animation->last_update_of_frame = false;
197 }
198 animation->current_frame++;
199 animation->need_update = true;
200 animation->first_update_of_frame = true;
201 if (animation->current_frame == animation->num_frames) {
202 if (animation->loop) {
203 animation->current_frame = 0;
204 }
205 else {
206 stop_keyframe_animation(animation);
207 return false;
208 }
209 }
210 delta = -left;
211 animation->time_left_in_frame = animation->frame_lengths[animation->current_frame];
212 animation->time_left_in_frame -= delta;
213 }
214 }
215 if (animation->need_update) {
216 animation->need_update = (*animation->frame_functions[animation->current_frame])(animation, state);
217 animation->first_update_of_frame = false;
218 }
219
220 systemticks_t wanted_sleep = animation->need_update ? gfxMillisecondsToTicks(10) : (unsigned)animation->time_left_in_frame;
221 if (wanted_sleep < *sleep_time) {
222 *sleep_time = wanted_sleep;
223 }
224
225 return true;
226}
227
228void run_next_keyframe(keyframe_animation_t* animation, visualizer_state_t* state) {
229 int next_frame = animation->current_frame + 1;
230 if (next_frame == animation->num_frames) {
231 next_frame = 0;
232 }
233 keyframe_animation_t temp_animation = *animation;
234 temp_animation.current_frame = next_frame;
235 temp_animation.time_left_in_frame = animation->frame_lengths[next_frame];
236 temp_animation.first_update_of_frame = true;
237 temp_animation.last_update_of_frame = false;
238 temp_animation.need_update = false;
239 visualizer_state_t temp_state = *state;
240 (*temp_animation.frame_functions[next_frame])(&temp_animation, &temp_state);
241}
242
243// TODO: Optimize the stack size, this is probably way too big
244static DECLARE_THREAD_STACK(visualizerThreadStack, 1024);
245static DECLARE_THREAD_FUNCTION(visualizerThread, arg) {
246 (void)arg;
247
248 GListener event_listener;
249 geventListenerInit(&event_listener);
250 geventAttachSource(&event_listener, (GSourceHandle)&current_status, 0);
251
252 visualizer_keyboard_status_t initial_status = {
253 .default_layer = 0xFFFFFFFF,
254 .layer = 0xFFFFFFFF,
255 .mods = 0xFF,
256 .leds = 0xFFFFFFFF,
257 .suspended = false,
258 #ifdef VISUALIZER_USER_DATA_SIZE
259 .user_data = {0},
260 #endif
261 };
262
263 visualizer_state_t state = {
264 .status = initial_status,
265 .current_lcd_color = 0,
266#ifdef LCD_ENABLE
267 .font_fixed5x8 = gdispOpenFont("fixed_5x8"),
268 .font_dejavusansbold12 = gdispOpenFont("DejaVuSansBold12")
269#endif
270 };
271 initialize_user_visualizer(&state);
272 state.prev_lcd_color = state.current_lcd_color;
273
274#ifdef LCD_BACKLIGHT_ENABLE
275 lcd_backlight_color(
276 LCD_HUE(state.current_lcd_color),
277 LCD_SAT(state.current_lcd_color),
278 LCD_INT(state.current_lcd_color));
279#endif
280
281 systemticks_t sleep_time = TIME_INFINITE;
282 systemticks_t current_time = gfxSystemTicks();
283 bool force_update = true;
284
285 while(true) {
286 systemticks_t new_time = gfxSystemTicks();
287 systemticks_t delta = new_time - current_time;
288 current_time = new_time;
289 bool enabled = visualizer_enabled;
290 if (force_update || !same_status(&state.status, &current_status)) {
291 force_update = false;
292 #if BACKLIGHT_ENABLE
293 if(current_status.backlight_level != state.status.backlight_level) {
294 if (current_status.backlight_level != 0) {
295 gdispGSetPowerMode(LED_DISPLAY, powerOn);
296 uint16_t percent = (uint16_t)current_status.backlight_level * 100 / BACKLIGHT_LEVELS;
297 gdispGSetBacklight(LED_DISPLAY, percent);
298 }
299 else {
300 gdispGSetPowerMode(LED_DISPLAY, powerOff);
301 }
302 }
303 #endif
304 if (visualizer_enabled) {
305 if (current_status.suspended) {
306 stop_all_keyframe_animations();
307 visualizer_enabled = false;
308 state.status = current_status;
309 user_visualizer_suspend(&state);
310 }
311 else {
312 visualizer_keyboard_status_t prev_status = state.status;
313 state.status = current_status;
314 update_user_visualizer_state(&state, &prev_status);
315 }
316 state.prev_lcd_color = state.current_lcd_color;
317 }
318 }
319 if (!enabled && state.status.suspended && current_status.suspended == false) {
320 // Setting the status to the initial status will force an update
321 // when the visualizer is enabled again
322 state.status = initial_status;
323 state.status.suspended = false;
324 stop_all_keyframe_animations();
325 user_visualizer_resume(&state);
326 state.prev_lcd_color = state.current_lcd_color;
327 }
328 sleep_time = TIME_INFINITE;
329 for (int i=0;i<MAX_SIMULTANEOUS_ANIMATIONS;i++) {
330 if (animations[i]) {
331 update_keyframe_animation(animations[i], &state, delta, &sleep_time);
332 }
333 }
334#ifdef BACKLIGHT_ENABLE
335 gdispGFlush(LED_DISPLAY);
336#endif
337
338#ifdef LCD_ENABLE
339 gdispGFlush(LCD_DISPLAY);
340#endif
341
342#ifdef EMULATOR
343 draw_emulator();
344#endif
345 // Enable the visualizer when the startup or the suspend animation has finished
346 if (!visualizer_enabled && state.status.suspended == false && get_num_running_animations() == 0) {
347 visualizer_enabled = true;
348 force_update = true;
349 sleep_time = 0;
350 }
351
352 systemticks_t after_update = gfxSystemTicks();
353 unsigned update_delta = after_update - current_time;
354 if (sleep_time != TIME_INFINITE) {
355 if (sleep_time > update_delta) {
356 sleep_time -= update_delta;
357 }
358 else {
359 sleep_time = 0;
360 }
361 }
362 dprintf("Update took %d, last delta %d, sleep_time %d\n", update_delta, delta, sleep_time);
363#ifdef PROTOCOL_CHIBIOS
364 // The gEventWait function really takes milliseconds, even if the documentation says ticks.
365 // Unfortunately there's no generic ugfx conversion from system time to milliseconds,
366 // so let's do it in a platform dependent way.
367
368 // On windows the system ticks is the same as milliseconds anyway
369 if (sleep_time != TIME_INFINITE) {
370 sleep_time = ST2MS(sleep_time);
371 }
372#endif
373 geventEventWait(&event_listener, sleep_time);
374 }
375#ifdef LCD_ENABLE
376 gdispCloseFont(state.font_fixed5x8);
377 gdispCloseFont(state.font_dejavusansbold12);
378#endif
379
380 return 0;
381}
382
383void visualizer_init(void) {
384 gfxInit();
385
386 #ifdef LCD_BACKLIGHT_ENABLE
387 lcd_backlight_init();
388 #endif
389
390 #ifdef SERIAL_LINK_ENABLE
391 add_remote_objects(remote_objects, sizeof(remote_objects) / sizeof(remote_object_t*) );
392 #endif
393
394 #ifdef LCD_ENABLE
395 LCD_DISPLAY = get_lcd_display();
396 #endif
397
398 #ifdef BACKLIGHT_ENABLE
399 LED_DISPLAY = get_led_display();
400 #endif
401
402 // We are using a low priority thread, the idea is to have it run only
403 // when the main thread is sleeping during the matrix scanning
404 gfxThreadCreate(visualizerThreadStack, sizeof(visualizerThreadStack),
405 VISUALIZER_THREAD_PRIORITY, visualizerThread, NULL);
406}
407
408void update_status(bool changed) {
409 if (changed) {
410 GSourceListener* listener = geventGetSourceListener((GSourceHandle)&current_status, NULL);
411 if (listener) {
412 geventSendEvent(listener);
413 }
414 }
415#ifdef SERIAL_LINK_ENABLE
416 static systime_t last_update = 0;
417 systime_t current_update = chVTGetSystemTimeX();
418 systime_t delta = current_update - last_update;
419 if (changed || delta > MS2ST(10)) {
420 last_update = current_update;
421 visualizer_keyboard_status_t* r = begin_write_current_status();
422 *r = current_status;
423 end_write_current_status();
424 }
425#endif
426}
427
428uint8_t visualizer_get_mods() {
429 uint8_t mods = get_mods();
430
431#ifndef NO_ACTION_ONESHOT
432 if (!has_oneshot_mods_timed_out()) {
433 mods |= get_oneshot_mods();
434 }
435#endif
436 return mods;
437}
438
439#ifdef VISUALIZER_USER_DATA_SIZE
440void visualizer_set_user_data(void* u) {
441 memcpy(user_data, u, VISUALIZER_USER_DATA_SIZE);
442}
443#endif
444
445void visualizer_update(uint32_t default_state, uint32_t state, uint8_t mods, uint32_t leds) {
446 // Note that there's a small race condition here, the thread could read
447 // a state where one of these are set but not the other. But this should
448 // not really matter as it will be fixed during the next loop step.
449 // Alternatively a mutex could be used instead of the volatile variables
450
451 bool changed = false;
452#ifdef SERIAL_LINK_ENABLE
453 if (is_serial_link_connected ()) {
454 visualizer_keyboard_status_t* new_status = read_current_status();
455 if (new_status) {
456 if (!same_status(&current_status, new_status)) {
457 changed = true;
458 current_status = *new_status;
459 }
460 }
461 }
462 else {
463#else
464 {
465#endif
466 visualizer_keyboard_status_t new_status = {
467 .layer = state,
468 .default_layer = default_state,
469 .mods = mods,
470 .leds = leds,
471#ifdef BACKLIGHT_ENABLE
472 .backlight_level = current_status.backlight_level,
473#endif
474 .suspended = current_status.suspended,
475 };
476#ifdef VISUALIZER_USER_DATA_SIZE
477 memcpy(new_status.user_data, user_data, VISUALIZER_USER_DATA_SIZE);
478#endif
479 if (!same_status(&current_status, &new_status)) {
480 changed = true;
481 current_status = new_status;
482 }
483 }
484 update_status(changed);
485}
486
487void visualizer_suspend(void) {
488 current_status.suspended = true;
489 update_status(true);
490}
491
492void visualizer_resume(void) {
493 current_status.suspended = false;
494 update_status(true);
495}
496
497#ifdef BACKLIGHT_ENABLE
498void backlight_set(uint8_t level) {
499 current_status.backlight_level = level;
500 update_status(true);
501}
502#endif