mirror of https://github.com/jetkvm/kvm.git
feat: native as lib
This commit is contained in:
parent
6e25d44597
commit
eb5827ccf9
|
@ -1,3 +1,7 @@
|
|||
{
|
||||
"tailwindCSS.classFunctions": ["cva", "cx"]
|
||||
"tailwindCSS.classFunctions": [
|
||||
"cva",
|
||||
"cx"
|
||||
],
|
||||
"cmake.sourceDirectory": "/workspaces/ymjk/jetkvm-native"
|
||||
}
|
15
Makefile
15
Makefile
|
@ -20,12 +20,12 @@ GO_LDFLAGS := \
|
|||
-X $(PROMETHEUS_TAG).Revision=$(REVISION) \
|
||||
-X $(KVM_PKG_NAME).builtTimestamp=$(BUILDTS)
|
||||
|
||||
GO_ARGS := GOOS=linux GOARCH=arm GOARM=7
|
||||
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="-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" \
|
||||
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" \
|
||||
CC="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-gcc" \
|
||||
LD="$(BUILDKIT_PATH)/bin/$(BUILDKIT_FLAVOR)-ld" \
|
||||
CGO_ENABLED=1
|
||||
|
@ -37,15 +37,16 @@ BIN_DIR := $(shell pwd)/bin
|
|||
|
||||
TEST_DIRS := $(shell find . -name "*_test.go" -type f -exec dirname {} \; | sort -u)
|
||||
|
||||
hash_resource:
|
||||
@shasum -a 256 resource/jetkvm_native | cut -d ' ' -f 1 > resource/jetkvm_native.sha256
|
||||
build_native:
|
||||
@echo "Building native..."
|
||||
cd internal/native/cgo && ./ui_index.gen.sh && ./build.sh
|
||||
|
||||
build_dev: hash_resource
|
||||
build_dev: build_native
|
||||
@echo "Building..."
|
||||
$(GO_CMD) build \
|
||||
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION_DEV)" \
|
||||
$(GO_RELEASE_BUILD_ARGS) \
|
||||
-o $(BIN_DIR)/jetkvm_app cmd/main.go
|
||||
-o $(BIN_DIR)/jetkvm_app -v cmd/main.go
|
||||
|
||||
build_afpacket:
|
||||
@echo "Building..."
|
||||
|
|
126
display.go
126
display.go
|
@ -9,7 +9,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
var currentScreen = "ui_Boot_Screen"
|
||||
var currentScreen = "boot_screen"
|
||||
var backlightState = 0 // 0 - NORMAL, 1 - DIMMED, 2 - OFF
|
||||
|
||||
var (
|
||||
|
@ -22,75 +22,6 @@ const (
|
|||
backlightControlClass string = "/sys/class/backlight/backlight/brightness"
|
||||
)
|
||||
|
||||
func switchToScreen(screen string) {
|
||||
_, err := CallCtrlAction("lv_scr_load", map[string]interface{}{"obj": screen})
|
||||
if err != nil {
|
||||
displayLogger.Warn().Err(err).Str("screen", screen).Msg("failed to switch to screen")
|
||||
return
|
||||
}
|
||||
currentScreen = screen
|
||||
}
|
||||
|
||||
var displayedTexts = make(map[string]string)
|
||||
|
||||
func lvObjSetState(objName string, state string) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_obj_set_state", map[string]interface{}{"obj": objName, "state": state})
|
||||
}
|
||||
|
||||
func lvObjAddFlag(objName string, flag string) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_obj_add_flag", map[string]interface{}{"obj": objName, "flag": flag})
|
||||
}
|
||||
|
||||
func lvObjClearFlag(objName string, flag string) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_obj_clear_flag", map[string]interface{}{"obj": objName, "flag": flag})
|
||||
}
|
||||
|
||||
func lvObjHide(objName string) (*CtrlResponse, error) {
|
||||
return lvObjAddFlag(objName, "LV_OBJ_FLAG_HIDDEN")
|
||||
}
|
||||
|
||||
func lvObjShow(objName string) (*CtrlResponse, error) {
|
||||
return lvObjClearFlag(objName, "LV_OBJ_FLAG_HIDDEN")
|
||||
}
|
||||
|
||||
func lvObjSetOpacity(objName string, opacity int) (*CtrlResponse, error) { // nolint:unused
|
||||
return CallCtrlAction("lv_obj_set_style_opa_layered", map[string]interface{}{"obj": objName, "opa": opacity})
|
||||
}
|
||||
|
||||
func lvObjFadeIn(objName string, duration uint32) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_obj_fade_in", map[string]interface{}{"obj": objName, "time": duration})
|
||||
}
|
||||
|
||||
func lvObjFadeOut(objName string, duration uint32) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_obj_fade_out", map[string]interface{}{"obj": objName, "time": duration})
|
||||
}
|
||||
|
||||
func lvLabelSetText(objName string, text string) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_label_set_text", map[string]interface{}{"obj": objName, "text": text})
|
||||
}
|
||||
|
||||
func lvImgSetSrc(objName string, src string) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_img_set_src", map[string]interface{}{"obj": objName, "src": src})
|
||||
}
|
||||
|
||||
func lvDispSetRotation(rotation string) (*CtrlResponse, error) {
|
||||
return CallCtrlAction("lv_disp_set_rotation", map[string]interface{}{"rotation": rotation})
|
||||
}
|
||||
|
||||
func updateLabelIfChanged(objName string, newText string) {
|
||||
if newText != "" && newText != displayedTexts[objName] {
|
||||
_, _ = lvLabelSetText(objName, newText)
|
||||
displayedTexts[objName] = newText
|
||||
}
|
||||
}
|
||||
|
||||
func switchToScreenIfDifferent(screenName string) {
|
||||
if currentScreen != screenName {
|
||||
displayLogger.Info().Str("from", currentScreen).Str("to", screenName).Msg("switching screen")
|
||||
switchToScreen(screenName)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
cloudBlinkLock sync.Mutex = sync.Mutex{}
|
||||
cloudBlinkStopped bool
|
||||
|
@ -98,44 +29,48 @@ var (
|
|||
)
|
||||
|
||||
func updateDisplay() {
|
||||
updateLabelIfChanged("ui_Home_Content_Ip", networkState.IPv4String())
|
||||
nativeInstance.UpdateLabelIfChanged("home_info_ipv4_addr", networkState.IPv4String())
|
||||
nativeInstance.UpdateLabelIfChanged("home_info_ipv6_addr", networkState.IPv6String())
|
||||
|
||||
nativeInstance.UpdateLabelIfChanged("home_info_mac_addr", networkState.MACString())
|
||||
|
||||
if usbState == "configured" {
|
||||
updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected")
|
||||
_, _ = lvObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_DEFAULT")
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Connected")
|
||||
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_DEFAULT")
|
||||
} else {
|
||||
updateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected")
|
||||
_, _ = lvObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_USER_2")
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Usb_Status_Label", "Disconnected")
|
||||
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Usb_Status_Label", "LV_STATE_USER_2")
|
||||
}
|
||||
if lastVideoState.Ready {
|
||||
updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected")
|
||||
_, _ = lvObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_DEFAULT")
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Connected")
|
||||
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_DEFAULT")
|
||||
} else {
|
||||
updateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected")
|
||||
_, _ = lvObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_USER_2")
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Home_Footer_Hdmi_Status_Label", "Disconnected")
|
||||
_, _ = nativeInstance.ObjSetState("ui_Home_Footer_Hdmi_Status_Label", "LV_STATE_USER_2")
|
||||
}
|
||||
updateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions))
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Home_Header_Cloud_Status_Label", fmt.Sprintf("%d active", actionSessions))
|
||||
|
||||
if networkState.IsUp() {
|
||||
switchToScreenIfDifferent("ui_Home_Screen")
|
||||
nativeInstance.SwitchToScreenIf("home_screen", []string{"no_network_screen", "boot_screen"})
|
||||
} else {
|
||||
switchToScreenIfDifferent("ui_No_Network_Screen")
|
||||
nativeInstance.SwitchToScreenIf("no_network_screen", []string{"home_screen", "boot_screen"})
|
||||
}
|
||||
|
||||
if cloudConnectionState == CloudConnectionStateNotConfigured {
|
||||
_, _ = lvObjHide("ui_Home_Header_Cloud_Status_Icon")
|
||||
_, _ = nativeInstance.ObjHide("ui_Home_Header_Cloud_Status_Icon")
|
||||
} else {
|
||||
_, _ = lvObjShow("ui_Home_Header_Cloud_Status_Icon")
|
||||
_, _ = nativeInstance.ObjShow("ui_Home_Header_Cloud_Status_Icon")
|
||||
}
|
||||
|
||||
switch cloudConnectionState {
|
||||
case CloudConnectionStateDisconnected:
|
||||
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud_disconnected.png")
|
||||
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud_disconnected.png")
|
||||
stopCloudBlink()
|
||||
case CloudConnectionStateConnecting:
|
||||
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
||||
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
||||
startCloudBlink()
|
||||
case CloudConnectionStateConnected:
|
||||
_, _ = lvImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
||||
_, _ = nativeInstance.ImgSetSrc("ui_Home_Header_Cloud_Status_Icon", "cloud.png")
|
||||
stopCloudBlink()
|
||||
}
|
||||
}
|
||||
|
@ -159,9 +94,9 @@ func startCloudBlink() {
|
|||
if cloudConnectionState != CloudConnectionStateConnecting {
|
||||
continue
|
||||
}
|
||||
_, _ = lvObjFadeOut("ui_Home_Header_Cloud_Status_Icon", 1000)
|
||||
_, _ = nativeInstance.ObjFadeOut("ui_Home_Header_Cloud_Status_Icon", 1000)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
_, _ = lvObjFadeIn("ui_Home_Header_Cloud_Status_Icon", 1000)
|
||||
_, _ = nativeInstance.ObjFadeIn("ui_Home_Header_Cloud_Status_Icon", 1000)
|
||||
time.Sleep(1000 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
@ -205,20 +140,20 @@ func waitCtrlAndRequestDisplayUpdate(shouldWakeDisplay bool) {
|
|||
waitDisplayUpdate.Lock()
|
||||
defer waitDisplayUpdate.Unlock()
|
||||
|
||||
waitCtrlClientConnected()
|
||||
// nativeInstance.WaitCtrlClientConnected()
|
||||
requestDisplayUpdate(shouldWakeDisplay)
|
||||
}
|
||||
|
||||
func updateStaticContents() {
|
||||
//contents that never change
|
||||
updateLabelIfChanged("ui_Home_Content_Mac", networkState.MACString())
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Home_Content_Mac", networkState.MACString())
|
||||
systemVersion, appVersion, err := GetLocalVersion()
|
||||
if err == nil {
|
||||
updateLabelIfChanged("ui_About_Content_Operating_System_Version_ContentLabel", systemVersion.String())
|
||||
updateLabelIfChanged("ui_About_Content_App_Version_Content_Label", appVersion.String())
|
||||
nativeInstance.UpdateLabelIfChanged("ui_About_Content_Operating_System_Version_ContentLabel", systemVersion.String())
|
||||
nativeInstance.UpdateLabelIfChanged("ui_About_Content_App_Version_Content_Label", appVersion.String())
|
||||
}
|
||||
|
||||
updateLabelIfChanged("ui_Status_Content_Device_Id_Content_Label", GetDeviceID())
|
||||
nativeInstance.UpdateLabelIfChanged("ui_Status_Content_Device_Id_Content_Label", GetDeviceID())
|
||||
}
|
||||
|
||||
// setDisplayBrightness sets /sys/class/backlight/backlight/brightness to alter
|
||||
|
@ -379,10 +314,9 @@ func startBacklightTickers() {
|
|||
|
||||
func initDisplay() {
|
||||
go func() {
|
||||
waitCtrlClientConnected()
|
||||
displayLogger.Info().Msg("setting initial display contents")
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
_, _ = lvDispSetRotation(config.DisplayRotation)
|
||||
_, _ = nativeInstance.DispSetRotation(config.DisplayRotation)
|
||||
updateStaticContents()
|
||||
displayInited = true
|
||||
displayLogger.Info().Msg("display inited")
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
build
|
||||
ui_index.c
|
|
@ -0,0 +1,69 @@
|
|||
cmake_minimum_required(VERSION 3.14)
|
||||
include(FetchContent)
|
||||
|
||||
project(jknative LANGUAGES C CXX)
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Specify path to own LVGL config header
|
||||
set(
|
||||
LV_CONF_PATH
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lv_conf.h
|
||||
CACHE STRING "" FORCE
|
||||
)
|
||||
|
||||
set(
|
||||
LV_DRIVERS_PUBLIC_HEADERS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lv_drv_conf.h
|
||||
CACHE STRING "" FORCE
|
||||
)
|
||||
|
||||
# Rockchip SDK paths
|
||||
set(RK_SDK_BASE "/opt/jetkvm-native-buildkit")
|
||||
set(RK_MEDIA_OUTPUT "${RK_SDK_BASE}/media/out")
|
||||
set(RK_MEDIA_INCLUDE_PATH "${RK_MEDIA_OUTPUT}/include")
|
||||
set(RK_APP_MEDIA_LIBS_PATH "${RK_MEDIA_OUTPUT}/lib")
|
||||
|
||||
# Fetch LVGL from GitHub
|
||||
FetchContent_Declare(
|
||||
lvgl
|
||||
GIT_REPOSITORY https://github.com/lvgl/lvgl.git
|
||||
GIT_TAG v8.3.11
|
||||
)
|
||||
FetchContent_MakeAvailable(lvgl)
|
||||
|
||||
# Fetch LVGL drivers from GitHub
|
||||
FetchContent_Declare(
|
||||
lv_drivers
|
||||
GIT_REPOSITORY https://github.com/lvgl/lv_drivers.git
|
||||
GIT_TAG v8.3.0
|
||||
)
|
||||
FetchContent_MakeAvailable(lv_drivers)
|
||||
|
||||
# Get source files, excluding CMake generated files
|
||||
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.c")
|
||||
list(FILTER sources EXCLUDE REGEX "CMakeFiles.*CompilerId.*\\.c$")
|
||||
|
||||
add_library(jknative STATIC ${sources} ${CMAKE_CURRENT_SOURCE_DIR}/jknative.h)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(jknative PRIVATE
|
||||
${RK_MEDIA_INCLUDE_PATH}
|
||||
${RK_MEDIA_INCLUDE_PATH}/libdrm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ui
|
||||
)
|
||||
|
||||
# Set library search path
|
||||
target_link_directories(jknative PRIVATE ${RK_APP_MEDIA_LIBS_PATH})
|
||||
|
||||
|
||||
target_link_libraries(jknative PRIVATE
|
||||
lvgl::lvgl
|
||||
lvgl::drivers
|
||||
pthread
|
||||
rockit
|
||||
rockchip_mpp
|
||||
rga
|
||||
m
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
#!/bin/bash
|
||||
|
||||
CMAKE_TOOLCHAIN_FILE=/opt/jetkvm-native-buildkit/rv1106-jetkvm-v2.cmake
|
||||
CLEAN_ALL=${CLEAN_ALL:-0}
|
||||
|
||||
if [ "$CLEAN_ALL" -eq 1 ]; then
|
||||
find . -name build -exec rm -r {} +
|
||||
fi
|
||||
|
||||
set -x
|
||||
VERBOSE=1 cmake -B build \
|
||||
-DCMAKE_SYSTEM_PROCESSOR=armv7l \
|
||||
-DCMAKE_SYSTEM_NAME=Linux \
|
||||
-DCMAKE_CROSSCOMPILING=1 \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$CMAKE_TOOLCHAIN_FILE \
|
||||
-DSKIP_GLIBC_NAMES=ON \
|
||||
|
||||
cmake --build build
|
|
@ -0,0 +1,315 @@
|
|||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include "frozen.h"
|
||||
#include "video.h"
|
||||
#include "screen.h"
|
||||
#include "edid.h"
|
||||
#include "ctrl.h"
|
||||
#include <lvgl.h>
|
||||
|
||||
jetkvm_video_state_t state;
|
||||
jetkvm_video_state_handler_t *video_state_handler = NULL;
|
||||
|
||||
void report_video_format(bool ready, const char *error, u_int16_t width, u_int16_t height, double frame_per_second)
|
||||
{
|
||||
state.ready = ready;
|
||||
state.error = error;
|
||||
state.width = width;
|
||||
state.height = height;
|
||||
state.frame_per_second = frame_per_second;
|
||||
if (video_state_handler != NULL) {
|
||||
(*video_state_handler)(&state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert a hexadecimal string to an array of uint8_t bytes
|
||||
*
|
||||
* @param hex_str The input hexadecimal string
|
||||
* @param bytes The output byte array (must be pre-allocated)
|
||||
* @param max_len The maximum number of bytes that can be stored in the output array
|
||||
* @return int The number of bytes converted, or -1 on error
|
||||
*/
|
||||
int hex_to_bytes(const char *hex_str, uint8_t *bytes, size_t max_len)
|
||||
{
|
||||
size_t hex_len = strnlen(hex_str, 4096);
|
||||
if (hex_len % 2 != 0 || hex_len / 2 > max_len)
|
||||
{
|
||||
return -1; // Invalid input length or insufficient output buffer
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < hex_len; i += 2)
|
||||
{
|
||||
char byte_str[3] = {hex_str[i], hex_str[i + 1], '\0'};
|
||||
char *end_ptr;
|
||||
long value = strtol(byte_str, &end_ptr, 16);
|
||||
|
||||
if (*end_ptr != '\0' || value < 0 || value > 255)
|
||||
{
|
||||
return -1; // Invalid hexadecimal value
|
||||
}
|
||||
|
||||
bytes[i / 2] = (uint8_t)value;
|
||||
}
|
||||
|
||||
return hex_len / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Convert an array of uint8_t bytes to a hexadecimal string, user must free the returned string
|
||||
*
|
||||
* @param bytes The input byte array
|
||||
* @param len The number of bytes in the input array
|
||||
* @return char* The output hexadecimal string (dynamically allocated, must be freed by the caller), or NULL on error
|
||||
*/
|
||||
const char *bytes_to_hex(const uint8_t *bytes, size_t len)
|
||||
{
|
||||
if (bytes == NULL || len == 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *hex_str = malloc(2 * len + 1); // Each byte becomes 2 hex chars, plus null terminator
|
||||
if (hex_str == NULL)
|
||||
{
|
||||
return NULL; // Memory allocation failed
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++)
|
||||
{
|
||||
snprintf(hex_str + (2 * i), 3, "%02x", bytes[i]);
|
||||
}
|
||||
|
||||
hex_str[2 * len] = '\0'; // Ensure null termination
|
||||
return hex_str;
|
||||
}
|
||||
|
||||
lv_obj_flag_t str_to_lv_obj_flag(const char *flag)
|
||||
{
|
||||
if (strcmp(flag, "LV_OBJ_FLAG_HIDDEN") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_HIDDEN;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_CLICKABLE") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_CLICKABLE;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_SCROLLABLE") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_SCROLLABLE;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_CLICK_FOCUSABLE") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_CLICK_FOCUSABLE;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_SCROLL_ON_FOCUS") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_SCROLL_ON_FOCUS;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_SCROLL_CHAIN") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_SCROLL_CHAIN;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_PRESS_LOCK") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_PRESS_LOCK;
|
||||
}
|
||||
else if (strcmp(flag, "LV_OBJ_FLAG_OVERFLOW_VISIBLE") == 0)
|
||||
{
|
||||
return LV_OBJ_FLAG_OVERFLOW_VISIBLE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0; // Unknown flag
|
||||
}
|
||||
}
|
||||
|
||||
void jetkvm_ui_init() {
|
||||
lvgl_init();
|
||||
}
|
||||
|
||||
void jetkvm_ui_tick() {
|
||||
lvgl_tick();
|
||||
}
|
||||
|
||||
void jetkvm_set_video_state_handler(jetkvm_video_state_handler_t *handler) {
|
||||
video_state_handler = handler;
|
||||
}
|
||||
|
||||
void jetkvm_ui_set_rotation(u_int8_t rotation)
|
||||
{
|
||||
printf("setting rotation to %d\n", rotation);
|
||||
if (rotation == 90)
|
||||
{
|
||||
lv_disp_set_rotation(NULL, LV_DISP_ROT_90);
|
||||
} else if (rotation == 180) {
|
||||
lv_disp_set_rotation(NULL, LV_DISP_ROT_180);
|
||||
} else if (rotation == 270) {
|
||||
lv_disp_set_rotation(NULL, LV_DISP_ROT_270);
|
||||
} else {
|
||||
lv_disp_set_rotation(NULL, LV_DISP_ROT_NONE);
|
||||
}
|
||||
}
|
||||
|
||||
const char *jetkvm_ui_get_current_screen() {
|
||||
return ui_get_current_screen();
|
||||
}
|
||||
|
||||
void jetkvm_ui_load_screen(const char *obj_name) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lv_scr_act() != obj) {
|
||||
lv_scr_load(obj);
|
||||
}
|
||||
}
|
||||
|
||||
int jetkvm_ui_set_text(const char *obj_name, const char *text) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (strcmp(lv_label_get_text(obj), text) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
lv_label_set_text(obj, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void jetkvm_ui_set_image(const char *obj_name, const char *image_name) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return;
|
||||
}
|
||||
lv_img_set_src(obj, image_name);
|
||||
}
|
||||
|
||||
void jetkvm_ui_set_state(const char *obj_name, const char *state_name) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return;
|
||||
}
|
||||
lv_obj_add_state(obj, LV_STATE_USER_1);
|
||||
lv_state_t state_val = LV_STATE_DEFAULT;
|
||||
if (strcmp(state_name, "LV_STATE_USER_1") == 0)
|
||||
{
|
||||
state_val = LV_STATE_USER_1;
|
||||
}
|
||||
else if (strcmp(state_name, "LV_STATE_USER_2") == 0)
|
||||
{
|
||||
state_val = LV_STATE_USER_2;
|
||||
}
|
||||
lv_obj_clear_state(obj, LV_STATE_USER_1 | LV_STATE_USER_2);
|
||||
lv_obj_add_state(obj, state_val);
|
||||
}
|
||||
|
||||
int jetkvm_ui_add_flag(const char *obj_name, const char *flag_name) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
lv_obj_flag_t flag_val = str_to_lv_obj_flag(flag_name);
|
||||
if (flag_val == 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
lv_obj_add_flag(obj, flag_val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int jetkvm_ui_clear_flag(const char *obj_name, const char *flag_name) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
lv_obj_flag_t flag_val = str_to_lv_obj_flag(flag_name);
|
||||
if (flag_val == 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
lv_obj_clear_flag(obj, flag_val);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void jetkvm_ui_fade_in(const char *obj_name, u_int32_t duration) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return;
|
||||
}
|
||||
lv_obj_fade_in(obj, duration, 0);
|
||||
}
|
||||
|
||||
void jetkvm_ui_fade_out(const char *obj_name, u_int32_t duration) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return;
|
||||
}
|
||||
lv_obj_fade_out(obj, duration, 0);
|
||||
}
|
||||
|
||||
void jetkvm_ui_set_opacity(const char *obj_name, u_int8_t opacity) {
|
||||
lv_obj_t *obj = ui_get_obj(obj_name);
|
||||
if (obj == NULL) {
|
||||
return;
|
||||
}
|
||||
lv_obj_set_style_opa(obj, opacity, LV_PART_MAIN);
|
||||
}
|
||||
|
||||
void jetkvm_video_start() {
|
||||
video_start_streaming();
|
||||
}
|
||||
|
||||
void jetkvm_video_stop() {
|
||||
video_stop_streaming();
|
||||
}
|
||||
|
||||
int jetkvm_video_set_quality_factor(float quality_factor) {
|
||||
if (quality_factor < 0 || quality_factor > 1) {
|
||||
return -1;
|
||||
}
|
||||
video_set_quality_factor(quality_factor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int jetkvm_video_set_edid(const char *edid_hex) {
|
||||
uint8_t edid[256];
|
||||
int edid_len = hex_to_bytes(edid_hex, edid, 256);
|
||||
if (edid_len < 0) {
|
||||
return -1;
|
||||
}
|
||||
return set_edid(edid, edid_len);
|
||||
}
|
||||
|
||||
char *jetkvm_video_get_edid_hex() {
|
||||
uint8_t edid[256];
|
||||
int edid_len = get_edid(edid, 256);
|
||||
if (edid_len < 0) {
|
||||
return NULL;
|
||||
}
|
||||
return bytes_to_hex(edid, edid_len);
|
||||
}
|
||||
|
||||
jetkvm_video_state_t *jetkvm_video_get_status() {
|
||||
return &state;
|
||||
}
|
||||
|
||||
int jetkvm_video_init() {
|
||||
return video_init();
|
||||
}
|
||||
|
||||
void jetkvm_video_shutdown() {
|
||||
video_shutdown();
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef VIDEO_DAEMON_CTRL_H
|
||||
#define VIDEO_DAEMON_CTRL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool ready;
|
||||
const char *error;
|
||||
u_int16_t width;
|
||||
u_int16_t height;
|
||||
double frame_per_second;
|
||||
} jetkvm_video_state_t;
|
||||
|
||||
typedef void (jetkvm_video_state_handler_t)(jetkvm_video_state_t *state);
|
||||
|
||||
void jetkvm_ui_init();
|
||||
void jetkvm_ui_tick();
|
||||
|
||||
void jetkvm_set_video_state_handler(jetkvm_video_state_handler_t *handler);
|
||||
|
||||
void jetkvm_ui_set_rotation(u_int8_t rotation);
|
||||
const char *jetkvm_ui_get_current_screen();
|
||||
void jetkvm_ui_load_screen(const char *obj_name);
|
||||
int jetkvm_ui_set_text(const char *obj_name, const char *text);
|
||||
void jetkvm_ui_set_image(const char *obj_name, const char *image_name);
|
||||
void jetkvm_ui_set_state(const char *obj_name, const char *state_name);
|
||||
void jetkvm_ui_fade_in(const char *obj_name, u_int32_t duration);
|
||||
void jetkvm_ui_fade_out(const char *obj_name, u_int32_t duration);
|
||||
void jetkvm_ui_set_opacity(const char *obj_name, u_int8_t opacity);
|
||||
int jetkvm_ui_add_flag(const char *obj_name, const char *flag_name);
|
||||
int jetkvm_ui_clear_flag(const char *obj_name, const char *flag_name);
|
||||
|
||||
int jetkvm_video_init();
|
||||
void jetkvm_video_shutdown();
|
||||
void jetkvm_video_start();
|
||||
void jetkvm_video_stop();
|
||||
int jetkvm_video_set_quality_factor(float 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();
|
||||
|
||||
#endif //VIDEO_DAEMON_CTRL_H
|
|
@ -0,0 +1,179 @@
|
|||
#include "edid.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <errno.h>
|
||||
#include <sys/klog.h>
|
||||
|
||||
#define MAX_EDID_SIZE 256
|
||||
#define V4L_SUBDEV "/dev/v4l-subdev2"
|
||||
|
||||
int get_edid(uint8_t *edid, size_t max_size)
|
||||
{
|
||||
if (edid == NULL)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (max_size != 128 && max_size != 256)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd;
|
||||
struct v4l2_edid v4l2_edid;
|
||||
|
||||
fd = open(V4L_SUBDEV, O_RDWR);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Failed to open device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&v4l2_edid, 0, sizeof(v4l2_edid));
|
||||
v4l2_edid.pad = 0;
|
||||
v4l2_edid.start_block = 0;
|
||||
v4l2_edid.blocks = 2;
|
||||
v4l2_edid.edid = edid;
|
||||
|
||||
if (ioctl(fd, VIDIOC_G_EDID, &v4l2_edid) < 0)
|
||||
{
|
||||
perror("Failed to get EDID");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return v4l2_edid.blocks * 128;
|
||||
}
|
||||
|
||||
static void fix_edid_checksum(uint8_t *edid, size_t size)
|
||||
{
|
||||
for (size_t block = 0; block < size / 128; block++)
|
||||
{
|
||||
uint8_t sum = 0;
|
||||
for (int i = 0; i < 127; i++)
|
||||
{
|
||||
sum += edid[block * 128 + i];
|
||||
}
|
||||
edid[block * 128 + 127] = (uint8_t)(256 - sum);
|
||||
}
|
||||
}
|
||||
|
||||
int set_edid(uint8_t *edid, size_t size)
|
||||
{
|
||||
if (edid == NULL)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (size != 128 && size != 256)
|
||||
{
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fd;
|
||||
struct v4l2_edid v4l2_edid;
|
||||
|
||||
fd = open(V4L_SUBDEV, O_RDWR);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Failed to open device");
|
||||
return -1;
|
||||
}
|
||||
|
||||
fix_edid_checksum(edid, size);
|
||||
|
||||
memset(&v4l2_edid, 0, sizeof(v4l2_edid));
|
||||
v4l2_edid.pad = 0;
|
||||
v4l2_edid.start_block = 0;
|
||||
v4l2_edid.blocks = size / 128;
|
||||
v4l2_edid.edid = edid;
|
||||
|
||||
if (ioctl(fd, VIDIOC_S_EDID, &v4l2_edid) < 0)
|
||||
{
|
||||
perror("Failed to set EDID");
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *videoc_log_status()
|
||||
{
|
||||
int fd;
|
||||
char *buffer = NULL;
|
||||
size_t buffer_size = 0;
|
||||
ssize_t bytes_read;
|
||||
|
||||
fd = open(V4L_SUBDEV, O_RDWR);
|
||||
if (fd < 0)
|
||||
{
|
||||
perror("Failed to open device");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ioctl(fd, VIDIOC_LOG_STATUS) == -1)
|
||||
{
|
||||
perror("VIDIOC_LOG_STATUS failed");
|
||||
close(fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
char buf[40960];
|
||||
int len = -1;
|
||||
|
||||
len = klogctl(3, buf, sizeof(buf) - 1);
|
||||
|
||||
if (len >= 0)
|
||||
{
|
||||
bool found_status = false;
|
||||
char *p = buf;
|
||||
char *q;
|
||||
|
||||
buf[len] = 0;
|
||||
while ((q = strstr(p, "START STATUS")))
|
||||
{
|
||||
found_status = true;
|
||||
p = q + 1;
|
||||
}
|
||||
if (found_status)
|
||||
{
|
||||
while (p > buf && *p != '<')
|
||||
p--;
|
||||
q = p;
|
||||
while ((q = strstr(q, "<6>")))
|
||||
{
|
||||
memcpy(q, " ", 3);
|
||||
}
|
||||
}
|
||||
buffer = strdup(p);
|
||||
if (buffer == NULL)
|
||||
{
|
||||
perror("Failed to allocate memory for status");
|
||||
return NULL;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Failed to read kernel log\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef EDID_H
|
||||
#define EDID_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* @brief Read the EDID from the display
|
||||
*
|
||||
* @param edid Buffer to store the EDID data
|
||||
* @param max_size Maximum size of the buffer (should be 128 or 256)
|
||||
* @return int Number of bytes read on success, -1 on failure
|
||||
*/
|
||||
int get_edid(uint8_t *edid, size_t max_size);
|
||||
|
||||
/**
|
||||
* @brief Set the EDID of the display
|
||||
*
|
||||
* @param edid The EDID to set, it can be modified
|
||||
* @param size The size of the EDID (should be 128 or 256)
|
||||
* @return int 0 on success, -1 on failure
|
||||
*/
|
||||
int set_edid(uint8_t *edid, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Get the status of the videocontroller, aka v4l2-ctl --log-status.
|
||||
* User should free the returned string
|
||||
*
|
||||
* @return const char* The status of the videocontroller
|
||||
*/
|
||||
const char* videoc_log_status();
|
||||
|
||||
#endif // EDID_H
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
* 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 <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef int bool;
|
||||
enum { false = 0, true = 1 };
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#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_ */
|
|
@ -0,0 +1,608 @@
|
|||
/**
|
||||
* @file lv_conf.h
|
||||
* Configuration file for v8.1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copy this file as `lv_conf.h`
|
||||
* 1. simply next to the `lvgl` folder
|
||||
* 2. or any other places and
|
||||
* - define `LV_CONF_INCLUDE_SIMPLE`
|
||||
* - add the path as include path
|
||||
*/
|
||||
|
||||
/* clang-format off */
|
||||
#if 1 /*Set it to "1" to enable content*/
|
||||
|
||||
#ifndef LV_CONF_H
|
||||
#define LV_CONF_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/*====================
|
||||
COLOR SETTINGS
|
||||
*====================*/
|
||||
|
||||
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
|
||||
#define LV_COLOR_DEPTH 16
|
||||
|
||||
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
|
||||
#define LV_COLOR_16_SWAP 0
|
||||
|
||||
/*Enable more complex drawing routines to manage screens transparency.
|
||||
*Can be used if the UI is above another layer, e.g. an OSD menu or video player.
|
||||
*Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/
|
||||
#define LV_COLOR_SCREEN_TRANSP 0
|
||||
|
||||
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
|
||||
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
|
||||
#define LV_COLOR_MIX_ROUND_OFS (LV_COLOR_DEPTH == 32 ? 0: 128)
|
||||
|
||||
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
|
||||
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
|
||||
|
||||
/*=========================
|
||||
MEMORY SETTINGS
|
||||
*=========================*/
|
||||
|
||||
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
|
||||
#define LV_MEM_CUSTOM 1
|
||||
#if LV_MEM_CUSTOM == 0
|
||||
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
|
||||
# define LV_MEM_SIZE (32U * 1024U) /*[bytes]*/
|
||||
|
||||
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
|
||||
# define LV_MEM_ADR 0 /*0: unused*/
|
||||
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
|
||||
#if LV_MEM_ADR == 0
|
||||
//#define LV_MEM_POOL_INCLUDE your_alloc_library /* Uncomment if using an external allocator*/
|
||||
//#define LV_MEM_POOL_ALLOC your_alloc /* Uncomment if using an external allocator*/
|
||||
#endif
|
||||
|
||||
#else /*LV_MEM_CUSTOM*/
|
||||
# define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
|
||||
# define LV_MEM_CUSTOM_ALLOC malloc
|
||||
# define LV_MEM_CUSTOM_FREE free
|
||||
# define LV_MEM_CUSTOM_REALLOC realloc
|
||||
#endif /*LV_MEM_CUSTOM*/
|
||||
|
||||
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
|
||||
*You will see an error log message if there wasn't enough buffers. */
|
||||
#define LV_MEM_BUF_MAX_NUM 16
|
||||
|
||||
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
|
||||
#define LV_MEMCPY_MEMSET_STD 0
|
||||
|
||||
/*====================
|
||||
HAL SETTINGS
|
||||
*====================*/
|
||||
|
||||
/*Default display refresh period. LVG will redraw changed areas with this period time*/
|
||||
#define LV_DISP_DEF_REFR_PERIOD 10 /*[ms]*/
|
||||
|
||||
/*Input device read period in milliseconds*/
|
||||
#define LV_INDEV_DEF_READ_PERIOD 10 /*[ms]*/
|
||||
|
||||
/*Use a custom tick source that tells the elapsed time in milliseconds.
|
||||
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
|
||||
// #define LV_TICK_CUSTOM 0
|
||||
// #if LV_TICK_CUSTOM
|
||||
// #define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
|
||||
// #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
|
||||
// #endif /*LV_TICK_CUSTOM*/
|
||||
#define LV_TICK_CUSTOM 1
|
||||
#if LV_TICK_CUSTOM
|
||||
#define LV_TICK_CUSTOM_INCLUDE <stdint.h> /*Header for the system time function*/
|
||||
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) /*Expression evaluating to current system time in ms*/
|
||||
#endif /*LV_TICK_CUSTOM*/
|
||||
|
||||
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
|
||||
*(Not so important, you can adjust it to modify default sizes and spaces)*/
|
||||
#define LV_DPI_DEF 130 /*[px/inch]*/
|
||||
|
||||
/*=======================
|
||||
* FEATURE CONFIGURATION
|
||||
*=======================*/
|
||||
|
||||
/*-------------
|
||||
* Drawing
|
||||
*-----------*/
|
||||
|
||||
/*Enable complex draw engine.
|
||||
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
|
||||
#define LV_DRAW_COMPLEX 1
|
||||
#if LV_DRAW_COMPLEX != 0
|
||||
|
||||
/*Allow buffering some shadow calculation.
|
||||
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
|
||||
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
|
||||
#define LV_SHADOW_CACHE_SIZE 0
|
||||
|
||||
/* Set number of maximally cached circle data.
|
||||
* The circumference of 1/4 circle are saved for anti-aliasing
|
||||
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
|
||||
* 0: to disable caching */
|
||||
#define LV_CIRCLE_CACHE_SIZE 4
|
||||
|
||||
#endif /*LV_DRAW_COMPLEX*/
|
||||
|
||||
/*Default image cache size. Image caching keeps the images opened.
|
||||
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
|
||||
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
|
||||
*However the opened images might consume additional RAM.
|
||||
*0: to disable caching*/
|
||||
#define LV_IMG_CACHE_DEF_SIZE 0
|
||||
|
||||
/*Maximum buffer size to allocate for rotation. Only used if software rotation is enabled in the display driver.*/
|
||||
#define LV_DISP_ROT_MAX_BUF (10*1024)
|
||||
|
||||
/*-------------
|
||||
* GPU
|
||||
*-----------*/
|
||||
|
||||
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
|
||||
#define LV_USE_GPU_STM32_DMA2D 0
|
||||
#if LV_USE_GPU_STM32_DMA2D
|
||||
/*Must be defined to include path of CMSIS header of target processor
|
||||
e.g. "stm32f769xx.h" or "stm32f429xx.h"*/
|
||||
#define LV_GPU_DMA2D_CMSIS_INCLUDE
|
||||
#endif
|
||||
|
||||
/*Use NXP's PXP GPU iMX RTxxx platforms*/
|
||||
#define LV_USE_GPU_NXP_PXP 0
|
||||
#if LV_USE_GPU_NXP_PXP
|
||||
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
|
||||
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
|
||||
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
|
||||
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
|
||||
*/
|
||||
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
|
||||
#endif
|
||||
|
||||
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
|
||||
#define LV_USE_GPU_NXP_VG_LITE 0
|
||||
|
||||
/*Use exnternal renderer*/
|
||||
#define LV_USE_EXTERNAL_RENDERER 0
|
||||
|
||||
/*Use SDL renderer API. Requires LV_USE_EXTERNAL_RENDERER*/
|
||||
#define LV_USE_GPU_SDL 0
|
||||
#if LV_USE_GPU_SDL
|
||||
# define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
|
||||
#endif
|
||||
|
||||
/*-------------
|
||||
* Logging
|
||||
*-----------*/
|
||||
|
||||
/*Enable the log module*/
|
||||
#define LV_USE_LOG 0
|
||||
#if LV_USE_LOG
|
||||
|
||||
/*How important log should be added:
|
||||
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
|
||||
*LV_LOG_LEVEL_INFO Log important events
|
||||
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
|
||||
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
|
||||
*LV_LOG_LEVEL_USER Only logs added by the user
|
||||
*LV_LOG_LEVEL_NONE Do not log anything*/
|
||||
# define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
|
||||
|
||||
/*1: Print the log with 'printf';
|
||||
*0: User need to register a callback with `lv_log_register_print_cb()`*/
|
||||
# define LV_LOG_PRINTF 0
|
||||
|
||||
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
|
||||
# define LV_LOG_TRACE_MEM 1
|
||||
# define LV_LOG_TRACE_TIMER 1
|
||||
# define LV_LOG_TRACE_INDEV 1
|
||||
# define LV_LOG_TRACE_DISP_REFR 1
|
||||
# define LV_LOG_TRACE_EVENT 1
|
||||
# define LV_LOG_TRACE_OBJ_CREATE 1
|
||||
# define LV_LOG_TRACE_LAYOUT 1
|
||||
# define LV_LOG_TRACE_ANIM 1
|
||||
|
||||
#endif /*LV_USE_LOG*/
|
||||
|
||||
/*-------------
|
||||
* Asserts
|
||||
*-----------*/
|
||||
|
||||
/*Enable asserts if an operation is failed or an invalid data is found.
|
||||
*If LV_USE_LOG is enabled an error message will be printed on failure*/
|
||||
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
|
||||
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
|
||||
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
|
||||
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
|
||||
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
|
||||
|
||||
/*Add a custom handler when assert happens e.g. to restart the MCU*/
|
||||
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
|
||||
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
|
||||
|
||||
/*-------------
|
||||
* Others
|
||||
*-----------*/
|
||||
|
||||
/*1: Show CPU usage and FPS count in the right bottom corner*/
|
||||
#define LV_USE_PERF_MONITOR 0
|
||||
#if LV_USE_PERF_MONITOR
|
||||
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
|
||||
#endif
|
||||
|
||||
/*1: Show the used memory and the memory fragmentation in the left bottom corner
|
||||
* Requires LV_MEM_CUSTOM = 0*/
|
||||
#define LV_USE_MEM_MONITOR 0
|
||||
#if LV_USE_PERF_MONITOR
|
||||
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
|
||||
#endif
|
||||
|
||||
/*1: Draw random colored rectangles over the redrawn areas*/
|
||||
#define LV_USE_REFR_DEBUG 0
|
||||
|
||||
/*Change the built in (v)snprintf functions*/
|
||||
#define LV_SPRINTF_CUSTOM 0
|
||||
#if LV_SPRINTF_CUSTOM
|
||||
# define LV_SPRINTF_INCLUDE <stdio.h>
|
||||
# define lv_snprintf snprintf
|
||||
# define lv_vsnprintf vsnprintf
|
||||
#else /*LV_SPRINTF_CUSTOM*/
|
||||
# define LV_SPRINTF_USE_FLOAT 0
|
||||
#endif /*LV_SPRINTF_CUSTOM*/
|
||||
|
||||
#define LV_USE_USER_DATA 1
|
||||
|
||||
/*Garbage Collector settings
|
||||
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
|
||||
#define LV_ENABLE_GC 0
|
||||
#if LV_ENABLE_GC != 0
|
||||
# define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
|
||||
#endif /*LV_ENABLE_GC*/
|
||||
|
||||
/*=====================
|
||||
* COMPILER SETTINGS
|
||||
*====================*/
|
||||
|
||||
/*For big endian systems set to 1*/
|
||||
#define LV_BIG_ENDIAN_SYSTEM 0
|
||||
|
||||
/*Define a custom attribute to `lv_tick_inc` function*/
|
||||
#define LV_ATTRIBUTE_TICK_INC
|
||||
|
||||
/*Define a custom attribute to `lv_timer_handler` function*/
|
||||
#define LV_ATTRIBUTE_TIMER_HANDLER
|
||||
|
||||
/*Define a custom attribute to `lv_disp_flush_ready` function*/
|
||||
#define LV_ATTRIBUTE_FLUSH_READY
|
||||
|
||||
/*Required alignment size for buffers*/
|
||||
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
|
||||
|
||||
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
|
||||
* E.g. __attribute__((aligned(4)))*/
|
||||
#define LV_ATTRIBUTE_MEM_ALIGN
|
||||
|
||||
/*Attribute to mark large constant arrays for example font's bitmaps*/
|
||||
#define LV_ATTRIBUTE_LARGE_CONST
|
||||
|
||||
/*Complier prefix for a big array declaration in RAM*/
|
||||
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
|
||||
|
||||
/*Place performance critical functions into a faster memory (e.g RAM)*/
|
||||
#define LV_ATTRIBUTE_FAST_MEM
|
||||
|
||||
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
|
||||
#define LV_ATTRIBUTE_DMA
|
||||
|
||||
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
|
||||
*should also appear on LVGL binding API such as Micropython.*/
|
||||
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
|
||||
|
||||
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
|
||||
#define LV_USE_LARGE_COORD 0
|
||||
|
||||
/*==================
|
||||
* FONT USAGE
|
||||
*===================*/
|
||||
|
||||
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
|
||||
*https://fonts.google.com/specimen/Montserrat*/
|
||||
#define LV_FONT_MONTSERRAT_8 0
|
||||
#define LV_FONT_MONTSERRAT_10 0
|
||||
#define LV_FONT_MONTSERRAT_12 0
|
||||
#define LV_FONT_MONTSERRAT_14 1
|
||||
#define LV_FONT_MONTSERRAT_16 0
|
||||
#define LV_FONT_MONTSERRAT_18 0
|
||||
#define LV_FONT_MONTSERRAT_20 0
|
||||
#define LV_FONT_MONTSERRAT_22 0
|
||||
#define LV_FONT_MONTSERRAT_24 0
|
||||
#define LV_FONT_MONTSERRAT_26 0
|
||||
#define LV_FONT_MONTSERRAT_28 0
|
||||
#define LV_FONT_MONTSERRAT_30 0
|
||||
#define LV_FONT_MONTSERRAT_32 0
|
||||
#define LV_FONT_MONTSERRAT_34 0
|
||||
#define LV_FONT_MONTSERRAT_36 0
|
||||
#define LV_FONT_MONTSERRAT_38 0
|
||||
#define LV_FONT_MONTSERRAT_40 0
|
||||
#define LV_FONT_MONTSERRAT_42 0
|
||||
#define LV_FONT_MONTSERRAT_44 0
|
||||
#define LV_FONT_MONTSERRAT_46 0
|
||||
#define LV_FONT_MONTSERRAT_48 0
|
||||
|
||||
/*Demonstrate special features*/
|
||||
#define LV_FONT_MONTSERRAT_12_SUBPX 0
|
||||
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
|
||||
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Perisan letters and all their forms*/
|
||||
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
|
||||
|
||||
/*Pixel perfect monospace fonts*/
|
||||
#define LV_FONT_UNSCII_8 0
|
||||
#define LV_FONT_UNSCII_16 0
|
||||
|
||||
/*Optionally declare custom fonts here.
|
||||
*You can use these fonts as default font too and they will be available globally.
|
||||
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
|
||||
#define LV_FONT_CUSTOM_DECLARE
|
||||
|
||||
/*Always set a default font*/
|
||||
#define LV_FONT_DEFAULT &lv_font_montserrat_14
|
||||
|
||||
/*Enable handling large font and/or fonts with a lot of characters.
|
||||
*The limit depends on the font size, font face and bpp.
|
||||
*Compiler error will be triggered if a font needs it.*/
|
||||
#define LV_FONT_FMT_TXT_LARGE 0
|
||||
|
||||
/*Enables/disables support for compressed fonts.*/
|
||||
#define LV_USE_FONT_COMPRESSED 0
|
||||
|
||||
/*Enable subpixel rendering*/
|
||||
#define LV_USE_FONT_SUBPX 0
|
||||
#if LV_USE_FONT_SUBPX
|
||||
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
|
||||
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
|
||||
#endif
|
||||
|
||||
/*=================
|
||||
* TEXT SETTINGS
|
||||
*=================*/
|
||||
|
||||
/**
|
||||
* Select a character encoding for strings.
|
||||
* Your IDE or editor should have the same character encoding
|
||||
* - LV_TXT_ENC_UTF8
|
||||
* - LV_TXT_ENC_ASCII
|
||||
*/
|
||||
#define LV_TXT_ENC LV_TXT_ENC_UTF8
|
||||
|
||||
/*Can break (wrap) texts on these chars*/
|
||||
#define LV_TXT_BREAK_CHARS " ,.;:-_"
|
||||
|
||||
/*If a word is at least this long, will break wherever "prettiest"
|
||||
*To disable, set to a value <= 0*/
|
||||
#define LV_TXT_LINE_BREAK_LONG_LEN 0
|
||||
|
||||
/*Minimum number of characters in a long word to put on a line before a break.
|
||||
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
|
||||
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
|
||||
|
||||
/*Minimum number of characters in a long word to put on a line after a break.
|
||||
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
|
||||
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
|
||||
|
||||
/*The control character to use for signalling text recoloring.*/
|
||||
#define LV_TXT_COLOR_CMD "#"
|
||||
|
||||
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
|
||||
*The direction will be processed according to the Unicode Bidirectional Algorithm:
|
||||
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
|
||||
#define LV_USE_BIDI 0
|
||||
#if LV_USE_BIDI
|
||||
/*Set the default direction. Supported values:
|
||||
*`LV_BASE_DIR_LTR` Left-to-Right
|
||||
*`LV_BASE_DIR_RTL` Right-to-Left
|
||||
*`LV_BASE_DIR_AUTO` detect texts base direction*/
|
||||
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
|
||||
#endif
|
||||
|
||||
/*Enable Arabic/Persian processing
|
||||
*In these languages characters should be replaced with an other form based on their position in the text*/
|
||||
#define LV_USE_ARABIC_PERSIAN_CHARS 0
|
||||
|
||||
/*==================
|
||||
* WIDGET USAGE
|
||||
*================*/
|
||||
|
||||
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
|
||||
|
||||
#define LV_USE_ARC 1
|
||||
|
||||
#define LV_USE_ANIMIMG 1
|
||||
|
||||
#define LV_USE_BAR 1
|
||||
|
||||
#define LV_USE_BTN 1
|
||||
|
||||
#define LV_USE_BTNMATRIX 1
|
||||
|
||||
#define LV_USE_CANVAS 1
|
||||
|
||||
#define LV_USE_CHECKBOX 1
|
||||
|
||||
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
|
||||
|
||||
#define LV_USE_IMG 1 /*Requires: lv_label*/
|
||||
|
||||
#define LV_USE_LABEL 1
|
||||
#if LV_USE_LABEL
|
||||
# define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
|
||||
# define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
|
||||
#endif
|
||||
|
||||
#define LV_USE_LINE 1
|
||||
|
||||
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
|
||||
#if LV_USE_ROLLER
|
||||
# define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
|
||||
#endif
|
||||
|
||||
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
|
||||
|
||||
#define LV_USE_SWITCH 1
|
||||
|
||||
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
|
||||
#if LV_USE_TEXTAREA != 0
|
||||
# define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
|
||||
#endif
|
||||
|
||||
#define LV_USE_TABLE 1
|
||||
|
||||
/*==================
|
||||
* EXTRA COMPONENTS
|
||||
*==================*/
|
||||
|
||||
/*-----------
|
||||
* Widgets
|
||||
*----------*/
|
||||
#define LV_USE_CALENDAR 1
|
||||
#if LV_USE_CALENDAR
|
||||
# define LV_CALENDAR_WEEK_STARTS_MONDAY 0
|
||||
# if LV_CALENDAR_WEEK_STARTS_MONDAY
|
||||
# define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
|
||||
# else
|
||||
# define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
|
||||
# endif
|
||||
|
||||
# define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
|
||||
# define LV_USE_CALENDAR_HEADER_ARROW 1
|
||||
# define LV_USE_CALENDAR_HEADER_DROPDOWN 1
|
||||
#endif /*LV_USE_CALENDAR*/
|
||||
|
||||
#define LV_USE_CHART 1
|
||||
|
||||
#define LV_USE_COLORWHEEL 1
|
||||
|
||||
#define LV_USE_IMGBTN 1
|
||||
|
||||
#define LV_USE_KEYBOARD 1
|
||||
|
||||
#define LV_USE_LED 1
|
||||
|
||||
#define LV_USE_LIST 1
|
||||
|
||||
#define LV_USE_METER 1
|
||||
|
||||
#define LV_USE_MSGBOX 1
|
||||
|
||||
#define LV_USE_SPINBOX 1
|
||||
|
||||
#define LV_USE_SPINNER 1
|
||||
|
||||
#define LV_USE_TABVIEW 1
|
||||
|
||||
#define LV_USE_TILEVIEW 1
|
||||
|
||||
#define LV_USE_WIN 1
|
||||
|
||||
#define LV_USE_SPAN 1
|
||||
#if LV_USE_SPAN
|
||||
/*A line text can contain maximum num of span descriptor */
|
||||
# define LV_SPAN_SNIPPET_STACK_SIZE 64
|
||||
#endif
|
||||
|
||||
/*-----------
|
||||
* Themes
|
||||
*----------*/
|
||||
|
||||
/*A simple, impressive and very complete theme*/
|
||||
#define LV_USE_THEME_DEFAULT 1
|
||||
#if LV_USE_THEME_DEFAULT
|
||||
|
||||
/*0: Light mode; 1: Dark mode*/
|
||||
# define LV_THEME_DEFAULT_DARK 0
|
||||
|
||||
/*1: Enable grow on press*/
|
||||
# define LV_THEME_DEFAULT_GROW 1
|
||||
|
||||
/*Default transition time in [ms]*/
|
||||
# define LV_THEME_DEFAULT_TRANSITION_TIME 80
|
||||
#endif /*LV_USE_THEME_DEFAULT*/
|
||||
|
||||
/*A very simple theme that is a good starting point for a custom theme*/
|
||||
#define LV_USE_THEME_BASIC 1
|
||||
|
||||
/*A theme designed for monochrome displays*/
|
||||
#define LV_USE_THEME_MONO 1
|
||||
|
||||
/*-----------
|
||||
* Layouts
|
||||
*----------*/
|
||||
|
||||
/*A layout similar to Flexbox in CSS.*/
|
||||
#define LV_USE_FLEX 1
|
||||
|
||||
/*A layout similar to Grid in CSS.*/
|
||||
#define LV_USE_GRID 1
|
||||
|
||||
/*---------------------
|
||||
* 3rd party libraries
|
||||
*--------------------*/
|
||||
|
||||
/*File system interfaces for common APIs
|
||||
*To enable set a driver letter for that API*/
|
||||
#define LV_USE_FS_STDIO '\0' /*Uses fopen, fread, etc*/
|
||||
//#define LV_FS_STDIO_PATH "/home/john/" /*Set the working directory. If commented it will be "./" */
|
||||
|
||||
#define LV_USE_FS_POSIX '\0' /*Uses open, read, etc*/
|
||||
//#define LV_FS_POSIX_PATH "/home/john/" /*Set the working directory. If commented it will be "./" */
|
||||
|
||||
#define LV_USE_FS_WIN32 '\0' /*Uses CreateFile, ReadFile, etc*/
|
||||
//#define LV_FS_WIN32_PATH "C:\\Users\\john\\" /*Set the working directory. If commented it will be ".\\" */
|
||||
|
||||
#define LV_USE_FS_FATFS '\0' /*Uses f_open, f_read, etc*/
|
||||
|
||||
/*PNG decoder library*/
|
||||
#define LV_USE_PNG 0
|
||||
|
||||
/*BMP decoder library*/
|
||||
#define LV_USE_BMP 0
|
||||
|
||||
/* JPG + split JPG decoder library.
|
||||
* Split JPG is a custom format optimized for embedded systems. */
|
||||
#define LV_USE_SJPG 0
|
||||
|
||||
/*GIF decoder library*/
|
||||
#define LV_USE_GIF 0
|
||||
|
||||
/*QR code library*/
|
||||
#define LV_USE_QRCODE 0
|
||||
|
||||
/*FreeType library*/
|
||||
#define LV_USE_FREETYPE 0
|
||||
#if LV_USE_FREETYPE
|
||||
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
|
||||
# define LV_FREETYPE_CACHE_SIZE (16 * 1024)
|
||||
#endif
|
||||
|
||||
/*Rlottie library*/
|
||||
#define LV_USE_RLOTTIE 0
|
||||
|
||||
/*-----------
|
||||
* Others
|
||||
*----------*/
|
||||
|
||||
/*1: Enable API to take snapshot for object*/
|
||||
#define LV_USE_SNAPSHOT 1
|
||||
|
||||
|
||||
/*==================
|
||||
* EXAMPLES
|
||||
*==================*/
|
||||
|
||||
/*Enable the examples to be built with the library*/
|
||||
#define LV_BUILD_EXAMPLES 0
|
||||
|
||||
/*--END OF LV_CONF_H--*/
|
||||
|
||||
#endif /*LV_CONF_H*/
|
||||
|
||||
#endif /*End of "Content enable"*/
|
|
@ -0,0 +1,494 @@
|
|||
/**
|
||||
* @file lv_drv_conf.h
|
||||
* Configuration file for v8.1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* COPY THIS FILE AS lv_drv_conf.h
|
||||
*/
|
||||
|
||||
/* clang-format off */
|
||||
#if 1 /*Set it to "1" to enable the content*/
|
||||
|
||||
#ifndef LV_DRV_CONF_H
|
||||
#define LV_DRV_CONF_H
|
||||
|
||||
#include "lv_conf.h"
|
||||
|
||||
/*********************
|
||||
* DELAY INTERFACE
|
||||
*********************/
|
||||
#define LV_DRV_DELAY_INCLUDE <stdint.h> /*Dummy include by default*/
|
||||
#define LV_DRV_DELAY_US(us) /*delay_us(us)*/ /*Delay the given number of microseconds*/
|
||||
#define LV_DRV_DELAY_MS(ms) /*delay_ms(ms)*/ /*Delay the given number of milliseconds*/
|
||||
|
||||
/*********************
|
||||
* DISPLAY INTERFACE
|
||||
*********************/
|
||||
|
||||
/*------------
|
||||
* Common
|
||||
*------------*/
|
||||
#define LV_DRV_DISP_INCLUDE <stdint.h> /*Dummy include by default*/
|
||||
#define LV_DRV_DISP_CMD_DATA(val) /*pin_x_set(val)*/ /*Set the command/data pin to 'val'*/
|
||||
#define LV_DRV_DISP_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/
|
||||
|
||||
/*---------
|
||||
* SPI
|
||||
*---------*/
|
||||
#define LV_DRV_DISP_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/
|
||||
#define LV_DRV_DISP_SPI_WR_BYTE(data) /*spi_wr(data)*/ /*Write a byte the SPI bus*/
|
||||
#define LV_DRV_DISP_SPI_WR_ARRAY(adr, n) /*spi_wr_mem(adr, n)*/ /*Write 'n' bytes to SPI bus from 'adr'*/
|
||||
|
||||
/*------------------
|
||||
* Parallel port
|
||||
*-----------------*/
|
||||
#define LV_DRV_DISP_PAR_CS(val) /*par_cs_set(val)*/ /*Set the Parallel port's Chip select to 'val'*/
|
||||
#define LV_DRV_DISP_PAR_SLOW /*par_slow()*/ /*Set low speed on the parallel port*/
|
||||
#define LV_DRV_DISP_PAR_FAST /*par_fast()*/ /*Set high speed on the parallel port*/
|
||||
#define LV_DRV_DISP_PAR_WR_WORD(data) /*par_wr(data)*/ /*Write a word to the parallel port*/
|
||||
#define LV_DRV_DISP_PAR_WR_ARRAY(adr, n) /*par_wr_mem(adr,n)*/ /*Write 'n' bytes to Parallel ports from 'adr'*/
|
||||
|
||||
/***************************
|
||||
* INPUT DEVICE INTERFACE
|
||||
***************************/
|
||||
|
||||
/*----------
|
||||
* Common
|
||||
*----------*/
|
||||
#define LV_DRV_INDEV_INCLUDE <stdint.h> /*Dummy include by default*/
|
||||
#define LV_DRV_INDEV_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/
|
||||
#define LV_DRV_INDEV_IRQ_READ 0 /*pn_x_read()*/ /*Read the IRQ pin*/
|
||||
|
||||
/*---------
|
||||
* SPI
|
||||
*---------*/
|
||||
#define LV_DRV_INDEV_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/
|
||||
#define LV_DRV_INDEV_SPI_XCHG_BYTE(data) 0 /*spi_xchg(val)*/ /*Write 'val' to SPI and give the read value*/
|
||||
|
||||
/*---------
|
||||
* I2C
|
||||
*---------*/
|
||||
#define LV_DRV_INDEV_I2C_START /*i2c_start()*/ /*Make an I2C start*/
|
||||
#define LV_DRV_INDEV_I2C_STOP /*i2c_stop()*/ /*Make an I2C stop*/
|
||||
#define LV_DRV_INDEV_I2C_RESTART /*i2c_restart()*/ /*Make an I2C restart*/
|
||||
#define LV_DRV_INDEV_I2C_WR(data) /*i2c_wr(data)*/ /*Write a byte to the I1C bus*/
|
||||
#define LV_DRV_INDEV_I2C_READ(last_read) 0 /*i2c_rd()*/ /*Read a byte from the I2C bud*/
|
||||
|
||||
|
||||
/*********************
|
||||
* DISPLAY DRIVERS
|
||||
*********************/
|
||||
|
||||
/*-------------------
|
||||
* SDL
|
||||
*-------------------*/
|
||||
|
||||
/* SDL based drivers for display, mouse, mousewheel and keyboard*/
|
||||
#ifndef USE_SDL
|
||||
# define USE_SDL 0
|
||||
#endif
|
||||
|
||||
/* Hardware accelerated SDL driver */
|
||||
#ifndef USE_SDL_GPU
|
||||
# define USE_SDL_GPU 0
|
||||
#endif
|
||||
|
||||
#if USE_SDL || USE_SDL_GPU
|
||||
# define SDL_HOR_RES 480
|
||||
# define SDL_VER_RES 320
|
||||
|
||||
/* Scale window by this factor (useful when simulating small screens) */
|
||||
# define SDL_ZOOM 1
|
||||
|
||||
/* Used to test true double buffering with only address changing.
|
||||
* Use 2 draw buffers, bith with SDL_HOR_RES x SDL_VER_RES size*/
|
||||
# define SDL_DOUBLE_BUFFERED 0
|
||||
|
||||
/*Eclipse: <SDL2/SDL.h> Visual Studio: <SDL.h>*/
|
||||
# define SDL_INCLUDE_PATH <SDL2/SDL.h>
|
||||
|
||||
/*Open two windows to test multi display support*/
|
||||
# define SDL_DUAL_DISPLAY 0
|
||||
#endif
|
||||
|
||||
/*-------------------
|
||||
* Monitor of PC
|
||||
*-------------------*/
|
||||
|
||||
/*DEPRECATED: Use the SDL driver instead. */
|
||||
#ifndef USE_MONITOR
|
||||
# define USE_MONITOR 0
|
||||
#endif
|
||||
|
||||
#if USE_MONITOR
|
||||
# define MONITOR_HOR_RES 480
|
||||
# define MONITOR_VER_RES 320
|
||||
|
||||
/* Scale window by this factor (useful when simulating small screens) */
|
||||
# define MONITOR_ZOOM 1
|
||||
|
||||
/* Used to test true double buffering with only address changing.
|
||||
* Use 2 draw buffers, bith with MONITOR_HOR_RES x MONITOR_VER_RES size*/
|
||||
# define MONITOR_DOUBLE_BUFFERED 0
|
||||
|
||||
/*Eclipse: <SDL2/SDL.h> Visual Studio: <SDL.h>*/
|
||||
# define MONITOR_SDL_INCLUDE_PATH <SDL2/SDL.h>
|
||||
|
||||
/*Open two windows to test multi display support*/
|
||||
# define MONITOR_DUAL 0
|
||||
#endif
|
||||
|
||||
/*-----------------------------------
|
||||
* Native Windows (including mouse)
|
||||
*----------------------------------*/
|
||||
#ifndef USE_WINDOWS
|
||||
# define USE_WINDOWS 0
|
||||
#endif
|
||||
|
||||
#if USE_WINDOWS
|
||||
# define WINDOW_HOR_RES 480
|
||||
# define WINDOW_VER_RES 320
|
||||
#endif
|
||||
|
||||
/*----------------------------
|
||||
* Native Windows (win32drv)
|
||||
*---------------------------*/
|
||||
#ifndef USE_WIN32DRV
|
||||
# define USE_WIN32DRV 0
|
||||
#endif
|
||||
|
||||
#if USE_WIN32DRV
|
||||
/* Scale window by this factor (useful when simulating small screens) */
|
||||
# define WIN32DRV_MONITOR_ZOOM 1
|
||||
#endif
|
||||
|
||||
/*----------------------------------------
|
||||
* GTK drivers (monitor, mouse, keyboard
|
||||
*---------------------------------------*/
|
||||
#ifndef USE_GTK
|
||||
# define USE_GTK 0
|
||||
#endif
|
||||
|
||||
/*----------------------------------------
|
||||
* Wayland drivers (monitor, mouse, keyboard, touchscreen)
|
||||
*---------------------------------------*/
|
||||
#ifndef USE_WAYLAND
|
||||
# define USE_WAYLAND 0
|
||||
#endif
|
||||
|
||||
#if USE_WAYLAND
|
||||
/* Support for client-side decorations */
|
||||
# ifndef LV_WAYLAND_CLIENT_SIDE_DECORATIONS
|
||||
# define LV_WAYLAND_CLIENT_SIDE_DECORATIONS 1
|
||||
# endif
|
||||
/* Support for (deprecated) wl-shell protocol */
|
||||
# ifndef LV_WAYLAND_WL_SHELL
|
||||
# define LV_WAYLAND_WL_SHELL 1
|
||||
# endif
|
||||
/* Support for xdg-shell protocol */
|
||||
# ifndef LV_WAYLAND_XDG_SHELL
|
||||
# define LV_WAYLAND_XDG_SHELL 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
/*----------------
|
||||
* SSD1963
|
||||
*--------------*/
|
||||
#ifndef USE_SSD1963
|
||||
# define USE_SSD1963 0
|
||||
#endif
|
||||
|
||||
#if USE_SSD1963
|
||||
# define SSD1963_HOR_RES LV_HOR_RES
|
||||
# define SSD1963_VER_RES LV_VER_RES
|
||||
# define SSD1963_HT 531
|
||||
# define SSD1963_HPS 43
|
||||
# define SSD1963_LPS 8
|
||||
# define SSD1963_HPW 10
|
||||
# define SSD1963_VT 288
|
||||
# define SSD1963_VPS 12
|
||||
# define SSD1963_FPS 4
|
||||
# define SSD1963_VPW 10
|
||||
# define SSD1963_HS_NEG 0 /*Negative hsync*/
|
||||
# define SSD1963_VS_NEG 0 /*Negative vsync*/
|
||||
# define SSD1963_ORI 0 /*0, 90, 180, 270*/
|
||||
# define SSD1963_COLOR_DEPTH 16
|
||||
#endif
|
||||
|
||||
/*----------------
|
||||
* R61581
|
||||
*--------------*/
|
||||
#ifndef USE_R61581
|
||||
# define USE_R61581 0
|
||||
#endif
|
||||
|
||||
#if USE_R61581
|
||||
# define R61581_HOR_RES LV_HOR_RES
|
||||
# define R61581_VER_RES LV_VER_RES
|
||||
# define R61581_HSPL 0 /*HSYNC signal polarity*/
|
||||
# define R61581_HSL 10 /*HSYNC length (Not Implemented)*/
|
||||
# define R61581_HFP 10 /*Horitontal Front poarch (Not Implemented)*/
|
||||
# define R61581_HBP 10 /*Horitontal Back poarch (Not Implemented */
|
||||
# define R61581_VSPL 0 /*VSYNC signal polarity*/
|
||||
# define R61581_VSL 10 /*VSYNC length (Not Implemented)*/
|
||||
# define R61581_VFP 8 /*Vertical Front poarch*/
|
||||
# define R61581_VBP 8 /*Vertical Back poarch */
|
||||
# define R61581_DPL 0 /*DCLK signal polarity*/
|
||||
# define R61581_EPL 1 /*ENABLE signal polarity*/
|
||||
# define R61581_ORI 0 /*0, 180*/
|
||||
# define R61581_LV_COLOR_DEPTH 16 /*Fix 16 bit*/
|
||||
#endif
|
||||
|
||||
/*------------------------------
|
||||
* ST7565 (Monochrome, low res.)
|
||||
*-----------------------------*/
|
||||
#ifndef USE_ST7565
|
||||
# define USE_ST7565 0
|
||||
#endif
|
||||
|
||||
#if USE_ST7565
|
||||
/*No settings*/
|
||||
#endif /*USE_ST7565*/
|
||||
|
||||
/*------------------------------
|
||||
* GC9A01 (color, low res.)
|
||||
*-----------------------------*/
|
||||
#ifndef USE_GC9A01
|
||||
# define USE_GC9A01 0
|
||||
#endif
|
||||
|
||||
#if USE_GC9A01
|
||||
/*No settings*/
|
||||
#endif /*USE_GC9A01*/
|
||||
|
||||
/*------------------------------------------
|
||||
* UC1610 (4 gray 160*[104|128])
|
||||
* (EA DOGXL160 160x104 tested)
|
||||
*-----------------------------------------*/
|
||||
#ifndef USE_UC1610
|
||||
# define USE_UC1610 0
|
||||
#endif
|
||||
|
||||
#if USE_UC1610
|
||||
# define UC1610_HOR_RES LV_HOR_RES
|
||||
# define UC1610_VER_RES LV_VER_RES
|
||||
# define UC1610_INIT_CONTRAST 33 /* init contrast, values in [%] */
|
||||
# define UC1610_INIT_HARD_RST 0 /* 1 : hardware reset at init, 0 : software reset */
|
||||
# define UC1610_TOP_VIEW 0 /* 0 : Bottom View, 1 : Top View */
|
||||
#endif /*USE_UC1610*/
|
||||
|
||||
/*-------------------------------------------------
|
||||
* SHARP memory in pixel monochrome display series
|
||||
* LS012B7DD01 (184x38 pixels.)
|
||||
* LS013B7DH03 (128x128 pixels.)
|
||||
* LS013B7DH05 (144x168 pixels.)
|
||||
* LS027B7DH01 (400x240 pixels.) (tested)
|
||||
* LS032B7DD02 (336x536 pixels.)
|
||||
* LS044Q7DH01 (320x240 pixels.)
|
||||
*------------------------------------------------*/
|
||||
#ifndef USE_SHARP_MIP
|
||||
# define USE_SHARP_MIP 0
|
||||
#endif
|
||||
|
||||
#if USE_SHARP_MIP
|
||||
# define SHARP_MIP_HOR_RES LV_HOR_RES
|
||||
# define SHARP_MIP_VER_RES LV_VER_RES
|
||||
# define SHARP_MIP_SOFT_COM_INVERSION 0
|
||||
# define SHARP_MIP_REV_BYTE(b) /*((uint8_t) __REV(__RBIT(b)))*/ /*Architecture / compiler dependent byte bits order reverse*/
|
||||
#endif /*USE_SHARP_MIP*/
|
||||
|
||||
/*-------------------------------------------------
|
||||
* ILI9341 240X320 TFT LCD
|
||||
*------------------------------------------------*/
|
||||
#ifndef USE_ILI9341
|
||||
# define USE_ILI9341 0
|
||||
#endif
|
||||
|
||||
#if USE_ILI9341
|
||||
# define ILI9341_HOR_RES LV_HOR_RES
|
||||
# define ILI9341_VER_RES LV_VER_RES
|
||||
# define ILI9341_GAMMA 1
|
||||
# define ILI9341_TEARING 0
|
||||
#endif /*USE_ILI9341*/
|
||||
|
||||
/*-----------------------------------------
|
||||
* Linux frame buffer device (/dev/fbx)
|
||||
*-----------------------------------------*/
|
||||
#ifndef USE_FBDEV
|
||||
# define USE_FBDEV 1
|
||||
#endif
|
||||
|
||||
#if USE_FBDEV
|
||||
# define FBDEV_PATH "/dev/fb0"
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------
|
||||
* FreeBSD frame buffer device (/dev/fbx)
|
||||
*.........................................*/
|
||||
#ifndef USE_BSD_FBDEV
|
||||
# define USE_BSD_FBDEV 0
|
||||
#endif
|
||||
|
||||
#if USE_BSD_FBDEV
|
||||
# define FBDEV_PATH "/dev/fb0"
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------
|
||||
* DRM/KMS device (/dev/dri/cardX)
|
||||
*-----------------------------------------*/
|
||||
#ifndef USE_DRM
|
||||
# define USE_DRM 0
|
||||
#endif
|
||||
|
||||
#if USE_DRM
|
||||
# define DRM_CARD "/dev/dri/card0"
|
||||
# define DRM_CONNECTOR_ID -1 /* -1 for the first connected one */
|
||||
#endif
|
||||
|
||||
/*********************
|
||||
* INPUT DEVICES
|
||||
*********************/
|
||||
|
||||
/*--------------
|
||||
* XPT2046
|
||||
*--------------*/
|
||||
#ifndef USE_XPT2046
|
||||
# define USE_XPT2046 0
|
||||
#endif
|
||||
|
||||
#if USE_XPT2046
|
||||
# define XPT2046_HOR_RES 480
|
||||
# define XPT2046_VER_RES 320
|
||||
# define XPT2046_X_MIN 200
|
||||
# define XPT2046_Y_MIN 200
|
||||
# define XPT2046_X_MAX 3800
|
||||
# define XPT2046_Y_MAX 3800
|
||||
# define XPT2046_AVG 4
|
||||
# define XPT2046_X_INV 0
|
||||
# define XPT2046_Y_INV 0
|
||||
# define XPT2046_XY_SWAP 0
|
||||
#endif
|
||||
|
||||
/*-----------------
|
||||
* FT5406EE8
|
||||
*-----------------*/
|
||||
#ifndef USE_FT5406EE8
|
||||
# define USE_FT5406EE8 0
|
||||
#endif
|
||||
|
||||
#if USE_FT5406EE8
|
||||
# define FT5406EE8_I2C_ADR 0x38 /*7 bit address*/
|
||||
#endif
|
||||
|
||||
/*---------------
|
||||
* AD TOUCH
|
||||
*--------------*/
|
||||
#ifndef USE_AD_TOUCH
|
||||
# define USE_AD_TOUCH 0
|
||||
#endif
|
||||
|
||||
#if USE_AD_TOUCH
|
||||
/*No settings*/
|
||||
#endif
|
||||
|
||||
|
||||
/*---------------------------------------
|
||||
* Mouse or touchpad on PC (using SDL)
|
||||
*-------------------------------------*/
|
||||
/*DEPRECATED: Use the SDL driver instead. */
|
||||
#ifndef USE_MOUSE
|
||||
# define USE_MOUSE 0
|
||||
#endif
|
||||
|
||||
#if USE_MOUSE
|
||||
/*No settings*/
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------
|
||||
* Mousewheel as encoder on PC (using SDL)
|
||||
*------------------------------------------*/
|
||||
/*DEPRECATED: Use the SDL driver instead. */
|
||||
#ifndef USE_MOUSEWHEEL
|
||||
# define USE_MOUSEWHEEL 0
|
||||
#endif
|
||||
|
||||
#if USE_MOUSEWHEEL
|
||||
/*No settings*/
|
||||
#endif
|
||||
|
||||
/*-------------------------------------------------
|
||||
* Touchscreen, mouse/touchpad or keyboard as libinput interface (for Linux based systems)
|
||||
*------------------------------------------------*/
|
||||
#ifndef USE_LIBINPUT
|
||||
# define USE_LIBINPUT 0
|
||||
#endif
|
||||
|
||||
#ifndef USE_BSD_LIBINPUT
|
||||
# define USE_BSD_LIBINPUT 0
|
||||
#endif
|
||||
|
||||
#if USE_LIBINPUT || USE_BSD_LIBINPUT
|
||||
/*If only a single device of the same type is connected, you can also auto detect it, e.g.:
|
||||
*#define LIBINPUT_NAME libinput_find_dev(LIBINPUT_CAPABILITY_TOUCH, false)*/
|
||||
# define LIBINPUT_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
|
||||
|
||||
#endif /*USE_LIBINPUT || USE_BSD_LIBINPUT*/
|
||||
|
||||
/*-------------------------------------------------
|
||||
* Mouse or touchpad as evdev interface (for Linux based systems)
|
||||
*------------------------------------------------*/
|
||||
#ifndef USE_EVDEV
|
||||
# define USE_EVDEV 1
|
||||
#endif
|
||||
|
||||
#ifndef USE_BSD_EVDEV
|
||||
# define USE_BSD_EVDEV 0
|
||||
#endif
|
||||
|
||||
#if USE_EVDEV || USE_BSD_EVDEV
|
||||
# define EVDEV_NAME "/dev/input/event1" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
|
||||
# define EVDEV_SWAP_AXES 0 /*Swap the x and y axes of the touchscreen*/
|
||||
|
||||
# define EVDEV_CALIBRATE 0 /*Scale and offset the touchscreen coordinates by using maximum and minimum values for each axis*/
|
||||
|
||||
# if EVDEV_CALIBRATE
|
||||
# define EVDEV_HOR_MIN 0 /*to invert axis swap EVDEV_XXX_MIN by EVDEV_XXX_MAX*/
|
||||
# define EVDEV_HOR_MAX 4096 /*"evtest" Linux tool can help to get the correct calibraion values>*/
|
||||
# define EVDEV_VER_MIN 0
|
||||
# define EVDEV_VER_MAX 4096
|
||||
# endif /*EVDEV_CALIBRATE*/
|
||||
#endif /*USE_EVDEV*/
|
||||
|
||||
/*-------------------------------------------------
|
||||
* Full keyboard support for evdev and libinput interface
|
||||
*------------------------------------------------*/
|
||||
# ifndef USE_XKB
|
||||
# define USE_XKB 0
|
||||
# endif
|
||||
|
||||
#if USE_LIBINPUT || USE_BSD_LIBINPUT || USE_EVDEV || USE_BSD_EVDEV
|
||||
# if USE_XKB
|
||||
# define XKB_KEY_MAP { .rules = NULL, \
|
||||
.model = "pc101", \
|
||||
.layout = "us", \
|
||||
.variant = NULL, \
|
||||
.options = NULL } /*"setxkbmap -query" can help find the right values for your keyboard*/
|
||||
# endif /*USE_XKB*/
|
||||
#endif /*USE_LIBINPUT || USE_BSD_LIBINPUT || USE_EVDEV || USE_BSD_EVDEV*/
|
||||
|
||||
/*-------------------------------
|
||||
* Keyboard of a PC (using SDL)
|
||||
*------------------------------*/
|
||||
/*DEPRECATED: Use the SDL driver instead. */
|
||||
#ifndef USE_KEYBOARD
|
||||
# define USE_KEYBOARD 0
|
||||
#endif
|
||||
|
||||
#if USE_KEYBOARD
|
||||
/*No settings*/
|
||||
#endif
|
||||
|
||||
#endif /*LV_DRV_CONF_H*/
|
||||
|
||||
#endif /*End of "Content enable"*/
|
|
@ -0,0 +1,109 @@
|
|||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "screen.h"
|
||||
#include <lvgl.h>
|
||||
#include <display/fbdev.h>
|
||||
#include <indev/evdev.h>
|
||||
#include "ui/ui.h"
|
||||
#include "ui_index.h"
|
||||
|
||||
#define DISP_BUF_SIZE (300 * 240 * 2)
|
||||
static lv_color_t buf[DISP_BUF_SIZE];
|
||||
static lv_disp_draw_buf_t disp_buf;
|
||||
static lv_disp_drv_t disp_drv;
|
||||
static lv_indev_drv_t indev_drv;
|
||||
|
||||
void lvgl_init(void) {
|
||||
lv_init();
|
||||
fbdev_init();
|
||||
lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);
|
||||
lv_disp_drv_init(&disp_drv);
|
||||
disp_drv.draw_buf = &disp_buf;
|
||||
disp_drv.flush_cb = fbdev_flush;
|
||||
disp_drv.hor_res = 240;
|
||||
disp_drv.ver_res = 300;
|
||||
disp_drv.rotated = LV_DISP_ROT_270;
|
||||
disp_drv.sw_rotate = true;
|
||||
// disp_drv.full_refresh = true;
|
||||
|
||||
lv_disp_drv_register(&disp_drv);
|
||||
|
||||
evdev_init();
|
||||
evdev_set_file("/dev/input/event1");
|
||||
|
||||
lv_indev_drv_init(&indev_drv);
|
||||
indev_drv.type = LV_INDEV_TYPE_POINTER;
|
||||
indev_drv.read_cb = evdev_read;
|
||||
lv_indev_drv_register(&indev_drv);
|
||||
|
||||
ui_init();
|
||||
// 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");
|
||||
}
|
||||
|
||||
void lvgl_tick(void) {
|
||||
lv_timer_handler();
|
||||
ui_tick();
|
||||
}
|
||||
|
||||
uint32_t custom_tick_get(void)
|
||||
{
|
||||
static uint64_t start_ms = 0;
|
||||
if(start_ms == 0) {
|
||||
struct timeval tv_start;
|
||||
gettimeofday(&tv_start, NULL);
|
||||
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
|
||||
}
|
||||
|
||||
struct timeval tv_now;
|
||||
gettimeofday(&tv_now, NULL);
|
||||
uint64_t now_ms;
|
||||
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
|
||||
|
||||
uint32_t time_ms = now_ms - start_ms;
|
||||
return time_ms;
|
||||
}
|
||||
|
||||
lv_obj_t *ui_get_obj(const char *name) {
|
||||
for (size_t i = 0; i < ui_objects_size; i++) {
|
||||
if (strcmp(ui_objects[i].name, name) == 0) {
|
||||
return *ui_objects[i].obj;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char *ui_get_current_screen() {
|
||||
lv_obj_t *scr = lv_scr_act();
|
||||
if (scr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < ui_objects_size; i++) {
|
||||
if (*(ui_objects[i].obj) == scr) {
|
||||
return ui_objects[i].name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lv_img_dsc_t *ui_get_image(const char *name) {
|
||||
for (size_t i = 0; i < ui_images_size; i++) {
|
||||
if (strcmp(ui_images[i].name, name) == 0) {
|
||||
return ui_images[i].img;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
lv_label_set_text(obj, text);
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef SCREEN_H
|
||||
#define SCREEN_H
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
void lvgl_init(void);
|
||||
void lvgl_tick(void);
|
||||
|
||||
void ui_set_text(const char *name, const char *text);
|
||||
lv_obj_t *ui_get_obj(const char *name);
|
||||
lv_img_dsc_t *ui_get_image(const char *name);
|
||||
|
||||
#endif // SCREEN_H
|
|
@ -0,0 +1 @@
|
|||
../eez/src/ui
|
|
@ -0,0 +1,24 @@
|
|||
#!/bin/bash
|
||||
|
||||
cat << EOF > ui_index.c
|
||||
// This file was generated by ui_index.gen.sh, do not edit it manually
|
||||
#include "ui_index.h"
|
||||
|
||||
ui_obj_map ui_objects[] = {
|
||||
$(grep -h "lv_obj_t \*" ui/screens.h | sed 's/lv_obj_t \*//g' | sed 's/;//g' | while read -r line; do
|
||||
echo " {\"$line\", &(objects.$line)},"
|
||||
done)
|
||||
};
|
||||
|
||||
const int ui_objects_size = sizeof(ui_objects) / sizeof(ui_objects[0]);
|
||||
|
||||
ui_img_map ui_images[] = {
|
||||
$(grep "extern const lv_img_dsc_t " ui/images.h | sed 's/extern const lv_img_dsc_t //g' | sed 's/;//g' | while read -r line; do
|
||||
echo " {\"$line\", &$line},"
|
||||
done)
|
||||
};
|
||||
|
||||
const int ui_images_size = sizeof(ui_images) / sizeof(ui_images[0]);
|
||||
EOF
|
||||
|
||||
echo "ui_index.c has been generated successfully."
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef UI_INDEX_H
|
||||
#define UI_INDEX_H
|
||||
|
||||
#include "ui/ui.h"
|
||||
#include "ui/screens.h"
|
||||
#include "ui/images.h"
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
lv_obj_t **obj; // Pointer to the object pointer, as the object pointer is only populated after the ui is initialized
|
||||
} ui_obj_map;
|
||||
|
||||
extern ui_obj_map ui_objects[];
|
||||
extern const int ui_objects_size;
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const lv_img_dsc_t *img; // Pointer to the image descriptor const
|
||||
} ui_img_map;
|
||||
|
||||
extern ui_img_map ui_images[];
|
||||
extern const int ui_images_size;
|
||||
|
||||
|
||||
#endif // UI_INDEX_H
|
|
@ -0,0 +1,754 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
#include <rk_type.h>
|
||||
#include <rk_mpi_venc.h>
|
||||
#include <string.h>
|
||||
#include <rk_debug.h>
|
||||
#include <malloc.h>
|
||||
#include <stdbool.h>
|
||||
#include <rk_mpi_mb.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <rk_mpi_mmz.h>
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/socket.h>
|
||||
#include "video.h"
|
||||
#include "ctrl.h"
|
||||
|
||||
#define VIDEO_DEV "/dev/video0"
|
||||
#define SUB_DEV "/dev/v4l-subdev2"
|
||||
|
||||
#define RK_ALIGN(x, a) (((x) + (a)-1) & ~((a)-1))
|
||||
#define RK_ALIGN_2(x) RK_ALIGN(x, 2)
|
||||
#define RK_ALIGN_16(x) RK_ALIGN(x, 16)
|
||||
#define RK_ALIGN_32(x) RK_ALIGN(x, 32)
|
||||
|
||||
int sub_dev_fd = -1;
|
||||
#define VENC_CHANNEL 0
|
||||
MB_POOL memPool = MB_INVALID_POOLID;
|
||||
|
||||
bool should_exit = false;
|
||||
float quality_factor = 1.0f;
|
||||
|
||||
static void *venc_read_stream(void *arg);
|
||||
|
||||
RK_U64 get_us()
|
||||
{
|
||||
struct timespec time = {0, 0};
|
||||
clock_gettime(CLOCK_MONOTONIC, &time);
|
||||
return (RK_U64)time.tv_sec * 1000000 + (RK_U64)time.tv_nsec / 1000; /* microseconds */
|
||||
}
|
||||
|
||||
double calculate_bitrate(float bitrate_factor, int width, int height)
|
||||
{
|
||||
const int32_t base_bitrate_high = 2000;
|
||||
const int32_t base_bitrate_low = 512;
|
||||
|
||||
double pixels = (double)width * height;
|
||||
double ref_pixels = 1920.0 * 1080.0;
|
||||
|
||||
double scale_factor = pixels / ref_pixels;
|
||||
|
||||
int32_t base_bitrate = base_bitrate_low + (int32_t)((base_bitrate_high - base_bitrate_low) * bitrate_factor);
|
||||
|
||||
int32_t bitrate = (int32_t)(base_bitrate * scale_factor);
|
||||
|
||||
const int32_t min_bitrate = 100;
|
||||
if (bitrate < min_bitrate)
|
||||
{
|
||||
bitrate = min_bitrate;
|
||||
}
|
||||
|
||||
return bitrate;
|
||||
}
|
||||
|
||||
static void populate_venc_attr(VENC_CHN_ATTR_S *stAttr, RK_U32 bitrate, RK_U32 max_bitrate, RK_U32 width, RK_U32 height)
|
||||
{
|
||||
memset(stAttr, 0, sizeof(VENC_CHN_ATTR_S));
|
||||
|
||||
stAttr->stRcAttr.enRcMode = VENC_RC_MODE_H264VBR;
|
||||
stAttr->stRcAttr.stH264Vbr.u32BitRate = bitrate;
|
||||
stAttr->stRcAttr.stH264Vbr.u32MaxBitRate = max_bitrate;
|
||||
stAttr->stRcAttr.stH264Vbr.u32Gop = 60;
|
||||
|
||||
stAttr->stVencAttr.enType = RK_VIDEO_ID_AVC;
|
||||
stAttr->stVencAttr.enPixelFormat = RK_FMT_YUV422_YUYV;
|
||||
stAttr->stVencAttr.u32Profile = H264E_PROFILE_HIGH;
|
||||
stAttr->stVencAttr.u32PicWidth = width;
|
||||
stAttr->stVencAttr.u32PicHeight = height;
|
||||
// stAttr->stVencAttr.u32VirWidth = (width + 15) & (~15);
|
||||
// stAttr->stVencAttr.u32VirHeight = (height + 15) & (~15);
|
||||
stAttr->stVencAttr.u32VirWidth = RK_ALIGN_2(width);
|
||||
stAttr->stVencAttr.u32VirHeight = RK_ALIGN_2(height);
|
||||
stAttr->stVencAttr.u32StreamBufCnt = 3;
|
||||
stAttr->stVencAttr.u32BufSize = width * height * 3 / 2;
|
||||
stAttr->stVencAttr.enMirror = MIRROR_NONE;
|
||||
}
|
||||
|
||||
pthread_t *venc_read_thread = NULL;
|
||||
volatile bool venc_running = false;
|
||||
static int32_t venc_start(int32_t bitrate, int32_t max_bitrate, int32_t width, int32_t height)
|
||||
{
|
||||
int32_t ret;
|
||||
VENC_CHN_ATTR_S stAttr;
|
||||
populate_venc_attr(&stAttr, bitrate, max_bitrate, width, height);
|
||||
|
||||
ret = RK_MPI_VENC_CreateChn(VENC_CHANNEL, &stAttr);
|
||||
if (ret < 0)
|
||||
{
|
||||
RK_LOGE("error RK_MPI_VENC_CreateChn, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
VENC_RECV_PIC_PARAM_S stRecvParam;
|
||||
memset(&stRecvParam, 0, sizeof(VENC_RECV_PIC_PARAM_S));
|
||||
stRecvParam.s32RecvPicNum = -1;
|
||||
ret = RK_MPI_VENC_StartRecvFrame(VENC_CHANNEL, &stRecvParam);
|
||||
if (ret < 0)
|
||||
{
|
||||
RK_LOGE("error RK_MPI_VENC_StartRecvFrame, %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
venc_running = true;
|
||||
venc_read_thread = malloc(sizeof(pthread_t));
|
||||
if (pthread_create(venc_read_thread, NULL, venc_read_stream, NULL) != 0)
|
||||
{
|
||||
RK_LOGE("Failed to create venc_read_thread");
|
||||
return RK_FAILURE;
|
||||
}
|
||||
|
||||
return RK_SUCCESS;
|
||||
}
|
||||
|
||||
static int32_t venc_stop()
|
||||
{
|
||||
venc_running = false;
|
||||
|
||||
int32_t ret;
|
||||
ret = RK_MPI_VENC_StopRecvFrame(VENC_CHANNEL);
|
||||
if (ret != RK_SUCCESS)
|
||||
{
|
||||
RK_LOGE("Failed to stop receiving frames for VENC_CHANNEL, error code: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (venc_read_thread != NULL)
|
||||
{
|
||||
pthread_join(*venc_read_thread, NULL);
|
||||
free(venc_read_thread);
|
||||
venc_read_thread = NULL;
|
||||
}
|
||||
|
||||
ret = RK_MPI_VENC_DestroyChn(VENC_CHANNEL);
|
||||
if (ret != RK_SUCCESS)
|
||||
{
|
||||
RK_LOGE("Failed to destroy VENC_CHANNEL, error code: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return RK_SUCCESS;
|
||||
}
|
||||
|
||||
struct buffer
|
||||
{
|
||||
struct v4l2_plane plane_buffer;
|
||||
MB_BLK mb_blk;
|
||||
};
|
||||
|
||||
const int input_buffer_count = 3;
|
||||
|
||||
static int32_t buf_init()
|
||||
{
|
||||
MB_POOL_CONFIG_S stMbPoolCfg;
|
||||
memset(&stMbPoolCfg, 0, sizeof(MB_POOL_CONFIG_S));
|
||||
stMbPoolCfg.u64MBSize = 1920 * 1080 * 3; // max resolution
|
||||
stMbPoolCfg.u32MBCnt = input_buffer_count;
|
||||
stMbPoolCfg.enAllocType = MB_ALLOC_TYPE_DMA;
|
||||
stMbPoolCfg.bPreAlloc = RK_TRUE;
|
||||
memPool = RK_MPI_MB_CreatePool(&stMbPoolCfg);
|
||||
if (memPool == MB_INVALID_POOLID)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
printf("Created memory pool\n");
|
||||
|
||||
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)
|
||||
{
|
||||
printf("can not connect to video socket\n");
|
||||
return -1;
|
||||
}
|
||||
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));
|
||||
return errno;
|
||||
}
|
||||
printf("Opened control sub device %s\n", SUB_DEV);
|
||||
}
|
||||
|
||||
int32_t ret = buf_init();
|
||||
if (ret != RK_SUCCESS)
|
||||
{
|
||||
RK_LOGE("buf_init failed with error: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
printf("buf_init completed successfully\n");
|
||||
|
||||
format_thread = malloc(sizeof(pthread_t));
|
||||
pthread_create(format_thread, NULL, run_detect_format, NULL);
|
||||
return RK_SUCCESS;
|
||||
}
|
||||
|
||||
// static int32_t venc_set_param(int32_t bitrate, int32_t max_bitrate, int32_t width, int32_t height)
|
||||
// {
|
||||
|
||||
// VENC_CHN_ATTR_S stAttr;
|
||||
// populate_venc_attr(&stAttr, bitrate, max_bitrate, width, height);
|
||||
// VENC_CHN_PARAM_S stParam;
|
||||
// memset(&stParam, 0, sizeof(VENC_CHN_PARAM_S));
|
||||
|
||||
// RK_MPI_VENC_StopRecvFrame(VENC_CHANNEL);
|
||||
|
||||
// int32_t ret = RK_MPI_VENC_SetChnParam(VENC_CHANNEL, &stAttr);
|
||||
// if (ret < 0)
|
||||
// {
|
||||
// RK_LOGE("error RK_MPI_VENC_SetChnParam, %d", ret);
|
||||
// return ret;
|
||||
// }
|
||||
// VENC_RECV_PIC_PARAM_S stRecvParam;
|
||||
// memset(&stRecvParam, 0, sizeof(VENC_RECV_PIC_PARAM_S));
|
||||
// stRecvParam.s32RecvPicNum = -1;
|
||||
// ret = RK_MPI_VENC_StartRecvFrame(VENC_CHANNEL, &stRecvParam);
|
||||
// if (ret < 0)
|
||||
// {
|
||||
// RK_LOGE("error RK_MPI_VENC_StartRecvFrame, %d", ret);
|
||||
// return ret;
|
||||
// }
|
||||
|
||||
// return RK_SUCCESS;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @brief Continuously reads encoded video streams and sends them over unix socket.
|
||||
*
|
||||
* @param arg Unused parameter (void pointer for thread compatibility)
|
||||
* @return NULL Always returns NULL
|
||||
*/
|
||||
static void *venc_read_stream(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
void *pData = RK_NULL;
|
||||
int loopCount = 0;
|
||||
int s32Ret;
|
||||
|
||||
VENC_STREAM_S stFrame;
|
||||
stFrame.pstPack = malloc(sizeof(VENC_PACK_S));
|
||||
while (venc_running)
|
||||
{
|
||||
// printf("RK_MPI_VENC_GetStream\n");
|
||||
s32Ret = RK_MPI_VENC_GetStream(VENC_CHANNEL, &stFrame, 200); // blocks max 200ms
|
||||
if (s32Ret == RK_SUCCESS)
|
||||
{
|
||||
RK_U64 nowUs = get_us();
|
||||
// printf("chn:0, loopCount:%d enc->seq:%d wd:%d pts=%llu delay=%lldus\n",
|
||||
// loopCount, stFrame.u32Seq, stFrame.pstPack->u32Len,
|
||||
// stFrame.pstPack->u64PTS, nowUs - stFrame.pstPack->u64PTS);
|
||||
pData = RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
|
||||
socket_send_frame(pData, (ssize_t)stFrame.pstPack->u32Len);
|
||||
s32Ret = RK_MPI_VENC_ReleaseStream(VENC_CHANNEL, &stFrame);
|
||||
if (s32Ret != RK_SUCCESS)
|
||||
{
|
||||
RK_LOGE("RK_MPI_VENC_ReleaseStream fail %x", s32Ret);
|
||||
}
|
||||
loopCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (s32Ret == RK_ERR_VENC_BUF_EMPTY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RK_LOGE("RK_MPI_VENC_GetStream fail %x", s32Ret);
|
||||
break;
|
||||
}
|
||||
}
|
||||
printf("exiting venc_read_stream\n");
|
||||
free(stFrame.pstPack);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t detected_width, detected_height;
|
||||
bool detected_signal = false, streaming_flag = false;
|
||||
|
||||
pthread_t *streaming_thread = NULL;
|
||||
|
||||
void write_buffer_to_file(const uint8_t *buffer, size_t length, const char *filename)
|
||||
{
|
||||
FILE *file = fopen(filename, "wb");
|
||||
fwrite(buffer, 1, length, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
void *run_video_stream(void *arg)
|
||||
{
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
|
||||
while (streaming_flag)
|
||||
{
|
||||
if (detected_signal == false)
|
||||
{
|
||||
usleep(100000);
|
||||
continue;
|
||||
}
|
||||
|
||||
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));
|
||||
usleep(1000000);
|
||||
continue;
|
||||
}
|
||||
printf("Opened video capture device %s\n", VIDEO_DEV);
|
||||
|
||||
uint32_t width = detected_width;
|
||||
uint32_t height = detected_height;
|
||||
struct v4l2_format fmt;
|
||||
memset(&fmt, 0, sizeof(struct v4l2_format));
|
||||
fmt.type = type;
|
||||
fmt.fmt.pix_mp.width = width;
|
||||
fmt.fmt.pix_mp.height = height;
|
||||
fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix_mp.field = V4L2_FIELD_ANY;
|
||||
|
||||
if (ioctl(video_dev_fd, VIDIOC_S_FMT, &fmt) < 0)
|
||||
{
|
||||
perror("Set format fail");
|
||||
usleep(100000); // Sleep for 100 milliseconds
|
||||
close(video_dev_fd);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct v4l2_buffer buf;
|
||||
|
||||
struct v4l2_requestbuffers req;
|
||||
req.count = input_buffer_count;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
req.memory = V4L2_MEMORY_DMABUF;
|
||||
|
||||
if (ioctl(video_dev_fd, VIDIOC_REQBUFS, &req) < 0)
|
||||
{
|
||||
perror("VIDIOC_REQBUFS failed");
|
||||
return errno;
|
||||
}
|
||||
printf("VIDIOC_REQBUFS successful\n");
|
||||
|
||||
struct buffer buffers[3] = {};
|
||||
printf("Allocated buffers\n");
|
||||
|
||||
for (int i = 0; i < input_buffer_count; i++)
|
||||
{
|
||||
struct v4l2_plane *planes_buffer = &buffers[i].plane_buffer;
|
||||
memset(planes_buffer, 0, sizeof(struct v4l2_plane));
|
||||
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
buf.memory = V4L2_MEMORY_DMABUF;
|
||||
buf.m.planes = planes_buffer;
|
||||
buf.length = 1;
|
||||
buf.index = i;
|
||||
|
||||
if (-1 == ioctl(video_dev_fd, VIDIOC_QUERYBUF, &buf))
|
||||
{
|
||||
perror("VIDIOC_QUERYBUF failed");
|
||||
req.count = i;
|
||||
return errno;
|
||||
}
|
||||
printf("VIDIOC_QUERYBUF successful for buffer %d\n", i);
|
||||
|
||||
printf("plane: length = %d\n", planes_buffer->length);
|
||||
printf("plane: offset = %d\n", planes_buffer->m.mem_offset);
|
||||
|
||||
MB_BLK blk = RK_MPI_MB_GetMB(memPool, (planes_buffer)->length, RK_TRUE);
|
||||
if (blk == NULL)
|
||||
{
|
||||
RK_LOGE("get mb blk failed!");
|
||||
return -1;
|
||||
}
|
||||
printf("Got memory block for buffer %d\n", i);
|
||||
|
||||
buffers[i].mb_blk = blk;
|
||||
|
||||
RK_S32 buf_fd = (RK_MPI_MB_Handle2Fd(blk));
|
||||
if (buf_fd < 0)
|
||||
{
|
||||
RK_LOGE("RK_MPI_MB_Handle2Fd failed!");
|
||||
return -1;
|
||||
}
|
||||
printf("Converted memory block to file descriptor for buffer %d\n", i);
|
||||
planes_buffer->m.fd = buf_fd;
|
||||
}
|
||||
|
||||
for (int i = 0; i < input_buffer_count; ++i)
|
||||
{
|
||||
struct v4l2_buffer buf;
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
buf.memory = V4L2_MEMORY_DMABUF;
|
||||
buf.length = 1;
|
||||
buf.index = i;
|
||||
buf.m.planes = &buffers[i].plane_buffer;
|
||||
if (ioctl(video_dev_fd, VIDIOC_QBUF, &buf) < 0)
|
||||
{
|
||||
perror("VIDIOC_QBUF failed");
|
||||
return errno;
|
||||
}
|
||||
printf("VIDIOC_QBUF successful for buffer %d\n", i);
|
||||
}
|
||||
|
||||
if (ioctl(video_dev_fd, VIDIOC_STREAMON, &type) < 0)
|
||||
{
|
||||
perror("VIDIOC_STREAMON failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
struct v4l2_plane tmp_plane;
|
||||
|
||||
// Set VENC parameters
|
||||
int32_t bitrate = calculate_bitrate(quality_factor, width, height);
|
||||
RK_S32 ret = venc_start(bitrate, bitrate * 2, width, height);
|
||||
if (ret != RK_SUCCESS)
|
||||
{
|
||||
RK_LOGE("Set VENC parameters failed with %#x", ret);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
int r;
|
||||
uint32_t num = 0;
|
||||
VIDEO_FRAME_INFO_S stFrame;
|
||||
|
||||
while (streaming_flag)
|
||||
{
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(video_dev_fd, &fds);
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
r = select(video_dev_fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (r == 0)
|
||||
{
|
||||
printf("select timeout \n");
|
||||
break;
|
||||
}
|
||||
if (r == -1)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
perror("select in video streaming");
|
||||
break;
|
||||
}
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
|
||||
buf.memory = V4L2_MEMORY_DMABUF;
|
||||
buf.m.planes = &tmp_plane;
|
||||
buf.length = 1;
|
||||
if (ioctl(video_dev_fd, VIDIOC_DQBUF, &buf) < 0)
|
||||
{
|
||||
perror("VIDIOC_DQBUF failed");
|
||||
break;
|
||||
}
|
||||
// printf("got frame, bytesused = %d\n", tmp_plane.bytesused);
|
||||
memset(&stFrame, 0, sizeof(VIDEO_FRAME_INFO_S));
|
||||
MB_BLK blk = RK_NULL;
|
||||
blk = RK_MPI_MMZ_Fd2Handle(tmp_plane.m.fd);
|
||||
assert(blk != RK_NULL);
|
||||
stFrame.stVFrame.pMbBlk = blk;
|
||||
stFrame.stVFrame.u32Width = width;
|
||||
stFrame.stVFrame.u32Height = height;
|
||||
// stFrame.stVFrame.u32VirWidth = (width + 15) & (~15);
|
||||
// stFrame.stVFrame.u32VirHeight = (height + 15) & (~15);
|
||||
stFrame.stVFrame.u32VirWidth = RK_ALIGN_2(width);
|
||||
stFrame.stVFrame.u32VirHeight = RK_ALIGN_2(height);
|
||||
stFrame.stVFrame.u32TimeRef = num; // frame number
|
||||
stFrame.stVFrame.u64PTS = get_us();
|
||||
stFrame.stVFrame.enPixelFormat = RK_FMT_YUV422_YUYV;
|
||||
stFrame.stVFrame.u32FrameFlag |= 0;
|
||||
stFrame.stVFrame.enCompressMode = COMPRESS_MODE_NONE;
|
||||
bool retried = false;
|
||||
// if (num == 100) {
|
||||
// RK_VOID *pData = RK_MPI_MB_Handle2VirAddr(stFrame.stVFrame.pMbBlk);
|
||||
// if (pData) {
|
||||
// size_t frameSize = tmp_plane.bytesused; // Use the actual size reported by the driver
|
||||
// write_buffer_to_file(pData, frameSize, "/userdata/banana.raw");
|
||||
// printf("Frame 100 written to /userdata/banana.raw\n");
|
||||
// } else {
|
||||
// printf("Failed to get virtual address for frame 100\n");
|
||||
// }
|
||||
// }
|
||||
retry_send_frame:
|
||||
if (RK_MPI_VENC_SendFrame(VENC_CHANNEL, &stFrame, 2000) != RK_SUCCESS)
|
||||
{
|
||||
if (retried == true)
|
||||
{
|
||||
RK_LOGE("RK_MPI_VENC_SendFrame retry failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
RK_LOGE("RK_MPI_VENC_SendFrame failed,retrying");
|
||||
retried = true;
|
||||
usleep(1000llu);
|
||||
goto retry_send_frame;
|
||||
}
|
||||
}
|
||||
|
||||
num++;
|
||||
|
||||
if (ioctl(video_dev_fd, VIDIOC_QBUF, &buf) < 0)
|
||||
printf("failture VIDIOC_QBUF\n");
|
||||
}
|
||||
cleanup:
|
||||
if (ioctl(video_dev_fd, VIDIOC_STREAMOFF, &type) < 0)
|
||||
{
|
||||
perror("VIDIOC_STREAMOFF failed");
|
||||
}
|
||||
|
||||
venc_stop();
|
||||
|
||||
for (int i = 0; i < input_buffer_count; i++)
|
||||
{
|
||||
if (buffers[i].mb_blk != NULL)
|
||||
{
|
||||
RK_MPI_MB_ReleaseMB((buffers + i)->mb_blk);
|
||||
}
|
||||
}
|
||||
|
||||
close(video_dev_fd);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void video_shutdown()
|
||||
{
|
||||
if (should_exit == true)
|
||||
{
|
||||
printf("shutting down in progress already\n");
|
||||
return;
|
||||
}
|
||||
video_stop_streaming();
|
||||
// if (buffers != NULL) {
|
||||
// for (int i = 0; i < input_buffer_count; i++) {
|
||||
// if ((buffers + i)->mb_blk != NULL) {
|
||||
// RK_MPI_MB_ReleaseMB((buffers + i)->mb_blk);
|
||||
// }
|
||||
// free((buffers + i)->planes_buffer);
|
||||
// }
|
||||
// free(buffers);
|
||||
// }
|
||||
should_exit = true;
|
||||
if (sub_dev_fd > 0)
|
||||
{
|
||||
shutdown(sub_dev_fd, SHUT_RDWR);
|
||||
// close(sub_dev_fd);
|
||||
printf("Closed sub_dev_fd\n");
|
||||
}
|
||||
|
||||
if (memPool != MB_INVALID_POOLID)
|
||||
{
|
||||
RK_MPI_MB_DestroyPool(memPool);
|
||||
}
|
||||
printf("Destroyed memory pool\n");
|
||||
// if (format_thread != NULL) {
|
||||
// pthread_join(*format_thread, NULL);
|
||||
// free(format_thread);
|
||||
// format_thread = NULL;
|
||||
// }
|
||||
// printf("Joined format detection thread\n");
|
||||
}
|
||||
|
||||
// TODO: mutex?
|
||||
|
||||
void video_start_streaming()
|
||||
{
|
||||
if (streaming_thread != NULL)
|
||||
{
|
||||
printf("video streaming already started\n");
|
||||
return;
|
||||
}
|
||||
streaming_thread = malloc(sizeof(pthread_t));
|
||||
assert(streaming_thread != NULL);
|
||||
streaming_flag = true;
|
||||
pthread_create(streaming_thread, NULL, run_video_stream, NULL);
|
||||
}
|
||||
|
||||
void video_stop_streaming()
|
||||
{
|
||||
if (streaming_thread != NULL)
|
||||
{
|
||||
streaming_flag = false;
|
||||
pthread_join(*streaming_thread, NULL);
|
||||
free(streaming_thread);
|
||||
streaming_thread = NULL;
|
||||
printf("video streaming stopped\n");
|
||||
}
|
||||
}
|
||||
|
||||
void *run_detect_format(void *arg)
|
||||
{
|
||||
struct v4l2_event_subscription sub;
|
||||
struct v4l2_event ev;
|
||||
struct v4l2_dv_timings dv_timings;
|
||||
|
||||
memset(&sub, 0, sizeof(sub));
|
||||
sub.type = V4L2_EVENT_SOURCE_CHANGE;
|
||||
if (ioctl(sub_dev_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) == -1)
|
||||
{
|
||||
perror("Cannot subscribe to event");
|
||||
goto exit;
|
||||
}
|
||||
|
||||
while (!should_exit)
|
||||
{
|
||||
memset(&dv_timings, 0, sizeof(dv_timings));
|
||||
if (ioctl(sub_dev_fd, VIDIOC_QUERY_DV_TIMINGS, &dv_timings) != 0)
|
||||
{
|
||||
detected_signal = false;
|
||||
if (errno == ENOLINK)
|
||||
{
|
||||
// No timings could be detected because no signal was found.
|
||||
printf("HDMI status: no signal\n");
|
||||
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");
|
||||
report_video_format(false, "no_lock", 0, 0, 0);
|
||||
}
|
||||
else if (errno == ERANGE)
|
||||
{
|
||||
// Timings were found, but they are out of range of the hardware capabilities.
|
||||
printf("HDMI status: out of range\n");
|
||||
report_video_format(false, "out_of_range", 0, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
perror("error VIDIOC_QUERY_DV_TIMINGS");
|
||||
sleep(1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Active width: %d\n", dv_timings.bt.width);
|
||||
printf("Active height: %d\n", 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);
|
||||
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");
|
||||
video_stop_streaming();
|
||||
video_start_streaming();
|
||||
}
|
||||
}
|
||||
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
if (ioctl(sub_dev_fd, VIDIOC_DQEVENT, &ev) != 0)
|
||||
{
|
||||
perror("failed to VIDIOC_DQEVENT");
|
||||
break;
|
||||
}
|
||||
printf("New event of type %u\n", ev.type);
|
||||
if (ev.type != V4L2_EVENT_SOURCE_CHANGE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
printf("source change detected!\n");
|
||||
}
|
||||
exit:
|
||||
close(sub_dev_fd);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void video_set_quality_factor(float factor)
|
||||
{
|
||||
quality_factor = factor;
|
||||
|
||||
// TODO: update venc bitrate without stopping streaming
|
||||
|
||||
if (streaming_flag == true)
|
||||
{
|
||||
printf("restarting on going video streaming due to quality factor change\n");
|
||||
video_stop_streaming();
|
||||
video_start_streaming();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#ifndef VIDEO_DAEMON_VIDEO_H
|
||||
#define VIDEO_DAEMON_VIDEO_H
|
||||
|
||||
int video_init();
|
||||
void video_shutdown();
|
||||
void *run_detect_format(void *arg);
|
||||
void video_start_streaming();
|
||||
void video_stop_streaming();
|
||||
|
||||
void video_set_quality_factor(float factor);
|
||||
|
||||
#endif //VIDEO_DAEMON_VIDEO_H
|
|
@ -0,0 +1 @@
|
|||
cgo/ctrl.h
|
|
@ -0,0 +1,179 @@
|
|||
//go:build linux
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #cgo LDFLAGS: -Ljetkvm-native
|
||||
// #include "ctrl.h"
|
||||
// #include <stdlib.h>
|
||||
// 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);
|
||||
// }
|
||||
import "C"
|
||||
|
||||
//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))
|
||||
}
|
||||
|
||||
func setVideoStateHandler() {
|
||||
C.jetkvm_setup_video_state_handler()
|
||||
}
|
||||
|
||||
func (n *Native) StartNativeVideo() {
|
||||
setVideoStateHandler()
|
||||
C.jetkvm_ui_init()
|
||||
|
||||
n.UpdateLabelIfChanged("boot_screen_version", n.AppVersion.String())
|
||||
|
||||
go func() {
|
||||
for {
|
||||
C.jetkvm_ui_tick()
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
if C.jetkvm_video_init() != 0 {
|
||||
nativeLogger.Error().Msg("failed to initialize video")
|
||||
return
|
||||
}
|
||||
C.jetkvm_video_start()
|
||||
|
||||
close(n.ready)
|
||||
}
|
||||
|
||||
func (n *Native) StopNativeVideo() {
|
||||
C.jetkvm_video_stop()
|
||||
}
|
||||
|
||||
func (n *Native) SwitchToScreen(screen string) {
|
||||
screenCStr := C.CString(screen)
|
||||
defer C.free(unsafe.Pointer(screenCStr))
|
||||
C.jetkvm_ui_load_screen(screenCStr)
|
||||
}
|
||||
|
||||
func (n *Native) GetCurrentScreen() string {
|
||||
screenCStr := C.jetkvm_ui_get_current_screen()
|
||||
return C.GoString(screenCStr)
|
||||
}
|
||||
|
||||
func (n *Native) ObjSetState(objName string, state string) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
stateCStr := C.CString(state)
|
||||
defer C.free(unsafe.Pointer(stateCStr))
|
||||
C.jetkvm_ui_set_state(objNameCStr, stateCStr)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjAddFlag(objName string, flag string) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
flagCStr := C.CString(flag)
|
||||
defer C.free(unsafe.Pointer(flagCStr))
|
||||
C.jetkvm_ui_add_flag(objNameCStr, flagCStr)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjClearFlag(objName string, flag string) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
flagCStr := C.CString(flag)
|
||||
defer C.free(unsafe.Pointer(flagCStr))
|
||||
C.jetkvm_ui_clear_flag(objNameCStr, flagCStr)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjHide(objName string) (bool, error) {
|
||||
return n.ObjAddFlag(objName, "LV_OBJ_FLAG_HIDDEN")
|
||||
}
|
||||
|
||||
func (n *Native) ObjShow(objName string) (bool, error) {
|
||||
return n.ObjClearFlag(objName, "LV_OBJ_FLAG_HIDDEN")
|
||||
}
|
||||
|
||||
func (n *Native) ObjSetOpacity(objName string, opacity int) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
|
||||
C.jetkvm_ui_set_opacity(objNameCStr, C.u_int8_t(opacity))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjFadeIn(objName string, duration uint32) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
|
||||
C.jetkvm_ui_fade_in(objNameCStr, C.u_int32_t(duration))
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjFadeOut(objName string, duration uint32) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
|
||||
C.jetkvm_ui_fade_out(objNameCStr, C.u_int32_t(duration))
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) LabelSetText(objName string, text string) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
|
||||
textCStr := C.CString(text)
|
||||
defer C.free(unsafe.Pointer(textCStr))
|
||||
|
||||
ret := C.jetkvm_ui_set_text(objNameCStr, textCStr)
|
||||
if ret < 0 {
|
||||
return false, fmt.Errorf("failed to set text: %d", ret)
|
||||
}
|
||||
return ret == 0, nil
|
||||
}
|
||||
|
||||
func (n *Native) ImgSetSrc(objName string, src string) (bool, error) {
|
||||
objNameCStr := C.CString(objName)
|
||||
defer C.free(unsafe.Pointer(objNameCStr))
|
||||
|
||||
srcCStr := C.CString(src)
|
||||
defer C.free(unsafe.Pointer(srcCStr))
|
||||
|
||||
C.jetkvm_ui_set_image(objNameCStr, srcCStr)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) DispSetRotation(rotation string) (bool, error) {
|
||||
rotationInt, err := strconv.Atoi(rotation)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
nativeLogger.Info().Int("rotation", rotationInt).Msg("setting rotation")
|
||||
// C.jetkvm_ui_set_rotation(C.u_int8_t(rotationInt))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (n *Native) GetStreamQualityFactor() (float64, error) {
|
||||
return 1.0, nil
|
||||
}
|
||||
|
||||
func (n *Native) SetStreamQualityFactor(factor float64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Native) GetEDID() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (n *Native) SetEDID(edid string) error {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
//go:build !linux
|
||||
|
||||
package native
|
||||
|
||||
func panicNotImplemented() {
|
||||
panic("not supported")
|
||||
}
|
||||
|
||||
func (n *Native) StartNativeVideo() {
|
||||
panicNotImplemented()
|
||||
}
|
||||
|
||||
func (n *Native) StopNativeVideo() {
|
||||
panicNotImplemented()
|
||||
}
|
||||
|
||||
func (n *Native) SwitchToScreen(screen string) {
|
||||
panicNotImplemented()
|
||||
}
|
||||
|
||||
func (n *Native) GetCurrentScreen() string {
|
||||
panicNotImplemented()
|
||||
}
|
||||
|
||||
func (n *Native) ObjSetState(objName string, state string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjAddFlag(objName string, flag string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjClearFlag(objName string, flag string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjHide(objName string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjShow(objName string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjSetOpacity(objName string, opacity int) (bool, error) { // nolint:unused
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjFadeIn(objName string, duration uint32) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ObjFadeOut(objName string, duration uint32) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) LabelSetText(objName string, text string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) ImgSetSrc(objName string, src string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) DispSetRotation(rotation string) (bool, error) {
|
||||
panicNotImplemented()
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (n *Native) GetStreamQualityFactor() (float64, error) {
|
||||
panicNotImplemented()
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (n *Native) SetStreamQualityFactor(factor float64) error {
|
||||
panicNotImplemented()
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package native
|
||||
|
||||
import "slices"
|
||||
|
||||
func (n *Native) UpdateLabelIfChanged(objName string, newText string) {
|
||||
l := n.lD.Trace().Str("obj", objName).Str("text", newText)
|
||||
|
||||
changed, err := n.LabelSetText(objName, newText)
|
||||
if err != nil {
|
||||
n.lD.Warn().Str("obj", objName).Str("text", newText).Err(err).Msg("failed to update label")
|
||||
return
|
||||
}
|
||||
|
||||
if changed {
|
||||
l.Msg("label changed")
|
||||
} else {
|
||||
l.Msg("label not changed")
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Native) SwitchToScreenIf(screenName string, shouldSwitch []string) {
|
||||
currentScreen := n.GetCurrentScreen()
|
||||
if currentScreen == screenName {
|
||||
return
|
||||
}
|
||||
if !slices.Contains(shouldSwitch, currentScreen) {
|
||||
displayLogger.Trace().Str("from", currentScreen).Str("to", screenName).Msg("skipping screen switch")
|
||||
return
|
||||
}
|
||||
displayLogger.Info().Str("from", currentScreen).Str("to", screenName).Msg("switching screen")
|
||||
n.SwitchToScreen(screenName)
|
||||
}
|
||||
|
||||
func (n *Native) SwitchToScreenIfDifferent(screenName string) {
|
||||
n.SwitchToScreenIf(screenName, []string{})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
src/ui
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
package native
|
||||
|
||||
import "github.com/jetkvm/kvm/internal/logging"
|
||||
|
||||
var nativeLogger = logging.GetSubsystemLogger("native")
|
||||
var displayLogger = logging.GetSubsystemLogger("display")
|
|
@ -0,0 +1,121 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"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
|
||||
}
|
||||
|
||||
func NewNative(systemVersion *semver.Version, appVersion *semver.Version) *Native {
|
||||
return &Native{
|
||||
ready: make(chan struct{}),
|
||||
l: nativeLogger,
|
||||
lD: displayLogger,
|
||||
SystemVersion: systemVersion,
|
||||
AppVersion: appVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Native) Start() {
|
||||
go n.StartNativeVideo()
|
||||
}
|
||||
|
||||
// func handleCtrlClient(conn net.Conn) {
|
||||
// defer conn.Close()
|
||||
|
||||
// scopedLogger := nativeLogger.With().
|
||||
// Str("addr", conn.RemoteAddr().String()).
|
||||
// Str("type", "ctrl").
|
||||
// Logger()
|
||||
|
||||
// scopedLogger.Info().Msg("native ctrl socket client connected")
|
||||
// if ctrlSocketConn != nil {
|
||||
// scopedLogger.Debug().Msg("closing existing native socket connection")
|
||||
// ctrlSocketConn.Close()
|
||||
// }
|
||||
|
||||
// ctrlSocketConn = conn
|
||||
|
||||
// // Restore HDMI EDID if applicable
|
||||
// go restoreHdmiEdid()
|
||||
|
||||
// readBuf := make([]byte, 4096)
|
||||
// for {
|
||||
// n, err := conn.Read(readBuf)
|
||||
// if err != nil {
|
||||
// scopedLogger.Warn().Err(err).Msg("error reading from ctrl sock")
|
||||
// break
|
||||
// }
|
||||
// readMsg := string(readBuf[:n])
|
||||
|
||||
// ctrlResp := CtrlResponse{}
|
||||
// err = json.Unmarshal([]byte(readMsg), &ctrlResp)
|
||||
// if err != nil {
|
||||
// scopedLogger.Warn().Err(err).Str("data", readMsg).Msg("error parsing ctrl sock msg")
|
||||
// continue
|
||||
// }
|
||||
// scopedLogger.Trace().Interface("data", ctrlResp).Msg("ctrl sock msg")
|
||||
|
||||
// if ctrlResp.Seq != 0 {
|
||||
// responseChan, ok := ongoingRequests[ctrlResp.Seq]
|
||||
// if ok {
|
||||
// responseChan <- &ctrlResp
|
||||
// }
|
||||
// }
|
||||
// switch ctrlResp.Event {
|
||||
// case "video_input_state":
|
||||
// HandleVideoStateMessage(ctrlResp)
|
||||
// }
|
||||
// }
|
||||
|
||||
// scopedLogger.Debug().Msg("ctrl sock disconnected")
|
||||
// }
|
||||
|
||||
// func handleVideoClient(conn net.Conn) {
|
||||
// defer conn.Close()
|
||||
|
||||
// scopedLogger := nativeLogger.With().
|
||||
// Str("addr", conn.RemoteAddr().String()).
|
||||
// Str("type", "video").
|
||||
// Logger()
|
||||
|
||||
// scopedLogger.Info().Msg("native video socket client connected")
|
||||
|
||||
// inboundPacket := make([]byte, maxFrameSize)
|
||||
// lastFrame := time.Now()
|
||||
// for {
|
||||
// n, err := conn.Read(inboundPacket)
|
||||
// if err != nil {
|
||||
// scopedLogger.Warn().Err(err).Msg("error during read")
|
||||
// return
|
||||
// }
|
||||
// now := time.Now()
|
||||
// sinceLastFrame := now.Sub(lastFrame)
|
||||
// lastFrame = now
|
||||
// if currentSession != nil {
|
||||
// err := currentSession.VideoTrack.WriteSample(media.Sample{Data: inboundPacket[:n], Duration: sinceLastFrame})
|
||||
// if err != nil {
|
||||
// scopedLogger.Warn().Err(err).Msg("error writing sample")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Restore the HDMI EDID value from the config.
|
||||
// // Called after successful connection to jetkvm_native.
|
||||
// func restoreHdmiEdid() {
|
||||
// if config.EdidString != "" {
|
||||
// nativeLogger.Info().Str("edid", config.EdidString).Msg("Restoring HDMI EDID")
|
||||
// _, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": config.EdidString})
|
||||
// if err != nil {
|
||||
// nativeLogger.Warn().Err(err).Msg("Failed to restore HDMI EDID")
|
||||
// }
|
||||
// }
|
||||
// }
|
14
jsonrpc.go
14
jsonrpc.go
|
@ -200,7 +200,7 @@ func rpcGetStreamQualityFactor() (float64, error) {
|
|||
|
||||
func rpcSetStreamQualityFactor(factor float64) error {
|
||||
logger.Info().Float64("factor", factor).Msg("Setting stream quality factor")
|
||||
var _, err = CallCtrlAction("set_video_quality_factor", map[string]interface{}{"quality_factor": factor})
|
||||
err := nativeInstance.SetStreamQualityFactor(factor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -222,15 +222,11 @@ func rpcSetAutoUpdateState(enabled bool) (bool, error) {
|
|||
}
|
||||
|
||||
func rpcGetEDID() (string, error) {
|
||||
resp, err := CallCtrlAction("get_edid", nil)
|
||||
resp, err := nativeInstance.GetEDID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
edid, ok := resp.Result["edid"]
|
||||
if ok {
|
||||
return edid.(string), nil
|
||||
}
|
||||
return "", errors.New("EDID not found in response")
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func rpcSetEDID(edid string) error {
|
||||
|
@ -240,7 +236,7 @@ func rpcSetEDID(edid string) error {
|
|||
} else {
|
||||
logger.Info().Str("edid", edid).Msg("Setting EDID")
|
||||
}
|
||||
_, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": edid})
|
||||
err := nativeInstance.SetEDID(edid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -291,7 +287,7 @@ func rpcTryUpdate() error {
|
|||
|
||||
func rpcSetDisplayRotation(params DisplayRotationSettings) error {
|
||||
var err error
|
||||
_, err = lvDispSetRotation(params.Rotation)
|
||||
_, err = nativeInstance.DispSetRotation(params.Rotation)
|
||||
if err == nil {
|
||||
config.DisplayRotation = params.Rotation
|
||||
if err := SaveConfig(); err != nil {
|
||||
|
|
16
main.go
16
main.go
|
@ -33,6 +33,8 @@ func Main() {
|
|||
go runWatchdog()
|
||||
go confirmCurrentSystem()
|
||||
|
||||
initNative(systemVersionLocal, appVersionLocal)
|
||||
|
||||
http.DefaultClient.Timeout = 1 * time.Minute
|
||||
|
||||
err = rootcerts.UpdateDefaultTransport()
|
||||
|
@ -59,22 +61,8 @@ func Main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Initialize native ctrl socket server
|
||||
StartNativeCtrlSocketServer()
|
||||
|
||||
// Initialize native video socket server
|
||||
StartNativeVideoSocketServer()
|
||||
|
||||
initPrometheus()
|
||||
|
||||
go func() {
|
||||
err = ExtractAndRunNativeBin()
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("failed to extract and run native bin")
|
||||
//TODO: prepare an error message screen buffer to show on kvm screen
|
||||
}
|
||||
}()
|
||||
|
||||
// initialize usb gadget
|
||||
initUsbGadget()
|
||||
if err := setInitialVirtualMediaState(); err != nil {
|
||||
|
|
335
native.go
335
native.go
|
@ -1,336 +1,13 @@
|
|||
package kvm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jetkvm/kvm/resource"
|
||||
|
||||
"github.com/pion/webrtc/v4/pkg/media"
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/jetkvm/kvm/internal/native"
|
||||
)
|
||||
|
||||
var ctrlSocketConn net.Conn
|
||||
var nativeInstance *native.Native
|
||||
|
||||
type CtrlAction struct {
|
||||
Action string `json:"action"`
|
||||
Seq int32 `json:"seq,omitempty"`
|
||||
Params map[string]interface{} `json:"params,omitempty"`
|
||||
}
|
||||
|
||||
type CtrlResponse struct {
|
||||
Seq int32 `json:"seq,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Errno int32 `json:"errno,omitempty"`
|
||||
Result map[string]interface{} `json:"result,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
Data json.RawMessage `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type EventHandler func(event CtrlResponse)
|
||||
|
||||
var seq int32 = 1
|
||||
|
||||
var ongoingRequests = make(map[int32]chan *CtrlResponse)
|
||||
|
||||
var lock = &sync.Mutex{}
|
||||
|
||||
func CallCtrlAction(action string, params map[string]interface{}) (*CtrlResponse, error) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
ctrlAction := CtrlAction{
|
||||
Action: action,
|
||||
Seq: seq,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
responseChan := make(chan *CtrlResponse)
|
||||
ongoingRequests[seq] = responseChan
|
||||
seq++
|
||||
|
||||
jsonData, err := json.Marshal(ctrlAction)
|
||||
if err != nil {
|
||||
delete(ongoingRequests, ctrlAction.Seq)
|
||||
return nil, fmt.Errorf("error marshaling ctrl action: %w", err)
|
||||
}
|
||||
|
||||
scopedLogger := nativeLogger.With().
|
||||
Str("action", ctrlAction.Action).
|
||||
Interface("params", ctrlAction.Params).Logger()
|
||||
|
||||
scopedLogger.Debug().Msg("sending ctrl action")
|
||||
|
||||
err = WriteCtrlMessage(jsonData)
|
||||
if err != nil {
|
||||
delete(ongoingRequests, ctrlAction.Seq)
|
||||
return nil, ErrorfL(&scopedLogger, "error writing ctrl message", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case response := <-responseChan:
|
||||
delete(ongoingRequests, seq)
|
||||
if response.Error != "" {
|
||||
return nil, ErrorfL(
|
||||
&scopedLogger,
|
||||
"error native response: %s",
|
||||
errors.New(response.Error),
|
||||
)
|
||||
}
|
||||
return response, nil
|
||||
case <-time.After(5 * time.Second):
|
||||
close(responseChan)
|
||||
delete(ongoingRequests, seq)
|
||||
return nil, ErrorfL(&scopedLogger, "timeout waiting for response", nil)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteCtrlMessage(message []byte) error {
|
||||
if ctrlSocketConn == nil {
|
||||
return fmt.Errorf("ctrl socket not conn ected")
|
||||
}
|
||||
_, err := ctrlSocketConn.Write(message)
|
||||
return err
|
||||
}
|
||||
|
||||
var nativeCtrlSocketListener net.Listener //nolint:unused
|
||||
var nativeVideoSocketListener net.Listener //nolint:unused
|
||||
|
||||
var ctrlClientConnected = make(chan struct{})
|
||||
|
||||
func waitCtrlClientConnected() {
|
||||
<-ctrlClientConnected
|
||||
}
|
||||
|
||||
func StartNativeSocketServer(socketPath string, handleClient func(net.Conn), isCtrl bool) net.Listener {
|
||||
scopedLogger := nativeLogger.With().
|
||||
Str("socket_path", socketPath).
|
||||
Logger()
|
||||
|
||||
// Remove the socket file if it already exists
|
||||
if _, err := os.Stat(socketPath); err == nil {
|
||||
if err := os.Remove(socketPath); err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("failed to remove existing socket file")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
listener, err := net.Listen("unixpacket", socketPath)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("failed to start server")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
scopedLogger.Info().Msg("server listening")
|
||||
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
listener.Close()
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("failed to accept socket")
|
||||
}
|
||||
if isCtrl {
|
||||
close(ctrlClientConnected)
|
||||
scopedLogger.Debug().Msg("first native ctrl socket client connected")
|
||||
}
|
||||
handleClient(conn)
|
||||
}()
|
||||
|
||||
return listener
|
||||
}
|
||||
|
||||
func StartNativeCtrlSocketServer() {
|
||||
nativeCtrlSocketListener = StartNativeSocketServer("/var/run/jetkvm_ctrl.sock", handleCtrlClient, true)
|
||||
nativeLogger.Debug().Msg("native app ctrl sock started")
|
||||
}
|
||||
|
||||
func StartNativeVideoSocketServer() {
|
||||
nativeVideoSocketListener = StartNativeSocketServer("/var/run/jetkvm_video.sock", handleVideoClient, false)
|
||||
nativeLogger.Debug().Msg("native app video sock started")
|
||||
}
|
||||
|
||||
func handleCtrlClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
scopedLogger := nativeLogger.With().
|
||||
Str("addr", conn.RemoteAddr().String()).
|
||||
Str("type", "ctrl").
|
||||
Logger()
|
||||
|
||||
scopedLogger.Info().Msg("native ctrl socket client connected")
|
||||
if ctrlSocketConn != nil {
|
||||
scopedLogger.Debug().Msg("closing existing native socket connection")
|
||||
ctrlSocketConn.Close()
|
||||
}
|
||||
|
||||
ctrlSocketConn = conn
|
||||
|
||||
// Restore HDMI EDID if applicable
|
||||
go restoreHdmiEdid()
|
||||
|
||||
readBuf := make([]byte, 4096)
|
||||
for {
|
||||
n, err := conn.Read(readBuf)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("error reading from ctrl sock")
|
||||
break
|
||||
}
|
||||
readMsg := string(readBuf[:n])
|
||||
|
||||
ctrlResp := CtrlResponse{}
|
||||
err = json.Unmarshal([]byte(readMsg), &ctrlResp)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Str("data", readMsg).Msg("error parsing ctrl sock msg")
|
||||
continue
|
||||
}
|
||||
scopedLogger.Trace().Interface("data", ctrlResp).Msg("ctrl sock msg")
|
||||
|
||||
if ctrlResp.Seq != 0 {
|
||||
responseChan, ok := ongoingRequests[ctrlResp.Seq]
|
||||
if ok {
|
||||
responseChan <- &ctrlResp
|
||||
}
|
||||
}
|
||||
switch ctrlResp.Event {
|
||||
case "video_input_state":
|
||||
HandleVideoStateMessage(ctrlResp)
|
||||
}
|
||||
}
|
||||
|
||||
scopedLogger.Debug().Msg("ctrl sock disconnected")
|
||||
}
|
||||
|
||||
func handleVideoClient(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
scopedLogger := nativeLogger.With().
|
||||
Str("addr", conn.RemoteAddr().String()).
|
||||
Str("type", "video").
|
||||
Logger()
|
||||
|
||||
scopedLogger.Info().Msg("native video socket client connected")
|
||||
|
||||
inboundPacket := make([]byte, maxFrameSize)
|
||||
lastFrame := time.Now()
|
||||
for {
|
||||
n, err := conn.Read(inboundPacket)
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("error during read")
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
sinceLastFrame := now.Sub(lastFrame)
|
||||
lastFrame = now
|
||||
if currentSession != nil {
|
||||
err := currentSession.VideoTrack.WriteSample(media.Sample{Data: inboundPacket[:n], Duration: sinceLastFrame})
|
||||
if err != nil {
|
||||
scopedLogger.Warn().Err(err).Msg("error writing sample")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExtractAndRunNativeBin() error {
|
||||
binaryPath := "/userdata/jetkvm/bin/jetkvm_native"
|
||||
if err := ensureBinaryUpdated(binaryPath); err != nil {
|
||||
return fmt.Errorf("failed to extract binary: %w", err)
|
||||
}
|
||||
|
||||
// Make the binary executable
|
||||
if err := os.Chmod(binaryPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to make binary executable: %w", err)
|
||||
}
|
||||
// Run the binary in the background
|
||||
cmd, err := startNativeBinary(binaryPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to start binary: %w", err)
|
||||
}
|
||||
|
||||
//TODO: add auto restart
|
||||
go func() {
|
||||
<-appCtx.Done()
|
||||
nativeLogger.Info().Int("pid", cmd.Process.Pid).Msg("killing process")
|
||||
err := cmd.Process.Kill()
|
||||
if err != nil {
|
||||
nativeLogger.Warn().Err(err).Msg("failed to kill process")
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
nativeLogger.Info().Int("pid", cmd.Process.Pid).Msg("jetkvm_native binary started")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func shouldOverwrite(destPath string, srcHash []byte) bool {
|
||||
if srcHash == nil {
|
||||
nativeLogger.Debug().Msg("error reading embedded jetkvm_native.sha256, doing overwriting")
|
||||
return true
|
||||
}
|
||||
|
||||
dstHash, err := os.ReadFile(destPath + ".sha256")
|
||||
if err != nil {
|
||||
nativeLogger.Debug().Msg("error reading existing jetkvm_native.sha256, doing overwriting")
|
||||
return true
|
||||
}
|
||||
|
||||
return !bytes.Equal(srcHash, dstHash)
|
||||
}
|
||||
|
||||
func ensureBinaryUpdated(destPath string) error {
|
||||
srcFile, err := resource.ResourceFS.Open("jetkvm_native")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
srcHash, err := resource.ResourceFS.ReadFile("jetkvm_native.sha256")
|
||||
if err != nil {
|
||||
nativeLogger.Debug().Msg("error reading embedded jetkvm_native.sha256, proceeding with update")
|
||||
srcHash = nil
|
||||
}
|
||||
|
||||
_, err = os.Stat(destPath)
|
||||
if shouldOverwrite(destPath, srcHash) || err != nil {
|
||||
nativeLogger.Info().
|
||||
Interface("hash", srcHash).
|
||||
Msg("writing jetkvm_native")
|
||||
|
||||
_ = os.Remove(destPath)
|
||||
destFile, err := os.OpenFile(destPath, os.O_CREATE|os.O_RDWR, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
destFile.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if srcHash != nil {
|
||||
err = os.WriteFile(destPath+".sha256", srcHash, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
nativeLogger.Info().Msg("jetkvm_native updated")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore the HDMI EDID value from the config.
|
||||
// Called after successful connection to jetkvm_native.
|
||||
func restoreHdmiEdid() {
|
||||
if config.EdidString != "" {
|
||||
nativeLogger.Info().Str("edid", config.EdidString).Msg("Restoring HDMI EDID")
|
||||
_, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": config.EdidString})
|
||||
if err != nil {
|
||||
nativeLogger.Warn().Err(err).Msg("Failed to restore HDMI EDID")
|
||||
}
|
||||
}
|
||||
func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
|
||||
nativeInstance = native.NewNative(systemVersion, appVersion)
|
||||
nativeInstance.Start()
|
||||
}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
//go:build linux
|
||||
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type nativeOutput struct {
|
||||
mu *sync.Mutex
|
||||
logger *zerolog.Event
|
||||
}
|
||||
|
||||
func (w *nativeOutput) Write(p []byte) (n int, err error) {
|
||||
w.mu.Lock()
|
||||
defer w.mu.Unlock()
|
||||
|
||||
w.logger.Msg(string(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func startNativeBinary(binaryPath string) (*exec.Cmd, error) {
|
||||
// Run the binary in the background
|
||||
cmd := exec.Command(binaryPath)
|
||||
|
||||
nativeOutputLock := sync.Mutex{}
|
||||
nativeStdout := &nativeOutput{
|
||||
mu: &nativeOutputLock,
|
||||
logger: nativeLogger.Info().Str("pipe", "stdout"),
|
||||
}
|
||||
nativeStderr := &nativeOutput{
|
||||
mu: &nativeOutputLock,
|
||||
logger: nativeLogger.Info().Str("pipe", "stderr"),
|
||||
}
|
||||
|
||||
// Redirect stdout and stderr to the current process
|
||||
cmd.Stdout = nativeStdout
|
||||
cmd.Stderr = nativeStderr
|
||||
|
||||
// Set the process group ID so we can kill the process and its children when this process exits
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pdeathsig: syscall.SIGKILL,
|
||||
}
|
||||
|
||||
// Start the command
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("failed to start binary: %w", err)
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
//go:build !linux
|
||||
|
||||
package kvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func startNativeBinary(binaryPath string) (*exec.Cmd, error) {
|
||||
return nil, fmt.Errorf("not supported")
|
||||
}
|
|
@ -4,5 +4,5 @@ import (
|
|||
"embed"
|
||||
)
|
||||
|
||||
//go:embed jetkvm_native jetkvm_native.sha256 netboot.xyz-multiarch.iso
|
||||
//go:embed netboot.xyz-multiarch.iso
|
||||
var ResourceFS embed.FS
|
||||
|
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
6dabd0e657dd099280d9173069687786a4a8c9c25cf7f9e7ce2f940cab67c521
|
37
video.go
37
video.go
|
@ -1,22 +1,10 @@
|
|||
package kvm
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// max frame size for 1080p video, specified in mpp venc setting
|
||||
const maxFrameSize = 1920 * 1080 / 2
|
||||
|
||||
func writeCtrlAction(action string) error {
|
||||
actionMessage := map[string]string{
|
||||
"action": action,
|
||||
}
|
||||
jsonMessage, err := json.Marshal(actionMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = WriteCtrlMessage(jsonMessage)
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
type VideoInputState struct {
|
||||
|
@ -34,17 +22,18 @@ func triggerVideoStateUpdate() {
|
|||
writeJSONRPCEvent("videoInputState", lastVideoState, currentSession)
|
||||
}()
|
||||
}
|
||||
func HandleVideoStateMessage(event CtrlResponse) {
|
||||
videoState := VideoInputState{}
|
||||
err := json.Unmarshal(event.Data, &videoState)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("Error parsing video state json")
|
||||
return
|
||||
}
|
||||
lastVideoState = videoState
|
||||
triggerVideoStateUpdate()
|
||||
requestDisplayUpdate(true)
|
||||
}
|
||||
|
||||
// func HandleVideoStateMessage(event CtrlResponse) {
|
||||
// videoState := VideoInputState{}
|
||||
// err := json.Unmarshal(event.Data, &videoState)
|
||||
// if err != nil {
|
||||
// logger.Warn().Err(err).Msg("Error parsing video state json")
|
||||
// return
|
||||
// }
|
||||
// lastVideoState = videoState
|
||||
// triggerVideoStateUpdate()
|
||||
// requestDisplayUpdate(true)
|
||||
// }
|
||||
|
||||
func rpcGetVideoState() (VideoInputState, error) {
|
||||
return lastVideoState, nil
|
||||
|
|
Loading…
Reference in New Issue