relative mouse, dynamic decor, refactors

This commit is contained in:
galister 2026-07-15 17:53:45 +09:00
parent 16325040a5
commit ee7be500af
13 changed files with 403 additions and 169 deletions

View File

@ -5,6 +5,7 @@
<elements>
<div flex_grow="1" height="48">
<rectangle
id="rect"
flex_grow="1" height="48"
padding="8" border="2"
round="8"
@ -13,7 +14,7 @@
color="background"
border_color="outline"
>
<label id="label_title" margin_left="8" color="on_background" size="22" weight="bold" />
<label id="title" text="~title" margin_left="8" color="on_background" size="22" weight="bold" />
<div gap="4">
<Button macro="window_button" _release="::EditToggle">
<sprite macro="window_button_icon" src="watch/edit.svg" />

View File

@ -102,6 +102,11 @@ pub enum SpawnPos {
Parent(OverlaySelector),
}
pub enum GlobalChange {
Settings,
Keyboard,
}
pub type ModifyOverlayTask = dyn FnOnce(&mut AppState, &mut OverlayWindowConfig) + Send;
pub type CreateOverlayTask = dyn FnOnce(&mut AppState) -> Option<OverlayWindowConfig> + Send;
pub enum OverlayTask {
@ -116,8 +121,7 @@ pub enum OverlayTask {
ToggleDashboard,
ShowHide,
CleanupMirrors,
SettingsChanged,
KeyboardChanged,
GlobalChange(GlobalChange),
Modify(OverlaySelector, Box<ModifyOverlayTask>),
Spawn(OverlaySelector, SpawnPos, Box<CreateOverlayTask>),
ModifyPanel(ModifyPanelTask),

View File

@ -7,12 +7,12 @@ use std::{
};
use anyhow::Context;
use glam::Vec2;
use glam::{DVec2, Vec2};
use smithay::{
backend::input::{Axis, AxisSource, ButtonState, Keycode},
input::{
keyboard::KeyboardHandle,
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerHandle},
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerHandle, RelativeMotionEvent},
},
reexports::wayland_server::{self, protocol::wl_surface::WlSurface},
utils::{Logical, Point, SerialCounter},
@ -262,8 +262,16 @@ impl WayVRCompositor {
.context("Failed to set keymap")
}
pub fn send_mouse_move(&mut self, focus: Option<(WlSurface, Vec2)>, global_pos: Vec2) {
pub fn send_mouse_move(
&mut self,
focus: Option<(WlSurface, Vec2)>,
global_pos: DVec2,
delta: DVec2,
delta_unaccel: DVec2,
) {
let location: Point<f64, Logical> = (global_pos.x as f64, global_pos.y as f64).into();
let delta: Point<f64, Logical> = (delta.x, delta.y).into();
let delta_unaccel: Point<f64, Logical> = (delta_unaccel.x, delta_unaccel.y).into();
let focus = focus.map(|(surface, origin)| {
let focus_location: Point<f64, Logical> = (origin.x as f64, origin.y as f64).into();
@ -273,13 +281,22 @@ impl WayVRCompositor {
self.seat_pointer.motion(
&mut self.state,
focus,
focus.clone(),
&MotionEvent {
location,
serial: self.serial_counter.next_serial(),
time: super::time::get_millis() as u32,
},
);
self.seat_pointer.relative_motion(
&mut self.state,
focus,
&RelativeMotionEvent {
delta,
delta_unaccel,
utime: super::time::get_millis(),
},
);
self.seat_pointer.frame(&mut self.state);
}
@ -312,7 +329,44 @@ impl WayVRCompositor {
self.seat_pointer.frame(&mut self.state);
}
pub fn send_pointer_axis_wheel(&mut self, delta: super::WheelDelta) {
pub fn send_pointer_axis_wheel_raw(&mut self, delta: super::WheelDelta) {
let time = super::time::get_millis() as u32;
let v120_x = delta.x as i32;
let v120_y = delta.y as i32;
if v120_x == 0 && v120_y == 0 {
return;
}
// 15 logical axis units for one wheel detent of 120 v120 units
const AXIS_VALUE_PER_DETENT: f64 = 15.0;
let mut frame = AxisFrame::new(time).source(AxisSource::Wheel);
if v120_x != 0 {
frame = frame
.value(
Axis::Horizontal,
f64::from(v120_x) / 120.0 * AXIS_VALUE_PER_DETENT,
)
.v120(Axis::Horizontal, v120_x);
}
if v120_y != 0 {
frame = frame
.value(
Axis::Vertical,
f64::from(v120_y) / 120.0 * AXIS_VALUE_PER_DETENT,
)
.v120(Axis::Vertical, v120_y);
}
self.seat_pointer.axis(&mut self.state, frame);
self.seat_pointer.frame(&mut self.state);
}
pub fn send_pointer_axis_wheel_accumulated(&mut self, delta: super::WheelDelta) {
let time = super::time::get_millis() as u32;
let multiplier = 32.0;

View File

@ -5,7 +5,7 @@ use smithay::desktop::{
PopupKeyboardGrab, PopupKind, PopupManager, PopupPointerGrab, PopupUngrabStrategy,
find_popup_root_surface, get_popup_toplevel_coords,
};
use smithay::input::pointer::Focus;
use smithay::input::pointer::{CursorImageStatus, Focus};
use smithay::input::{Seat, SeatHandler, SeatState};
use smithay::output::Output;
use smithay::reexports::rustix::fs::{OFlags, fcntl_setfl};
@ -24,6 +24,7 @@ use smithay::wayland::dmabuf::{
};
use smithay::wayland::fractional_scale::with_fractional_scale;
use smithay::wayland::output::OutputHandler;
use smithay::wayland::relative_pointer::RelativePointerManagerState;
use smithay::wayland::selection::data_device::{
ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, ServerDndGrabHandler,
set_data_device_focus, set_data_device_selection,
@ -45,8 +46,8 @@ use smithay::wayland::viewporter::ViewporterState;
use smithay::{
delegate_compositor, delegate_data_control, delegate_data_device, delegate_dmabuf,
delegate_ext_data_control, delegate_kde_decoration, delegate_output,
delegate_primary_selection, delegate_seat, delegate_shm, delegate_single_pixel_buffer,
delegate_viewporter, delegate_xdg_decoration, delegate_xdg_shell,
delegate_primary_selection, delegate_relative_pointer, delegate_seat, delegate_shm,
delegate_single_pixel_buffer, delegate_viewporter, delegate_xdg_decoration, delegate_xdg_shell,
};
use std::collections::{HashMap, HashSet};
use std::fs::File;
@ -80,6 +81,8 @@ pub struct Application {
#[allow(dead_code)]
pub xdg_decoration_state: XdgDecorationState,
pub kde_decoration_state: KdeDecorationState,
#[allow(dead_code)]
pub relative_pointer_state: RelativePointerManagerState,
pub wayvr_tasks: SyncEventQueue<WayVRTask>,
pub popup_manager: PopupManager,
#[allow(dead_code)]
@ -87,6 +90,7 @@ pub struct Application {
pub display_handle: DisplayHandle,
pub redraw_requests: HashSet<ObjectId>,
pub pending_frame_callbacks: HashMap<ObjectId, Vec<wl_callback::WlCallback>>,
pub cursor_image: CursorImageStatus,
}
impl Application {
@ -334,11 +338,8 @@ impl SeatHandler for Application {
set_primary_focus(dh, seat, client);
}
fn cursor_image(
&mut self,
_seat: &Seat<Self>,
_image: smithay::input::pointer::CursorImageStatus,
) {
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
self.cursor_image = image;
}
}
@ -581,6 +582,13 @@ impl XdgShellHandler for Application {
.send(WayVRTask::MinimizeRequest(client.id(), surface.clone()));
}
}
fn title_changed(&mut self, surface: ToplevelSurface) {
if let Some(client) = surface.wl_surface().client() {
self.wayvr_tasks
.send(WayVRTask::TitleChange(client.id(), surface.clone()));
}
}
}
impl ShmHandler for Application {
@ -683,6 +691,7 @@ delegate_ext_data_control!(Application);
delegate_xdg_decoration!(Application);
delegate_kde_decoration!(Application);
delegate_single_pixel_buffer!(Application);
delegate_relative_pointer!(Application);
const fn wl_transform_to_frame_transform(
transform: wl_output::Transform,

View File

@ -24,8 +24,6 @@ use std::{
time::{Duration, Instant},
};
pub const SEAT_NAME: &str = "wayvr";
const IGNORE_PREFIX: &str = "WayVR";
const WATCHDOG_TIMEOUT: Duration = Duration::from_millis(5000);
const POLL_TIMEOUT_MS: i32 = 20;
@ -51,6 +49,8 @@ pub enum CapturedEvent {
PointerMotion {
dx: f64,
dy: f64,
dx_raw: f64,
dy_raw: f64,
},
PointerAxis {
horizontal_v120: i32,
@ -319,10 +319,10 @@ fn worker_main(
};
let mut libinput = Libinput::new_with_udev(interface);
if libinput.udev_assign_seat(SEAT_NAME).is_err() {
if libinput.udev_assign_seat("seat0").is_err() {
let _ = init_tx.send(Err(io::Error::new(
io::ErrorKind::Other,
format!("failed to assign libinput seat {SEAT_NAME:?}"),
format!("failed to assign libinput seat"),
)));
return;
}
@ -550,9 +550,16 @@ fn process_libinput_event(
Event::Pointer(PointerEvent::Motion(event)) if emit => {
let dx = event.dx();
let dy = event.dy();
let dx_raw = event.dx_unaccelerated();
let dy_raw = event.dy_unaccelerated();
if (dx != 0.0 || dy != 0.0)
&& event_tx
.send(CapturedEvent::PointerMotion { dx, dy })
.send(CapturedEvent::PointerMotion {
dx,
dy,
dx_raw,
dy_raw,
})
.is_err()
{
return ProcessResult::ReceiverGone;
@ -805,6 +812,9 @@ fn check_key_combo(pressed: &HashSet<u16>) -> Option<KeyCombo> {
let alt =
pressed.contains(&KeyCode::KEY_LEFTALT.0) || pressed.contains(&KeyCode::KEY_RIGHTALT.0);
let ctrl =
pressed.contains(&KeyCode::KEY_LEFTCTRL.0) || pressed.contains(&KeyCode::KEY_RIGHTCTRL.0);
if alt && pressed.contains(&KeyCode::KEY_F4.0) {
return Some(KeyCombo::AltF4);
}
@ -813,9 +823,6 @@ fn check_key_combo(pressed: &HashSet<u16>) -> Option<KeyCombo> {
return Some(KeyCombo::AltTab);
}
let ctrl =
pressed.contains(&KeyCode::KEY_LEFTCTRL.0) || pressed.contains(&KeyCode::KEY_RIGHTCTRL.0);
if ctrl && alt && pressed.contains(&KeyCode::KEY_DELETE.0) {
return Some(KeyCombo::CtrlAltDel);
}

View File

@ -16,7 +16,7 @@ use slotmap::SecondaryMap;
use smallvec::SmallVec;
use smithay::{
desktop::PopupManager,
input::{SeatState, keyboard::XkbConfig},
input::{SeatState, keyboard::XkbConfig, pointer::CursorImageStatus},
output::{Mode, Output},
reexports::{
wayland_protocols_misc::server_decoration::server::org_kde_kwin_server_decoration_manager as kde_decoration,
@ -30,6 +30,7 @@ use smithay::{
wayland::{
compositor::{self, SurfaceData, with_states},
dmabuf::{DmabufFeedbackBuilder, DmabufState},
relative_pointer::RelativePointerManagerState,
selection::{
data_device::DataDeviceState, ext_data_control as selection_ext,
primary_selection::PrimarySelectionState, wlr_data_control as selection_wlr,
@ -65,19 +66,19 @@ use crate::{
task::{OverlayTask, SpawnPos, TaskContainer, TaskType, ToggleMode},
wayvr::{
image_importer::ImageImporter,
input_capture::{InputCapture, SEAT_NAME},
input_capture::InputCapture,
process::{KillSignal, Process},
},
},
graphics::{ExtentExt, WGfxExtras},
ipc::{event_queue::SyncEventQueue, ipc_server, signal::WayVRSignal},
overlays::wayvr::create_wl_window_overlay,
overlays::wayvr::{WvrCommand, create_wl_window_overlay},
state::AppState,
subsystem::{
dbus::DbusConnector,
hid::{MODS_TO_KEYS, WheelDelta},
},
windowing::{OverlayID, OverlaySelector},
windowing::{OverlayID, OverlaySelector, backend::OverlayEventData},
};
pub use hit_test::{WvrHitContext, WvrHitTarget, build_hit_context};
@ -117,6 +118,7 @@ pub enum WayVRTask {
NewToplevel(ClientId, ToplevelSurface),
DropToplevel(ClientId, ToplevelSurface),
MinimizeRequest(ClientId, ToplevelSurface),
TitleChange(ClientId, ToplevelSurface),
NewExternalProcess(ExternalProcessRequest),
ProcessTerminationRequest(process::ProcessHandle, KillSignal),
CloseWindowRequest(window::WindowHandle),
@ -158,7 +160,7 @@ pub enum TickTask {
const KEY_REPEAT_DELAY: i32 = 200;
const KEY_REPEAT_RATE: i32 = 50;
const WAYVR_SCREEN_RES: [i32; 2] = [2560, 2560];
const WAYVR_SCREEN_RES: [i32; 2] = [2560, 1440];
impl WvrServerState {
pub fn new(
@ -178,7 +180,7 @@ impl WvrServerState {
let shm = ShmState::new::<Application>(&dh, Vec::new());
let data_device = DataDeviceState::new::<Application>(&dh);
let primary_selection_state = PrimarySelectionState::new::<Application>(&dh);
let mut seat = seat_state.new_wl_seat(&dh, SEAT_NAME);
let mut seat = seat_state.new_wl_seat(&dh, "wayvr");
let ext_data_control_state = selection_ext::DataControlState::new::<Application, _>(
&dh,
@ -194,6 +196,7 @@ impl WvrServerState {
let xdg_decoration_state = XdgDecorationState::new::<Application>(&dh);
let kde_decoration_state =
KdeDecorationState::new::<Application>(&dh, kde_decoration::Mode::Server);
let relative_pointer_state = RelativePointerManagerState::new::<Application>(&dh);
let viewporter = ViewporterState::new::<Application>(&dh);
let dummy_milli_hz = 60000; /* refresh rate in millihertz */
@ -277,12 +280,14 @@ impl WvrServerState {
ext_data_control_state,
xdg_decoration_state,
kde_decoration_state,
relative_pointer_state,
wayvr_tasks: tasks.clone(),
dmabuf_state,
popup_manager: PopupManager::default(),
viewporter,
redraw_requests: HashSet::new(),
pending_frame_callbacks: HashMap::new(),
cursor_image: CursorImageStatus::default_named(),
};
Ok(Self {
@ -564,6 +569,29 @@ impl WvrServerState {
}
}
}
WayVRTask::TitleChange(client_id, toplevel) => {
for client in &wvr_server.manager.clients {
if client.client.id() != client_id {
continue;
}
let Some(window_handle) = wvr_server.wm.find_window_handle(&toplevel)
else {
log::warn!("MinimizeRequest: Couldn't find matching window handle");
continue;
};
if let Some(oid) = wvr_server.window_to_overlay.get(&window_handle) {
app.tasks.enqueue(TaskType::Overlay(OverlayTask::Modify(
OverlaySelector::Id(*oid),
Box::new(|app, owc| {
let _ = owc.backend.notify(
app,
OverlayEventData::WvrCommand(WvrCommand::ReloadTitle),
);
}),
)));
}
}
}
WayVRTask::ProcessTerminationRequest(process_handle, signal) => {
if let Some(process) = wvr_server.processes.get_mut(&process_handle) {
process.kill(signal);
@ -728,6 +756,7 @@ impl WvrServerState {
};
let mut mouse_delta = DVec2::ZERO;
let mut mouse_delta_raw = DVec2::ZERO;
for ev in input_capture.drain_events() {
match ev {
@ -738,61 +767,29 @@ impl WvrServerState {
let Some(mouse_index) = Self::button_to_mouse_index(button) else {
continue;
};
let Some(ref hover) = self.wm.mouse else {
continue;
};
let Some(window) = self.wm.windows.get(&hover.hover_window) else {
continue;
};
let toplevel = window.toplevel.wl_surface().clone();
let inner_extent = with_states(&toplevel, |states| {
SurfaceBufWithImage::get_from_surface(states)
.map(|s| s.image.extent_u32arr())
.unwrap_or([1, 1])
});
let Some(hit_ctx) = build_hit_context(
&toplevel,
&self.manager.state.popup_manager,
inner_extent,
) else {
continue;
};
let pos = Vec2::new(hover.x as f32, hover.y as f32);
let target = hit_ctx.hit_target_at(pos);
let focus_target = self.hit_target_to_focus(
target.unwrap_or(WvrHitTarget::Toplevel { pos }),
hover.hover_window,
pos,
);
let click_freeze = 0;
self.send_mouse_button(
focus_target,
pos,
hover.hover_window,
mouse_index,
pressed,
click_freeze,
);
self.manager.send_pointer_button(mouse_index, pressed);
}
input_capture::CapturedEvent::PointerMotion { dx, dy } => {
input_capture::CapturedEvent::PointerMotion {
dx,
dy,
dx_raw,
dy_raw,
} => {
mouse_delta += DVec2 { x: dx, y: dy };
mouse_delta_raw += DVec2 {
x: dx_raw,
y: dy_raw,
};
}
input_capture::CapturedEvent::PointerAxis {
horizontal_v120,
vertical_v120,
} => {
let Some(ref hover) = self.wm.mouse else {
continue;
};
let delta = WheelDelta {
x: horizontal_v120 as f32,
y: vertical_v120 as f32,
};
self.send_mouse_scroll(
hover.hover_window,
Vec2::new(hover.x as f32, hover.y as f32),
delta,
);
self.manager.send_pointer_axis_wheel_raw(delta);
}
input_capture::CapturedEvent::UngrabbedAll => {
self.manager.release_all_keys();
@ -806,7 +803,7 @@ impl WvrServerState {
self.close_window(window_handle);
}
}
input_capture::KeyCombo::AltTab => {}
input_capture::KeyCombo::AltTab => self.alt_tab(),
input_capture::KeyCombo::CtrlAltDel => { /* not exposed to us */ }
}
}
@ -819,10 +816,6 @@ impl WvrServerState {
break 'mouse_update;
};
let new_x = hover.x as f64 + mouse_delta.x;
let new_y = hover.y as f64 + mouse_delta.y;
let new_pos = Vec2::new(new_x as f32, new_y as f32);
let Some(window) = self.wm.windows.get(&hover.hover_window) else {
break 'mouse_update;
};
@ -837,17 +830,38 @@ impl WvrServerState {
else {
break 'mouse_update;
};
let target = hit_ctx.hit_target_at(new_pos);
let new_x = (hover.pos.x + mouse_delta.x).clamp(0., inner_extent[0] as f64);
let new_y = (hover.pos.y + mouse_delta.y).clamp(0., inner_extent[1] as f64);
let new_pos = DVec2::new(new_x, new_y);
let new_pos_f32 = Vec2::new(new_x as f32, new_y as f32);
let target = hit_ctx.hit_target_at(new_pos_f32);
let focus_target = self.hit_target_to_focus(
target.unwrap_or(WvrHitTarget::Toplevel { pos: new_pos }),
target.unwrap_or(WvrHitTarget::Toplevel { pos: new_pos_f32 }),
hover.hover_window,
new_pos_f32,
);
self.send_mouse_move_relative(
focus_target,
new_pos,
mouse_delta,
mouse_delta_raw,
hover.hover_window,
new_pos,
);
self.send_mouse_move(focus_target, new_pos, hover.hover_window);
}
}
}
fn alt_tab(&mut self) {
let mut windows: Vec<_> = self.wm.windows.iter().collect();
if windows.is_empty() {
return;
}
//TODO
}
fn button_to_mouse_index(button: u32) -> Option<MouseIndex> {
match button {
272 => Some(MouseIndex::Left),
@ -880,57 +894,25 @@ impl WvrServerState {
}
self.has_input_focus = has_focus;
}
pub fn send_mouse_move(
&mut self,
target: PointerFocusTarget,
global_pos: Vec2,
hover_window: window::WindowHandle,
) {
if self.mouse_freeze > Instant::now() {
return;
if !has_focus {
self.wm.mouse = None;
}
let focus = match target {
PointerFocusTarget::Surface { surface, origin } => Some((surface, origin)),
PointerFocusTarget::Toplevel => {
let surface = self
.wm
.windows
.get(&hover_window)
.map(|x| x.toplevel.wl_surface().clone());
surface.clone().map(|surface| (surface, Vec2::ZERO))
}
PointerFocusTarget::None => None,
};
self.manager.send_mouse_move(focus, global_pos);
self.mouse_freeze = Instant::now() + Duration::from_millis(1);
self.wm.mouse = Some(window::MouseState {
hover_window,
x: global_pos.x as u32,
y: global_pos.y as u32,
});
}
pub fn send_mouse_button(
pub fn get_focused_window(&self) -> Option<window::WindowHandle> {
if !self.has_input_focus {
return None;
}
self.wm.mouse.as_ref().map(|x| x.hover_window)
}
fn get_mouse_focus(
&mut self,
target: PointerFocusTarget,
global_pos: Vec2,
hover_window: window::WindowHandle,
index: MouseIndex,
pressed: bool,
click_freeze: i32,
) {
if pressed {
self.mouse_freeze = Instant::now() + Duration::from_millis(click_freeze.max(0) as u64);
}
let (focus, focus_keyboard) = match target {
) -> (Option<(WlSurface, Vec2)>, Option<WlSurface>) {
match target {
PointerFocusTarget::Surface { surface, origin } => (Some((surface, origin)), None),
PointerFocusTarget::Toplevel => {
let surface = self
@ -952,7 +934,76 @@ impl WvrServerState {
.map(|x| x.toplevel.wl_surface().clone());
(None, pressed.then_some(surface).flatten())
}
}
}
fn get_mouse_relative(&self, global_pos: DVec2, hover_window: window::WindowHandle) -> DVec2 {
let Some(ref mouse) = self.wm.mouse else {
return DVec2::ZERO;
};
if hover_window != mouse.hover_window {
// don't twitch if we just switched windows
return DVec2::ZERO;
}
global_pos - mouse.pos
}
fn send_mouse_move_relative(
&mut self,
target: PointerFocusTarget,
global_pos: DVec2,
delta: DVec2,
delta_unaccel: DVec2,
hover_window: window::WindowHandle,
) {
let (focus, _) = self.get_mouse_focus(target, hover_window, false);
self.manager
.send_mouse_move(focus, global_pos, delta, delta_unaccel);
self.mouse_freeze = Instant::now() + Duration::from_millis(1);
self.wm.mouse = Some(window::MouseState {
hover_window,
pos: global_pos,
});
}
pub fn send_mouse_move(
&mut self,
target: PointerFocusTarget,
global_pos: Vec2,
hover_window: window::WindowHandle,
) {
if self.mouse_freeze > Instant::now() {
return;
}
let global_pos = DVec2::from(global_pos);
let (focus, _) = self.get_mouse_focus(target, hover_window, false);
let linear_delta = self.get_mouse_relative(global_pos, hover_window);
self.manager
.send_mouse_move(focus, global_pos, linear_delta, linear_delta);
self.mouse_freeze = Instant::now() + Duration::from_millis(1);
self.wm.mouse = Some(window::MouseState {
hover_window,
pos: global_pos,
});
}
pub fn send_mouse_button(
&mut self,
target: PointerFocusTarget,
global_pos: Vec2,
hover_window: window::WindowHandle,
index: MouseIndex,
pressed: bool,
click_freeze: i32,
) {
if pressed {
self.mouse_freeze = Instant::now() + Duration::from_millis(click_freeze.max(0) as u64);
}
let (focus, focus_keyboard) = self.get_mouse_focus(target, hover_window, pressed);
if focus_keyboard.is_some() {
self.manager.seat_keyboard.set_focus(
@ -960,16 +1011,19 @@ impl WvrServerState {
focus_keyboard,
self.manager.serial_counter.next_serial(),
);
self.wm.keyboard_focus = Some(hover_window);
}
self.manager.send_mouse_move(focus, global_pos);
let global_pos = DVec2::from(global_pos);
let linear_delta = self.get_mouse_relative(global_pos, hover_window);
self.manager
.send_mouse_move(focus, global_pos, linear_delta, linear_delta);
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,
pos: global_pos,
});
self.manager.send_pointer_button(index, pressed);
}
@ -979,13 +1033,13 @@ impl WvrServerState {
global_pos: Vec2,
delta: WheelDelta,
) {
let global_pos = DVec2::from(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,
pos: global_pos,
});
self.manager.send_pointer_axis_wheel(delta);
self.manager.send_pointer_axis_wheel_accumulated(delta);
}
pub fn send_key(&mut self, virtual_key: u32, down: bool) {

View File

@ -1,5 +1,6 @@
use std::rc::Rc;
use glam::DVec2;
use smithay::utils::{Logical, Size};
use smithay::wayland::shell::xdg::ToplevelSurface;
use wayvr_ipc::packet_server;
@ -107,14 +108,14 @@ impl Window {
#[derive(Debug)]
pub struct MouseState {
pub hover_window: WindowHandle,
pub x: u32,
pub y: u32,
pub pos: DVec2,
}
#[derive(Debug)]
pub struct WindowManager {
pub windows: WindowVec,
pub mouse: Option<MouseState>,
pub keyboard_focus: Option<WindowHandle>,
}
impl WindowManager {
@ -122,6 +123,7 @@ impl WindowManager {
Self {
windows: WindowVec::new(),
mouse: None,
keyboard_focus: None,
}
}

View File

@ -37,7 +37,7 @@ use crate::{
backend::{
XrBackend,
input::{Haptics, HoverResult, PointerHit, PointerMode},
task::{OverlayTask, PlayspaceTask, TaskType, ToggleMode},
task::{GlobalChange, OverlayTask, PlayspaceTask, TaskType, ToggleMode},
wayvr::{
process::{KillSignal, ProcessHandle},
window::WindowHandle,
@ -532,9 +532,12 @@ impl DashInterface<AppState> for DashInterfaceLive {
data.session.config_dirty = true;
match kind {
ConfigChangeKind::OverlayConfig => data
.tasks
.enqueue(TaskType::Overlay(OverlayTask::SettingsChanged)),
ConfigChangeKind::OverlayConfig => {
data.tasks
.enqueue(TaskType::Overlay(OverlayTask::GlobalChange(
GlobalChange::Settings,
)))
}
ConfigChangeKind::EnvironmentBlend => {
#[cfg(feature = "openxr")]
{

View File

@ -9,7 +9,7 @@ use crate::{
KEYMAP_CHANGE,
backend::{
input::{HoverResult, PointerHit},
task::{OverlayTask, TaskType},
task::{GlobalChange, OverlayTask, TaskType},
},
gui::panel::{GuiPanel, overlay_list::OverlayList, set_list::SetList},
overlays::keyboard::builder::create_keyboard_panel,
@ -184,7 +184,9 @@ impl KeyboardBackend {
self.internal_switch_keymap(new_key);
}
app.tasks
.enqueue(TaskType::Overlay(OverlayTask::KeyboardChanged));
.enqueue(TaskType::Overlay(OverlayTask::GlobalChange(
GlobalChange::Keyboard,
)));
Ok(true)
}

View File

@ -4,24 +4,29 @@ use glam::{Affine2, Affine3A, Quat, Vec3, vec2, vec3};
use slotmap::Key;
use smithay::{
desktop::PopupManager,
input::pointer::CursorImageStatus,
reexports::wayland_server::Resource,
utils::{Logical, Size},
wayland::{compositor::with_states, shell::xdg::XdgPopupSurfaceData},
wayland::{
compositor::with_states,
shell::xdg::{XdgPopupSurfaceData, XdgToplevelSurfaceData},
},
};
use vulkano::{
buffer::BufferUsage, image::view::ImageView, pipeline::graphics::color_blend::AttachmentBlend,
};
use wayvr_ipc::packet_client::PositionMode;
use wgui::{
color::{WguiColor, WguiColorName},
components::button::ComponentButton,
event::EventCallback,
event::{CallbackDataCommon, EventCallback},
gfx::{
cmd::WGfxClearMode,
pipeline::{WGfxPipeline, WPipelineCreateInfo},
},
i18n::Translation,
parser::Fetchable,
widget::{EventResult, label::WidgetLabel},
widget::{EventResult, label::WidgetLabel, rectangle::WidgetRectangle},
};
use wlx_capture::frame::{MouseMeta, Transform};
use wlx_common::{
@ -35,7 +40,7 @@ use crate::{
input::{self, HoverResult},
task::{OverlayTask, TaskType},
wayvr::{
self, PointerFocusTarget, SurfaceBufWithImage,
self, PointerFocusTarget, SurfaceBufWithImage, WvrServerState,
hit_test::{
PopupRoot, RenderedSurface, WvrHitContext, WvrHitTarget,
collect_rendered_surface_tree, collect_rendered_surface_tree_at,
@ -65,6 +70,7 @@ use crate::{
#[derive(Clone)]
pub enum WvrCommand {
ReloadTitle,
CloseWindow,
KillProcess(KillSignal),
}
@ -154,6 +160,7 @@ pub struct WvrWindowBackend {
overlay_id: OverlayID,
scale: (f32, f32),
resizable: bool,
had_focus: bool,
}
impl WvrWindowBackend {
@ -220,20 +227,11 @@ impl WvrWindowBackend {
NewGuiPanelParams {
resize_to_parent: true,
on_custom_attrib: Some(on_custom_attrib),
extra_vars: [("title".into(), name.as_ref().into())].into(),
..Default::default()
},
)?;
{
let mut title = panel
.parser_state
.fetch_widget_as::<WidgetLabel>(&panel.layout.state, "label_title")?;
title.set_text_simple(
&mut app.wgui_globals.get(),
Translation::from_raw_text(&name),
);
}
panel.update_layout(app)?;
Ok(Self {
@ -267,6 +265,7 @@ impl WvrWindowBackend {
overlay_id: OverlayID::null(),
scale,
resizable,
had_focus: false,
})
}
@ -349,6 +348,67 @@ impl WvrWindowBackend {
Ok(())
}
fn update_title(&mut self, new_title: Rc<str>) {
let mut common = CallbackDataCommon {
state: &self.panel.layout.state,
alterables: &mut self.panel.layout.alterables,
};
if let Ok(mut title) = self
.panel
.parser_state
.fetch_widget_as::<WidgetLabel>(&self.panel.layout.state, "title")
{
title.set_text(&mut common, Translation::from_raw_text_rc(new_title));
}
}
fn update_decor(&mut self, wvr_server: &mut WvrServerState) {
let now_focused = wvr_server
.get_focused_window()
.is_some_and(|w| w == self.window);
if now_focused == self.had_focus {
return;
}
self.had_focus = now_focused;
let mut common = CallbackDataCommon {
state: &self.panel.layout.state,
alterables: &mut self.panel.layout.alterables,
};
const COLORS: [(WguiColor, WguiColor); 2] = [
(
WguiColorName::Outline.to_wgui_color(),
WguiColorName::OnBackground.to_wgui_color(),
),
(
WguiColorName::Tertiary.to_wgui_color(),
WguiColorName::Tertiary.to_wgui_color(),
),
];
let (rect_col, label_col) = COLORS[now_focused as usize];
if let Ok(mut rect) = self
.panel
.parser_state
.fetch_widget_as::<WidgetRectangle>(&self.panel.layout.state, "rect")
{
rect.set_border_color(&mut common, rect_col);
}
if let Ok(mut title) = self
.panel
.parser_state
.fetch_widget_as::<WidgetLabel>(&self.panel.layout.state, "title")
{
title.set_color(&mut common, label_col, true);
}
}
fn mouse_index_from_mode(mode: input::PointerMode) -> Option<wayvr::MouseIndex> {
match mode {
input::PointerMode::Left => Some(wayvr::MouseIndex::Left),
@ -468,7 +528,6 @@ impl OverlayBackend for WvrWindowBackend {
let surface_id = toplevel.wl_surface().id();
let surfaces = collect_rendered_surface_tree(toplevel.wl_surface());
let should_render_panel = self.panel.should_render(app)?;
let mut popup_roots = Vec::new();
let mut popups = Vec::new();
@ -505,6 +564,8 @@ impl OverlayBackend for WvrWindowBackend {
let mut tree_dirty = false;
if let Some(wvr_server) = app.wvr_server.as_mut() {
self.update_decor(wvr_server);
let state = &mut wvr_server.manager.state;
tree_dirty |= state.take_redraw_request(&surface_id);
tree_dirty |= state.has_pending_frame_callbacks(&surface_id);
@ -518,6 +579,7 @@ impl OverlayBackend for WvrWindowBackend {
}
}
let should_render_panel = self.panel.should_render(app)?;
let force_render = tree_dirty || mem::take(&mut self.just_resumed);
let hit_surfaces = surfaces.clone();
@ -600,8 +662,8 @@ impl OverlayBackend for WvrWindowBackend {
.as_ref()
.filter(|m| m.hover_window == self.window)
.map(|m| MouseMeta {
x: (m.x as f32) / (inner_extent[0] as f32),
y: (m.y as f32) / (inner_extent[1] as f32),
x: (m.pos.x as f32) / (inner_extent[0] as f32),
y: (m.pos.y as f32) / (inner_extent[1] as f32),
});
let dirty = self.mouse != mouse || rendered_surfaces_dirty(&self.popups, &popups);
@ -666,13 +728,20 @@ impl OverlayBackend for WvrWindowBackend {
callback_surfaces.push(&popup.surface_id);
}
if let Some(mouse) = self.mouse.as_ref() {
self.pipeline.as_mut().unwrap().render_mouse(mouse, rdr)?;
}
// frame callbacks for toplevel + subsurf + popup
if let Some(wvr_server) = app.wvr_server.as_mut() {
let state = &mut wvr_server.manager.state;
match state.cursor_image {
CursorImageStatus::Hidden => {}
CursorImageStatus::Named(_) | CursorImageStatus::Surface(_) => {
// FIXME: properly render surface?
if let Some(mouse) = self.mouse.as_ref() {
self.pipeline.as_mut().unwrap().render_mouse(mouse, rdr)?;
}
}
}
if let Some(window) = wvr_server.wm.windows.get(&self.window) {
let surface_id = window.toplevel.wl_surface().id();
state.send_frame_callbacks_for_surface_id(&surface_id);
@ -703,6 +772,24 @@ impl OverlayBackend for WvrWindowBackend {
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
wvr_server.overlay_added(oid, self.window);
}
OverlayEventData::WvrCommand(WvrCommand::ReloadTitle) => {
let wvr_server = app.wvr_server.as_mut().unwrap(); //never None
if let Some(window) = wvr_server.wm.windows.get(&self.window) {
let title = with_states(window.toplevel.wl_surface(), |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.title
.clone()
});
if let Some(title) = title {
self.update_title(title.into());
}
}
}
OverlayEventData::WvrCommand(WvrCommand::CloseWindow) => {
app.wvr_server.as_mut().unwrap().close_window(self.window);
}

View File

@ -106,9 +106,15 @@ impl AppState {
let session = AppSession::load();
let wvr_signals = SyncEventQueue::new();
let wvr_server = WvrServerState::new(gfx.clone(), &gfx_extras, wvr_signals.clone())
.log_err("Could not initialize WayVR Server")
.ok();
let wvr_server = {
let mut maybe_wvr = WvrServerState::new(gfx.clone(), &gfx_extras, wvr_signals.clone())
.log_err("Could not initialize WayVR Server")
.ok();
if let Some(wvr) = maybe_wvr.as_mut() {
wvr.config_changed(&session.config);
}
maybe_wvr
};
let (hid_provider, mut hid_error) = HidWrapper::new(session.config.input_emulation_method);

View File

@ -16,7 +16,7 @@ use wlx_common::{
use crate::{
FRAME_COUNTER,
backend::task::{CreateOverlayTask, OverlayTask, SpawnPos, ToggleMode},
backend::task::{CreateOverlayTask, GlobalChange, OverlayTask, SpawnPos, ToggleMode},
config::save_state,
overlays::{
anchor::{create_anchor, create_grab_help},
@ -307,7 +307,7 @@ where
self.restore_set = 0;
self.sets_changed(app);
}
OverlayTask::SettingsChanged => {
OverlayTask::GlobalChange(GlobalChange::Settings) => {
if let Some(watch) = self.mut_by_id(self.watch_id)
&& app.session.config.enable_watch != watch.config.active_state.is_some()
{
@ -326,7 +326,7 @@ where
.log_err("Could not notify SettingsChanged");
}
}
OverlayTask::KeyboardChanged => {
OverlayTask::GlobalChange(GlobalChange::Keyboard) => {
self.overlays_changed(app)?;
self.sets_changed(app);
}

View File

@ -43,6 +43,11 @@ impl WidgetRectangle {
self.params.color = color;
common.mark_widget_dirty(self.id);
}
pub fn set_border_color(&mut self, common: &mut CallbackDataCommon, color: WguiColor) {
self.params.border_color = color;
common.mark_widget_dirty(self.id);
}
}
impl WidgetObj for WidgetRectangle {