Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Support dragging and edge-snapping status windows in adjust-position-mode
src/server/cursor.rs | 78 ++++++++++++++++
src/server/window.rs | 10 ++
src/server/window_manager.rs | 211 ++++++++++++++++++++++++++++++-------------
3 files changed, 237 insertions(+), 62 deletions(-)
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 0f5f01f..66769d1 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -624,6 +624,45 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
return;
}
+ if (*event).button == 0x110 && (*(*seat).server).wm.adjust_position_mode {
+ let mut clicked_status: *mut crate::window::Window = std::ptr::null_mut();
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if let SceneNodeDataVal::Window(window) = result.data {
+ if (*window).is_status_bar() {
+ clicked_status = window;
+ }
+ }
+ }
+ if !clicked_status.is_null() {
+ (*server).wm.stop_panning_animation();
+ let cursor_x = (*cursor.wlr_cursor).x;
+ let cursor_y = (*cursor.wlr_cursor).y;
+ seat.op = Some(crate::seat::SeatOp {
+ sent_release: false,
+ input: crate::seat::SeatOpInput::Pointer,
+ start_x: cursor_x as i32,
+ start_y: cursor_y as i32,
+ x: cursor_x as i32,
+ y: cursor_y as i32,
+ window_ptr: clicked_status,
+ op_type: crate::seat::PointerOpType::Move,
+ start_win_x: (*clicked_status).box_geom.x,
+ start_win_y: (*clicked_status).box_geom.y,
+ start_win_w: (*clicked_status).box_geom.width as u32,
+ start_win_h: (*clicked_status).box_geom.height as u32,
+ start_win_virtual_x: (*clicked_status).virtual_x,
+ start_win_virtual_y: (*clicked_status).virtual_y,
+ start_tiling_mode: (*clicked_status).tiling_mode,
+ start_mode_locked: (*clicked_status).mode_locked,
+ started_in_overview: false,
+ });
+ cursor.op_start_pointer();
+ cursor.pressed.insert((*event).button, None);
+ cursor.set_xcursor(b"grab\0".as_ptr() as *const _);
+ return;
+ }
+ }
+
// --- ZOOMED OUT CLICK HANDLING ---
if (*event).button == 0x110 && (*(*seat).server).wm.mode == crate::window_manager::WindowManagerMode::Overview {
let mut clicked_win: *mut crate::window::Window = std::ptr::null_mut();
@@ -1025,6 +1064,45 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
seat.op_update(cursor_x as i32, cursor_y as i32);
let op = seat.op.unwrap();
+
+ if (*(*seat).server).wm.adjust_position_mode && !op.window_ptr.is_null() && (*op.window_ptr).is_status_bar() && (*event).button == 0x110 {
+ let win = op.window_ptr;
+ let mut closest_edge = crate::window::StatusEdge::Top;
+ let mut min_dist = f64::MAX;
+
+ let outputs_list = &mut (*server).om.outputs as *mut ffi::wl_list as *mut WlList;
+ let mut curr_out = (*outputs_list).next;
+ while curr_out != outputs_list {
+ let output = crate::container_of!(curr_out, crate::output::Output, link);
+ if (*output).sent.state == crate::output::OutputStateValue::Enabled {
+ let wlr_box = (*output).sent.box_layout();
+ let ox = wlr_box.x as f64;
+ let oy = wlr_box.y as f64;
+ let ow = wlr_box.width as f64;
+ let oh = wlr_box.height as f64;
+
+ if lx >= ox && lx <= ox + ow && ly >= oy && ly <= oy + oh {
+ let dt = ly - oy;
+ let db = (oy + oh) - ly;
+ let dl = lx - ox;
+ let dr = (ox + ow) - lx;
+
+ if dt < min_dist { min_dist = dt; closest_edge = crate::window::StatusEdge::Top; }
+ if db < min_dist { min_dist = db; closest_edge = crate::window::StatusEdge::Bottom; }
+ if dl < min_dist { min_dist = dl; closest_edge = crate::window::StatusEdge::Left; }
+ if dr < min_dist { min_dist = dr; closest_edge = crate::window::StatusEdge::Right; }
+ break;
+ }
+ }
+ curr_out = (*curr_out).next;
+ }
+
+ (*win).status_edge = closest_edge;
+ seat.op_end();
+ cursor.pressed.remove(&(*event).button);
+ (*server).wm.dirty_windowing();
+ return;
+ }
if op.started_in_overview && (*event).button == 0x110 {
let moved = (cursor_x as i32 - op.start_x).abs() > 5 || (cursor_y as i32 - op.start_y).abs() > 5;
if !moved && !op.window_ptr.is_null() {
diff --git a/src/server/window.rs b/src/server/window.rs
index 83ed25d..5c8e60a 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -258,6 +258,15 @@ pub struct Window {
pub foreign_toplevel_handle: *mut ffi::wlr_ext_foreign_toplevel_handle_v1,
pub wlr_toplevel_handle: *mut ffi::wlr_foreign_toplevel_handle_v1,
pub csd_buffer_size_bug: bool,
+ pub status_edge: StatusEdge,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum StatusEdge {
+ Top,
+ Bottom,
+ Left,
+ Right,
}
impl Window {
@@ -467,6 +476,7 @@ impl Window {
foreign_toplevel_handle: std::ptr::null_mut(),
wlr_toplevel_handle: std::ptr::null_mut(),
csd_buffer_size_bug: false,
+ status_edge: StatusEdge::Top,
});
ffi::wl_list_init(&mut window.decorations_below);
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index 29eb74b..4e97fac 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -109,6 +109,7 @@ pub struct WindowManager {
pub input_config: crate::config::InputConfig,
pub last_status_update: std::cell::RefCell<Option<crate::status_server::StatusUpdate>>,
pub status_hide_mode: bool,
+ pub adjust_position_mode: bool,
pub restore_queue: Vec<SavedWindowState>,
pub last_window_states: Vec<SavedWindowState>,
pub shutting_down: bool,
@@ -195,6 +196,7 @@ impl WindowManager {
self.input_config = crate::config::InputConfig::default();
self.last_status_update = std::cell::RefCell::new(None);
self.status_hide_mode = false;
+ self.adjust_position_mode = false;
ffi::wl_list_init(&mut self.sent.outputs);
ffi::wl_list_init(&mut self.sent.seats);
@@ -1545,16 +1547,23 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
b,
a,
};
- (*win_ptr).rendering_requested.blur = self.layout.window_blur;
+(*win_ptr).rendering_requested.blur = self.layout.window_blur;
(*win_ptr).rendering_requested.opacity = if is_focused { 1.0f32 } else {
if !self.layout.window_opacity { 1.0f32 } else { 0.90f32 }
};
}
-
// Position status bar windows on this output
- let mut left_status = Vec::new();
- let mut right_status = Vec::new();
- let mut full_status = Vec::new();
+ let bar_h = self.layout.bar_height as u32;
+ let spacing = 12;
+ let margin = 12;
+
+ let mut top_left = Vec::new();
+ let mut top_right = Vec::new();
+ let mut bottom_left = Vec::new();
+ let mut bottom_right = Vec::new();
+ let mut left_side = Vec::new();
+ let mut right_side = Vec::new();
+ let mut full_top = Vec::new();
for &win_ptr in self.windows.iter() {
if win_ptr.is_null() || (*win_ptr).closed {
@@ -1564,12 +1573,49 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
continue;
}
if let Some(app_id) = (*win_ptr).get_app_id_string() {
- if app_id.starts_with("cce-status-left-") {
- left_status.push(win_ptr);
- } else if app_id.starts_with("cce-status-right-") {
- right_status.push(win_ptr);
- } else if app_id.starts_with("cce-status") {
- full_status.push(win_ptr);
+ if app_id.starts_with("cce-status") {
+ // Skip if currently being dragged interactively
+ let mut is_being_dragged = false;
+ let seats_list = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr_seat = (*seats_list).next;
+ while curr_seat != seats_list {
+ let seat = crate::container_of!(curr_seat, crate::seat::Seat, link);
+ if let Some(ref op) = (*seat).op {
+ if op.window_ptr == win_ptr && matches!(op.op_type, crate::seat::PointerOpType::Move) {
+ is_being_dragged = true;
+ break;
+ }
+ }
+ curr_seat = (*curr_seat).next;
+ }
+ if is_being_dragged {
+ continue;
+ }
+
+ match (*win_ptr).status_edge {
+ crate::window::StatusEdge::Top => {
+ if app_id.starts_with("cce-status-left-") {
+ top_left.push(win_ptr);
+ } else if app_id.starts_with("cce-status-right-") {
+ top_right.push(win_ptr);
+ } else {
+ full_top.push(win_ptr);
+ }
+ }
+ crate::window::StatusEdge::Bottom => {
+ if app_id.starts_with("cce-status-left-") {
+ bottom_left.push(win_ptr);
+ } else {
+ bottom_right.push(win_ptr);
+ }
+ }
+ crate::window::StatusEdge::Left => {
+ left_side.push(win_ptr);
+ }
+ crate::window::StatusEdge::Right => {
+ right_side.push(win_ptr);
+ }
+ }
}
}
}
@@ -1577,81 +1623,108 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
const LEFT_ORDER: &[&str] = &["viewport", "window"];
const RIGHT_ORDER: &[&str] = &["tray", "cpu", "memory", "brightness", "volume", "battery", "clock"];
- left_status.sort_by_key(|&w| unsafe {
- let app_id = (*w).get_app_id_string().unwrap_or_default();
- let name = app_id.strip_prefix("cce-status-left-").unwrap_or(&app_id);
- LEFT_ORDER.iter().position(|&m| m == name).unwrap_or(99)
- });
+ let sort_left = |w_list: &mut Vec<*mut Window>| {
+ w_list.sort_by_key(|&w| unsafe {
+ let app_id = (*w).get_app_id_string().unwrap_or_default();
+ let name = app_id.strip_prefix("cce-status-left-").unwrap_or(&app_id);
+ LEFT_ORDER.iter().position(|&m| m == name).unwrap_or(99)
+ });
+ };
- right_status.sort_by_key(|&w| unsafe {
- let app_id = (*w).get_app_id_string().unwrap_or_default();
- let name = app_id.strip_prefix("cce-status-right-").unwrap_or(&app_id);
- RIGHT_ORDER.iter().position(|&m| m == name).unwrap_or(99)
- });
+ let sort_right = |w_list: &mut Vec<*mut Window>| {
+ w_list.sort_by_key(|&w| unsafe {
+ let app_id = (*w).get_app_id_string().unwrap_or_default();
+ let name = app_id.strip_prefix("cce-status-right-").unwrap_or(&app_id);
+ RIGHT_ORDER.iter().position(|&m| m == name).unwrap_or(99)
+ });
+ };
- let bar_h = self.layout.bar_height as u32;
- let spacing = 12;
- let margin = 12;
+ sort_left(&mut top_left);
+ sort_right(&mut top_right);
+ sort_left(&mut bottom_left);
+ sort_right(&mut bottom_right);
- let status_y = if self.status_hide_mode {
+ // 1. Top Edge
+ let status_y_top = if self.status_hide_mode {
let preview = self.layout.status_module_hide_mode_preview as i32;
wlr_box.y - (bar_h as i32 - preview)
} else {
wlr_box.y
};
- // Layout Left status windows
let mut cur_left_x = wlr_box.x + margin;
- for win_ptr in left_status {
+ for win_ptr in top_left {
let w = if (*win_ptr).box_geom.width > 0 { (*win_ptr).box_geom.width as u32 } else { 100 };
- let app_id = (*win_ptr).get_app_id_string().unwrap_or_default();
- log::info!("[ArrangeStatus] Left module app_id={} x={} y={} w={}", app_id, cur_left_x, status_y, w);
(*win_ptr).rendering_requested.x = cur_left_x;
- (*win_ptr).rendering_requested.y = status_y;
- (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
- width: w,
- height: bar_h,
- });
- (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
- width: w,
- height: bar_h,
- };
+ (*win_ptr).rendering_requested.y = status_y_top;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: w, height: bar_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: w, height: bar_h };
cur_left_x += w as i32 + spacing;
}
- // Layout Right status windows (right-to-left)
let mut cur_right_x = wlr_box.x + wlr_box.width - margin;
- for win_ptr in right_status.into_iter().rev() {
+ for win_ptr in top_right.into_iter().rev() {
let actual_w = (*win_ptr).box_geom.width;
let w = if actual_w > 0 { actual_w as u32 } else { 100 };
let x = cur_right_x - w as i32;
- let app_id = (*win_ptr).get_app_id_string().unwrap_or_default();
- log::info!("[ArrangeStatus] Right module app_id={} actual_box_w={} x={} y={} w={}", app_id, actual_w, x, status_y, w);
(*win_ptr).rendering_requested.x = x;
- (*win_ptr).rendering_requested.y = status_y;
- (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
- width: w,
- height: bar_h,
- });
- (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
- width: w,
- height: bar_h,
- };
+ (*win_ptr).rendering_requested.y = status_y_top;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: w, height: bar_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: w, height: bar_h };
cur_right_x = x - spacing;
}
- // Layout Full/Legacy status windows
- for win_ptr in full_status {
+ for win_ptr in full_top {
(*win_ptr).rendering_requested.x = wlr_box.x;
- (*win_ptr).rendering_requested.y = status_y;
- (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
- width: wlr_box.width as u32,
- height: bar_h,
- });
- (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
- width: wlr_box.width as u32,
- height: bar_h,
- };
+ (*win_ptr).rendering_requested.y = status_y_top;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: wlr_box.width as u32, height: bar_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: wlr_box.width as u32, height: bar_h };
+ }
+
+ // 2. Bottom Edge
+ let status_y_bottom = wlr_box.y + wlr_box.height - bar_h as i32;
+ let mut cur_left_x = wlr_box.x + margin;
+ for win_ptr in bottom_left {
+ let w = if (*win_ptr).box_geom.width > 0 { (*win_ptr).box_geom.width as u32 } else { 100 };
+ (*win_ptr).rendering_requested.x = cur_left_x;
+ (*win_ptr).rendering_requested.y = status_y_bottom;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: w, height: bar_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: w, height: bar_h };
+ cur_left_x += w as i32 + spacing;
+ }
+
+ let mut cur_right_x = wlr_box.x + wlr_box.width - margin;
+ for win_ptr in bottom_right.into_iter().rev() {
+ let actual_w = (*win_ptr).box_geom.width;
+ let w = if actual_w > 0 { actual_w as u32 } else { 100 };
+ let x = cur_right_x - w as i32;
+ (*win_ptr).rendering_requested.x = x;
+ (*win_ptr).rendering_requested.y = status_y_bottom;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: w, height: bar_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: w, height: bar_h };
+ cur_right_x = x - spacing;
+ }
+
+ // 3. Left Edge (Vertical stacking)
+ let mut cur_left_y = wlr_box.y + margin;
+ for win_ptr in left_side {
+ let actual_h = if (*win_ptr).box_geom.width > 0 { (*win_ptr).box_geom.width as u32 } else { 100 };
+ (*win_ptr).rendering_requested.x = wlr_box.x;
+ (*win_ptr).rendering_requested.y = cur_left_y;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: bar_h, height: actual_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: bar_h, height: actual_h };
+ cur_left_y += actual_h as i32 + spacing;
+ }
+
+ // 4. Right Edge (Vertical stacking)
+ let mut cur_right_y = wlr_box.y + margin;
+ for win_ptr in right_side {
+ let actual_h = if (*win_ptr).box_geom.width > 0 { (*win_ptr).box_geom.width as u32 } else { 100 };
+ (*win_ptr).rendering_requested.x = wlr_box.x + wlr_box.width - bar_h as i32;
+ (*win_ptr).rendering_requested.y = cur_right_y;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions { width: bar_h, height: actual_h });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions { width: bar_h, height: actual_h };
+ cur_right_y += actual_h as i32 + spacing;
}
}
// If the focused window is no longer visible, refocus
@@ -2392,6 +2465,20 @@ fn get_closest_tag(x: f64, y: f64) -> i32 {
self.dirty_windowing();
return format!("ok {}\n", enable);
}
+ "adjust-position-mode" => {
+ let enable = if parts.len() >= 2 {
+ match parts[1] {
+ "true" | "on" | "enable" | "1" => true,
+ "false" | "off" | "disable" | "0" => false,
+ _ => !self.adjust_position_mode,
+ }
+ } else {
+ !self.adjust_position_mode
+ };
+ self.adjust_position_mode = enable;
+ self.dirty_windowing();
+ return format!("ok {}\n", enable);
+ }
"view" => {
if parts.len() < 2 { return "error: missing tag\n".to_string(); }
if let Ok(tag) = parts[1].parse::<i32>() {