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:
libjpeg-turbo: Highly optimized SIMD JPEG decoding, essential for theTightencoding format.wolfssl: A lightweight TLS library providing the cryptographic backend required forVeNCryptandAnonTLSsecurity types.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
VncClientobject. - A
pthreadMutex to lock the framebuffer during rendering. pipe2File Descriptors. These act as an interrupt mechanism so the Android UI thread can safely breaklibvncserverout 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:
onGetPasswordandonGetCredentialcall back to Kotlin to prompt the user or fetch saved passwords. - TLS:
onVerifyServerCertificatepasses the X509 DER byte array to Kotlin to check against a local TrustStore file. - Clipboard:
onGotXCutTextpushes UTF-8 or Latin-1 text up to Android'sClipboardManager.
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.