Import of the watch repository from Pebble

This commit is contained in:
Matthieu Jeanson 2024-12-12 16:43:03 -08:00 committed by Katharine Berry
commit 3b92768480
10334 changed files with 2564465 additions and 0 deletions

View file

@ -0,0 +1,72 @@
/*
* 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 "emscripten_app.h"
#include "emscripten_graphics.h"
#include "emscripten_resources.h"
#include "emscripten_tick_timer_service.h"
#include "applib/app.h"
#include "applib/ui/app_window_stack.h"
#include "applib/rockyjs/api/rocky_api_graphics.h"
#include <emscripten/emscripten.h>
__attribute__((weak)) int app_main(void) {
Window *window = window_create();
app_window_stack_push(window, false);
app_event_loop();
return 0;
}
static void prv_event_loop(void) {
Window *window = app_window_stack_get_top_window();
if (window && window->is_render_scheduled) {
GContext *ctx = rocky_api_graphics_get_gcontext();
layer_render_tree(&window->layer, ctx);
window->is_render_scheduled = false;
EM_ASM(
// Implemented by html-binding.js:
if (Module.frameBufferMarkDirty) {
Module.frameBufferMarkDirty()
}
);
}
}
void emx_app_init(void) {
emx_graphics_init();
emx_resources_init();
emx_tick_timer_service_init();
}
void emx_app_deinit(void) {
emx_resources_deinit();
}
void emx_app_event_loop(void) {
emscripten_set_main_loop(prv_event_loop,
0, /* using window.requestAnimationFrame() */
1 /* Simulate infinite loop */);
}
int main(int argc, char **argv) {
emx_app_init();
app_main();
emx_app_deinit();
return 0;
}

View 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
void emx_app_init(void);
void emx_app_event_loop(void);

View file

