diff --git a/Cargo.toml b/Cargo.toml index 5c7ee6d..e411127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,13 @@ members = [ "keycodemap", "qemu-display-listener", + "qemu-rdw", "qemu-vnc", "xtask", ] +default-members = ["qemu-rdw"] + [patch.crates-io] zbus = { git = 'https://gitlab.freedesktop.org/dbus/zbus.git' } zvariant = { git = 'https://gitlab.freedesktop.org/dbus/zbus.git' } diff --git a/qemu-display-listener/src/audio.rs b/qemu-display-listener/src/audio.rs index 164b284..5d34cfd 100644 --- a/qemu-display-listener/src/audio.rs +++ b/qemu-display-listener/src/audio.rs @@ -1,9 +1,9 @@ use std::cell::RefCell; use std::os::unix::net::UnixStream; +use std::str::FromStr; use std::sync::mpsc::{self, Receiver, SendError}; use std::sync::Arc; use std::{os::unix::io::AsRawFd, thread}; -use std::str::FromStr; use zbus::{dbus_interface, dbus_proxy, export::zvariant::Fd}; @@ -237,9 +237,13 @@ impl Audio { pub fn available(conn: &zbus::Connection) -> bool { // TODO: we may want to generalize interface detection - let ip = zbus::fdo::IntrospectableProxy::new_for(&conn, "org.qemu", "/org/qemu/Display1").unwrap(); + let ip = zbus::fdo::IntrospectableProxy::new_for(&conn, "org.qemu", "/org/qemu/Display1") + .unwrap(); let introspect = zbus::xml::Node::from_str(&ip.introspect().unwrap()).unwrap(); - let has_audio = introspect.nodes().iter().any(|n| n.name().map(|n| n == "Audio").unwrap_or(false)); + let has_audio = introspect + .nodes() + .iter() + .any(|n| n.name().map(|n| n == "Audio").unwrap_or(false)); has_audio } diff --git a/qemu-display-listener/src/console_listener.rs b/qemu-display-listener/src/console_listener.rs index 22b484c..fc2e793 100644 --- a/qemu-display-listener/src/console_listener.rs +++ b/qemu-display-listener/src/console_listener.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; use std::ops::Drop; +use std::os::unix::io::IntoRawFd; use std::os::unix::io::{AsRawFd, RawFd}; use std::sync::mpsc::{Receiver, RecvError, SendError}; use std::sync::Arc; @@ -54,6 +55,12 @@ impl Drop for ScanoutDMABUF { } } +impl IntoRawFd for ScanoutDMABUF { + fn into_raw_fd(mut self) -> RawFd { + std::mem::replace(&mut self.fd, -1) + } +} + #[derive(Debug, Copy, Clone)] pub struct MouseSet { pub x: i32, diff --git a/qemu-rdw/.Cargo.toml.swp b/qemu-rdw/.Cargo.toml.swp new file mode 100644 index 0000000..9129121 Binary files /dev/null and b/qemu-rdw/.Cargo.toml.swp differ diff --git a/qemu-rdw/Cargo.toml b/qemu-rdw/Cargo.toml new file mode 100644 index 0000000..3dfaebd --- /dev/null +++ b/qemu-rdw/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "qemu-rdw" +version = "0.1.0" +authors = ["Marc-André Lureau "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4" +pretty_env_logger = "0.4" +once_cell = "1.5" +zbus = { version = "2.0.0-beta" } +qemu-display-listener = { path = "../qemu-display-listener", features = ["glib"] } +keycodemap = { path = "../keycodemap" } +gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs" } +rdw = { git = "https://gitlab.gnome.org/malureau/rdw.git" } diff --git a/qemu-rdw/src/display_qemu.rs b/qemu-rdw/src/display_qemu.rs new file mode 100644 index 0000000..84b590a --- /dev/null +++ b/qemu-rdw/src/display_qemu.rs @@ -0,0 +1,230 @@ +use glib::{clone, subclass::prelude::*, translate::*}; +use gtk::{glib, prelude::*}; +use once_cell::sync::OnceCell; + +use keycodemap::KEYMAP_XORGEVDEV2QNUM; +use qemu_display_listener::Console; +use rdw::DisplayExt; + +mod imp { + use super::*; + use gtk::subclass::prelude::*; + use std::os::unix::io::IntoRawFd; + + #[repr(C)] + pub struct RdwDisplayQemuClass { + pub parent_class: rdw::imp::RdwDisplayClass, + } + + unsafe impl ClassStruct for RdwDisplayQemuClass { + type Type = DisplayQemu; + } + + #[repr(C)] + pub struct RdwDisplayQemu { + parent: rdw::imp::RdwDisplay, + } + + impl std::fmt::Debug for RdwDisplayQemu { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("RdwDisplayQemu") + .field("parent", &self.parent) + .finish() + } + } + + unsafe impl InstanceStruct for RdwDisplayQemu { + type Type = DisplayQemu; + } + + #[derive(Debug, Default)] + pub struct DisplayQemu { + pub(crate) console: OnceCell, + } + + #[glib::object_subclass] + impl ObjectSubclass for DisplayQemu { + const NAME: &'static str = "RdwDisplayQemu"; + type Type = super::DisplayQemu; + type ParentType = rdw::Display; + type Class = RdwDisplayQemuClass; + type Instance = RdwDisplayQemu; + } + + impl ObjectImpl for DisplayQemu { + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + + obj.set_mouse_absolute(true); + + obj.connect_key_press(clone!(@weak obj => move |_, keyval, keycode| { + let self_ = Self::from_instance(&obj); + log::debug!("key-press: {:?}", (keyval, keycode)); + let console = self_.console.get().unwrap(); + if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) { + let _ = console.keyboard.press(*qnum as u32); + } + })); + + obj.connect_key_release(clone!(@weak obj => move |_, keyval, keycode| { + let self_ = Self::from_instance(&obj); + log::debug!("key-release: {:?}", (keyval, keycode)); + let console = self_.console.get().unwrap(); + if let Some(qnum) = KEYMAP_XORGEVDEV2QNUM.get(keycode as usize) { + let _ = console.keyboard.release(*qnum as u32); + } + })); + + obj.connect_motion(clone!(@weak obj => move |_, x, y| { + let self_ = Self::from_instance(&obj); + log::debug!("motion: {:?}", (x, y)); + let console = self_.console.get().unwrap(); + let _ = console.mouse.set_abs_position(x as _, y as _); + })); + + obj.connect_motion_relative(clone!(@weak obj => move |_, dx, dy| { + let self_ = Self::from_instance(&obj); + log::debug!("motion-relative: {:?}", (dx, dy)); + let console = self_.console.get().unwrap(); + let _ = console.mouse.rel_motion(dx as _, dy as _); + })); + + obj.connect_mouse_press(clone!(@weak obj => move |_, button| { + let self_ = Self::from_instance(&obj); + log::debug!("mouse-press: {:?}", button); + let button = from_gdk_button(button); + let console = self_.console.get().unwrap(); + let _ = console.mouse.press(button); + })); + + obj.connect_mouse_release(clone!(@weak obj => move |_, button| { + let self_ = Self::from_instance(&obj); + log::debug!("mouse-release: {:?}", button); + let button = from_gdk_button(button); + let console = self_.console.get().unwrap(); + let _ = console.mouse.release(button); + })); + + obj.connect_scroll_discrete(clone!(@weak obj => move |_, scroll| { + use qemu_display_listener::MouseButton; + + let self_ = Self::from_instance(&obj); + log::debug!("scroll-discrete: {:?}", scroll); + let console = self_.console.get().unwrap(); + + let button = match scroll { + rdw::Scroll::Up => MouseButton::WheelUp, + rdw::Scroll::Down => MouseButton::WheelDown, + _ => { + log::warn!("not yet implemented"); + return; + } + }; + let _ = console.mouse.press(button); + let _ = console.mouse.release(button); + })); + + obj.connect_resize_request(clone!(@weak obj => move |_, width, height, wmm, hmm| { + let self_ = Self::from_instance(&obj); + log::debug!("resize-request: {:?}", (width, height, wmm, hmm)); + let console = self_.console.get().unwrap(); + let _ = console.proxy.set_ui_info(wmm as _, hmm as _, 0, 0, width, height); + })); + } + } + + impl WidgetImpl for DisplayQemu { + fn realize(&self, widget: &Self::Type) { + self.parent_realize(widget); + + let console = self.console.get().unwrap(); + let (rx, wait_tx) = console + .glib_listen() + .expect("Failed to listen to the console"); + rx.attach( + None, + clone!(@weak widget => @default-panic, move |evt| { + use qemu_display_listener::ConsoleEvent::*; + + let self_ = Self::from_instance(&widget); + log::debug!("Console event: {:?}", evt); + match evt { + Scanout(s) => { + } + Update(u) => { + } + ScanoutDMABUF(s) => { + widget.set_display_size(Some((s.width as _, s.height as _))); + widget.set_dmabuf_scanout(rdw::DmabufScanout { + width: s.width, + height: s.height, + stride: s.stride, + fourcc: s.fourcc, + y0_top: s.y0_top, + modifier: s.modifier, + fd: s.into_raw_fd(), + }); + } + UpdateDMABUF { .. } => { + widget.render(); + let _ = wait_tx.send(()); + } + Disconnected => { + } + CursorDefine { width, height, hot_x, hot_y, data }=> { + let cursor = rdw::Display::make_cursor( + &data, + width, + height, + hot_x, + hot_y, + 1, + ); + widget.define_cursor(Some(cursor)); + } + MouseSet(m) => { + if m.on != 0 { + widget.set_cursor_position(Some((m.x as _, m.y as _))); + } else { + widget.set_cursor_position(None); + } + } + } + Continue(true) + }), + ); + } + } + + impl rdw::DisplayImpl for DisplayQemu {} + + impl DisplayQemu { + pub(crate) fn set_console(&self, console: Console) { + self.console.set(console).unwrap(); + } + } +} + +glib::wrapper! { + pub struct DisplayQemu(ObjectSubclass) @extends rdw::Display, gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; +} + +impl DisplayQemu { + pub fn new(console: Console) -> Self { + let obj = glib::Object::new::(&[]).unwrap(); + let self_ = imp::DisplayQemu::from_instance(&obj); + self_.set_console(console); + obj + } +} + +fn from_gdk_button(button: u32) -> qemu_display_listener::MouseButton { + use qemu_display_listener::MouseButton::*; + + match button { + 1 => Left, + 2 => Middle, + 3 => Right, + _ => Extra, + } +} diff --git a/qemu-rdw/src/main.rs b/qemu-rdw/src/main.rs new file mode 100644 index 0000000..36157a2 --- /dev/null +++ b/qemu-rdw/src/main.rs @@ -0,0 +1,29 @@ +use gio::ApplicationFlags; +use gtk::{gio, prelude::*}; +use qemu_display_listener::Console; +use zbus::Connection; + +mod display_qemu; + +fn main() { + pretty_env_logger::init(); + + let app = gtk::Application::new(Some("org.qemu.rdw.demo"), ApplicationFlags::NON_UNIQUE); + + let conn = Connection::new_session().expect("Failed to connect to DBus"); + + app.connect_activate(move |app| { + let window = gtk::ApplicationWindow::new(app); + + window.set_title(Some("rdw demo")); + window.set_default_size(1024, 768); + + let console = Console::new(&conn, 0).expect("Failed to get the QEMU console"); + let display = display_qemu::DisplayQemu::new(console); + window.set_child(Some(&display)); + + window.show(); + }); + + app.run(); +}