From 62b6fb8d0a955f784b8192def603798247006845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Tue, 1 Oct 2024 12:55:29 +0400 Subject: [PATCH] display/rdp: implement Unix.Map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marc-André Lureau --- qemu-display/Cargo.toml | 1 + qemu-display/src/console.rs | 27 +++++++----- qemu-display/src/console_listener.rs | 64 +++++++++++++++++++++++++--- qemu-display/src/util.rs | 7 --- qemu-display/src/win32.rs | 52 +++++++++++++++++++++- qemu-rdp/src/server/display.rs | 58 +++++++++++++++++++++++-- qemu-rdw/src/display.rs | 32 ++++++-------- 7 files changed, 194 insertions(+), 47 deletions(-) diff --git a/qemu-display/Cargo.toml b/qemu-display/Cargo.toml index d548fbd..4b60839 100644 --- a/qemu-display/Cargo.toml +++ b/qemu-display/Cargo.toml @@ -36,6 +36,7 @@ windows = { version = "0.58", features = [ "Win32_Networking_WinSock", "Win32_Foundation", "Win32_System_IO", + "Win32_System_Memory", "Win32_System_Threading", ] } diff --git a/qemu-display/src/console.rs b/qemu-display/src/console.rs index 43a0dbc..f8593d1 100644 --- a/qemu-display/src/console.rs +++ b/qemu-display/src/console.rs @@ -119,19 +119,24 @@ impl Console { Ok(()) } + #[tracing::instrument(skip(self, handler))] pub async fn set_map_listener(&self, handler: H) -> Result { - if let Some(l) = &*self.listener.write().unwrap() { - return l - .object_server() - .at( - "/org/qemu/Display1/Listener", - ConsoleListenerMap::new(handler), - ) - .await - .map_err(|e| e.into()); + let listener = self.listener.write().unwrap().take(); + match listener { + Some(l) => { + let res = l + .object_server() + .at( + "/org/qemu/Display1/Listener", + ConsoleListenerMap::new(handler), + ) + .await + .map_err(|e| e.into()); + *self.listener.write().unwrap() = Some(l); + res + } + _ => Err(crate::Error::Failed("Must call register first!".into())), } - - Err(crate::Error::Failed("Must call register first!".into())) } #[cfg(windows)] diff --git a/qemu-display/src/console_listener.rs b/qemu-display/src/console_listener.rs index a8cbf2f..0bec922 100644 --- a/qemu-display/src/console_listener.rs +++ b/qemu-display/src/console_listener.rs @@ -1,11 +1,13 @@ #[cfg(windows)] -use crate::win32::Fd; +use crate::win32::{Fd, Mmap}; use derivative::Derivative; #[cfg(unix)] -use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd}; -use std::{ - ops::Drop, - os::fd::{AsFd, OwnedFd}, +use memmap2::Mmap; +use std::ops::Drop; +#[cfg(unix)] +use std::os::{ + fd::{AsFd, OwnedFd}, + unix::io::{AsRawFd, IntoRawFd, RawFd}, }; #[cfg(unix)] use zbus::zvariant::Fd; @@ -47,6 +49,58 @@ pub struct ScanoutMap { pub format: u32, } +#[derive(Debug)] +pub struct ScanoutMmap { + scanout: ScanoutMap, + #[cfg(unix)] + mmap: Mmap, + #[cfg(windows)] + mmap: Mmap, +} + +impl ScanoutMap { + pub fn mmap(self) -> std::io::Result { + let len = self.height as usize * self.stride as usize; + let offset = self.offset; + + #[cfg(unix)] + let mmap = { + let fd = self.fd.as_raw_fd(); + unsafe { + memmap2::MmapOptions::new() + .len(len) + .offset(offset.into()) + .map(fd) + }? + }; + + #[cfg(windows)] + let mmap = { + let handle = windows::Win32::Foundation::HANDLE(self.handle as _); // taking ownership + Mmap::new(handle, offset.try_into().unwrap(), len)? + }; + + Ok(ScanoutMmap { + scanout: self, + mmap, + }) + } +} + +impl ScanoutMmap { + pub fn as_ref(&self) -> &[u8] { + self.mmap.as_ref() + } + + pub fn stride(&self) -> u32 { + self.scanout.stride + } + + pub fn format(&self) -> u32 { + self.scanout.format + } +} + #[derive(Debug, Copy, Clone)] pub struct UpdateMap { pub x: i32, diff --git a/qemu-display/src/util.rs b/qemu-display/src/util.rs index fe6ed0a..1e2bb9d 100644 --- a/qemu-display/src/util.rs +++ b/qemu-display/src/util.rs @@ -1,7 +1,5 @@ use crate::Result; -use std::os::fd::RawFd; - #[cfg(unix)] use std::os::unix::net::UnixStream; #[cfg(windows)] @@ -32,8 +30,3 @@ pub fn prepare_uds_pass(#[cfg(windows)] peer_pid: u32, us: &UnixStream) -> Resul p.duplicate_socket(SOCKET(us.as_raw_socket() as _)) } } - -#[cfg(unix)] -pub unsafe fn mmap(fd: RawFd, len: usize, offset: u64) -> std::io::Result { - memmap2::MmapOptions::new().len(len).offset(offset).map(fd) -} diff --git a/qemu-display/src/win32.rs b/qemu-display/src/win32.rs index c082fa5..405a90f 100644 --- a/qemu-display/src/win32.rs +++ b/qemu-display/src/win32.rs @@ -1,8 +1,13 @@ use std::io; +use tracing::warn; + use windows::Win32::{ Foundation::{CloseHandle, HANDLE}, Networking::WinSock::{WSADuplicateSocketW, SOCKET, WSAPROTOCOL_INFOW}, - System::Threading::PROCESS_ACCESS_RIGHTS, + System::{ + Memory::{MapViewOfFile, UnmapViewOfFile, FILE_MAP_READ, MEMORY_MAPPED_VIEW_ADDRESS}, + Threading::PROCESS_ACCESS_RIGHTS, + }, }; #[cfg(feature = "qmp")] @@ -117,3 +122,48 @@ pub(crate) fn unix_stream_get_peer_pid(stream: &UnixStream) -> Result std::io::Result { + let ptr = unsafe { MapViewOfFile(handle, FILE_MAP_READ, 0, 0, offset + size) }; + if ptr.Value.is_null() { + return Err(io::Error::last_os_error()); + } + Ok(Self { + handle, + ptr, + offset: offset.try_into().unwrap(), + size, + }) + } + + pub(crate) fn as_ref(&self) -> &[u8] { + unsafe { + std::slice::from_raw_parts(self.ptr.Value.cast::().offset(self.offset), self.size) + } + } +} diff --git a/qemu-rdp/src/server/display.rs b/qemu-rdp/src/server/display.rs index 33f9f2c..a7ae36c 100644 --- a/qemu-rdp/src/server/display.rs +++ b/qemu-rdp/src/server/display.rs @@ -4,7 +4,10 @@ use std::{ }; use anyhow::Result; -use qemu_display::{Console, ConsoleListenerHandler, Cursor, Display, MouseSet, Scanout, Update}; +use qemu_display::{ + Console, ConsoleListenerHandler, ConsoleListenerMapHandler, Cursor, Display, MouseSet, Scanout, + ScanoutMap, ScanoutMmap, Update, UpdateMap, +}; use ironrdp::{ connector::DesktopSize, @@ -14,7 +17,7 @@ use ironrdp::{ RdpServerDisplayUpdates, }, }; -use tracing::debug; +use tracing::{debug, warn}; use crate::{cast, utils::PixmanFormat}; @@ -45,7 +48,9 @@ impl DisplayHandler { let desktop_size = DesktopSize { width, height }; let listener = Listener::new(sender, desktop_size); self.console.unregister_listener(); - self.console.register_listener(listener).await?; + self.console.register_listener(listener.clone()).await?; + #[cfg(any(windows, unix))] + self.console.set_map_listener(listener.clone()).await?; Ok(DisplayUpdates { receiver }) } @@ -94,6 +99,7 @@ struct Inner { sender: queue::Sender, desktop_size: DesktopSize, cursor_hot: (i32, i32), + scanout_mmap: Option, } #[derive(Clone)] @@ -107,6 +113,7 @@ impl Listener { sender, desktop_size, cursor_hot: (0, 0), + scanout_mmap: None, }; Self { inner: Arc::new(Mutex::new(inner)), @@ -143,6 +150,45 @@ impl Listener { } } +#[async_trait::async_trait] +impl ConsoleListenerMapHandler for Listener { + async fn scanout_map(&mut self, scanout: ScanoutMap) { + self.set_desktop_size(scanout.width, scanout.height).await; + match scanout.mmap() { + Ok(mmap) => self.inner.lock().unwrap().scanout_mmap = Some(mmap), + Err(err) => warn!("Failed to mmap: {}", err), + } + } + + async fn update_map(&mut self, update: UpdateMap) { + let update = { + let inner = self.inner.lock().unwrap(); + let Some(mmap) = &inner.scanout_mmap else { + warn!("Update with no map!"); + return; + }; + let (stride, format) = (mmap.stride(), mmap.format()); + if format != 0x20020888 { + warn!("Format not yet supported: {:X}", format); + return; + } + // FIXME: use arc of data + let data = mmap.as_ref()[update.y as usize * stride as usize + update.x as usize * 4..] + .to_vec(); + Update { + x: update.x, + y: update.y, + w: update.w, + h: update.h, + stride, + format, + data, + } + }; + self.update(update).await + } +} + #[async_trait::async_trait] impl ConsoleListenerHandler for Listener { async fn scanout(&mut self, scanout: Scanout) { @@ -256,6 +302,10 @@ impl ConsoleListenerHandler for Listener { } fn interfaces(&self) -> Vec { - vec![] + if cfg!(unix) { + vec!["org.qemu.Display1.Listener.Unix.Map".to_string()] + } else { + vec![] + } } } diff --git a/qemu-rdw/src/display.rs b/qemu-rdw/src/display.rs index 355021e..6f86bcd 100644 --- a/qemu-rdw/src/display.rs +++ b/qemu-rdw/src/display.rs @@ -15,12 +15,11 @@ use std::os::unix::io::IntoRawFd; mod imp { use super::*; use gtk::subclass::prelude::*; - use qemu_display::{memmap2::Mmap, util::mmap}; + use qemu_display::ScanoutMmap; #[cfg(any(windows, unix))] use std::cell::RefCell; #[cfg(windows)] use std::ffi::c_void; - use std::os::fd::AsRawFd; #[cfg(windows)] use windows::Win32::Foundation::{CloseHandle, HANDLE}; @@ -84,10 +83,7 @@ mod imp { pub struct Display { pub(crate) console: OnceCell, keymap: Cell>, - #[cfg(unix)] - scanout_map: RefCell>, - #[cfg(windows)] - scanout_map: RefCell>, + scanout_map: RefCell>, } #[glib::object_subclass] @@ -333,39 +329,37 @@ mod imp { ); continue; } - let map = unsafe { - mmap( - scanout.fd.as_raw_fd(), - scanout.height as usize * scanout.stride as usize, - scanout.offset.into(), - ) - } - .unwrap(); this.obj().set_display_size(Some(( scanout.width as _, scanout.height as _, ))); - this.scanout_map.replace(Some((map, scanout.stride))); + let map = match scanout.mmap() { + Ok(map) => Some(map), + Err(err) => { + log::warn!("Failed to mmap: {}", err); + continue; + } + }; + this.scanout_map.replace(map); let _ = wait_tx.send(()); } #[cfg(unix)] UpdateMap(u) => { log::debug!("{u:?}"); let scanout_map = this.scanout_map.borrow(); - let Some((map, stride)) = scanout_map.as_ref() else { + let Some(map) = scanout_map.as_ref() else { log::warn!("No mapped scanout!"); continue; }; - let stride = *stride; let bytes = map.as_ref(); this.obj().update_area( u.x as _, u.y as _, u.w as _, u.h as _, - stride as _, + map.stride() as _, Some( - &bytes[u.y as usize * stride as usize + &bytes[u.y as usize * map.stride() as usize + u.x as usize * 4..], ), );