@ -0,0 +1,27 @@
/*
* 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 "services/common/clock.h"
static bool s_clock_is_24h_style;
bool clock_is_24h_style(void) {
return s_clock_is_24h_style;
}
void clock_set_24h_style(bool is_24h_style) {
s_clock_is_24h_style = is_24h_style;
}

View file

@ -0,0 +1,118 @@
/*
* 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 <stdio.h>
#include <string.h>
#include "applib/fonts/fonts.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/framebuffer.h"
#include "applib/graphics/gdraw_command_image.h"
#include "applib/graphics/gdraw_command_sequence.h"
#include "applib/graphics/gdraw_command_frame.h"
#include "applib/graphics/gbitmap_sequence.h"
#include "applib/graphics/text.h"
#include "applib/rockyjs/api/rocky_api_graphics.h"
#include "applib/rockyjs/api/rocky_api_util.h"
#include "applib/rockyjs/api/rocky_api_errors.h"
#include "jerry-api.h"
#include "process_state/app_state/app_state.h"
#include <emscripten/emscripten.h>
static GContext s_gcontext = {};
// FIXME: PBL-43469 Support for changing platforms will require a dynamic framebuffer.
static FrameBuffer s_framebuffer = {};
static TextRenderState s_text_render_state = {};
static UnobstructedAreaState s_unobstructed_area_state = {};
// FIXME: Right now, rocky only supports 1 window anyways
static Window *s_top_window;
GContext *emx_graphics_get_gcontext(void) {
return &s_gcontext;
}
void *emx_graphics_get_pixels(void) {
return s_gcontext.dest_bitmap.addr;
}
TextRenderState *app_state_get_text_render_state(void) {
return &s_text_render_state;
}
Layer** app_state_get_layer_tree_stack(void) {
static Layer *layer_tree_stack[LAYER_TREE_STACK_SIZE];
return layer_tree_stack;
}
Layer** kernel_applib_get_layer_tree_stack(void) {
PBL_ASSERT(0, "Not expected to be called when compiling to applib-emscripten...");
return NULL;
}
// FIXME: Emscripten is cannot deal with two files with the same name
// (even if the path is different) The framebuffer.c files end up not
// getting linked in. A longer term fix would be to rename the object
// file in WAF
volatile const int FrameBuffer_MaxX = DISP_COLS;
volatile const int FrameBuffer_MaxY = DISP_ROWS;
void framebuffer_mark_dirty_rect(FrameBuffer* f, GRect rect) {
}
size_t framebuffer_get_size_bytes(FrameBuffer *f) {
return FRAMEBUFFER_SIZE_BYTES;
}
Window *app_window_stack_get_top_window(void) {
return s_top_window;
}
void app_window_stack_push(Window *window, bool animated) {
PBL_ASSERT(!s_top_window, "Already have a window");
s_top_window = window;
}
GContext *graphics_context_get_current_context(void) {
return &s_gcontext;
}
// TODO: PBL-43467 Support a user-specified unobstructed area
UnobstructedAreaState *app_state_get_unobstructed_area_state(void) {
return &s_unobstructed_area_state;
}
void unobstructed_area_service_get_area(UnobstructedAreaState *state, GRect *area_out) {
*area_out = state->area;
}
// FIXME: PBL-43496 This should take width, height, and format to dynamically
// allocate our framebuffer GBitmap and support changing platforms.
void emx_graphics_init(void) {
framebuffer_init(&s_framebuffer, &(GSize) {DISP_COLS, DISP_ROWS});
memset(s_framebuffer.buffer, 0xff, FRAMEBUFFER_SIZE_BYTES);
framebuffer_dirty_all(&s_framebuffer);
graphics_context_init(&s_gcontext, &s_framebuffer, GContextInitializationMode_App);
s_unobstructed_area_state = (UnobstructedAreaState) {
.area = {
.origin = { .x = 0, .y = 0 },
.size = { .w = 144, .h = 168 },
},
};
}

View 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 "applib/graphics/gtypes.h"
#include "process_state/app_state/app_state.h"
void emx_graphics_init(void);
GContext *emx_graphics_get_gcontext(void);
void *emx_graphics_get_pixes(void);
TextRenderState *app_state_get_text_render_state(void);
void emx_graphics_call_canvas_update_proc(void);

View file

@ -0,0 +1,720 @@
/*
* 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 <emscripten/emscripten.h>
#include <string.h>
#define TYPE_ERROR \
jerry_create_error(JERRY_ERROR_TYPE, NULL);
#define TYPE_ERROR_ARG \
jerry_create_error(JERRY_ERROR_TYPE, (const jerry_char_t *)"wrong type of argument");
#define TYPE_ERROR_FLAG \
jerry_create_error(JERRY_ERROR_TYPE, (const jerry_char_t *)"argument cannot have an error flag");
////////////////////////////////////////////////////////////////////////////////
// Parser and Executor Function
////////////////////////////////////////////////////////////////////////////////
// Note that `is_strict` is currently unsupported by emscripten
jerry_value_t jerry_eval(const jerry_char_t *source_p, size_t source_size, bool is_strict) {
return (jerry_value_t)EM_ASM_INT({
// jerry_eval() uses an indirect eval() call,
// so the global execution context is used.
// Also see ECMA 5.1 -- 10.4.2 Entering Eval Code.
var indirectEval = eval;
try {
return __jerryRefs.ref(indirectEval(Module.Pointer_stringify($0, $1)));
} catch (e) {
var error_ref = __jerryRefs.ref(e);
__jerryRefs.setError(error_ref, true);
return error_ref;
}
}, source_p, source_size);
}
jerry_value_t jerry_acquire_value(jerry_value_t value) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.acquire($0);
}, value);
}
void jerry_release_value(jerry_value_t value) {
EM_ASM_INT({
__jerryRefs.release($0);
}, value);
}
////////////////////////////////////////////////////////////////////////////////
// Get the global context
////////////////////////////////////////////////////////////////////////////////
jerry_value_t jerry_get_global_object(void) {
return ((jerry_value_t)EM_ASM_INT_V({ \
return __jerryRefs.ref(Function('return this;')()); \
}));
}
jerry_value_t jerry_get_global_builtin(const jerry_char_t *builtin_name) {
return ((jerry_value_t)EM_ASM_INT({ \
return __jerryRefs.ref(Function('return this;')()[Module.Pointer_stringify($0)]); \
}, builtin_name));
}
////////////////////////////////////////////////////////////////////////////////
// Jerry Value Type Checking
////////////////////////////////////////////////////////////////////////////////
#define JERRY_VALUE_HAS_TYPE(ref, typename) \
((bool)EM_ASM_INT({ \
return typeof __jerryRefs.get($0) === (typename); \
}, (ref)))
#define JERRY_VALUE_IS_INSTANCE(ref, type) \
((bool)EM_ASM_INT({ \
return __jerryRefs.get($0) instanceof (type); \
}, (ref)))
bool jerry_value_is_array(const jerry_value_t value) {
return JERRY_VALUE_IS_INSTANCE(value, Array);
}
bool jerry_value_is_boolean(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'boolean');
}
bool jerry_value_is_constructor(const jerry_value_t value) {
return jerry_value_is_function(value);
}
bool jerry_value_is_function(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'function');
}
bool jerry_value_is_number(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'number');
}
bool jerry_value_is_null(const jerry_value_t value) {
return ((bool)EM_ASM_INT({
return __jerryRefs.get($0) === null;
}, value));
}
bool jerry_value_is_object(const jerry_value_t value) {
return !jerry_value_is_null(value) &&
(JERRY_VALUE_HAS_TYPE(value, 'object') || jerry_value_is_function(value));
}
bool jerry_value_is_string(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'string');
}
bool jerry_value_is_undefined(const jerry_value_t value) {
return JERRY_VALUE_HAS_TYPE(value, 'undefined');
}
////////////////////////////////////////////////////////////////////////////////
// Jerry Value Getter Functions
////////////////////////////////////////////////////////////////////////////////
bool jerry_get_boolean_value(const jerry_value_t value) {
if (!jerry_value_is_boolean(value)) {
return false;
}
return (bool)EM_ASM_INT({
return (__jerryRefs.get($0) === true);
}, value);
}
double jerry_get_number_value(const jerry_value_t value) {
if (!jerry_value_is_number(value)) {
return 0.0;
}
return EM_ASM_DOUBLE({
return __jerryRefs.get($0);
}, value);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for UTF-8 encoded string values
////////////////////////////////////////////////////////////////////////////////
jerry_size_t jerry_get_utf8_string_size(const jerry_value_t value) {
if (!jerry_value_is_string(value)) {
return 0;
}
return (jerry_size_t)EM_ASM_INT({
return Module.lengthBytesUTF8(__jerryRefs.get($0));
}, value);
}
jerry_size_t jerry_string_to_utf8_char_buffer(const jerry_value_t value,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
const jerry_size_t str_size = jerry_get_utf8_string_size(value);
if (str_size == 0 || buffer_size < str_size || buffer_p == NULL) {
return 0;
}
EM_ASM_INT({
var str = __jerryRefs.get($0);
// Add one onto the buffer size, since Module.stringToUTF8 adds a null
// character at the end. This will lead to truncation if we just use
// buffer_size. Since the actual jerry-api does not do this, we are
// always careful to allocate space for a null character at the end.
// Allow stringToUTF8 to write that extra null beyond the passed in
// buffer_length.
Module.stringToUTF8(str, $1, $2 + 1);
}, value, buffer_p, buffer_size);
return strlen((const char *)buffer_p);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for array object values
////////////////////////////////////////////////////////////////////////////////
uint32_t jerry_get_array_length(const jerry_value_t value) {
if (!jerry_value_is_array(value)) {
return 0;
}
return (uint32_t)EM_ASM_INT({
return __jerryRefs.get($0).length;
}, value);
}
////////////////////////////////////////////////////////////////////////////////
// Jerry Value Creation API
////////////////////////////////////////////////////////////////////////////////
#define JERRY_CREATE_VALUE(value) \
((jerry_value_t)EM_ASM_INT_V({ \
return __jerryRefs.ref((value)); \
}))
jerry_value_t jerry_create_array(uint32_t size) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(new Array($0));
}, size);
}
jerry_value_t jerry_create_boolean(bool value) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Boolean($0));
}, value);
}
jerry_value_t jerry_create_error(jerry_error_t error_type, const jerry_char_t *message_p) {
return jerry_create_error_sz(error_type, message_p, strlen((const char *)message_p));
}
#define JERRY_ERROR(type, msg, sz) (jerry_value_t)(EM_ASM_INT({ \
return __jerryRefs.ref(new (type)(Module.Pointer_stringify($0, $1))) \
}, (msg), (sz)))
jerry_value_t jerry_create_error_sz(jerry_error_t error_type,
const jerry_char_t *message_p,
jerry_size_t message_size) {
jerry_value_t error_ref = 0;
switch (error_type) {
case JERRY_ERROR_COMMON:
error_ref = JERRY_ERROR(Error, message_p, message_size);
break;
case JERRY_ERROR_EVAL:
error_ref = JERRY_ERROR(EvalError, message_p, message_size);
break;
case JERRY_ERROR_RANGE:
error_ref = JERRY_ERROR(RangeError, message_p, message_size);
break;
case JERRY_ERROR_REFERENCE:
error_ref = JERRY_ERROR(ReferenceError, message_p, message_size);
break;
case JERRY_ERROR_SYNTAX:
error_ref = JERRY_ERROR(SyntaxError, message_p, message_size);
break;
case JERRY_ERROR_TYPE:
error_ref = JERRY_ERROR(TypeError, message_p, message_size);
break;
case JERRY_ERROR_URI:
error_ref = JERRY_ERROR(URIError, message_p, message_size);
break;
default:
EM_ASM_INT({
abort('Cannot create error type: ' + $0);
}, error_type);
break;
}
jerry_value_set_error_flag(&error_ref);
return error_ref;
}
jerry_value_t jerry_create_external_function(jerry_external_handler_t handler_p) {
return (jerry_value_t)EM_ASM_INT({
return __jerry_create_external_function($0);
}, handler_p);
}
jerry_value_t jerry_create_number(double value) {
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref($0);
}, value);
}
jerry_value_t jerry_create_number_infinity(bool negative) {
if (negative) {
return JERRY_CREATE_VALUE(-Infinity);
} else {
return JERRY_CREATE_VALUE(Infinity);
}
}
jerry_value_t jerry_create_number_nan(void) {
return JERRY_CREATE_VALUE(NaN);
}
jerry_value_t jerry_create_null(void) {
return JERRY_CREATE_VALUE(null);
}
jerry_value_t jerry_create_object(void) {
return JERRY_CREATE_VALUE(new Object());
}
jerry_value_t jerry_create_string(const jerry_char_t *str_p) {
if (!str_p) {
return jerry_create_undefined();
}
return jerry_create_string_utf8_sz(str_p, strlen((const char *)str_p));
}
jerry_value_t jerry_create_string_sz(const jerry_char_t *str_p, jerry_size_t str_size) {
return jerry_create_string_utf8_sz(str_p, str_size);
}
jerry_value_t jerry_create_string_utf8(const jerry_char_t *str_p) {
if (!str_p) {
return jerry_create_undefined();
}
return jerry_create_string_utf8_sz(str_p, strlen((const char *)str_p));
}
jerry_value_t jerry_create_string_utf8_sz(const jerry_char_t *str_p, jerry_size_t str_size) {
if (!str_p) {
return jerry_create_undefined();
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Module.Pointer_stringify($0, $1));
}, str_p, str_size);
}
jerry_value_t jerry_create_undefined(void) {
return JERRY_CREATE_VALUE(undefined);
}
////////////////////////////////////////////////////////////////////////////////
// General API Functions of JS Objects
////////////////////////////////////////////////////////////////////////////////
bool jerry_has_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return false;
}
return (bool)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
return (name in obj);
}, obj_val, prop_name_val);
}
bool jerry_has_own_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return false;
}
return (bool)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
return obj.hasOwnProperty(name);
}, obj_val, prop_name_val);
}
bool jerry_delete_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return false;
}
return (bool)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
try {
return delete obj[name];
} catch (e) {
// In strict mode, delete throws SyntaxError if the property is an
// own non-configurable property.
return false;
}
return true;
}, obj_val, prop_name_val);
}
jerry_value_t jerry_get_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
return __jerryRefs.ref(obj[name]);
}, obj_val, prop_name_val);
}
jerry_value_t jerry_get_property_by_index(const jerry_value_t obj_val, uint32_t index) {
if (!jerry_value_is_object(obj_val)) {
return TYPE_ERROR;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
return __jerryRefs.ref(obj[$1]);
}, obj_val, index);
}
jerry_value_t jerry_set_property(const jerry_value_t obj_val,
const jerry_value_t prop_name_val,
const jerry_value_t value_to_set) {
if (jerry_value_has_error_flag(value_to_set) ||
!jerry_value_is_object(obj_val) ||
!jerry_value_is_string(prop_name_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var name = __jerryRefs.get($1);
var to_set = __jerryRefs.get($2);
obj[name] = to_set;
return __jerryRefs.ref(true);
}, obj_val, prop_name_val, value_to_set);
}
jerry_value_t jerry_set_property_by_index(const jerry_value_t obj_val,
uint32_t index,
const jerry_value_t value_to_set) {
if (jerry_value_has_error_flag(value_to_set) ||
!jerry_value_is_object(obj_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var obj = __jerryRefs.get($0);
var to_set = __jerryRefs.get($2);
obj[$1] = to_set;
return __jerryRefs.ref(true);
}, obj_val, index, value_to_set);
}
void jerry_init_property_descriptor_fields(jerry_property_descriptor_t *prop_desc_p) {
*prop_desc_p = (jerry_property_descriptor_t) {
.value = jerry_create_undefined(),
.getter = jerry_create_undefined(),
.setter = jerry_create_undefined(),
};
}
jerry_value_t jerry_define_own_property(const jerry_value_t obj_val,
const jerry_value_t prop_name_val,
const jerry_property_descriptor_t *pdp) {
if (!jerry_value_is_object(obj_val) && !jerry_value_is_string(obj_val)) {
return TYPE_ERROR_ARG;
}
if ((pdp->is_writable_defined || pdp->is_value_defined)
&& (pdp->is_get_defined || pdp->is_set_defined)) {
return TYPE_ERROR_ARG;
}
if (pdp->is_get_defined && !jerry_value_is_function(pdp->getter)) {
return TYPE_ERROR_ARG;
}
if (pdp->is_set_defined && !jerry_value_is_function(pdp->setter)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)(EM_ASM_INT({
var obj = __jerryRefs.get($12 /* obj_val */);
var name = __jerryRefs.get($13 /* prop_name_val */);
var desc = {};
if ($0 /* is_value_defined */) {
desc.value = __jerryRefs.get($9);
}
if ($1 /* is_get_defined */) {
desc.get = __jerryRefs.get($10);
}
if ($2 /* is_set_defined */) {
desc.set = __jerryRefs.get($11);
}
if ($3 /* is_writable_defined */) {
desc.writable = Boolean($4 /* is_writable */);
}
if ($5 /* is_enumerable_defined */) {
desc.enumerable = Boolean($6 /* is_enumerable */);
}
if ($7 /* is_configurable */) {
desc.configurable = Boolean($8 /* is_configurable */);
}
Object.defineProperty(obj, name, desc);
return __jerryRefs.ref(Boolean(true));
}, pdp->is_value_defined, /* $0 */
pdp->is_get_defined, /* $1 */
pdp->is_set_defined, /* $2 */
pdp->is_writable_defined, /* $3 */
pdp->is_writable, /* $4 */
pdp->is_enumerable_defined, /* $5 */
pdp->is_enumerable, /* $6 */
pdp->is_configurable_defined, /* $7 */
pdp->is_configurable, /* $8 */
pdp->value, /* $9 */
pdp->getter, /* $10 */
pdp->setter, /* $11 */
obj_val, /* $12 */
prop_name_val /* $13 */
));
}
jerry_value_t emscripten_call_jerry_function(jerry_external_handler_t func_obj_p,
const jerry_value_t func_obj_val,
const jerry_value_t this_val,
const jerry_value_t args_p[],
jerry_size_t args_count) {
return (func_obj_p)(func_obj_val, this_val, args_p, args_count);
}
jerry_value_t jerry_call_function(const jerry_value_t func_obj_val,
const jerry_value_t this_val,
const jerry_value_t args_p[],
jerry_size_t args_count) {
if (!jerry_value_is_function(func_obj_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var func_obj = __jerryRefs.get($0);
var this_val = __jerryRefs.get($1);
var args = [];
for (var i = 0; i < $3; ++i) {
args.push(__jerryRefs.get(getValue($2 + i*4, 'i32')));
}
try {
var rv = func_obj.apply(this_val, args);
} catch (e) {
var error_ref = __jerryRefs.ref(e);
__jerryRefs.setError(error_ref, true);
return error_ref;
}
return __jerryRefs.ref(rv);
}, func_obj_val, this_val, args_p, args_count);
}
jerry_value_t jerry_construct_object(const jerry_value_t func_obj_val,
const jerry_value_t args_p[],
jerry_size_t args_count) {
if (!jerry_value_is_constructor(func_obj_val)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
var func_obj = __jerryRefs.get($0);
var args = [];
for (var i = 0; i < $2; ++i) {
args.push(__jerryRefs.get(getValue($1 + i*4, 'i32')));
}
// Call the constructor with new object as `this`
var bindArgs = [null].concat(args);
var boundConstructor = func_obj.bind.apply(func_obj, bindArgs);
var rv = new boundConstructor();
return __jerryRefs.ref(rv);
}, func_obj_val, args_p, args_count);
}
jerry_size_t jerry_string_to_char_buffer(const jerry_value_t value,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
return jerry_string_to_utf8_char_buffer(value, buffer_p, buffer_size);
}
jerry_size_t jerry_object_to_string_to_utf8_char_buffer(const jerry_value_t object,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
jerry_value_t str_ref = (jerry_value_t)EM_ASM_INT({
var str = __jerryRefs.ref(String(__jerryRefs.get($0)));
return str;
}, object);
jerry_size_t len = jerry_string_to_utf8_char_buffer(str_ref, buffer_p, buffer_size);
jerry_release_value(str_ref);
return len;
}
// FIXME: PBL-43551 Propery CESU-8 => UTF-8 conversion.
jerry_size_t jerry_object_to_string_to_char_buffer(const jerry_value_t object,
jerry_char_t *buffer_p,
jerry_size_t buffer_size) {
return jerry_object_to_string_to_utf8_char_buffer(object,
buffer_p,
buffer_size);
}
jerry_value_t jerry_get_object_keys(const jerry_value_t value) {
if (!jerry_value_is_object(value)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Object.keys(__jerryRefs.get($0)));
}, value);
}
jerry_value_t jerry_get_prototype(const jerry_value_t value) {
if (!jerry_value_is_object(value)) {
return TYPE_ERROR_ARG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(__jerryRefs.get($0).prototype);
}, value);
}
jerry_value_t jerry_set_prototype(const jerry_value_t obj_val, const jerry_value_t proto_obj_val) {
return 0; // FIXME: Not sure what to do here
}
bool jerry_get_object_native_handle(const jerry_value_t obj_val, uintptr_t *out_handle_p) {
return EM_ASM_INT({
var ptr = __jerryRefs.getNativeHandle($0);
if (ptr === undefined) {
return false;
}
Module.setValue($1, ptr, '*');
return true;
}, obj_val, out_handle_p);
}
void jerry_set_object_native_handle(const jerry_value_t obj_val, uintptr_t handle_p,
jerry_object_free_callback_t freecb_p) {
EM_ASM_INT({
__jerryRefs.setNativeHandle($0, $1, $2);
}, obj_val, handle_p, freecb_p);
}
void emscripten_call_jerry_object_free_callback(jerry_object_free_callback_t freecb_p,
uintptr_t handle_p) {
if (freecb_p) {
freecb_p(handle_p);
}
}
////////////////////////////////////////////////////////////////////////////////
// Error flag manipulation functions
////////////////////////////////////////////////////////////////////////////////
//
// The error flag is stored alongside the value in __jerryRefs.
// This allows for us to keep a valid value, like jerryscript does, and be able
// to add / remove a flag specifying whether there was an error or not.
bool jerry_value_has_error_flag(const jerry_value_t value) {
return (bool)(EM_ASM_INT({
return __jerryRefs.getError($0);
}, value));
}
void jerry_value_clear_error_flag(jerry_value_t *value_p) {
EM_ASM_INT({
return __jerryRefs.setError($0, false);
}, *value_p);
}
void jerry_value_set_error_flag(jerry_value_t *value_p) {
EM_ASM_INT({
return __jerryRefs.setError($0, true);
}, *value_p);
}
////////////////////////////////////////////////////////////////////////////////
// Converters of `jerry_value_t`
////////////////////////////////////////////////////////////////////////////////
bool jerry_value_to_boolean(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return false;
}
return (bool)EM_ASM_INT({
return Boolean(__jerryRefs.get($0));
}, value);
}
jerry_value_t jerry_value_to_number(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(Number(__jerryRefs.get($0)));
}, value);
}
jerry_value_t jerry_value_to_object(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(new Object(__jerryRefs.get($0)));
}, value);
}
jerry_value_t jerry_value_to_primitive(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
var val = __jerryRefs.get($0);
var rv;
if ((typeof val === 'object' && val != null)
|| (typeof val === 'function')) {
rv = val.valueOf(); // unbox
} else {
rv = val; // already a primitive
}
return __jerryRefs.ref(rv);
}, value);
}
jerry_value_t jerry_value_to_string(const jerry_value_t value) {
if (jerry_value_has_error_flag(value)) {
return TYPE_ERROR_FLAG;
}
return (jerry_value_t)EM_ASM_INT({
return __jerryRefs.ref(String(__jerryRefs.get($0)));
}, value);
}
int jerry_obj_refcount(jerry_value_t o) {
return EM_ASM_INT({
try {
return __jerryRefs.getRefCount($0);
} catch (e) {
return 0;
}
}, o);
}
////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
void jerry_init(jerry_init_flag_t flags) {
EM_ASM(__jerryRefs.reset());
}
void jerry_cleanup(void) {
}

