Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Implement output scaling, side panel support, restacking stability fixes, and general cleanup in monolithic cce-server
Cargo.toml | 2 +-
src/server/config.rs | 20 +-
src/server/layer_shell.rs | 2 +-
src/server/output.rs | 2 +-
src/server/tiling.rs | 2 +-
src/server/window.rs | 111 ++++++++---
src/server/window_manager.rs | 436 +++++++++++++++++++++++++++++++++----------
src/server/wm_node.rs | 16 +-
src/server/xdg_toplevel.rs | 46 +----
9 files changed, 456 insertions(+), 181 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index 6172850..d1a60c9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,4 +29,4 @@ path = "src/bin/cce.rs"
[profile.release]
debug = true
-[workspace]
+
diff --git a/src/server/config.rs b/src/server/config.rs
index beae02a..2851851 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -104,8 +104,6 @@ pub enum Action {
LayoutNext,
ModeNext,
ModeNextShared,
- Reload,
- Restart,
View1,
View2,
View3,
@@ -163,6 +161,16 @@ pub struct PointerBind {
pub action: Action,
}
+#[derive(Debug, Deserialize, Clone)]
+pub struct OutputConfig {
+ #[serde(default = "default_scale")]
+ pub scale: f64,
+}
+
+fn default_scale() -> f64 {
+ 1.0
+}
+
#[derive(Debug, Deserialize)]
pub struct Config {
#[serde(default)]
@@ -179,6 +187,8 @@ pub struct Config {
pub tag_layout: Vec<TagLayoutConfig>,
#[serde(default)]
pub startup: Vec<StartupConfig>,
+ #[serde(default)]
+ pub output: Option<OutputConfig>,
}
#[derive(Debug, Deserialize)]
@@ -381,10 +391,6 @@ pub fn parse_action(s: &str) -> Action {
Action::ModeNext
} else if s == "mode-next-shared" {
Action::ModeNextShared
- } else if s == "reload" {
- Action::Reload
- } else if s == "restart" {
- Action::Restart
} else if s == "fullscreen" {
Action::Fullscreen
} else if s == "minimize" {
@@ -548,6 +554,8 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
Err(e) => return Err(format!("TOML parse error: {}", e)),
};
+ state.output_scale = config.output.as_ref().map(|o| o.scale as f32).unwrap_or(1.0f32);
+
state.layout.gap = config.layout.gap as i32;
state.layout.gap_top = config.layout.gap_top as i32;
state.layout.gap_left = config.layout.gap_left as i32;
diff --git a/src/server/layer_shell.rs b/src/server/layer_shell.rs
index 9a7c5e8..c3d7a49 100644
--- a/src/server/layer_shell.rs
+++ b/src/server/layer_shell.rs
@@ -74,7 +74,7 @@ impl LayerShell {
pub unsafe fn supported(&self) -> bool {
let wm_v1 = (*self.server).wm.object;
if wm_v1.is_null() {
- return false;
+ return true;
}
let wm_client = ffi::wl_resource_get_client(wm_v1);
diff --git a/src/server/output.rs b/src/server/output.rs
index e6357f5..4e642cc 100644
--- a/src/server/output.rs
+++ b/src/server/output.rs
@@ -347,7 +347,7 @@ impl Output {
x: 0,
y: 0,
mode: OutputMode::None,
- scale: 1.0,
+ scale: (*server).wm.output_scale,
transform: ffi::wl_output_transform_WL_OUTPUT_TRANSFORM_NORMAL,
adaptive_sync: ffi::river_wlr_output_get_adaptive_sync_status(wlr_output) == ffi::wlr_output_adaptive_sync_status_WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED,
auto_layout: true,
diff --git a/src/server/tiling.rs b/src/server/tiling.rs
index 9c21887..f538e84 100644
--- a/src/server/tiling.rs
+++ b/src/server/tiling.rs
@@ -69,7 +69,7 @@ pub fn tile_grid(
n_grid: i32,
idx: i32,
) -> (i32, i32, i32, i32) {
- let cols = 2i32;
+ let cols = if n_grid == 1 { 1i32 } else { 2i32 };
let row = idx / cols;
let col = idx % cols;
let rows = (n_grid + cols - 1) / cols;
diff --git a/src/server/window.rs b/src/server/window.rs
index 2d5fc32..8922138 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -455,19 +455,22 @@ impl Window {
}
}
- pub unsafe fn is_antigravity(&self) -> bool {
- let app_id_ptr = self.get_app_id();
- if app_id_ptr.is_null() { return false; }
- let app_id = std::ffi::CStr::from_ptr(app_id_ptr).to_str().unwrap_or("").to_lowercase();
- app_id.contains("antigravity")
+ pub unsafe fn get_app_id_string(&self) -> Option<String> {
+ let ptr = self.get_app_id();
+ if ptr.is_null() {
+ None
+ } else {
+ Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
+ }
}
- pub unsafe fn is_chromium_electron(&self) -> bool {
- if self.is_antigravity() { return true; }
- let app_id_ptr = self.get_app_id();
- if app_id_ptr.is_null() { return false; }
- let app_id = std::ffi::CStr::from_ptr(app_id_ptr).to_str().unwrap_or("").to_lowercase();
- app_id.contains("chromium") || app_id.contains("chrome") || app_id.contains("electron") || app_id.contains("code") || app_id.contains("discord") || app_id.contains("slack")
+ pub unsafe fn get_title_string(&self) -> Option<String> {
+ let ptr = self.get_title();
+ if ptr.is_null() {
+ None
+ } else {
+ Some(std::ffi::CStr::from_ptr(ptr).to_string_lossy().into_owned())
+ }
}
pub unsafe fn get_parent(&self) -> *mut Window {
@@ -557,6 +560,27 @@ impl Window {
assert!(!matches!(self.impl_type, WindowImpl::Destroying));
assert_eq!(self.state, WindowState::Initialized);
self.state = WindowState::Mapped;
+
+ let app_id_ptr = self.get_app_id();
+ let is_status_bar = if !app_id_ptr.is_null() {
+ let app_id = std::ffi::CStr::from_ptr(app_id_ptr).to_string_lossy();
+ app_id == "cce-status-interface"
+ } else {
+ false
+ };
+
+ if !is_status_bar {
+ let seats = &mut (*self.server).input_manager.seats as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*seats).next;
+ while curr != seats {
+ let next = (*curr).next;
+ let seat = crate::container_of!(curr, crate::seat::Seat, link);
+ (*seat).focus(crate::seat::Focus::Window(self as *mut Window));
+ curr = next;
+ }
+ }
+
+ (*self.server).wm.dirty_windowing();
Ok(())
}
@@ -749,6 +773,44 @@ impl Window {
WindowState::Ready | WindowState::Initialized | WindowState::Mapped => {
let wm_v1 = (*self.server).wm.object;
if wm_v1.is_null() {
+ let is_linked = self.node.link.prev as *const _ != &self.node.link as *const _;
+ if !is_linked {
+ wl_list_remove(&mut self.node.link as *mut ffi::wl_list as *mut WlList);
+ let rendering_list = &mut (*self.server).wm.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ wl_list_insert((*rendering_list).prev, &mut self.node.link as *mut ffi::wl_list as *mut WlList);
+
+ if self.foreign_toplevel_handle.is_null() {
+ let list = (*self.server).foreign_toplevel_list;
+ let title = self.get_title();
+ let app_id = self.get_app_id();
+ let state = ffi::wlr_ext_foreign_toplevel_handle_v1_state {
+ title,
+ app_id,
+ };
+ let handle = ffi::wlr_ext_foreign_toplevel_handle_v1_create(list, &state);
+ if !handle.is_null() {
+ self.foreign_toplevel_handle = handle;
+ (*handle).data = self as *mut Window as *mut _;
+ }
+ }
+
+ if self.wlr_toplevel_handle.is_null() {
+ let manager = (*self.server).wlr_foreign_toplevel_manager;
+ let handle = ffi::wlr_foreign_toplevel_handle_v1_create(manager);
+ if !handle.is_null() {
+ self.wlr_toplevel_handle = handle;
+ let title = self.get_title();
+ if !title.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_title(handle, title);
+ }
+ let app_id = self.get_app_id();
+ if !app_id.is_null() {
+ ffi::wlr_foreign_toplevel_handle_v1_set_app_id(handle, app_id);
+ }
+ }
+ }
+ self.rendering_scheduled.resend_dimensions = true;
+ }
return;
}
let new_resource = self.object.is_null();
@@ -1028,19 +1090,7 @@ impl Window {
};
self.wm_requested.dimensions = None;
- if self.wm_requested.ssd && self.is_chromium_electron() && self.wm_requested.fullscreen.is_null() {
- let margin_x = if self.margin_x > 0 { self.margin_x } else { 10 };
- let margin_y = if self.margin_y > 0 { self.margin_y } else { 10 };
- let is_antigravity = self.is_antigravity();
- let (left_margin, right_margin) = if margin_x == 10 && !is_antigravity { (10, 34) } else { (margin_x, margin_x) };
- let (top_margin, bottom_margin) = if margin_y == 10 && !is_antigravity { (10, 34) } else { (margin_y, margin_y) };
- if let Some(w) = width {
- width = Some(w + left_margin as u32 + right_margin as u32);
- }
- if let Some(h) = height {
- height = Some(h + top_margin as u32 + bottom_margin as u32);
- }
- }
+
self.configure_scheduled = Configure {
width,
@@ -1267,7 +1317,16 @@ impl Window {
let output = self.wm_requested.fullscreen;
self.box_geom.x = (*output).sent.x;
self.box_geom.y = (*output).sent.y;
- ffi::wlr_scene_node_set_enabled(self.fullscreen_background as *mut ffi::wlr_scene_node, true);
+
+ let app_id_ptr = self.get_app_id();
+ let is_status_bar = if !app_id_ptr.is_null() {
+ let app_id = std::ffi::CStr::from_ptr(app_id_ptr).to_string_lossy();
+ app_id == "cce-status-interface"
+ } else {
+ false
+ };
+
+ ffi::wlr_scene_node_set_enabled(self.fullscreen_background as *mut ffi::wlr_scene_node, !is_status_bar);
let (width, height) = (*output).sent.dimensions();
ffi::wlr_scene_rect_set_size(self.fullscreen_background, width as i32, height as i32);
clip = ffi::wlr_box { x: 0, y: 0, width: width as i32, height: height as i32 };
@@ -1337,7 +1396,7 @@ impl Window {
pub unsafe fn draw_borders(&mut self) {
let requested = &self.rendering_requested;
- if requested.circular || requested.border.width == 0 {
+ if requested.circular || requested.border.width == 0 || !self.wm_requested.ssd {
ffi::wlr_scene_node_set_enabled(self.border.left as *mut ffi::wlr_scene_node, false);
ffi::wlr_scene_node_set_enabled(self.border.right as *mut ffi::wlr_scene_node, false);
ffi::wlr_scene_node_set_enabled(self.border.top as *mut ffi::wlr_scene_node, false);
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index d087d21..3ef7482 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -66,6 +66,7 @@ pub struct WindowManager {
pub ipc_timer: *mut ffi::wl_event_source,
pub startup: Vec<crate::config::StartupConfig>,
pub status_sender: Option<crate::status_server::StatusSender>,
+ pub output_scale: f32,
}
impl WindowManager {
@@ -75,6 +76,7 @@ impl WindowManager {
ffi::wl_list_init(&mut self.sent.outputs);
self.scheduled.output_config = std::ptr::null_mut();
self.sent.output_config = std::ptr::null_mut();
+ self.output_scale = 1.0;
Ok(())
}
@@ -108,6 +110,7 @@ impl WindowManager {
self.tag_layouts = [crate::tiling::TilingMode::Cascade; 4];
self.has_tag_layout = [false; 4];
self.layout = crate::config::Layout::default();
+ self.output_scale = 1.0;
self.mode_rules = Vec::new();
self.keybinds = Vec::new();
self.pointer_binds = Vec::new();
@@ -423,6 +426,8 @@ impl WindowManager {
}
}
+ self.keep_status_bar_on_top();
+
let mut hasher = std::collections::hash_map::DefaultHasher::new();
let render_list = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
let mut curr = (*render_list).next;
@@ -522,26 +527,30 @@ impl WindowManager {
// Add legacy fields so structural offsets are preserved if layout-based code is compiled
pub fn sent_outputs_compat(&self) {}
+ pub unsafe fn get_rule_for_window(&self, win: *mut Window) -> Option<&crate::config::ModeRule> {
+ let app_id = (*win).get_app_id_string();
+ let title = (*win).get_title_string();
+
+ for rule in &self.mode_rules {
+ let match_app = rule.app_id_pattern == "*"
+ || app_id.as_ref().map_or(false, |aid| aid.contains(&rule.app_id_pattern));
+ let match_title = rule.title_pattern.as_ref().map_or(true, |tp| {
+ title.as_ref().map_or(false, |t| t.contains(tp))
+ });
+
+ if match_app && match_title {
+ return Some(rule);
+ }
+ }
+ None
+ }
+
pub unsafe fn get_mode_for_window(&self, win: *mut Window) -> crate::tiling::TilingMode {
if (*win).mode_locked {
return (*win).tiling_mode;
}
- // Query app_id/title
- let app_id_ptr = (*win).get_app_id();
- let app_id = if app_id_ptr.is_null() {
- None
- } else {
- Some(std::ffi::CStr::from_ptr(app_id_ptr).to_string_lossy().into_owned())
- };
-
- let title_ptr = (*win).get_title();
- let title = if title_ptr.is_null() {
- None
- } else {
- Some(std::ffi::CStr::from_ptr(title_ptr).to_string_lossy().into_owned())
- };
-
+ let app_id = (*win).get_app_id_string();
if app_id.as_deref() == Some("cce-status-interface") {
return crate::tiling::TilingMode::Fullscreen;
}
@@ -553,17 +562,8 @@ impl WindowManager {
return crate::tiling::TilingMode::Floating;
}
- // Match mode rules
- for rule in &self.mode_rules {
- let match_app = rule.app_id_pattern == "*"
- || app_id.as_ref().map_or(false, |aid| aid.contains(&rule.app_id_pattern));
- let match_title = rule.title_pattern.as_ref().map_or(true, |tp| {
- title.as_ref().map_or(false, |t| t.contains(tp))
- });
-
- if match_app && match_title {
- return rule.mode;
- }
+ if let Some(rule) = self.get_rule_for_window(win) {
+ return rule.mode;
}
// Check tag layouts
@@ -581,10 +581,8 @@ impl WindowManager {
log::info!("Monolithic arrange_views triggered. Windows: {}", self.windows.count());
for (idx, &win_ptr) in self.windows.iter().enumerate() {
if win_ptr.is_null() { continue; }
- let title_ptr = (*win_ptr).get_title();
- let title = if title_ptr.is_null() { "None".to_string() } else { std::ffi::CStr::from_ptr(title_ptr).to_string_lossy().into_owned() };
- let aid_ptr = (*win_ptr).get_app_id();
- let aid = if aid_ptr.is_null() { "None".to_string() } else { std::ffi::CStr::from_ptr(aid_ptr).to_string_lossy().into_owned() };
+ let title = (*win_ptr).get_title_string().unwrap_or_else(|| "None".to_string());
+ let aid = (*win_ptr).get_app_id_string().unwrap_or_else(|| "None".to_string());
log::info!(" window #{}: title={:?}, app_id={:?}, state={:?}, closed={}", idx, title, aid, (*win_ptr).state, (*win_ptr).closed);
}
@@ -640,6 +638,7 @@ impl WindowManager {
let mut tiled_windows: Vec<*mut Window> = Vec::new();
let mut floating_windows: Vec<*mut Window> = Vec::new();
+ let mut side_panel_windows: Vec<*mut Window> = Vec::new();
for &win_ptr in self.windows.iter() {
if (*win_ptr).closed {
@@ -659,13 +658,65 @@ impl WindowManager {
let mode = self.get_mode_for_window(win_ptr);
(*win_ptr).tiling_mode = mode;
+ // Apply ModeRule SSD configuration if defined and not locked
+ if !(*win_ptr).mode_locked {
+ if let Some(rule) = self.get_rule_for_window(win_ptr) {
+ if let Some(rule_ssd) = rule.ssd {
+ (*win_ptr).wm_requested.ssd = rule_ssd;
+ }
+ }
+ }
+
+ let app_id = (*win_ptr).get_app_id_string();
+
+ if app_id.as_deref() == Some("cce-status-interface") {
+ let wlr_box = (*output).sent.box_layout();
+ (*win_ptr).rendering_requested.x = wlr_box.x;
+ (*win_ptr).rendering_requested.y = wlr_box.y;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
+ width: wlr_box.width as u32,
+ height: wlr_box.height as u32,
+ });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
+ width: wlr_box.width as u32,
+ height: wlr_box.height as u32,
+ };
+ (*win_ptr).wm_requested.tiled = 0;
+ (*win_ptr).wm_requested.ssd = false;
+ continue;
+ }
+
if mode == crate::tiling::TilingMode::Floating || mode == crate::tiling::TilingMode::Popup {
floating_windows.push(win_ptr);
+ } else if mode == crate::tiling::TilingMode::SidePanel {
+ side_panel_windows.push(win_ptr);
} else {
tiled_windows.push(win_ptr);
}
}
+ let side_panel_win = side_panel_windows.first().copied();
+ let mut side_panel_w = 0;
+ let mut shift_x = 0;
+ if let Some(sp_win) = side_panel_win {
+ let hint_min_w = (*sp_win).wm_scheduled.dimensions_hint.min_width as i32;
+ side_panel_w = if hint_min_w > 32 {
+ std::cmp::max(self.layout.side_panel_width, hint_min_w)
+ } else {
+ self.layout.side_panel_width
+ };
+ if self.layout.side_panel_behavior != "above" {
+ shift_x = side_panel_w + self.layout.side_panel_border_gap;
+ }
+ }
+
+ let tiled_usable_w = (usable_w - shift_x).max(1);
+ let tiled_usable_x = if self.layout.side_panel_position == "right" || self.layout.side_panel_behavior == "above" {
+ usable_x
+ } else {
+ usable_x + shift_x
+ };
+
let n_tiled = tiled_windows.len() as i32;
let current_layout = if n_tiled > 0 {
self.get_mode_for_window(tiled_windows[0])
@@ -686,19 +737,19 @@ impl WindowManager {
let (x, y, w, h) = match current_layout {
crate::tiling::TilingMode::Cascade => {
crate::tiling::tile_cascade(
- usable_w, usable_h, gap, gap_top, gap_left, gap_right, gap_bottom,
+ tiled_usable_w, usable_h, gap, gap_top, gap_left, gap_right, gap_bottom,
bw, cascade_offset, bar_height, n_tiled, idx as i32
)
}
crate::tiling::TilingMode::Grid => {
crate::tiling::tile_grid(
- usable_w, usable_h, gap, gap_top, gap_left, gap_right, gap_bottom,
+ tiled_usable_w, usable_h, gap, gap_top, gap_left, gap_right, gap_bottom,
bw, bar_height, n_tiled, idx as i32
)
}
crate::tiling::TilingMode::Fullscreen => {
crate::tiling::tile_fullscreen(
- usable_w, usable_h, gap_top, gap_left, gap_right, gap_bottom,
+ tiled_usable_w, usable_h, gap_top, gap_left, gap_right, gap_bottom,
bw, bar_height
)
}
@@ -707,7 +758,7 @@ impl WindowManager {
}
};
- let final_x = usable_x + x;
+ let final_x = tiled_usable_x + x;
let final_y = usable_y + y;
(*win_ptr).rendering_requested.x = final_x;
@@ -724,7 +775,7 @@ impl WindowManager {
(*win_ptr).wm_requested.ssd = true;
let is_focused = win_ptr == focused_window;
- let (r, g, b, mut a) = if is_focused {
+ let (r, g_val, b, mut a) = if is_focused {
(
self.layout.border_r,
self.layout.border_g,
@@ -749,7 +800,7 @@ impl WindowManager {
edges: crate::window::Edges { top: true, bottom: true, left: true, right: true },
width: bw as u32,
r,
- g,
+ g: g_val,
b,
a,
};
@@ -758,10 +809,59 @@ impl WindowManager {
(*win_ptr).rendering_requested.opacity = if is_focused { 1.0f32 } else { 0.85f32 };
}
+ let g = self.layout.side_panel_border_gap;
+ let dec_h = std::cmp::max(bw, 16);
+ for (sp_idx, &win_ptr) in side_panel_windows.iter().enumerate() {
+ if sp_idx == 0 {
+ let sp_x = if self.layout.side_panel_position == "right" {
+ usable_x + usable_w - side_panel_w - g + bw
+ } else {
+ usable_x + g + bw
+ };
+ let sp_y = bar_height + usable_y + dec_h + g;
+ let sp_h = (usable_h - bar_height - (dec_h + bw) - 2 * g).max(1);
+ let sp_w = (side_panel_w - bw * 2).max(1);
+
+ (*win_ptr).rendering_requested.x = sp_x;
+ (*win_ptr).rendering_requested.y = sp_y;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
+ width: sp_w as u32,
+ height: sp_h as u32,
+ });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
+ width: sp_w as u32,
+ height: sp_h as u32,
+ };
+ (*win_ptr).wm_requested.tiled = 1 | 2 | 4 | 8;
+ (*win_ptr).wm_requested.ssd = true;
+
+ let opacity_factor = self.layout.side_panel_border_opacity as f32 / 100.0;
+ let is_focused = win_ptr == focused_window;
+ let r = self.layout.border_r;
+ let g_color = self.layout.border_g;
+ let b = self.layout.border_b;
+ let a = (self.layout.border_a as f32 * opacity_factor) as u32;
+
+ (*win_ptr).rendering_requested.border = crate::window::Border {
+ edges: crate::window::Edges { top: true, bottom: true, left: true, right: true },
+ width: bw as u32,
+ r,
+ g: g_color,
+ b,
+ a,
+ };
+ (*win_ptr).rendering_requested.blur = self.layout.window_blur;
+ (*win_ptr).rendering_requested.opacity = if is_focused { 1.0f32 } else { 0.85f32 * opacity_factor };
+ } else {
+ floating_windows.push(win_ptr);
+ }
+ }
+
+ let mut idx_floating = 0;
for &win_ptr in &floating_windows {
let is_focused = win_ptr == focused_window;
let r = self.layout.border_r;
- let g = self.layout.border_g;
+ let g_val = self.layout.border_g;
let b = self.layout.border_b;
let a = self.layout.border_a;
@@ -769,12 +869,97 @@ impl WindowManager {
edges: crate::window::Edges { top: true, bottom: true, left: true, right: true },
width: bw as u32,
r,
- g,
+ g: g_val,
b,
a,
};
(*win_ptr).rendering_requested.blur = self.layout.window_blur;
(*win_ptr).rendering_requested.opacity = if is_focused { 1.0f32 } else { 0.90f32 };
+
+ if (*win_ptr).tiling_mode == crate::tiling::TilingMode::Popup {
+ let hint_min_w = (*win_ptr).wm_scheduled.dimensions_hint.min_width as i32;
+ let hint_min_h = (*win_ptr).wm_scheduled.dimensions_hint.min_height as i32;
+ let fw = if (*win_ptr).box_geom.width > 0 {
+ (*win_ptr).box_geom.width as i32
+ } else if hint_min_w > 32 {
+ hint_min_w
+ } else {
+ 360
+ };
+ let fh = if (*win_ptr).box_geom.height > 0 {
+ (*win_ptr).box_geom.height as i32
+ } else if hint_min_h > 32 {
+ hint_min_h
+ } else {
+ 100
+ };
+ let fx = usable_x + usable_w - fw - self.layout.gap_right;
+ let fy = usable_y + bar_height + self.layout.gap_top;
+
+ (*win_ptr).rendering_requested.x = fx;
+ (*win_ptr).rendering_requested.y = fy;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
+ width: fw as u32,
+ height: fh as u32,
+ });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
+ width: fw as u32,
+ height: fh as u32,
+ };
+ } else {
+ let fbw = self.layout.floating_border_width;
+ let mut fw = if (*win_ptr).box_geom.width > 0 {
+ (*win_ptr).box_geom.width as i32
+ } else if (*win_ptr).wm_scheduled.dimensions_hint.min_width > 32 {
+ (*win_ptr).wm_scheduled.dimensions_hint.min_width as i32
+ } else {
+ usable_w * 2 / 3
+ };
+ let mut fh = if (*win_ptr).box_geom.height > 0 {
+ (*win_ptr).box_geom.height as i32
+ } else if (*win_ptr).wm_scheduled.dimensions_hint.min_height > 32 {
+ (*win_ptr).wm_scheduled.dimensions_hint.min_height as i32
+ } else {
+ usable_h * 2 / 3
+ };
+
+ let max_w = usable_w - gap_left - gap_right;
+ let max_h = usable_h - bar_height - gap_top - gap_bottom;
+ fw = fw.clamp(1, max_w.max(1));
+ fh = fh.clamp(1, max_h.max(1));
+
+ let mut fx = if (*win_ptr).rendering_requested.x != 0 || (*win_ptr).rendering_requested.y != 0 {
+ (*win_ptr).rendering_requested.x
+ } else {
+ usable_x + gap_left + fbw + cascade_offset * idx_floating
+ };
+ let mut fy = if (*win_ptr).rendering_requested.x != 0 || (*win_ptr).rendering_requested.y != 0 {
+ (*win_ptr).rendering_requested.y
+ } else {
+ usable_y + gap_left + fbw + bar_height + gap_top + cascade_offset * idx_floating
+ };
+
+ let min_x = usable_x + gap_left;
+ let max_x = usable_x + usable_w - gap_right - fw;
+ let min_y = usable_y + bar_height + gap_top;
+ let max_y = usable_y + usable_h - gap_bottom - fh;
+
+ fx = fx.clamp(min_x, max_x.max(min_x));
+ fy = fy.clamp(min_y, max_y.max(min_y));
+
+ (*win_ptr).rendering_requested.x = fx;
+ (*win_ptr).rendering_requested.y = fy;
+ (*win_ptr).wm_requested.dimensions = Some(crate::window::Dimensions {
+ width: fw as u32,
+ height: fh as u32,
+ });
+ (*win_ptr).wm_requested.bounds = crate::window::Dimensions {
+ width: fw as u32,
+ height: fh as u32,
+ };
+
+ idx_floating += 1;
+ }
}
}
self.update_status();
@@ -824,11 +1009,42 @@ impl WindowManager {
}
}
+ pub unsafe fn keep_status_bar_on_top(&mut self) {
+ let mut status_bar_windows = Vec::new();
+ for &win_ptr in self.windows.iter() {
+ if win_ptr.is_null() || (*win_ptr).closed {
+ continue;
+ }
+ if !matches!((*win_ptr).state, crate::window::WindowState::Mapped) {
+ continue;
+ }
+ if let Some(app_id) = (*win_ptr).get_app_id_string() {
+ if app_id == "cce-status-interface" {
+ let link = &(*win_ptr).node.link;
+ if !link.prev.is_null() && !link.next.is_null() {
+ status_bar_windows.push(win_ptr);
+ }
+ }
+ }
+ }
+ for win_ptr in status_bar_windows {
+ let node_link = &mut (*win_ptr).node.link as *mut ffi::wl_list as *mut WlList;
+ let list_head = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ if (*node_link).next != list_head {
+ crate::server::wl_list_remove(node_link);
+ crate::server::wl_list_insert((*list_head).prev, node_link);
+ }
+ }
+ }
+
pub unsafe fn raise_window(&mut self, window: *mut Window) {
let node_link = &mut (*window).node.link as *mut ffi::wl_list as *mut WlList;
- crate::server::wl_list_remove(node_link);
let list_head = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
- crate::server::wl_list_insert((*list_head).prev, node_link);
+ if (*node_link).next != list_head {
+ crate::server::wl_list_remove(node_link);
+ crate::server::wl_list_insert((*list_head).prev, node_link);
+ }
+ self.keep_status_bar_on_top();
}
pub unsafe fn execute_action(&mut self, action: &crate::config::Action, command: Option<&str>) {
@@ -865,18 +1081,8 @@ impl WindowManager {
let mut matched_win: *mut Window = std::ptr::null_mut();
for &w in self.windows.iter() {
if !w.is_null() && !(*w).closed {
- let aid_ptr = (*w).get_app_id();
- let aid = if aid_ptr.is_null() {
- None
- } else {
- Some(std::ffi::CStr::from_ptr(aid_ptr).to_string_lossy().into_owned())
- };
- let title_ptr = (*w).get_title();
- let title = if title_ptr.is_null() {
- None
- } else {
- Some(std::ffi::CStr::from_ptr(title_ptr).to_string_lossy().into_owned())
- };
+ let aid = (*w).get_app_id_string();
+ let title = (*w).get_title_string();
let mut match_aid = false;
if let Some(ref aid_str) = aid {
@@ -900,12 +1106,7 @@ impl WindowManager {
}
if !matched_win.is_null() {
- let aid_ptr = (*matched_win).get_app_id();
- let aid = if aid_ptr.is_null() {
- String::new()
- } else {
- std::ffi::CStr::from_ptr(aid_ptr).to_string_lossy().into_owned()
- };
+ let aid = (*matched_win).get_app_id_string().unwrap_or_default();
log::info!("toggle: closing window {:?}", aid);
(*matched_win).close();
if let Some(seat) = self.first_seat() {
@@ -942,6 +1143,58 @@ impl WindowManager {
}
}
}
+ Action::FocusNext | Action::FocusPrev => {
+ if let Some(seat) = self.first_seat() {
+ let focused_win = if let crate::seat::Focus::Window(fw) = (*seat).focused {
+ fw
+ } else {
+ std::ptr::null_mut()
+ };
+
+ let render_list = &mut self.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
+ let mut curr = (*render_list).next;
+ let mut visible_windows = Vec::new();
+ while curr != render_list {
+ let next = (*curr).next;
+ let node = crate::container_of!(curr, crate::wm_node::WmNode, link);
+ if let crate::wm_node::WmNodeType::Window(window) = (*node).get() {
+ if !window.is_null() && !(*window).closed && !(*window).minimized && ((*window).tags & self.active_tags) != 0 {
+ let is_status_bar = (*window).get_app_id_string()
+ .map_or(false, |aid| aid == "cce-status-interface");
+ if !is_status_bar {
+ visible_windows.push(window);
+ }
+ }
+ }
+ curr = next;
+ }
+
+ let n = visible_windows.len();
+ if n > 0 {
+ let current_idx = visible_windows.iter().position(|&w| w == focused_win);
+ let target_idx = match current_idx {
+ Some(idx) => {
+ if *action == Action::FocusNext {
+ (idx + 1) % n
+ } else {
+ (idx + n - 1) % n
+ }
+ }
+ None => {
+ if *action == Action::FocusNext {
+ 0
+ } else {
+ n - 1
+ }
+ }
+ };
+ let target_win = visible_windows[target_idx];
+ (*seat).focus(crate::seat::Focus::Window(target_win));
+ self.raise_window(target_win);
+ self.dirty_windowing();
+ }
+ }
+ }
Action::Exit => {
log::info!("monolithic execute_action: Exit requested");
ffi::wl_display_terminate((*self.server).wl_server);
@@ -1047,17 +1300,7 @@ impl WindowManager {
}
}
}
- Action::Reload | Action::Restart => {
- if let Some(cfg_path) = crate::config::default_config_path() {
- log::info!("Reloading configuration from {}", cfg_path);
- if let Err(e) = crate::config::parse_config(&cfg_path, self) {
- log::error!("Failed to reload config: {}", e);
- } else {
- self.dirty_windowing();
- }
- }
- }
- Action::View1 | Action::View2 | Action::View3 | Action::View4 => {
+ Action::View1 | Action::View2 | Action::View3 | Action::View4 => {
let tag = match action {
Action::View1 => 1,
Action::View2 => 2,
@@ -1197,18 +1440,8 @@ impl WindowManager {
let mut target: *mut Window = std::ptr::null_mut();
for &w in self.windows.iter() {
if !w.is_null() && !(*w).closed && !(*w).minimized && ((*w).tags & self.active_tags) != 0 {
- let aid_ptr = (*w).get_app_id();
- let aid = if aid_ptr.is_null() {
- None
- } else {
- Some(std::ffi::CStr::from_ptr(aid_ptr).to_string_lossy().into_owned())
- };
- let title_ptr = (*w).get_title();
- let title = if title_ptr.is_null() {
- None
- } else {
- Some(std::ffi::CStr::from_ptr(title_ptr).to_string_lossy().into_owned())
- };
+ let aid = (*w).get_app_id_string();
+ let title = (*w).get_title_string();
let mut match_found = false;
if let Some(ref aid_str) = aid {
@@ -1239,10 +1472,6 @@ impl WindowManager {
self.execute_action(&crate::config::Action::Exit, None);
"ok\n".to_string()
}
- "reload" | "restart" => {
- self.execute_action(&crate::config::Action::Reload, None);
- "ok\n".to_string()
- }
"retile" => {
self.dirty_windowing();
"ok\n".to_string()
@@ -1263,17 +1492,26 @@ impl WindowManager {
"gap_left" => { if let Ok(v) = val.parse::<i32>() { self.layout.gap_left = v; } }
"gap_right" => { if let Ok(v) = val.parse::<i32>() { self.layout.gap_right = v; } }
"gap_bottom" => { if let Ok(v) = val.parse::<i32>() { self.layout.gap_bottom = v; } }
- "offset" => { if let Ok(v) = val.parse::<i32>() { self.layout.cascade_offset = v; } }
+ "offset" | "cascade_offset" => { if let Ok(v) = val.parse::<i32>() { self.layout.cascade_offset = v; } }
"grid_gap" => { if let Ok(v) = val.parse::<i32>() { self.layout.grid_gap = v; } }
"bar_height" => { if let Ok(v) = val.parse::<i32>() { self.layout.bar_height = v; } }
"border_width" => { if let Ok(v) = val.parse::<i32>() { self.layout.border_width = v; } }
"fullscreen_border_width" => { if let Ok(v) = val.parse::<i32>() { self.layout.fullscreen_border_width = v; } }
+ "cascade_border_width" => { if let Ok(v) = val.parse::<i32>() { self.layout.cascade_border_width = v; } }
+ "grid_border_width" => { if let Ok(v) = val.parse::<i32>() { self.layout.grid_border_width = v; } }
+ "floating_border_width" => { if let Ok(v) = val.parse::<i32>() { self.layout.floating_border_width = v; } }
+ "transition_duration" => { if let Ok(v) = val.parse::<i32>() { self.layout.transition_duration = v; } }
"border_color" => {
let border_color_val = crate::config::parse_hex_color(val);
self.layout.border_r = (border_color_val >> 16) & 0xFF;
self.layout.border_g = (border_color_val >> 8) & 0xFF;
self.layout.border_b = border_color_val & 0xFF;
}
+ "side_panel_width" => { if let Ok(v) = val.parse::<i32>() { self.layout.side_panel_width = v; } }
+ "side_panel_behavior" => { self.layout.side_panel_behavior = val.to_string(); }
+ "side_panel_position" => { self.layout.side_panel_position = val.to_string(); }
+ "side_panel_border_gap" => { if let Ok(v) = val.parse::<i32>() { self.layout.side_panel_border_gap = v; } }
+ "side_panel_border_opacity" => { if let Ok(v) = val.parse::<i32>() { self.layout.side_panel_border_opacity = v; } }
_ => return format!("error: unknown layout key: {}\n", key),
}
self.dirty_windowing();
@@ -1352,14 +1590,14 @@ unsafe extern "C" fn dirty_idle_callback(data: *mut std::ffi::c_void) {
}
(*wm).dirty_idle = std::ptr::null_mut();
- if (*wm).scheduled.dirty || (*wm).scheduled.dirty_lazy {
- (*wm).scheduled.dirty = true;
- (*wm).scheduled.dirty_lazy = false;
- (*wm).manage_start();
- }
-
- if (*wm).rendering_scheduled.dirty && matches!((*wm).state, WindowManagerState::Idle) {
- (*wm).render_start();
+ if matches!((*wm).state, WindowManagerState::Idle) {
+ if (*wm).scheduled.dirty || (*wm).scheduled.dirty_lazy {
+ (*wm).scheduled.dirty = true;
+ (*wm).scheduled.dirty_lazy = false;
+ (*wm).manage_start();
+ } else if (*wm).rendering_scheduled.dirty {
+ (*wm).render_start();
+ }
}
}
@@ -1519,6 +1757,15 @@ unsafe extern "C" fn bind(
return;
}
+ let mut pid = 0;
+ let mut uid = 0;
+ let mut gid = 0;
+ ffi::wl_client_get_credentials(client, &mut pid, &mut uid, &mut gid);
+ let cmdline = std::fs::read_to_string(format!("/proc/{}/cmdline", pid))
+ .unwrap_or_default()
+ .replace('\0', " ");
+ log::info!("Client binding river_window_manager_v1: PID={}, cmdline='{}'", pid, cmdline);
+
let resource = ffi::wl_resource_create(client, &ffi::river_window_manager_v1_interface, version as i32, id);
if resource.is_null() {
ffi::wl_client_post_no_memory(client);
@@ -1527,6 +1774,7 @@ unsafe extern "C" fn bind(
}
if !(*wm).object.is_null() {
+ log::warn!("river_window_manager_v1 already bound, rejecting new client PID={}", pid);
ffi::wl_resource_post_event(resource, ffi::RIVER_WINDOW_MANAGER_V1_UNAVAILABLE);
ffi::wl_resource_set_implementation(
resource,
diff --git a/src/server/wm_node.rs b/src/server/wm_node.rs
index d7af93d..de11417 100644
--- a/src/server/wm_node.rs
+++ b/src/server/wm_node.rs
@@ -142,10 +142,12 @@ unsafe extern "C" fn node_place_top(
if !(*server).wm.ensure_rendering() {
return;
}
- wl_list_remove(&mut (*node).link as *mut ffi::wl_list as *mut WlList);
-
+ let node_link = &mut (*node).link as *mut ffi::wl_list as *mut WlList;
let list_head = &mut (*server).wm.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
- wl_list_insert((*list_head).prev, &mut (*node).link as *mut ffi::wl_list as *mut WlList);
+ if (*node_link).next != list_head {
+ wl_list_remove(node_link);
+ wl_list_insert((*list_head).prev, node_link);
+ }
}
unsafe extern "C" fn node_place_bottom(
@@ -163,10 +165,12 @@ unsafe extern "C" fn node_place_bottom(
if !(*server).wm.ensure_rendering() {
return;
}
- wl_list_remove(&mut (*node).link as *mut ffi::wl_list as *mut WlList);
-
+ let node_link = &mut (*node).link as *mut ffi::wl_list as *mut WlList;
let list_head = &mut (*server).wm.rendering_requested.list as *mut ffi::wl_list as *mut WlList;
- wl_list_insert(list_head, &mut (*node).link as *mut ffi::wl_list as *mut WlList);
+ if (*node_link).prev != list_head {
+ wl_list_remove(node_link);
+ wl_list_insert(list_head, node_link);
+ }
}
unsafe extern "C" fn node_place_above(
diff --git a/src/server/xdg_toplevel.rs b/src/server/xdg_toplevel.rs
index 0843c46..0726c28 100644
--- a/src/server/xdg_toplevel.rs
+++ b/src/server/xdg_toplevel.rs
@@ -411,7 +411,6 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
let capture_node = &mut (*(*window).capture_scene).tree as *mut ffi::wlr_scene_tree as *mut ffi::wlr_scene_node;
let mut geom = std::mem::zeroed();
ffi::river_wlr_xdg_surface_get_geometry(base, &mut geom);
- override_geometry_if_needed(window, &mut geom);
ffi::wlr_scene_subsurface_tree_set_clip(capture_node, &geom);
let mut min_w = 0;
@@ -443,7 +442,6 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
let old_geometry = (*toplevel).geometry;
let mut new_geometry = std::mem::zeroed();
ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
- override_geometry_if_needed(window, &mut new_geometry);
(*toplevel).geometry = new_geometry;
let size_changed = new_geometry.width != old_geometry.width || new_geometry.height != old_geometry.height;
@@ -464,7 +462,6 @@ unsafe extern "C" fn handle_commit(listener: *mut ffi::wl_listener, _data: *mut
ConfigureState::Acked | ConfigureState::TimedOutAcked => {
let mut new_geometry = std::mem::zeroed();
ffi::river_wlr_xdg_surface_get_geometry(base, &mut new_geometry);
- override_geometry_if_needed(window, &mut new_geometry);
(*toplevel).geometry = new_geometry;
(*window).rendering_scheduled.width = new_geometry.width as u32;
@@ -658,45 +655,4 @@ impl XdgDecoration {
}
}
-pub unsafe fn override_geometry_if_needed(window: *mut crate::window::Window, geom: &mut ffi::wlr_box) {
- let is_chromium = (*window).is_chromium_electron();
- if is_chromium {
- log::info!(
- "CHROMIUM COMMIT before override: x={} y={} w={} h={} wm_ssd={}",
- geom.x,
- geom.y,
- geom.width,
- geom.height,
- (*window).wm_requested.ssd,
- );
- }
- if (*window).wm_requested.ssd && is_chromium {
- if geom.x == 10 || geom.y == 10 {
- (*window).margin_x = 10;
- (*window).margin_y = 10;
- } else if geom.x == 6 || geom.y == 6 {
- (*window).margin_x = 6;
- (*window).margin_y = 6;
- }
-
- let margin_x = if (*window).margin_x > 0 { (*window).margin_x } else { 10 };
- let margin_y = if (*window).margin_y > 0 { (*window).margin_y } else { 10 };
-
- let is_antigravity = (*window).is_antigravity();
- let (left_margin, right_margin) = if margin_x == 10 && !is_antigravity { (10, 34) } else { (margin_x, margin_x) };
- let (top_margin, bottom_margin) = if margin_y == 10 && !is_antigravity { (10, 34) } else { (margin_y, margin_y) };
-
- geom.x = left_margin;
- geom.y = top_margin;
- geom.width = geom.width.saturating_sub(left_margin + right_margin);
- geom.height = geom.height.saturating_sub(top_margin + bottom_margin);
-
- log::info!(
- "CHROMIUM COMMIT after override: x={} y={} w={} h={}",
- geom.x,
- geom.y,
- geom.width,
- geom.height,
- );
- }
-}
+