mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 02:48:12 +08:00
Desktop: Isolate CEF-rendered UI into separate crate and process (#4321)
* Extract CEF rendered UI into a separate process and crate * Review * Review * Review * Review * Remove necessary workarounds * Block on frame copy ack * Crop and resample frames correctly * Skip blank frames * Fix deps * Fix fmt * Fix clippy warning * Review * Fix todo
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::RecvResult;
|
||||
use crate::frames::surface::FrameSurface;
|
||||
use crate::remote::HostConfig;
|
||||
use crate::remote::messages::EventMessage;
|
||||
pub(crate) const FRAME_SOCKET_CHILD_FD: RawFd = 3;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct FrameDescriptor {
|
||||
seq: u64,
|
||||
modifier: u64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
plane_count: u32,
|
||||
strides: [u32; 4],
|
||||
offsets: [u32; 4],
|
||||
content_x: u32,
|
||||
content_y: u32,
|
||||
content_width: u32,
|
||||
content_height: u32,
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
}
|
||||
|
||||
const DESCRIPTOR_BYTES: usize = std::mem::size_of::<FrameDescriptor>();
|
||||
const MAX_PLANES: usize = 4;
|
||||
|
||||
pub(crate) fn socketpair() -> std::io::Result<(OwnedFd, OwnedFd)> {
|
||||
let mut fds = [0 as RawFd; 2];
|
||||
// SAFETY: socketpair call; on success fds are owned by us.
|
||||
let result = unsafe { libc::socketpair(libc::AF_UNIX, libc::SOCK_SEQPACKET | libc::SOCK_CLOEXEC, 0, fds.as_mut_ptr()) };
|
||||
if result != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
// SAFETY: socketpair succeeded, so both fds are valid and not owned elsewhere.
|
||||
Ok(unsafe { (OwnedFd::from_raw_fd(fds[0]), OwnedFd::from_raw_fd(fds[1])) })
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneSender {
|
||||
socket: OwnedFd,
|
||||
}
|
||||
|
||||
impl PlaneSender {
|
||||
pub(crate) fn from_config(config: &HostConfig, _events: Arc<Mutex<IpcSender<EventMessage>>>) -> Option<Self> {
|
||||
let fd = config.frame_socket_fd?;
|
||||
// SAFETY: the spawner dup2'd this fd for us; nothing else owns it.
|
||||
let socket = unsafe { OwnedFd::from_raw_fd(fd) };
|
||||
|
||||
// Restore CLOEXEC so subprocesses don't inherit the socket.
|
||||
// SAFETY: plain fcntl on an fd we own.
|
||||
if unsafe { libc::fcntl(socket.as_raw_fd(), libc::F_SETFD, libc::FD_CLOEXEC) } != 0 {
|
||||
tracing::warn!("Failed to set CLOEXEC on the frame socket: {}", std::io::Error::last_os_error());
|
||||
}
|
||||
Some(Self { socket })
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self, info: &cef::AcceleratedPaintInfo) -> Option<StagedFrame> {
|
||||
let coded_size = &info.extra.coded_size;
|
||||
if coded_size.width <= 0 || coded_size.height <= 0 {
|
||||
tracing::error!("Accelerated paint delivered an invalid coded size: {}x{}", coded_size.width, coded_size.height);
|
||||
return None;
|
||||
}
|
||||
|
||||
let plane_count = (info.plane_count.max(0) as usize).min(info.planes.len());
|
||||
let mut fds = Vec::with_capacity(plane_count);
|
||||
let mut strides = [0u32; 4];
|
||||
let mut offsets = [0u32; 4];
|
||||
for (i, plane) in info.planes[..plane_count].iter().enumerate() {
|
||||
// SAFETY: CEF keeps the plane fds valid for the `on_accelerated_paint` callback.
|
||||
let fd = unsafe { BorrowedFd::borrow_raw(plane.fd) };
|
||||
match fd.try_clone_to_owned() {
|
||||
Ok(owned) => fds.push(owned),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to duplicate DMA-BUF plane fd: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
strides[i] = plane.stride;
|
||||
offsets[i] = plane.offset as u32;
|
||||
}
|
||||
|
||||
let content = crate::frames::import::ContentRect::try_from(info).unwrap_or_default();
|
||||
Some(StagedFrame {
|
||||
descriptor: FrameDescriptor {
|
||||
seq: 0,
|
||||
modifier: info.modifier,
|
||||
width: coded_size.width as u32,
|
||||
height: coded_size.height as u32,
|
||||
format: *info.format.as_ref() as u32,
|
||||
plane_count: plane_count as u32,
|
||||
strides,
|
||||
offsets,
|
||||
content_x: content.x,
|
||||
content_y: content.y,
|
||||
content_width: content.width,
|
||||
content_height: content.height,
|
||||
source_width: content.source_width,
|
||||
source_height: content.source_height,
|
||||
},
|
||||
fds,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, seq: u64, frame: StagedFrame) -> std::io::Result<()> {
|
||||
let mut descriptor = frame.descriptor;
|
||||
descriptor.seq = seq;
|
||||
|
||||
debug_assert!(frame.fds.len() <= MAX_PLANES);
|
||||
let fd_bytes = frame.fds.len() * std::mem::size_of::<RawFd>();
|
||||
let mut iov = libc::iovec {
|
||||
iov_base: &descriptor as *const FrameDescriptor as *mut libc::c_void,
|
||||
iov_len: DESCRIPTOR_BYTES,
|
||||
};
|
||||
let mut cmsg_buffer = [0u8; unsafe { libc::CMSG_SPACE((MAX_PLANES * std::mem::size_of::<RawFd>()) as u32) } as usize];
|
||||
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
|
||||
msg.msg_iov = &mut iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr().cast();
|
||||
msg.msg_controllen = unsafe { libc::CMSG_SPACE(fd_bytes as u32) } as _;
|
||||
unsafe {
|
||||
let cmsg = libc::CMSG_FIRSTHDR(&msg);
|
||||
(*cmsg).cmsg_level = libc::SOL_SOCKET;
|
||||
(*cmsg).cmsg_type = libc::SCM_RIGHTS;
|
||||
(*cmsg).cmsg_len = libc::CMSG_LEN(fd_bytes as u32) as _;
|
||||
let data = libc::CMSG_DATA(cmsg) as *mut RawFd;
|
||||
for (i, fd) in frame.fds.iter().enumerate() {
|
||||
data.add(i).write_unaligned(fd.as_raw_fd());
|
||||
}
|
||||
}
|
||||
loop {
|
||||
// SAFETY: msg and everything it points to are valid for the duration of the call.
|
||||
let sent = unsafe { libc::sendmsg(self.socket.as_raw_fd(), &msg, libc::MSG_NOSIGNAL) };
|
||||
if sent >= 0 {
|
||||
return Ok(());
|
||||
}
|
||||
let error = std::io::Error::last_os_error();
|
||||
if error.kind() != std::io::ErrorKind::Interrupted {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StagedFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
fds: Vec<OwnedFd>,
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneReceiver {
|
||||
socket: OwnedFd,
|
||||
}
|
||||
|
||||
impl PlaneReceiver {
|
||||
pub(crate) fn new(socket: OwnedFd) -> Self {
|
||||
Self { socket }
|
||||
}
|
||||
|
||||
pub(crate) fn recv_blocking(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(false)
|
||||
}
|
||||
|
||||
pub(crate) fn try_recv(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(true)
|
||||
}
|
||||
|
||||
fn recv(&self, nonblocking: bool) -> std::io::Result<RecvResult> {
|
||||
let mut descriptor: FrameDescriptor = bytemuck::Zeroable::zeroed();
|
||||
let mut iov = libc::iovec {
|
||||
iov_base: (&mut descriptor as *mut FrameDescriptor).cast(),
|
||||
iov_len: DESCRIPTOR_BYTES,
|
||||
};
|
||||
// SAFETY: pure size computation.
|
||||
let mut cmsg_buffer = [0u8; unsafe { libc::CMSG_SPACE((MAX_PLANES * std::mem::size_of::<RawFd>()) as u32) } as usize];
|
||||
|
||||
let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
|
||||
msg.msg_iov = &mut iov;
|
||||
msg.msg_iovlen = 1;
|
||||
msg.msg_control = cmsg_buffer.as_mut_ptr().cast();
|
||||
msg.msg_controllen = cmsg_buffer.len() as _;
|
||||
|
||||
let flags = libc::MSG_CMSG_CLOEXEC | if nonblocking { libc::MSG_DONTWAIT } else { 0 };
|
||||
let received = loop {
|
||||
// SAFETY: msg and everything it points to are valid for the duration of the call.
|
||||
let received = unsafe { libc::recvmsg(self.socket.as_raw_fd(), &mut msg, flags) };
|
||||
if received >= 0 {
|
||||
break received;
|
||||
}
|
||||
let error = std::io::Error::last_os_error();
|
||||
match error.kind() {
|
||||
std::io::ErrorKind::Interrupted => continue,
|
||||
std::io::ErrorKind::WouldBlock => return Ok(RecvResult::WouldBlock),
|
||||
_ => return Err(error),
|
||||
}
|
||||
};
|
||||
|
||||
let mut fds = Vec::new();
|
||||
// SAFETY: traversing the cmsgs recvmsg just filled; SCM_RIGHTS payload is fds now owned by us.
|
||||
unsafe {
|
||||
let mut cmsg = libc::CMSG_FIRSTHDR(&msg);
|
||||
while !cmsg.is_null() {
|
||||
if (*cmsg).cmsg_level == libc::SOL_SOCKET && (*cmsg).cmsg_type == libc::SCM_RIGHTS {
|
||||
let data = libc::CMSG_DATA(cmsg) as *const RawFd;
|
||||
let count = ((*cmsg).cmsg_len as usize - libc::CMSG_LEN(0) as usize) / std::mem::size_of::<RawFd>();
|
||||
for i in 0..count {
|
||||
fds.push(OwnedFd::from_raw_fd(data.add(i).read_unaligned()));
|
||||
}
|
||||
}
|
||||
cmsg = libc::CMSG_NXTHDR(&msg, cmsg);
|
||||
}
|
||||
}
|
||||
|
||||
if received == 0 {
|
||||
return Ok(RecvResult::Closed);
|
||||
}
|
||||
if received as usize != DESCRIPTOR_BYTES || (msg.msg_flags & libc::MSG_CTRUNC) != 0 {
|
||||
return Err(std::io::Error::other(format!(
|
||||
"malformed frame message: {received} bytes, flags {:#x} ({} fds)",
|
||||
msg.msg_flags,
|
||||
fds.len()
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(RecvResult::Frame(WireFrame { descriptor, fds }))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WireFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
fds: Vec<OwnedFd>,
|
||||
}
|
||||
|
||||
impl WireFrame {
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.descriptor.seq
|
||||
}
|
||||
|
||||
pub(crate) fn import(self, surface: &FrameSurface) -> Option<wgpu::Texture> {
|
||||
let descriptor = self.descriptor;
|
||||
let format = super::wire_color_type(descriptor.format)?;
|
||||
let plane_count = (descriptor.plane_count as usize).min(self.fds.len());
|
||||
let content = crate::frames::import::ContentRect {
|
||||
x: descriptor.content_x,
|
||||
y: descriptor.content_y,
|
||||
width: descriptor.content_width,
|
||||
height: descriptor.content_height,
|
||||
source_width: descriptor.source_width,
|
||||
source_height: descriptor.source_height,
|
||||
};
|
||||
let importer = crate::frames::import::dmabuf::DmaBufImporter::from_parts(
|
||||
self.fds,
|
||||
descriptor.strides[..plane_count].to_vec(),
|
||||
descriptor.offsets[..plane_count].to_vec(),
|
||||
descriptor.modifier,
|
||||
descriptor.width,
|
||||
descriptor.height,
|
||||
format,
|
||||
);
|
||||
surface.import_texture(importer, content)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::ffi::CString;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use mach2::kern_return::KERN_SUCCESS;
|
||||
use mach2::message::{
|
||||
MACH_MSG_PORT_DESCRIPTOR, MACH_MSG_SUCCESS, MACH_MSG_TIMEOUT_NONE, MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MOVE_SEND, MACH_MSGH_BITS_COMPLEX, MACH_RCV_MSG, MACH_RCV_TIMED_OUT, MACH_RCV_TIMEOUT,
|
||||
MACH_SEND_MSG, mach_msg, mach_msg_body_t, mach_msg_header_t,
|
||||
};
|
||||
use mach2::port::{MACH_PORT_NULL, mach_port_t};
|
||||
use mach2::traps::mach_task_self;
|
||||
use objc2_io_surface::IOSurfaceRef;
|
||||
|
||||
use super::RecvResult;
|
||||
use crate::frames::surface::FrameSurface;
|
||||
use crate::remote::HostConfig;
|
||||
use crate::remote::messages::EventMessage;
|
||||
|
||||
// From libSystem, stable since 10.0, not coverd by mach2
|
||||
unsafe extern "C" {
|
||||
static bootstrap_port: mach_port_t;
|
||||
fn bootstrap_check_in(bp: mach_port_t, service_name: *const std::ffi::c_char, sp: *mut mach_port_t) -> mach2::kern_return::kern_return_t;
|
||||
fn bootstrap_look_up(bp: mach_port_t, service_name: *const std::ffi::c_char, sp: *mut mach_port_t) -> mach2::kern_return::kern_return_t;
|
||||
}
|
||||
|
||||
// `mach_msg_port_descriptor_t` kernel ABI
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct PortDescriptor {
|
||||
name: mach_port_t,
|
||||
pad1: u32,
|
||||
pad2: u16,
|
||||
disposition: u8,
|
||||
descriptor_type: u8,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct FrameDescriptor {
|
||||
seq: u64,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
content_x: u32,
|
||||
content_y: u32,
|
||||
content_width: u32,
|
||||
content_height: u32,
|
||||
source_width: u32,
|
||||
source_height: u32,
|
||||
_pad: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct FrameMessage {
|
||||
header: mach_msg_header_t,
|
||||
body: mach_msg_body_t,
|
||||
surface: PortDescriptor,
|
||||
descriptor: FrameDescriptor,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct FrameMessageBuffer {
|
||||
message: FrameMessage,
|
||||
trailer: [u8; 64],
|
||||
}
|
||||
|
||||
struct SendRight(mach_port_t);
|
||||
|
||||
// SAFETY: mach port names are task-wide; rights may be used from any thread.
|
||||
unsafe impl Send for SendRight {}
|
||||
unsafe impl Sync for SendRight {}
|
||||
|
||||
impl Drop for SendRight {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: we own one reference on this send right.
|
||||
unsafe { mach2::mach_port::mach_port_deallocate(mach_task_self(), self.0) };
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_service(name: &str) -> std::io::Result<mach_port_t> {
|
||||
let c_name = CString::new(name).map_err(std::io::Error::other)?;
|
||||
let mut port: mach_port_t = MACH_PORT_NULL;
|
||||
// SAFETY: plain bootstrap call; on success we own the service's receive right.
|
||||
let result = unsafe { bootstrap_check_in(bootstrap_port, c_name.as_ptr(), &mut port) };
|
||||
if result != KERN_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("bootstrap_check_in failed: {result:#x}")));
|
||||
}
|
||||
Ok(port)
|
||||
}
|
||||
|
||||
fn look_up_service(name: &str) -> std::io::Result<SendRight> {
|
||||
let c_name = CString::new(name).map_err(std::io::Error::other)?;
|
||||
let mut port: mach_port_t = MACH_PORT_NULL;
|
||||
// SAFETY: plain bootstrap call; on success we own a send right.
|
||||
let result = unsafe { bootstrap_look_up(bootstrap_port, c_name.as_ptr(), &mut port) };
|
||||
if result != KERN_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("bootstrap_look_up failed: {result:#x}")));
|
||||
}
|
||||
Ok(SendRight(port))
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneSender {
|
||||
service: SendRight,
|
||||
}
|
||||
|
||||
impl PlaneSender {
|
||||
pub(crate) fn from_config(config: &HostConfig, _events: Arc<Mutex<IpcSender<EventMessage>>>) -> Option<Self> {
|
||||
let name = config.frame_service.as_deref()?;
|
||||
match look_up_service(name) {
|
||||
Ok(service) => Some(Self { service }),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to look up the accelerated frame service, falling back to software frames: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self, info: &cef::AcceleratedPaintInfo) -> Option<StagedFrame> {
|
||||
let coded_size = &info.extra.coded_size;
|
||||
if coded_size.width <= 0 || coded_size.height <= 0 {
|
||||
tracing::error!("Accelerated paint delivered an invalid coded size: {}x{}", coded_size.width, coded_size.height);
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(surface) = std::ptr::NonNull::new(info.shared_texture_io_surface.cast::<IOSurfaceRef>()) else {
|
||||
tracing::error!("Accelerated paint delivered a null IOSurface");
|
||||
return None;
|
||||
};
|
||||
|
||||
// SAFETY: CEF keeps the surface valid for the `on_accelerated_paint` callback.
|
||||
let port = unsafe { surface.as_ref() }.create_mach_port();
|
||||
if port == MACH_PORT_NULL {
|
||||
tracing::error!("Failed to wrap the IOSurface in a mach port");
|
||||
return None;
|
||||
}
|
||||
|
||||
let content = crate::frames::import::ContentRect::try_from(info).unwrap_or_default();
|
||||
Some(StagedFrame {
|
||||
descriptor: FrameDescriptor {
|
||||
seq: 0,
|
||||
width: coded_size.width as u32,
|
||||
height: coded_size.height as u32,
|
||||
format: *info.format.as_ref() as u32,
|
||||
content_x: content.x,
|
||||
content_y: content.y,
|
||||
content_width: content.width,
|
||||
content_height: content.height,
|
||||
source_width: content.source_width,
|
||||
source_height: content.source_height,
|
||||
_pad: 0,
|
||||
},
|
||||
surface: SendRight(port),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, seq: u64, frame: StagedFrame) -> std::io::Result<()> {
|
||||
let mut descriptor = frame.descriptor;
|
||||
descriptor.seq = seq;
|
||||
let mut message = FrameMessage {
|
||||
header: mach_msg_header_t {
|
||||
msgh_bits: MACH_MSG_TYPE_COPY_SEND | MACH_MSGH_BITS_COMPLEX,
|
||||
msgh_size: std::mem::size_of::<FrameMessage>() as u32,
|
||||
msgh_remote_port: self.service.0,
|
||||
msgh_local_port: MACH_PORT_NULL,
|
||||
msgh_voucher_port: MACH_PORT_NULL,
|
||||
msgh_id: 0,
|
||||
},
|
||||
body: mach_msg_body_t { msgh_descriptor_count: 1 },
|
||||
surface: PortDescriptor {
|
||||
name: frame.surface.0,
|
||||
pad1: 0,
|
||||
pad2: 0,
|
||||
disposition: MACH_MSG_TYPE_MOVE_SEND as u8,
|
||||
descriptor_type: MACH_MSG_PORT_DESCRIPTOR as u8,
|
||||
},
|
||||
descriptor,
|
||||
};
|
||||
|
||||
// SAFETY: message is a well-formed complex message of the declared size.
|
||||
let result = unsafe {
|
||||
mach_msg(
|
||||
&mut message.header,
|
||||
MACH_SEND_MSG,
|
||||
std::mem::size_of::<FrameMessage>() as u32,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
MACH_MSG_TIMEOUT_NONE,
|
||||
MACH_PORT_NULL,
|
||||
)
|
||||
};
|
||||
if result != MACH_MSG_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("mach_msg send failed: {result:#x}")));
|
||||
}
|
||||
|
||||
// Kernel took ownership of the surface and moves it to the receiver. We must not drop.
|
||||
std::mem::forget(frame.surface);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StagedFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
surface: SendRight,
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneReceiver {
|
||||
port: mach_port_t,
|
||||
}
|
||||
|
||||
impl PlaneReceiver {
|
||||
pub(crate) fn new(port: mach_port_t) -> Self {
|
||||
Self { port }
|
||||
}
|
||||
|
||||
pub(crate) fn recv_blocking(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(false)
|
||||
}
|
||||
|
||||
pub(crate) fn try_recv(&self) -> std::io::Result<RecvResult> {
|
||||
self.recv(true)
|
||||
}
|
||||
|
||||
fn recv(&self, nonblocking: bool) -> std::io::Result<RecvResult> {
|
||||
// SAFETY: zeroed is a valid representation for these plain-data structs.
|
||||
let mut buffer: FrameMessageBuffer = unsafe { std::mem::zeroed() };
|
||||
let (options, timeout) = if nonblocking {
|
||||
(MACH_RCV_MSG | MACH_RCV_TIMEOUT, 0)
|
||||
} else {
|
||||
(MACH_RCV_MSG, MACH_MSG_TIMEOUT_NONE)
|
||||
};
|
||||
|
||||
// SAFETY: the buffer is large enough for the message plus the basic trailer.
|
||||
let result = unsafe {
|
||||
mach_msg(
|
||||
&mut buffer.message.header,
|
||||
options,
|
||||
0,
|
||||
std::mem::size_of::<FrameMessageBuffer>() as u32,
|
||||
self.port,
|
||||
timeout,
|
||||
MACH_PORT_NULL,
|
||||
)
|
||||
};
|
||||
if result == MACH_RCV_TIMED_OUT {
|
||||
return Ok(RecvResult::WouldBlock);
|
||||
}
|
||||
if result != MACH_MSG_SUCCESS {
|
||||
return Err(std::io::Error::other(format!("mach_msg receive failed: {result:#x}")));
|
||||
}
|
||||
|
||||
let received_complex = buffer.message.header.msgh_bits & MACH_MSGH_BITS_COMPLEX != 0;
|
||||
let descriptor_count = if received_complex { buffer.message.body.msgh_descriptor_count } else { 0 };
|
||||
let surface = (descriptor_count == 1 && buffer.message.surface.descriptor_type == MACH_MSG_PORT_DESCRIPTOR as u8).then(|| SendRight(buffer.message.surface.name));
|
||||
|
||||
if buffer.message.header.msgh_size as usize != std::mem::size_of::<FrameMessage>() {
|
||||
return Err(std::io::Error::other(format!("malformed frame message: {} bytes", buffer.message.header.msgh_size)));
|
||||
}
|
||||
let Some(surface) = surface else {
|
||||
return Err(std::io::Error::other("frame message carried no surface port"));
|
||||
};
|
||||
|
||||
Ok(RecvResult::Frame(WireFrame {
|
||||
descriptor: buffer.message.descriptor,
|
||||
surface,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WireFrame {
|
||||
descriptor: FrameDescriptor,
|
||||
surface: SendRight,
|
||||
}
|
||||
|
||||
impl WireFrame {
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.descriptor.seq
|
||||
}
|
||||
|
||||
pub(crate) fn import(self, surface: &FrameSurface) -> Option<wgpu::Texture> {
|
||||
let WireFrame { descriptor, surface: port } = self;
|
||||
let format = super::wire_color_type(descriptor.format)?;
|
||||
|
||||
// Lookup takes its own reference on the surface, port can be dropped.
|
||||
let io_surface = IOSurfaceRef::lookup_from_mach_port(port.0);
|
||||
drop(port);
|
||||
|
||||
let Some(io_surface) = io_surface else {
|
||||
tracing::error!("Failed to look up the IOSurface for frame {}", descriptor.seq);
|
||||
return None;
|
||||
};
|
||||
let io_surface_ref: &IOSurfaceRef = &io_surface;
|
||||
|
||||
let content = crate::frames::import::ContentRect {
|
||||
x: descriptor.content_x,
|
||||
y: descriptor.content_y,
|
||||
width: descriptor.content_width,
|
||||
height: descriptor.content_height,
|
||||
source_width: descriptor.source_width,
|
||||
source_height: descriptor.source_height,
|
||||
};
|
||||
let importer = crate::frames::import::iosurface::IOSurfaceImporter::from_parts(io_surface_ref as *const _ as *mut std::os::raw::c_void, descriptor.width, descriptor.height, format);
|
||||
surface.import_texture(importer, content)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use windows::Win32::Foundation::{CloseHandle, DUPLICATE_CLOSE_SOURCE, DUPLICATE_SAME_ACCESS, DuplicateHandle, HANDLE};
|
||||
use windows::Win32::System::Threading::{GetCurrentProcess, OpenProcess, PROCESS_DUP_HANDLE};
|
||||
|
||||
use crate::frames::import::ContentRect;
|
||||
use crate::frames::surface::FrameSurface;
|
||||
use crate::remote::HostConfig;
|
||||
use crate::remote::messages::EventMessage;
|
||||
|
||||
struct MainProcess(HANDLE);
|
||||
|
||||
// SAFETY: process handles may be used and closed from any thread.
|
||||
unsafe impl Send for MainProcess {}
|
||||
unsafe impl Sync for MainProcess {}
|
||||
|
||||
impl MainProcess {
|
||||
fn open(pid: u32) -> windows::core::Result<Self> {
|
||||
// SAFETY: plain OpenProcess call; on success the handle is ours to close.
|
||||
unsafe { OpenProcess(PROCESS_DUP_HANDLE, false, pid).map(Self) }
|
||||
}
|
||||
|
||||
fn duplicate_into(&self, handle: HANDLE) -> windows::core::Result<u64> {
|
||||
let mut target = HANDLE::default();
|
||||
// SAFETY: both process handles are valid; `target` receives the duplicate.
|
||||
unsafe { DuplicateHandle(GetCurrentProcess(), handle, self.0, &mut target, 0, false, DUPLICATE_SAME_ACCESS)? };
|
||||
Ok(target.0 as u64)
|
||||
}
|
||||
|
||||
fn close_in_main(&self, handle: u64) {
|
||||
let mut reclaimed = HANDLE::default();
|
||||
// SAFETY: `handle` came from `duplicate_into` and is valid.
|
||||
unsafe {
|
||||
if let Err(e) = DuplicateHandle(self.0, HANDLE(handle as _), GetCurrentProcess(), &mut reclaimed, 0, false, DUPLICATE_CLOSE_SOURCE) {
|
||||
tracing::warn!("Failed to reclaim a frame handle from the main process: {e}");
|
||||
}
|
||||
if !reclaimed.is_invalid() {
|
||||
let _ = CloseHandle(reclaimed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MainProcess {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: we own the process handle.
|
||||
unsafe {
|
||||
let _ = CloseHandle(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PlaneSender {
|
||||
main: Arc<MainProcess>,
|
||||
events: Arc<Mutex<IpcSender<EventMessage>>>,
|
||||
}
|
||||
|
||||
impl PlaneSender {
|
||||
pub(crate) fn from_config(config: &HostConfig, events: Arc<Mutex<IpcSender<EventMessage>>>) -> Option<Self> {
|
||||
match MainProcess::open(config.main_pid) {
|
||||
Ok(main) => Some(Self { main: Arc::new(main), events }),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to open the main process for handle duplication, falling back to software frames: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn stage(&self, info: &cef::AcceleratedPaintInfo) -> Option<StagedFrame> {
|
||||
let coded_size = &info.extra.coded_size;
|
||||
if coded_size.width <= 0 || coded_size.height <= 0 {
|
||||
tracing::error!("Accelerated paint delivered an invalid coded size: {}x{}", coded_size.width, coded_size.height);
|
||||
return None;
|
||||
}
|
||||
|
||||
let handle = match self.main.duplicate_into(HANDLE(info.shared_texture_handle)) {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to duplicate the shared texture handle into the main process: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
Some(StagedFrame {
|
||||
handle: HandleInMain { handle, main: self.main.clone() },
|
||||
width: coded_size.width as u32,
|
||||
height: coded_size.height as u32,
|
||||
format: *info.format.as_ref() as u32,
|
||||
content: ContentRect::try_from(info).ok(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, seq: u64, frame: StagedFrame) -> std::io::Result<()> {
|
||||
let message = EventMessage::AcceleratedFrame {
|
||||
seq,
|
||||
handle: frame.handle.handle,
|
||||
width: frame.width,
|
||||
height: frame.height,
|
||||
format: frame.format,
|
||||
content: frame.content,
|
||||
};
|
||||
let sender = self.events.lock().map_err(|_| std::io::Error::other("the host message sender lock is poisoned"))?;
|
||||
match sender.send(message) {
|
||||
Ok(()) => {
|
||||
// Dropping the handle would reclaim it. We must not drop.
|
||||
std::mem::forget(frame.handle);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(std::io::Error::other(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct StagedFrame {
|
||||
handle: HandleInMain,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
content: Option<ContentRect>,
|
||||
}
|
||||
|
||||
struct HandleInMain {
|
||||
handle: u64,
|
||||
main: Arc<MainProcess>,
|
||||
}
|
||||
|
||||
impl Drop for HandleInMain {
|
||||
fn drop(&mut self) {
|
||||
self.main.close_in_main(self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WireFrame {
|
||||
seq: u64,
|
||||
handle: ReceivedHandle,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: u32,
|
||||
content: Option<ContentRect>,
|
||||
}
|
||||
|
||||
impl WireFrame {
|
||||
pub(crate) fn new(seq: u64, handle: u64, width: u32, height: u32, format: u32, content: Option<ContentRect>) -> Self {
|
||||
Self {
|
||||
seq,
|
||||
handle: ReceivedHandle(handle),
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn seq(&self) -> u64 {
|
||||
self.seq
|
||||
}
|
||||
|
||||
pub(crate) fn import(self, surface: &FrameSurface) -> Option<wgpu::Texture> {
|
||||
let format = super::wire_color_type(self.format)?;
|
||||
let content = self.content.unwrap_or_default();
|
||||
surface.import_texture(crate::frames::import::d3d11::D3D11Importer::from_parts(self.handle.0, self.width, self.height, format), content)
|
||||
}
|
||||
}
|
||||
|
||||
struct ReceivedHandle(u64);
|
||||
|
||||
impl Drop for ReceivedHandle {
|
||||
fn drop(&mut self) {
|
||||
// SAFETY: the host duplicated this handle into our process for us to own.
|
||||
if let Err(e) = unsafe { CloseHandle(HANDLE(self.0 as _)) } {
|
||||
tracing::warn!("Failed to close a remote frame handle: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user