mirror of
https://github.com/rustdesk/qemu-display.git
synced 2025-08-17 16:25:39 +00:00
Handle 2D scanouts
This commit is contained in:
parent
eeea1ce3d7
commit
e0320ca4ab
@ -7,11 +7,12 @@ 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]
|
||||||
|
derivative = "2.2.0"
|
||||||
zbus = "2.0.0-beta"
|
zbus = "2.0.0-beta"
|
||||||
derivative = "2.1.3"
|
zvariant = { version = "2.4.0", features = ["serde_bytes"] }
|
||||||
zvariant = "2.4.0"
|
|
||||||
libc = "0.2.86"
|
libc = "0.2.86"
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
||||||
enumflags2 = { version = "0.6.4", features = ["serde"] }
|
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_repr = "0.1.6"
|
||||||
|
serde_bytes = "0.11.5"
|
||||||
|
@ -4,10 +4,35 @@ use std::os::unix::io::{AsRawFd, RawFd};
|
|||||||
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
|
use std::sync::mpsc::{Receiver, RecvError, SendError, Sender};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use derivative::Derivative;
|
||||||
use zbus::{dbus_interface, export::zvariant::Fd};
|
use zbus::{dbus_interface, export::zvariant::Fd};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Derivative)]
|
||||||
|
#[derivative(Debug)]
|
||||||
pub struct Scanout {
|
pub struct Scanout {
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub stride: u32,
|
||||||
|
pub format: u32,
|
||||||
|
#[derivative(Debug = "ignore")]
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ScanoutDMABUF {
|
||||||
pub fd: RawFd,
|
pub fd: RawFd,
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
@ -17,7 +42,7 @@ pub struct Scanout {
|
|||||||
pub y0_top: bool,
|
pub y0_top: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Scanout {
|
impl Drop for ScanoutDMABUF {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.fd >= 0 {
|
if self.fd >= 0 {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -30,11 +55,10 @@ impl Drop for Scanout {
|
|||||||
// TODO: replace events mpsc with async traits
|
// TODO: replace events mpsc with async traits
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Switch {
|
Scanout(Scanout),
|
||||||
width: i32,
|
Update(Update),
|
||||||
height: i32,
|
ScanoutDMABUF(ScanoutDMABUF),
|
||||||
},
|
UpdateDMABUF {
|
||||||
Update {
|
|
||||||
x: i32,
|
x: i32,
|
||||||
y: i32,
|
y: i32,
|
||||||
w: i32,
|
w: i32,
|
||||||
@ -52,7 +76,6 @@ pub enum Event {
|
|||||||
hot_y: i32,
|
hot_y: i32,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
},
|
},
|
||||||
Scanout(Scanout),
|
|
||||||
Disconnected,
|
Disconnected,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,18 +105,46 @@ pub(crate) struct Listener<E: EventSender> {
|
|||||||
|
|
||||||
#[dbus_interface(name = "org.qemu.Display1.Listener")]
|
#[dbus_interface(name = "org.qemu.Display1.Listener")]
|
||||||
impl<E: 'static + EventSender> Listener<E> {
|
impl<E: 'static + EventSender> Listener<E> {
|
||||||
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(
|
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,
|
&mut self,
|
||||||
fd: Fd,
|
fd: Fd,
|
||||||
width: u32,
|
width: u32,
|
||||||
@ -104,7 +155,7 @@ impl<E: 'static + EventSender> Listener<E> {
|
|||||||
y0_top: bool,
|
y0_top: bool,
|
||||||
) {
|
) {
|
||||||
let fd = unsafe { libc::dup(fd.as_raw_fd()) };
|
let fd = unsafe { libc::dup(fd.as_raw_fd()) };
|
||||||
self.send(Event::Scanout(Scanout {
|
self.send(Event::ScanoutDMABUF(ScanoutDMABUF {
|
||||||
fd,
|
fd,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -115,6 +166,14 @@ impl<E: 'static + EventSender> Listener<E> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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) {
|
fn mouse_set(&mut self, x: i32, y: i32, on: i32) {
|
||||||
self.send(Event::MouseSet { x, y, on })
|
self.send(Event::MouseSet { x, y, on })
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ khronos-egl = { version = "3.0.0", features = ["dynamic"] }
|
|||||||
libloading = "0.6"
|
libloading = "0.6"
|
||||||
gl = "0.14.0"
|
gl = "0.14.0"
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
glib = { git = "https://github.com/gtk-rs/gtk-rs", optional = true }
|
||||||
|
derivative = "2.2.0"
|
||||||
|
|
||||||
[dependencies.gtk]
|
[dependencies.gtk]
|
||||||
package = "gtk4"
|
package = "gtk4"
|
||||||
|
@ -169,7 +169,19 @@ impl QemuConsole {
|
|||||||
clone!(@weak self as con => move |t| {
|
clone!(@weak self as con => move |t| {
|
||||||
let priv_ = imp::QemuConsole::from_instance(&con);
|
let priv_ = imp::QemuConsole::from_instance(&con);
|
||||||
match t {
|
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);
|
priv_.wait_rendering.set(priv_.wait_rendering.get() + 1);
|
||||||
// we don't simply queue_render, as we want a copy immediately
|
// we don't simply queue_render, as we want a copy immediately
|
||||||
priv_.area.make_current();
|
priv_.area.make_current();
|
||||||
@ -181,10 +193,6 @@ impl QemuConsole {
|
|||||||
};
|
};
|
||||||
priv_.area.queue_draw();
|
priv_.area.queue_draw();
|
||||||
}
|
}
|
||||||
Event::Scanout(s) => {
|
|
||||||
priv_.label.set_label(&format!("{:?}", s));
|
|
||||||
priv_.area.set_scanout(s);
|
|
||||||
}
|
|
||||||
Event::Disconnected => {
|
Event::Disconnected => {
|
||||||
priv_.label.set_label("Console 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);
|
let cur = gdk::Cursor::from_texture(&tex, hot_x, hot_y, None);
|
||||||
priv_.area.set_cursor(Some(&cur));
|
priv_.area.set_cursor(Some(&cur));
|
||||||
}
|
}
|
||||||
_ => ()
|
t => { dbg!(t); }
|
||||||
}
|
}
|
||||||
Continue(true)
|
Continue(true)
|
||||||
}),
|
}),
|
||||||
|
@ -10,7 +10,7 @@ use std::ffi::{CStr, CString};
|
|||||||
use crate::egl;
|
use crate::egl;
|
||||||
use crate::error::*;
|
use crate::error::*;
|
||||||
use gl::{self, types::*};
|
use gl::{self, types::*};
|
||||||
use qemu_display_listener::Scanout;
|
use qemu_display_listener::{Scanout, ScanoutDMABUF, Update};
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -22,7 +22,7 @@ mod imp {
|
|||||||
pub texture_blit_vao: Cell<GLuint>,
|
pub texture_blit_vao: Cell<GLuint>,
|
||||||
pub texture_blit_prog: Cell<GLuint>,
|
pub texture_blit_prog: Cell<GLuint>,
|
||||||
pub texture_blit_flip_prog: Cell<GLuint>,
|
pub texture_blit_flip_prog: Cell<GLuint>,
|
||||||
pub scanout: Cell<Option<Scanout>>,
|
pub scanout: Cell<Option<ScanoutDMABUF>>,
|
||||||
pub scanout_size: Cell<(u32, u32)>,
|
pub scanout_size: Cell<(u32, u32)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,6 +229,58 @@ mod imp {
|
|||||||
|
|
||||||
pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) {
|
pub fn set_scanout(&self, widget: &super::QemuConsoleArea, s: Scanout) {
|
||||||
widget.make_current();
|
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 = egl::egl();
|
||||||
|
|
||||||
let egl_dpy = if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>()
|
let egl_dpy = if let Ok(dpy) = widget.get_display().downcast::<gdk_wl::WaylandDisplay>()
|
||||||
@ -315,6 +367,18 @@ impl QemuConsoleArea {
|
|||||||
priv_.set_scanout(self, s);
|
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) {
|
pub fn save_to_png(&self, filename: &str) {
|
||||||
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
let priv_ = imp::QemuConsoleArea::from_instance(self);
|
||||||
|
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::{thread, time, io};
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::{io, thread, time};
|
||||||
|
|
||||||
use qemu_display_listener::{Console, Event};
|
|
||||||
use zbus::Connection;
|
|
||||||
use clap::Clap;
|
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)]
|
#[derive(Clap, Debug)]
|
||||||
pub struct SocketAddrArgs {
|
pub struct SocketAddrArgs {
|
||||||
@ -30,7 +33,6 @@ struct Cli {
|
|||||||
address: SocketAddrArgs,
|
address: SocketAddrArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
struct ServerInner {
|
struct ServerInner {
|
||||||
width: u16,
|
width: u16,
|
||||||
height: u16,
|
height: u16,
|
||||||
@ -43,10 +45,7 @@ struct Server {
|
|||||||
impl Server {
|
impl Server {
|
||||||
fn new(width: u16, height: u16) -> Self {
|
fn new(width: u16, height: u16) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: Arc::new(Mutex::new(ServerInner {
|
inner: Arc::new(Mutex::new(ServerInner { width, height })),
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,11 +64,13 @@ impl Server {
|
|||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
Err(VncError::Io(ref e)) if e.kind() == io::ErrorKind::WouldBlock => {
|
Err(VncError::Io(ref e)) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||||
continue;
|
continue;
|
||||||
},
|
}
|
||||||
Err(VncError::Disconnected) => {
|
Err(VncError::Disconnected) => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(e) => { return Err(e.into()); }
|
Err(e) => {
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
match event {
|
match event {
|
||||||
VncEvent::FramebufferUpdateRequest { .. } => {
|
VncEvent::FramebufferUpdateRequest { .. } => {
|
||||||
@ -108,16 +109,16 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
let server = Server::new(console.width()? as u16, console.height()? as u16);
|
let server = Server::new(console.width()? as u16, console.height()? as u16);
|
||||||
|
|
||||||
let _thread = thread::spawn(move || {
|
let _thread = thread::spawn(move || match rx.recv().unwrap() {
|
||||||
match rx.recv().unwrap() {
|
Event::ScanoutDMABUF(s) => {
|
||||||
Event::Scanout(s) => {
|
dbg!(&s);
|
||||||
dbg!(&s);
|
unsafe {
|
||||||
unsafe {
|
libc::close(s.fd);
|
||||||
libc::close(s.fd);
|
}
|
||||||
}
|
let _ = ack.send(());
|
||||||
let _ = ack.send(());
|
}
|
||||||
},
|
e => {
|
||||||
e => { dbg!(e); },
|
dbg!(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user