linux: Fix mouse cursor size and blur on Wayland (#21373)
Closes #15788, #13258 This is a long-standing issue with a few previous attempts to fix it, such as [this one](https://github.com/zed-industries/zed/pull/17496). However, that fix was later reverted because it resolved the blur issue but caused a size issue. Currently, both blur and size issues persist when you set a custom cursor size from GNOME Settings and use fractional scaling. This PR addresses both issues. --- ### Context A new Wayland protocol, [cursor-shape-v1](https://gitlab.freedesktop.org/wayland/wayland-protocols/-/merge_requests/194), allows the compositor to handle rendering the cursor at the correct size and shape. This protocol is implemented by KDE, wlroots (Sway-like environments), etc. Zed supports this protocol, so there are no issues on these desktop environments. However, GNOME has not yet [adopted](https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/6212) this protocol. As a result, apps must fall back to manually rendering the cursor by specifying the theme, size, scale, etc., themselves. Zed also implements this fallback but does not correctly account for the display scale. --- ### Scale Fix For example, if your cursor size is `64px` and you’re using fractional scaling (e.g., `150%`), the display scale reported by the window query will be an integer value, `2` in this case. Why `2` if the scale is `150%`? That’s what the new protocol aims to improve. However, since GNOME Wayland uses this integer scale everywhere, it’s sufficient for our use case. To fix the issue, we set the `buffer_scale` to this value. But that alone doesn’t solve the problem. We also need to generate a matching theme cursor size for this scaled version. This can be calculated as `64px` * `2`, resulting in `128px` as the theme cursor size. --- ### Size Fix The XDG Desktop Portal’s `cursor-size` event fails to read the cursor size because it expects an `i32` but encounters a type error with `u32`. Due to this, the cursor size was interpreted as the default `24px` instead of the actual size set via user. --- ### Tested This fix has been tested with all possible combinations of the following: - [x] GNOME Normal Scale (100%, 200%, etc.) - [x] GNOME Fractional Scaling (125%, 150%, etc.) - [x] GNOME Cursor Sizes (**Settings > Accessibility > Seeing**, e.g., `24px`, `64px`, etc.) - [x] GNOME Experimental Feature `scale-monitor-framebuffer` (both enabled and disabled) - [x] KDE (`cursor-shape-v1` protocol) --- **Result:** 64px custom cursor size + 150% Fractional Scale: https://github.com/user-attachments/assets/cf3b1a0f-9a25-45d0-ab03-75059d3305e7 --- Release Notes: - Fixed mouse cursor size and blur issues on Wayland
This commit is contained in:
parent
fd71801346
commit
d609931e1c
4 changed files with 79 additions and 43 deletions
|
@ -496,7 +496,7 @@ impl WaylandClient {
|
|||
XDPEvent::CursorTheme(theme) => {
|
||||
if let Some(client) = client.0.upgrade() {
|
||||
let mut client = client.borrow_mut();
|
||||
client.cursor.set_theme(theme.as_str(), None);
|
||||
client.cursor.set_theme(theme.as_str());
|
||||
}
|
||||
}
|
||||
XDPEvent::CursorSize(size) => {
|
||||
|
@ -649,15 +649,16 @@ impl LinuxClient for WaylandClient {
|
|||
|
||||
if let Some(cursor_shape_device) = &state.cursor_shape_device {
|
||||
cursor_shape_device.set_shape(serial, style.to_shape());
|
||||
} else if state.mouse_focused_window.is_some() {
|
||||
} else if let Some(focused_window) = &state.mouse_focused_window {
|
||||
// cursor-shape-v1 isn't supported, set the cursor using a surface.
|
||||
let wl_pointer = state
|
||||
.wl_pointer
|
||||
.clone()
|
||||
.expect("window is focused by pointer");
|
||||
let scale = focused_window.primary_output_scale();
|
||||
state
|
||||
.cursor
|
||||
.set_icon(&wl_pointer, serial, &style.to_icon_name());
|
||||
.set_icon(&wl_pointer, serial, &style.to_icon_name(), scale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1439,9 +1440,13 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
|
|||
if let Some(cursor_shape_device) = &state.cursor_shape_device {
|
||||
cursor_shape_device.set_shape(serial, style.to_shape());
|
||||
} else {
|
||||
state
|
||||
.cursor
|
||||
.set_icon(&wl_pointer, serial, &style.to_icon_name());
|
||||
let scale = window.primary_output_scale();
|
||||
state.cursor.set_icon(
|
||||
&wl_pointer,
|
||||
serial,
|
||||
&style.to_icon_name(),
|
||||
scale,
|
||||
);
|
||||
}
|
||||
}
|
||||
drop(state);
|
||||
|
|
|
@ -9,6 +9,7 @@ use wayland_cursor::{CursorImageBuffer, CursorTheme};
|
|||
pub(crate) struct Cursor {
|
||||
theme: Option<CursorTheme>,
|
||||
theme_name: Option<String>,
|
||||
theme_size: u32,
|
||||
surface: WlSurface,
|
||||
size: u32,
|
||||
shm: WlShm,
|
||||
|
@ -27,6 +28,7 @@ impl Cursor {
|
|||
Self {
|
||||
theme: CursorTheme::load(&connection, globals.shm.clone(), size).log_err(),
|
||||
theme_name: None,
|
||||
theme_size: size,
|
||||
surface: globals.compositor.create_surface(&globals.qh, ()),
|
||||
shm: globals.shm.clone(),
|
||||
connection: connection.clone(),
|
||||
|
@ -34,26 +36,26 @@ impl Cursor {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_theme(&mut self, theme_name: &str, size: Option<u32>) {
|
||||
if let Some(size) = size {
|
||||
self.size = size;
|
||||
}
|
||||
if let Some(theme) =
|
||||
CursorTheme::load_from_name(&self.connection, self.shm.clone(), theme_name, self.size)
|
||||
.log_err()
|
||||
pub fn set_theme(&mut self, theme_name: &str) {
|
||||
if let Some(theme) = CursorTheme::load_from_name(
|
||||
&self.connection,
|
||||
self.shm.clone(),
|
||||
theme_name,
|
||||
self.theme_size,
|
||||
)
|
||||
.log_err()
|
||||
{
|
||||
self.theme = Some(theme);
|
||||
self.theme_name = Some(theme_name.to_string());
|
||||
} else if let Some(theme) =
|
||||
CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err()
|
||||
CursorTheme::load(&self.connection, self.shm.clone(), self.theme_size).log_err()
|
||||
{
|
||||
self.theme = Some(theme);
|
||||
self.theme_name = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_size(&mut self, size: u32) {
|
||||
self.size = size;
|
||||
fn set_theme_size(&mut self, theme_size: u32) {
|
||||
self.theme = self
|
||||
.theme_name
|
||||
.as_ref()
|
||||
|
@ -62,14 +64,29 @@ impl Cursor {
|
|||
&self.connection,
|
||||
self.shm.clone(),
|
||||
name.as_str(),
|
||||
self.size,
|
||||
theme_size,
|
||||
)
|
||||
.log_err()
|
||||
})
|
||||
.or_else(|| CursorTheme::load(&self.connection, self.shm.clone(), self.size).log_err());
|
||||
.or_else(|| {
|
||||
CursorTheme::load(&self.connection, self.shm.clone(), theme_size).log_err()
|
||||
});
|
||||
}
|
||||
|
||||
pub fn set_icon(&mut self, wl_pointer: &WlPointer, serial_id: u32, mut cursor_icon_name: &str) {
|
||||
pub fn set_size(&mut self, size: u32) {
|
||||
self.size = size;
|
||||
self.set_theme_size(size);
|
||||
}
|
||||
|
||||
pub fn set_icon(
|
||||
&mut self,
|
||||
wl_pointer: &WlPointer,
|
||||
serial_id: u32,
|
||||
mut cursor_icon_name: &str,
|
||||
scale: i32,
|
||||
) {
|
||||
self.set_theme_size(self.size * scale as u32);
|
||||
|
||||
if let Some(theme) = &mut self.theme {
|
||||
let mut buffer: Option<&CursorImageBuffer>;
|
||||
|
||||
|
@ -91,7 +108,15 @@ impl Cursor {
|
|||
let (width, height) = buffer.dimensions();
|
||||
let (hot_x, hot_y) = buffer.hotspot();
|
||||
|
||||
wl_pointer.set_cursor(serial_id, Some(&self.surface), hot_x as i32, hot_y as i32);
|
||||
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,
|
||||
);
|
||||
|
||||
self.surface.attach(Some(&buffer), 0, 0);
|
||||
self.surface.damage(0, 0, width as i32, height as i32);
|
||||
self.surface.commit();
|
||||
|
|
|
@ -194,6 +194,23 @@ impl WaylandWindowState {
|
|||
self.decorations == WindowDecorations::Client
|
||||
|| self.background_appearance != WindowBackgroundAppearance::Opaque
|
||||
}
|
||||
|
||||
pub fn primary_output_scale(&mut self) -> i32 {
|
||||
let mut scale = 1;
|
||||
let mut current_output = self.display.take();
|
||||
for (id, output) in self.outputs.iter() {
|
||||
if let Some((_, output_data)) = ¤t_output {
|
||||
if output.scale > output_data.scale {
|
||||
current_output = Some((id.clone(), output.clone()));
|
||||
}
|
||||
} else {
|
||||
current_output = Some((id.clone(), output.clone()));
|
||||
}
|
||||
scale = scale.max(output.scale);
|
||||
}
|
||||
self.display = current_output;
|
||||
scale
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct WaylandWindow(pub WaylandWindowStatePtr);
|
||||
|
@ -560,7 +577,7 @@ impl WaylandWindowStatePtr {
|
|||
|
||||
state.outputs.insert(id, output.clone());
|
||||
|
||||
let scale = primary_output_scale(&mut state);
|
||||
let scale = state.primary_output_scale();
|
||||
|
||||
// We use `PreferredBufferScale` instead to set the scale if it's available
|
||||
if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
|
||||
|
@ -572,7 +589,7 @@ impl WaylandWindowStatePtr {
|
|||
wl_surface::Event::Leave { output } => {
|
||||
state.outputs.remove(&output.id());
|
||||
|
||||
let scale = primary_output_scale(&mut state);
|
||||
let scale = state.primary_output_scale();
|
||||
|
||||
// We use `PreferredBufferScale` instead to set the scale if it's available
|
||||
if state.surface.version() < wl_surface::EVT_PREFERRED_BUFFER_SCALE_SINCE {
|
||||
|
@ -719,6 +736,10 @@ impl WaylandWindowStatePtr {
|
|||
(fun)()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn primary_output_scale(&self) -> i32 {
|
||||
self.state.borrow_mut().primary_output_scale()
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_states<'a, S: TryFrom<u32> + 'a>(states: &'a [u8]) -> impl Iterator<Item = S> + 'a
|
||||
|
@ -732,23 +753,6 @@ where
|
|||
.flat_map(S::try_from)
|
||||
}
|
||||
|
||||
fn primary_output_scale(state: &mut RefMut<WaylandWindowState>) -> i32 {
|
||||
let mut scale = 1;
|
||||
let mut current_output = state.display.take();
|
||||
for (id, output) in state.outputs.iter() {
|
||||
if let Some((_, output_data)) = ¤t_output {
|
||||
if output.scale > output_data.scale {
|
||||
current_output = Some((id.clone(), output.clone()));
|
||||
}
|
||||
} else {
|
||||
current_output = Some((id.clone(), output.clone()));
|
||||
}
|
||||
scale = scale.max(output.scale);
|
||||
}
|
||||
state.display = current_output;
|
||||
scale
|
||||
}
|
||||
|
||||
impl rwh::HasWindowHandle for WaylandWindow {
|
||||
fn window_handle(&self) -> Result<rwh::WindowHandle<'_>, rwh::HandleError> {
|
||||
unimplemented!()
|
||||
|
|
|
@ -42,11 +42,13 @@ impl XDPEventSource {
|
|||
{
|
||||
sender.send(Event::CursorTheme(initial_theme))?;
|
||||
}
|
||||
|
||||
// If u32 is used here, it throws invalid type error
|
||||
if let Ok(initial_size) = settings
|
||||
.read::<u32>("org.gnome.desktop.interface", "cursor-size")
|
||||
.read::<i32>("org.gnome.desktop.interface", "cursor-size")
|
||||
.await
|
||||
{
|
||||
sender.send(Event::CursorSize(initial_size))?;
|
||||
sender.send(Event::CursorSize(initial_size as u32))?;
|
||||
}
|
||||
|
||||
if let Ok(mut cursor_theme_changed) = settings
|
||||
|
@ -69,7 +71,7 @@ impl XDPEventSource {
|
|||
}
|
||||
|
||||
if let Ok(mut cursor_size_changed) = settings
|
||||
.receive_setting_changed_with_args::<u32>(
|
||||
.receive_setting_changed_with_args::<i32>(
|
||||
"org.gnome.desktop.interface",
|
||||
"cursor-size",
|
||||
)
|
||||
|
@ -80,7 +82,7 @@ impl XDPEventSource {
|
|||
.spawn(async move {
|
||||
while let Some(size) = cursor_size_changed.next().await {
|
||||
let size = size?;
|
||||
sender.send(Event::CursorSize(size))?;
|
||||
sender.send(Event::CursorSize(size as u32))?;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue