git.lucas.co / cce-compositor
Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git

commitce848c00b8cf13eec20709010d68e5f5f33bf6cc
parent2eaaee09b0
authorLucas Galante <[email protected]>
date2026-06-30 14:38
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