rustvncserver_android/
lib.rs

1// Copyright 2025 Dustin McAfee
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Generic Android JNI bindings for rustvncserver.
16//!
17//! This crate compiles directly to a `.so` file that can be used by any Android app.
18//! No wrapper crate needed - just configure your Java package at build time!
19//!
20//! # Usage
21//!
22//! Build with environment variables to configure your app's Java package:
23//!
24//! ```bash
25//! VNC_PACKAGE="com/mycompany/vnc" cargo ndk -t arm64-v8a build --release
26//! ```
27//!
28//! Then copy `librustvncserver_android.so` to your app's `jniLibs` folder (rename as needed).
29//!
30//! # Environment Variables
31//!
32//! - `VNC_PACKAGE` - Java package path (required), e.g., `"com/example/vnc"`
33//! - `VNC_MAIN_SERVICE` - Main service class name (default: `"MainService"`)
34//! - `VNC_INPUT_SERVICE` - Input service class name (default: `"InputService"`)
35//! - `VNC_LOG_TAG` - Android log tag (default: `"RustVNC"`)
36//!
37//! # Example Gradle Integration
38//!
39//! ```groovy
40//! def vncPackage = "com/mycompany/vnc"
41//!
42//! environment "VNC_PACKAGE", vncPackage
43//! environment "VNC_LOG_TAG", "MyApp-VNC"
44//!
45//! commandLine 'cargo', 'ndk', '-t', 'arm64-v8a', 'build', '--release'
46//! ```
47//!
48//! # Java Side Requirements
49//!
50//! Your Java classes need these native method declarations and callbacks:
51//!
52//! ```java
53//! public class MainService {
54//!     static { System.loadLibrary("droidvnc_ng"); }  // or your lib name
55//!
56//!     // Native methods (registered at runtime via JNI_OnLoad)
57//!     public static native void vncInit();
58//!     public static native boolean vncStartServer(int w, int h, int port, String name, String pw, String httpDir);
59//!     public static native boolean vncStopServer();
60//!     public static native boolean vncIsActive();
61//!     public static native boolean vncUpdateFramebuffer(ByteBuffer buffer);
62//!     public static native boolean vncNewFramebuffer(int width, int height);
63//!     public static native long vncConnectRepeater(String host, int port, String id, String requestId);
64//!     // ... see full list in source
65//!
66//!     // Callbacks (called from Rust)
67//!     public static void onClientConnected(long clientId) { }
68//!     public static void onClientDisconnected(long clientId) { }
69//!     public static void notifyRfbMessageSent(String requestId, boolean success) { }
70//!     public static void notifyHandshakeComplete(String requestId, boolean success) { }
71//! }
72//!
73//! public class InputService {
74//!     public static void onKeyEvent(int down, long keysym, long clientId) { }
75//!     public static void onPointerEvent(int buttonMask, int x, int y, long clientId) { }
76//!     public static void onCutText(String text, long clientId) { }
77//! }
78//! ```
79
80use jni::objects::{GlobalRef, JClass, JObject, JString, JValue};
81use jni::sys::{jboolean, jint, jlong, JNI_FALSE, JNI_TRUE};
82use jni::JNIEnv;
83use log::{error, info, warn};
84use once_cell::sync::OnceCell;
85use rustvncserver::server::{ServerEvent, VncServer};
86use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
87use std::sync::{Arc, Mutex};
88use tokio::runtime::Runtime;
89use tokio::sync::{broadcast, mpsc};
90
91// ============================================================================
92// Global State
93// ============================================================================
94
95/// Global Tokio runtime for the VNC server.
96static VNC_RUNTIME: OnceCell<Runtime> = OnceCell::new();
97/// Global container for the VNC server instance.
98static VNC_SERVER: OnceCell<Arc<Mutex<Option<Arc<VncServer>>>>> = OnceCell::new();
99/// Global broadcast sender for shutdown signals.
100static SHUTDOWN_SIGNAL: OnceCell<broadcast::Sender<()>> = OnceCell::new();
101/// Atomic flag to track if the event handler is running.
102static EVENT_HANDLER_RUNNING: AtomicBool = AtomicBool::new(false);
103
104/// Global reference to the Java VM.
105static JAVA_VM: OnceCell<jni::JavaVM> = OnceCell::new();
106/// Global reference to the InputService Java class.
107static INPUT_SERVICE_CLASS: OnceCell<GlobalRef> = OnceCell::new();
108/// Global reference to the MainService Java class.
109static MAIN_SERVICE_CLASS: OnceCell<GlobalRef> = OnceCell::new();
110
111/// Unique client ID counter (unused but kept for compatibility).
112#[allow(dead_code)]
113static NEXT_CLIENT_ID: AtomicU64 = AtomicU64::new(1);
114
115/// Flag to prevent concurrent framebuffer updates.
116static FRAMEBUFFER_UPDATE_IN_PROGRESS: AtomicBool = AtomicBool::new(false);
117
118// ============================================================================
119// Public API - Registration
120// ============================================================================
121
122/// Registers VNC native methods with the specified Java classes.
123///
124/// This function uses JNI's `RegisterNatives` to dynamically bind Rust functions
125/// to Java native methods, allowing the same library to work with any package name.
126///
127/// # Arguments
128///
129/// * `env` - The JNI environment
130/// * `main_service_class` - Fully qualified class name (e.g., "com/example/app/MainService")
131/// * `input_service_class` - Fully qualified class name (e.g., "com/example/app/InputService")
132///
133/// # Returns
134///
135/// `Ok(())` if registration succeeds, `Err` with description otherwise.
136///
137/// # Example
138///
139/// ```rust,ignore
140/// #[no_mangle]
141/// pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint {
142///     let env = vm.get_env().unwrap();
143///     register_vnc_natives(
144///         &mut env,
145///         "com/mycompany/vnc/MainService",
146///         "com/mycompany/vnc/InputService",
147///     ).expect("Failed to register natives");
148///     jni::sys::JNI_VERSION_1_6
149/// }
150/// ```
151pub fn register_vnc_natives(
152    env: &mut JNIEnv,
153    main_service_class: &str,
154    input_service_class: &str,
155) -> Result<(), String> {
156    // Store Java VM reference
157    if let Ok(vm) = env.get_java_vm() {
158        let _ = JAVA_VM.set(vm);
159    }
160
161    // Find and cache MainService class
162    let main_class = env.find_class(main_service_class).map_err(|e| {
163        format!(
164            "Failed to find MainService class '{}': {}",
165            main_service_class, e
166        )
167    })?;
168
169    let main_global = env
170        .new_global_ref(main_class)
171        .map_err(|e| format!("Failed to create global ref for MainService: {}", e))?;
172
173    if MAIN_SERVICE_CLASS.set(main_global).is_err() {
174        return Err("MAIN_SERVICE_CLASS was already set".to_string());
175    }
176
177    // Find and cache InputService class
178    let input_class = env.find_class(input_service_class).map_err(|e| {
179        format!(
180            "Failed to find InputService class '{}': {}",
181            input_service_class, e
182        )
183    })?;
184
185    let input_global = env
186        .new_global_ref(input_class)
187        .map_err(|e| format!("Failed to create global ref for InputService: {}", e))?;
188
189    if INPUT_SERVICE_CLASS.set(input_global).is_err() {
190        return Err("INPUT_SERVICE_CLASS was already set".to_string());
191    }
192
193    // Register native methods for MainService
194    let main_class_local = env
195        .find_class(main_service_class)
196        .map_err(|e| format!("Failed to find MainService for registration: {}", e))?;
197
198    register_main_service_natives(env, main_class_local)?;
199
200    info!(
201        "VNC natives registered for {} and {}",
202        main_service_class, input_service_class
203    );
204
205    Ok(())
206}
207
208/// Registers native methods for the MainService class.
209///
210/// Required methods will fail registration if missing.
211/// Optional methods are registered individually and silently skipped if missing.
212fn register_main_service_natives(env: &mut JNIEnv, class: JClass) -> Result<(), String> {
213    use jni::NativeMethod;
214
215    // Required methods - registration fails if any are missing
216    let required_methods: Vec<NativeMethod> = vec![
217        NativeMethod {
218            name: "vncInit".into(),
219            sig: "()V".into(),
220            fn_ptr: native_vnc_init as *mut std::ffi::c_void,
221        },
222        NativeMethod {
223            name: "vncStartServer".into(),
224            sig: "(IIILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z".into(),
225            fn_ptr: native_vnc_start_server as *mut std::ffi::c_void,
226        },
227        NativeMethod {
228            name: "vncStopServer".into(),
229            sig: "()Z".into(),
230            fn_ptr: native_vnc_stop_server as *mut std::ffi::c_void,
231        },
232        NativeMethod {
233            name: "vncIsActive".into(),
234            sig: "()Z".into(),
235            fn_ptr: native_vnc_is_active as *mut std::ffi::c_void,
236        },
237        NativeMethod {
238            name: "vncConnectRepeater".into(),
239            sig: "(Ljava/lang/String;ILjava/lang/String;Ljava/lang/String;)J".into(),
240            fn_ptr: native_vnc_connect_repeater as *mut std::ffi::c_void,
241        },
242    ];
243
244    env.register_native_methods(&class, &required_methods)
245        .map_err(|e| format!("Failed to register required MainService natives: {}", e))?;
246
247    // Optional methods - registered individually, skip if missing
248    let optional_methods: Vec<NativeMethod> = vec![
249        NativeMethod {
250            name: "vncUpdateFramebuffer".into(),
251            sig: "(Ljava/nio/ByteBuffer;)Z".into(),
252            fn_ptr: native_vnc_update_framebuffer as *mut std::ffi::c_void,
253        },
254        NativeMethod {
255            name: "vncUpdateFramebufferAndSend".into(),
256            sig: "(Ljava/nio/ByteBuffer;II)Z".into(),
257            fn_ptr: native_vnc_update_framebuffer_and_send as *mut std::ffi::c_void,
258        },
259        NativeMethod {
260            name: "vncUpdateFramebufferCropped".into(),
261            sig: "(Ljava/nio/ByteBuffer;IIIIII)Z".into(),
262            fn_ptr: native_vnc_update_framebuffer_cropped as *mut std::ffi::c_void,
263        },
264        NativeMethod {
265            name: "vncNewFramebuffer".into(),
266            sig: "(II)Z".into(),
267            fn_ptr: native_vnc_new_framebuffer as *mut std::ffi::c_void,
268        },
269        NativeMethod {
270            name: "vncSendCutText".into(),
271            sig: "(Ljava/lang/String;)V".into(),
272            fn_ptr: native_vnc_send_cut_text as *mut std::ffi::c_void,
273        },
274        NativeMethod {
275            name: "vncGetFramebufferWidth".into(),
276            sig: "()I".into(),
277            fn_ptr: native_vnc_get_framebuffer_width as *mut std::ffi::c_void,
278        },
279        NativeMethod {
280            name: "vncGetFramebufferHeight".into(),
281            sig: "()I".into(),
282            fn_ptr: native_vnc_get_framebuffer_height as *mut std::ffi::c_void,
283        },
284        NativeMethod {
285            name: "vncConnectReverse".into(),
286            sig: "(Ljava/lang/String;I)J".into(),
287            fn_ptr: native_vnc_connect_reverse as *mut std::ffi::c_void,
288        },
289        NativeMethod {
290            name: "vncGetRemoteHost".into(),
291            sig: "(J)Ljava/lang/String;".into(),
292            fn_ptr: native_vnc_get_remote_host as *mut std::ffi::c_void,
293        },
294        NativeMethod {
295            name: "vncGetDestinationPort".into(),
296            sig: "(J)I".into(),
297            fn_ptr: native_vnc_get_destination_port as *mut std::ffi::c_void,
298        },
299        NativeMethod {
300            name: "vncGetRepeaterId".into(),
301            sig: "(J)Ljava/lang/String;".into(),
302            fn_ptr: native_vnc_get_repeater_id as *mut std::ffi::c_void,
303        },
304        NativeMethod {
305            name: "vncDisconnect".into(),
306            sig: "(J)Z".into(),
307            fn_ptr: native_vnc_disconnect as *mut std::ffi::c_void,
308        },
309        NativeMethod {
310            name: "vncScheduleCopyRect".into(),
311            sig: "(IIIIII)V".into(),
312            fn_ptr: native_vnc_schedule_copy_rect as *mut std::ffi::c_void,
313        },
314        NativeMethod {
315            name: "vncDoCopyRect".into(),
316            sig: "(IIIIII)Z".into(),
317            fn_ptr: native_vnc_do_copy_rect as *mut std::ffi::c_void,
318        },
319    ];
320
321    for method in optional_methods {
322        let method_name = method.name.to_str().unwrap_or("unknown").to_string();
323        if env.register_native_methods(&class, &[method]).is_err() {
324            // Clear the exception so we can continue
325            let _ = env.exception_clear();
326            log::debug!("Optional method {} not found, skipping", method_name);
327        }
328    }
329
330    Ok(())
331}
332
333// ============================================================================
334// Internal Helpers
335// ============================================================================
336
337/// Gets or initializes the global Tokio runtime.
338fn get_or_init_vnc_runtime() -> &'static Runtime {
339    VNC_RUNTIME.get_or_init(|| {
340        tokio::runtime::Builder::new_multi_thread()
341            .enable_all()
342            .build()
343            .expect("Failed to build VNC Tokio runtime")
344    })
345}
346
347/// Gets or initializes the shutdown signal broadcaster.
348fn get_or_init_shutdown_signal() -> &'static broadcast::Sender<()> {
349    SHUTDOWN_SIGNAL.get_or_init(|| {
350        let (tx, _) = broadcast::channel(16);
351        tx
352    })
353}
354
355// ============================================================================
356// Native Method Implementations
357// ============================================================================
358
359extern "system" fn native_vnc_init(env: JNIEnv, _class: JClass) {
360    // Initialize Android logger
361    android_logger::init_once(
362        android_logger::Config::default()
363            .with_max_level(log::LevelFilter::Info)
364            .with_tag("RustVNC"),
365    );
366
367    info!("Initializing Rust VNC Server");
368
369    // Initialize runtime and shutdown signal
370    get_or_init_vnc_runtime();
371    get_or_init_shutdown_signal();
372
373    // Store Java VM if not already stored
374    if JAVA_VM.get().is_none() {
375        if let Ok(vm) = env.get_java_vm() {
376            let _ = JAVA_VM.set(vm);
377        }
378    }
379
380    // Initialize server container
381    VNC_SERVER.get_or_init(|| Arc::new(Mutex::new(None)));
382
383    info!("Rust VNC Server initialized");
384}
385
386extern "system" fn native_vnc_start_server(
387    mut env: JNIEnv,
388    _class: JClass,
389    width: jint,
390    height: jint,
391    port: jint,
392    desktop_name: JString,
393    password: JString,
394    _http_root_dir: JString,
395) -> jboolean {
396    // Validate dimensions
397    const MAX_DIMENSION: i32 = 8192;
398    const MIN_DIMENSION: i32 = 1;
399
400    let width = match u16::try_from(width) {
401        Ok(w) if w >= MIN_DIMENSION as u16 && w <= MAX_DIMENSION as u16 => w,
402        _ => {
403            error!(
404                "Invalid width: {} (must be {}-{})",
405                width, MIN_DIMENSION, MAX_DIMENSION
406            );
407            return JNI_FALSE;
408        }
409    };
410
411    let height = match u16::try_from(height) {
412        Ok(h) if h >= MIN_DIMENSION as u16 && h <= MAX_DIMENSION as u16 => h,
413        _ => {
414            error!(
415                "Invalid height: {} (must be {}-{})",
416                height, MIN_DIMENSION, MAX_DIMENSION
417            );
418            return JNI_FALSE;
419        }
420    };
421
422    // Port -1 means "inbound connections disabled"
423    let port_opt: Option<u16> = if port == -1 {
424        None
425    } else {
426        match u16::try_from(port) {
427            Ok(p) if p > 0 => Some(p),
428            _ => {
429                error!(
430                    "Invalid port: {} (must be -1 for disabled or 1-65535)",
431                    port
432                );
433                return JNI_FALSE;
434            }
435        }
436    };
437
438    let desktop_name_str: String = match env.get_string(&desktop_name) {
439        Ok(s) => s.into(),
440        Err(e) => {
441            error!("Failed to get desktop name: {}", e);
442            return JNI_FALSE;
443        }
444    };
445
446    let password_str: Option<String> = if !password.is_null() {
447        match env.get_string(&password) {
448            Ok(s) => {
449                let pw: String = s.into();
450                if pw.is_empty() {
451                    None
452                } else {
453                    Some(pw)
454                }
455            }
456            Err(_) => None,
457        }
458    } else {
459        None
460    };
461
462    if let Some(p) = port_opt {
463        info!(
464            "Starting Rust VNC Server: {}x{} on port {}",
465            width, height, p
466        );
467    } else {
468        info!(
469            "Starting Rust VNC Server: {}x{} (inbound connections disabled)",
470            width, height
471        );
472    }
473
474    // Create server and event receiver
475    let (server, event_rx) = VncServer::new(width, height, desktop_name_str, password_str);
476    let server: Arc<VncServer> = Arc::new(server);
477
478    // Store the server globally
479    if let Some(server_container) = VNC_SERVER.get() {
480        match server_container.lock() {
481            Ok(mut guard) => {
482                *guard = Some(server.clone());
483            }
484            Err(e) => {
485                error!("Failed to lock server container: {}", e);
486                return JNI_FALSE;
487            }
488        }
489    } else {
490        error!("VNC server container not initialized");
491        return JNI_FALSE;
492    }
493
494    // Start event handler
495    spawn_event_handler(event_rx);
496
497    // Start listener if port specified
498    if let Some(listen_port) = port_opt {
499        let runtime = get_or_init_vnc_runtime();
500        let server_clone = server.clone();
501        let mut shutdown_rx = get_or_init_shutdown_signal().subscribe();
502
503        runtime.spawn(async move {
504            tokio::select! {
505                result = server_clone.listen(listen_port) => {
506                    if let Err(e) = result {
507                        error!("VNC server listen error: {}", e);
508                    }
509                }
510                _ = shutdown_rx.recv() => {
511                    info!("VNC server received shutdown signal");
512                }
513            }
514        });
515    } else {
516        info!("VNC server running in outbound-only mode (no listener)");
517    }
518
519    info!("Rust VNC Server started successfully");
520    JNI_TRUE
521}
522
523extern "system" fn native_vnc_stop_server(_env: JNIEnv, _class: JClass) -> jboolean {
524    info!("Stopping Rust VNC Server");
525
526    // Step 1: Disconnect all clients
527    if let Some(server_container) = VNC_SERVER.get() {
528        if let Ok(guard) = server_container.lock() {
529            if let Some(server_arc) = guard.as_ref() {
530                let client_ids = server_arc.get_client_ids().unwrap_or_else(|_| {
531                    warn!("Failed to get read lock on client IDs list");
532                    Vec::new()
533                });
534
535                info!("Found {} client(s) to disconnect", client_ids.len());
536
537                let runtime = get_or_init_vnc_runtime();
538                let server_clone = server_arc.clone();
539
540                // Call Java onClientDisconnected for each client
541                if let Some(vm) = JAVA_VM.get() {
542                    if let Ok(mut env) = vm.attach_current_thread() {
543                        if let Some(main_class) = MAIN_SERVICE_CLASS.get() {
544                            for client_id in &client_ids {
545                                info!("Calling Java onClientDisconnected for client {}", client_id);
546                                let args = [JValue::Long(*client_id as jlong)];
547                                if let Err(e) = env.call_static_method(
548                                    main_class,
549                                    "onClientDisconnected",
550                                    "(J)V",
551                                    &args,
552                                ) {
553                                    error!(
554                                        "Failed to call onClientDisconnected for client {}: {}",
555                                        client_id, e
556                                    );
557                                }
558                            }
559                        }
560                    }
561                }
562
563                // Disconnect all clients with timeout
564                info!("Disconnecting all clients with 3s timeout");
565                let disconnect_result = runtime.block_on(async {
566                    tokio::time::timeout(
567                        tokio::time::Duration::from_secs(3),
568                        server_clone.disconnect_all_clients(),
569                    )
570                    .await
571                });
572
573                match disconnect_result {
574                    Ok(_) => info!("All clients disconnected successfully"),
575                    Err(_) => warn!("Client disconnect timed out after 3s"),
576                }
577            }
578        }
579    }
580
581    // Step 2: Send shutdown signal
582    if let Some(shutdown_tx) = SHUTDOWN_SIGNAL.get() {
583        info!("Sending shutdown signal to tasks");
584        let _ = shutdown_tx.send(());
585    }
586
587    // Step 3: Clear server reference
588    if let Some(server_container) = VNC_SERVER.get() {
589        if let Ok(mut guard) = server_container.lock() {
590            *guard = None;
591            info!("Server reference cleared");
592        }
593    }
594
595    // Step 4: Reset event handler flag
596    EVENT_HANDLER_RUNNING.store(false, Ordering::SeqCst);
597
598    info!("Rust VNC Server stopped successfully");
599    JNI_TRUE
600}
601
602extern "system" fn native_vnc_update_framebuffer(
603    env: JNIEnv,
604    _class: JClass,
605    buffer: JObject,
606) -> jboolean {
607    let buffer_ptr = match env.get_direct_buffer_address((&buffer).into()) {
608        Ok(ptr) => ptr,
609        Err(e) => {
610            error!("Failed to get buffer address: {}", e);
611            return JNI_FALSE;
612        }
613    };
614
615    let buffer_capacity = match env.get_direct_buffer_capacity((&buffer).into()) {
616        Ok(cap) => cap,
617        Err(e) => {
618            error!("Failed to get buffer capacity: {}", e);
619            return JNI_FALSE;
620        }
621    };
622
623    // Copy buffer immediately
624    let buffer_copy = {
625        let buffer_slice = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_capacity) };
626        buffer_slice.to_vec()
627    };
628
629    // Skip if update already in progress
630    if FRAMEBUFFER_UPDATE_IN_PROGRESS
631        .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
632        .is_err()
633    {
634        return JNI_TRUE;
635    }
636
637    if let Some(server_container) = VNC_SERVER.get() {
638        if let Ok(guard) = server_container.lock() {
639            if let Some(server) = guard.as_ref() {
640                let rt = get_or_init_vnc_runtime();
641                let server_clone = server.clone();
642
643                rt.spawn(async move {
644                    if let Err(e) = server_clone
645                        .framebuffer()
646                        .update_from_slice(&buffer_copy)
647                        .await
648                    {
649                        error!("Failed to update framebuffer: {}", e);
650                    }
651                    FRAMEBUFFER_UPDATE_IN_PROGRESS.store(false, Ordering::SeqCst);
652                });
653
654                return JNI_TRUE;
655            }
656        }
657    }
658
659    FRAMEBUFFER_UPDATE_IN_PROGRESS.store(false, Ordering::SeqCst);
660    JNI_FALSE
661}
662
663extern "system" fn native_vnc_update_framebuffer_and_send(
664    env: JNIEnv,
665    class: JClass,
666    buffer: JObject,
667    _width: jint,
668    _height: jint,
669) -> jboolean {
670    native_vnc_update_framebuffer(env, class, buffer)
671}
672
673extern "system" fn native_vnc_update_framebuffer_cropped(
674    env: JNIEnv,
675    _class: JClass,
676    buffer: JObject,
677    _width: jint,
678    _height: jint,
679    crop_x: jint,
680    crop_y: jint,
681    crop_width: jint,
682    crop_height: jint,
683) -> jboolean {
684    let buffer_ptr = match env.get_direct_buffer_address((&buffer).into()) {
685        Ok(ptr) => ptr,
686        Err(e) => {
687            error!("Failed to get buffer address: {}", e);
688            return JNI_FALSE;
689        }
690    };
691
692    let buffer_capacity = match env.get_direct_buffer_capacity((&buffer).into()) {
693        Ok(cap) => cap,
694        Err(e) => {
695            error!("Failed to get buffer capacity: {}", e);
696            return JNI_FALSE;
697        }
698    };
699
700    // Validate crop dimensions
701    const MAX_DIMENSION: i32 = 8192;
702
703    if crop_x < 0 || crop_y < 0 || crop_width <= 0 || crop_height <= 0 {
704        error!(
705            "Invalid crop parameters: x={}, y={}, w={}, h={}",
706            crop_x, crop_y, crop_width, crop_height
707        );
708        return JNI_FALSE;
709    }
710
711    if crop_width > MAX_DIMENSION || crop_height > MAX_DIMENSION {
712        error!(
713            "Crop dimensions too large: {}x{} (max {})",
714            crop_width, crop_height, MAX_DIMENSION
715        );
716        return JNI_FALSE;
717    }
718
719    let expected_size = (crop_width as usize)
720        .checked_mul(crop_height as usize)
721        .and_then(|s| s.checked_mul(4))
722        .unwrap_or(0);
723
724    if expected_size == 0 || buffer_capacity != expected_size {
725        error!(
726            "Cropped buffer size mismatch: expected {}, got {}",
727            expected_size, buffer_capacity
728        );
729        return JNI_FALSE;
730    }
731
732    let buffer_copy = {
733        let buffer_slice = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_capacity) };
734        buffer_slice.to_vec()
735    };
736
737    if FRAMEBUFFER_UPDATE_IN_PROGRESS
738        .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
739        .is_err()
740    {
741        return JNI_TRUE;
742    }
743
744    if let Some(server_container) = VNC_SERVER.get() {
745        if let Ok(guard) = server_container.lock() {
746            if let Some(server) = guard.as_ref() {
747                let rt = get_or_init_vnc_runtime();
748                let server_clone = server.clone();
749
750                rt.spawn(async move {
751                    if let Err(e) = server_clone
752                        .framebuffer()
753                        .update_cropped(
754                            &buffer_copy,
755                            crop_x as u16,
756                            crop_y as u16,
757                            crop_width as u16,
758                            crop_height as u16,
759                        )
760                        .await
761                    {
762                        error!("Failed to update cropped framebuffer: {}", e);
763                    }
764                    FRAMEBUFFER_UPDATE_IN_PROGRESS.store(false, Ordering::SeqCst);
765                });
766
767                return JNI_TRUE;
768            }
769        }
770    }
771
772    FRAMEBUFFER_UPDATE_IN_PROGRESS.store(false, Ordering::SeqCst);
773    JNI_FALSE
774}
775
776extern "system" fn native_vnc_new_framebuffer(
777    _env: JNIEnv,
778    _class: JClass,
779    width: jint,
780    height: jint,
781) -> jboolean {
782    const MAX_DIMENSION: i32 = 8192;
783    const MIN_DIMENSION: i32 = 1;
784
785    let width = match u16::try_from(width) {
786        Ok(w) if w >= MIN_DIMENSION as u16 && w <= MAX_DIMENSION as u16 => w,
787        _ => {
788            error!(
789                "Invalid width: {} (must be {}-{})",
790                width, MIN_DIMENSION, MAX_DIMENSION
791            );
792            return JNI_FALSE;
793        }
794    };
795
796    let height = match u16::try_from(height) {
797        Ok(h) if h >= MIN_DIMENSION as u16 && h <= MAX_DIMENSION as u16 => h,
798        _ => {
799            error!(
800                "Invalid height: {} (must be {}-{})",
801                height, MIN_DIMENSION, MAX_DIMENSION
802            );
803            return JNI_FALSE;
804        }
805    };
806
807    info!("Resizing framebuffer to {}x{}", width, height);
808
809    if let Some(server_container) = VNC_SERVER.get() {
810        if let Ok(guard) = server_container.lock() {
811            if let Some(server) = guard.as_ref() {
812                let runtime = get_or_init_vnc_runtime();
813
814                if let Err(e) = runtime.block_on(server.framebuffer().resize(width, height)) {
815                    error!("Failed to resize framebuffer: {}", e);
816                    return JNI_FALSE;
817                }
818
819                info!("Framebuffer resized successfully to {}x{}", width, height);
820                return JNI_TRUE;
821            }
822        }
823    }
824
825    error!("VNC server not initialized");
826    JNI_FALSE
827}
828
829extern "system" fn native_vnc_send_cut_text(mut env: JNIEnv, _class: JClass, text: JString) {
830    let text_str: String = match env.get_string(&text) {
831        Ok(s) => s.into(),
832        Err(e) => {
833            error!("Failed to get cut text: {}", e);
834            return;
835        }
836    };
837
838    if let Some(server_container) = VNC_SERVER.get() {
839        if let Ok(guard) = server_container.lock() {
840            if let Some(server) = guard.as_ref() {
841                let runtime = get_or_init_vnc_runtime();
842                let server_clone = server.clone();
843                let text_clone = text_str;
844
845                runtime.spawn(async move {
846                    if let Err(e) = server_clone.send_cut_text_to_all(text_clone).await {
847                        error!("Failed to send cut text: {}", e);
848                    }
849                });
850            }
851        }
852    }
853}
854
855extern "system" fn native_vnc_is_active(_env: JNIEnv, _class: JClass) -> jboolean {
856    if let Some(server_container) = VNC_SERVER.get() {
857        if let Ok(guard) = server_container.lock() {
858            if guard.is_some() {
859                return JNI_TRUE;
860            }
861        }
862    }
863    JNI_FALSE
864}
865
866extern "system" fn native_vnc_get_framebuffer_width(_env: JNIEnv, _class: JClass) -> jint {
867    if let Some(server_container) = VNC_SERVER.get() {
868        if let Ok(guard) = server_container.lock() {
869            if let Some(server) = guard.as_ref() {
870                return server.framebuffer().width() as jint;
871            }
872        }
873    }
874    -1
875}
876
877extern "system" fn native_vnc_get_framebuffer_height(_env: JNIEnv, _class: JClass) -> jint {
878    if let Some(server_container) = VNC_SERVER.get() {
879        if let Ok(guard) = server_container.lock() {
880            if let Some(server) = guard.as_ref() {
881                return server.framebuffer().height() as jint;
882            }
883        }
884    }
885    -1
886}
887
888extern "system" fn native_vnc_connect_reverse(
889    mut env: JNIEnv,
890    _class: JClass,
891    host: JString,
892    port: jint,
893) -> jlong {
894    let host_str: String = match env.get_string(&host) {
895        Ok(s) => s.into(),
896        Err(e) => {
897            error!("Failed to get reverse connection host: {}", e);
898            return 0;
899        }
900    };
901
902    let port_u16 = port as u16;
903
904    info!("Initiating reverse connection to {}:{}", host_str, port_u16);
905
906    if let Some(server_container) = VNC_SERVER.get() {
907        let server = match server_container.lock() {
908            Ok(guard) => {
909                if let Some(s) = guard.as_ref() {
910                    s.clone()
911                } else {
912                    error!("VNC server not started");
913                    return 0;
914                }
915            }
916            Err(e) => {
917                error!("Failed to lock server container: {}", e);
918                return 0;
919            }
920        };
921
922        let runtime = get_or_init_vnc_runtime();
923
924        return runtime.block_on(async move {
925            match server.connect_reverse(host_str, port_u16).await {
926                Ok(client_id) => {
927                    info!("Reverse connection established, client ID: {}", client_id);
928                    client_id as jlong
929                }
930                Err(e) => {
931                    error!("Failed to establish reverse connection: {}", e);
932                    0
933                }
934            }
935        });
936    }
937
938    error!("VNC server not initialized");
939    0
940}
941
942extern "system" fn native_vnc_connect_repeater(
943    mut env: JNIEnv,
944    _class: JClass,
945    host: JString,
946    port: jint,
947    repeater_id: JString,
948    request_id: JString,
949) -> jlong {
950    let host_str: String = match env.get_string(&host) {
951        Ok(s) => s.into(),
952        Err(e) => {
953            error!("Failed to get repeater host: {}", e);
954            return 0;
955        }
956    };
957
958    let repeater_id_str: String = match env.get_string(&repeater_id) {
959        Ok(s) => s.into(),
960        Err(e) => {
961            error!("Failed to get repeater ID: {}", e);
962            return 0;
963        }
964    };
965
966    let request_id_opt: Option<String> = if !request_id.is_null() {
967        match env.get_string(&request_id) {
968            Ok(s) => {
969                let req_id: String = s.into();
970                if req_id.is_empty() {
971                    None
972                } else {
973                    Some(req_id)
974                }
975            }
976            Err(_) => None,
977        }
978    } else {
979        None
980    };
981
982    let port_u16 = port as u16;
983
984    info!(
985        "Connecting to VNC repeater {}:{} with ID: {}, request_id: {:?}",
986        host_str, port_u16, repeater_id_str, request_id_opt
987    );
988
989    if let Some(server_container) = VNC_SERVER.get() {
990        let server = match server_container.lock() {
991            Ok(guard) => {
992                if let Some(s) = guard.as_ref() {
993                    s.clone()
994                } else {
995                    error!("VNC server not started");
996                    return 0;
997                }
998            }
999            Err(e) => {
1000                error!("Failed to lock server container: {}", e);
1001                return 0;
1002            }
1003        };
1004
1005        let runtime = get_or_init_vnc_runtime();
1006
1007        return runtime.block_on(async move {
1008            match server
1009                .connect_repeater_with_request_id(
1010                    host_str,
1011                    port_u16,
1012                    repeater_id_str,
1013                    request_id_opt,
1014                )
1015                .await
1016            {
1017                Ok(client_id) => {
1018                    info!("Repeater connection established, client ID: {}", client_id);
1019                    client_id as jlong
1020                }
1021                Err(e) => {
1022                    error!("Failed to connect to repeater: {}", e);
1023                    0
1024                }
1025            }
1026        });
1027    }
1028
1029    error!("VNC server not initialized");
1030    0
1031}
1032
1033extern "system" fn native_vnc_get_remote_host<'local>(
1034    env: JNIEnv<'local>,
1035    _class: JClass<'local>,
1036    client_id: jlong,
1037) -> JString<'local> {
1038    if let Some(server_container) = VNC_SERVER.get() {
1039        if let Ok(guard) = server_container.lock() {
1040            if let Some(server) = guard.as_ref() {
1041                if let Ok(clients) = server.clients_try_read() {
1042                    for client_arc in clients.iter() {
1043                        if let Ok(client_guard) = client_arc.try_read() {
1044                            if client_guard.get_client_id() == client_id as usize {
1045                                let host = client_guard.get_remote_host().to_string();
1046                                if let Ok(jstr) = env.new_string(&host) {
1047                                    return jstr;
1048                                }
1049                            }
1050                        }
1051                    }
1052                }
1053            }
1054        }
1055    }
1056
1057    JString::default()
1058}
1059
1060extern "system" fn native_vnc_get_destination_port(
1061    _env: JNIEnv,
1062    _class: JClass,
1063    client_id: jlong,
1064) -> jint {
1065    if let Some(server_container) = VNC_SERVER.get() {
1066        if let Ok(guard) = server_container.lock() {
1067            if let Some(server) = guard.as_ref() {
1068                if let Ok(clients) = server.clients_try_read() {
1069                    for client_arc in clients.iter() {
1070                        if let Ok(client_guard) = client_arc.try_read() {
1071                            if client_guard.get_client_id() == client_id as usize {
1072                                return client_guard.get_destination_port();
1073                            }
1074                        }
1075                    }
1076                }
1077            }
1078        }
1079    }
1080
1081    -1
1082}
1083
1084extern "system" fn native_vnc_get_repeater_id<'local>(
1085    env: JNIEnv<'local>,
1086    _class: JClass<'local>,
1087    client_id: jlong,
1088) -> JString<'local> {
1089    if let Some(server_container) = VNC_SERVER.get() {
1090        if let Ok(guard) = server_container.lock() {
1091            if let Some(server) = guard.as_ref() {
1092                if let Ok(clients) = server.clients_try_read() {
1093                    for client_arc in clients.iter() {
1094                        if let Ok(client_guard) = client_arc.try_read() {
1095                            if client_guard.get_client_id() == client_id as usize {
1096                                if let Some(id) = client_guard.get_repeater_id() {
1097                                    if let Ok(jstr) = env.new_string(id) {
1098                                        return jstr;
1099                                    }
1100                                }
1101                            }
1102                        }
1103                    }
1104                }
1105            }
1106        }
1107    }
1108
1109    JString::default()
1110}
1111
1112extern "system" fn native_vnc_disconnect(
1113    _env: JNIEnv,
1114    _class: JClass,
1115    client_id: jlong,
1116) -> jboolean {
1117    if let Some(server_container) = VNC_SERVER.get() {
1118        if let Ok(guard) = server_container.lock() {
1119            if let Some(server) = guard.as_ref() {
1120                if let Ok(mut clients) = server.clients_try_write() {
1121                    let initial_len = clients.len();
1122
1123                    clients.retain(|client_arc| {
1124                        if let Ok(client_guard) = client_arc.try_read() {
1125                            client_guard.get_client_id() != client_id as usize
1126                        } else {
1127                            true
1128                        }
1129                    });
1130
1131                    let removed = clients.len() < initial_len;
1132                    if removed {
1133                        info!("Client {} disconnected successfully", client_id);
1134                        return JNI_TRUE;
1135                    } else {
1136                        warn!("Client {} not found for disconnect", client_id);
1137                        return JNI_FALSE;
1138                    }
1139                }
1140            }
1141        }
1142    }
1143
1144    JNI_FALSE
1145}
1146
1147extern "system" fn native_vnc_schedule_copy_rect(
1148    _env: JNIEnv,
1149    _class: JClass,
1150    x: jint,
1151    y: jint,
1152    width: jint,
1153    height: jint,
1154    dx: jint,
1155    dy: jint,
1156) {
1157    if x < 0 || y < 0 || width <= 0 || height <= 0 {
1158        error!(
1159            "Invalid copy rect parameters: x={}, y={}, w={}, h={}",
1160            x, y, width, height
1161        );
1162        return;
1163    }
1164
1165    if let Some(server_container) = VNC_SERVER.get() {
1166        if let Ok(guard) = server_container.lock() {
1167            if let Some(server) = guard.as_ref() {
1168                let runtime = get_or_init_vnc_runtime();
1169                let server_clone = server.clone();
1170
1171                runtime.spawn(async move {
1172                    server_clone
1173                        .schedule_copy_rect(
1174                            x as u16,
1175                            y as u16,
1176                            width as u16,
1177                            height as u16,
1178                            dx as i16,
1179                            dy as i16,
1180                        )
1181                        .await;
1182                });
1183            }
1184        }
1185    }
1186}
1187
1188extern "system" fn native_vnc_do_copy_rect(
1189    _env: JNIEnv,
1190    _class: JClass,
1191    x: jint,
1192    y: jint,
1193    width: jint,
1194    height: jint,
1195    dx: jint,
1196    dy: jint,
1197) -> jboolean {
1198    if x < 0 || y < 0 || width <= 0 || height <= 0 {
1199        error!(
1200            "Invalid copy rect parameters: x={}, y={}, w={}, h={}",
1201            x, y, width, height
1202        );
1203        return JNI_FALSE;
1204    }
1205
1206    if let Some(server_container) = VNC_SERVER.get() {
1207        if let Ok(guard) = server_container.lock() {
1208            if let Some(server) = guard.as_ref() {
1209                let runtime = get_or_init_vnc_runtime();
1210
1211                let result = runtime.block_on(server.do_copy_rect(
1212                    x as u16,
1213                    y as u16,
1214                    width as u16,
1215                    height as u16,
1216                    dx as i16,
1217                    dy as i16,
1218                ));
1219
1220                match result {
1221                    Ok(()) => return JNI_TRUE,
1222                    Err(e) => {
1223                        error!("Failed to perform copy rect: {}", e);
1224                        return JNI_FALSE;
1225                    }
1226                }
1227            }
1228        }
1229    }
1230
1231    error!("VNC server not initialized");
1232    JNI_FALSE
1233}
1234
1235// ============================================================================
1236// Event Handler
1237// ============================================================================
1238
1239/// Spawns the event handler task.
1240fn spawn_event_handler(mut event_rx: mpsc::UnboundedReceiver<ServerEvent>) {
1241    if EVENT_HANDLER_RUNNING
1242        .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
1243        .is_err()
1244    {
1245        warn!("Event handler already running");
1246        return;
1247    }
1248
1249    let runtime = get_or_init_vnc_runtime();
1250    let mut shutdown_rx = get_or_init_shutdown_signal().subscribe();
1251
1252    runtime.spawn(async move {
1253        info!("VNC event handler started");
1254
1255        loop {
1256            tokio::select! {
1257                Some(event) = event_rx.recv() => {
1258                    handle_server_event(event);
1259                }
1260                _ = shutdown_rx.recv() => {
1261                    info!("Event handler received shutdown signal");
1262                    break;
1263                }
1264            }
1265        }
1266
1267        EVENT_HANDLER_RUNNING.store(false, Ordering::SeqCst);
1268        info!("VNC event handler stopped");
1269    });
1270}
1271
1272/// Handles a server event by calling the appropriate Java method.
1273fn handle_server_event(event: ServerEvent) {
1274    let vm = match JAVA_VM.get() {
1275        Some(vm) => vm,
1276        None => {
1277            error!("Java VM not available");
1278            return;
1279        }
1280    };
1281
1282    let mut env = match vm.attach_current_thread() {
1283        Ok(env) => env,
1284        Err(e) => {
1285            error!("Failed to attach to Java thread: {}", e);
1286            return;
1287        }
1288    };
1289
1290    match event {
1291        ServerEvent::ClientConnected { client_id } => {
1292            info!("Client {} connected", client_id);
1293            if let Some(main_class) = MAIN_SERVICE_CLASS.get() {
1294                let args = [JValue::Long(client_id as jlong)];
1295                if let Err(e) =
1296                    env.call_static_method(main_class, "onClientConnected", "(J)V", &args)
1297                {
1298                    error!("Failed to call onClientConnected: {}", e);
1299                }
1300            }
1301        }
1302        ServerEvent::ClientDisconnected { client_id } => {
1303            info!("Client {} disconnected", client_id);
1304            if let Some(main_class) = MAIN_SERVICE_CLASS.get() {
1305                let args = [JValue::Long(client_id as jlong)];
1306                if let Err(e) =
1307                    env.call_static_method(main_class, "onClientDisconnected", "(J)V", &args)
1308                {
1309                    error!("Failed to call onClientDisconnected: {}", e);
1310                }
1311            }
1312        }
1313        ServerEvent::KeyPress {
1314            client_id,
1315            down,
1316            key,
1317        } => {
1318            if let Some(input_class) = INPUT_SERVICE_CLASS.get() {
1319                let args = [
1320                    JValue::Int(if down { 1 } else { 0 }),
1321                    JValue::Long(key as jlong),
1322                    JValue::Long(client_id as jlong),
1323                ];
1324                if let Err(e) = env.call_static_method(input_class, "onKeyEvent", "(IJJ)V", &args) {
1325                    error!("Failed to call onKeyEvent: {}", e);
1326                }
1327            }
1328        }
1329        ServerEvent::PointerMove {
1330            client_id,
1331            x,
1332            y,
1333            button_mask,
1334        } => {
1335            if let Some(input_class) = INPUT_SERVICE_CLASS.get() {
1336                let args = [
1337                    JValue::Int(button_mask as jint),
1338                    JValue::Int(x as jint),
1339                    JValue::Int(y as jint),
1340                    JValue::Long(client_id as jlong),
1341                ];
1342                if let Err(e) =
1343                    env.call_static_method(input_class, "onPointerEvent", "(IIIJ)V", &args)
1344                {
1345                    error!("Failed to call onPointerEvent: {}", e);
1346                }
1347            }
1348        }
1349        ServerEvent::CutText { client_id, text } => {
1350            if let Some(input_class) = INPUT_SERVICE_CLASS.get() {
1351                if let Ok(jtext) = env.new_string(&text) {
1352                    let args = [JValue::Object(&jtext), JValue::Long(client_id as jlong)];
1353                    if let Err(e) = env.call_static_method(
1354                        input_class,
1355                        "onCutText",
1356                        "(Ljava/lang/String;J)V",
1357                        &args,
1358                    ) {
1359                        error!("Failed to call onCutText: {}", e);
1360                    }
1361                }
1362            }
1363        }
1364        ServerEvent::RfbMessageSent {
1365            client_id: _,
1366            request_id,
1367            success,
1368        } => {
1369            info!(
1370                "RFB message sent: request_id={:?}, success={}",
1371                request_id, success
1372            );
1373            if let Some(main_class) = MAIN_SERVICE_CLASS.get() {
1374                let request_id_obj: JObject = match &request_id {
1375                    Some(id) => match env.new_string(id) {
1376                        Ok(jstr) => JObject::from(jstr),
1377                        Err(_) => JObject::null(),
1378                    },
1379                    None => JObject::null(),
1380                };
1381
1382                let args = [
1383                    JValue::Object(&request_id_obj),
1384                    JValue::Bool(u8::from(success)),
1385                ];
1386                if let Err(e) = env.call_static_method(
1387                    main_class,
1388                    "notifyRfbMessageSent",
1389                    "(Ljava/lang/String;Z)V",
1390                    &args,
1391                ) {
1392                    error!("Failed to call notifyRfbMessageSent: {}", e);
1393                }
1394            }
1395        }
1396        ServerEvent::HandshakeComplete {
1397            client_id: _,
1398            request_id,
1399            success,
1400        } => {
1401            info!(
1402                "Handshake complete: request_id={:?}, success={}",
1403                request_id, success
1404            );
1405            if let Some(main_class) = MAIN_SERVICE_CLASS.get() {
1406                let request_id_obj: JObject = match &request_id {
1407                    Some(id) => match env.new_string(id) {
1408                        Ok(jstr) => JObject::from(jstr),
1409                        Err(_) => JObject::null(),
1410                    },
1411                    None => JObject::null(),
1412                };
1413
1414                let args = [
1415                    JValue::Object(&request_id_obj),
1416                    JValue::Bool(u8::from(success)),
1417                ];
1418                if let Err(e) = env.call_static_method(
1419                    main_class,
1420                    "notifyHandshakeComplete",
1421                    "(Ljava/lang/String;Z)V",
1422                    &args,
1423                ) {
1424                    error!("Failed to call notifyHandshakeComplete: {}", e);
1425                }
1426            }
1427        }
1428    }
1429}
1430
1431// ============================================================================
1432// Standalone JNI_OnLoad (when VNC_PACKAGE env var is set at build time)
1433// ============================================================================
1434
1435/// JNI_OnLoad for standalone builds.
1436///
1437/// This is automatically included when building with `VNC_PACKAGE` environment variable.
1438/// No wrapper crate needed - just build this crate directly and use the .so file.
1439///
1440/// # Example Build
1441///
1442/// ```bash
1443/// VNC_PACKAGE="com/example/vnc" cargo build --release --target aarch64-linux-android
1444/// ```
1445///
1446/// Or with custom class names:
1447///
1448/// ```bash
1449/// VNC_PACKAGE="com/myapp" VNC_MAIN_SERVICE="VncService" cargo build --release
1450/// ```
1451#[cfg(vnc_standalone)]
1452#[no_mangle]
1453pub extern "system" fn JNI_OnLoad(
1454    vm: jni::JavaVM,
1455    _reserved: *mut std::ffi::c_void,
1456) -> jni::sys::jint {
1457    use jni::sys::{JNI_ERR, JNI_VERSION_1_6};
1458
1459    // Initialize Android logger with configured tag
1460    android_logger::init_once(
1461        android_logger::Config::default()
1462            .with_max_level(log::LevelFilter::Info)
1463            .with_tag(env!("VNC_LOG_TAG")),
1464    );
1465
1466    info!("JNI_OnLoad: Registering VNC native methods");
1467
1468    // Get JNI environment
1469    let mut env = match vm.get_env() {
1470        Ok(env) => env,
1471        Err(e) => {
1472            error!("Failed to get JNI environment: {}", e);
1473            return JNI_ERR;
1474        }
1475    };
1476
1477    // Build full class names from compile-time env vars
1478    let main_class = concat!(env!("VNC_PACKAGE"), "/", env!("VNC_MAIN_SERVICE"));
1479    let input_class = concat!(env!("VNC_PACKAGE"), "/", env!("VNC_INPUT_SERVICE"));
1480
1481    info!(
1482        "Registering for classes: {} and {}",
1483        main_class, input_class
1484    );
1485
1486    // Register native methods
1487    match register_vnc_natives(&mut env, main_class, input_class) {
1488        Ok(()) => {
1489            info!("VNC native methods registered successfully");
1490            JNI_VERSION_1_6
1491        }
1492        Err(e) => {
1493            error!("Failed to register VNC native methods: {}", e);
1494            JNI_ERR
1495        }
1496    }
1497}