1use anyhow::{Context as _, anyhow};
2use x11rb::connection::RequestConnection;
3
4use crate::linux::X11ClientStatePtr;
5use gpui::{
6 AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
7 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
8 Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
9 Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea,
10 WindowDecorations, WindowKind, WindowParams, px,
11};
12use gpui_wgpu::{CompositorGpuHint, WgpuRenderer, WgpuSurfaceConfig};
13
14use collections::FxHashSet;
15use raw_window_handle as rwh;
16use util::{ResultExt, maybe};
17use x11rb::{
18 connection::Connection,
19 cookie::{Cookie, VoidCookie},
20 errors::ConnectionError,
21 properties::WmSizeHints,
22 protocol::{
23 sync,
24 xinput::{self, ConnectionExt as _},
25 xproto::{self, ClientMessageEvent, ConnectionExt, TranslateCoordinatesReply},
26 },
27 wrapper::ConnectionExt as _,
28 xcb_ffi::XCBConnection,
29};
30
31use std::{
32 cell::RefCell, ffi::c_void, fmt::Display, num::NonZeroU32, ptr::NonNull, rc::Rc, sync::Arc,
33};
34
35use super::{X11Display, XINPUT_ALL_DEVICE_GROUPS, XINPUT_ALL_DEVICES};
36
37x11rb::atom_manager! {
38 pub XcbAtoms: AtomsCookie {
39 XA_ATOM,
40 XdndAware,
41 XdndStatus,
42 XdndEnter,
43 XdndLeave,
44 XdndPosition,
45 XdndSelection,
46 XdndDrop,
47 XdndFinished,
48 XdndTypeList,
49 XdndActionCopy,
50 TextUriList: b"text/uri-list",
51 UTF8_STRING,
52 TEXT,
53 STRING,
54 TEXT_PLAIN_UTF8: b"text/plain;charset=utf-8",
55 TEXT_PLAIN: b"text/plain",
56 XDND_DATA,
57 WM_PROTOCOLS,
58 WM_DELETE_WINDOW,
59 WM_CHANGE_STATE,
60 WM_TRANSIENT_FOR,
61 _NET_WM_PID,
62 _NET_WM_NAME,
63 _NET_WM_STATE,
64 _NET_WM_STATE_MAXIMIZED_VERT,
65 _NET_WM_STATE_MAXIMIZED_HORZ,
66 _NET_WM_STATE_FULLSCREEN,
67 _NET_WM_STATE_HIDDEN,
68 _NET_WM_STATE_FOCUSED,
69 _NET_ACTIVE_WINDOW,
70 _NET_WM_SYNC_REQUEST,
71 _NET_WM_SYNC_REQUEST_COUNTER,
72 _NET_WM_BYPASS_COMPOSITOR,
73 _NET_WM_MOVERESIZE,
74 _NET_WM_WINDOW_TYPE,
75 _NET_WM_WINDOW_TYPE_NOTIFICATION,
76 _NET_WM_WINDOW_TYPE_DIALOG,
77 _NET_WM_STATE_MODAL,
78 _NET_WM_SYNC,
79 _NET_SUPPORTED,
80 _MOTIF_WM_HINTS,
81 _GTK_SHOW_WINDOW_MENU,
82 _GTK_FRAME_EXTENTS,
83 _GTK_EDGE_CONSTRAINTS,
84 _NET_CLIENT_LIST_STACKING,
85 }
86}
87
88fn query_render_extent(
89 xcb: &Rc<XCBConnection>,
90 x_window: xproto::Window,
91) -> anyhow::Result<Size<DevicePixels>> {
92 let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
93 Ok(Size {
94 width: DevicePixels(reply.width as i32),
95 height: DevicePixels(reply.height as i32),
96 })
97}
98
99fn resize_edge_to_moveresize(edge: ResizeEdge) -> u32 {
100 match edge {
101 ResizeEdge::TopLeft => 0,
102 ResizeEdge::Top => 1,
103 ResizeEdge::TopRight => 2,
104 ResizeEdge::Right => 3,
105 ResizeEdge::BottomRight => 4,
106 ResizeEdge::Bottom => 5,
107 ResizeEdge::BottomLeft => 6,
108 ResizeEdge::Left => 7,
109 }
110}
111
112#[derive(Debug)]
113struct EdgeConstraints {
114 top_tiled: bool,
115 #[allow(dead_code)]
116 top_resizable: bool,
117
118 right_tiled: bool,
119 #[allow(dead_code)]
120 right_resizable: bool,
121
122 bottom_tiled: bool,
123 #[allow(dead_code)]
124 bottom_resizable: bool,
125
126 left_tiled: bool,
127 #[allow(dead_code)]
128 left_resizable: bool,
129}
130
131impl EdgeConstraints {
132 fn from_atom(atom: u32) -> Self {
133 EdgeConstraints {
134 top_tiled: (atom & (1 << 0)) != 0,
135 top_resizable: (atom & (1 << 1)) != 0,
136 right_tiled: (atom & (1 << 2)) != 0,
137 right_resizable: (atom & (1 << 3)) != 0,
138 bottom_tiled: (atom & (1 << 4)) != 0,
139 bottom_resizable: (atom & (1 << 5)) != 0,
140 left_tiled: (atom & (1 << 6)) != 0,
141 left_resizable: (atom & (1 << 7)) != 0,
142 }
143 }
144
145 fn to_tiling(&self) -> Tiling {
146 Tiling {
147 top: self.top_tiled,
148 right: self.right_tiled,
149 bottom: self.bottom_tiled,
150 left: self.left_tiled,
151 }
152 }
153}
154
155#[derive(Copy, Clone, Debug)]
156struct Visual {
157 id: xproto::Visualid,
158 colormap: u32,
159 depth: u8,
160}
161
162struct VisualSet {
163 inherit: Visual,
164 opaque: Option<Visual>,
165 transparent: Option<Visual>,
166 root: u32,
167 black_pixel: u32,
168}
169
170fn find_visuals(xcb: &XCBConnection, screen_index: usize) -> VisualSet {
171 let screen = &xcb.setup().roots[screen_index];
172 let mut set = VisualSet {
173 inherit: Visual {
174 id: screen.root_visual,
175 colormap: screen.default_colormap,
176 depth: screen.root_depth,
177 },
178 opaque: None,
179 transparent: None,
180 root: screen.root,
181 black_pixel: screen.black_pixel,
182 };
183
184 for depth_info in screen.allowed_depths.iter() {
185 for visual_type in depth_info.visuals.iter() {
186 let visual = Visual {
187 id: visual_type.visual_id,
188 colormap: 0,
189 depth: depth_info.depth,
190 };
191 log::debug!(
192 "Visual id: {}, class: {:?}, depth: {}, bits_per_value: {}, masks: 0x{:x} 0x{:x} 0x{:x}",
193 visual_type.visual_id,
194 visual_type.class,
195 depth_info.depth,
196 visual_type.bits_per_rgb_value,
197 visual_type.red_mask,
198 visual_type.green_mask,
199 visual_type.blue_mask,
200 );
201
202 if (
203 visual_type.red_mask,
204 visual_type.green_mask,
205 visual_type.blue_mask,
206 ) != (0xFF0000, 0xFF00, 0xFF)
207 {
208 continue;
209 }
210 let color_mask = visual_type.red_mask | visual_type.green_mask | visual_type.blue_mask;
211 let alpha_mask = color_mask as usize ^ ((1usize << depth_info.depth) - 1);
212
213 if alpha_mask == 0 {
214 if set.opaque.is_none() {
215 set.opaque = Some(visual);
216 }
217 } else {
218 if set.transparent.is_none() {
219 set.transparent = Some(visual);
220 }
221 }
222 }
223 }
224
225 set
226}
227
228#[derive(Debug, Clone, Copy)]
229struct RawWindow {
230 connection: *mut c_void,
231 screen_id: usize,
232 window_id: u32,
233 visual_id: u32,
234}
235
236// Safety: The raw pointers in RawWindow point to X11 connection
237// which is valid for the window's lifetime. These are used only for
238// passing to wgpu which needs Send+Sync for surface creation.
239unsafe impl Send for RawWindow {}
240unsafe impl Sync for RawWindow {}
241
242#[derive(Default)]
243pub struct Callbacks {
244 request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
245 input: Option<Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>>,
246 active_status_change: Option<Box<dyn FnMut(bool)>>,
247 hovered_status_change: Option<Box<dyn FnMut(bool)>>,
248 resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
249 moved: Option<Box<dyn FnMut()>>,
250 should_close: Option<Box<dyn FnMut() -> bool>>,
251 close: Option<Box<dyn FnOnce()>>,
252 appearance_changed: Option<Box<dyn FnMut()>>,
253 button_layout_changed: Option<Box<dyn FnMut()>>,
254}
255
256pub struct X11WindowState {
257 pub destroyed: bool,
258 parent: Option<X11WindowStatePtr>,
259 children: FxHashSet<xproto::Window>,
260 client: X11ClientStatePtr,
261 executor: ForegroundExecutor,
262 atoms: XcbAtoms,
263 x_root_window: xproto::Window,
264 x_screen_index: usize,
265 visual_id: u32,
266 pub(crate) counter_id: sync::Counter,
267 pub(crate) last_sync_counter: Option<sync::Int64>,
268 bounds: Bounds<Pixels>,
269 scale_factor: f32,
270 renderer: WgpuRenderer,
271 display: Rc<dyn PlatformDisplay>,
272 input_handler: Option<PlatformInputHandler>,
273 appearance: WindowAppearance,
274 background_appearance: WindowBackgroundAppearance,
275 maximized_vertical: bool,
276 maximized_horizontal: bool,
277 hidden: bool,
278 active: bool,
279 hovered: bool,
280 fullscreen: bool,
281 client_side_decorations_supported: bool,
282 decorations: WindowDecorations,
283 edge_constraints: Option<EdgeConstraints>,
284 pub handle: AnyWindowHandle,
285 last_insets: [u32; 4],
286}
287
288impl X11WindowState {
289 fn is_transparent(&self) -> bool {
290 self.background_appearance != WindowBackgroundAppearance::Opaque
291 }
292}
293
294#[derive(Clone)]
295pub(crate) struct X11WindowStatePtr {
296 pub state: Rc<RefCell<X11WindowState>>,
297 pub(crate) callbacks: Rc<RefCell<Callbacks>>,
298 xcb: Rc<XCBConnection>,
299 pub(crate) x_window: xproto::Window,
300}
301
302impl rwh::HasWindowHandle for RawWindow {
303 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
304 let Some(non_zero) = NonZeroU32::new(self.window_id) else {
305 log::error!("RawWindow.window_id zero when getting window handle.");
306 return Err(rwh::HandleError::Unavailable);
307 };
308 let mut handle = rwh::XcbWindowHandle::new(non_zero);
309 handle.visual_id = NonZeroU32::new(self.visual_id);
310 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
311 }
312}
313impl rwh::HasDisplayHandle for RawWindow {
314 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
315 let Some(non_zero) = NonNull::new(self.connection) else {
316 log::error!("Null RawWindow.connection when getting display handle.");
317 return Err(rwh::HandleError::Unavailable);
318 };
319 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), self.screen_id as i32);
320 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
321 }
322}
323
324impl rwh::HasWindowHandle for X11Window {
325 fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
326 let Some(non_zero) = NonZeroU32::new(self.0.x_window) else {
327 return Err(rwh::HandleError::Unavailable);
328 };
329 let handle = rwh::XcbWindowHandle::new(non_zero);
330 Ok(unsafe { rwh::WindowHandle::borrow_raw(handle.into()) })
331 }
332}
333
334impl rwh::HasDisplayHandle for X11Window {
335 fn display_handle(&self) -> Result<rwh::DisplayHandle<'_>, rwh::HandleError> {
336 let connection =
337 as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(&*self.0.xcb)
338 as *mut _;
339 let Some(non_zero) = NonNull::new(connection) else {
340 return Err(rwh::HandleError::Unavailable);
341 };
342 let screen_id = {
343 let state = self.0.state.borrow();
344 u32::from(state.display.id()) as i32
345 };
346 let handle = rwh::XcbDisplayHandle::new(Some(non_zero), screen_id);
347 Ok(unsafe { rwh::DisplayHandle::borrow_raw(handle.into()) })
348 }
349}
350
351pub(crate) fn xcb_flush(xcb: &XCBConnection) {
352 xcb.flush()
353 .map_err(handle_connection_error)
354 .context("X11 flush failed")
355 .log_err();
356}
357
358pub(crate) fn check_reply<E, F, C>(
359 failure_context: F,
360 result: Result<VoidCookie<'_, C>, ConnectionError>,
361) -> anyhow::Result<()>
362where
363 E: Display + Send + Sync + 'static,
364 F: FnOnce() -> E,
365 C: RequestConnection,
366{
367 result
368 .map_err(handle_connection_error)
369 .and_then(|response| response.check().map_err(|reply_error| anyhow!(reply_error)))
370 .with_context(failure_context)
371}
372
373pub(crate) fn get_reply<E, F, C, O>(
374 failure_context: F,
375 result: Result<Cookie<'_, C, O>, ConnectionError>,
376) -> anyhow::Result<O>
377where
378 E: Display + Send + Sync + 'static,
379 F: FnOnce() -> E,
380 C: RequestConnection,
381 O: x11rb::x11_utils::TryParse,
382{
383 result
384 .map_err(handle_connection_error)
385 .and_then(|response| response.reply().map_err(|reply_error| anyhow!(reply_error)))
386 .with_context(failure_context)
387}
388
389/// Convert X11 connection errors to `anyhow::Error` and panic for unrecoverable errors.
390pub(crate) fn handle_connection_error(err: ConnectionError) -> anyhow::Error {
391 match err {
392 ConnectionError::UnknownError => anyhow!("X11 connection: Unknown error"),
393 ConnectionError::UnsupportedExtension => anyhow!("X11 connection: Unsupported extension"),
394 ConnectionError::MaximumRequestLengthExceeded => {
395 anyhow!("X11 connection: Maximum request length exceeded")
396 }
397 ConnectionError::FdPassingFailed => {
398 panic!("X11 connection: File descriptor passing failed")
399 }
400 ConnectionError::ParseError(parse_error) => {
401 anyhow!(parse_error).context("Parse error in X11 response")
402 }
403 ConnectionError::InsufficientMemory => panic!("X11 connection: Insufficient memory"),
404 ConnectionError::IoError(err) => anyhow!(err).context("X11 connection: IOError"),
405 _ => anyhow!(err),
406 }
407}
408
409impl X11WindowState {
410 pub fn new(
411 handle: AnyWindowHandle,
412 client: X11ClientStatePtr,
413 executor: ForegroundExecutor,
414 gpu_context: gpui_wgpu::GpuContext,
415 compositor_gpu: Option<CompositorGpuHint>,
416 params: WindowParams,
417 xcb: &Rc<XCBConnection>,
418 client_side_decorations_supported: bool,
419 x_main_screen_index: usize,
420 x_window: xproto::Window,
421 atoms: &XcbAtoms,
422 scale_factor: f32,
423 appearance: WindowAppearance,
424 parent_window: Option<X11WindowStatePtr>,
425 ) -> anyhow::Result<Self> {
426 let x_screen_index = params
427 .display_id
428 .map_or(x_main_screen_index, |did| u32::from(did) as usize);
429
430 let visual_set = find_visuals(xcb, x_screen_index);
431
432 let visual = match visual_set.transparent {
433 Some(visual) => visual,
434 None => {
435 log::warn!("Unable to find a transparent visual",);
436 visual_set.inherit
437 }
438 };
439 log::info!("Using {:?}", visual);
440
441 let colormap = if visual.colormap != 0 {
442 visual.colormap
443 } else {
444 let id = xcb.generate_id()?;
445 log::info!("Creating colormap {}", id);
446 check_reply(
447 || format!("X11 CreateColormap failed. id: {}", id),
448 xcb.create_colormap(xproto::ColormapAlloc::NONE, id, visual_set.root, visual.id),
449 )?;
450 id
451 };
452
453 let win_aux = xproto::CreateWindowAux::new()
454 // https://stackoverflow.com/questions/43218127/x11-xlib-xcb-creating-a-window-requires-border-pixel-if-specifying-colormap-wh
455 .border_pixel(visual_set.black_pixel)
456 .colormap(colormap)
457 .override_redirect((params.kind == WindowKind::PopUp) as u32)
458 .event_mask(
459 xproto::EventMask::EXPOSURE
460 | xproto::EventMask::STRUCTURE_NOTIFY
461 | xproto::EventMask::FOCUS_CHANGE
462 | xproto::EventMask::KEY_PRESS
463 | xproto::EventMask::KEY_RELEASE
464 | xproto::EventMask::PROPERTY_CHANGE
465 | xproto::EventMask::VISIBILITY_CHANGE,
466 );
467
468 let mut bounds = params.bounds.to_device_pixels(scale_factor);
469 if bounds.size.width.0 == 0 || bounds.size.height.0 == 0 {
470 log::warn!(
471 "Window bounds contain a zero value. height={}, width={}. Falling back to defaults.",
472 bounds.size.height.0,
473 bounds.size.width.0
474 );
475 bounds.size.width = 800.into();
476 bounds.size.height = 600.into();
477 }
478
479 check_reply(
480 || {
481 format!(
482 "X11 CreateWindow failed. depth: {}, x_window: {}, visual_set.root: {}, bounds.origin.x.0: {}, bounds.origin.y.0: {}, bounds.size.width.0: {}, bounds.size.height.0: {}",
483 visual.depth,
484 x_window,
485 visual_set.root,
486 bounds.origin.x.0 + 2,
487 bounds.origin.y.0,
488 bounds.size.width.0,
489 bounds.size.height.0
490 )
491 },
492 xcb.create_window(
493 visual.depth,
494 x_window,
495 visual_set.root,
496 (bounds.origin.x.0 + 2) as i16,
497 bounds.origin.y.0 as i16,
498 bounds.size.width.0 as u16,
499 bounds.size.height.0 as u16,
500 0,
501 xproto::WindowClass::INPUT_OUTPUT,
502 visual.id,
503 &win_aux,
504 ),
505 )?;
506
507 // Collect errors during setup, so that window can be destroyed on failure.
508 let setup_result = maybe!({
509 let pid = std::process::id();
510 check_reply(
511 || "X11 ChangeProperty for _NET_WM_PID failed.",
512 xcb.change_property32(
513 xproto::PropMode::REPLACE,
514 x_window,
515 atoms._NET_WM_PID,
516 xproto::AtomEnum::CARDINAL,
517 &[pid],
518 ),
519 )?;
520
521 let reply = get_reply(|| "X11 GetGeometry failed.", xcb.get_geometry(x_window))?;
522 if reply.x == 0 && reply.y == 0 {
523 bounds.origin.x.0 += 2;
524 // Work around a bug where our rendered content appears
525 // outside the window bounds when opened at the default position
526 // (14px, 49px on X + Gnome + Ubuntu 22).
527 let x = bounds.origin.x.0;
528 let y = bounds.origin.y.0;
529 check_reply(
530 || format!("X11 ConfigureWindow failed. x: {}, y: {}", x, y),
531 xcb.configure_window(x_window, &xproto::ConfigureWindowAux::new().x(x).y(y)),
532 )?;
533 }
534 if let Some(titlebar) = params.titlebar
535 && let Some(title) = titlebar.title
536 {
537 check_reply(
538 || "X11 ChangeProperty8 on WM_NAME failed.",
539 xcb.change_property8(
540 xproto::PropMode::REPLACE,
541 x_window,
542 xproto::AtomEnum::WM_NAME,
543 xproto::AtomEnum::STRING,
544 title.as_bytes(),
545 ),
546 )?;
547 check_reply(
548 || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
549 xcb.change_property8(
550 xproto::PropMode::REPLACE,
551 x_window,
552 atoms._NET_WM_NAME,
553 atoms.UTF8_STRING,
554 title.as_bytes(),
555 ),
556 )?;
557 }
558
559 if params.kind == WindowKind::PopUp {
560 check_reply(
561 || "X11 ChangeProperty32 setting window type for pop-up failed.",
562 xcb.change_property32(
563 xproto::PropMode::REPLACE,
564 x_window,
565 atoms._NET_WM_WINDOW_TYPE,
566 xproto::AtomEnum::ATOM,
567 &[atoms._NET_WM_WINDOW_TYPE_NOTIFICATION],
568 ),
569 )?;
570 }
571
572 if params.kind == WindowKind::Floating || params.kind == WindowKind::Dialog {
573 if let Some(parent_window) = parent_window.as_ref().map(|w| w.x_window) {
574 // WM_TRANSIENT_FOR hint indicating the main application window. For floating windows, we set
575 // a parent window (WM_TRANSIENT_FOR) such that the window manager knows where to
576 // place the floating window in relation to the main window.
577 // https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html
578 check_reply(
579 || "X11 ChangeProperty32 setting WM_TRANSIENT_FOR for floating window failed.",
580 xcb.change_property32(
581 xproto::PropMode::REPLACE,
582 x_window,
583 atoms.WM_TRANSIENT_FOR,
584 xproto::AtomEnum::WINDOW,
585 &[parent_window],
586 ),
587 )?;
588 }
589 }
590
591 let parent = if params.kind == WindowKind::Dialog
592 && let Some(parent) = parent_window
593 {
594 parent.add_child(x_window);
595
596 Some(parent)
597 } else {
598 None
599 };
600
601 if params.kind == WindowKind::Dialog {
602 // _NET_WM_WINDOW_TYPE_DIALOG indicates that this is a dialog (floating) window
603 // https://specifications.freedesktop.org/wm-spec/1.4/ar01s05.html
604 check_reply(
605 || "X11 ChangeProperty32 setting window type for dialog window failed.",
606 xcb.change_property32(
607 xproto::PropMode::REPLACE,
608 x_window,
609 atoms._NET_WM_WINDOW_TYPE,
610 xproto::AtomEnum::ATOM,
611 &[atoms._NET_WM_WINDOW_TYPE_DIALOG],
612 ),
613 )?;
614
615 // We set the modal state for dialog windows, so that the window manager
616 // can handle it appropriately (e.g., prevent interaction with the parent window
617 // while the dialog is open).
618 check_reply(
619 || "X11 ChangeProperty32 setting modal state for dialog window failed.",
620 xcb.change_property32(
621 xproto::PropMode::REPLACE,
622 x_window,
623 atoms._NET_WM_STATE,
624 xproto::AtomEnum::ATOM,
625 &[atoms._NET_WM_STATE_MODAL],
626 ),
627 )?;
628 }
629
630 check_reply(
631 || "X11 ChangeProperty32 setting protocols failed.",
632 xcb.change_property32(
633 xproto::PropMode::REPLACE,
634 x_window,
635 atoms.WM_PROTOCOLS,
636 xproto::AtomEnum::ATOM,
637 &[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
638 ),
639 )?;
640
641 get_reply(
642 || "X11 sync protocol initialize failed.",
643 sync::initialize(xcb, 3, 1),
644 )?;
645 let sync_request_counter = xcb.generate_id()?;
646 check_reply(
647 || "X11 sync CreateCounter failed.",
648 sync::create_counter(xcb, sync_request_counter, sync::Int64 { lo: 0, hi: 0 }),
649 )?;
650
651 check_reply(
652 || "X11 ChangeProperty32 setting sync request counter failed.",
653 xcb.change_property32(
654 xproto::PropMode::REPLACE,
655 x_window,
656 atoms._NET_WM_SYNC_REQUEST_COUNTER,
657 xproto::AtomEnum::CARDINAL,
658 &[sync_request_counter],
659 ),
660 )?;
661
662 check_reply(
663 || "X11 XiSelectEvents failed.",
664 xcb.xinput_xi_select_events(
665 x_window,
666 &[xinput::EventMask {
667 deviceid: XINPUT_ALL_DEVICE_GROUPS,
668 mask: vec![
669 xinput::XIEventMask::MOTION
670 | xinput::XIEventMask::BUTTON_PRESS
671 | xinput::XIEventMask::BUTTON_RELEASE
672 | xinput::XIEventMask::ENTER
673 | xinput::XIEventMask::LEAVE,
674 ],
675 }],
676 ),
677 )?;
678
679 check_reply(
680 || "X11 XiSelectEvents for device changes failed.",
681 xcb.xinput_xi_select_events(
682 x_window,
683 &[xinput::EventMask {
684 deviceid: XINPUT_ALL_DEVICES,
685 mask: vec![
686 xinput::XIEventMask::HIERARCHY | xinput::XIEventMask::DEVICE_CHANGED,
687 ],
688 }],
689 ),
690 )?;
691
692 xcb_flush(xcb);
693
694 let renderer = {
695 let raw_window = RawWindow {
696 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
697 xcb,
698 ) as *mut _,
699 screen_id: x_screen_index,
700 window_id: x_window,
701 visual_id: visual.id,
702 };
703 let config = WgpuSurfaceConfig {
704 // Note: this has to be done after the GPU init, or otherwise
705 // the sizes are immediately invalidated.
706 size: query_render_extent(xcb, x_window)?,
707 // We set it to transparent by default, even if we have client-side
708 // decorations, since those seem to work on X11 even without `true` here.
709 // If the window appearance changes, then the renderer will get updated
710 // too
711 transparent: false,
712 };
713 WgpuRenderer::new(gpu_context, &raw_window, config, compositor_gpu)?
714 };
715
716 // Set max window size hints based on the GPU's maximum texture dimension.
717 // This prevents the window from being resized larger than what the GPU can render.
718 let max_texture_size = renderer.max_texture_size();
719 let mut size_hints = WmSizeHints::new();
720 if let Some(size) = params.window_min_size {
721 size_hints.min_size =
722 Some((f32::from(size.width) as i32, f32::from(size.height) as i32));
723 }
724 size_hints.max_size = Some((max_texture_size as i32, max_texture_size as i32));
725 check_reply(
726 || {
727 format!(
728 "X11 change of WM_SIZE_HINTS failed. max_size: {:?}",
729 max_texture_size
730 )
731 },
732 size_hints.set_normal_hints(xcb, x_window),
733 )?;
734
735 let display = Rc::new(X11Display::new(xcb, scale_factor, x_screen_index)?);
736
737 Ok(Self {
738 parent,
739 children: FxHashSet::default(),
740 client,
741 executor,
742 display,
743 x_root_window: visual_set.root,
744 x_screen_index,
745 visual_id: visual.id,
746 bounds: bounds.to_pixels(scale_factor),
747 scale_factor,
748 renderer,
749 atoms: *atoms,
750 input_handler: None,
751 active: false,
752 hovered: false,
753 fullscreen: false,
754 maximized_vertical: false,
755 maximized_horizontal: false,
756 hidden: false,
757 appearance,
758 handle,
759 background_appearance: WindowBackgroundAppearance::Opaque,
760 destroyed: false,
761 client_side_decorations_supported,
762 decorations: WindowDecorations::Server,
763 last_insets: [0, 0, 0, 0],
764 edge_constraints: None,
765 counter_id: sync_request_counter,
766 last_sync_counter: None,
767 })
768 });
769
770 if setup_result.is_err() {
771 check_reply(
772 || "X11 DestroyWindow failed while cleaning it up after setup failure.",
773 xcb.destroy_window(x_window),
774 )?;
775 xcb_flush(xcb);
776 }
777
778 setup_result
779 }
780
781 fn content_size(&self) -> Size<Pixels> {
782 self.bounds.size
783 }
784}
785
786pub(crate) struct X11Window(pub X11WindowStatePtr);
787
788impl Drop for X11Window {
789 fn drop(&mut self) {
790 let mut state = self.0.state.borrow_mut();
791
792 if let Some(parent) = state.parent.as_ref() {
793 parent.state.borrow_mut().children.remove(&self.0.x_window);
794 }
795
796 state.renderer.destroy();
797
798 let destroy_x_window = maybe!({
799 check_reply(
800 || "X11 DestroyWindow failure.",
801 self.0.xcb.destroy_window(self.0.x_window),
802 )?;
803 xcb_flush(&self.0.xcb);
804
805 anyhow::Ok(())
806 })
807 .log_err();
808
809 if destroy_x_window.is_some() {
810 state.destroyed = true;
811
812 let this_ptr = self.0.clone();
813 let client_ptr = state.client.clone();
814 state
815 .executor
816 .spawn(async move {
817 this_ptr.close();
818 client_ptr.drop_window(this_ptr.x_window);
819 })
820 .detach();
821 }
822
823 drop(state);
824 }
825}
826
827enum WmHintPropertyState {
828 // Remove = 0,
829 // Add = 1,
830 Toggle = 2,
831}
832
833impl X11Window {
834 pub fn new(
835 handle: AnyWindowHandle,
836 client: X11ClientStatePtr,
837 executor: ForegroundExecutor,
838 gpu_context: gpui_wgpu::GpuContext,
839 compositor_gpu: Option<CompositorGpuHint>,
840 params: WindowParams,
841 xcb: &Rc<XCBConnection>,
842 client_side_decorations_supported: bool,
843 x_main_screen_index: usize,
844 x_window: xproto::Window,
845 atoms: &XcbAtoms,
846 scale_factor: f32,
847 appearance: WindowAppearance,
848 parent_window: Option<X11WindowStatePtr>,
849 ) -> anyhow::Result<Self> {
850 let ptr = X11WindowStatePtr {
851 state: Rc::new(RefCell::new(X11WindowState::new(
852 handle,
853 client,
854 executor,
855 gpu_context,
856 compositor_gpu,
857 params,
858 xcb,
859 client_side_decorations_supported,
860 x_main_screen_index,
861 x_window,
862 atoms,
863 scale_factor,
864 appearance,
865 parent_window,
866 )?)),
867 callbacks: Rc::new(RefCell::new(Callbacks::default())),
868 xcb: xcb.clone(),
869 x_window,
870 };
871
872 let state = ptr.state.borrow_mut();
873 ptr.set_wm_properties(state)?;
874
875 Ok(Self(ptr))
876 }
877
878 fn set_wm_hints<C: Display + Send + Sync + 'static, F: FnOnce() -> C>(
879 &self,
880 failure_context: F,
881 wm_hint_property_state: WmHintPropertyState,
882 prop1: u32,
883 prop2: u32,
884 ) -> anyhow::Result<()> {
885 let state = self.0.state.borrow();
886 let message = ClientMessageEvent::new(
887 32,
888 self.0.x_window,
889 state.atoms._NET_WM_STATE,
890 [wm_hint_property_state as u32, prop1, prop2, 1, 0],
891 );
892 check_reply(
893 failure_context,
894 self.0.xcb.send_event(
895 false,
896 state.x_root_window,
897 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
898 message,
899 ),
900 )?;
901 xcb_flush(&self.0.xcb);
902 Ok(())
903 }
904
905 fn get_root_position(
906 &self,
907 position: Point<Pixels>,
908 ) -> anyhow::Result<TranslateCoordinatesReply> {
909 let state = self.0.state.borrow();
910 get_reply(
911 || "X11 TranslateCoordinates failed.",
912 self.0.xcb.translate_coordinates(
913 self.0.x_window,
914 state.x_root_window,
915 (f32::from(position.x) * state.scale_factor) as i16,
916 (f32::from(position.y) * state.scale_factor) as i16,
917 ),
918 )
919 }
920
921 fn send_moveresize(&self, flag: u32) -> anyhow::Result<()> {
922 let state = self.0.state.borrow();
923
924 check_reply(
925 || "X11 UngrabPointer before move/resize of window failed.",
926 self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
927 )?;
928
929 let pointer = get_reply(
930 || "X11 QueryPointer before move/resize of window failed.",
931 self.0.xcb.query_pointer(self.0.x_window),
932 )?;
933 let message = ClientMessageEvent::new(
934 32,
935 self.0.x_window,
936 state.atoms._NET_WM_MOVERESIZE,
937 [
938 pointer.root_x as u32,
939 pointer.root_y as u32,
940 flag,
941 0, // Left mouse button
942 0,
943 ],
944 );
945 check_reply(
946 || "X11 SendEvent to move/resize window failed.",
947 self.0.xcb.send_event(
948 false,
949 state.x_root_window,
950 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
951 message,
952 ),
953 )?;
954
955 xcb_flush(&self.0.xcb);
956 Ok(())
957 }
958}
959
960impl X11WindowStatePtr {
961 pub fn should_close(&self) -> bool {
962 let mut cb = self.callbacks.borrow_mut();
963 if let Some(mut should_close) = cb.should_close.take() {
964 let result = (should_close)();
965 cb.should_close = Some(should_close);
966 result
967 } else {
968 true
969 }
970 }
971
972 pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) -> anyhow::Result<()> {
973 let state = self.state.borrow_mut();
974 if event.atom == state.atoms._NET_WM_STATE {
975 self.set_wm_properties(state)?;
976 } else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
977 self.set_edge_constraints(state)?;
978 }
979 Ok(())
980 }
981
982 fn set_edge_constraints(
983 &self,
984 mut state: std::cell::RefMut<X11WindowState>,
985 ) -> anyhow::Result<()> {
986 let reply = get_reply(
987 || "X11 GetProperty for _GTK_EDGE_CONSTRAINTS failed.",
988 self.xcb.get_property(
989 false,
990 self.x_window,
991 state.atoms._GTK_EDGE_CONSTRAINTS,
992 xproto::AtomEnum::CARDINAL,
993 0,
994 4,
995 ),
996 )?;
997
998 if reply.value_len != 0 {
999 if let Ok(bytes) = reply.value[0..4].try_into() {
1000 let atom = u32::from_ne_bytes(bytes);
1001 let edge_constraints = EdgeConstraints::from_atom(atom);
1002 state.edge_constraints.replace(edge_constraints);
1003 } else {
1004 log::error!("Failed to parse GTK_EDGE_CONSTRAINTS");
1005 }
1006 }
1007
1008 Ok(())
1009 }
1010
1011 fn set_wm_properties(
1012 &self,
1013 mut state: std::cell::RefMut<X11WindowState>,
1014 ) -> anyhow::Result<()> {
1015 let reply = get_reply(
1016 || "X11 GetProperty for _NET_WM_STATE failed.",
1017 self.xcb.get_property(
1018 false,
1019 self.x_window,
1020 state.atoms._NET_WM_STATE,
1021 xproto::AtomEnum::ATOM,
1022 0,
1023 u32::MAX,
1024 ),
1025 )?;
1026
1027 let atoms = reply
1028 .value
1029 .chunks_exact(4)
1030 .map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
1031
1032 state.active = false;
1033 state.fullscreen = false;
1034 state.maximized_vertical = false;
1035 state.maximized_horizontal = false;
1036 state.hidden = false;
1037
1038 for atom in atoms {
1039 if atom == state.atoms._NET_WM_STATE_FOCUSED {
1040 state.active = true;
1041 } else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
1042 state.fullscreen = true;
1043 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
1044 state.maximized_vertical = true;
1045 } else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
1046 state.maximized_horizontal = true;
1047 } else if atom == state.atoms._NET_WM_STATE_HIDDEN {
1048 state.hidden = true;
1049 }
1050 }
1051
1052 Ok(())
1053 }
1054
1055 pub fn add_child(&self, child: xproto::Window) {
1056 let mut state = self.state.borrow_mut();
1057 state.children.insert(child);
1058 }
1059
1060 pub fn is_blocked(&self) -> bool {
1061 let state = self.state.borrow();
1062 !state.children.is_empty()
1063 }
1064
1065 pub fn close(&self) {
1066 let state = self.state.borrow();
1067 let client = state.client.clone();
1068 #[allow(clippy::mutable_key_type)]
1069 let children = state.children.clone();
1070 drop(state);
1071
1072 if let Some(client) = client.get_client() {
1073 for child in children {
1074 if let Some(child_window) = client.get_window(child) {
1075 child_window.close();
1076 }
1077 }
1078 }
1079
1080 let mut callbacks = self.callbacks.borrow_mut();
1081 if let Some(fun) = callbacks.close.take() {
1082 fun()
1083 }
1084 }
1085
1086 pub fn refresh(&self, request_frame_options: RequestFrameOptions) {
1087 let callback = self.callbacks.borrow_mut().request_frame.take();
1088 if let Some(mut fun) = callback {
1089 fun(request_frame_options);
1090 self.callbacks.borrow_mut().request_frame = Some(fun);
1091 }
1092 }
1093
1094 pub fn handle_input(&self, input: PlatformInput) {
1095 if self.is_blocked() {
1096 return;
1097 }
1098 let callback = self.callbacks.borrow_mut().input.take();
1099 if let Some(mut fun) = callback {
1100 let result = fun(input.clone());
1101 self.callbacks.borrow_mut().input = Some(fun);
1102 if !result.propagate {
1103 return;
1104 }
1105 }
1106 if let PlatformInput::KeyDown(event) = input {
1107 // only allow shift modifier when inserting text
1108 if event.keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
1109 let mut state = self.state.borrow_mut();
1110 if let Some(mut input_handler) = state.input_handler.take() {
1111 if let Some(key_char) = &event.keystroke.key_char {
1112 drop(state);
1113 input_handler.replace_text_in_range(None, key_char);
1114 state = self.state.borrow_mut();
1115 }
1116 state.input_handler = Some(input_handler);
1117 }
1118 }
1119 }
1120 }
1121
1122 pub fn handle_ime_commit(&self, text: String) {
1123 if self.is_blocked() {
1124 return;
1125 }
1126 let mut state = self.state.borrow_mut();
1127 if let Some(mut input_handler) = state.input_handler.take() {
1128 drop(state);
1129 input_handler.replace_text_in_range(None, &text);
1130 let mut state = self.state.borrow_mut();
1131 state.input_handler = Some(input_handler);
1132 }
1133 }
1134
1135 pub fn handle_ime_preedit(&self, text: String) {
1136 if self.is_blocked() {
1137 return;
1138 }
1139 let mut state = self.state.borrow_mut();
1140 if let Some(mut input_handler) = state.input_handler.take() {
1141 drop(state);
1142 input_handler.replace_and_mark_text_in_range(None, &text, None);
1143 let mut state = self.state.borrow_mut();
1144 state.input_handler = Some(input_handler);
1145 }
1146 }
1147
1148 pub fn handle_ime_unmark(&self) {
1149 if self.is_blocked() {
1150 return;
1151 }
1152 let mut state = self.state.borrow_mut();
1153 if let Some(mut input_handler) = state.input_handler.take() {
1154 drop(state);
1155 input_handler.unmark_text();
1156 let mut state = self.state.borrow_mut();
1157 state.input_handler = Some(input_handler);
1158 }
1159 }
1160
1161 pub fn handle_ime_delete(&self) {
1162 if self.is_blocked() {
1163 return;
1164 }
1165 let mut state = self.state.borrow_mut();
1166 if let Some(mut input_handler) = state.input_handler.take() {
1167 drop(state);
1168 if let Some(marked) = input_handler.marked_text_range() {
1169 input_handler.replace_text_in_range(Some(marked), "");
1170 }
1171 let mut state = self.state.borrow_mut();
1172 state.input_handler = Some(input_handler);
1173 }
1174 }
1175
1176 pub fn get_ime_area(&self) -> Option<Bounds<ScaledPixels>> {
1177 let mut state = self.state.borrow_mut();
1178 let scale_factor = state.scale_factor;
1179 let mut bounds: Option<Bounds<Pixels>> = None;
1180 if let Some(mut input_handler) = state.input_handler.take() {
1181 drop(state);
1182 if let Some(selection) = input_handler.selected_text_range(true) {
1183 bounds = input_handler.bounds_for_range(selection.range);
1184 }
1185 let mut state = self.state.borrow_mut();
1186 state.input_handler = Some(input_handler);
1187 };
1188 bounds.map(|b| b.scale(scale_factor))
1189 }
1190
1191 pub fn set_bounds(&self, bounds: Bounds<i32>) -> anyhow::Result<()> {
1192 let (is_resize, content_size, scale_factor) = {
1193 let mut state = self.state.borrow_mut();
1194 let bounds = bounds.map(|f| px(f as f32 / state.scale_factor));
1195
1196 let is_resize = bounds.size.width != state.bounds.size.width
1197 || bounds.size.height != state.bounds.size.height;
1198
1199 // If it's a resize event (only width/height changed), we ignore `bounds.origin`
1200 // because it contains wrong values.
1201 if is_resize {
1202 state.bounds.size = bounds.size;
1203 } else {
1204 state.bounds = bounds;
1205 }
1206
1207 let gpu_size = query_render_extent(&self.xcb, self.x_window)?;
1208 state.renderer.update_drawable_size(gpu_size);
1209 let result = (is_resize, state.content_size(), state.scale_factor);
1210 if let Some(value) = state.last_sync_counter.take() {
1211 check_reply(
1212 || "X11 sync SetCounter failed.",
1213 sync::set_counter(&self.xcb, state.counter_id, value),
1214 )?;
1215 }
1216 result
1217 };
1218
1219 let mut callbacks = self.callbacks.borrow_mut();
1220 if let Some(ref mut fun) = callbacks.resize {
1221 fun(content_size, scale_factor)
1222 }
1223
1224 if !is_resize && let Some(ref mut fun) = callbacks.moved {
1225 fun();
1226 }
1227
1228 Ok(())
1229 }
1230
1231 pub fn set_active(&self, focus: bool) {
1232 let callback = self.callbacks.borrow_mut().active_status_change.take();
1233 if let Some(mut fun) = callback {
1234 fun(focus);
1235 self.callbacks.borrow_mut().active_status_change = Some(fun);
1236 }
1237 }
1238
1239 pub fn set_hovered(&self, focus: bool) {
1240 let callback = self.callbacks.borrow_mut().hovered_status_change.take();
1241 if let Some(mut fun) = callback {
1242 fun(focus);
1243 self.callbacks.borrow_mut().hovered_status_change = Some(fun);
1244 }
1245 }
1246
1247 pub fn set_appearance(&mut self, appearance: WindowAppearance) {
1248 let mut state = self.state.borrow_mut();
1249 state.appearance = appearance;
1250 let is_transparent = state.is_transparent();
1251 state.renderer.update_transparency(is_transparent);
1252 state.appearance = appearance;
1253 drop(state);
1254 let callback = self.callbacks.borrow_mut().appearance_changed.take();
1255 if let Some(mut fun) = callback {
1256 fun();
1257 self.callbacks.borrow_mut().appearance_changed = Some(fun);
1258 }
1259 }
1260
1261 pub fn set_button_layout(&self) {
1262 let callback = self.callbacks.borrow_mut().button_layout_changed.take();
1263 if let Some(mut fun) = callback {
1264 fun();
1265 self.callbacks.borrow_mut().button_layout_changed = Some(fun);
1266 }
1267 }
1268}
1269
1270impl PlatformWindow for X11Window {
1271 fn bounds(&self) -> Bounds<Pixels> {
1272 self.0.state.borrow().bounds
1273 }
1274
1275 fn is_maximized(&self) -> bool {
1276 let state = self.0.state.borrow();
1277
1278 // A maximized window that gets minimized will still retain its maximized state.
1279 !state.hidden && state.maximized_vertical && state.maximized_horizontal
1280 }
1281
1282 fn window_bounds(&self) -> WindowBounds {
1283 let state = self.0.state.borrow();
1284 if self.is_maximized() {
1285 WindowBounds::Maximized(state.bounds)
1286 } else {
1287 WindowBounds::Windowed(state.bounds)
1288 }
1289 }
1290
1291 fn inner_window_bounds(&self) -> WindowBounds {
1292 let state = self.0.state.borrow();
1293 if self.is_maximized() {
1294 WindowBounds::Maximized(state.bounds)
1295 } else {
1296 let mut bounds = state.bounds;
1297 let [left, right, top, bottom] = state.last_insets;
1298
1299 let [left, right, top, bottom] = [
1300 px((left as f32) / state.scale_factor),
1301 px((right as f32) / state.scale_factor),
1302 px((top as f32) / state.scale_factor),
1303 px((bottom as f32) / state.scale_factor),
1304 ];
1305
1306 bounds.origin.x += left;
1307 bounds.origin.y += top;
1308 bounds.size.width -= left + right;
1309 bounds.size.height -= top + bottom;
1310
1311 WindowBounds::Windowed(bounds)
1312 }
1313 }
1314
1315 fn content_size(&self) -> Size<Pixels> {
1316 // After the wgpu migration, X11WindowState::content_size() returns logical pixels
1317 // (bounds.size is already divided by scale_factor in set_bounds), so no further
1318 // division is needed here. This matches the Wayland implementation.
1319 self.0.state.borrow().content_size()
1320 }
1321
1322 fn resize(&mut self, size: Size<Pixels>) {
1323 let state = self.0.state.borrow();
1324 let size = size.to_device_pixels(state.scale_factor);
1325 let width = size.width.0 as u32;
1326 let height = size.height.0 as u32;
1327
1328 check_reply(
1329 || {
1330 format!(
1331 "X11 ConfigureWindow failed. width: {}, height: {}",
1332 width, height
1333 )
1334 },
1335 self.0.xcb.configure_window(
1336 self.0.x_window,
1337 &xproto::ConfigureWindowAux::new()
1338 .width(width)
1339 .height(height),
1340 ),
1341 )
1342 .log_err();
1343 xcb_flush(&self.0.xcb);
1344 }
1345
1346 fn scale_factor(&self) -> f32 {
1347 self.0.state.borrow().scale_factor
1348 }
1349
1350 fn appearance(&self) -> WindowAppearance {
1351 self.0.state.borrow().appearance
1352 }
1353
1354 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
1355 Some(self.0.state.borrow().display.clone())
1356 }
1357
1358 fn mouse_position(&self) -> Point<Pixels> {
1359 get_reply(
1360 || "X11 QueryPointer failed.",
1361 self.0.xcb.query_pointer(self.0.x_window),
1362 )
1363 .log_err()
1364 .map_or(Point::new(Pixels::ZERO, Pixels::ZERO), |reply| {
1365 Point::new((reply.root_x as u32).into(), (reply.root_y as u32).into())
1366 })
1367 }
1368
1369 fn modifiers(&self) -> Modifiers {
1370 self.0
1371 .state
1372 .borrow()
1373 .client
1374 .0
1375 .upgrade()
1376 .map(|ref_cell| ref_cell.borrow().modifiers)
1377 .unwrap_or_default()
1378 }
1379
1380 fn capslock(&self) -> gpui::Capslock {
1381 self.0
1382 .state
1383 .borrow()
1384 .client
1385 .0
1386 .upgrade()
1387 .map(|ref_cell| ref_cell.borrow().capslock)
1388 .unwrap_or_default()
1389 }
1390
1391 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
1392 self.0.state.borrow_mut().input_handler = Some(input_handler);
1393 }
1394
1395 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
1396 self.0.state.borrow_mut().input_handler.take()
1397 }
1398
1399 fn prompt(
1400 &self,
1401 _level: PromptLevel,
1402 _msg: &str,
1403 _detail: Option<&str>,
1404 _answers: &[PromptButton],
1405 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
1406 None
1407 }
1408
1409 fn activate(&self) {
1410 let data = [1, xproto::Time::CURRENT_TIME.into(), 0, 0, 0];
1411 let message = xproto::ClientMessageEvent::new(
1412 32,
1413 self.0.x_window,
1414 self.0.state.borrow().atoms._NET_ACTIVE_WINDOW,
1415 data,
1416 );
1417 self.0
1418 .xcb
1419 .send_event(
1420 false,
1421 self.0.state.borrow().x_root_window,
1422 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1423 message,
1424 )
1425 .log_err();
1426 self.0
1427 .xcb
1428 .set_input_focus(
1429 xproto::InputFocus::POINTER_ROOT,
1430 self.0.x_window,
1431 xproto::Time::CURRENT_TIME,
1432 )
1433 .log_err();
1434 xcb_flush(&self.0.xcb);
1435 }
1436
1437 fn is_active(&self) -> bool {
1438 self.0.state.borrow().active
1439 }
1440
1441 fn is_hovered(&self) -> bool {
1442 self.0.state.borrow().hovered
1443 }
1444
1445 fn set_title(&mut self, title: &str) {
1446 check_reply(
1447 || "X11 ChangeProperty8 on WM_NAME failed.",
1448 self.0.xcb.change_property8(
1449 xproto::PropMode::REPLACE,
1450 self.0.x_window,
1451 xproto::AtomEnum::WM_NAME,
1452 xproto::AtomEnum::STRING,
1453 title.as_bytes(),
1454 ),
1455 )
1456 .log_err();
1457
1458 check_reply(
1459 || "X11 ChangeProperty8 on _NET_WM_NAME failed.",
1460 self.0.xcb.change_property8(
1461 xproto::PropMode::REPLACE,
1462 self.0.x_window,
1463 self.0.state.borrow().atoms._NET_WM_NAME,
1464 self.0.state.borrow().atoms.UTF8_STRING,
1465 title.as_bytes(),
1466 ),
1467 )
1468 .log_err();
1469 xcb_flush(&self.0.xcb);
1470 }
1471
1472 fn set_app_id(&mut self, app_id: &str) {
1473 let mut data = Vec::with_capacity(app_id.len() * 2 + 1);
1474 data.extend(app_id.bytes()); // instance https://unix.stackexchange.com/a/494170
1475 data.push(b'\0');
1476 data.extend(app_id.bytes()); // class
1477
1478 check_reply(
1479 || "X11 ChangeProperty8 for WM_CLASS failed.",
1480 self.0.xcb.change_property8(
1481 xproto::PropMode::REPLACE,
1482 self.0.x_window,
1483 xproto::AtomEnum::WM_CLASS,
1484 xproto::AtomEnum::STRING,
1485 &data,
1486 ),
1487 )
1488 .log_err();
1489 }
1490
1491 fn map_window(&mut self) -> anyhow::Result<()> {
1492 check_reply(
1493 || "X11 MapWindow failed.",
1494 self.0.xcb.map_window(self.0.x_window),
1495 )?;
1496 Ok(())
1497 }
1498
1499 fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
1500 let mut state = self.0.state.borrow_mut();
1501 state.background_appearance = background_appearance;
1502 let transparent = state.is_transparent();
1503 state.renderer.update_transparency(transparent);
1504 }
1505
1506 fn background_appearance(&self) -> WindowBackgroundAppearance {
1507 self.0.state.borrow().background_appearance
1508 }
1509
1510 fn is_subpixel_rendering_supported(&self) -> bool {
1511 self.0
1512 .state
1513 .borrow()
1514 .client
1515 .0
1516 .upgrade()
1517 .map(|ref_cell| {
1518 let state = ref_cell.borrow();
1519 state
1520 .gpu_context
1521 .borrow()
1522 .as_ref()
1523 .is_some_and(|ctx| ctx.supports_dual_source_blending())
1524 })
1525 .unwrap_or_default()
1526 }
1527
1528 fn minimize(&self) {
1529 let state = self.0.state.borrow();
1530 const WINDOW_ICONIC_STATE: u32 = 3;
1531 let message = ClientMessageEvent::new(
1532 32,
1533 self.0.x_window,
1534 state.atoms.WM_CHANGE_STATE,
1535 [WINDOW_ICONIC_STATE, 0, 0, 0, 0],
1536 );
1537 check_reply(
1538 || "X11 SendEvent to minimize window failed.",
1539 self.0.xcb.send_event(
1540 false,
1541 state.x_root_window,
1542 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1543 message,
1544 ),
1545 )
1546 .log_err();
1547 }
1548
1549 fn zoom(&self) {
1550 let state = self.0.state.borrow();
1551 self.set_wm_hints(
1552 || "X11 SendEvent to maximize a window failed.",
1553 WmHintPropertyState::Toggle,
1554 state.atoms._NET_WM_STATE_MAXIMIZED_VERT,
1555 state.atoms._NET_WM_STATE_MAXIMIZED_HORZ,
1556 )
1557 .log_err();
1558 }
1559
1560 fn toggle_fullscreen(&self) {
1561 let state = self.0.state.borrow();
1562 self.set_wm_hints(
1563 || "X11 SendEvent to fullscreen a window failed.",
1564 WmHintPropertyState::Toggle,
1565 state.atoms._NET_WM_STATE_FULLSCREEN,
1566 xproto::AtomEnum::NONE.into(),
1567 )
1568 .log_err();
1569 }
1570
1571 fn is_fullscreen(&self) -> bool {
1572 self.0.state.borrow().fullscreen
1573 }
1574
1575 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
1576 self.0.callbacks.borrow_mut().request_frame = Some(callback);
1577 }
1578
1579 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> gpui::DispatchEventResult>) {
1580 self.0.callbacks.borrow_mut().input = Some(callback);
1581 }
1582
1583 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1584 self.0.callbacks.borrow_mut().active_status_change = Some(callback);
1585 }
1586
1587 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
1588 self.0.callbacks.borrow_mut().hovered_status_change = Some(callback);
1589 }
1590
1591 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
1592 self.0.callbacks.borrow_mut().resize = Some(callback);
1593 }
1594
1595 fn on_moved(&self, callback: Box<dyn FnMut()>) {
1596 self.0.callbacks.borrow_mut().moved = Some(callback);
1597 }
1598
1599 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
1600 self.0.callbacks.borrow_mut().should_close = Some(callback);
1601 }
1602
1603 fn on_close(&self, callback: Box<dyn FnOnce()>) {
1604 self.0.callbacks.borrow_mut().close = Some(callback);
1605 }
1606
1607 fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
1608 }
1609
1610 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
1611 self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
1612 }
1613
1614 fn on_button_layout_changed(&self, callback: Box<dyn FnMut()>) {
1615 self.0.callbacks.borrow_mut().button_layout_changed = Some(callback);
1616 }
1617
1618 fn draw(&self, scene: &Scene) {
1619 let mut inner = self.0.state.borrow_mut();
1620
1621 if inner.renderer.device_lost() {
1622 let raw_window = RawWindow {
1623 connection: as_raw_xcb_connection::AsRawXcbConnection::as_raw_xcb_connection(
1624 &*self.0.xcb,
1625 ) as *mut _,
1626 screen_id: inner.x_screen_index,
1627 window_id: self.0.x_window,
1628 visual_id: inner.visual_id,
1629 };
1630 inner.renderer.recover(&raw_window).unwrap_or_else(|err| {
1631 panic!(
1632 "GPU device lost and recovery failed. \
1633 This may happen after system suspend/resume. \
1634 Please restart the application.\n\nError: {err}"
1635 )
1636 });
1637
1638 // The current scene references atlas textures that were cleared during recovery.
1639 // Skip this frame and let the next frame rebuild the scene with fresh textures.
1640 return;
1641 }
1642
1643 inner.renderer.draw(scene);
1644 }
1645
1646 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
1647 let inner = self.0.state.borrow();
1648 inner.renderer.sprite_atlas().clone()
1649 }
1650
1651 fn show_window_menu(&self, position: Point<Pixels>) {
1652 let state = self.0.state.borrow();
1653
1654 check_reply(
1655 || "X11 UngrabPointer failed.",
1656 self.0.xcb.ungrab_pointer(x11rb::CURRENT_TIME),
1657 )
1658 .log_err();
1659
1660 let Some(coords) = self.get_root_position(position).log_err() else {
1661 return;
1662 };
1663 let message = ClientMessageEvent::new(
1664 32,
1665 self.0.x_window,
1666 state.atoms._GTK_SHOW_WINDOW_MENU,
1667 [
1668 XINPUT_ALL_DEVICE_GROUPS as u32,
1669 coords.dst_x as u32,
1670 coords.dst_y as u32,
1671 0,
1672 0,
1673 ],
1674 );
1675 check_reply(
1676 || "X11 SendEvent to show window menu failed.",
1677 self.0.xcb.send_event(
1678 false,
1679 state.x_root_window,
1680 xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY,
1681 message,
1682 ),
1683 )
1684 .log_err();
1685 }
1686
1687 fn start_window_move(&self) {
1688 const MOVERESIZE_MOVE: u32 = 8;
1689 self.send_moveresize(MOVERESIZE_MOVE).log_err();
1690 }
1691
1692 fn start_window_resize(&self, edge: ResizeEdge) {
1693 self.send_moveresize(resize_edge_to_moveresize(edge))
1694 .log_err();
1695 }
1696
1697 fn window_decorations(&self) -> gpui::Decorations {
1698 let state = self.0.state.borrow();
1699
1700 // Client window decorations require compositor support
1701 if !state.client_side_decorations_supported {
1702 return Decorations::Server;
1703 }
1704
1705 match state.decorations {
1706 WindowDecorations::Server => Decorations::Server,
1707 WindowDecorations::Client => {
1708 let tiling = if state.fullscreen {
1709 Tiling::tiled()
1710 } else if let Some(edge_constraints) = &state.edge_constraints {
1711 edge_constraints.to_tiling()
1712 } else {
1713 // https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
1714 Tiling {
1715 top: state.maximized_vertical,
1716 bottom: state.maximized_vertical,
1717 left: state.maximized_horizontal,
1718 right: state.maximized_horizontal,
1719 }
1720 };
1721 Decorations::Client { tiling }
1722 }
1723 }
1724 }
1725
1726 fn set_client_inset(&self, inset: Pixels) {
1727 let mut state = self.0.state.borrow_mut();
1728
1729 let dp = (f32::from(inset) * state.scale_factor) as u32;
1730
1731 let insets = if state.fullscreen {
1732 [0, 0, 0, 0]
1733 } else if let Some(edge_constraints) = &state.edge_constraints {
1734 let left = if edge_constraints.left_tiled { 0 } else { dp };
1735 let top = if edge_constraints.top_tiled { 0 } else { dp };
1736 let right = if edge_constraints.right_tiled { 0 } else { dp };
1737 let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
1738
1739 [left, right, top, bottom]
1740 } else {
1741 let (left, right) = if state.maximized_horizontal {
1742 (0, 0)
1743 } else {
1744 (dp, dp)
1745 };
1746 let (top, bottom) = if state.maximized_vertical {
1747 (0, 0)
1748 } else {
1749 (dp, dp)
1750 };
1751 [left, right, top, bottom]
1752 };
1753
1754 if state.last_insets != insets {
1755 state.last_insets = insets;
1756
1757 check_reply(
1758 || "X11 ChangeProperty for _GTK_FRAME_EXTENTS failed.",
1759 self.0.xcb.change_property(
1760 xproto::PropMode::REPLACE,
1761 self.0.x_window,
1762 state.atoms._GTK_FRAME_EXTENTS,
1763 xproto::AtomEnum::CARDINAL,
1764 size_of::<u32>() as u8 * 8,
1765 4,
1766 bytemuck::cast_slice::<u32, u8>(&insets),
1767 ),
1768 )
1769 .log_err();
1770 }
1771 }
1772
1773 fn request_decorations(&self, mut decorations: gpui::WindowDecorations) {
1774 let mut state = self.0.state.borrow_mut();
1775
1776 if matches!(decorations, gpui::WindowDecorations::Client)
1777 && !state.client_side_decorations_supported
1778 {
1779 log::info!(
1780 "x11: no compositor present, falling back to server-side window decorations"
1781 );
1782 decorations = gpui::WindowDecorations::Server;
1783 }
1784
1785 // https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
1786 let hints_data: [u32; 5] = match decorations {
1787 WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
1788 WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
1789 };
1790
1791 let success = check_reply(
1792 || "X11 ChangeProperty for _MOTIF_WM_HINTS failed.",
1793 self.0.xcb.change_property(
1794 xproto::PropMode::REPLACE,
1795 self.0.x_window,
1796 state.atoms._MOTIF_WM_HINTS,
1797 state.atoms._MOTIF_WM_HINTS,
1798 size_of::<u32>() as u8 * 8,
1799 5,
1800 bytemuck::cast_slice::<u32, u8>(&hints_data),
1801 ),
1802 )
1803 .log_err();
1804
1805 let Some(()) = success else {
1806 return;
1807 };
1808
1809 match decorations {
1810 WindowDecorations::Server => {
1811 state.decorations = WindowDecorations::Server;
1812 let is_transparent = state.is_transparent();
1813 state.renderer.update_transparency(is_transparent);
1814 }
1815 WindowDecorations::Client => {
1816 state.decorations = WindowDecorations::Client;
1817 let is_transparent = state.is_transparent();
1818 state.renderer.update_transparency(is_transparent);
1819 }
1820 }
1821
1822 drop(state);
1823 let mut callbacks = self.0.callbacks.borrow_mut();
1824 if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
1825 appearance_changed();
1826 }
1827 }
1828
1829 fn update_ime_position(&self, bounds: Bounds<Pixels>) {
1830 let state = self.0.state.borrow();
1831 let client = state.client.clone();
1832 drop(state);
1833 client.update_ime_position(bounds);
1834 }
1835
1836 fn gpu_specs(&self) -> Option<GpuSpecs> {
1837 self.0.state.borrow().renderer.gpu_specs().into()
1838 }
1839}