Diff
diff --git a/Cargo.lock b/Cargo.lock
index 29e52c0..fd7f154 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,6 +70,27 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "dirs"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
[[package]]
name = "errno"
version = "0.3.14"
@@ -77,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys",
+ "windows-sys 0.61.0",
]
[[package]]
@@ -93,7 +114,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc257fdb4038301ce4b9cd1b3b51704509692bb3ff716a410cbd07925d9dae55"
dependencies = [
"rustix",
- "windows-targets",
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
]
[[package]]
@@ -136,6 +168,16 @@ version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
+[[package]]
+name = "libredox"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@@ -163,12 +205,19 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
[[package]]
name = "oxwm"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
+ "dirs",
"x11",
"x11rb",
]
@@ -197,6 +246,17 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "redox_users"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
[[package]]
name = "rustix"
version = "1.1.2"
@@ -207,7 +267,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys",
+ "windows-sys 0.61.0",
]
[[package]]
@@ -233,12 +293,38 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
@@ -357,6 +443,15 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-sys"
+version = "0.48.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
+dependencies = [
+ "windows-targets 0.48.5",
+]
+
[[package]]
name = "windows-sys"
version = "0.61.0"
@@ -366,34 +461,67 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-targets"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
+dependencies = [
+ "windows_aarch64_gnullvm 0.48.5",
+ "windows_aarch64_msvc 0.48.5",
+ "windows_i686_gnu 0.48.5",
+ "windows_i686_msvc 0.48.5",
+ "windows_x86_64_gnu 0.48.5",
+ "windows_x86_64_gnullvm 0.48.5",
+ "windows_x86_64_msvc 0.48.5",
+]
+
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
]
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
+
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+[[package]]
+name = "windows_i686_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
+
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -406,24 +534,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+[[package]]
+name = "windows_i686_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
+
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
+
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
+
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.48.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
diff --git a/Cargo.toml b/Cargo.toml
index 9bcfaaf..bb57ae3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,8 +3,17 @@ name = "oxwm"
version = "0.1.0"
edition = "2024"
+[lib]
+name = "oxwm"
+path = "src/lib.rs"
+
+[[bin]]
+name = "oxwm"
+path = "src/bin/main.rs"
+
[dependencies]
x11 = { version = "2.21", features = ["xlib", "xft"] }
x11rb = "0.13"
anyhow = "1"
chrono = "0.4"
+dirs = "5.0"
diff --git a/justfile b/justfile
index 77f67f8..890e089 100644
--- a/justfile
+++ b/justfile
@@ -1,28 +1,41 @@
-ensure-config:
- @if [ ! -f src/config.rs ]; then \
- echo "Creating config.rs from default_config.rs..."; \
- cp src/default_config.rs src/config.rs; \
- echo "✓ Edit src/config.rs to customize your setup"; \
- fi
-
-build: ensure-config
+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)"
+ @echo "✓ oxwm installed to /usr/local/bin/oxwm"
+ @echo " Run 'oxwm --init' to set up your config"
uninstall:
sudo rm -f /usr/local/bin/oxwm
+ @echo "✓ oxwm uninstalled"
+ @echo " Your config at ~/.config/oxwm is preserved"
clean:
cargo clean
-test: ensure-config
+test:
pkill Xephyr || true
Xephyr -screen 1280x800 :1 & sleep 1
- DISPLAY=:1 cargo run
+ DISPLAY=:1 cargo run -- --init
+ DISPLAY=:1 ~/.cache/oxwm/oxwm-binary
+
+init:
+ cargo run -- --init
+
+recompile:
+ cargo run -- --recompile
+
+edit:
+ $EDITOR ~/.config/oxwm/config.rs
+
+check:
+ cargo clippy -- -W clippy::all
+ cargo fmt -- --check
+
+fmt:
+ cargo fmt
+
+pre-commit: fmt check build
+ @echo "✓ All checks passed!"
-reset-config:
- cp src/default_config.rs src/config.rs
- @echo "✓ Config reset to default"
diff --git a/src/bar/bar.rs b/src/bar/bar.rs
index 1d2b7cc..fd8ac7b 100644
--- a/src/bar/bar.rs
+++ b/src/bar/bar.rs
@@ -1,6 +1,6 @@
use super::blocks::Block;
use super::font::{Font, FontDraw};
-use crate::config::{FONT, SCHEME_NORMAL, SCHEME_OCCUPIED, SCHEME_SELECTED, STATUS_BLOCKS, TAGS};
+use crate::Config;
use anyhow::Result;
use std::time::Instant;
use x11rb::COPY_DEPTH_FROM_PARENT;
@@ -25,10 +25,20 @@ pub struct Bar {
block_last_updates: Vec<Instant>,
block_underlines: Vec<bool>,
status_text: String,
+
+ tags: Vec<String>,
+ scheme_normal: crate::ColorScheme,
+ scheme_occupied: crate::ColorScheme,
+ scheme_selected: crate::ColorScheme,
}
impl Bar {
- pub fn new(connection: &RustConnection, screen: &Screen, screen_num: usize) -> Result<Self> {
+ pub fn new(
+ connection: &RustConnection,
+ screen: &Screen,
+ screen_num: usize,
+ config: &Config,
+ ) -> Result<Self> {
let window = connection.generate_id()?;
let graphics_context = connection.generate_id()?;
@@ -38,7 +48,7 @@ impl Bar {
if display.is_null() {
anyhow::bail!("Failed to open X11 display for XFT");
}
- let font = Font::new(display, screen_num as i32, FONT)?;
+ let font = Font::new(display, screen_num as i32, &config.font)?;
let height = (font.height() as f32 * 1.4) as u16;
@@ -54,7 +64,7 @@ impl Bar {
WindowClass::INPUT_OUTPUT,
screen.root_visual,
&CreateWindowAux::new()
- .background_pixel(SCHEME_NORMAL.background)
+ .background_pixel(config.scheme_normal.background)
.event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS)
.override_redirect(1),
)?;
@@ -63,8 +73,8 @@ impl Bar {
graphics_context,
window,
&CreateGCAux::new()
- .foreground(SCHEME_NORMAL.foreground)
- .background(SCHEME_NORMAL.background),
+ .foreground(config.scheme_normal.foreground)
+ .background(config.scheme_normal.background),
)?;
connection.map_window(window)?;
@@ -77,7 +87,8 @@ impl Bar {
let horizontal_padding = (font.height() as f32 * 0.4) as u16;
- let tag_widths = TAGS
+ let tag_widths = config
+ .tags
.iter()
.map(|tag| {
let text_width = font.text_width(tag);
@@ -85,14 +96,16 @@ impl Bar {
})
.collect();
- let blocks: Vec<Box<dyn Block>> = STATUS_BLOCKS
+ let blocks: Vec<Box<dyn Block>> = config
+ .status_blocks
.iter()
- .map(|config| config.to_block())
+ .map(|block_config| block_config.to_block())
.collect();
- let block_underlines: Vec<bool> = STATUS_BLOCKS
+ let block_underlines: Vec<bool> = config
+ .status_blocks
.iter()
- .map(|config| config.underline)
+ .map(|block_config| block_config.underline)
.collect();
let block_last_updates = vec![Instant::now(); blocks.len()];
@@ -111,6 +124,10 @@ impl Bar {
block_last_updates,
block_underlines,
status_text: String::new(),
+ tags: config.tags.clone(),
+ scheme_normal: config.scheme_normal,
+ scheme_occupied: config.scheme_occupied,
+ scheme_selected: config.scheme_selected,
})
}
@@ -134,7 +151,7 @@ impl Bar {
let elapsed = now.duration_since(self.block_last_updates[i]);
if elapsed >= block.interval() {
- if let Ok(_) = block.content() {
+ if block.content().is_ok() {
self.block_last_updates[i] = now;
changed = true;
}
@@ -167,7 +184,7 @@ impl Bar {
connection.change_gc(
self.graphics_context,
- &ChangeGCAux::new().foreground(SCHEME_NORMAL.background),
+ &ChangeGCAux::new().foreground(self.scheme_normal.background),
)?;
connection.poly_fill_rectangle(
self.window,
@@ -182,7 +199,7 @@ impl Bar {
let mut x_position: i16 = 0;
- for (tag_index, tag) in TAGS.iter().enumerate() {
+ for (tag_index, tag) in self.tags.iter().enumerate() {
let tag_mask = 1 << tag_index;
let is_selected = (current_tags & tag_mask) != 0;
let is_occupied = (occupied_tags & tag_mask) != 0;
@@ -190,11 +207,11 @@ impl Bar {
let tag_width = self.tag_widths[tag_index];
let scheme = if is_selected {
- &SCHEME_SELECTED
+ &self.scheme_selected
} else if is_occupied {
- &SCHEME_OCCUPIED
+ &self.scheme_occupied
} else {
- &SCHEME_NORMAL
+ &self.scheme_normal
};
let text_width = self.font.text_width(tag);
@@ -297,7 +314,7 @@ impl Bar {
}
current_x_position += tag_width as i16;
}
- return None;
+ None
}
pub fn needs_redraw(&self) -> bool {
diff --git a/src/bar/blocks/mod.rs b/src/bar/blocks/mod.rs
index e1babfb..ac73099 100644
--- a/src/bar/blocks/mod.rs
+++ b/src/bar/blocks/mod.rs
@@ -17,6 +17,7 @@ pub trait Block {
fn color(&self) -> u32;
}
+#[derive(Clone)]
pub struct BlockConfig {
pub format: &'static str,
pub command: BlockCommand,
@@ -25,6 +26,7 @@ pub struct BlockConfig {
pub underline: bool,
}
+#[derive(Clone)]
pub enum BlockCommand {
Shell(&'static str),
DateTime(&'static str),
diff --git a/src/bar/blocks/ram.rs b/src/bar/blocks/ram.rs
index 025525b..eeb8f66 100644
--- a/src/bar/blocks/ram.rs
+++ b/src/bar/blocks/ram.rs
@@ -40,8 +40,6 @@ impl Ram {
}
let used = total.saturating_sub(available);
- let used_gb = used as f32 / 1024.0 / 1024.0;
- let total_gb = total as f32 / 1024.0 / 1024.0;
let percentage = if total > 0 {
(used as f32 / total as f32) * 100.0
} else {
diff --git a/src/bin/main.rs b/src/bin/main.rs
new file mode 100644
index 0000000..6ba9687
--- /dev/null
+++ b/src/bin/main.rs
@@ -0,0 +1,195 @@
+use anyhow::Result;
+use std::path::PathBuf;
+use std::process::Command;
+
+fn main() -> Result<()> {
+ let args: Vec<String> = std::env::args().collect();
+
+ match args.get(1).map(|s| s.as_str()) {
+ Some("--init") => return init_config(),
+ Some("--recompile") => return recompile_config(),
+ Some("--version") => {
+ println!("oxwm {}", env!("CARGO_PKG_VERSION"));
+ return Ok(());
+ }
+ Some("--help") => {
+ print_help();
+ return Ok(());
+ }
+ _ => {}
+ }
+
+ let cache_binary = get_cache_binary_path();
+
+ if cache_binary.exists() {
+ let config_path = get_config_path().join("config.rs");
+ if should_recompile(&config_path, &cache_binary)? {
+ println!("Config changed, recompiling...");
+ recompile_config()?;
+ }
+
+ use std::os::unix::process::CommandExt;
+ let err = Command::new(&cache_binary).args(&args[1..]).exec();
+ anyhow::bail!("Failed to exec user binary: {}", err);
+ } else {
+ eprintln!("No configuration found.");
+ eprintln!("Run: oxwm --init");
+ eprintln!("Then add 'exec oxwm' to your ~/.xinitrc");
+ std::process::exit(1);
+ }
+}
+
+fn init_config() -> Result<()> {
+ let config_dir = get_config_path();
+ std::fs::create_dir_all(&config_dir)?;
+
+ if config_dir.join("config.rs").exists() {
+ eprintln!("Config already exists at ~/.config/oxwm/config.rs");
+ eprintln!("Remove it first if you want to reinitialize.");
+ return Ok(());
+ }
+
+ let config_template = include_str!("../../templates/config.rs");
+ std::fs::write(config_dir.join("config.rs"), config_template)?;
+
+ let main_template = include_str!("../../templates/main.rs");
+ std::fs::write(config_dir.join("main.rs"), main_template)?;
+
+ let manifest_dir = env!("CARGO_MANIFEST_DIR");
+ let cargo_toml = format!(
+ r#"[package]
+name = "oxwm-user"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+oxwm = {{ path = "{}" }}
+anyhow = "1"
+
+[[bin]]
+name = "oxwm-user"
+path = "main.rs"
+"#,
+ manifest_dir
+ );
+ std::fs::write(config_dir.join("Cargo.toml"), cargo_toml)?;
+
+ std::fs::write(config_dir.join(".gitignore"), "target/\nCargo.lock\n")?;
+
+ println!("✓ Created ~/.config/oxwm/config.rs");
+ println!("✓ Created ~/.config/oxwm/main.rs");
+ println!("✓ Edit ~/.config/oxwm/config.rs to customize your setup");
+
+ println!("\nCompiling initial configuration...");
+ recompile_config()?;
+
+ println!("\n✓ Setup complete!");
+ println!(" Add 'exec oxwm' to your ~/.xinitrc");
+ println!(" Reload config anytime with Mod+Shift+R");
+
+ Ok(())
+}
+
+fn recompile_config() -> Result<()> {
+ let config_dir = get_config_path();
+
+ if !config_dir.join("config.rs").exists() {
+ anyhow::bail!("No config found. Run: oxwm --init");
+ }
+
+ println!("Compiling oxwm configuration...");
+
+ let output = Command::new("cargo")
+ .args(&["build", "--release"])
+ .current_dir(&config_dir)
+ .output()?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ eprintln!("\n❌ Compilation failed:\n{}", stderr);
+
+ let _ = Command::new("notify-send")
+ .args(&[
+ "-u",
+ "critical",
+ "OXWM Compile Error",
+ "Check terminal for details",
+ ])
+ .spawn();
+
+ anyhow::bail!("Failed to compile configuration");
+ }
+
+ let source = config_dir.join("target/release/oxwm-user");
+ let dest = get_cache_binary_path();
+
+ std::fs::create_dir_all(dest.parent().unwrap())?;
+ std::fs::copy(&source, &dest)?;
+
+ println!("✓ Compiled successfully");
+
+ let _ = Command::new("notify-send")
+ .args(&[
+ "OXWM",
+ "Configuration recompiled! Hit Mod+Shift+R to restart.",
+ ])
+ .spawn();
+
+ Ok(())
+}
+
+fn should_recompile(config: &PathBuf, binary: &PathBuf) -> Result<bool> {
+ if !config.exists() {
+ return Ok(false);
+ }
+
+ let config_dir = get_config_path();
+ let binary_time = std::fs::metadata(binary)?.modified()?;
+
+ let watch_files = ["config.rs", "main.rs", "Cargo.toml"];
+
+ for filename in &watch_files {
+ let path = config_dir.join(filename);
+ if !path.exists() {
+ continue;
+ }
+
+ let file_time = std::fs::metadata(&path)?.modified()?;
+ if file_time > binary_time {
+ return Ok(true);
+ }
+ }
+
+ Ok(false)
+}
+
+fn get_config_path() -> PathBuf {
+ dirs::config_dir()
+ .expect("Could not find config directory")
+ .join("oxwm")
+}
+
+fn get_cache_binary_path() -> PathBuf {
+ dirs::cache_dir()
+ .expect("Could not find cache directory")
+ .join("oxwm/oxwm-binary")
+}
+
+fn print_help() {
+ println!("OXWM - A dynamic window manager written in Rust\n");
+ println!("USAGE:");
+ println!(" oxwm [OPTIONS]\n");
+ println!("OPTIONS:");
+ println!(" --init Initialize user configuration in ~/.config/oxwm");
+ println!(" --recompile Recompile user configuration");
+ println!(" --version Print version information");
+ println!(" --help Print this help message\n");
+ println!("SETUP:");
+ println!(" 1. Run 'oxwm --init' to create your config");
+ println!(" 2. Edit ~/.config/oxwm/config.rs");
+ println!(" 3. Add 'exec oxwm' to your ~/.xinitrc");
+ println!(" 4. Start X with 'startx'\n");
+ println!("CONFIGURATION:");
+ println!(" Config location: ~/.config/oxwm/config.rs");
+ println!(" Reload hotkey: Mod+Shift+R (auto-recompiles if needed)");
+}
diff --git a/src/keyboard/handlers.rs b/src/keyboard/handlers.rs
index 8313e86..3933192 100644
--- a/src/keyboard/handlers.rs
+++ b/src/keyboard/handlers.rs
@@ -1,4 +1,3 @@
-use crate::config::KEYBINDINGS;
use anyhow::Result;
use x11rb::connection::Connection;
use x11rb::protocol::xproto::*;
@@ -10,6 +9,7 @@ pub enum KeyAction {
FocusStack,
Quit,
Restart,
+ Recompile,
ViewTag,
ToggleGaps,
ToggleFullScreen,
@@ -17,7 +17,7 @@ pub enum KeyAction {
None,
}
-#[derive(Debug)]
+#[derive(Debug, Clone)]
pub enum Arg {
None,
Int(i32),
@@ -25,6 +25,13 @@ pub enum Arg {
Array(&'static [&'static str]),
}
+impl Arg {
+ pub const fn none() -> Self {
+ Arg::None
+ }
+}
+
+#[derive(Clone)]
pub struct Key {
pub(crate) modifiers: &'static [KeyButMask],
pub(crate) key: Keycode,
@@ -54,8 +61,12 @@ fn modifiers_to_mask(modifiers: &[KeyButMask]) -> u16 {
.fold(0u16, |acc, &modifier| acc | u16::from(modifier))
}
-pub fn setup_keybinds(connection: &impl Connection, root: Window) -> Result<()> {
- for keybinding in KEYBINDINGS {
+pub fn setup_keybinds(
+ connection: &impl Connection,
+ root: Window,
+ keybindings: &[Key],
+) -> Result<()> {
+ for keybinding in keybindings {
let modifier_mask = modifiers_to_mask(keybinding.modifiers);
connection.grab_key(
@@ -70,14 +81,14 @@ pub fn setup_keybinds(connection: &impl Connection, root: Window) -> Result<()>
Ok(())
}
-pub fn handle_key_press(event: KeyPressEvent) -> Result<(KeyAction, &'static Arg)> {
- for keybinding in KEYBINDINGS {
+pub fn handle_key_press(event: KeyPressEvent, keybindings: &[Key]) -> Result<(KeyAction, Arg)> {
+ for keybinding in keybindings {
let modifier_mask = modifiers_to_mask(keybinding.modifiers);
if event.detail == keybinding.key && event.state == modifier_mask.into() {
- return Ok((keybinding.func, &keybinding.arg));
+ return Ok((keybinding.func, keybinding.arg.clone()));
}
}
- Ok((KeyAction::None, &Arg::None))
+ Ok((KeyAction::None, Arg::None))
}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..dcd06a7
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,91 @@
+pub mod bar;
+pub mod keyboard;
+pub mod layout;
+pub mod window_manager;
+
+pub mod prelude {
+ pub use crate::ColorScheme;
+ pub use crate::bar::{BlockCommand, BlockConfig};
+ pub use crate::keyboard::{Arg, KeyAction, handlers::Key, keycodes};
+ pub use x11rb::protocol::xproto::KeyButMask;
+}
+
+#[derive(Clone)]
+pub struct Config {
+ // Appearance
+ pub border_width: u32,
+ pub border_focused: u32,
+ pub border_unfocused: u32,
+ pub font: String,
+
+ // Gaps
+ pub gaps_enabled: bool,
+ pub gap_inner_horizontal: u32,
+ pub gap_inner_vertical: u32,
+ pub gap_outer_horizontal: u32,
+ pub gap_outer_vertical: u32,
+
+ // Basics
+ pub terminal: String,
+ pub modkey: x11rb::protocol::xproto::KeyButMask,
+
+ // Tags
+ pub tags: Vec<String>,
+
+ // Keybindings
+ pub keybindings: Vec<crate::keyboard::handlers::Key>,
+
+ // Status bar
+ pub status_blocks: Vec<crate::bar::BlockConfig>,
+
+ // Bar color schemes
+ pub scheme_normal: ColorScheme,
+ pub scheme_occupied: ColorScheme,
+ pub scheme_selected: ColorScheme,
+}
+
+#[derive(Clone, Copy)]
+pub struct ColorScheme {
+ pub foreground: u32,
+ pub background: u32,
+ pub underline: u32,
+}
+
+impl Default for Config {
+ fn default() -> Self {
+ Self {
+ border_width: 2,
+ border_focused: 0x6dade3,
+ border_unfocused: 0xbbbbbb,
+ font: "monospace:size=12".to_string(),
+ gaps_enabled: false,
+ gap_inner_horizontal: 0,
+ gap_inner_vertical: 0,
+ gap_outer_horizontal: 0,
+ gap_outer_vertical: 0,
+ terminal: "xterm".to_string(),
+ modkey: x11rb::protocol::xproto::KeyButMask::MOD4,
+ tags: vec!["1", "2", "3", "4", "5", "6", "7", "8", "9"]
+ .into_iter()
+ .map(String::from)
+ .collect(),
+ keybindings: Vec::new(),
+ status_blocks: Vec::new(),
+ scheme_normal: ColorScheme {
+ foreground: 0xbbbbbb,
+ background: 0x1a1b26,
+ underline: 0x444444,
+ },
+ scheme_occupied: ColorScheme {
+ foreground: 0x0db9d7,
+ background: 0x1a1b26,
+ underline: 0x0db9d7,
+ },
+ scheme_selected: ColorScheme {
+ foreground: 0x0db9d7,
+ background: 0x1a1b26,
+ underline: 0xad8ee6,
+ },
+ }
+ }
+}
diff --git a/src/window_manager.rs b/src/window_manager.rs
index 5ebeaf1..56323a6 100644
--- a/src/window_manager.rs
+++ b/src/window_manager.rs
@@ -1,14 +1,13 @@
+use crate::Config;
use crate::bar::Bar;
-use crate::config::{
- BORDER_FOCUSED, BORDER_UNFOCUSED, BORDER_WIDTH, GAP_INNER_HORIZONTAL, GAP_INNER_VERTICAL,
- GAP_OUTER_HORIZONTAL, GAP_OUTER_VERTICAL, GAPS_ENABLED, MODKEY, TAG_COUNT,
-};
use crate::keyboard::{self, Arg, KeyAction};
use crate::layout::GapConfig;
use crate::layout::Layout;
use crate::layout::tiling::TilingLayout;
+
use anyhow::Result;
use std::collections::HashSet;
+use std::path::PathBuf;
use x11rb::connection::Connection;
use x11rb::protocol::Event;
@@ -21,6 +20,7 @@ pub fn tag_mask(tag: usize) -> TagMask {
}
pub struct WindowManager {
+ config: Config,
connection: RustConnection,
screen_number: usize,
root: Window,
@@ -37,7 +37,7 @@ pub struct WindowManager {
}
impl WindowManager {
- pub fn new() -> Result<Self> {
+ pub fn new(config: Config) -> Result<Self> {
let (connection, screen_number) = x11rb::connect(None)?;
let root = connection.setup().roots[screen_number].root;
let screen = connection.setup().roots[screen_number].clone();
@@ -64,7 +64,7 @@ impl WindowManager {
x11rb::NONE,
x11rb::NONE,
ButtonIndex::M1,
- u16::from(MODKEY).into(),
+ u16::from(config.modkey).into(),
)?;
connection.grab_button(
@@ -76,15 +76,16 @@ impl WindowManager {
x11rb::NONE,
x11rb::NONE,
ButtonIndex::M3,
- u16::from(MODKEY).into(),
+ u16::from(config.modkey).into(),
)?;
- let bar = Bar::new(&connection, &screen, screen_number)?;
+ let bar = Bar::new(&connection, &screen, screen_number, &config)?;
- let selected_tags = Self::get_saved_selected_tags(&connection, root)?;
- let gaps_enabled = GAPS_ENABLED;
+ let selected_tags = Self::get_saved_selected_tags(&connection, root, config.tags.len())?;
+ let gaps_enabled = config.gaps_enabled;
- let mut window_manger = Self {
+ let mut window_manager = Self {
+ config,
connection,
screen_number,
root,
@@ -100,13 +101,17 @@ impl WindowManager {
bar,
};
- window_manger.scan_existing_windows()?;
- window_manger.update_bar()?;
+ window_manager.scan_existing_windows()?;
+ window_manager.update_bar()?;
- Ok(window_manger)
+ Ok(window_manager)
}
- fn get_saved_selected_tags(connection: &RustConnection, root: Window) -> Result<TagMask> {
+ fn get_saved_selected_tags(
+ connection: &RustConnection,
+ root: Window,
+ tag_count: usize,
+ ) -> Result<TagMask> {
let net_current_desktop = connection
.intern_atom(false, b"_NET_CURRENT_DESKTOP")?
.reply()?
@@ -123,7 +128,7 @@ impl WindowManager {
prop.value[2],
prop.value[3],
]);
- if desktop < TAG_COUNT as u32 {
+ if desktop < tag_count as u32 {
let mask = tag_mask(desktop as usize);
return Ok(mask);
}
@@ -224,7 +229,7 @@ impl WindowManager {
prop.value[3],
]);
- if tags != 0 && tags < (1 << crate::config::TAG_COUNT) {
+ if tags != 0 && tags < (1 << self.config.tags.len()) {
return Ok(tags);
}
}
@@ -284,10 +289,81 @@ impl WindowManager {
Ok(())
}
+ fn handle_restart(&self) -> Result<bool> {
+ if !can_recompile() {
+ eprintln!("Error: cargo not found. Install rust toolchain.");
+ notify_error("OXWM", "Cannot recompile: cargo not installed");
+ return Ok(false);
+ }
+
+ if self.needs_recompile()? {
+ println!("Config changed, recompiling...");
+ self.recompile()?;
+ }
+
+ Ok(true)
+ }
+
+ fn needs_recompile(&self) -> Result<bool> {
+ let config_dir = get_config_path();
+ let binary_path = get_cache_binary_path();
+
+ if !binary_path.exists() {
+ return Ok(true);
+ }
+
+ let binary_time = std::fs::metadata(&binary_path)?.modified()?;
+
+ let watch_files = ["config.rs", "main.rs", "Cargo.toml"];
+
+ for filename in &watch_files {
+ let path = config_dir.join(filename);
+ if !path.exists() {
+ continue;
+ }
+
+ let file_time = std::fs::metadata(&path)?.modified()?;
+ if file_time > binary_time {
+ println!("✓ Change detected: {}", filename);
+ return Ok(true);
+ }
+ }
+
+ Ok(false)
+ }
+
+ fn recompile(&self) -> Result<()> {
+ let config_dir = get_config_path();
+
+ notify("OXWM", "Recompiling configuration...");
+
+ let output = std::process::Command::new("cargo")
+ .args(&["build", "--release"])
+ .current_dir(&config_dir)
+ .output()?;
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ notify_error("OXWM Compile Error", &stderr);
+ eprintln!("Compilation failed:\n{}", stderr);
+ anyhow::bail!("Failed to compile configuration");
+ }
+
+ let source = config_dir.join("target/release/oxwm-user");
+ let dest = get_cache_binary_path();
+
+ std::fs::create_dir_all(dest.parent().unwrap())?;
+ std::fs::copy(&source, &dest)?;
+
+ notify("OXWM", "Recompiled successfully! Restarting...");
+
+ Ok(())
+ }
+
pub fn run(&mut self) -> Result<bool> {
println!("oxwm started on display {}", self.screen_number);
- keyboard::setup_keybinds(&self.connection, self.root)?;
+ keyboard::setup_keybinds(&self.connection, self.root, &self.config.keybindings)?;
self.update_bar()?;
loop {
@@ -387,7 +463,12 @@ impl WindowManager {
}
}
KeyAction::Quit | KeyAction::Restart => {
- //no-op
+ // Handled in handle_event
+ }
+ KeyAction::Recompile => {
+ if let Err(e) = self.recompile() {
+ eprintln!("Recompile failed: {}", e);
+ }
}
KeyAction::ViewTag => {
if let Arg::Int(tag_index) = arg {
@@ -403,9 +484,7 @@ impl WindowManager {
self.gaps_enabled = !self.gaps_enabled;
self.apply_layout()?;
}
- KeyAction::None => {
- //no-op
- }
+ KeyAction::None => {}
}
Ok(())
}
@@ -439,7 +518,7 @@ impl WindowManager {
}
pub fn view_tag(&mut self, tag_index: usize) -> Result<()> {
- if tag_index >= TAG_COUNT {
+ if tag_index >= self.config.tags.len() {
return Ok(());
}
@@ -487,7 +566,7 @@ impl WindowManager {
}
pub fn move_to_tag(&mut self, tag_index: usize) -> Result<()> {
- if tag_index >= TAG_COUNT {
+ if tag_index >= self.config.tags.len() {
return Ok(());
}
@@ -547,9 +626,9 @@ impl WindowManager {
fn update_focus_visuals(&self) -> Result<()> {
for &window in &self.windows {
let (border_color, border_width) = if self.focused_window == Some(window) {
- (BORDER_FOCUSED, BORDER_WIDTH)
+ (self.config.border_focused, self.config.border_width)
} else {
- (BORDER_UNFOCUSED, BORDER_WIDTH)
+ (self.config.border_unfocused, self.config.border_width)
};
self.connection.configure_window(
@@ -585,7 +664,7 @@ impl WindowManager {
.reply()?;
let pointer = self.connection.query_pointer(self.root)?.reply()?;
- let (original_x_value, original_y_value) = (pointer.root_x, pointer.root_y);
+ let (start_x, start_y) = (pointer.root_x, pointer.root_y);
self.connection.configure_window(
window,
@@ -596,13 +675,11 @@ impl WindowManager {
let event = self.connection.wait_for_event()?;
match event {
Event::MotionNotify(e) => {
- let new_x_value = geometry.x + (e.root_x - original_x_value);
- let new_y_value = geometry.y + (e.root_y - original_y_value);
+ let new_x = geometry.x + (e.root_x - start_x);
+ let new_y = geometry.y + (e.root_y - start_y);
self.connection.configure_window(
window,
- &ConfigureWindowAux::new()
- .x(new_x_value as i32)
- .y(new_y_value as i32),
+ &ConfigureWindowAux::new().x(new_x as i32).y(new_y as i32),
)?;
self.connection.flush()?;
}
@@ -735,11 +812,11 @@ impl WindowManager {
}
}
Event::KeyPress(event) => {
- let (action, arg) = keyboard::handle_key_press(event)?;
+ let (action, arg) = keyboard::handle_key_press(event, &self.config.keybindings)?;
match action {
KeyAction::Quit => return Ok(Some(false)),
- KeyAction::Restart => return Ok(Some(true)),
- _ => self.handle_key_action(action, arg)?,
+ KeyAction::Restart => return Ok(Some(self.handle_restart()?)),
+ _ => self.handle_key_action(action, &arg)?,
}
}
Event::ButtonPress(event) => {
@@ -774,17 +851,17 @@ impl WindowManager {
}
let screen_width = self.screen.width_in_pixels as u32;
let screen_height = self.screen.height_in_pixels as u32;
- let border_width = BORDER_WIDTH;
+ let border_width = self.config.border_width;
let bar_height = self.bar.height() as u32;
let usable_height = screen_height.saturating_sub(bar_height);
let gaps = if self.gaps_enabled {
GapConfig {
- inner_horizontal: GAP_INNER_HORIZONTAL,
- inner_vertical: GAP_INNER_VERTICAL,
- outer_horizontal: GAP_OUTER_HORIZONTAL,
- outer_vertical: GAP_OUTER_VERTICAL,
+ inner_horizontal: self.config.gap_inner_horizontal,
+ inner_vertical: self.config.gap_inner_vertical,
+ outer_horizontal: self.config.gap_outer_horizontal,
+ outer_vertical: self.config.gap_outer_vertical,
}
} else {
GapConfig {
@@ -851,3 +928,35 @@ impl WindowManager {
Ok(())
}
}
+
+fn get_config_path() -> PathBuf {
+ dirs::config_dir()
+ .expect("Could not find config directory")
+ .join("oxwm")
+}
+
+fn get_cache_binary_path() -> PathBuf {
+ dirs::cache_dir()
+ .expect("Could not find cache directory")
+ .join("oxwm/oxwm-binary")
+}
+
+fn can_recompile() -> bool {
+ std::process::Command::new("cargo")
+ .arg("--version")
+ .output()
+ .map(|o| o.status.success())
+ .unwrap_or(false)
+}
+
+fn notify(title: &str, body: &str) {
+ let _ = std::process::Command::new("notify-send")
+ .args(&[title, body])
+ .spawn();
+}
+
+fn notify_error(title: &str, body: &str) {
+ let _ = std::process::Command::new("notify-send")
+ .args(&["-u", "critical", title, body])
+ .spawn();
+}
diff --git a/templates/config.rs b/templates/config.rs
new file mode 100644
index 0000000..cc10649
--- /dev/null
+++ b/templates/config.rs
@@ -0,0 +1,212 @@
+use oxwm::ColorScheme;
+use oxwm::prelude::*;
+
+// ========================================
+// APPEARANCE
+// ========================================
+pub const BORDER_WIDTH: u32 = 2;
+pub const BORDER_FOCUSED: u32 = 0x6dade3;
+pub const BORDER_UNFOCUSED: u32 = 0xbbbbbb;
+pub const FONT: &str = "JetBrainsMono Nerd Font:style=Bold:size=12";
+
+// ========================================
+// GAPS (Vanity Gaps)
+// ========================================
+pub const GAPS_ENABLED: bool = false;
+pub const GAP_INNER_HORIZONTAL: u32 = 3;
+pub const GAP_INNER_VERTICAL: u32 = 3;
+pub const GAP_OUTER_HORIZONTAL: u32 = 3;
+pub const GAP_OUTER_VERTICAL: u32 = 3;
+
+// ========================================
+// DEFAULTS
+// ========================================
+pub const TERMINAL: &str = "st";
+pub const MODKEY: KeyButMask = KeyButMask::MOD4;
+
+// ========================================
+// TAGS (Workspaces)
+// ========================================
+pub const TAG_COUNT: usize = 9;
+pub const TAGS: [&str; TAG_COUNT] = ["1", "2", "3", "4", "5", "6", "7", "8", "9"];
+
+// Alternative tag styles you can use:
+// pub const TAGS: [&str; TAG_COUNT] = ["", "", "", "", "", "", "", "", ""];
+// pub const TAGS: [&str; TAG_COUNT] = ["DEV", "WWW", "SYS", "DOC", "VBOX", "CHAT", "MUS", "VID", "MISC"];
+
+// ========================================
+// BAR COLORS
+// ========================================
+const GRAY_DARK: u32 = 0x1a1b26;
+const GRAY_SEP: u32 = 0xa9b1d6;
+const GRAY_MID: u32 = 0x444444;
+const GRAY_LIGHT: u32 = 0xbbbbbb;
+const CYAN: u32 = 0x0db9d7;
+const MAGENTA: u32 = 0xad8ee6;
+const RED: u32 = 0xf7768e;
+const GREEN: u32 = 0x9ece6a;
+const BLUE: u32 = 0x7aa2f7;
+const YELLOW: u32 = 0xe0af68;
+
+pub const SCHEME_NORMAL: ColorScheme = ColorScheme {
+ foreground: GRAY_LIGHT,
+ background: GRAY_DARK,
+ underline: GRAY_MID,
+};
+
+pub const SCHEME_OCCUPIED: ColorScheme = ColorScheme {
+ foreground: CYAN,
+ background: GRAY_DARK,
+ underline: CYAN,
+};
+
+pub const SCHEME_SELECTED: ColorScheme = ColorScheme {
+ foreground: CYAN,
+ background: GRAY_DARK,
+ underline: MAGENTA,
+};
+
+// ========================================
+// COMMANDS
+// ========================================
+const SCREENSHOT_CMD: &[&str] = &[
+ "sh",
+ "-c",
+ "maim -s | xclip -selection clipboard -t image/png",
+];
+
+const DMENU_CMD: &[&str] = &["sh", "-c", "dmenu_run -l 10"];
+
+// ========================================
+// KEYBINDINGS
+// ========================================
+const SHIFT: KeyButMask = KeyButMask::SHIFT;
+
+#[rustfmt::skip]
+pub const KEYBINDINGS: &[Key] = &[
+ // Launch applications
+ Key::new(&[MODKEY], keycodes::RETURN, KeyAction::Spawn, Arg::Str(TERMINAL)),
+ Key::new(&[MODKEY], keycodes::F, KeyAction::Spawn, Arg::Str("xclock")),
+ Key::new(&[MODKEY], keycodes::S, KeyAction::Spawn, Arg::Array(SCREENSHOT_CMD)),
+ Key::new(&[MODKEY], keycodes::D, KeyAction::Spawn, Arg::Array(DMENU_CMD)),
+
+ // Window management
+ Key::new(&[MODKEY], keycodes::Q, KeyAction::KillClient, Arg::None),
+ Key::new(&[MODKEY, SHIFT], keycodes::F, KeyAction::ToggleFullScreen, Arg::None),
+ Key::new(&[MODKEY], keycodes::A, KeyAction::ToggleGaps, Arg::None),
+
+ // WM controls
+ Key::new(&[MODKEY, SHIFT], keycodes::Q, KeyAction::Quit, Arg::None),
+ Key::new(&[MODKEY, SHIFT], keycodes::R, KeyAction::Restart, Arg::None),
+ Key::new(&[MODKEY, SHIFT], keycodes::C, KeyAction::Recompile, Arg::None),
+
+ // Focus
+ Key::new(&[MODKEY], keycodes::J, KeyAction::FocusStack, Arg::Int(-1)),
+ Key::new(&[MODKEY], keycodes::K, KeyAction::FocusStack, Arg::Int(1)),
+
+ // View tags (workspaces)
+ Key::new(&[MODKEY], keycodes::KEY_1, KeyAction::ViewTag, Arg::Int(0)),
+ Key::new(&[MODKEY], keycodes::KEY_2, KeyAction::ViewTag, Arg::Int(1)),
+ Key::new(&[MODKEY], keycodes::KEY_3, KeyAction::ViewTag, Arg::Int(2)),
+ Key::new(&[MODKEY], keycodes::KEY_4, KeyAction::ViewTag, Arg::Int(3)),
+ Key::new(&[MODKEY], keycodes::KEY_5, KeyAction::ViewTag, Arg::Int(4)),
+ Key::new(&[MODKEY], keycodes::KEY_6, KeyAction::ViewTag, Arg::Int(5)),
+ Key::new(&[MODKEY], keycodes::KEY_7, KeyAction::ViewTag, Arg::Int(6)),
+ Key::new(&[MODKEY], keycodes::KEY_8, KeyAction::ViewTag, Arg::Int(7)),
+ Key::new(&[MODKEY], keycodes::KEY_9, KeyAction::ViewTag, Arg::Int(8)),
+
+ // Move windows to tags
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_1, KeyAction::MoveToTag, Arg::Int(0)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_2, KeyAction::MoveToTag, Arg::Int(1)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_3, KeyAction::MoveToTag, Arg::Int(2)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_4, KeyAction::MoveToTag, Arg::Int(3)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_5, KeyAction::MoveToTag, Arg::Int(4)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_6, KeyAction::MoveToTag, Arg::Int(5)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_7, KeyAction::MoveToTag, Arg::Int(6)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_8, KeyAction::MoveToTag, Arg::Int(7)),
+ Key::new(&[MODKEY, SHIFT], keycodes::KEY_9, KeyAction::MoveToTag, Arg::Int(8)),
+];
+
+// ========================================
+// STATUS BAR BLOCKS
+// ========================================
+pub const STATUS_BLOCKS: &[BlockConfig] = &[
+ BlockConfig {
+ format: "",
+ command: BlockCommand::Battery {
+ format_charging: " Bat: {}%",
+ format_discharging: " Bat:{}%",
+ format_full: " Bat: {}%",
+ },
+ interval_secs: 30,
+ color: GREEN,
+ underline: true,
+ },
+ BlockConfig {
+ format: " │ ",
+ command: BlockCommand::Static(""),
+ interval_secs: u64::MAX,
+ color: GRAY_SEP,
+ underline: false,
+ },
+ BlockConfig {
+ format: " {used}/{total} GB",
+ command: BlockCommand::Ram,
+ interval_secs: 5,
+ color: BLUE,
+ underline: true,
+ },
+ BlockConfig {
+ format: " │ ",
+ command: BlockCommand::Static(""),
+ interval_secs: u64::MAX,
+ color: GRAY_SEP,
+ underline: false,
+ },
+ BlockConfig {
+ format: " {}",
+ command: BlockCommand::Shell("uname -r"),
+ interval_secs: u64::MAX,
+ color: RED,
+ underline: true,
+ },
+ BlockConfig {
+ format: " │ ",
+ command: BlockCommand::Static(""),
+ interval_secs: u64::MAX,
+ color: GRAY_SEP,
+ underline: false,
+ },
+ BlockConfig {
+ format: " {}",
+ command: BlockCommand::DateTime("%a, %b %d - %-I:%M %P"),
+ interval_secs: 1,
+ color: CYAN,
+ underline: true,
+ },
+];
+
+// ========================================
+// BUILD CONFIG FROM CONSTANTS
+// ========================================
+pub fn build_config() -> oxwm::Config {
+ oxwm::Config {
+ border_width: BORDER_WIDTH,
+ border_focused: BORDER_FOCUSED,
+ border_unfocused: BORDER_UNFOCUSED,
+ font: FONT.to_string(),
+ gaps_enabled: GAPS_ENABLED,
+ gap_inner_horizontal: GAP_INNER_HORIZONTAL,
+ gap_inner_vertical: GAP_INNER_VERTICAL,
+ gap_outer_horizontal: GAP_OUTER_HORIZONTAL,
+ gap_outer_vertical: GAP_OUTER_VERTICAL,
+ terminal: TERMINAL.to_string(),
+ modkey: MODKEY,
+ tags: TAGS.iter().map(|s| s.to_string()).collect(),
+ keybindings: KEYBINDINGS.to_vec(),
+ status_blocks: STATUS_BLOCKS.to_vec(),
+ scheme_normal: SCHEME_NORMAL,
+ scheme_occupied: SCHEME_OCCUPIED,
+ scheme_selected: SCHEME_SELECTED,
+ }
+}
diff --git a/templates/main.rs b/templates/main.rs
new file mode 100644
index 0000000..97c27c0
--- /dev/null
+++ b/templates/main.rs
@@ -0,0 +1,21 @@
+mod config;
+
+use anyhow::Result;
+
+fn main() -> Result<()> {
+ let cfg = config::build_config();
+
+ let args: Vec<String> = std::env::args().collect();
+ let mut wm = oxwm::window_manager::WindowManager::new(cfg)?;
+ let should_restart = wm.run()?;
+
+ drop(wm);
+
+ if should_restart {
+ use std::os::unix::process::CommandExt;
+ let err = std::process::Command::new(&args[0]).args(&args[1..]).exec();
+ eprintln!("Failed to restart: {}", err);
+ }
+
+ Ok(())
+}