feat: Allow fractional xwayland client scale
This commit is contained in:
parent
0159bce9db
commit
7472351de0
4 changed files with 160 additions and 37 deletions
|
|
@ -45,7 +45,7 @@ pub struct CosmicCompConfig {
|
||||||
/// The delay in milliseconds before focus follows mouse (if enabled)
|
/// The delay in milliseconds before focus follows mouse (if enabled)
|
||||||
pub focus_follows_cursor_delay: u64,
|
pub focus_follows_cursor_delay: u64,
|
||||||
/// Let X11 applications scale themselves
|
/// Let X11 applications scale themselves
|
||||||
pub descale_xwayland: bool,
|
pub descale_xwayland: XwaylandDescaling,
|
||||||
/// Let X11 applications snoop on certain key-presses to allow for global shortcuts
|
/// Let X11 applications snoop on certain key-presses to allow for global shortcuts
|
||||||
pub xwayland_eavesdropping: XwaylandEavesdropping,
|
pub xwayland_eavesdropping: XwaylandEavesdropping,
|
||||||
/// The threshold before windows snap themselves to output edges
|
/// The threshold before windows snap themselves to output edges
|
||||||
|
|
@ -80,7 +80,7 @@ impl Default for CosmicCompConfig {
|
||||||
focus_follows_cursor: false,
|
focus_follows_cursor: false,
|
||||||
cursor_follows_focus: false,
|
cursor_follows_focus: false,
|
||||||
focus_follows_cursor_delay: 250,
|
focus_follows_cursor_delay: 250,
|
||||||
descale_xwayland: false,
|
descale_xwayland: XwaylandDescaling::Fractional,
|
||||||
xwayland_eavesdropping: XwaylandEavesdropping::default(),
|
xwayland_eavesdropping: XwaylandEavesdropping::default(),
|
||||||
edge_snap_threshold: 0,
|
edge_snap_threshold: 0,
|
||||||
accessibility_zoom: ZoomConfig::default(),
|
accessibility_zoom: ZoomConfig::default(),
|
||||||
|
|
@ -172,3 +172,14 @@ pub enum EavesdroppingKeyboardMode {
|
||||||
Combinations,
|
Combinations,
|
||||||
All,
|
All,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum XwaylandDescaling {
|
||||||
|
#[serde(rename = "true")]
|
||||||
|
Enabled,
|
||||||
|
#[serde(rename = "false")]
|
||||||
|
Disabled,
|
||||||
|
#[default]
|
||||||
|
Fractional,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ pub use self::types::*;
|
||||||
use cosmic::config::CosmicTk;
|
use cosmic::config::CosmicTk;
|
||||||
use cosmic_comp_config::{
|
use cosmic_comp_config::{
|
||||||
input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior,
|
input::InputConfig, workspace::WorkspaceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior,
|
||||||
XkbConfig, XwaylandEavesdropping, ZoomConfig,
|
XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -917,7 +917,7 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"descale_xwayland" => {
|
"descale_xwayland" => {
|
||||||
let new = get_config::<bool>(&config, "descale_xwayland");
|
let new = get_config::<XwaylandDescaling>(&config, "descale_xwayland");
|
||||||
if new != state.common.config.cosmic_conf.descale_xwayland {
|
if new != state.common.config.cosmic_conf.descale_xwayland {
|
||||||
state.common.config.cosmic_conf.descale_xwayland = new;
|
state.common.config.cosmic_conf.descale_xwayland = new;
|
||||||
state.common.update_xwayland_scale();
|
state.common.update_xwayland_scale();
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ pub struct Common {
|
||||||
pub xdg_activation_state: XdgActivationState,
|
pub xdg_activation_state: XdgActivationState,
|
||||||
pub xdg_foreign_state: XdgForeignState,
|
pub xdg_foreign_state: XdgForeignState,
|
||||||
pub workspace_state: WorkspaceState<State>,
|
pub workspace_state: WorkspaceState<State>,
|
||||||
pub xwayland_scale: Option<i32>,
|
pub xwayland_scale: Option<f64>,
|
||||||
pub xwayland_state: Option<XWaylandState>,
|
pub xwayland_state: Option<XWaylandState>,
|
||||||
pub xwayland_shell_state: XWaylandShellState,
|
pub xwayland_shell_state: XWaylandShellState,
|
||||||
pub pointer_focus_state: Option<PointerFocusState>,
|
pub pointer_focus_state: Option<PointerFocusState>,
|
||||||
|
|
|
||||||
166
src/xwayland.rs
166
src/xwayland.rs
|
|
@ -11,16 +11,28 @@ use crate::{
|
||||||
toplevel_management::minimize_rectangle, xdg_activation::ActivationContext,
|
toplevel_management::minimize_rectangle, xdg_activation::ActivationContext,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use cosmic_comp_config::EavesdroppingKeyboardMode;
|
use cosmic_comp_config::{EavesdroppingKeyboardMode, XwaylandDescaling};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
allocator::Fourcc,
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
input::{ButtonState, KeyState, Keycode},
|
input::{ButtonState, KeyState, Keycode},
|
||||||
|
renderer::{
|
||||||
|
element::{
|
||||||
|
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
|
||||||
|
Kind,
|
||||||
|
},
|
||||||
|
pixman::{PixmanError, PixmanRenderer},
|
||||||
|
utils::draw_render_elements,
|
||||||
|
Bind, Frame, Offscreen, Renderer,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
desktop::space::SpaceElement,
|
desktop::space::SpaceElement,
|
||||||
input::{keyboard::ModifiersState, pointer::CursorIcon},
|
input::{keyboard::ModifiersState, pointer::CursorIcon},
|
||||||
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
|
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
|
||||||
utils::{Logical, Point, Rectangle, Serial, Size, SERIAL_COUNTER},
|
utils::{
|
||||||
|
Buffer as BufferCoords, Logical, Point, Rectangle, Serial, Size, Transform, SERIAL_COUNTER,
|
||||||
|
},
|
||||||
wayland::{
|
wayland::{
|
||||||
selection::{
|
selection::{
|
||||||
data_device::{
|
data_device::{
|
||||||
|
|
@ -41,6 +53,7 @@ use smithay::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tracing::{error, trace, warn};
|
use tracing::{error, trace, warn};
|
||||||
|
use xcursor::parser::Image;
|
||||||
use xkbcommon::xkb::Keysym;
|
use xkbcommon::xkb::Keysym;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -97,7 +110,7 @@ impl State {
|
||||||
last_modifier_state: None,
|
last_modifier_state: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut wm = match X11Wm::start_wm(
|
let wm = match X11Wm::start_wm(
|
||||||
data.common.event_loop_handle.clone(),
|
data.common.event_loop_handle.clone(),
|
||||||
x11_socket,
|
x11_socket,
|
||||||
client.clone(),
|
client.clone(),
|
||||||
|
|
@ -109,23 +122,9 @@ impl State {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (theme, size) = load_cursor_theme();
|
|
||||||
let cursor = Cursor::load(&theme, CursorIcon::Default, size);
|
|
||||||
let image = cursor.get_image(1, 0);
|
|
||||||
if let Err(err) = wm.set_cursor(
|
|
||||||
&image.pixels_rgba,
|
|
||||||
Size::from((image.width as u16, image.height as u16)),
|
|
||||||
Point::from((image.xhot as u16, image.yhot as u16)),
|
|
||||||
) {
|
|
||||||
warn!(
|
|
||||||
id = ?wm.id(),
|
|
||||||
?err,
|
|
||||||
"Failed to set default cursor for Xwayland WM",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let xwayland_state = data.common.xwayland_state.as_mut().unwrap();
|
let xwayland_state = data.common.xwayland_state.as_mut().unwrap();
|
||||||
xwayland_state.xwm = Some(wm);
|
xwayland_state.xwm = Some(wm);
|
||||||
|
xwayland_state.reload_cursor(1.);
|
||||||
data.notify_ready();
|
data.notify_ready();
|
||||||
|
|
||||||
data.common.update_xwayland_scale();
|
data.common.update_xwayland_scale();
|
||||||
|
|
@ -147,6 +146,102 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn scale_cursor(
|
||||||
|
scale: f64,
|
||||||
|
cursor_size: u32,
|
||||||
|
image: &Image,
|
||||||
|
) -> Result<(Vec<u8>, Size<u16, Logical>, Point<u16, Logical>), PixmanError> {
|
||||||
|
let mut renderer = PixmanRenderer::new()?;
|
||||||
|
let image_scale = (image.size / cursor_size).max(1);
|
||||||
|
let pixel_size = (cursor_size as f64 * scale).round() as i32;
|
||||||
|
let buffer_size = Size::<i32, BufferCoords>::from((pixel_size, pixel_size));
|
||||||
|
let output_size = buffer_size.to_logical(1, Transform::Normal).to_physical(1);
|
||||||
|
|
||||||
|
let image_buffer = MemoryRenderBuffer::from_slice(
|
||||||
|
&image.pixels_rgba,
|
||||||
|
Fourcc::Abgr8888,
|
||||||
|
(image.width as i32, image.height as i32),
|
||||||
|
image_scale as i32,
|
||||||
|
Transform::Normal,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let element = MemoryRenderBufferRenderElement::from_buffer(
|
||||||
|
&mut renderer,
|
||||||
|
(0., 0.),
|
||||||
|
&image_buffer,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
Kind::Unspecified,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut buffer = renderer.create_buffer(Fourcc::Abgr8888, buffer_size)?;
|
||||||
|
let mut fb = renderer.bind(&mut buffer)?;
|
||||||
|
let mut frame = renderer.render(&mut fb, output_size, Transform::Normal)?;
|
||||||
|
draw_render_elements(
|
||||||
|
&mut frame,
|
||||||
|
scale,
|
||||||
|
&[element],
|
||||||
|
&[Rectangle::new((0, 0).into(), output_size)],
|
||||||
|
)?;
|
||||||
|
let sync = frame.finish()?;
|
||||||
|
while let Err(_) = sync.wait() {}
|
||||||
|
|
||||||
|
let len = (buffer_size.w * buffer_size.h * 4) as usize;
|
||||||
|
let mut data = Vec::with_capacity(len);
|
||||||
|
assert_eq!(buffer.stride(), (buffer_size.w * 4) as usize);
|
||||||
|
data.extend_from_slice(unsafe { std::slice::from_raw_parts(buffer.data() as *mut u8, len) });
|
||||||
|
|
||||||
|
let hotspot = Point::<i32, BufferCoords>::from((image.xhot as i32, image.yhot as i32))
|
||||||
|
.to_f64()
|
||||||
|
.to_logical(
|
||||||
|
image_scale as f64,
|
||||||
|
Transform::Normal,
|
||||||
|
&Size::from((image.width as f64, image.height as f64)),
|
||||||
|
)
|
||||||
|
.to_buffer(
|
||||||
|
scale,
|
||||||
|
Transform::Normal,
|
||||||
|
&output_size.to_logical(1).to_f64(),
|
||||||
|
)
|
||||||
|
.to_i32_round::<i32>();
|
||||||
|
Ok((
|
||||||
|
data,
|
||||||
|
Size::from((buffer_size.w as u16, buffer_size.h as u16)),
|
||||||
|
Point::from((hotspot.x as u16, hotspot.y as u16)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl XWaylandState {
|
||||||
|
pub fn reload_cursor(&mut self, scale: f64) {
|
||||||
|
if let Some(wm) = self.xwm.as_mut() {
|
||||||
|
let (theme, size) = load_cursor_theme();
|
||||||
|
let cursor = Cursor::load(&theme, CursorIcon::Default, size);
|
||||||
|
let image = cursor.get_image(scale.ceil() as u32, 0);
|
||||||
|
|
||||||
|
let (pixels_rgba, size, hotspot) = match scale_cursor(scale, size, &image) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Failed to scale Xwayland cursor image: {}", err);
|
||||||
|
(
|
||||||
|
image.pixels_rgba,
|
||||||
|
Size::from((image.width as u16, image.height as u16)),
|
||||||
|
Point::from((image.xhot as u16, image.yhot as u16)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = wm.set_cursor(&pixels_rgba, size, hotspot) {
|
||||||
|
warn!(
|
||||||
|
id = ?wm.id(),
|
||||||
|
?err,
|
||||||
|
"Failed to set default cursor for Xwayland WM",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Common {
|
impl Common {
|
||||||
fn has_x_keyboard_focus(&self, xwmid: XwmId) -> bool {
|
fn has_x_keyboard_focus(&self, xwmid: XwmId) -> bool {
|
||||||
let keyboard = self
|
let keyboard = self
|
||||||
|
|
@ -461,15 +556,23 @@ impl Common {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_xwayland_scale(&mut self) {
|
pub fn update_xwayland_scale(&mut self) {
|
||||||
let new_scale = if self.config.cosmic_conf.descale_xwayland {
|
let new_scale = match self.config.cosmic_conf.descale_xwayland {
|
||||||
|
XwaylandDescaling::Disabled => 1.,
|
||||||
|
XwaylandDescaling::Enabled => {
|
||||||
let shell = self.shell.read().unwrap();
|
let shell = self.shell.read().unwrap();
|
||||||
shell
|
shell
|
||||||
.outputs()
|
.outputs()
|
||||||
.map(|o| o.current_scale().integer_scale())
|
.map(|o| o.current_scale().integer_scale())
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(1)
|
.unwrap_or(1) as f64
|
||||||
} else {
|
}
|
||||||
1
|
XwaylandDescaling::Fractional => {
|
||||||
|
let shell = self.shell.read().unwrap();
|
||||||
|
shell
|
||||||
|
.outputs()
|
||||||
|
.map(|o| o.current_scale().fractional_scale())
|
||||||
|
.fold(1f64, |acc, val| acc.max(val))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// compare with current scale
|
// compare with current scale
|
||||||
|
|
@ -487,12 +590,18 @@ impl Common {
|
||||||
|
|
||||||
// update xorg dpi
|
// update xorg dpi
|
||||||
if let Some(xwm) = xwayland.xwm.as_mut() {
|
if let Some(xwm) = xwayland.xwm.as_mut() {
|
||||||
let dpi = new_scale.abs() * 96 * 1024;
|
let dpi = new_scale * 96. * 1024.;
|
||||||
if let Err(err) = xwm.set_xsettings(
|
if let Err(err) = xwm.set_xsettings(
|
||||||
[
|
[
|
||||||
("Xft/DPI".into(), dpi.into()),
|
("Xft/DPI".into(), (dpi.round() as i32).into()),
|
||||||
("Gdk/UnscaledDPI".into(), (dpi / new_scale).into()),
|
(
|
||||||
("Gdk/WindowScalingFactor".into(), new_scale.into()),
|
"Gdk/UnscaledDPI".into(),
|
||||||
|
((dpi / new_scale).round() as i32).into(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Gdk/WindowScalingFactor".into(),
|
||||||
|
(new_scale.round() as i32).into(),
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.into_iter(),
|
.into_iter(),
|
||||||
) {
|
) {
|
||||||
|
|
@ -500,13 +609,16 @@ impl Common {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// update cursor
|
||||||
|
xwayland.reload_cursor(new_scale);
|
||||||
|
|
||||||
// update client scale
|
// update client scale
|
||||||
xwayland
|
xwayland
|
||||||
.client
|
.client
|
||||||
.get_data::<XWaylandClientData>()
|
.get_data::<XWaylandClientData>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.compositor_state
|
.compositor_state
|
||||||
.set_client_scale(new_scale as f64);
|
.set_client_scale(new_scale);
|
||||||
|
|
||||||
// update wl/xdg_outputs
|
// update wl/xdg_outputs
|
||||||
for output in self.shell.read().unwrap().outputs() {
|
for output in self.shell.read().unwrap().outputs() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue