Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Ignore clear-status-interface in focus/tiling; support logical screen geometry scaling
src/borders.rs | 6 +++++-
src/ipc.rs | 8 ++++----
src/tiling.rs | 20 ++++++++++----------
src/wayland.rs | 39 +++++++++++++++++++++++++++------------
src/wm.rs | 51 ++++++++++++++++++++++++++++++++++++++++-----------
5 files changed, 86 insertions(+), 38 deletions(-)
diff --git a/src/borders.rs b/src/borders.rs
index be0d493..c0f82b7 100644
--- a/src/borders.rs
+++ b/src/borders.rs
@@ -107,7 +107,7 @@ pub fn compute_border_colors(state: &WindowManager) -> Vec<WindowBorders> {
(r, g, b, ALPHA)
};
- let width = match win.tiling_mode {
+ let mut width = match win.tiling_mode {
TilingMode::Cascade => state.layout.cascade_border_width,
TilingMode::Fullscreen => state.layout.fullscreen_border_width,
TilingMode::Grid => state.layout.grid_border_width,
@@ -116,6 +116,10 @@ pub fn compute_border_colors(state: &WindowManager) -> Vec<WindowBorders> {
TilingMode::Floating => state.layout.floating_border_width,
};
+ if win.app_id.as_deref() == Some("clear-status-interface") {
+ width = 0;
+ }
+
eprintln!(
"[borders] -> r=#{:08x} g=#{:08x} b=#{:08x} a=#{:08x} width={}",
r, g, b, a, width,
diff --git a/src/ipc.rs b/src/ipc.rs
index 76f2771..41d34da 100644
--- a/src/ipc.rs
+++ b/src/ipc.rs
@@ -47,7 +47,7 @@ pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
let visible_ids: Vec<u64> = state
.windows
.iter()
- .filter(|w| (w.tags & active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if visible_ids.len() > 1 {
@@ -86,7 +86,7 @@ pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
let visible_ids: Vec<u64> = state
.windows
.iter()
- .filter(|w| (w.tags & state.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
seat.focused_window_id = visible_ids.last().copied();
@@ -116,7 +116,7 @@ pub fn handle_ipc_command(cmd: &str, state: &mut WindowManager) {
let visible_ids: Vec<u64> = state
.windows
.iter()
- .filter(|w| (w.tags & state.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if let Some(seat) = state.seats.iter_mut().find(|s| !s.removed) {
@@ -504,7 +504,7 @@ fn handle_set_tag_command(rest: &str, state: &mut WindowManager) {
let visible_ids: Vec<u64> = state
.windows
.iter()
- .filter(|w| (w.tags & state.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if let Some(seat) = state.seats.iter_mut().find(|s| !s.removed) {
diff --git a/src/tiling.rs b/src/tiling.rs
index baf262c..47e8ebb 100644
--- a/src/tiling.rs
+++ b/src/tiling.rs
@@ -35,7 +35,7 @@ pub fn tile_cascade(
idx: i32,
) -> (i32, i32, i32, i32) {
let width = screen_w - gap_left - gap_right - bw * 2 - cascade_offset * (n_cascade - 1);
- let height = screen_h - gap_top - gap_bottom - bw * 2 - cascade_offset * (n_cascade - 1);
+ let height = screen_h - bar_height - gap_top - gap_bottom - bw * 2 - cascade_offset * (n_cascade - 1);
let width = if width < 1 { 1 } else { width };
let height = if height < 1 { 1 } else { height };
let x = gap_left + bw + idx * cascade_offset;
@@ -72,7 +72,7 @@ pub fn tile_grid(
let col = idx % cols;
let rows = (n_grid + cols - 1) / cols;
let width = (screen_w - gap_left - gap_right - (cols - 1) * gap) / cols - 2 * bw;
- let height = (screen_h - gap_top - gap_bottom - (rows - 1) * gap) / rows - 2 * bw;
+ let height = (screen_h - bar_height - gap_top - gap_bottom - (rows - 1) * gap) / rows - 2 * bw;
let width = if width < 1 { 1 } else { width };
let height = if height < 1 { 1 } else { height };
let x = gap_left + bw + col * (width + 2 * bw + gap);
@@ -205,8 +205,8 @@ mod tests {
assert_eq!(y, 64); // bar_height + gap_top + bw
// w = 1920 - 18 - 18 - 18*2 - 32*0 = 1920 - 72 = 1848
assert_eq!(w, 1848);
- // h = 1080 - 18 - 18 - 18*2 - 32*0 = 1080 - 72 = 1008
- assert_eq!(h, 1008);
+ // h = 1080 - 28 (bar) - 18 - 18 - 18*2 - 32*0 = 1080 - 100 = 980
+ assert_eq!(h, 980);
}
#[test]
@@ -233,8 +233,8 @@ mod tests {
assert_eq!(y, 28 + 10 + 6); // bar_height + gap_top + bw = 44
// w = 1920 - 20 - 30 - 6*2 = 1920 - 62 = 1858
assert_eq!(w, 1858);
- // h = 1080 - 10 - 40 - 6*2 = 1080 - 62 = 1018
- assert_eq!(h, 1018);
+ // h = 1080 - 28 (bar) - 10 - 40 - 6*2 = 1080 - 90 = 990
+ assert_eq!(h, 990);
}
#[test]
@@ -252,8 +252,8 @@ mod tests {
// Width = (1920 - 18 - 18 - 1*18) / 2 - 2*18 = (1920-54)/2 - 36 = 933 - 36 = 897
assert_eq!(w0, 897);
- // rows=1, Height = (1080 - 18 - 18 - 0*18) / 1 - 2*18 = 1044 - 36 = 1008
- assert_eq!(h0, 1008);
+ // rows=1, Height = (1080 - 28 (bar) - 18 - 18 - 0*18) / 1 - 2*18 = 1016 - 36 = 980
+ assert_eq!(h0, 980);
}
#[test]
@@ -288,8 +288,8 @@ mod tests {
assert_eq!(x0, 26);
// y0 = bar_height + gap_top + bw = 28 + 10 + 6 = 44
assert_eq!(y0, 44);
- // rows=1, Height = (1080 - 10 - 40 - 0*12) / 1 - 2*6 = 1030 - 12 = 1018
- assert_eq!(h0, 1018);
+ // rows=1, Height = (1080 - 28 (bar) - 10 - 40 - 0*12) / 1 - 2*6 = 1002 - 12 = 990
+ assert_eq!(h0, 990);
// x1 = gap_left + bw + 1*(917 + 2*6 + 12) = 26 + 941 = 967
assert_eq!(x1, 967);
diff --git a/src/wayland.rs b/src/wayland.rs
index dc16c5d..4fbf2bf 100644
--- a/src/wayland.rs
+++ b/src/wayland.rs
@@ -428,7 +428,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
.windows
.iter()
.filter(|w| {
- !w.closed && (w.tags & active_tags) != 0 && w.id != *closed_id
+ !w.closed && (w.tags & active_tags) != 0 && w.id != *closed_id && w.app_id.as_deref() != Some("clear-status-interface")
})
.map(|w| w.id)
.collect();
@@ -653,7 +653,7 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
.wm
.windows
.iter()
- .filter(|w| w.is_new && !w.closed && (w.tags & active_tags) != 0)
+ .filter(|w| w.is_new && !w.closed && (w.tags & active_tags) != 0 && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.last();
if let Some(new_id) = new_focused_id {
@@ -798,11 +798,22 @@ impl Dispatch<RiverWindowManagerV1, ()> for AppState {
.wm
.get_window(focused_id)
.and_then(|w| w.app_id.clone());
- eprintln!(
- "[render] place_top for focused window id={} (app_id={:?})",
- focused_id, app_id
- );
- node.place_top();
+ if app_id.as_deref() != Some("clear-status-interface") {
+ eprintln!(
+ "[render] place_top for focused window id={} (app_id={:?})",
+ focused_id, app_id
+ );
+ node.place_top();
+ }
+ }
+ }
+ }
+
+ // Enforce that clear-status-interface is placed at the bottom
+ for &(wid, ref node) in &state.window_nodes {
+ if let Some(win) = state.wm.get_window(wid) {
+ if win.app_id.as_deref() == Some("clear-status-interface") {
+ node.place_bottom();
}
}
}
@@ -1210,6 +1221,10 @@ impl Dispatch<RiverSeatV1, ()> for AppState {
} => {
if let Some(wid) = state.window_id_for_proxy(&river_window) {
let target_app_id = state.wm.get_window(wid).and_then(|w| w.app_id.clone());
+ if target_app_id.as_deref() == Some("clear-status-interface") {
+ // Do not focus status bar!
+ return;
+ }
if let Some(seat) = state.wm.seats.iter_mut().find(|s| s.id == sid) {
eprintln!(
"[focus] WindowInteraction: seat={} focused_window_id={} -> {} (app_id={:?})",
@@ -1668,7 +1683,7 @@ fn execute_action(state: &mut AppState, action: &crate::types::Action, command:
.windows
.iter()
.filter(|w| {
- (w.tags & state.wm.active_tags) != 0 && !w.closed && w.id != focused_id
+ (w.tags & state.wm.active_tags) != 0 && !w.closed && w.id != focused_id && w.app_id.as_deref() != Some("clear-status-interface")
})
.map(|w| w.id)
.collect();
@@ -1691,7 +1706,7 @@ fn execute_action(state: &mut AppState, action: &crate::types::Action, command:
.wm
.windows
.iter()
- .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if let Some(fid) = focused_id {
@@ -1907,7 +1922,7 @@ fn execute_action(state: &mut AppState, action: &crate::types::Action, command:
.wm
.windows
.iter()
- .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if let Some(seat) = state.wm.seats.iter_mut().find(|s| !s.removed) {
@@ -1946,7 +1961,7 @@ fn execute_action(state: &mut AppState, action: &crate::types::Action, command:
.wm
.windows
.iter()
- .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if let Some(seat) = state.wm.seats.iter_mut().find(|s| !s.removed) {
@@ -1988,7 +2003,7 @@ fn execute_action(state: &mut AppState, action: &crate::types::Action, command:
.wm
.windows
.iter()
- .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed)
+ .filter(|w| (w.tags & state.wm.active_tags) != 0 && !w.closed && w.app_id.as_deref() != Some("clear-status-interface"))
.map(|w| w.id)
.collect();
if let Some(seat) = state.wm.seats.iter_mut().find(|s| !s.removed) {
diff --git a/src/wm.rs b/src/wm.rs
index 2f97f50..7ad5c5c 100644
--- a/src/wm.rs
+++ b/src/wm.rs
@@ -19,6 +19,10 @@ use wayland_client::QueueHandle;
/// Returns the resolved TilingMode, or None if the window's mode is locked
/// (i.e., the user manually set it and it should not be overridden).
pub fn get_mode_for_window(wm: &WindowManager, win: &Window) -> Option<TilingMode> {
+ if win.app_id.as_deref() == Some("clear-status-interface") {
+ return Some(TilingMode::Fullscreen);
+ }
+
// If the user manually locked the mode (via set-mode, fullscreen toggle, etc.),
// don't override it.
if win.mode_locked {
@@ -73,6 +77,13 @@ pub fn get_mode_for_window(wm: &WindowManager, win: &Window) -> Option<TilingMod
/// Assign tiling modes to all windows that aren't mode_locked.
/// Should be called during ManageStart before compute_tiling.
pub fn assign_window_modes(wm: &mut WindowManager) {
+ // Enforce that clear-status-interface is assigned all tags so that it is always visible
+ for win in &mut wm.windows {
+ if win.app_id.as_deref() == Some("clear-status-interface") {
+ win.tags = u32::MAX;
+ }
+ }
+
// Collect assignments first (borrow checker: can't borrow wm mutably while iterating mode_rules)
let assignments: Vec<(u64, TilingMode)> = wm
.windows
@@ -146,15 +157,28 @@ fn get_screen_geometry(wm: &WindowManager) -> (i32, i32, i32, i32, i32, i32) {
_ => return (800, 600, 800, 600, 0, 0),
};
+ let scale = if wm.output_scale > 0.0 { wm.output_scale } else { 1.0 };
+
let mut uw = output.usable_width;
let mut uh = output.usable_height;
- // Fall back to physical output dimensions if usable area not yet set
- if uw <= 0 && output.width > 0 {
- uw = output.width;
+ let log_width = if output.width > 0 {
+ (output.width as f64 / scale).round() as i32
+ } else {
+ 0
+ };
+ let log_height = if output.height > 0 {
+ (output.height as f64 / scale).round() as i32
+ } else {
+ 0
+ };
+
+ // Fall back to logical output dimensions if usable area not yet set
+ if uw <= 0 && log_width > 0 {
+ uw = log_width;
}
- if uh <= 0 && output.height > 0 {
- uh = output.height;
+ if uh <= 0 && log_height > 0 {
+ uh = log_height;
}
if uw <= 0 {
@@ -164,8 +188,8 @@ fn get_screen_geometry(wm: &WindowManager) -> (i32, i32, i32, i32, i32, i32) {
uh = 600;
}
- let pw = if output.width > 0 { output.width } else { uw };
- let ph = if output.height > 0 { output.height } else { uh };
+ let pw = if log_width > 0 { log_width } else { uw };
+ let ph = if log_height > 0 { log_height } else { uh };
let px = output.x;
let py = output.y;
@@ -241,13 +265,13 @@ fn compute_tiling(
.and_then(|s| s.focused_window_id)
.filter(|&fid| {
wm.get_window(fid).map_or(false, |w| {
- (w.tags & wm.active_tags) != 0 && !w.closed && w.tiling_mode == TilingMode::Fullscreen
+ (w.tags & wm.active_tags) != 0 && !w.closed && w.tiling_mode == TilingMode::Fullscreen && w.app_id.as_deref() != Some("clear-status-interface")
})
})
.or_else(|| {
// Fallback: first fullscreen window if no focused window qualifies
wm.windows.iter().find(|w| {
- (w.tags & wm.active_tags) != 0 && !w.closed && w.tiling_mode == TilingMode::Fullscreen
+ (w.tags & wm.active_tags) != 0 && !w.closed && w.tiling_mode == TilingMode::Fullscreen && w.app_id.as_deref() != Some("clear-status-interface")
}).map(|w| w.id)
});
@@ -268,7 +292,12 @@ fn compute_tiling(
let (x, y, w, h) = match mode {
TilingMode::Fullscreen => {
- if fullscreen_id == Some(wid) {
+ if fullscreen_id == Some(wid) || win.app_id.as_deref() == Some("clear-status-interface") {
+ let border_w = if win.app_id.as_deref() == Some("clear-status-interface") {
+ 0
+ } else {
+ wm.layout.fullscreen_border_width
+ };
let (tx, ty, tw, th) = tiling::tile_fullscreen(
phys_w,
phys_h,
@@ -276,7 +305,7 @@ fn compute_tiling(
gap_left,
gap_right,
gap_bottom,
- wm.layout.fullscreen_border_width,
+ border_w,
bar_height,
);
(tx + phys_x, ty + phys_y, tw, th)