Diff
diff --git a/resources/test-config.lua b/resources/test-config.lua
index ca9013f..1f8094f 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -72,6 +72,14 @@ oxwm.key.bind({ modkey }, "N", oxwm.layout.cycle())
oxwm.key.bind({ modkey }, "A", oxwm.toggle_gaps())
+-- Master area controls (dwm-style)
+-- NOTE: Mod+H/L are currently used for directional focus
+-- Using Comma/Period instead. To match dwm exactly, replace H/L bindings above
+oxwm.key.bind({ modkey }, "Comma", oxwm.set_master_factor(-5)) -- Decrease master area
+oxwm.key.bind({ modkey }, "Period", oxwm.set_master_factor(5)) -- Increase master area
+oxwm.key.bind({ modkey }, "I", oxwm.inc_num_master(1)) -- More master windows
+oxwm.key.bind({ modkey }, "P", oxwm.inc_num_master(-1)) -- Fewer master windows
+
oxwm.key.bind({ modkey, "Shift" }, "Q", oxwm.quit())
oxwm.key.bind({ modkey, "Shift" }, "R", oxwm.restart())
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index 5057830..4dc33b9 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -588,6 +588,14 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
create_action_table(lua, "ToggleGaps", Value::Nil)
})?;
+ let set_master_factor = lua.create_function(|lua, delta: i32| {
+ create_action_table(lua, "SetMasterFactor", Value::Integer(delta as i64))
+ })?;
+
+ let inc_num_master = lua.create_function(|lua, delta: i32| {
+ create_action_table(lua, "IncNumMaster", Value::Integer(delta as i64))
+ })?;
+
let show_keybinds = lua.create_function(|lua, ()| {
create_action_table(lua, "ShowKeybindOverlay", Value::Nil)
})?;
@@ -620,6 +628,8 @@ fn register_misc(lua: &Lua, parent: &Table, builder: SharedBuilder) -> Result<()
parent.set("restart", restart)?;
parent.set("recompile", recompile)?;
parent.set("toggle_gaps", toggle_gaps)?;
+ parent.set("set_master_factor", set_master_factor)?;
+ parent.set("inc_num_master", inc_num_master)?;
parent.set("show_keybinds", show_keybinds)?;
parent.set("focus_monitor", focus_monitor)?;
Ok(())
@@ -708,6 +718,8 @@ fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
"Recompile" => Ok(KeyAction::Recompile),
"ViewTag" => Ok(KeyAction::ViewTag),
"ToggleGaps" => Ok(KeyAction::ToggleGaps),
+ "SetMasterFactor" => Ok(KeyAction::SetMasterFactor),
+ "IncNumMaster" => Ok(KeyAction::IncNumMaster),
"ToggleFullScreen" => Ok(KeyAction::ToggleFullScreen),
"ToggleFloating" => Ok(KeyAction::ToggleFloating),
"ChangeLayout" => Ok(KeyAction::ChangeLayout),
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 79a44cd..475ecbd 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -30,6 +30,8 @@ pub enum KeyAction {
FocusMonitor,
ExchangeClient,
ShowKeybindOverlay,
+ SetMasterFactor,
+ IncNumMaster,
None,
}
diff --git a/src/layout/grid.rs b/src/layout/grid.rs
index a8962ec..7ad68e3 100644
--- a/src/layout/grid.rs
+++ b/src/layout/grid.rs
@@ -18,6 +18,8 @@ impl Layout for GridLayout {
screen_width: u32,
screen_height: u32,
gaps: &GapConfig,
+ _master_factor: f32,
+ _num_master: i32,
) -> Vec<WindowGeometry> {
let window_count = windows.len();
if window_count == 0 {
diff --git a/src/layout/mod.rs b/src/layout/mod.rs
index 6e92db1..61db85e 100644
--- a/src/layout/mod.rs
+++ b/src/layout/mod.rs
@@ -86,6 +86,8 @@ pub trait Layout {
screen_width: u32,
screen_height: u32,
gaps: &GapConfig,
+ master_factor: f32,
+ num_master: i32,
) -> Vec<WindowGeometry>;
fn name(&self) -> &'static str;
fn symbol(&self) -> &'static str;
diff --git a/src/layout/monocle.rs b/src/layout/monocle.rs
index 8bc8d10..e97b221 100644
--- a/src/layout/monocle.rs
+++ b/src/layout/monocle.rs
@@ -18,6 +18,8 @@ impl Layout for MonocleLayout {
screen_width: u32,
screen_height: u32,
gaps: &GapConfig,
+ _master_factor: f32,
+ _num_master: i32,
) -> Vec<WindowGeometry> {
let window_count = windows.len();
if window_count == 0 {
diff --git a/src/layout/normie.rs b/src/layout/normie.rs
index bc12b0e..2e1d4a3 100644
--- a/src/layout/normie.rs
+++ b/src/layout/normie.rs
@@ -19,6 +19,8 @@ impl Layout for NormieLayout {
screen_width: u32,
screen_height: u32,
_gaps: &GapConfig,
+ _master_factor: f32,
+ _num_master: i32,
) -> Vec<WindowGeometry> {
const DEFAULT_WIDTH_RATIO: f32 = 0.6;
const DEFAULT_HEIGHT_RATIO: f32 = 0.6;
diff --git a/src/layout/tabbed.rs b/src/layout/tabbed.rs
index 9245c1b..ee9f75e 100644
--- a/src/layout/tabbed.rs
+++ b/src/layout/tabbed.rs
@@ -20,6 +20,8 @@ impl Layout for TabbedLayout {
screen_width: u32,
screen_height: u32,
gaps: &GapConfig,
+ _master_factor: f32,
+ _num_master: i32,
) -> Vec<WindowGeometry> {
let window_count = windows.len();
if window_count == 0 {
diff --git a/src/layout/tiling.rs b/src/layout/tiling.rs
index dc0e1bf..a67defb 100644
--- a/src/layout/tiling.rs
+++ b/src/layout/tiling.rs
@@ -3,6 +3,73 @@ use x11rb::protocol::xproto::Window;
pub struct TilingLayout;
+struct GapValues {
+ outer_horizontal: u32,
+ outer_vertical: u32,
+ inner_horizontal: u32,
+ inner_vertical: u32,
+}
+
+struct FactValues {
+ master_facts: f32,
+ stack_facts: f32,
+ master_remainder: i32,
+ stack_remainder: i32,
+}
+
+impl TilingLayout {
+ fn getgaps(gaps: &GapConfig, window_count: usize, smartgaps_enabled: bool) -> GapValues {
+ let outer_enabled = if smartgaps_enabled && window_count == 1 {
+ 0
+ } else {
+ 1
+ };
+ let inner_enabled = 1;
+
+ GapValues {
+ outer_horizontal: gaps.outer_horizontal * outer_enabled,
+ outer_vertical: gaps.outer_vertical * outer_enabled,
+ inner_horizontal: gaps.inner_horizontal * inner_enabled,
+ inner_vertical: gaps.inner_vertical * inner_enabled,
+ }
+ }
+
+ fn getfacts(
+ window_count: usize,
+ num_master: i32,
+ master_size: i32,
+ stack_size: i32,
+ ) -> FactValues {
+ let num_master = num_master.max(0) as usize;
+ let master_facts = window_count.min(num_master) as f32;
+ let stack_facts = if window_count > num_master {
+ (window_count - num_master) as f32
+ } else {
+ 0.0
+ };
+
+ let mut master_total = 0;
+ let mut stack_total = 0;
+
+ for i in 0..window_count {
+ if i < num_master {
+ master_total += (master_size as f32 / master_facts) as i32;
+ } else {
+ if stack_facts > 0.0 {
+ stack_total += (stack_size as f32 / stack_facts) as i32;
+ }
+ }
+ }
+
+ FactValues {
+ master_facts,
+ stack_facts,
+ master_remainder: master_size - master_total,
+ stack_remainder: stack_size - stack_total,
+ }
+ }
+}
+
impl Layout for TilingLayout {
fn name(&self) -> &'static str {
super::LayoutType::Tiling.as_str()
@@ -18,68 +85,94 @@ impl Layout for TilingLayout {
screen_width: u32,
screen_height: u32,
gaps: &GapConfig,
+ master_factor: f32,
+ num_master: i32,
) -> Vec<WindowGeometry> {
let window_count = windows.len();
if window_count == 0 {
return Vec::new();
}
- if window_count == 1 {
- let x = gaps.outer_horizontal as i32;
- let y = gaps.outer_vertical as i32;
- let width = screen_width.saturating_sub(2 * gaps.outer_horizontal);
- let height = screen_height.saturating_sub(2 * gaps.outer_vertical);
-
- vec![WindowGeometry {
- x_coordinate: x,
- y_coordinate: y,
- width,
- height,
- }]
+ let smartgaps_enabled = true;
+ let gap_values = Self::getgaps(gaps, window_count, smartgaps_enabled);
+
+ let outer_gap_horizontal = gap_values.outer_horizontal;
+ let outer_gap_vertical = gap_values.outer_vertical;
+ let inner_gap_horizontal = gap_values.inner_horizontal;
+ let inner_gap_vertical = gap_values.inner_vertical;
+
+ let mut stack_x = outer_gap_vertical as i32;
+ let mut stack_y = outer_gap_horizontal as i32;
+ let master_x = outer_gap_vertical as i32;
+ let mut master_y = outer_gap_horizontal as i32;
+
+ let num_master_usize = num_master.max(0) as usize;
+ let master_count = window_count.min(num_master_usize);
+ let stack_count = if window_count > num_master_usize {
+ window_count - num_master_usize
} else {
- let mut geometries = Vec::new();
+ 0
+ };
- let master_width = (screen_width / 2)
- .saturating_sub(gaps.outer_horizontal)
- .saturating_sub(gaps.inner_horizontal / 2);
+ let master_height = (screen_height as i32)
+ - (2 * outer_gap_horizontal) as i32
+ - (inner_gap_horizontal as i32 * (master_count.saturating_sub(1)) as i32);
+ let stack_height = (screen_height as i32)
+ - (2 * outer_gap_horizontal) as i32
+ - (inner_gap_horizontal as i32 * stack_count.saturating_sub(1) as i32);
+ let mut stack_width = (screen_width as i32) - (2 * outer_gap_vertical) as i32;
+ let mut master_width = stack_width;
- let master_x = gaps.outer_horizontal as i32;
- let master_y = gaps.outer_vertical as i32;
- let master_height = screen_height.saturating_sub(2 * gaps.outer_vertical);
+ if num_master > 0 && window_count > num_master_usize {
+ stack_width = ((master_width as f32 - inner_gap_vertical as f32) * (1.0 - master_factor)) as i32;
+ master_width = master_width - inner_gap_vertical as i32 - stack_width;
+ stack_x = master_x + master_width + inner_gap_vertical as i32;
+ }
- geometries.push(WindowGeometry {
- x_coordinate: master_x,
- y_coordinate: master_y,
- width: master_width,
- height: master_height,
- });
+ let facts = Self::getfacts(window_count, num_master, master_height, stack_height);
- let stack_count = window_count - 1;
- let stack_x = (screen_width / 2 + gaps.inner_horizontal / 2) as i32;
- let stack_width = (screen_width / 2)
- .saturating_sub(gaps.outer_horizontal)
- .saturating_sub(gaps.inner_horizontal / 2);
+ let mut geometries = Vec::new();
- let total_stack_height = screen_height.saturating_sub(2 * gaps.outer_vertical);
+ for (i, _window) in windows.iter().enumerate() {
+ if i < num_master_usize {
+ let window_height = (master_height as f32 / facts.master_facts) as i32
+ + if (i as i32) < facts.master_remainder {
+ 1
+ } else {
+ 0
+ };
- let total_inner_gaps = gaps.inner_vertical * (stack_count as u32 - 1);
- let stack_height =
- total_stack_height.saturating_sub(total_inner_gaps) / stack_count as u32;
+ geometries.push(WindowGeometry {
+ x_coordinate: master_x,
+ y_coordinate: master_y,
+ width: master_width as u32,
+ height: window_height as u32,
+ });
- for i in 1..window_count {
- let stack_index = i - 1;
- let y_offset = gaps.outer_vertical
- + (stack_index as u32) * (stack_height + gaps.inner_vertical);
+ master_y += window_height + inner_gap_horizontal as i32;
+ } else {
+ let window_height = if facts.stack_facts > 0.0 {
+ (stack_height as f32 / facts.stack_facts) as i32
+ + if ((i - num_master_usize) as i32) < facts.stack_remainder {
+ 1
+ } else {
+ 0
+ }
+ } else {
+ stack_height
+ };
geometries.push(WindowGeometry {
x_coordinate: stack_x,
- y_coordinate: y_offset as i32,
- width: stack_width,
- height: stack_height,
+ y_coordinate: stack_y,
+ width: stack_width as u32,
+ height: window_height as u32,
});
- }
- return geometries;
+ stack_y += window_height + inner_gap_horizontal as i32;
+ }
}
+
+ geometries
}
}
diff --git a/src/overlay/keybind.rs b/src/overlay/keybind.rs
index a530eae..3a13bfe 100644
--- a/src/overlay/keybind.rs
+++ b/src/overlay/keybind.rs
@@ -217,6 +217,8 @@ impl KeybindOverlay {
KeyAction::CycleLayout => "Cycle Through Layouts".to_string(),
KeyAction::FocusMonitor => "Focus Next Monitor".to_string(),
KeyAction::ExchangeClient => "Exchange Client Windows".to_string(),
+ KeyAction::SetMasterFactor => "Adjust Master Area Size".to_string(),
+ KeyAction::IncNumMaster => "Adjust Number of Master Windows".to_string(),
KeyAction::None => "No Action".to_string(),
}
}
diff --git a/src/window_manager.rs b/src/window_manager.rs
index e22d214..97bc9e5 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -594,6 +594,24 @@ impl WindowManager {
Ok(())
}
+ fn set_master_factor(&mut self, delta: f32) -> WmResult<()> {
+ if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
+ let new_mfact = (monitor.master_factor + delta).max(0.05).min(0.95);
+ monitor.master_factor = new_mfact;
+ self.apply_layout()?;
+ }
+ Ok(())
+ }
+
+ fn inc_num_master(&mut self, delta: i32) -> WmResult<()> {
+ if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
+ let new_nmaster = (monitor.num_master + delta).max(0);
+ monitor.num_master = new_nmaster;
+ self.apply_layout()?;
+ }
+ Ok(())
+ }
+
fn exchange_client(&mut self, direction: i32) -> WmResult<()> {
let focused = match self
.monitors
@@ -907,6 +925,16 @@ impl WindowManager {
monitor.screen_height as u16,
)?;
}
+ KeyAction::SetMasterFactor => {
+ if let Arg::Int(delta) = arg {
+ self.set_master_factor(*delta as f32 / 100.0)?;
+ }
+ }
+ KeyAction::IncNumMaster => {
+ if let Arg::Int(delta) = arg {
+ self.inc_num_master(*delta)?;
+ }
+ }
KeyAction::None => {}
}
Ok(())
@@ -1732,7 +1760,7 @@ impl WindowManager {
self.clients.insert(window, client);
self.update_size_hints(window)?;
self.update_window_title(window)?;
- self.attach(window, monitor_index);
+ self.attach_aside(window, monitor_index);
self.attach_stack(window, monitor_index);
self.windows.push(window);
@@ -2668,10 +2696,17 @@ impl WindowManager {
0
};
let usable_height = monitor_height.saturating_sub(bar_height as i32);
-
- let geometries = self
- .layout
- .arrange(&visible, monitor_width as u32, usable_height as u32, &gaps);
+ let master_factor = monitor.master_factor;
+ let num_master = monitor.num_master;
+
+ let geometries = self.layout.arrange(
+ &visible,
+ monitor_width as u32,
+ usable_height as u32,
+ &gaps,
+ master_factor,
+ num_master,
+ );
for (window, geometry) in visible.iter().zip(geometries.iter()) {
let adjusted_width = geometry.width.saturating_sub(2 * border_width);
@@ -3044,6 +3079,49 @@ impl WindowManager {
}
}
+ fn attach_aside(&mut self, window: Window, monitor_index: usize) {
+ let monitor = match self.monitors.get(monitor_index) {
+ Some(m) => m,
+ None => return,
+ };
+
+ if monitor.clients_head.is_none() {
+ self.attach(window, monitor_index);
+ return;
+ }
+
+ let num_master = monitor.num_master.max(1) as usize;
+ let mut current = monitor.clients_head;
+ let mut position = 0;
+
+ while position < num_master - 1 {
+ if let Some(current_window) = current {
+ if let Some(current_client) = self.clients.get(¤t_window) {
+ current = current_client.next;
+ position += 1;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if let Some(insert_after) = current {
+ if let Some(after_client) = self.clients.get(&insert_after) {
+ let old_next = after_client.next;
+ if let Some(new_client) = self.clients.get_mut(&window) {
+ new_client.next = old_next;
+ }
+ if let Some(after_client_mut) = self.clients.get_mut(&insert_after) {
+ after_client_mut.next = Some(window);
+ }
+ }
+ } else {
+ self.attach(window, monitor_index);
+ }
+ }
+
fn detach(&mut self, window: Window) {
let monitor_index = self.clients.get(&window).map(|c| c.monitor_index);
if let Some(monitor_index) = monitor_index {