View 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.
*/
void rocky_runtime_context_init(void) {
}
void rocky_runtime_context_deinit(void) {
}

View file

@ -0,0 +1,249 @@
/*
* 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 "emscripten_resources.h"
#include "resource/resource_storage_impl.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct {
uint32_t offset;
uint32_t length;
} Resource;
#define MANIFEST_SIZE (sizeof(ResourceManifest))
#define TABLE_ENTRY_SIZE (sizeof(ResTableEntry))
#define MAX_RESOURCES_FOR_SYSTEM_STORE 512
#define SYSTEM_STORE_METADATA_BYTES \
(MANIFEST_SIZE + MAX_RESOURCES_FOR_SYSTEM_STORE * TABLE_ENTRY_SIZE)
////////////////////////////////////////////////
// Custom Resources
//
// Custom resources in Rocky.js are implemented with a set of callbacks
// on the javascript side which implement resource APIs
//
// We store those callbacks in an array and return a resource ID which
// can then be used as if it was a valid resource
//
// Under the hood, we just lookup & call the initially provided callbacks
typedef struct {
uint32_t resource_id;
ResourceReadCb read;
ResourceGetSizeCb get_size;
} EmxCustomResource;
typedef struct {
EmxCustomResource *custom_resources;
uint32_t last_id;
int array_size;
int next_index;
} CustomResList;
static CustomResList s_custom_res_list = {0};
static int prv_custom_resource_get_index(uint32_t resource_id) {
int index;
for (index = 0; index < s_custom_res_list.next_index; ++index) {
if (s_custom_res_list.custom_resources[index].resource_id == resource_id) {
return index;
}
}
return -1;
}
static EmxCustomResource *prv_custom_resource_get(uint32_t resource_id) {
int index = prv_custom_resource_get_index(resource_id);
if (index < 0) {
return NULL;
} else {
return &s_custom_res_list.custom_resources[index];
}
}
static void prv_custom_resource_add(EmxCustomResource *res) {
if (s_custom_res_list.array_size <= s_custom_res_list.next_index) {
// grow the list
int new_size = (s_custom_res_list.array_size + 1) * 2;
s_custom_res_list.custom_resources = realloc(s_custom_res_list.custom_resources, new_size);
s_custom_res_list.array_size = new_size;
}
s_custom_res_list.custom_resources[s_custom_res_list.next_index] = *res;
++s_custom_res_list.next_index;
}
static void prv_custom_resource_remove(uint32_t resource_id) {
int index = prv_custom_resource_get_index(resource_id);
if (index < 0) {
return;
}
--s_custom_res_list.next_index;
if (s_custom_res_list.next_index == index) {
// we had the last resource, we're done
return;
}
memmove(&s_custom_res_list.custom_resources[index],
&s_custom_res_list.custom_resources[index + 1],
(s_custom_res_list.next_index - index) * sizeof(EmxCustomResource));
}
//////////////////////////////////////
// System Resources
//
// In this case, we shove the pbpack in an emscripten "file" (baked into the resulting JS
// and reimplement system resource APIs using standard C file I/O
static FILE *s_resource_file = NULL;
static uint32_t prv_read(uint32_t offset, void *data, size_t num_bytes) {
if (!s_resource_file || fseek(s_resource_file, offset, SEEK_SET)) {
printf("%s: Couldn't seek to %d\n", __FILE__, offset);
return 0;
}
return fread(data, 1, num_bytes, s_resource_file);
}
static ResourceManifest prv_get_manifest(void) {
ResourceManifest manifest = {};
prv_read(0, &manifest, sizeof(ResourceManifest));
return manifest;
}
static bool prv_get_table_entry(ResTableEntry *entry, uint32_t index) {
uint32_t addr = sizeof(ResourceManifest) + index * sizeof(ResTableEntry);
return prv_read(addr, entry, sizeof(ResTableEntry));
}
static bool prv_get_resource(uint32_t resource_id, Resource *res) {
*res = (Resource){
.length = 0,
.offset = 0,
};
ResourceManifest manifest = prv_get_manifest();
if (resource_id > manifest.num_resources) {
printf("%s: resource id %d > %d is out of range\n", __FILE__,
resource_id,
manifest.num_resources);
return false;
}
ResTableEntry entry;
if (!prv_get_table_entry(&entry, resource_id - 1)) {
printf("%s: Failed to read table entry for %d\n", __FILE__, resource_id);
return false;
}
if ((entry.resource_id != resource_id) ||
(entry.length == 0)) {
// empty resource
printf("%s: Invalid resourcel for %d\n", __FILE__, resource_id);
return false;
}
res->offset = SYSTEM_STORE_METADATA_BYTES + entry.offset;
res->length = entry.length;
return true;
}
///////////////////////////////
// API
size_t emx_resources_read(ResAppNum app_num,
uint32_t resource_id,
uint32_t offset,
uint8_t *buf,
size_t num_bytes) {
if (offset > INT_MAX || num_bytes > INT_MAX) {
return 0;
}
EmxCustomResource *custom_res = NULL;
if (app_num != SYSTEM_APP && (custom_res = prv_custom_resource_get(resource_id))) {
return custom_res->read(offset, buf, num_bytes);
}
Resource resource = {};
if (!prv_get_resource(resource_id, &resource)) {
return 0;
}
if (offset + num_bytes > resource.length) {
if (offset >= resource.length) {
// Can't recover from trying to read from beyond the resource. Read nothing.
printf("%s: Reading past the end of the resource!\n", __FILE__);
return 0;
}
num_bytes = resource.length - offset;
}
return prv_read(offset + resource.offset, buf, num_bytes);
}
size_t emx_resources_get_size(ResAppNum app_num, uint32_t resource_id) {
EmxCustomResource *custom_res = NULL;
if (app_num != SYSTEM_APP && (custom_res = prv_custom_resource_get(resource_id))) {
return custom_res->get_size();
}
Resource resource = {};
if (!prv_get_resource(resource_id, &resource)) {
return 0;
}
return resource.length;
}
bool emx_resources_init(void) {
s_resource_file = fopen("system_resources.pbpack", "r");
if (!s_resource_file) {
printf("Error: Failed to open resources file\n");
return false;
}
return true;
}
void emx_resources_deinit(void) {
fclose(s_resource_file);
}
uint32_t emx_resources_register_custom(ResourceReadCb read_cb, ResourceGetSizeCb get_size_cb) {
EmxCustomResource custom_res = {
.resource_id = ++s_custom_res_list.last_id,
.read = read_cb,
.get_size = get_size_cb,
};
prv_custom_resource_add(&custom_res);
return custom_res.resource_id;
}
void emx_resources_remove_custom(uint32_t resource_id) {
prv_custom_resource_remove(resource_id);
}

View file

@ -0,0 +1,38 @@
/*
* 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 <stdint.h>
#include <string.h>
#include "resource/resource.h"
// transformed to int to avoid surpises between C->JS
typedef int (*ResourceReadCb)(int offset, uint8_t *buf, int num_bytes);
typedef int (*ResourceGetSizeCb)(void);
bool emx_resources_init(void);
void emx_resources_deinit(void);
size_t emx_resources_get_size(ResAppNum app_num, uint32_t resource_id);
size_t emx_resources_read(ResAppNum app_num,
uint32_t resource_id,
uint32_t offset,
uint8_t *buf,
size_t num_bytes);
uint32_t emx_resources_register_custom(ResourceReadCb read_cb, ResourceGetSizeCb get_size_cb);
void emx_resources_remove_custom(uint32_t resource_id);

View file

@ -0,0 +1,108 @@
/*
* 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/app_logging.h"
#include "applib/tick_timer_service_private.h"
#include <emscripten/emscripten.h>
#include <stdio.h>
#include <time.h>
time_t time(time_t *time);
static TickTimerServiceState s_state;
static void prv_schedule_next_update(void);
static void prv_do_update(void *data) {
if (!s_state.handler) {
return;
}
// The data pointer value is used to pass a boolean value directly:
const bool is_update_due_to_time_change = (uintptr_t)data;
struct tm currtime;
time_t t = time(NULL);
localtime_r(&t, &currtime);
TimeUnits units_changed;
if (is_update_due_to_time_change) {
units_changed = (SECOND_UNIT | MINUTE_UNIT | HOUR_UNIT | DAY_UNIT | YEAR_UNIT);
} else {
prv_schedule_next_update();
units_changed = 0;
if (!s_state.first_tick) {
if (s_state.last_time.tm_sec != currtime.tm_sec) {
units_changed |= SECOND_UNIT;
}
if (s_state.last_time.tm_min != currtime.tm_min) {
units_changed |= MINUTE_UNIT;
}
if (s_state.last_time.tm_hour != currtime.tm_hour) {
units_changed |= HOUR_UNIT;
}
if (s_state.last_time.tm_mday != currtime.tm_mday) {
units_changed |= DAY_UNIT;
}
if (s_state.last_time.tm_mon != currtime.tm_mon) {
units_changed |= MONTH_UNIT;
}
if (s_state.last_time.tm_year != currtime.tm_year) {
units_changed |= YEAR_UNIT;
}
}
}
s_state.last_time = currtime;
s_state.first_tick = false;
if ((s_state.tick_units & units_changed) || (units_changed == 0)) {
s_state.handler(&currtime, units_changed);
}
}
static void prv_schedule_next_update(void) {
// Schedule this to fire again at the top of the next second
const int ms_into_s = EM_ASM_INT_V({
return Date.now().getMilliseconds;
});
const double wait_ms = 1000 - ms_into_s;
const bool is_update_due_to_time_change = false;
emscripten_async_call(prv_do_update, (void *)(uintptr_t)is_update_due_to_time_change, wait_ms);
}
void tick_timer_service_handle_time_change(void) {
const bool is_update_due_to_time_change = true;
prv_do_update((void *)(uintptr_t)is_update_due_to_time_change);
}
void tick_timer_service_subscribe(TimeUnits tick_units, TickHandler handler) {
const bool first = (s_state.handler == NULL);
s_state = (TickTimerServiceState) {
.handler = handler,
.tick_units = tick_units,
.first_tick = true,
};
if (first && handler != NULL) {
prv_schedule_next_update();
}
}
void emx_tick_timer_service_init(void) {
s_state = (TickTimerServiceState){};
}

View file

@ -0,0 +1,19 @@
/*
* 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 emx_tick_timer_service_init(void);

View file

@ -0,0 +1,62 @@
["_main",
"_emx_graphics_get_pixels",
"_graphics_context_set_stroke_width",
"_graphics_context_set_antialiased",
"_emx_resources_read",
"_emx_resources_get_size",
"_app_state_get_graphics_context",
"_gdraw_command_image_create_with_resource",
"_gdraw_command_image_get_command_list",
"_gdraw_command_list_get_command",
"_gdraw_command_list_get_num_commands",
"_gdraw_command_sequence_create_with_resource",
"_gdraw_command_sequence_get_frame_by_elapsed",
"_gdraw_command_sequence_get_total_duration",
"_gdraw_command_get_type",
"_gdraw_command_get_stroke_width",
"_gdraw_command_set_stroke_width",
"_gdraw_command_set_path_open",
"_gdraw_command_get_path_open",
"_gdraw_command_set_hidden",
"_gdraw_command_get_hidden",
"_gdraw_command_get_num_points",
"_gdraw_command_set_radius",
"_gdraw_command_get_radius",
"_gdraw_command_sequence_get_play_count",
"_gdraw_command_sequence_set_play_count",
"_gdraw_command_sequence_get_total_duration",
"_gdraw_command_sequence_get_num_frames",
"_gdraw_command_sequence_get_frame_by_elapsed",
"_gdraw_command_sequence_get_frame_by_index",
"_gdraw_command_frame_set_duration",
"_gdraw_command_frame_get_duration",
"_gdraw_command_frame_get_command_list",
"_fonts_get_system_font",
"_fonts_load_custom_font",
"_fonts_unload_custom_font",
"_gbitmap_create_with_data",
"_gbitmap_create_from_png_data",
"_gbitmap_destroy",
"_gbitmap_get_format",
"_graphics_context_set_compositing_mode",
"_emx_resources_register_custom",
"_emx_resources_remove_custom",
"_gpath_draw_filled",
"_gpath_draw_outline",
"_gpath_draw_outline_open",
"_graphics_draw_text",
"_gbitmap_sequence_create_with_resource",
"_gbitmap_sequence_destroy",
"_gbitmap_sequence_get_current_frame_delay_ms",
"_gbitmap_sequence_get_total_num_frames",
"_gbitmap_sequence_get_current_frame_idx",
"_gbitmap_sequence_get_play_count",
"_gbitmap_sequence_set_play_count",
"_rocky_api_watchface_init",
"_jerry_call_function",
"_emscripten_call_jerry_function",
"_emscripten_call_jerry_object_free_callback",
"_tick_timer_service_handle_time_change",
"_clock_set_24h_style"
]

View file

@ -0,0 +1,83 @@
/*
Copyright © 2015-2016 Pebble Technology Corp.,
All Rights Reserved. http://pebble.github.io/rockyjs/LICENSE
This describes functionality to bind the Rocky Simulator to an HTML canvas
element. This file is included into the Emscripten output using --post-js,
as such it will end up in body of the RockySimulator(options) constructor.
*/
if (typeof(Module) === 'undefined') {
var Module = {};
}
Module.bindCanvas = function(canvas) {
// in a future version, these values should adapt automatically
// also, we want the ability to create framebuffers of larger sizes
var canvasW = canvas.width;
var canvasH = canvas.height;
var framebufferW = 144;
var framebufferH = 168;
// scale gives us the ability to do a nearest-neighbor scaling
var scale = options.scale ||
Math.min(canvasW / framebufferW, canvasH / framebufferH);
// pixel access to read (framebuffer) and write to (canvas)
var canvasCtx = canvas.getContext('2d');
var canvasPixelData = canvasCtx.createImageData(canvasW, canvasH);
var canvasPixels = canvasPixelData.data;
var framebufferPixelPTR = Module.ccall(
'emx_graphics_get_pixels', 'number', []
);
var isRenderRequested = false;
var copyFrameBufferToCanvas = function(timestamp) {
console.log('copying pixels...');
isRenderRequested = false;
var framebufferPixels = new Uint8Array(Module.HEAPU8.buffer,
framebufferPixelPTR,
framebufferW * framebufferH);
// renders current state of the framebuffer to the bound canvas
// respecting the passed scale
for (var y = 0; y < canvasH; y++) {
var pebbleY = (y / scale) >> 0;
if (pebbleY >= framebufferH) {
break;
}
for (var x = 0; x < canvasW; x++) {
var pebbleX = (x / scale) >> 0;
if (pebbleX >= framebufferW) {
break;
}
var pebbleOffset = pebbleY * framebufferW + pebbleX;
var in_values = framebufferPixels[pebbleOffset];
var r = ((in_values >> 4) & 0x3) * 85;
var g = ((in_values >> 2) & 0x3) * 85;
var b = ((in_values >> 0) & 0x3) * 85;
var canvasOffset = (y * canvasW + x) * 4;
canvasPixels[canvasOffset + 0] = r;
canvasPixels[canvasOffset + 1] = g;
canvasPixels[canvasOffset + 2] = b;
canvasPixels[canvasOffset + 3] = 255;
}
}
canvasCtx.putImageData(canvasPixelData, 0, 0);
};
Module.frameBufferMarkDirty = function() {
if (isRenderRequested) {
return;
}
console.log('request render');
isRenderRequested = true;
window.requestAnimationFrame(copyFrameBufferToCanvas);
}
};
// Apply `options` from the RockySimulator(options) constructor:
if (typeof(options) !== 'undefined' && options.canvas) {
Module.bindCanvas(options.canvas);
}

