1 // Copyright 2019 The Chromium OS Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #[path = "generated/xlib.rs"]
6 #[allow(
7 dead_code,
8 non_snake_case,
9 non_camel_case_types,
10 non_upper_case_globals
11 )]
12 mod xlib;
13
14 use linux_input_sys::virtio_input_event;
15 use std::cmp::max;
16 use std::ffi::{c_void, CStr, CString};
17 use std::mem::{transmute_copy, zeroed};
18 use std::os::raw::c_ulong;
19 use std::ptr::{null, null_mut, NonNull};
20 use std::rc::Rc;
21
22 use libc::{shmat, shmctl, shmdt, shmget, IPC_CREAT, IPC_PRIVATE, IPC_RMID};
23
24 use crate::{
25 keycode_converter::KeycodeTranslator, keycode_converter::KeycodeTypes, DisplayT,
26 EventDeviceKind, GpuDisplayError, GpuDisplayEvents, GpuDisplayFramebuffer, GpuDisplayResult,
27 GpuDisplaySurface, SurfaceType,
28 };
29
30 use base::{AsRawDescriptor, RawDescriptor};
31 use data_model::VolatileSlice;
32
33 const BUFFER_COUNT: usize = 2;
34
35 /// A wrapper for XFree that takes any type.
x_free<T>(t: *mut T)36 unsafe fn x_free<T>(t: *mut T) {
37 xlib::XFree(t as *mut c_void);
38 }
39
40 #[derive(Clone)]
41 struct XDisplay(Rc<NonNull<xlib::Display>>);
42 impl Drop for XDisplay {
drop(&mut self)43 fn drop(&mut self) {
44 if Rc::strong_count(&self.0) == 1 {
45 unsafe {
46 xlib::XCloseDisplay(self.as_ptr());
47 }
48 }
49 }
50 }
51
52 impl XDisplay {
53 /// Returns a pointer to the X display object.
as_ptr(&self) -> *mut xlib::Display54 fn as_ptr(&self) -> *mut xlib::Display {
55 self.0.as_ptr()
56 }
57
58 /// Sends any pending commands to the X server.
flush(&self)59 fn flush(&self) {
60 unsafe {
61 xlib::XFlush(self.as_ptr());
62 }
63 }
64
65 /// Returns true of the XShm extension is supported on this display.
supports_shm(&self) -> bool66 fn supports_shm(&self) -> bool {
67 unsafe { xlib::XShmQueryExtension(self.as_ptr()) != 0 }
68 }
69
70 /// Gets the default screen of this display.
default_screen(&self) -> Option<XScreen>71 fn default_screen(&self) -> Option<XScreen> {
72 Some(XScreen(NonNull::new(unsafe {
73 xlib::XDefaultScreenOfDisplay(self.as_ptr())
74 })?))
75 }
76
77 /// Blocks until the next event from the display is received and returns that event.
78 ///
79 /// Always flush before using this if any X commands where issued.
next_event(&self) -> XEvent80 fn next_event(&self) -> XEvent {
81 unsafe {
82 let mut ev = zeroed();
83 xlib::XNextEvent(self.as_ptr(), &mut ev);
84 ev.into()
85 }
86 }
87 }
88
89 impl AsRawDescriptor for XDisplay {
as_raw_descriptor(&self) -> RawDescriptor90 fn as_raw_descriptor(&self) -> RawDescriptor {
91 unsafe { xlib::XConnectionNumber(self.as_ptr()) }
92 }
93 }
94
95 struct XEvent(xlib::XEvent);
96 impl From<xlib::XEvent> for XEvent {
from(ev: xlib::XEvent) -> XEvent97 fn from(ev: xlib::XEvent) -> XEvent {
98 XEvent(ev)
99 }
100 }
101
102 impl XEvent {
any(&self) -> xlib::XAnyEvent103 fn any(&self) -> xlib::XAnyEvent {
104 // All events have the same xany field.
105 unsafe { self.0.xany }
106 }
107
type_(&self) -> u32108 fn type_(&self) -> u32 {
109 // All events have the same type_ field.
110 unsafe { self.0.type_ as u32 }
111 }
112
window(&self) -> xlib::Window113 fn window(&self) -> xlib::Window {
114 self.any().window
115 }
116
117 // Some of the event types are dynamic so they need to be passed in.
as_enum(&self, shm_complete_type: u32) -> XEventEnum118 fn as_enum(&self, shm_complete_type: u32) -> XEventEnum {
119 match self.type_() {
120 xlib::KeyPress | xlib::KeyRelease => XEventEnum::KeyEvent(unsafe { self.0.xkey }),
121 xlib::ButtonPress => XEventEnum::ButtonEvent {
122 event: unsafe { self.0.xbutton },
123 pressed: true,
124 },
125 xlib::ButtonRelease => XEventEnum::ButtonEvent {
126 event: unsafe { self.0.xbutton },
127 pressed: false,
128 },
129 xlib::MotionNotify => XEventEnum::Motion(unsafe { self.0.xmotion }),
130 xlib::Expose => XEventEnum::Expose,
131 xlib::ClientMessage => {
132 XEventEnum::ClientMessage(unsafe { self.0.xclient.data.l[0] as u64 })
133 }
134 t if t == shm_complete_type => {
135 // Because XShmCompletionEvent is not part of the XEvent union, simulate a union
136 // with transmute_copy. If the shm_complete_type turns out to be bogus, some of the
137 // data would be incorrect, but the common event fields would still be valid.
138 let ev_completion: xlib::XShmCompletionEvent = unsafe { transmute_copy(&self.0) };
139 XEventEnum::ShmCompletionEvent(ev_completion.shmseg)
140 }
141 _ => XEventEnum::Unhandled,
142 }
143 }
144 }
145
146 enum XEventEnum {
147 KeyEvent(xlib::XKeyEvent),
148 ButtonEvent {
149 event: xlib::XButtonEvent,
150 pressed: bool,
151 },
152 Motion(xlib::XMotionEvent),
153 Expose,
154 ClientMessage(u64),
155 ShmCompletionEvent(xlib::ShmSeg),
156 // We don't care about most kinds of events,
157 Unhandled,
158 }
159
160 struct XScreen(NonNull<xlib::Screen>);
161
162 impl XScreen {
as_ptr(&self) -> *mut xlib::Screen163 fn as_ptr(&self) -> *mut xlib::Screen {
164 self.0.as_ptr()
165 }
166
167 /// Gets the screen number of this screen.
get_number(&self) -> i32168 fn get_number(&self) -> i32 {
169 unsafe { xlib::XScreenNumberOfScreen(self.as_ptr()) }
170 }
171 }
172
173 struct Buffer {
174 display: XDisplay,
175 image: *mut xlib::XImage,
176 /// The documentation says XShmSegmentInfo must last at least as long as the XImage, which
177 /// probably precludes moving it as well.
178 segment_info: Box<xlib::XShmSegmentInfo>,
179 size: usize,
180 in_use: bool,
181 }
182
183 impl Drop for Buffer {
drop(&mut self)184 fn drop(&mut self) {
185 unsafe {
186 xlib::XShmDetach(self.display.as_ptr(), self.segment_info.as_mut());
187 xlib::XDestroyImage(self.image);
188 shmdt(self.segment_info.shmaddr as *const _);
189 shmctl(self.segment_info.shmid, IPC_RMID, null_mut());
190 }
191 }
192 }
193
194 impl Buffer {
as_volatile_slice(&self) -> VolatileSlice195 fn as_volatile_slice(&self) -> VolatileSlice {
196 unsafe { VolatileSlice::from_raw_parts(self.segment_info.shmaddr as *mut _, self.size) }
197 }
198
stride(&self) -> usize199 fn stride(&self) -> usize {
200 unsafe { (*self.image).bytes_per_line as usize }
201 }
202
bytes_per_pixel(&self) -> usize203 fn bytes_per_pixel(&self) -> usize {
204 let bytes_per_pixel = unsafe { (*self.image).bits_per_pixel / 8 };
205 bytes_per_pixel as usize
206 }
207 }
208
209 // Surfaces here are equivalent to XWindows.
210 struct XSurface {
211 display: XDisplay,
212 visual: *mut xlib::Visual,
213 depth: u32,
214 window: xlib::Window,
215 gc: xlib::GC,
216 width: u32,
217 height: u32,
218
219 // Fields for handling the buffer swap chain.
220 buffers: [Option<Buffer>; BUFFER_COUNT],
221 buffer_next: usize,
222 buffer_completion_type: u32,
223
224 // Fields for handling window close requests
225 delete_window_atom: c_ulong,
226 close_requested: bool,
227 }
228
229 impl XSurface {
230 /// Returns index of the current (on-screen) buffer, or 0 if there are no buffers.
current_buffer(&self) -> usize231 fn current_buffer(&self) -> usize {
232 match self.buffer_next.checked_sub(1) {
233 Some(i) => i,
234 None => self.buffers.len() - 1,
235 }
236 }
237
238 /// Draws the indicated buffer onto the screen.
draw_buffer(&mut self, buffer_index: usize)239 fn draw_buffer(&mut self, buffer_index: usize) {
240 let buffer = match self.buffers.get_mut(buffer_index) {
241 Some(Some(b)) => b,
242 _ => {
243 // If there is no buffer, that means the framebuffer was never set and we should
244 // simply blank the window with arbitrary contents.
245 unsafe {
246 xlib::XClearWindow(self.display.as_ptr(), self.window);
247 }
248 return;
249 }
250 };
251 // Mark the buffer as in use. When the XShmCompletionEvent occurs, this will get marked
252 // false.
253 buffer.in_use = true;
254 unsafe {
255 xlib::XShmPutImage(
256 self.display.as_ptr(),
257 self.window,
258 self.gc,
259 buffer.image,
260 0, // src x
261 0, // src y
262 0, // dst x
263 0, // dst y
264 self.width,
265 self.height,
266 true as i32, /* send XShmCompletionEvent event */
267 );
268 self.display.flush();
269 }
270 }
271
272 /// Gets the buffer at buffer_index, allocating it if necessary.
lazily_allocate_buffer(&mut self, buffer_index: usize) -> Option<&Buffer>273 fn lazily_allocate_buffer(&mut self, buffer_index: usize) -> Option<&Buffer> {
274 if buffer_index >= self.buffers.len() {
275 return None;
276 }
277
278 if self.buffers[buffer_index].is_some() {
279 return self.buffers[buffer_index].as_ref();
280 }
281 // The buffer_index is valid and the buffer was never created, so we create it now.
282 unsafe {
283 // The docs for XShmCreateImage imply that XShmSegmentInfo must be allocated to live at
284 // least as long as the XImage, which probably means it can't move either. Use a Box in
285 // order to fulfill those requirements.
286 let mut segment_info: Box<xlib::XShmSegmentInfo> = Box::new(zeroed());
287 let image = xlib::XShmCreateImage(
288 self.display.as_ptr(),
289 self.visual,
290 self.depth,
291 xlib::ZPixmap as i32,
292 null_mut(),
293 segment_info.as_mut(),
294 self.width,
295 self.height,
296 );
297 if image.is_null() {
298 return None;
299 }
300 let size = (*image)
301 .bytes_per_line
302 .checked_mul((*image).height)
303 .unwrap();
304 segment_info.shmid = shmget(IPC_PRIVATE, size as usize, IPC_CREAT | 0o777);
305 if segment_info.shmid == -1 {
306 xlib::XDestroyImage(image);
307 return None;
308 }
309 segment_info.shmaddr = shmat(segment_info.shmid, null_mut(), 0) as *mut _;
310 if segment_info.shmaddr == (-1isize) as *mut _ {
311 xlib::XDestroyImage(image);
312 shmctl(segment_info.shmid, IPC_RMID, null_mut());
313 return None;
314 }
315 (*image).data = segment_info.shmaddr;
316 segment_info.readOnly = true as i32;
317 xlib::XShmAttach(self.display.as_ptr(), segment_info.as_mut());
318 self.buffers[buffer_index] = Some(Buffer {
319 display: self.display.clone(),
320 image,
321 segment_info,
322 size: size as usize,
323 in_use: false,
324 });
325 self.buffers[buffer_index].as_ref()
326 }
327 }
328 }
329
330 impl GpuDisplaySurface for XSurface {
surface_descriptor(&self) -> u64331 fn surface_descriptor(&self) -> u64 {
332 self.window as u64
333 }
334
framebuffer(&mut self) -> Option<GpuDisplayFramebuffer>335 fn framebuffer(&mut self) -> Option<GpuDisplayFramebuffer> {
336 // Framebuffers are lazily allocated. If the next buffer is not in self.buffers, add it
337 // using push_new_buffer and then get its memory.
338 let framebuffer = self.lazily_allocate_buffer(self.buffer_next)?;
339 let bytes_per_pixel = framebuffer.bytes_per_pixel() as u32;
340 Some(GpuDisplayFramebuffer::new(
341 framebuffer.as_volatile_slice(),
342 framebuffer.stride() as u32,
343 bytes_per_pixel,
344 ))
345 }
346
next_buffer_in_use(&self) -> bool347 fn next_buffer_in_use(&self) -> bool {
348 // Buffers that have not yet been made are not in use, hence unwrap_or(false).
349 self.buffers
350 .get(self.buffer_next)
351 .and_then(|b| Some(b.as_ref()?.in_use))
352 .unwrap_or(false)
353 }
354
close_requested(&self) -> bool355 fn close_requested(&self) -> bool {
356 self.close_requested
357 }
358
flip(&mut self)359 fn flip(&mut self) {
360 let current_buffer_index = self.buffer_next;
361 self.buffer_next = (self.buffer_next + 1) % self.buffers.len();
362 self.draw_buffer(current_buffer_index);
363 }
364
buffer_completion_type(&self) -> u32365 fn buffer_completion_type(&self) -> u32 {
366 self.buffer_completion_type
367 }
368
draw_current_buffer(&mut self)369 fn draw_current_buffer(&mut self) {
370 self.draw_buffer(self.current_buffer())
371 }
372
on_client_message(&mut self, client_data: u64)373 fn on_client_message(&mut self, client_data: u64) {
374 if client_data == self.delete_window_atom {
375 self.close_requested = true;
376 }
377 }
378
on_shm_completion(&mut self, shm_complete: u64)379 fn on_shm_completion(&mut self, shm_complete: u64) {
380 for buffer in self.buffers.iter_mut().flatten() {
381 if buffer.segment_info.shmseg == shm_complete {
382 buffer.in_use = false;
383 }
384 }
385 }
386 }
387
388 impl Drop for XSurface {
drop(&mut self)389 fn drop(&mut self) {
390 // Safe given it should always be of the correct type.
391 unsafe {
392 xlib::XFreeGC(self.display.as_ptr(), self.gc);
393 xlib::XDestroyWindow(self.display.as_ptr(), self.window);
394 }
395 }
396 }
397
398 pub struct DisplayX {
399 display: XDisplay,
400 screen: XScreen,
401 visual: *mut xlib::Visual,
402 keycode_translator: KeycodeTranslator,
403 current_event: Option<XEvent>,
404 mt_tracking_id: u16,
405 }
406
407 impl DisplayX {
open_display(display: Option<&str>) -> GpuDisplayResult<DisplayX>408 pub fn open_display(display: Option<&str>) -> GpuDisplayResult<DisplayX> {
409 let display_cstr = match display.map(CString::new) {
410 Some(Ok(s)) => Some(s),
411 Some(Err(_)) => return Err(GpuDisplayError::InvalidPath),
412 None => None,
413 };
414
415 let keycode_translator = KeycodeTranslator::new(KeycodeTypes::XkbScancode);
416
417 unsafe {
418 // Open the display
419 let display = match NonNull::new(xlib::XOpenDisplay(
420 display_cstr
421 .as_ref()
422 .map(|s| CStr::as_ptr(s))
423 .unwrap_or(null()),
424 )) {
425 Some(display_ptr) => XDisplay(Rc::new(display_ptr)),
426 None => return Err(GpuDisplayError::Connect),
427 };
428
429 // Check for required extension.
430 if !display.supports_shm() {
431 return Err(GpuDisplayError::RequiredFeature("xshm extension"));
432 }
433
434 let screen = display
435 .default_screen()
436 .ok_or(GpuDisplayError::Connect)
437 .unwrap();
438 let screen_number = screen.get_number();
439
440 // Check for and save required visual (24-bit BGR for the default screen).
441 let mut visual_info_template = xlib::XVisualInfo {
442 visual: null_mut(),
443 visualid: 0,
444 screen: screen_number,
445 depth: 24,
446 class: 0,
447 red_mask: 0x00ff0000,
448 green_mask: 0x0000ff00,
449 blue_mask: 0x000000ff,
450 colormap_size: 0,
451 bits_per_rgb: 0,
452 };
453 let visual_info = xlib::XGetVisualInfo(
454 display.as_ptr(),
455 (xlib::VisualScreenMask
456 | xlib::VisualDepthMask
457 | xlib::VisualRedMaskMask
458 | xlib::VisualGreenMaskMask
459 | xlib::VisualBlueMaskMask) as i64,
460 &mut visual_info_template,
461 &mut 0,
462 );
463 if visual_info.is_null() {
464 return Err(GpuDisplayError::RequiredFeature("no matching visual"));
465 }
466 let visual = (*visual_info).visual;
467 x_free(visual_info);
468
469 Ok(DisplayX {
470 display,
471 screen,
472 visual,
473 keycode_translator,
474 current_event: None,
475 mt_tracking_id: 0,
476 })
477 }
478 }
479
next_tracking_id(&mut self) -> i32480 pub fn next_tracking_id(&mut self) -> i32 {
481 let cur_id: i32 = self.mt_tracking_id as i32;
482 self.mt_tracking_id = self.mt_tracking_id.wrapping_add(1);
483 cur_id
484 }
485
current_tracking_id(&self) -> i32486 pub fn current_tracking_id(&self) -> i32 {
487 self.mt_tracking_id as i32
488 }
489 }
490
491 impl DisplayT for DisplayX {
pending_events(&self) -> bool492 fn pending_events(&self) -> bool {
493 unsafe { xlib::XPending(self.display.as_ptr()) != 0 }
494 }
495
flush(&self)496 fn flush(&self) {
497 self.display.flush();
498 }
499
next_event(&mut self) -> GpuDisplayResult<u64>500 fn next_event(&mut self) -> GpuDisplayResult<u64> {
501 let ev = self.display.next_event();
502 let descriptor = ev.window() as u64;
503 self.current_event = Some(ev);
504 Ok(descriptor)
505 }
506
handle_next_event( &mut self, surface: &mut Box<dyn GpuDisplaySurface>, ) -> Option<GpuDisplayEvents>507 fn handle_next_event(
508 &mut self,
509 surface: &mut Box<dyn GpuDisplaySurface>,
510 ) -> Option<GpuDisplayEvents> {
511 // Should not panic since the common layer only calls this when an event exists.
512 let ev = self.current_event.take().unwrap();
513
514 match ev.as_enum(surface.buffer_completion_type()) {
515 XEventEnum::KeyEvent(key) => {
516 if let Some(linux_keycode) = self.keycode_translator.translate(key.keycode) {
517 let events = vec![virtio_input_event::key(
518 linux_keycode,
519 key.type_ == xlib::KeyPress as i32,
520 )];
521
522 return Some(GpuDisplayEvents {
523 events,
524 device_type: EventDeviceKind::Keyboard,
525 });
526 }
527 }
528 XEventEnum::ButtonEvent {
529 event: button_event,
530 pressed,
531 } => {
532 // We only support a single touch from button 1 (left mouse button).
533 // TODO(tutankhamen): slot is always 0, because all the input
534 // events come from mouse device, i.e. only one touch is possible at a time.
535 // Full MT protocol has to be implemented and properly wired later.
536 if button_event.button & xlib::Button1 != 0 {
537 // The touch event *must* be first per the Linux input subsystem's guidance.
538 let mut events = vec![virtio_input_event::multitouch_slot(0)];
539
540 if pressed {
541 events.push(virtio_input_event::multitouch_tracking_id(
542 self.next_tracking_id(),
543 ));
544 events.push(virtio_input_event::multitouch_absolute_x(max(
545 0,
546 button_event.x,
547 )));
548 events.push(virtio_input_event::multitouch_absolute_y(max(
549 0,
550 button_event.y,
551 )));
552 } else {
553 events.push(virtio_input_event::multitouch_tracking_id(-1));
554 }
555
556 return Some(GpuDisplayEvents {
557 events,
558 device_type: EventDeviceKind::Touchscreen,
559 });
560 }
561 }
562 XEventEnum::Motion(motion) => {
563 if motion.state & xlib::Button1Mask != 0 {
564 let events = vec![
565 virtio_input_event::multitouch_slot(0),
566 virtio_input_event::multitouch_tracking_id(self.current_tracking_id()),
567 virtio_input_event::multitouch_absolute_x(max(0, motion.x)),
568 virtio_input_event::multitouch_absolute_y(max(0, motion.y)),
569 ];
570
571 return Some(GpuDisplayEvents {
572 events,
573 device_type: EventDeviceKind::Touchscreen,
574 });
575 }
576 }
577 XEventEnum::Expose => surface.draw_current_buffer(),
578 XEventEnum::ClientMessage(xclient_data) => {
579 surface.on_client_message(xclient_data);
580 return None;
581 }
582 XEventEnum::ShmCompletionEvent(shmseg) => {
583 surface.on_shm_completion(shmseg);
584 return None;
585 }
586 XEventEnum::Unhandled => return None,
587 }
588
589 None
590 }
591
create_surface( &mut self, parent_surface_id: Option<u32>, _surface_id: u32, width: u32, height: u32, _surf_type: SurfaceType, ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>>592 fn create_surface(
593 &mut self,
594 parent_surface_id: Option<u32>,
595 _surface_id: u32,
596 width: u32,
597 height: u32,
598 _surf_type: SurfaceType,
599 ) -> GpuDisplayResult<Box<dyn GpuDisplaySurface>> {
600 if parent_surface_id.is_some() {
601 return Err(GpuDisplayError::Unsupported);
602 }
603
604 unsafe {
605 let depth = xlib::XDefaultDepthOfScreen(self.screen.as_ptr()) as u32;
606
607 let black_pixel = xlib::XBlackPixelOfScreen(self.screen.as_ptr());
608
609 let window = xlib::XCreateSimpleWindow(
610 self.display.as_ptr(),
611 xlib::XRootWindowOfScreen(self.screen.as_ptr()),
612 0,
613 0,
614 width,
615 height,
616 1,
617 black_pixel,
618 black_pixel,
619 );
620
621 let gc = xlib::XCreateGC(self.display.as_ptr(), window, 0, null_mut());
622
623 // Because the event is from an extension, its type must be calculated dynamically.
624 let buffer_completion_type =
625 xlib::XShmGetEventBase(self.display.as_ptr()) as u32 + xlib::ShmCompletion;
626
627 // Mark this window as responding to close requests.
628 let mut delete_window_atom = xlib::XInternAtom(
629 self.display.as_ptr(),
630 CStr::from_bytes_with_nul(b"WM_DELETE_WINDOW\0")
631 .unwrap()
632 .as_ptr(),
633 0,
634 );
635 xlib::XSetWMProtocols(self.display.as_ptr(), window, &mut delete_window_atom, 1);
636
637 let size_hints = xlib::XAllocSizeHints();
638 (*size_hints).flags = (xlib::PMinSize | xlib::PMaxSize) as i64;
639 (*size_hints).max_width = width as i32;
640 (*size_hints).min_width = width as i32;
641 (*size_hints).max_height = height as i32;
642 (*size_hints).min_height = height as i32;
643 xlib::XSetWMNormalHints(self.display.as_ptr(), window, size_hints);
644 x_free(size_hints);
645
646 // We will use redraw the buffer when we are exposed.
647 xlib::XSelectInput(
648 self.display.as_ptr(),
649 window,
650 (xlib::ExposureMask
651 | xlib::KeyPressMask
652 | xlib::KeyReleaseMask
653 | xlib::ButtonPressMask
654 | xlib::ButtonReleaseMask
655 | xlib::PointerMotionMask) as i64,
656 );
657
658 xlib::XClearWindow(self.display.as_ptr(), window);
659 xlib::XMapRaised(self.display.as_ptr(), window);
660
661 // Flush everything so that the window is visible immediately.
662 self.display.flush();
663
664 Ok(Box::new(XSurface {
665 display: self.display.clone(),
666 visual: self.visual,
667 depth,
668 window,
669 gc,
670 width,
671 height,
672 buffers: Default::default(),
673 buffer_next: 0,
674 buffer_completion_type,
675 delete_window_atom,
676 close_requested: false,
677 }))
678 }
679 }
680 }
681
682 impl AsRawDescriptor for DisplayX {
as_raw_descriptor(&self) -> RawDescriptor683 fn as_raw_descriptor(&self) -> RawDescriptor {
684 self.display.as_raw_descriptor()
685 }
686 }
687