Compare commits

..

10 Commits

Author SHA1 Message Date
Siyuan Miao 23a3aaa61d dd script to build inside docker 2025-09-25 16:27:01 +00:00
Siyuan Miao 358512fa83 cleanup 2025-09-25 15:21:08 +00:00
Siyuan Miao 70db172287 update state 2025-09-25 15:08:43 +00:00
Siyuan Miao 76c4144565 fix ipv6 address truncation 2025-09-25 13:05:50 +00:00
Siyuan Miao e9bcdc5f3f minor ui fixes 2025-09-25 13:00:29 +00:00
Siyuan Miao 028cb7ddd6 feat: add reset config and reboot buttons to UI 2025-09-25 12:35:03 +00:00
Siyuan Miao e930363b24 add locks and missing comments 2025-09-25 09:51:59 +00:00
Siyuan Miao f106d308a3 chore: use log_error in native library 2025-09-25 08:39:29 +00:00
Siyuan Miao b042adac67 fix: race condition in native library 2025-09-25 08:33:53 +00:00
Siyuan Miao e582486bec remove lldp 2025-09-24 21:05:05 +00:00
43 changed files with 2751 additions and 929 deletions

View File

@ -1,8 +1,19 @@
#!/bin/bash
set -e
SUDO_PATH=$(which sudo)
function sudo() {
if [ "$UID" -eq 0 ]; then
"$@"
else
${SUDO_PATH} "$@"
fi
}
sudo apt-get update && sudo apt-get install -y --no-install-recommends \
set -ex
export DEBIAN_FRONTEND=noninteractive
sudo apt-get update && \
sudo apt-get install -y --no-install-recommends \
build-essential \
device-tree-compiler \
gperf g++-multilib gcc-multilib \

View File

@ -3,5 +3,6 @@
"cva",
"cx"
],
"cmake.sourceDirectory": "/Users/aveline/Projects/JetKVM/ymjk/internal/native/cgo"
"cmake.sourceDirectory": "/Users/aveline/Projects/JetKVM/ymjk/internal/native/cgo",
"git.ignoreLimitWarning": true
}

View File

@ -1,11 +1,11 @@
# syntax=docker/dockerfile:1
FROM golang:1.25.1-trixie
FROM --platform=${BUILDPLATFORM} golang:1.25.1-trixie AS builder
ENV GOTOOLCHAIN=local
ENV GOPATH /go
ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH
ENV GOPATH=/go
ENV PATH=$GOPATH/bin:/usr/local/go/bin:$PATH
ADD .devcontainer/install-deps.sh /install-deps.sh
COPY .devcontainer/install-deps.sh /install-deps.sh
RUN /install-deps.sh
# Create build directory

View File

@ -3,15 +3,15 @@ BUILDDATE := $(shell date -u +%FT%T%z)
BUILDTS := $(shell date -u +%s)
REVISION := $(shell git rev-parse HEAD)
VERSION_DEV := 0.4.8-dev$(shell date +%Y%m%d%H%M)
VERSION := 0.4.7
VERSION := 0.4.9
PROMETHEUS_TAG := github.com/prometheus/common/version
KVM_PKG_NAME := github.com/jetkvm/kvm
BUILDKIT_FLAVOR := arm-rockchip830-linux-uclibcgnueabihf
BUILDKIT_PATH ?= /opt/jetkvm-native-buildkit
SKIP_NATIVE_IF_EXISTS ?= 1
SKIP_NATIVE_IF_EXISTS ?= 0
SKIP_UI_BUILD ?= 0
GO_BUILD_ARGS := -tags netgo -tags timetzdata
GO_RELEASE_BUILD_ARGS := -trimpath $(GO_BUILD_ARGS)
@ -88,6 +88,9 @@ build_dev_test: build_test2json build_gotestsum
tar czfv device-tests.tar.gz -C $(BIN_DIR)/tests .
frontend:
@if [ "$(SKIP_UI_BUILD)" = "1" ]; then \
echo "Skipping frontend build..."; \
else \
cd ui && npm ci && npm run build:device && \
find ../static/ \
-type f \
@ -102,8 +105,9 @@ frontend:
-o -name '*.svg' \
-o -name '*.webp' \
-o -name '*.woff2' \
\) \
-exec sh -c 'gzip -9 -kfv {}' \;
\) \
-exec sh -c 'gzip -9 -kfv {}' \; \
fi
dev_release: frontend build_dev
@echo "Uploading release... $(VERSION_DEV)"
@ -111,7 +115,7 @@ dev_release: frontend build_dev
rclone copyto bin/jetkvm_app r2://jetkvm-update/app/$(VERSION_DEV)/jetkvm_app
rclone copyto bin/jetkvm_app.sha256 r2://jetkvm-update/app/$(VERSION_DEV)/jetkvm_app.sha256
build_release: frontend
build_release: frontend build_native
@echo "Building release..."
$(GO_CMD) build \
-ldflags="$(GO_LDFLAGS) -X $(KVM_PKG_NAME).builtAppVersion=$(VERSION)" \

View File

