Merge branch 'Smithay:master' into master

This commit is contained in:
Victor Timofei 2022-02-06 17:08:39 +02:00 committed by GitHub
commit 6fc7503698
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 186 additions and 97 deletions

View File

@ -48,12 +48,15 @@ struct MoveSurfaceGrab {
impl PointerGrab for MoveSurfaceGrab { impl PointerGrab for MoveSurfaceGrab {
fn motion( fn motion(
&mut self, &mut self,
_handle: &mut PointerInnerHandle<'_>, handle: &mut PointerInnerHandle<'_>,
location: Point<f64, Logical>, location: Point<f64, Logical>,
_focus: Option<(wl_surface::WlSurface, Point<i32, Logical>)>, _focus: Option<(wl_surface::WlSurface, Point<i32, Logical>)>,
_serial: Serial, serial: Serial,
_time: u32, time: u32,
) { ) {
// While the grab is active, no client has pointer focus
handle.motion(location, None, serial, time);
let delta = location - self.start_data.location; let delta = location - self.start_data.location;
let new_location = self.initial_window_location.to_f64() + delta; let new_location = self.initial_window_location.to_f64() + delta;
@ -152,6 +155,9 @@ impl PointerGrab for ResizeSurfaceGrab {
return; return;
} }
// While the grab is active, no client has pointer focus
handle.motion(location, None, serial, time);
let (mut dx, mut dy) = (location - self.start_data.location).into(); let (mut dx, mut dy) = (location - self.start_data.location).into();
let mut new_window_width = self.initial_window_size.w; let mut new_window_width = self.initial_window_size.w;
@ -479,7 +485,7 @@ pub fn init_shell<BackendData: 'static>(display: Rc<RefCell<Display>>, log: ::sl
initial_window_location, initial_window_location,
}; };
pointer.set_grab(grab, serial); pointer.set_grab(grab, serial, 0);
} }
XdgRequest::Resize { XdgRequest::Resize {
@ -539,7 +545,7 @@ pub fn init_shell<BackendData: 'static>(display: Rc<RefCell<Display>>, log: ::sl
last_window_size: initial_window_size, last_window_size: initial_window_size,
}; };
pointer.set_grab(grab, serial); pointer.set_grab(grab, serial, 0);
} }
XdgRequest::AckConfigure { XdgRequest::AckConfigure {
@ -806,7 +812,7 @@ pub fn init_shell<BackendData: 'static>(display: Rc<RefCell<Display>>, log: ::sl
initial_window_location, initial_window_location,
}; };
pointer.set_grab(grab, serial); pointer.set_grab(grab, serial, 0);
} }
ShellRequest::Resize { ShellRequest::Resize {
@ -866,7 +872,7 @@ pub fn init_shell<BackendData: 'static>(display: Rc<RefCell<Display>>, log: ::sl
last_window_size: initial_window_size, last_window_size: initial_window_size,
}; };
pointer.set_grab(grab, serial); pointer.set_grab(grab, serial, 0);
} }
_ => (), _ => (),
} }

View File

@ -148,7 +148,7 @@ impl<BackendData: Backend + 'static> AnvilState<BackendData> {
DataDeviceEvent::DnDStarted { icon, .. } => { DataDeviceEvent::DnDStarted { icon, .. } => {
*dnd_icon2.lock().unwrap() = icon; *dnd_icon2.lock().unwrap() = icon;
} }
DataDeviceEvent::DnDDropped => { DataDeviceEvent::DnDDropped { .. } => {
*dnd_icon2.lock().unwrap() = None; *dnd_icon2.lock().unwrap() = None;
} }
_ => {} _ => {}

View File

@ -107,14 +107,6 @@ pub fn run_udev(log: Logger) {
let mut event_loop = EventLoop::try_new().unwrap(); let mut event_loop = EventLoop::try_new().unwrap();
let display = Rc::new(RefCell::new(Display::new())); let display = Rc::new(RefCell::new(Display::new()));
let name = display
.borrow_mut()
.add_socket_auto()
.unwrap()
.into_string()
.unwrap();
info!(log, "Listening on wayland socket"; "name" => name.clone());
::std::env::set_var("WAYLAND_DISPLAY", name);
/* /*
* Initialize session * Initialize session
*/ */

View File

@ -122,7 +122,7 @@ fn main() {
let mut event_loop = EventLoop::<()>::try_new().unwrap(); let mut event_loop = EventLoop::<()>::try_new().unwrap();
event_loop event_loop
.handle() .handle()
.insert_source(device, move |event, _: &mut (), _: &mut ()| match event { .insert_source(device, move |event, _: &mut _, _: &mut ()| match event {
DrmEvent::VBlank(crtc) => vblank_handler.vblank(crtc), DrmEvent::VBlank(crtc) => vblank_handler.vblank(crtc),
DrmEvent::Error(e) => panic!("{}", e), DrmEvent::Error(e) => panic!("{}", e),
}) })

View File

@ -96,14 +96,19 @@ impl<T> AsDmabuf for GbmBuffer<T> {
return Err(GbmConvertError::UnsupportedBuffer); //TODO return Err(GbmConvertError::UnsupportedBuffer); //TODO
} }
if self.fd()? == 0 { // Make sure to only call fd once as each call will create
// a new file descriptor which has to be closed
let fd = self.fd()?;
// gbm_bo_get_fd returns -1 if an error occurs
if fd == -1 {
return Err(GbmConvertError::InvalidFD); return Err(GbmConvertError::InvalidFD);
} }
let mut builder = Dmabuf::builder_from_buffer(self, DmabufFlags::empty()); let mut builder = Dmabuf::builder_from_buffer(self, DmabufFlags::empty());
for idx in 0..planes { for idx in 0..planes {
builder.add_plane( builder.add_plane(
self.fd()?, fd,
idx as u32, idx as u32,
self.offset(idx)?, self.offset(idx)?,
self.stride_for_plane(idx)?, self.stride_for_plane(idx)?,

View File

@ -3,10 +3,11 @@ use std::cell::RefCell;
use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{atomic::AtomicBool, Arc}; use std::sync::{atomic::AtomicBool, Arc};
use std::time::{Instant, SystemTime};
use calloop::{EventSource, Interest, Poll, PostAction, Readiness, Token, TokenFactory}; use calloop::{EventSource, Interest, Poll, PostAction, Readiness, Token, TokenFactory};
use drm::control::{connector, crtc, Device as ControlDevice, Event, Mode, ResourceHandles}; use drm::control::{connector, crtc, Device as ControlDevice, Event, Mode, ResourceHandles};
use drm::{ClientCapability, Device as BasicDevice}; use drm::{ClientCapability, Device as BasicDevice, DriverCapability};
use nix::libc::dev_t; use nix::libc::dev_t;
use nix::sys::stat::fstat; use nix::sys::stat::fstat;
@ -27,6 +28,7 @@ pub struct DrmDevice<A: AsRawFd + 'static> {
#[cfg(feature = "backend_session")] #[cfg(feature = "backend_session")]
pub(super) links: RefCell<Vec<crate::utils::signaling::SignalToken>>, pub(super) links: RefCell<Vec<crate::utils::signaling::SignalToken>>,
has_universal_planes: bool, has_universal_planes: bool,
has_monotonic_timestamps: bool,
resources: ResourceHandles, resources: ResourceHandles,
pub(super) logger: ::slog::Logger, pub(super) logger: ::slog::Logger,
token: Token, token: Token,
@ -136,6 +138,10 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
let has_universal_planes = dev let has_universal_planes = dev
.set_client_capability(ClientCapability::UniversalPlanes, true) .set_client_capability(ClientCapability::UniversalPlanes, true)
.is_ok(); .is_ok();
let has_monotonic_timestamps = dev
.get_driver_capability(DriverCapability::MonotonicTimestamp)
.unwrap_or(0)
== 1;
let resources = dev.resource_handles().map_err(|source| Error::Access { let resources = dev.resource_handles().map_err(|source| Error::Access {
errmsg: "Error loading resource handles", errmsg: "Error loading resource handles",
dev: dev.dev_path(), dev: dev.dev_path(),
@ -154,6 +160,7 @@ impl<A: AsRawFd + 'static> DrmDevice<A> {
#[cfg(feature = "backend_session")] #[cfg(feature = "backend_session")]
links: RefCell::new(Vec::new()), links: RefCell::new(Vec::new()),
has_universal_planes, has_universal_planes,
has_monotonic_timestamps,
resources, resources,
logger: log, logger: log,
token: Token::invalid(), token: Token::invalid(),
@ -311,12 +318,30 @@ pub enum DrmEvent {
Error(Error), Error(Error),
} }
/// Timing metadata for page-flip events
#[derive(Debug)]
pub struct EventMetadata {
/// The time the frame flip happend
pub time: Time,
/// The sequence number of the frame
pub sequence: u32,
}
/// Either a realtime or monotonic timestamp
#[derive(Debug)]
pub enum Time {
/// Monotonic time stamp
Monotonic(Instant),
/// Realtime time stamp
Realtime(SystemTime),
}
impl<A> EventSource for DrmDevice<A> impl<A> EventSource for DrmDevice<A>
where where
A: AsRawFd + 'static, A: AsRawFd + 'static,
{ {
type Event = DrmEvent; type Event = DrmEvent;
type Metadata = (); type Metadata = Option<EventMetadata>;
type Ret = (); type Ret = ();
fn process_events<F>( fn process_events<F>(
@ -336,7 +361,22 @@ where
for event in events { for event in events {
if let Event::PageFlip(event) = event { if let Event::PageFlip(event) = event {
trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc); trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc);
callback(DrmEvent::VBlank(event.crtc), &mut ()); let metadata = EventMetadata {
time: if self.has_monotonic_timestamps {
// There is no way to create an Instant, although the underlying type on unix systems
// is just libc::timespec, which is literally what drm-rs is getting from the kernel and just converting
// into a Duration. So we cheat and initialize a Zero-Instant (because although Instant::ZERO
// exists, its private, so you cannot create abitrary Instants). What we really need is a unix-Ext
// trait for both SystemTime and Instant to convert from a libc::timespec.
//
// But this works for now, although it is quite the hack.
Time::Monotonic(unsafe { std::mem::zeroed::<Instant>() } + event.duration)
} else {
Time::Realtime(SystemTime::UNIX_EPOCH + event.duration)
},
sequence: event.frame,
};
callback(DrmEvent::VBlank(event.crtc), &mut Some(metadata));
} else { } else {
trace!( trace!(
self.logger, self.logger,
@ -353,7 +393,7 @@ where
dev: self.dev_path(), dev: self.dev_path(),
source, source,
}), }),
&mut (), &mut None,
); );
} }
} }

View File

@ -76,7 +76,7 @@ pub mod node;
pub(self) mod session; pub(self) mod session;
pub(self) mod surface; pub(self) mod surface;
pub use device::{DevPath, DrmDevice, DrmEvent}; pub use device::{DevPath, DrmDevice, DrmEvent, EventMetadata as DrmEventMetadata, Time as DrmEventTime};
pub use error::Error as DrmError; pub use error::Error as DrmError;
pub use node::{CreateDrmNodeError, DrmNode, NodeType}; pub use node::{CreateDrmNodeError, DrmNode, NodeType};
#[cfg(feature = "backend_gbm")] #[cfg(feature = "backend_gbm")]

View File

@ -7,7 +7,7 @@ use libc::dev_t;
use std::{ use std::{
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
fs, io, fs, io, mem,
os::unix::prelude::{AsRawFd, IntoRawFd, RawFd}, os::unix::prelude::{AsRawFd, IntoRawFd, RawFd},
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -20,8 +20,7 @@ use nix::{
/// A node which refers to a DRM device. /// A node which refers to a DRM device.
#[derive(Debug)] #[derive(Debug)]
pub struct DrmNode { pub struct DrmNode {
// Always `Some`, None variant is used when taking ownership fd: RawFd,
fd: Option<RawFd>,
dev: dev_t, dev: dev_t,
ty: NodeType, ty: NodeType,
} }
@ -56,11 +55,7 @@ impl DrmNode {
_ => return Err(CreateDrmNodeError::NotDrmNode), _ => return Err(CreateDrmNodeError::NotDrmNode),
}; };
Ok(DrmNode { Ok(DrmNode { fd, dev, ty })
fd: Some(fd),
dev,
ty,
})
} }
/// Returns the type of the DRM node. /// Returns the type of the DRM node.
@ -120,22 +115,22 @@ impl Display for DrmNode {
} }
impl IntoRawFd for DrmNode { impl IntoRawFd for DrmNode {
fn into_raw_fd(mut self) -> RawFd { fn into_raw_fd(self) -> RawFd {
self.fd.take().unwrap() let fd = self.fd;
mem::forget(self);
fd
} }
} }
impl AsRawFd for DrmNode { impl AsRawFd for DrmNode {
fn as_raw_fd(&self) -> RawFd { fn as_raw_fd(&self) -> RawFd {
self.fd.unwrap() self.fd
} }
} }
impl Drop for DrmNode { impl Drop for DrmNode {
fn drop(&mut self) { fn drop(&mut self) {
if let Some(fd) = self.fd { let _ = close(self.fd);
let _ = close(fd);
}
} }
} }

View File

@ -53,12 +53,15 @@ impl DnDGrab {
impl PointerGrab for DnDGrab { impl PointerGrab for DnDGrab {
fn motion( fn motion(
&mut self, &mut self,
_handle: &mut PointerInnerHandle<'_>, handle: &mut PointerInnerHandle<'_>,
location: Point<f64, Logical>, location: Point<f64, Logical>,
focus: Option<(wl_surface::WlSurface, Point<i32, Logical>)>, focus: Option<(wl_surface::WlSurface, Point<i32, Logical>)>,
serial: Serial, serial: Serial,
time: u32, time: u32,
) { ) {
// While the grab is active, no client has pointer focus
handle.motion(location, None, serial, time);
let seat_data = self let seat_data = self
.seat .seat
.user_data() .user_data()
@ -209,7 +212,9 @@ impl PointerGrab for DnDGrab {
source.cancelled(); source.cancelled();
} }
} }
(&mut *self.callback.borrow_mut())(super::DataDeviceEvent::DnDDropped); (&mut *self.callback.borrow_mut())(super::DataDeviceEvent::DnDDropped {
seat: self.seat.clone(),
});
self.icon = None; self.icon = None;
// in all cases abandon the drop // in all cases abandon the drop
// no more buttons are pressed, release the grab // no more buttons are pressed, release the grab

View File

@ -88,13 +88,18 @@ pub enum DataDeviceEvent {
/// The icon the client requested to be used to be associated with the cursor icon /// The icon the client requested to be used to be associated with the cursor icon
/// during the drag'n'drop. /// during the drag'n'drop.
icon: Option<wl_surface::WlSurface>, icon: Option<wl_surface::WlSurface>,
/// The seat on which the DnD operation was started
seat: Seat,
}, },
/// The drag'n'drop action was finished by the user releasing the buttons /// The drag'n'drop action was finished by the user releasing the buttons
/// ///
/// At this point, any pointer icon should be removed. /// At this point, any pointer icon should be removed.
/// ///
/// Note that this event will only be generated for client-initiated drag'n'drop session. /// Note that this event will only be generated for client-initiated drag'n'drop session.
DnDDropped, DnDDropped {
/// The seat on which the DnD action was finished.
seat: Seat,
},
/// A client requested to read the server-set selection /// A client requested to read the server-set selection
SendSelection { SendSelection {
/// the requested mime type /// the requested mime type
@ -356,6 +361,7 @@ pub fn start_dnd<C>(
Rc::new(RefCell::new(callback)), Rc::new(RefCell::new(callback)),
), ),
serial, serial,
0,
); );
} }
} }
@ -445,6 +451,7 @@ where
(&mut *callback.borrow_mut())(DataDeviceEvent::DnDStarted { (&mut *callback.borrow_mut())(DataDeviceEvent::DnDStarted {
source: source.clone(), source: source.clone(),
icon: icon.clone(), icon: icon.clone(),
seat: seat.clone(),
}); });
let start_data = pointer.grab_start_data().unwrap(); let start_data = pointer.grab_start_data().unwrap();
pointer.set_grab( pointer.set_grab(
@ -457,6 +464,7 @@ where
callback.clone(), callback.clone(),
), ),
serial, serial,
0,
); );
return; return;
} }

View File

@ -91,6 +91,76 @@ impl PointerInternal {
} }
} }
fn set_grab<G: PointerGrab + 'static>(&mut self, serial: Serial, grab: G, time: u32) {
self.grab = GrabStatus::Active(serial, Box::new(grab));
// generate a move to let the grab change the focus or move the pointer as result of its initialization
let location = self.location;
let focus = self.focus.clone();
self.motion(location, focus, serial, time);
}
fn unset_grab(&mut self, serial: Serial, time: u32) {
self.grab = GrabStatus::None;
// restore the focus
let location = self.location;
let focus = self.pending_focus.clone();
self.motion(location, focus, serial, time);
}
fn motion(
&mut self,
location: Point<f64, Logical>,
focus: Option<(WlSurface, Point<i32, Logical>)>,
serial: Serial,
time: u32,
) {
// do we leave a surface ?
let mut leave = true;
self.location = location;
if let Some((ref current_focus, _)) = self.focus {
if let Some((ref surface, _)) = focus {
if current_focus.as_ref().equals(surface.as_ref()) {
leave = false;
}
}
}
if leave {
self.with_focused_pointers(|pointer, surface| {
pointer.leave(serial.into(), surface);
if pointer.as_ref().version() >= 5 {
pointer.frame();
}
});
self.focus = None;
(self.image_callback)(CursorImageStatus::Default);
}
// do we enter one ?
if let Some((surface, surface_location)) = focus {
let entered = self.focus.is_none();
// in all cases, update the focus, the coordinates of the surface
// might have changed
self.focus = Some((surface, surface_location));
let (x, y) = (location - surface_location.to_f64()).into();
if entered {
self.with_focused_pointers(|pointer, surface| {
pointer.enter(serial.into(), surface, x, y);
if pointer.as_ref().version() >= 5 {
pointer.frame();
}
})
} else {
// we were on top of a surface and remained on it
self.with_focused_pointers(|pointer, _| {
pointer.motion(time, x, y);
if pointer.as_ref().version() >= 5 {
pointer.frame();
}
})
}
}
}
fn with_focused_pointers<F>(&self, mut f: F) fn with_focused_pointers<F>(&self, mut f: F)
where where
F: FnMut(&WlPointer, &WlSurface), F: FnMut(&WlPointer, &WlSurface),
@ -160,13 +230,13 @@ impl PointerHandle {
/// Change the current grab on this pointer to the provided grab /// Change the current grab on this pointer to the provided grab
/// ///
/// Overwrites any current grab. /// Overwrites any current grab.
pub fn set_grab<G: PointerGrab + 'static>(&self, grab: G, serial: Serial) { pub fn set_grab<G: PointerGrab + 'static>(&self, grab: G, serial: Serial, time: u32) {
self.inner.borrow_mut().grab = GrabStatus::Active(serial, Box::new(grab)); self.inner.borrow_mut().set_grab(serial, grab, time);
} }
/// Remove any current grab on this pointer, resetting it to the default behavior /// Remove any current grab on this pointer, resetting it to the default behavior
pub fn unset_grab(&self) { pub fn unset_grab(&self, serial: Serial, time: u32) {
self.inner.borrow_mut().grab = GrabStatus::None; self.inner.borrow_mut().unset_grab(serial, time);
} }
/// Check if this pointer is currently grabbed with this serial /// Check if this pointer is currently grabbed with this serial
@ -283,6 +353,13 @@ pub struct GrabStartData {
/// rather than trying to guess when the grab will end. /// rather than trying to guess when the grab will end.
pub trait PointerGrab { pub trait PointerGrab {
/// A motion was reported /// A motion was reported
///
/// This method allows you attach additional behavior to a motion event, possibly altering it.
/// You generally will want to invoke `PointerInnerHandle::motion()` as part of your processing. If you
/// don't, the rest of the compositor will behave as if the motion event never occurred.
///
/// Some grabs (such as drag'n'drop, shell resize and motion) unset the focus while they are active,
/// this is achieved by just setting the focus to `None` when invoking `PointerInnerHandle::motion()`.
fn motion( fn motion(
&mut self, &mut self,
handle: &mut PointerInnerHandle<'_>, handle: &mut PointerInnerHandle<'_>,
@ -292,6 +369,10 @@ pub trait PointerGrab {
time: u32, time: u32,
); );
/// A button press was reported /// A button press was reported
///
/// This method allows you attach additional behavior to a button event, possibly altering it.
/// You generally will want to invoke `PointerInnerHandle::button()` as part of your processing. If you
/// don't, the rest of the compositor will behave as if the button event never occurred.
fn button( fn button(
&mut self, &mut self,
handle: &mut PointerInnerHandle<'_>, handle: &mut PointerInnerHandle<'_>,
@ -301,6 +382,10 @@ pub trait PointerGrab {
time: u32, time: u32,
); );
/// An axis scroll was reported /// An axis scroll was reported
///
/// This method allows you attach additional behavior to an axis event, possibly altering it.
/// You generally will want to invoke `PointerInnerHandle::axis()` as part of your processing. If you
/// don't, the rest of the compositor will behave as if the axis event never occurred.
fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame); fn axis(&mut self, handle: &mut PointerInnerHandle<'_>, details: AxisFrame);
/// The data about the event that started the grab. /// The data about the event that started the grab.
fn start_data(&self) -> &GrabStartData; fn start_data(&self) -> &GrabStartData;
@ -317,19 +402,15 @@ impl<'a> PointerInnerHandle<'a> {
/// Change the current grab on this pointer to the provided grab /// Change the current grab on this pointer to the provided grab
/// ///
/// Overwrites any current grab. /// Overwrites any current grab.
pub fn set_grab<G: PointerGrab + 'static>(&mut self, serial: Serial, grab: G) { pub fn set_grab<G: PointerGrab + 'static>(&mut self, serial: Serial, grab: G, time: u32) {
self.inner.grab = GrabStatus::Active(serial, Box::new(grab)); self.inner.set_grab(serial, grab, time);
} }
/// Remove any current grab on this pointer, resetting it to the default behavior /// Remove any current grab on this pointer, resetting it to the default behavior
/// ///
/// This will also restore the focus of the underlying pointer /// This will also restore the focus of the underlying pointer
pub fn unset_grab(&mut self, serial: Serial, time: u32) { pub fn unset_grab(&mut self, serial: Serial, time: u32) {
self.inner.grab = GrabStatus::None; self.inner.unset_grab(serial, time);
// restore the focus
let location = self.current_location();
let focus = self.inner.pending_focus.clone();
self.motion(location, focus, serial, time);
} }
/// Access the current focus of this pointer /// Access the current focus of this pointer
@ -368,51 +449,7 @@ impl<'a> PointerInnerHandle<'a> {
serial: Serial, serial: Serial,
time: u32, time: u32,
) { ) {
// do we leave a surface ? self.inner.motion(location, focus, serial, time);
let mut leave = true;
self.inner.location = location;
if let Some((ref current_focus, _)) = self.inner.focus {
if let Some((ref surface, _)) = focus {
if current_focus.as_ref().equals(surface.as_ref()) {
leave = false;
}
}
}
if leave {
self.inner.with_focused_pointers(|pointer, surface| {
pointer.leave(serial.into(), surface);
if pointer.as_ref().version() >= 5 {
pointer.frame();
}
});
self.inner.focus = None;
(self.inner.image_callback)(CursorImageStatus::Default);
}
// do we enter one ?
if let Some((surface, surface_location)) = focus {
let entered = self.inner.focus.is_none();
// in all cases, update the focus, the coordinates of the surface
// might have changed
self.inner.focus = Some((surface, surface_location));
let (x, y) = (location - surface_location.to_f64()).into();
if entered {
self.inner.with_focused_pointers(|pointer, surface| {
pointer.enter(serial.into(), surface, x, y);
if pointer.as_ref().version() >= 5 {
pointer.frame();
}
})
} else {
// we were on top of a surface and remained on it
self.inner.with_focused_pointers(|pointer, _| {
pointer.motion(time, x, y);
if pointer.as_ref().version() >= 5 {
pointer.frame();
}
})
}
}
} }
/// Notify that a button was pressed /// Notify that a button was pressed
@ -687,6 +724,7 @@ impl PointerGrab for DefaultGrab {
location: handle.current_location(), location: handle.current_location(),
}, },
}, },
time,
); );
} }
} }