winit_surface_window 0.1.0

A helper library to use existing Android Surfaces (like Presentations) as windows within the Rust ecosystem, compatible with raw-window-handle.
Documentation
# winit_surface_window

A helper library for using existing Android Surfaces (like Presentations) as windows within the Rust ecosystem, compatible with raw-window-handle.

This library allows you to create a window from an existing Android Surface object, which is particularly useful when working with Android Presentation APIs or other scenarios where you need to render to a secondary display or a specific surface.

## Features

- Create a [SurfaceWindow]src/window.rs#L14-L14 from an Android Surface object
- Compatible with [raw-window-handle]src/window.rs#L11-L11 for integration with various graphics libraries
- Optional Skia renderer support for easy 2D graphics rendering
- Thread-safe implementation

## Demo
```java
package com.mui;

import android.app.Presentation;
import android.content.Context;
import android.os.Bundle;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class PresentationBridge extends Presentation {
    private SurfaceView mSurfaceView;

    private long rustRendererHandle = 0;

    static {
        System.loadLibrary("presentation_demo"); 
    }

    private native long nativeInit(); 
    private native void nativeShutdown(long handle);
    private native void nativeSetSurface(long handle, Surface surface);
    private native void nativeClearSurface(long handle);
    private native void nativeSetSize(long handle, int width, int height);

    public PresentationBridge(Context context, Display display) {
        super(context, display);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mSurfaceView = new SurfaceView(getContext());
        setContentView(mSurfaceView);
        rustRendererHandle = nativeInit();

        mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder holder) {
                if (rustRendererHandle != 0) {
                    nativeSetSurface(rustRendererHandle, holder.getSurface());
                }
            }

            @Override
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                if (rustRendererHandle != 0) {
                    nativeSetSize(rustRendererHandle, width, height);
                }
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                if (rustRendererHandle != 0) {
                    nativeClearSurface(rustRendererHandle);
                }
            }

        });
    }


    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        nativeShutdown(rustRendererHandle);
        rustRendererHandle = 0;
    }


    @Override
    protected void onStop() {
        super.onStop();
    }
}

```

```rust
use std::ptr::NonNull;
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
use std::thread::{self, JoinHandle};
use std::time::Instant;

use jni::JNIEnv;
use jni::objects::{JClass, JObject};
use jni::sys::{jint, jlong};
use log::{error, info};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use skia_safe::{Color, Paint, Point, Rect};

use winit_surface_window::{error::Result, renderer::SkiaRenderer, window::SurfaceWindow};

enum RenderThreadMessage {
    SetSurface(SurfaceWindow),
    ClearSurface,
    SetSize(i32, i32),
    Shutdown,
}

struct RendererHandle {
    sender: Sender<RenderThreadMessage>,
    thread_handle: Option<JoinHandle<()>>,
}

impl Drop for RendererHandle {
    fn drop(&mut self) {
        info!("Dropping RendererHandle, sending shutdown message.");

        self.sender
            .send(RenderThreadMessage::Shutdown)
            .unwrap_or_else(|e| error!("Failed to send shutdown msg: {}", e));

        if let Some(handle) = self.thread_handle.take() {
            handle.join().unwrap();
        }
        info!("Render thread joined.");
    }
}

fn render_thread_main(receiver: Receiver<RenderThreadMessage>) {
    let mut renderer: Option<SkiaRenderer> = None;
    let mut window: Option<SurfaceWindow> = None;

    let mut start_time = Instant::now();
    let mut last_draw_time = Instant::now();

    info!("Render thread started.");

    'main_loop: loop {
        match receiver.try_recv() {
            Ok(message) => match message {
                RenderThreadMessage::SetSurface(secondary_window) => {
                    info!("Render thread received SetSurface");
                    match SkiaRenderer::new(&secondary_window) {
                        Ok(r) => {
                            renderer = Some(r);
                            window = Some(secondary_window);
                            info!("Renderer created successfully on render thread.");
                        }
                        Err(e) => {
                            error!("Failed to create renderer: {}", e);
                            window = None;
                            renderer = None;
                        }
                    }
                }
                RenderThreadMessage::ClearSurface => {
                    info!("Render thread received ClearSurface. Destroying renderer.");
                    renderer = None;
                    window = None;
                }
                RenderThreadMessage::SetSize(width, height) => {
                    info!(
                        "Resize event received ({}x{}). Handling not yet implemented.",
                        width, height
                    );
                }
                RenderThreadMessage::Shutdown => {
                    info!("Shutdown message received. Exiting render thread.");
                    break 'main_loop;
                }
            },
            Err(err) => match err {
                TryRecvError::Disconnected => {
                    info!("Channel disconnected. Shutting down render thread.");
                    break 'main_loop;
                }
                TryRecvError::Empty => {}
            },
        }

        if last_draw_time.elapsed().as_millis() >= 16 {
            if let Some(r) = renderer.as_mut() {
                if let Err(e) = r.draw(|canvas| {
                    canvas.clear(skia_safe::Color::MAGENTA);
                    let mut paint = skia_safe::Paint::default();
                    paint.set_color(skia_safe::Color::BLACK);
                    paint.set_anti_alias(true);
                    canvas.draw_str(
                        "Rendered with winit_secondary_display!",
                        skia_safe::Point::new(100.0, 100.0),
                        &Default::default(),
                        &paint,
                    );
                    canvas.draw_rect(
                        Rect::from_xywh(
                            start_time.elapsed().as_millis() as f32 % 500.0,
                            start_time.elapsed().as_millis() as f32 % 500.0,
                            100.0,
                            100.0,
                        ),
                        &paint,
                    );
                }) {
                    error!("Failed to draw frame: {}", e);
                }
            }
            last_draw_time = Instant::now();
        }
    }

    info!("Render thread finished.");
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeInit(
    _env: JNIEnv,
    _class: JClass,
) -> jlong {
    android_logger::init_once(android_logger::Config::default());
    let (sender, receiver) = std::sync::mpsc::channel::<RenderThreadMessage>();
    let thread_handle = thread::spawn(move || {
        render_thread_main(receiver);
    });

    let handle = RendererHandle {
        sender,
        thread_handle: Some(thread_handle),
    };

    Box::into_raw(Box::new(handle)) as jlong
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeShutdown(
    _env: JNIEnv,
    _class: JClass,
    handle_ptr: jlong,
) {
    if handle_ptr != 0 {
        let _handle = unsafe { Box::from_raw(handle_ptr as *mut RendererHandle) };
    }
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeSetSurface(
    mut env: JNIEnv,
    _class: JClass,
    handle_ptr: jlong,
    surface: JObject,
) {
    let handle = unsafe { &*(handle_ptr as *const RendererHandle) };

    match unsafe { SurfaceWindow::from_surface(&mut env, surface) } {
        Ok(native_window) => {
            handle
                .sender
                .send(RenderThreadMessage::SetSurface(native_window))
                .unwrap();
        }
        Err(e) => error!("Failed to create SurfaceWindow: {}", e),
    }
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeSetSize(
    mut env: JNIEnv,
    _class: JClass,
    handle_ptr: jlong,
    width: jint,
    height: jint,
) {
}

#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeClearSurface(
    _env: JNIEnv,
    _class: JClass,
    handle_ptr: jlong,
) {
    let handle = unsafe { &*(handle_ptr as *const RendererHandle) };
    handle
        .sender
        .send(RenderThreadMessage::ClearSurface)
        .unwrap();
}
```

## Usage

Add this to your `Cargo.toml`:

```toml
[dependencies]
winit_surface_window = { path = "path/to/winit_surface_window" }

# For Skia renderer support
[features]
skia-renderer = ["winit_surface_window/skia-renderer"]
```

## API

### SurfaceWindow

The main struct representing an Android Surface as a window. Implements `HasRawWindowHandle` and `HasRawDisplayHandle` for compatibility with various graphics libraries.

### SkiaRenderer

Optional Skia-based renderer that can draw to a [SurfaceWindow](src/window.rs#L14-L14). Provides a simple interface for drawing 2D graphics.

## License

This project is licensed under either of

* Apache License, Version 2.0 ([LICENSE-APACHE]LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT]LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.