mirror of
https://github.com/marcoallegretti/WEFT_OS.git
synced 2026-03-26 17:03:09 +00:00
feat(compositor): add weft-compositor crate
- Implement WeftCompositorState with all Wayland protocol globals: compositor, xdg-shell, layer-shell, shm, dmabuf, output, presentation, text-input, input-method, pointer-constraints, cursor-shape, seat. - Implement process_input_event covering keyboard, pointer (relative + absolute), axis, touch, and all gesture types (swipe, pinch, hold). - Implement Winit backend with damage-tracked rendering loop and frame callbacks. - Add DRM/KMS backend skeleton: libseat session, udev device discovery, calloop integration (rendering path deferred). - Add infra/systemd/weft-compositor.service (Type=notify). - Split CI into cross-platform and linux-only jobs. - Exclude weft-compositor from Windows check scripts.
This commit is contained in:
parent
3db9541f72
commit
feb69be199
13 changed files with 3775 additions and 10 deletions
38
.github/workflows/ci.yml
vendored
38
.github/workflows/ci.yml
vendored
|
|
@ -7,7 +7,8 @@ on:
|
|||
pull_request:
|
||||
|
||||
jobs:
|
||||
rust:
|
||||
# Crates that must compile on every supported host platform.
|
||||
cross-platform:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
|
@ -23,7 +24,34 @@ jobs:
|
|||
components: rustfmt, clippy
|
||||
- name: cargo fmt
|
||||
run: cargo fmt --all --check
|
||||
- name: cargo clippy
|
||||
run: cargo clippy --workspace --all-targets -- -D warnings
|
||||
- name: cargo test
|
||||
run: cargo test --workspace
|
||||
- name: cargo clippy (cross-platform crates)
|
||||
run: cargo clippy --workspace --exclude weft-compositor --all-targets -- -D warnings
|
||||
- name: cargo test (cross-platform crates)
|
||||
run: cargo test --workspace --exclude weft-compositor
|
||||
|
||||
# Wayland compositor and other Linux-only system crates.
|
||||
# These require libwayland-server and other Linux kernel interfaces.
|
||||
linux-only:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: 1.93.0
|
||||
components: rustfmt, clippy
|
||||
- name: install Linux system dependencies
|
||||
run: |
|
||||
sudo apt-get update -q
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
libwayland-dev \
|
||||
libxkbcommon-dev \
|
||||
libegl-dev \
|
||||
libgles2-mesa-dev \
|
||||
libinput-dev \
|
||||
libseat-dev \
|
||||
libudev-dev \
|
||||
pkg-config
|
||||
- name: cargo clippy (weft-compositor)
|
||||
run: cargo clippy -p weft-compositor --all-targets -- -D warnings
|
||||
- name: cargo test (weft-compositor)
|
||||
run: cargo test -p weft-compositor
|
||||
|
|
|
|||
2555
Cargo.lock
generated
2555
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
[workspace]
|
||||
members = ["crates/weft-build-meta"]
|
||||
members = ["crates/weft-build-meta", "crates/weft-compositor"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
|
|
|
|||
41
crates/weft-compositor/Cargo.toml
Normal file
41
crates/weft-compositor/Cargo.toml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[package]
|
||||
name = "weft-compositor"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "weft-compositor"
|
||||
path = "src/main.rs"
|
||||
|
||||
# Features available on all build platforms.
|
||||
[dependencies]
|
||||
smithay = { version = "0.7", default-features = false, features = [
|
||||
"backend_egl",
|
||||
"backend_winit",
|
||||
"renderer_gl",
|
||||
"wayland_frontend",
|
||||
"desktop",
|
||||
] }
|
||||
calloop = { version = "0.14", features = ["executor"] }
|
||||
calloop-wayland-source = "0.4"
|
||||
wayland-server = "0.31"
|
||||
wayland-protocols = { version = "0.32", features = ["server", "unstable"] }
|
||||
wayland-protocols-wlr = { version = "0.3", features = ["server"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
anyhow = "1"
|
||||
|
||||
# DRM/KMS and hardware input depend on Linux kernel interfaces; compile only on Linux.
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
smithay = { version = "0.7", default-features = false, features = [
|
||||
"backend_drm",
|
||||
"backend_gbm",
|
||||
"backend_libinput",
|
||||
"backend_udev",
|
||||
"backend_session_libseat",
|
||||
] }
|
||||
94
crates/weft-compositor/src/backend/drm.rs
Normal file
94
crates/weft-compositor/src/backend/drm.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// Non-Linux: DRM/KMS backend is unavailable; callers must use --winit.
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
anyhow::bail!("DRM/KMS backend requires Linux; pass --winit for development on other platforms")
|
||||
}
|
||||
|
||||
// Linux DRM/KMS backend.
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
|
||||
drm::{DrmDevice, DrmDeviceFd, DrmNode, NodeType},
|
||||
egl::EGLDevice,
|
||||
libinput::{LibinputInputBackend, LibinputSessionInterface},
|
||||
renderer::{
|
||||
damage::OutputDamageTracker,
|
||||
gles::GlesRenderer,
|
||||
multigpu::{gbm::GbmGlesBackend, GpuManager, MultiRenderer},
|
||||
},
|
||||
session::{
|
||||
libseat::{LibSeatSession, LibSeatSessionNotifier},
|
||||
Session,
|
||||
},
|
||||
udev::{UdevBackend, UdevEvent},
|
||||
},
|
||||
desktop::{space::space_render_elements, Space, Window},
|
||||
output::{Mode as OutputMode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
calloop::{
|
||||
timer::{TimeoutAction, Timer},
|
||||
EventLoop, Interest, Mode, PostAction,
|
||||
},
|
||||
wayland_server::Display,
|
||||
},
|
||||
utils::Transform,
|
||||
};
|
||||
|
||||
use crate::{input, state::WeftCompositorState};
|
||||
|
||||
let mut display: Display<WeftCompositorState> = Display::new()?;
|
||||
let display_handle = display.handle();
|
||||
|
||||
let mut event_loop: EventLoop<'static, WeftCompositorState> = EventLoop::try_new()?;
|
||||
let loop_handle = event_loop.handle();
|
||||
let loop_signal = event_loop.get_signal();
|
||||
|
||||
// Open a libseat session to gain DRM device access without root.
|
||||
let (session, notifier) = LibSeatSession::new()
|
||||
.map_err(|e| anyhow::anyhow!("libseat session failed: {e}"))?;
|
||||
|
||||
// Discover GPU nodes via udev.
|
||||
let udev_backend = UdevBackend::new(session.seat())?;
|
||||
|
||||
let mut state = WeftCompositorState::new(
|
||||
display_handle,
|
||||
loop_signal.clone(),
|
||||
loop_handle.clone(),
|
||||
session.seat(),
|
||||
);
|
||||
|
||||
// Register the udev backend with calloop so device hotplug is handled.
|
||||
loop_handle.insert_source(udev_backend, {
|
||||
let signal = loop_signal.clone();
|
||||
move |event, _, _state| match event {
|
||||
UdevEvent::Added { device_id, path } => {
|
||||
tracing::info!(?device_id, ?path, "GPU device added");
|
||||
}
|
||||
UdevEvent::Changed { device_id } => {
|
||||
tracing::debug!(?device_id, "GPU device changed");
|
||||
}
|
||||
UdevEvent::Removed { device_id } => {
|
||||
tracing::info!(?device_id, "GPU device removed");
|
||||
signal.stop();
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
tracing::info!("DRM/KMS backend initialised; entering event loop");
|
||||
|
||||
loop {
|
||||
display.dispatch_clients(&mut state)?;
|
||||
display.flush_clients()?;
|
||||
event_loop.dispatch(Some(Duration::from_millis(16)), &mut state)?;
|
||||
|
||||
if loop_signal.is_stopped() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
2
crates/weft-compositor/src/backend/mod.rs
Normal file
2
crates/weft-compositor/src/backend/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
pub mod drm;
|
||||
pub mod winit;
|
||||
170
crates/weft-compositor/src/backend/winit.rs
Normal file
170
crates/weft-compositor/src/backend/winit.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
renderer::{damage::OutputDamageTracker, gles::GlesRenderer},
|
||||
winit::{self, WinitEvent, WinitEventLoop, WinitGraphicsBackend},
|
||||
},
|
||||
desktop::{space::space_render_elements, Space, Window},
|
||||
output::{Mode as OutputMode, Output, PhysicalProperties, Scale, Subpixel},
|
||||
reexports::{calloop::EventLoop, wayland_server::Display},
|
||||
utils::Transform,
|
||||
};
|
||||
|
||||
use crate::{input, state::WeftCompositorState};
|
||||
|
||||
pub fn run() -> anyhow::Result<()> {
|
||||
let mut display: Display<WeftCompositorState> = Display::new()?;
|
||||
let display_handle = display.handle();
|
||||
|
||||
let mut event_loop: EventLoop<'static, WeftCompositorState> = EventLoop::try_new()?;
|
||||
let loop_handle = event_loop.handle();
|
||||
let loop_signal = event_loop.get_signal();
|
||||
let (mut winit_backend, mut winit_evt_loop) = winit::init::<GlesRenderer>()
|
||||
.map_err(|e| anyhow::anyhow!("winit backend init failed: {e}"))?;
|
||||
|
||||
let initial_size = winit_backend.window_size();
|
||||
let output = Output::new(
|
||||
"WEFT-winit".to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "WEFT".to_string(),
|
||||
model: "Winit".to_string(),
|
||||
},
|
||||
);
|
||||
let _wl_output_global = output.create_global::<WeftCompositorState>(&display_handle);
|
||||
|
||||
let initial_mode = OutputMode {
|
||||
size: initial_size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.change_current_state(
|
||||
Some(initial_mode),
|
||||
Some(Transform::Flipped180),
|
||||
None,
|
||||
Some((0, 0).into()),
|
||||
);
|
||||
output.set_preferred(initial_mode);
|
||||
|
||||
let mut state = WeftCompositorState::new(
|
||||
display_handle,
|
||||
loop_signal,
|
||||
loop_handle,
|
||||
"seat-0".to_string(),
|
||||
);
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
let mut damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
let start = std::time::Instant::now();
|
||||
|
||||
loop {
|
||||
let dispatch_result = dispatch_winit_events(
|
||||
&mut winit_evt_loop,
|
||||
&mut state,
|
||||
&output,
|
||||
&mut damage_tracker,
|
||||
);
|
||||
|
||||
if dispatch_result.is_err() || !state.running {
|
||||
break;
|
||||
}
|
||||
|
||||
display.dispatch_clients(&mut state)?;
|
||||
|
||||
render_frame(
|
||||
&mut winit_backend,
|
||||
&mut damage_tracker,
|
||||
&mut state,
|
||||
&output,
|
||||
start.elapsed(),
|
||||
)?;
|
||||
|
||||
display.flush_clients()?;
|
||||
|
||||
// Run any registered calloop sources (timers, signals) with a zero timeout so
|
||||
// the loop stays responsive without blocking.
|
||||
event_loop.dispatch(Some(Duration::ZERO), &mut state)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_winit_events(
|
||||
evt_loop: &mut WinitEventLoop,
|
||||
state: &mut WeftCompositorState,
|
||||
output: &Output,
|
||||
damage_tracker: &mut OutputDamageTracker,
|
||||
) -> Result<(), ()> {
|
||||
evt_loop
|
||||
.dispatch_new_events(|event| match event {
|
||||
WinitEvent::Resized { size, scale_factor } => {
|
||||
let new_mode = OutputMode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.change_current_state(
|
||||
Some(new_mode),
|
||||
None,
|
||||
Some(Scale::Fractional(scale_factor)),
|
||||
None,
|
||||
);
|
||||
output.set_preferred(new_mode);
|
||||
state.space.map_output(output, (0, 0));
|
||||
*damage_tracker = OutputDamageTracker::from_output(output);
|
||||
}
|
||||
WinitEvent::Input(input_event) => {
|
||||
input::process_input_event(state, input_event);
|
||||
}
|
||||
WinitEvent::Focus(_focused) => {}
|
||||
WinitEvent::Refresh => {}
|
||||
WinitEvent::CloseRequested => {
|
||||
state.running = false;
|
||||
}
|
||||
})
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
fn render_frame(
|
||||
backend: &mut WinitGraphicsBackend<GlesRenderer>,
|
||||
damage_tracker: &mut OutputDamageTracker,
|
||||
state: &mut WeftCompositorState,
|
||||
output: &Output,
|
||||
elapsed: Duration,
|
||||
) -> anyhow::Result<()> {
|
||||
backend
|
||||
.bind()
|
||||
.map_err(|e| anyhow::anyhow!("framebuffer bind failed: {e}"))?;
|
||||
|
||||
let age = backend.buffer_age().unwrap_or(0);
|
||||
let renderer = backend.renderer();
|
||||
|
||||
let elements =
|
||||
space_render_elements::<GlesRenderer, Window, &Space<Window>>(
|
||||
renderer,
|
||||
[&state.space],
|
||||
output,
|
||||
1.0_f64,
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("render element collection failed: {e}"))?;
|
||||
|
||||
let result = damage_tracker
|
||||
.render_output(renderer, age, &elements, [0.1_f32, 0.1, 0.1, 1.0])
|
||||
.map_err(|e| anyhow::anyhow!("render_output failed: {e}"))?;
|
||||
|
||||
backend
|
||||
.submit(result.damage.as_deref())
|
||||
.map_err(|e| anyhow::anyhow!("buffer submit failed: {e}"))?;
|
||||
|
||||
// Notify clients that a new frame has been presented so they can submit the next buffer.
|
||||
for window in state.space.elements() {
|
||||
window.send_frame(
|
||||
output,
|
||||
elapsed,
|
||||
Some(Duration::from_secs(1) / 60),
|
||||
|_, _| Some(output.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
462
crates/weft-compositor/src/input.rs
Normal file
462
crates/weft-compositor/src/input.rs
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
use smithay::{
|
||||
backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, GestureHoldBeginEvent,
|
||||
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, InputBackend, InputEvent, KeyState, KeyboardKeyEvent,
|
||||
PointerAxisEvent, PointerButtonEvent, PointerMotionAbsoluteEvent, PointerMotionEvent,
|
||||
TouchCancelEvent, TouchDownEvent, TouchFrameEvent, TouchMotionEvent, TouchUpEvent,
|
||||
},
|
||||
input::{
|
||||
keyboard::{FilterResult, KeysymHandle, ModifiersState},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, RelativeMotionEvent},
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_pointer,
|
||||
utils::{Logical, Point, Serial, SERIAL_COUNTER},
|
||||
};
|
||||
|
||||
use crate::state::WeftCompositorState;
|
||||
|
||||
pub fn process_input_event<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: InputEvent<B>,
|
||||
) {
|
||||
match event {
|
||||
InputEvent::Keyboard { event } => handle_keyboard(state, event),
|
||||
InputEvent::PointerMotion { event } => handle_pointer_motion(state, event),
|
||||
InputEvent::PointerMotionAbsolute { event } => {
|
||||
handle_pointer_motion_absolute(state, event)
|
||||
}
|
||||
InputEvent::PointerButton { event } => handle_pointer_button(state, event),
|
||||
InputEvent::PointerAxis { event } => handle_pointer_axis(state, event),
|
||||
InputEvent::TouchDown { event } => handle_touch_down(state, event),
|
||||
InputEvent::TouchUp { event } => handle_touch_up(state, event),
|
||||
InputEvent::TouchMotion { event } => handle_touch_motion(state, event),
|
||||
InputEvent::TouchFrame { event } => handle_touch_frame(state, event),
|
||||
InputEvent::TouchCancel { event } => handle_touch_cancel(state, event),
|
||||
InputEvent::GestureSwipeBegin { event } => handle_gesture_swipe_begin(state, event),
|
||||
InputEvent::GestureSwipeUpdate { event } => handle_gesture_swipe_update(state, event),
|
||||
InputEvent::GestureSwipeEnd { event } => handle_gesture_swipe_end(state, event),
|
||||
InputEvent::GesturePinchBegin { event } => handle_gesture_pinch_begin(state, event),
|
||||
InputEvent::GesturePinchUpdate { event } => handle_gesture_pinch_update(state, event),
|
||||
InputEvent::GesturePinchEnd { event } => handle_gesture_pinch_end(state, event),
|
||||
InputEvent::GestureHoldBegin { event } => handle_gesture_hold_begin(state, event),
|
||||
InputEvent::GestureHoldEnd { event } => handle_gesture_hold_end(state, event),
|
||||
// Device added/removed events are handled at the backend level.
|
||||
InputEvent::DeviceAdded { .. } | InputEvent::DeviceRemoved { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_keyboard<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::KeyboardKeyEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let time = event.time_msec();
|
||||
let key_state = event.state();
|
||||
|
||||
if let Some(keyboard) = state.seat.get_keyboard() {
|
||||
keyboard.input::<(), _>(
|
||||
state,
|
||||
event.key_code(),
|
||||
key_state,
|
||||
serial,
|
||||
time,
|
||||
|_state, _mods, _keysym| FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pointer_motion<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::PointerMotionEvent,
|
||||
) {
|
||||
let delta = event.delta();
|
||||
state.pointer_location += delta;
|
||||
clamp_pointer_to_output_space(state);
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let pointer_location = state.pointer_location;
|
||||
let under = surface_under(state, pointer_location);
|
||||
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.motion(
|
||||
state,
|
||||
under,
|
||||
&MotionEvent {
|
||||
location: pointer_location,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pointer_motion_absolute<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::PointerMotionAbsoluteEvent,
|
||||
) {
|
||||
let output = state.space.outputs().next().cloned();
|
||||
if let Some(output) = output {
|
||||
let output_geo = state
|
||||
.space
|
||||
.output_geometry(&output)
|
||||
.unwrap_or_default();
|
||||
let pos = event.position_transformed(output_geo.size);
|
||||
state.pointer_location = output_geo.loc.to_f64() + pos;
|
||||
}
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let pointer_location = state.pointer_location;
|
||||
let under = surface_under(state, pointer_location);
|
||||
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.motion(
|
||||
state,
|
||||
under,
|
||||
&MotionEvent {
|
||||
location: pointer_location,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pointer_button<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::PointerButtonEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let button = event.button_code();
|
||||
let button_state = event.state();
|
||||
|
||||
// On press: focus the surface under the pointer.
|
||||
if button_state == ButtonState::Pressed {
|
||||
let pointer_location = state.pointer_location;
|
||||
if let Some((surface, _loc)) = surface_under(state, pointer_location) {
|
||||
if let Some(keyboard) = state.seat.get_keyboard() {
|
||||
keyboard.set_focus(state, Some(surface.clone()), serial);
|
||||
}
|
||||
} else if let Some(keyboard) = state.seat.get_keyboard() {
|
||||
keyboard.set_focus(state, None, serial);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.button(
|
||||
state,
|
||||
&ButtonEvent {
|
||||
button,
|
||||
state: button_state,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
pointer.frame(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_pointer_axis<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::PointerAxisEvent,
|
||||
) {
|
||||
let horizontal = event.amount(Axis::Horizontal);
|
||||
let vertical = event.amount(Axis::Vertical);
|
||||
let h_discrete = event.amount_v120(Axis::Horizontal);
|
||||
let v_discrete = event.amount_v120(Axis::Vertical);
|
||||
let source = event.source();
|
||||
|
||||
let mut frame = AxisFrame::new(event.time_msec()).source(source);
|
||||
|
||||
if let Some(v) = horizontal {
|
||||
if v != 0.0 {
|
||||
frame = frame.value(Axis::Horizontal, v);
|
||||
}
|
||||
if let Some(d) = h_discrete {
|
||||
frame = frame.v120(Axis::Horizontal, d as i32);
|
||||
}
|
||||
}
|
||||
if let Some(v) = vertical {
|
||||
if v != 0.0 {
|
||||
frame = frame.value(Axis::Vertical, v);
|
||||
}
|
||||
if let Some(d) = v_discrete {
|
||||
frame = frame.v120(Axis::Vertical, d as i32);
|
||||
}
|
||||
}
|
||||
|
||||
if source == AxisSource::Finger {
|
||||
if event.amount(Axis::Horizontal).unwrap_or(0.0) == 0.0 {
|
||||
frame = frame.stop(Axis::Horizontal);
|
||||
}
|
||||
if event.amount(Axis::Vertical).unwrap_or(0.0) == 0.0 {
|
||||
frame = frame.stop(Axis::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.axis(state, frame);
|
||||
pointer.frame(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_touch_down<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::TouchDownEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let output = state.space.outputs().next().cloned();
|
||||
if let Some(output) = output {
|
||||
let output_geo = state.space.output_geometry(&output).unwrap_or_default();
|
||||
let pos = event.position_transformed(output_geo.size);
|
||||
let location = output_geo.loc.to_f64() + pos;
|
||||
let under = surface_under(state, location);
|
||||
|
||||
if let Some(touch) = state.seat.get_touch() {
|
||||
touch.down(
|
||||
state,
|
||||
under,
|
||||
&smithay::input::touch::DownEvent {
|
||||
slot: event.slot(),
|
||||
location,
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_touch_up<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::TouchUpEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(touch) = state.seat.get_touch() {
|
||||
touch.up(
|
||||
state,
|
||||
&smithay::input::touch::UpEvent {
|
||||
slot: event.slot(),
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_touch_motion<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::TouchMotionEvent,
|
||||
) {
|
||||
let output = state.space.outputs().next().cloned();
|
||||
if let Some(output) = output {
|
||||
let output_geo = state.space.output_geometry(&output).unwrap_or_default();
|
||||
let pos = event.position_transformed(output_geo.size);
|
||||
let location = output_geo.loc.to_f64() + pos;
|
||||
let under = surface_under(state, location);
|
||||
|
||||
if let Some(touch) = state.seat.get_touch() {
|
||||
touch.motion(
|
||||
state,
|
||||
under,
|
||||
&smithay::input::touch::MotionEvent {
|
||||
slot: event.slot(),
|
||||
location,
|
||||
time: event.time_msec(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_touch_frame<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
_event: B::TouchFrameEvent,
|
||||
) {
|
||||
if let Some(touch) = state.seat.get_touch() {
|
||||
touch.frame(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_touch_cancel<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
_event: B::TouchCancelEvent,
|
||||
) {
|
||||
if let Some(touch) = state.seat.get_touch() {
|
||||
touch.cancel(state);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_swipe_begin<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GestureSwipeBeginEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_swipe_begin(
|
||||
state,
|
||||
&smithay::input::pointer::GestureSwipeBeginEvent {
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
fingers: event.fingers(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_swipe_update<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GestureSwipeUpdateEvent,
|
||||
) {
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_swipe_update(
|
||||
state,
|
||||
&smithay::input::pointer::GestureSwipeUpdateEvent {
|
||||
time: event.time_msec(),
|
||||
delta: event.delta(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_swipe_end<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GestureSwipeEndEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_swipe_end(
|
||||
state,
|
||||
&smithay::input::pointer::GestureSwipeEndEvent {
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
cancelled: event.cancelled(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_pinch_begin<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GesturePinchBeginEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_pinch_begin(
|
||||
state,
|
||||
&smithay::input::pointer::GesturePinchBeginEvent {
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
fingers: event.fingers(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_pinch_update<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GesturePinchUpdateEvent,
|
||||
) {
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_pinch_update(
|
||||
state,
|
||||
&smithay::input::pointer::GesturePinchUpdateEvent {
|
||||
time: event.time_msec(),
|
||||
delta: event.delta(),
|
||||
scale: event.scale(),
|
||||
rotation: event.rotation(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_pinch_end<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GesturePinchEndEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_pinch_end(
|
||||
state,
|
||||
&smithay::input::pointer::GesturePinchEndEvent {
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
cancelled: event.cancelled(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_hold_begin<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GestureHoldBeginEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_hold_begin(
|
||||
state,
|
||||
&smithay::input::pointer::GestureHoldBeginEvent {
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
fingers: event.fingers(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_gesture_hold_end<B: InputBackend>(
|
||||
state: &mut WeftCompositorState,
|
||||
event: B::GestureHoldEndEvent,
|
||||
) {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
if let Some(pointer) = state.seat.get_pointer() {
|
||||
pointer.gesture_hold_end(
|
||||
state,
|
||||
&smithay::input::pointer::GestureHoldEndEvent {
|
||||
serial,
|
||||
time: event.time_msec(),
|
||||
cancelled: event.cancelled(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the surface and its local coordinates under the given position.
|
||||
pub fn surface_under(
|
||||
state: &WeftCompositorState,
|
||||
point: Point<f64, Logical>,
|
||||
) -> Option<(smithay::reexports::wayland_server::protocol::wl_surface::WlSurface, Point<f64, Logical>)> {
|
||||
state
|
||||
.space
|
||||
.element_under(point)
|
||||
.and_then(|(window, loc)| {
|
||||
window
|
||||
.surface_under(point - loc.to_f64(), smithay::desktop::WindowSurfaceType::ALL)
|
||||
.map(|(surface, surface_loc)| (surface, (loc.to_f64() + surface_loc.to_f64())))
|
||||
})
|
||||
}
|
||||
|
||||
fn clamp_pointer_to_output_space(state: &mut WeftCompositorState) {
|
||||
let bbox = state
|
||||
.space
|
||||
.outputs()
|
||||
.filter_map(|o| state.space.output_geometry(o))
|
||||
.fold(
|
||||
smithay::utils::Rectangle::<i32, Logical>::default(),
|
||||
|acc, r| acc.merge(r),
|
||||
);
|
||||
if bbox.size.w > 0 && bbox.size.h > 0 {
|
||||
state.pointer_location.x = state
|
||||
.pointer_location
|
||||
.x
|
||||
.clamp(bbox.loc.x as f64, (bbox.loc.x + bbox.size.w - 1) as f64);
|
||||
state.pointer_location.y = state
|
||||
.pointer_location
|
||||
.y
|
||||
.clamp(bbox.loc.y as f64, (bbox.loc.y + bbox.size.h - 1) as f64);
|
||||
}
|
||||
}
|
||||
26
crates/weft-compositor/src/main.rs
Normal file
26
crates/weft-compositor/src/main.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
mod backend;
|
||||
mod input;
|
||||
mod state;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
|
||||
)
|
||||
.init();
|
||||
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let use_winit = args.iter().any(|a| a == "--winit")
|
||||
|| std::env::var("DISPLAY").is_ok()
|
||||
|| std::env::var("WAYLAND_DISPLAY").is_ok();
|
||||
|
||||
if use_winit {
|
||||
tracing::info!("starting compositor with winit backend");
|
||||
backend::winit::run()
|
||||
} else {
|
||||
tracing::info!("starting compositor with DRM/KMS backend");
|
||||
backend::drm::run()
|
||||
}
|
||||
}
|
||||
366
crates/weft-compositor/src/state.rs
Normal file
366
crates/weft-compositor/src/state.rs
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
use smithay::{
|
||||
backend::renderer::utils::on_commit_buffer_handler,
|
||||
delegate_compositor, delegate_cursor_shape, delegate_dmabuf, delegate_input_method_manager,
|
||||
delegate_layer_shell, delegate_output, delegate_pointer_constraints, delegate_presentation,
|
||||
delegate_seat, delegate_shm, delegate_text_input_manager, delegate_xdg_shell,
|
||||
desktop::{
|
||||
layer_map_for_output, PopupKind, PopupManager, Space, Window, WindowSurfaceType,
|
||||
},
|
||||
input::{
|
||||
keyboard::XkbConfig,
|
||||
pointer::CursorImageStatus,
|
||||
Seat, SeatHandler, SeatState,
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
calloop::{LoopHandle, LoopSignal},
|
||||
wayland_server::{
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::{wl_output::WlOutput, wl_surface::WlSurface},
|
||||
Client, DisplayHandle,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point, Rectangle},
|
||||
wayland::{
|
||||
compositor::{CompositorClientState, CompositorHandler, CompositorState},
|
||||
cursor_shape::{CursorShapeHandler, CursorShapeManagerState},
|
||||
dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier},
|
||||
input_method::{InputMethodHandler, InputMethodManagerState},
|
||||
output::OutputManagerState,
|
||||
pointer_constraints::{PointerConstraintsHandler, PointerConstraintsState},
|
||||
presentation::{PresentationHandler, PresentationState},
|
||||
seat::WaylandFocus,
|
||||
shell::{
|
||||
wlr_layer::{Layer, LayerSurface, WlrLayerShellHandler, WlrLayerShellState},
|
||||
xdg::{PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState},
|
||||
},
|
||||
shm::{ShmHandler, ShmState},
|
||||
text_input::{TextInputHandler, TextInputManagerState},
|
||||
},
|
||||
};
|
||||
|
||||
// Per-client state that Smithay compositor protocol handlers need.
|
||||
#[derive(Default)]
|
||||
pub struct WeftClientState {
|
||||
pub compositor_state: CompositorClientState,
|
||||
}
|
||||
|
||||
impl ClientData for WeftClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {}
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
||||
}
|
||||
|
||||
pub struct WeftCompositorState {
|
||||
pub display_handle: DisplayHandle,
|
||||
pub loop_signal: LoopSignal,
|
||||
pub loop_handle: LoopHandle<'static, WeftCompositorState>,
|
||||
|
||||
// Wayland protocol globals
|
||||
pub compositor_state: CompositorState,
|
||||
pub xdg_shell_state: XdgShellState,
|
||||
pub layer_shell_state: WlrLayerShellState,
|
||||
pub shm_state: ShmState,
|
||||
pub dmabuf_state: DmabufState,
|
||||
pub output_manager_state: OutputManagerState,
|
||||
pub presentation_state: PresentationState,
|
||||
pub text_input_state: TextInputManagerState,
|
||||
pub input_method_state: InputMethodManagerState,
|
||||
pub pointer_constraints_state: PointerConstraintsState,
|
||||
pub cursor_shape_state: CursorShapeManagerState,
|
||||
|
||||
// Desktop abstraction layer
|
||||
pub space: Space<Window>,
|
||||
pub popups: PopupManager,
|
||||
|
||||
// Seat and input state
|
||||
pub seat_state: SeatState<Self>,
|
||||
pub seat: Seat<Self>,
|
||||
pub pointer_location: Point<f64, Logical>,
|
||||
pub cursor_image_status: CursorImageStatus,
|
||||
|
||||
// Set by the backend after renderer initialisation when DMA-BUF is supported.
|
||||
pub dmabuf_global: Option<DmabufGlobal>,
|
||||
|
||||
// Set to false when the compositor should exit the event loop.
|
||||
pub running: bool,
|
||||
}
|
||||
|
||||
impl WeftCompositorState {
|
||||
pub fn new(
|
||||
display_handle: DisplayHandle,
|
||||
loop_signal: LoopSignal,
|
||||
loop_handle: LoopHandle<'static, Self>,
|
||||
seat_name: String,
|
||||
) -> Self {
|
||||
let compositor_state = CompositorState::new::<Self>(&display_handle);
|
||||
let xdg_shell_state = XdgShellState::new::<Self>(&display_handle);
|
||||
let layer_shell_state = WlrLayerShellState::new::<Self>(&display_handle);
|
||||
let shm_state = ShmState::new::<Self>(&display_handle, vec![]);
|
||||
let dmabuf_state = DmabufState::new();
|
||||
let output_manager_state =
|
||||
OutputManagerState::new_with_xdg_output::<Self>(&display_handle);
|
||||
// Clock ID 1 = CLOCK_MONOTONIC
|
||||
let presentation_state = PresentationState::new::<Self>(&display_handle, 1);
|
||||
let text_input_state = TextInputManagerState::new::<Self>(&display_handle);
|
||||
let input_method_state =
|
||||
InputMethodManagerState::new::<Self, _>(&display_handle, |_client| true);
|
||||
let pointer_constraints_state = PointerConstraintsState::new::<Self>(&display_handle);
|
||||
let cursor_shape_state = CursorShapeManagerState::new::<Self>(&display_handle);
|
||||
|
||||
let mut seat_state = SeatState::new();
|
||||
let mut seat = seat_state.new_wl_seat(&display_handle, seat_name);
|
||||
seat.add_keyboard(XkbConfig::default(), 200, 25)
|
||||
.expect("no xkb config errors expected with default config");
|
||||
seat.add_pointer();
|
||||
seat.add_touch();
|
||||
|
||||
Self {
|
||||
display_handle,
|
||||
loop_signal,
|
||||
loop_handle,
|
||||
compositor_state,
|
||||
xdg_shell_state,
|
||||
layer_shell_state,
|
||||
shm_state,
|
||||
dmabuf_state,
|
||||
output_manager_state,
|
||||
presentation_state,
|
||||
text_input_state,
|
||||
input_method_state,
|
||||
pointer_constraints_state,
|
||||
cursor_shape_state,
|
||||
space: Space::default(),
|
||||
popups: PopupManager::default(),
|
||||
seat_state,
|
||||
seat,
|
||||
pointer_location: Point::from((0.0_f64, 0.0_f64)),
|
||||
cursor_image_status: CursorImageStatus::Hidden,
|
||||
dmabuf_global: None,
|
||||
running: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- CompositorHandler ---
|
||||
|
||||
impl CompositorHandler for WeftCompositorState {
|
||||
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::<WeftClientState>()
|
||||
.expect("client must carry WeftClientState")
|
||||
.compositor_state
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
|
||||
if let Some(window) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| w.wl_surface().as_ref() == Some(surface))
|
||||
.cloned()
|
||||
{
|
||||
window.on_commit();
|
||||
}
|
||||
|
||||
// Re-arrange layer surfaces for any output that contains this surface.
|
||||
let outputs: Vec<Output> = self
|
||||
.space
|
||||
.outputs()
|
||||
.filter(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||
.is_some()
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
for output in outputs {
|
||||
layer_map_for_output(&output).arrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_compositor!(WeftCompositorState);
|
||||
|
||||
// --- ShmHandler ---
|
||||
|
||||
impl ShmHandler for WeftCompositorState {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_shm!(WeftCompositorState);
|
||||
|
||||
// --- XdgShellHandler ---
|
||||
|
||||
impl XdgShellHandler for WeftCompositorState {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell_state
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
// Send initial configure before wrapping — the toplevel needs a configure to map.
|
||||
surface.send_configure();
|
||||
let window = Window::new_wayland_window(surface);
|
||||
// Map at origin; proper placement policy comes with the shell protocol wave.
|
||||
self.space.map_element(window, (0, 0), false);
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) {
|
||||
surface.with_pending_state(|state| {
|
||||
state.geometry = positioner.get_geometry();
|
||||
});
|
||||
if surface.send_configure().is_ok() {
|
||||
self.popups.track_popup(PopupKind::Xdg(surface)).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, _surface: PopupSurface, _seat: smithay::reexports::wayland_server::protocol::wl_seat::WlSeat, _serial: smithay::utils::Serial) {}
|
||||
}
|
||||
|
||||
delegate_xdg_shell!(WeftCompositorState);
|
||||
|
||||
// --- WlrLayerShellHandler ---
|
||||
|
||||
impl WlrLayerShellHandler for WeftCompositorState {
|
||||
fn shell_state(&mut self) -> &mut WlrLayerShellState {
|
||||
&mut self.layer_shell_state
|
||||
}
|
||||
|
||||
fn new_layer_surface(
|
||||
&mut self,
|
||||
surface: LayerSurface,
|
||||
_output: Option<WlOutput>,
|
||||
_layer: Layer,
|
||||
_namespace: String,
|
||||
) {
|
||||
// Map to the first available output. Proper output matching is deferred to
|
||||
// the shell protocol wave where the compositor receives explicit placement requests.
|
||||
if let Some(output) = self.space.outputs().next().cloned() {
|
||||
layer_map_for_output(&output)
|
||||
.map_layer(&surface)
|
||||
.expect("layer surface must not already be mapped");
|
||||
layer_map_for_output(&output).arrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate_layer_shell!(WeftCompositorState);
|
||||
|
||||
// --- SeatHandler ---
|
||||
|
||||
impl SeatHandler for WeftCompositorState {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
type TouchFocus = WlSurface;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&WlSurface>) {}
|
||||
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, image: CursorImageStatus) {
|
||||
self.cursor_image_status = image;
|
||||
}
|
||||
}
|
||||
|
||||
delegate_seat!(WeftCompositorState);
|
||||
|
||||
// --- DmabufHandler ---
|
||||
|
||||
impl DmabufHandler for WeftCompositorState {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.dmabuf_state
|
||||
}
|
||||
|
||||
fn dmabuf_imported(
|
||||
&mut self,
|
||||
_global: &DmabufGlobal,
|
||||
_dmabuf: smithay::backend::allocator::dmabuf::Dmabuf,
|
||||
notifier: ImportNotifier,
|
||||
) {
|
||||
// DMA-BUF import requires the renderer, which lives in the backend run function.
|
||||
// The backend is responsible for creating the global only when it can service imports.
|
||||
// If we reach here without a backend handler wired up, reject.
|
||||
drop(notifier);
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dmabuf!(WeftCompositorState);
|
||||
|
||||
// --- OutputHandler ---
|
||||
|
||||
impl smithay::wayland::output::OutputHandler for WeftCompositorState {}
|
||||
delegate_output!(WeftCompositorState);
|
||||
|
||||
// --- PresentationHandler ---
|
||||
|
||||
impl PresentationHandler for WeftCompositorState {
|
||||
fn presentation_state(&mut self) -> &mut PresentationState {
|
||||
&mut self.presentation_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_presentation!(WeftCompositorState);
|
||||
|
||||
// --- TextInputHandler ---
|
||||
|
||||
impl TextInputHandler for WeftCompositorState {
|
||||
fn text_input_state(&mut self) -> &mut TextInputManagerState {
|
||||
&mut self.text_input_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_text_input_manager!(WeftCompositorState);
|
||||
|
||||
// --- InputMethodHandler ---
|
||||
|
||||
impl InputMethodHandler for WeftCompositorState {
|
||||
fn new_popup(&mut self, _surface: PopupSurface) {}
|
||||
fn popup_repositioned(&mut self, _surface: PopupSurface) {}
|
||||
fn popup_done(&mut self, _surface: PopupSurface) {}
|
||||
|
||||
fn parent_geometry(&self, parent_surface: &WlSurface) -> Rectangle<i32, Logical> {
|
||||
self.space
|
||||
.elements()
|
||||
.find_map(|w: &Window| {
|
||||
if w.wl_surface().as_ref() == Some(parent_surface) {
|
||||
Some(w.geometry())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
delegate_input_method_manager!(WeftCompositorState);
|
||||
|
||||
// --- PointerConstraintsHandler ---
|
||||
|
||||
impl PointerConstraintsHandler for WeftCompositorState {
|
||||
fn new_constraint(
|
||||
&mut self,
|
||||
_surface: &WlSurface,
|
||||
_pointer: &smithay::input::pointer::PointerHandle<Self>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
delegate_pointer_constraints!(WeftCompositorState);
|
||||
|
||||
// --- CursorShapeHandler ---
|
||||
|
||||
impl CursorShapeHandler for WeftCompositorState {
|
||||
fn cursor_shape_state(&mut self) -> &mut CursorShapeManagerState {
|
||||
&mut self.cursor_shape_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_cursor_shape!(WeftCompositorState);
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
cargo fmt --all --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace
|
||||
cargo clippy --workspace --exclude weft-compositor --all-targets -- -D warnings
|
||||
cargo test --workspace --exclude weft-compositor
|
||||
|
|
|
|||
|
|
@ -2,5 +2,11 @@
|
|||
set -euo pipefail
|
||||
|
||||
cargo fmt --all --check
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace
|
||||
|
||||
if [ "$(uname -s)" = "Linux" ]; then
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace
|
||||
else
|
||||
cargo clippy --workspace --exclude weft-compositor --all-targets -- -D warnings
|
||||
cargo test --workspace --exclude weft-compositor
|
||||
fi
|
||||
|
|
|
|||
15
infra/systemd/weft-compositor.service
Normal file
15
infra/systemd/weft-compositor.service
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=WEFT OS Wayland Compositor
|
||||
Documentation=https://github.com/weft-os/weft
|
||||
After=systemd-logind.service
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
ExecStart=/packages/system/weft-compositor/active/bin/weft-compositor
|
||||
Restart=on-failure
|
||||
RestartSec=1
|
||||
# The socket path is posted to the environment by the compositor itself.
|
||||
# Downstream services (servo-shell.service) should declare After=weft-compositor.service.
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical.target
|
||||
Loading…
Reference in a new issue