Detailed changes
@@ -647,44 +647,60 @@ pub(super) unsafe fn read_fd(mut fd: filedescriptor::FileDescriptor) -> Result<V
Ok(buffer)
}
+#[cfg(any(feature = "wayland", feature = "x11"))]
+pub(super) const DEFAULT_CURSOR_ICON_NAME: &str = "left_ptr";
+
impl CursorStyle {
#[cfg(any(feature = "wayland", feature = "x11"))]
- pub(super) fn to_icon_name(&self) -> &'static str {
- // Based on cursor names from https://gitlab.gnome.org/GNOME/adwaita-icon-theme (GNOME)
- // and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
- // Web CSS cursor names: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values
+ pub(super) fn to_icon_names(&self) -> &'static [&'static str] {
+ // Based on cursor names from chromium:
+ // https://github.com/chromium/chromium/blob/d3069cf9c973dc3627fa75f64085c6a86c8f41bf/ui/base/cursor/cursor_factory.cc#L113
match self {
- CursorStyle::Arrow => "left_ptr",
- CursorStyle::IBeam => "text",
- CursorStyle::Crosshair => "crosshair",
- CursorStyle::ClosedHand => "grabbing",
- CursorStyle::OpenHand => "grab",
- CursorStyle::PointingHand => "pointer",
- CursorStyle::ResizeLeft => "w-resize",
- CursorStyle::ResizeRight => "e-resize",
- CursorStyle::ResizeLeftRight => "ew-resize",
- CursorStyle::ResizeUp => "n-resize",
- CursorStyle::ResizeDown => "s-resize",
- CursorStyle::ResizeUpDown => "ns-resize",
- CursorStyle::ResizeUpLeftDownRight => "nwse-resize",
- CursorStyle::ResizeUpRightDownLeft => "nesw-resize",
- CursorStyle::ResizeColumn => "col-resize",
- CursorStyle::ResizeRow => "row-resize",
- CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
- CursorStyle::OperationNotAllowed => "not-allowed",
- CursorStyle::DragLink => "alias",
- CursorStyle::DragCopy => "copy",
- CursorStyle::ContextualMenu => "context-menu",
+ CursorStyle::Arrow => &[DEFAULT_CURSOR_ICON_NAME],
+ CursorStyle::IBeam => &["text", "xterm"],
+ CursorStyle::Crosshair => &["crosshair", "cross"],
+ CursorStyle::ClosedHand => &["closedhand", "grabbing", "hand2"],
+ CursorStyle::OpenHand => &["openhand", "grab", "hand1"],
+ CursorStyle::PointingHand => &["pointer", "hand", "hand2"],
+ CursorStyle::ResizeLeft => &["w-resize", "left_side"],
+ CursorStyle::ResizeRight => &["e-resize", "right_side"],
+ CursorStyle::ResizeLeftRight => &["ew-resize", "sb_h_double_arrow"],
+ CursorStyle::ResizeUp => &["n-resize", "top_side"],
+ CursorStyle::ResizeDown => &["s-resize", "bottom_side"],
+ CursorStyle::ResizeUpDown => &["sb_v_double_arrow", "ns-resize"],
+ CursorStyle::ResizeUpLeftDownRight => &["size_fdiag", "bd_double_arrow", "nwse-resize"],
+ CursorStyle::ResizeUpRightDownLeft => &["size_bdiag", "nesw-resize", "fd_double_arrow"],
+ CursorStyle::ResizeColumn => &["col-resize", "sb_h_double_arrow"],
+ CursorStyle::ResizeRow => &["row-resize", "sb_v_double_arrow"],
+ CursorStyle::IBeamCursorForVerticalLayout => &["vertical-text"],
+ CursorStyle::OperationNotAllowed => &["not-allowed", "crossed_circle"],
+ CursorStyle::DragLink => &["alias"],
+ CursorStyle::DragCopy => &["copy"],
+ CursorStyle::ContextualMenu => &["context-menu"],
CursorStyle::None => {
#[cfg(debug_assertions)]
panic!("CursorStyle::None should be handled separately in the client");
#[cfg(not(debug_assertions))]
- "default"
+ &[DEFAULT_CURSOR_ICON_NAME]
}
}
}
}
+#[cfg(any(feature = "wayland", feature = "x11"))]
+pub(super) fn log_cursor_icon_warning(message: impl std::fmt::Display) {
+ if let Ok(xcursor_path) = env::var("XCURSOR_PATH") {
+ log::warn!(
+ "{:#}\ncursor icon loading may be failing if XCURSOR_PATH environment variable is invalid. \
+ XCURSOR_PATH overrides the default icon search. Its current value is '{}'",
+ message,
+ xcursor_path
+ );
+ } else {
+ log::warn!("{:#}", message);
+ }
+}
+
#[cfg(any(feature = "wayland", feature = "x11"))]
impl crate::Keystroke {
pub(super) fn from_xkb(
@@ -730,7 +730,7 @@ impl LinuxClient for WaylandClient {
let scale = focused_window.primary_output_scale();
state
.cursor
- .set_icon(&wl_pointer, serial, style.to_icon_name(), scale);
+ .set_icon(&wl_pointer, serial, style.to_icon_names(), scale);
}
}
}
@@ -1530,9 +1530,12 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
cursor_shape_device.set_shape(serial, style.to_shape());
} else {
let scale = window.primary_output_scale();
- state
- .cursor
- .set_icon(&wl_pointer, serial, style.to_icon_name(), scale);
+ state.cursor.set_icon(
+ &wl_pointer,
+ serial,
+ style.to_icon_names(),
+ scale,
+ );
}
}
drop(state);
@@ -1,4 +1,6 @@
use crate::Globals;
+use crate::platform::linux::{DEFAULT_CURSOR_ICON_NAME, log_cursor_icon_warning};
+use anyhow::anyhow;
use util::ResultExt;
use wayland_client::Connection;
@@ -82,47 +84,57 @@ impl Cursor {
&mut self,
wl_pointer: &WlPointer,
serial_id: u32,
- mut cursor_icon_name: &str,
+ mut cursor_icon_names: &[&str],
scale: i32,
) {
self.set_theme_size(self.size * scale as u32);
- if let Some(theme) = &mut self.theme {
- let mut buffer: Option<&CursorImageBuffer>;
+ let Some(theme) = &mut self.theme else {
+ log::warn!("Wayland: Unable to load cursor themes");
+ return;
+ };
- if let Some(cursor) = theme.get_cursor(&cursor_icon_name) {
- buffer = Some(&cursor[0]);
- } else if let Some(cursor) = theme.get_cursor("default") {
- buffer = Some(&cursor[0]);
- cursor_icon_name = "default";
- log::warn!(
- "Linux: Wayland: Unable to get cursor icon: {}. Using default cursor icon",
- cursor_icon_name
- );
+ let mut buffer: &CursorImageBuffer;
+ 'outer: {
+ for cursor_icon_name in cursor_icon_names {
+ if let Some(cursor) = theme.get_cursor(cursor_icon_name) {
+ buffer = &cursor[0];
+ break 'outer;
+ }
+ }
+
+ if let Some(cursor) = theme.get_cursor(DEFAULT_CURSOR_ICON_NAME) {
+ buffer = &cursor[0];
+ log_cursor_icon_warning(anyhow!(
+ "wayland: Unable to get cursor icon {:?}. \
+ Using default cursor icon: '{}'",
+ cursor_icon_names,
+ DEFAULT_CURSOR_ICON_NAME
+ ));
} else {
- buffer = None;
- log::warn!("Linux: Wayland: Unable to get default cursor too!");
+ log_cursor_icon_warning(anyhow!(
+ "wayland: Unable to fallback on default cursor icon '{}' for theme '{}'",
+ DEFAULT_CURSOR_ICON_NAME,
+ self.theme_name.as_deref().unwrap_or("default")
+ ));
+ return;
}
+ }
- if let Some(buffer) = &mut buffer {
- let (width, height) = buffer.dimensions();
- let (hot_x, hot_y) = buffer.hotspot();
+ let (width, height) = buffer.dimensions();
+ let (hot_x, hot_y) = buffer.hotspot();
- self.surface.set_buffer_scale(scale);
+ self.surface.set_buffer_scale(scale);
- wl_pointer.set_cursor(
- serial_id,
- Some(&self.surface),
- hot_x as i32 / scale,
- hot_y as i32 / scale,
- );
+ wl_pointer.set_cursor(
+ serial_id,
+ Some(&self.surface),
+ hot_x as i32 / scale,
+ hot_y as i32 / scale,
+ );
- self.surface.attach(Some(&buffer), 0, 0);
- self.surface.damage(0, 0, width as i32, height as i32);
- self.surface.commit();
- }
- } else {
- log::warn!("Linux: Wayland: Unable to load cursor themes");
- }
+ self.surface.attach(Some(&buffer), 0, 0);
+ self.surface.damage(0, 0, width as i32, height as i32);
+ self.surface.commit();
}
}
@@ -8,7 +8,7 @@ use std::{
time::{Duration, Instant},
};
-use anyhow::Context as _;
+use anyhow::{Context as _, anyhow};
use calloop::{
EventLoop, LoopHandle, RegistrationToken,
generic::{FdWrapper, Generic},
@@ -51,7 +51,8 @@ use crate::platform::{
LinuxCommon, PlatformWindow,
blade::BladeContext,
linux::{
- LinuxClient, get_xkb_compose_state, is_within_click_distance, open_uri_internal,
+ DEFAULT_CURSOR_ICON_NAME, LinuxClient, get_xkb_compose_state, is_within_click_distance,
+ log_cursor_icon_warning, open_uri_internal,
platform::{DOUBLE_CLICK_INTERVAL, SCROLL_LINES},
reveal_path_internal,
xdg_desktop_portal::{Event as XDPEvent, XDPEventSource},
@@ -211,7 +212,7 @@ pub struct X11ClientState {
pub(crate) pre_key_char_down: Option<Keystroke>,
pub(crate) cursor_handle: cursor::Handle,
pub(crate) cursor_styles: HashMap<xproto::Window, CursorStyle>,
- pub(crate) cursor_cache: HashMap<CursorStyle, xproto::Cursor>,
+ pub(crate) cursor_cache: HashMap<CursorStyle, Option<xproto::Cursor>>,
pointer_device_states: BTreeMap<xinput::DeviceId, PointerDeviceState>,
@@ -1501,22 +1502,8 @@ impl LinuxClient for X11Client {
return;
}
- let cursor = match state.cursor_cache.get(&style) {
- Some(cursor) => *cursor,
- None => {
- let Some(cursor) = (match style {
- CursorStyle::None => create_invisible_cursor(&state.xcb_connection).log_err(),
- _ => state
- .cursor_handle
- .load_cursor(&state.xcb_connection, style.to_icon_name())
- .log_err(),
- }) else {
- return;
- };
-
- state.cursor_cache.insert(style, cursor);
- cursor
- }
+ let Some(cursor) = state.get_cursor_icon(style) else {
+ return;
};
state.cursor_styles.insert(focused_window, style);
@@ -1773,6 +1760,78 @@ impl X11ClientState {
})
.expect("Failed to initialize refresh timer")
}
+
+ fn get_cursor_icon(&mut self, style: CursorStyle) -> Option<xproto::Cursor> {
+ if let Some(cursor) = self.cursor_cache.get(&style) {
+ return *cursor;
+ }
+
+ let mut result;
+ match style {
+ CursorStyle::None => match create_invisible_cursor(&self.xcb_connection) {
+ Ok(loaded_cursor) => result = Ok(loaded_cursor),
+ Err(err) => result = Err(err.context("error while creating invisible cursor")),
+ },
+ _ => 'outer: {
+ let mut errors = String::new();
+ let cursor_icon_names = style.to_icon_names();
+ for cursor_icon_name in cursor_icon_names {
+ match self
+ .cursor_handle
+ .load_cursor(&self.xcb_connection, cursor_icon_name)
+ {
+ Ok(loaded_cursor) => {
+ if loaded_cursor != x11rb::NONE {
+ result = Ok(loaded_cursor);
+ break 'outer;
+ }
+ }
+ Err(err) => {
+ errors.push_str(&err.to_string());
+ errors.push('\n');
+ }
+ }
+ }
+ if errors.is_empty() {
+ result = Err(anyhow!(
+ "errors while loading cursor icons {:?}:\n{}",
+ cursor_icon_names,
+ errors
+ ));
+ } else {
+ result = Err(anyhow!("did not find cursor icons {:?}", cursor_icon_names));
+ }
+ }
+ };
+
+ let cursor = match result {
+ Ok(cursor) => Some(cursor),
+ Err(err) => {
+ match self
+ .cursor_handle
+ .load_cursor(&self.xcb_connection, DEFAULT_CURSOR_ICON_NAME)
+ {
+ Ok(default) => {
+ log_cursor_icon_warning(err.context(format!(
+ "x11: error loading cursor icon, falling back on default icon '{}'",
+ DEFAULT_CURSOR_ICON_NAME
+ )));
+ Some(default)
+ }
+ Err(default_err) => {
+ log_cursor_icon_warning(err.context(default_err).context(format!(
+ "x11: error loading default cursor fallback '{}'",
+ DEFAULT_CURSOR_ICON_NAME
+ )));
+ None
+ }
+ }
+ }
+ };
+
+ self.cursor_cache.insert(style, cursor);
+ cursor
+ }
}
// Adapted from: