You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by ce...@apache.org on 2026/04/24 12:58:55 UTC
(nuttx-apps) branch master updated: examples/vncviewer: add VNC viewer for LCD display
This is an automated email from the ASF dual-hosted git repository.
cederom pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx-apps.git
The following commit(s) were added to refs/heads/master by this push:
new 62f3d3704 examples/vncviewer: add VNC viewer for LCD display
62f3d3704 is described below
commit 62f3d37041f3a72dc71cec50187c656d89fed4e5
Author: wangjianyu3 <wa...@xiaomi.com>
AuthorDate: Thu Apr 23 23:16:47 2026 +0800
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 <host> [port]
vncviewer -p <password> <host> [port]
vncviewer -p <password> -d <lcd_devno> <host> [port]
Assisted-by: GitHubCopilot:claude-4.6-opus
Signed-off-by: wangjianyu3 <wa...@xiaomi.com>
---
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(+)
diff --git a/system/vncviewer/CMakeLists.txt b/system/vncviewer/CMakeLists.txt
new file mode 100644
index 000000000..06fc69463
--- /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 000000000..49af6c94a
--- /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 000000000..eca552f64
--- /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 000000000..90fa66590
--- /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 000000000..9e940474e
--- /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 <nuttx/config.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+
+#include <nuttx/video/fb.h>
+#include <nuttx/lcd/lcd_dev.h>
+
+#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 000000000..5940b8f87
--- /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 <stdint.h>
+
+/****************************************************************************
+ * 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 000000000..ef324af83
--- /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 <nuttx/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#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 000000000..b5b4c1e78
--- /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 <stdint.h>
+#include <stdbool.h>
+
+/****************************************************************************
+ * 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 000000000..654740ccb
--- /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 <nuttx/config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+
+#include <nuttx/video/fb.h>
+
+#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] <host> [port]\n", progname);
+ printf("Options:\n");
+ printf(" -p <password> VNC password\n");
+ printf(" -d <devno> 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;
+}