Diff
diff --git a/flake.nix b/flake.nix
index d7e1da7..b0fa72d 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,9 +14,9 @@
buildInputs = [
pkgs.rustc
pkgs.cargo
- pkgs.xorg.xorgserver
pkgs.xorg.xclock
pkgs.xterm
+ pkgs.alacritty
pkgs.just
];
shellHook = ''
diff --git a/justfile b/justfile
index 5fe20ff..2cc6018 100644
--- a/justfile
+++ b/justfile
@@ -1,3 +1,16 @@
+build:
+ cargo build --release
+
+install: build
+ sudo cp target/release/oxwm /usr/local/bin/oxwm
+ @echo "✓ oxwm installed. Restart X or hit Mod+Shift+R (when implemented)"
+
+uninstall:
+ sudo rm -f /usr/local/bin/oxwm
+
+clean:
+ cargo clean
+
test:
pkill Xephyr || true
Xephyr -screen 1280x800 :1 & sleep 1
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index d4b315e..87cd452 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -9,6 +9,8 @@ pub enum KeyAction {
KillClient,
FocusStack,
Quit,
+ ViewTag,
+ MoveToTag,
None,
}
@@ -47,27 +49,73 @@ const KEYBINDINGS: &[Key] = &[
&[KeyButMask::MOD1],
keycodes::RETURN,
KeyAction::Spawn,
- Arg::Str("xclock"),
+ Arg::Str("alacritty"),
),
Key::new(
- &[KeyButMask::MOD1, KeyButMask::SHIFT],
+ &[KeyButMask::MOD1],
keycodes::Q,
KeyAction::KillClient,
Arg::None,
),
- Key::new(&[KeyButMask::MOD1], keycodes::Q, KeyAction::Quit, Arg::None),
+ Key::new(
+ &[KeyButMask::MOD1, KeyButMask::SHIFT],
+ keycodes::Q,
+ KeyAction::Quit,
+ Arg::None,
+ ),
Key::new(
&[KeyButMask::MOD1],
- keycodes::J,
+ keycodes::K,
KeyAction::FocusStack,
Arg::Int(1),
),
Key::new(
&[KeyButMask::MOD1],
- keycodes::K,
+ keycodes::J,
KeyAction::FocusStack,
Arg::Int(-1),
),
+ // NEW: Tag bindings (Mod+1 through Mod+9)
+ Key::new(
+ &[KeyButMask::MOD1],
+ keycodes::KEY_1,
+ KeyAction::ViewTag,
+ Arg::Int(0),
+ ),
+ Key::new(
+ &[KeyButMask::MOD1],
+ keycodes::KEY_2,
+ KeyAction::ViewTag,
+ Arg::Int(1),
+ ),
+ Key::new(
+ &[KeyButMask::MOD1],
+ keycodes::KEY_3,
+ KeyAction::ViewTag,
+ Arg::Int(2),
+ ),
+ // ... add KEY_4 through KEY_9
+
+ // NEW: Move window to tag (Mod+Shift+1 through Mod+Shift+9)
+ Key::new(
+ &[KeyButMask::MOD1, KeyButMask::SHIFT],
+ keycodes::KEY_1,
+ KeyAction::MoveToTag,
+ Arg::Int(0),
+ ),
+ Key::new(
+ &[KeyButMask::MOD1, KeyButMask::SHIFT],
+ keycodes::KEY_2,
+ KeyAction::MoveToTag,
+ Arg::Int(1),
+ ),
+ Key::new(
+ &[KeyButMask::MOD1, KeyButMask::SHIFT],
+ keycodes::KEY_3,
+ KeyAction::MoveToTag,
+ Arg::Int(2),
+ ),
+ // ... etc
];
fn modifiers_to_mask(modifiers: &[KeyButMask]) -> u16 {
diff --git a/src/keyboard/keycodes.rs b/src/keyboard/keycodes.rs
index f1508e2..3b57218 100644
--- a/src/keyboard/keycodes.rs
+++ b/src/keyboard/keycodes.rs
@@ -1,4 +1,6 @@
-// wip
+#![allow(dead_code)]
+// Allowing dead code here because its just a file for potential keybinds
+
pub const RETURN: u8 = 36;
pub const Q: u8 = 24;
pub const ESCAPE: u8 = 9;
@@ -7,13 +9,11 @@ pub const TAB: u8 = 23;
pub const BACKSPACE: u8 = 22;
pub const DELETE: u8 = 119;
-// Function keys
pub const F1: u8 = 67;
pub const F2: u8 = 68;
pub const F3: u8 = 69;
pub const F4: u8 = 70;
-// Letters (assuming QWERTY)
pub const A: u8 = 38;
pub const S: u8 = 39;
pub const D: u8 = 40;
@@ -22,7 +22,12 @@ pub const J: u8 = 44;
pub const K: u8 = 45;
pub const L: u8 = 46;
-// Numbers
pub const KEY_1: u8 = 10;
pub const KEY_2: u8 = 11;
pub const KEY_3: u8 = 12;
+pub const KEY_4: u8 = 13;
+pub const KEY_5: u8 = 14;
+pub const KEY_6: u8 = 15;
+pub const KEY_7: u8 = 16;
+pub const KEY_8: u8 = 17;
+pub const KEY_9: u8 = 18;
diff --git a/src/window_manager.rs b/src/window_manager.rs
index b344af5..422e09b 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -9,6 +9,15 @@ use x11rb::protocol::Event;
use x11rb::protocol::xproto::*;
use x11rb::rust_connection::RustConnection;
+// TODO: move this to its own file? (tag.rs?)
+pub type TagMask = u32;
+
+pub const TAG_COUNT: usize = 9;
+
+pub fn tag_mask(tag: usize) -> TagMask {
+ 1 << tag
+}
+
pub struct WindowManager {
connection: RustConnection,
screen_number: usize,
@@ -17,6 +26,8 @@ pub struct WindowManager {
windows: Vec<Window>,
focused_window: Option<Window>,
layout: Box<dyn Layout>,
+ window_tags: std::collections::HashMap<Window, TagMask>,
+ selected_tags: TagMask,
}
impl WindowManager {
@@ -45,6 +56,8 @@ impl WindowManager {
windows: Vec::new(),
focused_window: None,
layout: Box::new(TilingLayout),
+ window_tags: std::collections::HashMap::new(),
+ selected_tags: tag_mask(0),
});
}
@@ -95,6 +108,16 @@ impl WindowManager {
println!("Quitting window manager");
std::process::exit(0);
}
+ KeyAction::ViewTag => {
+ if let Arg::Int(tag_index) = arg {
+ self.view_tag(*tag_index as usize)?;
+ }
+ }
+ KeyAction::MoveToTag => {
+ if let Arg::Int(tag_index) = arg {
+ self.move_to_tag(*tag_index as usize)?;
+ }
+ }
KeyAction::None => {
//no-op
}
@@ -102,28 +125,89 @@ impl WindowManager {
Ok(())
}
+ fn is_window_visible(&self, window: Window) -> bool {
+ if let Some(&tags) = self.window_tags.get(&window) {
+ (tags & self.selected_tags) != 0
+ } else {
+ false
+ }
+ }
+
+ fn visible_windows(&self) -> Vec<Window> {
+ self.windows
+ .iter()
+ .filter(|&&w| self.is_window_visible(w))
+ .copied()
+ .collect()
+ }
+
+ fn update_window_visibility(&self) -> Result<()> {
+ for &window in &self.windows {
+ if self.is_window_visible(window) {
+ self.connection.map_window(window)?;
+ } else {
+ self.connection.unmap_window(window)?;
+ }
+ }
+ self.connection.flush()?;
+ Ok(())
+ }
+
+ pub fn view_tag(&mut self, tag_index: usize) -> Result<()> {
+ if tag_index >= TAG_COUNT {
+ return Ok(());
+ }
+
+ self.selected_tags = tag_mask(tag_index);
+ println!("Viewing tag {}", tag_index + 1);
+
+ self.update_window_visibility()?;
+ self.apply_layout()?;
+
+ let visible = self.visible_windows();
+ self.set_focus(visible.first().copied())?;
+
+ Ok(())
+ }
+
+ pub fn move_to_tag(&mut self, tag_index: usize) -> Result<()> {
+ if tag_index >= TAG_COUNT {
+ return Ok(());
+ }
+
+ if let Some(focused) = self.focused_window {
+ let mask = tag_mask(tag_index);
+ self.window_tags.insert(focused, mask);
+
+ println!("Moved window {} to tag {}", focused, tag_index + 1);
+
+ self.update_window_visibility()?;
+ self.apply_layout()?;
+ }
+
+ Ok(())
+ }
+
pub fn cycle_focus(&mut self, direction: i32) -> Result<()> {
- if self.windows.is_empty() {
+ let visible = self.visible_windows();
+
+ if visible.is_empty() {
return Ok(());
}
let next_window = if let Some(current) = self.focused_window {
- if let Some(current_index) = self.windows.iter().position(|&w| w == current) {
+ if let Some(current_index) = visible.iter().position(|&w| w == current) {
let next_index = if direction > 0 {
- (current_index + 1) % self.windows.len()
+ (current_index + 1) % visible.len()
} else {
- if current_index == 0 {
- self.windows.len() - 1
- } else {
- current_index - 1
- }
+ (current_index + visible.len() - 1) % visible.len()
};
- self.windows[next_index]
+ visible[next_index]
} else {
- self.windows[0]
+ visible[0]
}
} else {
- self.windows[0]
+ visible[0]
};
self.set_focus(Some(next_window))?;
@@ -174,15 +258,29 @@ impl WindowManager {
Event::MapRequest(event) => {
self.connection.map_window(event.window)?;
self.windows.push(event.window);
+ self.window_tags.insert(event.window, self.selected_tags);
self.apply_layout()?;
self.set_focus(Some(event.window))?;
}
Event::UnmapNotify(event) => {
if self.windows.contains(&event.window) {
- println!("Window {} unmapped, removing from layout", event.window);
- self.remove_window(event.window)?;
+ if self.is_window_visible(event.window) {
+ println!("Visible window {} unmapped, removing", event.window);
+ self.remove_window(event.window)?;
+ } else {
+ println!(
+ "Hidden window {} unmapped (expected), ignoring",
+ event.window
+ );
+ }
}
}
+ // Event::UnmapNotify(event) => {
+ // if self.windows.contains(&event.window) {
+ // println!("Window {} unmapped, removing from layout", event.window);
+ // self.remove_window(event.window)?;
+ // }
+ // }
Event::DestroyNotify(event) => {
if self.windows.contains(&event.window) {
println!("Window {} destroyed, removing from layout", event.window);
@@ -203,12 +301,10 @@ impl WindowManager {
let screen_height = self.screen.height_in_pixels as u32;
let border_width = 2u32;
- let geometries = self
- .layout
- .arrange(&self.windows, screen_width, screen_height);
+ let visible = self.visible_windows();
+ let geometries = self.layout.arrange(&visible, screen_width, screen_height);
- for (window, geometry) in self.windows.iter().zip(geometries.iter()) {
- // Adjust for full borders
+ for (window, geometry) in visible.iter().zip(geometries.iter()) {
let adjusted_width = geometry.width.saturating_sub(2 * border_width);
let adjusted_height = geometry.height.saturating_sub(2 * border_width);
@@ -222,12 +318,13 @@ impl WindowManager {
)?;
}
self.connection.flush()?;
- return Ok(());
+ Ok(())
}
fn remove_window(&mut self, window: Window) -> Result<()> {
let initial_count = self.windows.len();
self.windows.retain(|&w| w != window);
+ self.window_tags.remove(&window);
if self.windows.len() < initial_count {
println!("Removed window {} from management", window);
@@ -243,7 +340,6 @@ impl WindowManager {
self.apply_layout()?;
}
-
Ok(())
}
}