fixes
Authored by
mfwolffe <wolffemf@dukes.jmu.edu>
- SHA
e0f66b34e861915965c0534f6736f8f73065be82- Parents
-
83272c3 - Tree
01d20b4
e0f66b3
e0f66b34e861915965c0534f6736f8f73065be8283272c3
01d20b4| Status | File | + | - |
|---|---|---|---|
| M |
gar-session.sh
|
2 | 2 |
| M |
gar/src/config/lua.rs
|
35 | 2 |
| M |
gar/src/config/mod.rs
|
4 | 0 |
| M |
gar/src/core/mod.rs
|
3 | 0 |
| M |
gar/src/x11/events.rs
|
148 | 0 |
gar-session.shmodified@@ -47,8 +47,8 @@ fi | ||
| 47 | 47 | # Set log level |
| 48 | 48 | export GAR_LOG=info |
| 49 | 49 | |
| 50 | -# Launch polybar after gar starts (needs i3 IPC socket) | |
| 51 | -(sleep 0.5 && ~/.config/polybar/launch.sh) & | |
| 50 | +# garbar is now launched natively by gar when gar.bar is configured in init.lua | |
| 51 | +# (Legacy polybar launch removed - use gar.bar config instead) | |
| 52 | 52 | |
| 53 | 53 | # Start gar |
| 54 | 54 | exec /home/mfwolffe/GithubOrgs/tenseleyFlow/gar/target/release/gar |
gar/src/config/lua.rsmodified@@ -114,15 +114,48 @@ impl LuaConfig { | ||
| 114 | 114 | |
| 115 | 115 | self.lua.load(&source).exec()?; |
| 116 | 116 | |
| 117 | + // Check if gar.bar table exists - enables garbar integration | |
| 118 | + self.check_bar_config()?; | |
| 119 | + | |
| 117 | 120 | let state = self.state.lock().unwrap(); |
| 118 | 121 | tracing::info!( |
| 119 | - "Config loaded: {} keybinds registered", | |
| 120 | - state.keybinds.len() | |
| 122 | + "Config loaded: {} keybinds registered, bar_enabled={}", | |
| 123 | + state.keybinds.len(), | |
| 124 | + state.config.bar_enabled | |
| 121 | 125 | ); |
| 122 | 126 | |
| 123 | 127 | Ok(()) |
| 124 | 128 | } |
| 125 | 129 | |
| 130 | + /// Check if gar.bar table is configured, enabling garbar integration | |
| 131 | + fn check_bar_config(&self) -> LuaResult<()> { | |
| 132 | + let globals = self.lua.globals(); | |
| 133 | + let gar: Table = globals.get("gar")?; | |
| 134 | + | |
| 135 | + // Check if gar.bar exists and is a table | |
| 136 | + match gar.get::<Table>("bar") { | |
| 137 | + Ok(bar_table) => { | |
| 138 | + // gar.bar exists! Enable garbar integration | |
| 139 | + let mut state = self.state.lock().unwrap(); | |
| 140 | + state.config.bar_enabled = true; | |
| 141 | + | |
| 142 | + // Optionally read bar height from config for reserved space | |
| 143 | + if let Ok(height) = bar_table.get::<u32>("height") { | |
| 144 | + state.config.bar_height = height; | |
| 145 | + tracing::info!("garbar integration enabled (height={})", height); | |
| 146 | + } else { | |
| 147 | + tracing::info!("garbar integration enabled (default height)"); | |
| 148 | + } | |
| 149 | + } | |
| 150 | + Err(_) => { | |
| 151 | + // gar.bar not set, garbar won't be spawned | |
| 152 | + tracing::debug!("gar.bar not configured, garbar integration disabled"); | |
| 153 | + } | |
| 154 | + } | |
| 155 | + | |
| 156 | + Ok(()) | |
| 157 | + } | |
| 158 | + | |
| 126 | 159 | /// Reload configuration (clears existing keybinds and rules) |
| 127 | 160 | pub fn reload(&self) -> LuaResult<()> { |
| 128 | 161 | { |
gar/src/config/mod.rsmodified@@ -32,6 +32,8 @@ pub struct Config { | ||
| 32 | 32 | pub mouse_follows_focus: bool, |
| 33 | 33 | // Manual bar/panel reserved space (overrides struts) |
| 34 | 34 | pub bar_height: u32, |
| 35 | + // garbar integration: spawn garbar automatically if gar.bar is configured | |
| 36 | + pub bar_enabled: bool, | |
| 35 | 37 | // Compositor visual settings (picom) |
| 36 | 38 | // These are stored for reference and potential dynamic picom config generation |
| 37 | 39 | pub corner_radius: u32, |
@@ -405,6 +407,8 @@ impl Default for Config { | ||
| 405 | 407 | mouse_follows_focus: false, |
| 406 | 408 | // Manual bar height (0 = use struts from dock windows) |
| 407 | 409 | bar_height: 0, |
| 410 | + // garbar not enabled by default (enabled when gar.bar table is set) | |
| 411 | + bar_enabled: false, | |
| 408 | 412 | // Compositor settings (picom) - matching picom.conf defaults |
| 409 | 413 | corner_radius: 12, |
| 410 | 414 | blur_enabled: true, |
gar/src/core/mod.rsmodified@@ -45,6 +45,8 @@ pub struct WindowManager { | ||
| 45 | 45 | pub dock_struts: HashMap<XWindow, Strut>, |
| 46 | 46 | /// Current edge being displayed (for cursor changes on floating window edges) |
| 47 | 47 | pub current_edge_cursor: Option<(XWindow, crate::x11::events::ResizeEdge)>, |
| 48 | + /// garbar child process (managed automatically when gar.bar is configured) | |
| 49 | + pub garbar_process: Option<std::process::Child>, | |
| 48 | 50 | } |
| 49 | 51 | |
| 50 | 52 | impl WindowManager { |
@@ -140,6 +142,7 @@ impl WindowManager { | ||
| 140 | 142 | focus_history: Vec::new(), |
| 141 | 143 | dock_struts: HashMap::new(), |
| 142 | 144 | current_edge_cursor: None, |
| 145 | + garbar_process: None, | |
| 143 | 146 | }) |
| 144 | 147 | } |
| 145 | 148 | |
gar/src/x11/events.rsmodified@@ -71,6 +71,109 @@ fn stop_graphical_session() { | ||
| 71 | 71 | } |
| 72 | 72 | } |
| 73 | 73 | } |
| 74 | + | |
| 75 | +/// Spawn garbar as a child process. | |
| 76 | +/// Returns the child process handle if successful. | |
| 77 | +fn spawn_garbar() -> Option<std::process::Child> { | |
| 78 | + tracing::info!("Spawning garbar..."); | |
| 79 | + | |
| 80 | + // Try to find garbar in PATH or common locations | |
| 81 | + let garbar_cmd = which_garbar().unwrap_or_else(|| "garbar".to_string()); | |
| 82 | + | |
| 83 | + // Determine i3 IPC socket path (same as gar's i3_server.rs) | |
| 84 | + let i3sock = std::env::var("XDG_RUNTIME_DIR") | |
| 85 | + .map(|dir| format!("{}/gar-i3.sock", dir)) | |
| 86 | + .unwrap_or_else(|_| "/tmp/gar-i3.sock".to_string()); | |
| 87 | + | |
| 88 | + match Command::new(&garbar_cmd) | |
| 89 | + .arg("daemon") | |
| 90 | + .env("I3SOCK", &i3sock) | |
| 91 | + .spawn() | |
| 92 | + { | |
| 93 | + Ok(child) => { | |
| 94 | + tracing::info!("garbar started (PID {}), I3SOCK={}", child.id(), i3sock); | |
| 95 | + Some(child) | |
| 96 | + } | |
| 97 | + Err(e) => { | |
| 98 | + tracing::error!("Failed to spawn garbar: {}", e); | |
| 99 | + tracing::info!("Hint: Ensure garbar is installed and in PATH, or use 'cargo install --path garbar'"); | |
| 100 | + None | |
| 101 | + } | |
| 102 | + } | |
| 103 | +} | |
| 104 | + | |
| 105 | +/// Find garbar executable | |
| 106 | +fn which_garbar() -> Option<String> { | |
| 107 | + // Check if garbar is in PATH | |
| 108 | + if Command::new("which") | |
| 109 | + .arg("garbar") | |
| 110 | + .output() | |
| 111 | + .map(|o| o.status.success()) | |
| 112 | + .unwrap_or(false) | |
| 113 | + { | |
| 114 | + return Some("garbar".to_string()); | |
| 115 | + } | |
| 116 | + | |
| 117 | + // Check common cargo install location | |
| 118 | + if let Ok(home) = std::env::var("HOME") { | |
| 119 | + let cargo_bin = format!("{}/.cargo/bin/garbar", home); | |
| 120 | + if std::path::Path::new(&cargo_bin).exists() { | |
| 121 | + return Some(cargo_bin); | |
| 122 | + } | |
| 123 | + } | |
| 124 | + | |
| 125 | + // Check /usr/local/bin | |
| 126 | + if std::path::Path::new("/usr/local/bin/garbar").exists() { | |
| 127 | + return Some("/usr/local/bin/garbar".to_string()); | |
| 128 | + } | |
| 129 | + | |
| 130 | + None | |
| 131 | +} | |
| 132 | + | |
| 133 | +/// Stop garbar gracefully by sending SIGTERM. | |
| 134 | +fn stop_garbar(child: &mut std::process::Child) { | |
| 135 | + tracing::info!("Stopping garbar (PID {})...", child.id()); | |
| 136 | + | |
| 137 | + // Send SIGTERM for graceful shutdown | |
| 138 | + unsafe { | |
| 139 | + libc::kill(child.id() as i32, libc::SIGTERM); | |
| 140 | + } | |
| 141 | + | |
| 142 | + // Wait briefly for it to exit | |
| 143 | + match child.try_wait() { | |
| 144 | + Ok(Some(status)) => { | |
| 145 | + tracing::info!("garbar exited with status: {}", status); | |
| 146 | + } | |
| 147 | + Ok(None) => { | |
| 148 | + // Give it a moment to shut down | |
| 149 | + std::thread::sleep(std::time::Duration::from_millis(100)); | |
| 150 | + match child.try_wait() { | |
| 151 | + Ok(Some(status)) => { | |
| 152 | + tracing::info!("garbar exited with status: {}", status); | |
| 153 | + } | |
| 154 | + Ok(None) => { | |
| 155 | + // Force kill if still running | |
| 156 | + tracing::warn!("garbar did not exit gracefully, sending SIGKILL"); | |
| 157 | + let _ = child.kill(); | |
| 158 | + } | |
| 159 | + Err(e) => { | |
| 160 | + tracing::warn!("Error waiting for garbar: {}", e); | |
| 161 | + } | |
| 162 | + } | |
| 163 | + } | |
| 164 | + Err(e) => { | |
| 165 | + tracing::warn!("Error checking garbar status: {}", e); | |
| 166 | + } | |
| 167 | + } | |
| 168 | +} | |
| 169 | + | |
| 170 | +/// Signal garbar to reload its configuration (SIGHUP). | |
| 171 | +fn reload_garbar(child: &std::process::Child) { | |
| 172 | + tracing::info!("Signaling garbar to reload (PID {})...", child.id()); | |
| 173 | + unsafe { | |
| 174 | + libc::kill(child.id() as i32, libc::SIGHUP); | |
| 175 | + } | |
| 176 | +} | |
| 74 | 177 | use x11rb::protocol::xproto::{ |
| 75 | 178 | ButtonPressEvent, ButtonReleaseEvent, ClientMessageEvent, ConfigureRequestEvent, |
| 76 | 179 | ConfigureWindowAux, ConnectionExt, DestroyNotifyEvent, EnterNotifyEvent, EventMask, |
@@ -1562,6 +1665,40 @@ impl WindowManager { | ||
| 1562 | 1665 | // Re-apply layout with new settings |
| 1563 | 1666 | self.apply_layout()?; |
| 1564 | 1667 | |
| 1668 | + // Handle garbar lifecycle based on new config | |
| 1669 | + if self.config.bar_enabled { | |
| 1670 | + // Check if garbar is still running | |
| 1671 | + let garbar_alive = if let Some(ref mut child) = self.garbar_process { | |
| 1672 | + match child.try_wait() { | |
| 1673 | + Ok(None) => true, // Still running | |
| 1674 | + Ok(Some(status)) => { | |
| 1675 | + tracing::info!("garbar exited with status {}, will respawn", status); | |
| 1676 | + false | |
| 1677 | + } | |
| 1678 | + Err(e) => { | |
| 1679 | + tracing::warn!("Failed to check garbar status: {}", e); | |
| 1680 | + false | |
| 1681 | + } | |
| 1682 | + } | |
| 1683 | + } else { | |
| 1684 | + false | |
| 1685 | + }; | |
| 1686 | + | |
| 1687 | + if garbar_alive { | |
| 1688 | + // garbar running, signal it to reload | |
| 1689 | + if let Some(ref child) = self.garbar_process { | |
| 1690 | + reload_garbar(child); | |
| 1691 | + } | |
| 1692 | + } else { | |
| 1693 | + // garbar not running, spawn it | |
| 1694 | + self.garbar_process = spawn_garbar(); | |
| 1695 | + } | |
| 1696 | + } else if let Some(ref mut child) = self.garbar_process { | |
| 1697 | + // bar_enabled is now false, stop garbar | |
| 1698 | + stop_garbar(child); | |
| 1699 | + self.garbar_process = None; | |
| 1700 | + } | |
| 1701 | + | |
| 1565 | 1702 | tracing::info!("Configuration reloaded"); |
| 1566 | 1703 | Ok(()) |
| 1567 | 1704 | } |
@@ -1582,6 +1719,11 @@ impl WindowManager { | ||
| 1582 | 1719 | // This allows user services (like garbg) bound to graphical-session.target to start |
| 1583 | 1720 | start_graphical_session(); |
| 1584 | 1721 | |
| 1722 | + // Spawn garbar if gar.bar is configured | |
| 1723 | + if self.config.bar_enabled { | |
| 1724 | + self.garbar_process = spawn_garbar(); | |
| 1725 | + } | |
| 1726 | + | |
| 1585 | 1727 | while self.running { |
| 1586 | 1728 | // Handle X11 events (non-blocking poll) |
| 1587 | 1729 | while let Some(event) = self.conn.conn.poll_for_event()? { |
@@ -1601,6 +1743,12 @@ impl WindowManager { | ||
| 1601 | 1743 | std::thread::sleep(std::time::Duration::from_millis(10)); |
| 1602 | 1744 | } |
| 1603 | 1745 | |
| 1746 | + // Stop garbar if it was spawned | |
| 1747 | + if let Some(ref mut child) = self.garbar_process { | |
| 1748 | + stop_garbar(child); | |
| 1749 | + } | |
| 1750 | + self.garbar_process = None; | |
| 1751 | + | |
| 1604 | 1752 | // Signal systemd that graphical session has ended |
| 1605 | 1753 | // This stops user services bound to graphical-session.target (like garbg) |
| 1606 | 1754 | stop_graphical_session(); |