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