From 2b58ecfd2b3ecaf8f88f32be7b21f48978f55c71 Mon Sep 17 00:00:00 2001 From: wangjianyu3 Date: Thu, 23 Apr 2026 23:16:47 +0800 Subject: [PATCH] examples/vncviewer: add VNC viewer for LCD display Add a minimal VNC viewer application that connects to a VNC server and renders the remote desktop on a local LCD. Features: - RFB 3.8 protocol with VNC Authentication (DES, pure software) - Raw encoding with pixel format auto-detected from LCD driver - Row-by-row rendering via LCDDEVIO_PUTAREA (minimal RAM usage) - LCD resolution and format queried at runtime via LCDDEVIO_GETVIDEOINFO - Auto-reconnect on connection loss with 2-second retry Usage: vncviewer [port] vncviewer -p [port] vncviewer -p -d [port] Assisted-by: GitHubCopilot:claude-4.6-opus Signed-off-by: wangjianyu3 --- system/vncviewer/CMakeLists.txt | 35 + system/vncviewer/Kconfig | 32 + system/vncviewer/Make.defs | 25 + system/vncviewer/Makefile | 37 + system/vncviewer/lcd_render.c | 177 +++++ system/vncviewer/lcd_render.h | 106 +++ system/vncviewer/rfb_protocol.c | 1051 +++++++++++++++++++++++++++++ system/vncviewer/rfb_protocol.h | 220 ++++++ system/vncviewer/vncviewer_main.c | 306 +++++++++ 9 files changed, 1989 insertions(+) create mode 100644 system/vncviewer/CMakeLists.txt create mode 100644 system/vncviewer/Kconfig create mode 100644 system/vncviewer/Make.defs create mode 100644 system/vncviewer/Makefile create mode 100644 system/vncviewer/lcd_render.c create mode 100644 system/vncviewer/lcd_render.h create mode 100644 system/vncviewer/rfb_protocol.c create mode 100644 system/vncviewer/rfb_protocol.h create mode 100644 system/vncviewer/vncviewer_main.c diff --git a/system/vncviewer/CMakeLists.txt b/system/vncviewer/CMakeLists.txt new file mode 100644 index 00000000000..06fc69463ce --- /dev/null +++ b/system/vncviewer/CMakeLists.txt @@ -0,0 +1,35 @@ +# ############################################################################## +# apps/system/vncviewer/CMakeLists.txt +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you 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. +# +# ############################################################################## + +if(CONFIG_SYSTEM_VNCVIEWER) + nuttx_add_application( + NAME + ${CONFIG_SYSTEM_VNCVIEWER_PROGNAME} + SRCS + vncviewer_main.c + rfb_protocol.c + lcd_render.c + STACKSIZE + ${CONFIG_SYSTEM_VNCVIEWER_STACKSIZE} + PRIORITY + ${CONFIG_SYSTEM_VNCVIEWER_PRIORITY}) +endif() diff --git a/system/vncviewer/Kconfig b/system/vncviewer/Kconfig new file mode 100644 index 00000000000..49af6c94a1d --- /dev/null +++ b/system/vncviewer/Kconfig @@ -0,0 +1,32 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config SYSTEM_VNCVIEWER + tristate "VNC Viewer" + default n + depends on NET_TCP && LCD + ---help--- + Enable the VNC Viewer example application. + Connects to a VNC server and displays the remote + framebuffer on a local LCD. + +if SYSTEM_VNCVIEWER + +config SYSTEM_VNCVIEWER_PROGNAME + string "Program name" + default "vncviewer" + ---help--- + This is the name of the program that will be used when the + NSH ELF program is installed. + +config SYSTEM_VNCVIEWER_PRIORITY + int "VNC Viewer task priority" + default 100 + +config SYSTEM_VNCVIEWER_STACKSIZE + int "VNC Viewer stack size" + default 4096 + +endif diff --git a/system/vncviewer/Make.defs b/system/vncviewer/Make.defs new file mode 100644 index 00000000000..eca552f6414 --- /dev/null +++ b/system/vncviewer/Make.defs @@ -0,0 +1,25 @@ +############################################################################ +# apps/system/vncviewer/Make.defs +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you 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. +# +############################################################################ + +ifneq ($(CONFIG_SYSTEM_VNCVIEWER),) +CONFIGURED_APPS += $(APPDIR)/system/vncviewer +endif diff --git a/system/vncviewer/Makefile b/system/vncviewer/Makefile new file mode 100644 index 00000000000..90fa66590b6 --- /dev/null +++ b/system/vncviewer/Makefile @@ -0,0 +1,37 @@ +############################################################################ +# apps/system/vncviewer/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(APPDIR)/Make.defs + +# VNC Viewer built-in application info + +PROGNAME = $(CONFIG_SYSTEM_VNCVIEWER_PROGNAME) +PRIORITY = $(CONFIG_SYSTEM_VNCVIEWER_PRIORITY) +STACKSIZE = $(CONFIG_SYSTEM_VNCVIEWER_STACKSIZE) +MODULE = $(CONFIG_SYSTEM_VNCVIEWER) + +# VNC Viewer sources + +MAINSRC = vncviewer_main.c +CSRCS = rfb_protocol.c lcd_render.c + +include $(APPDIR)/Application.mk diff --git a/system/vncviewer/lcd_render.c b/system/vncviewer/lcd_render.c new file mode 100644 index 00000000000..9e940474eb4 --- /dev/null +++ b/system/vncviewer/lcd_render.c @@ -0,0 +1,177 @@ +/**************************************************************************** + * apps/system/vncviewer/lcd_render.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "lcd_render.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lcd_init + ****************************************************************************/ + +int lcd_init(struct lcd_ctx_s *ctx, int devno) +{ + char devpath[16]; + int ret; + + snprintf(devpath, sizeof(devpath), "/dev/lcd%d", devno); + + ctx->fd = open(devpath, O_RDWR); + if (ctx->fd < 0) + { + printf("vncviewer: failed to open %s: %d\n", devpath, errno); + return -errno; + } + + /* Query LCD resolution */ + + struct fb_videoinfo_s vinfo; + ret = ioctl(ctx->fd, LCDDEVIO_GETVIDEOINFO, + (unsigned long)(uintptr_t)&vinfo); + if (ret < 0) + { + printf("vncviewer: GETVIDEOINFO failed: %d\n", errno); + close(ctx->fd); + ctx->fd = -1; + return -errno; + } + else + { + ctx->xres = vinfo.xres; + ctx->yres = vinfo.yres; + ctx->fmt = vinfo.fmt; + } + + printf("vncviewer: LCD %s opened (%ux%u)\n", + devpath, ctx->xres, ctx->yres); + + /* Set power on */ + + ret = ioctl(ctx->fd, LCDDEVIO_SETPOWER, 1); + if (ret < 0) + { + printf("vncviewer: WARNING: SETPOWER failed: %d\n", errno); + } + + return OK; +} + +/**************************************************************************** + * Name: lcd_put_row + ****************************************************************************/ + +int lcd_put_row(struct lcd_ctx_s *ctx, uint16_t x, uint16_t y, + uint16_t w, const uint16_t *pixels) +{ + struct lcddev_area_s area; + int ret; + + /* Clip to LCD bounds */ + + if (x >= ctx->xres || y >= ctx->yres) + { + return OK; + } + + if (x + w > ctx->xres) + { + w = ctx->xres - x; + } + + area.row_start = y; + area.row_end = y; + area.col_start = x; + area.col_end = x + w - 1; + area.data = (uint8_t *)pixels; + + ret = ioctl(ctx->fd, LCDDEVIO_PUTAREA, (unsigned long)(uintptr_t)&area); + if (ret < 0) + { + return -errno; + } + + return OK; +} + +/**************************************************************************** + * Name: lcd_fill + ****************************************************************************/ + +int lcd_fill(struct lcd_ctx_s *ctx, uint16_t color) +{ + uint16_t y; + uint16_t i; + uint16_t w = ctx->xres; + FAR uint16_t *rowbuf; + + rowbuf = (FAR uint16_t *)malloc(w * sizeof(uint16_t)); + if (rowbuf == NULL) + { + return -ENOMEM; + } + + for (i = 0; i < w; i++) + { + rowbuf[i] = color; + } + + for (y = 0; y < ctx->yres; y++) + { + lcd_put_row(ctx, 0, y, w, rowbuf); + } + + free(rowbuf); + + return OK; +} + +/**************************************************************************** + * Name: lcd_uninit + ****************************************************************************/ + +void lcd_uninit(struct lcd_ctx_s *ctx) +{ + if (ctx->fd >= 0) + { + close(ctx->fd); + ctx->fd = -1; + } +} diff --git a/system/vncviewer/lcd_render.h b/system/vncviewer/lcd_render.h new file mode 100644 index 00000000000..5940b8f877e --- /dev/null +++ b/system/vncviewer/lcd_render.h @@ -0,0 +1,106 @@ +/**************************************************************************** + * apps/system/vncviewer/lcd_render.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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 __APPS_SYSTEM_VNCVIEWER_LCD_RENDER_H +#define __APPS_SYSTEM_VNCVIEWER_LCD_RENDER_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct lcd_ctx_s +{ + int fd; /* File descriptor for /dev/lcdN */ + uint16_t xres; /* LCD horizontal resolution */ + uint16_t yres; /* LCD vertical resolution */ + uint8_t fmt; /* Pixel format (FB_FMT_*) */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: lcd_init + * + * Description: + * Open and initialize the LCD device. + * + * Input Parameters: + * ctx - LCD context to initialize + * devno - LCD device number (0 for /dev/lcd0) + * + * Returned Value: + * OK on success, negative errno on failure. + * + ****************************************************************************/ + +int lcd_init(struct lcd_ctx_s *ctx, int devno); + +/**************************************************************************** + * Name: lcd_put_row + * + * Description: + * Write a single row of pixels to the LCD. + * + * Input Parameters: + * ctx - LCD context + * x - Starting X coordinate + * y - Y coordinate (row) + * w - Width in pixels + * pixels - Pixel data matching LCD native format + * + * Returned Value: + * OK on success, negative errno on failure. + * + ****************************************************************************/ + +int lcd_put_row(struct lcd_ctx_s *ctx, uint16_t x, uint16_t y, + uint16_t w, const uint16_t *pixels); + +/**************************************************************************** + * Name: lcd_fill + * + * Description: + * Fill the entire LCD with a solid color. + * + ****************************************************************************/ + +int lcd_fill(struct lcd_ctx_s *ctx, uint16_t color); + +/**************************************************************************** + * Name: lcd_uninit + * + * Description: + * Close the LCD device. + * + ****************************************************************************/ + +void lcd_uninit(struct lcd_ctx_s *ctx); + +#endif /* __APPS_SYSTEM_VNCVIEWER_LCD_RENDER_H */ diff --git a/system/vncviewer/rfb_protocol.c b/system/vncviewer/rfb_protocol.c new file mode 100644 index 00000000000..ef324af835b --- /dev/null +++ b/system/vncviewer/rfb_protocol.c @@ -0,0 +1,1051 @@ +/**************************************************************************** + * apps/system/vncviewer/rfb_protocol.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rfb_protocol.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Data - DES tables for VNC authentication + ****************************************************************************/ + +/* Initial permutation */ + +static const uint8_t g_ip[64] = +{ + 58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, + 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, + 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, + 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7 +}; + +/* Final permutation (IP inverse) */ + +static const uint8_t g_fp[64] = +{ + 40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, + 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, + 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, + 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25 +}; + +/* Expansion permutation */ + +static const uint8_t g_expand[48] = +{ + 32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, + 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, + 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, + 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1 +}; + +/* Permuted choice 1 */ + +static const uint8_t g_pc1[56] = +{ + 57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, + 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, + 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, + 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4 +}; + +/* Permuted choice 2 */ + +static const uint8_t g_pc2[48] = +{ + 14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32 +}; + +/* P-box permutation */ + +static const uint8_t g_pbox[32] = +{ + 16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, + 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, + 19, 13, 30, 6, 22, 11, 4, 25 +}; + +/* S-boxes */ + +static const uint8_t g_sbox[8][64] = +{ + { + 14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7, + 0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8, + 4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0, + 15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13 + }, + { + 15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10, + 3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5, + 0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15, + 13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9 + }, + { + 10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8, + 13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1, + 13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7, + 1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12 + }, + { + 7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15, + 13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9, + 10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4, + 3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14 + }, + { + 2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9, + 14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6, + 4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14, + 11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3 + }, + { + 12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11, + 10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8, + 9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6, + 4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13 + }, + { + 4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1, + 13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6, + 1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2, + 6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12 + }, + { + 13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7, + 1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2, + 7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8, + 2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11 + } +}; + +/* Key schedule left shifts */ + +static const uint8_t g_shifts[] = +{ + 1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1 +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: des_getbit / des_setbit + * + * Description: + * Get/set a single bit in a byte array (1-indexed, MSB first). + * + ****************************************************************************/ + +static int des_getbit(const uint8_t *data, int bitnum) +{ + return (data[(bitnum - 1) / 8] >> (7 - ((bitnum - 1) % 8))) & 1; +} + +static void des_setbit(uint8_t *data, int bitnum, int val) +{ + int byte_idx = (bitnum - 1) / 8; + int bit_idx = 7 - ((bitnum - 1) % 8); + + if (val) + { + data[byte_idx] |= (uint8_t)(1 << bit_idx); + } + else + { + data[byte_idx] &= (uint8_t)~(1 << bit_idx); + } +} + +/**************************************************************************** + * Name: des_permute + * + * Description: + * Apply a permutation table to a bit array. + * + ****************************************************************************/ + +static void des_permute(const uint8_t *in, uint8_t *out, + const uint8_t *table, int n) +{ + int i; + + memset(out, 0, (n + 7) / 8); + for (i = 0; i < n; i++) + { + des_setbit(out, i + 1, des_getbit(in, table[i])); + } +} + +/**************************************************************************** + * Name: des_encrypt_block + * + * Description: + * DES ECB encrypt a single 8-byte block in place. + * Straightforward implementation using standard tables. + * + ****************************************************************************/ + +static void des_encrypt_block(uint8_t *block, const uint8_t *key) +{ + uint8_t state[8]; + uint8_t cd[7]; + uint8_t l[4]; + uint8_t r[4]; + uint8_t er[6]; + uint8_t subkey[6]; + uint8_t sout[4]; + uint8_t f[4]; + uint8_t newl[4]; + uint8_t combined[8]; + uint8_t result[8]; + uint32_t c; + uint32_t d; + int round; + int i; + int s; + + /* Initial permutation */ + + des_permute(block, state, g_ip, 64); + memcpy(l, state, 4); + memcpy(r, state + 4, 4); + + /* Key schedule: apply PC1 to key */ + + des_permute(key, cd, g_pc1, 56); + + /* Extract C (bits 1-28) and D (bits 29-56) as 28-bit values */ + + for (round = 0; round < 16; round++) + { + int shift = g_shifts[round]; + + c = ((uint32_t)cd[0] << 20) | ((uint32_t)cd[1] << 12) | + ((uint32_t)cd[2] << 4) | ((uint32_t)cd[3] >> 4); + d = (((uint32_t)cd[3] & 0x0f) << 24) | ((uint32_t)cd[4] << 16) | + ((uint32_t)cd[5] << 8) | (uint32_t)cd[6]; + + /* Left rotate C and D by shift positions */ + + c = ((c << shift) | (c >> (28 - shift))) & 0x0fffffff; + d = ((d << shift) | (d >> (28 - shift))) & 0x0fffffff; + + /* Pack back into cd[] */ + + cd[0] = (uint8_t)(c >> 20); + cd[1] = (uint8_t)(c >> 12); + cd[2] = (uint8_t)(c >> 4); + cd[3] = (uint8_t)((c << 4) | (d >> 24)); + cd[4] = (uint8_t)(d >> 16); + cd[5] = (uint8_t)(d >> 8); + cd[6] = (uint8_t)d; + + /* Apply PC2 to get 48-bit subkey */ + + des_permute(cd, subkey, g_pc2, 48); + + /* Expand R from 32 to 48 bits */ + + des_permute(r, er, g_expand, 48); + + /* XOR with subkey */ + + for (i = 0; i < 6; i++) + { + er[i] ^= subkey[i]; + } + + /* S-box substitution: 48 bits -> 32 bits */ + + memset(sout, 0, 4); + for (s = 0; s < 8; s++) + { + int off = s * 6; + int b0 = des_getbit(er, off + 1); + int b1 = des_getbit(er, off + 2); + int b2 = des_getbit(er, off + 3); + int b3 = des_getbit(er, off + 4); + int b4 = des_getbit(er, off + 5); + int b5 = des_getbit(er, off + 6); + int row = (b0 << 1) | b5; + int col = (b1 << 3) | (b2 << 2) | (b3 << 1) | b4; + int val = g_sbox[s][row * 16 + col]; + + sout[s / 2] |= (uint8_t)(val << (((s & 1) == 0) ? 4 : 0)); + } + + /* P-box permutation */ + + des_permute(sout, f, g_pbox, 32); + + /* newR = L XOR f(R, K); L = old R */ + + for (i = 0; i < 4; i++) + { + newl[i] = l[i] ^ f[i]; + } + + memcpy(l, r, 4); + memcpy(r, newl, 4); + } + + /* Final: combine R16 | L16 (note the swap) and apply FP */ + + memcpy(combined, r, 4); + memcpy(combined + 4, l, 4); + des_permute(combined, result, g_fp, 64); + memcpy(block, result, 8); +} + +/**************************************************************************** + * Name: vnc_encrypt_challenge + * + * Description: + * Encrypt 16-byte VNC challenge using password as DES key. + * VNC reverses bits in each byte of the password. + * + ****************************************************************************/ + +static void vnc_encrypt_challenge(uint8_t *challenge, + const char *password) +{ + uint8_t key[8]; + int i; + + memset(key, 0, 8); + for (i = 0; i < 8 && password[i] != '\0'; i++) + { + uint8_t c = (uint8_t)password[i]; + + /* Reverse bits in byte (VNC quirk) */ + + key[i] = (uint8_t)(((c & 0x01) << 7) | ((c & 0x02) << 5) | + ((c & 0x04) << 3) | ((c & 0x08) << 1) | + ((c & 0x10) >> 1) | ((c & 0x20) >> 3) | + ((c & 0x40) >> 5) | ((c & 0x80) >> 7)); + } + + /* Encrypt two 8-byte blocks */ + + des_encrypt_block(challenge, key); + des_encrypt_block(challenge + 8, key); +} + +/**************************************************************************** + * Name: rfb_recv_exact + * + * Description: + * Receive exactly 'len' bytes from socket. + * + ****************************************************************************/ + +static int rfb_recv_exact(int sockfd, void *buf, size_t len) +{ + uint8_t *p = (uint8_t *)buf; + ssize_t n; + + while (len > 0) + { + n = recv(sockfd, p, len, 0); + if (n <= 0) + { + if (n == 0) + { + return -ECONNRESET; + } + + if (errno == EINTR) + { + continue; + } + + return -errno; + } + + p += n; + len -= n; + } + + return OK; +} + +/**************************************************************************** + * Name: rfb_send_exact + * + * Description: + * Send exactly 'len' bytes to socket. + * + ****************************************************************************/ + +static int rfb_send_exact(int sockfd, const void *buf, size_t len) +{ + const uint8_t *p = (const uint8_t *)buf; + ssize_t n; + + while (len > 0) + { + n = send(sockfd, p, len, 0); + if (n <= 0) + { + if (errno == EINTR) + { + continue; + } + + return -errno; + } + + p += n; + len -= n; + } + + return OK; +} + +/**************************************************************************** + * Name: rfb_negotiate_version + * + * Description: + * Exchange RFB protocol version strings. + * + ****************************************************************************/ + +static int rfb_negotiate_version(int sockfd) +{ + char buf[RFB_VERSION_LEN + 1]; + int ret; + + /* Receive server version */ + + ret = rfb_recv_exact(sockfd, buf, RFB_VERSION_LEN); + if (ret < 0) + { + printf("vncviewer: failed to receive server version\n"); + return ret; + } + + buf[RFB_VERSION_LEN] = '\0'; + printf("vncviewer: server version: %s", buf); + + /* Send our version (3.8) */ + + ret = rfb_send_exact(sockfd, RFB_VERSION_STRING, RFB_VERSION_LEN); + if (ret < 0) + { + printf("vncviewer: failed to send client version\n"); + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: rfb_negotiate_security + * + * Description: + * Negotiate security type. Supports None and VNC Authentication. + * + ****************************************************************************/ + +static int rfb_negotiate_security(int sockfd, const char *password) +{ + uint8_t num_types; + uint8_t types[16]; + uint8_t chosen; + uint32_t result; + int ret; + int i; + + /* Receive number of security types */ + + ret = rfb_recv_exact(sockfd, &num_types, 1); + if (ret < 0) + { + return ret; + } + + if (num_types == 0) + { + /* Connection failed - read reason string */ + + uint32_t reason_len; + rfb_recv_exact(sockfd, &reason_len, 4); + reason_len = ntohl(reason_len); + printf("vncviewer: connection refused by server\n"); + return -ECONNREFUSED; + } + + /* Receive security type list */ + + ret = rfb_recv_exact(sockfd, types, num_types); + if (ret < 0) + { + return ret; + } + + /* Choose security type: prefer None, fallback to VNC Auth */ + + chosen = RFB_SEC_INVALID; + for (i = 0; i < num_types; i++) + { + if (types[i] == RFB_SEC_NONE) + { + chosen = RFB_SEC_NONE; + break; + } + + if (types[i] == RFB_SEC_VNC_AUTH) + { + chosen = RFB_SEC_VNC_AUTH; + } + } + + if (chosen == RFB_SEC_INVALID) + { + printf("vncviewer: no supported security type\n"); + return -ENOTSUP; + } + + /* Send chosen type */ + + ret = rfb_send_exact(sockfd, &chosen, 1); + if (ret < 0) + { + return ret; + } + + printf("vncviewer: security type: %s\n", + chosen == RFB_SEC_NONE ? "None" : "VNC Auth"); + + if (chosen == RFB_SEC_VNC_AUTH) + { + /* VNC Authentication: receive 16-byte challenge */ + + uint8_t challenge[16]; + + ret = rfb_recv_exact(sockfd, challenge, 16); + if (ret < 0) + { + return ret; + } + + /* Encrypt challenge with password using DES */ + + if (password == NULL || password[0] == '\0') + { + printf("vncviewer: WARNING: VNC Auth required but no password\n"); + } + else + { + vnc_encrypt_challenge(challenge, password); + } + + ret = rfb_send_exact(sockfd, challenge, 16); + if (ret < 0) + { + return ret; + } + } + + /* Receive SecurityResult (for both None and VNC Auth in RFB 3.8) */ + + ret = rfb_recv_exact(sockfd, &result, 4); + if (ret < 0) + { + return ret; + } + + result = ntohl(result); + if (result != 0) + { + printf("vncviewer: authentication failed (result=%u)\n", + (unsigned)result); + return -EACCES; + } + + printf("vncviewer: authentication OK\n"); + return OK; +} + +/**************************************************************************** + * Name: rfb_client_init + * + * Description: + * Send ClientInit and receive ServerInit. + * + ****************************************************************************/ + +static int rfb_client_init(int sockfd, struct rfb_conn_s *conn) +{ + uint8_t shared = 1; /* shared session */ + uint8_t buf[24]; + uint32_t name_len; + int ret; + + /* Send ClientInit */ + + ret = rfb_send_exact(sockfd, &shared, 1); + if (ret < 0) + { + return ret; + } + + /* Receive ServerInit: 2+2+16+4 = 24 bytes header + name */ + + ret = rfb_recv_exact(sockfd, buf, 24); + if (ret < 0) + { + return ret; + } + + conn->fb_width = ntohs(*(uint16_t *)&buf[0]); + conn->fb_height = ntohs(*(uint16_t *)&buf[2]); + + /* bytes 4..19 = server pixel format (we'll override it) */ + + name_len = ntohl(*(uint32_t *)&buf[20]); + if (name_len > sizeof(conn->name) - 1) + { + name_len = sizeof(conn->name) - 1; + } + + if (name_len > 0) + { + ret = rfb_recv_exact(sockfd, conn->name, name_len); + if (ret < 0) + { + return ret; + } + } + + conn->name[name_len] = '\0'; + + printf("vncviewer: server desktop: \"%s\" %ux%u\n", + conn->name, conn->fb_width, conn->fb_height); + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rfb_connect + ****************************************************************************/ + +int rfb_connect(const char *host, uint16_t port) +{ + struct sockaddr_in addr; + int sockfd; + int ret; + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if (sockfd < 0) + { + printf("vncviewer: socket() failed: %d\n", errno); + return -errno; + } + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + ret = inet_pton(AF_INET, host, &addr.sin_addr); + if (ret <= 0) + { + printf("vncviewer: invalid address: %s\n", host); + close(sockfd); + return -EINVAL; + } + + printf("vncviewer: connecting to %s:%u...\n", host, port); + + ret = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) + { + printf("vncviewer: connect() failed: %d\n", errno); + close(sockfd); + return -errno; + } + + printf("vncviewer: connected\n"); + return sockfd; +} + +/**************************************************************************** + * Name: rfb_handshake + ****************************************************************************/ + +int rfb_handshake(struct rfb_conn_s *conn, const char *password) +{ + int ret; + + ret = rfb_negotiate_version(conn->sockfd); + if (ret < 0) + { + return ret; + } + + ret = rfb_negotiate_security(conn->sockfd, password); + if (ret < 0) + { + return ret; + } + + ret = rfb_client_init(conn->sockfd, conn); + if (ret < 0) + { + return ret; + } + + return OK; +} + +/**************************************************************************** + * Name: rfb_set_pixel_format + ****************************************************************************/ + +int rfb_set_pixel_format(struct rfb_conn_s *conn, uint8_t fmt) +{ + uint8_t msg[20]; + uint8_t bits; + uint8_t depth; + uint16_t rmax; + uint16_t gmax; + uint16_t bmax; + uint8_t rshift; + uint8_t gshift; + uint8_t bshift; + + switch (fmt) + { + case 11: /* FB_FMT_RGB16_565 */ + bits = 16; depth = 16; + rmax = 31; gmax = 63; bmax = 31; + rshift = 11; gshift = 5; bshift = 0; + conn->bpp = 2; + break; + + case 10: /* FB_FMT_RGB16_555 */ + bits = 16; depth = 15; + rmax = 31; gmax = 31; bmax = 31; + rshift = 10; gshift = 5; bshift = 0; + conn->bpp = 2; + break; + + case 12: /* FB_FMT_RGB24 */ + bits = 32; depth = 24; + rmax = 255; gmax = 255; bmax = 255; + rshift = 16; gshift = 8; bshift = 0; + conn->bpp = 4; + break; + + case 13: /* FB_FMT_RGB32 */ + bits = 32; depth = 24; + rmax = 255; gmax = 255; bmax = 255; + rshift = 16; gshift = 8; bshift = 0; + conn->bpp = 4; + break; + + default: + printf("vncviewer: unsupported LCD format %d\n", fmt); + return -ENOTSUP; + } + + memset(msg, 0, sizeof(msg)); + + /* Message type + 3 bytes padding */ + + msg[0] = RFB_SET_PIXEL_FORMAT; + + /* Pixel format at offset 4 */ + + msg[4] = bits; /* bits-per-pixel */ + msg[5] = depth; /* depth */ + msg[6] = 0; /* big-endian = false */ + msg[7] = 1; /* true-color = true */ + + msg[8] = (rmax >> 8) & 0xff; + msg[9] = rmax & 0xff; + msg[10] = (gmax >> 8) & 0xff; + msg[11] = gmax & 0xff; + msg[12] = (bmax >> 8) & 0xff; + msg[13] = bmax & 0xff; + + msg[14] = rshift; + msg[15] = gshift; + msg[16] = bshift; + + return rfb_send_exact(conn->sockfd, msg, sizeof(msg)); +} + +/**************************************************************************** + * Name: rfb_set_encodings + ****************************************************************************/ + +int rfb_set_encodings(struct rfb_conn_s *conn) +{ + uint8_t msg[8]; + + msg[0] = RFB_SET_ENCODINGS; + msg[1] = 0; /* padding */ + + /* number-of-encodings = 1 */ + + msg[2] = 0; + msg[3] = 1; + + /* encoding: Raw = 0 */ + + msg[4] = 0; + msg[5] = 0; + msg[6] = 0; + msg[7] = 0; + + return rfb_send_exact(conn->sockfd, msg, sizeof(msg)); +} + +/**************************************************************************** + * Name: rfb_request_update + ****************************************************************************/ + +int rfb_request_update(struct rfb_conn_s *conn, bool incremental, + uint16_t x, uint16_t y, uint16_t w, uint16_t h) +{ + uint8_t msg[10]; + + msg[0] = RFB_FB_UPDATE_REQUEST; + msg[1] = incremental ? 1 : 0; + *(uint16_t *)&msg[2] = htons(x); + *(uint16_t *)&msg[4] = htons(y); + *(uint16_t *)&msg[6] = htons(w); + *(uint16_t *)&msg[8] = htons(h); + + return rfb_send_exact(conn->sockfd, msg, sizeof(msg)); +} + +/**************************************************************************** + * Name: rfb_recv_update + ****************************************************************************/ + +int rfb_recv_update(struct rfb_conn_s *conn, rfb_rect_cb_t rect_cb, + void *arg) +{ + uint8_t msg_type; + int ret; + + /* Read message type */ + + ret = rfb_recv_exact(conn->sockfd, &msg_type, 1); + if (ret < 0) + { + return ret; + } + + switch (msg_type) + { + case RFB_FB_UPDATE: + { + uint8_t hdr[3]; + uint16_t num_rects; + uint16_t i; + + /* Read padding(1) + number-of-rectangles(2) */ + + ret = rfb_recv_exact(conn->sockfd, hdr, 3); + if (ret < 0) + { + return ret; + } + + num_rects = ntohs(*(uint16_t *)&hdr[1]); + + for (i = 0; i < num_rects; i++) + { + struct rfb_rect_hdr_s rect; + uint8_t rect_buf[12]; + uint32_t row_bytes; + uint32_t row; + + /* Read rectangle header: x(2)+y(2)+w(2)+h(2)+encoding(4) */ + + ret = rfb_recv_exact(conn->sockfd, rect_buf, 12); + if (ret < 0) + { + return ret; + } + + rect.x = ntohs(*(uint16_t *)&rect_buf[0]); + rect.y = ntohs(*(uint16_t *)&rect_buf[2]); + rect.w = ntohs(*(uint16_t *)&rect_buf[4]); + rect.h = ntohs(*(uint16_t *)&rect_buf[6]); + rect.encoding = (int32_t)ntohl(*(uint32_t *)&rect_buf[8]); + + if (rect.encoding != RFB_ENCODING_RAW) + { + printf("vncviewer: unsupported encoding %d\n", + (int)rect.encoding); + return -ENOTSUP; + } + + /* Read pixel data row by row to limit memory usage */ + + row_bytes = rect.w * conn->bpp; + + FAR uint8_t *rowbuf = malloc(row_bytes); + if (rowbuf == NULL) + { + return -ENOMEM; + } + + for (row = 0; row < rect.h; row++) + { + struct rfb_rect_hdr_s row_rect; + + ret = rfb_recv_exact(conn->sockfd, rowbuf, row_bytes); + if (ret < 0) + { + free(rowbuf); + return ret; + } + + /* Callback with single-row rectangle */ + + row_rect.x = rect.x; + row_rect.y = rect.y + row; + row_rect.w = rect.w; + row_rect.h = 1; + row_rect.encoding = RFB_ENCODING_RAW; + + if (rect_cb) + { + rect_cb(&row_rect, rowbuf, arg); + } + } + + free(rowbuf); + } + } + break; + + case RFB_SET_COLORMAP: + { + /* Skip: padding(1) + first-color(2) + num-colors(2) */ + + uint8_t cm_hdr[5]; + uint16_t num_colors; + + ret = rfb_recv_exact(conn->sockfd, cm_hdr, 5); + if (ret < 0) + { + return ret; + } + + num_colors = ntohs(*(uint16_t *)&cm_hdr[3]); + + /* Skip color entries: num_colors * 6 bytes (R+G+B each 2B) */ + + uint32_t skip = num_colors * 6; + uint8_t tmp[256]; + + while (skip > 0) + { + uint32_t chunk = skip > sizeof(tmp) ? sizeof(tmp) : skip; + ret = rfb_recv_exact(conn->sockfd, tmp, chunk); + if (ret < 0) + { + return ret; + } + + skip -= chunk; + } + } + break; + + case RFB_BELL: + + /* No payload */ + + break; + + case RFB_SERVER_CUT_TEXT: + { + uint8_t ct_hdr[7]; + uint32_t text_len; + + ret = rfb_recv_exact(conn->sockfd, ct_hdr, 7); + if (ret < 0) + { + return ret; + } + + text_len = ntohl(*(uint32_t *)&ct_hdr[3]); + + /* Skip text content */ + + uint8_t tmp[256]; + + while (text_len > 0) + { + uint32_t chunk = text_len > sizeof(tmp) + ? sizeof(tmp) : text_len; + ret = rfb_recv_exact(conn->sockfd, tmp, chunk); + if (ret < 0) + { + return ret; + } + + text_len -= chunk; + } + } + break; + + default: + printf("vncviewer: unknown message type %u\n", msg_type); + return -EPROTO; + } + + return OK; +} diff --git a/system/vncviewer/rfb_protocol.h b/system/vncviewer/rfb_protocol.h new file mode 100644 index 00000000000..b5b4c1e782c --- /dev/null +++ b/system/vncviewer/rfb_protocol.h @@ -0,0 +1,220 @@ +/**************************************************************************** + * apps/system/vncviewer/rfb_protocol.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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 __APPS_SYSTEM_VNCVIEWER_RFB_PROTOCOL_H +#define __APPS_SYSTEM_VNCVIEWER_RFB_PROTOCOL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* RFB Protocol Version */ + +#define RFB_VERSION_STRING "RFB 003.008\n" +#define RFB_VERSION_LEN 12 + +/* Security Types */ + +#define RFB_SEC_INVALID 0 +#define RFB_SEC_NONE 1 +#define RFB_SEC_VNC_AUTH 2 + +/* Client-to-Server Message Types */ + +#define RFB_SET_PIXEL_FORMAT 0 +#define RFB_SET_ENCODINGS 2 +#define RFB_FB_UPDATE_REQUEST 3 +#define RFB_KEY_EVENT 4 +#define RFB_POINTER_EVENT 5 +#define RFB_CLIENT_CUT_TEXT 6 + +/* Server-to-Client Message Types */ + +#define RFB_FB_UPDATE 0 +#define RFB_SET_COLORMAP 1 +#define RFB_BELL 2 +#define RFB_SERVER_CUT_TEXT 3 + +/* Encoding Types */ + +#define RFB_ENCODING_RAW 0 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Pixel format (negotiated with server based on LCD) */ + +struct rfb_pixel_format_s +{ + uint8_t bits_per_pixel; /* 16 */ + uint8_t depth; /* 16 */ + uint8_t big_endian; /* 0 = little-endian */ + uint8_t true_color; /* 1 */ + uint16_t red_max; /* 31 (5 bits) */ + uint16_t green_max; /* 63 (6 bits) */ + uint16_t blue_max; /* 31 (5 bits) */ + uint8_t red_shift; /* 11 */ + uint8_t green_shift; /* 5 */ + uint8_t blue_shift; /* 0 */ + uint8_t padding[3]; +}; + +/* Server init message (parsed) */ + +struct rfb_server_init_s +{ + uint16_t fb_width; + uint16_t fb_height; + struct rfb_pixel_format_s pixel_format; + char name[256]; +}; + +/* Framebuffer update rectangle header */ + +struct rfb_rect_hdr_s +{ + uint16_t x; + uint16_t y; + uint16_t w; + uint16_t h; + int32_t encoding; +}; + +/* RFB connection context */ + +struct rfb_conn_s +{ + int sockfd; + uint16_t fb_width; + uint16_t fb_height; + uint16_t view_width; + uint16_t view_height; + uint8_t bpp; /* Bytes per pixel */ + char name[256]; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: rfb_connect + * + * Description: + * Establish TCP connection to VNC server. + * + * Input Parameters: + * host - Server IP address string + * port - Server port number + * + * Returned Value: + * Socket fd on success, negative errno on failure. + * + ****************************************************************************/ + +int rfb_connect(const char *host, uint16_t port); + +/**************************************************************************** + * Name: rfb_handshake + * + * Description: + * Perform RFB protocol handshake: version, security, init. + * + * Input Parameters: + * conn - RFB connection context (sockfd must be set) + * password - VNC password (NULL or empty for no auth) + * + * Returned Value: + * OK on success, negative errno on failure. + * + ****************************************************************************/ + +int rfb_handshake(struct rfb_conn_s *conn, const char *password); + +/**************************************************************************** + * Name: rfb_set_pixel_format + * + * Description: + * Send SetPixelFormat message based on LCD pixel format. + * + ****************************************************************************/ + +int rfb_set_pixel_format(struct rfb_conn_s *conn, uint8_t fmt); + +/**************************************************************************** + * Name: rfb_set_encodings + * + * Description: + * Send SetEncodings message (Raw only). + * + ****************************************************************************/ + +int rfb_set_encodings(struct rfb_conn_s *conn); + +/**************************************************************************** + * Name: rfb_request_update + * + * Description: + * Send FramebufferUpdateRequest. + * + * Input Parameters: + * conn - RFB connection + * incremental - true for incremental update + * x, y, w, h - Region to request + * + ****************************************************************************/ + +int rfb_request_update(struct rfb_conn_s *conn, bool incremental, + uint16_t x, uint16_t y, uint16_t w, uint16_t h); + +/**************************************************************************** + * Name: rfb_recv_update + * + * Description: + * Receive and process one server message. For FramebufferUpdate, + * calls the provided callback for each rectangle. + * + * Input Parameters: + * conn - RFB connection + * rect_cb - Callback for each received rectangle + * arg - User argument passed to callback + * + * Returned Value: + * OK on success, negative errno on failure. + * + ****************************************************************************/ + +typedef void (*rfb_rect_cb_t)(const struct rfb_rect_hdr_s *rect, + const uint8_t *pixels, void *arg); + +int rfb_recv_update(struct rfb_conn_s *conn, rfb_rect_cb_t rect_cb, + void *arg); + +#endif /* __APPS_SYSTEM_VNCVIEWER_RFB_PROTOCOL_H */ diff --git a/system/vncviewer/vncviewer_main.c b/system/vncviewer/vncviewer_main.c new file mode 100644 index 00000000000..654740ccb5c --- /dev/null +++ b/system/vncviewer/vncviewer_main.c @@ -0,0 +1,306 @@ +/**************************************************************************** + * apps/system/vncviewer/vncviewer_main.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "rfb_protocol.h" +#include "lcd_render.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define VNCVIEWER_DEFAULT_PORT 5900 +#define VNCVIEWER_DEFAULT_LCD 0 + +#define COLOR_BLACK 0x0000 + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static struct lcd_ctx_s g_lcd; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rect_callback + * + * Description: + * Called for each row of pixel data received from VNC server. + * Writes directly to LCD, clipping to screen bounds. + * + ****************************************************************************/ + +static void rect_callback(const struct rfb_rect_hdr_s *rect, + const uint8_t *pixels, void *arg) +{ + /* rect->h is always 1 (row-by-row from rfb_recv_update) */ + + if (rect->y < g_lcd.yres && rect->x < g_lcd.xres) + { + uint16_t w = rect->w; + + if (rect->x + w > g_lcd.xres) + { + w = g_lcd.xres - rect->x; + } + + lcd_put_row(&g_lcd, rect->x, rect->y, w, (const uint16_t *)pixels); + } +} + +/**************************************************************************** + * Name: show_usage + ****************************************************************************/ + +static void show_usage(const char *progname) +{ + printf("Usage: %s [options] [port]\n", progname); + printf("Options:\n"); + printf(" -p VNC password\n"); + printf(" -d LCD device number (default: %d)\n", + VNCVIEWER_DEFAULT_LCD); + printf(" -h Show this help\n"); + printf("Default port: %d\n", VNCVIEWER_DEFAULT_PORT); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: vncviewer_main + * + * Description: + * VNC Viewer entry point. Connects to a VNC server and displays + * the remote framebuffer on the local LCD. + * + ****************************************************************************/ + +int main(int argc, char *argv[]) +{ + const char *host = NULL; + const char *password = ""; + uint16_t port = VNCVIEWER_DEFAULT_PORT; + int lcd_devno = VNCVIEWER_DEFAULT_LCD; + struct rfb_conn_s conn; + int opt; + int ret; + + /* Parse command line arguments */ + + while ((opt = getopt(argc, argv, "p:d:h")) != -1) + { + switch (opt) + { + case 'p': + password = optarg; + break; + + case 'd': + lcd_devno = atoi(optarg); + break; + + case 'h': + show_usage(argv[0]); + return EXIT_SUCCESS; + + default: + show_usage(argv[0]); + return EXIT_FAILURE; + } + } + + if (optind < argc) + { + host = argv[optind++]; + } + + if (optind < argc) + { + port = atoi(argv[optind]); + } + + if (host == NULL) + { + printf("vncviewer: host is required\n"); + show_usage(argv[0]); + return EXIT_FAILURE; + } + + /* Initialize LCD */ + + printf("vncviewer: initializing LCD...\n"); + + ret = lcd_init(&g_lcd, lcd_devno); + if (ret < 0) + { + printf("vncviewer: LCD init failed: %d\n", ret); + return EXIT_FAILURE; + } + + /* Fill screen black */ + + lcd_fill(&g_lcd, COLOR_BLACK); + + /* Connect to VNC server */ + + memset(&conn, 0, sizeof(conn)); + + /* Connection loop with retry */ + + for (; ; ) + { + int rcvbuf; + + conn.sockfd = rfb_connect(host, port); + if (conn.sockfd < 0) + { + printf("vncviewer: connection failed: %d, retrying...\n", + conn.sockfd); + sleep(2); + continue; + } + + /* Increase TCP receive buffer to handle bursts */ + + rcvbuf = 32768; + setsockopt(conn.sockfd, SOL_SOCKET, SO_RCVBUF, + &rcvbuf, sizeof(rcvbuf)); + + /* RFB handshake */ + + ret = rfb_handshake(&conn, password); + if (ret < 0) + { + printf("vncviewer: handshake failed: %d, retrying...\n", ret); + close(conn.sockfd); + sleep(2); + continue; + } + + /* Configure pixel format based on LCD */ + + ret = rfb_set_pixel_format(&conn, g_lcd.fmt); + if (ret < 0) + { + printf("vncviewer: set pixel format failed: %d, retrying...\n", + ret); + close(conn.sockfd); + sleep(2); + continue; + } + + /* Set encodings: Raw only */ + + ret = rfb_set_encodings(&conn); + if (ret < 0) + { + printf("vncviewer: set encodings failed: %d, retrying...\n", ret); + close(conn.sockfd); + sleep(2); + continue; + } + + /* Determine view size: min of server desktop and LCD */ + + conn.view_width = conn.fb_width < g_lcd.xres + ? conn.fb_width : g_lcd.xres; + conn.view_height = conn.fb_height < g_lcd.yres + ? conn.fb_height : g_lcd.yres; + + printf("vncviewer: view area: %ux%u (server: %ux%u, lcd: %ux%u)\n", + conn.view_width, conn.view_height, + conn.fb_width, conn.fb_height, + g_lcd.xres, g_lcd.yres); + + /* Request initial full framebuffer update */ + + ret = rfb_request_update(&conn, false, + 0, 0, conn.view_width, conn.view_height); + if (ret < 0) + { + printf("vncviewer: initial update request failed: %d, " + "retrying...\n", ret); + close(conn.sockfd); + sleep(2); + continue; + } + + /* Main loop: receive updates and render to LCD */ + + printf("vncviewer: entering main loop\n"); + + while (true) + { + /* Receive and process server message */ + + ret = rfb_recv_update(&conn, rect_callback, NULL); + if (ret < 0) + { + printf("vncviewer: recv error: %d\n", ret); + break; + } + + /* Request next incremental update */ + + ret = rfb_request_update(&conn, true, + 0, 0, + conn.view_width, conn.view_height); + if (ret < 0) + { + printf("vncviewer: update request failed: %d\n", ret); + break; + } + } + + /* Connection lost - retry */ + + printf("vncviewer: reconnecting in 2 seconds...\n"); + close(conn.sockfd); + sleep(2); + } + + /* Cleanup */ + + printf("vncviewer: disconnecting\n"); + close(conn.sockfd); + lcd_uninit(&g_lcd); + + return EXIT_SUCCESS; +}