diff --git a/qemu-display/src/console_listener.rs b/qemu-display/src/console_listener.rs index 1ca0c90..f8d641c 100644 --- a/qemu-display/src/console_listener.rs +++ b/qemu-display/src/console_listener.rs @@ -32,6 +32,24 @@ pub struct Update { pub data: Vec<u8>, } +#[derive(Debug)] +pub struct ScanoutMap { + pub handle: u64, + pub offset: u32, + pub width: u32, + pub height: u32, + pub stride: u32, + pub format: u32, +} + +#[derive(Debug, Copy, Clone)] +pub struct UpdateMap { + pub x: i32, + pub y: i32, + pub w: i32, + pub h: i32, +} + #[cfg(unix)] #[derive(Debug)] pub struct ScanoutDMABUF { @@ -98,6 +116,12 @@ pub trait ConsoleListenerHandler: 'static + Send + Sync { async fn update(&mut self, update: Update); + #[cfg(windows)] + async fn scanout_map(&mut self, scanout: ScanoutMap); + + #[cfg(windows)] + async fn update_map(&mut self, update: UpdateMap); + #[cfg(unix)] async fn scanout_dmabuf(&mut self, scanout: ScanoutDMABUF); @@ -160,6 +184,57 @@ impl<H: ConsoleListenerHandler> ConsoleListener<H> { .await; } + #[cfg(windows)] + async fn scanout_map( + &mut self, + handle: u64, + offset: u32, + width: u32, + height: u32, + stride: u32, + format: u32, + ) -> zbus::fdo::Result<()> { + let map = ScanoutMap { + handle, + offset, + width, + height, + stride, + format, + }; + self.handler.scanout_map(map).await; + Ok(()) + } + + #[cfg(not(windows))] + async fn scanout_map( + &mut self, + _handle: u64, + _offset: u32, + _width: u32, + _height: u32, + _stride: u32, + _format: u32, + ) -> zbus::fdo::Result<()> { + Err(zbus::fdo::Error::NotSupported( + "Shared map is not support on !windows".into(), + )) + } + + #[cfg(windows)] + async fn update_map(&mut self, x: i32, y: i32, w: i32, h: i32) -> zbus::fdo::Result<()> { + let up = UpdateMap { x, y, w, h }; + self.handler.update_map(up).await; + Ok(()) + } + + #[cfg(not(windows))] + async fn update_map(&mut self, _x: i32, _y: i32, _w: i32, _h: i32) -> zbus::fdo::Result<()> { + Err(zbus::fdo::Error::NotSupported( + "Shared map is not support on !windows".into(), + )) + } + #[cfg(not(unix))] #[dbus_interface(name = "ScanoutDMABUF")] async fn scanout_dmabuf( diff --git a/qemu-rdw/Cargo.toml b/qemu-rdw/Cargo.toml index bbbbe28..fceb220 100644 --- a/qemu-rdw/Cargo.toml +++ b/qemu-rdw/Cargo.toml @@ -19,5 +19,8 @@ rdw = { package = "rdw4", version = "0.1", features = ["bindings"] } futures-util = "0.3" futures = "0.3" async-trait = "0.1" -uds_windows = "1.0.2" tracing-subscriber = { version = "0.3.11", features = ["env-filter" , "fmt"], default-features = false } + +[target.'cfg(target_os = "windows")'.dependencies] +uds_windows = "1.0.2" +windows = { version = "0.43.0", features = ["Win32_System_Memory", "Win32_Foundation"] } diff --git a/qemu-rdw/src/display.rs b/qemu-rdw/src/display.rs index 3e4e15f..60a3d61 100644 --- a/qemu-rdw/src/display.rs +++ b/qemu-rdw/src/display.rs @@ -11,6 +11,12 @@ use std::os::unix::io::IntoRawFd; mod imp { use super::*; use gtk::subclass::prelude::*; + #[cfg(windows)] + use std::cell::RefCell; + #[cfg(windows)] + use std::ffi::c_void; + #[cfg(windows)] + use windows::Win32::Foundation::{CloseHandle, HANDLE}; #[repr(C)] pub struct RdwDisplayQemuClass { @@ -38,10 +44,42 @@ mod imp { type Type = Display; } + #[cfg(windows)] + #[derive(Debug)] + struct MemoryMap { + handle: HANDLE, + ptr: *const c_void, + offset: isize, + size: usize, + } + + #[cfg(windows)] + impl Drop for MemoryMap { + fn drop(&mut self) { + unsafe { + use windows::Win32::System::Memory::UnmapViewOfFile; + + UnmapViewOfFile(self.ptr); + CloseHandle(self.handle); + } + } + } + + #[cfg(windows)] + impl MemoryMap { + fn as_bytes(&self) -> &[u8] { + unsafe { + std::slice::from_raw_parts(self.ptr.cast::<u8>().offset(self.offset), self.size) + } + } + } + #[derive(Debug, Default)] pub struct Display { pub(crate) console: OnceCell<Console>, keymap: Cell<Option<&'static [u16]>>, + #[cfg(windows)] + scanout_map: RefCell<Option<(MemoryMap, u32)>>, } #[glib::object_subclass] @@ -174,6 +212,42 @@ mod imp { } this.obj().update_area(u.x as _, u.y as _, u.w as _, u.h as _, u.stride as _, &u.data); } + #[cfg(windows)] + ScanoutMap(s) => { + use windows::Win32::System::Memory::{FILE_MAP_READ, MapViewOfFile}; + + log::debug!("{s:?}"); + if s.format != 0x20020888 { + log::warn!("Format not yet supported: {:X}", s.format); + continue; + } + + let handle = HANDLE(s.handle as _); + let size = s.height as usize * s.stride as usize; + let offset = s.offset as isize; + let ptr = unsafe { MapViewOfFile(handle, FILE_MAP_READ, 0, 0, s.offset as usize + size) }; + if ptr.is_null() { + log::warn!("Failed to map scanout!"); + continue; + } + + let map = MemoryMap { ptr, handle, offset, size }; + this.obj().set_display_size(Some((s.width as _, s.height as _))); + this.obj().update_area(0, 0, s.width as _, s.height as _, s.stride as _, map.as_bytes()); + this.scanout_map.replace(Some((map, s.stride))); + } + #[cfg(windows)] + UpdateMap(u) => { + log::debug!("{u:?}"); + let scanout_map = this.scanout_map.borrow(); + let Some((map, stride)) = scanout_map.as_ref() else { + log::warn!("No mapped scanout!"); + continue; + }; + let stride = *stride; + let bytes = map.as_bytes(); + this.obj().update_area(u.x as _, u.y as _, u.w as _, u.h as _, stride as _, &bytes[u.y as usize * stride as usize + u.x as usize * 4..]); + } #[cfg(unix)] ScanoutDMABUF(s) => { this.obj().set_display_size(Some((s.width as _, s.height as _))); @@ -196,6 +270,7 @@ mod imp { log::warn!("Console disconnected"); } CursorDefine(c) => { + log::debug!("{c:?}"); let cursor = rdw::Display::make_cursor( &c.data, c.width, @@ -254,6 +329,10 @@ impl Display { enum ConsoleEvent { Scanout(qemu_display::Scanout), Update(qemu_display::Update), + #[cfg(windows)] + ScanoutMap(qemu_display::ScanoutMap), + #[cfg(windows)] + UpdateMap(qemu_display::UpdateMap), #[cfg(unix)] ScanoutDMABUF(qemu_display::ScanoutDMABUF), #[cfg(unix)] @@ -288,6 +367,16 @@ impl ConsoleListenerHandler for ConsoleHandler { self.send(ConsoleEvent::Update(update)); } + #[cfg(windows)] + async fn scanout_map(&mut self, scanout: qemu_display::ScanoutMap) { + self.send(ConsoleEvent::ScanoutMap(scanout)); + } + + #[cfg(windows)] + async fn update_map(&mut self, update: qemu_display::UpdateMap) { + self.send(ConsoleEvent::UpdateMap(update)); + } + #[cfg(unix)] async fn scanout_dmabuf(&mut self, scanout: qemu_display::ScanoutDMABUF) { self.send(ConsoleEvent::ScanoutDMABUF(scanout));