feat: add Phase 1 interaction protocols

This commit is contained in:
Samuel Onoja committed 2026-09-24 05:46:00 +01:00
1 parent a941b80aa9
commit 37550f447d
8 files changed
+169 -6

No files matched your search

Generated
+1
View File
@@ -368,6 +368,7 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
name = "ferese" name = "ferese"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"libc",
"smithay", "smithay",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
+1 -1
View File
@@ -9,6 +9,7 @@ license = "MIT"
rust-version = "1.87" rust-version = "1.87"
[workspace.dependencies] [workspace.dependencies]
libc = "0.2.176"
smithay = { version = "0.7.0", default-features = false, features = [ smithay = { version = "0.7.0", default-features = false, features = [
"backend_winit", "backend_winit",
"desktop", "desktop",
@@ -16,4 +17,3 @@ smithay = { version = "0.7.0", default-features = false, features = [
] } ] }
tracing = "0.1.41" tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["env-filter"] } tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
+1 -1
View File
@@ -6,7 +6,7 @@ license.workspace = true
rust-version.workspace = true rust-version.workspace = true
[dependencies] [dependencies]
libc.workspace = true
smithay.workspace = true smithay.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-subscriber.workspace = true tracing-subscriber.workspace = true
+59
View File
@@ -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();
}
});
}
}
+45 -1
View File
@@ -1,11 +1,18 @@
mod activation;
mod compositor; mod compositor;
mod xdg_shell; mod xdg_shell;
use smithay::{ use smithay::{
input::{Seat, SeatHandler, SeatState, pointer::CursorImageStatus}, input::{
Seat, SeatHandler, SeatState,
pointer::{CursorImageStatus, PointerHandle},
},
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface}, reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
wayland::{ wayland::{
compositor::with_states,
fractional_scale::{FractionalScaleHandler, with_fractional_scale},
output::OutputHandler, output::OutputHandler,
pointer_constraints::{PointerConstraintsHandler, with_pointer_constraint},
selection::{ selection::{
SelectionHandler, SelectionHandler,
data_device::{ data_device::{
@@ -41,6 +48,37 @@ impl SeatHandler for Ferese {
impl OutputHandler 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<Self>) {
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<Self>,
_location: smithay::utils::Point<f64, smithay::utils::Logical>,
) {
}
}
impl SelectionHandler for Ferese { impl SelectionHandler for Ferese {
type SelectionUserData = (); type SelectionUserData = ();
} }
@@ -62,9 +100,15 @@ impl PrimarySelectionHandler for Ferese {
smithay::delegate_compositor!(Ferese); smithay::delegate_compositor!(Ferese);
smithay::delegate_data_device!(Ferese); smithay::delegate_data_device!(Ferese);
smithay::delegate_fractional_scale!(Ferese);
smithay::delegate_output!(Ferese); smithay::delegate_output!(Ferese);
smithay::delegate_pointer_constraints!(Ferese);
smithay::delegate_presentation!(Ferese);
smithay::delegate_primary_selection!(Ferese); smithay::delegate_primary_selection!(Ferese);
smithay::delegate_relative_pointer!(Ferese);
smithay::delegate_seat!(Ferese); smithay::delegate_seat!(Ferese);
smithay::delegate_shm!(Ferese); smithay::delegate_shm!(Ferese);
smithay::delegate_viewporter!(Ferese);
smithay::delegate_xdg_activation!(Ferese);
smithay::delegate_xdg_decoration!(Ferese); smithay::delegate_xdg_decoration!(Ferese);
smithay::delegate_xdg_shell!(Ferese); smithay::delegate_xdg_shell!(Ferese);
+16 -2
View File
@@ -1,11 +1,11 @@
use smithay::{ use smithay::{
backend::input::{ backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent, AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent, PointerMotionEvent,
}, },
input::{ input::{
keyboard::FilterResult, keyboard::FilterResult,
pointer::{AxisFrame, ButtonEvent, MotionEvent}, pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent},
}, },
reexports::wayland_server::protocol::wl_surface::WlSurface, reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{SERIAL_COUNTER, Serial}, utils::{SERIAL_COUNTER, Serial},
@@ -47,6 +47,20 @@ impl Ferese {
); );
pointer.frame(self); 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, .. } => { InputEvent::PointerButton { event, .. } => {
let pointer = self.seat.get_pointer().expect("seat has a pointer"); let pointer = self.seat.get_pointer().expect("seat has a pointer");
let serial = SERIAL_COUNTER.next_serial(); let serial = SERIAL_COUNTER.next_serial();
+25
View File
@@ -14,12 +14,18 @@ use smithay::{
utils::{Logical, Point}, utils::{Logical, Point},
wayland::{ wayland::{
compositor::{CompositorClientState, CompositorState}, compositor::{CompositorClientState, CompositorState},
fractional_scale::FractionalScaleManagerState,
output::OutputManagerState, output::OutputManagerState,
pointer_constraints::PointerConstraintsState,
presentation::PresentationState,
relative_pointer::RelativePointerManagerState,
selection::{data_device::DataDeviceState, primary_selection::PrimarySelectionState}, selection::{data_device::DataDeviceState, primary_selection::PrimarySelectionState},
shell::xdg::XdgShellState, shell::xdg::XdgShellState,
shell::xdg::decoration::XdgDecorationState, shell::xdg::decoration::XdgDecorationState,
shm::ShmState, shm::ShmState,
socket::ListeningSocketSource, socket::ListeningSocketSource,
viewporter::ViewporterState,
xdg_activation::XdgActivationState,
}, },
}; };
@@ -34,10 +40,16 @@ pub struct Ferese {
pub compositor_state: CompositorState, pub compositor_state: CompositorState,
pub data_device_state: DataDeviceState, pub data_device_state: DataDeviceState,
pub decoration_state: XdgDecorationState, pub decoration_state: XdgDecorationState,
pub fractional_scale_state: FractionalScaleManagerState,
pub output_manager_state: OutputManagerState, pub output_manager_state: OutputManagerState,
pub pointer_constraints_state: PointerConstraintsState,
pub presentation_state: PresentationState,
pub primary_selection_state: PrimarySelectionState, pub primary_selection_state: PrimarySelectionState,
pub relative_pointer_state: RelativePointerManagerState,
pub seat_state: SeatState<Self>, pub seat_state: SeatState<Self>,
pub shm_state: ShmState, pub shm_state: ShmState,
pub viewporter_state: ViewporterState,
pub xdg_activation_state: XdgActivationState,
pub xdg_shell_state: XdgShellState, pub xdg_shell_state: XdgShellState,
} }
@@ -50,10 +62,17 @@ impl Ferese {
let compositor_state = CompositorState::new::<Self>(&display_handle); let compositor_state = CompositorState::new::<Self>(&display_handle);
let xdg_shell_state = XdgShellState::new::<Self>(&display_handle); let xdg_shell_state = XdgShellState::new::<Self>(&display_handle);
let decoration_state = XdgDecorationState::new::<Self>(&display_handle); let decoration_state = XdgDecorationState::new::<Self>(&display_handle);
let fractional_scale_state = FractionalScaleManagerState::new::<Self>(&display_handle);
let shm_state = ShmState::new::<Self>(&display_handle, Vec::new()); let shm_state = ShmState::new::<Self>(&display_handle, Vec::new());
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle); let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
let pointer_constraints_state = PointerConstraintsState::new::<Self>(&display_handle);
let presentation_state =
PresentationState::new::<Self>(&display_handle, libc::CLOCK_MONOTONIC as u32);
let data_device_state = DataDeviceState::new::<Self>(&display_handle); let data_device_state = DataDeviceState::new::<Self>(&display_handle);
let primary_selection_state = PrimarySelectionState::new::<Self>(&display_handle); let primary_selection_state = PrimarySelectionState::new::<Self>(&display_handle);
let relative_pointer_state = RelativePointerManagerState::new::<Self>(&display_handle);
let viewporter_state = ViewporterState::new::<Self>(&display_handle);
let xdg_activation_state = XdgActivationState::new::<Self>(&display_handle);
let mut seat_state = SeatState::new(); let mut seat_state = SeatState::new();
let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit"); let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit");
seat.add_keyboard(Default::default(), 200, 25)?; seat.add_keyboard(Default::default(), 200, 25)?;
@@ -71,10 +90,16 @@ impl Ferese {
compositor_state, compositor_state,
data_device_state, data_device_state,
decoration_state, decoration_state,
fractional_scale_state,
output_manager_state, output_manager_state,
pointer_constraints_state,
presentation_state,
primary_selection_state, primary_selection_state,
relative_pointer_state,
seat_state, seat_state,
shm_state, shm_state,
viewporter_state,
xdg_activation_state,
xdg_shell_state, xdg_shell_state,
}) })
} }
+21 -1
View File
@@ -8,9 +8,12 @@ use smithay::{
}, },
winit::{self, WinitEvent}, winit::{self, WinitEvent},
}, },
desktop::utils::OutputPresentationFeedback,
output::{Mode, Output, PhysicalProperties, Subpixel}, output::{Mode, Output, PhysicalProperties, Subpixel},
reexports::calloop::EventLoop, 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; use crate::Ferese;
@@ -41,6 +44,8 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
state.space.map_output(&output, (0, 0)); state.space.map_output(&output, (0, 0));
let mut damage_tracker = OutputDamageTracker::from_output(&output); let mut damage_tracker = OutputDamageTracker::from_output(&output);
let clock = Clock::<Monotonic>::new();
let mut sequence = 0_u64;
event_loop event_loop
.handle() .handle()
.insert_source(event_source, move |event, _, state| match event { .insert_source(event_source, move |event, _, state| match event {
@@ -56,6 +61,14 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
WinitEvent::Input(event) => state.process_input_event(event), WinitEvent::Input(event) => state.process_input_event(event),
WinitEvent::Redraw => { WinitEvent::Redraw => {
let damage = Rectangle::from_size(backend.window_size()); 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<dyn Error>> { let rendered = (|| -> Result<(), Box<dyn Error>> {
{ {
let (renderer, mut framebuffer) = backend.bind()?; let (renderer, mut framebuffer) = backend.bind()?;
@@ -84,6 +97,13 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
state.loop_signal.stop(); state.loop_signal.stop();
return; 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| { state.space.elements().for_each(|window| {
window.send_frame( window.send_frame(
&output, &output,