View file

@ -0,0 +1,116 @@
/**
* 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.
*/
@import url(https://fonts.googleapis.com/css?family=Bowlby+One+SC|Roboto:400,700,400italic);
body {
font: 16px/1.5 'Roboto', sans-serif;
}
body {
padding-bottom: 2em;
}
body > .container:first-child > .row:first-child > div > p:first-child {
display: none;
}
h1 {
font-size: 3em;
}
h1 + blockquote {
border: 0 none;
max-width: 600px;
text-align: center;
margin: 0 auto 3em;
padding: 0;
}
h1, h2 {
font-family: "Bowlby One SC";
overflow: hidden;
text-align: center;
margin: 2em 0 1em;
text-transform: uppercase;
}
h1:before, h1:after, h2:before, h2:after {
background-color: #ddd;
content: "";
display: inline-block;
height: 1px;
position: relative;
vertical-align: middle;
width: 50%;
}
h1:before, h2:before {
right: 0.5em;
margin-left: -50%;
}
h1:after, h2:after {
left: 0.5em;
margin-right: -50%;
}
h3 {
margin: 1em 0 0.5em;
}
form {
width: 100%;
}
label {
width: 100%;
}
#githubForkLink {
background: url(../img/forkBanner.png) no-repeat top right;
position: absolute;
display: block;
width: 149px;
height: 149px;
top: 0;
right: 0;
/* Hide the text. */
text-indent: 100%;
white-space: nowrap;
overflow: hidden;
}
table.table-compatibility>tbody>tr.standard {
background-color: #d9edf7;
}
table.table-compatibility>tbody>tr.implemented {
background-color: #dff0db;
}
table.table-compatibility>tbody>tr.partial {
background-color: #ffffff;
}
table.table-compatibility>tbody>tr.planned {
background-color: #fcf8e3;
}
table.table-compatibility>tbody>tr.not-planned {
background-color: #f2dede
}

