blob: e6f06dd8f1058ca1229b2a84e69bf2b0c19f5713 [file] [log] [blame]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#[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 data_model::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 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;
/// A wrapper for XFree that takes any type.
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 {
unsafe {
xlib::XCloseDisplay(self.as_ptr());
}
}
}
}
impl XDisplay {
/// Returns a pointer to the X display object.
fn as_ptr(&self) -> *mut xlib::Display {
self.0.as_ptr()
}
/// Sends any pending commands to the X server.
fn flush(&self) {
unsafe {
xlib::XFlush(self.as_ptr());
}
}
/// Returns true of the XShm extension is supported on this display.
fn supports_shm(&self) -> bool {
unsafe { xlib::XShmQueryExtension(self.as_ptr()) != 0 }
}
/// Gets the default screen of this display.
fn default_screen(&self) -> Option<XScreen> {
Some(XScreen(NonNull::new(unsafe {
xlib::XDefaultScreenOfDisplay(self.as_ptr())
})?))
}
/// Blocks until the next event from the display is received and returns that event.
///
/// Always flush before using this if any X commands where issued.
fn next_event(&self) -> XEvent {
unsafe {
let mut ev = zeroed();
xlib::XNextEvent(self.as_ptr(), &mut ev);
ev.into()
}
}
}
impl AsRawDescriptor for XDisplay {
fn as_raw_descriptor(&self) -> RawDescriptor {
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 {
// All events have the same xany field.
unsafe { self.0.xany }
}
fn type_(&self) -> u32 {
// All events have the same type_ field.
unsafe { self.0.type_ as u32 }
}
fn window(&self) -> xlib::Window {
self.any().window
}
// Some of the event types are dynamic so they need to be passed in.
fn as_enum(&self, shm_complete_type: u32) -> XEventEnum {
match self.type_() {
xlib::KeyPress | xlib::KeyRelease => XEventEnum::KeyEvent(unsafe { self.0.xkey }),
xlib::ButtonPress => XEventEnum::ButtonEvent {
event: unsafe { self.0.xbutton },
pressed: true,
},
xlib::ButtonRelease => XEventEnum::ButtonEvent {
event: unsafe { self.0.xbutton },
pressed: false,
},
xlib::MotionNotify => XEventEnum::Motion(unsafe { self.0.xmotion }),
xlib::Expose => XEventEnum::Expose,
xlib::ClientMessage => {
XEventEnum::ClientMessage(unsafe { self.0.xclient.data.l[0] as u64 })
}
t if t == shm_complete_type => {
// Because XShmCompletionEvent is not part of the XEvent union, simulate a union
// with transmute_copy. If the shm_complete_type turns out to be bogus, some of the
// data would be incorrect, but the common event fields would still be valid.
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),
// We don't care about most kinds of events,
Unhandled,
}
struct XScreen(NonNull<xlib::Screen>);
impl XScreen {
fn as_ptr(&self) -> *mut xlib::Screen {
self.0.as_ptr()
}
/// Gets the screen number of this screen.
fn get_number(&self) -> i32 {
unsafe { xlib::XScreenNumberOfScreen(self.as_ptr()) }
}
}
struct Buffer {
display: XDisplay,
image: *mut xlib::XImage,
/// The documentation says XShmSegmentInfo must last at least as long as the XImage, which
/// probably precludes moving it as well.
segment_info: Box<xlib::XShmSegmentInfo>,
size: usize,
in_use: bool,
}
impl Drop for Buffer {
fn drop(&mut self) {
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 {
unsafe { VolatileSlice::from_raw_parts(self.segment_info.shmaddr as *mut _, self.size) }
}
fn stride(&self) -> usize {
unsafe { (*self.image).bytes_per_line as usize }
}
fn bytes_per_pixel(&self) -> usize {
let bytes_per_pixel = unsafe { (*self.image).bits_per_pixel / 8 };
bytes_per_pixel as usize
}
}
// Surfaces here are equivalent to XWindows.
struct XSurface {
display: XDisplay,
visual: *mut xlib::Visual,
depth: u32,
window: xlib::Window,
gc: xlib::GC,
width: u32,
height: u32,
// Fields for handling the buffer swap chain.
buffers: [Option<Buffer>; BUFFER_COUNT],
buffer_next: usize,
buffer_completion_type: u32,
// Fields for handling window close requests
delete_window_atom: c_ulong,
close_requested: bool,
}
impl XSurface {
/// Returns index of the current (on-screen) buffer, or 0 if there are no buffers.
fn current_buffer(&self) -> usize {
match self.buffer_next.checked_sub(1) {
Some(i) => i,
None => self.buffers.len() - 1,
}
}
/// Draws the indicated buffer onto the screen.
fn draw_buffer(&mut self, buffer_index: usize) {
let buffer = match self.buffers.get_mut(buffer_index) {
Some(Some(b)) => b,
_ => {
// If there is no buffer, that means the framebuffer was never set and we should
// simply blank the window with arbitrary contents.
unsafe {
xlib::XClearWindow(self.display.as_ptr(), self.window);
}
return;
}
};
// Mark the buffer as in use. When the XShmCompletionEvent occurs, this will get marked
// false.
buffer.in_use = true;
unsafe {
xlib::XShmPutImage(
self.display.as_ptr(),
self.window,
self.gc,
buffer.image,
0, // src x
0, // src y
0, // dst x
0, // dst y
self.width,
self.height,
true as i32, /* send XShmCompletionEvent event */
);
self.display.flush();
}
}
/// Gets the buffer at buffer_index, allocating it if necessary.
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();
}
// The buffer_index is valid and the buffer was never created, so we create it now.
unsafe {
// The docs for XShmCreateImage imply that XShmSegmentInfo must be allocated to live at
// least as long as the XImage, which probably means it can't move either. Use a Box in
// order to fulfill those requirements.
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 {
fn surface_descriptor(&self) -> u64 {
self.window as u64
}
fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
// Framebuffers are lazily allocated. If the next buffer is not in self.buffers, add it
// using push_new_buffer and then get its memory.
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 {
// Buffers that have not yet been made are not in use, hence unwrap_or(false).
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) {
// Safe given it should always be of the correct type.
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);
unsafe {
// Open the display
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),
};
// Check for required extension.
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();
// Check for and save required visual (24-bit BGR for the default screen).
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 {
unsafe { xlib::XPending(self.display.as_ptr()) != 0 }
}
fn flush(&self) {
self.display.flush();
}
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> {
// Should not panic since the common layer only calls this when an event exists.
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,
)];
return Some(GpuDisplayEvents {
events,
device_type: EventDeviceKind::Keyboard,
});
}
}
XEventEnum::ButtonEvent {
event: button_event,
pressed,
} => {
// We only support a single touch from button 1 (left mouse button).
// TODO(tutankhamen): slot is always 0, because all the input
// events come from mouse device, i.e. only one touch is possible at a time.
// Full MT protocol has to be implemented and properly wired later.
if button_event.button & xlib::Button1 != 0 {
// The touch event *must* be first per the Linux input subsystem's guidance.
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,
width: u32,
height: u32,
_surf_type: SurfaceType,
) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
if parent_surface_id.is_some() {
return Err(GpuDisplayError::Unsupported);
}
unsafe {
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,
);
let gc = xlib::XCreateGC(self.display.as_ptr(), window, 0, null_mut());
// Because the event is from an extension, its type must be calculated dynamically.
let buffer_completion_type =
xlib::XShmGetEventBase(self.display.as_ptr()) as u32 + xlib::ShmCompletion;
// Mark this window as responding to close requests.
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);
// We will use redraw the buffer when we are exposed.
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);
// Flush everything so that the window is visible immediately.
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()
}
}