From 2484620d2065d4445f20a6a12b4a68080f582100 Mon Sep 17 00:00:00 2001 From: Siyuan Miao Date: Sat, 21 Jun 2025 22:37:10 +0000 Subject: [PATCH] feat: add video frame handling --- Makefile | 7 +- display.go | 52 +- internal/logging/sse.html | 2 +- internal/native/cgo/CMakeLists.txt | 4 +- internal/native/cgo/ctrl.c | 33 +- internal/native/cgo/ctrl.h | 8 + internal/native/cgo/edid.c | 3 +- internal/native/cgo/frozen.c | 1504 ------------- internal/native/cgo/frozen.h | 349 --- internal/native/cgo/log.h | 130 ++ internal/native/cgo/log_handler.c | 15 + internal/native/cgo/log_handler.h | 9 + internal/native/cgo/screen.c | 10 +- internal/native/cgo/video.c | 101 +- internal/native/cgo/video.h | 1 + internal/native/ctrl_linux.go | 113 +- internal/native/eez/jetkvm.eez-project | 1929 +++++++++++++---- .../native/eez/jetkvm.eez-project-ui-state | 64 +- internal/native/native.go | 62 +- internal/native/video.go | 16 + native.go | 21 +- video.go | 14 +- 22 files changed, 2065 insertions(+), 2382 deletions(-) delete mode 100644 internal/native/cgo/frozen.c delete mode 100644 internal/native/cgo/frozen.h create mode 100644 internal/native/cgo/log.h create mode 100644 internal/native/cgo/log_handler.c create mode 100644 internal/native/cgo/log_handler.h create mode 100644 internal/native/video.go diff --git a/Makefile b/Makefile index 2f68682..26b7af5 100644 --- a/Makefile +++ b/Makefile @@ -24,11 +24,12 @@ GO_ARGS := GOOS=linux GOARCH=arm GOARM=7 ARCHFLAGS="-arch arm" # if BUILDKIT_PATH exists, use buildkit to build ifneq ($(wildcard $(BUILDKIT_PATH)),) GO_ARGS := $(GO_ARGS) \ - CGO_CFLAGS="-Ijetkvm-native -Ijetkvm-native/ui -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/include -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/include" \ - CGO_LDFLAGS="-Ljetkvm-native/build -Ljetkvm-native/build/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/lib -ljknative -lrockit -lrockchip_mpp -lrga -lpthread -lm -llvgl" \ + CGO_CFLAGS="-I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/include -I$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/include" \ + CGO_LDFLAGS="-L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/lib -L$(BUILDKIT_PATH)/$(BUILDKIT_FLAVOR)/sysroot/usr/lib -lrockit -lrockchip_mpp -lrga -lpthread -lm" \ CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \ LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \ - CGO_ENABLED=1 + CGO_ENABLED=1 + # GO_RELEASE_BUILD_ARGS := $(GO_RELEASE_BUILD_ARGS) -x -work endif GO_CMD := $(GO_ARGS) go diff --git a/display.go b/display.go index 6d41755..486b9d7 100644 --- a/display.go +++ b/display.go @@ -30,25 +30,35 @@ var ( func updateDisplay() { nativeInstance.UpdateLabelIfChanged("home_info_ipv4_addr", networkState.IPv4String()) - nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", networkState.IPv6String()) + ipv6 := networkState.IPv6String() + if ipv6 != "" { + nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", ipv6) + nativeInstance.ObjShow("home_info_ipv6_addr") + } else { + nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", "") + nativeInstance.ObjHide("home_info_ipv6_addr") + } + + nativeInstance.ObjHide("menu_btn_network") + nativeInstance.ObjHide("menu_btn_access") nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString()) if usbState == "configured" { - nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected") - _, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_DEFAULT") + nativeInstance.UpdateLabelIfChanged("usb_status_label", "Connected") + _, _ = nativeInstance.ObjSetState("usb_status", "LV_STATE_DEFAULT") } else { - nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected") - _, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_USER_2") + nativeInstance.UpdateLabelIfChanged("usb_status_label", "Disconnected") + _, _ = nativeInstance.ObjSetState("usb_status", "LV_STATE_USER_2") } if lastVideoState.Ready { - nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected") - _, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_DEFAULT") + nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Connected") + _, _ = nativeInstance.ObjSetState("hdmi_status", "LV_STATE_DEFAULT") } else { - nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected") - _, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_USER_2") + nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Disconnected") + _, _ = nativeInstance.ObjSetState("hdmi_status", "LV_STATE_USER_2") } - nativeInstance.UpdateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions)) + nativeInstance.UpdateLabelIfChanged("cloud_status_label", fmt.Sprintf("%d active", actionSessions)) if networkState.IsUp() { nativeInstance.SwitchToScreenIf("home_screen", []string{"no_network_screen", "boot_screen"}) @@ -57,20 +67,20 @@ func updateDisplay() { } if cloudConnectionState == CloudConnectionStateNotConfigured { - _, _ = nativeInstance.ObjHide("ui_Home_Header_Cloud_Status_Icon") + _, _ = nativeInstance.ObjHide("cloud_status_icon") } else { - _, _ = nativeInstance.ObjShow("ui_Home_Header_Cloud_Status_Icon") + _, _ = nativeInstance.ObjShow("cloud_status_icon") } switch cloudConnectionState { case CloudConnectionStateDisconnected: - _, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud_disconnected.png") + _, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud_disconnected") stopCloudBlink() case CloudConnectionStateConnecting: - _, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png") + _, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud") startCloudBlink() case CloudConnectionStateConnected: - _, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png") + _, _ = nativeInstance.ImgSetSrc("cloud_status_icon", "cloud") stopCloudBlink() } } @@ -94,9 +104,9 @@ func startCloudBlink() { if cloudConnectionState != CloudConnectionStateConnecting { continue } - _, _ = nativeInstance.ObjFadeOut("ui_Home_Header_Cloud_Status_Icon", 1000) + _, _ = nativeInstance.ObjFadeOut("cloud_status_icon", 1000) time.Sleep(1000 * time.Millisecond) - _, _ = nativeInstance.ObjFadeIn("ui_Home_Header_Cloud_Status_Icon", 1000) + _, _ = nativeInstance.ObjFadeIn("cloud_status_icon", 1000) time.Sleep(1000 * time.Millisecond) } }() @@ -146,14 +156,14 @@ func waitCtrlAndRequestDisplayUpdate(shouldWakeDisplay bool) { func updateStaticContents() { //contents that never change - nativeInstance.UpdateLabelIfChanged("ui_Home_Content_Mac", networkState.MACString()) + nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString()) systemVersion, appVersion, err := GetLocalVersion() if err == nil { - nativeInstance.UpdateLabelIfChanged("ui_About_Content_Operating_System_Version_ContentLabel", systemVersion.String()) - nativeInstance.UpdateLabelIfChanged("ui_About_Content_App_Version_Content_Label", appVersion.String()) + nativeInstance.UpdateLabelIfChanged("boot_screen_version", systemVersion.String()) + nativeInstance.UpdateLabelIfChanged("boot_screen_app_version", appVersion.String()) } - nativeInstance.UpdateLabelIfChanged("ui_Status_Content_Device_Id_Content_Label", GetDeviceID()) + nativeInstance.UpdateLabelIfChanged("boot_screen_device_id", GetDeviceID()) } // setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter diff --git a/internal/logging/sse.html b/internal/logging/sse.html index 192b464..937ace8 100644 --- a/internal/logging/sse.html +++ b/internal/logging/sse.html @@ -128,7 +128,7 @@ this.statsElement = statsElement; this.stream = null; this.reconnectAttempts = 0; - this.maxReconnectAttempts = 10; + this.maxReconnectAttempts = 500; this.reconnectDelay = 1000; // Start with 1 second this.maxReconnectDelay = 30000; // Max 30 seconds this.isConnecting = false; diff --git a/internal/native/cgo/CMakeLists.txt b/internal/native/cgo/CMakeLists.txt index 33ba835..0e699ab 100644 --- a/internal/native/cgo/CMakeLists.txt +++ b/internal/native/cgo/CMakeLists.txt @@ -41,10 +41,10 @@ FetchContent_Declare( FetchContent_MakeAvailable(lv_drivers) # Get source files, excluding CMake generated files -file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.c") +file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.c" "ui/*.c") list(FILTER sources EXCLUDE REGEX "CMakeFiles.*CompilerId.*\\.c$") -add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/jknative.h) +add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/ctrl.h) # Include directories target_include_directories(jknative PRIVATE diff --git a/internal/native/cgo/ctrl.c b/internal/native/cgo/ctrl.c index ad5fe42..764a5ba 100644 --- a/internal/native/cgo/ctrl.c +++ b/internal/native/cgo/ctrl.c @@ -7,16 +7,28 @@ #include #include #include -#include "frozen.h" #include "video.h" #include "screen.h" #include "edid.h" #include "ctrl.h" #include +#include "log.h" +#include "log_handler.h" jetkvm_video_state_t state; jetkvm_video_state_handler_t *video_state_handler = NULL; +jetkvm_video_handler_t *video_handler = NULL; + + +void jetkvm_set_log_handler(jetkvm_log_handler_t *handler) { + log_set_handler(handler); +} + +void jetkvm_set_video_handler(jetkvm_video_handler_t *handler) { + video_handler = handler; +} + void report_video_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second) { state.ready = ready; @@ -29,6 +41,21 @@ void report_video_format(bool ready, const char *error, u_int16_t width, u_int16 } } +int socket_send_frame(const uint8_t *frame, ssize_t len) +{ + if (video_handler != NULL) { + (*video_handler)(frame, len); + } else { + log_error("video handler is not set"); + } + return 0; + // if (video_client_fd <= 0) + // { + // return -1; + // } + // return send(video_client_fd, frame, len, 0); +} + /** * @brief Convert a hexadecimal string to an array of uint8_t bytes * @@ -284,6 +311,10 @@ int jetkvm_video_set_quality_factor(float quality_factor) { return 0; } +float jetkvm_video_get_quality_factor() { + return video_get_quality_factor(); +} + int jetkvm_video_set_edid(const char *edid_hex) { uint8_t edid[256]; int edid_len = hex_to_bytes(edid_hex, edid, 256); diff --git a/internal/native/cgo/ctrl.h b/internal/native/cgo/ctrl.h index 2f42502..153fb6c 100644 --- a/internal/native/cgo/ctrl.h +++ b/internal/native/cgo/ctrl.h @@ -2,6 +2,7 @@ #define VIDEO_DAEMON_CTRL_H #include +#include #include typedef struct @@ -14,6 +15,12 @@ typedef struct } jetkvm_video_state_t; typedef void (jetkvm_video_state_handler_t)(jetkvm_video_state_t *state); +typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, int line, const char *message); + +typedef void (jetkvm_video_handler_t)(const uint8_t *frame, ssize_t len); + +void jetkvm_set_log_handler(jetkvm_log_handler_t *handler); +void jetkvm_set_video_handler(jetkvm_video_handler_t *handler); void jetkvm_ui_init(); void jetkvm_ui_tick(); @@ -37,6 +44,7 @@ void jetkvm_video_shutdown(); void jetkvm_video_start(); void jetkvm_video_stop(); int jetkvm_video_set_quality_factor(float quality_factor); +float jetkvm_video_get_quality_factor(); int jetkvm_video_set_edid(const char *edid_hex); char *jetkvm_video_get_edid_hex(); jetkvm_video_state_t *jetkvm_video_get_status(); diff --git a/internal/native/cgo/edid.c b/internal/native/cgo/edid.c index 872d89d..75e36cf 100644 --- a/internal/native/cgo/edid.c +++ b/internal/native/cgo/edid.c @@ -1,4 +1,5 @@ #include "edid.h" +#include "log.h" #include #include @@ -172,7 +173,7 @@ const char *videoc_log_status() } else { - printf("Failed to read kernel log\n"); + log_error("Failed to read kernel log\n"); return NULL; } diff --git a/internal/native/cgo/frozen.c b/internal/native/cgo/frozen.c deleted file mode 100644 index ba0a2e2..0000000 --- a/internal/native/cgo/frozen.c +++ /dev/null @@ -1,1504 +0,0 @@ -/* - * Copyright (c) 2004-2013 Sergey Lyubka - * Copyright (c) 2018 Cesanta Software Limited - * All rights reserved - * - * 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. - */ - -#define _CRT_SECURE_NO_WARNINGS /* Disable deprecation warning in VS2005+ */ - -#include "frozen.h" - -#include -#include -#include -#include -#include -#include - -#if !defined(WEAK) -#if (defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32) -#define WEAK __attribute__((weak)) -#else -#define WEAK -#endif -#endif - -#ifdef _WIN32 -#undef snprintf -#undef vsnprintf -#define snprintf cs_win_snprintf -#define vsnprintf cs_win_vsnprintf -int cs_win_snprintf(char *str, size_t size, const char *format, ...); -int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap); -#if _MSC_VER >= 1700 || (defined(__GNUC__)) -#include -#else -typedef _int64 int64_t; -typedef unsigned _int64 uint64_t; -#endif -#define PRId64 "I64d" -#define PRIu64 "I64u" -#else /* _WIN32 */ -/* wants this for C++ */ -#ifndef __STDC_FORMAT_MACROS -#define __STDC_FORMAT_MACROS -#endif -#include -#endif /* _WIN32 */ - -#ifndef INT64_FMT -#define INT64_FMT PRId64 -#endif -#ifndef UINT64_FMT -#define UINT64_FMT PRIu64 -#endif - -#ifndef va_copy -#define va_copy(x, y) x = y -#endif - -#ifndef JSON_ENABLE_ARRAY -#define JSON_ENABLE_ARRAY 1 -#endif - -struct frozen { - const char *end; - const char *cur; - - const char *cur_name; - size_t cur_name_len; - int limit; - - /* For callback API */ - char path[JSON_MAX_PATH_LEN]; - size_t path_len; - void *callback_data; - json_walk_callback_t callback; -}; - -struct fstate { - const char *ptr; - size_t path_len; -}; - -#define SET_STATE(fr, ptr, str, len) \ - struct fstate fstate = {(ptr), (fr)->path_len}; \ - json_append_to_path((fr), (str), (len)); - -#define CALL_BACK(fr, tok, value, len) \ - do { \ - if ((fr)->callback && \ - ((fr)->path_len == 0 || (fr)->path[(fr)->path_len - 1] != '.')) { \ - struct json_token t = {(value), (int) (len), (tok)}; \ - \ - /* Call the callback with the given value and current name */ \ - (fr)->callback((fr)->callback_data, (fr)->cur_name, (fr)->cur_name_len, \ - (fr)->path, &t); \ - \ - /* Reset the name */ \ - (fr)->cur_name = NULL; \ - (fr)->cur_name_len = 0; \ - } \ - } while (0) - -static int json_append_to_path(struct frozen *f, const char *str, int size) { - int n = f->path_len; - int left = sizeof(f->path) - n - 1; - if (size > left) size = left; - memcpy(f->path + n, str, size); - f->path[n + size] = '\0'; - f->path_len += size; - return n; -} - -static void json_truncate_path(struct frozen *f, size_t len) { - f->path_len = len; - f->path[len] = '\0'; -} - -static int json_parse_object(struct frozen *f); -static int json_parse_value(struct frozen *f); - -#define EXPECT(cond, err_code) \ - do { \ - if (!(cond)) return (err_code); \ - } while (0) - -#define TRY(expr) \ - do { \ - int _n = expr; \ - if (_n < 0) return _n; \ - } while (0) - -#define END_OF_STRING (-1) - -static int json_left(const struct frozen *f) { - return f->end - f->cur; -} - -static int json_isspace(int ch) { - return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'; -} - -static void json_skip_whitespaces(struct frozen *f) { - while (f->cur < f->end && json_isspace(*f->cur)) f->cur++; -} - -static int json_cur(struct frozen *f) { - json_skip_whitespaces(f); - return f->cur >= f->end ? END_OF_STRING : *(unsigned char *) f->cur; -} - -static int json_test_and_skip(struct frozen *f, int expected) { - int ch = json_cur(f); - if (ch == expected) { - f->cur++; - return 0; - } - return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; -} - -static int json_isalpha(int ch) { - return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); -} - -static int json_isdigit(int ch) { - return ch >= '0' && ch <= '9'; -} - -static int json_isxdigit(int ch) { - return json_isdigit(ch) || (ch >= 'a' && ch <= 'f') || - (ch >= 'A' && ch <= 'F'); -} - -static int json_get_escape_len(const char *s, int len) { - switch (*s) { - case 'u': - return len < 6 ? JSON_STRING_INCOMPLETE - : json_isxdigit(s[1]) && json_isxdigit(s[2]) && - json_isxdigit(s[3]) && json_isxdigit(s[4]) - ? 5 - : JSON_STRING_INVALID; - case '"': - case '\\': - case '/': - case 'b': - case 'f': - case 'n': - case 'r': - case 't': - return len < 2 ? JSON_STRING_INCOMPLETE : 1; - default: - return JSON_STRING_INVALID; - } -} - -/* identifier = letter { letter | digit | '_' } */ -static int json_parse_identifier(struct frozen *f) { - EXPECT(json_isalpha(json_cur(f)), JSON_STRING_INVALID); - { - SET_STATE(f, f->cur, "", 0); - while (f->cur < f->end && - (*f->cur == '_' || json_isalpha(*f->cur) || json_isdigit(*f->cur))) { - f->cur++; - } - json_truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); - } - return 0; -} - -static int json_get_utf8_char_len(unsigned char ch) { - if ((ch & 0x80) == 0) return 1; - switch (ch & 0xf0) { - case 0xf0: - return 4; - case 0xe0: - return 3; - default: - return 2; - } -} - -/* string = '"' { quoted_printable_chars } '"' */ -static int json_parse_string(struct frozen *f) { - int n, ch = 0, len = 0; - TRY(json_test_and_skip(f, '"')); - { - SET_STATE(f, f->cur, "", 0); - for (; f->cur < f->end; f->cur += len) { - ch = *(unsigned char *) f->cur; - len = json_get_utf8_char_len((unsigned char) ch); - EXPECT(ch >= 32 && len > 0, JSON_STRING_INVALID); /* No control chars */ - EXPECT(len <= json_left(f), JSON_STRING_INCOMPLETE); - if (ch == '\\') { - EXPECT((n = json_get_escape_len(f->cur + 1, json_left(f))) > 0, n); - len += n; - } else if (ch == '"') { - json_truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_STRING, fstate.ptr, f->cur - fstate.ptr); - f->cur++; - break; - }; - } - } - return ch == '"' ? 0 : JSON_STRING_INCOMPLETE; -} - -/* number = [ '-' ] digit+ [ '.' digit+ ] [ ['e'|'E'] ['+'|'-'] digit+ ] */ -static int json_parse_number(struct frozen *f) { - int ch = json_cur(f); - SET_STATE(f, f->cur, "", 0); - if (ch == '-') f->cur++; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - if (f->cur + 1 < f->end && f->cur[0] == '0' && f->cur[1] == 'x') { - f->cur += 2; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - EXPECT(json_isxdigit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && json_isxdigit(f->cur[0])) f->cur++; - } else { - EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; - if (f->cur < f->end && f->cur[0] == '.') { - f->cur++; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; - } - if (f->cur < f->end && (f->cur[0] == 'e' || f->cur[0] == 'E')) { - f->cur++; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - if ((f->cur[0] == '+' || f->cur[0] == '-')) f->cur++; - EXPECT(f->cur < f->end, JSON_STRING_INCOMPLETE); - EXPECT(json_isdigit(f->cur[0]), JSON_STRING_INVALID); - while (f->cur < f->end && json_isdigit(f->cur[0])) f->cur++; - } - } - json_truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_NUMBER, fstate.ptr, f->cur - fstate.ptr); - return 0; -} - -#if JSON_ENABLE_ARRAY -/* array = '[' [ value { ',' value } ] ']' */ -static int json_parse_array(struct frozen *f) { - int i = 0, current_path_len; - char buf[20]; - CALL_BACK(f, JSON_TYPE_ARRAY_START, NULL, 0); - TRY(json_test_and_skip(f, '[')); - { - { - SET_STATE(f, f->cur - 1, "", 0); - while (json_cur(f) != ']') { - snprintf(buf, sizeof(buf), "[%d]", i); - i++; - current_path_len = json_append_to_path(f, buf, strlen(buf)); - f->cur_name = - f->path + strlen(f->path) - strlen(buf) + 1 /*opening brace*/; - f->cur_name_len = strlen(buf) - 2 /*braces*/; - TRY(json_parse_value(f)); - json_truncate_path(f, current_path_len); - if (json_cur(f) == ',') f->cur++; - } - TRY(json_test_and_skip(f, ']')); - json_truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_ARRAY_END, fstate.ptr, f->cur - fstate.ptr); - } - } - return 0; -} -#endif /* JSON_ENABLE_ARRAY */ - -static int json_expect(struct frozen *f, const char *s, int len, - enum json_token_type tok_type) { - int i, n = json_left(f); - SET_STATE(f, f->cur, "", 0); - for (i = 0; i < len; i++) { - if (i >= n) return JSON_STRING_INCOMPLETE; - if (f->cur[i] != s[i]) return JSON_STRING_INVALID; - } - f->cur += len; - json_truncate_path(f, fstate.path_len); - - CALL_BACK(f, tok_type, fstate.ptr, f->cur - fstate.ptr); - - return 0; -} - -/* value = 'null' | 'true' | 'false' | number | string | array | object */ -static int json_parse_value(struct frozen *f) { - int ch = json_cur(f); - - if (--f->limit <= 0) - return JSON_DEPTH_LIMIT; - - switch (ch) { - case '"': - TRY(json_parse_string(f)); - break; - case '{': - TRY(json_parse_object(f)); - break; -#if JSON_ENABLE_ARRAY - case '[': - TRY(json_parse_array(f)); - break; -#endif - case 'n': - TRY(json_expect(f, "null", 4, JSON_TYPE_NULL)); - break; - case 't': - TRY(json_expect(f, "true", 4, JSON_TYPE_TRUE)); - break; - case 'f': - TRY(json_expect(f, "false", 5, JSON_TYPE_FALSE)); - break; - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - TRY(json_parse_number(f)); - break; - default: - return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; - } - - f->limit++; - return 0; -} - -/* key = identifier | string */ -static int json_parse_key(struct frozen *f) { - int ch = json_cur(f); - if (json_isalpha(ch)) { - TRY(json_parse_identifier(f)); - } else if (ch == '"') { - TRY(json_parse_string(f)); - } else { - return ch == END_OF_STRING ? JSON_STRING_INCOMPLETE : JSON_STRING_INVALID; - } - return 0; -} - -/* pair = key ':' value */ -static int json_parse_pair(struct frozen *f) { - int current_path_len; - const char *tok; - json_skip_whitespaces(f); - tok = f->cur; - TRY(json_parse_key(f)); - { - f->cur_name = *tok == '"' ? tok + 1 : tok; - f->cur_name_len = *tok == '"' ? f->cur - tok - 2 : f->cur - tok; - current_path_len = json_append_to_path(f, f->cur_name, f->cur_name_len); - } - TRY(json_test_and_skip(f, ':')); - TRY(json_parse_value(f)); - json_truncate_path(f, current_path_len); - return 0; -} - -/* object = '{' pair { ',' pair } '}' */ -static int json_parse_object(struct frozen *f) { - CALL_BACK(f, JSON_TYPE_OBJECT_START, NULL, 0); - TRY(json_test_and_skip(f, '{')); - { - SET_STATE(f, f->cur - 1, ".", 1); - while (json_cur(f) != '}') { - TRY(json_parse_pair(f)); - if (json_cur(f) == ',') f->cur++; - } - TRY(json_test_and_skip(f, '}')); - json_truncate_path(f, fstate.path_len); - CALL_BACK(f, JSON_TYPE_OBJECT_END, fstate.ptr, f->cur - fstate.ptr); - } - return 0; -} - -static int json_doit(struct frozen *f) { - if (f->cur == 0 || f->end < f->cur) return JSON_STRING_INVALID; - if (f->end == f->cur) return JSON_STRING_INCOMPLETE; - return json_parse_value(f); -} - -int json_escape(struct json_out *out, const char *p, size_t len) WEAK; -int json_escape(struct json_out *out, const char *p, size_t len) { - size_t i, cl, n = 0; - const char *hex_digits = "0123456789abcdef"; - const char *specials = "btnvfr"; - - for (i = 0; i < len; i++) { - unsigned char ch = ((unsigned char *) p)[i]; - if (ch == '"' || ch == '\\') { - n += out->printer(out, "\\", 1); - n += out->printer(out, p + i, 1); - } else if (ch >= '\b' && ch <= '\r') { - n += out->printer(out, "\\", 1); - n += out->printer(out, &specials[ch - '\b'], 1); - } else if (isprint(ch)) { - n += out->printer(out, p + i, 1); - } else if ((cl = json_get_utf8_char_len(ch)) == 1) { - n += out->printer(out, "\\u00", 4); - n += out->printer(out, &hex_digits[(ch >> 4) % 0xf], 1); - n += out->printer(out, &hex_digits[ch % 0xf], 1); - } else { - n += out->printer(out, p + i, cl); - i += cl - 1; - } - } - - return n; -} - -int json_printer_buf(struct json_out *out, const char *buf, size_t len) WEAK; -int json_printer_buf(struct json_out *out, const char *buf, size_t len) { - size_t avail = out->u.buf.size - out->u.buf.len; - size_t n = len < avail ? len : avail; - memcpy(out->u.buf.buf + out->u.buf.len, buf, n); - out->u.buf.len += n; - if (out->u.buf.size > 0) { - size_t idx = out->u.buf.len; - if (idx >= out->u.buf.size) idx = out->u.buf.size - 1; - out->u.buf.buf[idx] = '\0'; - } - return len; -} - -int json_printer_file(struct json_out *out, const char *buf, size_t len) WEAK; -int json_printer_file(struct json_out *out, const char *buf, size_t len) { - return fwrite(buf, 1, len, out->u.fp); -} - -#if JSON_ENABLE_BASE64 -static int b64idx(int c) { - if (c < 26) { - return c + 'A'; - } else if (c < 52) { - return c - 26 + 'a'; - } else if (c < 62) { - return c - 52 + '0'; - } else { - return c == 62 ? '+' : '/'; - } -} - -static int b64rev(int c) { - if (c >= 'A' && c <= 'Z') { - return c - 'A'; - } else if (c >= 'a' && c <= 'z') { - return c + 26 - 'a'; - } else if (c >= '0' && c <= '9') { - return c + 52 - '0'; - } else if (c == '+') { - return 62; - } else if (c == '/') { - return 63; - } else { - return 64; - } -} - -static int b64enc(struct json_out *out, const unsigned char *p, int n) { - char buf[4]; - int i, len = 0; - for (i = 0; i < n; i += 3) { - int a = p[i], b = i + 1 < n ? p[i + 1] : 0, c = i + 2 < n ? p[i + 2] : 0; - buf[0] = b64idx(a >> 2); - buf[1] = b64idx((a & 3) << 4 | (b >> 4)); - buf[2] = b64idx((b & 15) << 2 | (c >> 6)); - buf[3] = b64idx(c & 63); - if (i + 1 >= n) buf[2] = '='; - if (i + 2 >= n) buf[3] = '='; - len += out->printer(out, buf, sizeof(buf)); - } - return len; -} - -static int b64dec(const char *src, int n, char *dst) { - const char *end = src + n; - int len = 0; - while (src + 3 < end) { - int a = b64rev(src[0]), b = b64rev(src[1]), c = b64rev(src[2]), - d = b64rev(src[3]); - dst[len++] = (a << 2) | (b >> 4); - if (src[2] != '=') { - dst[len++] = (b << 4) | (c >> 2); - if (src[3] != '=') { - dst[len++] = (c << 6) | d; - } - } - src += 4; - } - return len; -} -#endif /* JSON_ENABLE_BASE64 */ - -static unsigned char hexdec(const char *s) { -#define HEXTOI(x) (x >= '0' && x <= '9' ? x - '0' : x - 'W') - int a = tolower(*(const unsigned char *) s); - int b = tolower(*(const unsigned char *) (s + 1)); - return (HEXTOI(a) << 4) | HEXTOI(b); -} - -int json_vprintf(struct json_out *out, const char *fmt, va_list xap) WEAK; -int json_vprintf(struct json_out *out, const char *fmt, va_list xap) { - int len = 0; - const char *quote = "\"", *null = "null"; - va_list ap; - va_copy(ap, xap); - - while (*fmt != '\0') { - if (strchr(":, \r\n\t[]{}\"", *fmt) != NULL) { - len += out->printer(out, fmt, 1); - fmt++; - } else if (fmt[0] == '%') { - char buf[21]; - size_t skip = 2; - - if (fmt[1] == 'l' && fmt[2] == 'l' && (fmt[3] == 'd' || fmt[3] == 'u')) { - int64_t val = va_arg(ap, int64_t); - const char *fmt2 = fmt[3] == 'u' ? "%" UINT64_FMT : "%" INT64_FMT; - snprintf(buf, sizeof(buf), fmt2, val); - len += out->printer(out, buf, strlen(buf)); - skip += 2; - } else if (fmt[1] == 'z' && fmt[2] == 'u') { - size_t val = va_arg(ap, size_t); - snprintf(buf, sizeof(buf), "%lu", (unsigned long) val); - len += out->printer(out, buf, strlen(buf)); - skip += 1; - } else if (fmt[1] == 'M') { - json_printf_callback_t f = va_arg(ap, json_printf_callback_t); - len += f(out, &ap); - } else if (fmt[1] == 'B') { - int val = va_arg(ap, int); - const char *str = val ? "true" : "false"; - len += out->printer(out, str, strlen(str)); - } else if (fmt[1] == 'H') { -#if JSON_ENABLE_HEX - const char *hex = "0123456789abcdef"; - int i, n = va_arg(ap, int); - const unsigned char *p = va_arg(ap, const unsigned char *); - len += out->printer(out, quote, 1); - for (i = 0; i < n; i++) { - len += out->printer(out, &hex[(p[i] >> 4) & 0xf], 1); - len += out->printer(out, &hex[p[i] & 0xf], 1); - } - len += out->printer(out, quote, 1); -#endif /* JSON_ENABLE_HEX */ - } else if (fmt[1] == 'V') { -#if JSON_ENABLE_BASE64 - const unsigned char *p = va_arg(ap, const unsigned char *); - int n = va_arg(ap, int); - len += out->printer(out, quote, 1); - len += b64enc(out, p, n); - len += out->printer(out, quote, 1); -#endif /* JSON_ENABLE_BASE64 */ - } else if (fmt[1] == 'Q' || - (fmt[1] == '.' && fmt[2] == '*' && fmt[3] == 'Q')) { - size_t l = 0; - const char *p; - - if (fmt[1] == '.') { - l = (size_t) va_arg(ap, int); - skip += 2; - } - p = va_arg(ap, char *); - - if (p == NULL) { - len += out->printer(out, null, 4); - } else { - if (fmt[1] == 'Q') { - l = strlen(p); - } - len += out->printer(out, quote, 1); - len += json_escape(out, p, l); - len += out->printer(out, quote, 1); - } - } else { - /* - * we delegate printing to the system printf. - * The goal here is to delegate all modifiers parsing to the system - * printf, as you can see below we still have to parse the format - * types. - * - * Currently, %s with strings longer than 20 chars will require - * double-buffering (an auxiliary buffer will be allocated from heap). - * TODO(dfrank): reimplement %s and %.*s in order to avoid that. - */ - - const char *end_of_format_specifier = "sdfFeEgGlhuIcx.*-0123456789"; - int n = strspn(fmt + 1, end_of_format_specifier); - char *pbuf = buf; - int need_len, size = sizeof(buf); - char fmt2[20]; - va_list ap_copy; - strncpy(fmt2, fmt, - n + 1 > (int) sizeof(fmt2) ? sizeof(fmt2) : (size_t) n + 1); - fmt2[n + 1] = '\0'; - - va_copy(ap_copy, ap); - need_len = vsnprintf(pbuf, size, fmt2, ap_copy); - va_end(ap_copy); - - if (need_len < 0) { - /* - * Windows & eCos vsnprintf implementation return -1 on overflow - * instead of needed size. - */ - pbuf = NULL; - while (need_len < 0) { - free(pbuf); - size *= 2; - if ((pbuf = (char *) malloc(size)) == NULL) break; - va_copy(ap_copy, ap); - need_len = vsnprintf(pbuf, size, fmt2, ap_copy); - va_end(ap_copy); - } - } else if (need_len >= (int) sizeof(buf)) { - /* - * resulting string doesn't fit into a stack-allocated buffer `buf`, - * so we need to allocate a new buffer from heap and use it - */ - if ((pbuf = (char *) malloc(need_len + 1)) != NULL) { - va_copy(ap_copy, ap); - vsnprintf(pbuf, need_len + 1, fmt2, ap_copy); - va_end(ap_copy); - } - } - if (pbuf == NULL) { - buf[0] = '\0'; - pbuf = buf; - } - - /* - * however we need to parse the type ourselves in order to advance - * the va_list by the correct amount; there is no portable way to - * inherit the advancement made by vprintf. - * 32-bit (linux or windows) passes va_list by value. - */ - if ((n + 1 == (int) strlen("%" PRId64) && - strcmp(fmt2, "%" PRId64) == 0) || - (n + 1 == (int) strlen("%" PRIu64) && - strcmp(fmt2, "%" PRIu64) == 0)) { - (void) va_arg(ap, int64_t); - } else if (strcmp(fmt2, "%.*s") == 0) { - (void) va_arg(ap, int); - (void) va_arg(ap, char *); - } else { - switch (fmt2[n]) { - case 'u': - case 'd': - (void) va_arg(ap, int); - break; - case 'g': - case 'f': - (void) va_arg(ap, double); - break; - case 'p': - (void) va_arg(ap, void *); - break; - default: - /* many types are promoted to int */ - (void) va_arg(ap, int); - } - } - - len += out->printer(out, pbuf, strlen(pbuf)); - skip = n + 1; - - /* If buffer was allocated from heap, free it */ - if (pbuf != buf) { - free(pbuf); - pbuf = NULL; - } - } - fmt += skip; - } else if (*fmt == '_' || json_isalpha(*fmt)) { - len += out->printer(out, quote, 1); - while (*fmt == '_' || json_isalpha(*fmt) || json_isdigit(*fmt)) { - len += out->printer(out, fmt, 1); - fmt++; - } - len += out->printer(out, quote, 1); - } else { - len += out->printer(out, fmt, 1); - fmt++; - } - } - va_end(ap); - - return len; -} - -int json_printf(struct json_out *out, const char *fmt, ...) WEAK; -int json_printf(struct json_out *out, const char *fmt, ...) { - int n; - va_list ap; - va_start(ap, fmt); - n = json_vprintf(out, fmt, ap); - va_end(ap); - return n; -} - -int json_printf_array(struct json_out *out, va_list *ap) WEAK; -int json_printf_array(struct json_out *out, va_list *ap) { - int len = 0; - char *arr = va_arg(*ap, char *); - size_t i, arr_size = va_arg(*ap, size_t); - size_t elem_size = va_arg(*ap, size_t); - const char *fmt = va_arg(*ap, char *); - len += json_printf(out, "[", 1); - for (i = 0; arr != NULL && i < arr_size / elem_size; i++) { - union { - int64_t i; - double d; - } val; - memcpy(&val, arr + i * elem_size, - elem_size > sizeof(val) ? sizeof(val) : elem_size); - if (i > 0) len += json_printf(out, ", "); - if (strpbrk(fmt, "efg") != NULL) { - len += json_printf(out, fmt, val.d); - } else { - len += json_printf(out, fmt, val.i); - } - } - len += json_printf(out, "]", 1); - return len; -} - -#ifdef _WIN32 -int cs_win_vsnprintf(char *str, size_t size, const char *format, - va_list ap) WEAK; -int cs_win_vsnprintf(char *str, size_t size, const char *format, va_list ap) { - int res = _vsnprintf(str, size, format, ap); - va_end(ap); - if (res >= size) { - str[size - 1] = '\0'; - } - return res; -} - -int cs_win_snprintf(char *str, size_t size, const char *format, ...) WEAK; -int cs_win_snprintf(char *str, size_t size, const char *format, ...) { - int res; - va_list ap; - va_start(ap, format); - res = vsnprintf(str, size, format, ap); - va_end(ap); - return res; -} -#endif /* _WIN32 */ - -int json_walk(const char *json_string, int json_string_length, - json_walk_callback_t callback, void *callback_data) WEAK; -int json_walk(const char *json_string, int json_string_length, - json_walk_callback_t callback, void *callback_data) { - - if (callback == NULL) - return (json_walk_args(json_string, json_string_length, NULL)); - - struct frozen_args args[1]; - - INIT_FROZEN_ARGS(args); - args->callback = callback; - args->callback_data = callback_data; - - return (json_walk_args(json_string, json_string_length, args)); -} - -int json_walk_args(const char *json_string, int json_string_length, - const struct frozen_args *args) WEAK; - -int json_walk_args(const char *json_string, int json_string_length, - const struct frozen_args *args) -{ - struct frozen frozen[1]; - - memset(frozen, 0, sizeof(*frozen)); - frozen->end = json_string + json_string_length; - frozen->cur = json_string; - - if (args == NULL) { - frozen->limit = INT_MAX; - } else { - frozen->callback = args->callback; - frozen->callback_data = args->callback_data; - frozen->limit = args->limit; - } - - TRY(json_doit(frozen)); - - assert(frozen->limit == (args ? args->limit : INT_MAX)); - - return (frozen->cur - json_string); -} - -struct scan_array_info { - int found; - char path[JSON_MAX_PATH_LEN]; - struct json_token *token; -}; - -static void json_scanf_array_elem_cb(void *callback_data, const char *name, - size_t name_len, const char *path, - const struct json_token *token) { - struct scan_array_info *info = (struct scan_array_info *) callback_data; - - (void) name; - (void) name_len; - - if (strcmp(path, info->path) == 0) { - *info->token = *token; - info->found = 1; - } -} - -int json_scanf_array_elem(const char *s, int len, const char *path, int idx, - struct json_token *token) WEAK; -int json_scanf_array_elem(const char *s, int len, const char *path, int idx, - struct json_token *token) { - struct scan_array_info info; - info.token = token; - info.found = 0; - memset(token, 0, sizeof(*token)); - snprintf(info.path, sizeof(info.path), "%s[%d]", path, idx); - json_walk(s, len, json_scanf_array_elem_cb, &info); - return info.found ? token->len : -1; -} - -struct json_scanf_info { - int num_conversions; - char *path; - const char *fmt; - void *target; - void *user_data; - int type; -}; - -int json_unescape(const char *src, int slen, char *dst, int dlen) WEAK; -int json_unescape(const char *src, int slen, char *dst, int dlen) { - char *send = (char *) src + slen, *dend = dst + dlen, *orig_dst = dst, *p; - const char *esc1 = "\"\\/bfnrt", *esc2 = "\"\\/\b\f\n\r\t"; - - while (src < send) { - if (*src == '\\') { - if (++src >= send) return JSON_STRING_INCOMPLETE; - if (*src == 'u') { - if (send - src < 5) return JSON_STRING_INCOMPLETE; - /* Here we go: this is a \u.... escape. Process simple one-byte chars */ - if (src[1] == '0' && src[2] == '0') { - /* This is \u00xx character from the ASCII range */ - if (dst < dend) *dst = hexdec(src + 3); - src += 4; - } else { - /* Complex \uXXXX escapes drag utf8 lib... Do it at some stage */ - return JSON_STRING_INVALID; - } - } else if ((p = (char *) strchr(esc1, *src)) != NULL) { - if (dst < dend) *dst = esc2[p - esc1]; - } else { - return JSON_STRING_INVALID; - } - } else { - if (dst < dend) *dst = *src; - } - dst++; - src++; - } - - return dst - orig_dst; -} - -static void json_scanf_cb(void *callback_data, const char *name, - size_t name_len, const char *path, - const struct json_token *token) { - struct json_scanf_info *info = (struct json_scanf_info *) callback_data; - char buf[32]; /* Must be enough to hold numbers */ - - (void) name; - (void) name_len; - - if (token->ptr == NULL) { - /* - * We're not interested here in the events for which we have no value; - * namely, JSON_TYPE_OBJECT_START and JSON_TYPE_ARRAY_START - */ - return; - } - - if (strcmp(path, info->path) != 0) { - /* It's not the path we're looking for, so, just ignore this callback */ - return; - } - - switch (info->type) { - case 'B': - info->num_conversions++; - switch (sizeof(bool)) { - case sizeof(char): - *(char *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); - break; - case sizeof(int): - *(int *) info->target = (token->type == JSON_TYPE_TRUE ? 1 : 0); - break; - default: - /* should never be here */ - abort(); - } - break; - case 'M': { - union { - void *p; - json_scanner_t f; - } u = {info->target}; - info->num_conversions++; - u.f(token->ptr, token->len, info->user_data); - break; - } - case 'Q': { - char **dst = (char **) info->target; - if (token->type == JSON_TYPE_NULL) { - *dst = NULL; - } else { - int unescaped_len = json_unescape(token->ptr, token->len, NULL, 0); - if (unescaped_len >= 0 && - (*dst = (char *) malloc(unescaped_len + 1)) != NULL) { - info->num_conversions++; - if (json_unescape(token->ptr, token->len, *dst, unescaped_len) == - unescaped_len) { - (*dst)[unescaped_len] = '\0'; - } else { - free(*dst); - *dst = NULL; - } - } - } - break; - } - case 'H': { -#if JSON_ENABLE_HEX - char **dst = (char **) info->user_data; - int i, len = token->len / 2; - *(int *) info->target = len; - if ((*dst = (char *) malloc(len + 1)) != NULL) { - for (i = 0; i < len; i++) { - (*dst)[i] = hexdec(token->ptr + 2 * i); - } - (*dst)[len] = '\0'; - info->num_conversions++; - } -#endif /* JSON_ENABLE_HEX */ - break; - } - case 'V': { -#if JSON_ENABLE_BASE64 - char **dst = (char **) info->target; - int len = token->len * 4 / 3 + 2; - if ((*dst = (char *) malloc(len + 1)) != NULL) { - int n = b64dec(token->ptr, token->len, *dst); - (*dst)[n] = '\0'; - *(int *) info->user_data = n; - info->num_conversions++; - } -#endif /* JSON_ENABLE_BASE64 */ - break; - } - case 'T': - info->num_conversions++; - *(struct json_token *) info->target = *token; - break; - default: - if (token->len >= (int) sizeof(buf)) break; - /* Before converting, copy into tmp buffer in order to 0-terminate it */ - memcpy(buf, token->ptr, token->len); - buf[token->len] = '\0'; - /* NB: Use of base 0 for %d, %ld, %u and %lu is intentional. */ - if (info->fmt[1] == 'd' || (info->fmt[1] == 'l' && info->fmt[2] == 'd') || - info->fmt[1] == 'i') { - char *endptr = NULL; - long r = strtol(buf, &endptr, 0 /* base */); - if (*endptr == '\0') { - if (info->fmt[1] == 'l') { - *((long *) info->target) = r; - } else { - *((int *) info->target) = (int) r; - } - info->num_conversions++; - } - } else if (info->fmt[1] == 'u' || - (info->fmt[1] == 'l' && info->fmt[2] == 'u')) { - char *endptr = NULL; - unsigned long r = strtoul(buf, &endptr, 0 /* base */); - if (*endptr == '\0') { - if (info->fmt[1] == 'l') { - *((unsigned long *) info->target) = r; - } else { - *((unsigned int *) info->target) = (unsigned int) r; - } - info->num_conversions++; - } - } else { -#if !JSON_MINIMAL - info->num_conversions += sscanf(buf, info->fmt, info->target); -#endif - } - break; - } -} - -int json_vscanf(const char *s, int len, const char *fmt, va_list ap) WEAK; -int json_vscanf(const char *s, int len, const char *fmt, va_list ap) { - char path[JSON_MAX_PATH_LEN] = "", fmtbuf[20]; - int i = 0; - char *p = NULL; - struct json_scanf_info info = {0, path, fmtbuf, NULL, NULL, 0}; - - while (fmt[i] != '\0') { - if (fmt[i] == '{') { - strcat(path, "."); - i++; - } else if (fmt[i] == '}') { - if ((p = strrchr(path, '.')) != NULL) *p = '\0'; - i++; - } else if (fmt[i] == '%') { - info.target = va_arg(ap, void *); - info.type = fmt[i + 1]; - switch (fmt[i + 1]) { - case 'M': - case 'V': - case 'H': - info.user_data = va_arg(ap, void *); - /* FALLTHROUGH */ - case 'B': - case 'Q': - case 'T': - i += 2; - break; - default: { - const char *delims = ", \t\r\n]}"; - int conv_len = strcspn(fmt + i + 1, delims) + 1; - memcpy(fmtbuf, fmt + i, conv_len); - fmtbuf[conv_len] = '\0'; - i += conv_len; - if (fmt[i] != '}') - i += strspn(fmt + i, delims); - break; - } - } - json_walk(s, len, json_scanf_cb, &info); - } else if (json_isalpha(fmt[i]) || json_get_utf8_char_len(fmt[i]) > 1) { - char *pe; - const char *delims = ": \r\n\t"; - int key_len = strcspn(&fmt[i], delims); - if ((p = strrchr(path, '.')) != NULL) p[1] = '\0'; - pe = path + strlen(path); - memcpy(pe, fmt + i, key_len); - pe[key_len] = '\0'; - i += key_len + strspn(fmt + i + key_len, delims); - } else { - i++; - } - } - return info.num_conversions; -} - -int json_scanf(const char *str, int len, const char *fmt, ...) WEAK; -int json_scanf(const char *str, int len, const char *fmt, ...) { - int result; - va_list ap; - va_start(ap, fmt); - result = json_vscanf(str, len, fmt, ap); - va_end(ap); - return result; -} - -int json_vfprintf(const char *file_name, const char *fmt, va_list ap) WEAK; -int json_vfprintf(const char *file_name, const char *fmt, va_list ap) { - int res = -1; - FILE *fp = fopen(file_name, "wb"); - if (fp != NULL) { - struct json_out out = JSON_OUT_FILE(fp); - res = json_vprintf(&out, fmt, ap); - fputc('\n', fp); - fclose(fp); - } - return res; -} - -int json_fprintf(const char *file_name, const char *fmt, ...) WEAK; -int json_fprintf(const char *file_name, const char *fmt, ...) { - int result; - va_list ap; - va_start(ap, fmt); - result = json_vfprintf(file_name, fmt, ap); - va_end(ap); - return result; -} - -char *json_fread(const char *path) WEAK; -char *json_fread(const char *path) { - FILE *fp; - char *data = NULL; - if ((fp = fopen(path, "rb")) == NULL) { - } else if (fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - } else { - long size = ftell(fp); - if (size > 0 && (data = (char *) malloc(size + 1)) != NULL) { - fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */ - if (fread(data, 1, size, fp) != (size_t) size) { - free(data); - data = NULL; - } else { - data[size] = '\0'; - } - } - fclose(fp); - } - return data; -} - -struct json_setf_data { - const char *json_path; - const char *base; /* Pointer to the source JSON string */ - int matched; /* Matched part of json_path */ - int pos; /* Offset of the mutated value begin */ - int end; /* Offset of the mutated value end */ - int prev; /* Offset of the previous token end */ -}; - -static int get_matched_prefix_len(const char *s1, const char *s2) { - int i = 0; - while (s1[i] && s2[i] && s1[i] == s2[i]) i++; - return i; -} - -static void json_vsetf_cb(void *userdata, const char *name, size_t name_len, - const char *path, const struct json_token *t) { - struct json_setf_data *data = (struct json_setf_data *) userdata; - int off, len = get_matched_prefix_len(path, data->json_path); - if (t->ptr == NULL) return; - off = t->ptr - data->base; - if (len > data->matched) data->matched = len; - - /* - * If there is no exact path match, set the mutation position to tbe end - * of the object or array - */ - if (len < data->matched && data->pos == 0 && - (t->type == JSON_TYPE_OBJECT_END || t->type == JSON_TYPE_ARRAY_END)) { - data->pos = data->end = data->prev; - } - - /* Exact path match. Set mutation position to the value of this token */ - if (strcmp(path, data->json_path) == 0 && t->type != JSON_TYPE_OBJECT_START && - t->type != JSON_TYPE_ARRAY_START) { - data->pos = off; - data->end = off + t->len; - } - - /* - * For deletion, we need to know where the previous value ends, because - * we don't know where matched value key starts. - * When the mutation position is not yet set, remember each value end. - * When the mutation position is already set, but it is at the beginning - * of the object/array, we catch the end of the object/array and see - * whether the object/array start is closer then previously stored prev. - */ - if (data->pos == 0) { - data->prev = off + t->len; /* pos is not yet set */ - } else if ((t->ptr[0] == '[' || t->ptr[0] == '{') && off + 1 < data->pos && - off + 1 > data->prev) { - data->prev = off + 1; - } - (void) name; - (void) name_len; -} - -int json_vsetf(const char *s, int len, struct json_out *out, - const char *json_path, const char *json_fmt, va_list ap) WEAK; -int json_vsetf(const char *s, int len, struct json_out *out, - const char *json_path, const char *json_fmt, va_list ap) { - struct json_setf_data data; - memset(&data, 0, sizeof(data)); - data.json_path = json_path; - data.base = s; - data.end = len; - json_walk(s, len, json_vsetf_cb, &data); - if (json_fmt == NULL) { - /* Deletion codepath */ - json_printf(out, "%.*s", data.prev, s); - /* Trim comma after the value that begins at object/array start */ - if (s[data.prev - 1] == '{' || s[data.prev - 1] == '[') { - int i = data.end; - while (i < len && json_isspace(s[i])) i++; - if (s[i] == ',') data.end = i + 1; /* Point after comma */ - } - json_printf(out, "%.*s", len - data.end, s + data.end); - } else { - /* Modification codepath */ - int n, off = data.matched, depth = 0; - - /* Print the unchanged beginning */ - json_printf(out, "%.*s", data.pos, s); - - /* Add missing keys */ - while ((n = strcspn(&json_path[off], ".[")) > 0) { - if (s[data.prev - 1] != '{' && s[data.prev - 1] != '[' && depth == 0) { - json_printf(out, ","); - } - if (off > 0 && json_path[off - 1] != '.') break; - json_printf(out, "%.*Q:", n, json_path + off); - off += n; - if (json_path[off] != '\0') { - json_printf(out, "%c", json_path[off] == '.' ? '{' : '['); - depth++; - off++; - } - } - /* Print the new value */ - json_vprintf(out, json_fmt, ap); - - /* Close brackets/braces of the added missing keys */ - for (; off > data.matched; off--) { - int ch = json_path[off]; - const char *p = ch == '.' ? "}" : ch == '[' ? "]" : ""; - json_printf(out, "%s", p); - } - - /* Print the rest of the unchanged string */ - json_printf(out, "%.*s", len - data.end, s + data.end); - } - return data.end > data.pos ? 1 : 0; -} - -int json_setf(const char *s, int len, struct json_out *out, - const char *json_path, const char *json_fmt, ...) WEAK; -int json_setf(const char *s, int len, struct json_out *out, - const char *json_path, const char *json_fmt, ...) { - int result; - va_list ap; - va_start(ap, json_fmt); - result = json_vsetf(s, len, out, json_path, json_fmt, ap); - va_end(ap); - return result; -} - -struct prettify_data { - struct json_out *out; - int level; - int last_token; -}; - -static void indent(struct json_out *out, int level) { - while (level-- > 0) out->printer(out, " ", 2); -} - -static void print_key(struct prettify_data *pd, const char *path, - const char *name, int name_len) { - if (pd->last_token != JSON_TYPE_INVALID && - pd->last_token != JSON_TYPE_ARRAY_START && - pd->last_token != JSON_TYPE_OBJECT_START) { - pd->out->printer(pd->out, ",", 1); - } - if (path[0] != '\0') pd->out->printer(pd->out, "\n", 1); - indent(pd->out, pd->level); - if (path[0] != '\0' && path[strlen(path) - 1] != ']') { - pd->out->printer(pd->out, "\"", 1); - pd->out->printer(pd->out, name, (int) name_len); - pd->out->printer(pd->out, "\"", 1); - pd->out->printer(pd->out, ": ", 2); - } -} - -static void prettify_cb(void *userdata, const char *name, size_t name_len, - const char *path, const struct json_token *t) { - struct prettify_data *pd = (struct prettify_data *) userdata; - switch (t->type) { - case JSON_TYPE_OBJECT_START: - case JSON_TYPE_ARRAY_START: - print_key(pd, path, name, name_len); - pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_START ? "[" : "{", - 1); - pd->level++; - break; - case JSON_TYPE_OBJECT_END: - case JSON_TYPE_ARRAY_END: - pd->level--; - if (pd->last_token != JSON_TYPE_INVALID && - pd->last_token != JSON_TYPE_ARRAY_START && - pd->last_token != JSON_TYPE_OBJECT_START) { - pd->out->printer(pd->out, "\n", 1); - indent(pd->out, pd->level); - } - pd->out->printer(pd->out, t->type == JSON_TYPE_ARRAY_END ? "]" : "}", 1); - break; - case JSON_TYPE_NUMBER: - case JSON_TYPE_NULL: - case JSON_TYPE_TRUE: - case JSON_TYPE_FALSE: - case JSON_TYPE_STRING: - print_key(pd, path, name, name_len); - if (t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); - pd->out->printer(pd->out, t->ptr, t->len); - if (t->type == JSON_TYPE_STRING) pd->out->printer(pd->out, "\"", 1); - break; - default: - break; - } - pd->last_token = t->type; -} - -int json_prettify(const char *s, int len, struct json_out *out) WEAK; -int json_prettify(const char *s, int len, struct json_out *out) { - struct prettify_data pd = {out, 0, JSON_TYPE_INVALID}; - return json_walk(s, len, prettify_cb, &pd); -} - -int json_prettify_file(const char *file_name) WEAK; -int json_prettify_file(const char *file_name) { - int res = -1; - char *s = json_fread(file_name); - FILE *fp; - if (s != NULL && (fp = fopen(file_name, "wb")) != NULL) { - struct json_out out = JSON_OUT_FILE(fp); - res = json_prettify(s, strlen(s), &out); - if (res < 0) { - /* On error, restore the old content */ - fclose(fp); - fp = fopen(file_name, "wb"); - fseek(fp, 0, SEEK_SET); - fwrite(s, 1, strlen(s), fp); - } else { - fputc('\n', fp); - } - fclose(fp); - } - free(s); - return res; -} - -struct next_data { - void *handle; // Passed handle. Changed if a next entry is found - const char *path; // Path to the iterated object/array - int path_len; // Path length - optimisation - int found; // Non-0 if found the next entry - struct json_token *key; // Object's key - struct json_token *val; // Object's value - int *idx; // Array index -}; - -static void next_set_key(struct next_data *d, const char *name, int name_len, - int is_array) { - if (is_array) { - /* Array. Set index and reset key */ - if (d->key != NULL) { - d->key->len = 0; - d->key->ptr = NULL; - } - if (d->idx != NULL) *d->idx = atoi(name); - } else { - /* Object. Set key and make index -1 */ - if (d->key != NULL) { - d->key->ptr = name; - d->key->len = name_len; - } - if (d->idx != NULL) *d->idx = -1; - } -} - -static void json_next_cb(void *userdata, const char *name, size_t name_len, - const char *path, const struct json_token *t) { - struct next_data *d = (struct next_data *) userdata; - const char *p = path + d->path_len; - if (d->found) return; - if (d->path_len >= (int) strlen(path)) return; - if (strncmp(d->path, path, d->path_len) != 0) return; - if (strchr(p + 1, '.') != NULL) return; /* More nested objects - skip */ - if (strchr(p + 1, '[') != NULL) return; /* Ditto for arrays */ - // {OBJECT,ARRAY}_END types do not pass name, _START does. Save key. - if (t->type == JSON_TYPE_OBJECT_START || t->type == JSON_TYPE_ARRAY_START) { - next_set_key(d, name, name_len, p[0] == '['); - } else if (d->handle == NULL || d->handle < (void *) t->ptr) { - if (t->type != JSON_TYPE_OBJECT_END && t->type != JSON_TYPE_ARRAY_END) { - next_set_key(d, name, name_len, p[0] == '['); - } - if (d->val != NULL) *d->val = *t; - d->handle = (void *) t->ptr; - d->found = 1; - } -} - -static void *json_next(const char *s, int len, void *handle, const char *path, - struct json_token *key, struct json_token *val, int *i) { - struct json_token tmpval, *v = val == NULL ? &tmpval : val; - struct json_token tmpkey, *k = key == NULL ? &tmpkey : key; - int tmpidx, *pidx = i == NULL ? &tmpidx : i; - struct next_data data = {handle, path, (int) strlen(path), 0, k, v, pidx}; - json_walk(s, len, json_next_cb, &data); - return data.found ? data.handle : NULL; -} - -void *json_next_key(const char *s, int len, void *handle, const char *path, - struct json_token *key, struct json_token *val) WEAK; -void *json_next_key(const char *s, int len, void *handle, const char *path, - struct json_token *key, struct json_token *val) { - return json_next(s, len, handle, path, key, val, NULL); -} - -void *json_next_elem(const char *s, int len, void *handle, const char *path, - int *idx, struct json_token *val) WEAK; -void *json_next_elem(const char *s, int len, void *handle, const char *path, - int *idx, struct json_token *val) { - return json_next(s, len, handle, path, NULL, val, idx); -} - -static int json_sprinter(struct json_out *out, const char *str, size_t len) { - size_t old_len = out->u.buf.buf == NULL ? 0 : strlen(out->u.buf.buf); - size_t new_len = len + old_len; - char *p = (char *) realloc(out->u.buf.buf, new_len + 1); - if (p != NULL) { - memcpy(p + old_len, str, len); - p[new_len] = '\0'; - out->u.buf.buf = p; - } - return len; -} - -char *json_vasprintf(const char *fmt, va_list ap) WEAK; -char *json_vasprintf(const char *fmt, va_list ap) { - struct json_out out; - memset(&out, 0, sizeof(out)); - out.printer = json_sprinter; - json_vprintf(&out, fmt, ap); - return out.u.buf.buf; -} - -char *json_asprintf(const char *fmt, ...) WEAK; -char *json_asprintf(const char *fmt, ...) { - char *result = NULL; - va_list ap; - va_start(ap, fmt); - result = json_vasprintf(fmt, ap); - va_end(ap); - return result; -} \ No newline at end of file diff --git a/internal/native/cgo/frozen.h b/internal/native/cgo/frozen.h deleted file mode 100644 index 5da411f..0000000 --- a/internal/native/cgo/frozen.h +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright (c) 2004-2013 Sergey Lyubka - * Copyright (c) 2018 Cesanta Software Limited - * All rights reserved - * - * 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. - */ - -#ifndef CS_FROZEN_FROZEN_H_ -#define CS_FROZEN_FROZEN_H_ - -#ifdef __cplusplus -extern "C" { -#endif /* __cplusplus */ - -#include -#include -#include -#include - -#if defined(_WIN32) && _MSC_VER < 1700 -typedef int bool; -enum { false = 0, true = 1 }; -#else -#include -#endif - -/* JSON token type */ -enum json_token_type { - JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */ - JSON_TYPE_STRING, - JSON_TYPE_NUMBER, - JSON_TYPE_TRUE, - JSON_TYPE_FALSE, - JSON_TYPE_NULL, - JSON_TYPE_OBJECT_START, - JSON_TYPE_OBJECT_END, - JSON_TYPE_ARRAY_START, - JSON_TYPE_ARRAY_END, - - JSON_TYPES_CNT -}; - -/* - * Structure containing token type and value. Used in `json_walk()` and - * `json_scanf()` with the format specifier `%T`. - */ -struct json_token { - const char *ptr; /* Points to the beginning of the value */ - int len; /* Value length */ - enum json_token_type type; /* Type of the token, possible values are above */ -}; - -#define JSON_INVALID_TOKEN \ - { 0, 0, JSON_TYPE_INVALID } - -/* Error codes */ -#define JSON_STRING_INVALID -1 -#define JSON_STRING_INCOMPLETE -2 -#define JSON_DEPTH_LIMIT -3 - -/* - * Callback-based SAX-like API. - * - * Property name and length is given only if it's available: i.e. if current - * event is an object's property. In other cases, `name` is `NULL`. For - * example, name is never given: - * - For the first value in the JSON string; - * - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END - * - * E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`, - * the sequence of callback invocations will be as follows: - * - * - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL - * - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123" - * - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL - * - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1" - * - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2" - * - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL - * - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true" - * - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\": - *true }" - * - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, { - *\"baz\": true } ]" - * - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123, - *\"bar\": [ 1, 2, { \"baz\": true } ] }" - */ -typedef void (*json_walk_callback_t)(void *callback_data, const char *name, - size_t name_len, const char *path, - const struct json_token *token); - -/* - * Parse `json_string`, invoking `callback` in a way similar to SAX parsers; - * see `json_walk_callback_t`. - * Return number of processed bytes, or a negative error code. - */ -int json_walk(const char *json_string, int json_string_length, - json_walk_callback_t callback, void *callback_data); - -/* - * Extensible argument passing interface - */ - -struct frozen_args { - json_walk_callback_t callback; - void *callback_data; - int limit; -}; - -int json_walk_args(const char *json_string, int json_string_length, - const struct frozen_args *args); - -#define INIT_FROZEN_ARGS(ptr) do { \ - memset((ptr), 0, sizeof(*(ptr))); \ - (ptr)->limit = INT_MAX; \ - } while(0) - -/* - * JSON generation API. - * struct json_out abstracts output, allowing alternative printing plugins. - */ -struct json_out { - int (*printer)(struct json_out *, const char *str, size_t len); - union { - struct { - char *buf; - size_t size; - size_t len; - } buf; - void *data; - FILE *fp; - } u; -}; - -extern int json_printer_buf(struct json_out *, const char *, size_t); -extern int json_printer_file(struct json_out *, const char *, size_t); - -#define JSON_OUT_BUF(buf, len) \ - { \ - json_printer_buf, { \ - { buf, len, 0 } \ - } \ - } -#define JSON_OUT_FILE(fp) \ - { \ - json_printer_file, { \ - { (char *) fp, 0, 0 } \ - } \ - } - -typedef int (*json_printf_callback_t)(struct json_out *, va_list *ap); - -/* - * Generate formatted output into a given sting buffer. - * This is a superset of printf() function, with extra format specifiers: - * - `%B` print json boolean, `true` or `false`. Accepts an `int`. - * - `%Q` print quoted escaped string or `null`. Accepts a `const char *`. - * - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *` - * - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`. - * - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`. - * - `%M` invokes a json_printf_callback_t function. That callback function - * can consume more parameters. - * - * Return number of bytes printed. If the return value is bigger than the - * supplied buffer, that is an indicator of overflow. In the overflow case, - * overflown bytes are not printed. - */ -int json_printf(struct json_out *, const char *fmt, ...); -int json_vprintf(struct json_out *, const char *fmt, va_list ap); - -/* - * Same as json_printf, but prints to a file. - * File is created if does not exist. File is truncated if already exists. - */ -int json_fprintf(const char *file_name, const char *fmt, ...); -int json_vfprintf(const char *file_name, const char *fmt, va_list ap); - -/* - * Print JSON into an allocated 0-terminated string. - * Return allocated string, or NULL on error. - * Example: - * - * ```c - * char *str = json_asprintf("{a:%H}", 3, "abc"); - * printf("%s\n", str); // Prints "616263" - * free(str); - * ``` - */ -char *json_asprintf(const char *fmt, ...); -char *json_vasprintf(const char *fmt, va_list ap); - -/* - * Helper %M callback that prints contiguous C arrays. - * Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt - * Return number of bytes printed. - */ -int json_printf_array(struct json_out *, va_list *ap); - -/* - * Scan JSON string `str`, performing scanf-like conversions according to `fmt`. - * This is a `scanf()` - like function, with following differences: - * - * 1. Object keys in the format string may be not quoted, e.g. "{key: %d}" - * 2. Order of keys in an object is irrelevant. - * 3. Several extra format specifiers are supported: - * - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`), - * expects boolean `true` or `false`. - * - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned - * string is malloc-ed, caller must free() the string. - * - %V: consumes `char **`, `int *`. Expects base64-encoded string. - * Result string is base64-decoded, malloced and NUL-terminated. - * The length of result string is stored in `int *` placeholder. - * Caller must free() the result. - * - %H: consumes `int *`, `char **`. - * Expects a hex-encoded string, e.g. "fa014f". - * Result string is hex-decoded, malloced and NUL-terminated. - * The length of the result string is stored in `int *` placeholder. - * Caller must free() the result. - * - %M: consumes custom scanning function pointer and - * `void *user_data` parameter - see json_scanner_t definition. - * - %T: consumes `struct json_token *`, fills it out with matched token. - * - * Return number of elements successfully scanned & converted. - * Negative number means scan error. - */ -int json_scanf(const char *str, int str_len, const char *fmt, ...); -int json_vscanf(const char *str, int str_len, const char *fmt, va_list ap); - -/* json_scanf's %M handler */ -typedef void (*json_scanner_t)(const char *str, int len, void *user_data); - -/* - * Helper function to scan array item with given path and index. - * Fills `token` with the matched JSON token. - * Return -1 if no array element found, otherwise non-negative token length. - */ -int json_scanf_array_elem(const char *s, int len, const char *path, int index, - struct json_token *token); - -/* - * Unescape JSON-encoded string src,slen into dst, dlen. - * src and dst may overlap. - * If destination buffer is too small (or zero-length), result string is not - * written but the length is counted nevertheless (similar to snprintf). - * Return the length of unescaped string in bytes. - */ -int json_unescape(const char *src, int slen, char *dst, int dlen); - -/* - * Escape a string `str`, `str_len` into the printer `out`. - * Return the number of bytes printed. - */ -int json_escape(struct json_out *out, const char *str, size_t str_len); - -/* - * Read the whole file in memory. - * Return malloc-ed file content, or NULL on error. The caller must free(). - */ -char *json_fread(const char *file_name); - -/* - * Update given JSON string `s,len` by changing the value at given `json_path`. - * The result is saved to `out`. If `json_fmt` == NULL, that deletes the key. - * If path is not present, missing keys are added. Array path without an - * index pushes a value to the end of an array. - * Return 1 if the string was changed, 0 otherwise. - * - * Example: s is a JSON string { "a": 1, "b": [ 2 ] } - * json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] } - * json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 } - * json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] } - * json_setf(s, len, out, ".b", NULL); // { "a": 1 } - */ -int json_setf(const char *s, int len, struct json_out *out, - const char *json_path, const char *json_fmt, ...); - -int json_vsetf(const char *s, int len, struct json_out *out, - const char *json_path, const char *json_fmt, va_list ap); - -/* - * Pretty-print JSON string `s,len` into `out`. - * Return number of processed bytes in `s`. - */ -int json_prettify(const char *s, int len, struct json_out *out); - -/* - * Prettify JSON file `file_name`. - * Return number of processed bytes, or negative number of error. - * On error, file content is not modified. - */ -int json_prettify_file(const char *file_name); - -/* - * Iterate over an object at given JSON `path`. - * On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL - * for `key`, or `val`, in which case they won't be populated. - * Return an opaque value suitable for the next iteration, or NULL when done. - * - * Example: - * - * ```c - * void *h = NULL; - * struct json_token key, val; - * while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) { - * printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr); - * } - * ``` - */ -void *json_next_key(const char *s, int len, void *handle, const char *path, - struct json_token *key, struct json_token *val); - -/* - * Iterate over an array at given JSON `path`. - * Similar to `json_next_key`, but fills array index `idx` instead of `key`. - */ -void *json_next_elem(const char *s, int len, void *handle, const char *path, - int *idx, struct json_token *val); - -#ifndef JSON_MAX_PATH_LEN -#define JSON_MAX_PATH_LEN 256 -#endif - -#ifndef JSON_MINIMAL -#define JSON_MINIMAL 0 -#endif - -#ifndef JSON_ENABLE_BASE64 -#define JSON_ENABLE_BASE64 !JSON_MINIMAL -#endif - -#ifndef JSON_ENABLE_HEX -#define JSON_ENABLE_HEX !JSON_MINIMAL -#endif - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* CS_FROZEN_FROZEN_H_ */ \ No newline at end of file diff --git a/internal/native/cgo/log.h b/internal/native/cgo/log.h new file mode 100644 index 0000000..1b4a255 --- /dev/null +++ b/internal/native/cgo/log.h @@ -0,0 +1,130 @@ +#ifndef VIDEO_DAEMON_LOG_H +#define VIDEO_DAEMON_LOG_H + +#include +#include +#include +#include "log_handler.h" + +/* Default level */ +#ifndef LOG_LEVEL + #define LOG_LEVEL LEVEL_INFO +#endif + +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) + +void jetkvm_log(const char *message); + +/* Log to screen */ +#define emit_log(level, file, func, line, ...) do { \ + /* call the log handler */ \ + char msg_buffer[1024]; \ + sprintf(msg_buffer, __VA_ARGS__); \ + log_message(level, file, func, line, msg_buffer); \ +} while (0) + // /* display the level */ \ + // printf("%10s%s", level, " "); \ + // \ + // /* display the function doing the logging */ \ + // printf("%s%s", func, " " : ""); \ + // \ + // /* display the file and/or the line number */ \ + // printf( \ + // "%s%s%s%.d%s%s", \ + // DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? "(" : "", \ + // DISPLAY_FILE ? file : "", \ + // DISPLAY_FILE && DISPLAY_LINE ? ":" : "", \ + // DISPLAY_LINE ? line : 0, \ + // DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? ") " : "", \ + // !DISPLAY_FUNC && (DISPLAY_FILE || DISPLAY_LINE) ? " " : "" \ + // ); \ + // \ + // /* display message border */ \ + // printf("%s%s", DISPLAY_BORDER ? BORDER : "", DISPLAY_BORDER ? " " : ""); \ + // \ + // /* display the callee's message */ \ + // if (DISPLAY_MESSAGE) printf(__VA_ARGS__); \ + // \ + // /* add the message ending (usually '\n') */ \ + // printf("%s", DISPLAY_ENDING ? MSG_ENDING : ""); \ + // \ + // /* reset the colour */ \ + // printf("%s", DISPLAY_RESET ? RESET_COLOUR : ""); \ + // \ + // /* add a newline */ \ + // printf("\n"); \ + // fflush(stdout); \ + +/* Level enum */ +#define LEVEL_PANIC 5 +#define LEVEL_FATAL 4 +#define LEVEL_ERROR 3 +#define LEVEL_WARN 2 +#define LEVEL_INFO 1 +#define LEVEL_DEBUG 0 +#define LEVEL_TRACE -1 + +/* TRACE LOG */ +#define log_trace(...) do { \ + if (LOG_LEVEL <= LEVEL_TRACE) { \ + emit_log( \ + LEVEL_TRACE, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \ + ); \ + } \ +} while (0) + +/* DEBUG LOG */ +#define log_debug(...) do { \ + if (LOG_LEVEL <= LEVEL_DEBUG) { \ + emit_log( \ + LEVEL_DEBUG, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \ + ); \ + } \ +} while (0) + +/* INFO LOG */ +#define log_info(...) do { \ + if (LOG_LEVEL <= LEVEL_INFO) { \ + emit_log( \ + LEVEL_INFO, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \ + ); \ + } \ +} while (0) + +/* NOTICE LOG */ +#define log_notice(...) do { \ + if (LOG_LEVEL <= LEVEL_NOTICE) { \ + emit_log( \ + LEVEL_NOTICE, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \ + ); \ + } \ +} while (0) + +/* WARN LOG */ +#define log_warn(...) do { \ + if (LOG_LEVEL <= LEVEL_WARN) { \ + emit_log( \ + LEVEL_WARN, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \ + ); \ + } \ +} while (0) + +/* ERROR LOG */ +#define log_error(...) do { \ + if (LOG_LEVEL <= LEVEL_ERROR) { \ + emit_log( \ + LEVEL_ERROR, __FILENAME__, __func__, __LINE__, __VA_ARGS__ \ + ); \ + } \ +} while (0) + +/* PANIC LOG */ +#define log_panic(...) do { \ + if (LOG_LEVEL <= LEVEL_PANIC) { \ + emit_log( \ + LEVEL_PANIC, __FILENAME__, __func__, __LINE__, __VA_ARGS__\ + ); \ + } \ +} while (0) + +#endif //VIDEO_DAEMON_LOG_H diff --git a/internal/native/cgo/log_handler.c b/internal/native/cgo/log_handler.c new file mode 100644 index 0000000..89694dd --- /dev/null +++ b/internal/native/cgo/log_handler.c @@ -0,0 +1,15 @@ +#include +#include "log_handler.h" + +/* Log handler */ +jetkvm_log_handler_t *log_handler = NULL; + +void log_message(int level, const char *filename, const char *funcname, const int line, const char *message) { + if (log_handler != NULL) { + log_handler(level, filename, funcname, line, message); + } +} + +void log_set_handler(jetkvm_log_handler_t *handler) { + log_handler = handler; +} \ No newline at end of file diff --git a/internal/native/cgo/log_handler.h b/internal/native/cgo/log_handler.h new file mode 100644 index 0000000..e019aa9 --- /dev/null +++ b/internal/native/cgo/log_handler.h @@ -0,0 +1,9 @@ +#ifndef LOG_HANDLER_H +#define LOG_HANDLER_H + +typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, const int line, const char *message); +void log_message(int level, const char *filename, const char *funcname, const int line, const char *message); + +void log_set_handler(jetkvm_log_handler_t *handler); + +#endif \ No newline at end of file diff --git a/internal/native/cgo/screen.c b/internal/native/cgo/screen.c index ae2a8b6..a5d807a 100644 --- a/internal/native/cgo/screen.c +++ b/internal/native/cgo/screen.c @@ -3,6 +3,7 @@ #include #include +#include "log.h" #include "screen.h" #include #include @@ -17,7 +18,10 @@ static lv_disp_drv_t disp_drv; static lv_indev_drv_t indev_drv; void lvgl_init(void) { + log_trace("initalizing lvgl"); lv_init(); + + log_trace("initalizing fbdev"); fbdev_init(); lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE); lv_disp_drv_init(&disp_drv); @@ -31,6 +35,7 @@ void lvgl_init(void) { lv_disp_drv_register(&disp_drv); + log_trace("initalizing evdev"); evdev_init(); evdev_set_file("/dev/input/event1"); @@ -39,7 +44,10 @@ void lvgl_init(void) { indev_drv.read_cb = evdev_read; lv_indev_drv_register(&indev_drv); + log_trace("initalizing ui"); ui_init(); + + log_info("ui initalized"); // lv_label_set_text(ui_Boot_Screen_Version, ""); // lv_label_set_text(ui_Home_Content_Ip, "..."); // lv_label_set_text(ui_Home_Header_Cloud_Status_Label, "0 active"); @@ -102,7 +110,7 @@ lv_img_dsc_t *ui_get_image(const char *name) { void ui_set_text(const char *name, const char *text) { lv_obj_t *obj = ui_get_obj(name); if(obj == NULL) { - printf("ui_set_text %s %s, obj not found\n", name, text); + log_error("ui_set_text %s %s, obj not found\n", name, text); return; } lv_label_set_text(obj, text); diff --git a/internal/native/cgo/video.c b/internal/native/cgo/video.c index 173b559..4ab721c 100644 --- a/internal/native/cgo/video.c +++ b/internal/native/cgo/video.c @@ -24,6 +24,7 @@ #include #include "video.h" #include "ctrl.h" +#include "log.h" #define VIDEO_DEV "/dev/video0" #define SUB_DEV "/dev/v4l-subdev2" @@ -181,75 +182,39 @@ static int32_t buf_init() { return -1; } - printf("Created memory pool\n"); + log_info("created memory pool"); return RK_SUCCESS; } pthread_t *format_thread = NULL; -int video_client_fd = 0; - -int connect_video_client(const char *path) -{ - int client_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0); - if (client_fd == -1) - { - perror("can not create socket"); - return -1; - } - - struct sockaddr_un addr; - memset(&addr, 0, sizeof(struct sockaddr_un)); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); - - if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) - { - perror("can not connect to video socket"); - close(client_fd); - return -1; - } - video_client_fd = client_fd; - return 0; -} - -int socket_send_frame(const uint8_t *frame, ssize_t len) -{ - if (video_client_fd <= 0) - { - return -1; - } - return send(video_client_fd, frame, len, 0); -} - int video_init() { - if (connect_video_client("/var/run/jetkvm_video.sock") != 0) + if (RK_MPI_SYS_Init() != RK_SUCCESS) { - printf("can not connect to video socket\n"); - return -1; + log_error("RK_MPI_SYS_Init failed"); + return RK_FAILURE; } - printf("Connected to video socket\n"); if (sub_dev_fd < 0) { sub_dev_fd = open(SUB_DEV, O_RDWR); if (sub_dev_fd < 0) { - printf("failed to open control sub device %s: %s\n", SUB_DEV, strerror(errno)); + log_error("failed to open control sub device %s: %s", SUB_DEV, strerror(errno)); return errno; } - printf("Opened control sub device %s\n", SUB_DEV); + log_info("opened control sub device %s", SUB_DEV); } int32_t ret = buf_init(); if (ret != RK_SUCCESS) { - RK_LOGE("buf_init failed with error: %d", ret); + log_error("buf_init failed with error: %d", ret); return ret; } - printf("buf_init completed successfully\n"); + log_info("buf_init completed successfully"); format_thread = malloc(sizeof(pthread_t)); pthread_create(format_thread, NULL, run_detect_format, NULL); @@ -315,7 +280,7 @@ static void *venc_read_stream(void *arg) s32Ret = RK_MPI_VENC_ReleaseStream(VENC_CHANNEL, &stFrame); if (s32Ret != RK_SUCCESS) { - RK_LOGE("RK_MPI_VENC_ReleaseStream fail %x", s32Ret); + log_error("RK_MPI_VENC_ReleaseStream fail %x", s32Ret); } loopCount++; } @@ -325,11 +290,11 @@ static void *venc_read_stream(void *arg) { continue; } - RK_LOGE("RK_MPI_VENC_GetStream fail %x", s32Ret); + log_error("RK_MPI_VENC_GetStream fail %x", s32Ret); break; } } - printf("exiting venc_read_stream\n"); + log_info("exiting venc_read_stream"); free(stFrame.pstPack); return NULL; } @@ -350,6 +315,8 @@ void *run_video_stream(void *arg) { enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + log_info("running video stream"); + while (streaming_flag) { if (detected_signal == false) @@ -361,11 +328,11 @@ void *run_video_stream(void *arg) int video_dev_fd = open(VIDEO_DEV, O_RDWR); if (video_dev_fd < 0) { - printf("failed to open video capture device %s: %s\n", VIDEO_DEV, strerror(errno)); + log_error("failed to open video capture device %s: %s", VIDEO_DEV, strerror(errno)); usleep(1000000); continue; } - printf("Opened video capture device %s\n", VIDEO_DEV); + log_info("opened video capture device %s", VIDEO_DEV); uint32_t width = detected_width; uint32_t height = detected_height; @@ -397,10 +364,10 @@ void *run_video_stream(void *arg) perror("VIDIOC_REQBUFS failed"); return errno; } - printf("VIDIOC_REQBUFS successful\n"); + log_info("VIDIOC_REQBUFS successful"); struct buffer buffers[3] = {}; - printf("Allocated buffers\n"); + log_info("allocated buffers"); for (int i = 0; i < input_buffer_count; i++) { @@ -475,7 +442,7 @@ void *run_video_stream(void *arg) RK_S32 ret = venc_start(bitrate, bitrate * 2, width, height); if (ret != RK_SUCCESS) { - RK_LOGE("Set VENC parameters failed with %#x", ret); + log_error("Set VENC parameters failed with %#x", ret); goto cleanup; } @@ -495,7 +462,7 @@ void *run_video_stream(void *arg) r = select(video_dev_fd + 1, &fds, NULL, NULL, &tv); if (r == 0) { - printf("select timeout \n"); + log_info("select timeout \n"); break; } if (r == -1) @@ -632,7 +599,7 @@ void video_start_streaming() { if (streaming_thread != NULL) { - printf("video streaming already started\n"); + log_info("video streaming already started"); return; } streaming_thread = malloc(sizeof(pthread_t)); @@ -649,7 +616,7 @@ void video_stop_streaming() pthread_join(*streaming_thread, NULL); free(streaming_thread); streaming_thread = NULL; - printf("video streaming stopped\n"); + log_info("video streaming stopped"); } } @@ -663,6 +630,7 @@ void *run_detect_format(void *arg) sub.type = V4L2_EVENT_SOURCE_CHANGE; if (ioctl(sub_dev_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) == -1) { + log_error("cannot subscribe to event"); perror("Cannot subscribe to event"); goto exit; } @@ -676,13 +644,13 @@ void *run_detect_format(void *arg) if (errno == ENOLINK) { // No timings could be detected because no signal was found. - printf("HDMI status: no signal\n"); + log_info("HDMI status: no signal"); report_video_format(false, "no_signal", 0, 0, 0); } else if (errno == ENOLCK) { // The signal was unstable and the hardware could not lock on to it. - printf("HDMI status: no lock\n"); + log_info("HDMI status: no lock"); report_video_format(false, "no_lock", 0, 0, 0); } else if (errno == ERANGE) @@ -700,21 +668,21 @@ void *run_detect_format(void *arg) } else { - printf("Active width: %d\n", dv_timings.bt.width); - printf("Active height: %d\n", dv_timings.bt.height); + log_info("Active width: %d", dv_timings.bt.width); + log_info("Active height: %d", dv_timings.bt.height); double frames_per_second = (double)dv_timings.bt.pixelclock / ((dv_timings.bt.height + dv_timings.bt.vfrontporch + dv_timings.bt.vsync + dv_timings.bt.vbackporch) * (dv_timings.bt.width + dv_timings.bt.hfrontporch + dv_timings.bt.hsync + dv_timings.bt.hbackporch)); - printf("Frames per second: %.2f fps\n", frames_per_second); + log_info("Frames per second: %.2f fps", frames_per_second); detected_width = dv_timings.bt.width; detected_height = dv_timings.bt.height; detected_signal = true; report_video_format(true, NULL, detected_width, detected_height, frames_per_second); if (streaming_flag == true) { - printf("restarting on going video streaming\n"); + log_info("restarting on going video streaming"); video_stop_streaming(); video_start_streaming(); } @@ -723,15 +691,16 @@ void *run_detect_format(void *arg) memset(&ev, 0, sizeof(ev)); if (ioctl(sub_dev_fd, VIDIOC_DQEVENT, &ev) != 0) { + log_error("failed to VIDIOC_DQEVENT"); perror("failed to VIDIOC_DQEVENT"); break; } - printf("New event of type %u\n", ev.type); + log_info("New event of type %u", ev.type); if (ev.type != V4L2_EVENT_SOURCE_CHANGE) { continue; } - printf("source change detected!\n"); + log_info("source change detected!"); } exit: close(sub_dev_fd); @@ -747,8 +716,12 @@ void video_set_quality_factor(float factor) if (streaming_flag == true) { - printf("restarting on going video streaming due to quality factor change\n"); + log_info("restarting on going video streaming due to quality factor change"); video_stop_streaming(); video_start_streaming(); } } + +float video_get_quality_factor() { + return quality_factor; +} \ No newline at end of file diff --git a/internal/native/cgo/video.h b/internal/native/cgo/video.h index dfd049b..e5e1d1a 100644 --- a/internal/native/cgo/video.h +++ b/internal/native/cgo/video.h @@ -8,5 +8,6 @@ void video_start_streaming(); void video_stop_streaming(); void video_set_quality_factor(float factor); +float video_get_quality_factor(); #endif //VIDEO_DAEMON_VIDEO_H diff --git a/internal/native/ctrl_linux.go b/internal/native/ctrl_linux.go index fb674f9..382700f 100644 --- a/internal/native/ctrl_linux.go +++ b/internal/native/ctrl_linux.go @@ -5,34 +5,128 @@ package native import ( "fmt" "strconv" + "sync" "time" "unsafe" + + "github.com/rs/zerolog" ) -// #cgo LDFLAGS: -Ljetkvm-native +// #cgo LDFLAGS: -Lcgo/build -Lcgo/build/lib -ljknative -llvgl +// #cgo CFLAGS: -Icgo -Icgo/ui -Icgo/build/_deps/lvgl-src // #include "ctrl.h" // #include +// typedef const char cchar_t; +// typedef const uint8_t cuint8_t; // extern void jetkvm_video_state_handler(jetkvm_video_state_t *state); // static inline void jetkvm_setup_video_state_handler() { // jetkvm_set_video_state_handler(&jetkvm_video_state_handler); // } +// extern void jetkvm_go_log_handler(int level, cchar_t *filename, cchar_t *funcname, int line, cchar_t *message); +// static inline void jetkvm_setup_log_handler() { +// jetkvm_set_log_handler(&jetkvm_go_log_handler); +// } +// extern void jetkvm_video_handler(cuint8_t *frame, ssize_t len); +// static inline void jetkvm_setup_video_handler() { +// jetkvm_set_video_handler(&jetkvm_video_handler); +// } import "C" +var ( + jkInstance *Native + jkInstanceLock sync.RWMutex + jkVideoChan chan []byte = make(chan []byte) +) + +func setUpJkInstance(instance *Native) { + jkInstanceLock.Lock() + defer jkInstanceLock.Unlock() + + if jkInstance == nil { + jkInstance = instance + } + + if jkInstance != instance { + panic("jkInstance is already set") + } +} + //export jetkvm_video_state_handler func jetkvm_video_state_handler(state *C.jetkvm_video_state_t) { - nativeLogger.Info().Msg("video state handler") - nativeLogger.Info().Msg(fmt.Sprintf("state: %+v", state)) + jkInstanceLock.RLock() + defer jkInstanceLock.RUnlock() + + if jkInstance != nil { + // convert state to VideoState + videoState := VideoState{ + Ready: bool(state.ready), + Error: C.GoString(state.error), + Width: int(state.width), + Height: int(state.height), + FramePerSecond: float64(state.frame_per_second), + } + jkInstance.handleVideoStateMessage(videoState) + } +} + +//export jetkvm_go_log_handler +func jetkvm_go_log_handler(level C.int, filename *C.cchar_t, funcname *C.cchar_t, line C.int, message *C.cchar_t) { + l := nativeLogger.With(). + Str("file", C.GoString(filename)). + Str("function", C.GoString(funcname)). + Int("line", int(line)). + Logger() + + gLevel := zerolog.Level(level) + switch gLevel { + case zerolog.DebugLevel: + l.Debug().Msg(C.GoString(message)) + case zerolog.InfoLevel: + l.Info().Msg(C.GoString(message)) + case zerolog.WarnLevel: + l.Warn().Msg(C.GoString(message)) + case zerolog.ErrorLevel: + l.Error().Msg(C.GoString(message)) + case zerolog.PanicLevel: + l.Panic().Msg(C.GoString(message)) + case zerolog.FatalLevel: + l.Fatal().Msg(C.GoString(message)) + case zerolog.TraceLevel: + l.Trace().Msg(C.GoString(message)) + case zerolog.NoLevel: + l.Info().Msg(C.GoString(message)) + default: + l.Info().Msg(C.GoString(message)) + } +} + +//export jetkvm_video_handler +func jetkvm_video_handler(frame *C.cuint8_t, len C.ssize_t) { + jkVideoChan <- C.GoBytes(unsafe.Pointer(frame), C.int(len)) } func setVideoStateHandler() { C.jetkvm_setup_video_state_handler() } +func setLogHandler() { + C.jetkvm_setup_log_handler() +} + +func setVideoHandler() { + C.jetkvm_setup_video_handler() +} + func (n *Native) StartNativeVideo() { + setUpJkInstance(n) + setVideoStateHandler() + setLogHandler() + setVideoHandler() + C.jetkvm_ui_init() - n.UpdateLabelIfChanged("boot_screen_version", n.AppVersion.String()) + n.UpdateLabelIfChanged("boot_screen_version", n.appVersion.String()) go func() { for { @@ -45,6 +139,7 @@ func (n *Native) StartNativeVideo() { nativeLogger.Error().Msg("failed to initialize video") return } + C.jetkvm_video_start() close(n.ready) @@ -163,17 +258,23 @@ func (n *Native) DispSetRotation(rotation string) (bool, error) { } func (n *Native) GetStreamQualityFactor() (float64, error) { - return 1.0, nil + factor := C.jetkvm_video_get_quality_factor() + return float64(factor), nil } func (n *Native) SetStreamQualityFactor(factor float64) error { + C.jetkvm_video_set_quality_factor(C.float(factor)) return nil } func (n *Native) GetEDID() (string, error) { - return "", nil + edidCStr := C.jetkvm_video_get_edid_hex() + return C.GoString(edidCStr), nil } func (n *Native) SetEDID(edid string) error { + edidCStr := C.CString(edid) + defer C.free(unsafe.Pointer(edidCStr)) + C.jetkvm_video_set_edid(edidCStr) return nil } diff --git a/internal/native/eez/jetkvm.eez-project b/internal/native/eez/jetkvm.eez-project index 7e29cd9..22858de 100644 --- a/internal/native/eez/jetkvm.eez-project +++ b/internal/native/eez/jetkvm.eez-project @@ -43,7 +43,7 @@ { "objID": "1efaf644-03dc-42c5-9892-5612d594cd6a", "fileName": "actions.h", - "template": "#ifndef EEZ_LVGL_UI_EVENTS_H\r\n#define EEZ_LVGL_UI_EVENTS_H\r\n\r\n//${eez-studio LVGL_INCLUDE}\r\n\r\n#ifdef __cplusplus\r\nextern \"C\" {\r\n#endif\r\n\r\n//${eez-studio LVGL_ACTIONS_DECL}\r\n\r\n#ifdef __cplusplus\r\n}\r\n#endif\r\n\r\n#endif /*EEZ_LVGL_UI_EVENTS_H*/" + "template": "#ifndef EEZ_LVGL_UI_EVENTS_H\r\n#define EEZ_LVGL_UI_EVENTS_H\r\n\r\n//${eez-studio LVGL_INCLUDE}\r\n\r\n#ifdef __cplusplus\r\nextern \"C\" {\r\n#endif\r\n\r\nextern int handle_gesture_screen_switch(lv_event_t *e, lv_dir_t direction, int screenId);\r\n\r\n//${eez-studio LVGL_ACTIONS_DECL}\r\n\r\n#ifdef __cplusplus\r\n}\r\n#endif\r\n\r\n#endif /*EEZ_LVGL_UI_EVENTS_H*/" }, { "objID": "1dbd1b7e-7270-47f0-ee02-e80bdae287cf", @@ -88,12 +88,12 @@ { "objID": "3697d44a-db04-48a2-f3e5-098172727346", "fileName": "ui.c", - "template": "#if defined(EEZ_FOR_LVGL)\n#include \n#endif\n\n#include \"ui.h\"\n#include \"screens.h\"\n#include \"images.h\"\n#include \"actions.h\"\n#include \"vars.h\"\n\n//${eez-studio GUI_ASSETS_DEF}\n\n//${eez-studio LVGL_NATIVE_VARS_TABLE_DEF}\n\n//${eez-studio LVGL_ACTIONS_ARRAY_DEF}\n\n#if defined(EEZ_FOR_LVGL)\n\nvoid ui_init() {\n eez_flow_init(assets, sizeof(assets), (lv_obj_t **)&objects, sizeof(objects), images, sizeof(images), actions);\n}\n\nvoid ui_tick() {\n eez_flow_tick();\n tick_screen(g_currentScreen);\n}\n\n#else\n\n#include \n\nstatic int16_t currentScreen = -1;\n\nstatic lv_obj_t *getLvglObjectFromIndex(int32_t index) {\n if (index == -1) {\n return 0;\n }\n return ((lv_obj_t **)&objects)[index];\n}\n\nvoid loadScreen(enum ScreensEnum screenId) {\n currentScreen = screenId - 1;\n lv_obj_t *screen = getLvglObjectFromIndex(currentScreen);\n lv_scr_load_anim(screen, LV_SCR_LOAD_ANIM_FADE_IN, 200, 0, false);\n}\n\nvoid ui_init() {\n create_screens();\n//${eez-studio LVGL_LOAD_FIRST_SCREEN}\n}\n\nvoid ui_tick() {\n tick_screen(currentScreen);\n}\n\n#endif\n" + "template": "#if defined(EEZ_FOR_LVGL)\n#include \n#endif\n\n#include \"ui.h\"\n#include \"screens.h\"\n#include \"images.h\"\n#include \"actions.h\"\n#include \"vars.h\"\n\n//${eez-studio GUI_ASSETS_DEF}\n\n//${eez-studio LVGL_NATIVE_VARS_TABLE_DEF}\n\n//${eez-studio LVGL_ACTIONS_ARRAY_DEF}\n\n#if defined(EEZ_FOR_LVGL)\n\nvoid ui_init() {\n eez_flow_init(assets, sizeof(assets), (lv_obj_t **)&objects, sizeof(objects), images, sizeof(images), actions);\n}\n\nvoid ui_tick() {\n eez_flow_tick();\n tick_screen(g_currentScreen);\n}\n\n#else\n\n#include \n\nstatic int16_t currentScreen = -1;\n\nstatic lv_obj_t *getLvglObjectFromIndex(int32_t index) {\n if (index == -1) {\n return 0;\n }\n return ((lv_obj_t **)&objects)[index];\n}\n\nvoid loadScreen(enum ScreensEnum screenId) {\n currentScreen = screenId - 1;\n lv_obj_t *screen = getLvglObjectFromIndex(currentScreen);\n lv_scr_load(screen);\n // lv_scr_load_anim(screen, LV_SCR_LOAD_ANIM_FADE_IN, 200, 0, false);\n}\n\nvoid ui_init() {\n create_screens();\n//${eez-studio LVGL_LOAD_FIRST_SCREEN}\n}\n\nvoid ui_tick() {\n tick_screen(currentScreen);\n}\n\n#endif\n" }, { "objID": "58af3ebb-96b3-494c-f4e3-9c23852e3e42", "fileName": "actions.c", - "template": "#include \"actions.h\"\n#include \"screens.h\"\n\nvoid action_switch_to_menu(lv_event_t *e) {\n loadScreen(SCREEN_ID_MENU_SCREEN);\n}\n" + "template": "#include \"actions.h\"\n#include \"screens.h\"\n\nint handle_gesture_screen_switch(lv_event_t *e, lv_dir_t direction, int screenId) {\n lv_event_code_t event_code = lv_event_get_code(e);\n if (event_code != LV_EVENT_GESTURE) {\n return 0;\n }\n\n if (lv_indev_get_gesture_dir(lv_indev_get_act()) != direction) {\n return 0;\n }\n lv_indev_wait_release(lv_indev_get_act());\n loadScreen(screenId);\n return 1;\n}\n\nvoid action_switch_to_menu(lv_event_t *e) {\n loadScreen(SCREEN_ID_MENU_SCREEN);\n}\n\nvoid action_switch_to_advanced_menu(lv_event_t *e) {\n loadScreen(SCREEN_ID_MENU_ADVANCED_SCREEN);\n}\n\nvoid action_menu_screen_gesture(lv_event_t * e) {\n handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_HOME_SCREEN);\n}\n\nvoid action_menu_advanced_screen_gesture(lv_event_t * e) {\n handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_MENU_SCREEN);\n}\n\nvoid action_reset_config_screen_gesture(lv_event_t * e) {\n handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_MENU_ADVANCED_SCREEN);\n}\n\nvoid action_home_screen_gesture(lv_event_t * e) {\n handle_gesture_screen_switch(e, LV_DIR_LEFT, SCREEN_ID_MENU_SCREEN);\n}" } ], "destinationFolder": "src\\ui", @@ -123,6 +123,46 @@ "localVariables": [], "userProperties": [], "name": "SwitchToMenu" + }, + { + "objID": "fa53f7a0-3f0b-440a-8bc2-1f760e0b9400", + "components": [], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "SwitchToAdvancedMenu" + }, + { + "objID": "7117af06-35a8-4fd4-b584-1772f7ad854a", + "components": [], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "MenuScreenGesture" + }, + { + "objID": "2207d6dc-e1d6-4333-802e-1a4c52ffcdde", + "components": [], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "HomeScreenGesture" + }, + { + "objID": "84e95cd8-2404-435f-bc25-cc895d1716a3", + "components": [], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "MenuAdvancedScreenGesture" + }, + { + "objID": "67277e91-2619-47c9-f471-8f090d0ac9d4", + "components": [], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "ResetConfigScreenGesture" } ], "userPages": [ @@ -385,6 +425,13 @@ "handlerType": "action", "action": "SwitchToMenu", "userData": 0 + }, + { + "objID": "6d57b250-50de-4815-9bd8-d9ef9713de94", + "eventName": "GESTURE", + "handlerType": "action", + "action": "HomeScreenGesture", + "userData": 0 } ], "leftUnit": "px", @@ -497,7 +544,7 @@ "type": "LVGLContainerWidget", "left": 0, "top": 0, - "width": 89, + "width": 90, "height": 22, "customInputs": [], "customOutputs": [], @@ -565,7 +612,7 @@ "type": "LVGLLabelWidget", "left": 0, "top": 0, - "width": 58, + "width": 59, "height": 17, "customInputs": [], "customOutputs": [], @@ -594,7 +641,7 @@ "objID": "59461e5c-e7a0-437c-a381-dcbcec8131e8" }, "groupIndex": 0, - "text": "0 active", + "text": "-1 active", "textType": "literal", "longMode": "WRAP", "recolor": false @@ -700,6 +747,45 @@ "recolor": false, "previewValue": "169.254.169.254" }, + { + "objID": "8e702d60-3603-4391-91d8-2653ee1fa468", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 194, + "height": 17, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "33218a07-582e-4e3a-f629-b80bb5697f6f", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "HomeInfoIPv6Addr", + "leftUnit": "%", + "topUnit": "%", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "LabelFont16", + "localStyles": { + "objID": "64467adf-a093-484d-b5cf-b70c5be5264f" + }, + "groupIndex": 0, + "text": "fe80::ffff:ffff:ffff:ffff:ffff:ffff", + "textType": "literal", + "longMode": "DOT", + "recolor": false + }, { "objID": "cc8f7c54-c932-45a2-b007-08ec2c63c00b", "type": "LVGLLabelWidget", @@ -997,7 +1083,7 @@ }, "timeline": [], "eventHandlers": [], - "identifier": "", + "identifier": "USBStatusLabel", "leftUnit": "%", "topUnit": "%", "widthUnit": "content", @@ -1193,7 +1279,7 @@ "type": "LVGLLabelWidget", "left": 0, "top": 0, - "width": 81, + "width": 100, "height": 17, "customInputs": [], "customOutputs": [], @@ -1205,7 +1291,7 @@ }, "timeline": [], "eventHandlers": [], - "identifier": "", + "identifier": "HDMIStatusLabel", "leftUnit": "%", "topUnit": "%", "widthUnit": "content", @@ -1222,7 +1308,7 @@ "objID": "00742432-4b32-47f4-a4c5-5bc75f6b458d" }, "groupIndex": 0, - "text": "Connected", + "text": "Disconnected", "textType": "literal", "longMode": "WRAP", "recolor": false @@ -1294,7 +1380,14 @@ "states": "", "useStyle": "FlexScreen", "localStyles": { - "objID": "e94fd1bc-2cb2-4da3-fea6-215f2995ff1e" + "objID": "e94fd1bc-2cb2-4da3-fea6-215f2995ff1e", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 34 + } + } + } }, "groupIndex": 0 } @@ -1330,7 +1423,15 @@ "childStyles": [] }, "timeline": [], - "eventHandlers": [], + "eventHandlers": [ + { + "objID": "9adf62c6-4b2f-413e-be43-4849a3a58c0e", + "eventName": "GESTURE", + "handlerType": "action", + "action": "MenuScreenGesture", + "userData": 0 + } + ], "leftUnit": "px", "topUnit": "px", "widthUnit": "px", @@ -1529,7 +1630,14 @@ "states": "", "useStyle": "FlowRowSpaceBetween", "localStyles": { - "objID": "2465824f-6c18-42df-8e7c-a31f604c0166" + "objID": "2465824f-6c18-42df-8e7c-a31f604c0166", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 4 + } + } + } }, "group": "", "groupIndex": 0 @@ -1540,7 +1648,7 @@ "left": 0, "top": 0, "width": 100, - "height": 282, + "height": 80, "customInputs": [], "customOutputs": [], "style": { @@ -1555,19 +1663,19 @@ "leftUnit": "px", "topUnit": "px", "widthUnit": "%", - "heightUnit": "content", + "heightUnit": "%", "children": [ { - "objID": "3f40dc47-951a-4442-ef4c-d8be1680490f", - "type": "LVGLButtonWidget", + "objID": "0b46cfec-94a5-426e-d043-8a97a4605991", + "type": "LVGLContainerWidget", "left": 0, "top": 0, "width": 100, - "height": 50, + "height": 282, "customInputs": [], "customOutputs": [], "style": { - "objID": "52e9a688-f92c-4749-f558-0ce8dea56d5a", + "objID": "e62f4e6d-41df-4fb4-d87d-cd0b6b91f5d8", "useStyle": "default", "conditionalStyles": [], "childStyles": [] @@ -1577,32 +1685,78 @@ "leftUnit": "px", "topUnit": "px", "widthUnit": "%", - "heightUnit": "px", + "heightUnit": "content", "children": [ { - "objID": "89a96e4a-f97c-49d5-a1bb-b29f57471812", - "type": "LVGLLabelWidget", + "objID": "3f40dc47-951a-4442-ef4c-d8be1680490f", + "type": "LVGLButtonWidget", "left": 0, "top": 0, - "width": 56, - "height": 20, + "width": 100, + "height": 50, "customInputs": [], "customOutputs": [], "style": { - "objID": "baf4042c-bb38-4d13-9f8f-8b9dcb40ece4", + "objID": "52e9a688-f92c-4749-f558-0ce8dea56d5a", "useStyle": "default", "conditionalStyles": [], "childStyles": [] }, "timeline": [], "eventHandlers": [], + "identifier": "MenuBtnStatus", "leftUnit": "px", "topUnit": "px", - "widthUnit": "content", - "heightUnit": "content", - "children": [], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "89a96e4a-f97c-49d5-a1bb-b29f57471812", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 56, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "baf4042c-bb38-4d13-9f8f-8b9dcb40ece4", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "00ba30c7-bde8-4881-8784-a39dbdaf56be" + }, + "group": "", + "groupIndex": 0, + "text": "Status", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", "hiddenFlagType": "literal", + "clickableFlag": true, "clickableFlagType": "literal", "flagScrollbarMode": "", "flagScrollDirection": "", @@ -1611,19 +1765,363 @@ "checkedStateType": "literal", "disabledStateType": "literal", "states": "", - "useStyle": "MenuButtonLabel", + "useStyle": "MenuButton", "localStyles": { - "objID": "00ba30c7-bde8-4881-8784-a39dbdaf56be" + "objID": "42fe64f7-2a6f-4357-f0f1-516213b295ea" }, "group": "", - "groupIndex": 0, - "text": "Status", - "textType": "literal", - "longMode": "WRAP", - "recolor": false + "groupIndex": 0 + }, + { + "objID": "177e0582-70b0-427d-981f-32febc3ef176", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "88e9323f-9185-470a-f38f-e647cbf2b8f7", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnNetwork", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "d4ee17e8-e376-4b82-fcd4-e2c80eebb357", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 78, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "6b093eeb-104e-4140-d772-3febdcea303a", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "04e7c57e-e667-4eab-a770-1396cc713102" + }, + "group": "", + "groupIndex": 0, + "text": "Network", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "81dad0a8-38fa-48cd-8df0-e9aed63f5214" + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "ec014d13-48f6-46a3-ec12-194974422779", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "9030bb0e-c95a-4b41-8fcb-fe6727dbd078", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnAccess", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "e6fdb4c0-fb30-4a34-911e-3164c27c9752", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 65, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "6e5354ff-fd55-4ded-ef47-c1ae2fde6de8", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "f14ab478-6d81-4917-9f71-22cdc0c0f4aa" + }, + "group": "", + "groupIndex": 0, + "text": "Access", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "0cdef532-f474-4574-ffb2-bac0ddf9726b" + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "fbd64414-34a6-471c-96cc-f9a9659ce231", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "4d805678-5be9-4fd1-d69c-681aa27f7424", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [ + { + "objID": "d5daf4d2-32f1-44bf-b9c6-1c7625247f77", + "eventName": "PRESSED", + "handlerType": "action", + "action": "SwitchToAdvancedMenu", + "userData": 0 + } + ], + "identifier": "MenuBtnAdvanced", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "a567cb43-e053-4847-874a-73bc9d7c6705", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 91, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "16b42043-c8ff-46f1-8487-eaa1539f20f6", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "1ca1835a-b588-45b9-99cf-40f695821d8e" + }, + "group": "", + "groupIndex": 0, + "text": "Advanced", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "402ffd45-ac95-415a-f4fe-b50af1e0a2fe" + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "fa18fe67-8c46-4e56-eed3-13b64e3f87dd", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "6ded1de5-79a4-45b1-9231-d2358acdddb9", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnAbout", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "8b684c7d-16a5-4586-da4e-d32bd6149b1d", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 55, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "95c98729-3561-4aef-97ad-92833bde2723", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "034b4b17-7979-42c9-b456-ee3f29995f6f" + }, + "group": "", + "groupIndex": 0, + "text": "About", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "6f20ae24-d3bb-40ed-dd29-38a94b24216f" + }, + "group": "", + "groupIndex": 0 } ], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", "hiddenFlagType": "literal", "clickableFlag": true, "clickableFlagType": "literal", @@ -1634,347 +2132,16 @@ "checkedStateType": "literal", "disabledStateType": "literal", "states": "", - "useStyle": "MenuButton", + "useStyle": "FlexColumnStart", "localStyles": { - "objID": "42fe64f7-2a6f-4357-f0f1-516213b295ea" - }, - "group": "", - "groupIndex": 0 - }, - { - "objID": "177e0582-70b0-427d-981f-32febc3ef176", - "type": "LVGLButtonWidget", - "left": 0, - "top": 0, - "width": 100, - "height": 50, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "88e9323f-9185-470a-f38f-e647cbf2b8f7", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "%", - "heightUnit": "px", - "children": [ - { - "objID": "d4ee17e8-e376-4b82-fcd4-e2c80eebb357", - "type": "LVGLLabelWidget", - "left": 0, - "top": 0, - "width": 78, - "height": 20, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "6b093eeb-104e-4140-d772-3febdcea303a", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "content", - "heightUnit": "content", - "children": [], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", - "hiddenFlagType": "literal", - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButtonLabel", - "localStyles": { - "objID": "04e7c57e-e667-4eab-a770-1396cc713102" - }, - "group": "", - "groupIndex": 0, - "text": "Network", - "textType": "literal", - "longMode": "WRAP", - "recolor": false + "objID": "b2a9879e-a158-4fae-bd71-90df52899fd7", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 10 + } + } } - ], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", - "hiddenFlagType": "literal", - "clickableFlag": true, - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButton", - "localStyles": { - "objID": "81dad0a8-38fa-48cd-8df0-e9aed63f5214" - }, - "group": "", - "groupIndex": 0 - }, - { - "objID": "ec014d13-48f6-46a3-ec12-194974422779", - "type": "LVGLButtonWidget", - "left": 0, - "top": 0, - "width": 100, - "height": 50, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "9030bb0e-c95a-4b41-8fcb-fe6727dbd078", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "%", - "heightUnit": "px", - "children": [ - { - "objID": "e6fdb4c0-fb30-4a34-911e-3164c27c9752", - "type": "LVGLLabelWidget", - "left": 0, - "top": 0, - "width": 65, - "height": 20, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "6e5354ff-fd55-4ded-ef47-c1ae2fde6de8", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "content", - "heightUnit": "content", - "children": [], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", - "hiddenFlagType": "literal", - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButtonLabel", - "localStyles": { - "objID": "f14ab478-6d81-4917-9f71-22cdc0c0f4aa" - }, - "group": "", - "groupIndex": 0, - "text": "Access", - "textType": "literal", - "longMode": "WRAP", - "recolor": false - } - ], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", - "hiddenFlagType": "literal", - "clickableFlag": true, - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButton", - "localStyles": { - "objID": "0cdef532-f474-4574-ffb2-bac0ddf9726b" - }, - "group": "", - "groupIndex": 0 - }, - { - "objID": "fbd64414-34a6-471c-96cc-f9a9659ce231", - "type": "LVGLButtonWidget", - "left": 0, - "top": 0, - "width": 100, - "height": 50, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "4d805678-5be9-4fd1-d69c-681aa27f7424", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "identifier": "", - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "%", - "heightUnit": "px", - "children": [ - { - "objID": "a567cb43-e053-4847-874a-73bc9d7c6705", - "type": "LVGLLabelWidget", - "left": 0, - "top": 0, - "width": 91, - "height": 20, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "16b42043-c8ff-46f1-8487-eaa1539f20f6", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "content", - "heightUnit": "content", - "children": [], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", - "hiddenFlagType": "literal", - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButtonLabel", - "localStyles": { - "objID": "1ca1835a-b588-45b9-99cf-40f695821d8e" - }, - "group": "", - "groupIndex": 0, - "text": "Advanced", - "textType": "literal", - "longMode": "WRAP", - "recolor": false - } - ], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", - "hiddenFlagType": "literal", - "clickableFlag": true, - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButton", - "localStyles": { - "objID": "402ffd45-ac95-415a-f4fe-b50af1e0a2fe" - }, - "group": "", - "groupIndex": 0 - }, - { - "objID": "fa18fe67-8c46-4e56-eed3-13b64e3f87dd", - "type": "LVGLButtonWidget", - "left": 0, - "top": 0, - "width": 100, - "height": 50, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "6ded1de5-79a4-45b1-9231-d2358acdddb9", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "identifier": "", - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "%", - "heightUnit": "px", - "children": [ - { - "objID": "8b684c7d-16a5-4586-da4e-d32bd6149b1d", - "type": "LVGLLabelWidget", - "left": 0, - "top": 0, - "width": 55, - "height": 20, - "customInputs": [], - "customOutputs": [], - "style": { - "objID": "95c98729-3561-4aef-97ad-92833bde2723", - "useStyle": "default", - "conditionalStyles": [], - "childStyles": [] - }, - "timeline": [], - "eventHandlers": [], - "leftUnit": "px", - "topUnit": "px", - "widthUnit": "content", - "heightUnit": "content", - "children": [], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", - "hiddenFlagType": "literal", - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButtonLabel", - "localStyles": { - "objID": "034b4b17-7979-42c9-b456-ee3f29995f6f" - }, - "group": "", - "groupIndex": 0, - "text": "About", - "textType": "literal", - "longMode": "WRAP", - "recolor": false - } - ], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", - "hiddenFlagType": "literal", - "clickableFlag": true, - "clickableFlagType": "literal", - "flagScrollbarMode": "", - "flagScrollDirection": "", - "scrollSnapX": "", - "scrollSnapY": "", - "checkedStateType": "literal", - "disabledStateType": "literal", - "states": "", - "useStyle": "MenuButton", - "localStyles": { - "objID": "6f20ae24-d3bb-40ed-dd29-38a94b24216f" }, "group": "", "groupIndex": 0 @@ -1985,21 +2152,28 @@ "clickableFlag": true, "clickableFlagType": "literal", "flagScrollbarMode": "auto", - "flagScrollDirection": "", - "scrollSnapX": "", + "flagScrollDirection": "ver", + "scrollSnapX": "start", "scrollSnapY": "", "checkedStateType": "literal", "disabledStateType": "literal", "states": "", "useStyle": "FlexColumnStart", "localStyles": { - "objID": "c6f1be09-1320-48e1-f375-24baad8c63f3" + "objID": "c6f1be09-1320-48e1-f375-24baad8c63f3", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 4 + } + } + } }, "group": "", "groupIndex": 0 } ], - "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE|SCROLL_ELASTIC", "hiddenFlagType": "literal", "clickableFlag": true, "clickableFlagType": "literal", @@ -2025,7 +2199,7 @@ "checkedStateType": "literal", "disabledStateType": "literal", "states": "", - "useStyle": "FlexScreen", + "useStyle": "FlexScreenMenu", "localStyles": { "objID": "431a2f38-2803-45dc-c035-8ea7d5457830" }, @@ -2043,6 +2217,980 @@ "isUsedAsUserWidget": false, "createAtStart": true, "deleteOnScreenUnload": false + }, + { + "objID": "2f715924-0a49-4b81-a3ba-467ec779b088", + "components": [ + { + "objID": "e4b7c2d3-4152-443a-f84a-b79a88d8fdd7", + "type": "LVGLScreenWidget", + "left": 0, + "top": 0, + "width": 300, + "height": 240, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "a25191f9-69d0-4f1c-d0dd-4c6593b847a6", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [ + { + "objID": "f8683add-c20d-44a3-8b81-95693c77339a", + "eventName": "GESTURE", + "handlerType": "action", + "action": "MenuAdvancedScreenGesture", + "userData": 0 + } + ], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "px", + "heightUnit": "px", + "children": [ + { + "objID": "9ce8104e-f70f-4a94-c5a0-a514c332d774", + "type": "LVGLContainerWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 100, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "346a8a57-0c1f-494c-b2f5-10da21997867", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "%", + "children": [ + { + "objID": "ed2ed3e4-7470-4423-d905-889241724011", + "type": "LVGLContainerWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 32, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "1be1a492-2be0-4ab5-87d4-706a321094a8", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuHeaderContainer_1", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "content", + "children": [ + { + "objID": "fa1cd459-93fa-4c93-ea69-98697221d4f0", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 32, + "height": 32, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "48245bb9-c3d9-4f75-ba85-33ee0cc5901d", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [ + { + "objID": "c73ca51a-49a7-494b-8495-fd033e87afaf", + "eventName": "PRESSED", + "handlerType": "action", + "action": "SwitchToMenu", + "userData": 0 + } + ], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "px", + "heightUnit": "px", + "children": [ + { + "objID": "a5142298-dd3f-438e-8151-5d3c4cf5387e", + "type": "LVGLImageWidget", + "left": -1, + "top": 2, + "width": 8, + "height": 12, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "296a003f-f474-4cae-de61-bfcc6c8d974a", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "ADV_HITTEST|CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "localStyles": { + "objID": "867b6848-6882-4b65-b6eb-b695b410d1b2" + }, + "group": "", + "groupIndex": 0, + "image": "back-caret", + "setPivot": false, + "pivotX": 0, + "pivotY": 0, + "zoom": 256, + "angle": 0, + "innerAlign": "CENTER" + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "localStyles": { + "objID": "68b25d3b-9ff7-4c22-c593-144728011425", + "definition": { + "MAIN": { + "DEFAULT": { + "bg_color": "262626", + "bg_opa": 255, + "radius": 10000 + } + } + } + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "b4b4697d-998b-4109-87f0-996c5588379f", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 91, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "67cc80e6-e187-4752-8e7e-18c8d2163582", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "", + "leftUnit": "%", + "topUnit": "%", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "HeaderLink", + "localStyles": { + "objID": "55dc29b9-c791-4d35-e328-61145a6c7524" + }, + "groupIndex": 0, + "text": "Advanced", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlowRowSpaceBetween", + "localStyles": { + "objID": "e90c3c49-d6c2-422d-fb06-0434c94f18ac", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 4 + } + } + } + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "db2eb660-b0b2-4fa5-ae38-5ab8ecab55fa", + "type": "LVGLContainerWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 80, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "c4dd5ef3-73be-42c6-e9f4-c15b7d81520c", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuItemsContainer_1", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "%", + "children": [ + { + "objID": "13d43f82-4d1c-40df-b3fa-e5bb841ced30", + "type": "LVGLContainerWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 224, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "99010f70-f403-411a-c74f-2776c86cc795", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "content", + "children": [ + { + "objID": "f9e454d1-5452-431d-95b5-dddbfa45ba62", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "c58c9979-437d-4b7d-b404-dffda5353237", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnAdvancedDeveloperMode", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "02c96ea6-6a34-4c40-84a5-5e13c71f1e1c", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 150, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "991006f2-cdd3-4a7d-a467-a43e9da6abf0", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "86a9131a-f666-4ca6-e4cc-5c323d59ace8" + }, + "group": "", + "groupIndex": 0, + "text": "Developer Mode", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "1cb67b1a-afac-4274-87c0-dc4d6551e794" + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "f02189f2-ce22-4af8-9fc2-82f89b249c61", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "7d3284f7-36f8-4ca4-d381-097b9efbeec8", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnAdvancedUSBEmulation", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "afab8946-4327-4ef5-afb8-fbf83e6b8f81", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 130, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "fce37d6c-b558-4aa7-ad56-f0f18c4df2d6", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "5b663894-c6db-4778-e142-106db86b2249" + }, + "group": "", + "groupIndex": 0, + "text": "USB Emulation", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "b4c1848b-927d-4b37-bcdf-ca92115f7e67" + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "069951e2-f01b-45e0-a1b3-fd74fadc803b", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "bdcc38b3-10d4-4a9b-db04-d235a4a7a1f0", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "hiddenInEditor": false, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnAdvancedReboot", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "c3abe5ed-b334-4ff1-f56c-a215465f1f65", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 131, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "8883fc71-b230-40be-f1a1-c4f532e28c2b", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "hiddenInEditor": false, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "412cdc11-6c68-4343-be4d-e8bc592cc700" + }, + "group": "", + "groupIndex": 0, + "text": "Reboot Device", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "fff1eefd-0f3c-45de-9b2c-df13c8e3c74a" + }, + "group": "", + "groupIndex": 0 + }, + { + "objID": "1077686a-8cc3-44f5-f09e-000d5e0ef066", + "type": "LVGLButtonWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 50, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "68c60f77-c693-471e-f9c5-0efce52cbb3b", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "MenuBtnAdvancedResetConfig", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "px", + "children": [ + { + "objID": "e3167f72-90a8-4892-f1fe-1a1dfb52e360", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 177, + "height": 20, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "395b21d5-7ee1-42d9-9dd8-03743f0e126e", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButtonLabel", + "localStyles": { + "objID": "89d83f91-1186-406b-969f-f9a073e6627c" + }, + "group": "", + "groupIndex": 0, + "text": "Reset Configuration", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_ON_FOCUS|SCROLL_WITH_ARROW", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "MenuButton", + "localStyles": { + "objID": "7c56f088-6b35-4b20-a6a2-95f8e4848b03", + "definition": { + "MAIN": { + "DEFAULT": { + "bg_color": "DC2626", + "bg_opa": 255 + } + } + } + }, + "group": "", + "groupIndex": 0 + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexColumnStart", + "localStyles": { + "objID": "4502b4fe-c279-45dc-af95-4c3769bedaab", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 10 + } + } + } + }, + "group": "", + "groupIndex": 0 + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_VER|SNAPPABLE|SCROLL_ELASTIC|SCROLL_WITH_ARROW|SCROLL_MOMENTUM|SCROLL_CHAIN_HOR|SCROLLABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "auto", + "flagScrollDirection": "ver", + "scrollSnapX": "start", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexColumnStart", + "localStyles": { + "objID": "811d8a5f-2096-45bf-c361-e72adb9e277a", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 4 + } + } + } + }, + "group": "", + "groupIndex": 0 + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE|SCROLL_ELASTIC", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexStart", + "localStyles": { + "objID": "4e036514-0029-4a30-8b11-67244bd414ce" + }, + "group": "", + "groupIndex": 0 + } + ], + "widgetFlags": "CLICKABLE|PRESS_LOCK|CLICK_FOCUSABLE|GESTURE_BUBBLE|SNAPPABLE|SCROLLABLE|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexScreenMenu", + "localStyles": { + "objID": "4a721556-2197-4617-a918-05875c85cfd6" + }, + "groupIndex": 0 + } + ], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "MenuAdvancedScreen", + "left": 0, + "top": 0, + "width": 300, + "height": 240, + "isUsedAsUserWidget": false, + "createAtStart": true, + "deleteOnScreenUnload": false + }, + { + "objID": "6ae65a65-7c13-4bb0-b1f4-140a7c81ce08", + "components": [ + { + "objID": "301d7de4-526d-4412-d53a-7bcccf53ad28", + "type": "LVGLScreenWidget", + "left": 0, + "top": 0, + "width": 800, + "height": 480, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "7c02286f-53af-45c9-cc16-501e63d58c1a", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "px", + "heightUnit": "px", + "children": [ + { + "objID": "0098b39f-84f3-4e84-a8de-6884a89b52a2", + "type": "LVGLContainerWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 100, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "4c070c5d-443d-48fc-f542-3a01ee676cbb", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "%", + "children": [ + { + "objID": "ae7509ee-e8b8-438b-af98-87e8867c83d7", + "type": "LVGLContainerWidget", + "left": 0, + "top": 0, + "width": 100, + "height": 100, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "361c1892-cb5b-4cb2-e2b5-c3dd1bda02cb", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "%", + "heightUnit": "%", + "children": [ + { + "objID": "e61ffde3-c633-4927-c64e-182f2b1a6f8e", + "type": "LVGLImageWidget", + "left": 0, + "top": 0, + "width": 153, + "height": 42, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "bb3f20c0-c34d-4044-8f3a-7035c2161933", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "BootLogo_1", + "leftUnit": "px", + "topUnit": "px", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "ADV_HITTEST|CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "localStyles": { + "objID": "5d4dd927-d9ea-4643-ceef-d071098f7c03", + "definition": { + "MAIN": { + "DEFAULT": { + "transform_width": 174, + "transform_height": 49 + } + } + } + }, + "group": "", + "groupIndex": 0, + "image": "boot-logo-2", + "setPivot": false, + "pivotX": 0, + "pivotY": 0, + "zoom": 256, + "angle": 0, + "innerAlign": "CENTER" + }, + { + "objID": "5e911c84-3720-4ac9-9d1b-959406aaca8e", + "type": "LVGLLabelWidget", + "left": 0, + "top": 0, + "width": 73, + "height": 17, + "customInputs": [], + "customOutputs": [], + "style": { + "objID": "11ac19dd-499d-4e80-81d0-6d36dc0b620d", + "useStyle": "default", + "conditionalStyles": [], + "childStyles": [] + }, + "timeline": [], + "eventHandlers": [], + "identifier": "BootScreenVersion_1", + "leftUnit": "%", + "topUnit": "%", + "widthUnit": "content", + "heightUnit": "content", + "children": [], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlagType": "literal", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "localStyles": { + "objID": "0a139d47-e2ee-4656-d3c0-73363b6fc89e", + "definition": { + "MAIN": { + "DEFAULT": { + "text_align": "CENTER", + "text_font": "FontBook16" + } + } + } + }, + "groupIndex": 0, + "text": "0.0.1-dev0", + "textType": "literal", + "longMode": "WRAP", + "recolor": false + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexCenter", + "localStyles": { + "objID": "af053538-49de-4956-daa3-edec918fde44" + }, + "group": "", + "groupIndex": 0 + } + ], + "widgetFlags": "CLICK_FOCUSABLE|GESTURE_BUBBLE|PRESS_LOCK|SCROLLABLE|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_WITH_ARROW|SNAPPABLE", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "flagScrollbarMode": "", + "flagScrollDirection": "", + "scrollSnapX": "", + "scrollSnapY": "", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexStart", + "localStyles": { + "objID": "47d03068-67d6-4e59-921f-937c8b9c9ba4" + }, + "group": "", + "groupIndex": 0 + } + ], + "widgetFlags": "CLICKABLE|PRESS_LOCK|CLICK_FOCUSABLE|GESTURE_BUBBLE|SNAPPABLE|SCROLLABLE|SCROLL_ELASTIC|SCROLL_MOMENTUM|SCROLL_CHAIN_HOR|SCROLL_CHAIN_VER", + "hiddenFlagType": "literal", + "clickableFlag": true, + "clickableFlagType": "literal", + "checkedStateType": "literal", + "disabledStateType": "literal", + "states": "", + "useStyle": "FlexColumnSpaceBetween", + "localStyles": { + "objID": "564ae91d-51b8-4786-fe9d-60f8bec030c0" + }, + "groupIndex": 0 + } + ], + "connectionLines": [], + "localVariables": [], + "userProperties": [], + "name": "BootScreen_1", + "left": 0, + "top": 0, + "width": 300, + "height": 240, + "isUsedAsUserWidget": false, + "createAtStart": true, + "deleteOnScreenUnload": false } ], "userWidgets": [], @@ -2175,7 +3323,24 @@ "objID": "e45e7fb9-a2bc-4905-93d3-f07a25b73bb5", "name": "FlexScreen", "forWidgetType": "LVGLScreenWidget", - "childStyles": [], + "childStyles": [ + { + "objID": "91b485bc-6859-4870-86b2-ff496806b992", + "name": "FlexScreenMenu", + "forWidgetType": "LVGLScreenWidget", + "childStyles": [], + "definition": { + "objID": "f8d7de21-d35f-4958-8dcf-1fb2fbf84e66", + "definition": { + "MAIN": { + "DEFAULT": { + "pad_right": 30 + } + } + } + } + } + ], "definition": { "objID": "cb412f12-95d3-4d6c-ddc2-7dbf7ee979cc", "definition": { diff --git a/internal/native/eez/jetkvm.eez-project-ui-state b/internal/native/eez/jetkvm.eez-project-ui-state index 417743f..89866ba 100644 --- a/internal/native/eez/jetkvm.eez-project-ui-state +++ b/internal/native/eez/jetkvm.eez-project-ui-state @@ -1,8 +1,7 @@ { "navigation": { - "selectedUserPageObject": "[jetkvm.eez-project]:/userPages/1", - "selectedActionObject": "[jetkvm.eez-project]:/actions/0", - "selectedGlobalVariableObject": "[jetkvm.eez-project]:/variables/globalVariables/-1", + "selectedUserPageObject": "[jetkvm.eez-project]:/userPages/2", + "selectedActionObject": "[jetkvm.eez-project]:/actions/1", "selectedStyleObject": "[jetkvm.eez-project]:/lvglStyles/styles/0", "selectedThemeObject": "[jetkvm.eez-project]:/themes/0", "selectedFontObject": "[jetkvm.eez-project]:/fonts/1", @@ -129,6 +128,7 @@ "type": "tabset", "id": "#2e3d9a08-c69c-42b5-b434-525109f2a5a7", "weight": 1, + "selected": 2, "enableClose": false, "children": [ { @@ -194,13 +194,14 @@ { "type": "tabset", "id": "EDITORS", - "weight": 43.26842349697695, + "weight": 63.667074907133006, + "selected": 3, "enableDeleteWhenEmpty": false, "enableClose": false, "children": [ { "type": "tab", - "id": "#f8258cf3-e12a-4be9-8021-33199674dbe6", + "id": "#b444e818-4663-4332-ab40-a34acb670c8c", "name": "HomeScreen", "component": "editor", "config": { @@ -209,6 +210,17 @@ }, "icon": "svg:page" }, + { + "type": "tab", + "id": "#f8258cf3-e12a-4be9-8021-33199674dbe6", + "name": "MenuScreen", + "component": "editor", + "config": { + "objectPath": "[jetkvm.eez-project]:/userPages/2", + "permanent": true + }, + "icon": "svg:page" + }, { "type": "tab", "id": "#ab5bf7de-d982-416b-b26b-8fffecd56449", @@ -232,12 +244,13 @@ }, "icon": "material:settings" } - ] + ], + "active": true }, { "type": "row", "id": "#ee319cf9-6145-49e4-b40e-1d999be897c8", - "weight": 36.48121981117664, + "weight": 16.082568401020577, "children": [ { "type": "tabset", @@ -252,8 +265,7 @@ "enableClose": false, "icon": "svg:properties" } - ], - "active": true + ] }, { "type": "tabset", @@ -1067,7 +1079,11 @@ "0": { "1": {} }, - "1": {}, + "1": { + "0": { + "$selected": true + } + }, "2": {}, "3": { "0": { @@ -1076,9 +1092,7 @@ }, "1": { "0": {}, - "1": { - "$selected": true - } + "1": {} } } } @@ -1111,13 +1125,13 @@ "0": {} }, "1": { - "0": {}, - "1": {}, - "2": {}, - "3": {}, - "4": { - "0": { - "$selected": true + "0": { + "0": {}, + "1": {}, + "2": {}, + "3": {}, + "4": { + "0": {} } } } @@ -1126,8 +1140,8 @@ }, "transform": { "translate": { - "x": -150, - "y": -120 + "x": -194, + "y": -454 }, "scale": 1 }, @@ -1154,9 +1168,9 @@ "lvglPart": "MAIN", "lvglState": "DEFAULT", "lvglExpandedPropertiesGroup": [ - "MARGIN", - "TEXT", - "POSITION AND SIZE" + "POSITION AND SIZE", + "PADDING", + "MARGIN" ], "showInactiveFlowsInDebugger": true, "globalFlowZoom": true, diff --git a/internal/native/native.go b/internal/native/native.go index c2d2b65..b1432bd 100644 --- a/internal/native/native.go +++ b/internal/native/native.go @@ -1,30 +1,70 @@ package native import ( + "time" + "github.com/Masterminds/semver/v3" "github.com/rs/zerolog" ) type Native struct { - ready chan struct{} - l *zerolog.Logger - lD *zerolog.Logger - SystemVersion *semver.Version - AppVersion *semver.Version + ready chan struct{} + l *zerolog.Logger + lD *zerolog.Logger + systemVersion *semver.Version + appVersion *semver.Version + onVideoStateChange func(state VideoState) + onVideoFrameReceived func(frame []byte, duration time.Duration) } -func NewNative(systemVersion *semver.Version, appVersion *semver.Version) *Native { +type NativeOptions struct { + SystemVersion *semver.Version + AppVersion *semver.Version + OnVideoStateChange func(state VideoState) + OnVideoFrameReceived func(frame []byte, duration time.Duration) +} + +func NewNative(opts NativeOptions) *Native { + onVideoStateChange := opts.OnVideoStateChange + if onVideoStateChange == nil { + onVideoStateChange = func(state VideoState) { + nativeLogger.Info().Msg("video state changed") + } + } + + onVideoFrameReceived := opts.OnVideoFrameReceived + if onVideoFrameReceived == nil { + onVideoFrameReceived = func(frame []byte, duration time.Duration) { + nativeLogger.Info().Msg("video frame received") + } + } + return &Native{ - ready: make(chan struct{}), - l: nativeLogger, - lD: displayLogger, - SystemVersion: systemVersion, - AppVersion: appVersion, + ready: make(chan struct{}), + l: nativeLogger, + lD: displayLogger, + systemVersion: opts.SystemVersion, + appVersion: opts.AppVersion, + onVideoStateChange: opts.OnVideoStateChange, + onVideoFrameReceived: opts.OnVideoFrameReceived, } } func (n *Native) Start() { go n.StartNativeVideo() + go n.HandleVideoChan() +} + +func (n *Native) HandleVideoChan() { + lastFrame := time.Now() + + for { + frame := <-jkVideoChan + now := time.Now() + sinceLastFrame := now.Sub(lastFrame) + lastFrame = now + n.onVideoFrameReceived(frame, sinceLastFrame) + } } // func handleCtrlClient(conn net.Conn) { diff --git a/internal/native/video.go b/internal/native/video.go new file mode 100644 index 0000000..ad3cdd1 --- /dev/null +++ b/internal/native/video.go @@ -0,0 +1,16 @@ +package native + +import "fmt" + +type VideoState struct { + Ready bool `json:"ready"` + Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range + Width int `json:"width"` + Height int `json:"height"` + FramePerSecond float64 `json:"fps"` +} + +func (n *Native) handleVideoStateMessage(state VideoState) { + nativeLogger.Info().Msg("video state handler") + nativeLogger.Info().Msg(fmt.Sprintf("state: %+v", state)) +} diff --git a/native.go b/native.go index c5ffada..e5be169 100644 --- a/native.go +++ b/native.go @@ -1,13 +1,32 @@ package kvm import ( + "time" + "github.com/Masterminds/semver/v3" "github.com/jetkvm/kvm/internal/native" + "github.com/pion/webrtc/v4/pkg/media" ) var nativeInstance *native.Native func initNative(systemVersion *semver.Version, appVersion *semver.Version) { - nativeInstance = native.NewNative(systemVersion, appVersion) + nativeInstance = native.NewNative(native.NativeOptions{ + SystemVersion: systemVersion, + AppVersion: appVersion, + OnVideoStateChange: func(state native.VideoState) { + lastVideoState = state + triggerVideoStateUpdate() + requestDisplayUpdate(true) + }, + OnVideoFrameReceived: func(frame []byte, duration time.Duration) { + if currentSession != nil { + err := currentSession.VideoTrack.WriteSample(media.Sample{Data: frame, Duration: duration}) + if err != nil { + nativeLogger.Warn().Err(err).Msg("error writing sample") + } + } + }, + }) nativeInstance.Start() } diff --git a/video.go b/video.go index 62c3a0d..6d407fb 100644 --- a/video.go +++ b/video.go @@ -1,5 +1,7 @@ package kvm +import "github.com/jetkvm/kvm/internal/native" + // max frame size for 1080p video, specified in mpp venc setting const maxFrameSize = 1920 * 1080 / 2 @@ -7,15 +9,7 @@ func writeCtrlAction(action string) error { return nil } -type VideoInputState struct { - Ready bool `json:"ready"` - Error string `json:"error,omitempty"` //no_signal, no_lock, out_of_range - Width int `json:"width"` - Height int `json:"height"` - FramePerSecond float64 `json:"fps"` -} - -var lastVideoState VideoInputState +var lastVideoState native.VideoState func triggerVideoStateUpdate() { go func() { @@ -35,6 +29,6 @@ func triggerVideoStateUpdate() { // requestDisplayUpdate(true) // } -func rpcGetVideoState() (VideoInputState, error) { +func rpcGetVideoState() (native.VideoState, error) { return lastVideoState, nil }