View file

@ -0,0 +1,94 @@
<!DOCTYPE html>
<!--
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.
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Simple Rocky Simulator</title>
<link rel="stylesheet" href="css/bootstrap.css"/>
<link rel="stylesheet" href="css/style.css"/>
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script type="text/javascript" src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script type="text/javascript" src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<script type="text/javascript" src="rocky.js"></script>
</head>
<body>
<div class="container">
<div class="page-header">
<h1>[Simple Rocky Simulator]</h1>
</div>
<div class="col-md-6">
<canvas id="pebble" class="rocky" width="432" height="504"></canvas>
</div>
<div class="col-md-6">
<h3>Controls</h3>
<p><form id="controls-form">
<label for="timestamp-input">Set Time (Epoch timestamp)</label>
<input id="timestamp-input" type="text" />
<label for="timezone-offset-input">Timezone Offset (<span id="timezone-offset-span"></span>)</label>
<input id="timezone-offset-input" type="range" min="-720" max="840" step="15" value="0" />
<label>24h Style</label>
<input id="24h-style-input" type="checkbox" />
</form></p>
</div>
</div>
<script type="text/javascript">
// Create a new simulator and bind it to the canvas:
var rockySimulator = new RockySimulator({
canvas: document.getElementById("pebble")
});
// in the future, we will replace the singleton
// `rocky` as well as the namespace `Rocky`, e.g.
// `Rocky.tween` and `Rocky.WatchfaceHelper` with modules
var rocky = _rocky;
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
}
// Load app source from 'src' query param (URL / data URI):
var src = getQueryVariable('src');
// If 'src' is set to empty string, don't load anything (for testing):
if (src !== '') {
src = src || 'js/tictoc.js';
var script = document.createElement('script');
script.src = src;
document.getElementsByTagName('html')[0].appendChild(script);
}
</script>
<script type="text/javascript" src="js/controls.js"></script>
</body>
</html>

View file

@ -0,0 +1,62 @@
/**
* 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.
*/
window.addEventListener('load', function() {
var form = document.getElementById("controls-form");
form.addEventListener('submit', function(e) {
e.preventDefault();
});
var timestampInput = document.getElementById("timestamp-input");
timestampInput.addEventListener('change', function(e) {
rockySimulator.setTime(Number(e.target.value) * 1000);
});
var timezoneSlider = document.getElementById("timezone-offset-input");
var timezoneSpan = document.getElementById("timezone-offset-span");
var timezoneSliderOnChange = function() {
var offset = timezoneSlider.value;
rockySimulator.setTimezoneOffset(offset);
var sign;
if (offset > 0) {
sign = '+';
} else if (offset < 0) {
sign = '-';
} else {
sign = '';
}
var offsetMinutes = (offset % 60);
var absOffsetHours = Math.abs((offset - offsetMinutes) / 60);
var gmtText = 'GMT ' + sign + absOffsetHours;
if (offsetMinutes) {
var absOffsetMinutes = Math.abs(offsetMinutes);
gmtText += ':' + (absOffsetMinutes < 10 ? '0' : '') + absOffsetMinutes;
}
timezoneSpan.innerText = gmtText;
};
timezoneSlider.addEventListener('change', timezoneSliderOnChange);
timezoneSlider.addEventListener('input', timezoneSliderOnChange);
// After loading the page, set the slider to the local TZ:
var localTimezoneOffset = new Date().getTimezoneOffset();
timezoneSlider.value = localTimezoneOffset;
timezoneSliderOnChange();
var time24hStyle = document.getElementById("24h-style-input");
time24hStyle.addEventListener('change', function(e) {
rockySimulator.set24hStyle(e.target.checked);
});
});

View file

@ -0,0 +1,91 @@
/**
* 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.
*/
var WatchfaceHelper = function(date) {
function clockwiseRad(fraction) {
// TODO: figure out if this is actually correct orientation for Canvas APIs
return (1.5 - fraction) * 2 * Math.PI;
}
date = date || new Date();
var secondFraction = date.getSeconds() / 60;
var minuteFraction = (date.getMinutes()) / 60;
var hourFraction = (date.getHours() % 12 + minuteFraction) / 12;
this.secondAngle = clockwiseRad(secondFraction);
this.minuteAngle = clockwiseRad(minuteFraction);
this.hourAngle = clockwiseRad(hourFraction);
};
// book keeping so that we can easily animate the two hands for the watchface
// .scale/.angle are updated by tween/event handler (see below)
var renderState = {
minute: {style: 'white', scale: 0.80, angle: 0},
hour: {style: 'red', scale: 0.51, angle: 0}
};
// helper function for the draw function (see below)
// extracted as a standalone function to satisfy common believe in efficient JS code
// TODO: verify that this has actually any effect on byte code level
var drawHand = function(handState, ctx, cx, cy, maxRadius) {
ctx.lineWidth = 8;
ctx.strokeStyle = handState.style;
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(cx + Math.sin(handState.angle) * handState.scale * maxRadius,
cy + Math.cos(handState.angle) * handState.scale * maxRadius);
ctx.stroke();
};
// the 'draw' event is being emitted after each call to rocky.requestDraw() but
// at most once for each screen update, even if .requestDraw() is called frequently
// the 'draw' event might also fire at other meaningful times (e.g. upon launch)
rocky.on('draw', function(drawEvent) {
var ctx = drawEvent.context;
var w = ctx.canvas.unobstructedWidth;
var h = ctx.canvas.unobstructedHeight;
// clear canvas on each render
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
// center point
var cx = w / 2;
var cy = h / 2;
var maxRadius = Math.min(w, h - 2 * 10) / 2;
drawHand(renderState.minute, ctx, cx, cy, maxRadius);
drawHand(renderState.hour, ctx, cx, cy, maxRadius);
// Draw a 12 o clock indicator
drawHand({style: 'white', scale: 0, angle: 0}, ctx, cx, 8, 0);
// overdraw center so that no white part of the minute hand is visible
drawHand({style: 'red', scale: 0, angle: 0}, ctx, cx, cy, 0);
});
// listener is called on each full minute and once immediately after registration
rocky.on('minutechange', function(e) {
// WatchfaceHelper will later be extracted as npm module
var wfh = new WatchfaceHelper(e.date);
renderState.minute.angle = wfh.minuteAngle;
renderState.hour.angle = wfh.hourAngle;
rocky.requestDraw();
});
rocky.on('secondchange', function(e) {
console.log(e.date.toLocaleTimeString() + ' ' + e.date.toLocaleDateString() +
' ' + e.date.toLocaleString());
});
console.log('TicToc launched');

View file

@ -0,0 +1 @@
node_modules

View file

@ -0,0 +1,6 @@
def configure(conf):
pass
def build(bld):
pass

View file

