-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathappmanager_app_runloop.c
More file actions
301 lines (250 loc) · 9.08 KB
/
appmanager_app_runloop.c
File metadata and controls
301 lines (250 loc) · 9.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
/* appmanager_app_runloop.c
* The main runloop and runloop handlers for the main foregound App
* RebbleOS
*
* Author: Barry Carter <barry.carter@gmail.com>.
*/
#include <stdlib.h>
#include "rebbleos.h"
#include "appmanager.h"
#include "overlay_manager.h"
#include "notification_manager.h"
#include "timers.h"
#include "ngfxwrap.h"
/* Configure Logging */
#define MODULE_NAME "apploop"
#define MODULE_TYPE "APLOOP"
#define LOG_LEVEL RBL_LOG_LEVEL_DEBUG //RBL_LOG_LEVEL_ERROR
void back_long_click_handler(ClickRecognizerRef recognizer, void *context);
void back_long_click_release_handler(ClickRecognizerRef recognizer, void *context);
void app_select_single_click_handler(ClickRecognizerRef recognizer, void *context);
void app_back_single_click_handler(ClickRecognizerRef recognizer, void *context);
bool booted = false;
static xQueueHandle _app_message_queue;
void appmanager_app_runloop_init(void)
{
_app_message_queue = xQueueCreate(5, sizeof(struct AppMessage));
timer_init();
}
/*
* Send a message to an app
*/
void appmanager_post_generic_app_message(AppMessage *am, TickType_t timeout)
{
app_running_thread *_thread = appmanager_get_thread(AppThreadMainApp);
if (_thread->status == AppThreadRunloop)
if (!xQueueSendToBack(_app_message_queue, am, timeout))
;
}
/*
* We are the main entrypoint for running a thread.
* When we are done, we notify the main thread we shutdown
* lest we get murdered
*/
void appmanager_app_main_entry(void)
{
app_running_thread *_this_thread = appmanager_get_current_thread();
_this_thread->status = AppThreadLoaded;
/* Before we even see them, we have to reset fonts -- otherwise, the
* font cache does some free business on old pointers, corrupting the
* heap before we even had a fighting chance! */
fonts_resetcache();
connection_service_unsubscribe();
n_GContext *context = rwatch_neographics_get_global_context();
/* not a memory leak. Context was erased on app load */
rwatch_neographics_init(_this_thread);
/* Call into the apps main runtime */
_this_thread->app->main();
_this_thread->status = AppThreadUnloading;
AppMessage am = {
.thread_id = _this_thread->thread_type,
.command = THREAD_MANAGER_APP_QUIT_CLEAN,
};
appmanager_post_generic_thread_message(&am, 100);
LOG_DEBUG("App Finished.");
/* We are done with our app. Block until we are killed */
vTaskDelay(portMAX_DELAY);
}
bool appmanager_is_app_shutting_down(void)
{
app_running_thread *_this_thread = appmanager_get_current_thread();
return _this_thread->status == AppThreadUnloading;
}
static void _draw(uint8_t force_draw)
{
/* Request a draw. This is mostly from an app invalidating something */
if (display_buffer_lock_take(0))
{
if (force_draw)
window_dirty(true);
bool force = window_draw();
if (overlay_window_count() > 0)
{
overlay_window_draw(true);
force = true;
}
if (force)
{
display_draw();
}
display_buffer_lock_give();
}
}
/*
* Once an application is spawned, it calls into app_event_loop
* This function is a busy loop, but with the benefit that it is also a task
* In here we are the main event handler, for buttons quits etc etc.
*/
void app_event_loop(void)
{
AppMessage data;
app_running_thread *_this_thread = appmanager_get_current_thread();
App *_running_app = _this_thread->app;
bool draw_requested = false;
if (_this_thread->thread_type != AppThreadMainApp)
{
LOG_ERROR("Runloop: You are not an app");
return;
}
LOG_INFO("App entered mainloop");
/* Do this before window load, that way they have a chance to override */
if (_running_app->type != APP_TYPE_FACE &&
overlay_window_count() == 0)
{
/* Enables default closing of windows, and through that, apps */
window_single_click_subscribe(BUTTON_ID_BACK, app_back_single_click_handler);
}
window_configure(window_stack_get_top_window());
if (_running_app->type == APP_TYPE_FACE)
{
window_single_click_subscribe(BUTTON_ID_SELECT, app_select_single_click_handler);
}
if (_running_app->type == APP_TYPE_APP)
{
window_long_click_subscribe(BUTTON_ID_BACK, 1100, back_long_click_handler, back_long_click_release_handler);
}
/* clear the queue of any work from the previous app
* ... such as an errant quit */
xQueueReset(_app_message_queue);
if (!booted)
{
GRect frame = GRect(0, DISPLAY_ROWS - 20, DISPLAY_COLS, 20);
notification_show_small_message("Welcome to RebbleOS", frame);
booted = true;
}
TickType_t next_timer;
_this_thread->status = AppThreadRunloop;
next_timer = portMAX_DELAY;
/* App is now fully initialised and inside the runloop. */
for ( ;; )
{
next_timer = appmanager_timer_get_next_expiry(_this_thread);
if (next_timer == 0)
{
appmanager_timer_expired(_this_thread);
appmanager_post_draw_message(0);
next_timer = appmanager_timer_get_next_expiry(_this_thread);
}
if (next_timer < 0)
next_timer = portMAX_DELAY;
/* we are inside the apps main loop event handler now */
if (xQueueReceive(_app_message_queue, &data, next_timer))
{
/* We woke up for some kind of event that someone posted. But what? */
if (data.command == APP_BUTTON)
{
if (appmanager_is_app_shutting_down())
continue;
if (overlay_window_accepts_keypress())
{
overlay_window_post_button_message(data.data);
continue;
}
/* execute the button's callback */
ButtonMessage *message = (ButtonMessage *)data.data;
((ClickHandler)(message->callback))((ClickRecognizerRef)(message->clickref), message->context);
appmanager_post_draw_message(0);
}
/* Someone has requested the application close.
* We will attempt graceful shutdown by unsubscribing timers
* Any app timers will fire and be nulled, or get erased.
* XXX could do with a timer mutex to wait on
*/
else if (data.command == APP_QUIT)
{
/* Set the shutdown time for this app. We will kill it then */
if (!appmanager_is_app_shutting_down())
{
_this_thread->shutdown_at_tick = xTaskGetTickCount() + pdMS_TO_TICKS(5000);
_this_thread->status = AppThreadUnloading;
}
/* remove all of the clck handlers */
button_unsubscribe_all();
/* remove the ticktimer service handler and stop it */
tick_timer_service_unsubscribe();
connection_service_unsubscribe();
_this_thread->status = AppThreadUnloading;
appmanager_app_quit();
LOG_INFO("App Quit");
/* app was quit, break out of this loop into the main handler */
break;
}
/* A draw is requested. Get a lock and then draw. if we can't lock we..
* try, try, try again
*/
else if (data.command == APP_DRAW)
{
if (appmanager_is_app_shutting_down())
continue;
_draw((uint32_t)data.data);
}
} else {
if (appmanager_is_app_shutting_down())
continue;
}
vTaskDelay(0);
}
LOG_INFO("App Signalled shutdown...");
/* We fall out of the apps main_ now and into deinit and thread completion
* We will hand back control to appmanager_app_main_entry above */
}
/* Apps click handlers */
void back_long_click_handler(ClickRecognizerRef recognizer, void *context)
{
app_running_thread *_this_thread = appmanager_get_current_thread();
switch(_this_thread->app->type)
{
case APP_TYPE_FACE:
LOG_DEBUG("TODO: Quiet time");
break;
case APP_TYPE_SYSTEM:
case APP_TYPE_APP:
// quit the app
appmanager_app_start("Simple");
break;
}
}
void back_long_click_release_handler(ClickRecognizerRef recognizer, void *context)
{
}
void app_select_single_click_handler(ClickRecognizerRef recognizer, void *context)
{
app_running_thread *_this_thread = appmanager_get_current_thread();
switch(_this_thread->app->type)
{
case APP_TYPE_FACE:
appmanager_app_start("System");
break;
}
}
void app_back_single_click_handler(ClickRecognizerRef recognizer, void *context)
{
// Pop windows off
Window *popped = window_stack_pop(true);
LOG_DEBUG("Window Count: %d", window_count());
if (window_count() == 0)
{
appmanager_app_start("System");
}
window_dirty(true);
}