2023-07-12 00:59:12 -07:00
|
|
|
use std::{
|
|
|
|
|
collections::HashMap,
|
|
|
|
|
error::Error,
|
|
|
|
|
fmt, ptr,
|
2023-07-20 13:16:51 +00:00
|
|
|
sync::{
|
|
|
|
|
atomic::{AtomicU32, Ordering},
|
2023-12-25 21:25:55 -08:00
|
|
|
Arc, Mutex, RwLock, RwLockReadGuard,
|
2023-07-20 13:16:51 +00:00
|
|
|
},
|
2023-07-12 00:59:12 -07:00
|
|
|
};
|
2015-05-17 11:19:06 +02:00
|
|
|
|
2019-08-26 19:06:59 -07:00
|
|
|
use crate::window::CursorIcon;
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
use super::{atoms::Atoms, ffi, monitor::MonitorHandle};
|
2023-12-29 22:04:27 -08:00
|
|
|
use x11rb::{
|
|
|
|
|
connection::Connection,
|
2024-01-30 04:52:29 -08:00
|
|
|
protocol::{
|
|
|
|
|
randr::ConnectionExt as _,
|
|
|
|
|
xproto::{self, ConnectionExt},
|
|
|
|
|
},
|
2023-12-29 22:04:27 -08:00
|
|
|
resource_manager,
|
|
|
|
|
xcb_ffi::XCBConnection,
|
|
|
|
|
};
|
2015-05-17 11:19:06 +02:00
|
|
|
|
|
|
|
|
/// A connection to an X server.
|
2024-02-09 05:52:09 +04:00
|
|
|
pub struct XConnection {
|
2015-05-17 11:19:06 +02:00
|
|
|
pub xlib: ffi::Xlib,
|
|
|
|
|
pub xcursor: ffi::Xcursor,
|
2023-08-29 14:01:25 -07:00
|
|
|
|
|
|
|
|
// TODO(notgull): I'd like to remove this, but apparently Xlib and Xinput2 are tied together
|
|
|
|
|
// for some reason.
|
2015-06-22 22:49:48 +01:00
|
|
|
pub xinput2: ffi::XInput2,
|
2023-08-29 14:01:25 -07:00
|
|
|
|
2015-05-17 11:19:06 +02:00
|
|
|
pub display: *mut ffi::Display,
|
2023-08-29 14:01:25 -07:00
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
/// The manager for the XCB connection.
|
|
|
|
|
///
|
|
|
|
|
/// The `Option` ensures that we can drop it before we close the `Display`.
|
|
|
|
|
xcb: Option<XCBConnection>,
|
|
|
|
|
|
|
|
|
|
/// The atoms used by `winit`.
|
|
|
|
|
///
|
|
|
|
|
/// This is a large structure, so I've elected to Box it to make accessing the fields of
|
|
|
|
|
/// this struct easier. Feel free to unbox it if you like kicking puppies.
|
|
|
|
|
atoms: Box<Atoms>,
|
|
|
|
|
|
|
|
|
|
/// The index of the default screen.
|
|
|
|
|
default_screen: usize,
|
|
|
|
|
|
2023-07-20 13:16:51 +00:00
|
|
|
/// The last timestamp received by this connection.
|
|
|
|
|
timestamp: AtomicU32,
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
/// List of monitor handles.
|
|
|
|
|
pub monitor_handles: Mutex<Option<Vec<MonitorHandle>>>,
|
|
|
|
|
|
|
|
|
|
/// The resource database.
|
2023-12-25 21:25:55 -08:00
|
|
|
database: RwLock<resource_manager::Database>,
|
2023-08-29 14:01:25 -07:00
|
|
|
|
2023-12-29 22:04:27 -08:00
|
|
|
/// RandR version.
|
|
|
|
|
randr_version: (u32, u32),
|
|
|
|
|
|
2024-01-30 04:52:29 -08:00
|
|
|
/// Atom for the XSettings screen.
|
2024-02-10 00:24:03 +04:00
|
|
|
xsettings_screen: Option<xproto::Atom>,
|
2024-01-30 04:52:29 -08:00
|
|
|
|
2015-12-24 10:57:08 +01:00
|
|
|
pub latest_error: Mutex<Option<XError>>,
|
2019-08-26 19:06:59 -07:00
|
|
|
pub cursor_cache: Mutex<HashMap<Option<CursorIcon>, ffi::Cursor>>,
|
2015-05-17 11:19:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
unsafe impl Send for XConnection {}
|
|
|
|
|
unsafe impl Sync for XConnection {}
|
|
|
|
|
|
2019-06-21 11:33:15 -04:00
|
|
|
pub type XErrorHandler =
|
2023-07-22 02:32:27 -07:00
|
|
|
Option<unsafe extern "C" fn(*mut ffi::Display, *mut ffi::XErrorEvent) -> std::os::raw::c_int>;
|
2015-12-14 23:05:07 +01:00
|
|
|
|
2015-05-17 11:19:06 +02:00
|
|
|
impl XConnection {
|
2015-12-14 23:05:07 +01:00
|
|
|
pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
|
2015-05-17 11:19:06 +02:00
|
|
|
// opening the libraries
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
let xlib = ffi::Xlib::open()?;
|
|
|
|
|
let xcursor = ffi::Xcursor::open()?;
|
|
|
|
|
let xlib_xcb = ffi::Xlib_xcb::open()?;
|
2023-08-29 14:01:25 -07:00
|
|
|
let xinput2 = ffi::XInput2::open()?;
|
2015-05-17 11:19:06 +02:00
|
|
|
|
|
|
|
|
unsafe { (xlib.XInitThreads)() };
|
2015-12-14 23:05:07 +01:00
|
|
|
unsafe { (xlib.XSetErrorHandler)(error_handler) };
|
2015-05-17 11:19:06 +02:00
|
|
|
|
|
|
|
|
// calling XOpenDisplay
|
|
|
|
|
let display = unsafe {
|
|
|
|
|
let display = (xlib.XOpenDisplay)(ptr::null());
|
|
|
|
|
if display.is_null() {
|
2015-09-20 08:48:53 +02:00
|
|
|
return Err(XNotSupported::XOpenDisplayFailed);
|
2015-05-17 11:19:06 +02:00
|
|
|
}
|
|
|
|
|
display
|
|
|
|
|
};
|
|
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
// Open the x11rb XCB connection.
|
|
|
|
|
let xcb = {
|
|
|
|
|
// Get a pointer to the underlying XCB connection
|
|
|
|
|
let xcb_connection =
|
|
|
|
|
unsafe { (xlib_xcb.XGetXCBConnection)(display as *mut ffi::Display) };
|
|
|
|
|
assert!(!xcb_connection.is_null());
|
|
|
|
|
|
|
|
|
|
// Wrap the XCB connection in an x11rb XCB connection
|
|
|
|
|
let conn =
|
|
|
|
|
unsafe { XCBConnection::from_raw_xcb_connection(xcb_connection.cast(), false) };
|
|
|
|
|
|
|
|
|
|
conn.map_err(|e| XNotSupported::XcbConversionError(Arc::new(WrapConnectError(e))))?
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Get the default screen.
|
|
|
|
|
let default_screen = unsafe { (xlib.XDefaultScreen)(display) } as usize;
|
|
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
// Load the database.
|
|
|
|
|
let database = resource_manager::new_from_default(&xcb)
|
|
|
|
|
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
|
|
|
|
|
|
2023-12-29 22:04:27 -08:00
|
|
|
// Load the RandR version.
|
|
|
|
|
let randr_version = xcb
|
|
|
|
|
.randr_query_version(1, 3)
|
|
|
|
|
.expect("failed to request XRandR version")
|
|
|
|
|
.reply()
|
|
|
|
|
.expect("failed to query XRandR version");
|
|
|
|
|
|
2024-02-10 00:24:03 +04:00
|
|
|
let xsettings_screen = Self::new_xsettings_screen(&xcb, default_screen);
|
|
|
|
|
if xsettings_screen.is_none() {
|
2024-02-25 19:20:39 -08:00
|
|
|
tracing::warn!("error setting XSETTINGS; Xft options won't reload automatically")
|
2024-02-10 00:24:03 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch atoms.
|
|
|
|
|
let atoms = Atoms::new(&xcb)
|
2024-01-30 04:52:29 -08:00
|
|
|
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?
|
|
|
|
|
.reply()
|
2024-02-10 00:24:03 +04:00
|
|
|
.map_err(|e| XNotSupported::XcbConversionError(Arc::new(e)))?;
|
2024-01-30 04:52:29 -08:00
|
|
|
|
2015-05-17 11:19:06 +02:00
|
|
|
Ok(XConnection {
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
xlib,
|
|
|
|
|
xcursor,
|
|
|
|
|
xinput2,
|
|
|
|
|
display,
|
2023-07-12 00:59:12 -07:00
|
|
|
xcb: Some(xcb),
|
|
|
|
|
atoms: Box::new(atoms),
|
|
|
|
|
default_screen,
|
2023-07-20 13:16:51 +00:00
|
|
|
timestamp: AtomicU32::new(0),
|
2015-12-24 10:57:08 +01:00
|
|
|
latest_error: Mutex::new(None),
|
2023-08-29 14:01:25 -07:00
|
|
|
monitor_handles: Mutex::new(None),
|
2023-12-25 21:25:55 -08:00
|
|
|
database: RwLock::new(database),
|
2019-08-26 19:06:59 -07:00
|
|
|
cursor_cache: Default::default(),
|
2023-12-29 22:04:27 -08:00
|
|
|
randr_version: (randr_version.major_version, randr_version.minor_version),
|
2024-01-30 04:52:29 -08:00
|
|
|
xsettings_screen,
|
2015-05-17 11:19:06 +02:00
|
|
|
})
|
|
|
|
|
}
|
2015-12-24 10:57:08 +01:00
|
|
|
|
2024-02-10 00:24:03 +04:00
|
|
|
fn new_xsettings_screen(xcb: &XCBConnection, default_screen: usize) -> Option<xproto::Atom> {
|
|
|
|
|
// Fetch the _XSETTINGS_S[screen number] atom.
|
|
|
|
|
let xsettings_screen = xcb
|
|
|
|
|
.intern_atom(false, format!("_XSETTINGS_S{}", default_screen).as_bytes())
|
|
|
|
|
.ok()?
|
|
|
|
|
.reply()
|
|
|
|
|
.ok()?
|
|
|
|
|
.atom;
|
|
|
|
|
|
|
|
|
|
// Get PropertyNotify events from the XSETTINGS window.
|
|
|
|
|
// TODO: The XSETTINGS window here can change. In the future, listen for DestroyNotify on this window
|
2024-03-04 14:04:53 -06:00
|
|
|
// in order to accommodate for a changed window here.
|
2024-02-10 00:24:03 +04:00
|
|
|
let selector_window = xcb
|
|
|
|
|
.get_selection_owner(xsettings_screen)
|
|
|
|
|
.ok()?
|
|
|
|
|
.reply()
|
|
|
|
|
.ok()?
|
|
|
|
|
.owner;
|
|
|
|
|
|
|
|
|
|
xcb.change_window_attributes(
|
|
|
|
|
selector_window,
|
|
|
|
|
&xproto::ChangeWindowAttributesAux::new()
|
|
|
|
|
.event_mask(xproto::EventMask::PROPERTY_CHANGE),
|
|
|
|
|
)
|
|
|
|
|
.ok()?
|
|
|
|
|
.check()
|
|
|
|
|
.ok()?;
|
|
|
|
|
|
|
|
|
|
Some(xsettings_screen)
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-24 10:57:08 +01:00
|
|
|
/// Checks whether an error has been triggered by the previous function calls.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn check_errors(&self) -> Result<(), XError> {
|
2022-08-31 18:32:19 +02:00
|
|
|
let error = self.latest_error.lock().unwrap().take();
|
2015-12-24 10:57:08 +01:00
|
|
|
if let Some(error) = error {
|
|
|
|
|
Err(error)
|
|
|
|
|
} else {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 00:59:12 -07:00
|
|
|
|
2023-12-29 22:04:27 -08:00
|
|
|
#[inline]
|
|
|
|
|
pub fn randr_version(&self) -> (u32, u32) {
|
|
|
|
|
self.randr_version
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 00:59:12 -07:00
|
|
|
/// Get the underlying XCB connection.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn xcb_connection(&self) -> &XCBConnection {
|
|
|
|
|
self.xcb
|
|
|
|
|
.as_ref()
|
|
|
|
|
.expect("xcb_connection somehow called after drop?")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the list of atoms.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn atoms(&self) -> &Atoms {
|
|
|
|
|
&self.atoms
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the index of the default screen.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn default_screen_index(&self) -> usize {
|
|
|
|
|
self.default_screen
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the default screen.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn default_root(&self) -> &xproto::Screen {
|
|
|
|
|
&self.xcb_connection().setup().roots[self.default_screen]
|
|
|
|
|
}
|
2023-07-20 13:16:51 +00:00
|
|
|
|
2023-08-29 14:01:25 -07:00
|
|
|
/// Get the resource database.
|
|
|
|
|
#[inline]
|
2023-12-25 21:25:55 -08:00
|
|
|
pub fn database(&self) -> RwLockReadGuard<'_, resource_manager::Database> {
|
|
|
|
|
self.database.read().unwrap_or_else(|e| e.into_inner())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reload the resource database.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn reload_database(&self) -> Result<(), super::X11Error> {
|
|
|
|
|
let database = resource_manager::new_from_default(self.xcb_connection())?;
|
|
|
|
|
*self.database.write().unwrap_or_else(|e| e.into_inner()) = database;
|
|
|
|
|
Ok(())
|
2023-08-29 14:01:25 -07:00
|
|
|
}
|
|
|
|
|
|
2023-07-20 13:16:51 +00:00
|
|
|
/// Get the latest timestamp.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn timestamp(&self) -> u32 {
|
|
|
|
|
self.timestamp.load(Ordering::Relaxed)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set the last witnessed timestamp.
|
|
|
|
|
#[inline]
|
|
|
|
|
pub fn set_timestamp(&self, timestamp: u32) {
|
|
|
|
|
// Store the timestamp in the slot if it's greater than the last one.
|
|
|
|
|
let mut last_timestamp = self.timestamp.load(Ordering::Relaxed);
|
|
|
|
|
loop {
|
|
|
|
|
let wrapping_sub = |a: xproto::Timestamp, b: xproto::Timestamp| (a as i32) - (b as i32);
|
|
|
|
|
|
|
|
|
|
if wrapping_sub(timestamp, last_timestamp) <= 0 {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match self.timestamp.compare_exchange(
|
|
|
|
|
last_timestamp,
|
|
|
|
|
timestamp,
|
|
|
|
|
Ordering::Relaxed,
|
|
|
|
|
Ordering::Relaxed,
|
|
|
|
|
) {
|
|
|
|
|
Ok(_) => break,
|
|
|
|
|
Err(x) => last_timestamp = x,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-01-30 04:52:29 -08:00
|
|
|
|
|
|
|
|
/// Get the atom for Xsettings.
|
|
|
|
|
#[inline]
|
2024-02-10 00:24:03 +04:00
|
|
|
pub fn xsettings_screen(&self) -> Option<xproto::Atom> {
|
2024-01-30 04:52:29 -08:00
|
|
|
self.xsettings_screen
|
|
|
|
|
}
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Debug for XConnection {
|
2019-06-18 02:27:00 +08:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
X11: General cleanup (#491)
* X11: General cleanup
This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.
- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).
* X11: Add better diagnostics when initialization fails
* X11: Handle XIQueryDevice failure
* X11: Use correct types in error handler
2018-05-03 09:15:49 -04:00
|
|
|
self.display.fmt(f)
|
2015-12-24 10:57:08 +01:00
|
|
|
}
|
2015-05-17 11:19:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for XConnection {
|
2015-09-21 14:42:05 +02:00
|
|
|
#[inline]
|
2015-05-17 11:19:06 +02:00
|
|
|
fn drop(&mut self) {
|
2023-07-12 00:59:12 -07:00
|
|
|
self.xcb = None;
|
2015-05-17 11:19:06 +02:00
|
|
|
unsafe { (self.xlib.XCloseDisplay)(self.display) };
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-20 08:42:32 +02:00
|
|
|
|
2015-12-24 10:57:08 +01:00
|
|
|
/// Error triggered by xlib.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct XError {
|
|
|
|
|
pub description: String,
|
|
|
|
|
pub error_code: u8,
|
|
|
|
|
pub request_code: u8,
|
|
|
|
|
pub minor_code: u8,
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-09 22:29:31 -07:00
|
|
|
impl Error for XError {}
|
2015-12-24 10:57:08 +01:00
|
|
|
|
|
|
|
|
impl fmt::Display for XError {
|
2019-06-18 02:27:00 +08:00
|
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
2019-06-21 11:33:15 -04:00
|
|
|
write!(
|
|
|
|
|
formatter,
|
|
|
|
|
"X error: {} (code: {}, request code: {}, minor code: {})",
|
|
|
|
|
self.description, self.error_code, self.request_code, self.minor_code
|
|
|
|
|
)
|
2015-12-24 10:57:08 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-20 08:42:32 +02:00
|
|
|
/// Error returned if this system doesn't have XLib or can't create an X connection.
|
2015-09-20 08:48:53 +02:00
|
|
|
#[derive(Clone, Debug)]
|
2024-02-09 05:52:09 +04:00
|
|
|
pub enum XNotSupported {
|
2015-09-20 08:48:53 +02:00
|
|
|
/// Failed to load one or several shared libraries.
|
|
|
|
|
LibraryOpenError(ffi::OpenError),
|
2023-07-12 00:59:12 -07:00
|
|
|
|
2015-09-20 08:48:53 +02:00
|
|
|
/// Connecting to the X server with `XOpenDisplay` failed.
|
2023-07-12 00:59:12 -07:00
|
|
|
XOpenDisplayFailed, // TODO: add better message.
|
|
|
|
|
|
|
|
|
|
/// We encountered an error while converting the connection to XCB.
|
|
|
|
|
XcbConversionError(Arc<dyn Error + Send + Sync + 'static>),
|
2015-09-20 08:48:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<ffi::OpenError> for XNotSupported {
|
2015-09-21 14:42:05 +02:00
|
|
|
#[inline]
|
2015-09-20 08:48:53 +02:00
|
|
|
fn from(err: ffi::OpenError) -> XNotSupported {
|
|
|
|
|
XNotSupported::LibraryOpenError(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-09-20 08:42:32 +02:00
|
|
|
|
2020-01-09 22:29:31 -07:00
|
|
|
impl XNotSupported {
|
|
|
|
|
fn description(&self) -> &'static str {
|
|
|
|
|
match self {
|
2015-09-20 08:48:53 +02:00
|
|
|
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
|
|
|
|
|
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
|
2023-07-12 00:59:12 -07:00
|
|
|
XNotSupported::XcbConversionError(_) => "Failed to convert Xlib connection to XCB",
|
2015-09-20 08:48:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
2020-01-09 22:29:31 -07:00
|
|
|
}
|
2015-09-20 08:48:53 +02:00
|
|
|
|
2020-01-09 22:29:31 -07:00
|
|
|
impl Error for XNotSupported {
|
2015-09-21 14:42:05 +02:00
|
|
|
#[inline]
|
2020-01-09 22:29:31 -07:00
|
|
|
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
2015-09-20 08:48:53 +02:00
|
|
|
match *self {
|
|
|
|
|
XNotSupported::LibraryOpenError(ref err) => Some(err),
|
2023-07-12 00:59:12 -07:00
|
|
|
XNotSupported::XcbConversionError(ref err) => Some(&**err),
|
2019-06-21 11:33:15 -04:00
|
|
|
_ => None,
|
2015-09-20 08:48:53 +02:00
|
|
|
}
|
2015-09-20 08:42:32 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for XNotSupported {
|
2019-06-18 02:27:00 +08:00
|
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
2015-09-20 08:42:32 +02:00
|
|
|
formatter.write_str(self.description())
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 00:59:12 -07:00
|
|
|
|
|
|
|
|
/// A newtype wrapper around a `ConnectError` that can't be accessed by downstream libraries.
|
|
|
|
|
///
|
|
|
|
|
/// Without this, `x11rb` would become a public dependency.
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct WrapConnectError(x11rb::rust_connection::ConnectError);
|
|
|
|
|
|
|
|
|
|
impl fmt::Display for WrapConnectError {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
|
fmt::Display::fmt(&self.0, f)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Error for WrapConnectError {
|
|
|
|
|
// We can't implement `source()` here or otherwise risk exposing `x11rb`.
|
|
|
|
|
}
|