@ -0,0 +1,192 @@
/**
* 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.
*/
// Polyfill Number.isNaN
if (Number.isNaN === undefined) {
Number.isNaN = Number.isNaN || function(value) {
return value !== value;
}
}
var __jerryRefs = {
// Jerryscript values (jerry_value_t) are integers which contain information
// about some javascript value that has been internally created.
// Create _objMap which will allow us to store and retrieve javascript values
// from a jerry_value_t, and perform refcounts on values that we still need an
// internal reference to, and avoid them from being garbage collected.
_objMap : {},
_nextObjectRef : 1,
_findValue : function(value) {
if (Number.isNaN(value)) {
// Special case to find NaN
for (var jerry_val in this._objMap) {
if (Number.isNaN(this._objMap[jerry_val].value)) {
return jerry_val;
}
}
} else {
for (var jerry_val in this._objMap) {
if (this._objMap[jerry_val].value === value) {
return jerry_val;
}
}
}
return 0;
},
_getEntry : function(jerry_value) {
var entry = this._objMap[jerry_value];
if (!entry) {
throw new Error('Entry at ' + jerry_value + ' does not exist');
}
return entry;
},
reset: function() {
this._objMap = {};
this._nextObjectRef = 1;
},
// Given a jerry value, return the stored javascript value.
get : function(jerry_value) {
return this._getEntry(jerry_value).value;
},
// Given a javascript value, return a jerry value that refers to it.
// If the value already exists in the map, increment its refcount and return
// the jerry value.
// Otherwise, create a new entry and return the jerry value.
ref : function(value) {
var jerry_value = this._findValue(value);
if (jerry_value) {
this._getEntry(jerry_value).refCount++;
return jerry_value;
}
jerry_value = this._nextObjectRef++;
this._objMap[jerry_value] = {
refCount : 1,
value : value,
error : false,
};
// console.log('created entry ' + jerry_value + ' for ' + value + ' at ' + stackTrace());
return jerry_value;
},
getRefCount : function(jerry_value) {
return this._getEntry(jerry_value).refCount;
},
// Increase the reference count of the given jerry value
acquire : function(jerry_value) {
this._getEntry(jerry_value).refCount++;
return jerry_value;
},
// Decrease the reference count of the given jerry value and delete it if
// there are no more internal references.
release : function(ref) {
var entry = this._getEntry(ref);
entry.refCount--;
if (entry.refCount <= 0) {
if (entry.freeCallbackPtr) {
Module.ccall(
'emscripten_call_jerry_object_free_callback',
null,
['number', 'number'],
[entry.freeCallbackPtr, entry.nativeHandlePtr]);
}
// console.log('deleting ' + ref + ' at ' + stackTrace());
delete this._objMap[ref];
}
},
setError : function(ref, state) {
this._getEntry(ref).error = state;
},
getError : function(ref) {
return this._getEntry(ref).error;
},
setNativeHandle : function(jerryValue, nativeHandlePtr, freeCallbackPtr) {
var entry = this._getEntry(jerryValue);
entry.nativeHandlePtr = nativeHandlePtr;
entry.freeCallbackPtr = freeCallbackPtr;
},
getNativeHandle : function(jerryValue) {
return this._getEntry(jerryValue).nativeHandlePtr;
}
};
function __jerry_create_external_function(function_ptr) {
var f = function() {
var nativeHandlerArgs = [
function_ptr, /* the function pointer for us to call */
__jerryRefs.ref(f), /* ref to the actual js function */
__jerryRefs.ref(this) /* our this object */
];
var numArgs = arguments.length;
var jsRefs = [];
for (var i = 0; i < numArgs; i++) {
jsRefs.push(__jerryRefs.ref(arguments[i]));
}
// Arg 4 is a uint32 array of jerry_value_t arguments
var jsArgs = Module._malloc(numArgs * 4);
for (var i = 0; i < numArgs; i++) {
Module.setValue(jsArgs + i*4, jsRefs[i], 'i32');
}
nativeHandlerArgs.push(jsArgs);
nativeHandlerArgs.push(numArgs);
// this is just the classy Emscripten calling. function_ptr is a C-pointer here
// and we know the signature of the C function as it needs to follow
var result_ref = Module.ccall('emscripten_call_jerry_function',
'number',
['number', 'number', 'number', 'number', 'number'],
nativeHandlerArgs);
// Free and release all js args
Module._free(jsArgs);
while (jsRefs.length > 0) {
__jerryRefs.release(jsRefs.pop());
}
// decrease refcount of native handler arguments
__jerryRefs.release(nativeHandlerArgs[1]); // jsFunctionRef
__jerryRefs.release(nativeHandlerArgs[2]); // our this object
// delete native handler arguments
nativeHandlerArgs.length = 0;
var result_val = __jerryRefs.get(result_ref);
var has_error = __jerryRefs.getError(result_ref);
__jerryRefs.release(result_ref);
if (has_error) {
throw result_val;
}
return result_val;
};
return __jerryRefs.ref(f);
}

View file

