1
0
mirror of https://gitlab.com/marcandre.lureau/qemu-display.git synced 2025-03-07 15:25:04 +00:00

qemu-display: start win32 support

This commit is contained in:
Marc-André Lureau 2022-08-26 21:29:57 +04:00
parent ecbeb857c5
commit 1a24e051d0
13 changed files with 256 additions and 52 deletions

View File

@ -7,6 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
cfg-if = "1.0"
log = "0.4" log = "0.4"
derivative = "2.2.0" derivative = "2.2.0"
zbus = { version = "2.0", features = ["xml"] } zbus = { version = "2.0", features = ["xml"] }
@ -23,3 +24,7 @@ usbredirhost = "0.0.1"
async-broadcast = "0.3.3" async-broadcast = "0.3.3"
async-trait = "0.1.48" async-trait = "0.1.48"
async-lock = "2.3.0" async-lock = "2.3.0"
[target.'cfg(windows)'.dependencies]
uds_windows = "1.0.1"
windows = { version = "0.39.0", features = ["Win32_Networking_WinSock", "Win32_Foundation", "Win32_System_IO", "Win32_System_Threading"] }

View File

@ -1,6 +1,14 @@
use std::os::unix::{io::AsRawFd, net::UnixStream}; #[cfg(windows)]
use zbus::{dbus_interface, dbus_proxy, zvariant::Fd, Connection}; use crate::win32::Fd;
#[cfg(unix)]
use std::os::unix::net::UnixStream;
#[cfg(windows)]
use uds_windows::UnixStream;
#[cfg(unix)]
use zbus::zvariant::Fd;
use zbus::{dbus_interface, dbus_proxy, Connection};
use crate::util;
use crate::Result; use crate::Result;
#[derive(Debug)] #[derive(Debug)]
@ -236,9 +244,8 @@ impl Audio {
pub async fn register_out_listener<H: AudioOutHandler>(&mut self, handler: H) -> Result<()> { pub async fn register_out_listener<H: AudioOutHandler>(&mut self, handler: H) -> Result<()> {
let (p0, p1) = UnixStream::pair()?; let (p0, p1) = UnixStream::pair()?;
self.proxy let p0 = util::prepare_uds_pass(&p0)?;
.register_out_listener(p0.as_raw_fd().into()) self.proxy.register_out_listener(p0).await?;
.await?;
let c = zbus::ConnectionBuilder::unix_stream(p1) let c = zbus::ConnectionBuilder::unix_stream(p1)
.p2p() .p2p()
.serve_at( .serve_at(
@ -253,9 +260,8 @@ impl Audio {
pub async fn register_in_listener<H: AudioInHandler>(&mut self, handler: H) -> Result<()> { pub async fn register_in_listener<H: AudioInHandler>(&mut self, handler: H) -> Result<()> {
let (p0, p1) = UnixStream::pair()?; let (p0, p1) = UnixStream::pair()?;
self.proxy let p0 = util::prepare_uds_pass(&p0)?;
.register_in_listener(p0.as_raw_fd().into()) self.proxy.register_in_listener(p0).await?;
.await?;
let c = zbus::ConnectionBuilder::unix_stream(p1) let c = zbus::ConnectionBuilder::unix_stream(p1)
.p2p() .p2p()
.serve_at( .serve_at(

View File

@ -1,14 +1,16 @@
#[cfg(windows)]
use crate::win32::Fd;
use std::convert::TryFrom; use std::convert::TryFrom;
use zbus::{ #[cfg(unix)]
dbus_proxy, use zbus::zvariant::Fd;
zvariant::{Fd, ObjectPath}, use zbus::{dbus_proxy, zvariant::ObjectPath};
};
use crate::Result; use crate::Result;
#[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Chardev")] #[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Chardev")]
pub trait Chardev { pub trait Chardev {
/// Register method /// Register method
#[cfg(unix)]
fn register(&self, stream: Fd) -> zbus::Result<()>; fn register(&self, stream: Fd) -> zbus::Result<()>;
/// SendBreak method /// SendBreak method

View File

@ -1,15 +1,15 @@
use std::{ #[cfg(windows)]
cell::RefCell, use crate::win32::Fd;
convert::TryFrom, #[cfg(unix)]
os::unix::{io::AsRawFd, net::UnixStream}, use std::os::unix::net::UnixStream;
}; use std::{cell::RefCell, convert::TryFrom};
use zbus::{ #[cfg(windows)]
dbus_proxy, use uds_windows::UnixStream;
zvariant::{Fd, ObjectPath}, #[cfg(unix)]
Connection, use zbus::zvariant::Fd;
}; use zbus::{dbus_proxy, zvariant::ObjectPath, Connection};
use crate::{ConsoleListener, ConsoleListenerHandler, KeyboardProxy, MouseProxy, Result}; use crate::{util, ConsoleListener, ConsoleListenerHandler, KeyboardProxy, MouseProxy, Result};
#[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Console")] #[dbus_proxy(default_service = "org.qemu", interface = "org.qemu.Display1.Console")]
pub trait Console { pub trait Console {
@ -87,7 +87,8 @@ impl Console {
pub async fn register_listener<H: ConsoleListenerHandler>(&self, handler: H) -> Result<()> { pub async fn register_listener<H: ConsoleListenerHandler>(&self, handler: H) -> Result<()> {
let (p0, p1) = UnixStream::pair()?; let (p0, p1) = UnixStream::pair()?;
self.proxy.register_listener(p0.as_raw_fd().into()).await?; let p0 = util::prepare_uds_pass(&p0)?;
self.proxy.register_listener(p0).await?;
let c = zbus::ConnectionBuilder::unix_stream(p1) let c = zbus::ConnectionBuilder::unix_stream(p1)
.p2p() .p2p()
.serve_at("/org/qemu/Display1/Listener", ConsoleListener::new(handler))? .serve_at("/org/qemu/Display1/Listener", ConsoleListener::new(handler))?

View File

@ -1,9 +1,12 @@
#[cfg(windows)]
use crate::win32::Fd;
use derivative::Derivative; use derivative::Derivative;
use std::{ use std::ops::Drop;
ops::Drop, #[cfg(unix)]
os::unix::io::{AsRawFd, IntoRawFd, RawFd}, use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
}; use zbus::dbus_interface;
use zbus::{dbus_interface, zvariant::Fd}; #[cfg(unix)]
use zbus::zvariant::Fd;
#[derive(Derivative)] #[derive(Derivative)]
#[derivative(Debug)] #[derivative(Debug)]
@ -29,6 +32,7 @@ pub struct Update {
pub data: Vec<u8>, pub data: Vec<u8>,
} }
#[cfg(unix)]
#[derive(Debug)] #[derive(Debug)]
pub struct ScanoutDMABUF { pub struct ScanoutDMABUF {
pub fd: RawFd, pub fd: RawFd,
@ -40,6 +44,10 @@ pub struct ScanoutDMABUF {
pub y0_top: bool, pub y0_top: bool,
} }
#[cfg(windows)]
#[derive(Debug)]
pub struct ScanoutDMABUF {}
#[derive(Derivative)] #[derive(Derivative)]
#[derivative(Debug)] #[derivative(Debug)]
pub struct Cursor { pub struct Cursor {
@ -51,6 +59,7 @@ pub struct Cursor {
pub data: Vec<u8>, pub data: Vec<u8>,
} }
#[cfg(unix)]
impl Drop for ScanoutDMABUF { impl Drop for ScanoutDMABUF {
fn drop(&mut self) { fn drop(&mut self) {
if self.fd >= 0 { if self.fd >= 0 {
@ -61,6 +70,7 @@ impl Drop for ScanoutDMABUF {
} }
} }
#[cfg(unix)]
impl IntoRawFd for ScanoutDMABUF { impl IntoRawFd for ScanoutDMABUF {
fn into_raw_fd(mut self) -> RawFd { fn into_raw_fd(mut self) -> RawFd {
std::mem::replace(&mut self.fd, -1) std::mem::replace(&mut self.fd, -1)
@ -148,6 +158,22 @@ impl<H: ConsoleListenerHandler> ConsoleListener<H> {
.await; .await;
} }
#[cfg(not(unix))]
#[dbus_interface(name = "ScanoutDMABUF")]
async fn scanout_dmabuf(
&mut self,
_fd: Fd,
_width: u32,
_height: u32,
_stride: u32,
_fourcc: u32,
_modifier: u64,
_y0_top: bool,
) {
unimplemented!()
}
#[cfg(unix)]
#[dbus_interface(name = "ScanoutDMABUF")] #[dbus_interface(name = "ScanoutDMABUF")]
async fn scanout_dmabuf( async fn scanout_dmabuf(
&mut self, &mut self,

View File

@ -12,7 +12,9 @@ use zbus::{
}; };
use zvariant::OwnedObjectPath; use zvariant::OwnedObjectPath;
use crate::{Audio, Chardev, Clipboard, Error, Result, UsbRedir, VMProxy}; #[cfg(unix)]
use crate::UsbRedir;
use crate::{Audio, Chardev, Clipboard, Error, Result, VMProxy};
struct Inner<'d> { struct Inner<'d> {
proxy: fdo::ObjectManagerProxy<'d>, proxy: fdo::ObjectManagerProxy<'d>,
@ -144,6 +146,7 @@ impl<'d> Display<'d> {
.await .await
} }
#[cfg(unix)]
pub async fn usbredir(&self) -> UsbRedir { pub async fn usbredir(&self) -> UsbRedir {
let chardevs = stream::iter(self.chardevs().await) let chardevs = stream::iter(self.chardevs().await)
.filter_map(|c| async move { .filter_map(|c| async move {

View File

@ -1,5 +1,9 @@
#![allow(clippy::too_many_arguments)] #![allow(clippy::too_many_arguments)]
pub mod util;
#[cfg(windows)]
mod win32;
mod error; mod error;
pub use error::*; pub use error::*;
@ -30,7 +34,9 @@ pub use mouse::*;
mod display; mod display;
pub use display::*; pub use display::*;
#[cfg(unix)]
mod usbredir; mod usbredir;
#[cfg(unix)]
pub use usbredir::UsbRedir; pub use usbredir::UsbRedir;
#[cfg(test)] #[cfg(test)]

View File

@ -1,19 +1,22 @@
use async_broadcast::{broadcast, Receiver, Sender}; use async_broadcast::{broadcast, Receiver, Sender};
use async_lock::RwLock; use async_lock::RwLock;
use futures::Stream; use futures::Stream;
#[cfg(unix)]
use std::os::unix::{
io::{AsRawFd, RawFd},
net::UnixStream,
};
use std::{ use std::{
collections::HashMap, collections::HashMap,
default::Default, default::Default,
io::{Read, Write}, io::{Read, Write},
os::unix::{
io::{AsRawFd, RawFd},
net::UnixStream,
},
pin::Pin, pin::Pin,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
task::{Context, Poll}, task::{Context, Poll},
thread::JoinHandle, thread::JoinHandle,
}; };
#[cfg(windows)]
use uds_windows::UnixStream;
use usbredirhost::{ use usbredirhost::{
rusb::{self, UsbContext}, rusb::{self, UsbContext},
Device, DeviceHandler, LogLevel, Device, DeviceHandler, LogLevel,
@ -24,6 +27,7 @@ use crate::{Chardev, Error, Result};
#[derive(Debug)] #[derive(Debug)]
struct InnerHandler { struct InnerHandler {
#[allow(unused)] // keep the device opened, as rusb doesn't take it #[allow(unused)] // keep the device opened, as rusb doesn't take it
#[cfg(unix)]
device_fd: Option<zvariant::OwnedFd>, device_fd: Option<zvariant::OwnedFd>,
stream: UnixStream, stream: UnixStream,
ctxt: rusb::Context, ctxt: rusb::Context,
@ -73,6 +77,7 @@ impl DeviceHandler for Handler {
fn flush_writes(&mut self) {} fn flush_writes(&mut self) {}
} }
#[cfg(unix)]
#[zbus::dbus_proxy( #[zbus::dbus_proxy(
interface = "org.freedesktop.usbredir1", interface = "org.freedesktop.usbredir1",
default_service = "org.freedesktop.usbredir1", default_service = "org.freedesktop.usbredir1",
@ -88,6 +93,7 @@ impl Handler {
let (dev, device_fd) = match device.open() { let (dev, device_fd) = match device.open() {
Ok(it) => (it, None), Ok(it) => (it, None),
#[cfg(unix)]
Err(rusb::Error::Access) => { Err(rusb::Error::Access) => {
let (bus, dev) = (device.bus_number(), device.address()); let (bus, dev) = (device.bus_number(), device.address());
let sysbus = zbus::Connection::system().await?; let sysbus = zbus::Connection::system().await?;
@ -120,6 +126,7 @@ impl Handler {
let handler = Self { let handler = Self {
inner: Arc::new(Mutex::new(InnerHandler { inner: Arc::new(Mutex::new(InnerHandler {
#[cfg(unix)]
device_fd, device_fd,
stream, stream,
event, event,
@ -302,6 +309,7 @@ impl Stream for NFreeChannelsStream {
} }
} }
#[cfg(unix)]
fn fd_poll_readable(fd: RawFd, wait: Option<RawFd>) -> std::io::Result<bool> { fn fd_poll_readable(fd: RawFd, wait: Option<RawFd>) -> std::io::Result<bool> {
let mut fds = vec![libc::pollfd { let mut fds = vec![libc::pollfd {
fd, fd,

45
qemu-display/src/util.rs Normal file
View File

@ -0,0 +1,45 @@
use crate::Result;
#[cfg(unix)]
use std::os::unix::{io::AsRawFd, net::UnixStream};
#[cfg(unix)]
use zbus::zvariant::Fd;
#[cfg(windows)]
use win32::Fd;
#[cfg(windows)]
use crate::win32;
#[cfg(windows)]
use std::os::windows::io::AsRawSocket;
#[cfg(windows)]
use uds_windows::UnixStream;
#[cfg(windows)]
use windows::Win32::Networking::WinSock::{WSADuplicateSocketW, SOCKET, WSAPROTOCOL_INFOW};
#[cfg(windows)]
use windows::Win32::System::Threading::PROCESS_DUP_HANDLE;
pub fn prepare_uds_pass(us: &UnixStream) -> Result<Fd> {
#[cfg(unix)]
{
Ok(us.as_raw_fd().into())
}
#[cfg(windows)]
{
let pid = win32::unix_stream_get_peer_pid(us)?;
let p = win32::ProcessHandle::open(Some(pid), PROCESS_DUP_HANDLE)?;
let mut info = unsafe { std::mem::zeroed() };
if unsafe { WSADuplicateSocketW(SOCKET(us.as_raw_socket() as _), p.process_id(), &mut info) }
!= 0
{
return Err(crate::Error::Io(win32::wsa_last_err()));
}
let info = unsafe {
std::slice::from_raw_parts(
&info as *const _ as *const u8,
std::mem::size_of::<WSAPROTOCOL_INFOW>(),
)
};
Ok(info.to_vec())
}
}

85
qemu-display/src/win32.rs Normal file
View File

@ -0,0 +1,85 @@
use std::io;
use uds_windows::UnixStream;
use windows::Win32::Foundation::{CloseHandle, HANDLE};
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
pub type Fd = Vec<u8>;
// A process handle
pub struct ProcessHandle(HANDLE);
impl Drop for ProcessHandle {
fn drop(&mut self) {
unsafe { CloseHandle(self.0) };
}
}
impl ProcessHandle {
// Open the process associated with the process_id (if None, the current process)
pub fn open(
process_id: Option<u32>,
desired_access: PROCESS_ACCESS_RIGHTS,
) -> Result<Self, io::Error> {
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcess};
let process = if let Some(process_id) = process_id {
unsafe { OpenProcess(desired_access, false, process_id)? }
} else {
unsafe { GetCurrentProcess() }
};
Ok(Self(process))
}
pub fn process_id(&self) -> u32 {
use windows::Win32::System::Threading::GetProcessId;
unsafe { GetProcessId(self.0) }
}
}
pub(crate) fn wsa_last_err() -> io::Error {
use windows::Win32::Networking::WinSock::WSAGetLastError;
let err = unsafe { WSAGetLastError() };
io::Error::from_raw_os_error(err.0)
}
// Get the process ID of the connected peer
pub fn unix_stream_get_peer_pid(stream: &UnixStream) -> Result<u32, io::Error> {
use std::os::windows::io::AsRawSocket;
use windows::Win32::Networking::WinSock::{
WSAIoctl, IOC_OUT, IOC_VENDOR, SOCKET, SOCKET_ERROR,
};
macro_rules! _WSAIOR {
($x:expr, $y:expr) => {
IOC_OUT | $x | $y
};
}
let socket = stream.as_raw_socket();
const SIO_AF_UNIX_GETPEERPID: u32 = _WSAIOR!(IOC_VENDOR, 256);
let mut ret = 0 as u32;
let mut bytes = 0;
let r = unsafe {
WSAIoctl(
SOCKET(socket as _),
SIO_AF_UNIX_GETPEERPID,
0 as *mut _,
0,
&mut ret as *mut _ as *mut _,
std::mem::size_of_val(&ret) as u32,
&mut bytes,
0 as *mut _,
None,
)
};
if r == SOCKET_ERROR {
return Err(wsa_last_err());
}
Ok(ret)
}

View File

@ -17,3 +17,4 @@ rdw = { package = "rdw4", version = "0.1", features = ["bindings"] }
futures-util = "0.3" futures-util = "0.3"
futures = "0.3" futures = "0.3"
async-trait = "0.1" async-trait = "0.1"
uds_windows = "1.0.2"

View File

@ -5,6 +5,7 @@ use keycodemap::KEYMAP_XORGEVDEV2QNUM;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use qemu_display::{Console, ConsoleListenerHandler}; use qemu_display::{Console, ConsoleListenerHandler};
use rdw::{gtk, DisplayExt}; use rdw::{gtk, DisplayExt};
#[cfg(unix)]
use std::os::unix::io::IntoRawFd; use std::os::unix::io::IntoRawFd;
mod imp { mod imp {
@ -158,6 +159,11 @@ mod imp {
} }
widget.update_area(u.x as _, u.y as _, u.w as _, u.h as _, u.stride as _, &u.data); widget.update_area(u.x as _, u.y as _, u.w as _, u.h as _, u.stride as _, &u.data);
} }
#[cfg(windows)]
ScanoutDMABUF(_) => {
unimplemented!()
}
#[cfg(unix)]
ScanoutDMABUF(s) => { ScanoutDMABUF(s) => {
widget.set_display_size(Some((s.width as _, s.height as _))); widget.set_display_size(Some((s.width as _, s.height as _)));
widget.set_dmabuf_scanout(rdw::RdwDmabufScanout { widget.set_dmabuf_scanout(rdw::RdwDmabufScanout {

View File

@ -2,17 +2,19 @@ use futures_util::StreamExt;
use gio::ApplicationFlags; use gio::ApplicationFlags;
use glib::MainContext; use glib::MainContext;
use gtk::{gio, glib, prelude::*}; use gtk::{gio, glib, prelude::*};
use qemu_display::{Chardev, Console, Display}; use qemu_display::{util, Chardev, Console, Display};
use rdw::gtk; use rdw::gtk;
use std::{cell::RefCell, sync::Arc}; use std::{cell::RefCell, sync::Arc};
mod audio; mod audio;
mod clipboard; mod clipboard;
mod display; mod display;
#[cfg(unix)]
mod usbredir; mod usbredir;
struct Inner { struct Inner {
app: gtk::Application, app: gtk::Application,
#[cfg(unix)]
usbredir: RefCell<Option<usbredir::Handler>>, usbredir: RefCell<Option<usbredir::Handler>>,
audio: RefCell<Option<audio::Handler>>, audio: RefCell<Option<audio::Handler>>,
clipboard: RefCell<Option<clipboard::Handler>>, clipboard: RefCell<Option<clipboard::Handler>>,
@ -101,6 +103,7 @@ impl App {
let app = App { let app = App {
inner: Arc::new(Inner { inner: Arc::new(Inner {
app, app,
#[cfg(unix)]
usbredir: Default::default(), usbredir: Default::default(),
audio: Default::default(), audio: Default::default(),
clipboard: Default::default(), clipboard: Default::default(),
@ -176,6 +179,7 @@ impl App {
.unwrap() .unwrap()
.set_child(Some(&rdw)); .set_child(Some(&rdw));
#[cfg(unix)]
app_clone.set_usbredir(usbredir::Handler::new(display.usbredir().await)); app_clone.set_usbredir(usbredir::Handler::new(display.usbredir().await));
if let Ok(Some(audio)) = display.audio().await { if let Ok(Some(audio)) = display.audio().await {
@ -197,13 +201,15 @@ impl App {
} }
if let Ok(c) = Chardev::new(&conn, "qmp").await { if let Ok(c) = Chardev::new(&conn, "qmp").await {
use std::{ use std::io::{prelude::*, BufReader};
io::{prelude::*, BufReader}, #[cfg(unix)]
os::unix::{io::AsRawFd, net::UnixStream}, use std::os::unix::net::UnixStream;
}; #[cfg(windows)]
use uds_windows::UnixStream;
let (p0, p1) = UnixStream::pair().unwrap(); let (p0, p1) = UnixStream::pair().unwrap();
if c.proxy.register(p1.as_raw_fd().into()).await.is_ok() { let fd = util::prepare_uds_pass(&p1).unwrap();
if c.proxy.register(fd).await.is_ok() {
let mut reader = BufReader::new(p0.try_clone().unwrap()); let mut reader = BufReader::new(p0.try_clone().unwrap());
let mut line = String::new(); let mut line = String::new();
std::thread::spawn(move || loop { std::thread::spawn(move || loop {
@ -218,6 +224,8 @@ impl App {
}); });
}); });
#[cfg(unix)]
{
let action_usb = gio::SimpleAction::new("usb", None); let action_usb = gio::SimpleAction::new("usb", None);
let app_clone = app.clone(); let app_clone = app.clone();
action_usb.connect_activate(move |_, _| { action_usb.connect_activate(move |_, _| {
@ -230,10 +238,12 @@ impl App {
} }
}); });
app.inner.app.add_action(&action_usb); app.inner.app.add_action(&action_usb);
}
app app
} }
#[cfg(unix)]
fn set_usbredir(&self, usbredir: usbredir::Handler) { fn set_usbredir(&self, usbredir: usbredir::Handler) {
self.inner.usbredir.replace(Some(usbredir)); self.inner.usbredir.replace(Some(usbredir));
} }