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