@ -0,0 +1,224 @@
/*
* 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 <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "util/heap.h"
#include "util/size.h"
#include "applib/app_logging.h"
#include "applib/fonts/fonts.h"
#include "applib/fonts/fonts_private.h"
#include "applib/graphics/text_resources.h"
#include "applib/rockyjs/api/rocky_api.h"
#include "resource/resource_ids.auto.h"
#include "font_resource_keys.auto.h"
#include "font_resource_table.auto.h"
#include "emscripten_app.h"
#include "emscripten_graphics.h"
#include "emscripten_resources.h"
#include <emscripten/emscripten.h>
#define NUM_SYSTEM_FONTS (ARRAY_LENGTH(s_font_resource_keys))
void *task_malloc(size_t bytes) {
return malloc(bytes);
}
void *task_zalloc(size_t bytes) {
void *ptr = malloc(bytes);
if (ptr) {
memset(ptr, 0, bytes);
}
return ptr;
}
void *task_zalloc_check(size_t bytes) {
void *ptr = task_zalloc(bytes);
if (!ptr) {
wtf();
}
return ptr;
}
void *task_realloc(void *ptr, size_t bytes) {
return realloc(ptr, bytes);
}
void task_free(void *ptr) {
free(ptr);
}
void app_log(uint8_t log_level, const char* src_filename,
int src_line_number, const char* fmt, ...) {
printf("%s:%d", src_filename, src_line_number);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf("\n");
}
GContext* app_state_get_graphics_context() {
return emx_graphics_get_gcontext();
}
bool app_state_get_text_perimeter_debugging_enabled(void) {
return false;
}
Heap *app_state_get_heap(void) {
return NULL;
}
GBitmap* app_state_legacy2_get_2bit_framebuffer(void) {
return NULL;
}
bool heap_is_allocated(Heap* const heap, void* ptr) {
return false;
}
void passert_failed(const char* filename, int line_number, const char* message, ...) {
APP_LOG(LOG_LEVEL_ERROR, "ASSERTION FAILED: %s:%d", filename, line_number);
EM_ASM_INT_V({ abort(); });
while (1) ;
}
void passert_failed_no_message(const char* filename, int line_number) {
passert_failed(filename, line_number, NULL);
while (1) ;
}
void passert_failed_hashed_no_message(void) {
EM_ASM_INT_V({ abort(); });
while (1);
}
void passert_failed_hashed(uint32_t packed_loghash, ...) {
EM_ASM_INT_V({ abort(); });
while (1);
}
void pbl_log(uint8_t log_level, const char* src_filename,
int src_line_number, const char* fmt, ...) {
printf("%s:%d ", src_filename, src_line_number);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
printf("\n");
}
bool process_manager_compiled_with_legacy2_sdk(void) {
return false;
}
ResAppNum sys_get_current_resource_num(void) {
return 1; // 0 is system
}
size_t sys_resource_load_range(ResAppNum app_num, uint32_t id, uint32_t start_bytes,
uint8_t *buffer, size_t num_bytes) {
return emx_resources_read(app_num, id, start_bytes, buffer, num_bytes);
}
size_t sys_resource_size(ResAppNum app_num, uint32_t handle) {
return emx_resources_get_size(app_num, handle);
}
GFont sys_font_get_system_font(const char *font_key) {
static FontInfo s_system_fonts_info_table[NUM_SYSTEM_FONTS + 1] = {};
for (int i = 0; i < (int) NUM_SYSTEM_FONTS; ++i) {
if (0 == strcmp(font_key, s_font_resource_keys[i].key_name)) {
FontInfo *fontinfo = &s_system_fonts_info_table[i];
uint32_t resource = s_font_resource_keys[i].resource_id;
// if the font has not been initialized yet
if (!fontinfo->loaded) {
if (!text_resources_init_font(SYSTEM_APP,
resource, 0, &s_system_fonts_info_table[i])) {
// Can't initialize the font for some reason
return NULL;
}
}
return &s_system_fonts_info_table[i];
}
}
// Didn't find the given font, invalid key.
return (GFont)NULL;
}
void sys_font_reload_font(FontInfo *fontinfo) {
text_resources_init_font(fontinfo->base.app_num, fontinfo->base.resource_id,
fontinfo->extension.resource_id, fontinfo);
}
uint32_t sys_resource_get_and_cache(ResAppNum app_num, uint32_t resource_id) {
return resource_id;
}
bool sys_resource_is_valid(ResAppNum app_num, uint32_t resource_id) {
return true;
}
ResourceCallbackHandle resource_watch(ResAppNum app_num,
uint32_t resource_id,
ResourceChangedCallback callback,
void *data) {
return NULL;
}
void applib_resource_munmap_or_free(void *bytes) {
free(bytes);
}
void *applib_resource_mmap_or_load(ResAppNum app_num, uint32_t resource_id,
size_t offset, size_t num_bytes, bool used_aligned) {
if (num_bytes == 0) {
return NULL;
}
uint8_t *result = malloc(num_bytes + (used_aligned ? 7 :0));
if (!result
|| sys_resource_load_range(app_num, resource_id, offset, result, num_bytes) != num_bytes) {
free(result);
return NULL;
}
return result;
}
void wtf(void) {
printf(">>>> WTF\n");
EM_ASM_INT_V({ abort(); });
while (1) ;
}
PebbleTask pebble_task_get_current(void) {
return PebbleTask_App;
}
void app_event_loop(void) {
// FIXME: PBL-43469 will need to remove this init from here when multiple
// platform support is implemented.
rocky_api_watchface_init();
emx_app_event_loop();
}

View file

@ -0,0 +1,132 @@
/*
* 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 "../../emscripten_resources.h"
#include <stdio.h>
#include <stdlib.h>
#define ASSERT(expr) \
do { \
if (!(expr)) { \
printf("%s:%d " #expr " false\n", __FILE__, __LINE__); \
exit(-1); \
} \
} while (0)
#define CUSTOM_RES_GEN(x) \
static uint32_t s_read_##x##_called = 0; \
static uint32_t s_size_##x##_called = 0; \
int custom_res_read_##x(int offset, uint8_t *buf, int num_bytes) { \
uint32_t *buf_ptr = (uint32_t *)buf; \
*buf_ptr = x; \
s_read_##x##_called++; \
return 4; \
} \
int custom_res_size_##x(void) { \
s_size_##x##_called++; \
return 4; \
}
CUSTOM_RES_GEN(1);
CUSTOM_RES_GEN(2);
CUSTOM_RES_GEN(3);
CUSTOM_RES_GEN(4);
int main(int argc, char **argv) {
// 1 res
uint32_t id_1 = emx_resources_register_custom(custom_res_read_1, custom_res_size_1);
ASSERT(emx_resources_get_size(1, id_1) == 4);
uint32_t buf = 0;
ASSERT(emx_resources_read(1, id_1, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 1);
ASSERT(s_read_1_called == 1);
ASSERT(s_size_1_called == 1);
// 2nd res
uint32_t id_2 = emx_resources_register_custom(custom_res_read_2, custom_res_size_2);
ASSERT(emx_resources_get_size(1, id_2) == 4);
buf = 0;
ASSERT(emx_resources_read(1, id_2, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 2);
ASSERT(s_read_2_called == 1);
ASSERT(s_size_2_called == 1);
// 3rd res
uint32_t id_3 = emx_resources_register_custom(custom_res_read_3, custom_res_size_3);
ASSERT(emx_resources_get_size(1, id_3) == 4);
buf = 0;
ASSERT(emx_resources_read(1, id_3, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 3);
ASSERT(s_read_3_called == 1);
ASSERT(s_size_3_called == 1);
// remove 2
emx_resources_remove_custom(id_2);
ASSERT(emx_resources_get_size(1, id_2) == 0);
buf = 0;
ASSERT(emx_resources_read(1, id_2, 0, (uint8_t *)&buf, 1) == 0);
ASSERT(buf == 0);
// verify 1 & 3 are OK
buf = 0;
ASSERT(emx_resources_read(1, id_3, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 3);
ASSERT(s_read_3_called == 2);
buf = 0;
ASSERT(emx_resources_read(1, id_1, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 1);
ASSERT(s_read_1_called == 2);
// add 4
uint32_t id_4 = emx_resources_register_custom(custom_res_read_4, custom_res_size_4);
ASSERT(emx_resources_get_size(1, id_4) == 4);
buf = 0;
ASSERT(emx_resources_read(1, id_4, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 4);
ASSERT(s_read_4_called == 1);
ASSERT(s_size_4_called == 1);
// remove 1 & 3
emx_resources_remove_custom(id_1);
ASSERT(emx_resources_get_size(1, id_1) == 0);
emx_resources_remove_custom(id_3);
ASSERT(emx_resources_get_size(1, id_3) == 0);
// verify 4 is ok
buf = 0;
ASSERT(emx_resources_read(1, id_4, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 4);
ASSERT(s_read_4_called == 2);
// remove 4
emx_resources_remove_custom(id_4);
ASSERT(emx_resources_get_size(1, id_4) == 0);
ASSERT(s_size_4_called == 1);
// add 4 again
id_4 = emx_resources_register_custom(custom_res_read_4, custom_res_size_4);
ASSERT(emx_resources_get_size(1, id_4) == 4);
buf = 0;
ASSERT(emx_resources_read(1, id_4, 0, (uint8_t *)&buf, 1) == 4);
ASSERT(buf == 4);
ASSERT(s_read_4_called == 3);
ASSERT(s_size_4_called == 2);
// remove 4 again
emx_resources_remove_custom(id_4);
ASSERT(emx_resources_get_size(1, id_4) == 0);
ASSERT(s_size_4_called == 2);
}

View file

@ -0,0 +1,42 @@
from waflib import Logs
def configure(conf):
conf.load('gcc waf_unit_test')
pass
def show_results(bld):
lst = getattr(bld, 'utest_results', [])
if lst:
Logs.pprint('CYAN', 'execution summary')
total = len(lst)
tfail = len([x for x in lst if x[1]])
Logs.pprint('CYAN', ' tests that pass %d/%d' % (total-tfail, total))
for (f, code, out, err) in lst:
if not code:
Logs.pprint('CYAN', ' %s' % f)
if (tfail):
Logs.pprint('RED', ' tests that fail %d/%d' % (tfail, total))
for (f, code, out, err) in lst:
if code:
Logs.pprint('CYAN', ' %s' % f)
Logs.pprint('WHITE', ' %s' % out)
def build(bld):
includes = ['.', '../', '../../../src/fw/']
sources = bld.path.ant_glob('*.c')
sources.append(bld.path.parent.find_node('emscripten_resources.c'))
bld.program(features='test',
source=sources,
target='test',
cflags='-g',
includes=includes)
bld.add_post_fun(show_results)
# vim:filetype=python

View file

@ -0,0 +1,134 @@
#!/usr/bin/env python
# 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.
import argparse
import re
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('js_file')
parser.add_argument('--unittest', action='store_true')
args = parser.parse_args()
# load file to be processed
with open(args.js_file, "r") as f:
source = f.read()
# remove all known functions for memory access
# note: this implementation uses a weak heuristic: only the closing } of a
# given function has no indentation
for func in ["SAFE_HEAP_LOAD", "SAFE_HEAP_LOAD_D", "SAFE_HEAP_STORE", "SAFE_HEAP_STORE_D"]:
source = re.sub("function %s\([^\)]*\)\s*{(.*\n)+?}" % func, "", source)
# applies the same patch as seen at
# https://github.com/kripken/emscripten/commit/bc11547fbf446993ee0f6f30a0deb3f80f205c35
# which is part of the fix for https://github.com/kripken/emscripten/issues/3945
# TODO: fix after PBL-32521 is done
orig_source = source
source = source.replace("funcstr += arg + '=' + convertCode.returnValue + ';';",
"funcstr += arg + '=(' + convertCode.returnValue + ');';")
# assert source != orig_source, "Emscripten output does not match expected output of 1.35.0"
# we're not using emscripten's --pre-js and --post-js as it interferes
# with --embed-file
with open(args.js_file, "w") as f:
f.write(PROLOGUE)
if args.unittest:
f.write(UNITTEST_PROLOGUE)
f.write(source)
f.write(EPILOGUE)
if args.unittest:
f.write("new RockySimulator();\n")
PROLOGUE = """
RockySimulator = function(options) {
options = options || {};
var Module = {
print: function(text) {
console.log(text);
},
printErr: function(text) {
console.error(text);
},
};
"""
EPILOGUE = """
// support non-aligned memory access
function SAFE_HEAP_STORE(dest, value, bytes, isFloat) {
if (dest <= 0) abort('segmentation fault storing ' + bytes + ' bytes to address ' + dest);
if (dest + bytes > Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault, exceeded the top of the available heap when storing ' + bytes + ' bytes to address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + DYNAMICTOP);
assert(DYNAMICTOP <= TOTAL_MEMORY);
if (dest % bytes !== 0) {
for (var i = 0; i < bytes; i++) {
HEAPU8[dest + i >> 0] = (value >> (8 * i)) & 0xff;
}
} else {
setValue(dest, value, getSafeHeapType(bytes, isFloat), 1);
}
}
function SAFE_HEAP_STORE_D(dest, value, bytes) {
SAFE_HEAP_STORE(dest, value, bytes, true);
}
function SAFE_HEAP_LOAD(dest, bytes, unsigned, isFloat) {
// overrule
if (dest <= 0) abort('segmentation fault loading ' + bytes + ' bytes from address ' + dest);
if (dest + bytes > Math.max(DYNAMICTOP, STATICTOP)) abort('segmentation fault, exceeded the top of the available heap when loading ' + bytes + ' bytes from address ' + dest + '. STATICTOP=' + STATICTOP + ', DYNAMICTOP=' + DYNAMICTOP);
assert(DYNAMICTOP <= TOTAL_MEMORY);
var type = getSafeHeapType(bytes, isFloat);
var ret;
if (dest % bytes !== 0) {
for (var i = 0; i < bytes; i++) {
ret |= HEAPU8[dest + i >> 0] << (8 * i);
}
} else {
ret = getValue(dest, type, 1);
}
if (unsigned) ret = unSign(ret, parseInt(type.substr(1)), 1);
return ret;
}
function SAFE_HEAP_LOAD_D(dest, bytes, unsigned) {
return SAFE_HEAP_LOAD(dest, bytes, unsigned, true);
}
return Module;
};
if (typeof(module) !== "undefined") {
module.exports = RockySimulator;
}
"""
UNITTEST_PROLOGUE = """
var defaultPreRun = Module.preRun;
Module.preRun = function() {
if (defaultPreRun) {
defaultPreRun();
}
// Mount the host filesystem to make the fixture files accessible:
FS.mkdir('/node_fs');
FS.mount(NODEFS, { root: '/' }, '/node_fs');
}
"""
if __name__ == "__main__":
main()

View file

@ -0,0 +1,137 @@
from waflib import Logs
def em_resource(task):
packager = task.env.EMSCRIPTEN_ROOT + '/tools/file_packager.py'
task.exec_command(['python',
packager,
task.outputs[0].abspath(),
'--embed',
task.inputs[0].abspath(),
'--js-output=' + task.outputs[0].abspath()])
def configure(conf):
conf.find_program('node', var='NODE', errmsg='node is not installed')
# Make sure we're about to modify the 'local' conf:
prev_env = conf.env
conf.set_env(conf.all_envs['local'])
conf.load('emscripten')
# The standard lib emscripten bundles uses a different format
# for its stack guard!!
conf.env.CFLAGS.append('-D_TIME_H')
# For unit tests: DUMA depends on pthread,
# which I didn't get to work with emscripten.
conf.env.DEFINES.append('DUMA_DISABLED')
# Flags that emcc doesn't support, just remove them:
unwanted_cflags = ['-gdwarf-4']
if 'RELEASE' not in conf.env.DEFINES:
conf.env.EMCC_DEBUG = 2
conf.env['CFLAGS'].extend(['-g4'])
unwanted_cflags.extend(['-g3', '-g'])
conf.env['CFLAGS'] = filter(
lambda flag: flag not in unwanted_cflags,
conf.env['CFLAGS']
)
conf.env.EMX_OTHER_SETTINGS = [
'SAFE_HEAP=1',
# absurdly large value so we don't worry:
'RESERVED_FUNCTION_POINTERS=1000',
'ERROR_ON_UNDEFINED_SYMBOLS=1'
]
conf.add_platform_defines(conf.env)
conf.recurse('integration_tests')
conf.setenv('emscripten', conf.env)
conf.set_env(prev_env)
def apply_config_for_applib_and_test_rocky_emx_builds(bld):
bld.env.DEFINES.append("APPLIB_EMSCRIPTEN=1")
# __builtin_return_address() doesn't seem to be supported by Emscripten,
# it fail at runtime due to a missing `llvm_return_address` function.
bld.env.CFLAGS.extend(['-D__builtin_return_address(level)=(0)'])
jerry_api_js = bld.path.make_node('jerry_api.js')
timeshift_js = bld.path.find_node('timeshift-js/timeshift.js')
html_binding_js = bld.path.make_node('html-binding.js')
bld.env.EMX_PRE_JS_FILES = [jerry_api_js, timeshift_js]
bld.env.EMX_POST_JS_FILES = [html_binding_js]
# use external transformation script instead of --pre-js and --post-js so
# we can replace functions and wrap entire file without interfering with
# --embed-file
transform_js_node_and_args = [bld.path.make_node('transform_js.py')]
if bld.variant == 'test_rocky_emx':
transform_js_node_and_args.append(' --unittest')
bld.env.EMX_TRANSFORM_JS_NODE_AND_ARGS = transform_js_node_and_args
def build(bld):
if bld.variant == 'test':
bld.recurse('tests')
return
# Extend waf's 'cprogram' feature with Emscripten-specific things:
bld.load('emscripten')
apply_config_for_applib_and_test_rocky_emx_builds(bld)
# Fine to use 'stlib' here vs emscripten_program, because we're only
# invoking emcc to generate an archive file, so only 'standard' compiler
# flags need to be passed.
bld.objects(source=['emscripten_jerry_api.c'],
target='emscripten_jerry_api',
use=['jerry_port_includes'])
if bld.variant == 'test_rocky_emx':
return
# Copy stuff from html folder:
html_node = bld.path.find_dir('html')
html_bld_node = bld.path.get_bld().make_node('html')
for file in html_node.ant_glob('**/*'):
bld(rule="cp ${SRC} ${TGT}",
source=file,
target=html_bld_node.make_node(file.path_from(html_node)))
pbpack = bld.path.parent.parent.get_bld().make_node('system_resources.pbpack')
exported_functions = bld.path.make_node('exported_functions.json')
sources = bld.path.ant_glob('*.c', excl='emscripten_jerry_api.c')
rockyjs_node = html_bld_node.make_node('rocky.js')
bld.program(source=sources,
target=rockyjs_node,
emx_pre_js_files=[],
emx_post_js_files=[],
emx_exported_functions=exported_functions,
emx_other_settings=[],
emx_embed_files=[pbpack],
use=['emscripten_jerry_api',
'applib',
'applib_includes',
'nanopb',
'fw_includes',
'libutil',
'upng'])
bld.recurse('integration_tests')
def print_index_html_path(bld):
index_html_path = html_bld_node.find_node('index.html').abspath()
Logs.pprint('PINK',
'Built Rocky Simulator: file://{}'.format(index_html_path))
bld.add_post_fun(print_index_html_path)
# vim:filetype=python

