1#[path = "generated/xlib.rs"]
6#[allow(
7 dead_code,
8 non_snake_case,
9 non_camel_case_types,
10 non_upper_case_globals
11)]
12mod xlib;
13
14use std::cmp::max;
15use std::ffi::c_void;
16use std::ffi::CStr;
17use std::ffi::CString;
18use std::mem::transmute_copy;
19use std::mem::zeroed;
20use std::os::raw::c_ulong;
21use std::ptr::null;
22use std::ptr::null_mut;
23use std::ptr::NonNull;
24use std::rc::Rc;
25
26use base::AsRawDescriptor;
27use base::RawDescriptor;
28use base::VolatileSlice;
29use libc::shmat;
30use libc::shmctl;
31use libc::shmdt;
32use libc::shmget;
33use libc::IPC_CREAT;
34use libc::IPC_PRIVATE;
35use libc::IPC_RMID;
36use linux_input_sys::virtio_input_event;
37use vm_control::gpu::DisplayParameters;
38
39use crate::keycode_converter::KeycodeTranslator;
40use crate::keycode_converter::KeycodeTypes;
41use crate::DisplayT;
42use crate::EventDeviceKind;
43use crate::GpuDisplayError;
44use crate::GpuDisplayEvents;
45use crate::GpuDisplayFramebuffer;
46use crate::GpuDisplayResult;
47use crate::GpuDisplaySurface;
48use crate::SurfaceType;
49use crate::SysDisplayT;
50
51const BUFFER_COUNT: usize = 2;
52
53unsafe fn x_free<T>(t: *mut T) {
57 xlib::XFree(t as *mut c_void);
58}
59
60#[derive(Clone)]
61struct XDisplay(Rc<NonNull<xlib::Display>>);
62impl Drop for XDisplay {
63 fn drop(&mut self) {
64 if Rc::strong_count(&self.0) == 1 {
65 #[allow(clippy::undocumented_unsafe_blocks)]
67 unsafe {
68 xlib::XCloseDisplay(self.as_ptr());
69 }
70 }
71 }
72}
73
74impl XDisplay {
75 fn as_ptr(&self) -> *mut xlib::Display {
77 self.0.as_ptr()
78 }
79
80 fn flush(&self) {
82 #[allow(clippy::undocumented_unsafe_blocks)]
84 unsafe {
85 xlib::XFlush(self.as_ptr());
86 }
87 }
88
89 fn supports_shm(&self) -> bool {
91 #[allow(clippy::undocumented_unsafe_blocks)]
93 unsafe {
94 xlib::XShmQueryExtension(self.as_ptr()) != 0
95 }
96 }
97
98 fn default_screen(&self) -> Option<XScreen> {
100 Some(XScreen(NonNull::new(
101 #[allow(clippy::undocumented_unsafe_blocks)]
103 unsafe {
104 xlib::XDefaultScreenOfDisplay(self.as_ptr())
105 },
106 )?))
107 }
108
109 fn next_event(&self) -> XEvent {
113 #[allow(clippy::undocumented_unsafe_blocks)]
115 unsafe {
116 let mut ev = zeroed();
117 xlib::XNextEvent(self.as_ptr(), &mut ev);
118 ev.into()
119 }
120 }
121}
122
123impl AsRawDescriptor for XDisplay {
124 fn as_raw_descriptor(&self) -> RawDescriptor {
125 #[allow(clippy::undocumented_unsafe_blocks)]
127 unsafe {
128 xlib::XConnectionNumber(self.as_ptr())
129 }
130 }
131}
132
133struct XEvent(xlib::XEvent);
134impl From<xlib::XEvent> for XEvent {
135 fn from(ev: xlib::XEvent) -> XEvent {
136 XEvent(ev)
137 }
138}
139
140impl XEvent {
141 fn any(&self) -> xlib::XAnyEvent {
142 #[allow(clippy::undocumented_unsafe_blocks)]
145 unsafe {
146 self.0.xany
147 }
148 }
149
150 fn type_(&self) -> u32 {
151 #[allow(clippy::undocumented_unsafe_blocks)]
154 unsafe {
155 self.0.type_ as u32
156 }
157 }
158
159 fn window(&self) -> xlib::Window {
160 self.any().window
161 }
162
163 fn as_enum(&self, shm_complete_type: u32) -> XEventEnum {
165 match self.type_() {
166 xlib::KeyPress | xlib::KeyRelease => XEventEnum::KeyEvent(
167 #[allow(clippy::undocumented_unsafe_blocks)]
169 unsafe {
170 self.0.xkey
171 },
172 ),
173 xlib::ButtonPress => {
174 #[allow(clippy::undocumented_unsafe_blocks)]
176 XEventEnum::ButtonEvent {
177 event: unsafe { self.0.xbutton },
178 pressed: true,
179 }
180 }
181 xlib::ButtonRelease => {
182 #[allow(clippy::undocumented_unsafe_blocks)]
184 XEventEnum::ButtonEvent {
185 event: unsafe { self.0.xbutton },
186 pressed: false,
187 }
188 }
189 xlib::MotionNotify => XEventEnum::Motion(
190 #[allow(clippy::undocumented_unsafe_blocks)]
192 unsafe {
193 self.0.xmotion
194 },
195 ),
196 xlib::Expose => XEventEnum::Expose,
197 xlib::ClientMessage => {
198 XEventEnum::ClientMessage(
199 #[allow(clippy::undocumented_unsafe_blocks)]
201 unsafe {
202 self.0.xclient.data.l[0] as u64
203 },
204 )
205 }
206 t if t == shm_complete_type => {
207 #[allow(clippy::undocumented_unsafe_blocks)]
212 let ev_completion: xlib::XShmCompletionEvent = unsafe { transmute_copy(&self.0) };
213 XEventEnum::ShmCompletionEvent(ev_completion.shmseg)
214 }
215 _ => XEventEnum::Unhandled,
216 }
217 }
218}
219
220enum XEventEnum {
221 KeyEvent(xlib::XKeyEvent),
222 ButtonEvent {
223 event: xlib::XButtonEvent,
224 pressed: bool,
225 },
226 Motion(xlib::XMotionEvent),
227 Expose,
228 ClientMessage(u64),
229 ShmCompletionEvent(xlib::ShmSeg),
230 Unhandled,
232}
233
234struct XScreen(NonNull<xlib::Screen>);
235
236impl XScreen {
237 fn as_ptr(&self) -> *mut xlib::Screen {
238 self.0.as_ptr()
239 }
240
241 fn get_number(&self) -> i32 {
243 #[allow(clippy::undocumented_unsafe_blocks)]
245 unsafe {
246 xlib::XScreenNumberOfScreen(self.as_ptr())
247 }
248 }
249}
250
251struct Buffer {
252 display: XDisplay,
253 image: *mut xlib::XImage,
254 segment_info: Box<xlib::XShmSegmentInfo>,
257 size: usize,
258 in_use: bool,
259}
260
261impl Drop for Buffer {
262 fn drop(&mut self) {
263 #[allow(clippy::undocumented_unsafe_blocks)]
265 unsafe {
266 xlib::XShmDetach(self.display.as_ptr(), self.segment_info.as_mut());
267 xlib::XDestroyImage(self.image);
268 shmdt(self.segment_info.shmaddr as *const _);
269 shmctl(self.segment_info.shmid, IPC_RMID, null_mut());
270 }
271 }
272}
273
274impl Buffer {
275 fn as_volatile_slice(&self) -> VolatileSlice {
276 #[allow(clippy::undocumented_unsafe_blocks)]
278 unsafe {
279 VolatileSlice::from_raw_parts(self.segment_info.shmaddr as *mut _, self.size)
280 }
281 }
282
283 fn stride(&self) -> usize {
284 #[allow(clippy::undocumented_unsafe_blocks)]
286 unsafe {
287 (*self.image).bytes_per_line as usize
288 }
289 }
290
291 fn bytes_per_pixel(&self) -> usize {
292 #[allow(clippy::undocumented_unsafe_blocks)]
294 let bytes_per_pixel = unsafe { (*self.image).bits_per_pixel / 8 };
295 bytes_per_pixel as usize
296 }
297}
298
299struct XSurface {
301 display: XDisplay,
302 visual: *mut xlib::Visual,
303 depth: u32,
304 window: xlib::Window,
305 gc: xlib::GC,
306 width: u32,
307 height: u32,
308
309 buffers: [Option<Buffer>; BUFFER_COUNT],
311 buffer_next: usize,
312 buffer_completion_type: u32,
313
314 delete_window_atom: c_ulong,
316 close_requested: bool,
317}
318
319impl XSurface {
320 fn current_buffer(&self) -> usize {
322 match self.buffer_next.checked_sub(1) {
323 Some(i) => i,
324 None => self.buffers.len() - 1,
325 }
326 }
327
328 fn draw_buffer(&mut self, buffer_index: usize) {
330 let buffer = match self.buffers.get_mut(buffer_index) {
331 Some(Some(b)) => b,
332 _ => {
333 #[allow(clippy::undocumented_unsafe_blocks)]
337 unsafe {
338 xlib::XClearWindow(self.display.as_ptr(), self.window);
339 }
340 return;
341 }
342 };
343 buffer.in_use = true;
346 #[allow(clippy::undocumented_unsafe_blocks)]
348 unsafe {
349 xlib::XShmPutImage(
350 self.display.as_ptr(),
351 self.window,
352 self.gc,
353 buffer.image,
354 0, 0, 0, 0, self.width,
359 self.height,
360 true as i32, );
362 self.display.flush();
363 }
364 }
365
366 fn lazily_allocate_buffer(&mut self, buffer_index: usize) -> Option<&Buffer> {
368 if buffer_index >= self.buffers.len() {
369 return None;
370 }
371
372 if self.buffers[buffer_index].is_some() {
373 return self.buffers[buffer_index].as_ref();
374 }
375 #[allow(clippy::undocumented_unsafe_blocks)]
378 unsafe {
379 let mut segment_info: Box<xlib::XShmSegmentInfo> = Box::new(zeroed());
383 let image = xlib::XShmCreateImage(
384 self.display.as_ptr(),
385 self.visual,
386 self.depth,
387 xlib::ZPixmap as i32,
388 null_mut(),
389 segment_info.as_mut(),
390 self.width,
391 self.height,
392 );
393 if image.is_null() {
394 return None;
395 }
396 let size = (*image)
397 .bytes_per_line
398 .checked_mul((*image).height)
399 .unwrap();
400 segment_info.shmid = shmget(IPC_PRIVATE, size as usize, IPC_CREAT | 0o777);
401 if segment_info.shmid == -1 {
402 xlib::XDestroyImage(image);
403 return None;
404 }
405 segment_info.shmaddr = shmat(segment_info.shmid, null_mut(), 0) as *mut _;
406 if segment_info.shmaddr == (-1isize) as *mut _ {
407 xlib::XDestroyImage(image);
408 shmctl(segment_info.shmid, IPC_RMID, null_mut());
409 return None;
410 }
411 (*image).data = segment_info.shmaddr;
412 segment_info.readOnly = true as i32;
413 xlib::XShmAttach(self.display.as_ptr(), segment_info.as_mut());
414 self.buffers[buffer_index] = Some(Buffer {
415 display: self.display.clone(),
416 image,
417 segment_info,
418 size: size as usize,
419 in_use: false,
420 });
421 self.buffers[buffer_index].as_ref()
422 }
423 }
424}
425
426impl GpuDisplaySurface for XSurface {
427 #[allow(clippy::unnecessary_cast)]
428 fn surface_descriptor(&self) -> u64 {
429 self.window as u64
430 }
431
432 fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
433 let framebuffer = self.lazily_allocate_buffer(self.buffer_next)?;
436 let bytes_per_pixel = framebuffer.bytes_per_pixel() as u32;
437 Some(GpuDisplayFramebuffer::new(
438 framebuffer.as_volatile_slice(),
439 framebuffer.stride() as u32,
440 bytes_per_pixel,
441 ))
442 }
443
444 fn next_buffer_in_use(&self) -> bool {
445 self.buffers
447 .get(self.buffer_next)
448 .and_then(|b| Some(b.as_ref()?.in_use))
449 .unwrap_or(false)
450 }
451
452 fn close_requested(&self) -> bool {
453 self.close_requested
454 }
455
456 fn flip(&mut self) {
457 let current_buffer_index = self.buffer_next;
458 self.buffer_next = (self.buffer_next + 1) % self.buffers.len();
459 self.draw_buffer(current_buffer_index);
460 }
461
462 fn buffer_completion_type(&self) -> u32 {
463 self.buffer_completion_type
464 }
465
466 fn draw_current_buffer(&mut self) {
467 self.draw_buffer(self.current_buffer())
468 }
469
470 fn on_client_message(&mut self, client_data: u64) {
471 if client_data == self.delete_window_atom {
472 self.close_requested = true;
473 }
474 }
475
476 fn on_shm_completion(&mut self, shm_complete: u64) {
477 for buffer in self.buffers.iter_mut().flatten() {
478 if buffer.segment_info.shmseg == shm_complete {
479 buffer.in_use = false;
480 }
481 }
482 }
483}
484
485impl Drop for XSurface {
486 fn drop(&mut self) {
487 unsafe {
490 xlib::XFreeGC(self.display.as_ptr(), self.gc);
491 xlib::XDestroyWindow(self.display.as_ptr(), self.window);
492 }
493 }
494}
495
496pub struct DisplayX {
497 display: XDisplay,
498 screen: XScreen,
499 visual: *mut xlib::Visual,
500 keycode_translator: KeycodeTranslator,
501 current_event: Option<XEvent>,
502 mt_tracking_id: u16,
503}
504
505impl DisplayX {
506 pub fn open_display(display: Option<&str>) -> GpuDisplayResult<DisplayX> {
507 let display_cstr = match display.map(CString::new) {
508 Some(Ok(s)) => Some(s),
509 Some(Err(_)) => return Err(GpuDisplayError::InvalidPath),
510 None => None,
511 };
512
513 let keycode_translator = KeycodeTranslator::new(KeycodeTypes::XkbScancode);
514
515 #[allow(clippy::undocumented_unsafe_blocks)]
517 unsafe {
518 let display = match NonNull::new(xlib::XOpenDisplay(
520 display_cstr
521 .as_ref()
522 .map(|s| CStr::as_ptr(s))
523 .unwrap_or(null()),
524 )) {
525 Some(display_ptr) => XDisplay(Rc::new(display_ptr)),
526 None => return Err(GpuDisplayError::Connect),
527 };
528
529 if !display.supports_shm() {
531 return Err(GpuDisplayError::RequiredFeature("xshm extension"));
532 }
533
534 let screen = display
535 .default_screen()
536 .ok_or(GpuDisplayError::Connect)
537 .unwrap();
538 let screen_number = screen.get_number();
539
540 let mut visual_info_template = xlib::XVisualInfo {
542 visual: null_mut(),
543 visualid: 0,
544 screen: screen_number,
545 depth: 24,
546 class: 0,
547 red_mask: 0x00ff0000,
548 green_mask: 0x0000ff00,
549 blue_mask: 0x000000ff,
550 colormap_size: 0,
551 bits_per_rgb: 0,
552 };
553 let visual_info = xlib::XGetVisualInfo(
554 display.as_ptr(),
555 (xlib::VisualScreenMask
556 | xlib::VisualDepthMask
557 | xlib::VisualRedMaskMask
558 | xlib::VisualGreenMaskMask
559 | xlib::VisualBlueMaskMask) as i64,
560 &mut visual_info_template,
561 &mut 0,
562 );
563 if visual_info.is_null() {
564 return Err(GpuDisplayError::RequiredFeature("no matching visual"));
565 }
566 let visual = (*visual_info).visual;
567 x_free(visual_info);
568
569 Ok(DisplayX {
570 display,
571 screen,
572 visual,
573 keycode_translator,
574 current_event: None,
575 mt_tracking_id: 0,
576 })
577 }
578 }
579
580 pub fn next_tracking_id(&mut self) -> i32 {
581 let cur_id: i32 = self.mt_tracking_id as i32;
582 self.mt_tracking_id = self.mt_tracking_id.wrapping_add(1);
583 cur_id
584 }
585
586 pub fn current_tracking_id(&self) -> i32 {
587 self.mt_tracking_id as i32
588 }
589}
590
591impl DisplayT for DisplayX {
592 fn pending_events(&self) -> bool {
593 #[allow(clippy::undocumented_unsafe_blocks)]
595 unsafe {
596 xlib::XPending(self.display.as_ptr()) != 0
597 }
598 }
599
600 fn flush(&self) {
601 self.display.flush();
602 }
603
604 #[allow(clippy::unnecessary_cast)]
605 fn next_event(&mut self) -> GpuDisplayResult<u64> {
606 let ev = self.display.next_event();
607 let descriptor = ev.window() as u64;
608 self.current_event = Some(ev);
609 Ok(descriptor)
610 }
611
612 fn handle_next_event(
613 &mut self,
614 surface: &mut Box<dyn GpuDisplaySurface>,
615 ) -> Option<GpuDisplayEvents> {
616 let ev = self.current_event.take().unwrap();
618
619 match ev.as_enum(surface.buffer_completion_type()) {
620 XEventEnum::KeyEvent(key) => {
621 if let Some(linux_keycode) = self.keycode_translator.translate(key.keycode) {
622 let events = vec![virtio_input_event::key(
623 linux_keycode,
624 key.type_ == xlib::KeyPress as i32,
625 false,
626 )];
627
628 return Some(GpuDisplayEvents {
629 events,
630 device_type: EventDeviceKind::Keyboard,
631 });
632 }
633 }
634 XEventEnum::ButtonEvent {
635 event: button_event,
636 pressed,
637 } => {
638 if button_event.button & xlib::Button1 != 0 {
643 let mut events = vec![virtio_input_event::multitouch_slot(0)];
645
646 if pressed {
647 events.push(virtio_input_event::multitouch_tracking_id(
648 self.next_tracking_id(),
649 ));
650 events.push(virtio_input_event::multitouch_absolute_x(max(
651 0,
652 button_event.x,
653 )));
654 events.push(virtio_input_event::multitouch_absolute_y(max(
655 0,
656 button_event.y,
657 )));
658 } else {
659 events.push(virtio_input_event::multitouch_tracking_id(-1));
660 }
661
662 return Some(GpuDisplayEvents {
663 events,
664 device_type: EventDeviceKind::Touchscreen,
665 });
666 }
667 }
668 XEventEnum::Motion(motion) => {
669 if motion.state & xlib::Button1Mask != 0 {
670 let events = vec![
671 virtio_input_event::multitouch_slot(0),
672 virtio_input_event::multitouch_tracking_id(self.current_tracking_id()),
673 virtio_input_event::multitouch_absolute_x(max(0, motion.x)),
674 virtio_input_event::multitouch_absolute_y(max(0, motion.y)),
675 ];
676
677 return Some(GpuDisplayEvents {
678 events,
679 device_type: EventDeviceKind::Touchscreen,
680 });
681 }
682 }
683 XEventEnum::Expose => surface.draw_current_buffer(),
684 XEventEnum::ClientMessage(xclient_data) => {
685 surface.on_client_message(xclient_data);
686 return None;
687 }
688 XEventEnum::ShmCompletionEvent(shmseg) => {
689 surface.on_shm_completion(shmseg);
690 return None;
691 }
692 XEventEnum::Unhandled => return None,
693 }
694
695 None
696 }
697
698 fn create_surface(
699 &mut self,
700 parent_surface_id: Option<u32>,
701 _surface_id: u32,
702 _scanout_id: Option<u32>,
703 display_params: &DisplayParameters,
704 _surf_type: SurfaceType,
705 ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
706 if parent_surface_id.is_some() {
707 return Err(GpuDisplayError::Unsupported);
708 }
709
710 #[allow(clippy::undocumented_unsafe_blocks)]
712 unsafe {
713 let (width, height) = display_params.get_virtual_display_size();
714 let depth = xlib::XDefaultDepthOfScreen(self.screen.as_ptr()) as u32;
715
716 let black_pixel = xlib::XBlackPixelOfScreen(self.screen.as_ptr());
717
718 let window = xlib::XCreateSimpleWindow(
719 self.display.as_ptr(),
720 xlib::XRootWindowOfScreen(self.screen.as_ptr()),
721 0,
722 0,
723 width,
724 height,
725 1,
726 black_pixel,
727 black_pixel,
728 );
729
730 xlib::XStoreName(self.display.as_ptr(), window, c"crosvm".as_ptr());
731
732 let gc = xlib::XCreateGC(self.display.as_ptr(), window, 0, null_mut());
733
734 let buffer_completion_type =
736 xlib::XShmGetEventBase(self.display.as_ptr()) as u32 + xlib::ShmCompletion;
737
738 let mut delete_window_atom =
740 xlib::XInternAtom(self.display.as_ptr(), c"WM_DELETE_WINDOW".as_ptr(), 0);
741 xlib::XSetWMProtocols(self.display.as_ptr(), window, &mut delete_window_atom, 1);
742
743 let size_hints = xlib::XAllocSizeHints();
744 (*size_hints).flags = (xlib::PMinSize | xlib::PMaxSize) as i64;
745 (*size_hints).max_width = width as i32;
746 (*size_hints).min_width = width as i32;
747 (*size_hints).max_height = height as i32;
748 (*size_hints).min_height = height as i32;
749 xlib::XSetWMNormalHints(self.display.as_ptr(), window, size_hints);
750 x_free(size_hints);
751
752 xlib::XSelectInput(
754 self.display.as_ptr(),
755 window,
756 (xlib::ExposureMask
757 | xlib::KeyPressMask
758 | xlib::KeyReleaseMask
759 | xlib::ButtonPressMask
760 | xlib::ButtonReleaseMask
761 | xlib::PointerMotionMask) as i64,
762 );
763
764 xlib::XClearWindow(self.display.as_ptr(), window);
765 xlib::XMapRaised(self.display.as_ptr(), window);
766
767 self.display.flush();
769
770 Ok(Box::new(XSurface {
771 display: self.display.clone(),
772 visual: self.visual,
773 depth,
774 window,
775 gc,
776 width,
777 height,
778 buffers: Default::default(),
779 buffer_next: 0,
780 buffer_completion_type,
781 delete_window_atom,
782 close_requested: false,
783 }))
784 }
785 }
786}
787
788impl SysDisplayT for DisplayX {}
789
790impl AsRawDescriptor for DisplayX {
791 fn as_raw_descriptor(&self) -> RawDescriptor {
792 self.display.as_raw_descriptor()
793 }
794}