Diff
diff --git a/resources/test-config.lua b/resources/test-config.lua
index 099a6cd..4ec331a 100644
--- a/resources/test-config.lua
+++ b/resources/test-config.lua
@@ -94,6 +94,7 @@ oxwm.key.bind({ modkey, "Shift" }, "J", oxwm.client.swap_direction("down"))
oxwm.key.bind({ modkey, "Shift" }, "K", oxwm.client.swap_direction("up"))
oxwm.key.bind({ modkey, "Shift" }, "L", oxwm.client.swap_direction("right"))
+-- View tag (switch workspace)
oxwm.key.bind({ modkey }, "1", oxwm.tag.view(0))
oxwm.key.bind({ modkey }, "2", oxwm.tag.view(1))
oxwm.key.bind({ modkey }, "3", oxwm.tag.view(2))
@@ -104,6 +105,7 @@ oxwm.key.bind({ modkey }, "7", oxwm.tag.view(6))
oxwm.key.bind({ modkey }, "8", oxwm.tag.view(7))
oxwm.key.bind({ modkey }, "9", oxwm.tag.view(8))
+-- Move window to tag
oxwm.key.bind({ modkey, "Shift" }, "1", oxwm.tag.move_to(0))
oxwm.key.bind({ modkey, "Shift" }, "2", oxwm.tag.move_to(1))
oxwm.key.bind({ modkey, "Shift" }, "3", oxwm.tag.move_to(2))
@@ -114,6 +116,30 @@ oxwm.key.bind({ modkey, "Shift" }, "7", oxwm.tag.move_to(6))
oxwm.key.bind({ modkey, "Shift" }, "8", oxwm.tag.move_to(7))
oxwm.key.bind({ modkey, "Shift" }, "9", oxwm.tag.move_to(8))
+-- Toggle view (view multiple tags at once) - dwm-style multi-tag viewing
+-- Example: Mod+Ctrl+2 while on tag 1 will show BOTH tags 1 and 2
+oxwm.key.bind({ modkey, "Control" }, "1", oxwm.tag.toggleview(0))
+oxwm.key.bind({ modkey, "Control" }, "2", oxwm.tag.toggleview(1))
+oxwm.key.bind({ modkey, "Control" }, "3", oxwm.tag.toggleview(2))
+oxwm.key.bind({ modkey, "Control" }, "4", oxwm.tag.toggleview(3))
+oxwm.key.bind({ modkey, "Control" }, "5", oxwm.tag.toggleview(4))
+oxwm.key.bind({ modkey, "Control" }, "6", oxwm.tag.toggleview(5))
+oxwm.key.bind({ modkey, "Control" }, "7", oxwm.tag.toggleview(6))
+oxwm.key.bind({ modkey, "Control" }, "8", oxwm.tag.toggleview(7))
+oxwm.key.bind({ modkey, "Control" }, "9", oxwm.tag.toggleview(8))
+
+-- Toggle tag (window on multiple tags) - dwm-style sticky windows
+-- Example: Mod+Ctrl+Shift+2 puts focused window on BOTH current tag and tag 2
+oxwm.key.bind({ modkey, "Control", "Shift" }, "1", oxwm.tag.toggletag(0))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "2", oxwm.tag.toggletag(1))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "3", oxwm.tag.toggletag(2))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "4", oxwm.tag.toggletag(3))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "5", oxwm.tag.toggletag(4))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "6", oxwm.tag.toggletag(5))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "7", oxwm.tag.toggletag(6))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "8", oxwm.tag.toggletag(7))
+oxwm.key.bind({ modkey, "Control", "Shift" }, "9", oxwm.tag.toggletag(8))
+
oxwm.bar.set_blocks({
oxwm.bar.block.battery({
format = "Bat: {}%",
diff --git a/src/config/lua_api.rs b/src/config/lua_api.rs
index 5ed03fd..0eff531 100644
--- a/src/config/lua_api.rs
+++ b/src/config/lua_api.rs
@@ -301,12 +301,22 @@ fn register_tag_module(lua: &Lua, parent: &Table) -> Result<(), ConfigError> {
create_action_table(lua, "ViewTag", Value::Integer(idx as i64))
})?;
+ let toggleview = lua.create_function(|lua, idx: i32| {
+ create_action_table(lua, "ToggleView", Value::Integer(idx as i64))
+ })?;
+
let move_to = lua.create_function(|lua, idx: i32| {
create_action_table(lua, "MoveToTag", Value::Integer(idx as i64))
})?;
+ let toggletag = lua.create_function(|lua, idx: i32| {
+ create_action_table(lua, "ToggleTag", Value::Integer(idx as i64))
+ })?;
+
tag_table.set("view", view)?;
+ tag_table.set("toggleview", toggleview)?;
tag_table.set("move_to", move_to)?;
+ tag_table.set("toggletag", toggletag)?;
parent.set("tag", tag_table)?;
Ok(())
}
@@ -726,6 +736,9 @@ fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
"Restart" => Ok(KeyAction::Restart),
"Recompile" => Ok(KeyAction::Recompile),
"ViewTag" => Ok(KeyAction::ViewTag),
+ "ToggleView" => Ok(KeyAction::ToggleView),
+ "MoveToTag" => Ok(KeyAction::MoveToTag),
+ "ToggleTag" => Ok(KeyAction::ToggleTag),
"ToggleGaps" => Ok(KeyAction::ToggleGaps),
"SetMasterFactor" => Ok(KeyAction::SetMasterFactor),
"IncNumMaster" => Ok(KeyAction::IncNumMaster),
@@ -733,7 +746,6 @@ fn string_to_action(s: &str) -> mlua::Result<KeyAction> {
"ToggleFloating" => Ok(KeyAction::ToggleFloating),
"ChangeLayout" => Ok(KeyAction::ChangeLayout),
"CycleLayout" => Ok(KeyAction::CycleLayout),
- "MoveToTag" => Ok(KeyAction::MoveToTag),
"FocusMonitor" => Ok(KeyAction::FocusMonitor),
"ExchangeClient" => Ok(KeyAction::ExchangeClient),
"ShowKeybindOverlay" => Ok(KeyAction::ShowKeybindOverlay),
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 475ecbd..7d867ec 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -21,12 +21,14 @@ pub enum KeyAction {
Restart,
Recompile,
ViewTag,
+ ToggleView,
+ MoveToTag,
+ ToggleTag,
ToggleGaps,
ToggleFullScreen,
ToggleFloating,
ChangeLayout,
CycleLayout,
- MoveToTag,
FocusMonitor,
ExchangeClient,
ShowKeybindOverlay,
diff --git a/src/overlay/keybind.rs b/src/overlay/keybind.rs
index 3a13bfe..9789510 100644
--- a/src/overlay/keybind.rs
+++ b/src/overlay/keybind.rs
@@ -209,7 +209,12 @@ impl KeybindOverlay {
Arg::Int(n) => format!("View Workspace {}", n),
_ => "View Workspace".to_string(),
},
+ KeyAction::ToggleView => match &binding.arg {
+ Arg::Int(n) => format!("Toggle View Workspace {}", n),
+ _ => "Toggle View Workspace".to_string(),
+ },
KeyAction::MoveToTag => "Move Window to Workspace".to_string(),
+ KeyAction::ToggleTag => "Toggle Window on Workspace".to_string(),
KeyAction::ToggleGaps => "Toggle Window Gaps".to_string(),
KeyAction::ToggleFullScreen => "Toggle Fullscreen Mode".to_string(),
KeyAction::ToggleFloating => "Toggle Floating Mode".to_string(),
diff --git a/src/window_manager.rs b/src/window_manager.rs
index e1ca9b4..ab64690 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -900,11 +900,21 @@ impl WindowManager {
self.view_tag(*tag_index as usize)?;
}
}
+ KeyAction::ToggleView => {
+ if let Arg::Int(tag_index) = arg {
+ self.toggleview(*tag_index as usize)?;
+ }
+ }
KeyAction::MoveToTag => {
if let Arg::Int(tag_index) = arg {
self.move_to_tag(*tag_index as usize)?;
}
}
+ KeyAction::ToggleTag => {
+ if let Arg::Int(tag_index) = arg {
+ self.toggletag(*tag_index as usize)?;
+ }
+ }
KeyAction::ToggleGaps => {
self.gaps_enabled = !self.gaps_enabled;
self.apply_layout()?;
@@ -1041,25 +1051,112 @@ impl WindowManager {
Ok(())
}
+ fn showhide(&mut self, window: Option<Window>) -> WmResult<()> {
+ let Some(window) = window else {
+ return Ok(());
+ };
+
+ let Some(client) = self.clients.get(&window).cloned() else {
+ return Ok(());
+ };
+
+ let monitor = match self.monitors.get(client.monitor_index) {
+ Some(m) => m,
+ None => return Ok(()),
+ };
+
+ let is_visible = (client.tags & monitor.tagset[monitor.selected_tags_index]) != 0;
+
+ if is_visible {
+ self.connection.configure_window(
+ window,
+ &ConfigureWindowAux::new()
+ .x(client.x_position as i32)
+ .y(client.y_position as i32),
+ )?;
+
+ let is_floating = client.is_floating;
+ let is_fullscreen = client.is_fullscreen;
+ let has_no_layout = self.layout.name() == LayoutType::Normie.as_str();
+
+ if (has_no_layout || is_floating) && !is_fullscreen {
+ self.connection.configure_window(
+ window,
+ &ConfigureWindowAux::new()
+ .x(client.x_position as i32)
+ .y(client.y_position as i32)
+ .width(client.width as u32)
+ .height(client.height as u32),
+ )?;
+ }
+
+ self.showhide(client.stack_next)?;
+ } else {
+ self.showhide(client.stack_next)?;
+
+ let width = client.width_with_border() as i32;
+ self.connection.configure_window(
+ window,
+ &ConfigureWindowAux::new()
+ .x(width * -2)
+ .y(client.y_position as i32),
+ )?;
+ }
+
+ Ok(())
+ }
+
pub fn view_tag(&mut self, tag_index: usize) -> WmResult<()> {
if tag_index >= self.config.tags.len() {
return Ok(());
}
- if let Some(monitor) = self.monitors.get_mut(self.selected_monitor) {
- monitor.tagset[monitor.selected_tags_index] = tag_mask(tag_index);
+ let monitor = match self.monitors.get_mut(self.selected_monitor) {
+ Some(m) => m,
+ None => return Ok(()),
+ };
+
+ let new_tagset = tag_mask(tag_index);
+
+ if new_tagset == monitor.tagset[monitor.selected_tags_index] {
+ return Ok(());
}
+ monitor.selected_tags_index ^= 1;
+ monitor.tagset[monitor.selected_tags_index] = new_tagset;
+
self.save_selected_tags()?;
- self.update_window_visibility()?;
- self.apply_layout()?;
+ self.focus(None)?;
+ self.apply_layout()?;
self.update_bar()?;
- let visible = self.visible_windows_on_monitor(self.selected_monitor);
- if let Some(&win) = visible.first() {
- self.focus(Some(win))?;
+ Ok(())
+ }
+
+ pub fn toggleview(&mut self, tag_index: usize) -> WmResult<()> {
+ if tag_index >= self.config.tags.len() {
+ return Ok(());
}
+ let monitor = match self.monitors.get_mut(self.selected_monitor) {
+ Some(m) => m,
+ None => return Ok(()),
+ };
+
+ let mask = tag_mask(tag_index);
+ let new_tagset = monitor.tagset[monitor.selected_tags_index] ^ mask;
+
+ if new_tagset == 0 {
+ return Ok(());
+ }
+
+ monitor.tagset[monitor.selected_tags_index] = new_tagset;
+
+ self.save_selected_tags()?;
+ self.focus(None)?;
+ self.apply_layout()?;
+ self.update_bar()?;
+
Ok(())
}
@@ -1093,23 +1190,70 @@ impl WindowManager {
return Ok(());
}
- if let Some(focused) = self
+ let focused = match self
.monitors
.get(self.selected_monitor)
.and_then(|m| m.selected_client)
{
- let mask = tag_mask(tag_index);
- self.window_tags.insert(focused, mask);
+ Some(win) => win,
+ None => return Ok(()),
+ };
- if let Err(error) = self.save_client_tag(focused, mask) {
- eprintln!("Failed to save client tag: {:?}", error);
- }
+ let mask = tag_mask(tag_index);
- self.update_window_visibility()?;
- self.apply_layout()?;
- self.update_bar()?;
+ if let Some(client) = self.clients.get_mut(&focused) {
+ client.tags = mask;
+ }
+
+ self.window_tags.insert(focused, mask);
+
+ if let Err(error) = self.save_client_tag(focused, mask) {
+ eprintln!("Failed to save client tag: {:?}", error);
}
+ self.focus(None)?;
+ self.apply_layout()?;
+ self.update_bar()?;
+
+ Ok(())
+ }
+
+ pub fn toggletag(&mut self, tag_index: usize) -> WmResult<()> {
+ if tag_index >= self.config.tags.len() {
+ return Ok(());
+ }
+
+ let focused = match self
+ .monitors
+ .get(self.selected_monitor)
+ .and_then(|m| m.selected_client)
+ {
+ Some(win) => win,
+ None => return Ok(()),
+ };
+
+ let mask = tag_mask(tag_index);
+ let current_tags = self.clients.get(&focused).map(|c| c.tags).unwrap_or(0);
+ let new_tags = current_tags ^ mask;
+
+ if new_tags == 0 {
+ return Ok(());
+ }
+
+ if let Some(client) = self.clients.get_mut(&focused) {
+ client.tags = new_tags;
+ }
+
+ self.window_tags.insert(focused, new_tags);
+
+ if let Err(error) = self.save_client_tag(focused, new_tags) {
+ eprintln!("Failed to save client tag: {:?}", error);
+ }
+
+ self.focus(None)?;
+ self.apply_layout()?;
+ self.update_bar()?;
+
Ok(())
}
@@ -2729,6 +2873,13 @@ impl WindowManager {
let adjusted_x = geometry.x_coordinate + monitor_x;
let adjusted_y = geometry.y_coordinate + monitor_y + bar_height as i32;
+ if let Some(client) = self.clients.get_mut(window) {
+ client.x_position = adjusted_x as i16;
+ client.y_position = adjusted_y as i16;
+ client.width = adjusted_width as u16;
+ client.height = adjusted_height as u16;
+ }
+
self.connection.configure_window(
*window,
&ConfigureWindowAux::new()
@@ -2749,6 +2900,11 @@ impl WindowManager {
}
}
+ for monitor_index in 0..self.monitors.len() {
+ let stack_head = self.monitors[monitor_index].stack_head;
+ self.showhide(stack_head)?;
+ }
+
self.connection.flush()?;
let is_tabbed = self.layout.name() == LayoutType::Tabbed.as_str();