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
61
src/fw/applib/rockyjs/api/rocky_api.c
Normal file
61
src/fw/applib/rockyjs/api/rocky_api.c
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 "rocky_api.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "rocky_api_app_message.h"
|
||||
#include "rocky_api_console.h"
|
||||
#include "rocky_api_datetime.h"
|
||||
#include "rocky_api_global.h"
|
||||
#include "rocky_api_graphics.h"
|
||||
#include "rocky_api_memory.h"
|
||||
#include "rocky_api_preferences.h"
|
||||
#include "rocky_api_tickservice.h"
|
||||
#include "rocky_api_timers.h"
|
||||
#include "rocky_api_watchinfo.h"
|
||||
|
||||
void rocky_api_watchface_init(void) {
|
||||
static const RockyGlobalAPI *const apis[] = {
|
||||
#if !APPLIB_EMSCRIPTEN
|
||||
&APP_MESSAGE_APIS,
|
||||
&CONSOLE_APIS,
|
||||
#endif
|
||||
|
||||
&DATETIME_APIS,
|
||||
&GRAPHIC_APIS,
|
||||
|
||||
#if !APPLIB_EMSCRIPTEN
|
||||
&MEMORY_APIS,
|
||||
&PREFERENCES_APIS,
|
||||
#endif
|
||||
|
||||
&TICKSERVICE_APIS,
|
||||
|
||||
#if !APPLIB_EMSCRIPTEN
|
||||
&TIMER_APIS,
|
||||
&WATCHINFO_APIS,
|
||||
#endif
|
||||
|
||||
NULL,
|
||||
};
|
||||
rocky_global_init(apis);
|
||||
}
|
||||
|
||||
void rocky_api_deinit(void) {
|
||||
rocky_global_deinit();
|
||||
}
|
44
src/fw/applib/rockyjs/api/rocky_api.h
Normal file
44
src/fw/applib/rockyjs/api/rocky_api.h
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stdbool.h"
|
||||
#include "jerry-api.h"
|
||||
|
||||
// generic callback per API, e.g. to (de-) initialize
|
||||
typedef void (*RockyAPIHandler)(void);
|
||||
|
||||
// callback to let APIs know when a caller registers an event rocky.on(event_name, handler)
|
||||
// return true, if you are interested in the given event so that the pair will be stored
|
||||
typedef bool (*RockyEventedAPIAddHandler)(const char *event_name, jerry_value_t handler);
|
||||
|
||||
// callback to let APIs know when a caller unregisters an event rocky.off(event_name, handler)
|
||||
typedef void (*RockyEventedAPIRemoveHandler)(const char *event_name, jerry_value_t handler);
|
||||
|
||||
typedef struct {
|
||||
RockyAPIHandler init;
|
||||
RockyAPIHandler deinit;
|
||||
|
||||
// responds to .on('someevent', f)
|
||||
RockyEventedAPIAddHandler add_handler;
|
||||
// responds to .off('someevent', f);
|
||||
RockyEventedAPIRemoveHandler remove_handler;
|
||||
} RockyGlobalAPI;
|
||||
|
||||
|
||||
void rocky_api_watchface_init(void);
|
||||
void rocky_api_deinit(void);
|
1070
src/fw/applib/rockyjs/api/rocky_api_app_message.c
Normal file
1070
src/fw/applib/rockyjs/api/rocky_api_app_message.c
Normal file
File diff suppressed because it is too large
Load diff
123
src/fw/applib/rockyjs/api/rocky_api_app_message.h
Normal file
123
src/fw/applib/rockyjs/api/rocky_api_app_message.h
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
#include <util/attributes.h>
|
||||
|
||||
#define POSTMESSAGE_PROTOCOL_MIN_VERSION ((uint8_t)1)
|
||||
#define POSTMESSAGE_PROTOCOL_MAX_VERSION ((uint8_t)1)
|
||||
|
||||
//! Max size in bytes of the largest Chunk payload that can be sent out.
|
||||
#define POSTMESSAGE_PROTOCOL_MAX_TX_CHUNK_SIZE ((uint16_t)1000)
|
||||
|
||||
//! Max size in bytes of the largest Chunk payload that can be received.
|
||||
#define POSTMESSAGE_PROTOCOL_MAX_RX_CHUNK_SIZE ((uint16_t)1000)
|
||||
|
||||
//! Message Types, expected values are byte arrays. See structs below.
|
||||
typedef enum {
|
||||
PostMessageKeyInvalid = 0,
|
||||
|
||||
//! Has no payload.
|
||||
PostMessageKeyResetRequest = 1,
|
||||
|
||||
//! Has PostMessageResetCompletePayload as payload.
|
||||
PostMessageKeyResetComplete = 2,
|
||||
|
||||
//! Has PostMessageChunkPayload as payload.
|
||||
PostMessageKeyChunk = 3,
|
||||
|
||||
//! Has PostMessageUnsupportedErrorPayload as payload.
|
||||
PostMessageKeyUnsupportedError = 4,
|
||||
|
||||
PostMessageKey_Count
|
||||
} PostMessageKey;
|
||||
|
||||
typedef struct PACKED {
|
||||
//! Lowest supported version (inclusive)
|
||||
uint8_t min_supported_version;
|
||||
|
||||
//! Highest supported version (inclusive)
|
||||
uint8_t max_supported_version;
|
||||
|
||||
//! Maximum Chunk size (little-endian) that the sender of ResetComplete is capable of sending.
|
||||
uint16_t max_tx_chunk_size;
|
||||
|
||||
//! Maximum Chunk size (little-endian) that the sender of ResetComplete is capable of receiving.
|
||||
uint16_t max_rx_chunk_size;
|
||||
} PostMessageResetCompletePayload;
|
||||
|
||||
_Static_assert(sizeof(PostMessageResetCompletePayload) >= 6,
|
||||
"Should never be smaller than the V1 payload!");
|
||||
|
||||
typedef struct PACKED {
|
||||
union PACKED {
|
||||
//! Header for first Chunk in a sequence of chunks. Valid when is_first == true.
|
||||
struct PACKED {
|
||||
//! Total size of the object (sum of the lengths of all chunk_data in the Chunk sequence)
|
||||
//! including a zero byte at the end of the JSON string.
|
||||
uint32_t total_size_bytes:31;
|
||||
//! Always set to true for the first Chunk.
|
||||
bool is_first:1;
|
||||
};
|
||||
//! Header for continuation Chunks in a sequence of chunks. Valid when is_first == false.
|
||||
struct PACKED {
|
||||
//! The offset of the chunk_data into the fully assembled object.
|
||||
uint32_t offset_bytes:31;
|
||||
//! Always set to false for a continuation Chunk.
|
||||
//! @note the compiler doesn't like it if the field is called the same, even though it's of
|
||||
//! the same type and occupies the same bits...
|
||||
bool continuation_is_first:1;
|
||||
};
|
||||
};
|
||||
//! JSON string data (potentially a partial fragment).
|
||||
//! The final Chunk's chunk_data MUST be zero-terminated!
|
||||
char chunk_data[0];
|
||||
} PostMessageChunkPayload;
|
||||
|
||||
typedef enum {
|
||||
PostMessageErrorUnsupportedVersion,
|
||||
PostMessageErrorMalformedResetComplete,
|
||||
} PostMessageError;
|
||||
|
||||
typedef struct PACKED {
|
||||
PostMessageError error_code:8;
|
||||
} PostMessageUnsupportedErrorPayload;
|
||||
|
||||
//! See statechart diagram over here:
|
||||
//! https://pebbletechnology.atlassian.net/wiki/display/PRODUCT/postMessage%28%29+protocol
|
||||
typedef enum {
|
||||
//! Transport (AppMessage / PP) Disconnected
|
||||
PostMessageStateDisconnected = 0,
|
||||
|
||||
//! No negotiated state, wait for remote to request reset
|
||||
PostMessageStateAwaitingResetRequest,
|
||||
|
||||
//! Waiting for a reset complete message, the remote side had initiated the ResetRequest.
|
||||
PostMessageStateAwaitingResetCompleteRemoteInitiated,
|
||||
|
||||
//! Waiting for a reset complete message, the local side had initiated the ResetRequest.
|
||||
PostMessageStateAwaitingResetCompleteLocalInitiated,
|
||||
|
||||
//! Transport connected, negotiation complete and ready to send and receive payload chunks.
|
||||
PostMessageStateSessionOpen,
|
||||
|
||||
PostMessageState_Count
|
||||
} PostMessageState;
|
||||
|
||||
extern const RockyGlobalAPI APP_MESSAGE_APIS;
|
65
src/fw/applib/rockyjs/api/rocky_api_console.c
Normal file
65
src/fw/applib/rockyjs/api/rocky_api_console.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 "rocky_api_console.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define ROCKY_CONSOLE "console"
|
||||
#define ROCKY_CONSOLE_LOG "log"
|
||||
#define ROCKY_CONSOLE_WARN "warn"
|
||||
#define ROCKY_CONSOLE_ERROR "error"
|
||||
|
||||
static jerry_value_t prv_log(uint8_t level, jerry_length_t argc, const jerry_value_t argv[]) {
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
char buffer[100] = {0};
|
||||
jerry_object_to_string_to_utf8_char_buffer(argv[i], (jerry_char_t *)buffer, sizeof(buffer));
|
||||
app_log(level, "JS", 0, "%s", buffer);
|
||||
}
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_console_log) {
|
||||
return prv_log(APP_LOG_LEVEL_INFO, argc, argv);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_console_warn) {
|
||||
return prv_log(APP_LOG_LEVEL_WARNING, argc, argv);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_console_error) {
|
||||
return prv_log(APP_LOG_LEVEL_ERROR, argc, argv);
|
||||
}
|
||||
|
||||
static void prv_init(void) {
|
||||
bool was_created;
|
||||
JS_VAR console =
|
||||
rocky_get_or_create_object(jerry_create_undefined(), ROCKY_CONSOLE, rocky_creator_object,
|
||||
NULL, &was_created);
|
||||
|
||||
// there must not be a global console object yet
|
||||
PBL_ASSERTN(was_created);
|
||||
|
||||
rocky_add_function(console, ROCKY_CONSOLE_LOG, prv_console_log);
|
||||
rocky_add_function(console, ROCKY_CONSOLE_WARN, prv_console_warn);
|
||||
rocky_add_function(console, ROCKY_CONSOLE_ERROR, prv_console_error);
|
||||
}
|
||||
|
||||
const RockyGlobalAPI CONSOLE_APIS = {
|
||||
.init = prv_init,
|
||||
};
|
21
src/fw/applib/rockyjs/api/rocky_api_console.h
Normal file
21
src/fw/applib/rockyjs/api/rocky_api_console.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
extern const RockyGlobalAPI CONSOLE_APIS;
|
393
src/fw/applib/rockyjs/api/rocky_api_datetime.c
Normal file
393
src/fw/applib/rockyjs/api/rocky_api_datetime.c
Normal file
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* 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 "rocky_api_datetime.h"
|
||||
#include "rocky_api_errors.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "services/common/clock.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define ROCKY_DATE_TOLOCALETIMESTRING "toLocaleTimeString"
|
||||
#define ROCKY_DATE_TOLOCALEDATESTRING "toLocaleDateString"
|
||||
#define ROCKY_DATE_TOLOCALESTRING "toLocaleString"
|
||||
#define ROCKY_DATE_FORMAT_NUMERIC "numeric"
|
||||
#define ROCKY_DATE_FORMAT_2DIGIT "2-digit"
|
||||
#define ROCKY_DATE_FORMAT_SHORT "short"
|
||||
#define ROCKY_DATE_FORMAT_LONG "long"
|
||||
|
||||
#define BUFFER_LEN_DATE 40
|
||||
#define BUFFER_LEN_TIME 20
|
||||
// 2 = strlen(", ")
|
||||
#define BUFFER_LEN_DATETIME (BUFFER_LEN_DATE + BUFFER_LEN_TIME + 2)
|
||||
|
||||
|
||||
static void prv_tm_from_js_date(jerry_value_t date, struct tm *tm) {
|
||||
JS_VAR js_seconds = jerry_get_object_getter_result(date, "getSeconds");
|
||||
JS_VAR js_minutes = jerry_get_object_getter_result(date, "getMinutes");
|
||||
JS_VAR js_hours = jerry_get_object_getter_result(date, "getHours");
|
||||
JS_VAR js_mdays = jerry_get_object_getter_result(date, "getDate");
|
||||
JS_VAR js_month = jerry_get_object_getter_result(date, "getMonth");
|
||||
JS_VAR js_year = jerry_get_object_getter_result(date, "getFullYear");
|
||||
JS_VAR js_wday = jerry_get_object_getter_result(date, "getDay");
|
||||
|
||||
*tm = (struct tm) {
|
||||
.tm_sec = (int)jerry_get_number_value(js_seconds),
|
||||
.tm_min = (int)jerry_get_number_value(js_minutes),
|
||||
.tm_hour = (int)jerry_get_number_value(js_hours),
|
||||
.tm_mday = (int)jerry_get_number_value(js_mdays),
|
||||
.tm_mon = (int)jerry_get_number_value(js_month),
|
||||
.tm_year = (int)jerry_get_number_value(js_year) - 1900,
|
||||
.tm_wday = (int)jerry_get_number_value(js_wday),
|
||||
// seems as we can live without those for now
|
||||
// .tm_yday = ,
|
||||
// .tm_isdst = ,
|
||||
// .tm_gmtoff = ,
|
||||
// .tm_zone = ,
|
||||
};
|
||||
}
|
||||
|
||||
static bool prv_matches_system_locale(jerry_value_t locale) {
|
||||
if (jerry_value_is_undefined(locale)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// in the future, we could run a case-insenstive compare against app_get_system_locale()
|
||||
// but as we want apps to encourage to be i18n, there's no real point to
|
||||
// receive strings such as 'en-us'. We will ask them to always pass undefined instead
|
||||
return false;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
ToStringFormatUnsupported = 1 << 0,
|
||||
ToStringFormatLocaleTime = 1 << 1,
|
||||
ToStringFormatSecondNumeric = 1 << 2,
|
||||
ToStringFormatSecond2Digit = 1 << 3,
|
||||
ToStringFormatMinuteNumeric = 1 << 4,
|
||||
ToStringFormatMinute2Digit = 1 << 5,
|
||||
ToStringFormatHourNumeric = 1 << 6,
|
||||
ToStringFormatHour2Digit = 1 << 7,
|
||||
ToStringFormatLocaleDate = 1 << 8,
|
||||
ToStringFormatDayNumeric = 1 << 9,
|
||||
ToStringFormatDay2Digit = 1 << 10,
|
||||
ToStringFormatDayShort = 1 << 11,
|
||||
ToStringFormatDayLong = 1 << 12,
|
||||
ToStringFormatMonthNumeric = 1 << 13,
|
||||
ToStringFormatMonth2Digit = 1 << 14,
|
||||
ToStringFormatMonthShort = 1 << 15,
|
||||
ToStringFormatMonthLong = 1 << 16,
|
||||
ToStringFormatYearNumeric = 1 << 17,
|
||||
ToStringFormatYear2Digit = 1 << 18,
|
||||
ToStringFormatEmpty = 1 << 19,
|
||||
} ToStringFormat;
|
||||
|
||||
static const ToStringFormat ToStringFormatTimeMask = (
|
||||
ToStringFormatLocaleTime |
|
||||
ToStringFormatSecondNumeric |
|
||||
ToStringFormatSecond2Digit |
|
||||
ToStringFormatMinuteNumeric |
|
||||
ToStringFormatMinute2Digit |
|
||||
ToStringFormatHourNumeric |
|
||||
ToStringFormatHour2Digit
|
||||
);
|
||||
|
||||
static const ToStringFormat ToStringFormatDateMask = (
|
||||
ToStringFormatLocaleDate |
|
||||
ToStringFormatDayNumeric |
|
||||
ToStringFormatDay2Digit |
|
||||
ToStringFormatDayShort |
|
||||
ToStringFormatDayLong |
|
||||
ToStringFormatMonthNumeric |
|
||||
ToStringFormatMonth2Digit |
|
||||
ToStringFormatMonthShort |
|
||||
ToStringFormatMonthLong |
|
||||
ToStringFormatYearNumeric |
|
||||
ToStringFormatYear2Digit
|
||||
);
|
||||
|
||||
static ToStringFormat prv_parse_to_string_format(const jerry_value_t options,
|
||||
ToStringFormat default_format,
|
||||
ToStringFormat mask,
|
||||
bool *is_24h_style) {
|
||||
JS_VAR second = jerry_get_object_field(options, "second");
|
||||
JS_VAR minute = jerry_get_object_field(options, "minute");
|
||||
JS_VAR hour = jerry_get_object_field(options, "hour");
|
||||
JS_VAR day = jerry_get_object_field(options, "day");
|
||||
JS_VAR month = jerry_get_object_field(options, "month");
|
||||
JS_VAR year = jerry_get_object_field(options, "year");
|
||||
JS_VAR hour12 = jerry_get_object_field(options, "hour12");
|
||||
|
||||
if (!jerry_value_is_undefined(hour12)) {
|
||||
*is_24h_style = !jerry_get_boolean_value(hour12);
|
||||
}
|
||||
|
||||
struct {
|
||||
const jerry_value_t field;
|
||||
const char *value;
|
||||
ToStringFormat format;
|
||||
} option_values[] = {
|
||||
{.field = second, .value = ROCKY_DATE_FORMAT_NUMERIC, .format = ToStringFormatSecondNumeric},
|
||||
{.field = second, .value = ROCKY_DATE_FORMAT_2DIGIT, .format = ToStringFormatSecond2Digit},
|
||||
{.field = minute, .value = ROCKY_DATE_FORMAT_NUMERIC, .format = ToStringFormatMinuteNumeric},
|
||||
{.field = minute, .value = ROCKY_DATE_FORMAT_2DIGIT, .format = ToStringFormatMinute2Digit},
|
||||
{.field = hour, .value = ROCKY_DATE_FORMAT_NUMERIC, .format = ToStringFormatHourNumeric},
|
||||
{.field = hour, .value = ROCKY_DATE_FORMAT_2DIGIT, .format = ToStringFormatHour2Digit},
|
||||
{.field = day, .value = ROCKY_DATE_FORMAT_NUMERIC, .format = ToStringFormatDayNumeric},
|
||||
{.field = day, .value = ROCKY_DATE_FORMAT_2DIGIT, .format = ToStringFormatDay2Digit},
|
||||
{.field = day, .value = ROCKY_DATE_FORMAT_SHORT, .format = ToStringFormatDayShort},
|
||||
{.field = day, .value = ROCKY_DATE_FORMAT_LONG, .format = ToStringFormatDayLong},
|
||||
{.field = month, .value = ROCKY_DATE_FORMAT_NUMERIC, .format = ToStringFormatMonthNumeric},
|
||||
{.field = month, .value = ROCKY_DATE_FORMAT_2DIGIT, .format = ToStringFormatMonth2Digit},
|
||||
{.field = month, .value = ROCKY_DATE_FORMAT_SHORT, .format = ToStringFormatMonthShort},
|
||||
{.field = month, .value = ROCKY_DATE_FORMAT_LONG, .format = ToStringFormatMonthLong},
|
||||
{.field = year, .value = ROCKY_DATE_FORMAT_NUMERIC, .format = ToStringFormatYearNumeric},
|
||||
{.field = year, .value = ROCKY_DATE_FORMAT_2DIGIT, .format = ToStringFormatYear2Digit},
|
||||
};
|
||||
|
||||
int found_options = 0;
|
||||
ToStringFormat result = default_format;
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(option_values); i++) {
|
||||
if ((option_values[i].format & mask) == 0) {
|
||||
// skip option values that are irrelevant
|
||||
continue;
|
||||
}
|
||||
if (rocky_str_equal(option_values[i].field, option_values[i].value)) {
|
||||
result = option_values[i].format;
|
||||
found_options++;
|
||||
}
|
||||
if (found_options > 1) {
|
||||
// today, we don't support combinations of several options, it's either none or one
|
||||
return ToStringFormatUnsupported;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const char *prv_strftime_format(ToStringFormat format, bool is_24h_style) {
|
||||
switch (format) {
|
||||
case ToStringFormatUnsupported:
|
||||
WTF;
|
||||
case ToStringFormatLocaleTime:
|
||||
return is_24h_style ? "%H:%M:%S" : "%I:%M:%S %p";
|
||||
case ToStringFormatSecondNumeric:
|
||||
case ToStringFormatSecond2Digit:
|
||||
return "%S";
|
||||
case ToStringFormatMinuteNumeric:
|
||||
case ToStringFormatMinute2Digit:
|
||||
return "%M";
|
||||
case ToStringFormatHourNumeric:
|
||||
case ToStringFormatHour2Digit:
|
||||
return is_24h_style ? "%H" : "%I %p";
|
||||
case ToStringFormatLocaleDate:
|
||||
return "%x";
|
||||
case ToStringFormatDayNumeric:
|
||||
case ToStringFormatDay2Digit:
|
||||
return "%d";
|
||||
case ToStringFormatDayShort:
|
||||
return "%a";
|
||||
case ToStringFormatDayLong:
|
||||
return "%A";
|
||||
case ToStringFormatMonthNumeric:
|
||||
case ToStringFormatMonth2Digit:
|
||||
return "%m";
|
||||
case ToStringFormatMonthShort:
|
||||
return "%b";
|
||||
case ToStringFormatMonthLong:
|
||||
return "%B";
|
||||
case ToStringFormatYearNumeric:
|
||||
return "%Y";
|
||||
case ToStringFormatYear2Digit:
|
||||
return "%y";
|
||||
case ToStringFormatEmpty:
|
||||
return "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static bool prv_strip_leading_zero(ToStringFormat format, bool is_24h_style) {
|
||||
switch (format) {
|
||||
case ToStringFormatLocaleTime:
|
||||
// %I adds leading zeros, for single digit hours. We don't want that for 12h
|
||||
return !is_24h_style;
|
||||
case ToStringFormatSecondNumeric:
|
||||
case ToStringFormatMinuteNumeric:
|
||||
case ToStringFormatHourNumeric:
|
||||
case ToStringFormatDayNumeric:
|
||||
case ToStringFormatMonthNumeric:
|
||||
return true;
|
||||
|
||||
case ToStringFormatUnsupported:
|
||||
case ToStringFormatEmpty:
|
||||
case ToStringFormatSecond2Digit:
|
||||
case ToStringFormatMinute2Digit:
|
||||
case ToStringFormatHour2Digit:
|
||||
case ToStringFormatLocaleDate:
|
||||
case ToStringFormatDay2Digit:
|
||||
case ToStringFormatDayShort:
|
||||
case ToStringFormatDayLong:
|
||||
case ToStringFormatMonthShort:
|
||||
case ToStringFormatMonthLong:
|
||||
case ToStringFormatMonth2Digit:
|
||||
return false;
|
||||
case ToStringFormatYearNumeric:
|
||||
case ToStringFormatYear2Digit:
|
||||
// yes, we want to keep leading zeros in both cases for year as we control the format
|
||||
// exclusively via the strftime format
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static size_t prv_to_locale_buffer(jerry_value_t this_val,
|
||||
jerry_length_t argc, const jerry_value_t *argv,
|
||||
ToStringFormat default_format,
|
||||
ToStringFormat mask,
|
||||
char *buffer, size_t buffer_len,
|
||||
jerry_value_t *error) {
|
||||
JS_VAR locale = argc >= 1 ? jerry_acquire_value(argv[0]) : jerry_create_undefined();
|
||||
JS_VAR options = argc >= 2 ? jerry_acquire_value(argv[1]) : jerry_create_object();
|
||||
|
||||
if (!prv_matches_system_locale(locale)) {
|
||||
if (error) {
|
||||
*error = rocky_error_argument_invalid("Unsupported locale");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
bool is_24h_style = clock_is_24h_style();
|
||||
const ToStringFormat format = prv_parse_to_string_format(options, default_format, mask,
|
||||
&is_24h_style);
|
||||
if (format == ToStringFormatUnsupported) {
|
||||
if (error) {
|
||||
*error = rocky_error_argument_invalid("Unsupported options");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct tm tm;
|
||||
prv_tm_from_js_date(this_val, &tm);
|
||||
|
||||
const char *strftime_format = prv_strftime_format(format, is_24h_style);
|
||||
const size_t str_len = strftime(buffer, buffer_len, strftime_format, &tm);
|
||||
|
||||
const bool strip_leading_char = buffer[0] == '0' && prv_strip_leading_zero(format, is_24h_style);
|
||||
if (strip_leading_char) {
|
||||
memmove(buffer, buffer + 1, str_len);
|
||||
}
|
||||
return str_len;
|
||||
}
|
||||
|
||||
static jerry_value_t prv_to_locale_time_or_date_string(jerry_value_t this_val,
|
||||
jerry_length_t argc,
|
||||
const jerry_value_t argv[],
|
||||
ToStringFormat date_default_format,
|
||||
ToStringFormat time_default_format) {
|
||||
// both, .toLocaleTimeString() and .toLocaleDateString() fall back to "<date>, <time>"
|
||||
// if clients specify options that are not part of time / date
|
||||
// similarly, .toLocaleString() falls back to "<date>, <time>" if no known option was specified
|
||||
// yes, in some code paths, this isn't the most-efficient but it's robust on the other hand
|
||||
|
||||
// format date
|
||||
char date_buffer[BUFFER_LEN_DATE] = {0};
|
||||
jerry_value_t error = jerry_create_undefined();
|
||||
const size_t date_len = prv_to_locale_buffer(this_val, argc, argv,
|
||||
date_default_format, ToStringFormatDateMask,
|
||||
date_buffer, sizeof(date_buffer), &error);
|
||||
if (jerry_value_has_error_flag(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
// format time
|
||||
char time_buffer[BUFFER_LEN_TIME] = {0};
|
||||
const size_t time_len = prv_to_locale_buffer(this_val, argc, argv,
|
||||
time_default_format, ToStringFormatTimeMask,
|
||||
time_buffer, sizeof(time_buffer), &error);
|
||||
if (jerry_value_has_error_flag(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
// concatenate result
|
||||
char result_buffer[BUFFER_LEN_DATETIME] = {0};
|
||||
size_t remaining_buffer_size = sizeof(result_buffer);
|
||||
|
||||
strncpy(result_buffer, date_buffer, sizeof(result_buffer));
|
||||
remaining_buffer_size -= date_len;
|
||||
|
||||
if (date_len > 0 && time_len > 0) {
|
||||
strncat(result_buffer, ", ", remaining_buffer_size);
|
||||
remaining_buffer_size -= 2; // 2 = strlen(", ")
|
||||
}
|
||||
|
||||
strncat(result_buffer, time_buffer, remaining_buffer_size);
|
||||
|
||||
return jerry_create_string_utf8((jerry_char_t *)result_buffer);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_to_locale_time_string) {
|
||||
return prv_to_locale_time_or_date_string(this_val, argc, argv,
|
||||
ToStringFormatEmpty, ToStringFormatLocaleTime);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_to_locale_date_string) {
|
||||
return prv_to_locale_time_or_date_string(this_val, argc, argv,
|
||||
ToStringFormatLocaleDate, ToStringFormatEmpty);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_to_locale_string) {
|
||||
jerry_value_t error = jerry_create_undefined();
|
||||
char buffer[BUFFER_LEN_DATETIME] = {0};
|
||||
|
||||
// we allow users to pick from any option to here
|
||||
const size_t total_len = prv_to_locale_buffer(this_val, argc, argv,
|
||||
ToStringFormatEmpty,
|
||||
ToStringFormatDateMask | ToStringFormatTimeMask,
|
||||
buffer, sizeof(buffer), &error);
|
||||
if (jerry_value_has_error_flag(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (total_len != 0) {
|
||||
// user picked options, so we formatted something into buffer
|
||||
return jerry_create_string_utf8((jerry_char_t *) buffer);
|
||||
}
|
||||
|
||||
// if total_len == 0, nothing was formatted and we default to "<date>, <time>"
|
||||
return prv_to_locale_time_or_date_string(this_val, 0, NULL,
|
||||
ToStringFormatLocaleDate, ToStringFormatLocaleTime);
|
||||
}
|
||||
|
||||
//
|
||||
static void prv_rocky_add_date_functions(jerry_value_t global) {
|
||||
JS_VAR date_constructor = jerry_get_object_field(global, "Date");
|
||||
JS_VAR date_prototype = jerry_get_object_field(date_constructor, "prototype");
|
||||
JS_VAR locale_time_string = jerry_create_external_function(prv_to_locale_time_string);
|
||||
jerry_set_object_field(date_prototype, ROCKY_DATE_TOLOCALETIMESTRING, locale_time_string);
|
||||
JS_VAR locale_date_string = jerry_create_external_function(prv_to_locale_date_string);
|
||||
jerry_set_object_field(date_prototype, ROCKY_DATE_TOLOCALEDATESTRING, locale_date_string);
|
||||
JS_VAR locale_string = jerry_create_external_function(prv_to_locale_string);
|
||||
jerry_set_object_field(date_prototype, ROCKY_DATE_TOLOCALESTRING, locale_string);
|
||||
}
|
||||
|
||||
static void prv_init(void) {
|
||||
JS_VAR global = jerry_get_global_object();
|
||||
prv_rocky_add_date_functions(global);
|
||||
}
|
||||
|
||||
const RockyGlobalAPI DATETIME_APIS = {
|
||||
.init = prv_init,
|
||||
};
|
21
src/fw/applib/rockyjs/api/rocky_api_datetime.h
Normal file
21
src/fw/applib/rockyjs/api/rocky_api_datetime.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
extern const RockyGlobalAPI DATETIME_APIS;
|
98
src/fw/applib/rockyjs/api/rocky_api_errors.c
Normal file
98
src/fw/applib/rockyjs/api/rocky_api_errors.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 "rocky_api_errors.h"
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
|
||||
jerry_value_t rocky_error_arguments_missing(void) {
|
||||
jerry_char_t *msg = (jerry_char_t *)"Not enough arguments";
|
||||
return jerry_create_error(JERRY_ERROR_TYPE, msg);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_error_argument_invalid(const char *msg) {
|
||||
return jerry_create_error(JERRY_ERROR_TYPE, (jerry_char_t *)msg);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_error_argument_invalid_at_index(uint32_t arg_idx, const char *error_msg) {
|
||||
char buffer[100] = {0};
|
||||
snprintf(buffer, sizeof(buffer), "Argument at index %"PRIu32" is invalid: %s",
|
||||
arg_idx, error_msg);
|
||||
return rocky_error_argument_invalid(buffer);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_error_unexpected_type(uint32_t arg_idx, const char *expected_type_name) {
|
||||
char buffer[100] = {0};
|
||||
snprintf(buffer, sizeof(buffer), "Argument at index %"PRIu32" is not a %s",
|
||||
arg_idx, expected_type_name);
|
||||
return rocky_error_argument_invalid(buffer);
|
||||
}
|
||||
|
||||
static jerry_value_t prv_error_two_parts(jerry_error_t error_type,
|
||||
const char *left, const char *right) {
|
||||
char buffer[100] = {0};
|
||||
snprintf(buffer, sizeof(buffer), "%s%s", left, right);
|
||||
return jerry_create_error(error_type, (jerry_char_t *)buffer);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_error_oom(const char *hint) {
|
||||
return prv_error_two_parts(JERRY_ERROR_RANGE, "Out of memory: ", hint);
|
||||
}
|
||||
|
||||
static char * prv_get_string_from_field(jerry_value_t object, const jerry_char_t *property) {
|
||||
JS_VAR prop_name = jerry_create_string((const jerry_char_t *) property);
|
||||
JS_VAR prop_val = jerry_get_property(object, prop_name);
|
||||
JS_VAR prop_str = jerry_value_to_string(prop_val);
|
||||
char *result = rocky_string_alloc_and_copy(prop_str);
|
||||
return result;
|
||||
}
|
||||
|
||||
// from lit-magic-string.inc.h
|
||||
const jerry_char_t ERROR_NAME_PROPERTY_NAME[] = "name";
|
||||
const jerry_char_t ERROR_MSG_PROPERTY_NAME[] = "message";
|
||||
|
||||
void rocky_error_print(jerry_value_t error_val) {
|
||||
char *name = NULL;
|
||||
char *msg = NULL;
|
||||
if (jerry_value_is_object(error_val)) {
|
||||
name = prv_get_string_from_field(error_val, ERROR_NAME_PROPERTY_NAME);
|
||||
msg = prv_get_string_from_field(error_val, ERROR_MSG_PROPERTY_NAME);
|
||||
} else {
|
||||
jerry_value_clear_error_flag(&error_val);
|
||||
JS_VAR error_str = jerry_value_to_string(error_val);
|
||||
msg = rocky_string_alloc_and_copy(error_str);
|
||||
}
|
||||
|
||||
if (name) {
|
||||
APP_LOG(LOG_LEVEL_ERROR, "Unhandled %s", name);
|
||||
} else {
|
||||
APP_LOG(LOG_LEVEL_ERROR, "Unhandled exception");
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
APP_LOG(LOG_LEVEL_ERROR, " %s", msg);
|
||||
}
|
||||
|
||||
task_free(name);
|
||||
task_free(msg);
|
||||
}
|
33
src/fw/applib/rockyjs/api/rocky_api_errors.h
Normal file
33
src/fw/applib/rockyjs/api/rocky_api_errors.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jerry-api.h"
|
||||
|
||||
// Raise TypeError: Not enough arguments
|
||||
jerry_value_t rocky_error_arguments_missing(void);
|
||||
|
||||
jerry_value_t rocky_error_argument_invalid_at_index(uint32_t arg_idx, const char *error_msg);
|
||||
|
||||
jerry_value_t rocky_error_argument_invalid(const char *msg);
|
||||
|
||||
jerry_value_t rocky_error_oom(const char *hint);
|
||||
|
||||
jerry_value_t rocky_error_unexpected_type(uint32_t arg_idx, const char *expected_type_name);
|
||||
|
||||
// Print error type & msg
|
||||
void rocky_error_print(jerry_value_t error_val);
|
351
src/fw/applib/rockyjs/api/rocky_api_global.c
Normal file
351
src/fw/applib/rockyjs/api/rocky_api_global.c
Normal file
|
@ -0,0 +1,351 @@
|
|||
/*
|
||||
* 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 "rocky_api_global.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "jerry-api.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/passert.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#include "rocky_api.h"
|
||||
#include "rocky_api_errors.h"
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#define ROCKY_LISTENERS "_listeners"
|
||||
|
||||
#define ROCKY_ON "on"
|
||||
#define ROCKY_ADD_EVENT_LISTENER "addEventListener"
|
||||
#define ROCKY_OFF "off"
|
||||
#define ROCKY_REMOVE_EVENT_LISTENER "removeEventListener"
|
||||
#define ROCKY_EVENT_CONSTRUCTOR "Event"
|
||||
#define ROCKY_EVENT_TYPE "type"
|
||||
|
||||
// TODO: PBL-35780 make this part of app_state_get_rocky_runtime_context()
|
||||
SECTION(".rocky_bss") static const RockyGlobalAPI *const *s_global_apis;
|
||||
|
||||
#define API_REFS_FOREACH(var_name) \
|
||||
for (RockyGlobalAPI const *const *var_name = s_global_apis; *var_name != NULL; var_name++)
|
||||
|
||||
|
||||
static jerry_value_t prv_get_or_create_listener_array(const char *event_name) {
|
||||
JS_VAR rocky = rocky_get_rocky_singleton();
|
||||
JS_VAR all_listeners = rocky_get_or_create_object(rocky, ROCKY_LISTENERS,
|
||||
rocky_creator_object,
|
||||
NULL, NULL);
|
||||
return rocky_get_or_create_object(all_listeners, event_name,
|
||||
rocky_creator_empty_array,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
// callback while iterating over listeners
|
||||
// @param event_listeners the array this listener is part of
|
||||
// @param idx position within the array at which the listener exists
|
||||
// @param listener the JS function that's registered as listener
|
||||
// @param data pointer provided when calling prv_iterate_event_listeners()
|
||||
// @return true, if you interested in further iterations. False to stop iterating.
|
||||
typedef bool (*EventListenerIteratorCb)(jerry_value_t event_listeners, uint32_t idx,
|
||||
jerry_value_t listener, void *data);
|
||||
|
||||
static void prv_iterate_event_listeners(const char *event_name,
|
||||
EventListenerIteratorCb callback,
|
||||
void *data) {
|
||||
PBL_ASSERTN(callback);
|
||||
|
||||
JS_VAR rocky = rocky_get_rocky_singleton();
|
||||
JS_VAR all_listeners = jerry_get_object_field(rocky, "_listeners");
|
||||
JS_VAR event_listeners = jerry_get_object_field(all_listeners, event_name);
|
||||
// printf("event_listeners.refcount: %d", jerry_port_)
|
||||
|
||||
const uint32_t len = jerry_get_array_length(event_listeners);
|
||||
for (uint32_t idx = 0; idx < len; idx++) {
|
||||
JS_VAR listener = jerry_get_property_by_index(event_listeners, idx);
|
||||
if (jerry_value_is_function(listener)) {
|
||||
bool wants_more = callback(event_listeners, idx, listener, data);
|
||||
if (!wants_more) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
jerry_value_t listener;
|
||||
bool found;
|
||||
} ListenerQueryData;
|
||||
|
||||
static bool prv_find_listener(jerry_value_t event_listeners, uint32_t idx,
|
||||
jerry_value_t listener, void *data) {
|
||||
ListenerQueryData *query_data = data;
|
||||
if (query_data->listener == listener) {
|
||||
query_data->found = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_listener_is_registered(const char *event_name, jerry_value_t listener) {
|
||||
ListenerQueryData data = {
|
||||
.listener = listener,
|
||||
};
|
||||
prv_iterate_event_listeners(event_name, prv_find_listener, &data);
|
||||
return data.found;
|
||||
}
|
||||
|
||||
T_STATIC void prv_add_event_listener_to_list(const char *event_name, jerry_value_t listener) {
|
||||
if (prv_listener_is_registered(event_name, listener)) {
|
||||
// we won't register the same listener twice
|
||||
return;
|
||||
}
|
||||
JS_VAR listeners = prv_get_or_create_listener_array(event_name);
|
||||
const uint32_t num_entries = jerry_get_array_length(listeners);
|
||||
JS_UNUSED_VAL = jerry_set_property_by_index(listeners, num_entries, listener);
|
||||
}
|
||||
|
||||
static bool prv_remove_listener(jerry_value_t event_listeners, uint32_t idx,
|
||||
jerry_value_t listener, void *data) {
|
||||
ListenerQueryData *query_data = data;
|
||||
if (query_data->listener == listener) {
|
||||
// calling `event_listeners.splice(idx, 1)` to remove item at idx
|
||||
// wow, this is a lot of code for this
|
||||
JS_VAR splice = jerry_get_object_field(event_listeners, "splice");
|
||||
const jerry_value_t args[] = {jerry_create_number(idx), jerry_create_number(1)};
|
||||
JS_VAR remove_result = jerry_call_function(splice, event_listeners,
|
||||
args, ARRAY_LENGTH(args));
|
||||
if (jerry_value_has_error_flag(remove_result)) {
|
||||
rocky_log_exception("removing event listener", remove_result);
|
||||
}
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(args); i++) {
|
||||
jerry_release_value(args[i]);
|
||||
}
|
||||
|
||||
// mark item as removed and stop iterating
|
||||
query_data->found = true;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
T_STATIC bool prv_remove_event_listener_from_list(const char *event_name, jerry_value_t listener) {
|
||||
ListenerQueryData data = {
|
||||
.listener = listener,
|
||||
};
|
||||
prv_iterate_event_listeners(event_name, prv_remove_listener, &data);
|
||||
return data.found;
|
||||
}
|
||||
|
||||
|
||||
// implementation of .on(event_name, handler) and stores them in a new property
|
||||
//
|
||||
// rocky._listeners = {
|
||||
// "event_1" : [ function_1, function_2, ... ],
|
||||
// "event_2" ; [ ... ] ,
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// please note that we ignore events, no API is interested in by asking each of them
|
||||
// via .add_handler(event_name, func)
|
||||
|
||||
static jerry_value_t prv_event_listener_extract_args(const jerry_length_t argc,
|
||||
const jerry_value_t argv[],
|
||||
char *event_name, size_t event_name_size,
|
||||
jerry_value_t *func) {
|
||||
PBL_ASSERTN(event_name && func);
|
||||
|
||||
if (argc < 2) {
|
||||
return rocky_error_arguments_missing();
|
||||
}
|
||||
|
||||
memset(event_name, 0, event_name_size);
|
||||
const jerry_size_t len = jerry_string_to_utf8_char_buffer(argv[0],
|
||||
(jerry_char_t *)event_name,
|
||||
event_name_size);
|
||||
if (len == 0 || len == event_name_size) {
|
||||
return rocky_error_argument_invalid("Not a valid event");
|
||||
}
|
||||
|
||||
if (!jerry_value_is_function(argv[1])) {
|
||||
return rocky_error_argument_invalid("Not a valid handler");
|
||||
}
|
||||
*func = argv[1];
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_add_event_listener) {
|
||||
char event_name[32];
|
||||
jerry_value_t func; // out parameter &func will not be acquired
|
||||
JS_VAR arg_result = prv_event_listener_extract_args(argc, argv,
|
||||
event_name, sizeof(event_name),
|
||||
&func);
|
||||
if (jerry_value_has_error_flag(arg_result)) {
|
||||
return jerry_acquire_value(arg_result);
|
||||
}
|
||||
|
||||
bool is_relevant = false;
|
||||
API_REFS_FOREACH(api_ref) {
|
||||
if ((*api_ref)->add_handler) {
|
||||
is_relevant = (*api_ref)->add_handler(event_name, func);
|
||||
if (is_relevant) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_relevant) {
|
||||
APP_LOG(LOG_LEVEL_WARNING, "Unknown event '%s'", event_name);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
prv_add_event_listener_to_list((char *)event_name, func);
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_remove_event_listener) {
|
||||
char event_name[32];
|
||||
jerry_value_t func; // out parameter &func will not be acquired
|
||||
JS_VAR arg_result = prv_event_listener_extract_args(argc, argv,
|
||||
event_name, sizeof(event_name),
|
||||
&func);
|
||||
if (jerry_value_has_error_flag(arg_result)) {
|
||||
return jerry_acquire_value(arg_result);
|
||||
}
|
||||
|
||||
const bool removed = prv_remove_event_listener_from_list(event_name, func);
|
||||
if (removed) {
|
||||
API_REFS_FOREACH(api_ref) {
|
||||
if ((*api_ref)->remove_handler) {
|
||||
(*api_ref)->remove_handler(event_name, func);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
APP_LOG(LOG_LEVEL_WARNING, "Unknown handler for event '%s'", event_name);
|
||||
}
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_event_constructor) {
|
||||
if (argc < 1) {
|
||||
return rocky_error_arguments_missing();
|
||||
}
|
||||
if (!jerry_value_is_string(argv[0])) {
|
||||
return rocky_error_unexpected_type(0, "String");
|
||||
}
|
||||
jerry_set_object_field(this_val, ROCKY_EVENT_TYPE, argv[0]);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static void prv_copy_property(const jerry_value_t rocky,
|
||||
const char *name_from, const char *name_to) {
|
||||
JS_VAR on = jerry_get_object_field(rocky, name_from);
|
||||
jerry_set_object_field(rocky, name_to, on);
|
||||
}
|
||||
|
||||
void rocky_global_init(const RockyGlobalAPI *const *global_apis) {
|
||||
PBL_ASSERTN(global_apis);
|
||||
s_global_apis = global_apis;
|
||||
|
||||
JS_VAR rocky = jerry_create_object();
|
||||
// this keeps a permanent reference to the singleton
|
||||
rocky_set_rocky_singleton(rocky);
|
||||
|
||||
rocky_add_function(rocky, ROCKY_ON, prv_add_event_listener);
|
||||
prv_copy_property(rocky, ROCKY_ON, ROCKY_ADD_EVENT_LISTENER);
|
||||
rocky_add_function(rocky, ROCKY_OFF, prv_remove_event_listener);
|
||||
prv_copy_property(rocky, ROCKY_OFF, ROCKY_REMOVE_EVENT_LISTENER);
|
||||
|
||||
JS_UNUSED_VAL = rocky_add_constructor(ROCKY_EVENT_CONSTRUCTOR, prv_event_constructor);
|
||||
|
||||
API_REFS_FOREACH(api_ref) {
|
||||
if ((*api_ref)->init) {
|
||||
(*api_ref)->init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rocky_global_deinit(void) {
|
||||
API_REFS_FOREACH(api_ref) {
|
||||
if ((*api_ref)->deinit) {
|
||||
(*api_ref)->deinit();
|
||||
}
|
||||
}
|
||||
|
||||
#if APPLIB_EMSCRIPTEN
|
||||
rocky_delete_singleton();
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool prv_has_any(jerry_value_t event_listeners, uint32_t idx,
|
||||
jerry_value_t listener, void *data) {
|
||||
*(bool *)data = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rocky_global_has_event_handlers(const char *event_name) {
|
||||
bool result = false;
|
||||
prv_iterate_event_listeners(event_name, prv_has_any, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const jerry_value_t this_arg;
|
||||
const jerry_value_t *args_p;
|
||||
jerry_size_t args_count;
|
||||
} EventHandlersCallArgs;
|
||||
|
||||
static bool prv_call_event_handlers_cb(jerry_value_t event_listeners, uint32_t idx,
|
||||
jerry_value_t listener, void *data) {
|
||||
EventHandlersCallArgs * const args = data;
|
||||
rocky_util_call_user_function_and_log_uncaught_error(listener, args->this_arg, args->args_p,
|
||||
args->args_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
void rocky_global_call_event_handlers(jerry_value_t event) {
|
||||
EventHandlersCallArgs args = {
|
||||
.this_arg = jerry_create_undefined(),
|
||||
.args_p = &event,
|
||||
.args_count = 1,
|
||||
};
|
||||
|
||||
JS_VAR event_str = jerry_get_object_field(event, "type");
|
||||
char *event_name = rocky_string_alloc_and_copy(event_str);
|
||||
prv_iterate_event_listeners(event_name, prv_call_event_handlers_cb, &args);
|
||||
|
||||
task_free(event_name);
|
||||
jerry_release_value(args.this_arg);
|
||||
}
|
||||
|
||||
static void prv_call_event_handlers_async_cb(void *ctx) {
|
||||
jerry_value_t event = (jerry_value_t)(uintptr_t) ctx;
|
||||
rocky_global_call_event_handlers(event);
|
||||
jerry_release_value(event); // was acquired in rocky_global_call_event_handlers_async() call
|
||||
}
|
||||
|
||||
void rocky_global_call_event_handlers_async(jerry_value_t event) {
|
||||
sys_current_process_schedule_callback(prv_call_event_handlers_async_cb,
|
||||
(void *)(uintptr_t)jerry_acquire_value(event));
|
||||
}
|
||||
|
||||
jerry_value_t rocky_global_create_event(const char *type_str) {
|
||||
JS_VAR jerry_type_str = jerry_create_string_utf8((const jerry_char_t *)type_str);
|
||||
JS_VAR event = rocky_create_with_constructor(ROCKY_EVENT_CONSTRUCTOR, &jerry_type_str, 1);
|
||||
return jerry_acquire_value(event);
|
||||
}
|
36
src/fw/applib/rockyjs/api/rocky_api_global.h
Normal file
36
src/fw/applib/rockyjs/api/rocky_api_global.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
#include "jerry-api.h"
|
||||
|
||||
// array of evented APIs, last entry must be NULL
|
||||
void rocky_global_init(const RockyGlobalAPI *const *evented_apis);
|
||||
|
||||
void rocky_global_deinit(void);
|
||||
|
||||
bool rocky_global_has_event_handlers(const char *event_name);
|
||||
|
||||
void rocky_global_call_event_handlers(jerry_value_t event);
|
||||
|
||||
//! Schedules the event to be processed on a later event loop iteration.
|
||||
void rocky_global_call_event_handlers_async(jerry_value_t event);
|
||||
|
||||
// Create a BaseEvent, filling in the type field with the given type string.
|
||||
// The returned event must be released by the caller.
|
||||
jerry_value_t rocky_global_create_event(const char *type_str);
|
358
src/fw/applib/rockyjs/api/rocky_api_graphics.c
Normal file
358
src/fw/applib/rockyjs/api/rocky_api_graphics.c
Normal file
|
@ -0,0 +1,358 @@
|
|||
/*
|
||||
* 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 "jerry-api.h"
|
||||
#include "rocky_api_graphics.h"
|
||||
#include "rocky_api_graphics_path2d.h"
|
||||
#include "rocky_api_graphics_text.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "rocky_api_util_args.h"
|
||||
|
||||
#include "applib/app.h"
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/ui/ui.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/size.h"
|
||||
#include "util/trig.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "rocky_api_errors.h"
|
||||
#include "rocky_api_global.h"
|
||||
#include "rocky_api_graphics_color.h"
|
||||
|
||||
#define ROCKY_EVENT_DRAW "draw"
|
||||
#define ROCKY_EVENT_DRAW_CONTEXT "context"
|
||||
#define ROCKY_REQUESTDRAW "requestDraw"
|
||||
#define ROCKY_CONTEXT2D_CONSTRUCTOR "CanvasRenderingContext2D"
|
||||
#define ROCKY_CONTEXT2D_CANVAS "canvas"
|
||||
#define ROCKY_CONTEXT2D_CLEARRECT "clearRect"
|
||||
#define ROCKY_CONTEXT2D_FILLRECT "fillRect"
|
||||
#define ROCKY_CONTEXT2D_FILLRADIAL "rockyFillRadial"
|
||||
#define ROCKY_CONTEXT2D_STROKERECT "strokeRect"
|
||||
#define ROCKY_CONTEXT2D_LINEWIDTH "lineWidth"
|
||||
#define ROCKY_CONTEXT2D_STROKESTYLE "strokeStyle"
|
||||
#define ROCKY_CONTEXT2D_FILLSTYLE "fillStyle"
|
||||
#define ROCKY_CONTEXT2D_SAVE "save"
|
||||
#define ROCKY_CONTEXT2D_RESTORE "restore"
|
||||
#define ROCKY_CANVAS_CONSTRUCTOR "CanvasElement"
|
||||
#define ROCKY_CANVAS_CLIENTWIDTH "clientWidth"
|
||||
#define ROCKY_CANVAS_CLIENTHEIGHT "clientHeight"
|
||||
#define ROCKY_CANVAS_UNOBSTRUCTEDLEFT "unobstructedLeft"
|
||||
#define ROCKY_CANVAS_UNOBSTRUCTEDTOP "unobstructedTop"
|
||||
#define ROCKY_CANVAS_UNOBSTRUCTEDWIDTH "unobstructedWidth"
|
||||
#define ROCKY_CANVAS_UNOBSTRUCTEDHEIGHT "unobstructedHeight"
|
||||
|
||||
typedef struct {
|
||||
ListNode node;
|
||||
GDrawState draw_state;
|
||||
} Context2DStoredState;
|
||||
|
||||
// TODO: PBL-35780 make this part of app_state_get_rocky_runtime_context()
|
||||
SECTION(".rocky_bss") static Context2DStoredState *s_canvas_context_2d_stored_states;
|
||||
|
||||
T_STATIC jerry_value_t prv_create_canvas_context_2d_for_layer(Layer *layer) {
|
||||
JS_VAR context_2d = rocky_create_with_constructor(ROCKY_CONTEXT2D_CONSTRUCTOR,
|
||||
/* no args: */ NULL, 0);
|
||||
|
||||
JS_VAR canvas = jerry_get_object_field(context_2d, ROCKY_CONTEXT2D_CANVAS);
|
||||
{
|
||||
JS_VAR client_width = jerry_create_number(layer->bounds.size.w);
|
||||
JS_VAR client_height = jerry_create_number(layer->bounds.size.h);
|
||||
jerry_set_object_field(canvas, ROCKY_CANVAS_CLIENTWIDTH, client_width);
|
||||
jerry_set_object_field(canvas, ROCKY_CANVAS_CLIENTHEIGHT, client_height);
|
||||
}
|
||||
|
||||
{
|
||||
GRect uo_rect;
|
||||
layer_get_unobstructed_bounds(layer, &uo_rect);
|
||||
JS_VAR unobstructed_left = jerry_create_number(uo_rect.origin.x);
|
||||
JS_VAR unobstructed_right = jerry_create_number(uo_rect.origin.y);
|
||||
JS_VAR unobstructed_width = jerry_create_number(uo_rect.size.w);
|
||||
JS_VAR unobstructed_height = jerry_create_number(uo_rect.size.h);
|
||||
jerry_set_object_field(canvas, ROCKY_CANVAS_UNOBSTRUCTEDLEFT, unobstructed_left);
|
||||
jerry_set_object_field(canvas, ROCKY_CANVAS_UNOBSTRUCTEDTOP, unobstructed_right);
|
||||
jerry_set_object_field(canvas, ROCKY_CANVAS_UNOBSTRUCTEDWIDTH, unobstructed_width);
|
||||
jerry_set_object_field(canvas, ROCKY_CANVAS_UNOBSTRUCTEDHEIGHT, unobstructed_height);
|
||||
}
|
||||
|
||||
return jerry_acquire_value(context_2d);
|
||||
}
|
||||
|
||||
static void prv_rocky_update_proc(Layer *layer, GContext *ctx) {
|
||||
if (!rocky_global_has_event_handlers(ROCKY_EVENT_DRAW)) {
|
||||
return;
|
||||
}
|
||||
rocky_api_graphics_text_reset_state();
|
||||
rocky_api_graphics_path2d_reset_state();
|
||||
JS_VAR event = rocky_global_create_event(ROCKY_EVENT_DRAW);
|
||||
JS_VAR context_2d = prv_create_canvas_context_2d_for_layer(layer);
|
||||
jerry_set_object_field(event, ROCKY_EVENT_DRAW_CONTEXT, context_2d);
|
||||
rocky_global_call_event_handlers(event);
|
||||
rocky_api_graphics_path2d_reset_state();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_request_draw) {
|
||||
Window *const top_window = app_window_stack_get_top_window();
|
||||
if (!top_window) {
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
layer_mark_dirty(&top_window->layer);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
GContext *rocky_api_graphics_get_gcontext(void) {
|
||||
return app_state_get_graphics_context();
|
||||
}
|
||||
|
||||
static jerry_value_t prv_rect_precise_call(const jerry_length_t argc, const jerry_value_t argv[],
|
||||
void (*func)(GContext *, const GRectPrecise *)) {
|
||||
GRectPrecise rect;
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(ROCKY_ARG(rect));
|
||||
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
func(ctx, &rect);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static GRect prv_grect_from_precise(const GRectPrecise *rect) {
|
||||
const int16_t x = Fixed_S16_3_rounded_int(rect->origin.x);
|
||||
const int16_t y = Fixed_S16_3_rounded_int(rect->origin.y);
|
||||
const int16_t w = Fixed_S16_3_rounded_int(grect_precise_get_max_x(rect)) - x;
|
||||
const int16_t h = Fixed_S16_3_rounded_int(grect_precise_get_max_y(rect)) - y;
|
||||
|
||||
return GRect(x, y, w, h);
|
||||
}
|
||||
|
||||
static jerry_value_t prv_rect_call(const jerry_length_t argc, const jerry_value_t argv[],
|
||||
void (*func)(GContext *, const GRect *)) {
|
||||
GRectPrecise prect;
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
||||
ROCKY_ARG(prect),
|
||||
);
|
||||
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
const GRect rect = prv_grect_from_precise(&prect);
|
||||
func(ctx, &rect);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_fill_rect) {
|
||||
return prv_rect_call(argc, argv, graphics_fill_rect);
|
||||
}
|
||||
|
||||
static void prv_draw_rect_impl(GContext *ctx, const GRectPrecise *rect) {
|
||||
GRectPrecise adjusted_rect = *rect;
|
||||
adjusted_rect.origin.x.raw_value -= FIXED_S16_3_HALF.raw_value;
|
||||
adjusted_rect.origin.y.raw_value -= FIXED_S16_3_HALF.raw_value;
|
||||
graphics_draw_rect_precise(ctx, &adjusted_rect);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_stroke_rect) {
|
||||
return prv_rect_precise_call(argc, argv, prv_draw_rect_impl);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_clear_rect) {
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
const GColor prev_color = ctx->draw_state.fill_color;
|
||||
ctx->draw_state.fill_color = GColorBlack;
|
||||
JS_VAR result = prv_rect_call(argc, argv, graphics_fill_rect);
|
||||
ctx->draw_state.fill_color = prev_color;
|
||||
return jerry_acquire_value(result);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_set_stroke_width) {
|
||||
uint8_t width;
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
||||
ROCKY_ARG(width),
|
||||
);
|
||||
graphics_context_set_stroke_width(rocky_api_graphics_get_gcontext(), width);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_get_stroke_width) {
|
||||
return jerry_create_number((double)rocky_api_graphics_get_gcontext()->draw_state.stroke_width);
|
||||
}
|
||||
|
||||
static jerry_value_t prv_graphics_set_color(const jerry_length_t argc,
|
||||
const jerry_value_t argv[],
|
||||
void (*func)(GContext *, GColor)) {
|
||||
GColor color;
|
||||
const RockyArgBinding binding = ROCKY_ARG(color);
|
||||
JS_VAR error_value = rocky_args_assign(argc, argv, &binding, 1);
|
||||
// Canvas APIs do a no-op if the color string is invalid
|
||||
if (!jerry_value_has_error_flag(error_value)) {
|
||||
func(rocky_api_graphics_get_gcontext(), color);
|
||||
};
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
#define COLOR_BUFFER_LENGTH (12)
|
||||
T_STATIC void prv_graphics_color_to_char_buffer(GColor8 color, char *buf_out) {
|
||||
if (color.a <= 1) {
|
||||
strncpy(buf_out, "transparent", COLOR_BUFFER_LENGTH);
|
||||
} else {
|
||||
snprintf(buf_out, COLOR_BUFFER_LENGTH, "#%02X%02X%02X",
|
||||
color.r * 85, color.g * 85, color.b * 85);
|
||||
}
|
||||
}
|
||||
|
||||
static jerry_value_t prv_graphics_get_color_string(GColor8 color) {
|
||||
char buf[COLOR_BUFFER_LENGTH];
|
||||
prv_graphics_color_to_char_buffer(color, buf);
|
||||
return jerry_create_string((const jerry_char_t *)buf);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_set_stroke_style) {
|
||||
return prv_graphics_set_color(argc, argv, graphics_context_set_stroke_color);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_get_stroke_style) {
|
||||
return prv_graphics_get_color_string(rocky_api_graphics_get_gcontext()->draw_state.stroke_color);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_set_fill_style) {
|
||||
return prv_graphics_set_color(argc, argv, graphics_context_set_fill_color);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_get_fill_style) {
|
||||
return prv_graphics_get_color_string(rocky_api_graphics_get_gcontext()->draw_state.fill_color);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_fill_radial) {
|
||||
// pblFillRadial(cx, cy, radius1, radius2, angle1, angle2)
|
||||
|
||||
// TODO: PBL-40555 consolidate angle handling here and in rocky_api_path2d.c
|
||||
GPointPrecise center;
|
||||
Fixed_S16_3 radius1, radius2;
|
||||
double angle_1, angle_2;
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
||||
ROCKY_ARG(center.x),
|
||||
ROCKY_ARG(center.y),
|
||||
ROCKY_ARG(radius1),
|
||||
ROCKY_ARG(radius2),
|
||||
ROCKY_ARG_ANGLE(angle_1),
|
||||
ROCKY_ARG_ANGLE(angle_2),
|
||||
);
|
||||
|
||||
// adjust for coordinate system
|
||||
center.x.raw_value -= FIXED_S16_3_HALF.raw_value;
|
||||
center.y.raw_value -= FIXED_S16_3_HALF.raw_value;
|
||||
|
||||
radius1.raw_value = MAX(0, radius1.raw_value);
|
||||
radius2.raw_value = MAX(0, radius2.raw_value);
|
||||
const Fixed_S16_3 inner_radius = Fixed_S16_3(MIN(radius1.raw_value, radius2.raw_value));
|
||||
const Fixed_S16_3 outer_radius = Fixed_S16_3(MAX(radius1.raw_value, radius2.raw_value));
|
||||
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
graphics_fill_radial_precise_internal(ctx, center, inner_radius, outer_radius,
|
||||
(int32_t)angle_1, (int32_t)angle_2);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_save) {
|
||||
Context2DStoredState *const new_head = task_zalloc(sizeof(*new_head));
|
||||
new_head->draw_state = rocky_api_graphics_get_gcontext()->draw_state;
|
||||
list_insert_before(&s_canvas_context_2d_stored_states->node, &new_head->node);
|
||||
s_canvas_context_2d_stored_states = new_head;
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_restore) {
|
||||
Context2DStoredState *const head = s_canvas_context_2d_stored_states;
|
||||
if (head) {
|
||||
rocky_api_graphics_get_gcontext()->draw_state = head->draw_state;
|
||||
list_remove(&head->node, (ListNode **)&s_canvas_context_2d_stored_states, NULL);
|
||||
task_free(head);
|
||||
}
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_canvas_rendering_context_2d_constructor) {
|
||||
JS_VAR canvas = rocky_create_with_constructor(ROCKY_CANVAS_CONSTRUCTOR,
|
||||
/* no args: */ NULL, 0);
|
||||
jerry_set_object_field(this_val, ROCKY_CONTEXT2D_CANVAS, canvas);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_canvas_constructor) {
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static void prv_configure_top_window_and_create_constructors(void) {
|
||||
Window *const window = app_window_stack_get_top_window();
|
||||
// rocky graphics require a window to already be on the current window stack
|
||||
PBL_ASSERTN(window);
|
||||
window->layer.update_proc = prv_rocky_update_proc;
|
||||
|
||||
// Create the CanvasRenderingContext2D constructor:
|
||||
JS_VAR ctx_prototype =
|
||||
rocky_add_constructor(ROCKY_CONTEXT2D_CONSTRUCTOR,
|
||||
prv_canvas_rendering_context_2d_constructor);
|
||||
|
||||
jerry_set_object_field(ctx_prototype, ROCKY_CONTEXT2D_CANVAS, jerry_create_undefined());
|
||||
rocky_add_function(ctx_prototype, ROCKY_CONTEXT2D_CLEARRECT, prv_clear_rect);
|
||||
rocky_add_function(ctx_prototype, ROCKY_CONTEXT2D_FILLRECT, prv_fill_rect);
|
||||
rocky_add_function(ctx_prototype, ROCKY_CONTEXT2D_FILLRADIAL, prv_fill_radial);
|
||||
rocky_add_function(ctx_prototype, ROCKY_CONTEXT2D_STROKERECT, prv_stroke_rect);
|
||||
rocky_add_function(ctx_prototype, ROCKY_CONTEXT2D_SAVE, prv_save);
|
||||
rocky_add_function(ctx_prototype, ROCKY_CONTEXT2D_RESTORE, prv_restore);
|
||||
rocky_define_property(ctx_prototype, ROCKY_CONTEXT2D_LINEWIDTH,
|
||||
prv_get_stroke_width, prv_set_stroke_width);
|
||||
rocky_define_property(ctx_prototype, ROCKY_CONTEXT2D_STROKESTYLE,
|
||||
prv_get_stroke_style, prv_set_stroke_style);
|
||||
rocky_define_property(ctx_prototype, ROCKY_CONTEXT2D_FILLSTYLE,
|
||||
prv_get_fill_style, prv_set_fill_style);
|
||||
|
||||
rocky_api_graphics_path2d_add_canvas_methods(ctx_prototype);
|
||||
rocky_api_graphics_text_add_canvas_methods(ctx_prototype);
|
||||
|
||||
// Create the CanvasElement constructor:
|
||||
JS_UNUSED_VAL = rocky_add_constructor(ROCKY_CANVAS_CONSTRUCTOR, prv_canvas_constructor);
|
||||
}
|
||||
|
||||
static void prv_init_apis(void) {
|
||||
prv_configure_top_window_and_create_constructors();
|
||||
JS_VAR rocky = rocky_get_rocky_singleton();
|
||||
rocky_add_function(rocky, ROCKY_REQUESTDRAW, prv_request_draw);
|
||||
rocky_api_graphics_text_init();
|
||||
rocky_api_graphics_path2d_reset_state(); // does not have an init, so we call reset_state()
|
||||
}
|
||||
|
||||
static void prv_deinit_apis(void) {
|
||||
while (s_canvas_context_2d_stored_states) {
|
||||
Context2DStoredState *state = s_canvas_context_2d_stored_states;
|
||||
s_canvas_context_2d_stored_states =
|
||||
(Context2DStoredState *)s_canvas_context_2d_stored_states->node.next;
|
||||
task_free(state);
|
||||
}
|
||||
rocky_api_graphics_text_deinit();
|
||||
rocky_api_graphics_path2d_reset_state();
|
||||
}
|
||||
|
||||
static bool prv_add_handler(const char *event_name, jerry_value_t handler) {
|
||||
return strcmp(ROCKY_EVENT_DRAW, event_name) == 0;
|
||||
}
|
||||
|
||||
const RockyGlobalAPI GRAPHIC_APIS = {
|
||||
.init = prv_init_apis,
|
||||
.deinit = prv_deinit_apis,
|
||||
.add_handler = prv_add_handler,
|
||||
};
|
26
src/fw/applib/rockyjs/api/rocky_api_graphics.h
Normal file
26
src/fw/applib/rockyjs/api/rocky_api_graphics.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
#include "jerry-api.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
|
||||
extern const RockyGlobalAPI GRAPHIC_APIS;
|
||||
|
||||
GContext *rocky_api_graphics_get_gcontext(void);
|
362
src/fw/applib/rockyjs/api/rocky_api_graphics_color.c
Normal file
362
src/fw/applib/rockyjs/api/rocky_api_graphics_color.c
Normal file
|
@ -0,0 +1,362 @@
|
|||
/*
|
||||
* 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 "rocky_api_graphics_color.h"
|
||||
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#include "string.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#define GColorARGB8FromRGBA(red, green, blue, alpha) \
|
||||
(uint8_t)( \
|
||||
((((alpha) >> 6) & 0b11) << 6) | \
|
||||
((((red) >> 6) & 0b11) << 4) | \
|
||||
((((green) >> 6) & 0b11) << 2) | \
|
||||
((((blue) >> 6) & 0b11) << 0) \
|
||||
)
|
||||
|
||||
#define GColorARGB8FromRGB(red, green, blue) GColorARGB8FromRGBA(red, green, blue, 255)
|
||||
#define GColorARGB8FromHEX(v) \
|
||||
GColorARGB8FromRGB(((v) >> 16) & 0xff, ((v) >> 8) & 0xff, ((v) & 0xff))
|
||||
|
||||
// if performance ever becomes an issue with this, we can sort the names and to a binary search
|
||||
T_STATIC const RockyAPIGraphicsColorDefinition s_color_definitions[] = {
|
||||
// taken from https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
|
||||
{"black", GColorARGB8FromHEX(0x000000)},
|
||||
{"silver", GColorARGB8FromHEX(0xc0c0c0)},
|
||||
{"gray", GColorARGB8FromHEX(0x808080)},
|
||||
{"white", GColorARGB8FromHEX(0xffffff)},
|
||||
{"maroon", GColorARGB8FromHEX(0x800000)},
|
||||
{"red", GColorARGB8FromHEX(0xff0000)},
|
||||
// {"purple", GColorARGBFromHEX(0x800080)}, inconsistent with Pebble color
|
||||
{"fuchsia", GColorARGB8FromHEX(0xff00ff)},
|
||||
// {"green", GColorARGBFromHEX(0x008000)}, inconsistent with Pebble color
|
||||
{"lime", GColorARGB8FromHEX(0x00ff00)},
|
||||
{"olive", GColorARGB8FromHEX(0x808000)},
|
||||
{"yellow", GColorARGB8FromHEX(0xffff00)},
|
||||
{"navy", GColorARGB8FromHEX(0x000080)},
|
||||
{"blue", GColorARGB8FromHEX(0x0000ff)},
|
||||
{"teal", GColorARGB8FromHEX(0x008080)},
|
||||
{"aqua", GColorARGB8FromHEX(0x00ffff)},
|
||||
{"antiquewhite", GColorARGB8FromHEX(0xfaebd7)},
|
||||
{"aquamarine", GColorARGB8FromHEX(0x7fffd4)},
|
||||
{"azure", GColorARGB8FromHEX(0xf0ffff)},
|
||||
{"beige", GColorARGB8FromHEX(0xf5f5dc)},
|
||||
{"bisque", GColorARGB8FromHEX(0xffe4c4)},
|
||||
{"blanchedalmond", GColorARGB8FromHEX(0xffebcd)},
|
||||
{"blueviolet", GColorARGB8FromHEX(0x8a2be2)},
|
||||
{"brown", GColorARGB8FromHEX(0xa52a2a)},
|
||||
{"burlywood", GColorARGB8FromHEX(0xdeb887)},
|
||||
// {"cadetblue", GColorARGBFromHEX(0x5f9ea0)}, inconsistent with Pebble color
|
||||
{"chartreuse", GColorARGB8FromHEX(0x7fff00)},
|
||||
{"chocolate", GColorARGB8FromHEX(0xd2691e)},
|
||||
{"coral", GColorARGB8FromHEX(0xff7f50)},
|
||||
{"cornflowerblue", GColorARGB8FromHEX(0x6495ed)},
|
||||
{"cornsilk", GColorARGB8FromHEX(0xfff8dc)},
|
||||
{"crimson", GColorARGB8FromHEX(0xdc143c)},
|
||||
{"darkblue", GColorARGB8FromHEX(0x00008b)},
|
||||
{"darkcyan", GColorARGB8FromHEX(0x008b8b)},
|
||||
{"darkgoldenrod", GColorARGB8FromHEX(0xb8860b)},
|
||||
// {"darkgray", GColorARGBFromHEX(0xa9a9a9)}, inconsistent with Pebble color
|
||||
// {"darkgreen", GColorARGBFromHEX(0x006400)}, inconsistent with Pebble color
|
||||
// {"darkgrey", GColorARGBFromHEX(0xa9a9a9)}, inconsistent with Pebble color
|
||||
{"darkkhaki", GColorARGB8FromHEX(0xbdb76b)},
|
||||
{"darkmagenta", GColorARGB8FromHEX(0x8b008b)},
|
||||
{"darkolivegreen", GColorARGB8FromHEX(0x556b2f)},
|
||||
{"darkorange", GColorARGB8FromHEX(0xff8c00)},
|
||||
{"darkorchid", GColorARGB8FromHEX(0x9932cc)},
|
||||
{"darkred", GColorARGB8FromHEX(0x8b0000)},
|
||||
{"darksalmon", GColorARGB8FromHEX(0xe9967a)},
|
||||
{"darkseagreen", GColorARGB8FromHEX(0x8fbc8f)},
|
||||
{"darkslateblue", GColorARGB8FromHEX(0x483d8b)},
|
||||
{"darkslategray", GColorARGB8FromHEX(0x2f4f4f)},
|
||||
{"darkslategrey", GColorARGB8FromHEX(0x2f4f4f)},
|
||||
{"darkturquoise", GColorARGB8FromHEX(0x00ced1)},
|
||||
{"darkviolet", GColorARGB8FromHEX(0x9400d3)},
|
||||
{"deeppink", GColorARGB8FromHEX(0xff1493)},
|
||||
{"deepskyblue", GColorARGB8FromHEX(0x00bfff)},
|
||||
{"dimgray", GColorARGB8FromHEX(0x696969)},
|
||||
{"dimgrey", GColorARGB8FromHEX(0x696969)},
|
||||
{"dodgerblue", GColorARGB8FromHEX(0x1e90ff)},
|
||||
{"firebrick", GColorARGB8FromHEX(0xb22222)},
|
||||
{"floralwhite", GColorARGB8FromHEX(0xfffaf0)},
|
||||
{"forestgreen", GColorARGB8FromHEX(0x228b22)},
|
||||
{"gainsboro", GColorARGB8FromHEX(0xdcdcdc)},
|
||||
{"ghostwhite", GColorARGB8FromHEX(0xf8f8ff)},
|
||||
{"gold", GColorARGB8FromHEX(0xffd700)},
|
||||
{"goldenrod", GColorARGB8FromHEX(0xdaa520)},
|
||||
{"greenyellow", GColorARGB8FromHEX(0xadff2f)},
|
||||
{"grey", GColorARGB8FromHEX(0x808080)},
|
||||
{"honeydew", GColorARGB8FromHEX(0xf0fff0)},
|
||||
{"hotpink", GColorARGB8FromHEX(0xff69b4)},
|
||||
{"indianred", GColorARGB8FromHEX(0xcd5c5c)},
|
||||
// {"indigo", GColorARGBFromHEX(0x4b0082)}, inconsistent with Pebble color
|
||||
{"ivory", GColorARGB8FromHEX(0xfffff0)},
|
||||
{"khaki", GColorARGB8FromHEX(0xf0e68c)},
|
||||
{"lavender", GColorARGB8FromHEX(0xe6e6fa)},
|
||||
{"lavenderblush", GColorARGB8FromHEX(0xfff0f5)},
|
||||
{"lawngreen", GColorARGB8FromHEX(0x7cfc00)},
|
||||
{"lemonchiffon", GColorARGB8FromHEX(0xfffacd)},
|
||||
{"lightblue", GColorARGB8FromHEX(0xadd8e6)},
|
||||
{"lightcoral", GColorARGB8FromHEX(0xf08080)},
|
||||
{"lightcyan", GColorARGB8FromHEX(0xe0ffff)},
|
||||
{"lightgoldenrodyellow", GColorARGB8FromHEX(0xfafad2)},
|
||||
// {"lightgray", GColorARGBFromHEX(0xd3d3d3)}, inconsistent with Pebble color
|
||||
{"lightgreen", GColorARGB8FromHEX(0x90ee90)},
|
||||
// {"lightgrey", GColorARGBFromHEX(0xd3d3d3)}, inconsistent with Pebble color
|
||||
{"lightpink", GColorARGB8FromHEX(0xffb6c1)},
|
||||
{"lightsalmon", GColorARGB8FromHEX(0xffa07a)},
|
||||
{"lightseagreen", GColorARGB8FromHEX(0x20b2aa)},
|
||||
{"lightskyblue", GColorARGB8FromHEX(0x87cefa)},
|
||||
{"lightslategray", GColorARGB8FromHEX(0x778899)},
|
||||
{"lightslategrey", GColorARGB8FromHEX(0x778899)},
|
||||
{"lightsteelblue", GColorARGB8FromHEX(0xb0c4de)},
|
||||
{"lightyellow", GColorARGB8FromHEX(0xffffe0)},
|
||||
{"limegreen", GColorARGB8FromHEX(0x32cd32)},
|
||||
{"linen", GColorARGB8FromHEX(0xfaf0e6)},
|
||||
// {"mediumaquamarine", GColorARGBFromHEX(0x66cdaa)}, inconsistent with Pebble color
|
||||
{"mediumblue", GColorARGB8FromHEX(0x0000cd)},
|
||||
{"mediumorchid", GColorARGB8FromHEX(0xba55d3)},
|
||||
{"mediumpurple", GColorARGB8FromHEX(0x9370db)},
|
||||
{"mediumseagreen", GColorARGB8FromHEX(0x3cb371)},
|
||||
{"mediumslateblue", GColorARGB8FromHEX(0x7b68ee)},
|
||||
// {"mediumspringgreen", GColorARGBFromHEX(0x00fa9a)}, inconsistent with Pebble color
|
||||
{"mediumturquoise", GColorARGB8FromHEX(0x48d1cc)},
|
||||
{"mediumvioletred", GColorARGB8FromHEX(0xc71585)},
|
||||
{"midnightblue", GColorARGB8FromHEX(0x191970)},
|
||||
{"mintcream", GColorARGB8FromHEX(0xf5fffa)},
|
||||
{"mistyrose", GColorARGB8FromHEX(0xffe4e1)},
|
||||
{"moccasin", GColorARGB8FromHEX(0xffe4b5)},
|
||||
{"navajowhite", GColorARGB8FromHEX(0xffdead)},
|
||||
{"oldlace", GColorARGB8FromHEX(0xfdf5e6)},
|
||||
{"olivedrab", GColorARGB8FromHEX(0x6b8e23)},
|
||||
{"orangered", GColorARGB8FromHEX(0xff4500)},
|
||||
{"orchid", GColorARGB8FromHEX(0xda70d6)},
|
||||
{"palegoldenrod", GColorARGB8FromHEX(0xeee8aa)},
|
||||
{"palegreen", GColorARGB8FromHEX(0x98fb98)},
|
||||
{"paleturquoise", GColorARGB8FromHEX(0xafeeee)},
|
||||
{"palevioletred", GColorARGB8FromHEX(0xdb7093)},
|
||||
{"papayawhip", GColorARGB8FromHEX(0xffefd5)},
|
||||
{"peachpuff", GColorARGB8FromHEX(0xffdab9)},
|
||||
{"peru", GColorARGB8FromHEX(0xcd853f)},
|
||||
{"pink", GColorARGB8FromHEX(0xffc0cb)},
|
||||
{"plum", GColorARGB8FromHEX(0xdda0dd)},
|
||||
{"powderblue", GColorARGB8FromHEX(0xb0e0e6)},
|
||||
{"rosybrown", GColorARGB8FromHEX(0xbc8f8f)},
|
||||
{"royalblue", GColorARGB8FromHEX(0x4169e1)},
|
||||
{"saddlebrown", GColorARGB8FromHEX(0x8b4513)},
|
||||
{"salmon", GColorARGB8FromHEX(0xfa8072)},
|
||||
{"sandybrown", GColorARGB8FromHEX(0xf4a460)},
|
||||
{"seagreen", GColorARGB8FromHEX(0x2e8b57)},
|
||||
{"seashell", GColorARGB8FromHEX(0xfff5ee)},
|
||||
{"sienna", GColorARGB8FromHEX(0xa0522d)},
|
||||
{"skyblue", GColorARGB8FromHEX(0x87ceeb)},
|
||||
{"slateblue", GColorARGB8FromHEX(0x6a5acd)},
|
||||
{"slategray", GColorARGB8FromHEX(0x708090)},
|
||||
{"slategrey", GColorARGB8FromHEX(0x708090)},
|
||||
{"snow", GColorARGB8FromHEX(0xfffafa)},
|
||||
{"springgreen", GColorARGB8FromHEX(0x00ff7f)},
|
||||
{"steelblue", GColorARGB8FromHEX(0x4682b4)},
|
||||
{"tan", GColorARGB8FromHEX(0xd2b48c)},
|
||||
{"thistle", GColorARGB8FromHEX(0xd8bfd8)},
|
||||
{"tomato", GColorARGB8FromHEX(0xff6347)},
|
||||
{"turquoise", GColorARGB8FromHEX(0x40e0d0)},
|
||||
{"violet", GColorARGB8FromHEX(0xee82ee)},
|
||||
{"wheat", GColorARGB8FromHEX(0xf5deb3)},
|
||||
{"whitesmoke", GColorARGB8FromHEX(0xf5f5f5)},
|
||||
{"yellowgreen", GColorARGB8FromHEX(0x9acd32)},
|
||||
|
||||
// CSS compatibility
|
||||
{"darkgrey", GColorDarkGrayARGB8},
|
||||
{"lightgrey", GColorLightGrayARGB8},
|
||||
|
||||
// special cases
|
||||
{"transparent", GColorClearARGB8},
|
||||
{"clear", GColorClearARGB8},
|
||||
|
||||
// Pebble colors taken from gcolor_defitions.h
|
||||
{"black", GColorBlackARGB8},
|
||||
{"oxfordblue", GColorOxfordBlueARGB8},
|
||||
{"dukeblue", GColorDukeBlueARGB8},
|
||||
{"blue", GColorBlueARGB8},
|
||||
{"darkgreen", GColorDarkGreenARGB8},
|
||||
{"midnightgreen", GColorMidnightGreenARGB8},
|
||||
{"cobaltblue", GColorCobaltBlueARGB8},
|
||||
{"bluemoon", GColorBlueMoonARGB8},
|
||||
{"islamicgreen", GColorIslamicGreenARGB8},
|
||||
{"jaegergreen", GColorJaegerGreenARGB8},
|
||||
{"tiffanyblue", GColorTiffanyBlueARGB8},
|
||||
{"vividcerulean", GColorVividCeruleanARGB8},
|
||||
{"green", GColorGreenARGB8},
|
||||
{"malachite", GColorMalachiteARGB8},
|
||||
{"mediumspringgreen", GColorMediumSpringGreenARGB8},
|
||||
{"cyan", GColorCyanARGB8},
|
||||
{"bulgarianrose", GColorBulgarianRoseARGB8},
|
||||
{"imperialpurple", GColorImperialPurpleARGB8},
|
||||
{"indigo", GColorIndigoARGB8},
|
||||
{"electricultramarine", GColorElectricUltramarineARGB8},
|
||||
{"armygreen", GColorArmyGreenARGB8},
|
||||
{"darkgray", GColorDarkGrayARGB8},
|
||||
{"liberty", GColorLibertyARGB8},
|
||||
{"verylightblue", GColorVeryLightBlueARGB8},
|
||||
{"kellygreen", GColorKellyGreenARGB8},
|
||||
{"maygreen", GColorMayGreenARGB8},
|
||||
{"cadetblue", GColorCadetBlueARGB8},
|
||||
{"pictonblue", GColorPictonBlueARGB8},
|
||||
{"brightgreen", GColorBrightGreenARGB8},
|
||||
{"screamingreen", GColorScreaminGreenARGB8},
|
||||
{"mediumaquamarine", GColorMediumAquamarineARGB8},
|
||||
{"electricblue", GColorElectricBlueARGB8},
|
||||
{"darkcandyapplered", GColorDarkCandyAppleRedARGB8},
|
||||
{"jazzberryjam", GColorJazzberryJamARGB8},
|
||||
{"purple", GColorPurpleARGB8},
|
||||
{"vividviolet", GColorVividVioletARGB8},
|
||||
{"windsortan", GColorWindsorTanARGB8},
|
||||
{"rosevale", GColorRoseValeARGB8},
|
||||
{"purpureus", GColorPurpureusARGB8},
|
||||
{"lavenderindigo", GColorLavenderIndigoARGB8},
|
||||
{"limerick", GColorLimerickARGB8},
|
||||
{"brass", GColorBrassARGB8},
|
||||
{"lightgray", GColorLightGrayARGB8},
|
||||
{"babyblueeyes", GColorBabyBlueEyesARGB8},
|
||||
{"springbud", GColorSpringBudARGB8},
|
||||
{"inchworm", GColorInchwormARGB8},
|
||||
{"mintgreen", GColorMintGreenARGB8},
|
||||
{"celeste", GColorCelesteARGB8},
|
||||
{"red", GColorRedARGB8},
|
||||
{"folly", GColorFollyARGB8},
|
||||
{"fashionmagenta", GColorFashionMagentaARGB8},
|
||||
{"magenta", GColorMagentaARGB8},
|
||||
{"orange", GColorOrangeARGB8},
|
||||
{"sunsetorange", GColorSunsetOrangeARGB8},
|
||||
{"brilliantrose", GColorBrilliantRoseARGB8},
|
||||
{"shockingpink", GColorShockingPinkARGB8},
|
||||
{"chromeyellow", GColorChromeYellowARGB8},
|
||||
{"rajah", GColorRajahARGB8},
|
||||
{"melon", GColorMelonARGB8},
|
||||
{"richbrilliantlavender", GColorRichBrilliantLavenderARGB8},
|
||||
{"yellow", GColorYellowARGB8},
|
||||
{"icterine", GColorIcterineARGB8},
|
||||
{"pastelyellow", GColorPastelYellowARGB8},
|
||||
{"white", GColorWhiteARGB8},
|
||||
|
||||
// terminator for unit-test
|
||||
{0},
|
||||
};
|
||||
|
||||
static bool prv_parse_name(const char *color_value, GColor8 *parsed_color) {
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(s_color_definitions); i++) {
|
||||
if (s_color_definitions[i].name && strcmp(s_color_definitions[i].name, color_value) == 0) {
|
||||
if (parsed_color) {
|
||||
*parsed_color = (GColor8) {.argb = s_color_definitions[i].value};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_parse_hex_comp(const char *color_value, size_t len, int32_t *value_out) {
|
||||
// we re-implement strtol here be able to limit the length of the str
|
||||
|
||||
int32_t value = 0;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char ch = color_value[i];
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
ch = ch - '0';
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
ch = ch - 'A' + 10;
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
ch = ch - 'a' + 10;
|
||||
} else { // This will also catch '\0'
|
||||
return false;
|
||||
}
|
||||
value = value * 16 + ch;
|
||||
}
|
||||
*value_out = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_parse_hex_comps(const char *color_value, size_t len,
|
||||
int32_t *r, int32_t *g, int32_t *b, int32_t *a) {
|
||||
return prv_parse_hex_comp(color_value + 0 * len, len, r) &&
|
||||
prv_parse_hex_comp(color_value + 1 * len, len, g) &&
|
||||
prv_parse_hex_comp(color_value + 2 * len, len, b) &&
|
||||
(a == NULL || prv_parse_hex_comp(color_value + 3 * len, len, a));
|
||||
}
|
||||
|
||||
static bool prv_parse_hex(const char *color_value, GColor8 *parsed_color) {
|
||||
const size_t len = strlen(color_value);
|
||||
if (len < 1 || color_value[0] != '#') {
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t r, g, b, a;
|
||||
switch (len) {
|
||||
case 4: { // #RGB
|
||||
if (prv_parse_hex_comps(color_value + 1, 1, &r, &g, &b, NULL)) {
|
||||
*parsed_color = GColorFromRGB(r * (255/15), g * (255/15), b * (255/15));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case 5: { // #RGBA
|
||||
if (prv_parse_hex_comps(color_value + 1, 1, &r, &g, &b, &a)) {
|
||||
*parsed_color = GColorFromRGBA(r * (255/15), g * (255/15), b * (255/15), a * (255/15));
|
||||
if (parsed_color->a == 0) {
|
||||
*parsed_color = GColorClear;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case 7: { // #RRGGBB
|
||||
if (prv_parse_hex_comps(color_value + 1, 2, &r, &g, &b, NULL)) {
|
||||
*parsed_color = GColorFromRGB(r, g, b);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
case 9: { // #RRGGBBAA
|
||||
if (prv_parse_hex_comps(color_value + 1, 2, &r, &g, &b, &a)) {
|
||||
*parsed_color = GColorFromRGBA(r, g, b, a);
|
||||
if (parsed_color->a == 0) {
|
||||
*parsed_color = GColorClear;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rocky_api_graphics_color_parse(const char *color_value, GColor8 *parsed_color) {
|
||||
return prv_parse_name(color_value, parsed_color) ||
|
||||
prv_parse_hex(color_value, parsed_color);
|
||||
}
|
||||
|
||||
bool rocky_api_graphics_color_from_value(jerry_value_t value, GColor *result) {
|
||||
if (jerry_value_is_number(value)) {
|
||||
*result = (GColor) {.argb = jerry_get_int32_value(value)};
|
||||
return true;
|
||||
}
|
||||
if (jerry_value_is_string(value)) {
|
||||
char color_str[50] = {0};
|
||||
jerry_string_to_utf8_char_buffer(value, (jerry_char_t *)color_str, sizeof(color_str));
|
||||
return rocky_api_graphics_color_parse(color_str, result);
|
||||
}
|
||||
return false;
|
||||
}
|
31
src/fw/applib/rockyjs/api/rocky_api_graphics_color.h
Normal file
31
src/fw/applib/rockyjs/api/rocky_api_graphics_color.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "jerry-api.h"
|
||||
|
||||
typedef struct RockyAPIGraphicsColorDefinition {
|
||||
const char *name;
|
||||
const uint8_t value;
|
||||
} RockyAPIGraphicsColorDefinition;
|
||||
|
||||
bool rocky_api_graphics_color_parse(const char *color_value, GColor8 *parsed_color);
|
||||
|
||||
bool rocky_api_graphics_color_from_value(jerry_value_t value, GColor *parsed_color);
|
391
src/fw/applib/rockyjs/api/rocky_api_graphics_path2d.c
Normal file
391
src/fw/applib/rockyjs/api/rocky_api_graphics_path2d.c
Normal file
|
@ -0,0 +1,391 @@
|
|||
/*
|
||||
* 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 "rocky_api_graphics_path2d.h"
|
||||
#include "rocky_api_graphics.h"
|
||||
#include "rocky_api_errors.h"
|
||||
|
||||
#include "applib/graphics/gpath.h"
|
||||
#include "applib/graphics/graphics_circle.h"
|
||||
#include "applib/graphics/graphics_line.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "rocky_api_util_args.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
#include "util/trig.h"
|
||||
|
||||
#define PATH2D_ARC "arc"
|
||||
#define PATH2D_RECT "rect"
|
||||
#define PATH2D_BEGINPATH "beginPath"
|
||||
#define PATH2D_MOVETO "moveTo"
|
||||
#define PATH2D_LINETO "lineTo"
|
||||
#define PATH2D_CLOSEPATH "closePath"
|
||||
#define ROCKY_CONTEXT2D_STROKE "stroke"
|
||||
#define ROCKY_CONTEXT2D_FILL "fill"
|
||||
|
||||
#define MINIMUM_ARRAY_LEN (8)
|
||||
|
||||
// TODO: PBL-35780 make this part of app_state_get_rocky_runtime_context()
|
||||
SECTION(".rocky_bss") T_STATIC RockyAPIPathStep *s_rocky_path_steps;
|
||||
SECTION(".rocky_bss") T_STATIC size_t s_rocky_path_steps_array_len;
|
||||
SECTION(".rocky_bss") T_STATIC size_t s_rocky_path_steps_num;
|
||||
|
||||
void rocky_api_graphics_path2d_reset_state(void) {
|
||||
s_rocky_path_steps_num = 0;
|
||||
|
||||
task_free(s_rocky_path_steps);
|
||||
s_rocky_path_steps = NULL;
|
||||
s_rocky_path_steps_array_len = 0;
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_begin_path) {
|
||||
rocky_api_graphics_path2d_reset_state();
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static size_t prv_get_realloc_array_len(const size_t required_array_len) {
|
||||
size_t len = s_rocky_path_steps_array_len ?: MINIMUM_ARRAY_LEN;
|
||||
while (required_array_len > len) {
|
||||
len *= 2;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static jerry_value_t prv_try_allocate_steps(const size_t num_steps_increment) {
|
||||
const size_t required_array_len = (s_rocky_path_steps_num + num_steps_increment);
|
||||
if (required_array_len <= s_rocky_path_steps_array_len) {
|
||||
goto success;
|
||||
}
|
||||
const size_t new_array_len = prv_get_realloc_array_len(required_array_len);
|
||||
void *new_steps_array = task_realloc(s_rocky_path_steps,
|
||||
sizeof(RockyAPIPathStep) * new_array_len);
|
||||
if (!new_steps_array) {
|
||||
return rocky_error_oom("can't create more path steps");
|
||||
}
|
||||
s_rocky_path_steps = new_steps_array;
|
||||
s_rocky_path_steps_array_len = new_array_len;
|
||||
success:
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
#define TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(num_steps_increment) \
|
||||
ROCKY_RETURN_IF_ERROR(prv_try_allocate_steps(num_steps_increment))
|
||||
|
||||
static jerry_value_t prv_add_pt(jerry_length_t argc, const jerry_value_t *argv,
|
||||
RockyAPIPathStepType step_type) {
|
||||
const double raw_x = argc > 0 ? (jerry_get_number_value(argv[0]) - 0.5) * FIXED_S16_3_FACTOR : 0;
|
||||
const double raw_y = argc > 1 ? (jerry_get_number_value(argv[1]) - 0.5) * FIXED_S16_3_FACTOR : 0;
|
||||
|
||||
if (raw_x < INT16_MIN || raw_x > INT16_MAX || raw_y < INT16_MIN || raw_x > INT16_MAX) {
|
||||
return rocky_error_argument_invalid("Value out of bounds");
|
||||
}
|
||||
|
||||
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(1);
|
||||
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = step_type,
|
||||
.pt.xy = GPointPrecise((int16_t)raw_x, (int16_t)raw_y),
|
||||
};
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_move_to) {
|
||||
return prv_add_pt(argc, argv, RockyAPIPathStepType_MoveTo);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_line_to) {
|
||||
return prv_add_pt(argc, argv, RockyAPIPathStepType_LineTo);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_stroke) {
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
|
||||
GPoint p = {0};
|
||||
GPointPrecise pp = {.x = {0}, .y = {0}};
|
||||
bool moved_already = false;
|
||||
|
||||
#define ASSIGN_P(new_p) do { \
|
||||
moved_already = true; \
|
||||
p = GPointFromGPointPrecise(new_p); \
|
||||
pp = new_p; \
|
||||
} while (0)
|
||||
|
||||
for (size_t i = 0; i < s_rocky_path_steps_num; i++) {
|
||||
RockyAPIPathStep *const step = &s_rocky_path_steps[i];
|
||||
|
||||
switch (step->type) {
|
||||
case RockyAPIPathStepType_MoveTo: {
|
||||
ASSIGN_P(step->pt.xy);
|
||||
break;
|
||||
}
|
||||
case RockyAPIPathStepType_LineTo: {
|
||||
if (moved_already) {
|
||||
graphics_line_draw_precise_stroked(ctx, pp, step->pt.xy);
|
||||
}
|
||||
ASSIGN_P(step->pt.xy);
|
||||
break;
|
||||
}
|
||||
case RockyAPIPathStepType_Arc: {
|
||||
if (moved_already) {
|
||||
const GPointPrecise pt_from = gpoint_from_polar_precise(
|
||||
&step->arc.center, (uint16_t)step->arc.radius.raw_value, step->arc.angle_start);
|
||||
graphics_line_draw_precise_stroked(ctx, pp, pt_from);
|
||||
}
|
||||
|
||||
int32_t angle_start = step->arc.angle_start;
|
||||
int32_t angle_end = step->arc.angle_end;
|
||||
if (step->arc.anti_clockwise) {
|
||||
const int32_t t = angle_start;
|
||||
angle_start = angle_end;
|
||||
angle_end = t;
|
||||
}
|
||||
while (angle_end < angle_start) {
|
||||
angle_end += TRIG_MAX_ANGLE;
|
||||
}
|
||||
graphics_draw_arc_precise_internal(ctx, step->arc.center, step->arc.radius,
|
||||
angle_start, angle_end);
|
||||
|
||||
const GPointPrecise pt_to = gpoint_from_polar_precise(
|
||||
&step->arc.center, (uint16_t)step->arc.radius.raw_value, step->arc.angle_end);
|
||||
ASSIGN_P(pt_to);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
WTF;
|
||||
}
|
||||
}
|
||||
#undef ASSIGN_P
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static void prv_fill_points(GPoint *points, size_t num) {
|
||||
if (num < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
GPath path = {
|
||||
.num_points = num,
|
||||
.points = points,
|
||||
};
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
gpath_draw_filled(ctx, &path);
|
||||
}
|
||||
|
||||
static GPointPrecise prv_point_add_vector_precise(GPointPrecise *pt, GVectorPrecise *v) {
|
||||
return GPointPrecise(
|
||||
pt->x.raw_value + v->dx.raw_value,
|
||||
pt->y.raw_value + v->dy.raw_value);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_fill) {
|
||||
GPoint *const points = task_zalloc(sizeof(*points) * s_rocky_path_steps_num);
|
||||
if (!points) {
|
||||
return rocky_error_oom("too many points to fill");
|
||||
}
|
||||
size_t points_num = 0;
|
||||
|
||||
jerry_value_t rv;
|
||||
|
||||
#define ADD_P(pt) \
|
||||
PBL_ASSERTN(points_num < s_rocky_path_steps_num); \
|
||||
points[points_num++] = GPointFromGPointPrecise( \
|
||||
prv_point_add_vector_precise(&(pt).xy, &(pt).fill_delta));
|
||||
|
||||
for (size_t i = 0; i < s_rocky_path_steps_num; i++) {
|
||||
RockyAPIPathStep *const step = &s_rocky_path_steps[i];
|
||||
switch (step->type) {
|
||||
case RockyAPIPathStepType_MoveTo: {
|
||||
prv_fill_points(points, points_num);
|
||||
points_num = 0;
|
||||
ADD_P(step->pt);
|
||||
break;
|
||||
}
|
||||
case RockyAPIPathStepType_LineTo: {
|
||||
ADD_P(step->pt);
|
||||
break;
|
||||
}
|
||||
case RockyAPIPathStepType_Arc: {
|
||||
rv = rocky_error_argument_invalid("fill() does not support arc()");
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
}
|
||||
rv = jerry_create_undefined();
|
||||
|
||||
prv_fill_points(points, points_num);
|
||||
|
||||
cleanup:
|
||||
task_free(points);
|
||||
return rv;
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_arc) {
|
||||
GPointPrecise center;
|
||||
Fixed_S16_3 radius;
|
||||
double angle_1, angle_2;
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
||||
ROCKY_ARG(center.x),
|
||||
ROCKY_ARG(center.y),
|
||||
ROCKY_ARG(radius),
|
||||
ROCKY_ARG_ANGLE(angle_1),
|
||||
ROCKY_ARG_ANGLE(angle_2));
|
||||
|
||||
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(1);
|
||||
|
||||
const bool anti_clockwise = (argc >= 6) ? jerry_get_boolean_value(argv[5]) : false;
|
||||
|
||||
// adjust for coordinate system
|
||||
center.x.raw_value -= FIXED_S16_3_HALF.raw_value;
|
||||
center.y.raw_value -= FIXED_S16_3_HALF.raw_value;
|
||||
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_Arc,
|
||||
.arc = (RockyAPIPathStepArc) {
|
||||
.center = center,
|
||||
.radius = radius,
|
||||
// TODO: PBL-40555 consolidate angle handling here and in rocky_api_graphics.c
|
||||
.angle_start = jerry_get_angle_value(argv[3]),
|
||||
.angle_end = jerry_get_angle_value(argv[4]),
|
||||
.anti_clockwise = anti_clockwise,
|
||||
},
|
||||
};
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_rect) {
|
||||
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(5);
|
||||
|
||||
if (argc >= 4) {
|
||||
GRectPrecise rect;
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(ROCKY_ARG(rect));
|
||||
grect_precise_standardize(&rect);
|
||||
|
||||
// shift rectangle into coordinate system
|
||||
const int16_t half_pt = FIXED_S16_3_HALF.raw_value;
|
||||
rect.origin.x.raw_value -= half_pt;
|
||||
rect.origin.y.raw_value -= half_pt;
|
||||
|
||||
// special casing for our filling algorithm to match fillRect()
|
||||
const int16_t full_pt = FIXED_S16_3_ONE.raw_value;
|
||||
const int16_t delta_t = full_pt;
|
||||
const int16_t delta_r = full_pt;
|
||||
const int16_t delta_b = 0;
|
||||
const int16_t delta_l = 0;
|
||||
const GVectorPrecise delta_tl = GVectorPrecise(delta_l, delta_t);
|
||||
const GVectorPrecise delta_tr = GVectorPrecise(delta_r, delta_t);
|
||||
const GVectorPrecise delta_br = GVectorPrecise(delta_r, delta_b);
|
||||
const GVectorPrecise delta_bl = GVectorPrecise(delta_l, delta_b);
|
||||
|
||||
const Fixed_S16_3 right = grect_precise_get_max_x(&rect);
|
||||
const Fixed_S16_3 bottom = grect_precise_get_max_y(&rect);
|
||||
|
||||
// top left
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_MoveTo,
|
||||
.pt.xy = rect.origin,
|
||||
.pt.fill_delta = delta_tl,
|
||||
};
|
||||
// top right
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_LineTo,
|
||||
.pt.xy = GPointPrecise(right.raw_value, rect.origin.y.raw_value),
|
||||
.pt.fill_delta = delta_tr,
|
||||
};
|
||||
// bottom right
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_LineTo,
|
||||
.pt.xy = GPointPrecise(right.raw_value, bottom.raw_value),
|
||||
.pt.fill_delta = delta_br,
|
||||
};
|
||||
// bottom left
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_LineTo,
|
||||
.pt.xy = GPointPrecise(rect.origin.x.raw_value, bottom.raw_value),
|
||||
.pt.fill_delta = delta_bl,
|
||||
};
|
||||
// top left again, to close path
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_LineTo,
|
||||
.pt.xy = rect.origin,
|
||||
.pt.fill_delta = delta_tl,
|
||||
};
|
||||
}
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_close_path) {
|
||||
// lineTo() back to most-recent .moveTo()
|
||||
|
||||
if (s_rocky_path_steps_num < 2) {
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
RockyAPIPathStep *step = &s_rocky_path_steps[s_rocky_path_steps_num - 1];
|
||||
|
||||
// if the last step was a moveTo(), there's nothing to do
|
||||
if (step->type == RockyAPIPathStepType_MoveTo) {
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(1);
|
||||
|
||||
do {
|
||||
step--;
|
||||
if (step->type == RockyAPIPathStepType_MoveTo) {
|
||||
// add a lintTo() at the end
|
||||
s_rocky_path_steps[s_rocky_path_steps_num++] = (RockyAPIPathStep) {
|
||||
.type = RockyAPIPathStepType_LineTo,
|
||||
.pt = step->pt,
|
||||
};
|
||||
break;
|
||||
}
|
||||
} while (step > &s_rocky_path_steps[0]);
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
void rocky_api_graphics_path2d_add_canvas_methods(jerry_value_t obj) {
|
||||
rocky_add_function(obj, PATH2D_BEGINPATH, prv_begin_path);
|
||||
rocky_add_function(obj, PATH2D_MOVETO, prv_move_to);
|
||||
rocky_add_function(obj, PATH2D_LINETO, prv_line_to);
|
||||
rocky_add_function(obj, PATH2D_ARC, prv_arc);
|
||||
rocky_add_function(obj, PATH2D_RECT, prv_rect);
|
||||
rocky_add_function(obj, PATH2D_CLOSEPATH, prv_close_path);
|
||||
rocky_add_function(obj, ROCKY_CONTEXT2D_STROKE, prv_stroke);
|
||||
rocky_add_function(obj, ROCKY_CONTEXT2D_FILL, prv_fill);
|
||||
}
|
||||
|
||||
//! For unit testing
|
||||
jerry_value_t rocky_api_graphics_path2d_try_allocate_steps(size_t inc_steps) {
|
||||
TRY_ALLOCATE_STEPS_OR_RETURN_ERROR(inc_steps);
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
size_t rocky_api_graphics_path2d_min_array_len(void) {
|
||||
return MINIMUM_ARRAY_LEN;
|
||||
}
|
||||
|
||||
size_t rocky_api_graphics_path2d_array_len(void) {
|
||||
return s_rocky_path_steps_array_len;
|
||||
}
|
||||
|
||||
jerry_value_t rocky_api_graphics_path2d_call_fill(void) {
|
||||
// Args aren't correct, but it doesn't matter right now because the function doesn't use them:
|
||||
return prv_fill(jerry_create_undefined(), jerry_create_undefined(), NULL, 0);
|
||||
}
|
56
src/fw/applib/rockyjs/api/rocky_api_graphics_path2d.h
Normal file
56
src/fw/applib/rockyjs/api/rocky_api_graphics_path2d.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
#include "jerry-api.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
|
||||
void rocky_api_graphics_path2d_add_canvas_methods(jerry_value_t obj);
|
||||
//! Resets the internal state and frees any memory associated with it.
|
||||
void rocky_api_graphics_path2d_reset_state(void);
|
||||
|
||||
// these types exist so that we can unit test the internal state
|
||||
typedef enum {
|
||||
RockyAPIPathStepType_MoveTo,
|
||||
RockyAPIPathStepType_LineTo,
|
||||
RockyAPIPathStepType_Arc,
|
||||
} RockyAPIPathStepType;
|
||||
|
||||
typedef struct RockyAPIPathStepPoint {
|
||||
GPointPrecise xy;
|
||||
// to be applied to xy when calling .fill() (not .stroke()) as a workaround for some of our
|
||||
// rendering quirks, needs to be solved for real
|
||||
GVectorPrecise fill_delta;
|
||||
} RockyAPIPathStepPoint;
|
||||
|
||||
typedef struct RockyAPIPathStepArc {
|
||||
GPointPrecise center;
|
||||
Fixed_S16_3 radius;
|
||||
int32_t angle_start;
|
||||
int32_t angle_end;
|
||||
bool anti_clockwise;
|
||||
} RockyAPIPathStepArc;
|
||||
|
||||
typedef struct RockyAPIPathStep {
|
||||
RockyAPIPathStepType type;
|
||||
union {
|
||||
RockyAPIPathStepPoint pt;
|
||||
RockyAPIPathStepArc arc;
|
||||
};
|
||||
} RockyAPIPathStep;
|
281
src/fw/applib/rockyjs/api/rocky_api_graphics_text.c
Normal file
281
src/fw/applib/rockyjs/api/rocky_api_graphics_text.c
Normal file
|
@ -0,0 +1,281 @@
|
|||
/*
|
||||
* 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 <applib/graphics/gcontext.h>
|
||||
#include <applib/graphics/gtypes.h>
|
||||
#include "rocky_api_graphics_text.h"
|
||||
|
||||
#include "applib/fonts/fonts.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "rocky_api_graphics.h"
|
||||
#include "rocky_api_util_args.h"
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ROCKY_CONTEXT2D_FILLTEXT "fillText"
|
||||
#define ROCKY_CONTEXT2D_FONT "font"
|
||||
#define ROCKY_CONTEXT2D_MEASURETEXT "measureText"
|
||||
#define ROCKY_CONTEXT2D_TEXTALIGN "textAlign"
|
||||
|
||||
// TODO: PBL-35780 use app_state_get_rocky_runtime_context().context_binding instead
|
||||
SECTION(".rocky_bss") T_STATIC RockyAPITextState s_rocky_text_state;
|
||||
SECTION(".rocky_bss") static GFont s_default_font;
|
||||
|
||||
|
||||
static GSize prv_get_max_used_size(GContext *ctx, const char *str_buffer, const GRect box) {
|
||||
return graphics_text_layout_get_max_used_size(ctx, str_buffer,
|
||||
s_rocky_text_state.font,
|
||||
box, s_rocky_text_state.overflow_mode,
|
||||
s_rocky_text_state.alignment, NULL);
|
||||
}
|
||||
|
||||
// fillText(text, x, y [, maxWidth])
|
||||
JERRY_FUNCTION(prv_fill_text) {
|
||||
char *str_buffer;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
// we don't use INT16_MAX as this seems to leads to overflows deep down in our code
|
||||
const int16_t large_int = 10000;
|
||||
int16_t box_width;
|
||||
|
||||
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
||||
ROCKY_ARG(str_buffer),
|
||||
ROCKY_ARG(x),
|
||||
ROCKY_ARG(y),
|
||||
);
|
||||
if (argc >= 4) {
|
||||
// we use this to get range checks and rounding for free
|
||||
JS_VAR result = rocky_args_assign(argc - 3, &argv[3],
|
||||
&(RockyArgBinding){.ptr = &box_width, .type = RockyArgTypeInt16}, 1);
|
||||
if (jerry_value_has_error_flag(result)) {
|
||||
return jerry_acquire_value(result);
|
||||
}
|
||||
} else {
|
||||
box_width = large_int;
|
||||
}
|
||||
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
GRect box = {
|
||||
.origin.x = x,
|
||||
.origin.y = y,
|
||||
.size.w = box_width,
|
||||
.size.h = large_int,
|
||||
};
|
||||
|
||||
// adjust box to accommodate for alignment
|
||||
switch (s_rocky_text_state.alignment) {
|
||||
case GTextAlignmentCenter: {
|
||||
box.origin.x -= box.size.w / 2;
|
||||
break;
|
||||
}
|
||||
case GTextAlignmentRight: {
|
||||
box.origin.x -= box.size.w;
|
||||
break;
|
||||
}
|
||||
default: {} // do nothing
|
||||
}
|
||||
|
||||
ctx->draw_state.text_color = ctx->draw_state.fill_color;
|
||||
graphics_draw_text(ctx, str_buffer, s_rocky_text_state.font,
|
||||
box,
|
||||
s_rocky_text_state.overflow_mode,
|
||||
s_rocky_text_state.alignment,
|
||||
s_rocky_text_state.text_attributes);
|
||||
|
||||
task_free(str_buffer);
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static bool prv_text_align_from_value(jerry_value_t value, GTextAlignment *result) {
|
||||
char str[10] = {0};
|
||||
jerry_string_to_utf8_char_buffer(value, (jerry_char_t *)str, sizeof(str));
|
||||
|
||||
#define HANDLE_CASE(identifer, value) \
|
||||
if (strcmp(identifer, str) == 0) { \
|
||||
*result = value; \
|
||||
return true; \
|
||||
}
|
||||
|
||||
HANDLE_CASE("left", GTextAlignmentLeft);
|
||||
HANDLE_CASE("right", GTextAlignmentRight);
|
||||
HANDLE_CASE("center", GTextAlignmentCenter);
|
||||
// assuming left-to-right
|
||||
HANDLE_CASE("start", GTextAlignmentLeft);
|
||||
HANDLE_CASE("end", GTextAlignmentRight);
|
||||
|
||||
#undef HANDLE_CASE
|
||||
|
||||
// unknown value
|
||||
return false;
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_set_text_align) {
|
||||
GTextAlignment alignment;
|
||||
if (argc >= 1 && prv_text_align_from_value(argv[0], &alignment)) {
|
||||
s_rocky_text_state.alignment = alignment;
|
||||
}
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_get_text_align) {
|
||||
char *align_str = NULL;
|
||||
switch (s_rocky_text_state.alignment) {
|
||||
case GTextAlignmentLeft:
|
||||
align_str = "left";
|
||||
break;
|
||||
case GTextAlignmentRight:
|
||||
align_str = "right";
|
||||
break;
|
||||
case GTextAlignmentCenter:
|
||||
align_str = "center";
|
||||
break;
|
||||
}
|
||||
|
||||
return jerry_create_string((const jerry_char_t *)align_str);
|
||||
}
|
||||
|
||||
// we speed this up, e.g. by sorting and doing binary search if this ever becomes an issue
|
||||
T_STATIC const RockyAPISystemFontDefinition s_font_definitions[] = {
|
||||
{.js_name = "18px bold Gothic", .res_key = FONT_KEY_GOTHIC_18_BOLD},
|
||||
{.js_name = "14px Gothic", .res_key = FONT_KEY_GOTHIC_14},
|
||||
{.js_name = "14px bold Gothic", .res_key = FONT_KEY_GOTHIC_14_BOLD},
|
||||
{.js_name = "18px Gothic", .res_key = FONT_KEY_GOTHIC_18},
|
||||
{.js_name = "24px Gothic", .res_key = FONT_KEY_GOTHIC_24},
|
||||
{.js_name = "24px bold Gothic", .res_key = FONT_KEY_GOTHIC_24_BOLD},
|
||||
{.js_name = "28px Gothic", .res_key = FONT_KEY_GOTHIC_28},
|
||||
{.js_name = "28px bold Gothic", .res_key = FONT_KEY_GOTHIC_28_BOLD},
|
||||
{.js_name = "30px bolder Bitham", .res_key = FONT_KEY_BITHAM_30_BLACK},
|
||||
{.js_name = "42px bold Bitham", .res_key = FONT_KEY_BITHAM_42_BOLD},
|
||||
{.js_name = "42px light Bitham", .res_key = FONT_KEY_BITHAM_42_LIGHT},
|
||||
{.js_name = "42px Bitham-numeric", .res_key = FONT_KEY_BITHAM_42_MEDIUM_NUMBERS},
|
||||
{.js_name = "34px Bitham-numeric", .res_key = FONT_KEY_BITHAM_34_MEDIUM_NUMBERS},
|
||||
{.js_name = "21px Roboto", .res_key = FONT_KEY_ROBOTO_CONDENSED_21},
|
||||
{.js_name = "49px Roboto-subset", .res_key = FONT_KEY_ROBOTO_BOLD_SUBSET_49},
|
||||
{.js_name = "28px bold Droid-serif", .res_key = FONT_KEY_DROID_SERIF_28_BOLD},
|
||||
{.js_name = "20px bold Leco-numbers", .res_key = FONT_KEY_LECO_20_BOLD_NUMBERS},
|
||||
{.js_name = "26px bold Leco-numbers-am-pm", .res_key = FONT_KEY_LECO_26_BOLD_NUMBERS_AM_PM},
|
||||
{.js_name = "32px bold numbers Leco-numbers", .res_key = FONT_KEY_LECO_32_BOLD_NUMBERS},
|
||||
{.js_name = "36px bold numbers Leco-numbers", .res_key = FONT_KEY_LECO_36_BOLD_NUMBERS},
|
||||
{.js_name = "38px bold numbers Leco-numbers", .res_key = FONT_KEY_LECO_38_BOLD_NUMBERS},
|
||||
{.js_name = "42px bold numbers Leco-numbers", .res_key = FONT_KEY_LECO_42_NUMBERS},
|
||||
{.js_name = "28px light numbers Leco-numbers", .res_key = FONT_KEY_LECO_28_LIGHT_NUMBERS},
|
||||
{ 0 }, // element to support unit-testing
|
||||
};
|
||||
|
||||
//! The index to the default font in s_font_definitions
|
||||
#define DEFAULT_FONT_DEFINITION (s_font_definitions[2])
|
||||
|
||||
T_STATIC bool prv_font_definition_from_value(
|
||||
jerry_value_t value, RockyAPISystemFontDefinition const **result) {
|
||||
char str[50] = {0};
|
||||
jerry_string_to_utf8_char_buffer(value, (jerry_char_t *)str, sizeof(str));
|
||||
|
||||
const RockyAPISystemFontDefinition *def = s_font_definitions;
|
||||
while (def->js_name) {
|
||||
if (strcmp(str, def->js_name) == 0) {
|
||||
*result = def;
|
||||
return true;
|
||||
}
|
||||
def++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_set_font) {
|
||||
const RockyAPISystemFontDefinition *font_definition = NULL;
|
||||
if (argc >= 1 && prv_font_definition_from_value(argv[0], &font_definition)) {
|
||||
s_rocky_text_state.font = fonts_get_system_font(font_definition->res_key);
|
||||
s_rocky_text_state.font_name = font_definition->js_name;
|
||||
}
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_get_font) {
|
||||
return jerry_create_string_utf8((const jerry_char_t *)s_rocky_text_state.font_name);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(prv_measure_text) {
|
||||
char *str_buffer;
|
||||
|
||||
ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(
|
||||
ROCKY_ARG(str_buffer),
|
||||
);
|
||||
|
||||
GContext *const ctx = rocky_api_graphics_get_gcontext();
|
||||
|
||||
const int16_t box_x = argc >= 2 ? (int16_t)jerry_get_int32_value(argv[1]) : 0;
|
||||
const int16_t box_y = argc >= 3 ? (int16_t)jerry_get_int32_value(argv[2]) : 0;
|
||||
const int16_t box_width = argc >= 4 ? (int16_t)jerry_get_int32_value(argv[3]) : INT16_MAX;
|
||||
const GRect box = {
|
||||
.origin.x = box_x,
|
||||
.origin.y = box_y,
|
||||
.size.w = box_width,
|
||||
.size.h = INT16_MAX,
|
||||
};
|
||||
|
||||
const GSize size = prv_get_max_used_size(ctx, str_buffer, box);
|
||||
task_free(str_buffer);
|
||||
|
||||
// return a TextMetrics object
|
||||
JS_VAR result = jerry_create_object();
|
||||
JS_VAR result_width = jerry_create_number(size.w);
|
||||
JS_VAR result_height = jerry_create_number(size.h);
|
||||
JS_VAR result_abbl = jerry_create_number(-1);
|
||||
JS_VAR result_abbr = jerry_create_number(-2);
|
||||
jerry_set_object_field(result, "width", result_width);
|
||||
jerry_set_object_field(result, "height", result_height);
|
||||
return jerry_acquire_value(result);
|
||||
}
|
||||
|
||||
void rocky_api_graphics_text_add_canvas_methods(jerry_value_t obj) {
|
||||
rocky_add_function(obj, ROCKY_CONTEXT2D_FILLTEXT, prv_fill_text);
|
||||
rocky_add_function(obj, ROCKY_CONTEXT2D_MEASURETEXT, prv_measure_text);
|
||||
rocky_define_property(obj, ROCKY_CONTEXT2D_TEXTALIGN, prv_get_text_align, prv_set_text_align);
|
||||
rocky_define_property(obj, ROCKY_CONTEXT2D_FONT, prv_get_font, prv_set_font);
|
||||
}
|
||||
|
||||
static void prv_text_state_deinit(void) {
|
||||
if (s_rocky_text_state.text_attributes) {
|
||||
graphics_text_attributes_destroy(s_rocky_text_state.text_attributes);
|
||||
s_rocky_text_state.text_attributes = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void rocky_api_graphics_text_reset_state(void) {
|
||||
prv_text_state_deinit();
|
||||
|
||||
s_rocky_text_state = (RockyAPITextState) {
|
||||
.font = s_default_font,
|
||||
.font_name = DEFAULT_FONT_DEFINITION.js_name,
|
||||
|
||||
.overflow_mode = GTextOverflowModeWordWrap,
|
||||
.alignment = GTextAlignmentLeft,
|
||||
.text_attributes = NULL,
|
||||
};
|
||||
}
|
||||
|
||||
void rocky_api_graphics_text_init(void) {
|
||||
s_default_font = fonts_get_system_font(DEFAULT_FONT_DEFINITION.res_key);
|
||||
rocky_api_graphics_text_reset_state();
|
||||
}
|
||||
|
||||
void rocky_api_graphics_text_deinit(void) {
|
||||
prv_text_state_deinit();
|
||||
}
|
41
src/fw/applib/rockyjs/api/rocky_api_graphics_text.h
Normal file
41
src/fw/applib/rockyjs/api/rocky_api_graphics_text.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
#include "jerry-api.h"
|
||||
|
||||
#include "applib/graphics/text.h"
|
||||
|
||||
void rocky_api_graphics_text_init(void);
|
||||
void rocky_api_graphics_text_deinit(void);
|
||||
void rocky_api_graphics_text_add_canvas_methods(jerry_value_t obj);
|
||||
void rocky_api_graphics_text_reset_state(void);
|
||||
|
||||
// these structs are exposed here so that we can unit-test the internal state
|
||||
typedef struct RockyAPISystemFontDefinition {
|
||||
const char *js_name;
|
||||
const char *res_key;
|
||||
} RockyAPISystemFontDefinition;
|
||||
|
||||
typedef struct RockyAPITextState {
|
||||
GFont font;
|
||||
const char *font_name;
|
||||
GTextOverflowMode overflow_mode;
|
||||
GTextAlignment alignment;
|
||||
GTextAttributes *text_attributes;
|
||||
} RockyAPITextState;
|
203
src/fw/applib/rockyjs/api/rocky_api_memory.c
Normal file
203
src/fw/applib/rockyjs/api/rocky_api_memory.c
Normal file
|
@ -0,0 +1,203 @@
|
|||
/*
|
||||
* 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 "rocky_api_memory.h"
|
||||
|
||||
#include "rocky_api_global.h"
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <ecma/base/ecma-gc.h>
|
||||
#include <jmem/jmem-allocator.h>
|
||||
#include <jmem/jmem-heap.h>
|
||||
#include <jmem/jmem-poolman.h>
|
||||
|
||||
#include <util/math.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ROCKY_EVENT_MEMORYPRESSURE "memorypressure"
|
||||
#define ROCKY_EVENT_MEMORYPRESSURE_LEVEL "level"
|
||||
#define ROCKY_EVENT_MEMORYPRESSURE_LEVEL_HIGH "high"
|
||||
// #define ROCKY_EVENT_MEMORYPRESSURE_LEVEL_NORMAL "normal" // NYI: PBL-42081
|
||||
// #define ROCKY_EVENT_MEMORYPRESSURE_LEVEL_LOW "low" // NYI: PBL-42081
|
||||
|
||||
#define HEADROOM_MIN_SIZE_BYTES (128)
|
||||
|
||||
//! This struct should only be accessed from the app task, so no locking is required.
|
||||
typedef struct RockyMemoryAPIContext {
|
||||
//! Reserved headroom that will be made available just before calling into the
|
||||
//! 'memorypressure' event handler.
|
||||
void *headroom;
|
||||
size_t headroom_size;
|
||||
|
||||
//! True if we're currently calling the 'memorypressure' event handler.
|
||||
bool is_calling_memory_callback;
|
||||
} RockyMemoryAPIContext;
|
||||
|
||||
static bool prv_is_headroom_allocated(const RockyMemoryAPIContext *ctx) {
|
||||
return (ctx->headroom != NULL);
|
||||
}
|
||||
|
||||
static void prv_allocate_headroom_or_die(RockyMemoryAPIContext *ctx) {
|
||||
// It's highly likely that while executing a the handler for the 'memorypressure' event,
|
||||
// new objects have been created on the heap. Therefore, it's unlikely we'll be able to reclaim
|
||||
// the desired headroom immediately after returning from the handler. Try to grab as much as we
|
||||
// can and resize it later on, see prv_resize_headroom_if_needed().
|
||||
jmem_heap_stats_t stats = {};
|
||||
jmem_heap_get_stats(&stats);
|
||||
if (stats.largest_free_block_bytes < HEADROOM_MIN_SIZE_BYTES) {
|
||||
jerry_port_fatal(ERR_OUT_OF_MEMORY, __builtin_return_address(0));
|
||||
return;
|
||||
}
|
||||
const size_t headroom_size = MIN(stats.largest_free_block_bytes,
|
||||
ROCKY_API_MEMORY_HEADROOM_DESIRED_SIZE_BYTES);
|
||||
// This will jerry_port_fatal() if the size isn't available:
|
||||
ctx->headroom = jmem_heap_alloc_block(headroom_size);
|
||||
ctx->headroom_size = headroom_size;
|
||||
}
|
||||
|
||||
static void prv_deallocate_headroom(RockyMemoryAPIContext *ctx) {
|
||||
jmem_heap_free_block(ctx->headroom, ctx->headroom_size);
|
||||
ctx->headroom = NULL;
|
||||
ctx->headroom_size = 0;
|
||||
}
|
||||
|
||||
static void prv_resize_headroom_if_needed(RockyMemoryAPIContext *ctx) {
|
||||
// If needed, try to get our headroom back at the level where we want it to be.
|
||||
if (ctx->headroom &&
|
||||
ctx->headroom_size < ROCKY_API_MEMORY_HEADROOM_DESIRED_SIZE_BYTES) {
|
||||
prv_deallocate_headroom(ctx);
|
||||
prv_allocate_headroom_or_die(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_collect_all_garbage(void) {
|
||||
ecma_free_unused_memory(JMEM_FREE_UNUSED_MEMORY_SEVERITY_HIGH, 0, true);
|
||||
jmem_pools_collect_empty();
|
||||
}
|
||||
|
||||
static void prv_memorypressure_app_log(const char *level, const jmem_heap_stats_t *stats) {
|
||||
APP_LOG(LOG_LEVEL_WARNING, "Memory pressure level: %s", level);
|
||||
APP_LOG(LOG_LEVEL_WARNING,
|
||||
"heap size: %zu, alloc'd: %zu, waste: %zu, largest free block: %zu,",
|
||||
stats->size, stats->allocated_bytes, stats->waste_bytes, stats->largest_free_block_bytes);
|
||||
APP_LOG(LOG_LEVEL_WARNING, "used blocks: %zu, free blocks: %zu",
|
||||
stats->alloc_count, stats->free_count);
|
||||
}
|
||||
|
||||
static void prv_call_memorypressure_handler(RockyMemoryAPIContext *ctx,
|
||||
const char *level, jmem_heap_stats_t *stats,
|
||||
bool fatal_if_not_freed) {
|
||||
if (ctx->is_calling_memory_callback && fatal_if_not_freed) {
|
||||
// If this happens, the event handler wasn't able to run because there wasn't enough memory
|
||||
// and triggered the OOM callback again -- basically this means our headroom was too small to
|
||||
// execute the handler...
|
||||
sys_analytics_inc(ANALYTICS_APP_METRIC_MEM_ROCKY_RECURSIVE_MEMORYPRESSURE_EVENT_COUNT,
|
||||
AnalyticsClient_CurrentTask);
|
||||
return;
|
||||
}
|
||||
ctx->is_calling_memory_callback = true;
|
||||
|
||||
// TODO: PBL-41990 -- Release caches internal to Rocky's API implementation
|
||||
|
||||
prv_memorypressure_app_log(level, stats);
|
||||
|
||||
prv_deallocate_headroom(ctx);
|
||||
prv_collect_all_garbage();
|
||||
|
||||
{ // New scope to cleanup the event immediately after the event handler call.
|
||||
JS_VAR memory_pressure_event = rocky_global_create_event(ROCKY_EVENT_MEMORYPRESSURE);
|
||||
JS_VAR level_val = jerry_create_string_utf8((const jerry_char_t *)level);
|
||||
jerry_set_object_field(memory_pressure_event, ROCKY_EVENT_MEMORYPRESSURE_LEVEL, level_val);
|
||||
rocky_global_call_event_handlers(memory_pressure_event);
|
||||
}
|
||||
|
||||
prv_collect_all_garbage();
|
||||
|
||||
prv_allocate_headroom_or_die(ctx);
|
||||
|
||||
ctx->is_calling_memory_callback = false;
|
||||
}
|
||||
|
||||
static void prv_memory_callback(jmem_free_unused_memory_severity_t severity,
|
||||
size_t requested_size_bytes,
|
||||
bool fatal_if_not_freed) {
|
||||
RockyMemoryAPIContext *ctx = app_state_get_rocky_memory_api_context();
|
||||
if (!fatal_if_not_freed || severity < JMEM_FREE_UNUSED_MEMORY_SEVERITY_HIGH) {
|
||||
ecma_free_unused_memory(severity, requested_size_bytes, fatal_if_not_freed);
|
||||
|
||||
if (!ctx->is_calling_memory_callback) {
|
||||
// It's likely memory has just been free'd, try resizing now.
|
||||
// See comment at the top of prv_allocate_headroom_or_die() why this is needed.
|
||||
prv_resize_headroom_if_needed(ctx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Trigger agressive garbage collection, force property hashmaps to be dropped:
|
||||
prv_collect_all_garbage();
|
||||
jmem_heap_stats_t stats = {};
|
||||
jmem_heap_get_stats(&stats);
|
||||
if (stats.largest_free_block_bytes >= requested_size_bytes + sizeof(jmem_heap_free_t)) {
|
||||
return;
|
||||
}
|
||||
|
||||
prv_call_memorypressure_handler(ctx, ROCKY_EVENT_MEMORYPRESSURE_LEVEL_HIGH, &stats,
|
||||
fatal_if_not_freed);
|
||||
}
|
||||
|
||||
static void prv_init(void) {
|
||||
RockyMemoryAPIContext *ctx = task_zalloc_check(sizeof(RockyMemoryAPIContext));
|
||||
app_state_set_rocky_memory_api_context(ctx);
|
||||
|
||||
jmem_unregister_free_unused_memory_callback(ecma_free_unused_memory);
|
||||
jmem_register_free_unused_memory_callback(prv_memory_callback);
|
||||
}
|
||||
|
||||
static void prv_deinit(void) {
|
||||
RockyMemoryAPIContext *ctx = app_state_get_rocky_memory_api_context();
|
||||
if (prv_is_headroom_allocated(ctx)) {
|
||||
prv_deallocate_headroom(ctx);
|
||||
}
|
||||
jmem_unregister_free_unused_memory_callback(prv_memory_callback);
|
||||
jmem_register_free_unused_memory_callback(ecma_free_unused_memory);
|
||||
|
||||
task_free(ctx);
|
||||
app_state_set_rocky_memory_api_context(NULL);
|
||||
}
|
||||
|
||||
static bool prv_add_handler(const char *event_name, jerry_value_t handler) {
|
||||
if (strcmp(event_name, ROCKY_EVENT_MEMORYPRESSURE) == 0) {
|
||||
RockyMemoryAPIContext *ctx = app_state_get_rocky_memory_api_context();
|
||||
if (!prv_is_headroom_allocated(ctx)) {
|
||||
prv_allocate_headroom_or_die(ctx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const RockyGlobalAPI MEMORY_APIS = {
|
||||
.init = prv_init,
|
||||
.deinit = prv_deinit,
|
||||
.add_handler = prv_add_handler,
|
||||
};
|
23
src/fw/applib/rockyjs/api/rocky_api_memory.h
Normal file
23
src/fw/applib/rockyjs/api/rocky_api_memory.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
#define ROCKY_API_MEMORY_HEADROOM_DESIRED_SIZE_BYTES (256)
|
||||
|
||||
extern const RockyGlobalAPI MEMORY_APIS;
|
77
src/fw/applib/rockyjs/api/rocky_api_preferences.c
Normal file
77
src/fw/applib/rockyjs/api/rocky_api_preferences.c
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 "rocky_api_preferences.h"
|
||||
|
||||
#include "applib/preferred_content_size.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#define ROCKY_USERPREFERENCES "userPreferences"
|
||||
#define ROCKY_USERPREFERENCES_CONTENTSIZE "contentSize"
|
||||
|
||||
static const char *prv_get_content_size_string(void) {
|
||||
PreferredContentSize size = preferred_content_size();
|
||||
|
||||
// make sure enum is among known values
|
||||
switch (size) {
|
||||
case PreferredContentSizeSmall:
|
||||
case PreferredContentSizeMedium:
|
||||
case PreferredContentSizeLarge:
|
||||
case PreferredContentSizeExtraLarge:
|
||||
break;
|
||||
default:
|
||||
size = PreferredContentSizeDefault;
|
||||
}
|
||||
|
||||
switch (size) {
|
||||
case PreferredContentSizeSmall:
|
||||
return "small";
|
||||
case PreferredContentSizeMedium:
|
||||
return "medium";
|
||||
case PreferredContentSizeLarge:
|
||||
return "large";
|
||||
case PreferredContentSizeExtraLarge:
|
||||
return "x-large";
|
||||
case NumPreferredContentSizes: {}
|
||||
}
|
||||
// unreachable
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
static jerry_value_t prv_get_content_size(void) {
|
||||
return jerry_create_string(
|
||||
(const jerry_char_t *)prv_get_content_size_string());
|
||||
}
|
||||
|
||||
static void prv_fill_preferences(jerry_value_t preferences) {
|
||||
JS_VAR content_size = prv_get_content_size();
|
||||
|
||||
jerry_set_object_field(preferences, ROCKY_USERPREFERENCES_CONTENTSIZE, content_size);
|
||||
}
|
||||
|
||||
static void prv_init(void) {
|
||||
bool was_created = false;
|
||||
JS_VAR rocky = rocky_get_rocky_singleton();
|
||||
JS_VAR preferences = rocky_get_or_create_object(rocky, ROCKY_USERPREFERENCES,
|
||||
rocky_creator_object, NULL, &was_created);
|
||||
PBL_ASSERTN(was_created);
|
||||
prv_fill_preferences(preferences);
|
||||
}
|
||||
|
||||
const RockyGlobalAPI PREFERENCES_APIS = {
|
||||
.init = prv_init,
|
||||
};
|
21
src/fw/applib/rockyjs/api/rocky_api_preferences.h
Normal file
21
src/fw/applib/rockyjs/api/rocky_api_preferences.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
extern const RockyGlobalAPI PREFERENCES_APIS;
|
105
src/fw/applib/rockyjs/api/rocky_api_tickservice.c
Normal file
105
src/fw/applib/rockyjs/api/rocky_api_tickservice.c
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 "rocky_api_errors.h"
|
||||
#include "rocky_api_tickservice.h"
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "applib/tick_timer_service.h"
|
||||
#include "rocky_api_global.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/attributes.h"
|
||||
#include "util/size.h"
|
||||
|
||||
#define ROCKY_EVENT_SECONDCHANGE "secondchange"
|
||||
#define ROCKY_EVENT_MINUTECHANGE "minutechange"
|
||||
#define ROCKY_EVENT_HOURCHANGE "hourchange"
|
||||
#define ROCKY_EVENT_DAYCHANGE "daychange"
|
||||
#define ROCKY_FIELD_EVENT_DATE "date"
|
||||
|
||||
// TODO: PBL-35780 use app_state_get_rocky_runtime_context().context_binding instead
|
||||
SECTION(".rocky_bss") static TimeUnits s_units;
|
||||
|
||||
static void prv_init(void) {
|
||||
s_units = (TimeUnits)0;
|
||||
}
|
||||
|
||||
static jerry_value_t prv_create_event(const char *event_name, struct tm *tick_time) {
|
||||
JS_VAR event = rocky_global_create_event(event_name);
|
||||
|
||||
JS_VAR date_obj = rocky_util_create_date(tick_time);
|
||||
jerry_set_object_field(event, ROCKY_FIELD_EVENT_DATE, date_obj);
|
||||
|
||||
return jerry_acquire_value(event);
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *event_name;
|
||||
TimeUnits time_units;
|
||||
} s_events[] = {
|
||||
{ROCKY_EVENT_SECONDCHANGE,
|
||||
// In some scenarios, our C-API doesn't trigger callbacks with just SECOND_UNIT or MINUTE_UNIT
|
||||
// if the hour changes. To make the JS-API more conveniently to use without changing the
|
||||
// existing C behavior, we subscribe to all "higher" units as well.
|
||||
SECOND_UNIT | MINUTE_UNIT | HOUR_UNIT | DAY_UNIT | MONTH_UNIT | YEAR_UNIT},
|
||||
{ROCKY_EVENT_MINUTECHANGE,
|
||||
MINUTE_UNIT | HOUR_UNIT | DAY_UNIT | MONTH_UNIT | YEAR_UNIT},
|
||||
{ROCKY_EVENT_HOURCHANGE,
|
||||
HOUR_UNIT | DAY_UNIT | MONTH_UNIT | YEAR_UNIT},
|
||||
{ROCKY_EVENT_DAYCHANGE,
|
||||
DAY_UNIT | MONTH_UNIT | YEAR_UNIT},
|
||||
};
|
||||
|
||||
T_STATIC void prv_tick_handler(struct tm *tick_time, TimeUnits units_changed) {
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(s_events); i++) {
|
||||
if (units_changed & s_events[i].time_units) {
|
||||
JS_VAR event = prv_create_event(s_events[i].event_name, tick_time);
|
||||
rocky_global_call_event_handlers(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_add_handler(const char *event_name, jerry_value_t handler) {
|
||||
TimeUnits added_units = (TimeUnits)0;
|
||||
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(s_events); i++) {
|
||||
if (strcmp(s_events[i].event_name, event_name) == 0) {
|
||||
added_units |= s_events[i].time_units;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (added_units == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s_units |= added_units;
|
||||
tick_timer_service_subscribe(s_units, prv_tick_handler);
|
||||
|
||||
// contract is: we call handler immediately after subscribe once
|
||||
JS_VAR event = prv_create_event(event_name, NULL);
|
||||
rocky_util_call_user_function_and_log_uncaught_error(handler, jerry_create_undefined(),
|
||||
&event, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
const RockyGlobalAPI TICKSERVICE_APIS = {
|
||||
.init = prv_init,
|
||||
.add_handler = prv_add_handler,
|
||||
// TODO: PBL-43380 apparently, we never unsubsrcibed from tick events…
|
||||
};
|
21
src/fw/applib/rockyjs/api/rocky_api_tickservice.h
Normal file
21
src/fw/applib/rockyjs/api/rocky_api_tickservice.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
extern const RockyGlobalAPI TICKSERVICE_APIS;
|
140
src/fw/applib/rockyjs/api/rocky_api_timers.c
Normal file
140
src/fw/applib/rockyjs/api/rocky_api_timers.c
Normal file
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
* 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 "rocky_api_errors.h"
|
||||
#include "jerry-api.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "applib/app_timer.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/logging.h"
|
||||
#include "rocky_api.h"
|
||||
|
||||
#define ROCKY_SETINTERVAL "setInterval"
|
||||
#define ROCKY_SETTIMEOUT "setTimeout"
|
||||
#define ROCKY_CLEARTIMEOUT "clearTimeout"
|
||||
#define ROCKY_CLEARINTERVAL "clearInterval"
|
||||
|
||||
typedef struct {
|
||||
bool is_repeating;
|
||||
jerry_value_t callback;
|
||||
AppTimer *timer;
|
||||
jerry_length_t argc;
|
||||
jerry_value_t argv[];
|
||||
} RockyTimerCbData;
|
||||
|
||||
static void prv_timer_cleanup(RockyTimerCbData *timer_data) {
|
||||
jerry_release_value(timer_data->callback);
|
||||
for (unsigned i = 0; i < timer_data->argc; i++) {
|
||||
jerry_release_value(timer_data->argv[i]);
|
||||
}
|
||||
task_free(timer_data);
|
||||
}
|
||||
|
||||
static void prv_timer_callback(void *data) {
|
||||
RockyTimerCbData *timer_data = data;
|
||||
|
||||
if (jerry_value_is_function(timer_data->callback)) {
|
||||
rocky_util_call_user_function_and_log_uncaught_error(
|
||||
timer_data->callback, jerry_create_undefined(), timer_data->argv, timer_data->argc);
|
||||
} else if (jerry_value_is_string(timer_data->callback)) {
|
||||
char *source_buf = rocky_string_alloc_and_copy(timer_data->callback);
|
||||
rocky_util_eval_and_log_uncaught_error((const jerry_char_t *)source_buf, strlen(source_buf));
|
||||
task_free(source_buf);
|
||||
}
|
||||
|
||||
if (!timer_data->is_repeating) {
|
||||
prv_timer_cleanup(timer_data);
|
||||
}
|
||||
}
|
||||
|
||||
static jerry_value_t prv_create_timer(const jerry_value_t *argv,
|
||||
const jerry_length_t argc,
|
||||
bool is_repeating) {
|
||||
if (argc < 1) {
|
||||
return rocky_error_arguments_missing();
|
||||
}
|
||||
|
||||
jerry_value_t callback = argv[0];
|
||||
if (!jerry_value_is_function(callback) &&
|
||||
!jerry_value_is_string(callback)) {
|
||||
// Nothing to call, but somehow this is valid ¯\_(ツ)_/¯, no-op
|
||||
return jerry_create_number(0);
|
||||
}
|
||||
jerry_acquire_value(callback);
|
||||
|
||||
uint32_t timeout = 0;
|
||||
jerry_length_t cb_argc = 0;
|
||||
if (argc >= 2) {
|
||||
// both numbers (123) and strings ('123') are valid
|
||||
// all others are 0
|
||||
timeout = rocky_util_uint_from_value(argv[1]);
|
||||
cb_argc = argc - 2;
|
||||
}
|
||||
|
||||
RockyTimerCbData *cb_data = task_zalloc_check(sizeof(RockyTimerCbData) +
|
||||
cb_argc * sizeof(jerry_value_t));
|
||||
*cb_data = (RockyTimerCbData){
|
||||
.is_repeating = is_repeating,
|
||||
.callback = callback,
|
||||
.argc = cb_argc
|
||||
};
|
||||
// copy arguments over to cb_data
|
||||
for (unsigned i = 0; i < cb_argc; i++) {
|
||||
cb_data->argv[i] = argv[i + 2];
|
||||
jerry_acquire_value(cb_data->argv[i]);
|
||||
}
|
||||
cb_data->timer = app_timer_register_repeatable(timeout,
|
||||
prv_timer_callback,
|
||||
cb_data,
|
||||
is_repeating);
|
||||
return jerry_create_number((uintptr_t) cb_data->timer);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(setInterval_handler) {
|
||||
return prv_create_timer(argv, argc, true /*is_repeating*/);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(setTimeout_handler) {
|
||||
return prv_create_timer(argv, argc, false /*is_repeating*/);
|
||||
}
|
||||
|
||||
JERRY_FUNCTION(clearTimer_handler) {
|
||||
if (argc < 1 || !jerry_value_is_number(argv[0])) {
|
||||
// Somehow this is valid ¯\_(ツ)_/¯, no-op
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
AppTimer *timer =
|
||||
(AppTimer *)(uintptr_t)rocky_util_uint_from_value(argv[0]);
|
||||
RockyTimerCbData *timer_data = app_timer_get_data(timer);
|
||||
app_timer_cancel(timer);
|
||||
if (timer_data) {
|
||||
prv_timer_cleanup(timer_data);
|
||||
}
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
static void prv_init_apis(void) {
|
||||
rocky_add_global_function(ROCKY_SETINTERVAL, setInterval_handler);
|
||||
rocky_add_global_function(ROCKY_SETTIMEOUT, setTimeout_handler);
|
||||
rocky_add_global_function(ROCKY_CLEARTIMEOUT, clearTimer_handler);
|
||||
rocky_add_global_function(ROCKY_CLEARINTERVAL, clearTimer_handler);
|
||||
}
|
||||
|
||||
const RockyGlobalAPI TIMER_APIS = {
|
||||
.init = prv_init_apis,
|
||||
};
|
21
src/fw/applib/rockyjs/api/rocky_api_timers.h
Normal file
21
src/fw/applib/rockyjs/api/rocky_api_timers.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
extern const RockyGlobalAPI TIMER_APIS;
|
260
src/fw/applib/rockyjs/api/rocky_api_util.c
Normal file
260
src/fw/applib/rockyjs/api/rocky_api_util.c
Normal file
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* 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 "rocky_api_util.h"
|
||||
#include "rocky_api_errors.h"
|
||||
|
||||
#include "applib/app_logging.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "system/passert.h"
|
||||
#include "util/size.h"
|
||||
#include "util/trig.h"
|
||||
#include "vendor/jerryscript/jerry-libm/include/math.h" // easiest way to get M_PI
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define ROCKY_SINGLETON "_rocky"
|
||||
|
||||
// [MT] including <math.h> causes the jerry-libm header to get included again :(
|
||||
extern double round(double d);
|
||||
|
||||
uintptr_t rocky_util_uint_from_value(const jerry_value_t value) {
|
||||
uintptr_t rv = 0;
|
||||
if (jerry_value_is_number(value)) {
|
||||
rv = jerry_get_number_value(value);
|
||||
} else if (jerry_value_is_string(value)) {
|
||||
uint32_t sz = jerry_get_utf8_string_size(value);
|
||||
char buf[sz + 1];
|
||||
memset(buf, 0, sz + 1);
|
||||
jerry_string_to_utf8_char_buffer(value, (jerry_char_t *)buf, sz);
|
||||
rv = strtoul(buf, NULL, 0);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
int32_t jerry_get_int32_value(jerry_value_t value) {
|
||||
return (int32_t)(round(jerry_get_number_value(value)));
|
||||
}
|
||||
|
||||
int32_t jerry_get_angle_value(jerry_value_t value) {
|
||||
return (int32_t)
|
||||
(jerry_get_number_value(value) * TRIG_MAX_ANGLE / (2 * M_PI)) + TRIG_MAX_ANGLE / 4;
|
||||
}
|
||||
|
||||
char *rocky_string_alloc_and_copy(const jerry_value_t string) {
|
||||
char *out_str = NULL;
|
||||
|
||||
if (jerry_value_is_string(string)) {
|
||||
uint32_t sz = jerry_get_utf8_string_size(string);
|
||||
out_str = task_zalloc_check(sz + 1);
|
||||
jerry_string_to_utf8_char_buffer(string, (jerry_char_t *)out_str, sz);
|
||||
}
|
||||
return out_str;
|
||||
}
|
||||
|
||||
void rocky_log_exception(const char *message, jerry_value_t exception) {
|
||||
// using APP_LOG in this function so that 3rd-parties will know what went wrong with their JS
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Exception while %s", message);
|
||||
|
||||
jerry_char_t buffer[100] = {};
|
||||
const ssize_t written = jerry_object_to_string_to_utf8_char_buffer(exception, buffer,
|
||||
sizeof(buffer) - 1);
|
||||
if (written > 0) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "%s", buffer);
|
||||
} else {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "no further info.");
|
||||
}
|
||||
}
|
||||
|
||||
void jerry_set_object_field(jerry_value_t object, const char *field, jerry_value_t value) {
|
||||
JS_VAR prop_name = jerry_create_string((jerry_char_t *)field);
|
||||
JS_UNUSED_VAL = jerry_set_property(object, prop_name, value);
|
||||
}
|
||||
|
||||
jerry_value_t jerry_get_object_field(jerry_value_t object, const char* field) {
|
||||
JS_VAR prop_name = jerry_create_string((jerry_char_t *) field);
|
||||
const bool has_property = jerry_has_property(object, prop_name);
|
||||
JS_VAR value = (has_property) ? jerry_get_property(object, prop_name) : jerry_create_undefined();
|
||||
return jerry_acquire_value(value);
|
||||
}
|
||||
|
||||
bool rocky_str_equal(jerry_value_t str_js, const char *str) {
|
||||
char buffer[40] = {0};
|
||||
PBL_ASSERTN(strlen(str) < sizeof(buffer));
|
||||
jerry_string_to_utf8_char_buffer(str_js, (jerry_char_t *) buffer, sizeof(buffer));
|
||||
return strcmp(buffer, str) == 0;
|
||||
}
|
||||
|
||||
jerry_value_t jerry_get_object_getter_result(jerry_value_t object, const char *getter_name) {
|
||||
JS_VAR getter = jerry_get_object_field(object, getter_name);
|
||||
const bool is_function = jerry_value_is_function(getter);
|
||||
JS_VAR result = (is_function) ? jerry_call_function(getter, object, NULL, 0)
|
||||
: jerry_create_undefined();
|
||||
return jerry_acquire_value(result);
|
||||
}
|
||||
|
||||
|
||||
jerry_value_t rocky_creator_object(void *ignore) {
|
||||
return jerry_create_object();
|
||||
}
|
||||
|
||||
jerry_value_t rocky_creator_empty_array(void *ignore) {
|
||||
return jerry_create_array(0);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_get_or_create_object(jerry_value_t parent, const char *name,
|
||||
RockyObjectCreatorFunc creator_func, void *data,
|
||||
bool *was_created) {
|
||||
if (jerry_value_is_undefined(parent)) {
|
||||
parent = jerry_get_global_object();
|
||||
// it's safe to release the global object here already, it will never be destroyed
|
||||
jerry_release_value(parent);
|
||||
}
|
||||
|
||||
// check the object doesn't already exist
|
||||
JS_VAR val = jerry_get_object_field(parent, name);
|
||||
if (!jerry_value_is_undefined(val)) {
|
||||
if (was_created) {
|
||||
*was_created = false;
|
||||
}
|
||||
return jerry_acquire_value(val);
|
||||
}
|
||||
|
||||
JS_VAR result = creator_func(data);
|
||||
jerry_set_object_field(parent, name, result);
|
||||
|
||||
if (was_created) {
|
||||
*was_created = true;
|
||||
}
|
||||
return jerry_acquire_value(result);
|
||||
}
|
||||
|
||||
static jerry_value_t prv_create_function(void *c_function_ptr) {
|
||||
return jerry_create_external_function(c_function_ptr);
|
||||
}
|
||||
|
||||
bool rocky_add_function(jerry_value_t parent, char *name, jerry_external_handler_t handler) {
|
||||
bool result = false;
|
||||
JS_UNUSED_VAL = rocky_get_or_create_object(parent, name, prv_create_function, handler, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool rocky_add_global_function(char *name, jerry_external_handler_t handler) {
|
||||
return rocky_add_function(jerry_create_undefined(), name, handler);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_add_constructor(char *name, jerry_external_handler_t handler) {
|
||||
JS_VAR prototype = jerry_create_object();
|
||||
JS_VAR rocky_object = rocky_get_rocky_singleton();
|
||||
JS_VAR constructor = rocky_get_or_create_object(rocky_object, name,
|
||||
prv_create_function, handler, NULL);
|
||||
// JerryScript doesn't create a prototype for external/C functions :( probably to save memory?
|
||||
jerry_set_object_field(prototype, "constructor", constructor);
|
||||
jerry_set_object_field(constructor, "prototype", prototype);
|
||||
return jerry_acquire_value(prototype);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_create_with_constructor(const char *rocky_constructor_name,
|
||||
const jerry_value_t args_p[],
|
||||
jerry_size_t args_count) {
|
||||
JS_VAR rocky_object = rocky_get_rocky_singleton();
|
||||
JS_VAR constructor = jerry_get_object_field(rocky_object, rocky_constructor_name);
|
||||
JS_VAR object = jerry_construct_object(constructor, args_p, args_count);
|
||||
|
||||
return jerry_acquire_value(object);
|
||||
}
|
||||
|
||||
// TODO: PBL-35780 make this part of app_state_get_rocky_runtime_context()
|
||||
SECTION(".rocky_bss") static jerry_value_t s_rocky_singleton;
|
||||
void rocky_set_rocky_singleton(jerry_value_t v) {
|
||||
s_rocky_singleton = jerry_acquire_value(v);
|
||||
JS_VAR global = jerry_get_global_object();
|
||||
jerry_set_object_field(global, ROCKY_SINGLETON, v);
|
||||
}
|
||||
|
||||
jerry_value_t rocky_get_rocky_singleton(void) {
|
||||
return jerry_acquire_value(s_rocky_singleton);
|
||||
}
|
||||
|
||||
void rocky_delete_singleton(void) {
|
||||
JS_VAR rocky_str = jerry_create_string((const jerry_char_t *)ROCKY_SINGLETON);
|
||||
JS_VAR global = jerry_get_global_object();
|
||||
jerry_delete_property(global, rocky_str);
|
||||
}
|
||||
|
||||
void rocky_define_property(jerry_value_t parent, const char *prop_name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter) {
|
||||
jerry_property_descriptor_t prop_desc = {};
|
||||
jerry_init_property_descriptor_fields(&prop_desc);
|
||||
prop_desc.is_get_defined = getter != NULL;
|
||||
prop_desc.getter = jerry_create_external_function(getter);
|
||||
prop_desc.is_set_defined = setter != NULL;
|
||||
prop_desc.setter = jerry_create_external_function(setter);
|
||||
JS_VAR prop_name_js = jerry_create_string((const jerry_char_t *) prop_name);
|
||||
JS_UNUSED_VAL = jerry_define_own_property(parent, prop_name_js, &prop_desc);
|
||||
jerry_release_value(prop_desc.getter);
|
||||
jerry_release_value(prop_desc.setter);
|
||||
}
|
||||
|
||||
// If result has an error flag set, log the error.
|
||||
// Note: this function releases the passed in value.
|
||||
T_STATIC void prv_log_uncaught_error(const jerry_value_t result) {
|
||||
if (jerry_value_has_error_flag(result)) {
|
||||
rocky_error_print(result);
|
||||
}
|
||||
jerry_release_value(result);
|
||||
}
|
||||
|
||||
void rocky_util_eval_and_log_uncaught_error(const jerry_char_t *source_p, size_t source_size) {
|
||||
prv_log_uncaught_error(jerry_eval(source_p, source_size, false /*strict*/));
|
||||
}
|
||||
|
||||
void rocky_util_call_user_function_and_log_uncaught_error(const jerry_value_t func_obj_val,
|
||||
const jerry_value_t this_val,
|
||||
const jerry_value_t args_p[],
|
||||
jerry_size_t args_count) {
|
||||
prv_log_uncaught_error(jerry_call_function(func_obj_val, this_val, args_p, args_count));
|
||||
}
|
||||
|
||||
jerry_value_t rocky_util_create_date(struct tm *tick_time) {
|
||||
JS_VAR date_constructor = jerry_get_global_builtin((const jerry_char_t *)"Date");
|
||||
if (!jerry_value_is_constructor(date_constructor)) {
|
||||
return jerry_create_undefined();
|
||||
}
|
||||
|
||||
jerry_value_t date_obj;
|
||||
if (tick_time) {
|
||||
jerry_value_t args[] = {
|
||||
jerry_create_number(1900 + tick_time->tm_year), jerry_create_number(tick_time->tm_mon),
|
||||
jerry_create_number(tick_time->tm_mday), jerry_create_number(tick_time->tm_hour),
|
||||
jerry_create_number(tick_time->tm_min), jerry_create_number(tick_time->tm_sec)
|
||||
};
|
||||
date_obj = jerry_construct_object(date_constructor, args, ARRAY_LENGTH(args));
|
||||
for (unsigned i = 0; i < ARRAY_LENGTH(args); ++i) {
|
||||
jerry_release_value(args[i]);
|
||||
}
|
||||
} else {
|
||||
date_obj = jerry_construct_object(date_constructor, NULL, 0);
|
||||
}
|
||||
return date_obj;
|
||||
}
|
||||
|
||||
void rocky_cleanup_js_var(const jerry_value_t *var) {
|
||||
jerry_release_value(*var);
|
||||
}
|
111
src/fw/applib/rockyjs/api/rocky_api_util.h
Normal file
111
src/fw/applib/rockyjs/api/rocky_api_util.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jerry-api.h"
|
||||
#include "util/macro.h"
|
||||
#include "util/time/time.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
// Create a const jerry value that will be released when it goes out of scope
|
||||
#define JS_VAR const jerry_value_t __attribute__((cleanup(rocky_cleanup_js_var)))
|
||||
|
||||
// Create a temporary jerry value with a unique name that will be released when it goes out of scope
|
||||
#define JS_UNUSED_VAL \
|
||||
const jerry_value_t __attribute__((unused,cleanup(rocky_cleanup_js_var))) MACRO_CONCAT(js, __COUNTER__)
|
||||
|
||||
void rocky_cleanup_js_var(const jerry_value_t *var);
|
||||
|
||||
uintptr_t rocky_util_uint_from_value(const jerry_value_t value);
|
||||
|
||||
//! Note: you need to free the return value explicitly
|
||||
char *rocky_string_alloc_and_copy(const jerry_value_t string);
|
||||
|
||||
void rocky_log_exception(const char *message, jerry_value_t exception);
|
||||
|
||||
#define ROCKY_RETURN_IF_ERROR(expr) \
|
||||
do { \
|
||||
const jerry_value_t rv = (expr); \
|
||||
if (jerry_value_has_error_flag(rv)) { \
|
||||
return rv; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define JERRY_FUNCTION(name) static jerry_value_t name(const jerry_value_t function_obj_p, \
|
||||
const jerry_value_t this_val, \
|
||||
const jerry_value_t argv[], \
|
||||
const jerry_length_t argc)
|
||||
|
||||
void jerry_set_object_field(jerry_value_t object, const char *field, jerry_value_t value);
|
||||
jerry_value_t jerry_get_object_field(jerry_value_t object, const char *field);
|
||||
jerry_value_t jerry_get_object_getter_result(jerry_value_t object, const char *getter_name);
|
||||
|
||||
bool rocky_add_function(jerry_value_t parent, char *name, jerry_external_handler_t handler);
|
||||
bool rocky_add_global_function(char *name, jerry_external_handler_t handler);
|
||||
|
||||
//! Adds a constructor function object to rocky.name (it sets up the prototype of the function
|
||||
//! which JerryScript normally does not do for external functions).
|
||||
//! @return the prototype object
|
||||
jerry_value_t rocky_add_constructor(char *name, jerry_external_handler_t handler);
|
||||
|
||||
// Creates an object using a global constructor, in other words: `new constructor_name(args)`
|
||||
jerry_value_t rocky_create_with_constructor(const char *rocky_constructor_name,
|
||||
const jerry_value_t args_p[],
|
||||
jerry_size_t args_count);
|
||||
|
||||
// does rounding to avoid Math.sin(2*Math.PI) issues and related problems
|
||||
int32_t jerry_get_int32_value(jerry_value_t value);
|
||||
|
||||
// converts JS angle (0 degrees at 3 o'clock, 360 degrees = 2 * PI)
|
||||
// to Pebble angle (0 degrees at 12 o'clock, 360 degrees = TRIG_MAX_ANGLE)
|
||||
int32_t jerry_get_angle_value(jerry_value_t value);
|
||||
|
||||
typedef jerry_value_t (*RockyObjectCreatorFunc)(void *data);
|
||||
|
||||
// implementations of RockyObjectCreatorFunc for convenience of rocky_get_or_create_object
|
||||
// these functions simply ignore the parameter
|
||||
jerry_value_t rocky_creator_object(void *ignore);
|
||||
jerry_value_t rocky_creator_empty_array(void *ignore);
|
||||
|
||||
jerry_value_t rocky_get_or_create_object(jerry_value_t parent, const char *name,
|
||||
RockyObjectCreatorFunc creator_func, void *data,
|
||||
bool *was_created);
|
||||
|
||||
// True, if jerry value represents a string that is equal to a given char buffer
|
||||
bool rocky_str_equal(jerry_value_t str_js, const char *str);
|
||||
|
||||
void rocky_set_rocky_singleton(jerry_value_t v);
|
||||
|
||||
// caller needs to call jerry_release_value() on return value
|
||||
jerry_value_t rocky_get_rocky_singleton(void);
|
||||
|
||||
void rocky_delete_singleton(void);
|
||||
|
||||
void rocky_define_property(jerry_value_t parent, const char *prop_name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter);
|
||||
|
||||
void rocky_util_eval_and_log_uncaught_error(const jerry_char_t *source_p, size_t source_size);
|
||||
|
||||
void rocky_util_call_user_function_and_log_uncaught_error(const jerry_value_t func_obj_val,
|
||||
const jerry_value_t this_val,
|
||||
const jerry_value_t args_p[],
|
||||
jerry_size_t args_count);
|
||||
|
||||
jerry_value_t rocky_util_create_date(struct tm *tick_time);
|
340
src/fw/applib/rockyjs/api/rocky_api_util_args.c
Normal file
340
src/fw/applib/rockyjs/api/rocky_api_util_args.c
Normal file
|
@ -0,0 +1,340 @@
|
|||
/*
|
||||
* 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 "rocky_api_util_args.h"
|
||||
|
||||
#include "rocky_api_errors.h"
|
||||
#include "rocky_api_graphics_color.h"
|
||||
#include "rocky_api_util.h"
|
||||
|
||||
#include "applib/graphics/gtypes.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <math.h>
|
||||
#include <util/math.h>
|
||||
|
||||
// from lit-magic-string.inc.h
|
||||
static const char ECMA_STRING_TYPE_NUMBER[] = "Number";
|
||||
static const char COLOR_TYPES[] = "String ('color name' or '#hex') or Number";
|
||||
static const char COLOR_ERROR_MSG[] = "Expecting String ('color name' or '#hex') or Number";
|
||||
|
||||
typedef struct {
|
||||
const char *expected_type_name;
|
||||
int arg_offset;
|
||||
} RockyArgTypeCheckError;
|
||||
|
||||
typedef struct {
|
||||
const char *error_msg;
|
||||
int arg_offset;
|
||||
} RockyArgValueCheckError;
|
||||
|
||||
typedef struct {
|
||||
bool (* check_value_and_assign)(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *val_error_out);
|
||||
bool (* check_type)(const jerry_value_t argv[], RockyArgTypeCheckError *type_error_out);
|
||||
uint8_t expected_num_args;
|
||||
} RockyArgAssignImp;
|
||||
|
||||
static bool prv_check_value_number_within_bounds(RockyArgType type, const double *val,
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
const struct {
|
||||
double min;
|
||||
double max;
|
||||
} bounds[] = {
|
||||
[RockyArgTypeUInt8] = {0.0, 255.0},
|
||||
[RockyArgTypeUInt16] = {0.0, 65535.0},
|
||||
[RockyArgTypeUInt32] = {0.0, 4294967295.0},
|
||||
[RockyArgTypeUInt64] = {0.0, 9223372036854775807.0},
|
||||
[RockyArgTypeInt8] = {-128.0, 127.0},
|
||||
[RockyArgTypeInt16] = {-32768.0, 32767.0},
|
||||
[RockyArgTypeInt32] = {-2147483648.0, 2147483647.0},
|
||||
[RockyArgTypeInt64] = {-9223372036854775808.0, 9223372036854775807.0},
|
||||
[RockyArgTypeDouble] = {-1.7976931348623157e+308, 1.7976931348623157e+308},
|
||||
[RockyArgTypeFixedS16_3] = {-32768.0 / FIXED_S16_3_FACTOR, 32767.0 / FIXED_S16_3_FACTOR},
|
||||
};
|
||||
const bool is_within_bounds = WITHIN(*val, bounds[type].min, bounds[type].max);
|
||||
if (!is_within_bounds) {
|
||||
*value_error_out = (RockyArgValueCheckError) {
|
||||
.error_msg = "Value out of bounds for native type",
|
||||
.arg_offset = 0,
|
||||
};
|
||||
}
|
||||
return is_within_bounds;
|
||||
}
|
||||
|
||||
static Fixed_S16_3 prv_fixed_s3_from_double(double d) {
|
||||
return (Fixed_S16_3 ){.raw_value = round(d * FIXED_S16_3_FACTOR)};
|
||||
}
|
||||
|
||||
static bool prv_assign_number(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
double val = jerry_get_number_value(argv[0]);
|
||||
|
||||
if (!prv_check_value_number_within_bounds(binding->type, &val, value_error_out)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (binding->type != RockyArgTypeDouble && binding->type != RockyArgTypeFixedS16_3) {
|
||||
val = round(val);
|
||||
}
|
||||
|
||||
void *const dest_ptr = binding->ptr;
|
||||
switch (binding->type) {
|
||||
case RockyArgTypeUInt8:
|
||||
*((uint8_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeUInt16:
|
||||
*((uint16_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeUInt32:
|
||||
*((uint32_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeUInt64:
|
||||
*((uint64_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeInt8:
|
||||
*((int8_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeInt16:
|
||||
*((int16_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeInt32:
|
||||
*((int32_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeInt64:
|
||||
*((int64_t *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeDouble:
|
||||
*((double *)dest_ptr) = val;
|
||||
return true;
|
||||
case RockyArgTypeFixedS16_3:
|
||||
*((Fixed_S16_3 *)dest_ptr) = prv_fixed_s3_from_double(val);
|
||||
return true;
|
||||
default:
|
||||
WTF;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool prv_assign_bool(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
*((bool *)binding->ptr) = jerry_value_to_boolean(argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_convert_to_string_and_apply(const jerry_value_t val,
|
||||
void (*apply)(const jerry_value_t,
|
||||
const RockyArgBinding *),
|
||||
const RockyArgBinding *binding) {
|
||||
jerry_value_t str_val;
|
||||
bool should_release = false;
|
||||
if (jerry_value_is_string(val)) {
|
||||
str_val = val;
|
||||
} else {
|
||||
str_val = jerry_value_to_string(val);
|
||||
should_release = true;
|
||||
}
|
||||
|
||||
apply(str_val, binding);
|
||||
|
||||
if (should_release) {
|
||||
jerry_release_value(str_val);
|
||||
}
|
||||
}
|
||||
|
||||
static void prv_malloc_and_assign_string_applier(const jerry_value_t str,
|
||||
const RockyArgBinding *binding) {
|
||||
*((char **)binding->ptr) = rocky_string_alloc_and_copy(str);
|
||||
}
|
||||
|
||||
static bool prv_malloc_and_assign_string(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
prv_convert_to_string_and_apply(argv[0], prv_malloc_and_assign_string_applier, binding);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void prv_copy_string_applier(const jerry_value_t str, const RockyArgBinding *binding) {
|
||||
const size_t buffer_size = binding->options.string.buffer_size;
|
||||
const size_t copied_size = jerry_string_to_utf8_char_buffer(str, (jerry_char_t *)binding->ptr,
|
||||
buffer_size);
|
||||
((char *)binding->ptr)[copied_size] = '\0';
|
||||
}
|
||||
|
||||
static bool prv_copy_string_no_malloc(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
prv_convert_to_string_and_apply(argv[0], prv_copy_string_applier, binding);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_convert_and_assign_angle(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
*((double *)binding->ptr) = jerry_get_angle_value(argv[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_assign_grect_precise(const RockyArgBinding *binding, const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
Fixed_S16_3 v[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
const double d = jerry_get_number_value(argv[i]);
|
||||
if (!prv_check_value_number_within_bounds(RockyArgTypeFixedS16_3, &d, value_error_out)) {
|
||||
value_error_out->arg_offset = i;
|
||||
return false;
|
||||
}
|
||||
v[i] = prv_fixed_s3_from_double(d);
|
||||
}
|
||||
*((GRectPrecise *)binding->ptr) = (GRectPrecise) {
|
||||
.origin.x = v[0],
|
||||
.origin.y = v[1],
|
||||
.size.w = v[2],
|
||||
.size.h = v[3],
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_convert_and_assign_gcolor(const RockyArgBinding *binding,
|
||||
const jerry_value_t argv[],
|
||||
RockyArgValueCheckError *value_error_out) {
|
||||
if (rocky_api_graphics_color_from_value(argv[0], (GColor *)binding->ptr)) {
|
||||
return true;
|
||||
}
|
||||
*value_error_out = (RockyArgValueCheckError) {
|
||||
.error_msg = COLOR_ERROR_MSG,
|
||||
.arg_offset = 0,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool prv_check_type_is_number(const jerry_value_t argv[],
|
||||
RockyArgTypeCheckError *type_error_out) {
|
||||
if (jerry_value_is_number(argv[0])) {
|
||||
return true;
|
||||
};
|
||||
*type_error_out = (RockyArgTypeCheckError) {
|
||||
.expected_type_name = ECMA_STRING_TYPE_NUMBER,
|
||||
.arg_offset = 0,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool prv_check_type_any(const jerry_value_t argv[],
|
||||
RockyArgTypeCheckError *type_error_out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_check_4x_number(const jerry_value_t argv[],
|
||||
RockyArgTypeCheckError *type_error_out) {
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
if (!prv_check_type_is_number(&argv[i], type_error_out)) {
|
||||
type_error_out->arg_offset = i;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool prv_check_color_type(const jerry_value_t argv[],
|
||||
RockyArgTypeCheckError *type_error_out) {
|
||||
if (jerry_value_is_number(argv[0]) || jerry_value_is_string(argv[0])) {
|
||||
return true;
|
||||
}
|
||||
*type_error_out = (RockyArgTypeCheckError) {
|
||||
.expected_type_name = COLOR_TYPES,
|
||||
.arg_offset = 0,
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
static void prv_init_arg_assign_imp(RockyArgType arg_type, RockyArgAssignImp *imp_out) {
|
||||
// Defaults:
|
||||
imp_out->expected_num_args = 1;
|
||||
|
||||
switch (arg_type) {
|
||||
case RockyArgTypeUInt8 ... RockyArgTypeFixedS16_3:
|
||||
imp_out->check_type = prv_check_type_is_number;
|
||||
imp_out->check_value_and_assign = prv_assign_number;
|
||||
break;
|
||||
|
||||
case RockyArgTypeBool:
|
||||
imp_out->check_type = prv_check_type_any;
|
||||
imp_out->check_value_and_assign = prv_assign_bool;
|
||||
break;
|
||||
|
||||
case RockyArgTypeStringMalloc:
|
||||
imp_out->check_type = prv_check_type_any;
|
||||
imp_out->check_value_and_assign = prv_malloc_and_assign_string;
|
||||
break;
|
||||
|
||||
case RockyArgTypeStringArray:
|
||||
imp_out->check_type = prv_check_type_any;
|
||||
imp_out->check_value_and_assign = prv_copy_string_no_malloc;
|
||||
break;
|
||||
|
||||
case RockyArgTypeAngle:
|
||||
imp_out->check_type = prv_check_type_is_number;
|
||||
imp_out->check_value_and_assign = prv_convert_and_assign_angle;
|
||||
break;
|
||||
|
||||
case RockyArgTypeGRectPrecise:
|
||||
imp_out->expected_num_args = 4;
|
||||
imp_out->check_type = prv_check_4x_number;
|
||||
imp_out->check_value_and_assign = prv_assign_grect_precise;
|
||||
break;
|
||||
|
||||
case RockyArgTypeGColor:
|
||||
imp_out->check_type = prv_check_color_type;
|
||||
imp_out->check_value_and_assign = prv_convert_and_assign_gcolor;
|
||||
break;
|
||||
|
||||
default:
|
||||
WTF;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
jerry_value_t rocky_args_assign(const jerry_length_t argc, const jerry_value_t argv[],
|
||||
const RockyArgBinding *arg_bindings, size_t num_arg_bindings) {
|
||||
for (uint32_t i = 0; i < num_arg_bindings; ++i) {
|
||||
const RockyArgBinding *binding = &arg_bindings[i];
|
||||
|
||||
RockyArgAssignImp imp;
|
||||
prv_init_arg_assign_imp(binding->type, &imp);
|
||||
|
||||
// Check number of arguments:
|
||||
// TODO: PBL-40644: support optional bindings
|
||||
if (i + imp.expected_num_args > argc) {
|
||||
return rocky_error_arguments_missing();
|
||||
}
|
||||
|
||||
// Type check:
|
||||
RockyArgTypeCheckError type_error;
|
||||
if (!imp.check_type(&argv[i], &type_error)) {
|
||||
return rocky_error_unexpected_type(i + type_error.arg_offset,
|
||||
type_error.expected_type_name);
|
||||
}
|
||||
|
||||
// Check value, transform & assign:
|
||||
RockyArgValueCheckError value_error;
|
||||
if (!imp.check_value_and_assign(&arg_bindings[i], &argv[i], &value_error)) {
|
||||
return rocky_error_argument_invalid_at_index(i + value_error.arg_offset,
|
||||
value_error.error_msg);
|
||||
}
|
||||
}
|
||||
// Just ignore any surplus args
|
||||
|
||||
return jerry_create_undefined();
|
||||
}
|
114
src/fw/applib/rockyjs/api/rocky_api_util_args.h
Normal file
114
src/fw/applib/rockyjs/api/rocky_api_util_args.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <util/size.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include "jerry-api.h"
|
||||
|
||||
typedef struct GRect GRect;
|
||||
|
||||
typedef enum {
|
||||
RockyArgTypeUnsupported = -1,
|
||||
|
||||
RockyArgTypeUInt8,
|
||||
RockyArgTypeUInt16,
|
||||
RockyArgTypeUInt32,
|
||||
RockyArgTypeUInt64,
|
||||
RockyArgTypeInt8,
|
||||
RockyArgTypeInt16,
|
||||
RockyArgTypeInt32,
|
||||
RockyArgTypeInt64,
|
||||
RockyArgTypeDouble,
|
||||
RockyArgTypeFixedS16_3,
|
||||
|
||||
RockyArgTypeBool,
|
||||
RockyArgTypeStringArray,
|
||||
RockyArgTypeStringMalloc,
|
||||
RockyArgTypeGRectPrecise,
|
||||
RockyArgTypeGColor,
|
||||
RockyArgTypeAngle,
|
||||
|
||||
RockyArgType_Count,
|
||||
} RockyArgType;
|
||||
|
||||
typedef struct {
|
||||
void *ptr;
|
||||
RockyArgType type;
|
||||
union {
|
||||
//! Valid when type == RockyArgTypeStringArray
|
||||
struct {
|
||||
size_t buffer_size;
|
||||
} string;
|
||||
} options;
|
||||
} RockyArgBinding;
|
||||
|
||||
#define ROCKY_ARG_MAKE(v, binding_type, opts) \
|
||||
((const RockyArgBinding) { .ptr = (v), .type = binding_type, .options = opts })
|
||||
|
||||
//! Binds a JS string argument to a C-string for which a buffer is provided by the client code and
|
||||
//! for which the size of the buffer cannot be derived from the variable type.
|
||||
//! @note For char[] arrays (with a static size), use ROCKY_ARG() instead.
|
||||
//! @note If the buffer is too small, nothing will be copied!
|
||||
#define ROCKY_ARG_STR(char_buf, _buffer_size) \
|
||||
ROCKY_ARG_MAKE(char_buf, RockyArgTypeStringArray, { .string = { .buffer_size = _buffer_size }})
|
||||
|
||||
#define ROCKY_ARG_ANGLE(var) ROCKY_ARG_MAKE(&(var), RockyArgTypeAngle, {})
|
||||
|
||||
#define RockyIfCTypeElse(var, c_type, then, else) \
|
||||
__builtin_choose_expr(__builtin_types_compatible_p(__typeof__(var), c_type), (then), (else))
|
||||
|
||||
#define ROCKY_ARG(var) \
|
||||
RockyIfCTypeElse(var, uint8_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeUInt8, {}), \
|
||||
RockyIfCTypeElse(var, uint16_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeUInt16, {}), \
|
||||
RockyIfCTypeElse(var, uint32_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeUInt32, {}), \
|
||||
RockyIfCTypeElse(var, uint32_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeUInt32, {}), \
|
||||
RockyIfCTypeElse(var, uint64_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeUInt64, {}), \
|
||||
RockyIfCTypeElse(var, int8_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeInt8, {}), \
|
||||
RockyIfCTypeElse(var, int16_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeInt16, {}), \
|
||||
RockyIfCTypeElse(var, int32_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeInt32, {}), \
|
||||
RockyIfCTypeElse(var, int32_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeInt32, {}), \
|
||||
RockyIfCTypeElse(var, int64_t, ROCKY_ARG_MAKE(&(var), RockyArgTypeInt64, {}), \
|
||||
RockyIfCTypeElse(var, double, ROCKY_ARG_MAKE(&(var), RockyArgTypeDouble, {}), \
|
||||
RockyIfCTypeElse(var, Fixed_S16_3, ROCKY_ARG_MAKE(&(var), RockyArgTypeFixedS16_3, {}), \
|
||||
RockyIfCTypeElse(var, bool, ROCKY_ARG_MAKE(&(var), RockyArgTypeBool, {}), \
|
||||
RockyIfCTypeElse(var, char *, ROCKY_ARG_MAKE(&(var), RockyArgTypeStringMalloc, {}), \
|
||||
RockyIfCTypeElse(var, char[], ROCKY_ARG_STR(&(var), sizeof(var)), \
|
||||
RockyIfCTypeElse(var, GRectPrecise, ROCKY_ARG_MAKE(&(var), RockyArgTypeGRectPrecise, {}), \
|
||||
RockyIfCTypeElse(var, GColor, ROCKY_ARG_MAKE(&(var), RockyArgTypeGColor, {}), \
|
||||
ROCKY_ARG_MAKE(&(var), RockyArgTypeUnsupported, {}))))))))))))))))))
|
||||
|
||||
//! Helper that uses arg_bindings to check whether all mandatory arguments are given, of the
|
||||
//! expected type and the input values are within the limits of the C type. If the checks pass,
|
||||
//! the function will transform the JerryScript values to the native equivalents and assign them
|
||||
//! to the storage as specified by the arg_bindings.
|
||||
//! @return `undefined` on success and an error object in case of a problem.
|
||||
jerry_value_t rocky_args_assign(const jerry_length_t argc, const jerry_value_t argv[],
|
||||
const RockyArgBinding *arg_bindings, size_t num_arg_bindings);
|
||||
|
||||
#define ROCKY_ARGS_ASSIGN_OR_RETURN_ERROR(...) \
|
||||
do { \
|
||||
const RockyArgBinding bindings[] = { \
|
||||
__VA_ARGS__ \
|
||||
}; \
|
||||
const jerry_value_t error_value = \
|
||||
rocky_args_assign(argc, argv, bindings, ARRAY_LENGTH(bindings)); \
|
||||
if (jerry_value_has_error_flag(error_value)) { \
|
||||
return error_value; \
|
||||
} \
|
||||
} while (0)
|
264
src/fw/applib/rockyjs/api/rocky_api_watchinfo.c
Normal file
264
src/fw/applib/rockyjs/api/rocky_api_watchinfo.c
Normal file
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* 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 "rocky_api_watchinfo.h"
|
||||
|
||||
#include "applib/app_watch_info.h"
|
||||
#include "applib/i18n.h"
|
||||
#include "mfg/mfg_info.h"
|
||||
#include "rocky_api_util.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "system/passert.h"
|
||||
#include "system/version.h"
|
||||
|
||||
// rocky.watchInfo = {
|
||||
// platform: "basalt",
|
||||
// model: "pebble_time_red",
|
||||
// language: "en_US",
|
||||
// firmware: {
|
||||
// major: 4,
|
||||
// minor: 0,
|
||||
// patch: 1,
|
||||
// suffix: "beta3"
|
||||
// }
|
||||
// }
|
||||
|
||||
#define ROCKY_WATCHINFO "watchInfo"
|
||||
#define ROCKY_WATCHINFO_PLATFORM "platform"
|
||||
#define ROCKY_WATCHINFO_MODEL "model"
|
||||
#define ROCKY_WATCHINFO_LANG "language"
|
||||
#define ROCKY_WATCHINFO_FW "firmware"
|
||||
#define ROCKY_WATCHINFO_FW_MAJOR "major"
|
||||
#define ROCKY_WATCHINFO_FW_MINOR "minor"
|
||||
#define ROCKY_WATCHINFO_FW_PATCH "patch"
|
||||
#define ROCKY_WATCHINFO_FW_SUFFIX "suffix"
|
||||
|
||||
static const char *prv_get_platform_name_string(void) {
|
||||
const PlatformType platform = sys_get_current_app_sdk_platform();
|
||||
return platform_type_get_name(platform);
|
||||
}
|
||||
|
||||
static jerry_value_t prv_get_platform_name(void) {
|
||||
return jerry_create_string(
|
||||
(const jerry_char_t *)prv_get_platform_name_string());
|
||||
}
|
||||
|
||||
#if PLATFORM_TINTIN
|
||||
# define TINTIN_MODEL(model_str) model_str
|
||||
#else
|
||||
# define TINTIN_MODEL(model_str) NULL
|
||||
#endif // PLATFORM_TINTIN
|
||||
|
||||
#if PLATFORM_SNOWY
|
||||
# define SNOWY_MODEL(model_str) model_str
|
||||
#else
|
||||
# define SNOWY_MODEL(model_str) NULL
|
||||
#endif // PLATFORM_SNOWY
|
||||
|
||||
#if PLATFORM_SPALDING
|
||||
# define SPALDING_MODEL(model_str) model_str
|
||||
#else
|
||||
# define SPALDING_MODEL(model_str) NULL
|
||||
#endif // PLATFORM_SPALDING
|
||||
|
||||
#if PLATFORM_SILK
|
||||
# define SILK_MODEL(model_str) model_str
|
||||
#else
|
||||
# define SILK_MODEL(model_str) NULL
|
||||
#endif // PLATFORM_SPALDING
|
||||
|
||||
#if PLATFORM_ROBERT
|
||||
# define ROBERT_MODEL(model_str) model_str
|
||||
#else
|
||||
# define ROBERT_MODEL(model_str) NULL
|
||||
#endif // PLATFORM_ROBERT
|
||||
|
||||
|
||||
static jerry_value_t prv_get_model_name(void) {
|
||||
WatchInfoColor color = sys_watch_info_get_color();
|
||||
char *model_name = NULL;
|
||||
|
||||
if (color < WATCH_INFO_COLOR__MAX) {
|
||||
switch (color) {
|
||||
case WATCH_INFO_COLOR_BLACK:
|
||||
model_name = TINTIN_MODEL("pebble_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_WHITE:
|
||||
model_name = TINTIN_MODEL("pebble_white");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_RED:
|
||||
model_name = TINTIN_MODEL("pebble_red");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_ORANGE:
|
||||
model_name = TINTIN_MODEL("pebble_orange");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_GRAY:
|
||||
model_name = TINTIN_MODEL("pebble_gray");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_STAINLESS_STEEL:
|
||||
model_name = TINTIN_MODEL("pebble_steel_silver");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_MATTE_BLACK:
|
||||
model_name = TINTIN_MODEL("pebble_steel_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_BLUE:
|
||||
model_name = TINTIN_MODEL("pebble_blue");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_GREEN:
|
||||
model_name = TINTIN_MODEL("pebble_green");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PINK:
|
||||
model_name = TINTIN_MODEL("pebble_pink");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_WHITE:
|
||||
model_name = SNOWY_MODEL("pebble_time_white");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_BLACK:
|
||||
model_name = SNOWY_MODEL("pebble_time_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_RED:
|
||||
model_name = SNOWY_MODEL("pebble_time_red");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_STEEL_SILVER:
|
||||
model_name = SNOWY_MODEL("pebble_time_steel_silver");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_STEEL_BLACK:
|
||||
model_name = SNOWY_MODEL("pebble_time_steel_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_STEEL_GOLD:
|
||||
model_name = SNOWY_MODEL("pebble_time_steel_gold");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_SILVER_14:
|
||||
model_name = SPALDING_MODEL("pebble_time_round_silver_14mm");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_BLACK_14:
|
||||
model_name = SPALDING_MODEL("pebble_time_round_black_14mm");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_SILVER_20:
|
||||
model_name = SPALDING_MODEL("pebble_time_round_silver_20mm");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_BLACK_20:
|
||||
model_name = SPALDING_MODEL("pebble_time_round_black_20mm");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_TIME_ROUND_ROSE_GOLD_14:
|
||||
model_name = SPALDING_MODEL("pebble_time_round_rose_gold_14mm");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_BLACK:
|
||||
model_name = SILK_MODEL("pebble_2_hr_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_LIME:
|
||||
model_name = SILK_MODEL("pebble_2_hr_lime");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_FLAME:
|
||||
model_name = SILK_MODEL("pebble_2_hr_flame");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_WHITE:
|
||||
model_name = SILK_MODEL("pebble_2_hr_white");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_HR_AQUA:
|
||||
model_name = SILK_MODEL("pebble_2_hr_aqua");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_SE_BLACK:
|
||||
model_name = SILK_MODEL("pebble_2_se_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_2_SE_WHITE:
|
||||
model_name = SILK_MODEL("pebble_2_se_white");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_TIME_2_BLACK:
|
||||
model_name = ROBERT_MODEL("pebble_time_2_black");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_TIME_2_SILVER:
|
||||
model_name = ROBERT_MODEL("pebble_time_2_silver");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_PEBBLE_TIME_2_GOLD:
|
||||
model_name = ROBERT_MODEL("pebble_time_2_gold");
|
||||
break;
|
||||
case WATCH_INFO_COLOR_UNKNOWN:
|
||||
case WATCH_INFO_COLOR__MAX:
|
||||
model_name = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (model_name) {
|
||||
return jerry_create_string((const jerry_char_t *)model_name);
|
||||
} else {
|
||||
// Assume we're running on QEMU
|
||||
const char *platform_name = prv_get_platform_name_string();
|
||||
const char *qemu_prefix = "qemu_platform_";
|
||||
char combined_string[strlen(qemu_prefix) + strlen(platform_name) + 1];
|
||||
strcpy(combined_string, qemu_prefix);
|
||||
strcat(combined_string, platform_name);
|
||||
return jerry_create_string((const jerry_char_t *)combined_string);
|
||||
}
|
||||
}
|
||||
|
||||
static jerry_value_t prv_get_language(void) {
|
||||
return jerry_create_string((const jerry_char_t *)app_get_system_locale());
|
||||
}
|
||||
|
||||
static jerry_value_t prv_get_fw_version(void) {
|
||||
WatchInfoVersion fw_version = watch_info_get_firmware_version();
|
||||
JS_VAR version_major = jerry_create_number(fw_version.major);
|
||||
JS_VAR version_minor = jerry_create_number(fw_version.minor);
|
||||
JS_VAR version_patch = jerry_create_number(fw_version.patch);
|
||||
|
||||
// Parse the suffix out of the version tag
|
||||
FirmwareMetadata md;
|
||||
version_copy_running_fw_metadata(&md);
|
||||
char *suffix_str = strstr(md.version_tag, "-");
|
||||
if (suffix_str) {
|
||||
// Skip the '-' character
|
||||
suffix_str++;
|
||||
} else {
|
||||
suffix_str = "";
|
||||
}
|
||||
JS_VAR version_suffix = jerry_create_string((const jerry_char_t *)suffix_str);
|
||||
|
||||
JS_VAR version_object = jerry_create_object();
|
||||
jerry_set_object_field(version_object, ROCKY_WATCHINFO_FW_MAJOR, version_major);
|
||||
jerry_set_object_field(version_object, ROCKY_WATCHINFO_FW_MINOR, version_minor);
|
||||
jerry_set_object_field(version_object, ROCKY_WATCHINFO_FW_PATCH, version_patch);
|
||||
jerry_set_object_field(version_object, ROCKY_WATCHINFO_FW_SUFFIX, version_suffix);
|
||||
|
||||
// TODO: PBL-40413: Support .toString() on the fwversion field
|
||||
return jerry_acquire_value(version_object);
|
||||
}
|
||||
|
||||
static void prv_fill_watchinfo(jerry_value_t watchinfo) {
|
||||
JS_VAR platform_name = prv_get_platform_name();
|
||||
JS_VAR model_name = prv_get_model_name();
|
||||
JS_VAR language = prv_get_language();
|
||||
JS_VAR fw_version = prv_get_fw_version();
|
||||
|
||||
jerry_set_object_field(watchinfo, ROCKY_WATCHINFO_PLATFORM, platform_name);
|
||||
jerry_set_object_field(watchinfo, ROCKY_WATCHINFO_MODEL, model_name);
|
||||
jerry_set_object_field(watchinfo, ROCKY_WATCHINFO_LANG, language);
|
||||
jerry_set_object_field(watchinfo, ROCKY_WATCHINFO_FW, fw_version);
|
||||
}
|
||||
|
||||
static void prv_init_apis(void) {
|
||||
bool was_created = false;
|
||||
JS_VAR rocky = rocky_get_rocky_singleton();
|
||||
JS_VAR watchinfo =
|
||||
rocky_get_or_create_object(rocky, ROCKY_WATCHINFO, rocky_creator_object, NULL, &was_created);
|
||||
PBL_ASSERTN(was_created);
|
||||
prv_fill_watchinfo(watchinfo);
|
||||
}
|
||||
|
||||
const RockyGlobalAPI WATCHINFO_APIS = {
|
||||
.init = prv_init_apis,
|
||||
};
|
21
src/fw/applib/rockyjs/api/rocky_api_watchinfo.h
Normal file
21
src/fw/applib/rockyjs/api/rocky_api_watchinfo.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocky_api.h"
|
||||
|
||||
extern const RockyGlobalAPI WATCHINFO_APIS;
|
36
src/fw/applib/rockyjs/code_space_reservation.h
Normal file
36
src/fw/applib/rockyjs/code_space_reservation.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/attributes.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* As we are running pretty low on space on some platforms, we want to reserve some code space
|
||||
* for future use on the rocky project so that we can ensure there is space to implement APIs
|
||||
* uniformly on all platforms. Start with 15K and we will dwindle this down as we continue to
|
||||
* work on the rocky / jerryscript support.
|
||||
*
|
||||
* Modification Log:
|
||||
* 15360 : Starting point
|
||||
* 13232 : Jerryscript upstream merge on Sept 14
|
||||
* 12620 : postMessage() re-implementation
|
||||
*
|
||||
*/
|
||||
|
||||
USED const uint8_t ROCKY_RESERVED_CODE_SPACE[12620] = {};
|
121
src/fw/applib/rockyjs/jerry_port.c
Normal file
121
src/fw/applib/rockyjs/jerry_port.c
Normal file
|
@ -0,0 +1,121 @@
|
|||
/*
|
||||
* 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 "pbl_jcontext.inc.h"
|
||||
#include "jerry-port.h"
|
||||
|
||||
#include "applib/app_heap_analytics.h"
|
||||
#include "applib/app_logging.h"
|
||||
#include "applib/pbl_std/pbl_std.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_state/app_state/app_state.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
#include "system/logging.h"
|
||||
#include "system/passert.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static uint8_t prv_pbl_log_level_from_jerry_log_level(const jerry_log_level_t level) {
|
||||
switch (level) {
|
||||
case JERRY_LOG_LEVEL_ERROR:
|
||||
return LOG_LEVEL_ERROR;
|
||||
case JERRY_LOG_LEVEL_WARNING:
|
||||
return LOG_LEVEL_WARNING;
|
||||
case JERRY_LOG_LEVEL_TRACE:
|
||||
return LOG_LEVEL_DEBUG_VERBOSE;
|
||||
case JERRY_LOG_LEVEL_DEBUG:
|
||||
default:
|
||||
return LOG_LEVEL_DEBUG;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide log message implementation for the engine.
|
||||
*/
|
||||
void jerry_port_log(jerry_log_level_t level, const char* format, ...) {
|
||||
const uint8_t log_level = prv_pbl_log_level_from_jerry_log_level(level);
|
||||
if (log_level > LOG_LEVEL_DEBUG) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
app_log_vargs(log_level, "JERRY-LOG", 0, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide console message implementation for the engine.
|
||||
*/
|
||||
void jerry_port_console(const char *format, ...) {
|
||||
if (format[0] == '\n' && strlen(format) == 1) {
|
||||
return;
|
||||
}
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
app_log_vargs(LOG_LEVEL_DEBUG, "JERRY-CONSOLE", 0, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void jerry_port_fatal(jerry_fatal_code_t code, void *lr) {
|
||||
if (ERR_OUT_OF_MEMORY == code) {
|
||||
app_heap_analytics_log_rocky_heap_oom_fault();
|
||||
}
|
||||
|
||||
jerry_port_log(JERRY_LOG_LEVEL_ERROR, "Fatal Error: %d", code);
|
||||
PBL_ASSERTN_LR(false, (uint32_t)lr);
|
||||
}
|
||||
|
||||
RockyRuntimeContext * rocky_runtime_context_get(void) {
|
||||
return app_state_get_rocky_runtime_context();
|
||||
}
|
||||
|
||||
#define ALIGNED_HEAP(ptr) (void *)JERRY_ALIGNUP((uintptr_t)(ptr), JMEM_ALIGNMENT)
|
||||
|
||||
void rocky_runtime_context_init(void) {
|
||||
uint8_t *unaligned_buffer = task_zalloc(sizeof(RockyRuntimeContext) + JMEM_ALIGNMENT);
|
||||
RockyRuntimeContext * const ctx = ALIGNED_HEAP(unaligned_buffer);
|
||||
app_state_set_rocky_runtime_context(unaligned_buffer, ctx);
|
||||
}
|
||||
|
||||
void rocky_runtime_context_deinit(void) {
|
||||
task_free(app_state_get_rocky_runtime_context_buffer());
|
||||
app_state_set_rocky_runtime_context(NULL, NULL);
|
||||
}
|
||||
|
||||
double jerry_port_get_current_time (void) {
|
||||
time_t seconds;
|
||||
uint16_t millis;
|
||||
time_ms(&seconds, &millis);
|
||||
return ((double)seconds * 1000.0) + millis;
|
||||
} /* jerry_port_get_current_time */
|
||||
|
||||
DEFINE_SYSCALL(bool, jerry_port_get_time_zone, jerry_time_zone_t *tz_p) {
|
||||
if (PRIVILEGE_WAS_ELEVATED) {
|
||||
syscall_assert_userspace_buffer(tz_p, sizeof(*tz_p));
|
||||
}
|
||||
|
||||
time_t utc_now;
|
||||
time_ms(&utc_now, NULL);
|
||||
int32_t dstoffset = time_get_isdst(utc_now) ? time_get_dstoffset() : 0;
|
||||
tz_p->daylight_saving_time = dstoffset / 3600;
|
||||
tz_p->offset = -1 * time_get_gmtoffset() / 60;
|
||||
|
||||
return true;
|
||||
}
|
41
src/fw/applib/rockyjs/pbl_jcontext.inc.h
Normal file
41
src/fw/applib/rockyjs/pbl_jcontext.inc.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// NOTE: This file is uses as an -include file when compiling each jerry-core source file, so
|
||||
// be careful what you add here!
|
||||
|
||||
#define JERRY_CONTEXT(field) (rocky_runtime_context_get()->jerry_global_context.field)
|
||||
#define JERRY_HEAP_CONTEXT(field) (rocky_runtime_context_get()->jerry_global_heap.field)
|
||||
#define JERRY_HASH_TABLE_CONTEXT(field) (rocky_runtime_context_get()->jerry_global_hash_table.field)
|
||||
|
||||
#include "jcontext.h"
|
||||
|
||||
typedef struct RockyRuntimeContext {
|
||||
jerry_context_t jerry_global_context;
|
||||
jmem_heap_t jerry_global_heap;
|
||||
#ifndef CONFIG_ECMA_LCACHE_DISABLE
|
||||
jerry_hash_table_t jerry_global_hash_table;
|
||||
#endif
|
||||
} RockyRuntimeContext;
|
||||
|
||||
_Static_assert(
|
||||
((offsetof(RockyRuntimeContext, jerry_global_heap) +
|
||||
offsetof(jmem_heap_t, area)) % JMEM_ALIGNMENT) == 0,
|
||||
"jerry_global_heap.area must be aligned to JMEM_ALIGNMENT!");
|
||||
|
||||
extern RockyRuntimeContext * rocky_runtime_context_get(void);
|
20
src/fw/applib/rockyjs/pbl_jerry_port.h
Normal file
20
src/fw/applib/rockyjs/pbl_jerry_port.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void rocky_runtime_context_init(void);
|
||||
void rocky_runtime_context_deinit(void);
|
158
src/fw/applib/rockyjs/rocky.c
Normal file
158
src/fw/applib/rockyjs/rocky.c
Normal file
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* 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 "rocky.h"
|
||||
|
||||
#include "jmem-heap.h"
|
||||
|
||||
#include "applib/rockyjs/api/rocky_api.h"
|
||||
#include "applib/rockyjs/api/rocky_api_util.h"
|
||||
#include "applib/rockyjs/pbl_jerry_port.h"
|
||||
#include "applib/app.h"
|
||||
#include "applib/applib_resource_private.h"
|
||||
#include "applib/app_heap_analytics.h"
|
||||
#include "applib/app_heap_util.h"
|
||||
#include "kernel/pbl_malloc.h"
|
||||
#include "process_management/app_manager.h"
|
||||
#include "system/passert.h"
|
||||
#include "syscall/syscall.h"
|
||||
#include "syscall/syscall_internal.h"
|
||||
|
||||
#include "code_space_reservation.h"
|
||||
|
||||
const RockySnapshotHeader ROCKY_EXPECTED_SNAPSHOT_HEADER = {
|
||||
.signature = {'P', 'J', 'S', 0}, // C-string terminator in case somebody treats this as source
|
||||
#if CAPABILITY_HAS_JAVASCRIPT
|
||||
.version = (uint8_t)CAPABILITY_JAVASCRIPT_BYTECODE_VERSION,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void prv_rocky_init(void) {
|
||||
rocky_runtime_context_init();
|
||||
jerry_init(JERRY_INIT_EMPTY);
|
||||
rocky_api_watchface_init();
|
||||
}
|
||||
|
||||
bool rocky_is_snapshot(const uint8_t *buffer, size_t buffer_size) {
|
||||
#if CAPABILITY_HAS_JAVASCRIPT
|
||||
const size_t header_length = sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
|
||||
if (buffer_size < header_length ||
|
||||
memcmp(ROCKY_EXPECTED_SNAPSHOT_HEADER.signature,
|
||||
buffer,
|
||||
sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER.signature)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t actual_version = buffer[offsetof(RockySnapshotHeader, version)];
|
||||
const uint8_t expected_version = ROCKY_EXPECTED_SNAPSHOT_HEADER.version;
|
||||
if (expected_version != actual_version) {
|
||||
PBL_LOG(LOG_LEVEL_WARNING, "incompatible JS snapshot version %"PRIu8" (expected: %"PRIu8")",
|
||||
actual_version, expected_version);
|
||||
return false;
|
||||
}
|
||||
|
||||
return jerry_is_snapshot(buffer + header_length, buffer_size - header_length);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool prv_rocky_eval_buffer(const uint8_t *buffer, size_t buffer_size) {
|
||||
jerry_value_t rv;
|
||||
if (rocky_is_snapshot(buffer, buffer_size)) {
|
||||
buffer += sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
|
||||
buffer_size -= sizeof(ROCKY_EXPECTED_SNAPSHOT_HEADER);
|
||||
PBL_ASSERTN((uintptr_t)buffer % 8 == 0);
|
||||
rv = jerry_exec_snapshot(buffer, buffer_size, false);
|
||||
} else {
|
||||
PBL_LOG(LOG_LEVEL_INFO, "Not a snapshot, interpreting buffer as JS source code");
|
||||
rv = jerry_eval((jerry_char_t *) buffer, buffer_size, false);
|
||||
}
|
||||
|
||||
bool error_occurred = jerry_value_has_error_flag(rv);
|
||||
if (error_occurred) {
|
||||
jerry_value_clear_error_flag(&rv);
|
||||
rocky_log_exception("Evaluating JS", rv);
|
||||
}
|
||||
|
||||
jerry_release_value(rv);
|
||||
return !(error_occurred);
|
||||
}
|
||||
|
||||
static void prv_rocky_deinit(void) {
|
||||
app_heap_analytics_log_stats_to_app_heartbeat(true /* is_rocky_app */);
|
||||
rocky_api_deinit();
|
||||
jerry_cleanup();
|
||||
rocky_runtime_context_deinit();
|
||||
}
|
||||
|
||||
bool rocky_event_loop_with_string_or_snapshot(const void *buffer, size_t buffer_size) {
|
||||
#if CAPABILITY_HAS_JAVASCRIPT
|
||||
prv_rocky_init();
|
||||
const bool result = prv_rocky_eval_buffer(buffer, buffer_size);
|
||||
if (result) {
|
||||
app_event_loop_common();
|
||||
}
|
||||
prv_rocky_deinit();
|
||||
|
||||
return result;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool prv_rocky_event_loop_with_resource(ResAppNum app_num, uint32_t resource_id) {
|
||||
#if CAPABILITY_HAS_JAVASCRIPT
|
||||
if (!sys_get_current_app_is_rocky_app()) {
|
||||
APP_LOG(APP_LOG_LEVEL_ERROR, "Cannot execute JavaScript, insufficient meta data.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rv = false;
|
||||
size_t sz = sys_resource_size(app_num, resource_id);
|
||||
char *script = applib_resource_mmap_or_load(app_num,
|
||||
resource_id,
|
||||
0, sz, true);
|
||||
if (script) {
|
||||
// TODO: PBL-40010 clean this up
|
||||
// hotfix: we're either dealing with mmap, which is 8 byte aligned already
|
||||
// or malloc`ed buffer which has 7 additional bytes at the end.
|
||||
// We're are moving over the bytes so that they are 8-byte aligned
|
||||
// and pass that pointer to rocky instead
|
||||
char *aligned_script = (char *)((uintptr_t)(script + 7) & ~7);
|
||||
if (aligned_script != script) {
|
||||
// don't write if it's aligned, to avoid writing to mmapped data
|
||||
memmove(aligned_script, script, sz);
|
||||
}
|
||||
|
||||
rv = rocky_event_loop_with_string_or_snapshot(aligned_script, sz);
|
||||
applib_resource_munmap_or_free(script);
|
||||
}
|
||||
|
||||
return rv;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool rocky_event_loop_with_system_resource(uint32_t resource_id) {
|
||||
return prv_rocky_event_loop_with_resource(SYSTEM_APP, resource_id);
|
||||
}
|
||||
|
||||
bool rocky_event_loop_with_resource(uint32_t resource_id) {
|
||||
return prv_rocky_event_loop_with_resource(sys_get_current_resource_num(),
|
||||
resource_id);
|
||||
}
|
48
src/fw/applib/rockyjs/rocky.h
Normal file
48
src/fw/applib/rockyjs/rocky.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jerry-api.h"
|
||||
#include "resource/resource.h"
|
||||
|
||||
typedef union {
|
||||
uint8_t data[8];
|
||||
struct {
|
||||
char signature[4];
|
||||
uint8_t version;
|
||||
uint8_t padding[3];
|
||||
};
|
||||
} RockySnapshotHeader;
|
||||
|
||||
#ifndef __clang__
|
||||
_Static_assert(sizeof(RockySnapshotHeader) == 8, "RockyJS snapshot header size");
|
||||
#endif
|
||||
|
||||
extern const RockySnapshotHeader ROCKY_EXPECTED_SNAPSHOT_HEADER;
|
||||
|
||||
bool rocky_add_global_function(char *name, jerry_external_handler_t handler);
|
||||
bool rocky_add_function(jerry_value_t parent, char *name, jerry_external_handler_t handler);
|
||||
jerry_value_t rocky_get_rocky_namespace(void);
|
||||
jerry_value_t rocky_get_rocky_singleton(void);
|
||||
|
||||
bool rocky_event_loop_with_resource(uint32_t resource_id);
|
||||
|
||||
bool rocky_event_loop_with_system_resource(uint32_t resource_id);
|
||||
|
||||
bool rocky_event_loop_with_string_or_snapshot(const void *buffer, size_t buffer_size);
|
||||
|
||||
bool rocky_is_snapshot(const uint8_t *buffer, size_t buffer_size);
|
58
src/fw/applib/rockyjs/rocky_res.c
Normal file
58
src/fw/applib/rockyjs/rocky_res.c
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 "rocky_res.h"
|
||||
|
||||
#include "process_management/process_manager.h"
|
||||
#include "resource/resource_storage.h"
|
||||
#include "rocky.h"
|
||||
|
||||
bool rocky_app_has_compatible_bytecode_res(ResAppNum app_num) {
|
||||
// we will iterate over each resource to detect any compatible JS byte code
|
||||
// if there's any, we can assume that there's also a resource with ID 1.
|
||||
const uint32_t entries = resource_storage_get_num_entries(app_num, 1);
|
||||
for (uint32_t entry_id = 1; entry_id <= entries; entry_id++) {
|
||||
// this buffer needs to be large enough to carry
|
||||
// RockySnapshotHeader + the relevant data JerryScript verifies
|
||||
uint8_t snapshot_start[sizeof(RockySnapshotHeader) + sizeof(uint64_t)];
|
||||
const size_t bytes_read = resource_load_byte_range_system(app_num, entry_id, 0,
|
||||
snapshot_start,
|
||||
sizeof(snapshot_start));
|
||||
if (rocky_is_snapshot(snapshot_start, bytes_read)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
RockyResourceValidation rocky_app_validate_resources(const PebbleProcessMd *md) {
|
||||
if (!md || !md->is_rocky_app) {
|
||||
// it's not a rocky app, so it cannot have incompatible byte code
|
||||
return RockyResourceValidation_NotRocky;
|
||||
}
|
||||
const ResAppNum app_num = (ResAppNum)process_metadata_get_res_bank_num(md);
|
||||
if (app_num == (ResAppNum)SYSTEM_APP_BANK_ID) {
|
||||
// in case of the FW, we can assume that our JS is valid
|
||||
return RockyResourceValidation_Valid;
|
||||
}
|
||||
|
||||
if (rocky_app_has_compatible_bytecode_res(app_num)) {
|
||||
return RockyResourceValidation_Valid;
|
||||
} else {
|
||||
return RockyResourceValidation_Invalid;
|
||||
}
|
||||
}
|
32
src/fw/applib/rockyjs/rocky_res.h
Normal file
32
src/fw/applib/rockyjs/rocky_res.h
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "process_management/pebble_process_md.h"
|
||||
#include "resource/resource.h"
|
||||
|
||||
// True, if there's any resource with a compatible snapshot
|
||||
bool rocky_app_has_compatible_bytecode_res(ResAppNum app_num);
|
||||
|
||||
typedef enum {
|
||||
RockyResourceValidation_NotRocky,
|
||||
RockyResourceValidation_Invalid,
|
||||
RockyResourceValidation_Valid,
|
||||
} RockyResourceValidation;
|
||||
|
||||
// True if md describes rocky app and resources contain invalid bytecode
|
||||
RockyResourceValidation rocky_app_validate_resources(const PebbleProcessMd *md);
|
Loading…
Add table
Add a link
Reference in a new issue