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;
+}