diff --git a/local/recipes/wayland/redbear-compositor/source/src/main.rs b/local/recipes/wayland/redbear-compositor/source/src/main.rs index 9babcb4ab8..daa70ea6a2 100644 --- a/local/recipes/wayland/redbear-compositor/source/src/main.rs +++ b/local/recipes/wayland/redbear-compositor/source/src/main.rs @@ -64,6 +64,8 @@ mod wire; mod handlers; mod display_backend; +use protocol::*; + struct MmapBuffer { base: *mut u8, base_len: usize, @@ -752,6 +754,63 @@ fn send_with_rights( Ok(()) } +fn send_with_rights_fds( + stream: &mut UnixStream, + msg: &[u8], + fds: &[RawFd], +) -> std::io::Result<()> { + if fds.is_empty() { + stream.write_all(msg)?; + return Ok(()); + } + + let mut msg = msg.to_vec(); + let mut iov = libc::iovec { + iov_base: msg.as_mut_ptr().cast(), + iov_len: msg.len(), + }; + let control_len = unsafe { libc::CMSG_SPACE((fds.len() * mem::size_of::()) as u32) as usize }; + let mut control = vec![0u8; control_len]; + let header = libc::msghdr { + msg_name: std::ptr::null_mut(), + msg_namelen: 0, + msg_iov: &mut iov, + msg_iovlen: 1, + msg_control: control.as_mut_ptr().cast(), + msg_controllen: control_len, + msg_flags: 0, + }; + let cmsg = unsafe { libc::CMSG_FIRSTHDR(&header) }; + if !cmsg.is_null() { + unsafe { + (*cmsg).cmsg_level = libc::SOL_SOCKET; + (*cmsg).cmsg_type = libc::SCM_RIGHTS; + (*cmsg).cmsg_len = unsafe { + libc::CMSG_LEN((fds.len() * mem::size_of::()) as u32) + } as _; + std::ptr::copy_nonoverlapping( + fds.as_ptr(), + libc::CMSG_DATA(cmsg).cast::(), + fds.len(), + ); + } + } + + let written = unsafe { libc::sendmsg(stream.as_raw_fd(), &header, 0) }; + if written < 0 { + return Err(std::io::Error::last_os_error()); + } + if written as usize != msg.len() { + return Err(std::io::Error::other(format!( + "short sendmsg write: expected {}, got {}", + msg.len(), + written + ))); + } + + Ok(()) +} + const WL_DISPLAY_SYNC: u16 = 0; const WL_DISPLAY_GET_REGISTRY: u16 = 1; // Protocol constant: reserved for future implementation. @@ -1127,21 +1186,25 @@ struct PositionerState { struct DataSourceState { mime_types: Vec, actions: Option, + buffer: Option>, } #[derive(Clone, Default)] struct DataDeviceState { selection_source: Option, drag_source: Option, + selection_offer: Option, } #[derive(Clone, Default)] struct DataOfferState { - source_id: Option, + source_client_id: u32, + source_id: u32, mime_types: Vec, - accepted: bool, - actions: u32, - source_actions: u32, + accepted_mime: Option, + actions: Option, + buffer: Option>, + finished: bool, } #[derive(Clone, Default)] @@ -1521,6 +1584,62 @@ impl Compositor { } } + fn write_event_with_fds( + &self, + client_id: u32, + stream: &mut UnixStream, + msg: &[u8], + fds: &mut VecDeque, + context: &str, + ) -> Result<(), String> { + if fds.is_empty() { + return self.write_event(client_id, stream, msg, context); + } + let fds_to_send: Vec = fds.drain(..).collect(); + let result = send_with_rights_fds(stream, msg, &fds_to_send); + match result { + Ok(_) => Ok(()), + Err(err) => { + self.disconnect_client( + client_id, + stream, + &format!("{} write failed: {}", context, err), + ); + Err(format!("{} write failed: {}", context, err)) + } + } + } + + fn open_pipe_for_payload(&self, bytes: &[u8]) -> Option { + let mut fds = [0i32; 2]; + let rc = unsafe { libc::pipe(fds.as_mut_ptr()) }; + if rc != 0 { + return None; + } + let write_fd = fds[1]; + let close_on_exec_flag = 0o2000000; + unsafe { + libc::fcntl(write_fd, 2, close_on_exec_flag); + } + let result = unsafe { + libc::write( + write_fd, + bytes.as_ptr() as *const _, + bytes.len(), + ) + }; + unsafe { + libc::close(write_fd); + } + if result < 0 { + unsafe { + libc::close(fds[0]); + } + return None; + } + Some(fds[0]) + } + fn protocol_error( &self, client_id: u32, @@ -3243,14 +3362,95 @@ OBJECT_TYPE_WP_PRESENTATION_FEEDBACK => match opcode { // Accepted — drag-and-drop requires pointer-grab tracking, not yet wired. } WL_DATA_DEVICE_SET_SELECTION => { - // Set the current selection source. // Payload: source_id: u32 (optionally 0 to clear) if let Some(source_id) = read_payload_u32(payload, 0) { + let new_offer_id = self.alloc_id(); + let mime_types: Vec = { + let clients = self.clients.lock().unwrap(); + clients + .get(&client_id) + .and_then(|c| c.data_sources.get(&source_id)) + .map(|s| s.mime_types.clone()) + .unwrap_or_default() + }; + let source_actions = { + let clients = self.clients.lock().unwrap(); + clients + .get(&client_id) + .and_then(|c| c.data_sources.get(&source_id)) + .and_then(|s| s.actions) + }; + let transfer_buffer = { + let clients = self.clients.lock().unwrap(); + clients + .get(&client_id) + .and_then(|c| c.data_sources.get(&source_id)) + .and_then(|s| s.buffer.clone()) + }; let mut clients = self.clients.lock().unwrap(); if let Some(client) = clients.get_mut(&client_id) { + client.objects.insert(new_offer_id, OBJECT_TYPE_WL_DATA_OFFER); + client.data_offers.insert( + new_offer_id, + DataOfferState { + source_client_id: client_id, + source_id, + mime_types: mime_types.clone(), + accepted_mime: None, + actions: source_actions, + buffer: transfer_buffer, + finished: false, + }, + ); let device = client.data_devices.entry(object_id).or_default(); device.selection_source = if source_id != 0 { Some(source_id) } else { None }; + device.selection_offer = if source_id != 0 { Some(new_offer_id) } else { None }; } + drop(clients); + let mut msg = Vec::with_capacity(8 + mime_types.len() * 16); + push_header( + &mut msg, + new_offer_id, + WL_DATA_OFFER_OFFER, + mime_types.len() * 16, + ); + for mt in &mime_types { + let mut padded = [0u8; 16]; + let bytes = mt.as_bytes(); + let copy_len = bytes.len().min(16); + padded[..copy_len].copy_from_slice(&bytes[..copy_len]); + msg.extend_from_slice(&padded); + } + self.write_event(client_id, stream, &msg, "wl_data_offer.offer")?; + if let Some(actions) = source_actions { + let mut msg = Vec::with_capacity(16); + push_header( + &mut msg, + new_offer_id, + WL_DATA_OFFER_SOURCE_ACTIONS, + 4, + ); + push_u32(&mut msg, actions); + self.write_event( + client_id, + stream, + &msg, + "wl_data_offer.source_actions", + )?; + } + let mut msg = Vec::with_capacity(8); + push_header( + &mut msg, + object_id, + WL_DATA_DEVICE_DATA_OFFER, + 4, + ); + push_u32(&mut msg, new_offer_id); + self.write_event(client_id, stream, &msg, "wl_data_device.data_offer")?; + let mut msg = Vec::with_capacity(8); + push_header(&mut msg, object_id, WL_DATA_DEVICE_SELECTION, 4); + push_u32(&mut msg, new_offer_id); + self.write_event(client_id, stream, &msg, "wl_data_device.selection")?; } } _ => { @@ -3260,6 +3460,94 @@ OBJECT_TYPE_WP_PRESENTATION_FEEDBACK => match opcode { ); } }, + 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 mime_type = read_payload_string(&payload[4..]); + 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) { + let _ = serial; + offer.accepted_mime = mime_type.map(|s| s.to_string()); + } + } + } + } + WL_DATA_OFFER_RECEIVE => { + if let Some(mime_bytes) = read_payload_string(payload) { + let mime_str = mime_bytes.to_string(); + let payload_bytes = { + let clients = self.clients.lock().unwrap(); + clients + .get(&client_id) + .and_then(|c| c.data_offers.get(&object_id)) + .filter(|o| o.accepted_mime.as_deref() == Some(mime_str.as_str())) + .and_then(|o| o.buffer.clone()) + }; + match payload_bytes { + Some(bytes) => { + let mut fds = VecDeque::new(); + if let Some(fd) = self.open_pipe_for_payload(&bytes) { + fds.push_back(fd); + } + let mut msg = Vec::with_capacity(8 + mime_bytes.len() + 1); + push_header(&mut msg, object_id, WL_DATA_OFFER_RECEIVE, 0); + let mut padded: Vec = + mime_bytes.bytes().chain(std::iter::once(0)).collect(); + msg.extend_from_slice(&padded); + self.write_event_with_fds( + client_id, + stream, + &msg, + &mut fds, + "wl_data_offer.receive", + )?; + if let Some(fd) = fds.pop_front() { + let _ = unsafe { libc::close(fd) }; + } + } + None => { + let mut msg = Vec::with_capacity(8 + mime_bytes.len() + 1); + push_header(&mut msg, object_id, WL_DATA_OFFER_RECEIVE, 0); + let mut padded: Vec = + mime_bytes.bytes().chain(std::iter::once(0)).collect(); + msg.extend_from_slice(&padded); + self.write_event( + client_id, + stream, + &msg, + "wl_data_offer.receive", + )?; + } + } + } + } + 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.finished = true; + } + } + } + WL_DATA_OFFER_DESTROY => { + let mut clients = self.clients.lock().unwrap(); + if let Some(client) = clients.get_mut(&client_id) { + client.data_offers.remove(&object_id); + } + drop(clients); + self.send_delete_id(client_id, stream, object_id)?; + } + _ => { + eprintln!( + "redbear-compositor: unhandled data_offer opcode {} on object {}", + opcode, object_id + ); + } + }, OBJECT_TYPE_WL_SUBCOMPOSITOR => match opcode { WL_SUBCOMPOSITOR_GET_SUBSURFACE => { // Payload: [new_id: u32][surface_id: u32][parent_id: u32]