Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Implement expose mode border highlight and pointer focus transitions
src/server/cursor.rs | 51 +++++++++++++++++++++++++
src/server/window.rs | 7 ++++
src/server/window_manager.rs | 89 +++++++++++++++++++++++++++++++++-----------
3 files changed, 126 insertions(+), 21 deletions(-)
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 3a1b302..fdbe3a1 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -458,8 +458,17 @@ impl Cursor {
}
if let SceneNodeDataVal::Window(window) = result.data {
+ if (*server).wm.expose_active {
+ let old_hovered = (*server).wm.expose_hovered_window;
+ if old_hovered != window {
+ (*server).wm.expose_hovered_window = window;
+ (*server).wm.dirty_windowing();
+ }
+ }
+
if (*window).tiling_mode != crate::tiling::TilingMode::Popup
&& (*window).tiling_mode != crate::tiling::TilingMode::Fullscreen
+ && !(*server).wm.expose_active
{
match get_border_zone(window, lx, ly) {
BorderZone::Resize(edges) => {
@@ -560,6 +569,48 @@ unsafe extern "C" fn handle_button(listener: *mut ffi::wl_listener, data: *mut s
return;
}
+ // --- EXPOSE MODE LEFT CLICK HANDLING ---
+ if (*(*seat).server).wm.expose_active && (*event).button == 0x110 {
+ let lx = cursor.x();
+ let ly = cursor.y();
+ let server = seat.server;
+ let mut clicked_win: *mut crate::window::Window = std::ptr::null_mut();
+ if let Some(result) = (*server).scene.at(lx, ly) {
+ if let SceneNodeDataVal::Window(window) = result.data {
+ clicked_win = window;
+ }
+ }
+
+ // Exit expose mode
+ (*server).wm.expose_active = false;
+ log::info!("Expose mode exited via left click");
+
+ if !clicked_win.is_null() {
+ seat.focus(Focus::Window(clicked_win));
+ if !seat.object.is_null() && !(*clicked_win).object.is_null() {
+ ffi::wl_resource_post_event(seat.object, 4, (*clicked_win).object);
+ }
+ } else {
+ let initial = (*server).wm.expose_initial_focus;
+ if (*server).wm.window_is_valid(initial) {
+ seat.focus(Focus::Window(initial));
+ if !seat.object.is_null() && !(*initial).object.is_null() {
+ ffi::wl_resource_post_event(seat.object, 4, (*initial).object);
+ }
+ } else {
+ seat.focus(Focus::None);
+ }
+ }
+
+ (*server).wm.expose_hovered_window = std::ptr::null_mut();
+ (*server).wm.expose_initial_focus = std::ptr::null_mut();
+ (*server).wm.dirty_windowing();
+
+ // Insert into pressed so we handle the release correctly (standard wayland behavior)
+ cursor.pressed.insert((*event).button, None);
+ return;
+ }
+
let wlr_keyboard = ffi::river_wlr_seat_get_keyboard(seat.wlr_seat);
let modifiers = if !wlr_keyboard.is_null() {
ffi::wlr_keyboard_get_modifiers(wlr_keyboard)
diff --git a/src/server/window.rs b/src/server/window.rs
index 8417584..02c484a 100644
--- a/src/server/window.rs
+++ b/src/server/window.rs
@@ -669,6 +669,13 @@ impl Window {
curr = next;
}
+ if (*(*window).server).wm.expose_hovered_window == window {
+ (*(*window).server).wm.expose_hovered_window = std::ptr::null_mut();
+ }
+ if (*(*window).server).wm.expose_initial_focus == window {
+ (*(*window).server).wm.expose_initial_focus = std::ptr::null_mut();
+ }
+
// Destroy decorations
for decorations in [&mut (*window).decorations_above as *mut ffi::wl_list, &mut (*window).decorations_below as *mut ffi::wl_list] {
let list_head = decorations as *mut WlList;
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index f33e1a8..fce5e77 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -71,6 +71,8 @@ pub struct WindowManager {
pub input_rules: Vec<crate::config::InputDeviceConfigRule>,
pub input_config: crate::config::InputConfig,
pub expose_active: bool,
+ pub expose_hovered_window: *mut Window,
+ pub expose_initial_focus: *mut Window,
pub last_status_update: std::cell::RefCell<Option<crate::status_server::StatusUpdate>>,
}
@@ -129,6 +131,8 @@ impl WindowManager {
self.input_rules = Vec::new();
self.input_config = crate::config::InputConfig::default();
self.expose_active = false;
+ self.expose_hovered_window = std::ptr::null_mut();
+ self.expose_initial_focus = std::ptr::null_mut();
self.last_status_update = std::cell::RefCell::new(None);
ffi::wl_list_init(&mut self.sent.outputs);
@@ -920,32 +924,47 @@ impl WindowManager {
}
let is_focused = win_ptr == focused_window;
- let (r, g_val, b, mut a) = if is_focused {
- (
- self.layout.border_r,
- self.layout.border_g,
- self.layout.border_b,
- self.layout.border_a,
- )
- } else {
- let mut tiled_stack = tiled_windows.clone();
- tiled_stack.sort_by_key(|&w| stack_order.iter().position(|&x| x == w).unwrap_or(usize::MAX));
- let pos = tiled_stack.iter().position(|&w| w == win_ptr).unwrap_or(0);
- let depth = n_tiled - 1 - pos as i32;
- let mut factor = 1.0_f64;
- for _ in 0..depth {
- factor *= 0.70; // UNFOCUSED_DEPTH_FACTOR
+ let (win_bw, r, g_val, b, a) = if self.expose_active {
+ if self.window_is_valid(self.expose_hovered_window) && win_ptr == self.expose_hovered_window {
+ (
+ bw as u32,
+ self.layout.border_r,
+ self.layout.border_g,
+ self.layout.border_b,
+ self.layout.border_a,
+ )
+ } else {
+ (0, 0, 0, 0, 0)
}
- let r = blend_channel(self.layout.background_r, self.layout.border_r, factor);
- let g = blend_channel(self.layout.background_g, self.layout.border_g, factor);
- let b = blend_channel(self.layout.background_b, self.layout.border_b, factor);
- let a = blend_channel(self.layout.background_a, self.layout.border_a, factor);
- (r, g, b, a)
+ } else {
+ let (r, g_val, b, a) = if is_focused {
+ (
+ self.layout.border_r,
+ self.layout.border_g,
+ self.layout.border_b,
+ self.layout.border_a,
+ )
+ } else {
+ let mut tiled_stack = tiled_windows.clone();
+ tiled_stack.sort_by_key(|&w| stack_order.iter().position(|&x| x == w).unwrap_or(usize::MAX));
+ let pos = tiled_stack.iter().position(|&w| w == win_ptr).unwrap_or(0);
+ let depth = n_tiled - 1 - pos as i32;
+ let mut factor = 1.0_f64;
+ for _ in 0..depth {
+ factor *= 0.70; // UNFOCUSED_DEPTH_FACTOR
+ }
+ let r = blend_channel(self.layout.background_r, self.layout.border_r, factor);
+ let g = blend_channel(self.layout.background_g, self.layout.border_g, factor);
+ let b = blend_channel(self.layout.background_b, self.layout.border_b, factor);
+ let a = blend_channel(self.layout.background_a, self.layout.border_a, factor);
+ (r, g, b, a)
+ };
+ (bw as u32, r, g_val, b, a)
};
(*win_ptr).rendering_requested.border = crate::window::Border {
edges: crate::window::Edges { top: true, bottom: true, left: true, right: true },
- width: bw as u32,
+ width: win_bw,
r,
g: g_val,
b,
@@ -1169,6 +1188,13 @@ impl WindowManager {
std::ptr::null_mut()
}
+ pub unsafe fn window_is_valid(&self, win: *mut Window) -> bool {
+ if win.is_null() {
+ return false;
+ }
+ self.windows.iter().any(|&w| w == win)
+ }
+
pub unsafe fn focused_layer_surface(&self) -> *mut ffi::wlr_surface {
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;
@@ -1594,6 +1620,27 @@ impl WindowManager {
Action::Expose => {
self.expose_active = !self.expose_active;
log::info!("Expose mode toggled: {}", self.expose_active);
+ if self.expose_active {
+ self.expose_initial_focus = self.focused_window();
+ let mut hovered_win = std::ptr::null_mut();
+ if let Some(seat) = self.first_seat() {
+ let cursor = &(*seat).cursor;
+ let lx = cursor.x();
+ let ly = cursor.y();
+ if let Some(result) = (*self.server).scene.at(lx, ly) {
+ if let crate::scene_node_data::SceneNodeDataVal::Window(window) = result.data {
+ hovered_win = window;
+ }
+ }
+ }
+ if hovered_win.is_null() {
+ hovered_win = self.expose_initial_focus;
+ }
+ self.expose_hovered_window = hovered_win;
+ } else {
+ self.expose_hovered_window = std::ptr::null_mut();
+ self.expose_initial_focus = std::ptr::null_mut();
+ }
self.dirty_windowing();
}
_ => {}