#[path = "generated/xlib.rs"]
#[allow(
dead_code,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]
mod xlib;
use std::cmp::max;
use std::ffi::c_void;
use std::ffi::CStr;
use std::ffi::CString;
use std::mem::transmute_copy;
use std::mem::zeroed;
use std::os::raw::c_ulong;
use std::ptr::null;
use std::ptr::null_mut;
use std::ptr::NonNull;
use std::rc::Rc;
use base::AsRawDescriptor;
use base::RawDescriptor;
use base::VolatileSlice;
use libc::shmat;
use libc::shmctl;
use libc::shmdt;
use libc::shmget;
use libc::IPC_CREAT;
use libc::IPC_PRIVATE;
use libc::IPC_RMID;
use linux_input_sys::virtio_input_event;
use vm_control::gpu::DisplayParameters;
use crate::keycode_converter::KeycodeTranslator;
use crate::keycode_converter::KeycodeTypes;
use crate::DisplayT;
use crate::EventDeviceKind;
use crate::GpuDisplayError;
use crate::GpuDisplayEvents;
use crate::GpuDisplayFramebuffer;
use crate::GpuDisplayResult;
use crate::GpuDisplaySurface;
use crate::SurfaceType;
use crate::SysDisplayT;
const BUFFER_COUNT: usize = 2;
unsafe fn x_free<T>(t: *mut T) {
xlib::XFree(t as *mut c_void);
}
#[derive(Clone)]
struct XDisplay(Rc<NonNull<xlib::Display>>);
impl Drop for XDisplay {
fn drop(&mut self) {
if Rc::strong_count(&self.0) == 1 {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XCloseDisplay(self.as_ptr());
}
}
}
}
impl XDisplay {
fn as_ptr(&self) -> *mut xlib::Display {
self.0.as_ptr()
}
fn flush(&self) {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XFlush(self.as_ptr());
}
}
fn supports_shm(&self) -> bool {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XShmQueryExtension(self.as_ptr()) != 0
}
}
fn default_screen(&self) -> Option<XScreen> {
Some(XScreen(NonNull::new(
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XDefaultScreenOfDisplay(self.as_ptr())
},
)?))
}
fn next_event(&self) -> XEvent {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
let mut ev = zeroed();
xlib::XNextEvent(self.as_ptr(), &mut ev);
ev.into()
}
}
}
impl AsRawDescriptor for XDisplay {
fn as_raw_descriptor(&self) -> RawDescriptor {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XConnectionNumber(self.as_ptr())
}
}
}
struct XEvent(xlib::XEvent);
impl From<xlib::XEvent> for XEvent {
fn from(ev: xlib::XEvent) -> XEvent {
XEvent(ev)
}
}
impl XEvent {
fn any(&self) -> xlib::XAnyEvent {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
self.0.xany
}
}
fn type_(&self) -> u32 {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
self.0.type_ as u32
}
}
fn window(&self) -> xlib::Window {
self.any().window
}
fn as_enum(&self, shm_complete_type: u32) -> XEventEnum {
match self.type_() {
xlib::KeyPress | xlib::KeyRelease => XEventEnum::KeyEvent(
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
self.0.xkey
},
),
xlib::ButtonPress => {
#[allow(clippy::undocumented_unsafe_blocks)]
XEventEnum::ButtonEvent {
event: unsafe { self.0.xbutton },
pressed: true,
}
}
xlib::ButtonRelease => {
#[allow(clippy::undocumented_unsafe_blocks)]
XEventEnum::ButtonEvent {
event: unsafe { self.0.xbutton },
pressed: false,
}
}
xlib::MotionNotify => XEventEnum::Motion(
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
self.0.xmotion
},
),
xlib::Expose => XEventEnum::Expose,
xlib::ClientMessage => {
XEventEnum::ClientMessage(
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
self.0.xclient.data.l[0] as u64
},
)
}
t if t == shm_complete_type => {
#[allow(clippy::undocumented_unsafe_blocks)]
let ev_completion: xlib::XShmCompletionEvent = unsafe { transmute_copy(&self.0) };
XEventEnum::ShmCompletionEvent(ev_completion.shmseg)
}
_ => XEventEnum::Unhandled,
}
}
}
enum XEventEnum {
KeyEvent(xlib::XKeyEvent),
ButtonEvent {
event: xlib::XButtonEvent,
pressed: bool,
},
Motion(xlib::XMotionEvent),
Expose,
ClientMessage(u64),
ShmCompletionEvent(xlib::ShmSeg),
Unhandled,
}
struct XScreen(NonNull<xlib::Screen>);
impl XScreen {
fn as_ptr(&self) -> *mut xlib::Screen {
self.0.as_ptr()
}
fn get_number(&self) -> i32 {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XScreenNumberOfScreen(self.as_ptr())
}
}
}
struct Buffer {
display: XDisplay,
image: *mut xlib::XImage,
segment_info: Box<xlib::XShmSegmentInfo>,
size: usize,
in_use: bool,
}
impl Drop for Buffer {
fn drop(&mut self) {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XShmDetach(self.display.as_ptr(), self.segment_info.as_mut());
xlib::XDestroyImage(self.image);
shmdt(self.segment_info.shmaddr as *const _);
shmctl(self.segment_info.shmid, IPC_RMID, null_mut());
}
}
}
impl Buffer {
fn as_volatile_slice(&self) -> VolatileSlice {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
VolatileSlice::from_raw_parts(self.segment_info.shmaddr as *mut _, self.size)
}
}
fn stride(&self) -> usize {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
(*self.image).bytes_per_line as usize
}
}
fn bytes_per_pixel(&self) -> usize {
#[allow(clippy::undocumented_unsafe_blocks)]
let bytes_per_pixel = unsafe { (*self.image).bits_per_pixel / 8 };
bytes_per_pixel as usize
}
}
struct XSurface {
display: XDisplay,
visual: *mut xlib::Visual,
depth: u32,
window: xlib::Window,
gc: xlib::GC,
width: u32,
height: u32,
buffers: [Option<Buffer>; BUFFER_COUNT],
buffer_next: usize,
buffer_completion_type: u32,
delete_window_atom: c_ulong,
close_requested: bool,
}
impl XSurface {
fn current_buffer(&self) -> usize {
match self.buffer_next.checked_sub(1) {
Some(i) => i,
None => self.buffers.len() - 1,
}
}
fn draw_buffer(&mut self, buffer_index: usize) {
let buffer = match self.buffers.get_mut(buffer_index) {
Some(Some(b)) => b,
_ => {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XClearWindow(self.display.as_ptr(), self.window);
}
return;
}
};
buffer.in_use = true;
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XShmPutImage(
self.display.as_ptr(),
self.window,
self.gc,
buffer.image,
0, 0, 0, 0, self.width,
self.height,
true as i32, );
self.display.flush();
}
}
fn lazily_allocate_buffer(&mut self, buffer_index: usize) -> Option<&Buffer> {
if buffer_index >= self.buffers.len() {
return None;
}
if self.buffers[buffer_index].is_some() {
return self.buffers[buffer_index].as_ref();
}
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
let mut segment_info: Box<xlib::XShmSegmentInfo> = Box::new(zeroed());
let image = xlib::XShmCreateImage(
self.display.as_ptr(),
self.visual,
self.depth,
xlib::ZPixmap as i32,
null_mut(),
segment_info.as_mut(),
self.width,
self.height,
);
if image.is_null() {
return None;
}
let size = (*image)
.bytes_per_line
.checked_mul((*image).height)
.unwrap();
segment_info.shmid = shmget(IPC_PRIVATE, size as usize, IPC_CREAT | 0o777);
if segment_info.shmid == -1 {
xlib::XDestroyImage(image);
return None;
}
segment_info.shmaddr = shmat(segment_info.shmid, null_mut(), 0) as *mut _;
if segment_info.shmaddr == (-1isize) as *mut _ {
xlib::XDestroyImage(image);
shmctl(segment_info.shmid, IPC_RMID, null_mut());
return None;
}
(*image).data = segment_info.shmaddr;
segment_info.readOnly = true as i32;
xlib::XShmAttach(self.display.as_ptr(), segment_info.as_mut());
self.buffers[buffer_index] = Some(Buffer {
display: self.display.clone(),
image,
segment_info,
size: size as usize,
in_use: false,
});
self.buffers[buffer_index].as_ref()
}
}
}
impl GpuDisplaySurface for XSurface {
#[allow(clippy::unnecessary_cast)]
fn surface_descriptor(&self) -> u64 {
self.window as u64
}
fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
let framebuffer = self.lazily_allocate_buffer(self.buffer_next)?;
let bytes_per_pixel = framebuffer.bytes_per_pixel() as u32;
Some(GpuDisplayFramebuffer::new(
framebuffer.as_volatile_slice(),
framebuffer.stride() as u32,
bytes_per_pixel,
))
}
fn next_buffer_in_use(&self) -> bool {
self.buffers
.get(self.buffer_next)
.and_then(|b| Some(b.as_ref()?.in_use))
.unwrap_or(false)
}
fn close_requested(&self) -> bool {
self.close_requested
}
fn flip(&mut self) {
let current_buffer_index = self.buffer_next;
self.buffer_next = (self.buffer_next + 1) % self.buffers.len();
self.draw_buffer(current_buffer_index);
}
fn buffer_completion_type(&self) -> u32 {
self.buffer_completion_type
}
fn draw_current_buffer(&mut self) {
self.draw_buffer(self.current_buffer())
}
fn on_client_message(&mut self, client_data: u64) {
if client_data == self.delete_window_atom {
self.close_requested = true;
}
}
fn on_shm_completion(&mut self, shm_complete: u64) {
for buffer in self.buffers.iter_mut().flatten() {
if buffer.segment_info.shmseg == shm_complete {
buffer.in_use = false;
}
}
}
}
impl Drop for XSurface {
fn drop(&mut self) {
unsafe {
xlib::XFreeGC(self.display.as_ptr(), self.gc);
xlib::XDestroyWindow(self.display.as_ptr(), self.window);
}
}
}
pub struct DisplayX {
display: XDisplay,
screen: XScreen,
visual: *mut xlib::Visual,
keycode_translator: KeycodeTranslator,
current_event: Option<XEvent>,
mt_tracking_id: u16,
}
impl DisplayX {
pub fn open_display(display: Option<&str>) -> GpuDisplayResult<DisplayX> {
let display_cstr = match display.map(CString::new) {
Some(Ok(s)) => Some(s),
Some(Err(_)) => return Err(GpuDisplayError::InvalidPath),
None => None,
};
let keycode_translator = KeycodeTranslator::new(KeycodeTypes::XkbScancode);
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
let display = match NonNull::new(xlib::XOpenDisplay(
display_cstr
.as_ref()
.map(|s| CStr::as_ptr(s))
.unwrap_or(null()),
)) {
Some(display_ptr) => XDisplay(Rc::new(display_ptr)),
None => return Err(GpuDisplayError::Connect),
};
if !display.supports_shm() {
return Err(GpuDisplayError::RequiredFeature("xshm extension"));
}
let screen = display
.default_screen()
.ok_or(GpuDisplayError::Connect)
.unwrap();
let screen_number = screen.get_number();
let mut visual_info_template = xlib::XVisualInfo {
visual: null_mut(),
visualid: 0,
screen: screen_number,
depth: 24,
class: 0,
red_mask: 0x00ff0000,
green_mask: 0x0000ff00,
blue_mask: 0x000000ff,
colormap_size: 0,
bits_per_rgb: 0,
};
let visual_info = xlib::XGetVisualInfo(
display.as_ptr(),
(xlib::VisualScreenMask
| xlib::VisualDepthMask
| xlib::VisualRedMaskMask
| xlib::VisualGreenMaskMask
| xlib::VisualBlueMaskMask) as i64,
&mut visual_info_template,
&mut 0,
);
if visual_info.is_null() {
return Err(GpuDisplayError::RequiredFeature("no matching visual"));
}
let visual = (*visual_info).visual;
x_free(visual_info);
Ok(DisplayX {
display,
screen,
visual,
keycode_translator,
current_event: None,
mt_tracking_id: 0,
})
}
}
pub fn next_tracking_id(&mut self) -> i32 {
let cur_id: i32 = self.mt_tracking_id as i32;
self.mt_tracking_id = self.mt_tracking_id.wrapping_add(1);
cur_id
}
pub fn current_tracking_id(&self) -> i32 {
self.mt_tracking_id as i32
}
}
impl DisplayT for DisplayX {
fn pending_events(&self) -> bool {
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
xlib::XPending(self.display.as_ptr()) != 0
}
}
fn flush(&self) {
self.display.flush();
}
#[allow(clippy::unnecessary_cast)]
fn next_event(&mut self) -> GpuDisplayResult<u64> {
let ev = self.display.next_event();
let descriptor = ev.window() as u64;
self.current_event = Some(ev);
Ok(descriptor)
}
fn handle_next_event(
&mut self,
surface: &mut Box<dyn GpuDisplaySurface>,
) -> Option<GpuDisplayEvents> {
let ev = self.current_event.take().unwrap();
match ev.as_enum(surface.buffer_completion_type()) {
XEventEnum::KeyEvent(key) => {
if let Some(linux_keycode) = self.keycode_translator.translate(key.keycode) {
let events = vec![virtio_input_event::key(
linux_keycode,
key.type_ == xlib::KeyPress as i32,
false,
)];
return Some(GpuDisplayEvents {
events,
device_type: EventDeviceKind::Keyboard,
});
}
}
XEventEnum::ButtonEvent {
event: button_event,
pressed,
} => {
if button_event.button & xlib::Button1 != 0 {
let mut events = vec![virtio_input_event::multitouch_slot(0)];
if pressed {
events.push(virtio_input_event::multitouch_tracking_id(
self.next_tracking_id(),
));
events.push(virtio_input_event::multitouch_absolute_x(max(
0,
button_event.x,
)));
events.push(virtio_input_event::multitouch_absolute_y(max(
0,
button_event.y,
)));
} else {
events.push(virtio_input_event::multitouch_tracking_id(-1));
}
return Some(GpuDisplayEvents {
events,
device_type: EventDeviceKind::Touchscreen,
});
}
}
XEventEnum::Motion(motion) => {
if motion.state & xlib::Button1Mask != 0 {
let events = vec![
virtio_input_event::multitouch_slot(0),
virtio_input_event::multitouch_tracking_id(self.current_tracking_id()),
virtio_input_event::multitouch_absolute_x(max(0, motion.x)),
virtio_input_event::multitouch_absolute_y(max(0, motion.y)),
];
return Some(GpuDisplayEvents {
events,
device_type: EventDeviceKind::Touchscreen,
});
}
}
XEventEnum::Expose => surface.draw_current_buffer(),
XEventEnum::ClientMessage(xclient_data) => {
surface.on_client_message(xclient_data);
return None;
}
XEventEnum::ShmCompletionEvent(shmseg) => {
surface.on_shm_completion(shmseg);
return None;
}
XEventEnum::Unhandled => return None,
}
None
}
fn create_surface(
&mut self,
parent_surface_id: Option<u32>,
_surface_id: u32,
_scanout_id: Option<u32>,
display_params: &DisplayParameters,
_surf_type: SurfaceType,
) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
if parent_surface_id.is_some() {
return Err(GpuDisplayError::Unsupported);
}
#[allow(clippy::undocumented_unsafe_blocks)]
unsafe {
let (width, height) = display_params.get_virtual_display_size();
let depth = xlib::XDefaultDepthOfScreen(self.screen.as_ptr()) as u32;
let black_pixel = xlib::XBlackPixelOfScreen(self.screen.as_ptr());
let window = xlib::XCreateSimpleWindow(
self.display.as_ptr(),
xlib::XRootWindowOfScreen(self.screen.as_ptr()),
0,
0,
width,
height,
1,
black_pixel,
black_pixel,
);
xlib::XStoreName(
self.display.as_ptr(),
window,
CStr::from_bytes_with_nul(b"crosvm\0").unwrap().as_ptr(),
);
let gc = xlib::XCreateGC(self.display.as_ptr(), window, 0, null_mut());
let buffer_completion_type =
xlib::XShmGetEventBase(self.display.as_ptr()) as u32 + xlib::ShmCompletion;
let mut delete_window_atom = xlib::XInternAtom(
self.display.as_ptr(),
CStr::from_bytes_with_nul(b"WM_DELETE_WINDOW\0")
.unwrap()
.as_ptr(),
0,
);
xlib::XSetWMProtocols(self.display.as_ptr(), window, &mut delete_window_atom, 1);
let size_hints = xlib::XAllocSizeHints();
(*size_hints).flags = (xlib::PMinSize | xlib::PMaxSize) as i64;
(*size_hints).max_width = width as i32;
(*size_hints).min_width = width as i32;
(*size_hints).max_height = height as i32;
(*size_hints).min_height = height as i32;
xlib::XSetWMNormalHints(self.display.as_ptr(), window, size_hints);
x_free(size_hints);
xlib::XSelectInput(
self.display.as_ptr(),
window,
(xlib::ExposureMask
| xlib::KeyPressMask
| xlib::KeyReleaseMask
| xlib::ButtonPressMask
| xlib::ButtonReleaseMask
| xlib::PointerMotionMask) as i64,
);
xlib::XClearWindow(self.display.as_ptr(), window);
xlib::XMapRaised(self.display.as_ptr(), window);
self.display.flush();
Ok(Box::new(XSurface {
display: self.display.clone(),
visual: self.visual,
depth,
window,
gc,
width,
height,
buffers: Default::default(),
buffer_next: 0,
buffer_completion_type,
delete_window_atom,
close_requested: false,
}))
}
}
}
impl SysDisplayT for DisplayX {}
impl AsRawDescriptor for DisplayX {
fn as_raw_descriptor(&self) -> RawDescriptor {
self.display.as_raw_descriptor()
}
}