diff --git a/qemu-display-listener/src/mouse.rs b/qemu-display-listener/src/mouse.rs index 8c1a88b..310bbfe 100644 --- a/qemu-display-listener/src/mouse.rs +++ b/qemu-display-listener/src/mouse.rs @@ -25,6 +25,9 @@ pub trait Mouse { /// SetAbsPosition method fn set_abs_position(&self, x: u32, y: u32) -> zbus::Result<()>; + /// RelMotion method + fn rel_motion(&self, dx: i32, dy: i32) -> zbus::Result<()>; + #[dbus_proxy(property)] fn is_absolute(&self) -> zbus::Result; } diff --git a/qemu-gtk4/Cargo.toml b/qemu-gtk4/Cargo.toml index 286d1bf..a541001 100644 --- a/qemu-gtk4/Cargo.toml +++ b/qemu-gtk4/Cargo.toml @@ -21,6 +21,8 @@ derivative = "2.2.0" gst = { package = "gstreamer", version = "0.16.7" } gst-app = { package = "gstreamer-app", version = "0.16.5" } gst-audio = { package = "gstreamer-audio", version = "0.16.5" } +wayland-protocols = { version = "0.28.5", features = ["unstable_protocols", "client"] } +wayland-client = "0.28.5" [dependencies.gtk] package = "gtk4" diff --git a/qemu-gtk4/src/console.rs b/qemu-gtk4/src/console.rs index 4b32a16..274b3d8 100644 --- a/qemu-gtk4/src/console.rs +++ b/qemu-gtk4/src/console.rs @@ -5,7 +5,17 @@ use gtk::prelude::*; use gtk::{gdk, glib, CompositeTemplate}; use log::debug; use once_cell::sync::OnceCell; -use std::cell::Cell; +use std::cell::{Cell, RefCell}; + +use wayland_client::{Display, GlobalManager}; +use wayland_protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1; +use wayland_protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ + Lifetime, ZwpPointerConstraintsV1, +}; +use wayland_protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_manager_v1::ZwpRelativePointerManagerV1; +use wayland_protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::{ + Event as RelEvent, ZwpRelativePointerV1, +}; use keycodemap::*; use qemu_display_listener::{Console, ConsoleEvent as Event, MouseButton}; @@ -26,6 +36,12 @@ mod imp { pub shortcuts_inhibited_id: Cell>, pub ungrab_shortcut: OnceCell, pub key_controller: OnceCell, + pub event_queue: OnceCell, + pub rel_manager: OnceCell>, + pub rel_pointer: RefCell>>, + pub pointer_constraints: OnceCell>, + pub lock_pointer: RefCell>>, + pub has_grab: Cell, } #[glib::object_subclass] @@ -76,13 +92,9 @@ mod imp { ec.connect_motion(clone!(@weak obj => move |_, x, y| { let priv_ = imp::QemuConsole::from_instance(&obj); let c = obj.qemu_console(); - if let Ok(abs) = c.mouse.is_absolute() { - if abs { - priv_.motion(x, y); - } else { - dbg!() - } - } + if c.mouse.is_absolute().unwrap_or(true) { + priv_.motion(x, y); + }; })); let ec = gtk::GestureClick::new(); @@ -95,6 +107,43 @@ mod imp { priv_.motion(x, y); let _ = c.mouse.press(button); + if !c.mouse.is_absolute().unwrap_or(true) { + priv_.area.set_cursor_abs(false); + if let Some(device) = gesture.get_device() { + if let Ok(device) = device.downcast::() { + let pointer = device.get_wl_pointer(); + if priv_.lock_pointer.borrow().is_none() { + if let Some(constraints) = priv_.pointer_constraints.get() { + if let Some(surf) = priv_.area.get_native() + .and_then(|n| n.get_surface()) + .and_then(|s| s.downcast::().ok()) + .map(|w| w.get_wl_surface()) { + let lock = constraints.lock_pointer(&surf, &pointer, None, Lifetime::Persistent as _); + lock.quick_assign(move |_, event, _| { + debug!("{:?}", event); + }); + priv_.lock_pointer.replace(Some(lock)); + } + } + } + if priv_.rel_pointer.borrow().is_none() { + if let Some(rel_manager) = priv_.rel_manager.get() { + let rel_pointer = rel_manager.get_relative_pointer(&pointer); + rel_pointer.quick_assign(clone!(@weak obj => @default-panic, move |_, event, _| { + if let RelEvent::RelativeMotion { dx_unaccel, dy_unaccel, .. } = event { + let priv_ = imp::QemuConsole::from_instance(&obj); + let c = obj.qemu_console(); + let scale = priv_.area.get_scale_factor(); + let _ = c.mouse.rel_motion(dx_unaccel as i32 / scale, dy_unaccel as i32 / scale); + } + })); + priv_.rel_pointer.replace(Some(rel_pointer)); + } + } + } + } + } + if let Some(toplevel) = priv_.get_toplevel() { if !toplevel.get_property_shortcuts_inhibited() { toplevel.inhibit_system_shortcuts::(None); @@ -106,11 +155,20 @@ mod imp { if let Some(ref e) = ec.get_current_event() { if priv_.ungrab_shortcut.get().unwrap().trigger(e, false) == gdk::KeyMatch::Exact { //widget.remove_controller(ec); here crashes badly - glib::idle_add_local(clone!(@weak ec, @weak toplevel => @default-panic, move || { + glib::idle_add_local(clone!(@weak ec, @weak toplevel, @weak obj => @default-panic, move || { + let priv_ = imp::QemuConsole::from_instance(&obj); if let Some(widget) = ec.get_widget() { widget.remove_controller(&ec); } toplevel.restore_system_shortcuts(); + if let Some(lock) = priv_.lock_pointer.take() { + lock.destroy(); + } + if let Some(rel_pointer) = priv_.rel_pointer.take() { + rel_pointer.destroy(); + } + priv_.area.set_cursor_abs(true); + priv_.has_grab.set(false); glib::Continue(false) })); } else { @@ -141,6 +199,7 @@ mod imp { } } + priv_.has_grab.set(true); priv_.area.grab_focus(); })); ec.connect_released(clone!(@weak obj => move |gesture, _n_press, x, y| { @@ -210,7 +269,38 @@ mod imp { } } - impl WidgetImpl for QemuConsole {} + impl WidgetImpl for QemuConsole { + fn realize(&self, widget: &Self::Type) { + self.parent_realize(widget); + + if let Ok(dpy) = widget.get_display().downcast::() { + let display = unsafe { + Display::from_external_display(dpy.get_wl_display().as_ref().c_ptr() as *mut _) + }; + let mut event_queue = display.create_event_queue(); + let attached_display = display.attach(event_queue.token()); + let globals = GlobalManager::new(&attached_display); + event_queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .unwrap(); + let rel_manager = globals + .instantiate_exact::(1) + .unwrap(); + self.rel_manager.set(rel_manager).unwrap(); + let pointer_constraints = globals + .instantiate_exact::(1) + .unwrap(); + self.pointer_constraints.set(pointer_constraints).unwrap(); + let fd = display.get_connection_fd(); + let _ = glib::unix_fd_add_local(fd, glib::IOCondition::IN, move |_, _| { + event_queue + .sync_roundtrip(&mut (), |_, _, _| unreachable!()) + .unwrap(); + glib::Continue(true) + }); + } + } + } impl QemuConsole { fn get_toplevel(&self) -> Option { @@ -296,7 +386,8 @@ mod imp { Event::MouseSet(m) => { priv_.area.mouse_set(m); let c = obj.qemu_console(); - if let Ok(abs) = c.mouse.is_absolute() { + let abs = c.mouse.is_absolute().unwrap_or(true); + if priv_.has_grab.get() { priv_.area.set_cursor_abs(abs); } priv_.area.queue_render(); diff --git a/qemu-gtk4/src/console_area.rs b/qemu-gtk4/src/console_area.rs index 3937dde..fa01386 100644 --- a/qemu-gtk4/src/console_area.rs +++ b/qemu-gtk4/src/console_area.rs @@ -101,7 +101,7 @@ mod imp { if !self.cursor_abs.get() { if let Some(mouse) = self.mouse.get() { if mouse.on != 0 { - if let Some(cursor) = self.cursor.borrow().clone() { + if let Some(cursor) = &*self.cursor.borrow() { if let Some(texture) = cursor.get_texture() { let sf = widget.get_scale_factor(); snapshot.append_texture(