@ -41,12 +41,16 @@ show_help() {
REMOTE_USER="root"
REMOTE_PATH="/userdata/jetkvm/bin"
SKIP_UI_BUILD=false
SKIP_UI_BUILD_RELEASE=0
SKIP_NATIVE_BUILD=0
RESET_USB_HID_DEVICE=false
LOG_TRACE_SCOPES="${LOG_TRACE_SCOPES:-jetkvm,cloud,websocket,native,jsonrpc}"
RUN_GO_TESTS=false
RUN_GO_TESTS_ONLY=false
INSTALL_APP=false
BUILD_IN_DOCKER=false
DOCKER_BUILD_DEBUG=false
DOCKER_BUILD_TAG=ghcr.io/jetkvm/buildkit:latest
# Parse command line arguments
while [[ $# -gt 0 ]]; do
@ -71,6 +75,14 @@ while [[ $# -gt 0 ]]; do
RESET_USB_HID_DEVICE=true
shift
;;
--build-in-docker)
BUILD_IN_DOCKER=true
shift
;;
--docker-build-debug)
DOCKER_BUILD_DEBUG=true
shift
;;
--run-go-tests)
RUN_GO_TESTS=true
shift
@ -103,10 +115,59 @@ if [ -z "$REMOTE_HOST" ]; then
exit 1
fi
# check if the current CPU architecture is x86_64
if [ "$(uname -m)" != "x86_64" ]; then
msg_warn "Warning: This script is only supported on x86_64 architecture"
BUILD_IN_DOCKER=true
fi
if [ "$BUILD_IN_DOCKER" = true ]; then
if [ "$JETKVM_INSIDE_DOCKER" = 1 ]; then
msg_err "Error: already running inside Docker"
exit
fi
BUILD_ARGS="--build-arg BUILDPLATFORM=linux/amd64"
if [ "$DOCKER_BUILD_DEBUG" = true ]; then
BUILD_ARGS="$BUILD_ARGS --progress=plain --no-cache"
fi
msg_info "Checking if Docker is available ..."
if ! command -v docker &> /dev/null; then
msg_err "Error: Docker is not installed"
exit 1
fi
msg_info "▶ Building build environment ..."
TMP_DIR=$(mktemp -d)
cp -r .devcontainer "${TMP_DIR}"
cp go.mod go.sum Dockerfile.build "${TMP_DIR}"
pushd "${TMP_DIR}" > /dev/null
docker build $BUILD_ARGS -t ${DOCKER_BUILD_TAG} -f Dockerfile.build .
popd > /dev/null
rm -rf "${TMP_DIR}"
fi
do_make() {
if [ "$BUILD_IN_DOCKER" = true ]; then
msg_info "▶ Building the project in Docker ..."
docker run \
--interactive \
--tty \
--rm \
--env JETKVM_INSIDE_DOCKER=1 \
-v "$(pwd):/build" \
${DOCKER_BUILD_TAG} make "$@"
else
make "$@"
fi
}
# Build the development version on the host
if [ "$SKIP_UI_BUILD" = false ]; then
# When using `make build_release`, the frontend will be built regardless of the `SKIP_UI_BUILD` flag
if [[ "$SKIP_UI_BUILD" = false && "$JETKVM_INSIDE_DOCKER" != 1 ]]; then
msg_info "▶ Building frontend"
make frontend
make frontend SKIP_UI_BUILD=0
SKIP_UI_BUILD_RELEASE=1
fi
if [ "$RUN_GO_TESTS" = true ]; then
@ -154,7 +215,7 @@ fi
if [ "$INSTALL_APP" = true ]
then
msg_info "▶ Building release binary"
make build_release SKIP_NATIVE_IF_EXISTS=${SKIP_NATIVE_BUILD}
do_make build_release SKIP_NATIVE_IF_EXISTS=${SKIP_NATIVE_BUILD} SKIP_UI_BUILD=${SKIP_UI_BUILD_RELEASE}
# Copy the binary to the remote host as if we were the OTA updater.
ssh "${REMOTE_USER}@${REMOTE_HOST}" "cat > /userdata/jetkvm/jetkvm_app.update" < bin/jetkvm_app
@ -163,7 +224,7 @@ then
ssh "${REMOTE_USER}@${REMOTE_HOST}" "reboot"
else
msg_info "▶ Building development binary"
make build_dev SKIP_NATIVE_IF_EXISTS=${SKIP_NATIVE_BUILD}
do_make build_dev SKIP_NATIVE_IF_EXISTS=${SKIP_NATIVE_BUILD} SKIP_UI_BUILD=${SKIP_UI_BUILD_RELEASE}
# Kill any existing instances of the application
ssh "${REMOTE_USER}@${REMOTE_HOST}" "killall jetkvm_app_debug || true"

View File

@ -23,7 +23,6 @@ var (
)
const (
touchscreenDevice string = "/dev/input/event1"
backlightControlClass string = "/sys/class/backlight/backlight/brightness"
)
@ -38,23 +37,25 @@ func updateDisplay() {
if usbState == "configured" {
nativeInstance.UpdateLabelIfChanged("usb_status_label", "Connected")
_, _ = nativeInstance.UIObjSetState("usb_status", "LV_STATE_DEFAULT")
_, _ = nativeInstance.UIObjAddState("usb_status_label", "LV_STATE_CHECKED")
} else {
nativeInstance.UpdateLabelIfChanged("usb_status_label", "Disconnected")
_, _ = nativeInstance.UIObjSetState("usb_status", "LV_STATE_DISABLED")
_, _ = nativeInstance.UIObjClearState("usb_status_label", "LV_STATE_CHECKED")
}
if lastVideoState.Ready {
nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Connected")
_, _ = nativeInstance.UIObjSetState("hdmi_status", "LV_STATE_DEFAULT")
_, _ = nativeInstance.UIObjAddState("hdmi_status_label", "LV_STATE_CHECKED")
} else {
nativeInstance.UpdateLabelIfChanged("hdmi_status_label", "Disconnected")
_, _ = nativeInstance.UIObjSetState("hdmi_status", "LV_STATE_DISABLED")
_, _ = nativeInstance.UIObjClearState("hdmi_status_label", "LV_STATE_CHECKED")
}
nativeInstance.UpdateLabelIfChanged("cloud_status_label", fmt.Sprintf("%d active", actionSessions))
if networkState.IsUp() {
nativeInstance.UISetVar("main_screen", "home_screen")
nativeInstance.SwitchToScreenIf("home_screen", []string{"no_network_screen", "boot_screen"})
} else {
nativeInstance.UISetVar("main_screen", "no_network_screen")
nativeInstance.SwitchToScreenIf("no_network_screen", []string{"home_screen", "boot_screen"})
}

View File

@ -1,84 +0,0 @@
package lldp
import (
"fmt"
"net"
"os"
"syscall"
"unsafe"
"github.com/google/gopacket/afpacket"
"golang.org/x/sys/unix"
)
const (
afPacketBufferSize = 2 // in MiB
afPacketSnaplen = 9216
)
func afPacketComputeSize(targetSizeMb int, snaplen int, pageSize int) (
frameSize int, blockSize int, numBlocks int, err error) {
if snaplen < pageSize {
frameSize = pageSize / (pageSize / snaplen)
} else {
frameSize = (snaplen/pageSize + 1) * pageSize
}
// 128 is the default from the gopacket library so just use that
blockSize = frameSize * 128
numBlocks = (targetSizeMb * 1024 * 1024) / blockSize
if numBlocks == 0 {
return 0, 0, 0, fmt.Errorf("interface buffersize is too small")
}
return frameSize, blockSize, numBlocks, nil
}
func afPacketNewTPacket(ifName string) (*afpacket.TPacket, error) {
szFrame, szBlock, numBlocks, err := afPacketComputeSize(
afPacketBufferSize,
afPacketSnaplen,
os.Getpagesize())
if err != nil {
return nil, err
}
return afpacket.NewTPacket(
afpacket.OptInterface(ifName),
afpacket.OptFrameSize(szFrame),
afpacket.OptBlockSize(szBlock),
afpacket.OptNumBlocks(numBlocks),
afpacket.OptAddVLANHeader(false),
afpacket.SocketRaw,
afpacket.TPacketVersion3)
}
type ifreq struct {
ifrName [IFNAMSIZ]byte
ifrHwaddr syscall.RawSockaddr
}
func addMulticastAddr(ifName string, addr net.HardwareAddr) error {
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
if err != nil {
return err
}
defer syscall.Close(fd)
var name [IFNAMSIZ]byte
copy(name[:], []byte(ifName))
ifr := &ifreq{
ifrName: name,
ifrHwaddr: toRawSockaddr(addr),
}
_, _, ep := unix.Syscall(unix.SYS_IOCTL, uintptr(fd),
unix.SIOCADDMULTI, uintptr(unsafe.Pointer(ifr)))
if ep != 0 {
return syscall.Errno(ep)
}
return nil
}

View File

@ -1,15 +0,0 @@
//go:build arm && linux
package lldp
import (
"net"
"syscall"
)
func toRawSockaddr(mac net.HardwareAddr) (sockaddr syscall.RawSockaddr) {
for i, n := range mac {
sockaddr.Data[i] = uint8(n)
}
return
}

View File

@ -1,15 +0,0 @@
//go:build !arm && linux
package lldp
import (
"net"
"syscall"
)
func toRawSockaddr(mac net.HardwareAddr) (sockaddr syscall.RawSockaddr) {
for i, n := range mac {
sockaddr.Data[i] = int8(n)
}
return
}

View File

@ -1,106 +0,0 @@
package lldp
import (
"context"
"sync"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/afpacket"
"github.com/jellydator/ttlcache/v3"
"github.com/jetkvm/kvm/internal/logging"
"github.com/rs/zerolog"
)
var defaultLogger = logging.GetSubsystemLogger("lldp")
type LLDP struct {
l *zerolog.Logger
tPacket *afpacket.TPacket
pktSource *gopacket.PacketSource
rxCtx context.Context
rxCancel context.CancelFunc
rxLock sync.Mutex
enableRx bool
enableTx bool
packets chan gopacket.Packet
interfaceName string
stop chan struct{}
neighbors *ttlcache.Cache[string, Neighbor]
}
type LLDPOptions struct {
InterfaceName string
EnableRx bool
EnableTx bool
Logger *zerolog.Logger
}
func NewLLDP(opts *LLDPOptions) *LLDP {
if opts.Logger == nil {
opts.Logger = defaultLogger
}
if opts.InterfaceName == "" {
opts.Logger.Fatal().Msg("InterfaceName is required")
}
return &LLDP{
interfaceName: opts.InterfaceName,
enableRx: opts.EnableRx,
enableTx: opts.EnableTx,
l: opts.Logger,
neighbors: ttlcache.New(ttlcache.WithTTL[string, Neighbor](1 * time.Hour)),
}
}
func (l *LLDP) Start() error {
l.rxLock.Lock()
defer l.rxLock.Unlock()
if l.rxCtx != nil {
l.l.Info().Msg("LLDP already started")
return nil
}
l.rxCtx, l.rxCancel = context.WithCancel(context.Background())
if l.enableRx {
l.l.Info().Msg("setting up AF_PACKET")
if err := l.setUpCapture(); err != nil {
l.l.Error().Err(err).Msg("unable to set up AF_PACKET")
return err
}
if err := l.startCapture(); err != nil {
l.l.Error().Err(err).Msg("unable to start capture")
return err
}
}
go l.neighbors.Start()
return nil
}
func (l *LLDP) Stop() error {
l.rxLock.Lock()
defer l.rxLock.Unlock()
if l.rxCancel != nil {
l.rxCancel()
l.rxCancel = nil
l.rxCtx = nil
}
if l.enableRx {
_ = l.shutdownCapture()
}
l.neighbors.Stop()
l.neighbors.DeleteAll()
return nil
}

View File

@ -1,57 +0,0 @@
package lldp
import "time"
type Neighbor struct {
Mac string `json:"mac"`
Source string `json:"source"`
ChassisID string `json:"chassis_id"`
PortID string `json:"port_id"`
PortDescription string `json:"port_description"`
SystemName string `json:"system_name"`
SystemDescription string `json:"system_description"`
TTL uint16 `json:"ttl"`
ManagementAddress string `json:"management_address"`
Values map[string]string `json:"values"`
}
func (l *LLDP) addNeighbor(mac string, neighbor Neighbor, ttl time.Duration) {
logger := l.l.With().
Str("mac", mac).
Interface("neighbor", neighbor).
Logger()
current_neigh := l.neighbors.Get(mac)
if current_neigh != nil {
current_source := current_neigh.Value().Source
if current_source == "lldp" && neighbor.Source != "lldp" {
logger.Info().Msg("skip updating neighbor, as LLDP has higher priority")
return
}
}
logger.Info().Msg("adding neighbor")
l.neighbors.Set(mac, neighbor, ttl)
}
func (l *LLDP) deleteNeighbor(mac string) {
logger := l.l.With().
Str("mac", mac).
Logger()
logger.Info().Msg("deleting neighbor")
l.neighbors.Delete(mac)
}
func (l *LLDP) GetNeighbors() []Neighbor {
items := l.neighbors.Items()
neighbors := make([]Neighbor, 0, len(items))
for _, item := range items {
neighbors = append(neighbors, item.Value())
}
l.l.Info().Interface("neighbors", neighbors).Msg("neighbors")
return neighbors
}

View File

@ -1,264 +0,0 @@
package lldp
import (
"fmt"
"net"
"time"
"github.com/google/gopacket"
"github.com/google/gopacket/layers"
"github.com/rs/zerolog"
"golang.org/x/net/bpf"
)
const IFNAMSIZ = 16
var (
lldpDefaultTTL = 120 * time.Second
cdpDefaultTTL = 180 * time.Second
)
// from lldpd
// https://github.com/lldpd/lldpd/blob/9034c9332cca0c8b1a20e1287f0e5fed81f7eb2a/src/daemon/lldpd.h#L246
//
//nolint:govet
var bpfFilter = []bpf.RawInstruction{
{0x30, 0, 0, 0x00000000}, {0x54, 0, 0, 0x00000001}, {0x15, 0, 16, 0x00000001},
{0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000088cc},
{0x20, 0, 0, 0x00000002}, {0x15, 2, 0, 0xc200000e},
{0x15, 1, 0, 0xc2000003}, {0x15, 0, 2, 0xc2000000},
{0x28, 0, 0, 0x00000000}, {0x15, 12, 13, 0x00000180},
{0x20, 0, 0, 0x00000002}, {0x15, 0, 2, 0x52cccccc},
{0x28, 0, 0, 0x00000000}, {0x15, 8, 9, 0x000001e0},
{0x15, 1, 0, 0x0ccccccc}, {0x15, 0, 2, 0x81000100},
{0x28, 0, 0, 0x00000000}, {0x15, 4, 5, 0x00000100},
{0x20, 0, 0, 0x00000002}, {0x15, 0, 3, 0x2b000000},
{0x28, 0, 0, 0x00000000}, {0x15, 0, 1, 0x000000e0},
{0x6, 0, 0, 0x00040000},
{0x6, 0, 0, 0x00000000},
}
var multicastAddrs = []string{
// LLDP
"01:80:C2:00:00:00",
"01:80:C2:00:00:03",
"01:80:C2:00:00:0E",
// CDP
"01:00:0C:CC:CC:CC",
}
func (l *LLDP) setUpCapture() error {
logger := l.l.With().Str("interface", l.interfaceName).Logger()
tPacket, err := afPacketNewTPacket(l.interfaceName)
if err != nil {
return err
}
logger.Info().Msg("created TPacket")
// set up multicast addresses
// otherwise the kernel might discard the packets
// another workaround would be to enable promiscuous mode but that's too tricky
for _, mac := range multicastAddrs {
hwAddr, err := net.ParseMAC(mac)
if err != nil {
logger.Error().Msgf("unable to parse MAC address %s: %s", mac, err)
continue
}
if err := addMulticastAddr(l.interfaceName, hwAddr); err != nil {
logger.Error().Msgf("unable to add multicast address %s: %s", mac, err)
continue
}
logger.Info().
Interface("hwaddr", hwAddr).
Msgf("added multicast address")
}
if err = tPacket.SetBPF(bpfFilter); err != nil {
logger.Error().Msgf("unable to set BPF filter: %s", err)
tPacket.Close()
return err
}
logger.Info().Msg("BPF filter set")
l.pktSource = gopacket.NewPacketSource(tPacket, layers.LayerTypeEthernet)
l.tPacket = tPacket
return nil
}
func (l *LLDP) startCapture() error {
logger := l.l.With().Str("interface", l.interfaceName).Logger()
if l.tPacket == nil {
return fmt.Errorf("AFPacket not initialized")
}
if l.pktSource == nil {
return fmt.Errorf("packet source not initialized")
}
go func() {
logger.Info().Msg("starting capture LLDP ethernet frames")
for {
select {
case <-l.rxCtx.Done():
logger.Info().Msg("shutting down LLDP capture")
return
case packet := <-l.pktSource.Packets():
if err := l.handlePacket(packet, &logger); err != nil {
logger.Error().Msgf("error handling packet: %s", err)
}
}
}
}()
return nil
}
func (l *LLDP) handlePacket(packet gopacket.Packet, logger *zerolog.Logger) error {
linkLayer := packet.LinkLayer()
if linkLayer == nil {
return fmt.Errorf("no link layer")
}
srcMac := linkLayer.LinkFlow().Src().String()
dstMac := linkLayer.LinkFlow().Dst().String()
logger.Trace().
Str("src_mac", srcMac).
Str("dst_mac", dstMac).
Int("length", len(packet.Data())).
Hex("data", packet.Data()).
Msg("received packet")
lldpRaw := packet.Layer(layers.LayerTypeLinkLayerDiscovery)
if lldpRaw != nil {
logger.Trace().Msgf("Found LLDP Frame")
lldpInfo := packet.Layer(layers.LayerTypeLinkLayerDiscoveryInfo)
if lldpInfo == nil {
return fmt.Errorf("no LLDP info layer")
}
return l.handlePacketLLDP(
srcMac,
lldpRaw.(*layers.LinkLayerDiscovery),
lldpInfo.(*layers.LinkLayerDiscoveryInfo),
)
}
cdpRaw := packet.Layer(layers.LayerTypeCiscoDiscovery)
if cdpRaw != nil {
logger.Trace().Msgf("Found CDP Frame")
cdpInfo := packet.Layer(layers.LayerTypeCiscoDiscoveryInfo)
if cdpInfo == nil {
return fmt.Errorf("no CDP info layer")
}
return l.handlePacketCDP(
srcMac,
cdpRaw.(*layers.CiscoDiscovery),
cdpInfo.(*layers.CiscoDiscoveryInfo),
)
}
return nil
}
func (l *LLDP) handlePacketLLDP(mac string, raw *layers.LinkLayerDiscovery, info *layers.LinkLayerDiscoveryInfo) error {
n := &Neighbor{
Values: make(map[string]string),
Source: "lldp",
Mac: mac,
}
gotEnd := false
ttl := lldpDefaultTTL
for _, v := range raw.Values {
switch v.Type {
case layers.LLDPTLVEnd:
gotEnd = true
case layers.LLDPTLVChassisID:
n.ChassisID = string(raw.ChassisID.ID)
n.Values["chassis_id"] = n.ChassisID
case layers.LLDPTLVPortID:
n.PortID = string(raw.PortID.ID)
n.Values["port_id"] = n.PortID
case layers.LLDPTLVPortDescription:
n.PortDescription = info.PortDescription
n.Values["port_description"] = n.PortDescription
case layers.LLDPTLVSysName:
n.SystemName = info.SysName
n.Values["system_name"] = n.SystemName
case layers.LLDPTLVSysDescription:
n.SystemDescription = info.SysDescription
n.Values["system_description"] = n.SystemDescription
case layers.LLDPTLVMgmtAddress:
// n.ManagementAddress = info.MgmtAddress.Address
case layers.LLDPTLVTTL:
n.TTL = uint16(raw.TTL)
ttl = time.Duration(n.TTL) * time.Second
n.Values["ttl"] = fmt.Sprintf("%d", n.TTL)
case layers.LLDPTLVOrgSpecific:
for _, org := range info.OrgTLVs {
n.Values[fmt.Sprintf("org_specific_%d", org.OUI)] = string(org.Info)
}
}
}
if gotEnd || ttl < 1*time.Second {
l.deleteNeighbor(mac)
} else {
l.addNeighbor(mac, *n, ttl)
}
return nil
}
func (l *LLDP) handlePacketCDP(mac string, raw *layers.CiscoDiscovery, info *layers.CiscoDiscoveryInfo) error {
// TODO: implement full CDP parsing
n := &Neighbor{
Values: make(map[string]string),
Source: "cdp",
Mac: mac,
}
ttl := cdpDefaultTTL
n.ChassisID = info.DeviceID
n.PortID = info.PortID
n.SystemName = info.SysName
n.SystemDescription = info.Platform
n.TTL = uint16(raw.TTL)
if n.TTL > 1 {
ttl = time.Duration(n.TTL) * time.Second
}
if len(info.MgmtAddresses) > 0 {
n.ManagementAddress = string(info.MgmtAddresses[0])
}
l.addNeighbor(mac, *n, ttl)
return nil
}
func (l *LLDP) shutdownCapture() error {
if l.tPacket != nil {
l.l.Info().Msg("closing TPacket")
l.tPacket.Close()
l.tPacket = nil
}
if l.pktSource != nil {
l.l.Info().Msg("closing packet source")
l.pktSource = nil
}
return nil
}

View File

@ -18,7 +18,7 @@
jetkvm_video_state_t state;
jetkvm_video_state_handler_t *video_state_handler = NULL;
jetkvm_rpc_handler_t *rpc_handler = NULL;
jetkvm_video_handler_t *video_handler = NULL;
@ -34,6 +34,16 @@ void jetkvm_set_indev_handler(jetkvm_indev_handler_t *handler) {
lvgl_set_indev_handler(handler);
}
void jetkvm_set_rpc_handler(jetkvm_rpc_handler_t *handler) {
rpc_handler = handler;
}
void jetkvm_call_rpc_handler(const char *method, const char *params) {
if (rpc_handler != NULL) {
(*rpc_handler)(method, params);
}
}
const char *jetkvm_ui_event_code_to_name(int code) {
lv_event_code_t cCode = (lv_event_code_t)code;
return lv_event_code_get_name(code);
@ -237,38 +247,52 @@ void jetkvm_ui_set_image(const char *obj_name, const char *image_name) {
lv_img_set_src(obj, image_name);
}
void jetkvm_ui_set_state(const char *obj_name, const char *state_name) {
lv_state_t str_to_lv_state(const char *state_name) {
if (strcmp(state_name, "LV_STATE_USER_1") == 0) {
return LV_STATE_USER_1;
}
else if (strcmp(state_name, "LV_STATE_USER_2") == 0) {
return LV_STATE_USER_2;
}
else if (strcmp(state_name, "LV_STATE_USER_3") == 0) {
return LV_STATE_USER_3;
}
else if (strcmp(state_name, "LV_STATE_USER_4") == 0) {
return LV_STATE_USER_4;
}
else if (strcmp(state_name, "LV_STATE_DISABLED") == 0) {
return LV_STATE_DISABLED;
}
else if (strcmp(state_name, "LV_STATE_DEFAULT") == 0) {
return LV_STATE_DEFAULT;
}
else if (strcmp(state_name, "LV_STATE_CHECKED") == 0) {
return LV_STATE_CHECKED;
}
else if (strcmp(state_name, "LV_STATE_FOCUSED") == 0) {
return LV_STATE_FOCUSED;
}
return LV_STATE_DEFAULT;
}
void jetkvm_ui_add_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;
}
else if (strcmp(state_name, "LV_STATE_USER_3") == 0)
{
state_val = LV_STATE_USER_3;
}
else if (strcmp(state_name, "LV_STATE_USER_4") == 0)
{
state_val = LV_STATE_USER_4;
}
else if (strcmp(state_name, "LV_STATE_DISABLED") == 0)
{
state_val = LV_STATE_DISABLED;
}
// TODO: use LV_STATE_USER_* once eez supports it
lv_obj_clear_state(obj, LV_STATE_USER_1 | LV_STATE_USER_2 | LV_STATE_USER_3 | LV_STATE_USER_4 | LV_STATE_DISABLED);
lv_state_t state_val = str_to_lv_state(state_name);
lv_obj_add_state(obj, state_val);
}
void jetkvm_ui_clear_state(const char *obj_name, const char *state_name) {
lv_obj_t *obj = ui_get_obj(obj_name);
if (obj == NULL) {
return;
}
lv_state_t state_val = str_to_lv_state(state_name);
lv_obj_clear_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) {

View File

@ -16,12 +16,15 @@ typedef struct
typedef void (jetkvm_video_state_handler_t)(jetkvm_video_state_t *state);
typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, int line, const char *message);
typedef void (jetkvm_rpc_handler_t)(const char *method, const char *params);
typedef void (jetkvm_video_handler_t)(const uint8_t *frame, ssize_t len);
typedef void (jetkvm_indev_handler_t)(int code);
void jetkvm_set_log_handler(jetkvm_log_handler_t *handler);
void jetkvm_set_video_handler(jetkvm_video_handler_t *handler);
void jetkvm_set_indev_handler(jetkvm_indev_handler_t *handler);
void jetkvm_set_rpc_handler(jetkvm_rpc_handler_t *handler);
void jetkvm_call_rpc_handler(const char *method, const char *params);
void jetkvm_set_video_state_handler(jetkvm_video_state_handler_t *handler);
void jetkvm_ui_set_var(const char *name, const char *value);
@ -36,7 +39,8 @@ 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_add_state(const char *obj_name, const char *state_name);
void jetkvm_ui_clear_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);

View File

@ -36,7 +36,7 @@ int get_edid(uint8_t *edid, size_t max_size)
fd = open(V4L_SUBDEV, O_RDWR);
if (fd < 0)
{
perror("Failed to open device");
log_error("Failed to open device");
return -1;
}
@ -48,7 +48,7 @@ int get_edid(uint8_t *edid, size_t max_size)
if (ioctl(fd, VIDIOC_G_EDID, &v4l2_edid) < 0)
{
perror("Failed to get EDID");
log_error("Failed to get EDID");
close(fd);
return -1;
}
@ -90,7 +90,7 @@ int set_edid(uint8_t *edid, size_t size)
fd = open(V4L_SUBDEV, O_RDWR);
if (fd < 0)
{
perror("Failed to open device");
log_error("Failed to open device");
return -1;
}
@ -104,7 +104,7 @@ int set_edid(uint8_t *edid, size_t size)
if (ioctl(fd, VIDIOC_S_EDID, &v4l2_edid) < 0)
{
perror("Failed to set EDID");
log_error("Failed to set EDID");
close(fd);
return -1;
}
@ -123,13 +123,13 @@ const char *videoc_log_status()
fd = open(V4L_SUBDEV, O_RDWR);
if (fd < 0)
{
perror("Failed to open device");
log_error("Failed to open device");
return NULL;
}
if (ioctl(fd, VIDIOC_LOG_STATUS) == -1)
{
perror("VIDIOC_LOG_STATUS failed");
log_error("VIDIOC_LOG_STATUS failed");
close(fd);
return NULL;
}
@ -166,14 +166,14 @@ const char *videoc_log_status()
buffer = strdup(p);
if (buffer == NULL)
{
perror("Failed to allocate memory for status");
log_error("Failed to allocate memory for status");
return NULL;
}
return buffer;
}
else
{
log_error("Failed to read kernel log\n");
log_error("Failed to read kernel log");
return NULL;
}

View File

@ -2,8 +2,25 @@
#define LOG_HANDLER_H
typedef void (jetkvm_log_handler_t)(int level, const char *filename, const char *funcname, const int line, const char *message);
/**
* @brief Log a message
*
* @param level The level of the message
* @param filename The filename of the message
* @param funcname The function name of the message
* @param line The line number of the message
* @param message The message to log
* @return void
*/
void log_message(int level, const char *filename, const char *funcname, const int line, const char *message);
/**
* @brief Set the log handler
*
* @param handler The handler to set
* @return void
*/
void log_set_handler(jetkvm_log_handler_t *handler);
#endif

View File

@ -9,6 +9,7 @@
// #include "st7789/lcd.h"
#include "ui/ui.h"
#include "ui_index.h"
#include "ctrl.h"
#define DISP_BUF_SIZE (300 * 240 * 2)
static lv_color_t buf[DISP_BUF_SIZE];
@ -32,8 +33,6 @@ void lvgl_init(u_int16_t rotation) {
/*LittlevGL init*/
lv_init();
/*Linux frame buffer device init*/
/*Linux frame buffer device init*/
lv_display_t *disp = lv_linux_fbdev_create();
// lv_display_set_physical_resolution(disp, 240, 300);
@ -42,29 +41,6 @@ void lvgl_init(u_int16_t rotation) {
lvgl_set_rotation(disp, rotation);
// lv_display_t *disp = lv_st7789_create(LCD_H_RES, LCD_V_RES, LV_LCD_FLAG_NONE, lcd_send_cmd, lcd_send_color);
// lv_display_set_resolution(disp, 240, 300);
// lv_display_set_rotation(disp, LV_DISP_ROTATION_270);
// lv_color_t * buf1 = NULL;
// lv_color_t * buf2 = NULL;
// uint32_t buf_size = LCD_H_RES * LCD_V_RES / 10 * lv_color_format_get_size(lv_display_get_color_format(disp));
// buf1 = lv_malloc(buf_size);
// if(buf1 == NULL) {
// log_error("display draw buffer malloc failed");
// return;
// }
// buf2 = lv_malloc(buf_size);
// if(buf2 == NULL) {
// log_error("display buffer malloc failed");
// lv_free(buf1);
// return;
// }
// lv_display_set_buffers(disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
/* Linux input device init */
lv_indev_t *mouse = lv_evdev_create(LV_INDEV_TYPE_POINTER, "/dev/input/event1");
lv_indev_set_group(mouse, lv_group_get_default());
@ -76,10 +52,9 @@ void lvgl_init(u_int16_t rotation) {
ui_init();
ui_set_rpc_handler((jetkvm_rpc_handler_t *)jetkvm_call_rpc_handler);
log_info("ui initalized");
// lv_label_set_text(ui_Boot_Screen_Version, "");
// lv_label_set_text(ui_Home_Content_Ip, "...");
// lv_label_set_text(ui_Home_Header_Cloud_Status_Label, "0 active");
}
void lvgl_tick(void) {
@ -188,7 +163,7 @@ lv_img_dsc_t *ui_get_image(const char *name) {
void ui_set_text(const char *name, const char *text) {
lv_obj_t *obj = ui_get_obj(name);
if(obj == NULL) {
log_error("ui_set_text %s %s, obj not found\n", name, text);
log_error("ui_set_text %s %s, obj not found", name, text);
return;
}
lv_label_set_text(obj, text);

View File

@ -12,10 +12,37 @@ void lvgl_tick(void);
void lvgl_set_rotation(lv_display_t *disp, u_int16_t rotation);
/**
* @brief Set the text of an object
*
* @param name The name of the object
* @param text The text to set
* @return void
*/
void ui_set_text(const char *name, const char *text);
/**
* @brief Get the object with the given name
*
* @param name The name of the object
* @return lv_obj_t* The object with the given name
*/
lv_obj_t *ui_get_obj(const char *name);
/**
* @brief Get the style with the given name
*
* @param name The name of the style
* @return lv_style_t* The style with the given name
*/
lv_style_t *ui_get_style(const char *name);
/**
* @brief Get the image with the given name
*
* @param name The name of the image
* @return lv_img_dsc_t* The image with the given name
*/
lv_img_dsc_t *ui_get_image(const char *name);
#endif // SCREEN_H

View File

@ -268,14 +268,14 @@ static void *venc_read_stream(void *arg)
stFrame.pstPack = malloc(sizeof(VENC_PACK_S));
while (venc_running)
{
// printf("RK_MPI_VENC_GetStream\n");
log_trace("RK_MPI_VENC_GetStream");
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);
log_trace("chn:0, loopCount:%d enc->seq:%d wd:%d pts=%llu delay=%lldus",
loopCount, stFrame.u32Seq, stFrame.pstPack->u32Len,
stFrame.pstPack->u64PTS, nowUs - stFrame.pstPack->u64PTS);
pData = RK_MPI_MB_Handle2VirAddr(stFrame.pstPack->pMbBlk);
video_send_frame(pData, (ssize_t)stFrame.pstPack->u32Len);
s32Ret = RK_MPI_VENC_ReleaseStream(VENC_CHANNEL, &stFrame);
@ -304,6 +304,7 @@ uint32_t detected_width, detected_height;
bool detected_signal = false, streaming_flag = false;
pthread_t *streaming_thread = NULL;
pthread_mutex_t streaming_mutex = PTHREAD_MUTEX_INITIALIZER;
void write_buffer_to_file(const uint8_t *buffer, size_t length, const char *filename)
{
@ -322,7 +323,7 @@ void *run_video_stream(void *arg)
{
if (detected_signal == false)
{
usleep(100000);
usleep(10000); // Reduced to 10ms for better responsiveness to streaming_flag changes
continue;
}
@ -347,7 +348,7 @@ void *run_video_stream(void *arg)
if (ioctl(video_dev_fd, VIDIOC_S_FMT, &fmt) < 0)
{
perror("Set format fail");
log_error("Set format fail: %s", strerror(errno));
usleep(100000); // Sleep for 100 milliseconds
close(video_dev_fd);
continue;
@ -362,7 +363,8 @@ void *run_video_stream(void *arg)
if (ioctl(video_dev_fd, VIDIOC_REQBUFS, &req) < 0)
{
perror("VIDIOC_REQBUFS failed");
log_error("VIDIOC_REQBUFS failed: %s", strerror(errno));
close(video_dev_fd);
return errno;
}
log_info("VIDIOC_REQBUFS successful");
@ -384,32 +386,35 @@ void *run_video_stream(void *arg)
if (-1 == ioctl(video_dev_fd, VIDIOC_QUERYBUF, &buf))
{
perror("VIDIOC_QUERYBUF failed");
log_error("VIDIOC_QUERYBUF failed: %s", strerror(errno));
req.count = i;
close(video_dev_fd);
return errno;
}
printf("VIDIOC_QUERYBUF successful for buffer %d\n", i);
log_info("VIDIOC_QUERYBUF successful for buffer %d", i);
printf("plane: length = %d\n", planes_buffer->length);
printf("plane: offset = %d\n", planes_buffer->m.mem_offset);
log_info("plane: length = %d", planes_buffer->length);
log_info("plane: offset = %d", 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!");
log_error("get mb blk failed!");
close(video_dev_fd);
return -1;
}
printf("Got memory block for buffer %d\n", i);
log_info("Got memory block for buffer %d", 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!");
log_error("RK_MPI_MB_Handle2Fd failed!");
close(video_dev_fd);
return -1;
}
printf("Converted memory block to file descriptor for buffer %d\n", i);
log_info("Converted memory block to file descriptor for buffer %d", i);
planes_buffer->m.fd = buf_fd;
}
@ -424,15 +429,16 @@ void *run_video_stream(void *arg)
buf.m.planes = &buffers[i].plane_buffer;
if (ioctl(video_dev_fd, VIDIOC_QBUF, &buf) < 0)
{
perror("VIDIOC_QBUF failed");
log_error("VIDIOC_QBUF failed: %s", strerror(errno));
close(video_dev_fd);
return errno;
}
printf("VIDIOC_QBUF successful for buffer %d\n", i);
log_info("VIDIOC_QBUF successful for buffer %d", i);
}
if (ioctl(video_dev_fd, VIDIOC_STREAMON, &type) < 0)
{
perror("VIDIOC_STREAMON failed");
log_error("VIDIOC_STREAMON failed: %s", strerror(errno));
goto cleanup;
}
@ -463,7 +469,7 @@ void *run_video_stream(void *arg)
r = select(video_dev_fd + 1, &fds, NULL, NULL, &tv);
if (r == 0)
{
log_info("select timeout \n");
log_info("select timeout");
break;
}
if (r == -1)
@ -472,7 +478,7 @@ void *run_video_stream(void *arg)
{
continue;
}
perror("select in video streaming");
log_error("select in video streaming");
break;
}
memset(&buf, 0, sizeof(buf));
@ -482,10 +488,10 @@ void *run_video_stream(void *arg)
buf.length = 1;
if (ioctl(video_dev_fd, VIDIOC_DQBUF, &buf) < 0)
{
perror("VIDIOC_DQBUF failed");
log_error("VIDIOC_DQBUF failed: %s", strerror(errno));
break;
}
// printf("got frame, bytesused = %d\n", tmp_plane.bytesused);
log_trace("got frame, bytesused = %d", 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);
@ -503,26 +509,16 @@ void *run_video_stream(void *arg)
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");
log_error("RK_MPI_VENC_SendFrame retry failed");
}
else
{
RK_LOGE("RK_MPI_VENC_SendFrame failed,retrying");
log_error("RK_MPI_VENC_SendFrame failed,retrying");
retried = true;
usleep(1000llu);
goto retry_send_frame;
@ -532,12 +528,13 @@ void *run_video_stream(void *arg)
num++;
if (ioctl(video_dev_fd, VIDIOC_QBUF, &buf) < 0)
printf("failture VIDIOC_QBUF\n");
log_error("failure VIDIOC_QBUF: %s", strerror(errno));
}
cleanup:
log_info("cleaning up video capture device %s", VIDEO_DEV);
if (ioctl(video_dev_fd, VIDIOC_STREAMOFF, &type) < 0)
{
perror("VIDIOC_STREAMOFF failed");
log_error("VIDIOC_STREAMOFF failed: %s", strerror(errno));
}
venc_stop();
@ -550,9 +547,11 @@ void *run_video_stream(void *arg)
}
}
log_info("closing video capture device %s", VIDEO_DEV);
close(video_dev_fd);
}
log_info("video stream thread exiting");
return NULL;
}
@ -560,65 +559,78 @@ void video_shutdown()
{
if (should_exit == true)
{
printf("shutting down in progress already\n");
log_info("shutting down in progress already");
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");
log_info("Closed sub_dev_fd");
}
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");
log_info("Destroyed memory pool");
pthread_mutex_destroy(&streaming_mutex);
log_info("Destroyed streaming mutex");
}
// TODO: mutex?
void video_start_streaming()
{
pthread_mutex_lock(&streaming_mutex);
if (streaming_thread != NULL)
{
log_info("video streaming already started");
return;
log_warn("video streaming already started");
goto cleanup;
}
streaming_thread = malloc(sizeof(pthread_t));
assert(streaming_thread != NULL);
pthread_t *new_thread = malloc(sizeof(pthread_t));
if (new_thread == NULL)
{
log_error("Failed to allocate memory for streaming thread");
goto cleanup;
}
streaming_flag = true;
pthread_create(streaming_thread, NULL, run_video_stream, NULL);
int result = pthread_create(new_thread, NULL, run_video_stream, NULL);
if (result != 0)
{
log_error("Failed to create streaming thread: %s", strerror(result));
streaming_flag = false;
free(new_thread);
goto cleanup;
}
// Only set streaming_thread after successful creation, and before unlocking the mutex
streaming_thread = new_thread;
cleanup:
pthread_mutex_unlock(&streaming_mutex);
return;
}
void video_stop_streaming()
{
pthread_mutex_lock(&streaming_mutex);
if (streaming_thread != NULL)
{
streaming_flag = false;
log_info("stopping video streaming");
// wait 100ms for the thread to exit
usleep(1000000);
log_info("waiting for video streaming thread to exit");
pthread_join(*streaming_thread, NULL);
free(streaming_thread);
streaming_thread = NULL;
log_info("video streaming stopped");
}
pthread_mutex_unlock(&streaming_mutex);
}
void *run_detect_format(void *arg)
@ -632,7 +644,6 @@ void *run_detect_format(void *arg)
if (ioctl(sub_dev_fd, VIDIOC_SUBSCRIBE_EVENT, &sub) == -1)
{
log_error("cannot subscribe to event");
perror("Cannot subscribe to event");
goto exit;
}
@ -657,12 +668,12 @@ void *run_detect_format(void *arg)
else if (errno == ERANGE)
{
// Timings were found, but they are out of range of the hardware capabilities.
printf("HDMI status: out of range\n");
log_warn("HDMI status: out of range");
video_report_format(false, "out_of_range", 0, 0, 0);
}
else
{
perror("error VIDIOC_QUERY_DV_TIMINGS");
log_error("error VIDIOC_QUERY_DV_TIMINGS: %s", strerror(errno));
sleep(1);
continue;
}
@ -681,19 +692,24 @@ void *run_detect_format(void *arg)
detected_height = dv_timings.bt.height;
detected_signal = true;
video_report_format(true, NULL, detected_width, detected_height, frames_per_second);
pthread_mutex_lock(&streaming_mutex);
if (streaming_flag == true)
{
pthread_mutex_unlock(&streaming_mutex);
log_info("restarting on going video streaming");
video_stop_streaming();
video_start_streaming();
}
else
{
pthread_mutex_unlock(&streaming_mutex);
}
}
memset(&ev, 0, sizeof(ev));
if (ioctl(sub_dev_fd, VIDIOC_DQEVENT, &ev) != 0)
{
log_error("failed to VIDIOC_DQEVENT");
perror("failed to VIDIOC_DQEVENT");
log_error("failed to VIDIOC_DQEVENT: %s", strerror(errno));
break;
}
log_info("New event of type %u", ev.type);
@ -715,12 +731,18 @@ void video_set_quality_factor(float factor)
// TODO: update venc bitrate without stopping streaming
pthread_mutex_lock(&streaming_mutex);
if (streaming_flag == true)
{
pthread_mutex_unlock(&streaming_mutex);
log_info("restarting on going video streaming due to quality factor change");
video_stop_streaming();
video_start_streaming();
}
else
{
pthread_mutex_unlock(&streaming_mutex);
}
}
float video_get_quality_factor() {

View File

@ -1,13 +1,48 @@
#ifndef VIDEO_DAEMON_VIDEO_H
#define VIDEO_DAEMON_VIDEO_H
/**
* @brief Initialize the video subsystem
*
* @return int 0 on success, -1 on failure
*/
int video_init();
/**
* @brief Shutdown the video subsystem
*/
void video_shutdown();
/**
* @brief Run the detect format thread
*
* @param arg The argument to pass to the thread
* @return void* The result of the thread
*/
void *run_detect_format(void *arg);
/**
* @brief Start the video streaming
*/
void video_start_streaming();
/**
* @brief Stop the video streaming
*/
void video_stop_streaming();
/**
* @brief Set the quality factor of the video
*
* @param factor The quality factor to set
*/
void video_set_quality_factor(float factor);
/**
* @brief Get the quality factor of the video
*
* @return float The quality factor of the video
*/
float video_get_quality_factor();
#endif //VIDEO_DAEMON_VIDEO_H

View File

@ -4,6 +4,7 @@ package native
import (
"fmt"
"sync"
"unsafe"
"github.com/rs/zerolog"
@ -37,9 +38,16 @@ extern void jetkvm_go_indev_handler(int code);
static inline void jetkvm_cgo_setup_indev_handler() {
jetkvm_set_indev_handler(&jetkvm_go_indev_handler);
}
extern void jetkvm_go_rpc_handler(cchar_t *method, cchar_t *params);
static inline void jetkvm_cgo_setup_rpc_handler() {
jetkvm_set_rpc_handler(&jetkvm_go_rpc_handler);
}
*/
import "C"
var cgoLock sync.Mutex
//export jetkvm_go_video_state_handler
func jetkvm_go_video_state_handler(state *C.jetkvm_video_state_t) {
videoState := VideoState{
@ -75,6 +83,11 @@ func jetkvm_go_indev_handler(code C.int) {
indevEventChan <- int(code)
}
//export jetkvm_go_rpc_handler
func jetkvm_go_rpc_handler(method *C.cchar_t, params *C.cchar_t) {
rpcEventChan <- C.GoString(method)
}
var eventCodeToNameMap = map[int]string{}
func uiEventCodeToName(code int) string {
@ -90,13 +103,20 @@ func uiEventCodeToName(code int) string {
}
func setUpNativeHandlers() {
cgoLock.Lock()
defer cgoLock.Unlock()
C.jetkvm_cgo_setup_log_handler()
C.jetkvm_cgo_setup_video_state_handler()
C.jetkvm_cgo_setup_video_handler()
C.jetkvm_cgo_setup_indev_handler()
C.jetkvm_cgo_setup_rpc_handler()
}
func uiInit(rotation uint16) {
cgoLock.Lock()
defer cgoLock.Unlock()
cRotation := C.u_int16_t(rotation)
defer C.free(unsafe.Pointer(&cRotation))
@ -104,10 +124,16 @@ func uiInit(rotation uint16) {
}
func uiTick() {
cgoLock.Lock()
defer cgoLock.Unlock()
C.jetkvm_ui_tick()
}
func videoInit() error {
cgoLock.Lock()
defer cgoLock.Unlock()
ret := C.jetkvm_video_init()
if ret != 0 {
return fmt.Errorf("failed to initialize video: %d", ret)
@ -116,18 +142,30 @@ func videoInit() error {
}
func videoShutdown() {
cgoLock.Lock()
defer cgoLock.Unlock()
C.jetkvm_video_shutdown()
}
func videoStart() {
cgoLock.Lock()
defer cgoLock.Unlock()
C.jetkvm_video_start()
}
func videoStop() {
cgoLock.Lock()
defer cgoLock.Unlock()
C.jetkvm_video_stop()
}
func uiSetVar(name string, value string) {
cgoLock.Lock()
defer cgoLock.Unlock()
nameCStr := C.CString(name)
defer C.free(unsafe.Pointer(nameCStr))
@ -138,6 +176,9 @@ func uiSetVar(name string, value string) {
}
func uiGetVar(name string) string {
cgoLock.Lock()
defer cgoLock.Unlock()
nameCStr := C.CString(name)
defer C.free(unsafe.Pointer(nameCStr))
@ -145,31 +186,58 @@ func uiGetVar(name string) string {
}
func uiSwitchToScreen(screen string) {
cgoLock.Lock()
defer cgoLock.Unlock()
screenCStr := C.CString(screen)
defer C.free(unsafe.Pointer(screenCStr))
C.jetkvm_ui_load_screen(screenCStr)
}
func uiGetCurrentScreen() string {
cgoLock.Lock()
defer cgoLock.Unlock()
screenCStr := C.jetkvm_ui_get_current_screen()
return C.GoString(screenCStr)
}
func uiObjSetState(objName string, state string) (bool, error) {
func uiObjAddState(objName string, state string) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
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)
C.jetkvm_ui_add_state(objNameCStr, stateCStr)
return true, nil
}
func uiObjClearState(objName string, state string) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
stateCStr := C.CString(state)
defer C.free(unsafe.Pointer(stateCStr))
C.jetkvm_ui_clear_state(objNameCStr, stateCStr)
return true, nil
}
func uiGetLVGLVersion() string {
cgoLock.Lock()
defer cgoLock.Unlock()
return C.GoString(C.jetkvm_ui_get_lvgl_version())
}
// TODO: use Enum instead of string but it's not a hot path and performance is not a concern now
func uiObjAddFlag(objName string, flag string) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
flagCStr := C.CString(flag)
@ -179,6 +247,9 @@ func uiObjAddFlag(objName string, flag string) (bool, error) {
}
func uiObjClearFlag(objName string, flag string) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
flagCStr := C.CString(flag)
@ -196,6 +267,9 @@ func uiObjShow(objName string) (bool, error) {
}
func uiObjSetOpacity(objName string, opacity int) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
@ -204,6 +278,9 @@ func uiObjSetOpacity(objName string, opacity int) (bool, error) {
}
func uiObjFadeIn(objName string, duration uint32) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
@ -213,6 +290,9 @@ func uiObjFadeIn(objName string, duration uint32) (bool, error) {
}
func uiObjFadeOut(objName string, duration uint32) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
@ -222,6 +302,9 @@ func uiObjFadeOut(objName string, duration uint32) (bool, error) {
}
func uiLabelSetText(objName string, text string) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
@ -236,6 +319,9 @@ func uiLabelSetText(objName string, text string) (bool, error) {
}
func uiImgSetSrc(objName string, src string) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
objNameCStr := C.CString(objName)
defer C.free(unsafe.Pointer(objNameCStr))
@ -248,6 +334,9 @@ func uiImgSetSrc(objName string, src string) (bool, error) {
}
func uiDispSetRotation(rotation uint16) (bool, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
nativeLogger.Info().Uint16("rotation", rotation).Msg("setting rotation")
cRotation := C.u_int16_t(rotation)
@ -258,21 +347,33 @@ func uiDispSetRotation(rotation uint16) (bool, error) {
}
func videoGetStreamQualityFactor() (float64, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
factor := C.jetkvm_video_get_quality_factor()
return float64(factor), nil
}
func videoSetStreamQualityFactor(factor float64) error {
cgoLock.Lock()
defer cgoLock.Unlock()
C.jetkvm_video_set_quality_factor(C.float(factor))
return nil
}
func videoGetEDID() (string, error) {
cgoLock.Lock()
defer cgoLock.Unlock()
edidCStr := C.jetkvm_video_get_edid_hex()
return C.GoString(edidCStr), nil
}
func videoSetEDID(edid string) error {
cgoLock.Lock()
defer cgoLock.Unlock()
edidCStr := C.CString(edid)
defer C.free(unsafe.Pointer(edidCStr))
C.jetkvm_video_set_edid(edidCStr)

View File

@ -28,7 +28,12 @@ func uiGetCurrentScreen() string {
return ""
}
func uiObjSetState(objName string, state string) (bool, error) {
func uiObjAddState(objName string, state string) (bool, error) {
panicPlatformNotSupported()
return false, nil
}
func uiObjClearState(objName string, state string) (bool, error) {
panicPlatformNotSupported()
return false, nil
}

View File

@ -11,6 +11,7 @@ var (
videoStateChan chan VideoState = make(chan VideoState)
logChan chan nativeLogMessage = make(chan nativeLogMessage)
indevEventChan chan int = make(chan int)
rpcEventChan chan string = make(chan string)
)
func (n *Native) handleVideoFrameChan() {
@ -70,3 +71,10 @@ func (n *Native) handleIndevEventChan() {
n.onIndevEvent(name)
}
}
func (n *Native) handleRpcEventChan() {
for {
event := <-rpcEventChan
n.onRpcEvent(event)
}
}

View File

@ -37,9 +37,24 @@ func (n *Native) UIObjShow(objName string) (bool, error) {
return uiObjShow(objName)
}
// UIObjSetState clears the state then adds the new state
func (n *Native) UIObjSetState(objName string, state string) (bool, error) {
return uiObjSetState(objName, state)
// UISetVar sets the variable
func (n *Native) UISetVar(name string, value string) {
uiSetVar(name, value)
}
// UIGetVar gets the variable
func (n *Native) UIGetVar(name string) string {
return uiGetVar(name)
}
// UIObjAddState adds the state to the object
func (n *Native) UIObjAddState(objName string, state string) (bool, error) {
return uiObjAddState(objName, state)
}
// UIObjClearState clears the state from the object
func (n *Native) UIObjClearState(objName string, state string) (bool, error) {
return uiObjClearState(objName, state)
}
// UIObjAddFlag adds the flag to the object

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
{
"navigation": {
"selectedUserPageObject": "[jetkvm.eez-project]:/userPages/5",
"selectedActionObject": "[jetkvm.eez-project]:/actions/0",
"selectedUserPageObject": "[jetkvm.eez-project]:/userPages/8",
"selectedActionObject": "[jetkvm.eez-project]:/actions/12",
"selectedGlobalVariableObject": "[jetkvm.eez-project]:/variables/globalVariables/1",
"selectedStyleObject": "[jetkvm.eez-project]:/lvglStyles/styles/0",
"selectedStyleObject": "[jetkvm.eez-project]:/lvglStyles/styles/8",
"selectedThemeObject": "[jetkvm.eez-project]:/themes/0",
"selectedFontObject": "[jetkvm.eez-project]:/fonts/4",
"selectedBitmapObject": "[jetkvm.eez-project]:/bitmaps/11",
"selectedBitmapObject": "[jetkvm.eez-project]:/bitmaps/9",
"subnavigationSelectedItems": {
"variables-tab/sub-navigation/selected-item": "Global"
}
@ -30,7 +30,7 @@
{
"type": "border",
"selected": 2,
"size": 113.5,
"size": 271.5,
"location": "right",
"children": [
{
@ -77,7 +77,7 @@
},
{
"type": "border",
"selected": 1,
"selected": 0,
"location": "bottom",
"children": [
{
@ -188,38 +188,48 @@
"enableClose": false,
"icon": "svg:variable"
}
],
"active": true
]
}
]
},
{
"type": "tabset",
"id": "EDITORS",
"weight": 49.31058517127421,
"selected": 4,
"weight": 52.479136828866125,
"selected": 5,
"enableDeleteWhenEmpty": false,
"enableClose": false,
"children": [
{
"type": "tab",
"id": "#aec56ae8-5b75-4a2f-ad81-f0d7d683c77a",
"name": "FontBook24",
"id": "#2b774476-9ef3-4363-83f8-8b478f163b02",
"name": "MenuAdvancedScreen",
"component": "editor",
"config": {
"objectPath": "[jetkvm.eez-project]:/fonts/4",
"objectPath": "[jetkvm.eez-project]:/userPages/4",
"permanent": false
},
"icon": "material:font_download"
"icon": "svg:page"
},
{
"type": "tab",
"id": "#7bbd8382-ea41-467d-8ad3-4312a2d47266",
"name": "ResetConfigScreen",
"component": "editor",
"config": {
"objectPath": "[jetkvm.eez-project]:/userPages/8",
"permanent": true
},
"icon": "svg:page"
},
{
"type": "tab",
"id": "#c8dece00-e490-46b8-8a14-5dcfa8bbce36",
"name": "AboutScreen",
"name": "StatusScreen",
"component": "editor",
"config": {
"objectPath": "[jetkvm.eez-project]:/userPages/5",
"permanent": false
"objectPath": "[jetkvm.eez-project]:/userPages/7",
"permanent": true
},
"icon": "svg:page"
},
@ -237,7 +247,7 @@
{
"type": "tab",
"id": "#f5a057a5-977c-46be-8702-5447d603a34f",
"name": "MenuScreen",
"name": "HomeScreen",
"component": "editor",
"config": {
"objectPath": "[jetkvm.eez-project]:/userPages/2",
@ -257,12 +267,13 @@
},
"icon": "material:settings"
}
]
],
"active": true
},
{
"type": "row",
"id": "#ee319cf9-6145-49e4-b40e-1d999be897c8",
"weight": 24.95234430353635,
"weight": 21.78379264594443,
"children": [
{
"type": "tabset",
@ -1065,8 +1076,7 @@
"0": {
"0": {
"0": {
"1": {},
"$selected": true
"1": {}
}
}
}
@ -1086,11 +1096,58 @@
}
},
"[jetkvm.eez-project]:/userPages/1[flow-state]": {
"selection": {
"0": {
"0": {
"0": {},
"1": {},
"$selected": true
}
}
},
"transform": {
"translate": {
"x": -180,
"y": -207
},
"scale": 1
},
"timeline": {
"isEditorActive": false,
"position": 0,
"secondToPx": 200,
"scrollLeft": 0
}
},
"[jetkvm.eez-project]:/lvglStyles/styles[tree-state]": {
"0": {},
"1": {},
"2": {},
"3": {},
"4": {
"$selected": true
},
"5": {
"$selected": true
},
"6": {
"$selected": true
},
"7": {
"$selected": true
},
"8": {
"$selected": true
}
},
"[jetkvm.eez-project]:/userPages/2[flow-state]": {
"selection": {
"0": {
"0": {
"0": {
"0": {},
"0": {
"$selected": true
},
"1": {
"0": {}
}
@ -1102,9 +1159,7 @@
"3": {
"0": {
"0": {},
"1": {
"$selected": true
}
"1": {}
},
"1": {
"0": {},
@ -1128,12 +1183,7 @@
"scrollLeft": 0
}
},
"[jetkvm.eez-project]:/lvglStyles/styles[tree-state]": {
"0": {
"$selected": true
}
},
"[jetkvm.eez-project]:/userPages/2[flow-state]": {
"[jetkvm.eez-project]:/userPages/3[flow-state]": {
"selection": {
"0": {
"0": {
@ -1142,13 +1192,14 @@
},
"1": {
"0": {
"0": {},
"0": {
"$selected": true
},
"1": {},
"2": {},
"3": {},
"4": {
"0": {},
"$selected": true
"0": {}
}
}
}
@ -1169,19 +1220,15 @@
"scrollLeft": 0
}
},
"[jetkvm.eez-project]:/userPages/3[flow-state]": {
"[jetkvm.eez-project]:/userPages/4[flow-state]": {
"selection": {
"0": {
"0": {
"0": {
"0": {},
"$selected": true
"0": {}
},
"1": {
"0": {
"0": {},
"1": {},
"2": {},
"3": {}
}
}
@ -1190,41 +1237,8 @@
},
"transform": {
"translate": {
"x": -150,
"y": -120
},
"scale": 1
},
"timeline": {
"isEditorActive": false,
"position": 0,
"secondToPx": 200,
"scrollLeft": 0
}
},
"[jetkvm.eez-project]:/userPages/4[flow-state]": {
"selection": {
"0": {
"0": {
"0": {
"0": {
"$selected": true
}
},
"1": {
"0": {
"0": {},
"1": {},
"2": {}
}
}
}
}
},
"transform": {
"translate": {
"x": -176,
"y": -127
"x": -181,
"y": -256.3828125
},
"scale": 1
},
@ -1245,19 +1259,8 @@
"1": {
"0": {
"0": {},
"1": {
"1": {}
},
"2": {},
"3": {},
"4": {},
"5": {
"1": {
"$selected": true
}
},
"6": {},
"7": {}
"1": {},
"2": {}
}
}
}
@ -1300,11 +1303,11 @@
"0": {},
"1": {
"0": {
"0": {},
"0": {
"1": {}
},
"2": {
"1": {
"$selected": true
}
"1": {}
}
}
}
@ -1313,7 +1316,7 @@
},
"transform": {
"translate": {
"x": -138,
"x": -10.425644531250029,
"y": -122
},
"scale": 1
@ -1324,6 +1327,67 @@
"secondToPx": 200,
"scrollLeft": 0
}
},
"[jetkvm.eez-project]:/userPages/7[flow-state]": {
"selection": {
"0": {
"0": {
"0": {},
"1": {},
"$selected": true
}
}
},
"transform": {
"translate": {
"x": -180,
"y": -207
},
"scale": 1
},
"timeline": {
"isEditorActive": false,
"position": 0,
"secondToPx": 200,
"scrollLeft": 0
}
},
"[jetkvm.eez-project]:/userPages/8[flow-state]": {
"selection": {
"0": {
"0": {
"0": {
"0": {}
},
"1": {
"0": {
"0": {
"0": {
"$selected": true
}
},
"1": {},
"2": {
"0": {}
}
}
}
}
}
},
"transform": {
"translate": {
"x": -194,
"y": -37
},
"scale": 1
},
"timeline": {
"isEditorActive": false,
"position": 0,
"secondToPx": 200,
"scrollLeft": 0
}
}
},
"activeOutputSection": 0,
@ -1339,10 +1403,10 @@
"logsPanelFilter": "all",
"selectedStylePropertyName": "",
"lvglPart": "MAIN",
"lvglState": "DISABLED",
"lvglState": "DEFAULT",
"lvglExpandedPropertiesGroup": [
"MARGIN",
"TEXT"
"POSITION AND SIZE",
"LAYOUT"
],
"showInactiveFlowsInDebugger": true,
"globalFlowZoom": true,

View File

@ -1,5 +1,8 @@
#include "actions.h"
#include "screens.h"
#include <stdio.h>
#include "ui.h"
#include "vars.h"
int handle_gesture_screen_switch(lv_event_t *e, lv_dir_t direction, int screenId) {
lv_event_code_t event_code = lv_event_get_code(e);
@ -15,6 +18,15 @@ int handle_gesture_screen_switch(lv_event_t *e, lv_dir_t direction, int screenId
return 1;
}
void handle_gesture_main_screen_switch(lv_event_t *e, lv_dir_t direction) {
const char *main_screen = get_var_main_screen();
if (strcmp(main_screen, "home_screen") == 0) {
loadScreen(SCREEN_ID_HOME_SCREEN);
} else if (strcmp(main_screen, "no_network_screen") == 0) {
loadScreen(SCREEN_ID_NO_NETWORK_SCREEN);
}
}
void action_switch_to_menu(lv_event_t *e) {
loadScreen(SCREEN_ID_MENU_SCREEN);
}
@ -31,8 +43,16 @@ void action_switch_to_about(lv_event_t *e) {
loadScreen(SCREEN_ID_ABOUT_SCREEN);
}
void action_switch_to_reset_config(lv_event_t *e) {
loadScreen(SCREEN_ID_RESET_CONFIG_SCREEN);
}
void action_switch_to_reboot(lv_event_t *e) {
loadScreen(SCREEN_ID_REBOOT_SCREEN);
}
void action_menu_screen_gesture(lv_event_t * e) {
handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_HOME_SCREEN);
handle_gesture_main_screen_switch(e, LV_DIR_RIGHT);
}
void action_menu_advanced_screen_gesture(lv_event_t * e) {
@ -40,7 +60,7 @@ void action_menu_advanced_screen_gesture(lv_event_t * e) {
}
void action_reset_config_screen_gesture(lv_event_t * e) {
handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_MENU_ADVANCED_SCREEN);
handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_MENU_SCREEN);
}
void action_home_screen_gesture(lv_event_t * e) {
@ -50,3 +70,71 @@ void action_home_screen_gesture(lv_event_t * e) {
void action_about_screen_gesture(lv_event_t * e) {
handle_gesture_screen_switch(e, LV_DIR_RIGHT, SCREEN_ID_MENU_SCREEN);
}
// user_data doesn't seem to be working, so we use a global variable here
static uint32_t t_reset_config;
static uint32_t t_reboot;
static bool b_reboot = false;
static bool b_reset_config = false;
const int RESET_CONFIG_HOLD_TIME = 10;
const int REBOOT_HOLD_TIME = 5;
void action_reset_config(lv_event_t * e) {
lv_event_code_t event_code = lv_event_get_code(e);
lv_obj_t *obj = lv_event_get_target(e);
if (event_code == LV_EVENT_PRESSED) {
t_reset_config = lv_tick_get();
}
else if (event_code == LV_EVENT_PRESSING) {
int remaining_time = RESET_CONFIG_HOLD_TIME * 1000 - lv_tick_elaps(t_reset_config);
if (remaining_time <= 0) {
lv_obj_add_flag(objects.reset_config_button, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(objects.reset_config_spinner, LV_OBJ_FLAG_HIDDEN);
ui_call_rpc_handler("resetConfig", NULL);
b_reset_config = true;
} else {
b_reset_config = false;
char buf[100];
int remaining_time_seconds = remaining_time / 1000;
if (remaining_time_seconds <= 1) {
remaining_time_seconds = 1;
}
sprintf(buf, "Press and hold for %d seconds", remaining_time_seconds);
lv_label_set_text(objects.reset_config_label, buf);
}
} else if (event_code == LV_EVENT_RELEASED) {
if (!b_reset_config) {
lv_label_set_text(objects.reset_config_label, "Press and hold for 10 seconds");
}
}
}
void action_reboot(lv_event_t * e) {
lv_event_code_t event_code = lv_event_get_code(e);
lv_obj_t *obj = lv_event_get_target(e);
if (event_code == LV_EVENT_PRESSED) {
t_reboot = lv_tick_get();
}
else if (event_code == LV_EVENT_PRESSING) {
int remaining_time = REBOOT_HOLD_TIME * 1000 - lv_tick_elaps(t_reboot);
if (remaining_time <= 0) {
ui_call_rpc_handler("reboot", NULL);
b_reboot = false;
} else {
b_reboot = false;
char buf[100];
int remaining_time_seconds = remaining_time / 1000;
if (remaining_time_seconds <= 1) {
remaining_time_seconds = 1;
}
sprintf(buf, "Press and hold for %d seconds", remaining_time_seconds);
lv_label_set_text(objects.reboot_label, buf);
}
} else if (event_code == LV_EVENT_RELEASED) {
if (!b_reboot) {
lv_label_set_text(objects.reboot_label, "Press and hold for 5 seconds");
}
}
}

View File

@ -11,6 +11,7 @@ extern int handle_gesture_screen_switch(lv_event_t *e, lv_dir_t direction, int s
extern void action_switch_to_menu(lv_event_t * e);
extern void action_switch_to_advanced_menu(lv_event_t * e);
extern void action_switch_to_reset_config(lv_event_t * e);
extern void action_switch_to_about(lv_event_t * e);
extern void action_menu_screen_gesture(lv_event_t * e);
extern void action_home_screen_gesture(lv_event_t * e);
@ -20,6 +21,9 @@ extern void action_about_screen_gesture(lv_event_t * e);
extern void action_switch_to_status(lv_event_t * e);
extern void action_common_click_event(lv_event_t * e);
extern void action_handle_common_press_event(lv_event_t * e);
extern void action_reset_config(lv_event_t * e);
extern void action_reboot(lv_event_t * e);
extern void action_switch_to_reboot(lv_event_t * e);
#ifdef __cplusplus

View File

@ -325,10 +325,11 @@ void create_screen_home_screen() {
lv_obj_t *obj = lv_label_create(parent_obj);
objects.home_info_ipv6_addr = obj;
lv_obj_set_pos(obj, LV_PCT(0), LV_PCT(0));
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_size(obj, LV_PCT(98), 17);
lv_label_set_long_mode(obj, LV_LABEL_LONG_DOT);
add_style_label_font16(obj);
lv_label_set_text(obj, "fe80::ffff:ffff:ffff:ffff:ffff:ffff");
lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_label_set_text(obj, "fe80::ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
}
{
// HomeInfoMACAddr
@ -431,10 +432,11 @@ void create_screen_home_screen() {
objects.usb_status_label = obj;
lv_obj_set_pos(obj, LV_PCT(0), LV_PCT(0));
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
add_style_label_font16(obj);
lv_obj_set_style_text_color(obj, lv_color_hex(0xff22c55e), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_color(obj, lv_color_hex(0xff808080), LV_PART_MAIN | LV_STATE_DISABLED);
lv_label_set_text(obj, "Connected");
lv_obj_set_style_text_color(obj, lv_color_hex(0xff808080), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_color(obj, lv_color_hex(0xff22c55e), LV_PART_MAIN | LV_STATE_CHECKED);
lv_label_set_text(obj, "Unknown");
}
}
}
@ -493,11 +495,11 @@ void create_screen_home_screen() {
objects.hdmi_status_label = obj;
lv_obj_set_pos(obj, LV_PCT(0), LV_PCT(0));
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_add_state(obj, LV_STATE_DISABLED);
lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
add_style_label_font16(obj);
lv_obj_set_style_text_color(obj, lv_color_hex(0xff22c55e), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_color(obj, lv_color_hex(0xff808080), LV_PART_MAIN | LV_STATE_DISABLED);
lv_label_set_text(obj, "Disconnected");
lv_obj_set_style_text_color(obj, lv_color_hex(0xff22c55e), LV_PART_MAIN | LV_STATE_CHECKED);
lv_obj_set_style_text_color(obj, lv_color_hex(0xff808080), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_label_set_text(obj, "Unknown");
}
}
}
@ -829,6 +831,8 @@ void create_screen_menu_advanced_screen() {
objects.menu_btn_advanced_developer_mode = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), 50);
lv_obj_add_event_cb(obj, action_reset_config, LV_EVENT_PRESSED, (void *)0);
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
add_style_menu_button(obj);
{
lv_obj_t *parent_obj = obj;
@ -847,6 +851,7 @@ void create_screen_menu_advanced_screen() {
objects.menu_btn_advanced_usb_emulation = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), 50);
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
add_style_menu_button(obj);
{
lv_obj_t *parent_obj = obj;
@ -865,6 +870,7 @@ void create_screen_menu_advanced_screen() {
objects.menu_btn_advanced_reboot = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), 50);
lv_obj_add_event_cb(obj, action_switch_to_reboot, LV_EVENT_PRESSED, (void *)0);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SNAPPABLE);
add_style_menu_button(obj);
{
@ -884,6 +890,7 @@ void create_screen_menu_advanced_screen() {
objects.menu_btn_advanced_reset_config = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), 50);
lv_obj_add_event_cb(obj, action_switch_to_reset_config, LV_EVENT_PRESSED, (void *)0);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SNAPPABLE);
add_style_menu_button(obj);
lv_obj_set_style_bg_color(obj, lv_color_hex(0xffdc2626), LV_PART_MAIN | LV_STATE_DEFAULT);
@ -1719,6 +1726,403 @@ void create_screen_status_screen() {
void tick_screen_status_screen() {
}
void create_screen_reset_config_screen() {
lv_obj_t *obj = lv_obj_create(0);
objects.reset_config_screen = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 300, 240);
lv_obj_add_event_cb(obj, action_about_screen_gesture, LV_EVENT_GESTURE, (void *)0);
add_style_flex_screen_menu(obj);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_obj_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_start(obj);
{
lv_obj_t *parent_obj = obj;
{
// ResetConfigHeader
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reset_config_header = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
add_style_flow_row_space_between(obj);
lv_obj_set_style_pad_right(obj, 4, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_button_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 32, 32);
lv_obj_add_event_cb(obj, action_switch_to_menu, LV_EVENT_CLICKED, (void *)0);
add_style_back_button(obj);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_image_create(parent_obj);
lv_obj_set_pos(obj, -1, 2);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_image_set_src(obj, &img_back_caret);
}
}
}
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, LV_PCT(0), LV_PCT(0));
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
add_style_header_link(obj);
lv_label_set_text(obj, "Reset Config");
}
}
}
{
// ResetConfigContainer
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reset_config_container = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(80));
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_scroll_dir(obj, LV_DIR_VER);
lv_obj_set_scroll_snap_x(obj, LV_SCROLL_SNAP_START);
add_style_flex_column_start(obj);
lv_obj_set_style_pad_right(obj, 4, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_obj_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
lv_obj_set_style_pad_right(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
// ResetConfigLabelContainer
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reset_config_label_container = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
lv_obj_set_style_pad_right(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_left(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
// ResetConfigLabel
lv_obj_t *obj = lv_label_create(parent_obj);
objects.reset_config_label = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
add_style_info_content_label(obj);
lv_obj_set_style_text_font(obj, &ui_font_font_book20, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_label_set_text(obj, "Press and hold for\n10 seconds");
}
}
}
{
// ResetConfigSpinner
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reset_config_spinner = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_add_flag(obj, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE|LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
lv_obj_set_style_flex_main_place(obj, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_flex_cross_place(obj, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_flex_track_place(obj, LV_FLEX_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_spinner_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 80, 80);
lv_spinner_set_anim_params(obj, 1000, 60);
}
}
}
{
// ResetConfigButton
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reset_config_button = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_button_create(parent_obj);
objects.obj0 = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), 50);
lv_obj_add_event_cb(obj, action_reset_config, LV_EVENT_PRESSED, (void *)0);
lv_obj_add_event_cb(obj, action_reset_config, LV_EVENT_PRESSING, (void *)0);
lv_obj_add_event_cb(obj, action_reset_config, LV_EVENT_RELEASED, (void *)0);
lv_obj_set_style_bg_color(obj, lv_color_hex(0xffdc2626), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 13, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_style_align(obj, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_label_set_text(obj, "Reset configuration");
}
}
}
}
}
}
}
}
}
}
}
}
tick_screen_reset_config_screen();
}
void tick_screen_reset_config_screen() {
}
void create_screen_reboot_screen() {
lv_obj_t *obj = lv_obj_create(0);
objects.reboot_screen = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 300, 240);
lv_obj_add_event_cb(obj, action_about_screen_gesture, LV_EVENT_GESTURE, (void *)0);
add_style_flex_screen_menu(obj);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_obj_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100));
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_start(obj);
{
lv_obj_t *parent_obj = obj;
{
// RebootHeader
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reboot_header = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
add_style_flow_row_space_between(obj);
lv_obj_set_style_pad_right(obj, 4, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_button_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, 32, 32);
lv_obj_add_event_cb(obj, action_switch_to_menu, LV_EVENT_CLICKED, (void *)0);
add_style_back_button(obj);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_image_create(parent_obj);
lv_obj_set_pos(obj, -1, 2);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_image_set_src(obj, &img_back_caret);
}
}
}
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, LV_PCT(0), LV_PCT(0));
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
add_style_header_link(obj);
lv_label_set_text(obj, "Reboot Device");
}
}
}
{
// RebootContainer
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reboot_container = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_PCT(80));
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_scrollbar_mode(obj, LV_SCROLLBAR_MODE_AUTO);
lv_obj_set_scroll_dir(obj, LV_DIR_VER);
lv_obj_set_scroll_snap_x(obj, LV_SCROLL_SNAP_START);
add_style_flex_column_start(obj);
lv_obj_set_style_pad_right(obj, 4, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_obj_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
lv_obj_set_style_pad_right(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
// RebootLabelContainer
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reboot_label_container = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
lv_obj_set_style_pad_right(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_left(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 10, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
// RebootLabel
lv_obj_t *obj = lv_label_create(parent_obj);
objects.reboot_label = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
add_style_info_content_label(obj);
lv_obj_set_style_text_font(obj, &ui_font_font_book20, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_label_set_text(obj, "Press and hold for\n5 seconds");
}
}
}
{
// RebootConfigButton
lv_obj_t *obj = lv_obj_create(parent_obj);
objects.reboot_config_button = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_left(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_top(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_bottom(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_border_width(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_radius(obj, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
add_style_flex_column_start(obj);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_button_create(parent_obj);
objects.obj1 = obj;
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_PCT(100), 50);
lv_obj_add_event_cb(obj, action_reboot, LV_EVENT_PRESSED, (void *)0);
lv_obj_add_event_cb(obj, action_reboot, LV_EVENT_PRESSING, (void *)0);
lv_obj_add_event_cb(obj, action_reboot, LV_EVENT_RELEASED, (void *)0);
lv_obj_set_style_bg_color(obj, lv_color_hex(0xffdc2626), LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_pad_right(obj, 13, LV_PART_MAIN | LV_STATE_DEFAULT);
{
lv_obj_t *parent_obj = obj;
{
lv_obj_t *obj = lv_label_create(parent_obj);
lv_obj_set_pos(obj, 0, 0);
lv_obj_set_size(obj, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
lv_obj_set_style_align(obj, LV_ALIGN_CENTER, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_set_style_text_align(obj, LV_TEXT_ALIGN_LEFT, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_label_set_text(obj, "Hold to reboot");
}
}
}
}
}
}
}
}
}
}
}
}
tick_screen_reboot_screen();
}
void tick_screen_reboot_screen() {
}
typedef void (*tick_screen_func_t)();
@ -1731,6 +2135,8 @@ tick_screen_func_t tick_screen_funcs[] = {
tick_screen_menu_network_screen,
tick_screen_about_screen,
tick_screen_status_screen,
tick_screen_reset_config_screen,
tick_screen_reboot_screen,
};
void tick_screen(int screen_index) {
tick_screen_funcs[screen_index]();
@ -1752,4 +2158,6 @@ void create_screens() {
create_screen_menu_network_screen();
create_screen_about_screen();
create_screen_status_screen();
create_screen_reset_config_screen();
create_screen_reboot_screen();
}

View File

@ -16,6 +16,8 @@ typedef struct _objects_t {
lv_obj_t *menu_network_screen;
lv_obj_t *about_screen;
lv_obj_t *status_screen;
lv_obj_t *reset_config_screen;
lv_obj_t *reboot_screen;
lv_obj_t *boot_logo;
lv_obj_t *boot_screen_version;
lv_obj_t *no_network_header_container;
@ -83,6 +85,19 @@ typedef struct _objects_t {
lv_obj_t *app_version_1;
lv_obj_t *cloud_domain_container;
lv_obj_t *cloud_domain;
lv_obj_t *reset_config_header;
lv_obj_t *reset_config_container;
lv_obj_t *reset_config_label_container;
lv_obj_t *reset_config_label;
lv_obj_t *reset_config_spinner;
lv_obj_t *reset_config_button;
lv_obj_t *obj0;
lv_obj_t *reboot_header;
lv_obj_t *reboot_container;
lv_obj_t *reboot_label_container;
lv_obj_t *reboot_label;
lv_obj_t *reboot_config_button;
lv_obj_t *obj1;
} objects_t;
extern objects_t objects;
@ -96,6 +111,8 @@ enum ScreensEnum {
SCREEN_ID_MENU_NETWORK_SCREEN = 6,
SCREEN_ID_ABOUT_SCREEN = 7,
SCREEN_ID_STATUS_SCREEN = 8,
SCREEN_ID_RESET_CONFIG_SCREEN = 9,
SCREEN_ID_REBOOT_SCREEN = 10,
};
void create_screen_boot_screen();
@ -122,6 +139,12 @@ void tick_screen_about_screen();
void create_screen_status_screen();
void tick_screen_status_screen();
void create_screen_reset_config_screen();
void tick_screen_reset_config_screen();
void create_screen_reboot_screen();
void tick_screen_reboot_screen();
void tick_screen_by_id(enum ScreensEnum screenId);
void tick_screen(int screen_index);

View File

@ -14,6 +14,19 @@
jetkvm_rpc_handler_t *ui_rpc_handler = NULL;
void ui_set_rpc_handler(jetkvm_rpc_handler_t *handler) {
ui_rpc_handler = handler;
}
void ui_call_rpc_handler(const char *method, const char *params) {
if (ui_rpc_handler != NULL) {
(*ui_rpc_handler)(method, params);
}
}
#if defined(EEZ_FOR_LVGL)
void ui_init() {

View File

@ -3,6 +3,13 @@
#include <lvgl.h>
typedef void (jetkvm_rpc_handler_t)(const char *method, const char *params);
void ui_set_rpc_handler(jetkvm_rpc_handler_t *handler);
void ui_call_rpc_handler(const char *method, const char *params);
#if defined(EEZ_FOR_LVGL)

View File

@ -5,6 +5,7 @@
char app_version[100] = { 0 };
char system_version[100] = { 0 };
char lvgl_version[32] = { 0 };
char main_screen[32] = "home_screen";
const char *get_var_app_version() {
return app_version;
@ -37,3 +38,12 @@ void set_var_system_version(const char *value) {
}
void set_var_lvgl_version(const char *value) {}
void set_var_main_screen(const char *value) {
strncpy(main_screen, value, sizeof(main_screen) / sizeof(char));
main_screen[sizeof(main_screen) / sizeof(char) - 1] = 0;
}
const char *get_var_main_screen() {
return main_screen;
}

View File

@ -17,7 +17,8 @@ extern "C" {
enum FlowGlobalVariables {
FLOW_GLOBAL_VARIABLE_APP_VERSION = 0,
FLOW_GLOBAL_VARIABLE_SYSTEM_VERSION = 1,
FLOW_GLOBAL_VARIABLE_LVGL_VERSION = 2
FLOW_GLOBAL_VARIABLE_LVGL_VERSION = 2,
FLOW_GLOBAL_VARIABLE_MAIN_SCREEN = 3
};
// Native global variables
@ -28,6 +29,8 @@ extern const char *get_var_system_version();
extern void set_var_system_version(const char *value);
extern const char *get_var_lvgl_version();
extern void set_var_lvgl_version(const char *value);
extern const char *get_var_main_screen();
extern void set_var_main_screen(const char *value);
#ifdef __cplusplus

Binary file not shown.

View File

@ -1,6 +1,7 @@
package native
import (
"sync"
"time"
"github.com/Masterminds/semver/v3"
@ -17,6 +18,9 @@ type Native struct {
onVideoStateChange func(state VideoState)
onVideoFrameReceived func(frame []byte, duration time.Duration)
onIndevEvent func(event string)
onRpcEvent func(event string)
videoLock sync.Mutex
screenLock sync.Mutex
}
type NativeOptions struct {
@ -26,6 +30,7 @@ type NativeOptions struct {
OnVideoStateChange func(state VideoState)
OnVideoFrameReceived func(frame []byte, duration time.Duration)
OnIndevEvent func(event string)
OnRpcEvent func(event string)
}
func NewNative(opts NativeOptions) *Native {
@ -50,6 +55,13 @@ func NewNative(opts NativeOptions) *Native {
}
}
onRpcEvent := opts.OnRpcEvent
if onRpcEvent == nil {
onRpcEvent = func(event string) {
nativeLogger.Info().Str("event", event).Msg("rpc event")
}
}
return &Native{
ready: make(chan struct{}),
l: nativeLogger,
@ -60,6 +72,9 @@ func NewNative(opts NativeOptions) *Native {
onVideoStateChange: opts.OnVideoStateChange,
onVideoFrameReceived: opts.OnVideoFrameReceived,
onIndevEvent: opts.OnIndevEvent,
onRpcEvent: opts.OnRpcEvent,
videoLock: sync.Mutex{},
screenLock: sync.Mutex{},
}
}
@ -73,6 +88,7 @@ func (n *Native) Start() {
go n.handleVideoStateChan()
go n.handleVideoFrameChan()
go n.handleIndevEventChan()
go n.handleRpcEventChan()
n.initUI()
go n.tickUI()

View File

@ -9,27 +9,45 @@ type VideoState struct {
}
func (n *Native) VideoSetQualityFactor(factor float64) error {
n.videoLock.Lock()
defer n.videoLock.Unlock()
return videoSetStreamQualityFactor(factor)
}
func (n *Native) VideoGetQualityFactor() (float64, error) {
n.videoLock.Lock()
defer n.videoLock.Unlock()
return videoGetStreamQualityFactor()
}
func (n *Native) VideoSetEDID(edid string) error {
n.videoLock.Lock()
defer n.videoLock.Unlock()
return videoSetEDID(edid)
}
func (n *Native) VideoGetEDID() (string, error) {
n.videoLock.Lock()
defer n.videoLock.Unlock()
return videoGetEDID()
}
func (n *Native) VideoStop() error {
n.videoLock.Lock()
defer n.videoLock.Unlock()
videoStop()
return nil
}
func (n *Native) VideoStart() error {
n.videoLock.Lock()
defer n.videoLock.Unlock()
videoStart()
return nil
}

View File

@ -1,46 +0,0 @@
package network
import (
"errors"
"github.com/jetkvm/kvm/internal/lldp"
)
func (s *NetworkInterfaceState) shouldStartLLDP() bool {
if s.lldp == nil {
s.l.Trace().Msg("LLDP not initialized")
return false
}
s.l.Trace().Msgf("LLDP mode: %s", s.config.LLDPMode.String)
return s.config.LLDPMode.String != "disabled"
}
func (s *NetworkInterfaceState) startLLDP() {
if !s.shouldStartLLDP() || s.lldp == nil {
return
}
s.l.Trace().Msg("starting LLDP")
if err := s.lldp.Start(); err != nil {
s.l.Error().Err(err).Msg("unable to start LLDP")
}
}
func (s *NetworkInterfaceState) stopLLDP() {
if s.lldp == nil {
return
}
s.l.Trace().Msg("stopping LLDP")
if err := s.lldp.Stop(); err != nil {
s.l.Error().Err(err).Msg("unable to stop LLDP")
}
}
func (s *NetworkInterfaceState) GetLLDPNeighbors() ([]lldp.Neighbor, error) {
if s.lldp == nil {
return nil, errors.New("lldp not initialized")
}
return s.lldp.GetNeighbors(), nil
}

View File

@ -6,7 +6,6 @@ import (
"sync"
"github.com/jetkvm/kvm/internal/confparser"
"github.com/jetkvm/kvm/internal/lldp"
"github.com/jetkvm/kvm/internal/logging"
"github.com/jetkvm/kvm/internal/udhcpc"
"github.com/rs/zerolog"
@ -31,8 +30,6 @@ type NetworkInterfaceState struct {
config *NetworkConfig
dhcpClient *udhcpc.DHCPClient
lldp *lldp.LLDP
defaultHostname string
currentHostname string
currentFqdn string
@ -101,24 +98,7 @@ func NewNetworkInterfaceState(opts *NetworkInterfaceOptions) (*NetworkInterfaceS
},
})
// create the lldp service
lldpClient := lldp.NewLLDP(&lldp.LLDPOptions{
InterfaceName: opts.InterfaceName,
EnableRx: true,
EnableTx: true,
Logger: l,
})
// create the lldp service
lldpClient = lldp.NewLLDP(&lldp.LLDPOptions{
InterfaceName: opts.InterfaceName,
EnableRx: true,
EnableTx: true,
Logger: l,
})
s.dhcpClient = dhcpClient
s.lldp = lldpClient
return s, nil
}
@ -387,16 +367,13 @@ func (s *NetworkInterfaceState) updateNtpServersFromLease(lease *udhcpc.Lease) e
func (s *NetworkInterfaceState) handleInitialCheck() {
if s.IsUp() {
s.startLLDP()
}
s.onInitialCheck(s)
}
func (s *NetworkInterfaceState) handleStateChange() {
if s.IsUp() {
s.startLLDP()
} else {
s.stopLLDP()
}
s.onStateChange(s)
}

View File

@ -1259,5 +1259,4 @@ var rpcHandlers = map[string]RPCHandler{
"setKeyboardMacros": {Func: setKeyboardMacros, Params: []string{"params"}},
"getLocalLoopbackOnly": {Func: rpcGetLocalLoopbackOnly},
"setLocalLoopbackOnly": {Func: rpcSetLocalLoopbackOnly, Params: []string{"enabled"}},
"getLLDPNeighbors": {Func: rpcGetLLDPNeighbors},
}

View File

@ -24,6 +24,18 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
nativeLogger.Trace().Str("event", event).Msg("indev event received")
wakeDisplay(false, "indev_event")
},
OnRpcEvent: func(event string) {
nativeLogger.Trace().Str("event", event).Msg("rpc event received")
switch event {
case "resetConfig":
rpcResetConfig()
rpcReboot(true)
case "reboot":
rpcReboot(true)
default:
nativeLogger.Warn().Str("event", event).Msg("unknown rpc event received")
}
},
OnVideoFrameReceived: func(frame []byte, duration time.Duration) {
if currentSession != nil {
err := currentSession.VideoTrack.WriteSample(media.Sample{Data: frame, Duration: duration})

View File

@ -3,7 +3,6 @@ package kvm
import (
"fmt"
"github.com/jetkvm/kvm/internal/lldp"
"github.com/jetkvm/kvm/internal/network"
"github.com/jetkvm/kvm/internal/udhcpc"
)
@ -124,7 +123,3 @@ func rpcSetNetworkSettings(settings network.RpcNetworkSettings) (*network.RpcNet
func rpcRenewDHCPLease() error {
return networkState.RpcRenewDHCPLease()
}
func rpcGetLLDPNeighbors() ([]lldp.Neighbor, error) {
return networkState.GetLLDPNeighbors()
}

View File

@ -1,13 +1,8 @@
package kvm
import "github.com/jetkvm/kvm/internal/native"
// max frame size for 1080p video, specified in mpp venc setting
const maxFrameSize = 1920 * 1080 / 2
func writeCtrlAction(action string) error {
return nil
}
import (
"github.com/jetkvm/kvm/internal/native"
)
var lastVideoState native.VideoState
@ -15,19 +10,9 @@ func triggerVideoStateUpdate() {
go func() {
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)
// }
nativeLogger.Info().Interface("state", lastVideoState).Msg("video state updated")
}
func rpcGetVideoState() (native.VideoState, error) {
return lastVideoState, nil