Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Add config reload action, bind it to super+shift+r, and add trackpad gesture config support
.antigravityignore | 6 +++
src/server/config.rs | 60 ++++++++++++++++++++-
src/server/cursor.rs | 124 +++++++++++++++++++++++++++++++++++++++++++
src/server/server.rs | 1 +
src/server/window_manager.rs | 17 ++++++
5 files changed, 207 insertions(+), 1 deletion(-)
diff --git a/.antigravityignore b/.antigravityignore
new file mode 100644
index 0000000..54cf910
--- /dev/null
+++ b/.antigravityignore
@@ -0,0 +1,6 @@
+/target
+.antigravitycli/
+.gemini/
+*.log
+check_errors.txt
+Cargo.lock
diff --git a/src/server/config.rs b/src/server/config.rs
index 1ba3c81..3200652 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -102,6 +102,7 @@ pub enum Action {
Move,
Resize,
Exit,
+ Reload,
Fullscreen,
LayoutNext,
ModeNext,
@@ -139,6 +140,18 @@ pub struct PointerBindConfig {
pub action: String,
}
+#[derive(Debug, Deserialize, Clone)]
+pub struct GestureBindConfig {
+ #[serde(default)]
+ pub mods: Option<String>,
+ #[serde(rename = "type")]
+ pub gesture_type: String,
+ pub fingers: u32,
+ pub direction: String,
+ pub action: String,
+ pub command: Option<String>,
+}
+
#[derive(Debug, Deserialize, Clone)]
pub struct StartupConfig {
pub exec: String,
@@ -163,6 +176,16 @@ pub struct PointerBind {
pub action: Action,
}
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct GestureBind {
+ pub mods: u32,
+ pub gesture_type: String,
+ pub fingers: u32,
+ pub direction: String,
+ pub action: Action,
+ pub command: Option<String>,
+}
+
#[derive(Debug, Deserialize, Clone)]
pub struct OutputConfig {
#[serde(default = "default_scale")]
@@ -213,6 +236,8 @@ pub struct Config {
pub device: Vec<InputDeviceConfigRule>,
#[serde(default)]
pub input: Option<InputConfig>,
+ #[serde(default)]
+ pub gesture_bind: Vec<GestureBindConfig>,
}
#[derive(Debug, Deserialize)]
@@ -404,6 +429,8 @@ pub fn parse_action(s: &str) -> Action {
Action::Close
} else if s == "exit" {
Action::Exit
+ } else if s == "reload" {
+ Action::Reload
} else if s == "focus-next" {
Action::FocusNext
} else if s == "focus-prev" {
@@ -673,6 +700,17 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
});
}
+ let super_shift_mod = parse_modifiers("super+shift");
+ let r_sym = parse_keysym("r");
+ if !state.keybinds.iter().any(|b| b.mods == super_shift_mod && b.keysym == r_sym) {
+ state.keybinds.push(Keybind {
+ mods: super_shift_mod,
+ keysym: r_sym,
+ action: Action::Reload,
+ command: None,
+ });
+ }
+
state.pointer_binds.clear();
for pb in &config.pointer_bind {
let mods = parse_modifiers(&pb.mods);
@@ -685,6 +723,25 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
});
}
+ state.gesture_binds.clear();
+ for gb in &config.gesture_bind {
+ let mods = gb.mods.as_ref().map(|m| parse_modifiers(m)).unwrap_or(0);
+ let action = parse_action(&gb.action);
+ let command = if action == Action::Spawn || action == Action::Toggle {
+ gb.command.clone()
+ } else {
+ None
+ };
+ state.gesture_binds.push(GestureBind {
+ mods,
+ gesture_type: gb.gesture_type.clone(),
+ fingers: gb.fingers,
+ direction: gb.direction.clone(),
+ action,
+ command,
+ });
+ }
+
state.mode_rules.clear();
for rule in config.mode_rule {
state.mode_rules.push(ModeRule {
@@ -712,9 +769,10 @@ pub fn parse_config(path: &str, state: &mut crate::window_manager::WindowManager
}
log::info!(
- "Parsed config: {} keybinds, {} pointer binds, {} startup programs",
+ "Parsed config: {} keybinds, {} pointer binds, {} gesture binds, {} startup programs",
state.keybinds.len(),
state.pointer_binds.len(),
+ state.gesture_binds.len(),
state.startup.len()
);
diff --git a/src/server/cursor.rs b/src/server/cursor.rs
index 90aed1f..3c073b7 100644
--- a/src/server/cursor.rs
+++ b/src/server/cursor.rs
@@ -41,6 +41,11 @@ pub struct Cursor {
pub hold_begin_listener: ffi::wl_listener,
pub hold_end_listener: ffi::wl_listener,
+
+ pub gesture_dx: f64,
+ pub gesture_dy: f64,
+ pub gesture_scale: f64,
+ pub gesture_triggered: bool,
}
impl Default for Cursor {
@@ -79,6 +84,11 @@ impl Default for Cursor {
hold_begin_listener: unsafe { std::mem::zeroed() },
hold_end_listener: unsafe { std::mem::zeroed() },
+
+ gesture_dx: 0.0,
+ gesture_dy: 0.0,
+ gesture_scale: 1.0,
+ gesture_triggered: false,
}
}
}
@@ -1031,6 +1041,10 @@ unsafe extern "C" fn handle_swipe_begin(listener: *mut ffi::wl_listener, data: *
let seat = &mut *cursor.seat;
seat.handle_activity();
+ cursor.gesture_dx = 0.0;
+ cursor.gesture_dy = 0.0;
+ cursor.gesture_triggered = false;
+
let server = seat.server;
let pointer_gestures = (*server).input_manager.pointer_gestures;
if !pointer_gestures.is_null() {
@@ -1050,6 +1064,56 @@ unsafe extern "C" fn handle_swipe_update(listener: *mut ffi::wl_listener, data:
let seat = &mut *cursor.seat;
seat.handle_activity();
+ if cursor.gesture_triggered {
+ return;
+ }
+
+ cursor.gesture_dx += (*event).dx;
+ cursor.gesture_dy += (*event).dy;
+
+ 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)
+ } else {
+ 0
+ };
+
+ let mut matched_action = crate::config::Action::None;
+ let mut matched_command = None;
+
+ for gb in &(*seat.server).wm.gesture_binds {
+ if gb.gesture_type == "swipe" && gb.fingers == (*event).fingers && gb.mods == modifiers {
+ let matched = match gb.direction.as_str() {
+ "left" => cursor.gesture_dx < -150.0,
+ "right" => cursor.gesture_dx > 150.0,
+ "up" => cursor.gesture_dy < -150.0,
+ "down" => cursor.gesture_dy > 150.0,
+ _ => false,
+ };
+ if matched {
+ matched_action = gb.action;
+ matched_command = gb.command.clone();
+ break;
+ }
+ }
+ }
+
+ if matched_action != crate::config::Action::None {
+ cursor.gesture_triggered = true;
+ (*seat.server).wm.execute_action(&matched_action, matched_command.as_deref());
+
+ let pointer_gestures = (*seat.server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_swipe_end(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ true, // cancelled: true
+ );
+ }
+ return;
+ }
+
let server = seat.server;
let pointer_gestures = (*server).input_manager.pointer_gestures;
if !pointer_gestures.is_null() {
@@ -1070,6 +1134,11 @@ unsafe extern "C" fn handle_swipe_end(listener: *mut ffi::wl_listener, data: *mu
let seat = &mut *cursor.seat;
seat.handle_activity();
+ if cursor.gesture_triggered {
+ cursor.gesture_triggered = false;
+ return;
+ }
+
let server = seat.server;
let pointer_gestures = (*server).input_manager.pointer_gestures;
if !pointer_gestures.is_null() {
@@ -1089,6 +1158,9 @@ unsafe extern "C" fn handle_pinch_begin(listener: *mut ffi::wl_listener, data: *
let seat = &mut *cursor.seat;
seat.handle_activity();
+ cursor.gesture_scale = 1.0;
+ cursor.gesture_triggered = false;
+
let server = seat.server;
let pointer_gestures = (*server).input_manager.pointer_gestures;
if !pointer_gestures.is_null() {
@@ -1108,6 +1180,53 @@ unsafe extern "C" fn handle_pinch_update(listener: *mut ffi::wl_listener, data:
let seat = &mut *cursor.seat;
seat.handle_activity();
+ if cursor.gesture_triggered {
+ return;
+ }
+
+ cursor.gesture_scale = (*event).scale;
+
+ 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)
+ } else {
+ 0
+ };
+
+ let mut matched_action = crate::config::Action::None;
+ let mut matched_command = None;
+
+ for gb in &(*seat.server).wm.gesture_binds {
+ if gb.gesture_type == "pinch" && gb.fingers == (*event).fingers && gb.mods == modifiers {
+ let matched = match gb.direction.as_str() {
+ "in" => cursor.gesture_scale < 0.7,
+ "out" => cursor.gesture_scale > 1.3,
+ _ => false,
+ };
+ if matched {
+ matched_action = gb.action;
+ matched_command = gb.command.clone();
+ break;
+ }
+ }
+ }
+
+ if matched_action != crate::config::Action::None {
+ cursor.gesture_triggered = true;
+ (*seat.server).wm.execute_action(&matched_action, matched_command.as_deref());
+
+ let pointer_gestures = (*seat.server).input_manager.pointer_gestures;
+ if !pointer_gestures.is_null() {
+ ffi::wlr_pointer_gestures_v1_send_pinch_end(
+ pointer_gestures,
+ seat.wlr_seat,
+ (*event).time_msec,
+ true, // cancelled: true
+ );
+ }
+ return;
+ }
+
let server = seat.server;
let pointer_gestures = (*server).input_manager.pointer_gestures;
if !pointer_gestures.is_null() {
@@ -1130,6 +1249,11 @@ unsafe extern "C" fn handle_pinch_end(listener: *mut ffi::wl_listener, data: *mu
let seat = &mut *cursor.seat;
seat.handle_activity();
+ if cursor.gesture_triggered {
+ cursor.gesture_triggered = false;
+ return;
+ }
+
let server = seat.server;
let pointer_gestures = (*server).input_manager.pointer_gestures;
if !pointer_gestures.is_null() {
diff --git a/src/server/server.rs b/src/server/server.rs
index 692998e..a3eace8 100644
--- a/src/server/server.rs
+++ b/src/server/server.rs
@@ -798,6 +798,7 @@ impl Default for Server {
std::ptr::write(&mut (*server.as_mut_ptr()).wm.mode_rules, Vec::new());
std::ptr::write(&mut (*server.as_mut_ptr()).wm.keybinds, Vec::new());
std::ptr::write(&mut (*server.as_mut_ptr()).wm.pointer_binds, Vec::new());
+ std::ptr::write(&mut (*server.as_mut_ptr()).wm.gesture_binds, Vec::new());
std::ptr::write(&mut (*server.as_mut_ptr()).wm.ipc_rx, None);
std::ptr::write(&mut (*server.as_mut_ptr()).wm.startup, Vec::new());
std::ptr::write(&mut (*server.as_mut_ptr()).wm.status_sender, None);
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index fcef2a1..919e6a9 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -62,6 +62,7 @@ pub struct WindowManager {
pub mode_rules: Vec<crate::config::ModeRule>,
pub keybinds: Vec<crate::config::Keybind>,
pub pointer_binds: Vec<crate::config::PointerBind>,
+ pub gesture_binds: Vec<crate::config::GestureBind>,
pub ipc_rx: Option<std::sync::mpsc::Receiver<crate::ipc_server::IpcRequest>>,
pub ipc_timer: *mut ffi::wl_event_source,
pub startup: Vec<crate::config::StartupConfig>,
@@ -118,6 +119,7 @@ impl WindowManager {
self.mode_rules = Vec::new();
self.keybinds = Vec::new();
self.pointer_binds = Vec::new();
+ self.gesture_binds = Vec::new();
self.ipc_rx = None;
self.ipc_timer = std::ptr::null_mut();
self.startup = Vec::new();
@@ -1262,6 +1264,21 @@ impl WindowManager {
}
}
}
+ Action::Reload => {
+ log::info!("monolithic execute_action: Reload requested");
+ if let Some(path) = crate::config::default_config_path() {
+ match crate::config::parse_config(&path, self) {
+ Ok(()) => {
+ self.dirty_windowing();
+ }
+ Err(e) => {
+ log::error!("failed to reload config: {}", e);
+ }
+ }
+ } else {
+ log::error!("no config file found to reload");
+ }
+ }
Action::Exit => {
log::info!("monolithic execute_action: Exit requested");
ffi::wl_display_terminate((*self.server).wl_server);