Integrate native Rime and stabilize input, output, and rendering

- Add optional modifier-only Super binding whose tap is cancelled by
  other input, focus changes, grabs, locks, and session teardown.
- Ship the native ferese-rime user service, Rime defaults, installer
  inventory, package dependencies, session environment, and docs.
- Vendor Smithay 8a27023 with the SHM texture-cache format fix and add
  GPU regressions for format changes and resize snapshots.
- Release reused CRTC owners before staging output modes, then restore
  changed outputs before former owners when a transaction fails.
- Wait for resize-capture GPU completion, expand filtered surface
  damage, and preserve IME popup origin under fractional scaling.
- Mark weekends like holidays in both calendar layouts.
This commit is contained in:
aria committed 2026-10-30 03:13:43 +09:00
1 parent a2be421974
commit 993bc0f88f
391 files changed
+130908 -76

No files matched your search

+17
View File
@@ -0,0 +1,17 @@
[package]
name = "smallvil"
version = "0.1.0"
edition = "2024"
[dependencies]
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
bitflags = "2.2.1"
[dependencies.smithay]
path = "../"
default-features = false
features = [
"backend_winit",
"wayland_frontend",
"desktop",
]
+5
View File
@@ -0,0 +1,5 @@
pub mod move_grab;
pub use move_grab::MoveSurfaceGrab;
pub mod resize_grab;
pub use resize_grab::ResizeSurfaceGrab;
+160
View File
@@ -0,0 +1,160 @@
//! Move grab is the state of a composer during which the client window is being dragged around.
//!
//! eg. Usually whenever a user clicks on the app's titlebar and starts dragging, the compositors
//! enters a MoveSurfaceGrab state.
use crate::Smallvil;
use smithay::{
desktop::Window,
input::pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle, RelativeMotionEvent,
},
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::{Logical, Point},
};
pub struct MoveSurfaceGrab {
pub start_data: PointerGrabStartData<Smallvil>,
pub window: Window,
pub initial_window_location: Point<i32, Logical>,
}
impl PointerGrab<Smallvil> for MoveSurfaceGrab {
fn motion(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
_focus: Option<(WlSurface, Point<f64, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
let delta = event.location - self.start_data.location;
let new_location = self.initial_window_location.to_f64() + delta;
data.space
.map_element(self.window.clone(), new_location.to_i32_round(), true);
}
fn relative_motion(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
focus: Option<(WlSurface, Point<f64, Logical>)>,
event: &RelativeMotionEvent,
) {
handle.relative_motion(data, focus, event);
}
fn button(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &ButtonEvent,
) {
handle.button(data, event);
// The button is a button code as defined in the
// Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT.
const BTN_LEFT: u32 = 0x110;
if !handle.current_pressed().contains(&BTN_LEFT) {
// No more buttons are pressed, release the grab.
handle.unset_grab(self, data, event.serial, event.time, true);
}
}
fn axis(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
details: AxisFrame,
) {
handle.axis(data, details)
}
fn frame(&mut self, data: &mut Smallvil, handle: &mut PointerInnerHandle<'_, Smallvil>) {
handle.frame(data);
}
fn gesture_swipe_begin(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureSwipeBeginEvent,
) {
handle.gesture_swipe_begin(data, event)
}
fn gesture_swipe_update(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureSwipeUpdateEvent,
) {
handle.gesture_swipe_update(data, event)
}
fn gesture_swipe_end(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureSwipeEndEvent,
) {
handle.gesture_swipe_end(data, event)
}
fn gesture_pinch_begin(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GesturePinchBeginEvent,
) {
handle.gesture_pinch_begin(data, event)
}
fn gesture_pinch_update(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GesturePinchUpdateEvent,
) {
handle.gesture_pinch_update(data, event)
}
fn gesture_pinch_end(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GesturePinchEndEvent,
) {
handle.gesture_pinch_end(data, event)
}
fn gesture_hold_begin(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureHoldBeginEvent,
) {
handle.gesture_hold_begin(data, event)
}
fn gesture_hold_end(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureHoldEndEvent,
) {
handle.gesture_hold_end(data, event)
}
fn start_data(&self) -> &PointerGrabStartData<Smallvil> {
&self.start_data
}
fn unset(&mut self, _data: &mut Smallvil) {}
}
+365
View File
@@ -0,0 +1,365 @@
//! Resize grab is the state of a composer during which the client window is being resized.
//!
//! eg. Usually whenever a user clicks on the app's border and starts dragging, the compositors
//! enters a ResizeSurfaceGrab state.
use crate::Smallvil;
use smithay::{
desktop::{Space, Window},
input::pointer::{
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
PointerInnerHandle, RelativeMotionEvent,
},
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::protocol::wl_surface::WlSurface,
},
utils::{Logical, Point, Rectangle, Size},
wayland::{compositor, shell::xdg::SurfaceCachedState},
};
use std::cell::RefCell;
bitflags::bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ResizeEdge: u32 {
const TOP = 0b0001;
const BOTTOM = 0b0010;
const LEFT = 0b0100;
const RIGHT = 0b1000;
const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits();
const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits();
const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits();
const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits();
}
}
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
#[inline]
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
Self::from_bits(x as u32).unwrap()
}
}
pub struct ResizeSurfaceGrab {
start_data: PointerGrabStartData<Smallvil>,
window: Window,
edges: ResizeEdge,
initial_rect: Rectangle<i32, Logical>,
last_window_size: Size<i32, Logical>,
}
impl ResizeSurfaceGrab {
pub fn start(
start_data: PointerGrabStartData<Smallvil>,
window: Window,
edges: ResizeEdge,
initial_window_rect: Rectangle<i32, Logical>,
) -> Self {
let initial_rect = initial_window_rect;
ResizeSurfaceState::with(window.toplevel().unwrap().wl_surface(), |state| {
*state = ResizeSurfaceState::Resizing { edges, initial_rect };
});
Self {
start_data,
window,
edges,
initial_rect,
last_window_size: initial_rect.size,
}
}
}
impl PointerGrab<Smallvil> for ResizeSurfaceGrab {
fn motion(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
_focus: Option<(WlSurface, Point<f64, Logical>)>,
event: &MotionEvent,
) {
// While the grab is active, no client has pointer focus
handle.motion(data, None, event);
let mut delta = event.location - self.start_data.location;
let mut new_window_width = self.initial_rect.size.w;
let mut new_window_height = self.initial_rect.size.h;
if self.edges.intersects(ResizeEdge::LEFT | ResizeEdge::RIGHT) {
if self.edges.intersects(ResizeEdge::LEFT) {
delta.x = -delta.x;
}
new_window_width = (self.initial_rect.size.w as f64 + delta.x) as i32;
}
if self.edges.intersects(ResizeEdge::TOP | ResizeEdge::BOTTOM) {
if self.edges.intersects(ResizeEdge::TOP) {
delta.y = -delta.y;
}
new_window_height = (self.initial_rect.size.h as f64 + delta.y) as i32;
}
let (min_size, max_size) =
compositor::with_states(self.window.toplevel().unwrap().wl_surface(), |states| {
let mut guard = states.cached_state.get::<SurfaceCachedState>();
let data = guard.current();
(data.min_size, data.max_size)
});
let min_width = min_size.w.max(1);
let min_height = min_size.h.max(1);
let max_width = if max_size.w == 0 { i32::MAX } else { max_size.w };
let max_height = if max_size.h == 0 { i32::MAX } else { max_size.h };
self.last_window_size = Size::from((
new_window_width.max(min_width).min(max_width),
new_window_height.max(min_height).min(max_height),
));
let xdg = self.window.toplevel().unwrap();
xdg.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size);
});
xdg.send_pending_configure();
}
fn relative_motion(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
focus: Option<(WlSurface, Point<f64, Logical>)>,
event: &RelativeMotionEvent,
) {
handle.relative_motion(data, focus, event);
}
fn button(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &ButtonEvent,
) {
handle.button(data, event);
// The button is a button code as defined in the
// Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT.
const BTN_LEFT: u32 = 0x110;
if !handle.current_pressed().contains(&BTN_LEFT) {
// No more buttons are pressed, release the grab.
handle.unset_grab(self, data, event.serial, event.time, true);
let xdg = self.window.toplevel().unwrap();
xdg.with_pending_state(|state| {
state.states.unset(xdg_toplevel::State::Resizing);
state.size = Some(self.last_window_size);
});
xdg.send_pending_configure();
ResizeSurfaceState::with(xdg.wl_surface(), |state| {
*state = ResizeSurfaceState::WaitingForLastCommit {
edges: self.edges,
initial_rect: self.initial_rect,
};
});
}
}
fn axis(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
details: AxisFrame,
) {
handle.axis(data, details)
}
fn frame(&mut self, data: &mut Smallvil, handle: &mut PointerInnerHandle<'_, Smallvil>) {
handle.frame(data);
}
fn gesture_swipe_begin(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureSwipeBeginEvent,
) {
handle.gesture_swipe_begin(data, event)
}
fn gesture_swipe_update(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureSwipeUpdateEvent,
) {
handle.gesture_swipe_update(data, event)
}
fn gesture_swipe_end(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureSwipeEndEvent,
) {
handle.gesture_swipe_end(data, event)
}
fn gesture_pinch_begin(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GesturePinchBeginEvent,
) {
handle.gesture_pinch_begin(data, event)
}
fn gesture_pinch_update(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GesturePinchUpdateEvent,
) {
handle.gesture_pinch_update(data, event)
}
fn gesture_pinch_end(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GesturePinchEndEvent,
) {
handle.gesture_pinch_end(data, event)
}
fn gesture_hold_begin(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureHoldBeginEvent,
) {
handle.gesture_hold_begin(data, event)
}
fn gesture_hold_end(
&mut self,
data: &mut Smallvil,
handle: &mut PointerInnerHandle<'_, Smallvil>,
event: &GestureHoldEndEvent,
) {
handle.gesture_hold_end(data, event)
}
fn start_data(&self) -> &PointerGrabStartData<Smallvil> {
&self.start_data
}
fn unset(&mut self, _data: &mut Smallvil) {}
}
/// State of the resize operation.
///
/// It is stored inside of WlSurface,
/// and can be accessed using [`ResizeSurfaceState::with`]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
enum ResizeSurfaceState {
#[default]
Idle,
Resizing {
edges: ResizeEdge,
/// The initial window size and location.
initial_rect: Rectangle<i32, Logical>,
},
/// Resize is done, we are now waiting for last commit, to do the final move
WaitingForLastCommit {
edges: ResizeEdge,
/// The initial window size and location.
initial_rect: Rectangle<i32, Logical>,
},
}
impl ResizeSurfaceState {
fn with<F, T>(surface: &WlSurface, cb: F) -> T
where
F: FnOnce(&mut Self) -> T,
{
compositor::with_states(surface, |states| {
states.data_map.insert_if_missing(RefCell::<Self>::default);
let state = states.data_map.get::<RefCell<Self>>().unwrap();
cb(&mut state.borrow_mut())
})
}
fn commit(&mut self) -> Option<(ResizeEdge, Rectangle<i32, Logical>)> {
match *self {
Self::Resizing { edges, initial_rect } => Some((edges, initial_rect)),
Self::WaitingForLastCommit { edges, initial_rect } => {
// The resize is done, let's go back to idle
*self = Self::Idle;
Some((edges, initial_rect))
}
Self::Idle => None,
}
}
}
/// Should be called on `WlSurface::commit`
pub fn handle_commit(space: &mut Space<Window>, surface: &WlSurface) -> Option<()> {
let window = space
.elements()
.find(|w| w.toplevel().unwrap().wl_surface() == surface)
.cloned()?;
let mut window_loc = space.element_location(&window)?;
let geometry = window.geometry();
let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| {
state
.commit()
.and_then(|(edges, initial_rect)| {
// If the window is being resized by top or left, its location must be adjusted
// accordingly.
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
let new_x = edges
.intersects(ResizeEdge::LEFT)
.then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w));
let new_y = edges
.intersects(ResizeEdge::TOP)
.then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h));
(new_x, new_y).into()
})
})
.unwrap_or_default()
});
if let Some(new_x) = new_loc.x {
window_loc.x = new_x;
}
if let Some(new_y) = new_loc.y {
window_loc.y = new_y;
}
if new_loc.x.is_some() || new_loc.y.is_some() {
// If TOP or LEFT side of the window got resized, we have to move it
space.map_element(window, window_loc, false);
}
Some(())
}
+57
View File
@@ -0,0 +1,57 @@
use crate::{Smallvil, grabs::resize_grab, state::ClientState};
use smithay::{
backend::renderer::utils::on_commit_buffer_handler,
reexports::wayland_server::{
Client,
protocol::{wl_buffer, wl_surface::WlSurface},
},
wayland::{
buffer::BufferHandler,
compositor::{
CompositorClientState, CompositorHandler, CompositorState, get_parent, is_sync_subsurface,
},
shm::{ShmHandler, ShmState},
},
};
use super::xdg_shell;
impl CompositorHandler for Smallvil {
fn compositor_state(&mut self) -> &mut CompositorState {
&mut self.compositor_state
}
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
&client.get_data::<ClientState>().unwrap().compositor_state
}
fn commit(&mut self, surface: &WlSurface) {
on_commit_buffer_handler::<Self>(surface);
if !is_sync_subsurface(surface) {
let mut root = surface.clone();
while let Some(parent) = get_parent(&root) {
root = parent;
}
if let Some(window) = self
.space
.elements()
.find(|w| w.toplevel().unwrap().wl_surface() == &root)
{
window.on_commit();
}
};
xdg_shell::handle_commit(&mut self.popups, &self.space, surface);
resize_grab::handle_commit(&mut self.space, surface);
}
}
impl BufferHandler for Smallvil {
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
}
impl ShmHandler for Smallvil {
fn shm_state(&self) -> &ShmState {
&self.shm_state
}
}
+90
View File
@@ -0,0 +1,90 @@
mod compositor;
mod xdg_shell;
use crate::Smallvil;
//
// Wl Seat
//
use smithay::input::dnd::{DnDGrab, DndGrabHandler, GrabType, Source};
use smithay::input::pointer::Focus;
use smithay::input::{Seat, SeatHandler, SeatState};
use smithay::reexports::wayland_server::Resource;
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
use smithay::utils::Serial;
use smithay::wayland::output::OutputHandler;
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
use smithay::wayland::selection::SelectionHandler;
use smithay::wayland::selection::data_device::{
DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
};
impl SeatHandler for Smallvil {
type KeyboardFocus = WlSurface;
type PointerFocus = WlSurface;
type TouchFocus = WlSurface;
fn seat_state(&mut self) -> &mut SeatState<Smallvil> {
&mut self.seat_state
}
fn cursor_image(&mut self, _seat: &Seat<Self>, _image: smithay::input::pointer::CursorImageStatus) {}
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
let dh = &self.display_handle;
let client = focused.and_then(|s| dh.get_client(s.id()).ok());
set_data_device_focus(dh, seat, client);
}
}
impl PointerConstraintsHandler for Smallvil {}
//
// Wl Data Device
//
impl SelectionHandler for Smallvil {
type SelectionUserData = ();
}
impl DataDeviceHandler for Smallvil {
fn data_device_state(&mut self) -> &mut DataDeviceState {
&mut self.data_device_state
}
}
impl DndGrabHandler for Smallvil {}
impl WaylandDndGrabHandler for Smallvil {
fn dnd_requested<S: Source>(
&mut self,
source: S,
_icon: Option<WlSurface>,
seat: Seat<Self>,
serial: Serial,
type_: GrabType,
) {
match type_ {
GrabType::Pointer => {
let ptr = seat.get_pointer().unwrap();
let start_data = ptr.grab_start_data().unwrap();
// create a dnd grab to start the operation
let grab = DnDGrab::new_pointer(&self.display_handle, start_data, source, seat);
ptr.set_grab(self, grab, serial, Focus::Keep);
}
GrabType::Touch => {
// smallvil lacks touch handling
source.cancel();
}
}
}
}
//
// Wl Output & Xdg Output
//
impl OutputHandler for Smallvil {}
smithay::delegate_dispatch2!(Smallvil);
+214
View File
@@ -0,0 +1,214 @@
use smithay::{
desktop::{PopupKind, PopupManager, Space, Window, find_popup_root_surface, get_popup_toplevel_coords},
input::{
Seat,
pointer::{Focus, GrabStartData as PointerGrabStartData},
},
reexports::{
wayland_protocols::xdg::shell::server::xdg_toplevel,
wayland_server::{
Resource,
protocol::{wl_seat, wl_surface::WlSurface},
},
},
utils::{Rectangle, Serial},
wayland::{
compositor::with_states,
shell::xdg::{
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
XdgToplevelSurfaceData,
},
},
};
use crate::{
Smallvil,
grabs::{MoveSurfaceGrab, ResizeSurfaceGrab},
};
impl XdgShellHandler for Smallvil {
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
&mut self.xdg_shell_state
}
fn new_toplevel(&mut self, surface: ToplevelSurface) {
let window = Window::new_wayland_window(surface);
self.space.map_element(window, (0, 0), false);
}
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
self.unconstrain_popup(&surface);
let _ = self.popups.track_popup(PopupKind::Xdg(surface));
}
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
surface.with_pending_state(|state| {
let geometry = positioner.get_geometry();
state.geometry = geometry;
state.positioner = positioner;
});
self.unconstrain_popup(&surface);
surface.send_repositioned(token);
}
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
let seat = Seat::from_resource(&seat).unwrap();
let wl_surface = surface.wl_surface();
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
let pointer = seat.get_pointer().unwrap();
let window = self
.space
.elements()
.find(|w| w.toplevel().unwrap().wl_surface() == wl_surface)
.unwrap()
.clone();
let initial_window_location = self.space.element_location(&window).unwrap();
let grab = MoveSurfaceGrab {
start_data,
window,
initial_window_location,
};
pointer.set_grab(self, grab, serial, Focus::Clear);
}
}
fn resize_request(
&mut self,
surface: ToplevelSurface,
seat: wl_seat::WlSeat,
serial: Serial,
edges: xdg_toplevel::ResizeEdge,
) {
let seat = Seat::from_resource(&seat).unwrap();
let wl_surface = surface.wl_surface();
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
let pointer = seat.get_pointer().unwrap();
let window = self
.space
.elements()
.find(|w| w.toplevel().unwrap().wl_surface() == wl_surface)
.unwrap()
.clone();
let initial_window_location = self.space.element_location(&window).unwrap();
let initial_window_size = window.geometry().size;
surface.with_pending_state(|state| {
state.states.set(xdg_toplevel::State::Resizing);
});
surface.send_pending_configure();
let grab = ResizeSurfaceGrab::start(
start_data,
window,
edges.into(),
Rectangle::new(initial_window_location, initial_window_size),
);
pointer.set_grab(self, grab, serial, Focus::Clear);
}
}
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
// TODO popup grabs
}
}
fn check_grab(
seat: &Seat<Smallvil>,
surface: &WlSurface,
serial: Serial,
) -> Option<PointerGrabStartData<Smallvil>> {
let pointer = seat.get_pointer()?;
// Check that this surface has a click grab.
if !pointer.has_grab(serial) {
return None;
}
let start_data = pointer.grab_start_data()?;
let (focus, _) = start_data.focus.as_ref()?;
// If the focus was for a different surface, ignore the request.
if !focus.id().same_client_as(&surface.id()) {
return None;
}
Some(start_data)
}
/// Should be called on `WlSurface::commit`
pub fn handle_commit(popups: &mut PopupManager, space: &Space<Window>, surface: &WlSurface) {
// Handle toplevel commits.
if let Some(window) = space
.elements()
.find(|w| w.toplevel().unwrap().wl_surface() == surface)
.cloned()
{
let initial_configure_sent = with_states(surface, |states| {
states
.data_map
.get::<XdgToplevelSurfaceData>()
.unwrap()
.lock()
.unwrap()
.initial_configure_sent
});
if !initial_configure_sent {
window.toplevel().unwrap().send_configure();
}
}
// Handle popup commits.
popups.commit(surface);
if let Some(popup) = popups.find_popup(surface) {
match popup {
PopupKind::Xdg(ref xdg) => {
if !xdg.is_initial_configure_sent() {
// NOTE: This should never fail as the initial configure is always
// allowed.
xdg.send_configure().expect("initial configure failed");
}
}
PopupKind::InputMethod(ref _input_method) => {}
}
}
}
impl Smallvil {
fn unconstrain_popup(&self, popup: &PopupSurface) {
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
return;
};
let Some(window) = self
.space
.elements()
.find(|w| w.toplevel().unwrap().wl_surface() == &root)
else {
return;
};
let output = self.space.outputs().next().unwrap();
let output_geo = self.space.output_geometry(output).unwrap();
let window_geo = self.space.element_geometry(window).unwrap();
// The target geometry for the positioner should be relative to its parent's geometry, so
// we will compute that here.
let mut target = output_geo;
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
target.loc -= window_geo.loc;
popup.with_pending_state(|state| {
state.geometry = state.positioner.get_unconstrained_geometry(target);
});
}
}
+144
View File
@@ -0,0 +1,144 @@
use smithay::{
backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent,
},
input::{
keyboard::FilterResult,
pointer::{AxisFrame, ButtonEvent, MotionEvent},
},
reexports::wayland_server::protocol::wl_surface::WlSurface,
utils::SERIAL_COUNTER,
};
use crate::state::Smallvil;
impl Smallvil {
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
match event {
InputEvent::Keyboard { event, .. } => {
let serial = SERIAL_COUNTER.next_serial();
let time = Event::time(&event);
self.seat.get_keyboard().unwrap().input::<(), _>(
self,
event.key_code(),
event.state(),
serial,
time,
|_, _, _| FilterResult::Forward,
);
}
InputEvent::PointerMotion { .. } => {}
InputEvent::PointerMotionAbsolute { event, .. } => {
let output = self.space.outputs().next().unwrap();
let output_geo = self.space.output_geometry(output).unwrap();
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
let serial = SERIAL_COUNTER.next_serial();
let pointer = self.seat.get_pointer().unwrap();
let under = self.surface_under(pos);
pointer.motion(
self,
under,
&MotionEvent {
location: pos,
serial,
time: event.time(),
},
);
pointer.frame(self);
}
InputEvent::PointerButton { event, .. } => {
let pointer = self.seat.get_pointer().unwrap();
let keyboard = self.seat.get_keyboard().unwrap();
let serial = SERIAL_COUNTER.next_serial();
let button = event.button_code();
let button_state = event.state();
if ButtonState::Pressed == button_state && !pointer.is_grabbed() {
if let Some((window, _loc)) = self
.space
.element_under(pointer.current_location())
.map(|(w, l)| (w.clone(), l))
{
self.space.raise_element(&window, true);
keyboard.set_focus(
self,
Some(window.toplevel().unwrap().wl_surface().clone()),
serial,
);
self.space.elements().for_each(|window| {
window.toplevel().unwrap().send_pending_configure();
});
} else {
self.space.elements().for_each(|window| {
window.set_activated(false);
window.toplevel().unwrap().send_pending_configure();
});
keyboard.set_focus(self, Option::<WlSurface>::None, serial);
}
};
pointer.button(
self,
&ButtonEvent {
button,
state: button_state,
serial,
time: event.time(),
},
);
pointer.frame(self);
}
InputEvent::PointerAxis { event, .. } => {
let source = event.source();
let horizontal_amount = event
.amount(Axis::Horizontal)
.unwrap_or_else(|| event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 15.0 / 120.);
let vertical_amount = event
.amount(Axis::Vertical)
.unwrap_or_else(|| event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 15.0 / 120.);
let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal);
let vertical_amount_discrete = event.amount_v120(Axis::Vertical);
let mut frame = AxisFrame::new(event.time()).source(source);
if horizontal_amount != 0.0 {
frame = frame.value(Axis::Horizontal, horizontal_amount);
if let Some(discrete) = horizontal_amount_discrete {
frame = frame.v120(Axis::Horizontal, discrete as i32);
}
}
if vertical_amount != 0.0 {
frame = frame.value(Axis::Vertical, vertical_amount);
if let Some(discrete) = vertical_amount_discrete {
frame = frame.v120(Axis::Vertical, discrete as i32);
}
}
if source == AxisSource::Finger {
if event.amount(Axis::Horizontal) == Some(0.0) {
frame = frame.stop(Axis::Horizontal);
}
if event.amount(Axis::Vertical) == Some(0.0) {
frame = frame.stop(Axis::Vertical);
}
}
let pointer = self.seat.get_pointer().unwrap();
pointer.axis(self, frame);
pointer.frame(self);
}
_ => {}
}
}
}
+60
View File
@@ -0,0 +1,60 @@
#![allow(irrefutable_let_patterns)]
mod handlers;
mod grabs;
mod input;
mod state;
mod winit;
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
pub use state::Smallvil;
fn main() -> Result<(), Box<dyn std::error::Error>> {
init_logging();
let mut event_loop: EventLoop<Smallvil> = EventLoop::try_new()?;
let display: Display<Smallvil> = Display::new()?;
let mut state = Smallvil::new(&mut event_loop, display);
// Open a Wayland/X11 window for our nested compositor
crate::winit::init_winit(&mut event_loop, &mut state)?;
// Set WAYLAND_DISPLAY to our socket name, so child processes connect to Smallvil rather
// than the host compositor
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
// Spawn a test client, that will run under Smallvil
spawn_client();
event_loop.run(None, &mut state, move |_| {
// Smallvil is running
})?;
Ok(())
}
fn init_logging() {
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
tracing_subscriber::fmt().with_env_filter(env_filter).init();
} else {
tracing_subscriber::fmt().init();
}
}
fn spawn_client() {
let mut args = std::env::args().skip(1);
let flag = args.next();
let arg = args.next();
match (flag.as_deref(), arg) {
(Some("-c") | Some("--command"), Some(command)) => {
std::process::Command::new(command).spawn().ok();
}
_ => {
std::process::Command::new("weston-terminal").spawn().ok();
}
}
}
+168
View File
@@ -0,0 +1,168 @@
use std::{ffi::OsString, sync::Arc};
use smithay::{
desktop::{PopupManager, Space, Window, WindowSurfaceType},
input::{Seat, SeatState},
reexports::{
calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic},
wayland_server::{
Display, DisplayHandle,
backend::{ClientData, ClientId, DisconnectReason},
protocol::wl_surface::WlSurface,
},
},
utils::{Logical, Point},
wayland::{
compositor::{CompositorClientState, CompositorState},
output::OutputManagerState,
selection::data_device::DataDeviceState,
shell::xdg::XdgShellState,
shm::ShmState,
socket::ListeningSocketSource,
},
};
pub struct Smallvil {
pub start_time: std::time::Instant,
pub socket_name: OsString,
pub display_handle: DisplayHandle,
pub space: Space<Window>,
pub loop_signal: LoopSignal,
// Smithay State
pub compositor_state: CompositorState,
pub xdg_shell_state: XdgShellState,
pub shm_state: ShmState,
pub output_manager_state: OutputManagerState,
pub seat_state: SeatState<Smallvil>,
pub data_device_state: DataDeviceState,
pub popups: PopupManager,
pub seat: Seat<Self>,
}
impl Smallvil {
pub fn new(event_loop: &mut EventLoop<Self>, display: Display<Self>) -> Self {
let start_time = std::time::Instant::now();
let dh = display.handle();
// Here we initialize implementations of some wayland protocols
// Some of them require us to implement traits on the Smallvil state,
// you can find those implementations in the `crate::handlers` module
// Initialize protocols needed for displaying windows
let compositor_state = CompositorState::new::<Self>(&dh);
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
let shm_state = ShmState::new::<Self>(&dh, vec![]);
let popups = PopupManager::default();
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&dh);
// Data device is responsible for clipboard and drag-and-drop
let data_device_state = DataDeviceState::new::<Self>(&dh);
// A seat is a group of keyboards, pointer and touch devices.
// A seat typically has a pointer and maintains a keyboard focus and a pointer focus.
let mut seat_state = SeatState::new();
let mut seat: Seat<Self> = seat_state.new_wl_seat(&dh, "winit");
// Notify clients that we have a keyboard, for the sake of the example we assume that keyboard is always present.
// You may want to track keyboard hot-plug in real compositor.
seat.add_keyboard(Default::default(), 200, 25).unwrap();
// Notify clients that we have a pointer (mouse)
// Here we assume that there is always pointer plugged in
seat.add_pointer();
// A space represents a two-dimensional plane. Windows and Outputs can be mapped onto it.
//
// Windows get a position and stacking order through mapping.
// Outputs become views of a part of the Space and can be rendered via Space::render_output.
let space = Space::default();
// Setup a wayland socket that will be used to accept clients
let socket_name = Self::init_wayland_listener(display, event_loop);
// Get the loop signal, used to stop the event loop
let loop_signal = event_loop.get_signal();
Self {
start_time,
display_handle: dh,
space,
loop_signal,
socket_name,
compositor_state,
xdg_shell_state,
shm_state,
output_manager_state,
seat_state,
data_device_state,
popups,
seat,
}
}
fn init_wayland_listener(display: Display<Smallvil>, event_loop: &mut EventLoop<Self>) -> OsString {
// Creates a new listening socket, automatically choosing the next available `wayland` socket name.
let listening_socket = ListeningSocketSource::new_auto().unwrap();
// Get the name of the listening socket.
// Clients will connect to this socket.
let socket_name = listening_socket.socket_name().to_os_string();
let loop_handle = event_loop.handle();
loop_handle
.insert_source(listening_socket, move |client_stream, _, state| {
// Inside the callback, you should insert the client into the display.
//
// You may also associate some data with the client when inserting the client.
state
.display_handle
.insert_client(client_stream, Arc::new(ClientState::default()))
.unwrap();
})
.expect("Failed to init the wayland event source.");
// You also need to add the display itself to the event loop, so that client events will be processed by wayland-server.
loop_handle
.insert_source(
Generic::new(display, Interest::READ, Mode::Level),
|_, display, state| {
// Safety: we don't drop the display
unsafe {
display.get_mut().dispatch_clients(state).unwrap();
}
Ok(PostAction::Continue)
},
)
.unwrap();
socket_name
}
pub fn surface_under(&self, pos: Point<f64, Logical>) -> Option<(WlSurface, Point<f64, Logical>)> {
self.space.element_under(pos).and_then(|(window, location)| {
window
.surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)
.map(|(s, p)| (s, (p + location).to_f64()))
})
}
}
/// Data associated with a wayland client that connects to Smallvil.
/// One instance of this type per client.
#[derive(Default)]
pub struct ClientState {
pub compositor_state: CompositorClientState,
}
impl ClientData for ClientState {
fn initialized(&self, _client_id: ClientId) {}
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
}
+110
View File
@@ -0,0 +1,110 @@
use std::time::Duration;
use smithay::{
backend::{
renderer::{
damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, gles::GlesRenderer,
},
winit::{self, WinitEvent},
},
output::{Mode, Output, PhysicalProperties, Subpixel},
reexports::calloop::EventLoop,
utils::{Rectangle, Transform},
};
use crate::Smallvil;
pub fn init_winit(
event_loop: &mut EventLoop<Smallvil>,
state: &mut Smallvil,
) -> Result<(), Box<dyn std::error::Error>> {
let (mut backend, winit) = winit::init()?;
let mode = Mode {
size: backend.window_size(),
refresh: 60_000,
};
let output = Output::new(
"winit".to_string(),
PhysicalProperties {
size: (0, 0).into(),
subpixel: Subpixel::Unknown,
make: "Smithay".into(),
model: "Winit".into(),
serial_number: "Unknown".into(),
},
);
let _global = output.create_global::<Smallvil>(&state.display_handle);
output.change_current_state(Some(mode), Some(Transform::Flipped180), None, Some((0, 0).into()));
output.set_preferred(mode);
state.space.map_output(&output, (0, 0));
let mut damage_tracker = OutputDamageTracker::from_output(&output);
event_loop.handle().insert_source(winit, move |event, _, state| {
match event {
WinitEvent::Resized { size, .. } => {
output.change_current_state(
Some(Mode {
size,
refresh: 60_000,
}),
None,
None,
None,
);
}
WinitEvent::Input(event) => state.process_input_event(event),
WinitEvent::Redraw => {
let size = backend.window_size();
let damage = Rectangle::from_size(size);
{
let (renderer, mut framebuffer) = backend.bind().unwrap();
smithay::desktop::space::render_output::<
_,
WaylandSurfaceRenderElement<GlesRenderer>,
_,
_,
>(
&output,
renderer,
&mut framebuffer,
1.0,
0,
[&state.space],
&[],
&mut damage_tracker,
[0.1, 0.1, 0.1, 1.0],
)
.unwrap();
}
backend.submit(Some(&[damage])).unwrap();
state.space.elements().for_each(|window| {
window.send_frame(
&output,
state.start_time.elapsed(),
Some(Duration::ZERO),
|_, _| Some(output.clone()),
)
});
state.space.refresh();
state.popups.cleanup();
let _ = state.display_handle.flush_clients();
// Ask for redraw to schedule new frame.
backend.window().request_redraw();
}
WinitEvent::CloseRequested => {
state.loop_signal.stop();
}
_ => (),
};
})?;
Ok(())
}