diff --git a/Cargo.lock b/Cargo.lock index 709a418..ccc9c27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,6 +368,7 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" name = "ferese" version = "0.1.0" dependencies = [ + "libc", "smithay", "tracing", "tracing-subscriber", diff --git a/Cargo.toml b/Cargo.toml index dfd0b4d..2da9511 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ license = "MIT" rust-version = "1.87" [workspace.dependencies] +libc = "0.2.176" smithay = { version = "0.7.0", default-features = false, features = [ "backend_winit", "desktop", @@ -16,4 +17,3 @@ smithay = { version = "0.7.0", default-features = false, features = [ ] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } - diff --git a/compositor/Cargo.toml b/compositor/Cargo.toml index fbaf1cf..2a7fd02 100644 --- a/compositor/Cargo.toml +++ b/compositor/Cargo.toml @@ -6,7 +6,7 @@ license.workspace = true rust-version.workspace = true [dependencies] +libc.workspace = true smithay.workspace = true tracing.workspace = true tracing-subscriber.workspace = true - diff --git a/compositor/src/handlers/activation.rs b/compositor/src/handlers/activation.rs new file mode 100644 index 0000000..a321869 --- /dev/null +++ b/compositor/src/handlers/activation.rs @@ -0,0 +1,59 @@ +use std::time::Duration; + +use smithay::{ + reexports::wayland_server::protocol::wl_surface::WlSurface, + utils::SERIAL_COUNTER, + wayland::xdg_activation::{ + XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData, + }, +}; + +use crate::Ferese; + +const TOKEN_MAX_AGE: Duration = Duration::from_secs(10); + +impl XdgActivationHandler for Ferese { + fn activation_state(&mut self) -> &mut XdgActivationState { + &mut self.xdg_activation_state + } + + fn token_created(&mut self, _token: XdgActivationToken, data: XdgActivationTokenData) -> bool { + data.serial.is_some() && data.timestamp.elapsed() <= TOKEN_MAX_AGE + } + + fn request_activation( + &mut self, + token: XdgActivationToken, + data: XdgActivationTokenData, + surface: WlSurface, + ) { + let valid = data.serial.is_some() && data.timestamp.elapsed() <= TOKEN_MAX_AGE; + self.xdg_activation_state.remove_token(&token); + if !valid { + tracing::debug!("ignored stale or non-interactive activation request"); + return; + } + + let Some(window) = self + .space + .elements() + .find(|window| { + window + .toplevel() + .is_some_and(|toplevel| toplevel.wl_surface() == &surface) + }) + .cloned() + else { + return; + }; + + self.space.raise_element(&window, true); + let keyboard = self.seat.get_keyboard().expect("seat has a keyboard"); + keyboard.set_focus(self, Some(surface), SERIAL_COUNTER.next_serial()); + self.space.elements().for_each(|window| { + if let Some(toplevel) = window.toplevel() { + toplevel.send_pending_configure(); + } + }); + } +} diff --git a/compositor/src/handlers/mod.rs b/compositor/src/handlers/mod.rs index 12e9f84..47ba796 100644 --- a/compositor/src/handlers/mod.rs +++ b/compositor/src/handlers/mod.rs @@ -1,11 +1,18 @@ +mod activation; mod compositor; mod xdg_shell; use smithay::{ - input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus}, + input::{ + Seat, SeatHandler, SeatState, + pointer::{CursorImageStatus, PointerHandle}, + }, reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface}, wayland::{ + compositor::with_states, + fractional_scale::{FractionalScaleHandler, with_fractional_scale}, output::OutputHandler, + pointer_constraints::{PointerConstraintsHandler, with_pointer_constraint}, selection::{ SelectionHandler, data_device::{ @@ -41,6 +48,37 @@ impl SeatHandler for Ferese { impl OutputHandler for Ferese {} +impl FractionalScaleHandler for Ferese { + fn new_fractional_scale(&mut self, surface: WlSurface) { + with_states(&surface, |states| { + with_fractional_scale(states, |scale| scale.set_preferred_scale(1.0)); + }); + } +} + +impl PointerConstraintsHandler for Ferese { + fn new_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle) { + let focused = pointer + .current_focus() + .is_some_and(|focused| focused == *surface); + if focused { + with_pointer_constraint(surface, pointer, |constraint| { + if let Some(constraint) = constraint { + constraint.activate(); + } + }); + } + } + + fn cursor_position_hint( + &mut self, + _surface: &WlSurface, + _pointer: &PointerHandle, + _location: smithay::utils::Point, + ) { + } +} + impl SelectionHandler for Ferese { type SelectionUserData = (); } @@ -62,9 +100,15 @@ impl PrimarySelectionHandler for Ferese { smithay::delegate_compositor!(Ferese); smithay::delegate_data_device!(Ferese); +smithay::delegate_fractional_scale!(Ferese); smithay::delegate_output!(Ferese); +smithay::delegate_pointer_constraints!(Ferese); +smithay::delegate_presentation!(Ferese); smithay::delegate_primary_selection!(Ferese); +smithay::delegate_relative_pointer!(Ferese); smithay::delegate_seat!(Ferese); smithay::delegate_shm!(Ferese); +smithay::delegate_viewporter!(Ferese); +smithay::delegate_xdg_activation!(Ferese); smithay::delegate_xdg_decoration!(Ferese); smithay::delegate_xdg_shell!(Ferese); diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 29f61a2..856b1bb 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -1,11 +1,11 @@ use smithay::{ backend::input::{ AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, - KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, + KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent, }, input::{ keyboard::FilterResult, - pointer::{AxisFrame, ButtonEvent, MotionEvent}, + pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent}, }, reexports::wayland_server::protocol::wl_surface::WlSurface, utils::{SERIAL_COUNTER, Serial}, @@ -47,6 +47,20 @@ impl Ferese { ); pointer.frame(self); } + InputEvent::PointerMotion { event, .. } => { + let pointer = self.seat.get_pointer().expect("seat has a pointer"); + let focus = self.surface_under(pointer.current_location()); + pointer.relative_motion( + self, + focus, + &RelativeMotionEvent { + delta: event.delta(), + delta_unaccel: event.delta_unaccel(), + utime: (event.time_msec() as u64).saturating_mul(1_000), + }, + ); + pointer.frame(self); + } InputEvent::PointerButton { event, .. } => { let pointer = self.seat.get_pointer().expect("seat has a pointer"); let serial = SERIAL_COUNTER.next_serial(); diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 2b9e5a0..0837830 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -14,12 +14,18 @@ use smithay::{ utils::{Logical, Point}, wayland::{ compositor::{CompositorClientState, CompositorState}, + fractional_scale::FractionalScaleManagerState, output::OutputManagerState, + pointer_constraints::PointerConstraintsState, + presentation::PresentationState, + relative_pointer::RelativePointerManagerState, selection::{data_device::DataDeviceState, primary_selection::PrimarySelectionState}, shell::xdg::XdgShellState, shell::xdg::decoration::XdgDecorationState, shm::ShmState, socket::ListeningSocketSource, + viewporter::ViewporterState, + xdg_activation::XdgActivationState, }, }; @@ -34,10 +40,16 @@ pub struct Ferese { pub compositor_state: CompositorState, pub data_device_state: DataDeviceState, pub decoration_state: XdgDecorationState, + pub fractional_scale_state: FractionalScaleManagerState, pub output_manager_state: OutputManagerState, + pub pointer_constraints_state: PointerConstraintsState, + pub presentation_state: PresentationState, pub primary_selection_state: PrimarySelectionState, + pub relative_pointer_state: RelativePointerManagerState, pub seat_state: SeatState, pub shm_state: ShmState, + pub viewporter_state: ViewporterState, + pub xdg_activation_state: XdgActivationState, pub xdg_shell_state: XdgShellState, } @@ -50,10 +62,17 @@ impl Ferese { let compositor_state = CompositorState::new::(&display_handle); let xdg_shell_state = XdgShellState::new::(&display_handle); let decoration_state = XdgDecorationState::new::(&display_handle); + let fractional_scale_state = FractionalScaleManagerState::new::(&display_handle); let shm_state = ShmState::new::(&display_handle, Vec::new()); let output_manager_state = OutputManagerState::new_with_xdg_output::(&display_handle); + let pointer_constraints_state = PointerConstraintsState::new::(&display_handle); + let presentation_state = + PresentationState::new::(&display_handle, libc::CLOCK_MONOTONIC as u32); let data_device_state = DataDeviceState::new::(&display_handle); let primary_selection_state = PrimarySelectionState::new::(&display_handle); + let relative_pointer_state = RelativePointerManagerState::new::(&display_handle); + let viewporter_state = ViewporterState::new::(&display_handle); + let xdg_activation_state = XdgActivationState::new::(&display_handle); let mut seat_state = SeatState::new(); let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit"); seat.add_keyboard(Default::default(), 200, 25)?; @@ -71,10 +90,16 @@ impl Ferese { compositor_state, data_device_state, decoration_state, + fractional_scale_state, output_manager_state, + pointer_constraints_state, + presentation_state, primary_selection_state, + relative_pointer_state, seat_state, shm_state, + viewporter_state, + xdg_activation_state, xdg_shell_state, }) } diff --git a/compositor/src/winit.rs b/compositor/src/winit.rs index 41be199..2498ebe 100644 --- a/compositor/src/winit.rs +++ b/compositor/src/winit.rs @@ -8,9 +8,12 @@ use smithay::{ }, winit::{self, WinitEvent}, }, + desktop::utils::OutputPresentationFeedback, output::{Mode, Output, PhysicalProperties, Subpixel}, reexports::calloop::EventLoop, - utils::{Rectangle, Transform}, + reexports::wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind, + utils::{Clock, Monotonic, Rectangle, Transform}, + wayland::presentation::Refresh, }; use crate::Ferese; @@ -41,6 +44,8 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() state.space.map_output(&output, (0, 0)); let mut damage_tracker = OutputDamageTracker::from_output(&output); + let clock = Clock::::new(); + let mut sequence = 0_u64; event_loop .handle() .insert_source(event_source, move |event, _, state| match event { @@ -56,6 +61,14 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() WinitEvent::Input(event) => state.process_input_event(event), WinitEvent::Redraw => { let damage = Rectangle::from_size(backend.window_size()); + let mut presentation = OutputPresentationFeedback::new(&output); + state.space.elements().for_each(|window| { + window.take_presentation_feedback( + &mut presentation, + |_, _| Some(output.clone()), + |_, _| Kind::Vsync, + ); + }); let rendered = (|| -> Result<(), Box> { { let (renderer, mut framebuffer) = backend.bind()?; @@ -84,6 +97,13 @@ pub fn init(event_loop: &mut EventLoop, state: &mut Ferese) -> Result<() state.loop_signal.stop(); return; } + sequence = sequence.wrapping_add(1); + presentation.presented( + clock.now(), + Refresh::fixed(Duration::from_nanos(1_000_000_000 / 60)), + sequence, + Kind::Vsync, + ); state.space.elements().for_each(|window| { window.send_frame( &output,