View file

@ -0,0 +1,19 @@
/*
* 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
typedef long BaseType_t;

View file

@ -0,0 +1,34 @@
/*
* 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 <stdlib.h>
#include <string.h>
static void* applib_zalloc(size_t size) {
void* result = malloc(size);
if (result) {
memset(result, 0, size);
}
return result;
}
#define applib_type_zalloc(Type) applib_zalloc(sizeof(Type))
#define applib_type_malloc(Type) malloc(sizeof(Type))
#define applib_type_size(Type) sizeof(Type)
#define applib_malloc(size) malloc(size)
#define applib_free(ptr) free(ptr)

View file

@ -0,0 +1,22 @@
/*
* 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
struct pebble_mutex_t;
typedef struct pebble_mutex_t PebbleMutex;
struct pebble_recursive_mutex_t;
typedef struct pebble_recursive_mutex_t PebbleRecursiveMutex;

View file

@ -0,0 +1,19 @@
/*
* 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
typedef long BaseType_t;

View file

@ -0,0 +1,19 @@
/*
* 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
typedef void * QueueHandle_t;

View file

@ -0,0 +1,17 @@
/*
* 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

View file

@ -0,0 +1,22 @@
/*
* 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
typedef void * TaskHandle_t;
typedef struct {
} TaskParameters_t;

View file

@ -0,0 +1,40 @@
/*
* 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 <stdio.h>
#include "sdl_graphics.h"
#include "sdl_app.h"
#include "applib/app.h"
#include "applib/graphics/gtypes.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/graphics_line.h"
int main(void) {
GContext *context = sdl_graphics_get_gcontext();
graphics_context_set_stroke_color(context, GColorBrightGreen);
graphics_context_set_stroke_width(context, 2);
graphics_draw_line(context, (GPoint){0, 0}, (GPoint){100, 100});
graphics_draw_line(context, (GPoint){0, 10}, (GPoint){100, 10});
graphics_draw_line(context, (GPoint){0, 20}, (GPoint){100, 20});
graphics_draw_line(context, (GPoint){0, 30}, (GPoint){100, 30});
graphics_draw_circle(context, (GPoint){50, 50}, 20);
app_event_loop();
return 0;
}

View file

@ -0,0 +1,12 @@
from waflib import Task
from waflib.TaskGen import feature, before_method
def build(bld):
sources = bld.path.ant_glob('*.c')
bld.program(source=sources,
target='sdl-example',
defines=['main=app_main'],
use=['applib_sdl', 'fw_includes'])
# vim:filetype=python

View file

@ -0,0 +1,72 @@
/*
* 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 "sdl_app.h"
#include "sdl_graphics.h"
#include <stdio.h>
extern int app_main(void);
int main(int argc, char **argv) {
if (!sdl_app_init()) {
return -1;
}
app_main();
sdl_app_deinit();
return 0;
}
#include <SDL.h>
bool sdl_app_init(void) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("Error: Failed to init SDL\n");
return false;
}
if (!sdl_graphics_init()) {
printf("Error: Failed to init graphics\n");
return false;
}
return true;
}
void sdl_app_deinit(void) {
SDL_Quit();
}
void sdl_app_event_loop(void) {
SDL_Event event;
int keypress = 0;
while (!keypress) {
sdl_graphics_render();
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
keypress = 1;
break;
case SDL_KEYDOWN:
keypress = 1;
break;
}
}
}
}

View 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 <stdbool.h>
bool sdl_app_init(void);
void sdl_app_deinit(void);
void sdl_app_event_loop(void);

View 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 "sdl_graphics.h"
#include "applib/graphics/graphics.h"
#include "applib/graphics/8_bit/framebuffer.h"
#include "util/circular_cache.h"
#include <stdint.h>
#include <stdio.h>
#include <SDL.h>
static GContext s_gcontext = {};
static FrameBuffer fb = {};
static SDL_Surface *screen = NULL;
bool sdl_graphics_init(void) {
if (!(screen = SDL_SetVideoMode(DISP_COLS, DISP_ROWS, 8 /* bits/pixel */, SDL_HWSURFACE))) {
return false;
}
framebuffer_init(&fb, &(GSize) {DISP_COLS, DISP_ROWS});
s_gcontext = (GContext) {
.dest_bitmap = (GBitmap) {
.addr = screen->pixels,
.row_size_bytes = DISP_COLS,
.info = (BitmapInfo) {.format = GBITMAP_NATIVE_FORMAT },
.bounds = (GRect) { { 0, 0 }, { DISP_COLS, DISP_ROWS } },
.data_row_infos = NULL,
},
.parent_framebuffer = &fb,
.parent_framebuffer_vertical_offset = 0,
.lock = false
};
graphics_context_set_default_drawing_state(&s_gcontext, GContextInitializationMode_App);
return true;
}
GContext *sdl_graphics_get_gcontext(void) {
return &s_gcontext;
}
void sdl_graphics_render(void) {
SDL_Flip(screen);
}

View file

@ -0,0 +1,24 @@
/*
* 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 "applib/graphics/gtypes.h"
bool sdl_graphics_init(void);
void sdl_graphics_render(void);
GContext *sdl_graphics_get_gcontext(void);

105
applib-targets/sdl/shims.c Normal file
View 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 <stdio.h>
#include <stdarg.h>
#include <stdint.h>
#include "util/heap.h"
#include "util/circular_cache.h"
#include "sdl_app.h"
#include "sdl_graphics.h"
void *task_malloc(size_t bytes) {
return malloc(bytes);
}
void task_free(void *ptr) {
free(ptr);
}
void app_log(uint8_t log_level, const char* src_filename,
int src_line_number, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
GContext* app_state_get_graphics_context() {
return sdl_graphics_get_gcontext();
}
Heap *app_state_get_heap(void) {
return NULL;
}
GBitmap* app_state_legacy2_get_2bit_framebuffer(void) {
return NULL;
}
void circular_cache_init(CircularCache* c, uint8_t* buffer, size_t item_size,
int total_items, Comparator compare_cb) {
}
bool heap_is_allocated(Heap* const heap, void* ptr) {
return false;
}
void passert_failed(const char* filename, int line_number, const char* message, ...) {
}
void passert_failed_no_message(const char* filename, int line_number) {
}
void pbl_log(uint8_t log_level, const char* src_filename,
int src_line_number, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
bool process_manager_compiled_with_legacy2_sdk(void) {
return false;
}
ResAppNum sys_get_current_resource_num(void) {
return 0;
}
const uint8_t * sys_resource_builtin_bytes(ResAppNum app_num, uint32_t resource_id,
uint32_t *num_bytes_out) {
return 0;
}
size_t sys_resource_load_range(ResAppNum app_num, uint32_t id, uint32_t start_bytes,
uint8_t *buffer, size_t num_bytes) {
return 0;
}
size_t sys_resource_size(ResAppNum app_num, uint32_t handle) {
return 0;
}
void app_event_loop(void) {
sdl_app_event_loop();
}
void wtf(void) {
printf(">>> WTF\n");
}

View file

@ -0,0 +1,27 @@
import waflib
def configure(conf):
conf.check_cfg(msg='Checking for sdl-config',
path='sdl-config',
package='',
args='--cflags --libs',
uselib_store='SDL')
conf.find_program('objcopy gobjcopy', var='OBJCOPY')
# We are overriding the gcc toolchain include/time.h with our own
# just to check/force our version of <time.h>
conf.env.CFLAGS.append('-D_TIME_H_')
def build(bld):
sources = bld.path.ant_glob('*.c')
bld.stlib(source=sources,
target='applib_sdl',
includes='.',
export_includes='.',
use=['applib', 'fw_includes', 'libutil', 'upng', 'SDL'])
bld.recurse('examples')
# vim:filetype=python

33
applib-targets/wscript Normal file
View file

@ -0,0 +1,33 @@
def options(opt):
opt.add_option('--target', action='store',
choices=['sdl', 'emscripten'],
help='What backend we are compiling applib against (#rockyJS)')
def configure(conf):
if conf.options.target is None:
return
else:
conf.env.APPLIB_TARGET = conf.options.target
conf.recurse(conf.options.target)
def build(bld):
if bld.variant == 'test':
bld.recurse('emscripten')
return
if bld.env.APPLIB_TARGET is None:
bld(export_includes=[], name='target_includes')
return
bld.set_env(bld.all_envs['local'])
# time_t is defined in sys/types in newlib, and time.h on recent Linux
# so just force the defined type for testing time
bld.env.CFLAGS.append('-Dtime_t=__SYSCALL_SLONG_TYPE')
bld(export_includes=['overrides'], name='target_includes')
bld.recurse(bld.env.APPLIB_TARGET)
# vim:filetype=python