Move X11 backend to winit-x11 (#4253)
This commit is contained in:
parent
1126e9ea2f
commit
256bbe949e
42 changed files with 232 additions and 227 deletions
32
winit-x11/src/util/client_msg.rs
Normal file
32
winit-x11/src/util/client_msg.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use x11rb::x11_utils::Serialize;
|
||||
|
||||
use super::*;
|
||||
|
||||
impl XConnection {
|
||||
pub fn send_client_msg(
|
||||
&self,
|
||||
window: xproto::Window, // The window this is "about"; not necessarily this window
|
||||
target_window: xproto::Window, // The window we're sending to
|
||||
message_type: xproto::Atom,
|
||||
event_mask: Option<xproto::EventMask>,
|
||||
data: impl Into<xproto::ClientMessageData>,
|
||||
) -> Result<VoidCookie<'_>, X11Error> {
|
||||
let event = xproto::ClientMessageEvent {
|
||||
response_type: xproto::CLIENT_MESSAGE_EVENT,
|
||||
window,
|
||||
format: 32,
|
||||
data: data.into(),
|
||||
sequence: 0,
|
||||
type_: message_type,
|
||||
};
|
||||
|
||||
self.xcb_connection()
|
||||
.send_event(
|
||||
false,
|
||||
target_window,
|
||||
event_mask.unwrap_or(xproto::EventMask::NO_EVENT),
|
||||
event.serialize(),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
55
winit-x11/src/util/cookie.rs
Normal file
55
winit-x11/src/util/cookie.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use std::ffi::c_int;
|
||||
use std::sync::Arc;
|
||||
|
||||
use x11_dl::xlib::{self, XEvent, XGenericEventCookie};
|
||||
|
||||
use crate::xdisplay::XConnection;
|
||||
|
||||
/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure.
|
||||
/// This is a wrapper to extract the cookie from a GenericEvent XEvent and release the cookie data
|
||||
/// once it has been processed
|
||||
pub struct GenericEventCookie {
|
||||
cookie: XGenericEventCookie,
|
||||
xconn: Arc<XConnection>,
|
||||
}
|
||||
|
||||
impl GenericEventCookie {
|
||||
pub fn from_event(xconn: Arc<XConnection>, event: XEvent) -> Option<GenericEventCookie> {
|
||||
unsafe {
|
||||
let mut cookie: XGenericEventCookie = From::from(event);
|
||||
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == xlib::True {
|
||||
Some(GenericEventCookie { cookie, xconn })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn extension(&self) -> u8 {
|
||||
self.cookie.extension as u8
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn evtype(&self) -> c_int {
|
||||
self.cookie.evtype
|
||||
}
|
||||
|
||||
/// Borrow inner event data as `&T`.
|
||||
///
|
||||
/// ## SAFETY
|
||||
///
|
||||
/// The caller must ensure that the event has the `T` inside of it.
|
||||
#[inline]
|
||||
pub unsafe fn as_event<T>(&self) -> &T {
|
||||
unsafe { &*(self.cookie.data as *const _) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GenericEventCookie {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
246
winit-x11/src/util/cursor.rs
Normal file
246
winit-x11/src/util/cursor.rs
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
use std::collections::hash_map::Entry;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use winit_core::cursor::{CursorIcon, CustomCursorProvider, CustomCursorSource};
|
||||
use winit_core::error::{NotSupportedError, RequestError};
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::protocol::render::{self, ConnectionExt as _};
|
||||
use x11rb::protocol::xproto;
|
||||
|
||||
use super::super::ActiveEventLoop;
|
||||
use super::*;
|
||||
|
||||
impl XConnection {
|
||||
pub fn set_cursor_icon(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
cursor: Option<CursorIcon>,
|
||||
) -> Result<(), X11Error> {
|
||||
let cursor = {
|
||||
let mut cache = self.cursor_cache.lock().unwrap_or_else(|e| e.into_inner());
|
||||
|
||||
match cache.entry(cursor) {
|
||||
Entry::Occupied(o) => *o.get(),
|
||||
Entry::Vacant(v) => *v.insert(self.get_cursor(cursor)?),
|
||||
}
|
||||
};
|
||||
|
||||
self.update_cursor(window, cursor)
|
||||
}
|
||||
|
||||
pub(crate) fn set_custom_cursor(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
cursor: &CustomCursor,
|
||||
) -> Result<(), X11Error> {
|
||||
self.update_cursor(window, cursor.cursor)
|
||||
}
|
||||
|
||||
/// Create a cursor from an image.
|
||||
fn create_cursor_from_image(
|
||||
&self,
|
||||
width: u16,
|
||||
height: u16,
|
||||
hotspot_x: u16,
|
||||
hotspot_y: u16,
|
||||
image: &[u8],
|
||||
) -> Result<xproto::Cursor, X11Error> {
|
||||
// Create a pixmap for the default root window.
|
||||
let root = self.default_root().root;
|
||||
let pixmap =
|
||||
xproto::PixmapWrapper::create_pixmap(self.xcb_connection(), 32, root, width, height)?;
|
||||
|
||||
// Create a GC to draw with.
|
||||
let gc = xproto::GcontextWrapper::create_gc(
|
||||
self.xcb_connection(),
|
||||
pixmap.pixmap(),
|
||||
&Default::default(),
|
||||
)?;
|
||||
|
||||
// Draw the data into it.
|
||||
self.xcb_connection()
|
||||
.put_image(
|
||||
xproto::ImageFormat::Z_PIXMAP,
|
||||
pixmap.pixmap(),
|
||||
gc.gcontext(),
|
||||
width,
|
||||
height,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
32,
|
||||
image,
|
||||
)?
|
||||
.ignore_error();
|
||||
drop(gc);
|
||||
|
||||
// Create the XRender picture.
|
||||
let picture = render::PictureWrapper::create_picture(
|
||||
self.xcb_connection(),
|
||||
pixmap.pixmap(),
|
||||
self.find_argb32_format()?,
|
||||
&Default::default(),
|
||||
)?;
|
||||
drop(pixmap);
|
||||
|
||||
// Create the cursor.
|
||||
let cursor = self.xcb_connection().generate_id()?;
|
||||
self.xcb_connection()
|
||||
.render_create_cursor(cursor, picture.picture(), hotspot_x, hotspot_y)?
|
||||
.check()?;
|
||||
|
||||
Ok(cursor)
|
||||
}
|
||||
|
||||
/// Find the render format that corresponds to ARGB32.
|
||||
fn find_argb32_format(&self) -> Result<render::Pictformat, X11Error> {
|
||||
macro_rules! direct {
|
||||
($format:expr, $shift_name:ident, $mask_name:ident, $shift:expr) => {{
|
||||
($format).direct.$shift_name == $shift && ($format).direct.$mask_name == 0xff
|
||||
}};
|
||||
}
|
||||
|
||||
self.render_formats()
|
||||
.formats
|
||||
.iter()
|
||||
.find(|format| {
|
||||
format.type_ == render::PictType::DIRECT
|
||||
&& format.depth == 32
|
||||
&& direct!(format, red_shift, red_mask, 16)
|
||||
&& direct!(format, green_shift, green_mask, 8)
|
||||
&& direct!(format, blue_shift, blue_mask, 0)
|
||||
&& direct!(format, alpha_shift, alpha_mask, 24)
|
||||
})
|
||||
.ok_or(X11Error::NoArgb32Format)
|
||||
.map(|format| format.id)
|
||||
}
|
||||
|
||||
fn create_empty_cursor(&self) -> Result<xproto::Cursor, X11Error> {
|
||||
self.create_cursor_from_image(1, 1, 0, 0, &[0, 0, 0, 0])
|
||||
}
|
||||
|
||||
fn get_cursor(&self, cursor: Option<CursorIcon>) -> Result<xproto::Cursor, X11Error> {
|
||||
let cursor = match cursor {
|
||||
Some(cursor) => cursor,
|
||||
None => return self.create_empty_cursor(),
|
||||
};
|
||||
|
||||
let database = self.database();
|
||||
let handle = x11rb::cursor::Handle::new(
|
||||
self.xcb_connection(),
|
||||
self.default_screen_index(),
|
||||
&database,
|
||||
)?
|
||||
.reply()?;
|
||||
|
||||
let mut last_error = None;
|
||||
for &name in iter::once(&cursor.name()).chain(cursor.alt_names().iter()) {
|
||||
match handle.load_cursor(self.xcb_connection(), name) {
|
||||
Ok(cursor) => return Ok(cursor),
|
||||
Err(err) => last_error = Some(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
Err(last_error.unwrap())
|
||||
}
|
||||
|
||||
fn update_cursor(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
cursor: xproto::Cursor,
|
||||
) -> Result<(), X11Error> {
|
||||
self.xcb_connection()
|
||||
.change_window_attributes(
|
||||
window,
|
||||
&xproto::ChangeWindowAttributesAux::new().cursor(cursor),
|
||||
)?
|
||||
.ignore_error();
|
||||
|
||||
self.xcb_connection().flush()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SelectedCursor {
|
||||
Custom(CustomCursor),
|
||||
Named(CursorIcon),
|
||||
}
|
||||
|
||||
impl Default for SelectedCursor {
|
||||
fn default() -> Self {
|
||||
SelectedCursor::Named(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CustomCursor {
|
||||
xconn: Arc<XConnection>,
|
||||
cursor: xproto::Cursor,
|
||||
}
|
||||
|
||||
impl Hash for CustomCursor {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.cursor.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for CustomCursor {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.cursor == other.cursor
|
||||
}
|
||||
}
|
||||
impl Eq for CustomCursor {}
|
||||
|
||||
impl CustomCursor {
|
||||
pub(crate) fn new(
|
||||
event_loop: &ActiveEventLoop,
|
||||
cursor: CustomCursorSource,
|
||||
) -> Result<CustomCursor, RequestError> {
|
||||
let mut cursor = match cursor {
|
||||
CustomCursorSource::Image(cursor_image) => cursor_image,
|
||||
CustomCursorSource::Animation { .. } | CustomCursorSource::Url { .. } => {
|
||||
return Err(NotSupportedError::new("unsupported cursor kind").into())
|
||||
},
|
||||
};
|
||||
|
||||
// Reverse RGBA order to BGRA.
|
||||
cursor.buffer_mut().chunks_mut(4).for_each(|chunk| {
|
||||
let chunk: &mut [u8; 4] = chunk.try_into().unwrap();
|
||||
chunk[0..3].reverse();
|
||||
|
||||
// Byteswap if we need to.
|
||||
if event_loop.xconn.needs_endian_swap() {
|
||||
let value = u32::from_ne_bytes(*chunk).swap_bytes();
|
||||
*chunk = value.to_ne_bytes();
|
||||
}
|
||||
});
|
||||
|
||||
let cursor = event_loop
|
||||
.xconn
|
||||
.create_cursor_from_image(
|
||||
cursor.width(),
|
||||
cursor.height(),
|
||||
cursor.hotspot_x(),
|
||||
cursor.hotspot_y(),
|
||||
cursor.buffer(),
|
||||
)
|
||||
.map_err(|err| os_error!(err))?;
|
||||
|
||||
Ok(Self { xconn: event_loop.xconn.clone(), cursor })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CustomCursor {
|
||||
fn drop(&mut self) {
|
||||
self.xconn.xcb_connection().free_cursor(self.cursor).map(|r| r.ignore_error()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
impl CustomCursorProvider for CustomCursor {
|
||||
fn is_animated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
283
winit-x11/src/util/geometry.rs
Normal file
283
winit-x11/src/util/geometry.rs
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
use std::cmp;
|
||||
|
||||
use super::*;
|
||||
|
||||
// Friendly neighborhood axis-aligned rectangle
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AaRect {
|
||||
x: i64,
|
||||
y: i64,
|
||||
width: i64,
|
||||
height: i64,
|
||||
}
|
||||
|
||||
impl AaRect {
|
||||
pub fn new((x, y): (i32, i32), (width, height): (u32, u32)) -> Self {
|
||||
let (x, y) = (x as i64, y as i64);
|
||||
let (width, height) = (width as i64, height as i64);
|
||||
AaRect { x, y, width, height }
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, x: i64, y: i64) -> bool {
|
||||
x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
|
||||
}
|
||||
|
||||
pub fn get_overlapping_area(&self, other: &Self) -> i64 {
|
||||
let x_overlap = cmp::max(
|
||||
0,
|
||||
cmp::min(self.x + self.width, other.x + other.width) - cmp::max(self.x, other.x),
|
||||
);
|
||||
let y_overlap = cmp::max(
|
||||
0,
|
||||
cmp::min(self.y + self.height, other.y + other.height) - cmp::max(self.y, other.y),
|
||||
);
|
||||
x_overlap * y_overlap
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameExtents {
|
||||
pub left: u32,
|
||||
pub right: u32,
|
||||
pub top: u32,
|
||||
pub bottom: u32,
|
||||
}
|
||||
|
||||
impl FrameExtents {
|
||||
pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self {
|
||||
FrameExtents { left, right, top, bottom }
|
||||
}
|
||||
|
||||
pub fn from_border(border: u32) -> Self {
|
||||
Self::new(border, border, border, border)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FrameExtentsHeuristicPath {
|
||||
Supported,
|
||||
UnsupportedNested,
|
||||
UnsupportedBordered,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FrameExtentsHeuristic {
|
||||
pub frame_extents: FrameExtents,
|
||||
pub heuristic_path: FrameExtentsHeuristicPath,
|
||||
}
|
||||
|
||||
impl FrameExtentsHeuristic {
|
||||
pub fn surface_position(&self) -> (i32, i32) {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
if self.heuristic_path != UnsupportedBordered {
|
||||
(self.frame_extents.left as i32, self.frame_extents.top as i32)
|
||||
} else {
|
||||
(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn inner_pos_to_outer(&self, x: i32, y: i32) -> (i32, i32) {
|
||||
let (left, top) = self.surface_position();
|
||||
(x - left, y - top)
|
||||
}
|
||||
|
||||
pub fn surface_size_to_outer(&self, width: u32, height: u32) -> (u32, u32) {
|
||||
(
|
||||
width.saturating_add(
|
||||
self.frame_extents.left.saturating_add(self.frame_extents.right) as _
|
||||
),
|
||||
height.saturating_add(
|
||||
self.frame_extents.top.saturating_add(self.frame_extents.bottom) as _
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is adequate for inner_position
|
||||
pub fn translate_coords_root(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
root: xproto::Window,
|
||||
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
|
||||
self.xcb_connection().translate_coordinates(window, root, 0, 0)?.reply().map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn translate_coords(
|
||||
&self,
|
||||
src_w: xproto::Window,
|
||||
dst_w: xproto::Window,
|
||||
src_x: i16,
|
||||
src_y: i16,
|
||||
) -> Result<xproto::TranslateCoordinatesReply, X11Error> {
|
||||
self.xcb_connection()
|
||||
.translate_coordinates(src_w, dst_w, src_x, src_y)?
|
||||
.reply()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
// This is adequate for surface_size
|
||||
pub fn get_geometry(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
) -> Result<xproto::GetGeometryReply, X11Error> {
|
||||
self.xcb_connection().get_geometry(window)?.reply().map_err(Into::into)
|
||||
}
|
||||
|
||||
fn get_frame_extents(&self, window: xproto::Window) -> Option<FrameExtents> {
|
||||
let atoms = self.atoms();
|
||||
let extents_atom = atoms[_NET_FRAME_EXTENTS];
|
||||
|
||||
if !hint_is_supported(extents_atom) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
|
||||
// support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
|
||||
// be unsupported by many smaller WMs.
|
||||
let extents: Option<Vec<u32>> = self
|
||||
.get_property(window, extents_atom, xproto::Atom::from(xproto::AtomEnum::CARDINAL))
|
||||
.ok();
|
||||
|
||||
extents.and_then(|extents| {
|
||||
if extents.len() >= 4 {
|
||||
Some(FrameExtents {
|
||||
left: extents[0],
|
||||
right: extents[1],
|
||||
top: extents[2],
|
||||
bottom: extents[3],
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_top_level(&self, window: xproto::Window, root: xproto::Window) -> Option<bool> {
|
||||
let atoms = self.atoms();
|
||||
let client_list_atom = atoms[_NET_CLIENT_LIST];
|
||||
|
||||
if !hint_is_supported(client_list_atom) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let client_list: Option<Vec<xproto::Window>> = self
|
||||
.get_property(root, client_list_atom, xproto::Atom::from(xproto::AtomEnum::WINDOW))
|
||||
.ok();
|
||||
|
||||
client_list.map(|client_list| client_list.contains(&(window as xproto::Window)))
|
||||
}
|
||||
|
||||
fn get_parent_window(&self, window: xproto::Window) -> Result<xproto::Window, X11Error> {
|
||||
let parent = self.xcb_connection().query_tree(window)?.reply()?.parent;
|
||||
Ok(parent)
|
||||
}
|
||||
|
||||
fn climb_hierarchy(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
root: xproto::Window,
|
||||
) -> Result<xproto::Window, X11Error> {
|
||||
let mut outer_window = window;
|
||||
loop {
|
||||
let candidate = self.get_parent_window(outer_window)?;
|
||||
if candidate == root {
|
||||
break;
|
||||
}
|
||||
outer_window = candidate;
|
||||
}
|
||||
Ok(outer_window)
|
||||
}
|
||||
|
||||
pub fn get_frame_extents_heuristic(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
root: xproto::Window,
|
||||
) -> FrameExtentsHeuristic {
|
||||
use self::FrameExtentsHeuristicPath::*;
|
||||
|
||||
// Position relative to root window.
|
||||
// With rare exceptions, this is the position of a nested window. Cases where the window
|
||||
// isn't nested are outlined in the comments throughout this function, but in addition to
|
||||
// that, fullscreen windows often aren't nested.
|
||||
let (inner_y_rel_root, child) = {
|
||||
let coords = self
|
||||
.translate_coords_root(window, root)
|
||||
.expect("Failed to translate window coordinates");
|
||||
(coords.dst_y, coords.child)
|
||||
};
|
||||
|
||||
let (width, height, border) = {
|
||||
let inner_geometry =
|
||||
self.get_geometry(window).expect("Failed to get inner window geometry");
|
||||
(inner_geometry.width, inner_geometry.height, inner_geometry.border_width)
|
||||
};
|
||||
|
||||
// The first condition is only false for un-nested windows, but isn't always false for
|
||||
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
|
||||
// when y is on the range [0, 2] and if the window has been unfocused since being
|
||||
// undecorated (or was undecorated upon construction), the first condition is true,
|
||||
// requiring us to rely on the second condition.
|
||||
let nested = !(window == child || self.is_top_level(child, root) == Some(true));
|
||||
|
||||
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
|
||||
if let Some(mut frame_extents) = self.get_frame_extents(window) {
|
||||
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
|
||||
// decorations are disabled, but since the window becomes un-nested, it's easy to
|
||||
// catch.
|
||||
if !nested {
|
||||
frame_extents = FrameExtents::new(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
// The difference between the nested window's position and the outermost window's
|
||||
// position is equivalent to the frame size. In most scenarios, this is equivalent to
|
||||
// manually climbing the hierarchy as is done in the case below. Here's a list of
|
||||
// known discrepancies:
|
||||
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
|
||||
// addition to a 1px semi-transparent border. The margin can be easily observed by
|
||||
// using a screenshot tool to get a screenshot of a selected window, and is presumably
|
||||
// used for drawing drop shadows. Getting window geometry information via
|
||||
// hierarchy-climbing results in this margin being included in both the position and
|
||||
// outer size, so a window positioned at (0, 0) would be reported as having a position
|
||||
// (-10, -8).
|
||||
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px on
|
||||
// all sides, and there's no additional border.
|
||||
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root. Without
|
||||
// decorations, there's no difference. This is presumably related to Enlightenment's
|
||||
// fairly unique concept of window position; it interprets positions given to
|
||||
// XMoveWindow as a client area position rather than a position of the overall window.
|
||||
|
||||
FrameExtentsHeuristic { frame_extents, heuristic_path: Supported }
|
||||
} else if nested {
|
||||
// If the position value we have is for a nested window used as the client area, we'll
|
||||
// just climb up the hierarchy and get the geometry of the outermost window we're
|
||||
// nested in.
|
||||
let outer_window =
|
||||
self.climb_hierarchy(window, root).expect("Failed to climb window hierarchy");
|
||||
let (outer_y, outer_width, outer_height) = {
|
||||
let outer_geometry =
|
||||
self.get_geometry(outer_window).expect("Failed to get outer window geometry");
|
||||
(outer_geometry.y, outer_geometry.width, outer_geometry.height)
|
||||
};
|
||||
|
||||
// Since we have the geometry of the outermost window and the geometry of the client
|
||||
// area, we can figure out what's in between.
|
||||
let diff_x = outer_width.saturating_sub(width) as u32;
|
||||
let diff_y = outer_height.saturating_sub(height) as u32;
|
||||
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u32;
|
||||
|
||||
let left = diff_x / 2;
|
||||
let right = left;
|
||||
let top = offset_y;
|
||||
let bottom = diff_y.saturating_sub(offset_y);
|
||||
|
||||
let frame_extents = FrameExtents::new(left, right, top, bottom);
|
||||
FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedNested }
|
||||
} else {
|
||||
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
|
||||
// border value. This is convenient, since we can use it to get an accurate frame.
|
||||
let frame_extents = FrameExtents::from_border(border.into());
|
||||
FrameExtentsHeuristic { frame_extents, heuristic_path: UnsupportedBordered }
|
||||
}
|
||||
}
|
||||
}
|
||||
169
winit-x11/src/util/hint.rs
Normal file
169
winit-x11/src/util/hint.rs
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::WindowType;
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub enum StateOperation {
|
||||
Remove = 0, // _NET_WM_STATE_REMOVE
|
||||
Add = 1, // _NET_WM_STATE_ADD
|
||||
Toggle = 2, // _NET_WM_STATE_TOGGLE
|
||||
}
|
||||
|
||||
impl From<bool> for StateOperation {
|
||||
fn from(op: bool) -> Self {
|
||||
if op {
|
||||
StateOperation::Add
|
||||
} else {
|
||||
StateOperation::Remove
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowType {
|
||||
pub(crate) fn as_atom(&self, xconn: &Arc<XConnection>) -> xproto::Atom {
|
||||
use self::WindowType::*;
|
||||
let atom_name = match *self {
|
||||
Desktop => _NET_WM_WINDOW_TYPE_DESKTOP,
|
||||
Dock => _NET_WM_WINDOW_TYPE_DOCK,
|
||||
Toolbar => _NET_WM_WINDOW_TYPE_TOOLBAR,
|
||||
Menu => _NET_WM_WINDOW_TYPE_MENU,
|
||||
Utility => _NET_WM_WINDOW_TYPE_UTILITY,
|
||||
Splash => _NET_WM_WINDOW_TYPE_SPLASH,
|
||||
Dialog => _NET_WM_WINDOW_TYPE_DIALOG,
|
||||
DropdownMenu => _NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
|
||||
PopupMenu => _NET_WM_WINDOW_TYPE_POPUP_MENU,
|
||||
Tooltip => _NET_WM_WINDOW_TYPE_TOOLTIP,
|
||||
Notification => _NET_WM_WINDOW_TYPE_NOTIFICATION,
|
||||
Combo => _NET_WM_WINDOW_TYPE_COMBO,
|
||||
Dnd => _NET_WM_WINDOW_TYPE_DND,
|
||||
Normal => _NET_WM_WINDOW_TYPE_NORMAL,
|
||||
};
|
||||
|
||||
let atoms = xconn.atoms();
|
||||
atoms[atom_name]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MotifHints {
|
||||
hints: MwmHints,
|
||||
}
|
||||
|
||||
struct MwmHints {
|
||||
flags: u32,
|
||||
functions: u32,
|
||||
decorations: u32,
|
||||
input_mode: u32,
|
||||
status: u32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
mod mwm {
|
||||
// Motif WM hints are obsolete, but still widely supported.
|
||||
// https://stackoverflow.com/a/1909708
|
||||
pub const MWM_HINTS_FUNCTIONS: u32 = 1 << 0;
|
||||
pub const MWM_HINTS_DECORATIONS: u32 = 1 << 1;
|
||||
|
||||
pub const MWM_FUNC_ALL: u32 = 1 << 0;
|
||||
pub const MWM_FUNC_RESIZE: u32 = 1 << 1;
|
||||
pub const MWM_FUNC_MOVE: u32 = 1 << 2;
|
||||
pub const MWM_FUNC_MINIMIZE: u32 = 1 << 3;
|
||||
pub const MWM_FUNC_MAXIMIZE: u32 = 1 << 4;
|
||||
pub const MWM_FUNC_CLOSE: u32 = 1 << 5;
|
||||
}
|
||||
|
||||
impl MotifHints {
|
||||
pub fn new() -> MotifHints {
|
||||
MotifHints {
|
||||
hints: MwmHints { flags: 0, functions: 0, decorations: 0, input_mode: 0, status: 0 },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_decorations(&mut self, decorations: bool) {
|
||||
self.hints.flags |= mwm::MWM_HINTS_DECORATIONS;
|
||||
self.hints.decorations = decorations as u32;
|
||||
}
|
||||
|
||||
pub fn set_maximizable(&mut self, maximizable: bool) {
|
||||
if maximizable {
|
||||
self.add_func(mwm::MWM_FUNC_MAXIMIZE);
|
||||
} else {
|
||||
self.remove_func(mwm::MWM_FUNC_MAXIMIZE);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_func(&mut self, func: u32) {
|
||||
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 {
|
||||
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
|
||||
self.hints.functions &= !func;
|
||||
} else {
|
||||
self.hints.functions |= func;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_func(&mut self, func: u32) {
|
||||
if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 {
|
||||
self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS;
|
||||
self.hints.functions = mwm::MWM_FUNC_ALL;
|
||||
}
|
||||
|
||||
if self.hints.functions & mwm::MWM_FUNC_ALL != 0 {
|
||||
self.hints.functions |= func;
|
||||
} else {
|
||||
self.hints.functions &= !func;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MotifHints {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_motif_hints(&self, window: xproto::Window) -> MotifHints {
|
||||
let atoms = self.atoms();
|
||||
let motif_hints = atoms[_MOTIF_WM_HINTS];
|
||||
|
||||
let mut hints = MotifHints::new();
|
||||
|
||||
if let Ok(props) = self.get_property::<u32>(window, motif_hints, motif_hints) {
|
||||
hints.hints.flags = props.first().cloned().unwrap_or(0);
|
||||
hints.hints.functions = props.get(1).cloned().unwrap_or(0);
|
||||
hints.hints.decorations = props.get(2).cloned().unwrap_or(0);
|
||||
hints.hints.input_mode = props.get(3).cloned().unwrap_or(0);
|
||||
hints.hints.status = props.get(4).cloned().unwrap_or(0);
|
||||
}
|
||||
|
||||
hints
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
pub fn set_motif_hints(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
hints: &MotifHints,
|
||||
) -> Result<VoidCookie<'_>, X11Error> {
|
||||
let atoms = self.atoms();
|
||||
let motif_hints = atoms[_MOTIF_WM_HINTS];
|
||||
|
||||
let hints_data: [u32; 5] = [
|
||||
hints.hints.flags as u32,
|
||||
hints.hints.functions as u32,
|
||||
hints.hints.decorations as u32,
|
||||
hints.hints.input_mode as u32,
|
||||
hints.hints.status as u32,
|
||||
];
|
||||
|
||||
self.change_property(
|
||||
window,
|
||||
motif_hints,
|
||||
motif_hints,
|
||||
xproto::PropMode::REPLACE,
|
||||
&hints_data,
|
||||
)
|
||||
}
|
||||
}
|
||||
46
winit-x11/src/util/icon.rs
Normal file
46
winit-x11/src/util/icon.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#![allow(clippy::assertions_on_constants)]
|
||||
|
||||
use winit_core::icon::RgbaIcon;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) const PIXEL_SIZE: usize = mem::size_of::<Pixel>();
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Pixel {
|
||||
pub(crate) r: u8,
|
||||
pub(crate) g: u8,
|
||||
pub(crate) b: u8,
|
||||
pub(crate) a: u8,
|
||||
}
|
||||
|
||||
impl Pixel {
|
||||
pub fn to_packed_argb(&self) -> Cardinal {
|
||||
let mut cardinal = 0;
|
||||
assert!(CARDINAL_SIZE >= PIXEL_SIZE);
|
||||
let as_bytes = &mut cardinal as *mut _ as *mut u8;
|
||||
unsafe {
|
||||
*as_bytes.offset(0) = self.b;
|
||||
*as_bytes.offset(1) = self.g;
|
||||
*as_bytes.offset(2) = self.r;
|
||||
*as_bytes.offset(3) = self.a;
|
||||
}
|
||||
cardinal
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rgba_to_cardinals(icon: &RgbaIcon) -> Vec<Cardinal> {
|
||||
assert_eq!(icon.buffer().len() % PIXEL_SIZE, 0);
|
||||
let pixel_count = icon.buffer().len() / PIXEL_SIZE;
|
||||
assert_eq!(pixel_count, (icon.width() * icon.height()) as usize);
|
||||
let mut data = Vec::with_capacity(pixel_count);
|
||||
data.push(icon.width() as Cardinal);
|
||||
data.push(icon.height() as Cardinal);
|
||||
let pixels = icon.buffer().as_ptr() as *const Pixel;
|
||||
for pixel_index in 0..pixel_count {
|
||||
let pixel = unsafe { &*pixels.add(pixel_index) };
|
||||
data.push(pixel.to_packed_argb());
|
||||
}
|
||||
data
|
||||
}
|
||||
106
winit-x11/src/util/input.rs
Normal file
106
winit-x11/src/util/input.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
use std::{slice, str};
|
||||
|
||||
use x11rb::protocol::xinput::{self, ConnectionExt as _};
|
||||
use x11rb::protocol::xkb;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const VIRTUAL_CORE_POINTER: u16 = 2;
|
||||
|
||||
// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to
|
||||
// re-allocate (and make another round-trip) in the *vast* majority of cases.
|
||||
// To test if `lookup_utf8` works correctly, set this to 1.
|
||||
const TEXT_BUFFER_SIZE: usize = 1024;
|
||||
|
||||
impl XConnection {
|
||||
pub fn select_xinput_events(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
device_id: u16,
|
||||
mask: xinput::XIEventMask,
|
||||
) -> Result<VoidCookie<'_>, X11Error> {
|
||||
self.xcb_connection()
|
||||
.xinput_xi_select_events(window, &[xinput::EventMask {
|
||||
deviceid: device_id,
|
||||
mask: vec![mask],
|
||||
}])
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn select_xkb_events(
|
||||
&self,
|
||||
device_id: xkb::DeviceSpec,
|
||||
mask: xkb::EventType,
|
||||
) -> Result<bool, X11Error> {
|
||||
let mask = u16::from(mask) as _;
|
||||
let status =
|
||||
unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id as _, mask, mask) };
|
||||
|
||||
if status == ffi::True {
|
||||
self.flush_requests()?;
|
||||
Ok(true)
|
||||
} else {
|
||||
tracing::error!("Could not select XKB events: The XKB extension is not initialized!");
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query_pointer(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
device_id: u16,
|
||||
) -> Result<xinput::XIQueryPointerReply, X11Error> {
|
||||
self.xcb_connection()
|
||||
.xinput_xi_query_pointer(window, device_id)?
|
||||
.reply()
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn lookup_utf8_inner(
|
||||
&self,
|
||||
ic: ffi::XIC,
|
||||
key_event: &mut ffi::XKeyEvent,
|
||||
buffer: *mut u8,
|
||||
size: usize,
|
||||
) -> (ffi::KeySym, ffi::Status, c_int) {
|
||||
let mut keysym: ffi::KeySym = 0;
|
||||
let mut status: ffi::Status = 0;
|
||||
let count = unsafe {
|
||||
(self.xlib.Xutf8LookupString)(
|
||||
ic,
|
||||
key_event,
|
||||
buffer as *mut c_char,
|
||||
size as c_int,
|
||||
&mut keysym,
|
||||
&mut status,
|
||||
)
|
||||
};
|
||||
(keysym, status, count)
|
||||
}
|
||||
|
||||
pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String {
|
||||
// `assume_init` is safe here because the array consists of `MaybeUninit` values,
|
||||
// which do not require initialization.
|
||||
let mut buffer: [MaybeUninit<u8>; TEXT_BUFFER_SIZE] =
|
||||
unsafe { MaybeUninit::uninit().assume_init() };
|
||||
// If the buffer overflows, we'll make a new one on the heap.
|
||||
let mut vec;
|
||||
|
||||
let (_, status, count) =
|
||||
self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len());
|
||||
|
||||
let bytes = if status == ffi::XBufferOverflow {
|
||||
vec = Vec::with_capacity(count as usize);
|
||||
let (_, _, new_count) =
|
||||
self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity());
|
||||
debug_assert_eq!(count, new_count);
|
||||
|
||||
unsafe { vec.set_len(count as usize) };
|
||||
&vec[..count as usize]
|
||||
} else {
|
||||
unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) }
|
||||
};
|
||||
|
||||
str::from_utf8(bytes).unwrap_or("").to_string()
|
||||
}
|
||||
}
|
||||
75
winit-x11/src/util/keys.rs
Normal file
75
winit-x11/src/util/keys.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use std::iter::Enumerate;
|
||||
use std::slice::Iter;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub struct Keymap {
|
||||
keys: [u8; 32],
|
||||
}
|
||||
|
||||
pub struct KeymapIter<'a> {
|
||||
iter: Enumerate<Iter<'a, u8>>,
|
||||
index: usize,
|
||||
item: Option<u8>,
|
||||
}
|
||||
|
||||
impl Keymap {
|
||||
pub fn iter(&self) -> KeymapIter<'_> {
|
||||
KeymapIter { iter: self.keys.iter().enumerate(), index: 0, item: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Keymap {
|
||||
type IntoIter = KeymapIter<'a>;
|
||||
type Item = ffi::KeyCode;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for KeymapIter<'_> {
|
||||
type Item = ffi::KeyCode;
|
||||
|
||||
fn next(&mut self) -> Option<ffi::KeyCode> {
|
||||
if self.item.is_none() {
|
||||
for (index, &item) in self.iter.by_ref() {
|
||||
if item != 0 {
|
||||
self.index = index;
|
||||
self.item = Some(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.item.take().map(|item| {
|
||||
debug_assert!(item != 0);
|
||||
|
||||
let bit = first_bit(item);
|
||||
|
||||
if item != bit {
|
||||
// Remove the first bit; save the rest for further iterations
|
||||
self.item = Some(item ^ bit);
|
||||
}
|
||||
|
||||
let shift = bit.trailing_zeros() + (self.index * 8) as u32;
|
||||
shift as ffi::KeyCode
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn query_keymap(&self) -> Keymap {
|
||||
let mut keys = [0; 32];
|
||||
|
||||
unsafe {
|
||||
(self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char);
|
||||
}
|
||||
|
||||
Keymap { keys }
|
||||
}
|
||||
}
|
||||
|
||||
fn first_bit(b: u8) -> u8 {
|
||||
1 << b.trailing_zeros()
|
||||
}
|
||||
26
winit-x11/src/util/memory.rs
Normal file
26
winit-x11/src/util/memory.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use super::*;
|
||||
|
||||
pub(crate) struct XSmartPointer<'a, T> {
|
||||
xconn: &'a XConnection,
|
||||
pub ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<'a, T> XSmartPointer<'a, T> {
|
||||
// You're responsible for only passing things to this that should be XFree'd.
|
||||
// Returns None if ptr is null.
|
||||
pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option<Self> {
|
||||
if !ptr.is_null() {
|
||||
Some(XSmartPointer { xconn, ptr })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Drop for XSmartPointer<'_, T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
(self.xconn.xlib.XFree)(self.ptr as *mut _);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
winit-x11/src/util/mod.rs
Normal file
80
winit-x11/src/util/mod.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// Welcome to the util module, where we try to keep you from shooting yourself in the foot.
|
||||
// *results may vary
|
||||
|
||||
use std::mem::{self, MaybeUninit};
|
||||
use std::ops::BitAnd;
|
||||
use std::os::raw::*;
|
||||
|
||||
mod client_msg;
|
||||
pub mod cookie;
|
||||
mod cursor;
|
||||
mod geometry;
|
||||
mod hint;
|
||||
mod icon;
|
||||
mod input;
|
||||
pub mod keys;
|
||||
pub(crate) mod memory;
|
||||
mod mouse;
|
||||
mod randr;
|
||||
mod window_property;
|
||||
mod wm;
|
||||
mod xmodmap;
|
||||
|
||||
use x11rb::protocol::xproto::{self, ConnectionExt as _};
|
||||
|
||||
pub use self::cursor::*;
|
||||
pub use self::geometry::*;
|
||||
pub use self::hint::*;
|
||||
pub(crate) use self::icon::rgba_to_cardinals;
|
||||
pub use self::input::*;
|
||||
pub use self::mouse::*;
|
||||
pub use self::window_property::*;
|
||||
pub use self::wm::*;
|
||||
pub use self::xmodmap::ModifierKeymap;
|
||||
use super::atoms::*;
|
||||
use super::ffi;
|
||||
use crate::event_loop::{VoidCookie, X11Error};
|
||||
use crate::xdisplay::{XConnection, XError};
|
||||
|
||||
pub fn maybe_change<T: PartialEq>(field: &mut Option<T>, value: T) -> bool {
|
||||
let wrapped = Some(value);
|
||||
if *field != wrapped {
|
||||
*field = wrapped;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_flag<T>(bitset: T, flag: T) -> bool
|
||||
where
|
||||
T: Copy + PartialEq + BitAnd<T, Output = T>,
|
||||
{
|
||||
bitset & flag == flag
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// This is important, so pay attention!
|
||||
// Xlib has an output buffer, and tries to hide the async nature of X from you.
|
||||
// This buffer contains the requests you make, and is flushed under various circumstances:
|
||||
// 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed"
|
||||
// 2. `XFlush` explicitly flushes
|
||||
// 3. `XSync` flushes and blocks until all requests are responded to
|
||||
// 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync
|
||||
// internally. When in doubt, check the X11 source; if a function calls `_XReply`, it flushes
|
||||
// and waits.
|
||||
// All util functions that abstract an async function will return a `Flusher`.
|
||||
pub fn flush_requests(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XFlush)(self.display) };
|
||||
// println!("XFlush");
|
||||
// This isn't necessarily a useful time to check for errors (since our request hasn't
|
||||
// necessarily been processed yet)
|
||||
self.check_errors()
|
||||
}
|
||||
|
||||
pub fn sync_with_server(&self) -> Result<(), XError> {
|
||||
unsafe { (self.xlib.XSync)(self.display, ffi::False) };
|
||||
// println!("XSync");
|
||||
self.check_errors()
|
||||
}
|
||||
}
|
||||
187
winit-x11/src/util/modifiers.rs
Normal file
187
winit-x11/src/util/modifiers.rs
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
use std::{collections::HashMap, slice};
|
||||
|
||||
use super::*;
|
||||
|
||||
use winit_core::event::{ElementState, ModifiersState};
|
||||
|
||||
// Offsets within XModifierKeymap to each set of keycodes.
|
||||
// We are only interested in Shift, Control, Alt, and Logo.
|
||||
//
|
||||
// There are 8 sets total. The order of keycode sets is:
|
||||
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
|
||||
//
|
||||
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
|
||||
const SHIFT_OFFSET: usize = 0;
|
||||
const CONTROL_OFFSET: usize = 2;
|
||||
const ALT_OFFSET: usize = 3;
|
||||
const LOGO_OFFSET: usize = 6;
|
||||
const NUM_MODS: usize = 8;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Modifier {
|
||||
Alt,
|
||||
Ctrl,
|
||||
Shift,
|
||||
Logo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct ModifierKeymap {
|
||||
// Maps keycodes to modifiers
|
||||
keys: HashMap<ffi::KeyCode, Modifier>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct ModifierKeyState {
|
||||
// Contains currently pressed modifier keys and their corresponding modifiers
|
||||
keys: HashMap<ffi::KeyCode, Modifier>,
|
||||
state: ModifiersState,
|
||||
}
|
||||
|
||||
impl ModifierKeymap {
|
||||
pub fn new() -> ModifierKeymap {
|
||||
ModifierKeymap::default()
|
||||
}
|
||||
|
||||
pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option<Modifier> {
|
||||
self.keys.get(&keycode).cloned()
|
||||
}
|
||||
|
||||
pub fn reset_from_x_connection(&mut self, xconn: &XConnection) {
|
||||
{
|
||||
let keymap = xconn.xcb_connection().get_modifier_mapping().expect("get_modifier_mapping failed").reply().expect("get_modifier_mapping failed");
|
||||
|
||||
if keymap.is_null() {
|
||||
panic!("failed to allocate XModifierKeymap");
|
||||
}
|
||||
|
||||
self.reset_from_x_keymap(&*keymap);
|
||||
|
||||
(xconn.xlib.XFreeModifiermap)(keymap);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) {
|
||||
let keys_per_mod = keymap.max_keypermod as usize;
|
||||
|
||||
let keys = unsafe {
|
||||
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
|
||||
};
|
||||
|
||||
self.keys.clear();
|
||||
|
||||
self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift);
|
||||
self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl);
|
||||
self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt);
|
||||
self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo);
|
||||
}
|
||||
|
||||
fn read_x_keys(
|
||||
&mut self,
|
||||
keys: &[ffi::KeyCode],
|
||||
offset: usize,
|
||||
keys_per_mod: usize,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
let start = offset * keys_per_mod;
|
||||
let end = start + keys_per_mod;
|
||||
|
||||
for &keycode in &keys[start..end] {
|
||||
if keycode != 0 {
|
||||
self.keys.insert(keycode, modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModifierKeyState {
|
||||
pub fn update_keymap(&mut self, mods: &ModifierKeymap) {
|
||||
self.keys.retain(|k, v| {
|
||||
if let Some(m) = mods.get_modifier(*k) {
|
||||
*v = m;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
self.reset_state();
|
||||
}
|
||||
|
||||
pub fn update_state(
|
||||
&mut self,
|
||||
state: &ModifiersState,
|
||||
except: Option<Modifier>,
|
||||
) -> Option<ModifiersState> {
|
||||
let mut new_state = *state;
|
||||
|
||||
match except {
|
||||
Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()),
|
||||
Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()),
|
||||
Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()),
|
||||
Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()),
|
||||
None => (),
|
||||
}
|
||||
|
||||
if self.state == new_state {
|
||||
None
|
||||
} else {
|
||||
self.keys.retain(|_k, v| get_modifier(&new_state, *v));
|
||||
self.state = new_state;
|
||||
Some(new_state)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn modifiers(&self) -> ModifiersState {
|
||||
self.state
|
||||
}
|
||||
|
||||
pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) {
|
||||
match state {
|
||||
ElementState::Pressed => self.key_press(keycode, modifier),
|
||||
ElementState::Released => self.key_release(keycode),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) {
|
||||
self.keys.insert(keycode, modifier);
|
||||
|
||||
set_modifier(&mut self.state, modifier, true);
|
||||
}
|
||||
|
||||
pub fn key_release(&mut self, keycode: ffi::KeyCode) {
|
||||
if let Some(modifier) = self.keys.remove(&keycode) {
|
||||
if !self.keys.values().any(|&m| m == modifier) {
|
||||
set_modifier(&mut self.state, modifier, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_state(&mut self) {
|
||||
let mut new_state = ModifiersState::default();
|
||||
|
||||
for &m in self.keys.values() {
|
||||
set_modifier(&mut new_state, m, true);
|
||||
}
|
||||
|
||||
self.state = new_state;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool {
|
||||
match modifier {
|
||||
Modifier::Alt => state.alt(),
|
||||
Modifier::Ctrl => state.ctrl(),
|
||||
Modifier::Shift => state.shift(),
|
||||
Modifier::Logo => state.logo(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) {
|
||||
match modifier {
|
||||
Modifier::Alt => state.set(ModifiersState::ALT, value),
|
||||
Modifier::Ctrl => state.set(ModifiersState::CTRL, value),
|
||||
Modifier::Shift => state.set(ModifiersState::SHIFT, value),
|
||||
Modifier::Logo => state.set(ModifiersState::LOGO, value),
|
||||
}
|
||||
}
|
||||
49
winit-x11/src/util/mouse.rs
Normal file
49
winit-x11/src/util/mouse.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
//! Utilities for handling mouse events.
|
||||
|
||||
/// Recorded mouse delta designed to filter out noise.
|
||||
pub struct Delta<T> {
|
||||
x: T,
|
||||
y: T,
|
||||
}
|
||||
|
||||
impl<T: Default> Default for Delta<T> {
|
||||
fn default() -> Self {
|
||||
Self { x: Default::default(), y: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default> Delta<T> {
|
||||
pub(crate) fn set_x(&mut self, x: T) {
|
||||
self.x = x;
|
||||
}
|
||||
|
||||
pub(crate) fn set_y(&mut self, y: T) {
|
||||
self.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! consume {
|
||||
($this:expr, $ty:ty) => {{
|
||||
let this = $this;
|
||||
let (x, y) = match (this.x.abs() < <$ty>::EPSILON, this.y.abs() < <$ty>::EPSILON) {
|
||||
(true, true) => return None,
|
||||
(false, true) => (this.x, 0.0),
|
||||
(true, false) => (0.0, this.y),
|
||||
(false, false) => (this.x, this.y),
|
||||
};
|
||||
|
||||
Some((x, y))
|
||||
}};
|
||||
}
|
||||
|
||||
impl Delta<f32> {
|
||||
pub(crate) fn consume(self) -> Option<(f32, f32)> {
|
||||
consume!(self, f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl Delta<f64> {
|
||||
pub(crate) fn consume(self) -> Option<(f64, f64)> {
|
||||
consume!(self, f64)
|
||||
}
|
||||
}
|
||||
186
winit-x11/src/util/randr.rs
Normal file
186
winit-x11/src/util/randr.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
use std::num::NonZeroU16;
|
||||
use std::str::FromStr;
|
||||
use std::{env, str};
|
||||
|
||||
use dpi::validate_scale_factor;
|
||||
use tracing::warn;
|
||||
use winit_core::monitor::VideoMode;
|
||||
use x11rb::protocol::randr::{self, ConnectionExt as _};
|
||||
|
||||
use super::*;
|
||||
use crate::monitor::{self, VideoModeHandle};
|
||||
|
||||
/// Represents values of `WINIT_HIDPI_FACTOR`.
|
||||
pub enum EnvVarDPI {
|
||||
Randr,
|
||||
Scale(f64),
|
||||
NotSet,
|
||||
}
|
||||
|
||||
pub fn calc_dpi_factor(
|
||||
(width_px, height_px): (u32, u32),
|
||||
(width_mm, height_mm): (u64, u64),
|
||||
) -> f64 {
|
||||
// See http://xpra.org/trac/ticket/728 for more information.
|
||||
if width_mm == 0 || height_mm == 0 {
|
||||
warn!("XRandR reported that the display's 0mm in size, which is certifiably insane");
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let ppmm = ((width_px as f64 * height_px as f64) / (width_mm as f64 * height_mm as f64)).sqrt();
|
||||
// Quantize 1/12 step size
|
||||
let dpi_factor = ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0);
|
||||
assert!(validate_scale_factor(dpi_factor));
|
||||
if dpi_factor <= 20. {
|
||||
dpi_factor
|
||||
} else {
|
||||
1.
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
// Retrieve DPI from Xft.dpi property
|
||||
pub fn get_xft_dpi(&self) -> Option<f64> {
|
||||
// Try to get it from XSETTINGS first.
|
||||
if let Some(xsettings_screen) = self.xsettings_screen() {
|
||||
match self.xsettings_dpi(xsettings_screen) {
|
||||
Ok(Some(dpi)) => return Some(dpi),
|
||||
Ok(None) => {},
|
||||
Err(err) => {
|
||||
tracing::warn!("failed to fetch XSettings: {err}");
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
self.database().get_string("Xft.dpi", "").and_then(|s| f64::from_str(s).ok())
|
||||
}
|
||||
|
||||
pub fn get_output_info(
|
||||
&self,
|
||||
resources: &monitor::ScreenResources,
|
||||
crtc: &randr::GetCrtcInfoReply,
|
||||
) -> Option<(String, f64, Vec<VideoModeHandle>)> {
|
||||
let output_info = match self
|
||||
.xcb_connection()
|
||||
.randr_get_output_info(crtc.outputs[0], x11rb::CURRENT_TIME)
|
||||
.map_err(X11Error::from)
|
||||
.and_then(|r| r.reply().map_err(X11Error::from))
|
||||
{
|
||||
Ok(output_info) => output_info,
|
||||
Err(err) => {
|
||||
warn!("Failed to get output info: {:?}", err);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
|
||||
let bit_depth = self.default_root().root_depth;
|
||||
let output_modes = &output_info.modes;
|
||||
let resource_modes = resources.modes();
|
||||
let current_mode = crtc.mode;
|
||||
|
||||
let modes = resource_modes
|
||||
.iter()
|
||||
// XRROutputInfo contains an array of mode ids that correspond to
|
||||
// modes in the array in XRRScreenResources
|
||||
.filter(|x| output_modes.contains(&x.id))
|
||||
.map(|mode| VideoModeHandle {
|
||||
current: mode.id == current_mode,
|
||||
mode: VideoMode::new(
|
||||
(mode.width as u32, mode.height as u32).into(),
|
||||
NonZeroU16::new(bit_depth as u16),
|
||||
monitor::mode_refresh_rate_millihertz(mode),
|
||||
),
|
||||
native_mode: mode.id,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let name = match str::from_utf8(&output_info.name) {
|
||||
Ok(name) => name.to_owned(),
|
||||
Err(err) => {
|
||||
warn!("Failed to get output name: {:?}", err);
|
||||
return None;
|
||||
},
|
||||
};
|
||||
// Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set
|
||||
let deprecated_dpi_override = env::var("WINIT_HIDPI_FACTOR").ok();
|
||||
if deprecated_dpi_override.is_some() {
|
||||
warn!(
|
||||
"The WINIT_HIDPI_FACTOR environment variable is deprecated; use \
|
||||
WINIT_X11_SCALE_FACTOR"
|
||||
)
|
||||
}
|
||||
let dpi_env = env::var("WINIT_X11_SCALE_FACTOR").ok().map_or_else(
|
||||
|| EnvVarDPI::NotSet,
|
||||
|var| {
|
||||
if var.to_lowercase() == "randr" {
|
||||
EnvVarDPI::Randr
|
||||
} else if let Ok(dpi) = f64::from_str(&var) {
|
||||
EnvVarDPI::Scale(dpi)
|
||||
} else if var.is_empty() {
|
||||
EnvVarDPI::NotSet
|
||||
} else {
|
||||
panic!(
|
||||
"`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \
|
||||
floats greater than 0, or `randr`. Got `{var}`"
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let scale_factor = match dpi_env {
|
||||
EnvVarDPI::Randr => calc_dpi_factor(
|
||||
(crtc.width.into(), crtc.height.into()),
|
||||
(output_info.mm_width as _, output_info.mm_height as _),
|
||||
),
|
||||
EnvVarDPI::Scale(dpi_override) => {
|
||||
if !validate_scale_factor(dpi_override) {
|
||||
panic!(
|
||||
"`WINIT_X11_SCALE_FACTOR` invalid; DPI factors must be either normal \
|
||||
floats greater than 0, or `randr`. Got `{dpi_override}`",
|
||||
);
|
||||
}
|
||||
dpi_override
|
||||
},
|
||||
EnvVarDPI::NotSet => {
|
||||
if let Some(dpi) = self.get_xft_dpi() {
|
||||
dpi / 96.
|
||||
} else {
|
||||
calc_dpi_factor(
|
||||
(crtc.width.into(), crtc.height.into()),
|
||||
(output_info.mm_width as _, output_info.mm_height as _),
|
||||
)
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Some((name, scale_factor, modes))
|
||||
}
|
||||
|
||||
pub fn set_crtc_config(
|
||||
&self,
|
||||
crtc_id: randr::Crtc,
|
||||
mode_id: randr::Mode,
|
||||
) -> Result<(), X11Error> {
|
||||
let crtc =
|
||||
self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?;
|
||||
|
||||
self.xcb_connection()
|
||||
.randr_set_crtc_config(
|
||||
crtc_id,
|
||||
crtc.timestamp,
|
||||
x11rb::CURRENT_TIME,
|
||||
crtc.x,
|
||||
crtc.y,
|
||||
mode_id,
|
||||
crtc.rotation,
|
||||
&crtc.outputs,
|
||||
)?
|
||||
.reply()
|
||||
.map(|_| ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn get_crtc_mode(&self, crtc_id: randr::Crtc) -> Result<randr::Mode, X11Error> {
|
||||
Ok(self.xcb_connection().randr_get_crtc_info(crtc_id, x11rb::CURRENT_TIME)?.reply()?.mode)
|
||||
}
|
||||
}
|
||||
195
winit-x11/src/util/window_property.rs
Normal file
195
winit-x11/src/util/window_property.rs
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytemuck::{NoUninit, Pod};
|
||||
use x11rb::connection::Connection;
|
||||
use x11rb::errors::ReplyError;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub const CARDINAL_SIZE: usize = mem::size_of::<u32>();
|
||||
|
||||
pub type Cardinal = u32;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum GetPropertyError {
|
||||
X11rbError(Arc<ReplyError>),
|
||||
TypeMismatch(xproto::Atom),
|
||||
FormatMismatch(c_int),
|
||||
}
|
||||
|
||||
impl GetPropertyError {
|
||||
pub fn is_actual_property_type(&self, t: xproto::Atom) -> bool {
|
||||
if let GetPropertyError::TypeMismatch(actual_type) = *self {
|
||||
actual_type == t
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<ReplyError>> From<T> for GetPropertyError {
|
||||
fn from(e: T) -> Self {
|
||||
Self::X11rbError(Arc::new(e.into()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for GetPropertyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
GetPropertyError::X11rbError(err) => err.fmt(f),
|
||||
GetPropertyError::TypeMismatch(err) => write!(f, "type mismatch: {err}"),
|
||||
GetPropertyError::FormatMismatch(err) => write!(f, "format mismatch: {err}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for GetPropertyError {}
|
||||
|
||||
// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop.
|
||||
// To test if `get_property` works correctly, set this to 1.
|
||||
const PROPERTY_BUFFER_SIZE: u32 = 1024; // 4k of RAM ought to be enough for anyone!
|
||||
|
||||
impl XConnection {
|
||||
pub fn get_property<T: Pod>(
|
||||
&self,
|
||||
window: xproto::Window,
|
||||
property: xproto::Atom,
|
||||
property_type: xproto::Atom,
|
||||
) -> Result<Vec<T>, GetPropertyError> {
|
||||
let mut iter = PropIterator::new(self.xcb_connection(), window, property, property_type);
|
||||
let mut data = vec![];
|
||||
|
||||
loop {
|
||||
if !iter.next_window(&mut data)? {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn change_property<'a, T: NoUninit>(
|
||||
&'a self,
|
||||
window: xproto::Window,
|
||||
property: xproto::Atom,
|
||||
property_type: xproto::Atom,
|
||||
mode: xproto::PropMode,
|
||||
new_value: &[T],
|
||||
) -> Result<VoidCookie<'a>, X11Error> {
|
||||
assert!([1usize, 2, 4].contains(&mem::size_of::<T>()));
|
||||
self.xcb_connection()
|
||||
.change_property(
|
||||
mode,
|
||||
window,
|
||||
property,
|
||||
property_type,
|
||||
(mem::size_of::<T>() * 8) as u8,
|
||||
new_value.len().try_into().expect("too many items for property"),
|
||||
bytemuck::cast_slice::<T, u8>(new_value),
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the "windows" of the property that we are fetching.
|
||||
struct PropIterator<'a, C: ?Sized, T> {
|
||||
/// Handle to the connection.
|
||||
conn: &'a C,
|
||||
|
||||
/// The window that we're fetching the property from.
|
||||
window: xproto::Window,
|
||||
|
||||
/// The property that we're fetching.
|
||||
property: xproto::Atom,
|
||||
|
||||
/// The type of the property that we're fetching.
|
||||
property_type: xproto::Atom,
|
||||
|
||||
/// The offset of the next window, in 32-bit chunks.
|
||||
offset: u32,
|
||||
|
||||
/// The format of the type.
|
||||
format: u8,
|
||||
|
||||
/// Keep a reference to `T`.
|
||||
_phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, C: Connection + ?Sized, T: Pod> PropIterator<'a, C, T> {
|
||||
/// Create a new property iterator.
|
||||
fn new(
|
||||
conn: &'a C,
|
||||
window: xproto::Window,
|
||||
property: xproto::Atom,
|
||||
property_type: xproto::Atom,
|
||||
) -> Self {
|
||||
let format = match mem::size_of::<T>() {
|
||||
1 => 8,
|
||||
2 => 16,
|
||||
4 => 32,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Self {
|
||||
conn,
|
||||
window,
|
||||
property,
|
||||
property_type,
|
||||
offset: 0,
|
||||
format,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the next window and append it to `data`.
|
||||
///
|
||||
/// Returns whether there are more windows to fetch.
|
||||
fn next_window(&mut self, data: &mut Vec<T>) -> Result<bool, GetPropertyError> {
|
||||
// Send the request and wait for the reply.
|
||||
let reply = self
|
||||
.conn
|
||||
.get_property(
|
||||
false,
|
||||
self.window,
|
||||
self.property,
|
||||
self.property_type,
|
||||
self.offset,
|
||||
PROPERTY_BUFFER_SIZE,
|
||||
)?
|
||||
.reply()?;
|
||||
|
||||
// Make sure that the reply is of the correct type.
|
||||
if reply.type_ != self.property_type {
|
||||
return Err(GetPropertyError::TypeMismatch(reply.type_));
|
||||
}
|
||||
|
||||
// Make sure that the reply is of the correct format.
|
||||
if reply.format != self.format {
|
||||
return Err(GetPropertyError::FormatMismatch(reply.format.into()));
|
||||
}
|
||||
|
||||
// Append the data to the output.
|
||||
if mem::size_of::<T>() == 1 && mem::align_of::<T>() == 1 {
|
||||
// We can just do a bytewise append.
|
||||
data.extend_from_slice(bytemuck::cast_slice(&reply.value));
|
||||
} else {
|
||||
// Rust's borrowing and types system makes this a bit tricky.
|
||||
//
|
||||
// We need to make sure that the data is properly aligned. Unfortunately the best
|
||||
// safe way to do this is to copy the data to another buffer and then append.
|
||||
//
|
||||
// TODO(notgull): It may be worth it to use `unsafe` to copy directly from
|
||||
// `reply.value` to `data`; check if this is faster. Use benchmarks!
|
||||
let old_len = data.len();
|
||||
let added_len = reply.value.len() / mem::size_of::<T>();
|
||||
data.resize(old_len + added_len, T::zeroed());
|
||||
bytemuck::cast_slice_mut::<T, u8>(&mut data[old_len..]).copy_from_slice(&reply.value);
|
||||
}
|
||||
|
||||
// Check `bytes_after` to see if there are more windows to fetch.
|
||||
self.offset += PROPERTY_BUFFER_SIZE;
|
||||
Ok(reply.bytes_after != 0)
|
||||
}
|
||||
}
|
||||
137
winit-x11/src/util/wm.rs
Normal file
137
winit-x11/src/util/wm.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
use std::sync::Mutex;
|
||||
|
||||
use super::*;
|
||||
|
||||
// https://specifications.freedesktop.org/wm-spec/latest/ar01s04.html#idm46075117309248
|
||||
pub const MOVERESIZE_TOPLEFT: isize = 0;
|
||||
pub const MOVERESIZE_TOP: isize = 1;
|
||||
pub const MOVERESIZE_TOPRIGHT: isize = 2;
|
||||
pub const MOVERESIZE_RIGHT: isize = 3;
|
||||
pub const MOVERESIZE_BOTTOMRIGHT: isize = 4;
|
||||
pub const MOVERESIZE_BOTTOM: isize = 5;
|
||||
pub const MOVERESIZE_BOTTOMLEFT: isize = 6;
|
||||
pub const MOVERESIZE_LEFT: isize = 7;
|
||||
pub const MOVERESIZE_MOVE: isize = 8;
|
||||
|
||||
// This info is global to the window manager.
|
||||
static SUPPORTED_HINTS: Mutex<Vec<xproto::Atom>> = Mutex::new(Vec::new());
|
||||
static WM_NAME: Mutex<Option<String>> = Mutex::new(None);
|
||||
|
||||
pub fn hint_is_supported(hint: xproto::Atom) -> bool {
|
||||
(*SUPPORTED_HINTS.lock().unwrap()).contains(&hint)
|
||||
}
|
||||
|
||||
pub fn wm_name_is_one_of(names: &[&str]) -> bool {
|
||||
if let Some(ref name) = *WM_NAME.lock().unwrap() {
|
||||
names.contains(&name.as_str())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl XConnection {
|
||||
pub fn update_cached_wm_info(&self, root: xproto::Window) {
|
||||
*SUPPORTED_HINTS.lock().unwrap() = self.get_supported_hints(root);
|
||||
*WM_NAME.lock().unwrap() = self.get_wm_name(root);
|
||||
}
|
||||
|
||||
fn get_supported_hints(&self, root: xproto::Window) -> Vec<xproto::Atom> {
|
||||
let atoms = self.atoms();
|
||||
let supported_atom = atoms[_NET_SUPPORTED];
|
||||
self.get_property(root, supported_atom, xproto::Atom::from(xproto::AtomEnum::ATOM))
|
||||
.unwrap_or_else(|_| Vec::with_capacity(0))
|
||||
}
|
||||
|
||||
#[allow(clippy::useless_conversion)]
|
||||
fn get_wm_name(&self, root: xproto::Window) -> Option<String> {
|
||||
let atoms = self.atoms();
|
||||
let check_atom = atoms[_NET_SUPPORTING_WM_CHECK];
|
||||
let wm_name_atom = atoms[_NET_WM_NAME];
|
||||
|
||||
// Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
|
||||
// it working and being supported. This has been reported upstream, but due to the
|
||||
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
|
||||
// regardless of whether or not the WM claims to support it.
|
||||
//
|
||||
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be
|
||||
// fixed in 0.72.
|
||||
// if !supported_hints.contains(&check_atom) {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
|
||||
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
|
||||
// if !supported_hints.contains(&wm_name_atom) {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
|
||||
|
||||
// Querying this property on the root window will give us the ID of a child window created
|
||||
// by the WM.
|
||||
let root_window_wm_check = {
|
||||
let result = self.get_property::<xproto::Window>(
|
||||
root,
|
||||
check_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::WINDOW),
|
||||
);
|
||||
|
||||
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
|
||||
|
||||
wm_check?
|
||||
};
|
||||
|
||||
// Querying the same property on the child window we were given, we should get this child
|
||||
// window's ID again.
|
||||
let child_window_wm_check = {
|
||||
let result = self.get_property::<xproto::Window>(
|
||||
root_window_wm_check.into(),
|
||||
check_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::WINDOW),
|
||||
);
|
||||
|
||||
let wm_check = result.ok().and_then(|wm_check| wm_check.first().cloned());
|
||||
|
||||
wm_check?
|
||||
};
|
||||
|
||||
// These values should be the same.
|
||||
if root_window_wm_check != child_window_wm_check {
|
||||
return None;
|
||||
}
|
||||
|
||||
// All of that work gives us a window ID that we can get the WM name from.
|
||||
let wm_name = {
|
||||
let atoms = self.atoms();
|
||||
let utf8_string_atom = atoms[UTF8_STRING];
|
||||
|
||||
let result =
|
||||
self.get_property(root_window_wm_check.into(), wm_name_atom, utf8_string_atom);
|
||||
|
||||
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
|
||||
// string. For more fun trivia, IceWM is also unique in including version and uname
|
||||
// information in this string (this means you'll have to be careful if you want to match
|
||||
// against it, though).
|
||||
// The unofficial 1.4 fork of IceWM still includes the extra details, but properly
|
||||
// returns a UTF8 string that isn't null-terminated.
|
||||
let no_utf8 = if let Err(ref err) = result {
|
||||
err.is_actual_property_type(xproto::Atom::from(xproto::AtomEnum::STRING))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if no_utf8 {
|
||||
self.get_property(
|
||||
root_window_wm_check.into(),
|
||||
wm_name_atom,
|
||||
xproto::Atom::from(xproto::AtomEnum::STRING),
|
||||
)
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
.ok();
|
||||
|
||||
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
|
||||
}
|
||||
}
|
||||
56
winit-x11/src/util/xmodmap.rs
Normal file
56
winit-x11/src/util/xmodmap.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use std::collections::HashSet;
|
||||
use std::slice;
|
||||
|
||||
use x11_dl::xlib::{KeyCode as XKeyCode, XModifierKeymap};
|
||||
|
||||
// Offsets within XModifierKeymap to each set of keycodes.
|
||||
// We are only interested in Shift, Control, Alt, and Logo.
|
||||
//
|
||||
// There are 8 sets total. The order of keycode sets is:
|
||||
// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5
|
||||
//
|
||||
// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html
|
||||
const NUM_MODS: usize = 8;
|
||||
|
||||
/// Track which keys are modifiers, so we can properly replay them when they were filtered.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ModifierKeymap {
|
||||
// Maps keycodes to modifiers
|
||||
modifiers: HashSet<XKeyCode>,
|
||||
}
|
||||
|
||||
impl ModifierKeymap {
|
||||
pub fn new() -> ModifierKeymap {
|
||||
ModifierKeymap::default()
|
||||
}
|
||||
|
||||
pub fn is_modifier(&self, keycode: XKeyCode) -> bool {
|
||||
self.modifiers.contains(&keycode)
|
||||
}
|
||||
|
||||
pub fn reload_from_x_connection(&mut self, xconn: &super::XConnection) {
|
||||
unsafe {
|
||||
let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display);
|
||||
|
||||
if keymap.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.reset_from_x_keymap(&*keymap);
|
||||
|
||||
(xconn.xlib.XFreeModifiermap)(keymap);
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_from_x_keymap(&mut self, keymap: &XModifierKeymap) {
|
||||
let keys_per_mod = keymap.max_keypermod as usize;
|
||||
|
||||
let keys = unsafe {
|
||||
slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS)
|
||||
};
|
||||
self.modifiers.clear();
|
||||
for key in keys {
|
||||
self.modifiers.insert(*key);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue