Native Interface (JNI)

The core of the VNC protocol implementation is not written in Kotlin. AVNC leverages the robust open-source C library libvncserver. To bridge the JVM and C++ worlds, AVNC uses the Java Native Interface (JNI).

CMake Configuration

The native build is orchestrated by app/CMakeLists.txt. It statically links three submodules into a shared library named libnative-vnc.so:

  1. libjpeg-turbo: Highly optimized SIMD JPEG decoding, essential for the Tight encoding format.
  2. wolfssl: A lightweight TLS library providing the cryptographic backend required for VeNCrypt and AnonTLS security types.
  3. libvncserver: The client protocol implementation.

JNI Bindings (native-vnc.cpp)

This file contains the C++ implementations of the external functions declared in VncClient.kt.

Thread Synchronization and ClientEx

libvncserver is primarily designed for single-threaded polling. Because AVNC processes Android UI events on one thread and reads network sockets on another, it attaches a custom struct, ClientEx, to the rfbClient->clientData pointer.

ClientEx holds:

  • A reference to the managed Kotlin VncClient object.
  • A pthread Mutex to lock the framebuffer during rendering.
  • pipe2 File Descriptors. These act as an interrupt mechanism so the Android UI thread can safely break libvncserver out of blocking network reads when the user hits the disconnect button.

Callbacks

The native layer hooks into libvncserver's function pointers to push events back to the JVM:

  • Authentication: onGetPassword and onGetCredential call back to Kotlin to prompt the user or fetch saved passwords.
  • TLS: onVerifyServerCertificate passes the X509 DER byte array to Kotlin to check against a local TrustStore file.
  • Clipboard: onGotXCutText pushes UTF-8 or Latin-1 text up to Android's ClipboardManager.

Zero-Copy OpenGL Rendering

Traditionally, a JNI application might copy the VNC framebuffer byte array into a Java ByteBuffer to draw it on a Canvas. This is CPU intensive.

Instead, AVNC uses an OpenGL ES GLSurfaceView. When the Kotlin renderer thread issues a draw call, it invokes nativeUploadFrameTexture. The C++ code directly calls OpenGL functions, bypassing the JVM entirely, and uploads the C-allocated memory straight to the GPU:

// native-vnc.cpp
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ex->fbRealWidth, ex->fbRealHeight, 
             0, GL_RGBA, GL_UNSIGNED_BYTE, client->frameBuffer);

Because the VNC protocol natively uses a BGRA byte layout, and OpenGL ES expects RGBA, the conversion is done at zero CPU cost via the Fragment Shader (Shaders.kt): texture2D(u_TextureUnit, v_TextureCoordinates).bgra.