mirror of
https://github.com/google/pebble.git
synced 2025-05-04 08:51:41 -04:00
284 lines
10 KiB
C
284 lines
10 KiB
C
/*
|
|
* Copyright 2024 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "app.h"
|
|
|
|
#include "applib/app_heap_analytics.h"
|
|
#include "applib/graphics/graphics_private.h"
|
|
#include "applib/ui/app_window_stack.h"
|
|
#include "applib/ui/window_stack.h"
|
|
#include "applib/ui/window_private.h"
|
|
#include "mcu/fpu.h"
|
|
#include "process_management/app_manager.h"
|
|
#include "process_state/app_state/app_state.h"
|
|
#include "syscall/syscall.h"
|
|
#include "system/logging.h"
|
|
#include "system/profiler.h"
|
|
|
|
static void prv_render_app(void) {
|
|
WindowStack *stack = app_state_get_window_stack();
|
|
GContext *ctx = app_state_get_graphics_context();
|
|
if (!window_stack_is_animating(stack)) {
|
|
SYS_PROFILER_NODE_START(render_app);
|
|
window_render(app_window_stack_get_top_window(), ctx);
|
|
SYS_PROFILER_NODE_STOP(render_app);
|
|
} else {
|
|
// TODO: PBL-17645 render container layer instead of the two windows
|
|
WindowTransitioningContext *transition_context = &stack->transition_context;
|
|
|
|
if (transition_context->implementation->render) {
|
|
transition_context->implementation->render(transition_context, ctx);
|
|
}
|
|
}
|
|
|
|
*app_state_get_framebuffer_render_pending() = true;
|
|
|
|
PebbleEvent event = {
|
|
.type = PEBBLE_RENDER_READY_EVENT,
|
|
};
|
|
sys_send_pebble_event_to_kernel(&event);
|
|
}
|
|
|
|
static bool prv_window_is_render_scheduled(Window *window) {
|
|
return window && window->is_render_scheduled;
|
|
}
|
|
|
|
static bool prv_app_is_render_scheduled() {
|
|
Window *top_window = app_window_stack_get_top_window();
|
|
if (prv_window_is_render_scheduled(top_window)) {
|
|
return true;
|
|
}
|
|
|
|
WindowStack *stack = app_state_get_window_stack();
|
|
if (!window_stack_is_animating(stack)) {
|
|
return false;
|
|
}
|
|
|
|
WindowTransitioningContext *transition_ctx = &stack->transition_context;
|
|
return prv_window_is_render_scheduled(transition_ctx->window_from) ||
|
|
prv_window_is_render_scheduled(transition_ctx->window_to);
|
|
}
|
|
|
|
void app_request_render(void) {
|
|
Window *window = app_window_stack_get_top_window();
|
|
if (window) {
|
|
window_schedule_render(window);
|
|
}
|
|
}
|
|
|
|
//! Tasks that have to be done in between each event.
|
|
static NOINLINE void event_loop_upkeep(void) {
|
|
|
|
// Check to see if the most recent event caused us to pop our final window. If that's the case, we need to
|
|
// kill ourselves.
|
|
if (app_window_stack_count() == 0) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "No more windows, killing current app");
|
|
|
|
PebbleEvent event = { .type = PEBBLE_PROCESS_KILL_EVENT, .kill = { .gracefully = true, .task=PebbleTask_App } };
|
|
sys_send_pebble_event_to_kernel(&event);
|
|
return;
|
|
}
|
|
|
|
// Check to see if handling the previous event requires us to rerender ourselves.
|
|
if (prv_app_is_render_scheduled() &&
|
|
!*app_state_get_framebuffer_render_pending()) {
|
|
prv_render_app();
|
|
}
|
|
}
|
|
|
|
static void prv_app_will_focus_handler(PebbleEvent *e, void *context) {
|
|
Window *window = app_window_stack_get_top_window();
|
|
if (e->app_focus.in_focus) {
|
|
if (window) {
|
|
// Do not call 'appear' handler on window displacing modal window
|
|
window_set_on_screen(window, true, false);
|
|
window_render(window, app_state_get_graphics_context());
|
|
}
|
|
click_manager_reset(app_state_get_click_manager());
|
|
} else if (window) {
|
|
// Do not call 'disappear' handler on window displaced by modal window
|
|
window_set_on_screen(window, false, false);
|
|
}
|
|
}
|
|
|
|
static void prv_app_button_down_handler(PebbleEvent *e, void *context) {
|
|
WindowStack *app_window_stack = app_state_get_window_stack();
|
|
if (window_stack_is_animating(app_window_stack)) {
|
|
return;
|
|
}
|
|
|
|
sys_analytics_inc(ANALYTICS_APP_METRIC_BUTTONS_PRESSED_COUNT, AnalyticsClient_App);
|
|
|
|
if (e->button.button_id == BUTTON_ID_BACK &&
|
|
!app_window_stack_get_top_window()->overrides_back_button) {
|
|
// a transition of NULL means we will use the stored pop transition for this stack item
|
|
window_stack_pop_with_transition(app_window_stack, NULL /* transition */);
|
|
return;
|
|
}
|
|
|
|
click_recognizer_handle_button_down(
|
|
&app_state_get_click_manager()->recognizers[e->button.button_id]);
|
|
}
|
|
|
|
static void prv_app_button_up_handler(PebbleEvent *e, void *context) {
|
|
if (window_stack_is_animating(app_state_get_window_stack())) {
|
|
return;
|
|
}
|
|
|
|
click_recognizer_handle_button_up(
|
|
&app_state_get_click_manager()->recognizers[e->button.button_id]);
|
|
}
|
|
|
|
// this handler is called via the legacy2_status_bar_change_event and
|
|
// will update the status bar once a minute for non-fullscreen legacy2 apps
|
|
static void prv_legacy2_status_bar_handler(PebbleEvent *e, void *context) {
|
|
Window *window = app_window_stack_get_top_window();
|
|
// only force render if we're not fullscreen
|
|
if (!window->is_fullscreen) {
|
|
// a little logic to only force update when the minute changes
|
|
ApplibInternalEventsInfo *events_info =
|
|
app_state_get_applib_internal_events_info();
|
|
struct tm currtime;
|
|
sys_localtime_r(&e->clock_tick.tick_time, &currtime);
|
|
const int minute_of_day = (currtime.tm_hour * 60) + currtime.tm_min;
|
|
if (events_info->minute_of_last_legacy2_statusbar_change != minute_of_day) {
|
|
events_info->minute_of_last_legacy2_statusbar_change = minute_of_day;
|
|
window_schedule_render(window);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void prv_legacy2_status_bar_timer_subscribe(void) {
|
|
// we only need this tick event if we are a legacy2 app
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
ApplibInternalEventsInfo *events_info =
|
|
app_state_get_applib_internal_events_info();
|
|
// Initialize the state for the status bar handler.
|
|
events_info->minute_of_last_legacy2_statusbar_change = -1;
|
|
events_info->legacy2_status_bar_change_event = (EventServiceInfo) {
|
|
.type = PEBBLE_TICK_EVENT,
|
|
.handler = prv_legacy2_status_bar_handler,
|
|
};
|
|
event_service_client_subscribe(
|
|
&events_info->legacy2_status_bar_change_event);
|
|
}
|
|
// NOTE: We could be super fancy and register and unregister when the fullscreen
|
|
// status changes, but it's probably not worth it as we'll be waking up once a
|
|
// minute anyway to update the face itself and it will happen as part of the same interval
|
|
}
|
|
|
|
static void prv_legacy2_status_bar_timer_unsubscribe(void) {
|
|
// we should only unsubscribe if we subscribed in the first place
|
|
if (process_manager_compiled_with_legacy2_sdk()) {
|
|
ApplibInternalEventsInfo *events_info =
|
|
app_state_get_applib_internal_events_info();
|
|
event_service_client_unsubscribe(
|
|
&events_info->legacy2_status_bar_change_event);
|
|
}
|
|
}
|
|
|
|
static void prv_app_callback_handler(PebbleEvent *e) {
|
|
e->callback.callback(e->callback.data);
|
|
}
|
|
|
|
static NOINLINE void prv_handle_deinit_event(void) {
|
|
ApplibInternalEventsInfo *events_info =
|
|
app_state_get_applib_internal_events_info();
|
|
event_service_client_unsubscribe(&events_info->will_focus_event);
|
|
event_service_client_unsubscribe(&events_info->button_down_event);
|
|
event_service_client_unsubscribe(&events_info->button_up_event);
|
|
prv_legacy2_status_bar_timer_unsubscribe(); // a no-op on sdk3+ applications
|
|
WindowStack *app_window_stack = app_state_get_window_stack();
|
|
window_stack_lock_push(app_window_stack);
|
|
window_stack_pop_all(app_window_stack, false);
|
|
window_stack_unlock_push(app_window_stack);
|
|
}
|
|
|
|
// Get the app_id for the current app or worker
|
|
// @return INSTALL_ID_INVALID if unsuccessful
|
|
AppInstallId app_get_app_id(void) {
|
|
// Only support from app or workers
|
|
PebbleTask task = pebble_task_get_current();
|
|
if ((task != PebbleTask_App) && (task != PebbleTask_Worker)) {
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "Only supported from app or worker tasks");
|
|
return INSTALL_ID_INVALID;
|
|
}
|
|
|
|
// Get the app id
|
|
if (task == PebbleTask_App) {
|
|
return sys_app_manager_get_current_app_id();
|
|
} else {
|
|
return sys_worker_manager_get_current_worker_id();
|
|
}
|
|
}
|
|
|
|
void app_event_loop_common(void) {
|
|
// Register our event handlers before we do anything else. Registering for an event requires
|
|
// an event being sent to the kernel and therefore should be done before any other events are
|
|
// generated by us to ensure we don't miss out on anything.
|
|
ApplibInternalEventsInfo *events_info =
|
|
app_state_get_applib_internal_events_info();
|
|
events_info->will_focus_event = (EventServiceInfo) {
|
|
.type = PEBBLE_APP_WILL_CHANGE_FOCUS_EVENT,
|
|
.handler = prv_app_will_focus_handler,
|
|
};
|
|
events_info->button_down_event = (EventServiceInfo) {
|
|
.type = PEBBLE_BUTTON_DOWN_EVENT,
|
|
.handler = prv_app_button_down_handler,
|
|
};
|
|
events_info->button_up_event = (EventServiceInfo) {
|
|
.type = PEBBLE_BUTTON_UP_EVENT,
|
|
.handler = prv_app_button_up_handler,
|
|
};
|
|
event_service_client_subscribe(&events_info->will_focus_event);
|
|
event_service_client_subscribe(&events_info->button_down_event);
|
|
event_service_client_subscribe(&events_info->button_up_event);
|
|
prv_legacy2_status_bar_timer_subscribe(); // a no-op on sdk3+ applications
|
|
|
|
event_loop_upkeep();
|
|
|
|
// Event loop:
|
|
while (1) {
|
|
PebbleEvent event;
|
|
|
|
sys_get_pebble_event(&event);
|
|
|
|
if (event.type == PEBBLE_PROCESS_DEINIT_EVENT) {
|
|
prv_handle_deinit_event();
|
|
// We're done here. Return the app's main function.
|
|
event_cleanup(&event);
|
|
return;
|
|
} else if (event.type == PEBBLE_CALLBACK_EVENT) {
|
|
prv_app_callback_handler(&event);
|
|
} else if (event.type == PEBBLE_RENDER_REQUEST_EVENT) {
|
|
app_request_render();
|
|
} else if (event.type == PEBBLE_RENDER_FINISHED_EVENT) {
|
|
*app_state_get_framebuffer_render_pending() = false;
|
|
} else {
|
|
event_service_client_handle_event(&event);
|
|
}
|
|
|
|
mcu_fpu_cleanup();
|
|
event_cleanup(&event);
|
|
|
|
event_loop_upkeep();
|
|
}
|
|
}
|
|
|
|
void app_event_loop(void) {
|
|
app_event_loop_common();
|
|
app_heap_analytics_log_stats_to_app_heartbeat(false /* is_rocky_app */);
|
|
}
|