wvr_server: context menu mouse fixes

This commit is contained in:
galister 2026-07-04 19:39:19 +09:00
parent f0f3402650
commit 9b3f315ff1
4 changed files with 251 additions and 17 deletions

View File

@ -3,10 +3,13 @@ use std::{io::Read, os::unix::net::UnixStream, path::PathBuf, sync::Arc};
use anyhow::Context;
use glam::Vec2;
use smithay::{
backend::input::Keycode,
input::{keyboard::KeyboardHandle, pointer::PointerHandle},
reexports::wayland_server::{self, protocol::wl_surface::WlSurface},
utils::SerialCounter,
backend::input::{ButtonState, Keycode},
input::{
keyboard::KeyboardHandle,
pointer::{ButtonEvent, MotionEvent, PointerHandle},
},
reexports::wayland_server::{self, Resource, protocol::wl_surface::WlSurface},
utils::{Logical, Point, SerialCounter},
};
use xkbcommon::xkb;
@ -211,29 +214,80 @@ impl WayVRCompositor {
.context("Failed to set keymap")
}
pub fn send_mouse_move_unfocused(&mut self, global_pos: Vec2) {
let location: Point<f64, Logical> = (global_pos.x as f64, global_pos.y as f64).into();
self.seat_pointer.motion(
&mut self.state,
None,
&MotionEvent {
location,
serial: self.serial_counter.next_serial(),
time: super::time::get_millis() as u32,
},
);
self.seat_pointer.frame(&mut self.state);
}
pub fn send_mouse_move_to_surface(
&mut self,
surface: WlSurface,
global_pos: Vec2,
surface_origin: Vec2,
) {
use smithay::input::pointer::MotionEvent;
use smithay::utils::{Logical, Point};
let location: Point<f64, Logical> = (global_pos.x as f64, global_pos.y as f64).into();
let focus_location: Point<f64, Logical> =
(surface_origin.x as f64, surface_origin.y as f64).into();
let serial = self.serial_counter.next_serial();
let time = super::time::get_millis() as u32;
self.seat_pointer.motion(
&mut self.state,
Some((surface, focus_location)),
&MotionEvent {
location,
serial: self.serial_counter.next_serial(),
time: 0,
serial,
time,
},
);
self.seat_pointer.frame(&mut self.state);
}
pub fn send_pointer_button(&mut self, index: super::MouseIndex, pressed: bool) {
let state = if pressed {
ButtonState::Pressed
} else {
ButtonState::Released
};
let serial = self.serial_counter.next_serial();
let time = super::time::get_millis() as u32;
let button = match index {
super::MouseIndex::Left => 0x110,
super::MouseIndex::Center => 0x112,
super::MouseIndex::Right => 0x111,
};
log::trace!(
"pointer button: button={button:#x} pressed={pressed} focus={:?}",
self.seat_pointer.current_focus().map(|s| s.id())
);
self.seat_pointer.button(
&mut self.state,
&ButtonEvent {
serial,
time,
button,
state,
},
);
self.seat_pointer.frame(&mut self.state);
}
}

View File

