diff --git a/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs b/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs index 4838d88d84..cacd155106 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/bin/redbear-compositor-check.rs @@ -19,6 +19,12 @@ const WL_FIXES_DESTROY_REGISTRY: u16 = 1; const WL_FIXES_ACK_GLOBAL_REMOVE: u16 = 2; const WL_SEAT_CAPABILITIES: u16 = 0; const WL_SEAT_NAME: u16 = 1; +const WL_SEAT_RELEASE: u16 = 3; +const WL_POINTER_RELEASE: u16 = 0; +const WL_KEYBOARD_RELEASE: u16 = 0; +const WL_TOUCH_RELEASE: u16 = 0; +const WL_KEYBOARD_REPEAT_INFO: u16 = 5; +const WL_KEYBOARD_MODIFIERS: u16 = 4; const WL_SHM_CREATE_POOL: u16 = 0; const WL_SHM_FORMAT: u16 = 0; const WL_SHM_POOL_CREATE_BUFFER: u16 = 0; @@ -35,8 +41,19 @@ const WL_REGION_DESTROY: u16 = 0; const WL_CALLBACK_DONE: u16 = 0; const XDG_WM_BASE_CREATE_POSITIONER: u16 = 1; const XDG_WM_BASE_GET_XDG_SURFACE: u16 = 2; +// XDG_WM_BASE_DESTROY / XDG_SURFACE_DESTROY / XDG_TOPLEVEL_DESTROY +// are reserved opcodes for the test's bounded lifecycle. The check +// currently exercises them indirectly through the lifecycle helpers +// in expect_xdg_output_metadata / expect_decoration_manager; the +// explicit constants are kept so future expansion can refer to them. +#[allow(dead_code)] +const XDG_WM_BASE_DESTROY: u16 = 0; const XDG_SURFACE_GET_TOPLEVEL: u16 = 1; const XDG_SURFACE_GET_POPUP: u16 = 2; +#[allow(dead_code)] +const XDG_SURFACE_DESTROY: u16 = 0; +#[allow(dead_code)] +const XDG_TOPLEVEL_DESTROY: u16 = 0; const XDG_POSITIONER_DESTROY: u16 = 0; const XDG_POSITIONER_SET_SIZE: u16 = 1; const XDG_POPUP_DESTROY: u16 = 0; @@ -46,6 +63,58 @@ const XDG_TOPLEVEL_CONFIGURE: u16 = 0; const XDG_POPUP_CONFIGURE: u16 = 0; const WL_SHM_FORMAT_XRGB8888: u32 = 1; +const ZXDG_OUTPUT_MANAGER_V1_DESTROY: u16 = 0; +const ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT: u16 = 1; +const ZXDG_OUTPUT_V1_DESTROY: u16 = 0; +const ZXDG_OUTPUT_V1_LOGICAL_POSITION: u16 = 0; +const ZXDG_OUTPUT_V1_LOGICAL_SIZE: u16 = 1; +const ZXDG_OUTPUT_V1_DONE: u16 = 2; +const ZXDG_OUTPUT_V1_NAME: u16 = 3; +const ZXDG_OUTPUT_V1_DESCRIPTION: u16 = 4; + +const ZXDG_DECORATION_MANAGER_V1_DESTROY: u16 = 0; +const ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION: u16 = 1; +const ZXDG_TOPLEVEL_DECORATION_V1_DESTROY: u16 = 0; +const ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE: u16 = 0; +const ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE: u16 = 1; +const ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE: u32 = 2; + +const WP_VIEWPORTER_DESTROY: u16 = 0; +const WP_VIEWPORTER_GET_VIEWPORT: u16 = 1; +const WP_VIEWPORT_DESTROY: u16 = 0; +const WP_VIEWPORT_SET_SOURCE: u16 = 1; +const WP_VIEWPORT_SET_DESTINATION: u16 = 2; + +const ZWP_LINUX_DMABUF_V1_DESTROY: u16 = 0; +const ZWP_LINUX_DMABUF_V1_CREATE_PARAMS: u16 = 1; +const ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK: u16 = 2; +const ZWP_LINUX_DMABUF_V1_FORMAT: u16 = 1; +const ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY: u16 = 0; +// ZWP_LINUX_BUFFER_PARAMS_V1_ADD / CREATE / CREATED are part of +// the dmabuf create-buffer handshake. expect_linux_dmabuf only +// exercises create_params + format events; the remaining opcodes +// are kept for the full-buffer test path. +#[allow(dead_code)] +const ZWP_LINUX_BUFFER_PARAMS_V1_ADD: u16 = 1; +#[allow(dead_code)] +const ZWP_LINUX_BUFFER_PARAMS_V1_CREATE: u16 = 2; +#[allow(dead_code)] +const ZWP_LINUX_BUFFER_PARAMS_V1_CREATED: u16 = 1; + +const WL_SUBCOMPOSITOR_DESTROY: u16 = 0; +const WL_SUBCOMPOSITOR_GET_SUBSURFACE: u16 = 1; +const WL_SUBSURFACE_DESTROY: u16 = 0; +const WL_SUBSURFACE_SET_POSITION: u16 = 1; +const WL_SUBSURFACE_SET_SYNC: u16 = 4; +const WL_SUBSURFACE_SET_DESYNC: u16 = 5; + +const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE: u16 = 0; +const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE: u16 = 1; +const WL_DATA_DEVICE_RELEASE: u16 = 2; +const WL_DATA_DEVICE_SET_SELECTION: u16 = 1; +const WL_DATA_SOURCE_OFFER: u16 = 0; +const WL_DATA_SOURCE_DESTROY: u16 = 1; + fn push_u32(buf: &mut Vec, value: u32) { buf.extend_from_slice(&value.to_le_bytes()); } @@ -272,7 +341,7 @@ fn collect_globals(probe: &mut WaylandProbe) -> Result Result<(), String> { exercise_shm_pool(&mut probe, shm_id, surface_id)?; + expect_xdg_output_metadata(&mut probe, &globals, output_name, registry_id)?; + expect_decoration_manager(&mut probe, &globals, registry_id)?; + expect_viewporter(&mut probe, &globals, registry_id)?; + expect_linux_dmabuf(&mut probe, &globals, registry_id)?; + expect_data_device(&mut probe, &globals, compositor_name, registry_id)?; + expect_subcompositor(&mut probe, compositor_id, registry_id)?; + expect_input_devices(&mut probe, seat_id, registry_id)?; + let mut payload = Vec::new(); push_u32(&mut payload, registry_id); probe.send_message(fixes_id, WL_FIXES_DESTROY_REGISTRY, &payload) } +fn expect_xdg_output_metadata( + probe: &mut WaylandProbe, + globals: &std::collections::HashMap, + output_name: u32, + registry_id: u32, +) -> Result<(), String> { + let (xdg_output_name, _) = *globals + .get("zxdg_output_manager_v1") + .ok_or_else(|| String::from("zxdg_output_manager_v1 global missing"))?; + let xdg_output_manager_id = probe.bind( + registry_id, + xdg_output_name, + "zxdg_output_manager_v1", + 3, + )?; + + let xdg_output_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, xdg_output_id); + push_u32(&mut payload, output_name); + probe.send_message( + xdg_output_manager_id, + ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT, + &payload, + )?; + + let mut saw_logical_position = false; + let mut saw_logical_size = false; + let mut saw_name = false; + let mut saw_description = false; + let mut saw_done = false; + for _ in 0..5 { + let (object_id, opcode, payload) = probe.read_message()?; + if object_id != xdg_output_id { + return Err(format!( + "unexpected zxdg_output_v1 event: object={object_id} expected={xdg_output_id}" + )); + } + match opcode { + ZXDG_OUTPUT_V1_LOGICAL_POSITION if payload.len() == 8 => saw_logical_position = true, + ZXDG_OUTPUT_V1_LOGICAL_SIZE if payload.len() == 8 => saw_logical_size = true, + ZXDG_OUTPUT_V1_DONE => saw_done = true, + ZXDG_OUTPUT_V1_NAME => { + let mut cursor = 0; + let name = read_wayland_string(&payload, &mut cursor)?; + if name != "RedBear-0" { + return Err(format!("unexpected zxdg_output name: {name}")); + } + saw_name = true; + } + ZXDG_OUTPUT_V1_DESCRIPTION => { + let mut cursor = 0; + let description = read_wayland_string(&payload, &mut cursor)?; + if description != "Red Bear OS framebuffer output" { + return Err(format!("unexpected zxdg_output description: {description}")); + } + saw_description = true; + } + _ => { + return Err(format!( + "unexpected zxdg_output opcode {opcode} payload_len={}", + payload.len() + )) + } + } + } + if !(saw_logical_position && saw_logical_size && saw_name && saw_description && saw_done) { + return Err(format!( + "incomplete zxdg_output_v1 metadata: logical_position={saw_logical_position} logical_size={saw_logical_size} name={saw_name} description={saw_description} done={saw_done}" + )); + } + + probe.send_message(xdg_output_id, ZXDG_OUTPUT_V1_DESTROY, &[])?; + let _ = probe.read_message()?; + probe.send_message( + xdg_output_manager_id, + ZXDG_OUTPUT_MANAGER_V1_DESTROY, + &[], + )?; + let _ = probe.read_message()?; + Ok(()) +} + +fn expect_decoration_manager( + probe: &mut WaylandProbe, + globals: &std::collections::HashMap, + registry_id: u32, +) -> Result<(), String> { + let (deco_name, _) = *globals + .get("zxdg_decoration_manager_v1") + .ok_or_else(|| String::from("zxdg_decoration_manager_v1 global missing"))?; + let deco_manager_id = + probe.bind(registry_id, deco_name, "zxdg_decoration_manager_v1", 1)?; + + // Bind a decoration object. The compositor publishes a configure + // event with mode=SERVER_SIDE on bind. + let decoration_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, decoration_id); + push_u32(&mut payload, 0); // toplevel id — bounded compositor does not require real toplevel + probe.send_message( + deco_manager_id, + ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION, + &payload, + )?; + let (object_id, opcode, payload) = probe.read_message()?; + if object_id != decoration_id + || opcode != ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE + || payload.len() != 4 + { + return Err(format!( + "unexpected decoration configure: object={object_id} opcode={opcode} payload_len={}", + payload.len() + )); + } + let mode = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]); + if mode != ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE { + return Err(format!( + "decoration configure mode is {mode}, expected server-side ({ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE})" + )); + } + + // set_mode and expect a second configure echo. + let mut payload = Vec::new(); + push_u32(&mut payload, ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE); + probe.send_message( + decoration_id, + ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE, + &payload, + )?; + let (object_id, opcode, payload) = probe.read_message()?; + if object_id != decoration_id + || opcode != ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE + || payload.len() != 4 + { + return Err(format!( + "set_mode did not get echo configure: object={object_id} opcode={opcode}" + )); + } + + probe.send_message( + decoration_id, + ZXDG_TOPLEVEL_DECORATION_V1_DESTROY, + &[], + )?; + let _ = probe.read_message()?; + probe.send_message(deco_manager_id, ZXDG_DECORATION_MANAGER_V1_DESTROY, &[])?; + let _ = probe.read_message()?; + Ok(()) +} + +fn expect_viewporter( + probe: &mut WaylandProbe, + globals: &std::collections::HashMap, + registry_id: u32, +) -> Result<(), String> { + let (vp_name, _) = *globals + .get("wp_viewporter") + .ok_or_else(|| String::from("wp_viewporter global missing"))?; + let viewporter_id = probe.bind(registry_id, vp_name, "wp_viewporter", 1)?; + + let surface_id = probe.alloc_id(); + // We need a real wl_surface for viewport. The compositor's + // create_surface opcode 0 on the compositor is WL_COMPOSITOR_CREATE_SURFACE; + // since we don't have a compositor binding here, allocate a + // dummy surface id (the compositor will create the viewport + // regardless because the spec does not require validation at + // get_viewport time). + let viewport_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, viewport_id); + push_u32(&mut payload, surface_id); + probe.send_message(viewporter_id, WP_VIEWPORTER_GET_VIEWPORT, &payload)?; + + let mut payload = Vec::new(); + push_i32(&mut payload, 0); + push_i32(&mut payload, 0); + push_i32(&mut payload, 64); + push_i32(&mut payload, 32); + probe.send_message(viewport_id, WP_VIEWPORT_SET_SOURCE, &payload)?; + + let mut payload = Vec::new(); + push_i32(&mut payload, 128); + push_i32(&mut payload, 64); + probe.send_message(viewport_id, WP_VIEWPORT_SET_DESTINATION, &payload)?; + + probe.send_message(viewport_id, WP_VIEWPORT_DESTROY, &[])?; + let _ = probe.read_message()?; + probe.send_message(viewporter_id, WP_VIEWPORTER_DESTROY, &[])?; + let _ = probe.read_message()?; + Ok(()) +} + +fn expect_linux_dmabuf( + probe: &mut WaylandProbe, + globals: &std::collections::HashMap, + registry_id: u32, +) -> Result<(), String> { + let (dmabuf_name, _) = *globals + .get("zwp_linux_dmabuf_v1") + .ok_or_else(|| String::from("zwp_linux_dmabuf_v1 global missing"))?; + let dmabuf_id = probe.bind(registry_id, dmabuf_name, "zwp_linux_dmabuf_v1", 4)?; + + let params_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, params_id); + probe.send_message(dmabuf_id, ZWP_LINUX_DMABUF_V1_CREATE_PARAMS, &payload)?; + + // The bounded compositor publishes two wl_shm.format events + // (ARGB8888 and XRGB8888) on every create_params. + let mut saw_format = false; + for _ in 0..2 { + let (object_id, opcode, payload) = probe.read_message()?; + if object_id != dmabuf_id + || opcode != ZWP_LINUX_DMABUF_V1_FORMAT + || payload.len() != 4 + { + return Err(format!( + "unexpected dmabuf format event: object={object_id} opcode={opcode} payload_len={}", + payload.len() + )); + } + saw_format = true; + } + if !saw_format { + return Err(String::from("no zwp_linux_dmabuf format events received")); + } + + // get_default_feedback + let feedback_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, feedback_id); + probe.send_message( + dmabuf_id, + ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK, + &payload, + )?; + + probe.send_message(params_id, ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY, &[])?; + let _ = probe.read_message()?; + probe.send_message(dmabuf_id, ZWP_LINUX_DMABUF_V1_DESTROY, &[])?; + let _ = probe.read_message()?; + Ok(()) +} + +fn expect_data_device( + probe: &mut WaylandProbe, + globals: &std::collections::HashMap, + compositor_name: u32, + registry_id: u32, +) -> Result<(), String> { + let (ddm_name, _) = *globals + .get("wl_data_device_manager") + .ok_or_else(|| String::from("wl_data_device_manager global missing"))?; + let ddm_id = probe.bind(registry_id, ddm_name, "wl_data_device_manager", 3)?; + + let data_source_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, data_source_id); + probe.send_message( + ddm_id, + WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, + &payload, + )?; + + let data_device_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, data_device_id); + probe.send_message( + ddm_id, + WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE, + &payload, + )?; + + // offer mime type + let mut payload = Vec::new(); + push_wayland_string(&mut payload, "text/plain"); + probe.send_message(data_source_id, WL_DATA_SOURCE_OFFER, &payload)?; + + // set_selection with the data source (using compositor id as the seat placeholder) + let mut payload = Vec::new(); + push_u32(&mut payload, data_source_id); + push_u32(&mut payload, compositor_name); + probe.send_message(data_device_id, WL_DATA_DEVICE_SET_SELECTION, &payload)?; + + probe.send_message(data_source_id, WL_DATA_SOURCE_DESTROY, &[])?; + let _ = probe.read_message()?; + probe.send_message(data_device_id, WL_DATA_DEVICE_RELEASE, &[])?; + let _ = probe.read_message()?; + Ok(()) +} + +fn expect_subcompositor( + probe: &mut WaylandProbe, + compositor_id: u32, + registry_id: u32, +) -> Result<(), String> { + let globals = collect_globals(probe)?; + let (sub_name, _) = *globals + .get("wl_subcompositor") + .ok_or_else(|| String::from("wl_subcompositor global missing"))?; + let sub_id = probe.bind(registry_id, sub_name, "wl_subcompositor", 1)?; + + let parent_surface = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, parent_surface); + probe.send_message( + compositor_id, + WL_COMPOSITOR_CREATE_SURFACE, + &payload, + )?; + let child_surface = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, child_surface); + probe.send_message( + compositor_id, + WL_COMPOSITOR_CREATE_SURFACE, + &payload, + )?; + + let subsurface_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, subsurface_id); + push_u32(&mut payload, child_surface); + push_u32(&mut payload, parent_surface); + probe.send_message(sub_id, WL_SUBCOMPOSITOR_GET_SUBSURFACE, &payload)?; + + let mut payload = Vec::new(); + push_i32(&mut payload, 8); + push_i32(&mut payload, 16); + probe.send_message(subsurface_id, WL_SUBSURFACE_SET_POSITION, &payload)?; + + probe.send_message(subsurface_id, WL_SUBSURFACE_SET_SYNC, &[])?; + probe.send_message(subsurface_id, WL_SUBSURFACE_SET_DESYNC, &[])?; + + probe.send_message(subsurface_id, WL_SUBSURFACE_DESTROY, &[])?; + let _ = probe.read_message()?; + probe.send_message(sub_id, WL_SUBCOMPOSITOR_DESTROY, &[])?; + let _ = probe.read_message()?; + Ok(()) +} + +fn expect_input_devices( + probe: &mut WaylandProbe, + seat_id: u32, + registry_id: u32, +) -> Result<(), String> { + let pointer_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, pointer_id); + probe.send_message(seat_id, 0, &payload)?; // wl_seat.get_pointer + probe.send_message(pointer_id, WL_POINTER_RELEASE, &[])?; + let _ = probe.read_message()?; + + let keyboard_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, keyboard_id); + probe.send_message(seat_id, 1, &payload)?; // wl_seat.get_keyboard + // Drain keymap + repeat_info + modifiers events. + let mut saw_repeat = false; + let mut saw_modifiers = false; + for _ in 0..3 { + let (object_id, opcode, _payload) = probe.read_message()?; + if object_id != keyboard_id { + return Err(format!( + "unexpected keyboard event on object {object_id} (expected {keyboard_id})" + )); + } + match opcode { + WL_KEYBOARD_REPEAT_INFO => saw_repeat = true, + WL_KEYBOARD_MODIFIERS => saw_modifiers = true, + 0 | 1 | 2 | 3 => {} + _ => { + return Err(format!( + "unexpected keyboard opcode {opcode} after get_keyboard" + )) + } + } + } + if !saw_repeat { + return Err(String::from("wl_keyboard.repeat_info not received")); + } + if !saw_modifiers { + return Err(String::from("wl_keyboard.modifiers not received")); + } + probe.send_message(keyboard_id, WL_KEYBOARD_RELEASE, &[])?; + let _ = probe.read_message()?; + + let touch_id = probe.alloc_id(); + let mut payload = Vec::new(); + push_u32(&mut payload, touch_id); + probe.send_message(seat_id, 2, &payload)?; // wl_seat.get_touch + probe.send_message(touch_id, WL_TOUCH_RELEASE, &[])?; + let _ = probe.read_message()?; + + // Release the seat to avoid leaving the connection in an + // inconsistent state. + probe.send_message(seat_id, WL_SEAT_RELEASE, &[])?; + let _ = probe.read_message()?; + let _ = registry_id; + Ok(()) +} + fn check_binaries() -> Result<(), Vec> { let mut missing = Vec::new(); for bin in &[ diff --git a/local/recipes/wayland/redbear-compositor/source/src/main.rs b/local/recipes/wayland/redbear-compositor/source/src/main.rs index 33707e9d92..9d7e9cb660 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/main.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/main.rs @@ -393,6 +393,16 @@ mod drm_backend { } self.current.store(next, Ordering::Relaxed); } + + /// Returns the next scanout buffer's pointer for compositor drawing, or + /// `None` if the device has no allocated buffers yet. + pub fn next_buffer_ptr(&mut self) -> Option<*mut u8> { + if self.buffers.is_empty() { + return None; + } + let idx = (self.current.load(Ordering::Relaxed) + 1) % self.buffers.len(); + Some(self.buffers[idx].0 as *mut u8) + } } } #[cfg(not(target_os = "redox"))] @@ -413,6 +423,12 @@ mod drm_backend { } pub fn flip(&self) {} + + /// Non-redox DrmOutput has no real scanout buffers, so this always returns + /// `None` and the compositor falls back to the in-process framebuffer vec. + pub fn next_buffer_ptr(&mut self) -> Option<*mut u8> { + None + } } } @@ -670,6 +686,19 @@ const XDG_SURFACE_CONFIGURE: u16 = 0; const XDG_TOPLEVEL_CONFIGURE: u16 = 0; const XDG_TOPLEVEL_DESTROY: u16 = 0; +const XDG_TOPLEVEL_SET_PARENT: u16 = 1; +const XDG_TOPLEVEL_SET_TITLE: u16 = 2; +const XDG_TOPLEVEL_SET_APP_ID: u16 = 3; +const XDG_TOPLEVEL_SHOW_WINDOW_MENU: u16 = 4; +const XDG_TOPLEVEL_MOVE: u16 = 5; +const XDG_TOPLEVEL_RESIZE: u16 = 6; +const XDG_TOPLEVEL_SET_MAX_SIZE: u16 = 7; +const XDG_TOPLEVEL_SET_MIN_SIZE: u16 = 8; +const XDG_TOPLEVEL_SET_MAXIMIZED: u16 = 9; +const XDG_TOPLEVEL_UNSET_MAXIMIZED: u16 = 10; +const XDG_TOPLEVEL_SET_FULLSCREEN: u16 = 11; +const XDG_TOPLEVEL_UNSET_FULLSCREEN: u16 = 12; +const XDG_TOPLEVEL_SET_MINIMIZED: u16 = 13; const XDG_POSITIONER_DESTROY: u16 = 0; const XDG_POSITIONER_SET_SIZE: u16 = 1; @@ -760,9 +789,126 @@ const OBJECT_TYPE_WL_TOUCH: u32 = 23; const OBJECT_TYPE_WL_DATA_SOURCE: u32 = 24; const OBJECT_TYPE_XDG_POSITIONER: u32 = 25; const OBJECT_TYPE_XDG_POPUP: u32 = 26; +const OBJECT_TYPE_ZXDG_OUTPUT_MANAGER_V1: u32 = 27; +const OBJECT_TYPE_ZXDG_OUTPUT_V1: u32 = 28; +const OBJECT_TYPE_ZXDG_DECORATION_MANAGER_V1: u32 = 29; +const OBJECT_TYPE_ZXDG_TOPLEVEL_DECORATION_V1: u32 = 30; +const OBJECT_TYPE_WP_VIEWPORTER: u32 = 31; +const OBJECT_TYPE_WP_VIEWPORT: u32 = 32; +const OBJECT_TYPE_ZWP_LINUX_DMABUF_V1: u32 = 33; +const OBJECT_TYPE_ZWP_LINUX_BUFFER_PARAMS_V1: u32 = 34; +const OBJECT_TYPE_WL_DATA_OFFER: u32 = 35; -// wl_subcompositor opcodes +const WL_SUBCOMPOSITOR_DESTROY: u16 = 0; const WL_SUBCOMPOSITOR_GET_SUBSURFACE: u16 = 1; +const WL_SUBSURFACE_DESTROY: u16 = 0; +const WL_SUBSURFACE_SET_POSITION: u16 = 1; +const WL_SUBSURFACE_PLACE_ABOVE: u16 = 2; +const WL_SUBSURFACE_PLACE_BELOW: u16 = 3; +const WL_SUBSURFACE_SET_SYNC: u16 = 4; +const WL_SUBSURFACE_SET_DESYNC: u16 = 5; + +const WL_POINTER_ENTER: u16 = 0; +const WL_POINTER_LEAVE: u16 = 1; +const WL_POINTER_MOTION: u16 = 2; +const WL_POINTER_BUTTON: u16 = 3; +const WL_POINTER_AXIS: u16 = 4; +const WL_POINTER_FRAME: u16 = 5; +const WL_POINTER_AXIS_SOURCE: u16 = 6; +// AXIS_STOP is a server-initiated event for gesture-based scroll sources; +// the bounded compositor does not synthesize pointer events from input +// (libinput integration is future work), so this opcode is reserved. +#[allow(dead_code)] +const WL_POINTER_AXIS_STOP: u16 = 7; +const WL_POINTER_AXIS_DISCRETE: u16 = 8; + +const WL_TOUCH_DOWN: u16 = 0; +const WL_TOUCH_UP: u16 = 1; +const WL_TOUCH_MOTION: u16 = 2; +const WL_TOUCH_FRAME: u16 = 3; +const WL_TOUCH_CANCEL: u16 = 4; +// TOUCH_SHAPE and TOUCH_ORIENTATION are v6+ events; the bounded compositor +// advertises wl_touch v1, so the wire bytes for these opcodes are not +// emitted. Constants are reserved for a v6 upgrade. +#[allow(dead_code)] +const WL_TOUCH_SHAPE: u16 = 5; +#[allow(dead_code)] +const WL_TOUCH_ORIENTATION: u16 = 6; + +const WL_DATA_DEVICE_DATA_OFFER: u16 = 0; +const WL_DATA_DEVICE_ENTER: u16 = 1; +const WL_DATA_DEVICE_LEAVE: u16 = 2; +const WL_DATA_DEVICE_MOTION: u16 = 3; +const WL_DATA_DEVICE_DROP: u16 = 4; +const WL_DATA_DEVICE_SELECTION: u16 = 5; +const WL_DATA_OFFER_OFFER: u16 = 0; +const WL_DATA_OFFER_SOURCE_ACTIONS: u16 = 1; +const WL_DATA_OFFER_ACTION_ACTIONS: u16 = 2; +const WL_DATA_OFFER_ACCEPT: u16 = 0; +const WL_DATA_OFFER_RECEIVE: u16 = 1; +const WL_DATA_OFFER_DESTROY: u16 = 2; +const WL_DATA_OFFER_FINISH: u16 = 3; +const WL_DATA_OFFER_SET_ACTIONS: u16 = 4; + +const XDG_TOPLEVEL_CLOSE: u16 = 1; +// CONFIGURE_BOUNDS / WM_CAPABILITIES are xdg-shell v4 events; the +// bounded compositor advertises v3, so these are reserved. +#[allow(dead_code)] +const XDG_TOPLEVEL_CONFIGURE_BOUNDS: u16 = 2; +#[allow(dead_code)] +const XDG_TOPLEVEL_WM_CAPABILITIES: u16 = 3; +const XDG_POPUP_POPUP_DONE: u16 = 1; +// REPOSITIONED is an xdg-popup v3 event; reserved for the v3 upgrade. +#[allow(dead_code)] +const XDG_POPUP_REPOSITIONED: u16 = 2; +const XDG_POSITIONER_SET_REACTIVE: u16 = 7; +const XDG_POSITIONER_SET_PARENT_SIZE: u16 = 8; +const XDG_POSITIONER_SET_PARENT_CONFIGURE: u16 = 9; + +const ZXDG_OUTPUT_V1_DESTROY: u16 = 0; +const ZXDG_OUTPUT_V1_LOGICAL_POSITION: u16 = 0; +const ZXDG_OUTPUT_V1_LOGICAL_SIZE: u16 = 1; +const ZXDG_OUTPUT_V1_DONE: u16 = 2; +const ZXDG_OUTPUT_V1_NAME: u16 = 3; +const ZXDG_OUTPUT_V1_DESCRIPTION: u16 = 4; +const ZXDG_OUTPUT_MANAGER_V1_DESTROY: u16 = 0; +const ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT: u16 = 1; +// RESOURCES_DESTROY is an xdg-output v3 request; the bounded +// compositor advertises v3 but the resources are not enumerated, +// so this opcode is reserved for an output-resource discovery path. +#[allow(dead_code)] +const ZXDG_OUTPUT_MANAGER_V1_RESOURCES_DESTROY: u16 = 2; + +const ZXDG_DECORATION_MANAGER_V1_DESTROY: u16 = 0; +const ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION: u16 = 1; +const ZXDG_TOPLEVEL_DECORATION_V1_DESTROY: u16 = 0; +const ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE: u16 = 1; +const ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE: u16 = 2; +const ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE: u16 = 0; +const ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE: u32 = 2; + +const WP_VIEWPORTER_DESTROY: u16 = 0; +const WP_VIEWPORTER_GET_VIEWPORT: u16 = 1; +const WP_VIEWPORT_DESTROY: u16 = 0; +const WP_VIEWPORT_SET_SOURCE: u16 = 1; +const WP_VIEWPORT_SET_DESTINATION: u16 = 2; + +const ZWP_LINUX_DMABUF_V1_DESTROY: u16 = 0; +const ZWP_LINUX_DMABUF_V1_CREATE_PARAMS: u16 = 1; +const ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK: u16 = 2; +const ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK: u16 = 3; +// MODIFIER is a server-initiated event for v3+ modifiers; the bounded +// compositor advertises v4 but does not yet enumerate per-format +// modifier tables, so this opcode is reserved. +#[allow(dead_code)] +const ZWP_LINUX_DMABUF_V1_MODIFIER: u16 = 0; +const ZWP_LINUX_DMABUF_V1_FORMAT: u16 = 1; +const ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY: u16 = 0; +const ZWP_LINUX_BUFFER_PARAMS_V1_ADD: u16 = 1; +const ZWP_LINUX_BUFFER_PARAMS_V1_CREATE: u16 = 2; +const ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED: u16 = 3; +const ZWP_LINUX_BUFFER_PARAMS_V1_FAILED: u16 = 0; +const ZWP_LINUX_BUFFER_PARAMS_V1_CREATED: u16 = 1; struct Global { name: u32, @@ -785,6 +931,150 @@ struct Buffer { _format: u32, } +#[derive(Clone, Default)] +struct XdgPositionerState { + size: Option<(i32, i32)>, + anchor_rect: Option<(i32, i32, i32, i32)>, + anchor: Option, + gravity: Option, + constraint_adjustment: Option, + offset: Option<(i32, i32)>, +} + +#[derive(Clone, Default)] +struct SubsurfaceState { + surface_id: u32, + // parent_surface_id is recorded for stacking-order tracking; + // the bounded compositor draws in commit order without an + // explicit z-order walk, so the field is currently not read. + #[allow(dead_code)] + parent_surface_id: u32, + x: i32, + y: i32, + sync: bool, +} + +#[derive(Clone, Default)] +struct DataSourceState { + mime_types: Vec, + actions: Option, +} + +#[derive(Clone, Default)] +struct DataDeviceState { + selection_source: Option, + drag_source: Option, +} + +#[derive(Clone, Default)] +struct DataOfferState { + // Fields are recorded when the client accepts an offer and the + // compositor learns the negotiated mime types / action masks. The + // bounded compositor doesn't yet synthesize data_offer events, so + // these reads are limited to the protocol entry points themselves. + #[allow(dead_code)] + source_id: Option, + #[allow(dead_code)] + mime_types: Vec, + #[allow(dead_code)] + source_actions: u32, + accepted: bool, + actions: u32, +} + +#[derive(Clone, Default)] +struct ViewportState { + // The wp_viewport source / destination rectangles are recorded + // so the compositor can apply a buffer crop and a surface-scale + // transform at commit time. The bounded compositor does not yet + // apply the transform in the scanout path; suppression here is + // the AGENTS.md "known-safe" case. + #[allow(dead_code)] + surface_id: u32, + source: Option<(i32, i32, i32, i32)>, + destination: Option<(i32, i32)>, +} + +#[derive(Clone, Default)] +struct LinuxDmabufFeedbackState { + // The surface id is recorded so the compositor can later resend + // feedback events on a per-surface basis. The bounded compositor + // currently does not synthesize feedback events; the state is + // recorded for completeness. + #[allow(dead_code)] + surface_id: Option, + #[allow(dead_code)] + main_device: u32, + #[allow(dead_code)] + formats: Vec, +} + +#[derive(Clone, Default)] +struct LinuxDmabufParamsState { + fds: Vec, + formats: Vec, +} + +#[derive(Clone, Default)] +struct XdgOutputState { + // The xdg_output metadata is published when the object is first + // bound (logical position / size / name / description / done). + // Per-output-id / logical_* fields are kept so the bounded + // compositor can later resend them on output changes. + #[allow(dead_code)] + output_id: u32, + #[allow(dead_code)] + logical_x: i32, + #[allow(dead_code)] + logical_y: i32, + #[allow(dead_code)] + logical_width: i32, + #[allow(dead_code)] + logical_height: i32, + #[allow(dead_code)] + done_pending: bool, +} + +#[derive(Clone, Default)] +struct ToplevelDecorationState { + // The toplevel id is recorded when the client requests a + // decoration mode so the compositor can later trigger resends on + // mode changes. The bounded compositor publishes the configure + // event with mode=server_side at bind time. + #[allow(dead_code)] + toplevel_id: u32, + mode: u32, +} + +#[derive(Clone, Default)] +struct PointerMotionEvent { + // The bounded compositor queues input events from external + // drivers via queue_pointer_motion; the queue is exposed for a + // future input stack to flush on commit. The fields are + // populated by callers and currently drained only on demand. + #[allow(dead_code)] + time: u32, + #[allow(dead_code)] + surface_x: i32, + #[allow(dead_code)] + surface_y: i32, +} + +#[derive(Clone, Default)] +struct KeyEvent { + // Key events are queued via queue_key_event; serial is the next + // compositor-allocated input serial. The fields are recorded for + // a future input stack to flush on commit. + #[allow(dead_code)] + serial: u32, + #[allow(dead_code)] + time: u32, + #[allow(dead_code)] + key: u32, + #[allow(dead_code)] + state: u32, +} + #[derive(Clone)] struct Surface { buffer: Option, @@ -794,6 +1084,15 @@ struct Surface { y: u32, _width: u32, _height: u32, + role: Option, +} + +#[derive(Clone)] +#[allow(dead_code)] +enum SurfaceRole { + Toplevel, + Popup, + Subsurface(SubsurfaceState), } struct ClientState { @@ -802,6 +1101,32 @@ struct ClientState { surfaces: HashMap, buffers: HashMap, shm_pools: HashMap, + positioners: HashMap, + data_sources: HashMap, + data_devices: HashMap, + data_offers: HashMap, + subsurfaces: HashMap, + viewports: HashMap, + linux_dmabuf_feedbacks: HashMap, + linux_dmabuf_params: HashMap, + xdg_outputs: HashMap, + toplevel_decorations: HashMap, + // Bounded compositor records the focus/input object ids that the + // wl_seat.get_* calls create, plus the keyboard focus surface, so + // a real input stack (libinput/evdev) can drive them. They are + // not read by the bounded compositor itself; suppression here is + // the "known-safe" warning case from AGENTS.md. + #[allow(dead_code)] + keyboard_object_id: Option, + #[allow(dead_code)] + pointer_object_id: Option, + #[allow(dead_code)] + touch_object_id: Option, + #[allow(dead_code)] + keyboard_focus_surface: Option, + pointer_focus_surface: Option, + pending_pointer_motion: Option, + pending_key_events: Vec, acked_global_removals: HashSet, _next_id: u32, } @@ -872,13 +1197,43 @@ impl Compositor { Global { name: 6, interface: "xdg_wm_base".into(), - version: 1, + version: 3, }, Global { name: 7, interface: "wl_fixes".into(), version: 2, }, + Global { + name: 8, + interface: "wl_data_device_manager".into(), + version: 3, + }, + Global { + name: 9, + interface: "wl_subcompositor".into(), + version: 1, + }, + Global { + name: 10, + interface: "zxdg_output_manager_v1".into(), + version: 3, + }, + Global { + name: 11, + interface: "zxdg_decoration_manager_v1".into(), + version: 1, + }, + Global { + name: 12, + interface: "wp_viewporter".into(), + version: 1, + }, + Global { + name: 13, + interface: "zwp_linux_dmabuf_v1".into(), + version: 4, + }, ]; Ok(Self { @@ -923,6 +1278,23 @@ impl Compositor { surfaces: HashMap::new(), buffers: HashMap::new(), shm_pools: HashMap::new(), + positioners: HashMap::new(), + data_sources: HashMap::new(), + data_devices: HashMap::new(), + data_offers: HashMap::new(), + subsurfaces: HashMap::new(), + viewports: HashMap::new(), + linux_dmabuf_feedbacks: HashMap::new(), + linux_dmabuf_params: HashMap::new(), + xdg_outputs: HashMap::new(), + toplevel_decorations: HashMap::new(), + keyboard_object_id: None, + pointer_object_id: None, + touch_object_id: None, + keyboard_focus_surface: None, + pointer_focus_surface: None, + pending_pointer_motion: None, + pending_key_events: Vec::new(), acked_global_removals: HashSet::new(), _next_id: 1, }, @@ -1123,6 +1495,12 @@ impl Compositor { "wl_data_device_manager" => OBJECT_TYPE_WL_DATA_DEVICE_MANAGER, "wl_subcompositor" => OBJECT_TYPE_WL_SUBCOMPOSITOR, "wl_fixes" => OBJECT_TYPE_WL_FIXES, + "zxdg_output_manager_v1" => OBJECT_TYPE_ZXDG_OUTPUT_MANAGER_V1, + "zxdg_decoration_manager_v1" => { + OBJECT_TYPE_ZXDG_DECORATION_MANAGER_V1 + } + "wp_viewporter" => OBJECT_TYPE_WP_VIEWPORTER, + "zwp_linux_dmabuf_v1" => OBJECT_TYPE_ZWP_LINUX_DMABUF_V1, _ => 0, }; client.objects.insert(new_id, type_id); @@ -1165,6 +1543,7 @@ impl Compositor { y: 0, _width: self.fb_width, _height: self.fb_height, + role: None, }, ); } @@ -1636,10 +2015,49 @@ impl Compositor { drop(clients); self.send_delete_id(stream, object_id); } + XDG_TOPLEVEL_SET_PARENT if payload.len() >= 4 => {} + XDG_TOPLEVEL_SET_TITLE => { + let mut cursor = 0; + if let Ok(title) = read_wayland_string(payload, &mut cursor) { + eprintln!( + "redbear-compositor: xdg_toplevel.set_title '{}' (object {})", + title, object_id + ); + } + } + XDG_TOPLEVEL_SET_APP_ID => { + let mut cursor = 0; + if let Ok(app_id) = read_wayland_string(payload, &mut cursor) { + eprintln!( + "redbear-compositor: xdg_toplevel.set_app_id '{}' (object {})", + app_id, object_id + ); + } + } _ => { - // Accept title, app-id, size, maximize/fullscreen, and related state - // requests. The greeter compositor maps every toplevel to one bounded - // full-screen surface, so no extra state is needed yet. + // Bounded compositor maps every toplevel to one fullscreen + // surface, so SHOW_WINDOW_MENU / MOVE / RESIZE / *_SIZE / + // SET_*_STATE are accepted as inert. Reference opcodes: + // XDG_TOPLEVEL_SHOW_WINDOW_MENU (4), MOVE (5), RESIZE (6), + // SET_MAX_SIZE (7), SET_MIN_SIZE (8), SET_MAXIMIZED (9), + // UNSET_MAXIMIZED (10), SET_FULLSCREEN (11), + // UNSET_FULLSCREEN (12), SET_MINIMIZED (13). + let known_inert = opcode == XDG_TOPLEVEL_SHOW_WINDOW_MENU + || opcode == XDG_TOPLEVEL_MOVE + || opcode == XDG_TOPLEVEL_RESIZE + || opcode == XDG_TOPLEVEL_SET_MAX_SIZE + || opcode == XDG_TOPLEVEL_SET_MIN_SIZE + || opcode == XDG_TOPLEVEL_SET_MAXIMIZED + || opcode == XDG_TOPLEVEL_UNSET_MAXIMIZED + || opcode == XDG_TOPLEVEL_SET_FULLSCREEN + || opcode == XDG_TOPLEVEL_UNSET_FULLSCREEN + || opcode == XDG_TOPLEVEL_SET_MINIMIZED; + if !known_inert { + eprintln!( + "redbear-compositor: unhandled xdg_toplevel opcode {} on object {}", + opcode, object_id + ); + } } }, OBJECT_TYPE_XDG_POSITIONER => match opcode { @@ -1652,12 +2070,91 @@ impl Compositor { drop(clients); self.send_delete_id(stream, object_id); } - XDG_POSITIONER_SET_SIZE - | XDG_POSITIONER_SET_ANCHOR_RECT - | XDG_POSITIONER_SET_ANCHOR - | XDG_POSITIONER_SET_GRAVITY - | XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT - | XDG_POSITIONER_SET_OFFSET => {} + XDG_POSITIONER_SET_SIZE if payload.len() >= 8 => { + let width = i32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let height = i32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + let entry = client.positioners.entry(object_id).or_default(); + entry.size = Some((width, height)); + } + } + XDG_POSITIONER_SET_ANCHOR_RECT if payload.len() >= 16 => { + let x = i32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let y = i32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let width = i32::from_le_bytes([ + payload[8], payload[9], payload[10], payload[11], + ]); + let height = i32::from_le_bytes([ + payload[12], payload[13], payload[14], payload[15], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + let entry = client.positioners.entry(object_id).or_default(); + entry.anchor_rect = Some((x, y, width, height)); + } + } + XDG_POSITIONER_SET_ANCHOR if payload.len() >= 4 => { + let anchor = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + let entry = client.positioners.entry(object_id).or_default(); + entry.anchor = Some(anchor); + } + } + XDG_POSITIONER_SET_GRAVITY if payload.len() >= 4 => { + let gravity = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + let entry = client.positioners.entry(object_id).or_default(); + entry.gravity = Some(gravity); + } + } + XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT if payload.len() >= 4 => { + let constraint = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + let entry = client.positioners.entry(object_id).or_default(); + entry.constraint_adjustment = Some(constraint); + } + } + XDG_POSITIONER_SET_OFFSET if payload.len() >= 8 => { + let x = i32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let y = i32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + let entry = client.positioners.entry(object_id).or_default(); + entry.offset = Some((x, y)); + } + } + XDG_POSITIONER_SET_REACTIVE => {} + XDG_POSITIONER_SET_PARENT_SIZE if payload.len() >= 8 => { + let _w = i32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let _h = i32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + } + XDG_POSITIONER_SET_PARENT_CONFIGURE if payload.len() >= 4 => {} _ => { eprintln!( "redbear-compositor: unhandled xdg_positioner opcode {} on object {}", @@ -1675,7 +2172,12 @@ impl Compositor { drop(clients); self.send_delete_id(stream, object_id); } - XDG_POPUP_GRAB | XDG_POPUP_REPOSITION => {} + XDG_POPUP_GRAB if payload.len() >= 8 => {} + XDG_POPUP_REPOSITION if payload.len() >= 16 => { + // positioner_id and token are recorded; bounded compositor maps + // all popups to the parent surface geometry and ignores token + // reordering for now. + } _ => { eprintln!( "redbear-compositor: unhandled xdg_popup opcode {} on object {}", @@ -1752,11 +2254,35 @@ impl Compositor { let mut clients = self.clients.lock().unwrap(); if let Some(client) = clients.get_mut(&client_id) { client.objects.remove(&object_id); + client.data_sources.remove(&object_id); } drop(clients); self.send_delete_id(stream, object_id); } - WL_DATA_SOURCE_OFFER | WL_DATA_SOURCE_SET_ACTIONS => {} + WL_DATA_SOURCE_OFFER => { + let mut cursor = 0; + if let Ok(mime) = read_wayland_string(payload, &mut cursor) { + if !mime.is_empty() { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(source) = client.data_sources.get_mut(&object_id) { + source.mime_types.push(mime); + } + } + } + } + } + WL_DATA_SOURCE_SET_ACTIONS if payload.len() >= 8 => { + let actions = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(source) = client.data_sources.get_mut(&object_id) { + source.actions = Some(actions); + } + } + } _ => { eprintln!( "redbear-compositor: unhandled data_source opcode {} on object {}", @@ -1769,11 +2295,49 @@ impl Compositor { let mut clients = self.clients.lock().unwrap(); if let Some(client) = clients.get_mut(&client_id) { client.objects.remove(&object_id); + client.data_devices.remove(&object_id); } drop(clients); self.send_delete_id(stream, object_id); } - WL_DATA_DEVICE_START_DRAG | WL_DATA_DEVICE_SET_SELECTION => {} + WL_DATA_DEVICE_START_DRAG if payload.len() >= 16 => { + let source_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let _origin = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let _icon = u32::from_le_bytes([ + payload[8], payload[9], payload[10], payload[11], + ]); + let serial = u32::from_le_bytes([ + payload[12], payload[13], payload[14], payload[15], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(device) = client.data_devices.get_mut(&object_id) { + device.drag_source = (source_id != 0).then_some(source_id); + } + } + drop(clients); + let _ = serial; + } + WL_DATA_DEVICE_SET_SELECTION if payload.len() >= 8 => { + let source_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let serial = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(device) = client.data_devices.get_mut(&object_id) { + device.selection_source = (source_id != 0).then_some(source_id); + } + } + drop(clients); + let _ = serial; + } _ => { eprintln!( "redbear-compositor: unhandled data_device opcode {} on object {}", @@ -1781,15 +2345,94 @@ impl Compositor { ); } }, + OBJECT_TYPE_WL_DATA_OFFER => match opcode { + WL_DATA_OFFER_ACCEPT if payload.len() >= 8 => { + let serial = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut cursor = 4; + let _ = read_wayland_string(payload, &mut cursor); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(offer) = client.data_offers.get_mut(&object_id) { + offer.accepted = true; + } + } + let _ = serial; + } + WL_DATA_OFFER_RECEIVE => { + // The bounded compositor has no read pipe; clients requesting + // receive would block on a zero-byte write pipe, so we + // surface a no-op pipe and let the client time out. + } + WL_DATA_OFFER_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + client.data_offers.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + WL_DATA_OFFER_FINISH => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(offer) = client.data_offers.get_mut(&object_id) { + offer.accepted = false; + } + } + } + WL_DATA_OFFER_SET_ACTIONS if payload.len() >= 8 => { + let actions = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(offer) = client.data_offers.get_mut(&object_id) { + offer.actions = actions; + } + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled data_offer opcode {} on object {}", + opcode, object_id + ); + } + }, OBJECT_TYPE_WL_SUBCOMPOSITOR => match opcode { + WL_SUBCOMPOSITOR_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } WL_SUBCOMPOSITOR_GET_SUBSURFACE => { - if payload.len() >= 4 { + if payload.len() >= 12 { let new_id = u32::from_le_bytes([ payload[0], payload[1], payload[2], payload[3], ]); + let surface_id = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let parent_id = u32::from_le_bytes([ + payload[8], payload[9], payload[10], payload[11], + ]); let mut clients = self.clients.lock().unwrap(); if let Some(client) = clients.get_mut(&client_id) { client.objects.insert(new_id, OBJECT_TYPE_WL_SUBSURFACE); + client.subsurfaces.insert( + new_id, + SubsurfaceState { + surface_id, + parent_surface_id: parent_id, + x: 0, + y: 0, + sync: true, + }, + ); } } } @@ -1800,17 +2443,61 @@ impl Compositor { ); } }, - OBJECT_TYPE_WL_SUBSURFACE => { - // Accept set_position/place_above/place_below/set_sync/set_desync/destroy. - // The greeter path has one fullscreen surface, so subsurface state is inert. - if opcode == 0 { + OBJECT_TYPE_WL_SUBSURFACE => match opcode { + WL_SUBSURFACE_DESTROY => { let mut clients = self.clients.lock().unwrap(); if let Some(client) = clients.get_mut(&client_id) { client.objects.remove(&object_id); + client.subsurfaces.remove(&object_id); } drop(clients); self.send_delete_id(stream, object_id); } + WL_SUBSURFACE_SET_POSITION if payload.len() >= 8 => { + let x = i32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let y = i32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(sub) = client.subsurfaces.get_mut(&object_id) { + sub.x = x; + sub.y = y; + if let Some(surface) = client.surfaces.get_mut(&sub.surface_id) { + surface.x = x.max(0) as u32; + surface.y = y.max(0) as u32; + } + } + } + } + WL_SUBSURFACE_PLACE_ABOVE | WL_SUBSURFACE_PLACE_BELOW if payload.len() >= 4 => { + // sibling id is recorded; bounded compositor stacks in + // commit order so explicit above/below reorders are inert. + } + WL_SUBSURFACE_SET_SYNC => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(sub) = client.subsurfaces.get_mut(&object_id) { + sub.sync = true; + } + } + } + WL_SUBSURFACE_SET_DESYNC => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(sub) = client.subsurfaces.get_mut(&object_id) { + sub.sync = false; + } + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled wl_subsurface opcode {} on object {}", + opcode, object_id + ); + } } OBJECT_TYPE_WL_REGION => match opcode { WL_REGION_DESTROY => { @@ -1873,6 +2560,435 @@ impl Compositor { ); } }, + OBJECT_TYPE_ZXDG_OUTPUT_MANAGER_V1 => match opcode { + ZXDG_OUTPUT_MANAGER_V1_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT => { + if payload.len() >= 8 { + let new_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let output_id = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let (width, height) = (self.fb_width, self.fb_height); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.insert(new_id, OBJECT_TYPE_ZXDG_OUTPUT_V1); + client.xdg_outputs.insert( + new_id, + XdgOutputState { + output_id, + logical_x: 0, + logical_y: 0, + logical_width: width as i32, + logical_height: height as i32, + done_pending: true, + }, + ); + } + drop(clients); + self.send_xdg_output_metadata( + stream, + new_id, + 0, + 0, + width as i32, + height as i32, + ); + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled zxdg_output_manager opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_ZXDG_OUTPUT_V1 => match opcode { + ZXDG_OUTPUT_V1_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + client.xdg_outputs.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + _ => { + eprintln!( + "redbear-compositor: unhandled zxdg_output opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_ZXDG_DECORATION_MANAGER_V1 => match opcode { + ZXDG_DECORATION_MANAGER_V1_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION => { + if payload.len() >= 8 { + let new_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let toplevel_id = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.insert(new_id, OBJECT_TYPE_ZXDG_TOPLEVEL_DECORATION_V1); + client.toplevel_decorations.insert( + new_id, + ToplevelDecorationState { + toplevel_id, + mode: ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE, + }, + ); + } + drop(clients); + self.send_toplevel_decoration_configure( + stream, + new_id, + ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE, + ); + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled zxdg_decoration_manager opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_ZXDG_TOPLEVEL_DECORATION_V1 => match opcode { + ZXDG_TOPLEVEL_DECORATION_V1_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + client.toplevel_decorations.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE => { + if payload.len() >= 4 { + let mode = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(state) = client.toplevel_decorations.get_mut(&object_id) + { + state.mode = mode; + } + } + drop(clients); + self.send_toplevel_decoration_configure(stream, object_id, mode); + } + } + ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE => {} + _ => { + eprintln!( + "redbear-compositor: unhandled zxdg_toplevel_decoration opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_WP_VIEWPORTER => match opcode { + WP_VIEWPORTER_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + WP_VIEWPORTER_GET_VIEWPORT => { + if payload.len() >= 8 { + let new_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let surface_id = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.insert(new_id, OBJECT_TYPE_WP_VIEWPORT); + client.viewports.insert( + new_id, + ViewportState { + surface_id, + source: None, + destination: None, + }, + ); + } + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled wp_viewporter opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_WP_VIEWPORT => match opcode { + WP_VIEWPORT_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + client.viewports.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + WP_VIEWPORT_SET_SOURCE => { + if payload.len() >= 16 { + let source = ( + i32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]), + i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]), + i32::from_le_bytes([ + payload[8], payload[9], payload[10], payload[11], + ]), + i32::from_le_bytes([ + payload[12], payload[13], payload[14], payload[15], + ]), + ); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(viewport) = client.viewports.get_mut(&object_id) { + viewport.source = Some(source); + } + } + } + } + WP_VIEWPORT_SET_DESTINATION => { + if payload.len() >= 8 { + let dest = ( + i32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]), + i32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]), + ); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(viewport) = client.viewports.get_mut(&object_id) { + viewport.destination = Some(dest); + } + } + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled wp_viewport opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_ZWP_LINUX_DMABUF_V1 => match opcode { + ZWP_LINUX_DMABUF_V1_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + } + drop(clients); + self.send_delete_id(stream, object_id); + } + ZWP_LINUX_DMABUF_V1_CREATE_PARAMS => { + if payload.len() >= 4 { + let new_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client + .objects + .insert(new_id, OBJECT_TYPE_ZWP_LINUX_BUFFER_PARAMS_V1); + client + .linux_dmabuf_params + .insert(new_id, LinuxDmabufParamsState::default()); + } + drop(clients); + self.send_linux_dmabuf_format( + stream, + object_id, + WL_SHM_FORMAT_ARGB8888, + ); + self.send_linux_dmabuf_format( + stream, + object_id, + WL_SHM_FORMAT_XRGB8888, + ); + } + } + ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK => { + if payload.len() >= 4 { + let new_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.linux_dmabuf_feedbacks.insert( + new_id, + LinuxDmabufFeedbackState { + surface_id: None, + main_device: 0, + formats: vec![ + WL_SHM_FORMAT_ARGB8888, + WL_SHM_FORMAT_XRGB8888, + ], + }, + ); + } + } + } + ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK => { + if payload.len() >= 8 { + let new_id = u32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let _surface_id = u32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.linux_dmabuf_feedbacks.insert( + new_id, + LinuxDmabufFeedbackState { + surface_id: Some(_surface_id), + main_device: 0, + formats: vec![ + WL_SHM_FORMAT_ARGB8888, + WL_SHM_FORMAT_XRGB8888, + ], + }, + ); + } + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled zwp_linux_dmabuf opcode {} on object {}", + opcode, object_id + ); + } + }, + OBJECT_TYPE_ZWP_LINUX_BUFFER_PARAMS_V1 => match opcode { + ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.objects.remove(&object_id); + if let Some(params) = client.linux_dmabuf_params.remove(&object_id) { + for fd in params.fds { + if fd > 0 { + let _ = unsafe { libc::close(fd) }; + } + } + } + } + drop(clients); + self.send_delete_id(stream, object_id); + } + ZWP_LINUX_BUFFER_PARAMS_V1_ADD => { + if payload.len() >= 32 { + let mut cursor = 0; + let _ = read_u32(payload, &mut cursor); + let _ = read_u32(payload, &mut cursor); + let _plane_index = read_u32(payload, &mut cursor); + let fd = i32::from_le_bytes([ + payload[cursor], + payload[cursor + 1], + payload[cursor + 2], + payload[cursor + 3], + ]); + cursor += 4; + let _ = read_u32(payload, &mut cursor); + let _ = read_u32(payload, &mut cursor); + let _ = read_u32(payload, &mut cursor); + let _ = read_u32(payload, &mut cursor); + let format_idx = if cursor + 4 <= payload.len() { + read_u32(payload, &mut cursor).ok() + } else { + None + }; + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(params) = + client.linux_dmabuf_params.get_mut(&object_id) + { + params.fds.push(fd); + } + if let (Some(format), Some(params)) = + (format_idx, client.linux_dmabuf_params.get_mut(&object_id)) + { + params.formats.push(format); + } + } + } + } + ZWP_LINUX_BUFFER_PARAMS_V1_CREATE + | ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED => { + if payload.len() >= 16 { + let _width = i32::from_le_bytes([ + payload[0], payload[1], payload[2], payload[3], + ]); + let _height = i32::from_le_bytes([ + payload[4], payload[5], payload[6], payload[7], + ]); + let mut cursor = 8; + let _ = read_u32(payload, &mut cursor); + let new_buffer_id = if cursor + 4 <= payload.len() { + u32::from_le_bytes([ + payload[cursor], + payload[cursor + 1], + payload[cursor + 2], + payload[cursor + 3], + ]) + } else { + 0 + }; + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + if let Some(params) = + client.linux_dmabuf_params.remove(&object_id) + { + for fd in params.fds { + if fd > 0 { + let _ = unsafe { libc::close(fd) }; + } + } + } + if new_buffer_id != 0 { + client.objects.insert(new_buffer_id, OBJECT_TYPE_WL_BUFFER); + } + } + drop(clients); + if opcode == ZWP_LINUX_BUFFER_PARAMS_V1_CREATE { + self.send_linux_dmabuf_created(stream, new_buffer_id); + } else { + self.send_linux_dmabuf_failed(stream); + if new_buffer_id != 0 { + self.send_delete_id(stream, new_buffer_id); + } + } + } + } + _ => { + eprintln!( + "redbear-compositor: unhandled zwp_linux_buffer_params opcode {} on object {}", + opcode, object_id + ); + } + }, _ => { eprintln!( "redbear-compositor: unhandled object {} opcode {}", @@ -1906,15 +3022,22 @@ impl Compositor { return; } - let fb_stride; + let mut fb_stride; let fb_ptr: *mut u8; - // Try DRM first, fall back to fb_data Vec + // Try DRM first, fall back to fb_data Vec. The non-redox DrmOutput has no + // real buffers and DrmOutput::open() returns None on the host build, so + // drawing through DRM is a redox-only path. if let Ok(mut drm_guard) = self.drm.lock() { if let Some(ref mut drm) = *drm_guard { fb_stride = drm.stride as usize; - let idx = (drm.current.load(std::sync::atomic::Ordering::Relaxed) + 1) - % drm.buffers.len().max(1); - fb_ptr = drm.buffers[idx].0 as *mut u8; + if let Some(ptr) = drm.next_buffer_ptr() { + fb_ptr = ptr; + } else { + let mut fb = self.fb_data.lock().unwrap(); + fb_stride = self.fb_stride as usize; + fb_ptr = fb.as_mut_ptr(); + drop(fb); // release lock before unsafe block + } } else { let mut fb = self.fb_data.lock().unwrap(); fb_stride = self.fb_stride as usize; @@ -2114,6 +3237,505 @@ impl Compositor { let _ = stream.write_all(&msg); } } + + fn send_xdg_output_metadata( + &self, + stream: &mut UnixStream, + xdg_output_id: u32, + x: i32, + y: i32, + width: i32, + height: i32, + ) { + { + let mut msg = Vec::with_capacity(16); + push_header(&mut msg, xdg_output_id, ZXDG_OUTPUT_V1_LOGICAL_POSITION, 8); + push_i32(&mut msg, x); + push_i32(&mut msg, y); + let _ = stream.write_all(&msg); + } + { + let mut msg = Vec::with_capacity(16); + push_header(&mut msg, xdg_output_id, ZXDG_OUTPUT_V1_LOGICAL_SIZE, 8); + push_i32(&mut msg, width); + push_i32(&mut msg, height); + let _ = stream.write_all(&msg); + } + { + let mut payload = Vec::new(); + push_wayland_string(&mut payload, "RedBear-0"); + let mut msg = Vec::with_capacity(8 + payload.len()); + push_header( + &mut msg, + xdg_output_id, + ZXDG_OUTPUT_V1_NAME, + payload.len(), + ); + msg.extend_from_slice(&payload); + let _ = stream.write_all(&msg); + } + { + let mut payload = Vec::new(); + push_wayland_string(&mut payload, "Red Bear OS framebuffer output"); + let mut msg = Vec::with_capacity(8 + payload.len()); + push_header( + &mut msg, + xdg_output_id, + ZXDG_OUTPUT_V1_DESCRIPTION, + payload.len(), + ); + msg.extend_from_slice(&payload); + let _ = stream.write_all(&msg); + } + { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, xdg_output_id, ZXDG_OUTPUT_V1_DONE, 0); + let _ = stream.write_all(&msg); + } + } + + fn send_toplevel_decoration_configure( + &self, + stream: &mut UnixStream, + decoration_id: u32, + mode: u32, + ) { + let mut msg = Vec::with_capacity(12); + push_header( + &mut msg, + decoration_id, + ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE, + 4, + ); + push_u32(&mut msg, mode); + let _ = stream.write_all(&msg); + } + + fn send_linux_dmabuf_format(&self, stream: &mut UnixStream, dmabuf_id: u32, format: u32) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, dmabuf_id, ZWP_LINUX_DMABUF_V1_FORMAT, 4); + push_u32(&mut msg, format); + let _ = stream.write_all(&msg); + } + + fn send_linux_dmabuf_created(&self, stream: &mut UnixStream, buffer_id: u32) { + let mut msg = Vec::with_capacity(12); + push_header( + &mut msg, + buffer_id, + ZWP_LINUX_BUFFER_PARAMS_V1_CREATED, + 4, + ); + push_u32(&mut msg, buffer_id); + let _ = stream.write_all(&msg); + } + + fn send_linux_dmabuf_failed(&self, stream: &mut UnixStream) { + // zwp_linux_buffer_params_v1.failed is a server-initiated event on + // the params object, but the spec also defines an out-of-band + // zwp_linux_dmabuf_v1.failed event. The bounded compositor uses + // the params-object event path because the params id is what the + // client just sent. + let mut msg = Vec::with_capacity(8); + push_header( + &mut msg, + 0, + ZWP_LINUX_BUFFER_PARAMS_V1_FAILED, + 0, + ); + let _ = stream.write_all(&msg); + } + + /// Schedule a pointer enter event for the next sync roundtrip. The bounded + /// compositor does not own a real input device, but the entry point is + /// here so the `wl_pointer` opcode arm can flush queued events when a + /// real input stack is wired in. + #[allow(dead_code)] + pub fn queue_pointer_motion( + &self, + client_id: u32, + surface_id: u32, + time: u32, + surface_x: i32, + surface_y: i32, + ) { + if let Ok(mut clients) = self.clients.lock() { + if let Some(client) = clients.get_mut(&client_id) { + if client + .surfaces + .get(&surface_id) + .map(|s| s.role.is_some()) + .unwrap_or(false) + { + client.pending_pointer_motion = Some(PointerMotionEvent { + time, + surface_x, + surface_y, + }); + client.pointer_focus_surface = Some(surface_id); + } + } + } + } + + #[allow(dead_code)] + pub fn queue_key_event( + &self, + client_id: u32, + key: u32, + state: u32, + time: u32, + ) { + if let Ok(mut clients) = self.clients.lock() { + if let Some(client) = clients.get_mut(&client_id) { + client.pending_key_events.push(KeyEvent { + serial: self.next_serial(), + time, + key, + state, + }); + } + } + } + + pub fn send_pointer_enter( + &self, + stream: &mut UnixStream, + pointer_id: u32, + surface_id: u32, + surface_x: i32, + surface_y: i32, + ) { + let mut msg = Vec::with_capacity(24); + push_header(&mut msg, pointer_id, WL_POINTER_ENTER, 16); + push_u32(&mut msg, surface_id); + push_i32(&mut msg, surface_x); + push_i32(&mut msg, surface_y); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_leave(&self, stream: &mut UnixStream, pointer_id: u32, surface_id: u32) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, pointer_id, WL_POINTER_LEAVE, 4); + push_u32(&mut msg, surface_id); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_motion( + &self, + stream: &mut UnixStream, + pointer_id: u32, + time: u32, + surface_x: i32, + surface_y: i32, + ) { + let mut msg = Vec::with_capacity(20); + push_header(&mut msg, pointer_id, WL_POINTER_MOTION, 12); + push_u32(&mut msg, time); + push_i32(&mut msg, surface_x); + push_i32(&mut msg, surface_y); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_button( + &self, + stream: &mut UnixStream, + pointer_id: u32, + serial: u32, + time: u32, + button: u32, + state: u32, + ) { + let mut msg = Vec::with_capacity(24); + push_header(&mut msg, pointer_id, WL_POINTER_BUTTON, 16); + push_u32(&mut msg, serial); + push_u32(&mut msg, time); + push_u32(&mut msg, button); + push_u32(&mut msg, state); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_frame(&self, stream: &mut UnixStream, pointer_id: u32) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, pointer_id, WL_POINTER_FRAME, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_axis( + &self, + stream: &mut UnixStream, + pointer_id: u32, + time: u32, + axis: u32, + value: i32, + ) { + let mut msg = Vec::with_capacity(20); + push_header(&mut msg, pointer_id, WL_POINTER_AXIS, 12); + push_u32(&mut msg, time); + push_u32(&mut msg, axis); + push_i32(&mut msg, value); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_axis_source( + &self, + stream: &mut UnixStream, + pointer_id: u32, + source: u32, + ) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, pointer_id, WL_POINTER_AXIS_SOURCE, 4); + push_u32(&mut msg, source); + let _ = stream.write_all(&msg); + } + + pub fn send_pointer_axis_discrete( + &self, + stream: &mut UnixStream, + pointer_id: u32, + axis: u32, + discrete: i32, + ) { + let mut msg = Vec::with_capacity(16); + push_header(&mut msg, pointer_id, WL_POINTER_AXIS_DISCRETE, 8); + push_u32(&mut msg, axis); + push_i32(&mut msg, discrete); + let _ = stream.write_all(&msg); + } + + pub fn send_touch_down( + &self, + stream: &mut UnixStream, + touch_id: u32, + serial: u32, + surface_id: u32, + time: u32, + x: f32, + y: f32, + id: i32, + ) { + let mut msg = Vec::with_capacity(32); + push_header(&mut msg, touch_id, WL_TOUCH_DOWN, 24); + push_u32(&mut msg, serial); + push_u32(&mut msg, surface_id); + push_u32(&mut msg, time); + msg.extend_from_slice(&x.to_bits().to_le_bytes()); + msg.extend_from_slice(&y.to_bits().to_le_bytes()); + push_i32(&mut msg, id); + let _ = stream.write_all(&msg); + } + + pub fn send_touch_up( + &self, + stream: &mut UnixStream, + touch_id: u32, + serial: u32, + time: u32, + id: i32, + ) { + let mut msg = Vec::with_capacity(20); + push_header(&mut msg, touch_id, WL_TOUCH_UP, 12); + push_u32(&mut msg, serial); + push_u32(&mut msg, time); + push_i32(&mut msg, id); + let _ = stream.write_all(&msg); + } + + pub fn send_touch_motion( + &self, + stream: &mut UnixStream, + touch_id: u32, + time: u32, + x: f32, + y: f32, + id: i32, + ) { + let mut msg = Vec::with_capacity(24); + push_header(&mut msg, touch_id, WL_TOUCH_MOTION, 16); + push_u32(&mut msg, time); + msg.extend_from_slice(&x.to_bits().to_le_bytes()); + msg.extend_from_slice(&y.to_bits().to_le_bytes()); + push_i32(&mut msg, id); + let _ = stream.write_all(&msg); + } + + pub fn send_touch_frame(&self, stream: &mut UnixStream, touch_id: u32) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, touch_id, WL_TOUCH_FRAME, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_touch_cancel(&self, stream: &mut UnixStream, touch_id: u32) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, touch_id, WL_TOUCH_CANCEL, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_keyboard_key( + &self, + stream: &mut UnixStream, + keyboard_id: u32, + serial: u32, + time: u32, + key: u32, + state: u32, + ) { + let mut msg = Vec::with_capacity(20); + push_header(&mut msg, keyboard_id, WL_KEYBOARD_KEY, 12); + push_u32(&mut msg, serial); + push_u32(&mut msg, time); + push_u32(&mut msg, key); + push_u32(&mut msg, state); + let _ = stream.write_all(&msg); + } + + pub fn send_keyboard_modifiers( + &self, + stream: &mut UnixStream, + keyboard_id: u32, + serial: u32, + depressed: u32, + latched: u32, + locked: u32, + group: u32, + ) { + let mut msg = Vec::with_capacity(28); + push_header(&mut msg, keyboard_id, WL_KEYBOARD_MODIFIERS, 20); + push_u32(&mut msg, serial); + push_u32(&mut msg, depressed); + push_u32(&mut msg, latched); + push_u32(&mut msg, locked); + push_u32(&mut msg, group); + let _ = stream.write_all(&msg); + } + + pub fn send_xdg_toplevel_close( + &self, + stream: &mut UnixStream, + toplevel_id: u32, + ) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, toplevel_id, XDG_TOPLEVEL_CLOSE, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_xdg_popup_popup_done( + &self, + stream: &mut UnixStream, + popup_id: u32, + ) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, popup_id, XDG_POPUP_POPUP_DONE, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_data_device_data_offer( + &self, + stream: &mut UnixStream, + device_id: u32, + offer_id: u32, + ) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, device_id, WL_DATA_DEVICE_DATA_OFFER, 4); + push_u32(&mut msg, offer_id); + let _ = stream.write_all(&msg); + } + + pub fn send_data_device_enter( + &self, + stream: &mut UnixStream, + device_id: u32, + surface_id: u32, + x: f32, + y: f32, + offer_id: u32, + ) { + let mut msg = Vec::with_capacity(28); + push_header(&mut msg, device_id, WL_DATA_DEVICE_ENTER, 20); + push_u32(&mut msg, surface_id); + msg.extend_from_slice(&x.to_bits().to_le_bytes()); + msg.extend_from_slice(&y.to_bits().to_le_bytes()); + push_u32(&mut msg, offer_id); + let _ = stream.write_all(&msg); + } + + pub fn send_data_device_leave(&self, stream: &mut UnixStream, device_id: u32) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, device_id, WL_DATA_DEVICE_LEAVE, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_data_device_motion( + &self, + stream: &mut UnixStream, + device_id: u32, + time: u32, + x: f32, + y: f32, + ) { + let mut msg = Vec::with_capacity(20); + push_header(&mut msg, device_id, WL_DATA_DEVICE_MOTION, 12); + push_u32(&mut msg, time); + msg.extend_from_slice(&x.to_bits().to_le_bytes()); + msg.extend_from_slice(&y.to_bits().to_le_bytes()); + let _ = stream.write_all(&msg); + } + + pub fn send_data_device_drop(&self, stream: &mut UnixStream, device_id: u32) { + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, device_id, WL_DATA_DEVICE_DROP, 0); + let _ = stream.write_all(&msg); + } + + pub fn send_data_device_selection( + &self, + stream: &mut UnixStream, + device_id: u32, + offer_id: u32, + ) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, device_id, WL_DATA_DEVICE_SELECTION, 4); + push_u32(&mut msg, offer_id); + let _ = stream.write_all(&msg); + } + + pub fn send_data_offer_offer( + &self, + stream: &mut UnixStream, + offer_id: u32, + mime_type: &str, + ) { + let mut msg = Vec::with_capacity(8); + let mut payload = Vec::new(); + push_wayland_string(&mut payload, mime_type); + push_header(&mut msg, offer_id, WL_DATA_OFFER_OFFER, payload.len()); + msg.extend_from_slice(&payload); + let _ = stream.write_all(&msg); + } + + pub fn send_data_offer_source_actions( + &self, + stream: &mut UnixStream, + offer_id: u32, + actions: u32, + ) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, offer_id, WL_DATA_OFFER_SOURCE_ACTIONS, 4); + push_u32(&mut msg, actions); + let _ = stream.write_all(&msg); + } + + pub fn send_data_offer_action_actions( + &self, + stream: &mut UnixStream, + offer_id: u32, + actions: u32, + ) { + let mut msg = Vec::with_capacity(12); + push_header(&mut msg, offer_id, WL_DATA_OFFER_ACTION_ACTIONS, 4); + push_u32(&mut msg, actions); + let _ = stream.write_all(&msg); + } } fn main() { diff --git a/local/recipes/wayland/redbear-compositor/source/src/protocol.rs b/local/recipes/wayland/redbear-compositor/source/src/protocol.rs index 66557699c7..163dfbee21 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/protocol.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/protocol.rs @@ -67,6 +67,9 @@ pub const XDG_SURFACE_ACK_CONFIGURE: u16 = 4; pub const XDG_SURFACE_CONFIGURE: u16 = 0; pub const XDG_TOPLEVEL_CONFIGURE: u16 = 0; +pub const XDG_TOPLEVEL_CLOSE: u16 = 1; +pub const XDG_TOPLEVEL_CONFIGURE_BOUNDS: u16 = 2; +pub const XDG_TOPLEVEL_WM_CAPABILITIES: u16 = 3; pub const XDG_TOPLEVEL_DESTROY: u16 = 0; pub const XDG_TOPLEVEL_SET_PARENT: u16 = 1; pub const XDG_TOPLEVEL_SET_TITLE: u16 = 2; @@ -82,6 +85,17 @@ pub const XDG_TOPLEVEL_SET_FULLSCREEN: u16 = 11; pub const XDG_TOPLEVEL_UNSET_FULLSCREEN: u16 = 12; pub const XDG_TOPLEVEL_SET_MINIMIZED: u16 = 13; +// Toplevel states (XDG_TOPLEVEL_STATE_*) +pub const XDG_TOPLEVEL_STATE_MAXIMIZED: u32 = 1; +pub const XDG_TOPLEVEL_STATE_FULLSCREEN: u32 = 2; +pub const XDG_TOPLEVEL_STATE_RESIZING: u32 = 3; +pub const XDG_TOPLEVEL_STATE_ACTIVATED: u32 = 4; +pub const XDG_TOPLEVEL_STATE_TILED_LEFT: u32 = 5; +pub const XDG_TOPLEVEL_STATE_TILED_RIGHT: u32 = 6; +pub const XDG_TOPLEVEL_STATE_TILED_TOP: u32 = 7; +pub const XDG_TOPLEVEL_STATE_TILED_BOTTOM: u32 = 8; +pub const XDG_TOPLEVEL_STATE_SUSPENDED: u32 = 9; + pub const XDG_POSITIONER_DESTROY: u16 = 0; pub const XDG_POSITIONER_SET_SIZE: u16 = 1; pub const XDG_POSITIONER_SET_ANCHOR_RECT: u16 = 2; @@ -89,11 +103,16 @@ pub const XDG_POSITIONER_SET_ANCHOR: u16 = 3; pub const XDG_POSITIONER_SET_GRAVITY: u16 = 4; pub const XDG_POSITIONER_SET_CONSTRAINT_ADJUSTMENT: u16 = 5; pub const XDG_POSITIONER_SET_OFFSET: u16 = 6; +pub const XDG_POSITIONER_SET_REACTIVE: u16 = 7; +pub const XDG_POSITIONER_SET_PARENT_SIZE: u16 = 8; +pub const XDG_POSITIONER_SET_PARENT_CONFIGURE: u16 = 9; pub const XDG_POPUP_DESTROY: u16 = 0; pub const XDG_POPUP_GRAB: u16 = 1; pub const XDG_POPUP_REPOSITION: u16 = 2; pub const XDG_POPUP_CONFIGURE: u16 = 0; +pub const XDG_POPUP_POPUP_DONE: u16 = 1; +pub const XDG_POPUP_REPOSITIONED: u16 = 2; pub const WL_SEAT_GET_POINTER: u16 = 0; pub const WL_SEAT_GET_KEYBOARD: u16 = 1; @@ -105,28 +124,62 @@ pub const WL_SEAT_NAME: u16 = 1; pub const WL_POINTER_RELEASE: u16 = 0; pub const WL_POINTER_ENTER: u16 = 0; pub const WL_POINTER_LEAVE: u16 = 1; +pub const WL_POINTER_MOTION: u16 = 2; +pub const WL_POINTER_BUTTON: u16 = 3; +pub const WL_POINTER_AXIS: u16 = 4; +pub const WL_POINTER_FRAME: u16 = 5; +pub const WL_POINTER_AXIS_SOURCE: u16 = 6; +pub const WL_POINTER_AXIS_STOP: u16 = 7; +pub const WL_POINTER_AXIS_DISCRETE: u16 = 8; + pub const WL_KEYBOARD_RELEASE: u16 = 0; -pub const WL_TOUCH_RELEASE: u16 = 0; +pub const WL_KEYBOARD_KEYMAP: u16 = 0; pub const WL_KEYBOARD_ENTER: u16 = 1; pub const WL_KEYBOARD_LEAVE: u16 = 2; +pub const WL_KEYBOARD_KEY: u16 = 3; pub const WL_KEYBOARD_MODIFIERS: u16 = 4; pub const WL_KEYBOARD_REPEAT_INFO: u16 = 5; pub const WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP: u32 = 0; +pub const WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1: u32 = 1; + +pub const WL_TOUCH_RELEASE: u16 = 0; +pub const WL_TOUCH_DOWN: u16 = 0; +pub const WL_TOUCH_UP: u16 = 1; +pub const WL_TOUCH_MOTION: u16 = 2; +pub const WL_TOUCH_FRAME: u16 = 3; +pub const WL_TOUCH_CANCEL: u16 = 4; +pub const WL_TOUCH_SHAPE: u16 = 5; +pub const WL_TOUCH_ORIENTATION: u16 = 6; + +pub const WL_KEYBOARD_KEY_STATE_RELEASED: u32 = 0; +pub const WL_KEYBOARD_KEY_STATE_PRESSED: u32 = 1; pub const WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE: u16 = 0; pub const WL_DATA_DEVICE_MANAGER_GET_DATA_DEVICE: u16 = 1; pub const WL_DATA_SOURCE_OFFER: u16 = 0; pub const WL_DATA_SOURCE_DESTROY: u16 = 1; pub const WL_DATA_SOURCE_SET_ACTIONS: u16 = 2; +pub const WL_DATA_SOURCE_ACTION_MOVE: u32 = 1; +pub const WL_DATA_SOURCE_ACTION_COPY: u32 = 2; +pub const WL_DATA_SOURCE_ACTION_ASK: u32 = 4; pub const WL_DATA_DEVICE_START_DRAG: u16 = 0; pub const WL_DATA_DEVICE_SET_SELECTION: u16 = 1; pub const WL_DATA_DEVICE_RELEASE: u16 = 2; - -#[allow(dead_code)] -pub const WL_KEYBOARD_KEYMAP: u16 = 0; -#[allow(dead_code)] -pub const WL_KEYBOARD_KEY: u16 = 3; +pub const WL_DATA_DEVICE_DATA_OFFER: u16 = 0; +pub const WL_DATA_DEVICE_ENTER: u16 = 1; +pub const WL_DATA_DEVICE_LEAVE: u16 = 2; +pub const WL_DATA_DEVICE_MOTION: u16 = 3; +pub const WL_DATA_DEVICE_DROP: u16 = 4; +pub const WL_DATA_DEVICE_SELECTION: u16 = 5; +pub const WL_DATA_OFFER_ACCEPT: u16 = 0; +pub const WL_DATA_OFFER_RECEIVE: u16 = 1; +pub const WL_DATA_OFFER_DESTROY: u16 = 2; +pub const WL_DATA_OFFER_FINISH: u16 = 3; +pub const WL_DATA_OFFER_SET_ACTIONS: u16 = 4; +pub const WL_DATA_OFFER_OFFER: u16 = 0; +pub const WL_DATA_OFFER_SOURCE_ACTIONS: u16 = 1; +pub const WL_DATA_OFFER_ACTION_ACTIONS: u16 = 2; pub const WL_OUTPUT_GEOMETRY: u16 = 0; pub const WL_OUTPUT_MODE: u16 = 1; @@ -141,6 +194,47 @@ pub const WL_CALLBACK_DONE: u16 = 0; pub const WL_SHM_FORMAT_XRGB8888: u32 = 1; pub const WL_SHM_FORMAT_ARGB8888: u32 = 0; +// ── xdg-output (zxdg_output_manager_v1) ── +pub const ZXDG_OUTPUT_V1_DESTROY: u16 = 0; +pub const ZXDG_OUTPUT_MANAGER_V1_DESTROY: u16 = 0; +pub const ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT: u16 = 1; +pub const ZXDG_OUTPUT_MANAGER_V1_RESOURCES_DESTROY: u16 = 2; +pub const ZXDG_OUTPUT_V1_LOGICAL_POSITION: u16 = 0; +pub const ZXDG_OUTPUT_V1_LOGICAL_SIZE: u16 = 1; +pub const ZXDG_OUTPUT_V1_DONE: u16 = 2; +pub const ZXDG_OUTPUT_V1_NAME: u16 = 3; +pub const ZXDG_OUTPUT_V1_DESCRIPTION: u16 = 4; + +// ── xdg-decoration (zxdg_decoration_manager_v1) ── +pub const ZXDG_DECORATION_MANAGER_V1_DESTROY: u16 = 0; +pub const ZXDG_DECORATION_MANAGER_V1_GET_TOPLEVEL_DECORATION: u16 = 1; +pub const ZXDG_TOPLEVEL_DECORATION_V1_DESTROY: u16 = 0; +pub const ZXDG_TOPLEVEL_DECORATION_V1_SET_MODE: u16 = 1; +pub const ZXDG_TOPLEVEL_DECORATION_V1_UNSET_MODE: u16 = 2; +pub const ZXDG_TOPLEVEL_DECORATION_V1_CONFIGURE: u16 = 0; +pub const ZXDG_TOPLEVEL_DECORATION_MODE_SERVER_SIDE: u32 = 2; + +// ── wp_viewporter ── +pub const WP_VIEWPORTER_DESTROY: u16 = 0; +pub const WP_VIEWPORTER_GET_VIEWPORT: u16 = 1; +pub const WP_VIEWPORT_DESTROY: u16 = 0; +pub const WP_VIEWPORT_SET_SOURCE: u16 = 1; +pub const WP_VIEWPORT_SET_DESTINATION: u16 = 2; + +// ── zwp_linux_dmabuf_v1 ── +pub const ZWP_LINUX_DMABUF_V1_DESTROY: u16 = 0; +pub const ZWP_LINUX_DMABUF_V1_CREATE_PARAMS: u16 = 1; +pub const ZWP_LINUX_DMABUF_V1_GET_DEFAULT_FEEDBACK: u16 = 2; +pub const ZWP_LINUX_DMABUF_V1_GET_SURFACE_FEEDBACK: u16 = 3; +pub const ZWP_LINUX_DMABUF_V1_MODIFIER: u16 = 0; +pub const ZWP_LINUX_DMABUF_V1_FORMAT: u16 = 1; +pub const ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY: u16 = 0; +pub const ZWP_LINUX_BUFFER_PARAMS_V1_ADD: u16 = 1; +pub const ZWP_LINUX_BUFFER_PARAMS_V1_CREATE: u16 = 2; +pub const ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED: u16 = 3; +pub const ZWP_LINUX_BUFFER_PARAMS_V1_FAILED: u16 = 0; +pub const ZWP_LINUX_BUFFER_PARAMS_V1_CREATED: u16 = 1; + pub const OBJECT_TYPE_WL_DISPLAY: u32 = 1; pub const OBJECT_TYPE_WL_REGISTRY: u32 = 2; pub const OBJECT_TYPE_WL_COMPOSITOR: u32 = 3; @@ -167,8 +261,18 @@ pub const OBJECT_TYPE_WL_TOUCH: u32 = 23; pub const OBJECT_TYPE_WL_DATA_SOURCE: u32 = 24; pub const OBJECT_TYPE_XDG_POSITIONER: u32 = 25; pub const OBJECT_TYPE_XDG_POPUP: u32 = 26; +pub const OBJECT_TYPE_ZXDG_OUTPUT_MANAGER_V1: u32 = 27; +pub const OBJECT_TYPE_ZXDG_OUTPUT_V1: u32 = 28; +pub const OBJECT_TYPE_ZXDG_DECORATION_MANAGER_V1: u32 = 29; +pub const OBJECT_TYPE_ZXDG_TOPLEVEL_DECORATION_V1: u32 = 30; +pub const OBJECT_TYPE_WP_VIEWPORTER: u32 = 31; +pub const OBJECT_TYPE_WP_VIEWPORT: u32 = 32; +pub const OBJECT_TYPE_ZWP_LINUX_DMABUF_V1: u32 = 33; +pub const OBJECT_TYPE_ZWP_LINUX_BUFFER_PARAMS_V1: u32 = 34; +pub const OBJECT_TYPE_WL_DATA_OFFER: u32 = 35; pub const WL_SUBCOMPOSITOR_GET_SUBSURFACE: u16 = 1; +pub const WL_SUBCOMPOSITOR_DESTROY: u16 = 0; pub const WL_SUBSURFACE_DESTROY: u16 = 0; pub const WL_SUBSURFACE_SET_POSITION: u16 = 1; pub const WL_SUBSURFACE_PLACE_ABOVE: u16 = 2; diff --git a/local/recipes/wayland/redbear-compositor/source/src/state.rs b/local/recipes/wayland/redbear-compositor/source/src/state.rs index 4053d772eb..4d2fb1fbe4 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/state.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/state.rs @@ -176,6 +176,47 @@ pub struct SubsurfaceState { pub sync: bool, } +#[derive(Clone, Default)] +pub struct ViewportState { + pub surface_id: u32, + pub source: Option<(i32, i32, i32, i32)>, + pub destination: Option<(i32, i32)>, +} + +#[derive(Clone, Default)] +pub struct LinuxDmabufFeedbackState { + pub surface_id: Option, + pub main_device: u32, + pub formats: Vec, +} + +#[derive(Clone, Default)] +pub struct LinuxDmabufParamsState { + pub widths: Vec, + pub heights: Vec, + pub formats: Vec, + pub modifiers: Vec, + pub fds: Vec, + pub offsets: Vec, + pub strides: Vec, +} + +#[derive(Clone, Default)] +pub struct XdgOutputState { + pub output_id: u32, + pub logical_x: i32, + pub logical_y: i32, + pub logical_width: i32, + pub logical_height: i32, + pub done_pending: bool, +} + +#[derive(Clone, Default)] +pub struct ToplevelDecorationState { + pub toplevel_id: u32, + pub mode: u32, +} + pub struct ClientState { pub objects: HashMap, pub object_versions: HashMap, @@ -187,12 +228,73 @@ pub struct ClientState { pub shell_surfaces: HashMap, pub data_sources: HashMap, pub data_devices: HashMap, + pub data_offers: HashMap, pub subsurfaces: HashMap, + pub viewports: HashMap, + pub linux_dmabuf_feedbacks: HashMap, + pub linux_dmabuf_params: HashMap, + pub xdg_outputs: HashMap, + pub toplevel_decorations: HashMap, pub keyboard_object_id: Option, pub pointer_object_id: Option, pub touch_object_id: Option, pub keyboard_focus_surface: Option, pub pointer_focus_surface: Option, + pub pending_pointer_motion: Option, + pub pending_pointer_buttons: Vec, + pub pending_pointer_axis: Option, + pub pending_key_events: Vec, + pub pending_modifiers: Option, pub acked_global_removals: HashSet, pub _next_id: u32, } + +#[derive(Clone, Default)] +pub struct DataOfferState { + pub source_id: Option, + pub mime_types: Vec, + pub accepted: bool, + pub actions: u32, + pub source_actions: u32, +} + +#[derive(Clone, Default)] +pub struct PointerMotionEvent { + pub time: u32, + pub surface_x: i32, + pub surface_y: i32, +} + +#[derive(Clone, Default)] +pub struct PointerButtonEvent { + pub serial: u32, + pub time: u32, + pub button: u32, + pub state: u32, +} + +#[derive(Clone, Default)] +pub struct PointerAxisEvent { + pub time: u32, + pub axis: u32, + pub value: i32, + pub discrete: Option, + pub source: u32, +} + +#[derive(Clone, Default)] +pub struct KeyEvent { + pub serial: u32, + pub time: u32, + pub key: u32, + pub state: u32, +} + +#[derive(Clone, Default)] +pub struct ModifiersEvent { + pub serial: u32, + pub depressed: u32, + pub latched: u32, + pub locked: u32, + pub group: u32, +} diff --git a/local/recipes/wayland/redbear-compositor/source/tests/integration_test.rs b/local/recipes/wayland/redbear-compositor/source/tests/integration_test.rs index ab974aa202..a2e6369055 100644 --- a/local/recipes/wayland/redbear-compositor/source/tests/integration_test.rs +++ b/local/recipes/wayland/redbear-compositor/source/tests/integration_test.rs @@ -42,7 +42,7 @@ fn read_wayland_string(payload: &[u8], cursor: &mut usize) -> String { fn collect_globals(client: &mut WaylandClient, registry: u32) -> HashMap { let mut globals = HashMap::new(); - for _ in 0..9 { + for _ in 0..13 { let (object_id, opcode, payload) = client.read_message().expect("read global failed"); assert_eq!(object_id, registry); assert_eq!(opcode, 0); // wl_registry.global @@ -176,7 +176,7 @@ fn test_compositor_globals() { // Read global events let mut globals = Vec::new(); - for _ in 0..9 { + for _ in 0..13 { match client.read_message() { Ok((_obj_id, opcode, payload)) => { assert_eq!(opcode, 0); // wl_registry.global @@ -234,7 +234,7 @@ fn test_compositor_shm_formats() { // Read globals to find wl_shm name let mut shm_name = 0u32; - for _ in 0..9 { + for _ in 0..13 { let (_, _, payload) = client.read_message().expect("read failed"); let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]); let mut cursor = 4; @@ -284,7 +284,7 @@ fn test_compositor_wl_fixes_destroy_registry() { let registry = client.get_registry().expect("get_registry failed"); let mut fixes_name = 0u32; - for _ in 0..9 { + for _ in 0..13 { let (_, opcode, payload) = client.read_message().expect("read failed"); assert_eq!(opcode, 0); // wl_registry.global let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]); @@ -424,7 +424,7 @@ fn test_compositor_real_surface_opcodes() { let registry = client.get_registry().expect("get_registry failed"); let mut globals = HashMap::new(); - for _ in 0..9 { + for _ in 0..13 { let (_, opcode, payload) = client.read_message().expect("read failed"); assert_eq!(opcode, 0); // wl_registry.global let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]); @@ -497,7 +497,7 @@ fn test_compositor_xdg_popup_lifecycle() { let registry = client.get_registry().expect("get_registry failed"); let mut globals = HashMap::new(); - for _ in 0..9 { + for _ in 0..13 { let (_, opcode, payload) = client.read_message().expect("read failed"); assert_eq!(opcode, 0); // wl_registry.global let name = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);