1 | use std::ffi::CString; |
2 | use std::mem::replace; |
3 | use std::os::raw::*; |
4 | use std::path::Path; |
5 | use std::sync::{Arc, Mutex, MutexGuard}; |
6 | use std::{cmp, env}; |
7 | |
8 | use tracing::{debug, info, warn}; |
9 | use x11rb::connection::Connection; |
10 | use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification}; |
11 | use x11rb::protocol::shape::SK; |
12 | use x11rb::protocol::xfixes::{ConnectionExt, RegionWrapper}; |
13 | use x11rb::protocol::xproto::{self, ConnectionExt as _, Rectangle}; |
14 | use x11rb::protocol::{randr, xinput}; |
15 | |
16 | use crate::cursor::{Cursor, CustomCursor as RootCustomCursor}; |
17 | use crate::dpi::{PhysicalPosition, PhysicalSize, Position, Size}; |
18 | use crate::error::{ExternalError, NotSupportedError, OsError as RootOsError}; |
19 | use crate::event::{Event, InnerSizeWriter, WindowEvent}; |
20 | use crate::event_loop::AsyncRequestSerial; |
21 | use crate::platform::x11::WindowType; |
22 | use crate::platform_impl::x11::atoms::*; |
23 | use crate::platform_impl::x11::{ |
24 | xinput_fp1616_to_float, MonitorHandle as X11MonitorHandle, WakeSender, X11Error, |
25 | }; |
26 | use crate::platform_impl::{ |
27 | Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformCustomCursor, |
28 | PlatformIcon, VideoModeHandle as PlatformVideoModeHandle, |
29 | }; |
30 | use crate::window::{ |
31 | CursorGrabMode, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, |
32 | WindowButtons, WindowLevel, |
33 | }; |
34 | |
35 | use super::util::{self, SelectedCursor}; |
36 | use super::{ |
37 | ffi, ActiveEventLoop, CookieResultExt, ImeRequest, ImeSender, VoidCookie, WindowId, XConnection, |
38 | }; |
39 | |
40 | #[derive (Debug)] |
41 | pub struct SharedState { |
42 | pub cursor_pos: Option<(f64, f64)>, |
43 | pub size: Option<(u32, u32)>, |
44 | pub position: Option<(i32, i32)>, |
45 | pub inner_position: Option<(i32, i32)>, |
46 | pub inner_position_rel_parent: Option<(i32, i32)>, |
47 | pub is_resizable: bool, |
48 | pub is_decorated: bool, |
49 | pub last_monitor: X11MonitorHandle, |
50 | pub dpi_adjusted: Option<(u32, u32)>, |
51 | pub(crate) fullscreen: Option<Fullscreen>, |
52 | // Set when application calls `set_fullscreen` when window is not visible |
53 | pub(crate) desired_fullscreen: Option<Option<Fullscreen>>, |
54 | // Used to restore position after exiting fullscreen |
55 | pub restore_position: Option<(i32, i32)>, |
56 | // Used to restore video mode after exiting fullscreen |
57 | pub desktop_video_mode: Option<(randr::Crtc, randr::Mode)>, |
58 | pub frame_extents: Option<util::FrameExtentsHeuristic>, |
59 | pub min_inner_size: Option<Size>, |
60 | pub max_inner_size: Option<Size>, |
61 | pub resize_increments: Option<Size>, |
62 | pub base_size: Option<Size>, |
63 | pub visibility: Visibility, |
64 | pub has_focus: bool, |
65 | // Use `Option` to not apply hittest logic when it was never requested. |
66 | pub cursor_hittest: Option<bool>, |
67 | } |
68 | |
69 | #[derive (Copy, Clone, Debug, Eq, PartialEq)] |
70 | pub enum Visibility { |
71 | No, |
72 | Yes, |
73 | // Waiting for VisibilityNotify |
74 | YesWait, |
75 | } |
76 | |
77 | impl SharedState { |
78 | fn new(last_monitor: X11MonitorHandle, window_attributes: &WindowAttributes) -> Mutex<Self> { |
79 | let visibility = |
80 | if window_attributes.visible { Visibility::YesWait } else { Visibility::No }; |
81 | |
82 | Mutex::new(SharedState { |
83 | last_monitor, |
84 | visibility, |
85 | |
86 | is_resizable: window_attributes.resizable, |
87 | is_decorated: window_attributes.decorations, |
88 | cursor_pos: None, |
89 | size: None, |
90 | position: None, |
91 | inner_position: None, |
92 | inner_position_rel_parent: None, |
93 | dpi_adjusted: None, |
94 | fullscreen: None, |
95 | desired_fullscreen: None, |
96 | restore_position: None, |
97 | desktop_video_mode: None, |
98 | frame_extents: None, |
99 | min_inner_size: None, |
100 | max_inner_size: None, |
101 | resize_increments: None, |
102 | base_size: None, |
103 | has_focus: false, |
104 | cursor_hittest: None, |
105 | }) |
106 | } |
107 | } |
108 | |
109 | unsafe impl Send for UnownedWindow {} |
110 | unsafe impl Sync for UnownedWindow {} |
111 | |
112 | pub struct UnownedWindow { |
113 | pub(crate) xconn: Arc<XConnection>, // never changes |
114 | xwindow: xproto::Window, // never changes |
115 | #[allow (dead_code)] |
116 | visual: u32, // never changes |
117 | root: xproto::Window, // never changes |
118 | #[allow (dead_code)] |
119 | screen_id: i32, // never changes |
120 | selected_cursor: Mutex<SelectedCursor>, |
121 | cursor_grabbed_mode: Mutex<CursorGrabMode>, |
122 | #[allow (clippy::mutex_atomic)] |
123 | cursor_visible: Mutex<bool>, |
124 | ime_sender: Mutex<ImeSender>, |
125 | pub shared_state: Mutex<SharedState>, |
126 | redraw_sender: WakeSender<WindowId>, |
127 | activation_sender: WakeSender<super::ActivationToken>, |
128 | } |
129 | |
130 | macro_rules! leap { |
131 | ($e:expr) => { |
132 | match $e { |
133 | Ok(x) => x, |
134 | Err(err) => return Err(os_error!(OsError::XError(X11Error::from(err).into()))), |
135 | } |
136 | }; |
137 | } |
138 | |
139 | impl UnownedWindow { |
140 | #[allow (clippy::unnecessary_cast)] |
141 | pub(crate) fn new( |
142 | event_loop: &ActiveEventLoop, |
143 | window_attrs: WindowAttributes, |
144 | ) -> Result<UnownedWindow, RootOsError> { |
145 | let xconn = &event_loop.xconn; |
146 | let atoms = xconn.atoms(); |
147 | |
148 | let screen_id = match window_attrs.platform_specific.x11.screen_id { |
149 | Some(id) => id, |
150 | None => xconn.default_screen_index() as c_int, |
151 | }; |
152 | |
153 | let screen = { |
154 | let screen_id_usize = usize::try_from(screen_id) |
155 | .map_err(|_| os_error!(OsError::Misc("screen id must be non-negative" )))?; |
156 | xconn.xcb_connection().setup().roots.get(screen_id_usize).ok_or(os_error!( |
157 | OsError::Misc("requested screen id not present in server's response" ) |
158 | ))? |
159 | }; |
160 | |
161 | #[cfg (feature = "rwh_06" )] |
162 | let root = match window_attrs.parent_window.as_ref().map(|handle| handle.0) { |
163 | Some(rwh_06::RawWindowHandle::Xlib(handle)) => handle.window as xproto::Window, |
164 | Some(rwh_06::RawWindowHandle::Xcb(handle)) => handle.window.get(), |
165 | Some(raw) => unreachable!("Invalid raw window handle {raw:?} on X11" ), |
166 | None => screen.root, |
167 | }; |
168 | #[cfg (not(feature = "rwh_06" ))] |
169 | let root = event_loop.root; |
170 | |
171 | let mut monitors = leap!(xconn.available_monitors()); |
172 | let guessed_monitor = if monitors.is_empty() { |
173 | X11MonitorHandle::dummy() |
174 | } else { |
175 | xconn |
176 | .query_pointer(root, util::VIRTUAL_CORE_POINTER) |
177 | .ok() |
178 | .and_then(|pointer_state| { |
179 | let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64); |
180 | |
181 | for i in 0..monitors.len() { |
182 | if monitors[i].rect.contains_point(x, y) { |
183 | return Some(monitors.swap_remove(i)); |
184 | } |
185 | } |
186 | |
187 | None |
188 | }) |
189 | .unwrap_or_else(|| monitors.swap_remove(0)) |
190 | }; |
191 | let scale_factor = guessed_monitor.scale_factor(); |
192 | |
193 | info!("Guessed window scale factor: {}" , scale_factor); |
194 | |
195 | let max_inner_size: Option<(u32, u32)> = |
196 | window_attrs.max_inner_size.map(|size| size.to_physical::<u32>(scale_factor).into()); |
197 | let min_inner_size: Option<(u32, u32)> = |
198 | window_attrs.min_inner_size.map(|size| size.to_physical::<u32>(scale_factor).into()); |
199 | |
200 | let position = |
201 | window_attrs.position.map(|position| position.to_physical::<i32>(scale_factor)); |
202 | |
203 | let dimensions = { |
204 | // x11 only applies constraints when the window is actively resized |
205 | // by the user, so we have to manually apply the initial constraints |
206 | let mut dimensions: (u32, u32) = window_attrs |
207 | .inner_size |
208 | .map(|size| size.to_physical::<u32>(scale_factor)) |
209 | .or_else(|| Some((800, 600).into())) |
210 | .map(Into::into) |
211 | .unwrap(); |
212 | if let Some(max) = max_inner_size { |
213 | dimensions.0 = cmp::min(dimensions.0, max.0); |
214 | dimensions.1 = cmp::min(dimensions.1, max.1); |
215 | } |
216 | if let Some(min) = min_inner_size { |
217 | dimensions.0 = cmp::max(dimensions.0, min.0); |
218 | dimensions.1 = cmp::max(dimensions.1, min.1); |
219 | } |
220 | debug!("Calculated physical dimensions: {}x {}" , dimensions.0, dimensions.1); |
221 | dimensions |
222 | }; |
223 | |
224 | // An iterator over the visuals matching screen id combined with their depths. |
225 | let mut all_visuals = screen |
226 | .allowed_depths |
227 | .iter() |
228 | .flat_map(|depth| depth.visuals.iter().map(move |visual| (visual, depth.depth))); |
229 | |
230 | // creating |
231 | let (visualtype, depth, require_colormap) = |
232 | match window_attrs.platform_specific.x11.visual_id { |
233 | Some(vi) => { |
234 | // Find this specific visual. |
235 | let (visualtype, depth) = |
236 | all_visuals.find(|(visual, _)| visual.visual_id == vi).ok_or_else( |
237 | || os_error!(OsError::XError(X11Error::NoSuchVisual(vi).into())), |
238 | )?; |
239 | |
240 | (Some(visualtype), depth, true) |
241 | }, |
242 | None if window_attrs.transparent => { |
243 | // Find a suitable visual, true color with 32 bits of depth. |
244 | all_visuals |
245 | .find_map(|(visual, depth)| { |
246 | (depth == 32 && visual.class == xproto::VisualClass::TRUE_COLOR) |
247 | .then_some((Some(visual), depth, true)) |
248 | }) |
249 | .unwrap_or_else(|| { |
250 | debug!( |
251 | "Could not set transparency, because XMatchVisualInfo returned \ |
252 | zero for the required parameters" |
253 | ); |
254 | (None as _, x11rb::COPY_FROM_PARENT as _, false) |
255 | }) |
256 | }, |
257 | _ => (None, x11rb::COPY_FROM_PARENT as _, false), |
258 | }; |
259 | let mut visual = visualtype.map_or(x11rb::COPY_FROM_PARENT, |v| v.visual_id); |
260 | |
261 | let window_attributes = { |
262 | use xproto::EventMask; |
263 | |
264 | let mut aux = xproto::CreateWindowAux::new(); |
265 | let event_mask = EventMask::EXPOSURE |
266 | | EventMask::STRUCTURE_NOTIFY |
267 | | EventMask::VISIBILITY_CHANGE |
268 | | EventMask::KEY_PRESS |
269 | | EventMask::KEY_RELEASE |
270 | | EventMask::KEYMAP_STATE |
271 | | EventMask::BUTTON_PRESS |
272 | | EventMask::BUTTON_RELEASE |
273 | | EventMask::POINTER_MOTION |
274 | | EventMask::PROPERTY_CHANGE; |
275 | |
276 | aux = aux.event_mask(event_mask).border_pixel(0); |
277 | |
278 | if window_attrs.platform_specific.x11.override_redirect { |
279 | aux = aux.override_redirect(true as u32); |
280 | } |
281 | |
282 | // Add a colormap if needed. |
283 | let colormap_visual = match window_attrs.platform_specific.x11.visual_id { |
284 | Some(vi) => Some(vi), |
285 | None if require_colormap => Some(visual), |
286 | _ => None, |
287 | }; |
288 | |
289 | if let Some(visual) = colormap_visual { |
290 | let colormap = leap!(xconn.xcb_connection().generate_id()); |
291 | leap!(xconn.xcb_connection().create_colormap( |
292 | xproto::ColormapAlloc::NONE, |
293 | colormap, |
294 | root, |
295 | visual, |
296 | )); |
297 | aux = aux.colormap(colormap); |
298 | } else { |
299 | aux = aux.colormap(0); |
300 | } |
301 | |
302 | aux |
303 | }; |
304 | |
305 | // Figure out the window's parent. |
306 | let parent = window_attrs.platform_specific.x11.embed_window.unwrap_or(root); |
307 | |
308 | // finally creating the window |
309 | let xwindow = { |
310 | let (x, y) = position.map_or((0, 0), Into::into); |
311 | let wid = leap!(xconn.xcb_connection().generate_id()); |
312 | let result = xconn.xcb_connection().create_window( |
313 | depth, |
314 | wid, |
315 | parent, |
316 | x, |
317 | y, |
318 | dimensions.0.try_into().unwrap(), |
319 | dimensions.1.try_into().unwrap(), |
320 | 0, |
321 | xproto::WindowClass::INPUT_OUTPUT, |
322 | visual, |
323 | &window_attributes, |
324 | ); |
325 | leap!(leap!(result).check()); |
326 | |
327 | wid |
328 | }; |
329 | |
330 | // The COPY_FROM_PARENT is a special value for the visual used to copy |
331 | // the visual from the parent window, thus we have to query the visual |
332 | // we've got when we built the window above. |
333 | if visual == x11rb::COPY_FROM_PARENT { |
334 | visual = leap!(leap!(xconn |
335 | .xcb_connection() |
336 | .get_window_attributes(xwindow as xproto::Window)) |
337 | .reply()) |
338 | .visual; |
339 | } |
340 | |
341 | #[allow (clippy::mutex_atomic)] |
342 | let mut window = UnownedWindow { |
343 | xconn: Arc::clone(xconn), |
344 | xwindow: xwindow as xproto::Window, |
345 | visual, |
346 | root, |
347 | screen_id, |
348 | selected_cursor: Default::default(), |
349 | cursor_grabbed_mode: Mutex::new(CursorGrabMode::None), |
350 | cursor_visible: Mutex::new(true), |
351 | ime_sender: Mutex::new(event_loop.ime_sender.clone()), |
352 | shared_state: SharedState::new(guessed_monitor, &window_attrs), |
353 | redraw_sender: event_loop.redraw_sender.clone(), |
354 | activation_sender: event_loop.activation_sender.clone(), |
355 | }; |
356 | |
357 | // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window |
358 | // title to determine placement/etc., so doing this after mapping would cause the WM to |
359 | // act on the wrong title state. |
360 | leap!(window.set_title_inner(&window_attrs.title)).ignore_error(); |
361 | leap!(window.set_decorations_inner(window_attrs.decorations)).ignore_error(); |
362 | |
363 | if let Some(theme) = window_attrs.preferred_theme { |
364 | leap!(window.set_theme_inner(Some(theme))).ignore_error(); |
365 | } |
366 | |
367 | // Embed the window if needed. |
368 | if window_attrs.platform_specific.x11.embed_window.is_some() { |
369 | window.embed_window()?; |
370 | } |
371 | |
372 | { |
373 | // Enable drag and drop (TODO: extend API to make this toggleable) |
374 | { |
375 | let dnd_aware_atom = atoms[XdndAware]; |
376 | let version = &[5u32]; // Latest version; hasn't changed since 2002 |
377 | leap!(xconn.change_property( |
378 | window.xwindow, |
379 | dnd_aware_atom, |
380 | u32::from(xproto::AtomEnum::ATOM), |
381 | xproto::PropMode::REPLACE, |
382 | version, |
383 | )) |
384 | .ignore_error(); |
385 | } |
386 | |
387 | // WM_CLASS must be set *before* mapping the window, as per ICCCM! |
388 | { |
389 | let (instance, class) = if let Some(name) = window_attrs.platform_specific.name { |
390 | (name.instance, name.general) |
391 | } else { |
392 | let class = env::args_os() |
393 | .next() |
394 | .as_ref() |
395 | // Default to the name of the binary (via argv[0]) |
396 | .and_then(|path| Path::new(path).file_name()) |
397 | .and_then(|bin_name| bin_name.to_str()) |
398 | .map(|bin_name| bin_name.to_owned()) |
399 | .unwrap_or_else(|| window_attrs.title.clone()); |
400 | // This environment variable is extraordinarily unlikely to actually be used... |
401 | let instance = env::var("RESOURCE_NAME" ).ok().unwrap_or_else(|| class.clone()); |
402 | (instance, class) |
403 | }; |
404 | |
405 | let class = format!(" {instance}\0{class}\0" ); |
406 | leap!(xconn.change_property( |
407 | window.xwindow, |
408 | xproto::Atom::from(xproto::AtomEnum::WM_CLASS), |
409 | xproto::Atom::from(xproto::AtomEnum::STRING), |
410 | xproto::PropMode::REPLACE, |
411 | class.as_bytes(), |
412 | )) |
413 | .ignore_error(); |
414 | } |
415 | |
416 | if let Some(flusher) = leap!(window.set_pid()) { |
417 | flusher.ignore_error() |
418 | } |
419 | |
420 | leap!(window.set_window_types(window_attrs.platform_specific.x11.x11_window_types)) |
421 | .ignore_error(); |
422 | |
423 | // Set size hints. |
424 | let mut min_inner_size = |
425 | window_attrs.min_inner_size.map(|size| size.to_physical::<u32>(scale_factor)); |
426 | let mut max_inner_size = |
427 | window_attrs.max_inner_size.map(|size| size.to_physical::<u32>(scale_factor)); |
428 | |
429 | if !window_attrs.resizable { |
430 | if util::wm_name_is_one_of(&["Xfwm4" ]) { |
431 | warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4" ); |
432 | } else { |
433 | max_inner_size = Some(dimensions.into()); |
434 | min_inner_size = Some(dimensions.into()); |
435 | } |
436 | } |
437 | |
438 | let shared_state = window.shared_state.get_mut().unwrap(); |
439 | shared_state.min_inner_size = min_inner_size.map(Into::into); |
440 | shared_state.max_inner_size = max_inner_size.map(Into::into); |
441 | shared_state.resize_increments = window_attrs.resize_increments; |
442 | shared_state.base_size = window_attrs.platform_specific.x11.base_size; |
443 | |
444 | let normal_hints = WmSizeHints { |
445 | position: position.map(|PhysicalPosition { x, y }| { |
446 | (WmSizeHintsSpecification::UserSpecified, x, y) |
447 | }), |
448 | size: Some(( |
449 | WmSizeHintsSpecification::UserSpecified, |
450 | cast_dimension_to_hint(dimensions.0), |
451 | cast_dimension_to_hint(dimensions.1), |
452 | )), |
453 | max_size: max_inner_size.map(cast_physical_size_to_hint), |
454 | min_size: min_inner_size.map(cast_physical_size_to_hint), |
455 | size_increment: window_attrs |
456 | .resize_increments |
457 | .map(|size| cast_size_to_hint(size, scale_factor)), |
458 | base_size: window_attrs |
459 | .platform_specific |
460 | .x11 |
461 | .base_size |
462 | .map(|size| cast_size_to_hint(size, scale_factor)), |
463 | aspect: None, |
464 | win_gravity: None, |
465 | }; |
466 | leap!(leap!(normal_hints.set( |
467 | xconn.xcb_connection(), |
468 | window.xwindow as xproto::Window, |
469 | xproto::AtomEnum::WM_NORMAL_HINTS, |
470 | )) |
471 | .check()); |
472 | |
473 | // Set window icons |
474 | if let Some(icon) = window_attrs.window_icon { |
475 | leap!(window.set_icon_inner(icon.inner)).ignore_error(); |
476 | } |
477 | |
478 | // Opt into handling window close |
479 | let result = xconn.xcb_connection().change_property( |
480 | xproto::PropMode::REPLACE, |
481 | window.xwindow, |
482 | atoms[WM_PROTOCOLS], |
483 | xproto::AtomEnum::ATOM, |
484 | 32, |
485 | 2, |
486 | bytemuck::cast_slice::<xproto::Atom, u8>(&[ |
487 | atoms[WM_DELETE_WINDOW], |
488 | atoms[_NET_WM_PING], |
489 | ]), |
490 | ); |
491 | leap!(result).ignore_error(); |
492 | |
493 | // Select XInput2 events |
494 | let mask = xinput::XIEventMask::MOTION |
495 | | xinput::XIEventMask::BUTTON_PRESS |
496 | | xinput::XIEventMask::BUTTON_RELEASE |
497 | | xinput::XIEventMask::ENTER |
498 | | xinput::XIEventMask::LEAVE |
499 | | xinput::XIEventMask::FOCUS_IN |
500 | | xinput::XIEventMask::FOCUS_OUT |
501 | | xinput::XIEventMask::TOUCH_BEGIN |
502 | | xinput::XIEventMask::TOUCH_UPDATE |
503 | | xinput::XIEventMask::TOUCH_END; |
504 | leap!(xconn.select_xinput_events(window.xwindow, super::ALL_MASTER_DEVICES, mask)) |
505 | .ignore_error(); |
506 | |
507 | // Set visibility (map window) |
508 | if window_attrs.visible { |
509 | leap!(xconn.xcb_connection().map_window(window.xwindow)).ignore_error(); |
510 | leap!(xconn.xcb_connection().configure_window( |
511 | xwindow, |
512 | &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE) |
513 | )) |
514 | .ignore_error(); |
515 | } |
516 | |
517 | // Attempt to make keyboard input repeat detectable |
518 | unsafe { |
519 | let mut supported_ptr = ffi::False; |
520 | (xconn.xlib.XkbSetDetectableAutoRepeat)( |
521 | xconn.display, |
522 | ffi::True, |
523 | &mut supported_ptr, |
524 | ); |
525 | if supported_ptr == ffi::False { |
526 | return Err(os_error!(OsError::Misc("`XkbSetDetectableAutoRepeat` failed" ))); |
527 | } |
528 | } |
529 | |
530 | // Try to create input context for the window. |
531 | if let Some(ime) = event_loop.ime.as_ref() { |
532 | let result = ime.borrow_mut().create_context(window.xwindow as ffi::Window, false); |
533 | leap!(result); |
534 | } |
535 | |
536 | // These properties must be set after mapping |
537 | if window_attrs.maximized { |
538 | leap!(window.set_maximized_inner(window_attrs.maximized)).ignore_error(); |
539 | } |
540 | if window_attrs.fullscreen.is_some() { |
541 | if let Some(flusher) = |
542 | leap!(window |
543 | .set_fullscreen_inner(window_attrs.fullscreen.clone().map(Into::into))) |
544 | { |
545 | flusher.ignore_error() |
546 | } |
547 | |
548 | if let Some(PhysicalPosition { x, y }) = position { |
549 | let shared_state = window.shared_state.get_mut().unwrap(); |
550 | |
551 | shared_state.restore_position = Some((x, y)); |
552 | } |
553 | } |
554 | |
555 | leap!(window.set_window_level_inner(window_attrs.window_level)).ignore_error(); |
556 | } |
557 | |
558 | window.set_cursor(window_attrs.cursor); |
559 | |
560 | // Remove the startup notification if we have one. |
561 | if let Some(startup) = window_attrs.platform_specific.activation_token.as_ref() { |
562 | leap!(xconn.remove_activation_token(xwindow, &startup.token)); |
563 | } |
564 | |
565 | // We never want to give the user a broken window, since by then, it's too late to handle. |
566 | let window = leap!(xconn.sync_with_server().map(|_| window)); |
567 | |
568 | Ok(window) |
569 | } |
570 | |
571 | /// Embed this window into a parent window. |
572 | pub(super) fn embed_window(&self) -> Result<(), RootOsError> { |
573 | let atoms = self.xconn.atoms(); |
574 | leap!(leap!(self.xconn.change_property( |
575 | self.xwindow, |
576 | atoms[_XEMBED], |
577 | atoms[_XEMBED], |
578 | xproto::PropMode::REPLACE, |
579 | &[0u32, 1u32], |
580 | )) |
581 | .check()); |
582 | |
583 | Ok(()) |
584 | } |
585 | |
586 | pub(super) fn shared_state_lock(&self) -> MutexGuard<'_, SharedState> { |
587 | self.shared_state.lock().unwrap() |
588 | } |
589 | |
590 | fn set_pid(&self) -> Result<Option<VoidCookie<'_>>, X11Error> { |
591 | let atoms = self.xconn.atoms(); |
592 | let pid_atom = atoms[_NET_WM_PID]; |
593 | let client_machine_atom = atoms[WM_CLIENT_MACHINE]; |
594 | |
595 | // Get the hostname and the PID. |
596 | let uname = rustix::system::uname(); |
597 | let pid = rustix::process::getpid(); |
598 | |
599 | self.xconn |
600 | .change_property( |
601 | self.xwindow, |
602 | pid_atom, |
603 | xproto::Atom::from(xproto::AtomEnum::CARDINAL), |
604 | xproto::PropMode::REPLACE, |
605 | &[pid.as_raw_nonzero().get() as util::Cardinal], |
606 | )? |
607 | .ignore_error(); |
608 | let flusher = self.xconn.change_property( |
609 | self.xwindow, |
610 | client_machine_atom, |
611 | xproto::Atom::from(xproto::AtomEnum::STRING), |
612 | xproto::PropMode::REPLACE, |
613 | uname.nodename().to_bytes(), |
614 | ); |
615 | flusher.map(Some) |
616 | } |
617 | |
618 | fn set_window_types(&self, window_types: Vec<WindowType>) -> Result<VoidCookie<'_>, X11Error> { |
619 | let atoms = self.xconn.atoms(); |
620 | let hint_atom = atoms[_NET_WM_WINDOW_TYPE]; |
621 | let atoms: Vec<_> = window_types.iter().map(|t| t.as_atom(&self.xconn)).collect(); |
622 | |
623 | self.xconn.change_property( |
624 | self.xwindow, |
625 | hint_atom, |
626 | xproto::Atom::from(xproto::AtomEnum::ATOM), |
627 | xproto::PropMode::REPLACE, |
628 | &atoms, |
629 | ) |
630 | } |
631 | |
632 | pub fn set_theme_inner(&self, theme: Option<Theme>) -> Result<VoidCookie<'_>, X11Error> { |
633 | let atoms = self.xconn.atoms(); |
634 | let hint_atom = atoms[_GTK_THEME_VARIANT]; |
635 | let utf8_atom = atoms[UTF8_STRING]; |
636 | let variant = match theme { |
637 | Some(Theme::Dark) => "dark" , |
638 | Some(Theme::Light) => "light" , |
639 | None => "dark" , |
640 | }; |
641 | let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte" ); |
642 | self.xconn.change_property( |
643 | self.xwindow, |
644 | hint_atom, |
645 | utf8_atom, |
646 | xproto::PropMode::REPLACE, |
647 | variant.as_bytes(), |
648 | ) |
649 | } |
650 | |
651 | #[inline ] |
652 | pub fn set_theme(&self, theme: Option<Theme>) { |
653 | self.set_theme_inner(theme).expect("Failed to change window theme" ).ignore_error(); |
654 | |
655 | self.xconn.flush_requests().expect("Failed to change window theme" ); |
656 | } |
657 | |
658 | fn set_netwm( |
659 | &self, |
660 | operation: util::StateOperation, |
661 | properties: (u32, u32, u32, u32), |
662 | ) -> Result<VoidCookie<'_>, X11Error> { |
663 | let atoms = self.xconn.atoms(); |
664 | let state_atom = atoms[_NET_WM_STATE]; |
665 | self.xconn.send_client_msg( |
666 | self.xwindow, |
667 | self.root, |
668 | state_atom, |
669 | Some(xproto::EventMask::SUBSTRUCTURE_REDIRECT | xproto::EventMask::SUBSTRUCTURE_NOTIFY), |
670 | [operation as u32, properties.0, properties.1, properties.2, properties.3], |
671 | ) |
672 | } |
673 | |
674 | fn set_fullscreen_hint(&self, fullscreen: bool) -> Result<VoidCookie<'_>, X11Error> { |
675 | let atoms = self.xconn.atoms(); |
676 | let fullscreen_atom = atoms[_NET_WM_STATE_FULLSCREEN]; |
677 | let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0)); |
678 | |
679 | if fullscreen { |
680 | // Ensure that the fullscreen window receives input focus to prevent |
681 | // locking up the user's display. |
682 | self.xconn |
683 | .xcb_connection() |
684 | .set_input_focus(xproto::InputFocus::PARENT, self.xwindow, x11rb::CURRENT_TIME)? |
685 | .ignore_error(); |
686 | } |
687 | |
688 | flusher |
689 | } |
690 | |
691 | fn set_fullscreen_inner( |
692 | &self, |
693 | fullscreen: Option<Fullscreen>, |
694 | ) -> Result<Option<VoidCookie<'_>>, X11Error> { |
695 | let mut shared_state_lock = self.shared_state_lock(); |
696 | |
697 | match shared_state_lock.visibility { |
698 | // Setting fullscreen on a window that is not visible will generate an error. |
699 | Visibility::No | Visibility::YesWait => { |
700 | shared_state_lock.desired_fullscreen = Some(fullscreen); |
701 | return Ok(None); |
702 | }, |
703 | Visibility::Yes => (), |
704 | } |
705 | |
706 | let old_fullscreen = shared_state_lock.fullscreen.clone(); |
707 | if old_fullscreen == fullscreen { |
708 | return Ok(None); |
709 | } |
710 | shared_state_lock.fullscreen.clone_from(&fullscreen); |
711 | |
712 | match (&old_fullscreen, &fullscreen) { |
713 | // Store the desktop video mode before entering exclusive |
714 | // fullscreen, so we can restore it upon exit, as XRandR does not |
715 | // provide a mechanism to set this per app-session or restore this |
716 | // to the desktop video mode as macOS and Windows do |
717 | (&None, &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)))) |
718 | | ( |
719 | &Some(Fullscreen::Borderless(_)), |
720 | &Some(Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode))), |
721 | ) => { |
722 | let monitor = video_mode.monitor.as_ref().unwrap(); |
723 | shared_state_lock.desktop_video_mode = Some(( |
724 | monitor.id, |
725 | self.xconn.get_crtc_mode(monitor.id).expect("Failed to get desktop video mode" ), |
726 | )); |
727 | }, |
728 | // Restore desktop video mode upon exiting exclusive fullscreen |
729 | (&Some(Fullscreen::Exclusive(_)), &None) |
730 | | (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => { |
731 | let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap(); |
732 | self.xconn |
733 | .set_crtc_config(monitor_id, mode_id) |
734 | .expect("failed to restore desktop video mode" ); |
735 | }, |
736 | _ => (), |
737 | } |
738 | |
739 | drop(shared_state_lock); |
740 | |
741 | match fullscreen { |
742 | None => { |
743 | let flusher = self.set_fullscreen_hint(false); |
744 | let mut shared_state_lock = self.shared_state_lock(); |
745 | if let Some(position) = shared_state_lock.restore_position.take() { |
746 | drop(shared_state_lock); |
747 | self.set_position_inner(position.0, position.1) |
748 | .expect_then_ignore_error("Failed to restore window position" ); |
749 | } |
750 | flusher.map(Some) |
751 | }, |
752 | Some(fullscreen) => { |
753 | let (video_mode, monitor) = match fullscreen { |
754 | Fullscreen::Exclusive(PlatformVideoModeHandle::X(ref video_mode)) => { |
755 | (Some(video_mode), video_mode.monitor.clone().unwrap()) |
756 | }, |
757 | Fullscreen::Borderless(Some(PlatformMonitorHandle::X(monitor))) => { |
758 | (None, monitor) |
759 | }, |
760 | Fullscreen::Borderless(None) => { |
761 | (None, self.shared_state_lock().last_monitor.clone()) |
762 | }, |
763 | #[cfg (wayland_platform)] |
764 | _ => unreachable!(), |
765 | }; |
766 | |
767 | // Don't set fullscreen on an invalid dummy monitor handle |
768 | if monitor.is_dummy() { |
769 | return Ok(None); |
770 | } |
771 | |
772 | if let Some(video_mode) = video_mode { |
773 | // FIXME: this is actually not correct if we're setting the |
774 | // video mode to a resolution higher than the current |
775 | // desktop resolution, because XRandR does not automatically |
776 | // reposition the monitors to the right and below this |
777 | // monitor. |
778 | // |
779 | // What ends up happening is we will get the fullscreen |
780 | // window showing up on those monitors as well, because |
781 | // their virtual position now overlaps with the monitor that |
782 | // we just made larger.. |
783 | // |
784 | // It'd be quite a bit of work to handle this correctly (and |
785 | // nobody else seems to bother doing this correctly either), |
786 | // so we're just leaving this broken. Fixing this would |
787 | // involve storing all CRTCs upon entering fullscreen, |
788 | // restoring them upon exit, and after entering fullscreen, |
789 | // repositioning displays to the right and below this |
790 | // display. I think there would still be edge cases that are |
791 | // difficult or impossible to handle correctly, e.g. what if |
792 | // a new monitor was plugged in while in fullscreen? |
793 | // |
794 | // I think we might just want to disallow setting the video |
795 | // mode higher than the current desktop video mode (I'm sure |
796 | // this will make someone unhappy, but it's very unusual for |
797 | // games to want to do this anyway). |
798 | self.xconn |
799 | .set_crtc_config(monitor.id, video_mode.native_mode) |
800 | .expect("failed to set video mode" ); |
801 | } |
802 | |
803 | let window_position = self.outer_position_physical(); |
804 | self.shared_state_lock().restore_position = Some(window_position); |
805 | let monitor_origin: (i32, i32) = monitor.position().into(); |
806 | self.set_position_inner(monitor_origin.0, monitor_origin.1) |
807 | .expect_then_ignore_error("Failed to set window position" ); |
808 | self.set_fullscreen_hint(true).map(Some) |
809 | }, |
810 | } |
811 | } |
812 | |
813 | #[inline ] |
814 | pub(crate) fn fullscreen(&self) -> Option<Fullscreen> { |
815 | let shared_state = self.shared_state_lock(); |
816 | |
817 | shared_state.desired_fullscreen.clone().unwrap_or_else(|| shared_state.fullscreen.clone()) |
818 | } |
819 | |
820 | #[inline ] |
821 | pub(crate) fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) { |
822 | if let Some(flusher) = |
823 | self.set_fullscreen_inner(fullscreen).expect("Failed to change window fullscreen state" ) |
824 | { |
825 | flusher.check().expect("Failed to change window fullscreen state" ); |
826 | self.invalidate_cached_frame_extents(); |
827 | } |
828 | } |
829 | |
830 | // Called by EventProcessor when a VisibilityNotify event is received |
831 | pub(crate) fn visibility_notify(&self) { |
832 | let mut shared_state = self.shared_state_lock(); |
833 | |
834 | match shared_state.visibility { |
835 | Visibility::No => self |
836 | .xconn |
837 | .xcb_connection() |
838 | .unmap_window(self.xwindow) |
839 | .expect_then_ignore_error("Failed to unmap window" ), |
840 | Visibility::Yes => (), |
841 | Visibility::YesWait => { |
842 | shared_state.visibility = Visibility::Yes; |
843 | |
844 | if let Some(fullscreen) = shared_state.desired_fullscreen.take() { |
845 | drop(shared_state); |
846 | self.set_fullscreen(fullscreen); |
847 | } |
848 | }, |
849 | } |
850 | } |
851 | |
852 | pub fn current_monitor(&self) -> Option<X11MonitorHandle> { |
853 | Some(self.shared_state_lock().last_monitor.clone()) |
854 | } |
855 | |
856 | pub fn available_monitors(&self) -> Vec<X11MonitorHandle> { |
857 | self.xconn.available_monitors().expect("Failed to get available monitors" ) |
858 | } |
859 | |
860 | pub fn primary_monitor(&self) -> Option<X11MonitorHandle> { |
861 | Some(self.xconn.primary_monitor().expect("Failed to get primary monitor" )) |
862 | } |
863 | |
864 | #[inline ] |
865 | pub fn is_minimized(&self) -> Option<bool> { |
866 | let atoms = self.xconn.atoms(); |
867 | let state_atom = atoms[_NET_WM_STATE]; |
868 | let state = self.xconn.get_property( |
869 | self.xwindow, |
870 | state_atom, |
871 | xproto::Atom::from(xproto::AtomEnum::ATOM), |
872 | ); |
873 | let hidden_atom = atoms[_NET_WM_STATE_HIDDEN]; |
874 | |
875 | Some(match state { |
876 | Ok(atoms) => { |
877 | atoms.iter().any(|atom: &xproto::Atom| *atom as xproto::Atom == hidden_atom) |
878 | }, |
879 | _ => false, |
880 | }) |
881 | } |
882 | |
883 | /// Refresh the API for the given monitor. |
884 | #[inline ] |
885 | pub(super) fn refresh_dpi_for_monitor<T: 'static>( |
886 | &self, |
887 | new_monitor: &X11MonitorHandle, |
888 | maybe_prev_scale_factor: Option<f64>, |
889 | mut callback: impl FnMut(Event<T>), |
890 | ) { |
891 | // Check if the self is on this monitor |
892 | let monitor = self.shared_state_lock().last_monitor.clone(); |
893 | if monitor.name == new_monitor.name { |
894 | let (width, height) = self.inner_size_physical(); |
895 | let (new_width, new_height) = self.adjust_for_dpi( |
896 | // If we couldn't determine the previous scale |
897 | // factor (e.g., because all monitors were closed |
898 | // before), just pick whatever the current monitor |
899 | // has set as a baseline. |
900 | maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), |
901 | new_monitor.scale_factor, |
902 | width, |
903 | height, |
904 | &self.shared_state_lock(), |
905 | ); |
906 | |
907 | let window_id = crate::window::WindowId(self.id()); |
908 | let old_inner_size = PhysicalSize::new(width, height); |
909 | let inner_size = Arc::new(Mutex::new(PhysicalSize::new(new_width, new_height))); |
910 | callback(Event::WindowEvent { |
911 | window_id, |
912 | event: WindowEvent::ScaleFactorChanged { |
913 | scale_factor: new_monitor.scale_factor, |
914 | inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&inner_size)), |
915 | }, |
916 | }); |
917 | |
918 | let new_inner_size = *inner_size.lock().unwrap(); |
919 | drop(inner_size); |
920 | |
921 | if new_inner_size != old_inner_size { |
922 | let (new_width, new_height) = new_inner_size.into(); |
923 | self.request_inner_size_physical(new_width, new_height); |
924 | } |
925 | } |
926 | } |
927 | |
928 | fn set_minimized_inner(&self, minimized: bool) -> Result<VoidCookie<'_>, X11Error> { |
929 | let atoms = self.xconn.atoms(); |
930 | |
931 | if minimized { |
932 | let root_window = self.xconn.default_root().root; |
933 | |
934 | self.xconn.send_client_msg( |
935 | self.xwindow, |
936 | root_window, |
937 | atoms[WM_CHANGE_STATE], |
938 | Some( |
939 | xproto::EventMask::SUBSTRUCTURE_REDIRECT |
940 | | xproto::EventMask::SUBSTRUCTURE_NOTIFY, |
941 | ), |
942 | [3u32, 0, 0, 0, 0], |
943 | ) |
944 | } else { |
945 | self.xconn.send_client_msg( |
946 | self.xwindow, |
947 | self.root, |
948 | atoms[_NET_ACTIVE_WINDOW], |
949 | Some( |
950 | xproto::EventMask::SUBSTRUCTURE_REDIRECT |
951 | | xproto::EventMask::SUBSTRUCTURE_NOTIFY, |
952 | ), |
953 | [1, x11rb::CURRENT_TIME, 0, 0, 0], |
954 | ) |
955 | } |
956 | } |
957 | |
958 | #[inline ] |
959 | pub fn set_minimized(&self, minimized: bool) { |
960 | self.set_minimized_inner(minimized) |
961 | .expect_then_ignore_error("Failed to change window minimization" ); |
962 | |
963 | self.xconn.flush_requests().expect("Failed to change window minimization" ); |
964 | } |
965 | |
966 | #[inline ] |
967 | pub fn is_maximized(&self) -> bool { |
968 | let atoms = self.xconn.atoms(); |
969 | let state_atom = atoms[_NET_WM_STATE]; |
970 | let state = self.xconn.get_property( |
971 | self.xwindow, |
972 | state_atom, |
973 | xproto::Atom::from(xproto::AtomEnum::ATOM), |
974 | ); |
975 | let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; |
976 | let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; |
977 | match state { |
978 | Ok(atoms) => { |
979 | let horz_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == horz_atom); |
980 | let vert_maximized = atoms.iter().any(|atom: &xproto::Atom| *atom == vert_atom); |
981 | horz_maximized && vert_maximized |
982 | }, |
983 | _ => false, |
984 | } |
985 | } |
986 | |
987 | fn set_maximized_inner(&self, maximized: bool) -> Result<VoidCookie<'_>, X11Error> { |
988 | let atoms = self.xconn.atoms(); |
989 | let horz_atom = atoms[_NET_WM_STATE_MAXIMIZED_HORZ]; |
990 | let vert_atom = atoms[_NET_WM_STATE_MAXIMIZED_VERT]; |
991 | |
992 | self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0)) |
993 | } |
994 | |
995 | #[inline ] |
996 | pub fn set_maximized(&self, maximized: bool) { |
997 | self.set_maximized_inner(maximized) |
998 | .expect_then_ignore_error("Failed to change window maximization" ); |
999 | self.xconn.flush_requests().expect("Failed to change window maximization" ); |
1000 | self.invalidate_cached_frame_extents(); |
1001 | } |
1002 | |
1003 | fn set_title_inner(&self, title: &str) -> Result<VoidCookie<'_>, X11Error> { |
1004 | let atoms = self.xconn.atoms(); |
1005 | |
1006 | let title = CString::new(title).expect("Window title contained null byte" ); |
1007 | self.xconn |
1008 | .change_property( |
1009 | self.xwindow, |
1010 | xproto::Atom::from(xproto::AtomEnum::WM_NAME), |
1011 | xproto::Atom::from(xproto::AtomEnum::STRING), |
1012 | xproto::PropMode::REPLACE, |
1013 | title.as_bytes(), |
1014 | )? |
1015 | .ignore_error(); |
1016 | self.xconn.change_property( |
1017 | self.xwindow, |
1018 | atoms[_NET_WM_NAME], |
1019 | atoms[UTF8_STRING], |
1020 | xproto::PropMode::REPLACE, |
1021 | title.as_bytes(), |
1022 | ) |
1023 | } |
1024 | |
1025 | #[inline ] |
1026 | pub fn set_title(&self, title: &str) { |
1027 | self.set_title_inner(title).expect_then_ignore_error("Failed to set window title" ); |
1028 | |
1029 | self.xconn.flush_requests().expect("Failed to set window title" ); |
1030 | } |
1031 | |
1032 | #[inline ] |
1033 | pub fn set_transparent(&self, _transparent: bool) {} |
1034 | |
1035 | #[inline ] |
1036 | pub fn set_blur(&self, _blur: bool) {} |
1037 | |
1038 | fn set_decorations_inner(&self, decorations: bool) -> Result<VoidCookie<'_>, X11Error> { |
1039 | self.shared_state_lock().is_decorated = decorations; |
1040 | let mut hints = self.xconn.get_motif_hints(self.xwindow); |
1041 | |
1042 | hints.set_decorations(decorations); |
1043 | |
1044 | self.xconn.set_motif_hints(self.xwindow, &hints) |
1045 | } |
1046 | |
1047 | #[inline ] |
1048 | pub fn set_decorations(&self, decorations: bool) { |
1049 | self.set_decorations_inner(decorations) |
1050 | .expect_then_ignore_error("Failed to set decoration state" ); |
1051 | self.xconn.flush_requests().expect("Failed to set decoration state" ); |
1052 | self.invalidate_cached_frame_extents(); |
1053 | } |
1054 | |
1055 | #[inline ] |
1056 | pub fn is_decorated(&self) -> bool { |
1057 | self.shared_state_lock().is_decorated |
1058 | } |
1059 | |
1060 | fn set_maximizable_inner(&self, maximizable: bool) -> Result<VoidCookie<'_>, X11Error> { |
1061 | let mut hints = self.xconn.get_motif_hints(self.xwindow); |
1062 | |
1063 | hints.set_maximizable(maximizable); |
1064 | |
1065 | self.xconn.set_motif_hints(self.xwindow, &hints) |
1066 | } |
1067 | |
1068 | fn toggle_atom(&self, atom_name: AtomName, enable: bool) -> Result<VoidCookie<'_>, X11Error> { |
1069 | let atoms = self.xconn.atoms(); |
1070 | let atom = atoms[atom_name]; |
1071 | self.set_netwm(enable.into(), (atom, 0, 0, 0)) |
1072 | } |
1073 | |
1074 | fn set_window_level_inner(&self, level: WindowLevel) -> Result<VoidCookie<'_>, X11Error> { |
1075 | self.toggle_atom(_NET_WM_STATE_ABOVE, level == WindowLevel::AlwaysOnTop)?.ignore_error(); |
1076 | self.toggle_atom(_NET_WM_STATE_BELOW, level == WindowLevel::AlwaysOnBottom) |
1077 | } |
1078 | |
1079 | #[inline ] |
1080 | pub fn set_window_level(&self, level: WindowLevel) { |
1081 | self.set_window_level_inner(level) |
1082 | .expect_then_ignore_error("Failed to set window-level state" ); |
1083 | self.xconn.flush_requests().expect("Failed to set window-level state" ); |
1084 | } |
1085 | |
1086 | fn set_icon_inner(&self, icon: PlatformIcon) -> Result<VoidCookie<'_>, X11Error> { |
1087 | let atoms = self.xconn.atoms(); |
1088 | let icon_atom = atoms[_NET_WM_ICON]; |
1089 | let data = icon.to_cardinals(); |
1090 | self.xconn.change_property( |
1091 | self.xwindow, |
1092 | icon_atom, |
1093 | xproto::Atom::from(xproto::AtomEnum::CARDINAL), |
1094 | xproto::PropMode::REPLACE, |
1095 | data.as_slice(), |
1096 | ) |
1097 | } |
1098 | |
1099 | fn unset_icon_inner(&self) -> Result<VoidCookie<'_>, X11Error> { |
1100 | let atoms = self.xconn.atoms(); |
1101 | let icon_atom = atoms[_NET_WM_ICON]; |
1102 | let empty_data: [util::Cardinal; 0] = []; |
1103 | self.xconn.change_property( |
1104 | self.xwindow, |
1105 | icon_atom, |
1106 | xproto::Atom::from(xproto::AtomEnum::CARDINAL), |
1107 | xproto::PropMode::REPLACE, |
1108 | &empty_data, |
1109 | ) |
1110 | } |
1111 | |
1112 | #[inline ] |
1113 | pub(crate) fn set_window_icon(&self, icon: Option<PlatformIcon>) { |
1114 | match icon { |
1115 | Some(icon) => self.set_icon_inner(icon), |
1116 | None => self.unset_icon_inner(), |
1117 | } |
1118 | .expect_then_ignore_error("Failed to set icons" ); |
1119 | |
1120 | self.xconn.flush_requests().expect("Failed to set icons" ); |
1121 | } |
1122 | |
1123 | #[inline ] |
1124 | pub fn set_visible(&self, visible: bool) { |
1125 | let mut shared_state = self.shared_state_lock(); |
1126 | |
1127 | match (visible, shared_state.visibility) { |
1128 | (true, Visibility::Yes) | (true, Visibility::YesWait) | (false, Visibility::No) => { |
1129 | return |
1130 | }, |
1131 | _ => (), |
1132 | } |
1133 | |
1134 | if visible { |
1135 | self.xconn |
1136 | .xcb_connection() |
1137 | .map_window(self.xwindow) |
1138 | .expect_then_ignore_error("Failed to call `xcb_map_window`" ); |
1139 | self.xconn |
1140 | .xcb_connection() |
1141 | .configure_window( |
1142 | self.xwindow, |
1143 | &xproto::ConfigureWindowAux::new().stack_mode(xproto::StackMode::ABOVE), |
1144 | ) |
1145 | .expect_then_ignore_error("Failed to call `xcb_configure_window`" ); |
1146 | self.xconn.flush_requests().expect("Failed to call XMapRaised" ); |
1147 | shared_state.visibility = Visibility::YesWait; |
1148 | } else { |
1149 | self.xconn |
1150 | .xcb_connection() |
1151 | .unmap_window(self.xwindow) |
1152 | .expect_then_ignore_error("Failed to call `xcb_unmap_window`" ); |
1153 | self.xconn.flush_requests().expect("Failed to call XUnmapWindow" ); |
1154 | shared_state.visibility = Visibility::No; |
1155 | } |
1156 | } |
1157 | |
1158 | #[inline ] |
1159 | pub fn is_visible(&self) -> Option<bool> { |
1160 | Some(self.shared_state_lock().visibility == Visibility::Yes) |
1161 | } |
1162 | |
1163 | fn update_cached_frame_extents(&self) { |
1164 | let extents = self.xconn.get_frame_extents_heuristic(self.xwindow, self.root); |
1165 | self.shared_state_lock().frame_extents = Some(extents); |
1166 | } |
1167 | |
1168 | pub(crate) fn invalidate_cached_frame_extents(&self) { |
1169 | self.shared_state_lock().frame_extents.take(); |
1170 | } |
1171 | |
1172 | pub(crate) fn outer_position_physical(&self) -> (i32, i32) { |
1173 | let extents = self.shared_state_lock().frame_extents.clone(); |
1174 | if let Some(extents) = extents { |
1175 | let (x, y) = self.inner_position_physical(); |
1176 | extents.inner_pos_to_outer(x, y) |
1177 | } else { |
1178 | self.update_cached_frame_extents(); |
1179 | self.outer_position_physical() |
1180 | } |
1181 | } |
1182 | |
1183 | #[inline ] |
1184 | pub fn outer_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { |
1185 | let extents = self.shared_state_lock().frame_extents.clone(); |
1186 | if let Some(extents) = extents { |
1187 | let (x, y) = self.inner_position_physical(); |
1188 | Ok(extents.inner_pos_to_outer(x, y).into()) |
1189 | } else { |
1190 | self.update_cached_frame_extents(); |
1191 | self.outer_position() |
1192 | } |
1193 | } |
1194 | |
1195 | pub(crate) fn inner_position_physical(&self) -> (i32, i32) { |
1196 | // This should be okay to unwrap since the only error XTranslateCoordinates can return |
1197 | // is BadWindow, and if the window handle is bad we have bigger problems. |
1198 | self.xconn |
1199 | .translate_coords(self.xwindow, self.root) |
1200 | .map(|coords| (coords.dst_x.into(), coords.dst_y.into())) |
1201 | .unwrap() |
1202 | } |
1203 | |
1204 | #[inline ] |
1205 | pub fn inner_position(&self) -> Result<PhysicalPosition<i32>, NotSupportedError> { |
1206 | Ok(self.inner_position_physical().into()) |
1207 | } |
1208 | |
1209 | pub(crate) fn set_position_inner( |
1210 | &self, |
1211 | mut x: i32, |
1212 | mut y: i32, |
1213 | ) -> Result<VoidCookie<'_>, X11Error> { |
1214 | // There are a few WMs that set client area position rather than window position, so |
1215 | // we'll translate for consistency. |
1216 | if util::wm_name_is_one_of(&["Enlightenment" , "FVWM" ]) { |
1217 | let extents = self.shared_state_lock().frame_extents.clone(); |
1218 | if let Some(extents) = extents { |
1219 | x += cast_dimension_to_hint(extents.frame_extents.left); |
1220 | y += cast_dimension_to_hint(extents.frame_extents.top); |
1221 | } else { |
1222 | self.update_cached_frame_extents(); |
1223 | return self.set_position_inner(x, y); |
1224 | } |
1225 | } |
1226 | |
1227 | self.xconn |
1228 | .xcb_connection() |
1229 | .configure_window(self.xwindow, &xproto::ConfigureWindowAux::new().x(x).y(y)) |
1230 | .map_err(Into::into) |
1231 | } |
1232 | |
1233 | pub(crate) fn set_position_physical(&self, x: i32, y: i32) { |
1234 | self.set_position_inner(x, y).expect_then_ignore_error("Failed to call `XMoveWindow`" ); |
1235 | } |
1236 | |
1237 | #[inline ] |
1238 | pub fn set_outer_position(&self, position: Position) { |
1239 | let (x, y) = position.to_physical::<i32>(self.scale_factor()).into(); |
1240 | self.set_position_physical(x, y); |
1241 | } |
1242 | |
1243 | pub(crate) fn inner_size_physical(&self) -> (u32, u32) { |
1244 | // This should be okay to unwrap since the only error XGetGeometry can return |
1245 | // is BadWindow, and if the window handle is bad we have bigger problems. |
1246 | self.xconn |
1247 | .get_geometry(self.xwindow) |
1248 | .map(|geo| (geo.width.into(), geo.height.into())) |
1249 | .unwrap() |
1250 | } |
1251 | |
1252 | #[inline ] |
1253 | pub fn inner_size(&self) -> PhysicalSize<u32> { |
1254 | self.inner_size_physical().into() |
1255 | } |
1256 | |
1257 | #[inline ] |
1258 | pub fn outer_size(&self) -> PhysicalSize<u32> { |
1259 | let extents = self.shared_state_lock().frame_extents.clone(); |
1260 | if let Some(extents) = extents { |
1261 | let (width, height) = self.inner_size_physical(); |
1262 | extents.inner_size_to_outer(width, height).into() |
1263 | } else { |
1264 | self.update_cached_frame_extents(); |
1265 | self.outer_size() |
1266 | } |
1267 | } |
1268 | |
1269 | pub(crate) fn request_inner_size_physical(&self, width: u32, height: u32) { |
1270 | self.xconn |
1271 | .xcb_connection() |
1272 | .configure_window( |
1273 | self.xwindow, |
1274 | &xproto::ConfigureWindowAux::new().width(width).height(height), |
1275 | ) |
1276 | .expect_then_ignore_error("Failed to call `xcb_configure_window`" ); |
1277 | self.xconn.flush_requests().expect("Failed to call XResizeWindow" ); |
1278 | // cursor_hittest needs to be reapplied after each window resize. |
1279 | if self.shared_state_lock().cursor_hittest.unwrap_or(false) { |
1280 | let _ = self.set_cursor_hittest(true); |
1281 | } |
1282 | } |
1283 | |
1284 | #[inline ] |
1285 | pub fn request_inner_size(&self, size: Size) -> Option<PhysicalSize<u32>> { |
1286 | let scale_factor = self.scale_factor(); |
1287 | let size = size.to_physical::<u32>(scale_factor).into(); |
1288 | if !self.shared_state_lock().is_resizable { |
1289 | self.update_normal_hints(|normal_hints| { |
1290 | normal_hints.min_size = Some(size); |
1291 | normal_hints.max_size = Some(size); |
1292 | }) |
1293 | .expect("Failed to call `XSetWMNormalHints`" ); |
1294 | } |
1295 | self.request_inner_size_physical(size.0 as u32, size.1 as u32); |
1296 | |
1297 | None |
1298 | } |
1299 | |
1300 | fn update_normal_hints<F>(&self, callback: F) -> Result<(), X11Error> |
1301 | where |
1302 | F: FnOnce(&mut WmSizeHints), |
1303 | { |
1304 | let mut normal_hints = WmSizeHints::get( |
1305 | self.xconn.xcb_connection(), |
1306 | self.xwindow as xproto::Window, |
1307 | xproto::AtomEnum::WM_NORMAL_HINTS, |
1308 | )? |
1309 | .reply()? |
1310 | .unwrap_or_default(); |
1311 | callback(&mut normal_hints); |
1312 | normal_hints |
1313 | .set( |
1314 | self.xconn.xcb_connection(), |
1315 | self.xwindow as xproto::Window, |
1316 | xproto::AtomEnum::WM_NORMAL_HINTS, |
1317 | )? |
1318 | .ignore_error(); |
1319 | Ok(()) |
1320 | } |
1321 | |
1322 | pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { |
1323 | self.update_normal_hints(|normal_hints| { |
1324 | normal_hints.min_size = |
1325 | dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h))) |
1326 | }) |
1327 | .expect("Failed to call `XSetWMNormalHints`" ); |
1328 | } |
1329 | |
1330 | #[inline ] |
1331 | pub fn set_min_inner_size(&self, dimensions: Option<Size>) { |
1332 | self.shared_state_lock().min_inner_size = dimensions; |
1333 | let physical_dimensions = |
1334 | dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into()); |
1335 | self.set_min_inner_size_physical(physical_dimensions); |
1336 | } |
1337 | |
1338 | pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { |
1339 | self.update_normal_hints(|normal_hints| { |
1340 | normal_hints.max_size = |
1341 | dimensions.map(|(w, h)| (cast_dimension_to_hint(w), cast_dimension_to_hint(h))) |
1342 | }) |
1343 | .expect("Failed to call `XSetWMNormalHints`" ); |
1344 | } |
1345 | |
1346 | #[inline ] |
1347 | pub fn set_max_inner_size(&self, dimensions: Option<Size>) { |
1348 | self.shared_state_lock().max_inner_size = dimensions; |
1349 | let physical_dimensions = |
1350 | dimensions.map(|dimensions| dimensions.to_physical::<u32>(self.scale_factor()).into()); |
1351 | self.set_max_inner_size_physical(physical_dimensions); |
1352 | } |
1353 | |
1354 | #[inline ] |
1355 | pub fn resize_increments(&self) -> Option<PhysicalSize<u32>> { |
1356 | WmSizeHints::get( |
1357 | self.xconn.xcb_connection(), |
1358 | self.xwindow as xproto::Window, |
1359 | xproto::AtomEnum::WM_NORMAL_HINTS, |
1360 | ) |
1361 | .ok() |
1362 | .and_then(|cookie| cookie.reply().ok()) |
1363 | .flatten() |
1364 | .and_then(|hints| hints.size_increment) |
1365 | .map(|(width, height)| (width as u32, height as u32).into()) |
1366 | } |
1367 | |
1368 | #[inline ] |
1369 | pub fn set_resize_increments(&self, increments: Option<Size>) { |
1370 | self.shared_state_lock().resize_increments = increments; |
1371 | let physical_increments = |
1372 | increments.map(|increments| cast_size_to_hint(increments, self.scale_factor())); |
1373 | self.update_normal_hints(|hints| hints.size_increment = physical_increments) |
1374 | .expect("Failed to call `XSetWMNormalHints`" ); |
1375 | } |
1376 | |
1377 | pub(crate) fn adjust_for_dpi( |
1378 | &self, |
1379 | old_scale_factor: f64, |
1380 | new_scale_factor: f64, |
1381 | width: u32, |
1382 | height: u32, |
1383 | shared_state: &SharedState, |
1384 | ) -> (u32, u32) { |
1385 | let scale_factor = new_scale_factor / old_scale_factor; |
1386 | self.update_normal_hints(|normal_hints| { |
1387 | let dpi_adjuster = |size: Size| -> (i32, i32) { cast_size_to_hint(size, scale_factor) }; |
1388 | let max_size = shared_state.max_inner_size.map(dpi_adjuster); |
1389 | let min_size = shared_state.min_inner_size.map(dpi_adjuster); |
1390 | let resize_increments = shared_state.resize_increments.map(dpi_adjuster); |
1391 | let base_size = shared_state.base_size.map(dpi_adjuster); |
1392 | |
1393 | normal_hints.max_size = max_size; |
1394 | normal_hints.min_size = min_size; |
1395 | normal_hints.size_increment = resize_increments; |
1396 | normal_hints.base_size = base_size; |
1397 | }) |
1398 | .expect("Failed to update normal hints" ); |
1399 | |
1400 | let new_width = (width as f64 * scale_factor).round() as u32; |
1401 | let new_height = (height as f64 * scale_factor).round() as u32; |
1402 | |
1403 | (new_width, new_height) |
1404 | } |
1405 | |
1406 | pub fn set_resizable(&self, resizable: bool) { |
1407 | if util::wm_name_is_one_of(&["Xfwm4" ]) { |
1408 | // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` |
1409 | // from being detected. This makes it impossible for resizing to be |
1410 | // re-enabled, and also breaks DPI scaling. As such, we choose the lesser of |
1411 | // two evils and do nothing. |
1412 | warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4" ); |
1413 | return; |
1414 | } |
1415 | |
1416 | let (min_size, max_size) = if resizable { |
1417 | let shared_state_lock = self.shared_state_lock(); |
1418 | (shared_state_lock.min_inner_size, shared_state_lock.max_inner_size) |
1419 | } else { |
1420 | let window_size = Some(Size::from(self.inner_size())); |
1421 | (window_size, window_size) |
1422 | }; |
1423 | self.shared_state_lock().is_resizable = resizable; |
1424 | |
1425 | self.set_maximizable_inner(resizable) |
1426 | .expect_then_ignore_error("Failed to call `XSetWMNormalHints`" ); |
1427 | |
1428 | let scale_factor = self.scale_factor(); |
1429 | let min_inner_size = min_size.map(|size| cast_size_to_hint(size, scale_factor)); |
1430 | let max_inner_size = max_size.map(|size| cast_size_to_hint(size, scale_factor)); |
1431 | self.update_normal_hints(|normal_hints| { |
1432 | normal_hints.min_size = min_inner_size; |
1433 | normal_hints.max_size = max_inner_size; |
1434 | }) |
1435 | .expect("Failed to call `XSetWMNormalHints`" ); |
1436 | } |
1437 | |
1438 | #[inline ] |
1439 | pub fn is_resizable(&self) -> bool { |
1440 | self.shared_state_lock().is_resizable |
1441 | } |
1442 | |
1443 | #[inline ] |
1444 | pub fn set_enabled_buttons(&self, _buttons: WindowButtons) {} |
1445 | |
1446 | #[inline ] |
1447 | pub fn enabled_buttons(&self) -> WindowButtons { |
1448 | WindowButtons::all() |
1449 | } |
1450 | |
1451 | #[allow (dead_code)] |
1452 | #[inline ] |
1453 | pub fn xlib_display(&self) -> *mut c_void { |
1454 | self.xconn.display as _ |
1455 | } |
1456 | |
1457 | #[allow (dead_code)] |
1458 | #[inline ] |
1459 | pub fn xlib_window(&self) -> c_ulong { |
1460 | self.xwindow as ffi::Window |
1461 | } |
1462 | |
1463 | #[inline ] |
1464 | pub fn set_cursor(&self, cursor: Cursor) { |
1465 | match cursor { |
1466 | Cursor::Icon(icon) => { |
1467 | let old_cursor = replace( |
1468 | &mut *self.selected_cursor.lock().unwrap(), |
1469 | SelectedCursor::Named(icon), |
1470 | ); |
1471 | |
1472 | #[allow (clippy::mutex_atomic)] |
1473 | if SelectedCursor::Named(icon) != old_cursor && *self.cursor_visible.lock().unwrap() |
1474 | { |
1475 | self.xconn.set_cursor_icon(self.xwindow, Some(icon)); |
1476 | } |
1477 | }, |
1478 | Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::X(cursor) }) => { |
1479 | #[allow (clippy::mutex_atomic)] |
1480 | if *self.cursor_visible.lock().unwrap() { |
1481 | self.xconn.set_custom_cursor(self.xwindow, &cursor); |
1482 | } |
1483 | |
1484 | *self.selected_cursor.lock().unwrap() = SelectedCursor::Custom(cursor); |
1485 | }, |
1486 | #[cfg (wayland_platform)] |
1487 | Cursor::Custom(RootCustomCursor { inner: PlatformCustomCursor::Wayland(_) }) => { |
1488 | tracing::error!("passed a Wayland cursor to X11 backend" ) |
1489 | }, |
1490 | } |
1491 | } |
1492 | |
1493 | #[inline ] |
1494 | pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> { |
1495 | // We don't support the locked cursor yet, so ignore it early on. |
1496 | if mode == CursorGrabMode::Locked { |
1497 | return Err(ExternalError::NotSupported(NotSupportedError::new())); |
1498 | } |
1499 | |
1500 | let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); |
1501 | if mode == *grabbed_lock { |
1502 | return Ok(()); |
1503 | } |
1504 | |
1505 | // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`. |
1506 | // Therefore, this is common to both codepaths. |
1507 | self.xconn |
1508 | .xcb_connection() |
1509 | .ungrab_pointer(x11rb::CURRENT_TIME) |
1510 | .expect_then_ignore_error("Failed to call `xcb_ungrab_pointer`" ); |
1511 | *grabbed_lock = CursorGrabMode::None; |
1512 | |
1513 | let result = match mode { |
1514 | CursorGrabMode::None => self.xconn.flush_requests().map_err(|err| { |
1515 | ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) |
1516 | }), |
1517 | CursorGrabMode::Confined => { |
1518 | let result = self |
1519 | .xconn |
1520 | .xcb_connection() |
1521 | .grab_pointer( |
1522 | true as _, |
1523 | self.xwindow, |
1524 | xproto::EventMask::BUTTON_PRESS |
1525 | | xproto::EventMask::BUTTON_RELEASE |
1526 | | xproto::EventMask::ENTER_WINDOW |
1527 | | xproto::EventMask::LEAVE_WINDOW |
1528 | | xproto::EventMask::POINTER_MOTION |
1529 | | xproto::EventMask::POINTER_MOTION_HINT |
1530 | | xproto::EventMask::BUTTON1_MOTION |
1531 | | xproto::EventMask::BUTTON2_MOTION |
1532 | | xproto::EventMask::BUTTON3_MOTION |
1533 | | xproto::EventMask::BUTTON4_MOTION |
1534 | | xproto::EventMask::BUTTON5_MOTION |
1535 | | xproto::EventMask::KEYMAP_STATE, |
1536 | xproto::GrabMode::ASYNC, |
1537 | xproto::GrabMode::ASYNC, |
1538 | self.xwindow, |
1539 | 0u32, |
1540 | x11rb::CURRENT_TIME, |
1541 | ) |
1542 | .expect("Failed to call `grab_pointer`" ) |
1543 | .reply() |
1544 | .expect("Failed to receive reply from `grab_pointer`" ); |
1545 | |
1546 | match result.status { |
1547 | xproto::GrabStatus::SUCCESS => Ok(()), |
1548 | xproto::GrabStatus::ALREADY_GRABBED => { |
1549 | Err("Cursor could not be confined: already confined by another client" ) |
1550 | }, |
1551 | xproto::GrabStatus::INVALID_TIME => { |
1552 | Err("Cursor could not be confined: invalid time" ) |
1553 | }, |
1554 | xproto::GrabStatus::NOT_VIEWABLE => { |
1555 | Err("Cursor could not be confined: confine location not viewable" ) |
1556 | }, |
1557 | xproto::GrabStatus::FROZEN => { |
1558 | Err("Cursor could not be confined: frozen by another client" ) |
1559 | }, |
1560 | _ => unreachable!(), |
1561 | } |
1562 | .map_err(|err| ExternalError::Os(os_error!(OsError::Misc(err)))) |
1563 | }, |
1564 | CursorGrabMode::Locked => return Ok(()), |
1565 | }; |
1566 | |
1567 | if result.is_ok() { |
1568 | *grabbed_lock = mode; |
1569 | } |
1570 | |
1571 | result |
1572 | } |
1573 | |
1574 | #[inline ] |
1575 | pub fn set_cursor_visible(&self, visible: bool) { |
1576 | #[allow (clippy::mutex_atomic)] |
1577 | let mut visible_lock = self.cursor_visible.lock().unwrap(); |
1578 | if visible == *visible_lock { |
1579 | return; |
1580 | } |
1581 | let cursor = |
1582 | if visible { Some((*self.selected_cursor.lock().unwrap()).clone()) } else { None }; |
1583 | *visible_lock = visible; |
1584 | drop(visible_lock); |
1585 | match cursor { |
1586 | Some(SelectedCursor::Custom(cursor)) => { |
1587 | self.xconn.set_custom_cursor(self.xwindow, &cursor); |
1588 | }, |
1589 | Some(SelectedCursor::Named(cursor)) => { |
1590 | self.xconn.set_cursor_icon(self.xwindow, Some(cursor)); |
1591 | }, |
1592 | None => { |
1593 | self.xconn.set_cursor_icon(self.xwindow, None); |
1594 | }, |
1595 | } |
1596 | } |
1597 | |
1598 | #[inline ] |
1599 | pub fn scale_factor(&self) -> f64 { |
1600 | self.shared_state_lock().last_monitor.scale_factor |
1601 | } |
1602 | |
1603 | pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { |
1604 | { |
1605 | self.xconn |
1606 | .xcb_connection() |
1607 | .warp_pointer(x11rb::NONE, self.xwindow, 0, 0, 0, 0, x as _, y as _) |
1608 | .map_err(|e| { |
1609 | ExternalError::Os(os_error!(OsError::XError(X11Error::from(e).into()))) |
1610 | })?; |
1611 | self.xconn.flush_requests().map_err(|e| { |
1612 | ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(e).into()))) |
1613 | }) |
1614 | } |
1615 | } |
1616 | |
1617 | #[inline ] |
1618 | pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { |
1619 | let (x, y) = position.to_physical::<i32>(self.scale_factor()).into(); |
1620 | self.set_cursor_position_physical(x, y) |
1621 | } |
1622 | |
1623 | #[inline ] |
1624 | pub fn set_cursor_hittest(&self, hittest: bool) -> Result<(), ExternalError> { |
1625 | let mut rectangles: Vec<Rectangle> = Vec::new(); |
1626 | if hittest { |
1627 | let size = self.inner_size(); |
1628 | rectangles.push(Rectangle { |
1629 | x: 0, |
1630 | y: 0, |
1631 | width: size.width as u16, |
1632 | height: size.height as u16, |
1633 | }) |
1634 | } |
1635 | let region = RegionWrapper::create_region(self.xconn.xcb_connection(), &rectangles) |
1636 | .map_err(|_e| ExternalError::Ignored)?; |
1637 | self.xconn |
1638 | .xcb_connection() |
1639 | .xfixes_set_window_shape_region(self.xwindow, SK::INPUT, 0, 0, region.region()) |
1640 | .map_err(|_e| ExternalError::Ignored)?; |
1641 | self.shared_state_lock().cursor_hittest = Some(hittest); |
1642 | Ok(()) |
1643 | } |
1644 | |
1645 | /// Moves the window while it is being dragged. |
1646 | pub fn drag_window(&self) -> Result<(), ExternalError> { |
1647 | self.drag_initiate(util::MOVERESIZE_MOVE) |
1648 | } |
1649 | |
1650 | #[inline ] |
1651 | pub fn show_window_menu(&self, _position: Position) {} |
1652 | |
1653 | /// Resizes the window while it is being dragged. |
1654 | pub fn drag_resize_window(&self, direction: ResizeDirection) -> Result<(), ExternalError> { |
1655 | self.drag_initiate(match direction { |
1656 | ResizeDirection::East => util::MOVERESIZE_RIGHT, |
1657 | ResizeDirection::North => util::MOVERESIZE_TOP, |
1658 | ResizeDirection::NorthEast => util::MOVERESIZE_TOPRIGHT, |
1659 | ResizeDirection::NorthWest => util::MOVERESIZE_TOPLEFT, |
1660 | ResizeDirection::South => util::MOVERESIZE_BOTTOM, |
1661 | ResizeDirection::SouthEast => util::MOVERESIZE_BOTTOMRIGHT, |
1662 | ResizeDirection::SouthWest => util::MOVERESIZE_BOTTOMLEFT, |
1663 | ResizeDirection::West => util::MOVERESIZE_LEFT, |
1664 | }) |
1665 | } |
1666 | |
1667 | /// Initiates a drag operation while the left mouse button is pressed. |
1668 | fn drag_initiate(&self, action: isize) -> Result<(), ExternalError> { |
1669 | let pointer = self |
1670 | .xconn |
1671 | .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) |
1672 | .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; |
1673 | |
1674 | let window = self.inner_position().map_err(ExternalError::NotSupported)?; |
1675 | |
1676 | let atoms = self.xconn.atoms(); |
1677 | let message = atoms[_NET_WM_MOVERESIZE]; |
1678 | |
1679 | // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer` |
1680 | // if the cursor isn't currently grabbed |
1681 | let mut grabbed_lock = self.cursor_grabbed_mode.lock().unwrap(); |
1682 | self.xconn |
1683 | .xcb_connection() |
1684 | .ungrab_pointer(x11rb::CURRENT_TIME) |
1685 | .map_err(|err| { |
1686 | ExternalError::Os(os_error!(OsError::XError(X11Error::from(err).into()))) |
1687 | })? |
1688 | .ignore_error(); |
1689 | self.xconn.flush_requests().map_err(|err| { |
1690 | ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) |
1691 | })?; |
1692 | *grabbed_lock = CursorGrabMode::None; |
1693 | |
1694 | // we keep the lock until we are done |
1695 | self.xconn |
1696 | .send_client_msg( |
1697 | self.xwindow, |
1698 | self.root, |
1699 | message, |
1700 | Some( |
1701 | xproto::EventMask::SUBSTRUCTURE_REDIRECT |
1702 | | xproto::EventMask::SUBSTRUCTURE_NOTIFY, |
1703 | ), |
1704 | [ |
1705 | (window.x + xinput_fp1616_to_float(pointer.win_x) as i32) as u32, |
1706 | (window.y + xinput_fp1616_to_float(pointer.win_y) as i32) as u32, |
1707 | action.try_into().unwrap(), |
1708 | 1, // Button 1 |
1709 | 1, |
1710 | ], |
1711 | ) |
1712 | .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; |
1713 | |
1714 | self.xconn.flush_requests().map_err(|err| { |
1715 | ExternalError::Os(os_error!(OsError::XError(X11Error::Xlib(err).into()))) |
1716 | }) |
1717 | } |
1718 | |
1719 | #[inline ] |
1720 | pub fn set_ime_cursor_area(&self, spot: Position, _size: Size) { |
1721 | let (x, y) = spot.to_physical::<i32>(self.scale_factor()).into(); |
1722 | let _ = self.ime_sender.lock().unwrap().send(ImeRequest::Position( |
1723 | self.xwindow as ffi::Window, |
1724 | x, |
1725 | y, |
1726 | )); |
1727 | } |
1728 | |
1729 | #[inline ] |
1730 | pub fn set_ime_allowed(&self, allowed: bool) { |
1731 | let _ = self |
1732 | .ime_sender |
1733 | .lock() |
1734 | .unwrap() |
1735 | .send(ImeRequest::Allow(self.xwindow as ffi::Window, allowed)); |
1736 | } |
1737 | |
1738 | #[inline ] |
1739 | pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} |
1740 | |
1741 | #[inline ] |
1742 | pub fn focus_window(&self) { |
1743 | let atoms = self.xconn.atoms(); |
1744 | let state_atom = atoms[WM_STATE]; |
1745 | let state_type_atom = atoms[CARD32]; |
1746 | let is_minimized = if let Ok(state) = |
1747 | self.xconn.get_property::<u32>(self.xwindow, state_atom, state_type_atom) |
1748 | { |
1749 | state.contains(&super::ICONIC_STATE) |
1750 | } else { |
1751 | false |
1752 | }; |
1753 | let is_visible = match self.shared_state_lock().visibility { |
1754 | Visibility::Yes => true, |
1755 | Visibility::YesWait | Visibility::No => false, |
1756 | }; |
1757 | |
1758 | if is_visible && !is_minimized { |
1759 | self.xconn |
1760 | .send_client_msg( |
1761 | self.xwindow, |
1762 | self.root, |
1763 | atoms[_NET_ACTIVE_WINDOW], |
1764 | Some( |
1765 | xproto::EventMask::SUBSTRUCTURE_REDIRECT |
1766 | | xproto::EventMask::SUBSTRUCTURE_NOTIFY, |
1767 | ), |
1768 | [1, x11rb::CURRENT_TIME, 0, 0, 0], |
1769 | ) |
1770 | .expect_then_ignore_error("Failed to send client message" ); |
1771 | if let Err(e) = self.xconn.flush_requests() { |
1772 | tracing::error!( |
1773 | "`flush` returned an error when focusing the window. Error was: {}" , |
1774 | e |
1775 | ); |
1776 | } |
1777 | } |
1778 | } |
1779 | |
1780 | #[inline ] |
1781 | pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) { |
1782 | let mut wm_hints = |
1783 | WmHints::get(self.xconn.xcb_connection(), self.xwindow as xproto::Window) |
1784 | .ok() |
1785 | .and_then(|cookie| cookie.reply().ok()) |
1786 | .flatten() |
1787 | .unwrap_or_default(); |
1788 | |
1789 | wm_hints.urgent = request_type.is_some(); |
1790 | wm_hints |
1791 | .set(self.xconn.xcb_connection(), self.xwindow as xproto::Window) |
1792 | .expect_then_ignore_error("Failed to set WM hints" ); |
1793 | } |
1794 | |
1795 | #[inline ] |
1796 | pub(crate) fn generate_activation_token(&self) -> Result<String, X11Error> { |
1797 | // Get the title from the WM_NAME property. |
1798 | let atoms = self.xconn.atoms(); |
1799 | let title = { |
1800 | let title_bytes = self |
1801 | .xconn |
1802 | .get_property(self.xwindow, atoms[_NET_WM_NAME], atoms[UTF8_STRING]) |
1803 | .expect("Failed to get title" ); |
1804 | |
1805 | String::from_utf8(title_bytes).expect("Bad title" ) |
1806 | }; |
1807 | |
1808 | // Get the activation token and then put it in the event queue. |
1809 | let token = self.xconn.request_activation_token(&title)?; |
1810 | |
1811 | Ok(token) |
1812 | } |
1813 | |
1814 | #[inline ] |
1815 | pub fn request_activation_token(&self) -> Result<AsyncRequestSerial, NotSupportedError> { |
1816 | let serial = AsyncRequestSerial::get(); |
1817 | self.activation_sender |
1818 | .send((self.id(), serial)) |
1819 | .expect("activation token channel should never be closed" ); |
1820 | Ok(serial) |
1821 | } |
1822 | |
1823 | #[inline ] |
1824 | pub fn id(&self) -> WindowId { |
1825 | WindowId(self.xwindow as _) |
1826 | } |
1827 | |
1828 | #[inline ] |
1829 | pub fn request_redraw(&self) { |
1830 | self.redraw_sender.send(WindowId(self.xwindow as _)).unwrap(); |
1831 | } |
1832 | |
1833 | #[inline ] |
1834 | pub fn pre_present_notify(&self) { |
1835 | // TODO timer |
1836 | } |
1837 | |
1838 | #[cfg (feature = "rwh_04" )] |
1839 | #[inline ] |
1840 | pub fn raw_window_handle_rwh_04(&self) -> rwh_04::RawWindowHandle { |
1841 | let mut window_handle = rwh_04::XlibHandle::empty(); |
1842 | window_handle.display = self.xlib_display(); |
1843 | window_handle.window = self.xlib_window(); |
1844 | window_handle.visual_id = self.visual as c_ulong; |
1845 | rwh_04::RawWindowHandle::Xlib(window_handle) |
1846 | } |
1847 | |
1848 | #[cfg (feature = "rwh_05" )] |
1849 | #[inline ] |
1850 | pub fn raw_window_handle_rwh_05(&self) -> rwh_05::RawWindowHandle { |
1851 | let mut window_handle = rwh_05::XlibWindowHandle::empty(); |
1852 | window_handle.window = self.xlib_window(); |
1853 | window_handle.visual_id = self.visual as c_ulong; |
1854 | window_handle.into() |
1855 | } |
1856 | |
1857 | #[cfg (feature = "rwh_05" )] |
1858 | #[inline ] |
1859 | pub fn raw_display_handle_rwh_05(&self) -> rwh_05::RawDisplayHandle { |
1860 | let mut display_handle = rwh_05::XlibDisplayHandle::empty(); |
1861 | display_handle.display = self.xlib_display(); |
1862 | display_handle.screen = self.screen_id; |
1863 | display_handle.into() |
1864 | } |
1865 | |
1866 | #[cfg (feature = "rwh_06" )] |
1867 | #[inline ] |
1868 | pub fn raw_window_handle_rwh_06(&self) -> Result<rwh_06::RawWindowHandle, rwh_06::HandleError> { |
1869 | let mut window_handle = rwh_06::XlibWindowHandle::new(self.xlib_window()); |
1870 | window_handle.visual_id = self.visual as c_ulong; |
1871 | Ok(window_handle.into()) |
1872 | } |
1873 | |
1874 | #[cfg (feature = "rwh_06" )] |
1875 | #[inline ] |
1876 | pub fn raw_display_handle_rwh_06( |
1877 | &self, |
1878 | ) -> Result<rwh_06::RawDisplayHandle, rwh_06::HandleError> { |
1879 | Ok(rwh_06::XlibDisplayHandle::new( |
1880 | Some( |
1881 | std::ptr::NonNull::new(self.xlib_display()) |
1882 | .expect("display pointer should never be null" ), |
1883 | ), |
1884 | self.screen_id, |
1885 | ) |
1886 | .into()) |
1887 | } |
1888 | |
1889 | #[inline ] |
1890 | pub fn theme(&self) -> Option<Theme> { |
1891 | None |
1892 | } |
1893 | |
1894 | pub fn set_content_protected(&self, _protected: bool) {} |
1895 | |
1896 | #[inline ] |
1897 | pub fn has_focus(&self) -> bool { |
1898 | self.shared_state_lock().has_focus |
1899 | } |
1900 | |
1901 | pub fn title(&self) -> String { |
1902 | String::new() |
1903 | } |
1904 | } |
1905 | |
1906 | /// Cast a dimension value into a hinted dimension for `WmSizeHints`, clamping if too large. |
1907 | fn cast_dimension_to_hint(val: u32) -> i32 { |
1908 | val.try_into().unwrap_or(default:i32::MAX) |
1909 | } |
1910 | |
1911 | /// Use the above strategy to cast a physical size into a hinted size. |
1912 | fn cast_physical_size_to_hint(size: PhysicalSize<u32>) -> (i32, i32) { |
1913 | let PhysicalSize { width: u32, height: u32 } = size; |
1914 | (cast_dimension_to_hint(val:width), cast_dimension_to_hint(val:height)) |
1915 | } |
1916 | |
1917 | /// Use the above strategy to cast a size into a hinted size. |
1918 | fn cast_size_to_hint(size: Size, scale_factor: f64) -> (i32, i32) { |
1919 | match size { |
1920 | Size::Physical(size: PhysicalSize) => cast_physical_size_to_hint(size), |
1921 | Size::Logical(size: LogicalSize) => size.to_physical::<i32>(scale_factor).into(), |
1922 | } |
1923 | } |
1924 | |