@ -1,7 +1,11 @@
use anyhow::Context;
use smithay::backend::allocator::dmabuf::Dmabuf;
use smithay::backend::renderer::{BufferType, buffer_type};
use smithay::desktop::{PopupKind, PopupManager};
use smithay::desktop::{
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy,
find_popup_root_surface,
};
use smithay::input::pointer::Focus;
use smithay::input::{Seat, SeatHandler, SeatState};
use smithay::output::Output;
use smithay::reexports::rustix::fs::{OFlags, fcntl_setfl};
@ -400,8 +404,59 @@ impl XdgShellHandler for Application {
self.popup_manager.cleanup();
}
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
// Handle popup grab here
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
log::info!(
"xdg_popup.grab: surface={:?} serial={:?}",
surface.wl_surface().id(),
serial
);
let popup = PopupKind::Xdg(surface.clone());
let Ok(root_surface) = find_popup_root_surface(&popup) else {
log::warn!("xdg_popup.grab: could not find popup root surface");
return;
};
let Some(seat) = Seat::<Application>::from_resource(&seat) else {
log::warn!("xdg_popup.grab: unknown seat");
return;
};
let root_focus = root_surface;
let Ok(mut grab) = self
.popup_manager
.grab_popup::<Application>(root_focus, popup, &seat, serial)
else {
log::debug!("xdg_popup.grab denied");
return;
};
if let Some(keyboard) = seat.get_keyboard() {
if keyboard.is_grabbed()
&& !(keyboard.has_grab(serial)
|| keyboard.has_grab(grab.previous_serial().unwrap_or(serial)))
{
grab.ungrab(PopupUngrabStrategy::All);
return;
}
keyboard.set_focus(self, grab.current_grab(), serial);
keyboard.set_grab(self, PopupKeyboardGrab::new(&grab), serial);
}
if let Some(pointer) = seat.get_pointer() {
if pointer.is_grabbed()
&& !(pointer.has_grab(serial)
|| pointer.has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial())))
{
grab.ungrab(PopupUngrabStrategy::All);
return;
}
pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep);
}
}
fn reposition_request(

View File

@ -119,6 +119,7 @@ pub struct WvrServerState {
overlay_to_window: SecondaryMap<OverlayID, window::WindowHandle>,
}
#[derive(Clone, Copy, PartialEq)]
pub enum MouseIndex {
Left,
Center,
@ -581,6 +582,10 @@ impl WvrServerState {
self.window_to_overlay.get(&window).copied()
}
pub fn pointer_is_grabbed(&self) -> bool {
self.manager.seat_pointer.is_grabbed()
}
pub fn send_mouse_move_to_surface(
&mut self,
surface: WlSurface,
@ -604,6 +609,50 @@ impl WvrServerState {
});
}
pub fn send_mouse_button_to_surface(
&mut self,
surface: WlSurface,
global_pos: Vec2,
surface_origin: Vec2,
hover_window: window::WindowHandle,
index: MouseIndex,
pressed: bool,
) {
self.manager
.send_mouse_move_to_surface(surface, global_pos, surface_origin);
self.wm.mouse = Some(window::MouseState {
hover_window,
x: global_pos.x as u32,
y: global_pos.y as u32,
});
self.manager.send_pointer_button(index, pressed);
}
pub fn send_mouse_button_main_surface(
&mut self,
click_freeze: i32,
hover_window: window::WindowHandle,
global_pos: Vec2,
index: MouseIndex,
pressed: bool,
) {
if pressed {
self.mouse_freeze = Instant::now() + Duration::from_millis(click_freeze.max(0) as u64);
}
self.manager.send_mouse_move_unfocused(global_pos);
self.wm.mouse = Some(window::MouseState {
hover_window,
x: global_pos.x.max(0.0) as u32,
y: global_pos.y.max(0.0) as u32,
});
self.manager.send_pointer_button(index, pressed);
}
pub fn send_mouse_move(&mut self, handle: window::WindowHandle, x: u32, y: u32) {
if self.mouse_freeze > Instant::now() {
return;

View File

@ -129,6 +129,7 @@ pub struct WvrWindowBackend {
icon: Arc<str>,
pipeline: Option<ScreenPipeline>,
popups_pipeline: Arc<WGfxPipeline<Vert2Uv>>,
popup_outside_button: Option<wayvr::MouseIndex>,
interaction_transform: Option<Affine2>,
window: WindowHandle,
popups: Vec<RenderedPopup>,
@ -230,6 +231,7 @@ impl WvrWindowBackend {
window,
popups: vec![],
popups_pipeline,
popup_outside_button: None,
interaction_transform: None,
just_resumed: false,
meta: None,
@ -317,6 +319,11 @@ impl WvrWindowBackend {
)
}
fn unclamped_client_pos_from_hit(&self, hit: &input::PointerHit) -> Vec2 {
let transformed = self.transformed_uv_from_hit(hit);
self.client_pos_from_transformed_uv(transformed)
}
fn is_inside_client_area(&self, transformed: Vec2) -> bool {
self.uv_range.contains(&transformed.x) && self.uv_range.contains(&transformed.y)
}
@ -724,20 +731,89 @@ impl OverlayBackend for WvrWindowBackend {
}
fn on_pointer(&mut self, app: &mut state::AppState, hit: &input::PointerHit, pressed: bool) {
match self.hit_target(hit) {
let Some(index) = Self::mouse_index_from_mode(hit.mode) else {
return;
};
let target = self.hit_target(hit);
let outside_pos = self.unclamped_client_pos_from_hit(hit);
// if the press was consumed to dismiss a popup, consume the matching release too.
if !pressed && self.popup_outside_button == Some(index) {
self.popup_outside_button = None;
let click_freeze = app.session.config.click_freeze_time_ms;
app.wvr_server
.as_mut()
.unwrap()
.send_mouse_button_main_surface(
click_freeze,
self.window,
outside_pos,
index,
false,
);
return;
}
let popup_grab_active = !self.popups.is_empty()
&& app
.wvr_server
.as_ref()
.is_some_and(|server| server.pointer_is_grabbed());
let outside_grabbed_popup =
popup_grab_active && !matches!(&target, Some(WvrHitTarget::Popup { .. }));
if outside_grabbed_popup {
if pressed {
self.popup_outside_button = Some(index);
}
let click_freeze = app.session.config.click_freeze_time_ms;
app.wvr_server
.as_mut()
.unwrap()
.send_mouse_button_main_surface(
click_freeze,
self.window,
outside_pos,
index,
pressed,
);
return;
}
match target {
Some(WvrHitTarget::Panel(hit2)) => {
self.panel_hovered = true;
self.panel.on_pointer(app, &hit2, pressed);
}
Some(WvrHitTarget::Popup { .. }) | Some(WvrHitTarget::Toplevel { .. }) => {
let Some(index) = Self::mouse_index_from_mode(hit.mode) else {
return;
};
Some(WvrHitTarget::Popup {
surface,
global_pos,
surface_origin,
}) => {
let wvr_server = app.wvr_server.as_mut().unwrap();
wvr_server.send_mouse_button_to_surface(
surface,
global_pos,
surface_origin,
self.window,
index,
pressed,
);
}
Some(WvrHitTarget::Toplevel { pos }) => {
let click_freeze = app.session.config.click_freeze_time_ms;
let wvr_server = app.wvr_server.as_mut().unwrap();
// normal toplevel click path, only when no popup grab is active.
wvr_server.send_mouse_move(self.window, pos.x as u32, pos.y as u32);
if pressed {
wvr_server.send_mouse_down(click_freeze, self.window, index);
} else {