diff --git a/qemu-display-listener/Cargo.toml b/qemu-display-listener/Cargo.toml index 33d981d..aa2393d 100644 --- a/qemu-display-listener/Cargo.toml +++ b/qemu-display-listener/Cargo.toml @@ -7,11 +7,12 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +derivative = "2.2.0" zbus = "2.0.0-beta" -derivative = "2.1.3" -zvariant = "2.4.0" +zvariant = { version = "2.4.0", features = ["serde_bytes"] } libc = "0.2.86" glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true } enumflags2 = { version = "0.6.4", features = ["serde"] } -serde = { version = "1.0", features = ["derive"] } +serde = { version = "1.0.123", features = ["derive"] } serde_repr = "0.1.6" +serde_bytes = "0.11.5" diff --git a/qemu-display-listener/src/listener.rs b/qemu-display-listener/src/listener.rs index 977e311..90e69f7 100644 --- a/qemu-display-listener/src/listener.rs +++ b/qemu-display-listener/src/listener.rs @@ -4,10 +4,35 @@ use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::mpsc::{Receiver, RecvError, SendError, Sender}; use std::sync::Arc; +use derivative::Derivative; use zbus::{dbus_interface, export::zvariant::Fd}; -#[derive(Debug)] +#[derive(Derivative)] +#[derivative(Debug)] pub struct Scanout { + pub width: u32, + pub height: u32, + pub stride: u32, + pub format: u32, + #[derivative(Debug = "ignore")] + pub data: Vec, +} + +#[derive(Derivative)] +#[derivative(Debug)] +pub struct Update { + pub x: i32, + pub y: i32, + pub w: i32, + pub h: i32, + pub stride: u32, + pub format: u32, + #[derivative(Debug = "ignore")] + pub data: Vec, +} + +#[derive(Debug)] +pub struct ScanoutDMABUF { pub fd: RawFd, pub width: u32, pub height: u32, @@ -17,7 +42,7 @@ pub struct Scanout { pub y0_top: bool, } -impl Drop for Scanout { +impl Drop for ScanoutDMABUF { fn drop(&mut self) { if self.fd >= 0 { unsafe { @@ -30,11 +55,10 @@ impl Drop for Scanout { // TODO: replace events mpsc with async traits #[derive(Debug)] pub enum Event { - Switch { - width: i32, - height: i32, - }, - Update { + Scanout(Scanout), + Update(Update), + ScanoutDMABUF(ScanoutDMABUF), + UpdateDMABUF { x: i32, y: i32, w: i32, @@ -52,7 +76,6 @@ pub enum Event { hot_y: i32, data: Vec, }, - Scanout(Scanout), Disconnected, } @@ -82,18 +105,46 @@ pub(crate) struct Listener { #[dbus_interface(name = "org.qemu.Display1.Listener")] impl Listener { - fn switch(&mut self, width: i32, height: i32) { - self.send(Event::Switch { width, height }) - } - - fn update(&mut self, x: i32, y: i32, w: i32, h: i32) { - self.send(Event::Update { x, y, w, h }); - if let Err(e) = self.wait() { - eprintln!("update returned error: {}", e) - } - } - fn scanout( + &mut self, + width: u32, + height: u32, + stride: u32, + format: u32, + data: serde_bytes::ByteBuf, + ) { + self.send(Event::Scanout(Scanout { + width, + height, + stride, + format, + data: data.into_vec(), + })) + } + + fn update( + &mut self, + x: i32, + y: i32, + w: i32, + h: i32, + stride: u32, + format: u32, + data: serde_bytes::ByteBuf, + ) { + self.send(Event::Update(Update { + x, + y, + w, + h, + stride, + format, + data: data.into_vec(), + })) + } + + #[dbus_interface(name = "ScanoutDMABUF")] + fn scanout_dmabuf( &mut self, fd: Fd, width: u32, @@ -104,7 +155,7 @@ impl Listener { y0_top: bool, ) { let fd = unsafe { libc::dup(fd.as_raw_fd()) }; - self.send(Event::Scanout(Scanout { + self.send(Event::ScanoutDMABUF(ScanoutDMABUF { fd, width, height, @@ -115,6 +166,14 @@ impl Listener { })) } + #[dbus_interface(name = "UpdateDMABUF")] + fn update_dmabuf(&mut self, x: i32, y: i32, w: i32, h: i32) { + self.send(Event::UpdateDMABUF { x, y, w, h }); + if let Err(e) = self.wait() { + eprintln!("update returned error: {}", e) + } + } + fn mouse_set(&mut self, x: i32, y: i32, on: i32) { self.send(Event::MouseSet { x, y, on }) } diff --git a/qemu-gtk4/Cargo.toml b/qemu-gtk4/Cargo.toml index 2eae256..9ba7eb8 100644 --- a/qemu-gtk4/Cargo.toml +++ b/qemu-gtk4/Cargo.toml @@ -17,6 +17,7 @@ khronos-egl = { version = "3.0.0", features = ["dynamic"] } libloading = "0.6" gl = "0.14.0" glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true } +derivative = "2.2.0" [dependencies.gtk] package = "gtk4" diff --git a/qemu-gtk4/src/console.rs b/qemu-gtk4/src/console.rs index 19a7edc..bd25607 100644 --- a/qemu-gtk4/src/console.rs +++ b/qemu-gtk4/src/console.rs @@ -169,7 +169,19 @@ impl QemuConsole { clone!(@weak self as con => move |t| { let priv_ = imp::QemuConsole::from_instance(&con); match t { - Event::Update { .. } => { + Event::Scanout(s) => { + priv_.area.set_scanout(s); + priv_.area.queue_render(); + } + Event::Update(u) => { + priv_.area.update(u); + priv_.area.queue_render(); + } + Event::ScanoutDMABUF(s) => { + priv_.label.set_label(&format!("{:?}", s)); + priv_.area.set_scanout_dmabuf(s); + } + Event::UpdateDMABUF { .. } => { priv_.wait_rendering.set(priv_.wait_rendering.get() + 1); // we don't simply queue_render, as we want a copy immediately priv_.area.make_current(); @@ -181,10 +193,6 @@ impl QemuConsole { }; priv_.area.queue_draw(); } - Event::Scanout(s) => { - priv_.label.set_label(&format!("{:?}", s)); - priv_.area.set_scanout(s); - } Event::Disconnected => { priv_.label.set_label("Console disconnected!"); } @@ -194,7 +202,7 @@ impl QemuConsole { let cur = gdk::Cursor::from_texture(&tex, hot_x, hot_y, None); priv_.area.set_cursor(Some(&cur)); } - _ => () + t => { dbg!(t); } } Continue(true) }), diff --git a/qemu-gtk4/src/console_area.rs b/qemu-gtk4/src/console_area.rs index 6f3252b..c4da6ed 100644 --- a/qemu-gtk4/src/console_area.rs +++ b/qemu-gtk4/src/console_area.rs @@ -10,7 +10,7 @@ use std::ffi::{CStr, CString}; use crate::egl; use crate::error::*; use gl::{self, types::*}; -use qemu_display_listener::Scanout; +use qemu_display_listener::{Scanout, ScanoutDMABUF, Update}; mod imp { use super::*; @@ -22,7 +22,7 @@ mod imp { pub texture_blit_vao: Cell, pub texture_blit_prog: Cell, pub texture_blit_flip_prog: Cell, - pub scanout: Cell>, + pub scanout: Cell>, pub scanout_size: Cell<(u32, u32)>, } @@ -229,6 +229,58 @@ mod imp { pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) { widget.make_current(); + + if s.format != 0x20020888 { + todo!(); + } + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.tex_id()); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + gl::PixelStorei(gl::UNPACK_ROW_LENGTH, s.stride as i32 / 4); + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as _, + s.width as _, + s.height as _, + 0, + gl::BGRA, + gl::UNSIGNED_BYTE, + s.data.as_ptr() as _, + ); + } + + self.scanout_size.set((s.width, s.height)); + } + + pub fn update(&self, widget: &super::QemuConsoleArea, u: Update) { + widget.make_current(); + + if u.format != 0x20020888 { + todo!(); + } + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.tex_id()); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as _); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as _); + gl::PixelStorei(gl::UNPACK_ROW_LENGTH, u.stride as i32 / 4); + gl::TexSubImage2D( + gl::TEXTURE_2D, + 0, + u.x, + u.y, + u.w, + u.h, + gl::BGRA, + gl::UNSIGNED_BYTE, + u.data.as_ptr() as _, + ); + } + } + + pub fn set_scanout_dmabuf(&self, widget: &super::QemuConsoleArea, s: ScanoutDMABUF) { + widget.make_current(); let egl = egl::egl(); let egl_dpy = if let Ok(dpy) = widget.get_display().downcast::() @@ -315,6 +367,18 @@ impl QemuConsoleArea { priv_.set_scanout(self, s); } + pub fn update(&self, u: Update) { + let priv_ = imp::QemuConsoleArea::from_instance(self); + + priv_.update(self, u); + } + + pub fn set_scanout_dmabuf(&self, s: ScanoutDMABUF) { + let priv_ = imp::QemuConsoleArea::from_instance(self); + + priv_.set_scanout_dmabuf(self, s); + } + pub fn save_to_png(&self, filename: &str) { let priv_ = imp::QemuConsoleArea::from_instance(self); diff --git a/qemu-vnc/src/main.rs b/qemu-vnc/src/main.rs index 374c780..9a42bf4 100644 --- a/qemu-vnc/src/main.rs +++ b/qemu-vnc/src/main.rs @@ -1,12 +1,15 @@ use std::error::Error; use std::net::{TcpListener, TcpStream}; -use std::{thread, time, io}; use std::sync::{Arc, Mutex}; +use std::{io, thread, time}; -use qemu_display_listener::{Console, Event}; -use zbus::Connection; use clap::Clap; -use vnc::{server::FramebufferUpdate, server::Event as VncEvent, PixelFormat, Rect, Server as VncServer, Error as VncError}; +use qemu_display_listener::{Console, Event}; +use vnc::{ + server::Event as VncEvent, server::FramebufferUpdate, Error as VncError, PixelFormat, Rect, + Server as VncServer, +}; +use zbus::Connection; #[derive(Clap, Debug)] pub struct SocketAddrArgs { @@ -30,7 +33,6 @@ struct Cli { address: SocketAddrArgs, } - struct ServerInner { width: u16, height: u16, @@ -43,10 +45,7 @@ struct Server { impl Server { fn new(width: u16, height: u16) -> Self { Self { - inner: Arc::new(Mutex::new(ServerInner { - width, - height, - })) + inner: Arc::new(Mutex::new(ServerInner { width, height })), } } @@ -65,11 +64,13 @@ impl Server { Ok(e) => e, Err(VncError::Io(ref e)) if e.kind() == io::ErrorKind::WouldBlock => { continue; - }, + } Err(VncError::Disconnected) => { return Ok(()); } - Err(e) => { return Err(e.into()); } + Err(e) => { + return Err(e.into()); + } }; match event { VncEvent::FramebufferUpdateRequest { .. } => { @@ -108,16 +109,16 @@ fn main() -> Result<(), Box> { let server = Server::new(console.width()? as u16, console.height()? as u16); - let _thread = thread::spawn(move || { - match rx.recv().unwrap() { - Event::Scanout(s) => { - dbg!(&s); - unsafe { - libc::close(s.fd); - } - let _ = ack.send(()); - }, - e => { dbg!(e); }, + let _thread = thread::spawn(move || match rx.recv().unwrap() { + Event::ScanoutDMABUF(s) => { + dbg!(&s); + unsafe { + libc::close(s.fd); + } + let _ = ack.send(()); + } + e => { + dbg!(e); } }); for stream in listener.incoming() {