mirror of
https://github.com/google/pebble.git
synced 2025-05-01 15:51:40 -04:00
386 lines
13 KiB
C
386 lines
13 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 "click.h"
|
|
#include "click_internal.h"
|
|
|
|
#include "window_stack_private.h"
|
|
|
|
#include "process_state/app_state/app_state.h"
|
|
#include "process_management/app_manager.h"
|
|
#include "util/size.h"
|
|
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
//! The time that the user has to hold the button before repetition kicks in.
|
|
static const uint32_t CLICK_REPETITION_DELAY_MS = 400;
|
|
//! Default minimum number of multi-clicks before the multi_click.handler gets fired
|
|
static const uint8_t MULTI_CLICK_DEFAULT_MIN = 2;
|
|
//! Default timeout after which looking for follow up clicks will be stopped
|
|
static const uint32_t MULTI_CLICK_DEFAULT_TIMEOUT_MS = 300;
|
|
//! Default delay before long click is fired
|
|
static const uint32_t LONG_CLICK_DEFAULT_DELAY_MS = 400;
|
|
|
|
typedef enum {
|
|
ClickHandlerOffsetSingle = offsetof(ClickConfig, click.handler),
|
|
ClickHandlerOffsetMulti = offsetof(ClickConfig, multi_click.handler),
|
|
ClickHandlerOffsetLong = offsetof(ClickConfig, long_click.handler),
|
|
ClickHandlerOffsetLongRelease = offsetof(ClickConfig, long_click.release_handler),
|
|
ClickHandlerOffsetRawUp = offsetof(ClickConfig, raw.up_handler),
|
|
ClickHandlerOffsetRawDown = offsetof(ClickConfig, raw.down_handler),
|
|
} ClickHandlerOffset;
|
|
|
|
static ClickHandler prv_get_handler(ClickRecognizer *recognizer, ClickHandlerOffset offset) {
|
|
return *((ClickHandler*)(((uint8_t*)&recognizer->config) + offset));
|
|
}
|
|
|
|
static void prv_cancel_timer(AppTimer **timer) {
|
|
if (*timer) {
|
|
app_timer_cancel(*timer);
|
|
*timer = NULL;
|
|
}
|
|
}
|
|
|
|
static void prv_click_reset(ClickRecognizerRef recognizer_ref) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer *)recognizer_ref;
|
|
recognizer->number_of_clicks_counted = 0;
|
|
recognizer->is_button_down = false;
|
|
recognizer->is_repeating = false;
|
|
|
|
prv_cancel_timer(&recognizer->hold_timer);
|
|
prv_cancel_timer(&recognizer->multi_click_timer);
|
|
}
|
|
|
|
static bool prv_dispatch_event(ClickRecognizer *recognizer, ClickHandlerOffset handler_offset,
|
|
bool needs_reset) {
|
|
if (recognizer) {
|
|
ClickHandler handler = prv_get_handler(recognizer, handler_offset);
|
|
if (handler) {
|
|
void *context;
|
|
if ((handler_offset == ClickHandlerOffsetRawUp || handler_offset == ClickHandlerOffsetRawDown) &&
|
|
recognizer->config.raw.context != NULL) {
|
|
// The context for raw click events is overridable:
|
|
context = recognizer->config.raw.context;
|
|
} else {
|
|
context = recognizer->config.context;
|
|
}
|
|
|
|
handler(recognizer, context);
|
|
}
|
|
|
|
if (needs_reset) {
|
|
prv_click_reset(recognizer);
|
|
}
|
|
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
inline static bool prv_is_hold_to_repeat_enabled(ClickRecognizer *recognizer) {
|
|
return (recognizer->config.click.repeat_interval_ms >= 30);
|
|
}
|
|
|
|
inline static bool prv_is_multi_click_enabled(ClickRecognizer *recognizer) {
|
|
return (recognizer->config.multi_click.handler != NULL);
|
|
}
|
|
|
|
inline static bool prv_is_long_click_enabled(ClickRecognizer *recognizer) {
|
|
return (recognizer->config.long_click.handler != NULL || recognizer->config.long_click.release_handler != NULL);
|
|
}
|
|
|
|
static void prv_auto_repeat_single_click(ClickRecognizer *recognizer) {
|
|
if (!recognizer->is_button_down) {
|
|
// If this button isn't being held down anymore, don't re-register the timer.
|
|
return;
|
|
}
|
|
++(recognizer->number_of_clicks_counted);
|
|
// Start the repetition timer:
|
|
// Note: We're not using the timer_register_repeating() here, so we have the possibility
|
|
// of changing the interval in the handler.
|
|
recognizer->hold_timer = app_timer_register(recognizer->config.click.repeat_interval_ms,
|
|
(AppTimerCallback)prv_auto_repeat_single_click, recognizer);
|
|
recognizer->is_repeating = true;
|
|
|
|
// Fire right once:
|
|
const bool needs_reset = false;
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetSingle, needs_reset);
|
|
}
|
|
|
|
static void prv_repetition_delay_callback(void *data) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer *) data;
|
|
|
|
// User has been holding the button down for more than the repetition delay.
|
|
prv_auto_repeat_single_click(recognizer);
|
|
}
|
|
|
|
static uint8_t prv_multi_click_get_min(ClickRecognizer *recognizer) {
|
|
if (false == prv_is_multi_click_enabled(recognizer)) {
|
|
return 0;
|
|
}
|
|
if (recognizer->config.multi_click.min == 0) {
|
|
return MULTI_CLICK_DEFAULT_MIN;
|
|
}
|
|
return recognizer->config.multi_click.min;
|
|
}
|
|
|
|
static uint8_t prv_multi_click_get_max(ClickRecognizer *recognizer) {
|
|
if (false == prv_is_multi_click_enabled(recognizer)) {
|
|
return 0;
|
|
}
|
|
if (recognizer->config.multi_click.max == 0) {
|
|
return prv_multi_click_get_min(recognizer);
|
|
}
|
|
return recognizer->config.multi_click.max;
|
|
}
|
|
|
|
static uint32_t prv_multi_click_get_timeout(ClickRecognizer *recognizer) {
|
|
if (false == prv_is_multi_click_enabled(recognizer)) {
|
|
return 0;
|
|
}
|
|
if (recognizer->config.multi_click.timeout == 0) {
|
|
return MULTI_CLICK_DEFAULT_TIMEOUT_MS;
|
|
}
|
|
return recognizer->config.multi_click.timeout;
|
|
}
|
|
|
|
static uint32_t prv_long_click_get_delay(ClickRecognizer *recognizer) {
|
|
if (false == prv_is_long_click_enabled(recognizer)) {
|
|
return 0;
|
|
}
|
|
if (recognizer->config.long_click.delay_ms == 0) {
|
|
return LONG_CLICK_DEFAULT_DELAY_MS;
|
|
}
|
|
return recognizer->config.long_click.delay_ms;
|
|
}
|
|
|
|
inline static bool prv_can_more_clicks_follow(ClickRecognizer *recognizer) {
|
|
if (recognizer->number_of_clicks_counted >= prv_multi_click_get_max(recognizer)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
uint8_t click_number_of_clicks_counted(ClickRecognizerRef recognizer_ref) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer*)recognizer_ref;
|
|
return recognizer->number_of_clicks_counted;
|
|
}
|
|
|
|
ButtonId click_recognizer_get_button_id(ClickRecognizerRef recognizer_ref) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer*)recognizer_ref;
|
|
return recognizer->button;
|
|
}
|
|
|
|
bool click_recognizer_is_repeating(ClickRecognizerRef recognizer_ref) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer*)recognizer_ref;
|
|
return recognizer->is_repeating;
|
|
}
|
|
|
|
bool click_recognizer_is_held_down(ClickRecognizerRef recognizer_ref) {
|
|
return (((ClickRecognizer *)recognizer_ref)->is_button_down);
|
|
}
|
|
|
|
ClickConfig *click_recognizer_get_config(ClickRecognizerRef recognizer_ref) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer*)recognizer_ref;
|
|
return &recognizer->config;
|
|
}
|
|
|
|
static void prv_long_click_callback(void *data) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer *) data;
|
|
|
|
recognizer->hold_timer = NULL;
|
|
const bool needs_reset = false;
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetLong, needs_reset);
|
|
}
|
|
|
|
//! Called at the end of a click pattern, either on the button up, or after a multi-click timeout:
|
|
static void prv_click_pattern_done(ClickRecognizer *recognizer) {
|
|
// In case multi_click is also configured, if there was only one click, regard it as
|
|
// a "single click" after the multi-click timeout passed and this callback is called:
|
|
if (recognizer->number_of_clicks_counted >= 1 && recognizer->is_repeating == false) {
|
|
int clicks_over = recognizer->number_of_clicks_counted;
|
|
for(int i = 0; i < clicks_over; i++) {
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetSingle, false);
|
|
}
|
|
}
|
|
prv_click_reset(recognizer);
|
|
}
|
|
|
|
static void prv_multi_click_timeout_callback(void *data) {
|
|
ClickRecognizer *recognizer = (ClickRecognizer *) data;
|
|
|
|
recognizer->multi_click_timer = NULL;
|
|
if (recognizer->config.multi_click.last_click_only &&
|
|
(recognizer->number_of_clicks_counted >= prv_multi_click_get_min(recognizer)) &&
|
|
(recognizer->number_of_clicks_counted <= prv_multi_click_get_max(recognizer))) {
|
|
const bool needs_reset = true;
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetMulti, needs_reset);
|
|
} else {
|
|
prv_click_pattern_done(recognizer);
|
|
}
|
|
}
|
|
|
|
void command_put_button_event(const char* button_index, const char* click_type) {
|
|
int button = atoi(button_index);
|
|
const bool needs_reset = false;
|
|
ClickHandlerOffset offset;
|
|
|
|
if ((button < 0 || button > NUM_BUTTONS)) {
|
|
return;
|
|
}
|
|
|
|
switch(*click_type) {
|
|
case 's':
|
|
offset = ClickHandlerOffsetSingle;
|
|
break;
|
|
case 'm':
|
|
offset = ClickHandlerOffsetMulti;
|
|
break;
|
|
case 'l':
|
|
offset = ClickHandlerOffsetLong;
|
|
break;
|
|
case 'r':
|
|
offset = ClickHandlerOffsetLongRelease;
|
|
break;
|
|
case 'u':
|
|
offset = ClickHandlerOffsetRawUp;
|
|
break;
|
|
case 'd':
|
|
offset = ClickHandlerOffsetRawDown;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
prv_dispatch_event(&(app_state_get_click_manager()->recognizers[button]), offset, needs_reset);
|
|
}
|
|
|
|
void click_recognizer_handle_button_down(ClickRecognizer *recognizer) {
|
|
recognizer->is_button_down = true;
|
|
|
|
prv_cancel_timer(&recognizer->multi_click_timer);
|
|
|
|
const bool needs_reset = false;
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetRawDown, needs_reset);
|
|
|
|
if (prv_is_long_click_enabled(recognizer)) {
|
|
const uint32_t long_click_delay = prv_long_click_get_delay(recognizer);
|
|
recognizer->hold_timer = app_timer_register(
|
|
long_click_delay, prv_long_click_callback, recognizer);
|
|
} else {
|
|
const bool local_is_hold_to_repeat_enabled = prv_is_hold_to_repeat_enabled(recognizer);
|
|
if (local_is_hold_to_repeat_enabled) {
|
|
// If there's a repeat interval configured, start the repetition delay timer:
|
|
recognizer->hold_timer = app_timer_register(
|
|
CLICK_REPETITION_DELAY_MS, prv_repetition_delay_callback, recognizer);
|
|
}
|
|
if (false == prv_is_multi_click_enabled(recognizer)) {
|
|
// No long click nor multi click, fire handler immediately on button down:
|
|
++(recognizer->number_of_clicks_counted);
|
|
const bool needs_reset = (false == local_is_hold_to_repeat_enabled);
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetSingle, needs_reset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void click_recognizer_handle_button_up(ClickRecognizer *recognizer) {
|
|
const bool needs_reset = false;
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetRawUp, needs_reset);
|
|
|
|
if (recognizer->is_button_down == false) {
|
|
// Ignore this button up event. Most likely, the recognizer has been
|
|
// reset while the button was still pressed down.
|
|
return;
|
|
}
|
|
recognizer->is_button_down = false;
|
|
|
|
const bool local_is_long_click_enabled = prv_is_long_click_enabled(recognizer);
|
|
const bool local_is_multi_click_enabled = prv_is_multi_click_enabled(recognizer);
|
|
//const bool local_is_hold_to_repeat_enabled = is_hold_to_repeat_enabled(recognizer);
|
|
|
|
if (false == local_is_long_click_enabled &&
|
|
false == local_is_multi_click_enabled) {
|
|
// Handler already fired in button down.
|
|
prv_click_reset(recognizer);
|
|
return;
|
|
}
|
|
|
|
++(recognizer->number_of_clicks_counted);
|
|
|
|
const bool has_long_click_been_fired = (local_is_long_click_enabled && recognizer->hold_timer == NULL);
|
|
if (has_long_click_been_fired) {
|
|
const bool needs_reset = true;
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetLongRelease, needs_reset);
|
|
return;
|
|
}
|
|
|
|
prv_cancel_timer(&recognizer->hold_timer);
|
|
|
|
if (local_is_multi_click_enabled && false == recognizer->is_repeating) {
|
|
const bool local_can_more_clicks_follow = prv_can_more_clicks_follow(recognizer);
|
|
bool should_fire_multi_click_handler = ((recognizer->config.multi_click.last_click_only && local_can_more_clicks_follow) == false);
|
|
bool reset_using_event = false;
|
|
|
|
if (should_fire_multi_click_handler) {
|
|
if ((recognizer->number_of_clicks_counted >= prv_multi_click_get_min(recognizer)) &&
|
|
(recognizer->number_of_clicks_counted <= prv_multi_click_get_max(recognizer))) {
|
|
reset_using_event = (false == local_can_more_clicks_follow);
|
|
prv_dispatch_event(recognizer, ClickHandlerOffsetMulti, reset_using_event);
|
|
}
|
|
}
|
|
|
|
if (prv_can_more_clicks_follow(recognizer)) {
|
|
const uint32_t timeout = prv_multi_click_get_timeout(recognizer);
|
|
recognizer->multi_click_timer = app_timer_register(
|
|
timeout, prv_multi_click_timeout_callback, recognizer);
|
|
return;
|
|
} else {
|
|
if (reset_using_event) {
|
|
return;
|
|
}
|
|
}
|
|
// fall-through if no more clicks can follow,
|
|
// and we're not resetting using a click event that has been put.
|
|
}
|
|
|
|
prv_click_pattern_done(recognizer);
|
|
}
|
|
|
|
void click_manager_init(ClickManager* click_manager) {
|
|
for (unsigned int button_id = 0;
|
|
button_id < ARRAY_LENGTH(click_manager->recognizers); ++button_id) {
|
|
ClickRecognizer *recognizer = &click_manager->recognizers[button_id];
|
|
recognizer->button = button_id;
|
|
prv_click_reset(recognizer);
|
|
}
|
|
}
|
|
|
|
void click_manager_clear(ClickManager* click_manager) {
|
|
for (unsigned int button_id = 0;
|
|
button_id < ARRAY_LENGTH(click_manager->recognizers); ++button_id) {
|
|
prv_click_reset(&click_manager->recognizers[button_id]);
|
|
click_manager->recognizers[button_id].config = (ClickConfig){};
|
|
}
|
|
}
|
|
|
|
void click_manager_reset(ClickManager* click_manager) {
|
|
for (unsigned int button_id = 0; button_id < NUM_BUTTONS; button_id++) {
|
|
prv_click_reset(&click_manager->recognizers[button_id]);
|
|
}
|
|
}
|
|
|