mirror of
https://github.com/google/pebble.git
synced 2025-06-02 08:23:10 +00:00
Import of the watch repository from Pebble
This commit is contained in:
commit
3b92768480
10334 changed files with 2564465 additions and 0 deletions
284
src/fw/applib/app.c
Normal file
284
src/fw/applib/app.c
Normal file
|
@ -0,0 +1,284 @@
|
|||
/*
|
||||
* 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 */);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue