use crate::positioner::PositionerGeometry;
use std::collections::HashMap;
use std::os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc;
use calloop::generic::Generic;
use calloop::{EventLoop, Interest, LoopSignal, PostAction};
use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_device_v1::{
self, WpCursorShapeDeviceV1,
};
use wayland_protocols::wp::cursor_shape::v1::server::wp_cursor_shape_manager_v1::{
self, WpCursorShapeManagerV1,
};
use wayland_protocols::wp::fractional_scale::v1::server::wp_fractional_scale_manager_v1::{
self, WpFractionalScaleManagerV1,
};
use wayland_protocols::wp::fractional_scale::v1::server::wp_fractional_scale_v1::WpFractionalScaleV1;
use wayland_protocols::wp::presentation_time::server::wp_presentation::{
self, WpPresentation,
};
use wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::{
Kind as WpPresentationFeedbackKind, WpPresentationFeedback,
};
use wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_buffer_params_v1::{
self, ZwpLinuxBufferParamsV1,
};
use wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_v1::{
self, ZwpLinuxDmabufV1,
};
use wayland_protocols::wp::pointer_constraints::zv1::server::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use wayland_protocols::wp::pointer_constraints::zv1::server::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use wayland_protocols::wp::pointer_constraints::zv1::server::zwp_pointer_constraints_v1::{
self, ZwpPointerConstraintsV1,
};
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_manager_v1::{
self, ZwpPrimarySelectionDeviceManagerV1,
};
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_device_v1::{
self, ZwpPrimarySelectionDeviceV1,
};
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_offer_v1::{
self, ZwpPrimarySelectionOfferV1,
};
use wayland_protocols::wp::primary_selection::zv1::server::zwp_primary_selection_source_v1::{
self, ZwpPrimarySelectionSourceV1,
};
use wayland_protocols::wp::relative_pointer::zv1::server::zwp_relative_pointer_manager_v1::{
self, ZwpRelativePointerManagerV1,
};
use wayland_protocols::wp::relative_pointer::zv1::server::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use wayland_protocols::wp::text_input::zv3::server::zwp_text_input_manager_v3::{
self, ZwpTextInputManagerV3,
};
use wayland_protocols::wp::text_input::zv3::server::zwp_text_input_v3::{
self, ZwpTextInputV3,
};
use wayland_protocols::wp::viewporter::server::wp_viewport::WpViewport;
use wayland_protocols::wp::viewporter::server::wp_viewporter::{self, WpViewporter};
use wayland_protocols::xdg::activation::v1::server::xdg_activation_token_v1::{
self, XdgActivationTokenV1,
};
use wayland_protocols::xdg::activation::v1::server::xdg_activation_v1::{
self, XdgActivationV1,
};
use wayland_protocols::xdg::decoration::zv1::server::zxdg_decoration_manager_v1::{
self, ZxdgDecorationManagerV1,
};
use wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::{
self, ZxdgToplevelDecorationV1,
};
use wayland_protocols::xdg::shell::server::xdg_popup::{self, XdgPopup};
use wayland_protocols::xdg::shell::server::xdg_positioner::XdgPositioner;
use wayland_protocols::xdg::shell::server::xdg_surface::{self, XdgSurface};
use wayland_protocols::xdg::shell::server::xdg_toplevel::{self, XdgToplevel};
use wayland_protocols::xdg::shell::server::xdg_wm_base::{self, XdgWmBase};
use wayland_server::protocol::wl_buffer::WlBuffer;
use wayland_server::protocol::wl_callback::WlCallback;
use wayland_server::protocol::wl_compositor::WlCompositor;
use wayland_server::protocol::wl_data_device::{self, WlDataDevice};
use wayland_server::protocol::wl_data_device_manager::{self, WlDataDeviceManager};
use wayland_server::protocol::wl_data_offer::{self, WlDataOffer};
use wayland_server::protocol::wl_data_source::{self, WlDataSource};
use wayland_server::protocol::wl_keyboard::{self, WlKeyboard};
use wayland_server::protocol::wl_output::{self, WlOutput};
use wayland_server::protocol::wl_pointer::{self, WlPointer};
use wayland_server::protocol::wl_region::WlRegion;
use wayland_server::protocol::wl_seat::{self, WlSeat};
use wayland_server::protocol::wl_shm::{self, WlShm};
use wayland_server::protocol::wl_shm_pool::WlShmPool;
use wayland_server::protocol::wl_subcompositor::WlSubcompositor;
use wayland_server::protocol::wl_subsurface::WlSubsurface;
use wayland_server::protocol::wl_surface::WlSurface;
use wayland_server::backend::ObjectId;
use wayland_server::{
Client, DataInit, Dispatch, Display, DisplayHandle, GlobalDispatch, New, Resource,
};
#[derive(Clone)]
pub enum PixelData {
Bgra(Arc<Vec<u8>>),
Rgba(Arc<Vec<u8>>),
Nv12 {
data: Arc<Vec<u8>>,
y_stride: usize,
uv_stride: usize,
},
DmaBuf {
fd: Arc<OwnedFd>,
fourcc: u32,
modifier: u64,
stride: u32,
offset: u32,
},
VaSurface {
surface_id: u32,
va_display: usize, _fd: Arc<OwnedFd>,
},
}
#[derive(Clone)]
pub struct PixelLayer {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
pub pixels: PixelData,
}
pub struct ExternalOutputBuffer {
pub fd: Arc<OwnedFd>,
pub fourcc: u32,
pub modifier: u64,
pub stride: u32,
pub offset: u32,
pub width: u32,
pub height: u32,
pub va_surface_id: u32,
pub va_display: usize,
}
pub mod drm_fourcc {
pub const ARGB8888: u32 = u32::from_le_bytes(*b"AR24");
pub const XRGB8888: u32 = u32::from_le_bytes(*b"XR24");
pub const ABGR8888: u32 = u32::from_le_bytes(*b"AB24");
pub const XBGR8888: u32 = u32::from_le_bytes(*b"XB24");
pub const NV12: u32 = u32::from_le_bytes(*b"NV12");
}
impl PixelData {
pub fn to_rgba(&self, width: u32, height: u32) -> Vec<u8> {
let w = width as usize;
let h = height as usize;
match self {
PixelData::Rgba(data) => data.as_ref().clone(),
PixelData::Bgra(data) => {
let mut rgba = Vec::with_capacity(w * h * 4);
for px in data.chunks_exact(4) {
rgba.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
}
rgba
}
PixelData::Nv12 {
data,
y_stride,
uv_stride,
} => {
let y_plane_size = *y_stride * h;
let uv_h = h.div_ceil(2);
let uv_plane_size = *uv_stride * uv_h;
if data.len() < y_plane_size + uv_plane_size {
return Vec::new();
}
let y_plane = &data[..y_plane_size];
let uv_plane = &data[y_plane_size..];
let mut rgba = Vec::with_capacity(w * h * 4);
for row in 0..h {
for col in 0..w {
let y = y_plane[row * y_stride + col];
let uv_idx = (row / 2) * uv_stride + (col / 2) * 2;
if uv_idx + 1 >= uv_plane.len() {
rgba.extend_from_slice(&[0, 0, 0, 255]);
continue;
}
let u = uv_plane[uv_idx];
let v = uv_plane[uv_idx + 1];
let [r, g, b] = yuv420_to_rgb(y, u, v);
rgba.extend_from_slice(&[r, g, b, 255]);
}
}
rgba
}
PixelData::DmaBuf {
fd,
fourcc,
stride,
offset,
..
} => {
let raw = fd.as_raw_fd();
let stride_usize = *stride as usize;
let plane_offset = *offset as usize;
let map_size = plane_offset + stride_usize * h;
if map_size == 0 {
return Vec::new();
}
const DMA_BUF_SYNC_READ: u64 = 1;
const DMA_BUF_SYNC_START: u64 = 0;
const DMA_BUF_SYNC_END: u64 = 4;
const DMA_BUF_IOCTL_SYNC: libc::c_ulong = 0x40086200;
let did_sync = {
let mut pfd = libc::pollfd {
fd: raw,
events: libc::POLLIN,
revents: 0,
};
let ready = unsafe { libc::poll(&mut pfd, 1, 0) };
if ready > 0 {
let s: u64 = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ;
unsafe { libc::ioctl(raw, DMA_BUF_IOCTL_SYNC as _, &s) };
true
} else {
false
}
};
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
map_size,
libc::PROT_READ,
libc::MAP_SHARED,
raw,
0,
)
};
if ptr == libc::MAP_FAILED {
if did_sync {
let s: u64 = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ;
unsafe { libc::ioctl(raw, DMA_BUF_IOCTL_SYNC as _, &s) };
}
return Vec::new();
}
let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_size) };
let row_bytes = w * 4;
let mut pixels = Vec::with_capacity(w * h * 4);
for row in 0..h {
let start = plane_offset + row * stride_usize;
if start + row_bytes <= slice.len() {
pixels.extend_from_slice(&slice[start..start + row_bytes]);
}
}
let is_bgr_mem = matches!(*fourcc, drm_fourcc::ARGB8888 | drm_fourcc::XRGB8888);
let force_alpha = matches!(*fourcc, drm_fourcc::XRGB8888 | drm_fourcc::XBGR8888);
for px in pixels.chunks_exact_mut(4) {
if is_bgr_mem {
px.swap(0, 2);
}
if force_alpha {
px[3] = 255;
}
}
unsafe { libc::munmap(ptr, map_size) };
if did_sync {
let s: u64 = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ;
unsafe { libc::ioctl(raw, DMA_BUF_IOCTL_SYNC as _, &s) };
}
pixels
}
PixelData::VaSurface { .. } => Vec::new(),
}
}
pub fn to_bgra_vec(&self, width: u32, height: u32) -> Vec<u8> {
match self {
PixelData::Bgra(v) => v.as_ref().to_vec(),
PixelData::Rgba(v) => {
let mut bgra = v.as_ref().to_vec();
for px in bgra.chunks_exact_mut(4) {
px.swap(0, 2);
}
bgra
}
PixelData::DmaBuf {
fd,
stride,
fourcc,
offset,
..
} => {
let raw = fd.as_raw_fd();
let s = *stride as usize;
let h = height as usize;
let w = width as usize;
let off = *offset as usize;
let map_size = off + s * h;
if map_size == 0 {
return Vec::new();
}
const DMA_BUF_SYNC_READ: u64 = 1;
const DMA_BUF_SYNC_START: u64 = 0;
const DMA_BUF_SYNC_END: u64 = 4;
const DMA_BUF_IOCTL_SYNC: libc::c_ulong = 0x40086200;
let sync_start: u64 = DMA_BUF_SYNC_START | DMA_BUF_SYNC_READ;
unsafe { libc::ioctl(raw, DMA_BUF_IOCTL_SYNC as _, &sync_start) };
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
map_size,
libc::PROT_READ,
libc::MAP_SHARED,
raw,
0,
)
};
if ptr == libc::MAP_FAILED {
return Vec::new();
}
let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, map_size) };
let row_bytes = w * 4;
let is_bgr = matches!(fourcc, 0x34325241 | 0x34325258);
let mut bgra = Vec::with_capacity(w * h * 4);
for row in 0..h {
let row_start = off + row * s;
let src = &slice[row_start..row_start + row_bytes.min(slice.len() - row_start)];
if is_bgr {
bgra.extend_from_slice(src);
} else {
for px in src.chunks_exact(4) {
bgra.extend_from_slice(&[px[2], px[1], px[0], px[3]]);
}
}
}
let sync_end: u64 = DMA_BUF_SYNC_END | DMA_BUF_SYNC_READ;
unsafe {
libc::munmap(ptr, map_size);
libc::ioctl(raw, DMA_BUF_IOCTL_SYNC as _, &sync_end);
}
bgra
}
_ => Vec::new(),
}
}
pub fn is_empty(&self) -> bool {
match self {
PixelData::Bgra(v) | PixelData::Rgba(v) => v.is_empty(),
PixelData::Nv12 { data, .. } => data.is_empty(),
PixelData::DmaBuf { .. } | PixelData::VaSurface { .. } => false,
}
}
pub fn is_dmabuf(&self) -> bool {
matches!(self, PixelData::DmaBuf { .. })
}
pub fn is_va_surface(&self) -> bool {
matches!(self, PixelData::VaSurface { .. })
}
}
#[derive(Clone)]
pub enum CursorImage {
Named(String),
Custom {
hotspot_x: u16,
hotspot_y: u16,
width: u16,
height: u16,
rgba: Vec<u8>,
},
Hidden,
}
pub enum CompositorEvent {
SurfaceCreated {
surface_id: u16,
title: String,
app_id: String,
parent_id: u16,
width: u16,
height: u16,
},
SurfaceDestroyed {
surface_id: u16,
},
SurfaceCommit {
surface_id: u16,
width: u32,
height: u32,
pixels: PixelData,
},
SurfaceTitle {
surface_id: u16,
title: String,
},
SurfaceAppId {
surface_id: u16,
app_id: String,
},
SurfaceResized {
surface_id: u16,
width: u16,
height: u16,
},
ClipboardContent {
mime_type: String,
data: Vec<u8>,
},
SurfaceCursor {
surface_id: u16,
cursor: CursorImage,
},
}
pub enum CompositorCommand {
KeyInput {
surface_id: u16,
keycode: u32,
pressed: bool,
},
PointerMotion {
surface_id: u16,
x: f64,
y: f64,
},
PointerButton {
surface_id: u16,
button: u32,
pressed: bool,
},
PointerAxis {
surface_id: u16,
axis: u8,
value: f64,
},
SurfaceResize {
surface_id: u16,
width: u16,
height: u16,
scale_120: u16,
},
SurfaceFocus {
surface_id: u16,
},
SurfaceClose {
surface_id: u16,
},
ClipboardOffer {
mime_type: String,
data: Vec<u8>,
},
Capture {
surface_id: u16,
scale_120: u16,
reply: mpsc::SyncSender<Option<(u32, u32, Vec<u8>)>>,
},
RequestFrame {
surface_id: u16,
},
ReleaseKeys {
keycodes: Vec<u32>,
},
ClipboardListMimes {
reply: mpsc::SyncSender<Vec<String>>,
},
ClipboardGet {
mime_type: String,
reply: mpsc::SyncSender<Option<Vec<u8>>>,
},
SetExternalOutputBuffers {
buffers: Vec<ExternalOutputBuffer>,
},
TextInput {
text: String,
},
Shutdown,
}
pub(crate) struct Surface {
pub surface_id: u16,
pub wl_surface: WlSurface,
pending_buffer: Option<WlBuffer>,
pending_buffer_scale: i32,
pending_damage: bool,
pending_frame_callbacks: Vec<WlCallback>,
pending_presentation_feedbacks: Vec<WpPresentationFeedback>,
pending_opaque: bool,
pub buffer_scale: i32,
pub is_opaque: bool,
pub parent_surface_id: Option<ObjectId>,
pending_subsurface_position: Option<(i32, i32)>,
pub subsurface_position: (i32, i32),
pub children: Vec<ObjectId>,
xdg_surface: Option<XdgSurface>,
xdg_toplevel: Option<XdgToplevel>,
xdg_popup: Option<XdgPopup>,
pub xdg_geometry: Option<(i32, i32, i32, i32)>,
title: String,
app_id: String,
pending_viewport_destination: Option<(i32, i32)>,
pub viewport_destination: Option<(i32, i32)>,
is_cursor: bool,
cursor_hotspot: (i32, i32),
}
struct ShmPool {
resource: WlShmPool,
fd: OwnedFd,
size: usize,
mmap_ptr: *mut u8,
}
impl ShmPool {
fn new(resource: WlShmPool, fd: OwnedFd, size: i32) -> Self {
let sz = size.max(0) as usize;
let ptr = if sz > 0 {
unsafe {
libc::mmap(
std::ptr::null_mut(),
sz,
libc::PROT_READ,
libc::MAP_SHARED,
fd.as_raw_fd(),
0,
)
}
} else {
libc::MAP_FAILED
};
ShmPool {
resource,
fd,
size: sz,
mmap_ptr: if ptr == libc::MAP_FAILED {
std::ptr::null_mut()
} else {
ptr as *mut u8
},
}
}
fn resize(&mut self, new_size: i32) {
let new_sz = new_size.max(0) as usize;
if new_sz <= self.size {
return;
}
if !self.mmap_ptr.is_null() {
unsafe {
libc::munmap(self.mmap_ptr as *mut _, self.size);
}
}
let ptr = unsafe {
libc::mmap(
std::ptr::null_mut(),
new_sz,
libc::PROT_READ,
libc::MAP_SHARED,
self.fd.as_raw_fd(),
0,
)
};
self.mmap_ptr = if ptr == libc::MAP_FAILED {
std::ptr::null_mut()
} else {
ptr as *mut u8
};
self.size = new_sz;
}
fn read_buffer(
&self,
offset: i32,
width: i32,
height: i32,
stride: i32,
format: wl_shm::Format,
) -> Option<(u32, u32, PixelData)> {
if self.mmap_ptr.is_null() {
return None;
}
let w = width as u32;
let h = height as u32;
let s = stride as usize;
let off = offset as usize;
let row_bytes = w as usize * 4;
let needed = off + s * (h as usize).saturating_sub(1) + row_bytes;
if needed > self.size {
return None;
}
let mut bgra = if s == row_bytes && off == 0 {
let total = row_bytes * h as usize;
unsafe { std::slice::from_raw_parts(self.mmap_ptr, total) }.to_vec()
} else {
let mut packed = Vec::with_capacity(row_bytes * h as usize);
for row in 0..h as usize {
let src = unsafe {
std::slice::from_raw_parts(self.mmap_ptr.add(off + row * s), row_bytes)
};
packed.extend_from_slice(src);
}
packed
};
if matches!(format, wl_shm::Format::Xrgb8888 | wl_shm::Format::Xbgr8888) {
for px in bgra.chunks_exact_mut(4) {
px[3] = 255;
}
}
if matches!(format, wl_shm::Format::Abgr8888 | wl_shm::Format::Xbgr8888) {
Some((w, h, PixelData::Rgba(Arc::new(bgra))))
} else {
Some((w, h, PixelData::Bgra(Arc::new(bgra))))
}
}
}
impl Drop for ShmPool {
fn drop(&mut self) {
if !self.mmap_ptr.is_null() {
unsafe {
libc::munmap(self.mmap_ptr as *mut _, self.size);
}
}
}
}
unsafe impl Send for ShmPool {}
struct ShmBufferData {
pool_id: ObjectId,
offset: i32,
width: i32,
height: i32,
stride: i32,
format: wl_shm::Format,
}
struct DmaBufBufferData {
width: i32,
height: i32,
fourcc: u32,
modifier: u64,
planes: Vec<DmaBufPlane>,
}
struct DmaBufPlane {
fd: OwnedFd,
offset: u32,
stride: u32,
}
struct DmaBufParamsPending {
resource: ZwpLinuxBufferParamsV1,
planes: Vec<DmaBufPlane>,
modifier: u64,
}
struct ClientState;
struct XdgSurfaceData {
wl_surface_id: ObjectId,
}
struct XdgToplevelData {
wl_surface_id: ObjectId,
}
struct XdgPopupData {
wl_surface_id: ObjectId,
}
struct SubsurfaceData {
wl_surface_id: ObjectId,
parent_surface_id: ObjectId,
}
struct DataSourceData {
mime_types: std::sync::Mutex<Vec<String>>,
}
struct DataOfferData {
external: bool,
}
struct ExternalClipboard {
mime_type: String,
data: Vec<u8>,
}
struct PrimarySourceData {
mime_types: std::sync::Mutex<Vec<String>>,
}
struct PrimaryOfferData {
external: bool,
}
struct ActivationTokenData {
serial: u32,
}
struct PositionerState {
resource: XdgPositioner,
geometry: PositionerGeometry,
}
fn char_to_keycode(ch: char) -> Option<(u32, bool)> {
const KEY_1: u32 = 2;
const KEY_2: u32 = 3;
const KEY_3: u32 = 4;
const KEY_4: u32 = 5;
const KEY_5: u32 = 6;
const KEY_6: u32 = 7;
const KEY_7: u32 = 8;
const KEY_8: u32 = 9;
const KEY_9: u32 = 10;
const KEY_0: u32 = 11;
const KEY_MINUS: u32 = 12;
const KEY_EQUAL: u32 = 13;
const KEY_TAB: u32 = 15;
const KEY_Q: u32 = 16;
const KEY_W: u32 = 17;
const KEY_E: u32 = 18;
const KEY_R: u32 = 19;
const KEY_T: u32 = 20;
const KEY_Y: u32 = 21;
const KEY_U: u32 = 22;
const KEY_I: u32 = 23;
const KEY_O: u32 = 24;
const KEY_P: u32 = 25;
const KEY_LEFTBRACE: u32 = 26;
const KEY_RIGHTBRACE: u32 = 27;
const KEY_ENTER: u32 = 28;
const KEY_A: u32 = 30;
const KEY_S: u32 = 31;
const KEY_D: u32 = 32;
const KEY_F: u32 = 33;
const KEY_G: u32 = 34;
const KEY_H: u32 = 35;
const KEY_J: u32 = 36;
const KEY_K: u32 = 37;
const KEY_L: u32 = 38;
const KEY_SEMICOLON: u32 = 39;
const KEY_APOSTROPHE: u32 = 40;
const KEY_GRAVE: u32 = 41;
const KEY_BACKSLASH: u32 = 43;
const KEY_Z: u32 = 44;
const KEY_X: u32 = 45;
const KEY_C: u32 = 46;
const KEY_V: u32 = 47;
const KEY_B: u32 = 48;
const KEY_N: u32 = 49;
const KEY_M: u32 = 50;
const KEY_COMMA: u32 = 51;
const KEY_DOT: u32 = 52;
const KEY_SLASH: u32 = 53;
const KEY_SPACE: u32 = 57;
fn letter_kc(ch: char) -> u32 {
match ch {
'a' => KEY_A,
'b' => KEY_B,
'c' => KEY_C,
'd' => KEY_D,
'e' => KEY_E,
'f' => KEY_F,
'g' => KEY_G,
'h' => KEY_H,
'i' => KEY_I,
'j' => KEY_J,
'k' => KEY_K,
'l' => KEY_L,
'm' => KEY_M,
'n' => KEY_N,
'o' => KEY_O,
'p' => KEY_P,
'q' => KEY_Q,
'r' => KEY_R,
's' => KEY_S,
't' => KEY_T,
'u' => KEY_U,
'v' => KEY_V,
'w' => KEY_W,
'x' => KEY_X,
'y' => KEY_Y,
'z' => KEY_Z,
_ => KEY_SPACE,
}
}
let (kc, shift) = match ch {
'a'..='z' => (letter_kc(ch), false),
'A'..='Z' => (letter_kc(ch.to_ascii_lowercase()), true),
'0' => (KEY_0, false),
'1'..='9' => (KEY_1 + (ch as u32 - '1' as u32), false),
' ' => (KEY_SPACE, false),
'-' => (KEY_MINUS, false),
'=' => (KEY_EQUAL, false),
'[' => (KEY_LEFTBRACE, false),
']' => (KEY_RIGHTBRACE, false),
';' => (KEY_SEMICOLON, false),
'\'' => (KEY_APOSTROPHE, false),
',' => (KEY_COMMA, false),
'.' => (KEY_DOT, false),
'/' => (KEY_SLASH, false),
'\\' => (KEY_BACKSLASH, false),
'`' => (KEY_GRAVE, false),
'\t' => (KEY_TAB, false),
'\n' => (KEY_ENTER, false),
'!' => (KEY_1, true),
'@' => (KEY_2, true),
'#' => (KEY_3, true),
'$' => (KEY_4, true),
'%' => (KEY_5, true),
'^' => (KEY_6, true),
'&' => (KEY_7, true),
'*' => (KEY_8, true),
'(' => (KEY_9, true),
')' => (KEY_0, true),
'_' => (KEY_MINUS, true),
'+' => (KEY_EQUAL, true),
'{' => (KEY_LEFTBRACE, true),
'}' => (KEY_RIGHTBRACE, true),
':' => (KEY_SEMICOLON, true),
'"' => (KEY_APOSTROPHE, true),
'<' => (KEY_COMMA, true),
'>' => (KEY_DOT, true),
'?' => (KEY_SLASH, true),
'|' => (KEY_BACKSLASH, true),
'~' => (KEY_GRAVE, true),
_ => return None,
};
Some((kc, shift))
}
const MOD_SHIFT: u32 = 1 << 0;
const MOD_LOCK: u32 = 1 << 1;
const MOD_CONTROL: u32 = 1 << 2;
const MOD_MOD1: u32 = 1 << 3; const MOD_MOD4: u32 = 1 << 6;
fn keycode_to_mod(keycode: u32) -> u32 {
match keycode {
42 | 54 => MOD_SHIFT, 58 => MOD_LOCK, 29 | 97 => MOD_CONTROL, 56 | 100 => MOD_MOD1, 125 | 126 => MOD_MOD4, _ => 0,
}
}
struct TextInputState {
resource: ZwpTextInputV3,
enabled: bool,
}
struct Compositor {
display_handle: DisplayHandle,
surfaces: HashMap<ObjectId, Surface>,
toplevel_surface_ids: HashMap<u16, ObjectId>,
next_surface_id: u16,
shm_pools: HashMap<ObjectId, ShmPool>,
pixel_cache: HashMap<ObjectId, (u32, u32, i32, bool, PixelData)>,
dmabuf_params: HashMap<ObjectId, DmaBufParamsPending>,
vulkan_renderer: Option<super::vulkan_render::VulkanRenderer>,
output_width: i32,
output_height: i32,
output_scale_120: u16,
outputs: Vec<WlOutput>,
keyboards: Vec<WlKeyboard>,
pointers: Vec<WlPointer>,
keyboard_keymap_data: Vec<u8>,
mods_depressed: u32,
mods_locked: u32,
serial: u32,
event_tx: mpsc::Sender<CompositorEvent>,
event_notify: Arc<dyn Fn() + Send + Sync>,
loop_signal: LoopSignal,
pending_commits: HashMap<u16, (u32, u32, u32, u32, PixelData)>,
focused_surface_id: u16,
pointer_entered_id: Option<ObjectId>,
pending_kb_reenter: bool,
verbose: bool,
shutdown: Arc<AtomicBool>,
last_reported_size: HashMap<u16, (u32, u32, u32, u32)>,
surface_sizes: HashMap<u16, (i32, i32)>,
positioners: HashMap<ObjectId, PositionerState>,
fractional_scales: Vec<WpFractionalScaleV1>,
data_devices: Vec<WlDataDevice>,
selection_source: Option<WlDataSource>,
external_clipboard: Option<ExternalClipboard>,
primary_devices: Vec<ZwpPrimarySelectionDeviceV1>,
primary_source: Option<ZwpPrimarySelectionSourceV1>,
external_primary: Option<ExternalClipboard>,
relative_pointers: Vec<ZwpRelativePointerV1>,
text_inputs: Vec<TextInputState>,
#[expect(dead_code)]
text_input_serial: u32,
next_activation_token: u32,
}
impl Compositor {
fn next_serial(&mut self) -> u32 {
self.serial = self.serial.wrapping_add(1);
self.serial
}
fn update_and_send_modifiers(&mut self, keycode: u32, pressed: bool) {
let m = keycode_to_mod(keycode);
if m == 0 {
return;
}
if keycode == 58 {
if pressed {
self.mods_locked ^= MOD_LOCK;
}
} else if pressed {
self.mods_depressed |= m;
} else {
self.mods_depressed &= !m;
}
let serial = self.next_serial();
let focused_wl = self
.toplevel_surface_ids
.get(&self.focused_surface_id)
.and_then(|root_id| self.surfaces.get(root_id))
.map(|s| s.wl_surface.clone());
for kb in &self.keyboards {
if let Some(ref wl) = focused_wl
&& same_client(kb, wl)
{
kb.modifiers(serial, self.mods_depressed, 0, self.mods_locked, 0);
}
}
}
fn set_keyboard_focus(&mut self, new_surface_id: u16) {
let old_id = self.focused_surface_id;
if old_id == new_surface_id {
self.focused_surface_id = new_surface_id;
if let Some(root_id) = self.toplevel_surface_ids.get(&new_surface_id)
&& let Some(wl_surface) = self.surfaces.get(root_id).map(|s| s.wl_surface.clone())
{
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &wl_surface) {
kb.enter(serial, &wl_surface, vec![]);
}
}
for ti in &self.text_inputs {
if same_client(&ti.resource, &wl_surface) {
ti.resource.enter(&wl_surface);
}
}
}
return;
}
if old_id != 0
&& let Some(old_root) = self.toplevel_surface_ids.get(&old_id)
&& let Some(old_wl) = self.surfaces.get(old_root).map(|s| s.wl_surface.clone())
{
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &old_wl) {
kb.leave(serial, &old_wl);
}
}
for ti in &self.text_inputs {
if same_client(&ti.resource, &old_wl) {
ti.resource.leave(&old_wl);
}
}
}
self.focused_surface_id = new_surface_id;
if let Some(root_id) = self.toplevel_surface_ids.get(&new_surface_id)
&& let Some(wl_surface) = self.surfaces.get(root_id).map(|s| s.wl_surface.clone())
{
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &wl_surface) {
kb.enter(serial, &wl_surface, vec![]);
}
}
for ti in &self.text_inputs {
if same_client(&ti.resource, &wl_surface) {
ti.resource.enter(&wl_surface);
}
}
}
}
fn allocate_surface_id(&mut self) -> u16 {
let mut id = self.next_surface_id;
let start = id;
loop {
if !self.toplevel_surface_ids.contains_key(&id) {
break;
}
id = id.wrapping_add(1);
if id == 0 {
id = 1;
}
if id == start {
break;
}
}
self.next_surface_id = id.wrapping_add(1);
if self.next_surface_id == 0 {
self.next_surface_id = 1;
}
id
}
fn flush_pending_commits(&mut self) {
for (surface_id, (width, height, log_w, log_h, pixels)) in self.pending_commits.drain() {
let prev = self.last_reported_size.get(&surface_id).copied();
if prev.is_none() || prev.map(|(pw, ph, _, _)| (pw, ph)) != Some((width, height)) {
self.last_reported_size
.insert(surface_id, (width, height, log_w, log_h));
let _ = self.event_tx.send(CompositorEvent::SurfaceResized {
surface_id,
width: width as u16,
height: height as u16,
});
}
let _ = self.event_tx.send(CompositorEvent::SurfaceCommit {
surface_id,
width,
height,
pixels,
});
}
(self.event_notify)();
}
fn read_shm_buffer(&self, buffer: &WlBuffer) -> Option<(u32, u32, PixelData)> {
let data = buffer.data::<ShmBufferData>()?;
let pool = self.shm_pools.get(&data.pool_id)?;
pool.read_buffer(
data.offset,
data.width,
data.height,
data.stride,
data.format,
)
}
fn read_dmabuf_buffer(&self, buffer: &WlBuffer) -> Option<(u32, u32, PixelData)> {
let data = buffer.data::<DmaBufBufferData>()?;
let width = data.width as u32;
let height = data.height as u32;
if width == 0 || height == 0 || data.planes.is_empty() {
return None;
}
let plane = &data.planes[0];
if matches!(
data.fourcc,
drm_fourcc::ARGB8888
| drm_fourcc::XRGB8888
| drm_fourcc::ABGR8888
| drm_fourcc::XBGR8888
) {
use std::os::fd::AsRawFd;
let raw_fd = plane.fd.as_raw_fd();
let _is_drm = {
let mut link_buf = [0u8; 256];
let path = format!("/proc/self/fd/{raw_fd}\0");
let n = unsafe {
libc::readlink(
path.as_ptr() as *const _,
link_buf.as_mut_ptr() as *mut _,
255,
)
};
n > 0 && link_buf[..n as usize].starts_with(b"/dev/dri/")
};
let owned = plane.fd.try_clone().ok()?;
return Some((
width,
height,
PixelData::DmaBuf {
fd: Arc::new(owned),
fourcc: data.fourcc,
modifier: data.modifier,
stride: plane.stride,
offset: plane.offset,
},
));
}
None
}
fn read_buffer(&self, buffer: &WlBuffer) -> Option<(u32, u32, PixelData)> {
self.read_shm_buffer(buffer)
.or_else(|| self.read_dmabuf_buffer(buffer))
}
fn handle_surface_commit(&mut self, surface_id: &ObjectId) {
let (root_id, toplevel_sid) = self.find_toplevel_root(surface_id);
let had_buffer = self
.surfaces
.get(surface_id)
.is_some_and(|s| s.pending_buffer.is_some());
self.apply_pending_state(surface_id);
let toplevel_sid = match toplevel_sid {
Some(sid) => sid,
None => {
self.fire_surface_frame_callbacks(surface_id);
let _ = self.display_handle.flush_clients();
return;
}
};
let s120 = self.output_scale_120;
let target_phys = self.surface_sizes.get(&toplevel_sid).map(|&(lw, lh)| {
let pw = super::render::to_physical(lw as u32, s120 as u32);
let ph = super::render::to_physical(lh as u32, s120 as u32);
(pw, ph)
});
let composited = if let Some(ref mut vk) = self.vulkan_renderer {
vk.render_tree_sized(
&root_id,
&self.surfaces,
&self.pixel_cache,
s120,
target_phys,
)
} else {
None
};
let gpu_ok = composited.is_some();
let composited = composited.or_else(|| {
super::render::cpu_composite_from_cache(
&root_id,
&self.surfaces,
&self.pixel_cache,
s120,
)
});
if let Some((w, h, ref pixels)) = composited
&& !pixels.is_empty()
{
let kind = match pixels {
PixelData::Bgra(_) => "bgra",
PixelData::Rgba(_) => "rgba",
PixelData::Nv12 { .. } => "nv12",
PixelData::VaSurface { .. } => "va-surface",
PixelData::DmaBuf { fd, .. } => {
use std::os::fd::AsRawFd;
let raw = fd.as_raw_fd();
let mut lb = [0u8; 128];
let p = format!("/proc/self/fd/{raw}\0");
let n = unsafe {
libc::readlink(p.as_ptr() as *const _, lb.as_mut_ptr() as *mut _, 127)
};
if n > 0 && lb[..n as usize].starts_with(b"/dev/dri/") {
"dmabuf-drm"
} else {
"dmabuf-anon"
}
}
};
if self.verbose {
static LC: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let lc = LC.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if lc < 3 || lc.is_multiple_of(1000) {
eprintln!("[pending #{lc}] {w}x{h} gpu={gpu_ok} kind={kind}");
}
}
let s120_u32 = (s120 as u32).max(120);
let log_w = (w * 120).div_ceil(s120_u32);
let log_h = (h * 120).div_ceil(s120_u32);
self.pending_commits
.insert(toplevel_sid, (w, h, log_w, log_h, composited.unwrap().2));
}
self.fire_frame_callbacks_for_toplevel(toplevel_sid);
if self.pending_kb_reenter {
self.pending_kb_reenter = false;
let root_ids: Vec<ObjectId> = self.toplevel_surface_ids.values().cloned().collect();
for root_id in root_ids {
let wl = self.surfaces.get(&root_id).map(|s| s.wl_surface.clone());
if let Some(wl) = wl {
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &wl) {
kb.leave(serial, &wl);
}
}
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &wl) {
kb.enter(serial, &wl, vec![]);
}
}
}
}
let _ = self.display_handle.flush_clients();
}
if self.verbose {
let cache_entries = self.pixel_cache.len();
let has_pending = self.pending_commits.contains_key(&toplevel_sid);
static COMMIT_COUNT: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
let n = COMMIT_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if n < 5 || n.is_multiple_of(1000) {
eprintln!(
"[commit #{n}] sid={surface_id:?} root={root_id:?} cache={cache_entries} pending={has_pending} buf={had_buffer}",
);
}
}
}
fn surface_absolute_position(&self, surface_id: &ObjectId) -> (i32, i32) {
let mut x = 0i32;
let mut y = 0i32;
let mut current = surface_id.clone();
while let Some(surf) = self.surfaces.get(¤t) {
x += surf.subsurface_position.0;
y += surf.subsurface_position.1;
match surf.parent_surface_id {
Some(ref parent) => current = parent.clone(),
None => break,
}
}
(x, y)
}
fn find_toplevel_root(&self, surface_id: &ObjectId) -> (ObjectId, Option<u16>) {
let mut current = surface_id.clone();
loop {
match self.surfaces.get(¤t) {
Some(surf) => {
if let Some(ref parent) = surf.parent_surface_id {
current = parent.clone();
} else {
return (
current,
if surf.surface_id > 0 {
Some(surf.surface_id)
} else {
None
},
);
}
}
None => return (current, None),
}
}
}
fn collect_surface_tree(&self, root_id: &ObjectId) -> Vec<ObjectId> {
let mut result = Vec::new();
self.collect_tree_recursive(root_id, &mut result);
result
}
fn collect_tree_recursive(&self, surface_id: &ObjectId, result: &mut Vec<ObjectId>) {
result.push(surface_id.clone());
if let Some(surf) = self.surfaces.get(surface_id) {
for child_id in &surf.children {
self.collect_tree_recursive(child_id, result);
}
}
}
fn hit_test_surface_at(
&self,
root_id: &ObjectId,
x: f64,
y: f64,
) -> Option<(WlSurface, f64, f64)> {
self.hit_test_recursive(root_id, x, y, 0, 0).or_else(|| {
self.surfaces
.get(root_id)
.map(|s| (s.wl_surface.clone(), x, y))
})
}
fn hit_test_recursive(
&self,
surface_id: &ObjectId,
x: f64,
y: f64,
offset_x: i32,
offset_y: i32,
) -> Option<(WlSurface, f64, f64)> {
let surf = self.surfaces.get(surface_id)?;
let sx = offset_x + surf.subsurface_position.0;
let sy = offset_y + surf.subsurface_position.1;
for child_id in surf.children.iter().rev() {
if let Some(hit) = self.hit_test_recursive(child_id, x, y, sx, sy) {
return Some(hit);
}
}
if let Some(&(w, h, scale, _, _)) = self.pixel_cache.get(surface_id) {
let s = scale.max(1) as f64;
let (lw, lh) = surf
.viewport_destination
.filter(|&(dw, dh)| dw > 0 && dh > 0)
.map(|(dw, dh)| (dw as f64, dh as f64))
.unwrap_or((w as f64 / s, h as f64 / s));
let lx = x - sx as f64;
let ly = y - sy as f64;
if lx >= 0.0 && ly >= 0.0 && lx < lw && ly < lh {
return Some((surf.wl_surface.clone(), lx, ly));
}
}
None
}
fn apply_pending_state(&mut self, surface_id: &ObjectId) {
let (buffer, scale, is_opaque) = {
let Some(surf) = self.surfaces.get_mut(surface_id) else {
return;
};
let buffer = surf.pending_buffer.take();
let scale = surf.pending_buffer_scale;
surf.buffer_scale = scale;
surf.viewport_destination = surf.pending_viewport_destination;
surf.is_opaque = surf.pending_opaque;
surf.pending_damage = false;
if let Some(pos) = surf.pending_subsurface_position.take() {
surf.subsurface_position = pos;
}
(buffer, scale, surf.is_opaque)
};
let Some(buf) = buffer else { return };
if let Some((w, h, pixels)) = self.read_buffer(&buf) {
let pixels = if pixels.is_dmabuf() {
let rgba = pixels.to_rgba(w, h);
if !rgba.is_empty() {
PixelData::Rgba(std::sync::Arc::new(rgba))
} else {
pixels
}
} else {
pixels
};
self.pixel_cache
.insert(surface_id.clone(), (w, h, scale, is_opaque, pixels));
}
buf.release();
}
fn fire_surface_frame_callbacks(&mut self, surface_id: &ObjectId) {
let (callbacks, feedbacks) = {
let Some(surf) = self.surfaces.get_mut(surface_id) else {
return;
};
(
std::mem::take(&mut surf.pending_frame_callbacks),
std::mem::take(&mut surf.pending_presentation_feedbacks),
)
};
let time = elapsed_ms();
for cb in callbacks {
cb.done(time);
}
if !feedbacks.is_empty() {
let (sec, nsec) = monotonic_timespec();
for fb in feedbacks {
for output in &self.outputs {
if same_client(&fb, output) {
fb.sync_output(output);
}
}
fb.presented(
(sec >> 32) as u32,
sec as u32,
nsec as u32,
0, 0, 0, WpPresentationFeedbackKind::empty(),
);
}
}
}
fn cleanup_dead_surfaces(&mut self) {
self.fractional_scales.retain(|fs| fs.is_alive());
self.outputs.retain(|o| o.is_alive());
self.keyboards.retain(|k| k.is_alive());
self.pointers.retain(|p| p.is_alive());
self.data_devices.retain(|d| d.is_alive());
self.primary_devices.retain(|d| d.is_alive());
self.relative_pointers.retain(|p| p.is_alive());
self.text_inputs.retain(|ti| ti.resource.is_alive());
self.shm_pools.retain(|_, p| p.resource.is_alive());
self.dmabuf_params.retain(|_, p| p.resource.is_alive());
self.positioners.retain(|_, p| p.resource.is_alive());
let dead: Vec<ObjectId> = self
.surfaces
.iter()
.filter(|(_, surf)| !surf.wl_surface.is_alive())
.map(|(id, _)| id.clone())
.collect();
for proto_id in &dead {
self.pixel_cache.remove(proto_id);
if let Some(surf) = self.surfaces.remove(proto_id) {
for fb in surf.pending_presentation_feedbacks {
fb.discarded();
}
if let Some(ref parent_id) = surf.parent_surface_id
&& let Some(parent) = self.surfaces.get_mut(parent_id)
{
parent.children.retain(|c| c != proto_id);
}
if surf.surface_id > 0 {
self.toplevel_surface_ids.remove(&surf.surface_id);
self.last_reported_size.remove(&surf.surface_id);
self.surface_sizes.remove(&surf.surface_id);
let _ = self.event_tx.send(CompositorEvent::SurfaceDestroyed {
surface_id: surf.surface_id,
});
(self.event_notify)();
}
}
}
}
fn fire_frame_callbacks_for_toplevel(&mut self, toplevel_sid: u16) {
let Some(root_id) = self.toplevel_surface_ids.get(&toplevel_sid).cloned() else {
return;
};
let tree = self.collect_surface_tree(&root_id);
for sid in &tree {
self.fire_surface_frame_callbacks(sid);
}
let _ = self.display_handle.flush_clients();
}
fn handle_cursor_commit(&mut self, surface_id: &ObjectId) {
self.apply_pending_state(surface_id);
let hotspot = self
.surfaces
.get(surface_id)
.map(|s| s.cursor_hotspot)
.unwrap_or((0, 0));
if let Some((w, h, _scale, _opaque, pixels)) = self.pixel_cache.get(surface_id) {
let rgba = pixels.to_rgba(*w, *h);
if !rgba.is_empty() {
let _ = self.event_tx.send(CompositorEvent::SurfaceCursor {
surface_id: self.focused_surface_id,
cursor: CursorImage::Custom {
hotspot_x: hotspot.0 as u16,
hotspot_y: hotspot.1 as u16,
width: *w as u16,
height: *h as u16,
rgba,
},
});
}
}
self.fire_surface_frame_callbacks(surface_id);
let _ = self.display_handle.flush_clients();
}
fn handle_command(&mut self, cmd: CompositorCommand) {
match cmd {
CompositorCommand::KeyInput {
surface_id: _,
keycode,
pressed,
} => {
let serial = self.next_serial();
let time = elapsed_ms();
let state = if pressed {
wl_keyboard::KeyState::Pressed
} else {
wl_keyboard::KeyState::Released
};
let focused_wl = self
.toplevel_surface_ids
.get(&self.focused_surface_id)
.and_then(|root_id| self.surfaces.get(root_id))
.map(|s| s.wl_surface.clone());
for kb in &self.keyboards {
if let Some(ref wl) = focused_wl
&& same_client(kb, wl)
{
kb.key(serial, time, keycode, state);
}
}
self.update_and_send_modifiers(keycode, pressed);
let _ = self.display_handle.flush_clients();
}
CompositorCommand::TextInput { text } => {
let focused_wl = self
.toplevel_surface_ids
.get(&self.focused_surface_id)
.and_then(|root_id| self.surfaces.get(root_id))
.map(|s| s.wl_surface.clone());
let Some(focused_wl) = focused_wl else { return };
const KEY_LEFTSHIFT: u32 = 42;
for ch in text.chars() {
if let Some((kc, need_shift)) = char_to_keycode(ch) {
let time = elapsed_ms();
if need_shift {
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &focused_wl) {
kb.key(
serial,
time,
KEY_LEFTSHIFT,
wl_keyboard::KeyState::Pressed,
);
}
}
self.update_and_send_modifiers(KEY_LEFTSHIFT, true);
}
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &focused_wl) {
kb.key(serial, time, kc, wl_keyboard::KeyState::Pressed);
}
}
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &focused_wl) {
kb.key(serial, time, kc, wl_keyboard::KeyState::Released);
}
}
if need_shift {
let serial = self.next_serial();
for kb in &self.keyboards {
if same_client(kb, &focused_wl) {
kb.key(
serial,
time,
KEY_LEFTSHIFT,
wl_keyboard::KeyState::Released,
);
}
}
self.update_and_send_modifiers(KEY_LEFTSHIFT, false);
}
}
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::PointerMotion { surface_id, x, y } => {
let time = elapsed_ms();
let (mut x, mut y) =
if let Some(&(cw, ch, lw, lh)) = self.last_reported_size.get(&surface_id) {
let sx = if cw > 0 { lw as f64 / cw as f64 } else { 1.0 };
let sy = if ch > 0 { lh as f64 / ch as f64 } else { 1.0 };
(x * sx, y * sy)
} else {
(x, y)
};
if let Some((gx, gy, _, _)) = self
.toplevel_surface_ids
.get(&surface_id)
.and_then(|rid| self.surfaces.get(rid))
.and_then(|s| s.xdg_geometry)
{
x += gx as f64;
y += gy as f64;
}
let target_wl = self
.toplevel_surface_ids
.get(&surface_id)
.and_then(|root_id| self.hit_test_surface_at(root_id, x, y))
.map(|(wl_surface, lx, ly)| (wl_surface.id(), wl_surface, lx, ly));
static PTR_DBG: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let pn = PTR_DBG.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
if pn < 5 || pn.is_multiple_of(500) {
let root = self.toplevel_surface_ids.get(&surface_id).cloned();
let lrs = self.last_reported_size.get(&surface_id).copied();
eprintln!(
"[pointer #{pn}] sid={surface_id} logical=({x:.1},{y:.1}) lrs={lrs:?} root={root:?} hit={:?}",
target_wl.as_ref().map(|(pid, _, lx, ly)| format!(
"proto={pid:?} local=({lx:.1},{ly:.1})"
))
);
}
if let Some((proto_id, wl_surface, lx, ly)) = target_wl {
if self.pointer_entered_id.as_ref() != Some(&proto_id) {
let serial = self.next_serial();
let matching_ptrs = self
.pointers
.iter()
.filter(|p| same_client(*p, &wl_surface))
.count();
eprintln!(
"[pointer-enter] proto={proto_id:?} matching_ptrs={matching_ptrs} total_ptrs={}",
self.pointers.len()
);
if self.pointer_entered_id.is_some() {
let old_wl = self
.surfaces
.values()
.find(|s| Some(s.wl_surface.id()) == self.pointer_entered_id)
.map(|s| s.wl_surface.clone());
if let Some(old_wl) = old_wl {
for ptr in &self.pointers {
if same_client(ptr, &old_wl) {
ptr.leave(serial, &old_wl);
}
}
}
}
for ptr in &self.pointers {
if same_client(ptr, &wl_surface) {
ptr.enter(serial, &wl_surface, lx, ly);
}
}
self.pointer_entered_id = Some(proto_id);
}
for ptr in &self.pointers {
if same_client(ptr, &wl_surface) {
ptr.motion(time, lx, ly);
ptr.frame();
}
}
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::PointerButton {
surface_id: _,
button,
pressed,
} => {
let serial = self.next_serial();
let time = elapsed_ms();
let state = if pressed {
wl_pointer::ButtonState::Pressed
} else {
wl_pointer::ButtonState::Released
};
let focused_wl = self
.surfaces
.values()
.find(|s| Some(s.wl_surface.id()) == self.pointer_entered_id)
.map(|s| s.wl_surface.clone());
for ptr in &self.pointers {
if let Some(ref wl) = focused_wl
&& same_client(ptr, wl)
{
ptr.button(serial, time, button, state);
ptr.frame();
}
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::PointerAxis {
surface_id: _,
axis,
value,
} => {
let time = elapsed_ms();
let wl_axis = if axis == 0 {
wl_pointer::Axis::VerticalScroll
} else {
wl_pointer::Axis::HorizontalScroll
};
let focused_wl = self
.surfaces
.values()
.find(|s| Some(s.wl_surface.id()) == self.pointer_entered_id)
.map(|s| s.wl_surface.clone());
for ptr in &self.pointers {
if let Some(ref wl) = focused_wl
&& same_client(ptr, wl)
{
ptr.axis(time, wl_axis, value);
ptr.frame();
}
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::SurfaceResize {
surface_id,
width,
height,
scale_120,
} => {
let s_in = (scale_120 as i32).max(120);
let w = (width as i32) * 120 / s_in;
let h = (height as i32) * 120 / s_in;
self.surface_sizes.insert(surface_id, (w, h));
let mut output_changed = false;
if scale_120 > 0 && scale_120 != self.output_scale_120 {
self.output_scale_120 = scale_120;
output_changed = true;
}
let s120 = self.output_scale_120 as i32;
let (max_w, max_h) = self
.surface_sizes
.values()
.fold((0i32, 0i32), |(mw, mh), &(sw, sh)| (mw.max(sw), mh.max(sh)));
let max_w = max_w.max(1);
let max_h = max_h.max(1);
if max_w != self.output_width || max_h != self.output_height {
self.output_width = max_w;
self.output_height = max_h;
output_changed = true;
}
if output_changed {
let int_scale = ((s120) + 119) / 120;
for output in &self.outputs {
output.geometry(
0,
0,
0,
0,
wl_output::Subpixel::None,
"blit".to_string(),
"virtual".to_string(),
wl_output::Transform::Normal,
);
let mode_w = self.output_width * s120 / 120;
let mode_h = self.output_height * s120 / 120;
output.mode(
wl_output::Mode::Current | wl_output::Mode::Preferred,
mode_w,
mode_h,
60_000,
);
if output.version() >= 2 {
output.scale(int_scale);
}
}
for fs in &self.fractional_scales {
fs.preferred_scale(s120 as u32);
}
}
if output_changed {
for output in &self.outputs {
if output.version() >= 2 {
output.done();
}
}
}
let states = xdg_toplevel_states(&[
xdg_toplevel::State::Activated,
xdg_toplevel::State::Maximized,
]);
if output_changed {
for (&sid, root_id) in &self.toplevel_surface_ids {
let (lw, lh) = self.surface_sizes.get(&sid).copied().unwrap_or((w, h));
if let Some(surf) = self.surfaces.get(root_id) {
if let Some(ref tl) = surf.xdg_toplevel {
tl.configure(lw, lh, states.clone());
}
if let Some(ref xs) = surf.xdg_surface {
let serial = self.serial.wrapping_add(1);
self.serial = serial;
xs.configure(serial);
}
}
}
let all_sids: Vec<u16> = self.toplevel_surface_ids.keys().copied().collect();
for sid in all_sids {
self.fire_frame_callbacks_for_toplevel(sid);
}
self.pointer_entered_id = None;
self.pending_kb_reenter = true;
} else {
if let Some(root_id) = self.toplevel_surface_ids.get(&surface_id)
&& let Some(surf) = self.surfaces.get(root_id)
{
if let Some(ref tl) = surf.xdg_toplevel {
tl.configure(w, h, states);
}
if let Some(ref xs) = surf.xdg_surface {
let serial = self.serial.wrapping_add(1);
self.serial = serial;
xs.configure(serial);
}
}
self.fire_frame_callbacks_for_toplevel(surface_id);
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::SurfaceFocus { surface_id } => {
self.set_keyboard_focus(surface_id);
let _ = self.display_handle.flush_clients();
}
CompositorCommand::SurfaceClose { surface_id } => {
if let Some(root_id) = self.toplevel_surface_ids.get(&surface_id)
&& let Some(surf) = self.surfaces.get(root_id)
&& let Some(ref tl) = surf.xdg_toplevel
{
tl.close();
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::ClipboardOffer { mime_type, data } => {
self.external_clipboard = Some(ExternalClipboard { mime_type, data });
self.selection_source = None;
self.offer_external_clipboard();
}
CompositorCommand::Capture {
surface_id,
scale_120,
reply,
} => {
let cap_s120 = if scale_120 > 0 {
scale_120
} else {
self.output_scale_120
};
let result = if let Some(root_id) = self.toplevel_surface_ids.get(&surface_id) {
super::render::cpu_composite_from_cache(
root_id,
&self.surfaces,
&self.pixel_cache,
cap_s120,
)
.map(|(w, h, pixels)| {
let rgba = pixels.to_rgba(w, h);
(w, h, rgba)
})
} else {
None
};
let _ = reply.send(result);
}
CompositorCommand::RequestFrame { surface_id } => {
self.fire_frame_callbacks_for_toplevel(surface_id);
}
CompositorCommand::ReleaseKeys { keycodes } => {
let time = elapsed_ms();
let focused_wl = self
.toplevel_surface_ids
.get(&self.focused_surface_id)
.and_then(|root_id| self.surfaces.get(root_id))
.map(|s| s.wl_surface.clone());
for keycode in &keycodes {
let serial = self.next_serial();
for kb in &self.keyboards {
if let Some(ref wl) = focused_wl
&& same_client(kb, wl)
{
kb.key(serial, time, *keycode, wl_keyboard::KeyState::Released);
}
}
}
for keycode in &keycodes {
self.update_and_send_modifiers(*keycode, false);
}
let _ = self.display_handle.flush_clients();
}
CompositorCommand::ClipboardListMimes { reply } => {
let mimes = self.collect_clipboard_mime_types();
let _ = reply.send(mimes);
}
CompositorCommand::ClipboardGet { mime_type, reply } => {
let data = self.get_clipboard_content(&mime_type);
let _ = reply.send(data);
}
CompositorCommand::SetExternalOutputBuffers { buffers } => {
if let Some(ref mut vk) = self.vulkan_renderer {
vk.set_external_output_buffers(buffers);
}
}
CompositorCommand::Shutdown => {
self.shutdown.store(true, Ordering::Relaxed);
self.loop_signal.stop();
}
}
}
}
impl Compositor {
fn collect_clipboard_mime_types(&self) -> Vec<String> {
if let Some(ref src) = self.selection_source {
let data = src.data::<DataSourceData>().unwrap();
return data.mime_types.lock().unwrap().clone();
}
if let Some(ref cb) = self.external_clipboard
&& !cb.mime_type.is_empty()
{
let mut mimes = vec![cb.mime_type.clone()];
if cb.mime_type.starts_with("text/plain") {
if cb.mime_type != "text/plain" {
mimes.push("text/plain".to_string());
}
if cb.mime_type != "text/plain;charset=utf-8" {
mimes.push("text/plain;charset=utf-8".to_string());
}
mimes.push("UTF8_STRING".to_string());
}
return mimes;
}
Vec::new()
}
fn get_clipboard_content(&mut self, mime_type: &str) -> Option<Vec<u8>> {
if let Some(ref cb) = self.external_clipboard
&& self.selection_source.is_none()
{
let matches = cb.mime_type == mime_type
|| (cb.mime_type.starts_with("text/plain")
&& (mime_type == "text/plain"
|| mime_type == "text/plain;charset=utf-8"
|| mime_type == "UTF8_STRING"));
if matches {
return Some(cb.data.clone());
}
return None;
}
if let Some(src) = self.selection_source.clone() {
return self.read_data_source_sync(&src, mime_type);
}
None
}
fn read_data_source_sync(&mut self, source: &WlDataSource, mime_type: &str) -> Option<Vec<u8>> {
let mut fds = [0i32; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return None;
}
let read_fd = unsafe { OwnedFd::from_raw_fd(fds[0]) };
let write_fd = unsafe { OwnedFd::from_raw_fd(fds[1]) };
source.send(mime_type.to_string(), write_fd.as_fd());
let _ = self.display_handle.flush_clients();
drop(write_fd); unsafe {
libc::fcntl(read_fd.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
}
std::thread::sleep(std::time::Duration::from_millis(5));
let mut buf = Vec::new();
let mut tmp = [0u8; 8192];
loop {
let n = unsafe {
libc::read(
read_fd.as_raw_fd(),
tmp.as_mut_ptr() as *mut libc::c_void,
tmp.len(),
)
};
if n <= 0 {
break;
}
buf.extend_from_slice(&tmp[..n as usize]);
if buf.len() > 1024 * 1024 {
break; }
}
if buf.is_empty() { None } else { Some(buf) }
}
}
fn monotonic_timespec() -> (i64, i64) {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
(ts.tv_sec, ts.tv_nsec)
}
fn elapsed_ms() -> u32 {
let (sec, nsec) = monotonic_timespec();
(sec as u32)
.wrapping_mul(1000)
.wrapping_add(nsec as u32 / 1_000_000)
}
fn same_client<R1: Resource, R2: Resource>(a: &R1, b: &R2) -> bool {
match (a.client(), b.client()) {
(Some(ca), Some(cb)) => ca.id() == cb.id(),
_ => false,
}
}
fn yuv420_to_rgb(y: u8, u: u8, v: u8) -> [u8; 3] {
let y = (y as i32 - 16).max(0);
let u = u as i32 - 128;
let v = v as i32 - 128;
let r = ((298 * y + 409 * v + 128) >> 8).clamp(0, 255) as u8;
let g = ((298 * y - 100 * u - 208 * v + 128) >> 8).clamp(0, 255) as u8;
let b = ((298 * y + 516 * u + 128) >> 8).clamp(0, 255) as u8;
[r, g, b]
}
fn xdg_toplevel_states(states: &[xdg_toplevel::State]) -> Vec<u8> {
let mut bytes = Vec::with_capacity(states.len() * 4);
for state in states {
bytes.extend_from_slice(&(*state as u32).to_ne_bytes());
}
bytes
}
fn create_keymap_fd(keymap_data: &[u8]) -> Option<OwnedFd> {
use std::io::Write;
let name = c"blit-keymap";
let raw_fd = unsafe { libc::memfd_create(name.as_ptr(), libc::MFD_CLOEXEC) };
if raw_fd < 0 {
return None;
}
let fd = unsafe { OwnedFd::from_raw_fd(raw_fd) };
let mut file = std::fs::File::from(fd);
file.write_all(keymap_data).ok()?;
Some(file.into())
}
impl GlobalDispatch<WlCompositor, ()> for Compositor {
fn bind(
_state: &mut Self,
_handle: &DisplayHandle,
_client: &Client,
resource: New<WlCompositor>,
_data: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WlCompositor, ()> for Compositor {
fn request(
state: &mut Self,
_client: &Client,
_resource: &WlCompositor,
request: <WlCompositor as Resource>::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_compositor::Request;
match request {
Request::CreateSurface { id } => {
let surface = data_init.init(id, ());
let proto_id = surface.id();
state.surfaces.insert(
proto_id,
Surface {
surface_id: 0,
wl_surface: surface,
pending_buffer: None,
pending_buffer_scale: 1,
pending_damage: false,
pending_frame_callbacks: Vec::new(),
pending_presentation_feedbacks: Vec::new(),
pending_opaque: false,
buffer_scale: 1,
is_opaque: false,
parent_surface_id: None,
pending_subsurface_position: None,
subsurface_position: (0, 0),
children: Vec::new(),
xdg_surface: None,
xdg_toplevel: None,
xdg_popup: None,
xdg_geometry: None,
title: String::new(),
app_id: String::new(),
pending_viewport_destination: None,
viewport_destination: None,
is_cursor: false,
cursor_hotspot: (0, 0),
},
);
}
Request::CreateRegion { id } => {
data_init.init(id, ());
}
_ => {}
}
}
}
impl Dispatch<WlSurface, ()> for Compositor {
fn request(
state: &mut Self,
_client: &Client,
resource: &WlSurface,
request: <WlSurface as Resource>::Request,
_data: &(),
_dh: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_surface::Request;
let sid = resource.id();
match request {
Request::Attach { buffer, x: _, y: _ } => {
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.pending_buffer = buffer;
}
}
Request::Damage { .. } | Request::DamageBuffer { .. } => {
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.pending_damage = true;
}
}
Request::Frame { callback } => {
let cb = data_init.init(callback, ());
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.pending_frame_callbacks.push(cb);
}
}
Request::SetBufferScale { scale } => {
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.pending_buffer_scale = scale;
}
}
Request::SetOpaqueRegion { region: _ } => {
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.pending_opaque = true;
}
}
Request::SetInputRegion { .. } => {}
Request::Commit => {
let is_cursor = state.surfaces.get(&sid).is_some_and(|s| s.is_cursor);
if is_cursor {
state.handle_cursor_commit(&sid);
} else {
state.handle_surface_commit(&sid);
}
}
Request::SetBufferTransform { .. } => {}
Request::Offset { .. } => {}
Request::Destroy => {
state.pixel_cache.remove(&sid);
if let Some(parent_id) = state
.surfaces
.get(&sid)
.and_then(|s| s.parent_surface_id.clone())
&& let Some(parent) = state.surfaces.get_mut(&parent_id)
{
parent.children.retain(|c| *c != sid);
}
if let Some(surf) = state.surfaces.remove(&sid) {
for fb in surf.pending_presentation_feedbacks {
fb.discarded();
}
if surf.surface_id > 0 {
state.toplevel_surface_ids.remove(&surf.surface_id);
state.last_reported_size.remove(&surf.surface_id);
state.surface_sizes.remove(&surf.surface_id);
let _ = state.event_tx.send(CompositorEvent::SurfaceDestroyed {
surface_id: surf.surface_id,
});
(state.event_notify)();
}
}
}
_ => {}
}
}
}
impl Dispatch<WlCallback, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WlCallback,
_: <WlCallback as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl GlobalDispatch<WpPresentation, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WpPresentation>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
let pres = data_init.init(resource, ());
pres.clock_id(libc::CLOCK_MONOTONIC as u32);
}
}
impl Dispatch<WpPresentation, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WpPresentation,
request: <WpPresentation as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wp_presentation::Request;
match request {
Request::Feedback { surface, callback } => {
let fb = data_init.init(callback, ());
let sid = surface.id();
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.pending_presentation_feedbacks.push(fb);
}
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<WpPresentationFeedback, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WpPresentationFeedback,
_: <WpPresentationFeedback as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl Dispatch<WlRegion, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WlRegion,
_: <WlRegion as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl GlobalDispatch<WlSubcompositor, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WlSubcompositor>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WlSubcompositor, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlSubcompositor,
request: <WlSubcompositor as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_subcompositor::Request;
match request {
Request::GetSubsurface {
id,
surface,
parent,
} => {
let child_id = surface.id();
let parent_id = parent.id();
data_init.init(
id,
SubsurfaceData {
wl_surface_id: child_id.clone(),
parent_surface_id: parent_id.clone(),
},
);
if let Some(surf) = state.surfaces.get_mut(&child_id) {
surf.parent_surface_id = Some(parent_id.clone());
}
if let Some(parent_surf) = state.surfaces.get_mut(&parent_id)
&& !parent_surf.children.contains(&child_id)
{
parent_surf.children.push(child_id);
}
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<WlSubsurface, SubsurfaceData> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlSubsurface,
request: <WlSubsurface as Resource>::Request,
data: &SubsurfaceData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_subsurface::Request;
match request {
Request::SetPosition { x, y } => {
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id) {
surf.pending_subsurface_position = Some((x, y));
}
}
Request::PlaceAbove { sibling } => {
let sibling_id = sibling.id();
if let Some(parent) = state.surfaces.get_mut(&data.parent_surface_id) {
let child_id = &data.wl_surface_id;
parent.children.retain(|c| c != child_id);
let pos = parent
.children
.iter()
.position(|c| *c == sibling_id)
.map(|p| p + 1)
.unwrap_or(parent.children.len());
parent.children.insert(pos, child_id.clone());
}
}
Request::PlaceBelow { sibling } => {
let sibling_id = sibling.id();
if let Some(parent) = state.surfaces.get_mut(&data.parent_surface_id) {
let child_id = &data.wl_surface_id;
parent.children.retain(|c| c != child_id);
let pos = parent
.children
.iter()
.position(|c| *c == sibling_id)
.unwrap_or(0);
parent.children.insert(pos, child_id.clone());
}
}
Request::SetSync | Request::SetDesync => {}
Request::Destroy => {
let child_id = &data.wl_surface_id;
if let Some(parent) = state.surfaces.get_mut(&data.parent_surface_id) {
parent.children.retain(|c| c != child_id);
}
if let Some(surf) = state.surfaces.get_mut(child_id) {
surf.parent_surface_id = None;
}
}
_ => {}
}
}
}
impl GlobalDispatch<XdgWmBase, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<XdgWmBase>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<XdgWmBase, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &XdgWmBase,
request: <XdgWmBase as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use xdg_wm_base::Request;
match request {
Request::GetXdgSurface { id, surface } => {
let wl_surface_id = surface.id();
let xdg_surface = data_init.init(
id,
XdgSurfaceData {
wl_surface_id: wl_surface_id.clone(),
},
);
if let Some(surf) = state.surfaces.get_mut(&wl_surface_id) {
surf.xdg_surface = Some(xdg_surface);
}
}
Request::CreatePositioner { id } => {
let positioner = data_init.init(id, ());
let pos_id = positioner.id();
state.positioners.insert(
pos_id,
PositionerState {
resource: positioner,
geometry: PositionerGeometry {
size: (0, 0),
anchor_rect: (0, 0, 0, 0),
anchor: 0,
gravity: 0,
constraint_adjustment: 0,
offset: (0, 0),
},
},
);
}
Request::Pong { .. } => {}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<XdgSurface, XdgSurfaceData> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &XdgSurface,
request: <XdgSurface as Resource>::Request,
data: &XdgSurfaceData,
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use xdg_surface::Request;
match request {
Request::GetToplevel { id } => {
let toplevel = data_init.init(
id,
XdgToplevelData {
wl_surface_id: data.wl_surface_id.clone(),
},
);
let surface_id = state.allocate_surface_id();
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id) {
surf.xdg_toplevel = Some(toplevel.clone());
surf.surface_id = surface_id;
}
state
.toplevel_surface_ids
.insert(surface_id, data.wl_surface_id.clone());
let (cw, ch) = state
.surface_sizes
.get(&surface_id)
.copied()
.unwrap_or((state.output_width, state.output_height));
let states = xdg_toplevel_states(&[
xdg_toplevel::State::Activated,
xdg_toplevel::State::Maximized,
]);
toplevel.configure(cw, ch, states);
let serial = state.next_serial();
resource.configure(serial);
state.set_keyboard_focus(surface_id);
if let Some(surf) = state.surfaces.get(&data.wl_surface_id) {
for output in &state.outputs {
if same_client(output, &surf.wl_surface) {
surf.wl_surface.enter(output);
}
}
}
let _ = state.display_handle.flush_clients();
let _ = state.event_tx.send(CompositorEvent::SurfaceCreated {
surface_id,
title: String::new(),
app_id: String::new(),
parent_id: 0,
width: 0,
height: 0,
});
(state.event_notify)();
if state.verbose {
eprintln!("[compositor] new_toplevel sid={surface_id}");
}
}
Request::GetPopup {
id,
parent,
positioner,
} => {
let popup = data_init.init(
id,
XdgPopupData {
wl_surface_id: data.wl_surface_id.clone(),
},
);
let parent_wl_id: Option<ObjectId> = parent
.as_ref()
.and_then(|p| p.data::<XdgSurfaceData>())
.map(|d| d.wl_surface_id.clone());
let parent_geom_offset = parent_wl_id
.as_ref()
.and_then(|pid| state.surfaces.get(pid))
.and_then(|s| s.xdg_geometry)
.map(|(gx, gy, _, _)| (gx, gy))
.unwrap_or((0, 0));
let parent_abs = parent_wl_id
.as_ref()
.map(|pid| {
let abs = state.surface_absolute_position(pid);
(abs.0 + parent_geom_offset.0, abs.1 + parent_geom_offset.1)
})
.unwrap_or((0, 0));
let (_, toplevel_root) = parent_wl_id
.as_ref()
.map(|pid| state.find_toplevel_root(pid))
.unwrap_or_else(|| {
(data.wl_surface_id.clone(), None)
});
let bounds = toplevel_root
.and_then(|_| {
let root_wl_id = parent_wl_id.as_ref().map(|pid| {
let (rid, _) = state.find_toplevel_root(pid);
rid
})?;
let surf = state.surfaces.get(&root_wl_id)?;
if let Some((gx, gy, gw, gh)) = surf.xdg_geometry
&& gw > 0
&& gh > 0
{
return Some((gx, gy, gw, gh));
}
let (w, h, scale, _, _) = state.pixel_cache.get(&root_wl_id)?;
let s = (*scale).max(1);
let (lw, lh) = surf
.viewport_destination
.filter(|&(dw, dh)| dw > 0 && dh > 0)
.unwrap_or((*w as i32 / s, *h as i32 / s));
Some((0, 0, lw, lh))
})
.unwrap_or((0, 0, state.output_width, state.output_height));
eprintln!(
"[popup] parent_abs={parent_abs:?} bounds={bounds:?} parent_wl={parent_wl_id:?} geom_off={parent_geom_offset:?}"
);
let pos_id = positioner.id();
let (px, py, pw, ph) = state
.positioners
.get(&pos_id)
.map(|p| p.geometry.compute_position(parent_abs, bounds))
.unwrap_or((0, 0, 200, 200));
eprintln!("[popup] result=({px},{py},{pw},{ph})");
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id) {
surf.xdg_popup = Some(popup.clone());
surf.parent_surface_id = parent_wl_id.clone();
surf.subsurface_position =
(parent_geom_offset.0 + px, parent_geom_offset.1 + py);
}
if let Some(ref parent_id) = parent_wl_id
&& let Some(parent_surf) = state.surfaces.get_mut(parent_id)
&& !parent_surf.children.contains(&data.wl_surface_id)
{
parent_surf.children.push(data.wl_surface_id.clone());
}
popup.configure(px, py, pw, ph);
let serial = state.next_serial();
resource.configure(serial);
let _ = state.display_handle.flush_clients();
}
Request::SetWindowGeometry {
x,
y,
width,
height,
} => {
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id) {
if surf.xdg_popup.is_some() {
let (old_gx, old_gy) = surf
.xdg_geometry
.map(|(gx, gy, _, _)| (gx, gy))
.unwrap_or((0, 0));
surf.subsurface_position.0 += old_gx - x;
surf.subsurface_position.1 += old_gy - y;
}
surf.xdg_geometry = Some((x, y, width, height));
}
}
Request::AckConfigure { .. } => {}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<XdgToplevel, XdgToplevelData> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &XdgToplevel,
request: <XdgToplevel as Resource>::Request,
data: &XdgToplevelData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use xdg_toplevel::Request;
match request {
Request::SetTitle { title } => {
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id)
&& surf.title != title
{
surf.title = title.clone();
if surf.surface_id > 0 {
let _ = state.event_tx.send(CompositorEvent::SurfaceTitle {
surface_id: surf.surface_id,
title,
});
(state.event_notify)();
}
}
}
Request::SetAppId { app_id } => {
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id)
&& surf.app_id != app_id
{
surf.app_id = app_id.clone();
if surf.surface_id > 0 {
let _ = state.event_tx.send(CompositorEvent::SurfaceAppId {
surface_id: surf.surface_id,
app_id,
});
(state.event_notify)();
}
}
}
Request::Destroy => {
let wl_surface_id = &data.wl_surface_id;
state.pixel_cache.remove(wl_surface_id);
if let Some(surf) = state.surfaces.get_mut(wl_surface_id) {
let sid = surf.surface_id;
surf.xdg_toplevel = None;
if sid > 0 {
state.toplevel_surface_ids.remove(&sid);
state.last_reported_size.remove(&sid);
state.surface_sizes.remove(&sid);
let _ = state
.event_tx
.send(CompositorEvent::SurfaceDestroyed { surface_id: sid });
(state.event_notify)();
surf.surface_id = 0;
}
}
}
_ => {}
}
}
}
impl Dispatch<XdgPopup, XdgPopupData> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &XdgPopup,
request: <XdgPopup as Resource>::Request,
data: &XdgPopupData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use xdg_popup::Request;
if let Request::Destroy = request {
if let Some(parent_id) = state
.surfaces
.get(&data.wl_surface_id)
.and_then(|s| s.parent_surface_id.clone())
&& let Some(parent) = state.surfaces.get_mut(&parent_id)
{
parent.children.retain(|c| *c != data.wl_surface_id);
}
if let Some(surf) = state.surfaces.get_mut(&data.wl_surface_id) {
surf.xdg_popup = None;
surf.parent_surface_id = None;
}
}
}
}
use wayland_protocols::xdg::shell::server::xdg_positioner;
impl Dispatch<XdgPositioner, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &XdgPositioner,
request: <XdgPositioner as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use xdg_positioner::Request;
let pos_id = resource.id();
let Some(pos) = state.positioners.get_mut(&pos_id) else {
return;
};
match request {
Request::SetSize { width, height } => {
pos.geometry.size = (width, height);
}
Request::SetAnchorRect {
x,
y,
width,
height,
} => {
pos.geometry.anchor_rect = (x, y, width, height);
}
Request::SetAnchor {
anchor: wayland_server::WEnum::Value(v),
} => {
pos.geometry.anchor = v as u32;
}
Request::SetGravity {
gravity: wayland_server::WEnum::Value(v),
} => {
pos.geometry.gravity = v as u32;
}
Request::SetOffset { x, y } => {
pos.geometry.offset = (x, y);
}
Request::SetConstraintAdjustment {
constraint_adjustment,
} => {
pos.geometry.constraint_adjustment = constraint_adjustment.into();
}
Request::Destroy => {
state.positioners.remove(&pos_id);
}
_ => {}
}
}
}
impl GlobalDispatch<ZxdgDecorationManagerV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<ZxdgDecorationManagerV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZxdgDecorationManagerV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &ZxdgDecorationManagerV1,
request: <ZxdgDecorationManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zxdg_decoration_manager_v1::Request;
match request {
Request::GetToplevelDecoration { id, toplevel: _ } => {
let decoration = data_init.init(id, ());
decoration.configure(zxdg_toplevel_decoration_v1::Mode::ServerSide);
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<ZxdgToplevelDecorationV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
resource: &ZxdgToplevelDecorationV1,
request: <ZxdgToplevelDecorationV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use zxdg_toplevel_decoration_v1::Request;
match request {
Request::SetMode { .. } | Request::UnsetMode => {
resource.configure(zxdg_toplevel_decoration_v1::Mode::ServerSide);
}
Request::Destroy => {}
_ => {}
}
}
}
impl GlobalDispatch<WlShm, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WlShm>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
let shm = data_init.init(resource, ());
shm.format(wl_shm::Format::Argb8888);
shm.format(wl_shm::Format::Xrgb8888);
shm.format(wl_shm::Format::Abgr8888);
shm.format(wl_shm::Format::Xbgr8888);
}
}
impl Dispatch<WlShm, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlShm,
request: <WlShm as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_shm::Request;
if let Request::CreatePool { id, fd, size } = request {
let pool = data_init.init(id, ());
let pool_id = pool.id();
state
.shm_pools
.insert(pool_id, ShmPool::new(pool, fd, size));
}
}
}
impl Dispatch<WlShmPool, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &WlShmPool,
request: <WlShmPool as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_shm_pool::Request;
let pool_id = resource.id();
match request {
Request::CreateBuffer {
id,
offset,
width,
height,
stride,
format,
} => {
let fmt = match format {
wayland_server::WEnum::Value(f) => f,
_ => wl_shm::Format::Argb8888, };
data_init.init(
id,
ShmBufferData {
pool_id: pool_id.clone(),
offset,
width,
height,
stride,
format: fmt,
},
);
}
Request::Resize { size } => {
if let Some(pool) = state.shm_pools.get_mut(&pool_id) {
pool.resize(size);
}
}
Request::Destroy => {
state.shm_pools.remove(&pool_id);
}
_ => {}
}
}
}
impl Dispatch<WlBuffer, ShmBufferData> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WlBuffer,
_: <WlBuffer as Resource>::Request,
_: &ShmBufferData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl Dispatch<WlBuffer, DmaBufBufferData> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WlBuffer,
_: <WlBuffer as Resource>::Request,
_: &DmaBufBufferData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl GlobalDispatch<WlOutput, ()> for Compositor {
fn bind(
state: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WlOutput>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
let output = data_init.init(resource, ());
output.geometry(
0,
0,
0,
0,
wl_output::Subpixel::Unknown,
"Virtual".to_string(),
"Headless".to_string(),
wl_output::Transform::Normal,
);
let s120 = state.output_scale_120 as i32;
let mode_w = state.output_width * s120 / 120;
let mode_h = state.output_height * s120 / 120;
output.mode(
wl_output::Mode::Current | wl_output::Mode::Preferred,
mode_w,
mode_h,
60_000,
);
if output.version() >= 2 {
output.scale(((state.output_scale_120 as i32) + 119) / 120);
}
if output.version() >= 2 {
output.done();
}
state.outputs.push(output);
}
}
impl Dispatch<WlOutput, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &WlOutput,
request: <WlOutput as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_output::Request;
if let Request::Release = request {
state.outputs.retain(|o| o.id() != resource.id());
}
}
}
impl GlobalDispatch<WlSeat, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WlSeat>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
let seat = data_init.init(resource, ());
seat.capabilities(wl_seat::Capability::Keyboard | wl_seat::Capability::Pointer);
if seat.version() >= 2 {
seat.name("headless".to_string());
}
}
}
impl Dispatch<WlSeat, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlSeat,
request: <WlSeat as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wayland_server::protocol::wl_seat::Request;
match request {
Request::GetKeyboard { id } => {
let kb = data_init.init(id, ());
if let Some(fd) = create_keymap_fd(&state.keyboard_keymap_data) {
kb.keymap(
wl_keyboard::KeymapFormat::XkbV1,
fd.as_fd(),
state.keyboard_keymap_data.len() as u32,
);
}
if kb.version() >= 4 {
kb.repeat_info(25, 200);
}
state.keyboards.push(kb);
}
Request::GetPointer { id } => {
let ptr = data_init.init(id, ());
state.pointers.push(ptr);
}
Request::GetTouch { id } => {
data_init.init(id, ());
}
Request::Release => {}
_ => {}
}
}
}
impl Dispatch<WlKeyboard, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &WlKeyboard,
request: <WlKeyboard as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
if let wl_keyboard::Request::Release = request {
state.keyboards.retain(|k| k.id() != resource.id());
}
}
}
impl Dispatch<WlPointer, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &WlPointer,
request: <WlPointer as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wl_pointer::Request;
match request {
Request::SetCursor {
serial: _,
surface,
hotspot_x,
hotspot_y,
} => {
if let Some(surface) = surface {
let sid = surface.id();
if let Some(surf) = state.surfaces.get_mut(&sid) {
surf.is_cursor = true;
surf.cursor_hotspot = (hotspot_x, hotspot_y);
}
} else {
let _ = state.event_tx.send(CompositorEvent::SurfaceCursor {
surface_id: state.focused_surface_id,
cursor: CursorImage::Hidden,
});
}
}
Request::Release => {
state.pointers.retain(|p| p.id() != resource.id());
}
_ => {}
}
}
}
impl Dispatch<wayland_server::protocol::wl_touch::WlTouch, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &wayland_server::protocol::wl_touch::WlTouch,
_: <wayland_server::protocol::wl_touch::WlTouch as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl GlobalDispatch<ZwpLinuxDmabufV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<ZwpLinuxDmabufV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
let dmabuf = data_init.init(resource, ());
if dmabuf.version() >= 3 {
let formats: &[(u32, u32, u32)] = &[
(drm_fourcc::ARGB8888, 0, 0),
(drm_fourcc::XRGB8888, 0, 0),
(drm_fourcc::ABGR8888, 0, 0),
(drm_fourcc::XBGR8888, 0, 0),
(drm_fourcc::ARGB8888, 0x00ffffff, 0xffffffff),
(drm_fourcc::XRGB8888, 0x00ffffff, 0xffffffff),
(drm_fourcc::ABGR8888, 0x00ffffff, 0xffffffff),
(drm_fourcc::XBGR8888, 0x00ffffff, 0xffffffff),
];
for &(fmt, mod_hi, mod_lo) in formats {
dmabuf.modifier(fmt, mod_hi, mod_lo);
}
} else {
dmabuf.format(drm_fourcc::ARGB8888);
dmabuf.format(drm_fourcc::XRGB8888);
dmabuf.format(drm_fourcc::ABGR8888);
dmabuf.format(drm_fourcc::XBGR8888);
}
}
}
impl Dispatch<ZwpLinuxDmabufV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &ZwpLinuxDmabufV1,
request: <ZwpLinuxDmabufV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zwp_linux_dmabuf_v1::Request;
match request {
Request::CreateParams { params_id } => {
data_init.init(params_id, ());
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<ZwpLinuxBufferParamsV1, ()> for Compositor {
fn request(
state: &mut Self,
client: &Client,
resource: &ZwpLinuxBufferParamsV1,
request: <ZwpLinuxBufferParamsV1 as Resource>::Request,
_: &(),
dh: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zwp_linux_buffer_params_v1::Request;
let params_id = resource.id();
match request {
Request::Add {
fd,
plane_idx: _,
offset,
stride,
modifier_hi,
modifier_lo,
} => {
let modifier = ((modifier_hi as u64) << 32) | (modifier_lo as u64);
let entry = state
.dmabuf_params
.entry(params_id.clone())
.or_insert_with(|| DmaBufParamsPending {
resource: resource.clone(),
planes: Vec::new(),
modifier,
});
entry.modifier = modifier;
entry.planes.push(DmaBufPlane { fd, offset, stride });
}
Request::Create {
width,
height,
format,
flags: _,
} => {
let pending = state.dmabuf_params.remove(¶ms_id);
let (planes, modifier) = match pending {
Some(p) => (p.planes, p.modifier),
None => {
resource.failed();
return;
}
};
match client.create_resource::<WlBuffer, DmaBufBufferData, Compositor>(
dh,
1,
DmaBufBufferData {
width,
height,
fourcc: format,
modifier,
planes,
},
) {
Ok(buffer) => resource.created(&buffer),
Err(_) => resource.failed(),
}
}
Request::CreateImmed {
buffer_id,
width,
height,
format,
flags: _,
} => {
let (planes, modifier) = state
.dmabuf_params
.remove(¶ms_id)
.map(|p| (p.planes, p.modifier))
.unwrap_or_default();
data_init.init(
buffer_id,
DmaBufBufferData {
width,
height,
fourcc: format,
modifier,
planes,
},
);
}
Request::Destroy => {
state.dmabuf_params.remove(¶ms_id);
}
_ => {}
}
}
}
impl GlobalDispatch<WpFractionalScaleManagerV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WpFractionalScaleManagerV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WpFractionalScaleManagerV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WpFractionalScaleManagerV1,
request: <WpFractionalScaleManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wp_fractional_scale_manager_v1::Request;
match request {
Request::GetFractionalScale { id, surface: _ } => {
let fs = data_init.init(id, ());
fs.preferred_scale(state.output_scale_120 as u32);
state.fractional_scales.push(fs);
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<WpFractionalScaleV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &WpFractionalScaleV1,
_: <WpFractionalScaleV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
state
.fractional_scales
.retain(|fs| fs.id() != resource.id());
}
}
impl GlobalDispatch<WpViewporter, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WpViewporter>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WpViewporter, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WpViewporter,
request: <WpViewporter as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wp_viewporter::Request;
match request {
Request::GetViewport { id, surface } => {
let obj_id = surface.id();
data_init.init(id, obj_id);
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<WpViewport, ObjectId> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WpViewport,
request: <WpViewport as Resource>::Request,
surface_obj_id: &ObjectId,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wayland_protocols::wp::viewporter::server::wp_viewport::Request;
match request {
Request::SetDestination { width, height } => {
if let Some(surf) = state.surfaces.get_mut(surface_obj_id) {
if width > 0 && height > 0 {
surf.pending_viewport_destination = Some((width, height));
} else {
surf.pending_viewport_destination = None;
}
}
}
Request::SetSource { .. } => {
}
Request::Destroy => {}
_ => {}
}
}
}
impl GlobalDispatch<WlDataDeviceManager, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WlDataDeviceManager>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WlDataDeviceManager, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlDataDeviceManager,
request: <WlDataDeviceManager as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wl_data_device_manager::Request;
match request {
Request::CreateDataSource { id } => {
data_init.init(
id,
DataSourceData {
mime_types: std::sync::Mutex::new(Vec::new()),
},
);
}
Request::GetDataDevice { id, seat: _ } => {
let dd = data_init.init(id, ());
state.data_devices.push(dd);
}
_ => {}
}
}
}
impl Dispatch<WlDataSource, DataSourceData> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WlDataSource,
request: <WlDataSource as Resource>::Request,
data: &DataSourceData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wl_data_source::Request;
match request {
Request::Offer { mime_type } => {
data.mime_types.lock().unwrap().push(mime_type);
}
Request::Destroy => {}
_ => {} }
}
fn destroyed(
state: &mut Self,
_: wayland_server::backend::ClientId,
resource: &WlDataSource,
_: &DataSourceData,
) {
if state
.selection_source
.as_ref()
.is_some_and(|s| s.id() == resource.id())
{
state.selection_source = None;
}
}
}
impl Dispatch<WlDataDevice, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlDataDevice,
request: <WlDataDevice as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wl_data_device::Request;
match request {
Request::SetSelection { source, serial: _ } => {
state.selection_source = source.clone();
if let Some(ref src) = source {
let data = src.data::<DataSourceData>().unwrap();
let mimes = data.mime_types.lock().unwrap();
let text_mime = mimes
.iter()
.find(|m| {
m.as_str() == "text/plain;charset=utf-8"
|| m.as_str() == "text/plain"
|| m.as_str() == "UTF8_STRING"
})
.cloned();
drop(mimes);
if let Some(mime) = text_mime {
state.read_data_source_and_emit(src, &mime);
}
}
}
Request::Release => {}
_ => {} }
}
fn destroyed(
state: &mut Self,
_: wayland_server::backend::ClientId,
resource: &WlDataDevice,
_: &(),
) {
state.data_devices.retain(|d| d.id() != resource.id());
}
}
impl Dispatch<WlDataOffer, DataOfferData> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WlDataOffer,
request: <WlDataOffer as Resource>::Request,
data: &DataOfferData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wl_data_offer::Request;
match request {
Request::Receive { mime_type, fd } => {
if data.external {
if let Some(ref cb) = state.external_clipboard
&& (cb.mime_type == mime_type
|| mime_type == "text/plain"
|| mime_type == "text/plain;charset=utf-8"
|| mime_type == "UTF8_STRING")
{
use std::io::Write;
let mut f = std::fs::File::from(fd);
let _ = f.write_all(&cb.data);
}
} else if let Some(ref src) = state.selection_source {
src.send(mime_type, fd.as_fd());
}
}
Request::Destroy => {}
_ => {} }
}
}
impl Compositor {
fn read_data_source_and_emit(&mut self, source: &WlDataSource, mime_type: &str) {
let mut fds = [0i32; 2];
if unsafe { libc::pipe(fds.as_mut_ptr()) } != 0 {
return;
}
let read_fd = unsafe { OwnedFd::from_raw_fd(fds[0]) };
let write_fd = unsafe { OwnedFd::from_raw_fd(fds[1]) };
source.send(mime_type.to_string(), write_fd.as_fd());
let _ = self.display_handle.flush_clients();
unsafe {
libc::fcntl(read_fd.as_raw_fd(), libc::F_SETFL, libc::O_NONBLOCK);
}
std::thread::sleep(std::time::Duration::from_millis(5));
let mut buf = Vec::new();
let mut tmp = [0u8; 8192];
loop {
let n = unsafe {
libc::read(
read_fd.as_raw_fd(),
tmp.as_mut_ptr() as *mut libc::c_void,
tmp.len(),
)
};
if n <= 0 {
break;
}
buf.extend_from_slice(&tmp[..n as usize]);
if buf.len() > 1024 * 1024 {
break; }
}
if !buf.is_empty() {
let _ = self.event_tx.send(CompositorEvent::ClipboardContent {
mime_type: mime_type.to_string(),
data: buf,
});
(self.event_notify)();
}
}
fn offer_external_clipboard(&mut self) {
let Some(ref cb) = self.external_clipboard else {
return;
};
let mime = cb.mime_type.clone();
for dd in &self.data_devices {
if let Some(client) = dd.client() {
let offer = client
.create_resource::<WlDataOffer, DataOfferData, Compositor>(
&self.display_handle,
dd.version(),
DataOfferData { external: true },
)
.unwrap();
dd.data_offer(&offer);
offer.offer(mime.clone());
if mime.starts_with("text/plain") {
if mime != "text/plain" {
offer.offer("text/plain".to_string());
}
if mime != "text/plain;charset=utf-8" {
offer.offer("text/plain;charset=utf-8".to_string());
}
offer.offer("UTF8_STRING".to_string());
}
dd.selection(Some(&offer));
}
}
let _ = self.display_handle.flush_clients();
}
}
impl GlobalDispatch<ZwpPrimarySelectionDeviceManagerV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<ZwpPrimarySelectionDeviceManagerV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZwpPrimarySelectionDeviceManagerV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionDeviceManagerV1,
request: <ZwpPrimarySelectionDeviceManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zwp_primary_selection_device_manager_v1::Request;
match request {
Request::CreateSource { id } => {
data_init.init(
id,
PrimarySourceData {
mime_types: std::sync::Mutex::new(Vec::new()),
},
);
}
Request::GetDevice { id, seat: _ } => {
let pd = data_init.init(id, ());
state.primary_devices.push(pd);
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<ZwpPrimarySelectionSourceV1, PrimarySourceData> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionSourceV1,
request: <ZwpPrimarySelectionSourceV1 as Resource>::Request,
data: &PrimarySourceData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use zwp_primary_selection_source_v1::Request;
match request {
Request::Offer { mime_type } => {
data.mime_types.lock().unwrap().push(mime_type);
}
Request::Destroy => {}
_ => {}
}
}
fn destroyed(
state: &mut Self,
_: wayland_server::backend::ClientId,
resource: &ZwpPrimarySelectionSourceV1,
_: &PrimarySourceData,
) {
if state
.primary_source
.as_ref()
.is_some_and(|s| s.id() == resource.id())
{
state.primary_source = None;
}
}
}
impl Dispatch<ZwpPrimarySelectionDeviceV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionDeviceV1,
request: <ZwpPrimarySelectionDeviceV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use zwp_primary_selection_device_v1::Request;
match request {
Request::SetSelection { source, serial: _ } => {
state.primary_source = source;
}
Request::Destroy => {}
_ => {}
}
}
fn destroyed(
state: &mut Self,
_: wayland_server::backend::ClientId,
resource: &ZwpPrimarySelectionDeviceV1,
_: &(),
) {
state.primary_devices.retain(|d| d.id() != resource.id());
}
}
impl Dispatch<ZwpPrimarySelectionOfferV1, PrimaryOfferData> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpPrimarySelectionOfferV1,
request: <ZwpPrimarySelectionOfferV1 as Resource>::Request,
data: &PrimaryOfferData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use zwp_primary_selection_offer_v1::Request;
match request {
Request::Receive { mime_type, fd } => {
if data.external {
if let Some(ref cb) = state.external_primary {
use std::io::Write;
let mut f = std::fs::File::from(fd);
let _ = f.write_all(&cb.data);
let _ = mime_type; }
} else if let Some(ref src) = state.primary_source {
src.send(mime_type, fd.as_fd());
}
}
Request::Destroy => {}
_ => {}
}
}
}
impl GlobalDispatch<ZwpPointerConstraintsV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<ZwpPointerConstraintsV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZwpPointerConstraintsV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &ZwpPointerConstraintsV1,
request: <ZwpPointerConstraintsV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zwp_pointer_constraints_v1::Request;
match request {
Request::LockPointer {
id,
surface: _,
pointer: _,
region: _,
lifetime: _,
} => {
let lp = data_init.init(id, ());
lp.locked();
}
Request::ConfinePointer {
id,
surface: _,
pointer: _,
region: _,
lifetime: _,
} => {
let cp = data_init.init(id, ());
cp.confined();
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<ZwpLockedPointerV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &ZwpLockedPointerV1,
_: <ZwpLockedPointerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl Dispatch<ZwpConfinedPointerV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &ZwpConfinedPointerV1,
_: <ZwpConfinedPointerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
}
}
impl GlobalDispatch<ZwpRelativePointerManagerV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<ZwpRelativePointerManagerV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZwpRelativePointerManagerV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpRelativePointerManagerV1,
request: <ZwpRelativePointerManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zwp_relative_pointer_manager_v1::Request;
match request {
Request::GetRelativePointer { id, pointer: _ } => {
let rp = data_init.init(id, ());
state.relative_pointers.push(rp);
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<ZwpRelativePointerV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &ZwpRelativePointerV1,
_: <ZwpRelativePointerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
state
.relative_pointers
.retain(|rp| rp.id() != resource.id());
}
}
impl GlobalDispatch<ZwpTextInputManagerV3, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<ZwpTextInputManagerV3>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<ZwpTextInputManagerV3, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &ZwpTextInputManagerV3,
request: <ZwpTextInputManagerV3 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use zwp_text_input_manager_v3::Request;
match request {
Request::GetTextInput { id, seat: _ } => {
let ti = data_init.init(id, ());
state.text_inputs.push(TextInputState {
resource: ti,
enabled: false,
});
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<ZwpTextInputV3, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
resource: &ZwpTextInputV3,
request: <ZwpTextInputV3 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use zwp_text_input_v3::Request;
match request {
Request::Enable => {
if let Some(ti) = state
.text_inputs
.iter_mut()
.find(|t| t.resource.id() == resource.id())
{
ti.enabled = true;
}
}
Request::Disable => {
if let Some(ti) = state
.text_inputs
.iter_mut()
.find(|t| t.resource.id() == resource.id())
{
ti.enabled = false;
}
}
Request::Commit => {
}
Request::Destroy => {
state
.text_inputs
.retain(|t| t.resource.id() != resource.id());
}
_ => {}
}
}
}
impl GlobalDispatch<XdgActivationV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<XdgActivationV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<XdgActivationV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &XdgActivationV1,
request: <XdgActivationV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use xdg_activation_v1::Request;
match request {
Request::GetActivationToken { id } => {
let serial = state.next_activation_token;
state.next_activation_token = serial.wrapping_add(1);
data_init.init(id, ActivationTokenData { serial });
}
Request::Activate {
token: _,
surface: _,
} => {
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<XdgActivationTokenV1, ActivationTokenData> for Compositor {
fn request(
_: &mut Self,
_: &Client,
resource: &XdgActivationTokenV1,
request: <XdgActivationTokenV1 as Resource>::Request,
data: &ActivationTokenData,
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use xdg_activation_token_v1::Request;
match request {
Request::Commit => {
resource.done(format!("blit-token-{}", data.serial));
}
Request::SetSerial { .. } | Request::SetAppId { .. } | Request::SetSurface { .. } => {}
Request::Destroy => {}
_ => {}
}
}
}
impl GlobalDispatch<WpCursorShapeManagerV1, ()> for Compositor {
fn bind(
_: &mut Self,
_: &DisplayHandle,
_: &Client,
resource: New<WpCursorShapeManagerV1>,
_: &(),
data_init: &mut DataInit<'_, Self>,
) {
data_init.init(resource, ());
}
}
impl Dispatch<WpCursorShapeManagerV1, ()> for Compositor {
fn request(
_: &mut Self,
_: &Client,
_: &WpCursorShapeManagerV1,
request: <WpCursorShapeManagerV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
data_init: &mut DataInit<'_, Self>,
) {
use wp_cursor_shape_manager_v1::Request;
match request {
Request::GetPointer {
cursor_shape_device,
pointer: _,
} => {
data_init.init(cursor_shape_device, ());
}
Request::GetTabletToolV2 {
cursor_shape_device,
tablet_tool: _,
} => {
data_init.init(cursor_shape_device, ());
}
Request::Destroy => {}
_ => {}
}
}
}
impl Dispatch<WpCursorShapeDeviceV1, ()> for Compositor {
fn request(
state: &mut Self,
_: &Client,
_: &WpCursorShapeDeviceV1,
request: <WpCursorShapeDeviceV1 as Resource>::Request,
_: &(),
_: &DisplayHandle,
_: &mut DataInit<'_, Self>,
) {
use wp_cursor_shape_device_v1::Request;
match request {
Request::SetShape { serial: _, shape } => {
use wayland_server::WEnum;
use wp_cursor_shape_device_v1::Shape;
let name = match shape {
WEnum::Value(Shape::Default) => "default",
WEnum::Value(Shape::ContextMenu) => "context-menu",
WEnum::Value(Shape::Help) => "help",
WEnum::Value(Shape::Pointer) => "pointer",
WEnum::Value(Shape::Progress) => "progress",
WEnum::Value(Shape::Wait) => "wait",
WEnum::Value(Shape::Cell) => "cell",
WEnum::Value(Shape::Crosshair) => "crosshair",
WEnum::Value(Shape::Text) => "text",
WEnum::Value(Shape::VerticalText) => "vertical-text",
WEnum::Value(Shape::Alias) => "alias",
WEnum::Value(Shape::Copy) => "copy",
WEnum::Value(Shape::Move) => "move",
WEnum::Value(Shape::NoDrop) => "no-drop",
WEnum::Value(Shape::NotAllowed) => "not-allowed",
WEnum::Value(Shape::Grab) => "grab",
WEnum::Value(Shape::Grabbing) => "grabbing",
WEnum::Value(Shape::EResize) => "e-resize",
WEnum::Value(Shape::NResize) => "n-resize",
WEnum::Value(Shape::NeResize) => "ne-resize",
WEnum::Value(Shape::NwResize) => "nw-resize",
WEnum::Value(Shape::SResize) => "s-resize",
WEnum::Value(Shape::SeResize) => "se-resize",
WEnum::Value(Shape::SwResize) => "sw-resize",
WEnum::Value(Shape::WResize) => "w-resize",
WEnum::Value(Shape::EwResize) => "ew-resize",
WEnum::Value(Shape::NsResize) => "ns-resize",
WEnum::Value(Shape::NeswResize) => "nesw-resize",
WEnum::Value(Shape::NwseResize) => "nwse-resize",
WEnum::Value(Shape::ColResize) => "col-resize",
WEnum::Value(Shape::RowResize) => "row-resize",
WEnum::Value(Shape::AllScroll) => "all-scroll",
WEnum::Value(Shape::ZoomIn) => "zoom-in",
WEnum::Value(Shape::ZoomOut) => "zoom-out",
_ => "default",
};
let _ = state.event_tx.send(CompositorEvent::SurfaceCursor {
surface_id: state.focused_surface_id,
cursor: CursorImage::Named(name.to_string()),
});
(state.event_notify)();
}
Request::Destroy => {}
_ => {}
}
}
}
impl wayland_server::backend::ClientData for ClientState {
fn initialized(&self, _: wayland_server::backend::ClientId) {}
fn disconnected(
&self,
_: wayland_server::backend::ClientId,
_: wayland_server::backend::DisconnectReason,
) {
}
}
pub struct CompositorHandle {
pub event_rx: mpsc::Receiver<CompositorEvent>,
pub command_tx: mpsc::Sender<CompositorCommand>,
pub socket_name: String,
pub thread: std::thread::JoinHandle<()>,
pub shutdown: Arc<AtomicBool>,
loop_signal: LoopSignal,
}
impl CompositorHandle {
pub fn wake(&self) {
self.loop_signal.wakeup();
}
}
pub fn spawn_compositor(
verbose: bool,
event_notify: Arc<dyn Fn() + Send + Sync>,
gpu_device: &str,
) -> CompositorHandle {
let _gpu_device = gpu_device.to_string();
let (event_tx, event_rx) = mpsc::channel();
let (command_tx, command_rx) = mpsc::channel();
let (socket_tx, socket_rx) = mpsc::sync_channel(1);
let (signal_tx, signal_rx) = mpsc::sync_channel::<LoopSignal>(1);
let shutdown = Arc::new(AtomicBool::new(false));
let shutdown_clone = shutdown.clone();
let runtime_dir = std::env::var_os("XDG_RUNTIME_DIR")
.map(std::path::PathBuf::from)
.filter(|p| {
let probe = p.join(".blit-probe");
if std::fs::write(&probe, b"").is_ok() {
let _ = std::fs::remove_file(&probe);
true
} else {
false
}
})
.unwrap_or_else(std::env::temp_dir);
let runtime_dir_clone = runtime_dir.clone();
let thread = std::thread::Builder::new()
.name("compositor".into())
.spawn(move || {
unsafe { std::env::set_var("XDG_RUNTIME_DIR", &runtime_dir_clone) };
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
run_compositor(
event_tx,
command_rx,
socket_tx,
signal_tx,
event_notify,
shutdown_clone,
verbose,
_gpu_device,
);
}));
if let Err(e) = result {
let msg = if let Some(s) = e.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = e.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic".to_string()
};
eprintln!("[compositor] PANIC: {msg}");
}
})
.expect("failed to spawn compositor thread");
let socket_name = socket_rx.recv().expect("compositor failed to start");
let socket_name = runtime_dir
.join(&socket_name)
.to_string_lossy()
.into_owned();
let loop_signal = signal_rx
.recv()
.expect("compositor failed to send loop signal");
CompositorHandle {
event_rx,
command_tx,
socket_name,
thread,
shutdown,
loop_signal,
}
}
#[allow(clippy::too_many_arguments)]
fn run_compositor(
event_tx: mpsc::Sender<CompositorEvent>,
command_rx: mpsc::Receiver<CompositorCommand>,
socket_tx: mpsc::SyncSender<String>,
signal_tx: mpsc::SyncSender<LoopSignal>,
event_notify: Arc<dyn Fn() + Send + Sync>,
shutdown: Arc<AtomicBool>,
verbose: bool,
gpu_device: String,
) {
let mut event_loop: EventLoop<Compositor> =
EventLoop::try_new().expect("failed to create event loop");
let loop_signal = event_loop.get_signal();
let display: Display<Compositor> = Display::new().expect("failed to create display");
let dh = display.handle();
dh.create_global::<Compositor, WlCompositor, ()>(6, ());
dh.create_global::<Compositor, WlSubcompositor, ()>(1, ());
dh.create_global::<Compositor, XdgWmBase, ()>(6, ());
dh.create_global::<Compositor, WlShm, ()>(1, ());
dh.create_global::<Compositor, WlOutput, ()>(4, ());
dh.create_global::<Compositor, WlSeat, ()>(9, ());
dh.create_global::<Compositor, ZwpLinuxDmabufV1, ()>(3, ());
dh.create_global::<Compositor, WpViewporter, ()>(1, ());
dh.create_global::<Compositor, WpFractionalScaleManagerV1, ()>(1, ());
dh.create_global::<Compositor, ZxdgDecorationManagerV1, ()>(1, ());
dh.create_global::<Compositor, WlDataDeviceManager, ()>(3, ());
dh.create_global::<Compositor, ZwpPointerConstraintsV1, ()>(1, ());
dh.create_global::<Compositor, ZwpRelativePointerManagerV1, ()>(1, ());
dh.create_global::<Compositor, XdgActivationV1, ()>(1, ());
dh.create_global::<Compositor, WpCursorShapeManagerV1, ()>(1, ());
dh.create_global::<Compositor, ZwpPrimarySelectionDeviceManagerV1, ()>(1, ());
dh.create_global::<Compositor, WpPresentation, ()>(1, ());
dh.create_global::<Compositor, ZwpTextInputManagerV3, ()>(1, ());
let keymap_string = include_str!("../data/us-qwerty.xkb");
let mut keymap_data = keymap_string.as_bytes().to_vec();
keymap_data.push(0);
let listening_socket = wayland_server::ListeningSocket::bind_auto("wayland", 0..33)
.unwrap_or_else(|e| {
let dir = std::env::var("XDG_RUNTIME_DIR").unwrap_or_else(|_| "(unset)".into());
panic!("failed to create wayland socket in XDG_RUNTIME_DIR={dir}: {e}\nhint: ensure the directory exists and is writable by the current user");
});
let socket_name = listening_socket
.socket_name()
.unwrap()
.to_string_lossy()
.into_owned();
socket_tx.send(socket_name).unwrap();
let _ = signal_tx.send(loop_signal.clone());
let mut compositor = Compositor {
display_handle: dh,
surfaces: HashMap::new(),
toplevel_surface_ids: HashMap::new(),
next_surface_id: 1,
shm_pools: HashMap::new(),
pixel_cache: HashMap::new(),
dmabuf_params: HashMap::new(),
vulkan_renderer: {
eprintln!("[compositor] trying Vulkan renderer for {gpu_device}");
let r = super::vulkan_render::VulkanRenderer::try_new(&gpu_device);
eprintln!("[compositor] Vulkan renderer: {}", r.is_some());
r
},
output_width: 1920,
output_height: 1080,
output_scale_120: 120,
outputs: Vec::new(),
keyboards: Vec::new(),
pointers: Vec::new(),
keyboard_keymap_data: keymap_data,
mods_depressed: 0,
mods_locked: 0,
serial: 0,
event_tx,
event_notify,
loop_signal: loop_signal.clone(),
pending_commits: HashMap::new(),
focused_surface_id: 0,
pointer_entered_id: None,
pending_kb_reenter: false,
verbose,
shutdown: shutdown.clone(),
last_reported_size: HashMap::new(),
surface_sizes: HashMap::new(),
positioners: HashMap::new(),
fractional_scales: Vec::new(),
data_devices: Vec::new(),
selection_source: None,
external_clipboard: None,
primary_devices: Vec::new(),
primary_source: None,
external_primary: None,
relative_pointers: Vec::new(),
text_inputs: Vec::new(),
text_input_serial: 0,
next_activation_token: 1,
};
let handle = event_loop.handle();
let display_source = Generic::new(display, Interest::READ, calloop::Mode::Level);
handle
.insert_source(display_source, |_, display, state| {
let d = unsafe { display.get_mut() };
if let Err(e) = d.dispatch_clients(state)
&& state.verbose
{
eprintln!("[compositor] dispatch_clients error: {e}");
}
state.cleanup_dead_surfaces();
if let Err(e) = d.flush_clients()
&& state.verbose
{
eprintln!("[compositor] flush_clients error: {e}");
}
Ok(PostAction::Continue)
})
.expect("failed to insert display source");
let socket_source = Generic::new(listening_socket, Interest::READ, calloop::Mode::Level);
handle
.insert_source(socket_source, |_, socket, state| {
let ls = unsafe { socket.get_mut() };
if let Some(client_stream) = ls.accept().ok().flatten()
&& let Err(e) = state
.display_handle
.insert_client(client_stream, Arc::new(ClientState))
&& state.verbose
{
eprintln!("[compositor] insert_client error: {e}");
}
Ok(PostAction::Continue)
})
.expect("failed to insert listening socket");
if verbose {
eprintln!("[compositor] entering event loop");
}
while !shutdown.load(Ordering::Relaxed) {
while let Ok(cmd) = command_rx.try_recv() {
match cmd {
CompositorCommand::Shutdown => {
shutdown.store(true, Ordering::Relaxed);
return;
}
other => compositor.handle_command(other),
}
}
if let Err(e) =
event_loop.dispatch(Some(std::time::Duration::from_secs(1)), &mut compositor)
&& verbose
{
eprintln!("[compositor] event loop error: {e}");
}
if !compositor.pending_commits.is_empty() {
compositor.flush_pending_commits();
}
if let Err(e) = compositor.display_handle.flush_clients()
&& verbose
{
eprintln!("[compositor] flush error: {e}");
}
}
if verbose {
eprintln!("[compositor] event loop exited");
}
}