Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Optimize viewport panning and grid rendering performance
src/server/config.rs | 214 ++++++++++++++++++++-----------
src/server/cursor.rs | 36 ++++--
src/server/libinput_device.rs | 12 +-
src/server/output.rs | 264 +++++++++++++++++++++++++++++----------
src/server/window_manager.rs | 100 +++++++++++++--
src/server/wlroots_log_wrapper.c | 4 +
wrapper.h | 2 +
7 files changed, 465 insertions(+), 167 deletions(-)
diff --git a/src/server/config.rs b/src/server/config.rs
index 858b84b..27e9977 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -39,12 +39,14 @@ pub struct Layout {
pub overlay_border_gap: i32,
pub status_normal_color: String,
pub status_background_blur: f32,
- pub desktop_background_color: String,
+ pub desktop_gap_color: String,
pub transparency_opacity: f32,
pub window_opacity: bool,
- pub desktop_grid_color: [f32; 4],
+ pub desktop_cell_color: [f32; 4],
pub desktop_grid_scale: f64,
- pub desktop_line_width: i32,
+ pub desktop_gap_width: i32,
+ pub desktop_cell_corner_radius: i32,
+ pub desktop_cell_fade_inset: i64,
pub scenefx_optimized_blur: bool,
pub status_backdrop_blur_ignore_transparent: bool,
pub window_backdrop_blur_ignore_transparent: bool,
@@ -85,12 +87,14 @@ impl Default for Layout {
overlay_border_gap: 0,
status_normal_color: "#ccccd8".to_string(),
status_background_blur: 0.8,
- desktop_background_color: "#000000".to_string(),
+ desktop_gap_color: "#000000".to_string(),
transparency_opacity: 0.9,
window_opacity: true,
- desktop_grid_color: [1.0, 1.0, 1.0, 0.05],
+ desktop_cell_color: [1.0, 1.0, 1.0, 0.05],
desktop_grid_scale: 100.0,
- desktop_line_width: 1,
+ desktop_gap_width: 1,
+ desktop_cell_corner_radius: 0,
+ desktop_cell_fade_inset: 0,
scenefx_optimized_blur: true,
status_backdrop_blur_ignore_transparent: true,
window_backdrop_blur_ignore_transparent: true,
@@ -235,19 +239,29 @@ pub struct GesturesConfig {
pub pinch: Option<bool>,
}
-#[derive(Debug, Deserialize, Clone, Default)]
-pub struct InputConfig {
+#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
+pub struct TouchpadConfig {
pub tap_to_click: Option<bool>,
- pub accel_speed: Option<f64>,
- pub accel_profile: Option<String>,
pub natural_scroll: Option<bool>,
pub dwt: Option<bool>,
pub dwtp: Option<bool>,
- pub trackpoint_accel_speed: Option<f64>,
- pub trackpoint_accel_profile: Option<String>,
pub gestures: Option<GesturesConfig>,
}
+#[derive(Debug, Deserialize, Clone, Default, PartialEq)]
+pub struct TrackpointConfig {
+ pub accel_speed: Option<f64>,
+ pub accel_profile: Option<String>,
+}
+
+#[derive(Debug, Deserialize, Clone, Default)]
+pub struct InputConfig {
+ pub accel_speed: Option<f64>,
+ pub accel_profile: Option<String>,
+ pub touchpad: Option<TouchpadConfig>,
+ pub trackpoint: Option<TrackpointConfig>,
+}
+
#[derive(Debug, Deserialize, Clone, Default)]
pub struct TransparencyConfig {
pub opacity: Option<f64>,
@@ -255,14 +269,18 @@ pub struct TransparencyConfig {
#[derive(Debug, Deserialize, Clone)]
pub struct SurfacesConfig {
- #[serde(default = "default_desktop_background_color")]
- pub desktop_background_color: String,
- #[serde(default = "default_desktop_grid_color")]
- pub desktop_grid_color: String,
+ #[serde(default = "default_desktop_gap_color")]
+ pub desktop_gap_color: String,
+ #[serde(default = "default_desktop_cell_color")]
+ pub desktop_cell_color: String,
#[serde(default = "default_desktop_grid_scale")]
pub desktop_grid_scale: i64,
- #[serde(default = "default_desktop_line_width")]
- pub desktop_line_width: i64,
+ #[serde(default = "default_desktop_gap_width")]
+ pub desktop_gap_width: i64,
+ #[serde(default = "default_desktop_cell_corner_radius")]
+ pub desktop_cell_corner_radius: i64,
+ #[serde(default = "default_desktop_cell_fade_inset")]
+ pub desktop_cell_fade_inset: i64,
#[serde(default = "default_backplate_color")]
pub backplate_color: String,
#[serde(default = "default_backplate_blur")]
@@ -274,10 +292,12 @@ pub struct SurfacesConfig {
impl Default for SurfacesConfig {
fn default() -> Self {
Self {
- desktop_background_color: default_desktop_background_color(),
- desktop_grid_color: default_desktop_grid_color(),
+ desktop_gap_color: default_desktop_gap_color(),
+ desktop_cell_color: default_desktop_cell_color(),
desktop_grid_scale: default_desktop_grid_scale(),
- desktop_line_width: default_desktop_line_width(),
+ desktop_gap_width: default_desktop_gap_width(),
+ desktop_cell_corner_radius: default_desktop_cell_corner_radius(),
+ desktop_cell_fade_inset: default_desktop_cell_fade_inset(),
backplate_color: default_backplate_color(),
backplate_blur: default_backplate_blur(),
backplate_corner_radius: default_backplate_corner_radius(),
@@ -285,11 +305,11 @@ impl Default for SurfacesConfig {
}
}
-fn default_desktop_background_color() -> String {
+fn default_desktop_gap_color() -> String {
"#000000".to_string()
}
-fn default_desktop_grid_color() -> String {
+fn default_desktop_cell_color() -> String {
"#ffffff0d".to_string()
}
@@ -297,10 +317,18 @@ fn default_desktop_grid_scale() -> i64 {
100
}
-fn default_desktop_line_width() -> i64 {
+fn default_desktop_gap_width() -> i64 {
1
}
+fn default_desktop_cell_corner_radius() -> i64 {
+ 0
+}
+
+fn default_desktop_cell_fade_inset() -> i64 {
+ 0
+}
+
fn default_backplate_color() -> String {
"#151520e6".to_string()
}
@@ -320,8 +348,8 @@ pub struct Config {
pub layout: LayoutConfig,
#[serde(default)]
pub env: HashMap<String, String>,
- #[serde(default)]
- pub keybind: Vec<KeybindConfig>,
+ #[serde(default, rename = "key_bindings")]
+ pub key_bindings: Vec<KeybindConfig>,
#[serde(default)]
pub pointer_bind: Vec<PointerBindConfig>,
#[serde(default)]
@@ -1003,7 +1031,7 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
}
// 3. lists
- let mut keybind = Vec::new();
+ let mut key_bindings = Vec::new();
let mut pointer_bind = Vec::new();
let mut gesture_bind = Vec::new();
let mut mode_rule = Vec::new();
@@ -1013,12 +1041,12 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
for node in doc.nodes() {
match node.name().value() {
- "keybind" => {
+ "key_bindings" => {
let mods = get_prop_string(node, "mods", "");
let key = get_prop_string(node, "key", "");
let action = get_prop_string(node, "action", "");
let command = get_prop_string_opt(node, "command");
- keybind.push(KeybindConfig { mods, key, action, command });
+ key_bindings.push(KeybindConfig { mods, key, action, command });
}
"pointer_bind" => {
let mods = get_prop_string(node, "mods", "");
@@ -1067,20 +1095,20 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
// 4. output
let mut output = None;
+ let mut display = HashMap::new();
if let Some(node) = doc.nodes().iter().find(|n| n.name().value() == "output") {
let scale = get_child_arg_f64(node, "scale", 1.0);
let scenefx_optimized_blur = get_child_arg_bool(node, "scenefx_optimized_blur", true);
output = Some(OutputConfig { scale, scenefx_optimized_blur });
- }
- // 5. display
- let mut display = HashMap::new();
- if let Some(node) = doc.nodes().iter().find(|n| n.name().value() == "display") {
if let Some(children) = node.children() {
for child in children.nodes() {
- if let Some(entry) = child.entries().first() {
- if let Some(num) = entry.value().as_f64() {
- display.insert(child.name().value().to_string(), num);
+ let name = child.name().value();
+ if name.starts_with("scale_") {
+ if let Some(entry) = child.entries().first() {
+ if let Some(num) = entry.value().as_f64() {
+ display.insert(name.to_string(), num);
+ }
}
}
}
@@ -1090,34 +1118,53 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
// 6. input
let mut input = None;
if let Some(node) = doc.nodes().iter().find(|n| n.name().value() == "input") {
- let tap_to_click = get_child_arg_bool_opt(node, "tap_to_click");
let accel_speed = get_child_arg_f64_opt(node, "accel_speed");
let accel_profile = get_child_arg_string_opt(node, "accel_profile");
- let natural_scroll = get_child_arg_bool_opt(node, "natural_scroll");
- let dwt = get_child_arg_bool_opt(node, "dwt");
- let dwtp = get_child_arg_bool_opt(node, "dwtp");
- let trackpoint_accel_speed = get_child_arg_f64_opt(node, "trackpoint_accel_speed");
- let trackpoint_accel_profile = get_child_arg_string_opt(node, "trackpoint_accel_profile");
-
- let mut gestures = None;
+
+ let mut touchpad = None;
if let Some(children) = node.children() {
- if let Some(gestures_node) = children.nodes().iter().find(|n| n.name().value() == "gestures") {
- let swipe = get_child_arg_bool_opt(gestures_node, "swipe");
- let pinch = get_child_arg_bool_opt(gestures_node, "pinch");
- gestures = Some(GesturesConfig { swipe, pinch });
+ if let Some(tp_node) = children.nodes().iter().find(|n| n.name().value() == "touchpad") {
+ let tap_to_click = get_child_arg_bool_opt(tp_node, "tap_to_click");
+ let natural_scroll = get_child_arg_bool_opt(tp_node, "natural_scroll");
+ let dwt = get_child_arg_bool_opt(tp_node, "dwt");
+ let dwtp = get_child_arg_bool_opt(tp_node, "dwtp");
+
+ let mut gestures = None;
+ if let Some(tp_children) = tp_node.children() {
+ if let Some(gestures_node) = tp_children.nodes().iter().find(|n| n.name().value() == "gestures") {
+ let swipe = get_child_arg_bool_opt(gestures_node, "swipe");
+ let pinch = get_child_arg_bool_opt(gestures_node, "pinch");
+ gestures = Some(GesturesConfig { swipe, pinch });
+ }
+ }
+
+ touchpad = Some(TouchpadConfig {
+ tap_to_click,
+ natural_scroll,
+ dwt,
+ dwtp,
+ gestures,
+ });
}
}
-
+
+ let mut trackpoint = None;
+ if let Some(children) = node.children() {
+ if let Some(tp_node) = children.nodes().iter().find(|n| n.name().value() == "trackpoint") {
+ let accel_speed = get_child_arg_f64_opt(tp_node, "accel_speed");
+ let accel_profile = get_child_arg_string_opt(tp_node, "accel_profile");
+ trackpoint = Some(TrackpointConfig {
+ accel_speed,
+ accel_profile,
+ });
+ }
+ }
+
input = Some(InputConfig {
- tap_to_click,
accel_speed,
accel_profile,
- natural_scroll,
- dwt,
- dwtp,
- trackpoint_accel_speed,
- trackpoint_accel_profile,
- gestures,
+ touchpad,
+ trackpoint,
});
}
@@ -1140,19 +1187,29 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
for entry in desktop_node.entries() {
if let Some(id) = entry.name() {
match id.value() {
- "background_color" => {
+ "gap_color" => {
if let Some(val) = entry.value().as_string() {
- surfaces.desktop_background_color = val.to_string();
+ surfaces.desktop_gap_color = val.to_string();
}
}
- "grid_color" => {
+ "cell_color" => {
if let Some(val) = entry.value().as_string() {
- surfaces.desktop_grid_color = val.to_string();
+ surfaces.desktop_cell_color = val.to_string();
+ }
+ }
+ "gap_width" => {
+ if let Some(val) = entry.value().as_i64() {
+ surfaces.desktop_gap_width = val;
}
}
- "line_width" => {
+ "cell_corner_radius" => {
if let Some(val) = entry.value().as_i64() {
- surfaces.desktop_line_width = val;
+ surfaces.desktop_cell_corner_radius = val;
+ }
+ }
+ "cell_fade_inset" => {
+ if let Some(val) = entry.value().as_i64() {
+ surfaces.desktop_cell_fade_inset = val;
}
}
_ => {}
@@ -1191,10 +1248,12 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
}
if !found_nested {
if let Some(node) = doc.nodes().iter().find(|n| n.name().value() == "surfaces") {
- surfaces.desktop_background_color = get_child_arg_string(node, "desktop_background_color", &default_desktop_background_color());
- surfaces.desktop_grid_color = get_child_arg_string(node, "desktop_grid_color", &default_desktop_grid_color());
+ surfaces.desktop_gap_color = get_child_arg_string(node, "desktop_gap_color", &default_desktop_gap_color());
+ surfaces.desktop_cell_color = get_child_arg_string(node, "desktop_cell_color", &default_desktop_cell_color());
surfaces.desktop_grid_scale = get_child_arg_i64(node, "desktop_grid_scale", default_desktop_grid_scale());
- surfaces.desktop_line_width = get_child_arg_i64(node, "desktop_line_width", default_desktop_line_width());
+ surfaces.desktop_gap_width = get_child_arg_i64(node, "desktop_gap_width", default_desktop_gap_width());
+ surfaces.desktop_cell_corner_radius = get_child_arg_i64(node, "desktop_cell_corner_radius", default_desktop_cell_corner_radius());
+ surfaces.desktop_cell_fade_inset = get_child_arg_i64(node, "desktop_cell_fade_inset", default_desktop_cell_fade_inset());
surfaces.backplate_color = get_child_arg_string(node, "backplate_color", &default_backplate_color());
surfaces.backplate_blur = get_child_arg_f64(node, "backplate_blur", default_backplate_blur());
surfaces.backplate_corner_radius = get_child_arg_i64(node, "backplate_corner_radius", default_backplate_corner_radius());
@@ -1204,7 +1263,7 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
Ok(Config {
layout,
env,
- keybind,
+ key_bindings,
pointer_bind,
mode_rule,
tag_layout,
@@ -1248,8 +1307,8 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
state.layout.border_b = 0x3E3E3E3E;
state.layout.border_a = 0xFFFFFFFF;
- let desktop_background_str = config.surfaces.desktop_background_color.clone();
- state.layout.desktop_background_color = desktop_background_str.clone();
+ let desktop_background_str = config.surfaces.desktop_gap_color.clone();
+ state.layout.desktop_gap_color = desktop_background_str.clone();
let background_color_val = parse_hex_color(&desktop_background_str);
state.layout.background_r = ((background_color_val >> 16) & 0xFF) * 0x01010101;
@@ -1257,9 +1316,11 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
state.layout.background_b = (background_color_val & 0xFF) * 0x01010101;
state.layout.background_a = 0xFFFFFFFF;
- state.layout.desktop_grid_color = parse_hex_color_rgba(&config.surfaces.desktop_grid_color);
+ state.layout.desktop_cell_color = parse_hex_color_rgba(&config.surfaces.desktop_cell_color);
state.layout.desktop_grid_scale = config.surfaces.desktop_grid_scale as f64;
- state.layout.desktop_line_width = config.surfaces.desktop_line_width as i32;
+ state.layout.desktop_gap_width = config.surfaces.desktop_gap_width as i32;
+ state.layout.desktop_cell_corner_radius = config.surfaces.desktop_cell_corner_radius as i32;
+ state.layout.desktop_cell_fade_inset = config.surfaces.desktop_cell_fade_inset;
state.layout.border_font_size = 11;
state.layout.transition_duration = config.layout.transition_duration as i32;
@@ -1293,7 +1354,7 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
}
state.keybinds.clear();
- for kb in &config.keybind {
+ for kb in &config.key_bindings {
let mods = parse_modifiers(&kb.mods);
let keysym = parse_keysym(&kb.key);
let action = parse_action(&kb.action);
@@ -1491,15 +1552,24 @@ mod tests {
if let Some(path) = default_config_path() {
let mut server = crate::server::Server::default();
parse_config(&path, &mut server.wm).unwrap();
+ assert_eq!(server.wm.layout.desktop_gap_color, "#a5cfc2");
+ assert_eq!(server.wm.layout.desktop_gap_width, 24);
+ assert_eq!(server.wm.layout.desktop_cell_corner_radius, 8);
println!("TEST_WM_STARTUP: {:?}", server.wm.startup);
println!("TEST_WM_PATH: {:?}", std::env::var("PATH"));
}
}
+ #[test]
+ fn test_parse_rgba() {
+ let color = parse_hex_color_rgba("#a5cfc2");
+ assert_eq!(color, [165.0/255.0, 207.0/255.0, 194.0/255.0, 1.0]);
+ }
+
#[test]
fn test_kdl_display_scale_parsing() {
let content = r#"
- display {
+ output {
scale_eDP-1 (f64)2.0
scale_DP-1 (f64)1.5
}
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 3e8dfef..95896b1 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -1055,7 +1055,11 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
}
(*server).wm.stop_panning_animation();
- (*server).wm.dirty_windowing();
+ if matches!((*server).wm.state, crate::window_manager::WindowManagerState::Idle) {
+ (*server).wm.update_viewport_local();
+ } else {
+ (*server).wm.dirty_windowing();
+ }
}
}
}
@@ -1152,7 +1156,11 @@ unsafe extern "C" fn handle_axis(listener: *mut ffi::wl_listener, data: *mut std
wm.desk_pan_y += (cy - phys_y) * (1.0 / old_zoom - 1.0 / new_zoom);
wm.desk_zoom = new_zoom;
wm.mode = if (new_zoom - 1.0).abs() > 0.001 { crate::window_manager::WindowManagerMode::Overview } else { crate::window_manager::WindowManagerMode::Normal };
- wm.dirty_windowing();
+ if matches!(wm.state, crate::window_manager::WindowManagerState::Idle) {
+ wm.update_viewport_local();
+ } else {
+ wm.dirty_windowing();
+ }
}
}
}
@@ -1202,7 +1210,11 @@ unsafe extern "C" fn handle_axis(listener: *mut ffi::wl_listener, data: *mut std
}
_ => {}
}
- wm.dirty_windowing();
+ if matches!(wm.state, crate::window_manager::WindowManagerState::Idle) {
+ wm.update_viewport_local();
+ } else {
+ wm.dirty_windowing();
+ }
return;
}
@@ -1535,7 +1547,7 @@ unsafe extern "C" fn handle_swipe_begin(listener: *mut ffi::wl_listener, data: *
let seat = &mut *cursor.seat;
let wm = &(*seat.server).wm;
- let swipe_enabled = wm.input_config.gestures.as_ref().and_then(|g| g.swipe).unwrap_or(true);
+ let swipe_enabled = wm.input_config.touchpad.as_ref().and_then(|t| t.gestures.as_ref()).and_then(|g| g.swipe).unwrap_or(true);
if !swipe_enabled {
return;
}
@@ -1565,7 +1577,7 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
let seat = &mut *cursor.seat;
let wm = &(*seat.server).wm;
- let swipe_enabled = wm.input_config.gestures.as_ref().and_then(|g| g.swipe).unwrap_or(true);
+ let swipe_enabled = wm.input_config.touchpad.as_ref().and_then(|t| t.gestures.as_ref()).and_then(|g| g.swipe).unwrap_or(true);
if !swipe_enabled {
return;
}
@@ -1595,7 +1607,11 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
wm.desk_pan_y += (cy - phys_y) * (1.0 / old_zoom - 1.0 / new_zoom);
wm.desk_zoom = new_zoom;
wm.mode = if (new_zoom - 1.0).abs() > 0.001 { crate::window_manager::WindowManagerMode::Overview } else { crate::window_manager::WindowManagerMode::Normal };
- wm.dirty_windowing();
+ if matches!(wm.state, crate::window_manager::WindowManagerState::Idle) {
+ wm.update_viewport_local();
+ } else {
+ wm.dirty_windowing();
+ }
}
let server = seat.server;
@@ -1709,7 +1725,7 @@ unsafe extern "C" fn handle_swipe_end(listener: *mut ffi::wl_listener, data: *mu
let seat = &mut *cursor.seat;
let wm = &(*seat.server).wm;
- let swipe_enabled = wm.input_config.gestures.as_ref().and_then(|g| g.swipe).unwrap_or(true);
+ let swipe_enabled = wm.input_config.touchpad.as_ref().and_then(|t| t.gestures.as_ref()).and_then(|g| g.swipe).unwrap_or(true);
if !swipe_enabled {
return;
}
@@ -1740,7 +1756,7 @@ unsafe extern "C" fn handle_pinch_begin(listener: *mut ffi::wl_listener, data: *
let seat = &mut *cursor.seat;
let wm = &(*seat.server).wm;
- let pinch_enabled = wm.input_config.gestures.as_ref().and_then(|g| g.pinch).unwrap_or(true);
+ let pinch_enabled = wm.input_config.touchpad.as_ref().and_then(|t| t.gestures.as_ref()).and_then(|g| g.pinch).unwrap_or(true);
if !pinch_enabled {
return;
}
@@ -1767,7 +1783,7 @@ unsafe extern "C" fn handle_pinch_update(listener: *mut ffi::wl_listener, data:
let seat = &mut *cursor.seat;
let wm = &(*seat.server).wm;
- let pinch_enabled = wm.input_config.gestures.as_ref().and_then(|g| g.pinch).unwrap_or(true);
+ let pinch_enabled = wm.input_config.touchpad.as_ref().and_then(|t| t.gestures.as_ref()).and_then(|g| g.pinch).unwrap_or(true);
if !pinch_enabled {
return;
}
@@ -1841,7 +1857,7 @@ unsafe extern "C" fn handle_pinch_end(listener: *mut ffi::wl_listener, data: *mu
let seat = &mut *cursor.seat;
let wm = &(*seat.server).wm;
- let pinch_enabled = wm.input_config.gestures.as_ref().and_then(|g| g.pinch).unwrap_or(true);
+ let pinch_enabled = wm.input_config.touchpad.as_ref().and_then(|t| t.gestures.as_ref()).and_then(|g| g.pinch).unwrap_or(true);
if !pinch_enabled {
return;
}
diff --git a/src/server/libinput_device.rs b/src/server/libinput_device.rs
index d0e0b0d..ca55f3a 100644
--- a/src/server/libinput_device.rs
+++ b/src/server/libinput_device.rs
@@ -56,7 +56,7 @@ impl LibinputDevice {
}
// Tap to click
- if let Some(tap) = config.tap_to_click {
+ if let Some(tap) = config.touchpad.as_ref().and_then(|t| t.tap_to_click) {
if ffi::libinput_device_config_tap_get_finger_count(handle) > 0 {
let state = if tap { 1 } else { 0 };
ffi::libinput_device_config_tap_set_enabled(handle, state);
@@ -64,7 +64,7 @@ impl LibinputDevice {
}
// Natural scroll
- if let Some(natural) = config.natural_scroll {
+ if let Some(natural) = config.touchpad.as_ref().and_then(|t| t.natural_scroll) {
if ffi::libinput_device_config_scroll_has_natural_scroll(handle) != 0 {
let state = if natural { 1 } else { 0 };
ffi::libinput_device_config_scroll_set_natural_scroll_enabled(handle, state);
@@ -72,7 +72,7 @@ impl LibinputDevice {
}
// Dwt (Disable while typing)
- if let Some(dwt) = config.dwt {
+ if let Some(dwt) = config.touchpad.as_ref().and_then(|t| t.dwt) {
if ffi::libinput_device_config_dwt_is_available(handle) != 0 {
let state = if dwt { 1 } else { 0 };
ffi::libinput_device_config_dwt_set_enabled(handle, state);
@@ -80,7 +80,7 @@ impl LibinputDevice {
}
// Dwtp (Disable while trackpointing)
- if let Some(dwtp) = config.dwtp {
+ if let Some(dwtp) = config.touchpad.as_ref().and_then(|t| t.dwtp) {
if ffi::libinput_device_config_dwtp_is_available(handle) != 0 {
let state = if dwtp { 1 } else { 0 };
ffi::libinput_device_config_dwtp_set_enabled(handle, state);
@@ -98,7 +98,7 @@ impl LibinputDevice {
// Acceleration Speed
let speed = if is_trackpoint {
- config.trackpoint_accel_speed.or(config.accel_speed)
+ config.trackpoint.as_ref().and_then(|t| t.accel_speed).or(config.accel_speed)
} else {
config.accel_speed
};
@@ -110,7 +110,7 @@ impl LibinputDevice {
// Acceleration Profile
let profile_str = if is_trackpoint {
- config.trackpoint_accel_profile.as_ref().or(config.accel_profile.as_ref())
+ config.trackpoint.as_ref().and_then(|t| t.accel_profile.as_ref()).or(config.accel_profile.as_ref())
} else {
config.accel_profile.as_ref()
};
diff --git a/src/server/output.rs b/src/server/output.rs
index ec1960f..453f86c 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -161,8 +161,11 @@ pub struct Output {
pub last_grid_pan_x: f64,
pub last_grid_pan_y: f64,
pub last_grid_spacing: f64,
- pub last_grid_line_width: i32,
- pub last_grid_color: [f32; 4],
+ pub last_grid_gap_width: i32,
+ pub last_grid_cell_color: [f32; 4],
+ pub last_grid_cell_corner_radius: i32,
+ pub last_grid_cell_fade_inset: i64,
+ pub last_grid_gap_color: String,
pub destroy: ffi::wl_listener,
pub request_state: ffi::wl_listener,
@@ -405,8 +408,11 @@ impl Output {
last_grid_pan_x: 0.0,
last_grid_pan_y: 0.0,
last_grid_spacing: 0.0,
- last_grid_line_width: 0,
- last_grid_color: [0.0, 0.0, 0.0, 0.0],
+ last_grid_gap_width: 0,
+ last_grid_cell_color: [0.0, 0.0, 0.0, 0.0],
+ last_grid_cell_corner_radius: 0,
+ last_grid_cell_fade_inset: 0,
+ last_grid_gap_color: String::new(),
destroy: std::mem::zeroed(),
request_state: std::mem::zeroed(),
frame: std::mem::zeroed(),
@@ -544,7 +550,7 @@ impl Output {
// LOD calculations for cells and gaps
let mut cell_size = wm.layout.desktop_grid_scale.max(5.0);
- let mut gap_size = (wm.layout.desktop_line_width as f64).max(0.0);
+ let mut gap_size = (wm.layout.desktop_gap_width as f64).max(0.0);
let mut period = cell_size + gap_size;
while period * zoom < 40.0 {
@@ -553,75 +559,201 @@ impl Output {
period = cell_size + gap_size;
}
- let grid_color: [f32; 4] = wm.layout.desktop_grid_color;
-
- // Check if cached grid parameters match current parameters
- if self.last_grid_viewport_w == viewport_w
- && self.last_grid_viewport_h == viewport_h
- && self.last_grid_zoom == zoom
- && self.last_grid_pan_x == wm.desk_pan_x
- && self.last_grid_pan_y == wm.desk_pan_y
- && self.last_grid_spacing == cell_size
- && self.last_grid_line_width == gap_size as i32
- && self.last_grid_color == grid_color
- {
- return;
- }
-
- // Cache current parameters
- self.last_grid_viewport_w = viewport_w;
- self.last_grid_viewport_h = viewport_h;
- self.last_grid_zoom = zoom;
- self.last_grid_pan_x = wm.desk_pan_x;
- self.last_grid_pan_y = wm.desk_pan_y;
- self.last_grid_spacing = cell_size;
- self.last_grid_line_width = gap_size as i32;
- self.last_grid_color = grid_color;
-
- // Clear previous grid rendering
- ffi::river_scene_tree_clear_children(self.grid_tree);
-
- let min_x = wm.desk_pan_x;
- let max_x = wm.desk_pan_x + (viewport_w as f64) / zoom;
- let min_y = wm.desk_pan_y;
- let max_y = wm.desk_pan_y + (viewport_h as f64) / zoom;
-
- let min_col = ((min_x - cell_size) / period).floor() as i32;
- let max_col = (max_x / period).ceil() as i32;
- let min_row = ((min_y - cell_size) / period).floor() as i32;
- let max_row = (max_y / period).ceil() as i32;
-
- for col in min_col..=max_col {
- let vx = (col as f64) * period;
- let rel_x = ((vx - wm.desk_pan_x) * zoom) as i32;
- let rw = (cell_size * zoom) as i32;
- if rw <= 0 {
- continue;
+ let cell_color: [f32; 4] = wm.layout.desktop_cell_color;
+ let cell_corner_radius = wm.layout.desktop_cell_corner_radius;
+ let cell_fade_inset = wm.layout.desktop_cell_fade_inset;
+ let gap_color = wm.layout.desktop_gap_color.clone();
+
+ let structure_changed = self.last_grid_viewport_w != viewport_w
+ || self.last_grid_viewport_h != viewport_h
+ || self.last_grid_zoom != zoom
+ || self.last_grid_spacing != cell_size
+ || self.last_grid_gap_width != gap_size as i32
+ || self.last_grid_cell_color != cell_color
+ || self.last_grid_cell_corner_radius != cell_corner_radius
+ || self.last_grid_cell_fade_inset != cell_fade_inset
+ || self.last_grid_gap_color != gap_color;
+
+ let period_pixels = period * zoom;
+
+ if structure_changed {
+ // Cache current parameters (excluding pan coordinates)
+ self.last_grid_viewport_w = viewport_w;
+ self.last_grid_viewport_h = viewport_h;
+ self.last_grid_zoom = zoom;
+ self.last_grid_spacing = cell_size;
+ self.last_grid_gap_width = gap_size as i32;
+ self.last_grid_cell_color = cell_color;
+ self.last_grid_cell_corner_radius = cell_corner_radius;
+ self.last_grid_cell_fade_inset = cell_fade_inset;
+ self.last_grid_gap_color = gap_color;
+
+ // Clear previous grid rendering
+ ffi::river_scene_tree_clear_children(self.grid_tree);
+ ffi::wlr_scene_node_raise_to_top(self.grid_tree as *mut ffi::wlr_scene_node);
+
+ // Draw the base level background rect (gap color)
+ // Sized larger to allow shifting by up to one period
+ let bg_w = viewport_w + period_pixels.ceil() as i32;
+ let bg_h = viewport_h + period_pixels.ceil() as i32;
+ let gap_color_arr = crate::config::parse_hex_color_rgba(&self.last_grid_gap_color);
+ let bg_rect = ffi::wlr_scene_rect_create(
+ self.grid_tree,
+ bg_w,
+ bg_h,
+ gap_color_arr.as_ptr(),
+ );
+ if !bg_rect.is_null() {
+ ffi::wlr_scene_node_set_position(
+ bg_rect as *mut ffi::wlr_scene_node,
+ 0,
+ 0,
+ );
}
- for row in min_row..=max_row {
- let vy = (row as f64) * period;
- let rel_y = ((vy - wm.desk_pan_y) * zoom) as i32;
- let rh = (cell_size * zoom) as i32;
- if rh <= 0 {
+ // Determine grid range needed to cover viewport + shifting buffer
+ let cols = (viewport_w as f64 / period_pixels).ceil() as i32 + 1;
+ let rows = (viewport_h as f64 / period_pixels).ceil() as i32 + 1;
+
+ for col in 0..=cols {
+ let rel_x = (col as f64 * period_pixels) as i32;
+ let rw = (cell_size * zoom) as i32;
+ if rw <= 0 {
continue;
}
- let cell_rect = ffi::wlr_scene_rect_create(
- self.grid_tree,
- rw,
- rh,
- grid_color.as_ptr(),
- );
- if !cell_rect.is_null() {
- ffi::wlr_scene_node_set_position(
- cell_rect as *mut ffi::wlr_scene_node,
- rel_x,
- rel_y,
- );
+ for row in 0..=rows {
+ let rel_y = (row as f64 * period_pixels) as i32;
+ let rh = (cell_size * zoom) as i32;
+ if rh <= 0 {
+ continue;
+ }
+
+ if cell_fade_inset <= 0 {
+ let cell_rect = ffi::wlr_scene_rect_create(
+ self.grid_tree,
+ rw,
+ rh,
+ cell_color.as_ptr(),
+ );
+ if !cell_rect.is_null() {
+ ffi::wlr_scene_node_set_position(
+ cell_rect as *mut ffi::wlr_scene_node,
+ rel_x,
+ rel_y,
+ );
+ if cell_corner_radius > 0 {
+ ffi::river_scene_rect_set_corner_radius(
+ cell_rect,
+ cell_corner_radius,
+ );
+ }
+ }
+ } else {
+ let step = 4;
+ let inset_scaled = (cell_fade_inset as f64 * zoom) as i32;
+
+ // 0. Draw the cell-sized gap color background rect behind the fade layers
+ let cell_bg_rect = ffi::wlr_scene_rect_create(
+ self.grid_tree,
+ rw,
+ rh,
+ gap_color_arr.as_ptr(),
+ );
+ if !cell_bg_rect.is_null() {
+ ffi::wlr_scene_node_set_position(
+ cell_bg_rect as *mut ffi::wlr_scene_node,
+ rel_x,
+ rel_y,
+ );
+ if cell_corner_radius > 0 {
+ ffi::river_scene_rect_set_corner_radius(
+ cell_bg_rect,
+ cell_corner_radius,
+ );
+ }
+ }
+
+ // 1. Draw the fade layers
+ let mut i = step;
+ while i < inset_scaled {
+ let rect_w = rw - 2 * i;
+ let rect_h = rh - 2 * i;
+ if rect_w <= 0 || rect_h <= 0 {
+ break;
+ }
+ let progress = i as f32 / inset_scaled as f32;
+ let alpha = cell_color[3] * progress;
+ let layer_color = [
+ cell_color[0] * alpha,
+ cell_color[1] * alpha,
+ cell_color[2] * alpha,
+ alpha,
+ ];
+
+ let cell_rect = ffi::wlr_scene_rect_create(
+ self.grid_tree,
+ rect_w,
+ rect_h,
+ layer_color.as_ptr(),
+ );
+ if !cell_rect.is_null() {
+ ffi::wlr_scene_node_set_position(
+ cell_rect as *mut ffi::wlr_scene_node,
+ rel_x + i,
+ rel_y + i,
+ );
+ if cell_corner_radius > 0 {
+ let r_inner = (cell_corner_radius - i).max(0);
+ if r_inner > 0 {
+ ffi::river_scene_rect_set_corner_radius(cell_rect, r_inner);
+ }
+ }
+ }
+ i += step;
+ }
+
+ // 2. Draw the fully opaque core
+ let core_w = rw - 2 * inset_scaled;
+ let core_h = rh - 2 * inset_scaled;
+ if core_w > 0 && core_h > 0 {
+ let cell_rect = ffi::wlr_scene_rect_create(
+ self.grid_tree,
+ core_w,
+ core_h,
+ cell_color.as_ptr(),
+ );
+ if !cell_rect.is_null() {
+ ffi::wlr_scene_node_set_position(
+ cell_rect as *mut ffi::wlr_scene_node,
+ rel_x + inset_scaled,
+ rel_y + inset_scaled,
+ );
+ if cell_corner_radius > 0 {
+ let r_inner = (cell_corner_radius - inset_scaled).max(0);
+ if r_inner > 0 {
+ ffi::river_scene_rect_set_corner_radius(cell_rect, r_inner);
+ }
+ }
+ }
+ }
+ }
}
}
}
+
+ // Apply modulo translation to the grid tree based on panning
+ let shift_x = -((wm.desk_pan_x * zoom).rem_euclid(period_pixels));
+ let shift_y = -((wm.desk_pan_y * zoom).rem_euclid(period_pixels));
+
+ ffi::river_scene_node_set_position_if_changed(
+ self.grid_tree as *mut ffi::wlr_scene_node,
+ shift_x as i32,
+ shift_y as i32,
+ );
+
+ self.last_grid_pan_x = wm.desk_pan_x;
+ self.last_grid_pan_y = wm.desk_pan_y;
}
}
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index de135bc..d8291b0 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -1370,6 +1370,42 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
self.rendering_scheduled.dirty = true;
}
+ pub unsafe fn update_viewport_local(&mut self) {
+ self.arrange_views();
+ // Clear rendering dirty flag so we don't trigger the idle callback's IPC handshake
+ self.rendering_scheduled.dirty = false;
+ self.remove_dirty_idle();
+
+ for &window in self.windows.iter() {
+ if !window.is_null() {
+ (*window).render_finish();
+ }
+ }
+
+ // Commit outputs or schedule frame updates
+ let outputs_list = &mut (*self.server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*outputs_list).next;
+ while curr != outputs_list {
+ let next = (*curr).next;
+ let output = crate::container_of!(curr, crate::output::Output, link);
+ if (*output).sent.state == crate::output::OutputStateValue::Enabled {
+ ffi::wlr_output_schedule_frame((*output).wlr_output);
+ }
+ curr = next;
+ }
+
+ // Re-evaluate cursor focus/hover since windows have moved relative to pointers
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr_seat = (*seats).next;
+ while curr_seat != seats {
+ let next_seat = (*curr_seat).next;
+ let seat = crate::container_of!(curr_seat, crate::seat::Seat, link);
+ (*seat).cursor.update_hovered();
+ curr_seat = next_seat;
+ }
+ }
+
+
pub unsafe fn focused_window(&self) -> *mut crate::window::Window {
let seats_list = &(*self.server).input_manager.seats as *const ffi::wl_list as *const WlList as *mut WlList;
let mut curr_seat = (*seats_list).next;
@@ -1836,7 +1872,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
Action::PanDown => self.desk_pan_y += step,
_ => {}
}
- self.dirty_windowing();
+ if matches!(self.state, WindowManagerState::Idle) {
+ self.update_viewport_local();
+ } else {
+ self.dirty_windowing();
+ }
}
Action::OverlayLeft => {
self.layout.overlay_position = "left".to_string();
@@ -1875,7 +1915,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
self.desk_pan_x = center_x - viewport_w / 2.0;
self.desk_pan_y = center_y - viewport_h / 2.0;
self.stop_panning_animation();
- self.dirty_windowing();
+ if matches!(self.state, WindowManagerState::Idle) {
+ self.update_viewport_local();
+ } else {
+ self.dirty_windowing();
+ }
return;
}
}
@@ -1885,7 +1929,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
self.desk_pan_x = 0.0;
self.desk_pan_y = 0.0;
self.stop_panning_animation();
- self.dirty_windowing();
+ if matches!(self.state, WindowManagerState::Idle) {
+ self.update_viewport_local();
+ } else {
+ self.dirty_windowing();
+ }
} else {
let mut min_vx = f64::MAX;
let mut max_vx = f64::MIN;
@@ -1961,7 +2009,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
self.mode = WindowManagerMode::Overview;
self.desk_pan_x = center_x - (viewport_w / 2.0) / new_zoom;
self.desk_pan_y = center_y - (viewport_h / 2.0) / new_zoom;
- self.dirty_windowing();
+ if matches!(self.state, WindowManagerState::Idle) {
+ self.update_viewport_local();
+ } else {
+ self.dirty_windowing();
+ }
}
}
}
@@ -2020,7 +2072,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
if let (Ok(dx), Ok(dy)) = (parts[1].parse::<f64>(), parts[2].parse::<f64>()) {
self.desk_pan_x += dx;
self.desk_pan_y += dy;
- self.dirty_windowing();
+ if matches!(self.state, WindowManagerState::Idle) {
+ self.update_viewport_local();
+ } else {
+ self.dirty_windowing();
+ }
return "ok\n".to_string();
}
"error: invalid dx or dy\n".to_string()
@@ -2030,7 +2086,11 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
if let (Ok(x), Ok(y)) = (parts[1].parse::<f64>(), parts[2].parse::<f64>()) {
self.desk_pan_x = x;
self.desk_pan_y = y;
- self.dirty_windowing();
+ if matches!(self.state, WindowManagerState::Idle) {
+ self.update_viewport_local();
+ } else {
+ self.dirty_windowing();
+ }
return "ok\n".to_string();
}
"error: invalid x or y\n".to_string()
@@ -2268,8 +2328,8 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
let key = parts[1];
let val = parts[2];
match key {
- "desktop_background_color" => {
- self.layout.desktop_background_color = val.to_string();
+ "desktop_gap_color" => {
+ self.layout.desktop_gap_color = val.to_string();
let parsed_color = crate::config::parse_hex_color(val);
self.layout.background_r = ((parsed_color >> 16) & 0xFF) * 0x01010101;
self.layout.background_g = ((parsed_color >> 8) & 0xFF) * 0x01010101;
@@ -2285,17 +2345,27 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
}
}
}
- "desktop_grid_color" => {
- self.layout.desktop_grid_color = crate::config::parse_hex_color_rgba(val);
+ "desktop_cell_color" => {
+ self.layout.desktop_cell_color = crate::config::parse_hex_color_rgba(val);
}
"desktop_grid_scale" => {
if let Ok(v) = val.parse::<f64>() {
self.layout.desktop_grid_scale = v;
}
}
- "desktop_line_width" => {
+ "desktop_gap_width" => {
if let Ok(v) = val.parse::<i32>() {
- self.layout.desktop_line_width = v;
+ self.layout.desktop_gap_width = v;
+ }
+ }
+ "desktop_cell_corner_radius" => {
+ if let Ok(v) = val.parse::<i32>() {
+ self.layout.desktop_cell_corner_radius = v;
+ }
+ }
+ "desktop_cell_fade_inset" => {
+ if let Ok(v) = val.parse::<i64>() {
+ self.layout.desktop_cell_fade_inset = v;
}
}
"gap" => { if let Ok(v) = val.parse::<i32>() { self.layout.gap = v; } }
@@ -2856,7 +2926,11 @@ pub(crate) unsafe extern "C" fn handle_panning_animation_tick(data: *mut std::ff
}
}
- (*wm).dirty_windowing();
+ if matches!((*wm).state, WindowManagerState::Idle) {
+ (*wm).update_viewport_local();
+ } else {
+ (*wm).dirty_windowing();
+ }
if !done {
if !(*wm).animation_timer.is_null() {
diff --git a/src/server/wlroots_log_wrapper.c b/src/server/wlroots_log_wrapper.c
index 401aafa..f0626b7 100644
--- a/src/server/wlroots_log_wrapper.c
+++ b/src/server/wlroots_log_wrapper.c
@@ -727,6 +727,10 @@ void river_scene_rect_set_size_if_changed(struct wlr_scene_rect *rect, int width
}
}
+void river_scene_rect_set_corner_radius(struct wlr_scene_rect *rect, int radius) {
+ wlr_scene_rect_set_corner_radius(rect, radius, CORNER_LOCATION_ALL);
+}
+
int river_scene_buffer_get_width(struct wlr_scene_buffer *scene_buffer) {
if (scene_buffer->buffer) {
return scene_buffer->buffer->width;
diff --git a/wrapper.h b/wrapper.h
index 51b4c01..4e6d132 100644
--- a/wrapper.h
+++ b/wrapper.h
@@ -271,4 +271,6 @@ void river_scene_node_set_position_if_changed(struct wlr_scene_node *node, int x
void river_scene_rect_set_size_if_changed(struct wlr_scene_rect *rect, int width, int height);
+void river_scene_rect_set_corner_radius(struct wlr_scene_rect *rect, int radius);
+
#endif // WRAPPER_H