mirror of
https://github.com/YaLTeR/niri.git
synced 2025-10-05 16:12:47 +02:00
Init from smallvil
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/target
|
1591
Cargo.lock
generated
Normal file
1591
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
Cargo.toml
Normal file
17
Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "niri"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
bitflags = "2.2.1"
|
||||
|
||||
[dependencies.smithay]
|
||||
git = "https://github.com/Smithay/smithay"
|
||||
default-features = false
|
||||
features = [
|
||||
"backend_winit",
|
||||
"wayland_frontend",
|
||||
"desktop",
|
||||
]
|
5
src/grabs/mod.rs
Normal file
5
src/grabs/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod move_grab;
|
||||
pub use move_grab::MoveSurfaceGrab;
|
||||
|
||||
pub mod resize_grab;
|
||||
pub use resize_grab::ResizeSurfaceGrab;
|
75
src/grabs/move_grab.rs
Normal file
75
src/grabs/move_grab.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use crate::Smallvil;
|
||||
use smithay::{
|
||||
desktop::Window,
|
||||
input::pointer::{
|
||||
AxisFrame, ButtonEvent, 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<i32, 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<i32, 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(data, event.serial, event.time);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<Smallvil> {
|
||||
&self.start_data
|
||||
}
|
||||
}
|
278
src/grabs/resize_grab.rs
Normal file
278
src/grabs/resize_grab.rs
Normal file
@@ -0,0 +1,278 @@
|
||||
use crate::Smallvil;
|
||||
use smithay::{
|
||||
desktop::{Space, Window},
|
||||
input::pointer::{
|
||||
AxisFrame, ButtonEvent, 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().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<i32, 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().wl_surface(), |states| {
|
||||
let data = states.cached_state.current::<SurfaceCachedState>();
|
||||
(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 = (max_size.w == 0).then(i32::max_value).unwrap_or(max_size.w);
|
||||
let max_height = (max_size.h == 0).then(i32::max_value).unwrap_or(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();
|
||||
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<i32, 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(data, event.serial, event.time);
|
||||
|
||||
let xdg = self.window.toplevel();
|
||||
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 start_data(&self) -> &PointerGrabStartData<Smallvil> {
|
||||
&self.start_data
|
||||
}
|
||||
}
|
||||
|
||||
/// 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().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
src/handlers/compositor.rs
Normal file
57
src/handlers/compositor.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::{grabs::resize_grab, state::ClientState, Smallvil};
|
||||
use smithay::{
|
||||
backend::renderer::utils::on_commit_buffer_handler,
|
||||
delegate_compositor, delegate_shm,
|
||||
reexports::wayland_server::{
|
||||
protocol::{wl_buffer, wl_surface::WlSurface},
|
||||
Client,
|
||||
},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{
|
||||
get_parent, is_sync_subsurface, CompositorClientState, CompositorHandler, CompositorState,
|
||||
},
|
||||
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().wl_surface() == &root) {
|
||||
window.on_commit();
|
||||
}
|
||||
};
|
||||
|
||||
xdg_shell::handle_commit(&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
|
||||
}
|
||||
}
|
||||
|
||||
delegate_compositor!(Smallvil);
|
||||
delegate_shm!(Smallvil);
|
54
src/handlers/mod.rs
Normal file
54
src/handlers/mod.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
mod compositor;
|
||||
mod xdg_shell;
|
||||
|
||||
use crate::Smallvil;
|
||||
|
||||
//
|
||||
// Wl Seat
|
||||
//
|
||||
|
||||
use smithay::input::{SeatHandler, SeatState};
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::wayland::data_device::{ClientDndGrabHandler, DataDeviceHandler, ServerDndGrabHandler};
|
||||
use smithay::{delegate_data_device, delegate_output, delegate_seat};
|
||||
|
||||
impl SeatHandler for Smallvil {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Smallvil> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn cursor_image(
|
||||
&mut self,
|
||||
_seat: &smithay::input::Seat<Self>,
|
||||
_image: smithay::input::pointer::CursorImageStatus,
|
||||
) {
|
||||
}
|
||||
fn focus_changed(&mut self, _seat: &smithay::input::Seat<Self>, _focused: Option<&WlSurface>) {}
|
||||
}
|
||||
|
||||
delegate_seat!(Smallvil);
|
||||
|
||||
//
|
||||
// Wl Data Device
|
||||
//
|
||||
|
||||
impl DataDeviceHandler for Smallvil {
|
||||
type SelectionUserData = ();
|
||||
fn data_device_state(&self) -> &smithay::wayland::data_device::DataDeviceState {
|
||||
&self.data_device_state
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientDndGrabHandler for Smallvil {}
|
||||
impl ServerDndGrabHandler for Smallvil {}
|
||||
|
||||
delegate_data_device!(Smallvil);
|
||||
|
||||
//
|
||||
// Wl Output & Xdg Output
|
||||
//
|
||||
|
||||
delegate_output!(Smallvil);
|
163
src/handlers/xdg_shell.rs
Normal file
163
src/handlers/xdg_shell.rs
Normal file
@@ -0,0 +1,163 @@
|
||||
use smithay::{
|
||||
delegate_xdg_shell,
|
||||
desktop::{Space, Window},
|
||||
input::{
|
||||
pointer::{Focus, GrabStartData as PointerGrabStartData},
|
||||
Seat,
|
||||
},
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||
wayland_server::{
|
||||
protocol::{wl_seat, wl_surface::WlSurface},
|
||||
Resource,
|
||||
},
|
||||
},
|
||||
utils::{Rectangle, Serial},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
XdgToplevelSurfaceData,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
grabs::{MoveSurfaceGrab, ResizeSurfaceGrab},
|
||||
Smallvil,
|
||||
};
|
||||
|
||||
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(surface);
|
||||
self.space.map_element(window, (0, 0), false);
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {
|
||||
// TODO: Popup handling using PopupManager
|
||||
}
|
||||
|
||||
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().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().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::from_loc_and_size(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
|
||||
}
|
||||
}
|
||||
|
||||
// Xdg Shell
|
||||
delegate_xdg_shell!(Smallvil);
|
||||
|
||||
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(space: &Space<Window>, surface: &WlSurface) -> Option<()> {
|
||||
let window = space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().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().send_configure();
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
131
src/input.rs
Normal file
131
src/input.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
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_msec(&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_pointer(&pointer);
|
||||
|
||||
pointer.motion(
|
||||
self,
|
||||
under,
|
||||
&MotionEvent {
|
||||
location: pos,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
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().wl_surface().clone()), serial);
|
||||
self.space.elements().for_each(|window| {
|
||||
window.toplevel().send_pending_configure();
|
||||
});
|
||||
} else {
|
||||
self.space.elements().for_each(|window| {
|
||||
window.set_activated(false);
|
||||
window.toplevel().send_pending_configure();
|
||||
});
|
||||
keyboard.set_focus(self, Option::<WlSurface>::None, serial);
|
||||
}
|
||||
};
|
||||
|
||||
pointer.button(
|
||||
self,
|
||||
&ButtonEvent {
|
||||
button,
|
||||
state: button_state,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
InputEvent::PointerAxis { event, .. } => {
|
||||
let source = event.source();
|
||||
|
||||
let horizontal_amount = event
|
||||
.amount(Axis::Horizontal)
|
||||
.unwrap_or_else(|| event.amount_discrete(Axis::Horizontal).unwrap() * 3.0);
|
||||
let vertical_amount = event
|
||||
.amount(Axis::Vertical)
|
||||
.unwrap_or_else(|| event.amount_discrete(Axis::Vertical).unwrap() * 3.0);
|
||||
let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal);
|
||||
let vertical_amount_discrete = event.amount_discrete(Axis::Vertical);
|
||||
|
||||
let mut frame = AxisFrame::new(event.time_msec()).source(source);
|
||||
if horizontal_amount != 0.0 {
|
||||
frame = frame.value(Axis::Horizontal, horizontal_amount);
|
||||
if let Some(discrete) = horizontal_amount_discrete {
|
||||
frame = frame.discrete(Axis::Horizontal, discrete as i32);
|
||||
}
|
||||
} else if source == AxisSource::Finger {
|
||||
frame = frame.stop(Axis::Horizontal);
|
||||
}
|
||||
if vertical_amount != 0.0 {
|
||||
frame = frame.value(Axis::Vertical, vertical_amount);
|
||||
if let Some(discrete) = vertical_amount_discrete {
|
||||
frame = frame.discrete(Axis::Vertical, discrete as i32);
|
||||
}
|
||||
} else if source == AxisSource::Finger {
|
||||
frame = frame.stop(Axis::Vertical);
|
||||
}
|
||||
|
||||
self.seat.get_pointer().unwrap().axis(self, frame);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
52
src/main.rs
Normal file
52
src/main.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
#![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;
|
||||
|
||||
pub struct CalloopData {
|
||||
state: Smallvil,
|
||||
display: Display<Smallvil>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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();
|
||||
}
|
||||
|
||||
let mut event_loop: EventLoop<CalloopData> = EventLoop::try_new()?;
|
||||
|
||||
let mut display: Display<Smallvil> = Display::new()?;
|
||||
let state = Smallvil::new(&mut event_loop, &mut display);
|
||||
|
||||
let mut data = CalloopData { state, display };
|
||||
|
||||
crate::winit::init_winit(&mut event_loop, &mut data)?;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
event_loop.run(None, &mut data, move |_| {
|
||||
// Smallvil is running
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
164
src/state.rs
Normal file
164
src/state.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::{ffi::OsString, os::unix::io::AsRawFd, sync::Arc};
|
||||
|
||||
use smithay::{
|
||||
desktop::{Space, Window, WindowSurfaceType},
|
||||
input::{pointer::PointerHandle, Seat, SeatState},
|
||||
reexports::{
|
||||
calloop::{generic::Generic, EventLoop, Interest, LoopSignal, Mode, PostAction},
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_surface::WlSurface,
|
||||
Display,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point},
|
||||
wayland::{
|
||||
compositor::{CompositorClientState, CompositorState},
|
||||
data_device::DataDeviceState,
|
||||
output::OutputManagerState,
|
||||
shell::xdg::XdgShellState,
|
||||
shm::ShmState,
|
||||
socket::ListeningSocketSource,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::CalloopData;
|
||||
|
||||
pub struct Smallvil {
|
||||
pub start_time: std::time::Instant,
|
||||
pub socket_name: OsString,
|
||||
|
||||
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 seat: Seat<Self>,
|
||||
}
|
||||
|
||||
impl Smallvil {
|
||||
pub fn new(event_loop: &mut EventLoop<CalloopData>, display: &mut Display<Self>) -> Self {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let dh = display.handle();
|
||||
|
||||
let compositor_state = CompositorState::new::<Self>(&dh);
|
||||
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
|
||||
let shm_state = ShmState::new::<Self>(&dh, vec![]);
|
||||
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&dh);
|
||||
let mut seat_state = SeatState::new();
|
||||
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: 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, 200).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();
|
||||
|
||||
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,
|
||||
|
||||
space,
|
||||
loop_signal,
|
||||
socket_name,
|
||||
|
||||
compositor_state,
|
||||
xdg_shell_state,
|
||||
shm_state,
|
||||
output_manager_state,
|
||||
seat_state,
|
||||
data_device_state,
|
||||
seat,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_wayland_listener(
|
||||
display: &mut Display<Smallvil>,
|
||||
event_loop: &mut EventLoop<CalloopData>,
|
||||
) -> 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 handle = event_loop.handle();
|
||||
|
||||
event_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.
|
||||
handle
|
||||
.insert_source(
|
||||
Generic::new(
|
||||
display.backend().poll_fd().as_raw_fd(),
|
||||
Interest::READ,
|
||||
Mode::Level,
|
||||
),
|
||||
|_, _, state| {
|
||||
state.display.dispatch_clients(&mut state.state).unwrap();
|
||||
Ok(PostAction::Continue)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
socket_name
|
||||
}
|
||||
|
||||
pub fn surface_under_pointer(
|
||||
&self,
|
||||
pointer: &PointerHandle<Self>,
|
||||
) -> Option<(WlSurface, Point<i32, Logical>)> {
|
||||
let pos = pointer.current_location();
|
||||
self.space.element_under(pos).and_then(|(window, location)| {
|
||||
window
|
||||
.surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)
|
||||
.map(|(s, p)| (s, p + location))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[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) {}
|
||||
}
|
139
src/winit.rs
Normal file
139
src/winit.rs
Normal file
@@ -0,0 +1,139 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
renderer::{
|
||||
damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, gles::GlesRenderer,
|
||||
},
|
||||
winit::{self, WinitError, WinitEvent, WinitEventLoop, WinitGraphicsBackend},
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::calloop::{
|
||||
timer::{TimeoutAction, Timer},
|
||||
EventLoop,
|
||||
},
|
||||
utils::{Rectangle, Transform},
|
||||
};
|
||||
|
||||
use crate::{CalloopData, Smallvil};
|
||||
|
||||
pub fn init_winit(
|
||||
event_loop: &mut EventLoop<CalloopData>,
|
||||
data: &mut CalloopData,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let display = &mut data.display;
|
||||
let state = &mut data.state;
|
||||
|
||||
let (mut backend, mut winit) = winit::init()?;
|
||||
|
||||
let mode = Mode {
|
||||
size: backend.window_size().physical_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(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<Smallvil>(&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);
|
||||
|
||||
std::env::set_var("WAYLAND_DISPLAY", &state.socket_name);
|
||||
|
||||
let mut full_redraw = 0u8;
|
||||
|
||||
let timer = Timer::immediate();
|
||||
event_loop.handle().insert_source(timer, move |_, _, data| {
|
||||
winit_dispatch(
|
||||
&mut backend,
|
||||
&mut winit,
|
||||
data,
|
||||
&output,
|
||||
&mut damage_tracker,
|
||||
&mut full_redraw,
|
||||
)
|
||||
.unwrap();
|
||||
TimeoutAction::ToDuration(Duration::from_millis(16))
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn winit_dispatch(
|
||||
backend: &mut WinitGraphicsBackend<GlesRenderer>,
|
||||
winit: &mut WinitEventLoop,
|
||||
data: &mut CalloopData,
|
||||
output: &Output,
|
||||
damage_tracker: &mut OutputDamageTracker,
|
||||
full_redraw: &mut u8,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let display = &mut data.display;
|
||||
let state = &mut data.state;
|
||||
|
||||
let res = winit.dispatch_new_events(|event| 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),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
if let Err(WinitError::WindowClosed) = res {
|
||||
// Stop the loop
|
||||
state.loop_signal.stop();
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
res?;
|
||||
}
|
||||
|
||||
*full_redraw = full_redraw.saturating_sub(1);
|
||||
|
||||
let size = backend.window_size().physical_size;
|
||||
let damage = Rectangle::from_loc_and_size((0, 0), size);
|
||||
|
||||
backend.bind()?;
|
||||
smithay::desktop::space::render_output::<_, WaylandSurfaceRenderElement<GlesRenderer>, _, _>(
|
||||
output,
|
||||
backend.renderer(),
|
||||
1.0,
|
||||
0,
|
||||
[&state.space],
|
||||
&[],
|
||||
damage_tracker,
|
||||
[0.1, 0.1, 0.1, 1.0],
|
||||
)?;
|
||||
backend.submit(Some(&[damage]))?;
|
||||
|
||||
state.space.elements().for_each(|window| {
|
||||
window.send_frame(
|
||||
output,
|
||||
state.start_time.elapsed(),
|
||||
Some(Duration::ZERO),
|
||||
|_, _| Some(output.clone()),
|
||||
)
|
||||
});
|
||||
|
||||
state.space.refresh();
|
||||
display.flush_clients()?;
|
||||
|
||||
Ok(())
|
||||
}
|
Reference in New Issue
Block a user