├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src ├── config.rs ├── cycle.rs ├── deck.rs ├── gallium.rs ├── gallium_log.rs ├── key.rs ├── layout.rs ├── window_manager.rs └── xserver.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bitflags" 5 | version = "0.1.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "2a6577517ecd0ee0934f48a7295a89aaef3e6dfafeac404f94c0b3448518ddfe" 8 | 9 | [[package]] 10 | name = "cfg-if" 11 | version = "1.0.0" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 14 | 15 | [[package]] 16 | name = "gallium" 17 | version = "0.0.1" 18 | dependencies = [ 19 | "bitflags", 20 | "libc", 21 | "log 0.4.14", 22 | "rustc-serialize", 23 | "x11", 24 | "xcb", 25 | ] 26 | 27 | [[package]] 28 | name = "libc" 29 | version = "0.2.95" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" 32 | 33 | [[package]] 34 | name = "log" 35 | version = "0.3.9" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 38 | dependencies = [ 39 | "log 0.4.14", 40 | ] 41 | 42 | [[package]] 43 | name = "log" 44 | version = "0.4.14" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 47 | dependencies = [ 48 | "cfg-if", 49 | ] 50 | 51 | [[package]] 52 | name = "pkg-config" 53 | version = "0.3.19" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 56 | 57 | [[package]] 58 | name = "rustc-serialize" 59 | version = "0.3.24" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 62 | 63 | [[package]] 64 | name = "x11" 65 | version = "2.18.2" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "77ecd092546cb16f25783a5451538e73afc8d32e242648d54f4ae5459ba1e773" 68 | dependencies = [ 69 | "libc", 70 | "pkg-config", 71 | ] 72 | 73 | [[package]] 74 | name = "xcb" 75 | version = "0.7.8" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "a0e3f0ea52c9adff258d3f8df802e53b81653eab4bfd2af16eea3678d8721b88" 78 | dependencies = [ 79 | "libc", 80 | "log 0.3.9", 81 | "x11", 82 | ] 83 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "gallium" 4 | version = "0.0.1" 5 | authors = ["Charlie Cummings "] 6 | 7 | [dependencies.log] 8 | log = "*" 9 | 10 | [dependencies.xcb] 11 | version = "^0.7" 12 | features = ["xlib_xcb"] 13 | 14 | [dependencies] 15 | bitflags = "^0.1" 16 | rustc-serialize = "0.3" 17 | libc = "*" 18 | x11 = "*" 19 | 20 | [[bin]] 21 | name = "gallium" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gallium 2 | ======= 3 | 4 | ``` 5 | For a second Kassad thought it was another person 6 | Wearing the chromium forcefields he and Moneta were 7 | draped in─but only for a second. There was nothing human 8 | about this particular quicksilver-over-chrome construct. 9 | Kassad dreamily noted the four arms, retractable 10 | fingerblades, the profusion of thornspikes on throat, 11 | forehead, wrists, knees, and body, but not once did his gaze 12 | leave the two thousand-faceted eyes which burned with a red 13 | flame that paled sunlight and dimmed the day to blood 14 | shadows. 15 | - Hyperion Cantos 16 | ``` 17 | 18 | 19 | A WM written in Rust, inspired and based loosely off of Kintaro's wtftw project. 20 | This is a toy project. 21 | 22 | (I've actually been using it as my WM in an Arch VM for a few years now. It's workable, but feature-lite, and probably has bugs that I just never run into since my setup doesn't change. Also it crashes from some dialog boxes since it doesn't do ewmh hints - you'll want `export GPG_AGENT_INFO=""` at the least so it prompts from the console instead of popup.) 23 | 24 | Currently supports: 25 | * Customizable keybindings 26 | * Tall tiling mode and fullscreen mode 27 | * Gaps and padding for windows 28 | * Workspaces 29 | * ??? 30 | 31 | Still to do: 32 | * More keybinds (sending windows across workspaces, etc.) 33 | * Hot-reloading of WM without losing all your windows 34 | * More layout options (BSP?) 35 | * Per-window tweaks 36 | * EWMH exporting 37 | 38 | There should probably be core 5 Layouts - Tall, Wide, Grid, Focused, and Floating. 39 | Tall tiles vertically, Wide horizontally, Grid will have a resizable number of rows and columns with an overflow square in the bottom-right, Focused will full-screen the currently selected window and hide the rest, and Floating will remap all existing windows and any new ones to floating, but switch them back when switched to another layout. 40 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::env::home_dir; 2 | use std::sync::RwLock; 3 | use rustc_serialize::{Encodable,Decodable,json,Encoder,Decoder}; 4 | use rustc_serialize::Decoder as StdDecoder; 5 | use rustc_serialize::json::ParserError; 6 | use std::io::{Read,Write}; 7 | use std::fs::OpenOptions; 8 | use std::path::PathBuf; 9 | use key::Key; 10 | use xserver::XServer; 11 | use layout::Layouts; 12 | use self::Direction::*; 13 | // Common configuration options for the window manager. 14 | 15 | #[derive(Clone)] 16 | pub struct KeyBind { 17 | pub binding: Option, 18 | pub chord: String, 19 | pub message: Message 20 | } 21 | 22 | #[derive(Clone,RustcEncodable,RustcDecodable)] 23 | pub struct WorkspaceConf { 24 | pub name: String, 25 | pub layout: Layouts, 26 | } 27 | 28 | impl KeyBind { 29 | pub fn new(s: &str, mess: Message) -> KeyBind { 30 | KeyBind { 31 | binding: None, 32 | chord: s.to_string(), 33 | message: mess 34 | } 35 | } 36 | 37 | pub fn unwrap(&self) -> &Key { 38 | self.binding.as_ref().unwrap() 39 | } 40 | } 41 | //When decoding JSON to Config, we need to have the JSON hold *only* the chord 42 | impl Decodable for KeyBind { 43 | fn decode(d: &mut D) -> Result { 44 | d.read_tuple(2,|d: &mut D|{ 45 | let k = KeyBind { 46 | binding: None, 47 | chord: try!(d.read_str()), 48 | message: try!(::decode(d)) 49 | }; 50 | Ok(k) 51 | }) 52 | } 53 | } 54 | //Manually implement KeyBind serialization so it saves it in key chord format 55 | impl Encodable for KeyBind { 56 | fn encode(&self, s: &mut S) -> Result<(),S::Error> { 57 | s.emit_tuple(2,|s: &mut S|{ 58 | s.emit_tuple_arg(0,|s: &mut S| s.emit_str(&self.chord[..]) ); 59 | s.emit_tuple_arg(1,|s: &mut S| self.message.encode::(s) ) 60 | }) 61 | } 62 | } 63 | 64 | #[derive(Clone)] 65 | pub struct Color(pub u32); 66 | impl Encodable for Color { 67 | fn encode(&self, s: &mut S) -> Result<(),S::Error> { 68 | s.emit_str(&format!("{:X}",self.0)) 69 | } 70 | } 71 | impl Decodable for Color { 72 | fn decode(d: &mut D) -> Result { 73 | let c = try!( 74 | u32::from_str_radix(&try!(d.read_str()).to_string(),16) 75 | .map_err(|e| 76 | d.error("ParseIntError") // no clue how this should actually work 77 | ) 78 | ); 79 | Ok(Color(c)) 80 | } 81 | } 82 | 83 | #[derive(RustcEncodable,RustcDecodable,Clone)] 84 | pub struct Config { 85 | kommand: KeyBind, 86 | pub padding: u16, 87 | pub border: u32, 88 | pub spacing: u16, 89 | pub follow_mouse: bool, 90 | pub focus_color: Color, 91 | pub unfocus_color: Color, 92 | pub workspaces: Vec, 93 | pub keys: Vec, 94 | } 95 | pub struct ConfigLock { 96 | conf: RwLock, 97 | pub path: Option 98 | } 99 | 100 | impl ConfigLock { 101 | pub fn current(&self) -> Config { 102 | self.conf.read().unwrap().clone() 103 | } 104 | pub fn setup(&mut self,serv: &mut XServer){ 105 | self.conf.write().unwrap().setup(serv); 106 | } 107 | } 108 | 109 | /* 110 | You have a config 111 | You can pass window.dostuff(blah,blah,&config.current()) (a copy of the config 112 | at that point in time) 113 | You can also do config.update(new_config) on a keybind, so everything 114 | is super hotswappable and happy. Yaay. 115 | */ 116 | 117 | /* 118 | * Ok so problem: 119 | * Adding new keys is a giant pain. 120 | * Add a new field to Config, add a new default 121 | * set the new default to KeyBind::Create("new-bind") 122 | * add the key to the initializion list 123 | * (eventually) add it to the callback registering 124 | * 125 | * Solution: 126 | * Instead just have a Vec of Keys 127 | * Have the Key also have a command field 128 | * command is an enum for what the message is 129 | * Command::Reload would be matched...uh, somewhere 130 | * and do the actual command 131 | * 132 | * (i totally didn't steal this idea from xr3wm nope i am super original) 133 | */#[derive(Clone,RustcDecodable,RustcEncodable)] 134 | pub enum Direction { 135 | Backward, 136 | Forward, 137 | Up, 138 | Down 139 | } 140 | // Special messages, for handling/ignore with layout.special 141 | #[derive(Clone,RustcDecodable,RustcEncodable)] 142 | pub enum SpecialMsg { 143 | Grow, 144 | Shrink, 145 | Add, 146 | Subtract 147 | } 148 | 149 | #[derive(Clone,RustcDecodable,RustcEncodable)] 150 | pub enum Message { 151 | Spawn(String,String), 152 | Reload, 153 | Quit, 154 | Kill, 155 | Tile, 156 | Focus(Direction), 157 | Translate(Direction), 158 | Resize(Direction), 159 | Move(Direction), 160 | Switch(Direction), 161 | Bring(Direction), 162 | Master, 163 | Special(SpecialMsg), 164 | Chain(Vec), 165 | None, 166 | } 167 | 168 | fn default() -> Config { 169 | Config { 170 | kommand: KeyBind::new("M4-",Message::None), 171 | // The dead-space from the borders of the screen 172 | padding: 5, 173 | border: 1, 174 | // The blank-space in between tiled windows 175 | spacing: 5, 176 | // If window focus should follow your mouse cursor or not 177 | follow_mouse: false, 178 | // Border color for the focused window 179 | focus_color: Color(0x3DAFDC), 180 | // ...and for unfocused ones 181 | unfocus_color: Color(0x282828), 182 | workspaces: vec!( 183 | WorkspaceConf { name: "|".to_string(), layout: Layouts::Tall }, 184 | WorkspaceConf { name: "||".to_string(), layout: Layouts::Tall }, 185 | WorkspaceConf { name: "|||".to_string(), layout: Layouts::Full }, 186 | ), 187 | keys: vec!( 188 | KeyBind::new("K-S-Return",Message::Spawn("urxvt".to_string(),"".to_string())), 189 | KeyBind::new("K-q",Message::Reload), 190 | KeyBind::new("K-S-q",Message::Quit), 191 | KeyBind::new("K-x",Message::Kill), 192 | KeyBind::new("K-t",Message::Tile), 193 | KeyBind::new("K-j",Message::Special(SpecialMsg::Shrink)), 194 | KeyBind::new("K-semicolon",Message::Special(SpecialMsg::Grow)), 195 | KeyBind::new("K-k",Message::Focus(Forward)), 196 | KeyBind::new("K-l",Message::Focus(Backward)), 197 | KeyBind::new("K-S-k",Message::Translate(Forward)), 198 | KeyBind::new("K-S-l",Message::Translate(Backward)), 199 | 200 | KeyBind::new("K-Right",Message::Switch(Forward)), 201 | KeyBind::new("K-Left",Message::Switch(Backward)), 202 | KeyBind::new("K-S-Right",Message::Bring(Forward)), 203 | KeyBind::new("K-S-Left",Message::Bring(Backward)), 204 | 205 | KeyBind::new("K-Return",Message::Master), 206 | KeyBind::new("K-minus",Message::Special(SpecialMsg::Subtract)), 207 | KeyBind::new("K-equal",Message::Special(SpecialMsg::Add)), 208 | 209 | // For easy rearranging of floating windows 210 | KeyBind::new("K-m",Message::Resize(Backward)), 211 | KeyBind::new("K-comma",Message::Resize(Down)), 212 | KeyBind::new("K-period",Message::Resize(Up)), 213 | KeyBind::new("K-slash",Message::Resize(Forward)), 214 | KeyBind::new("K-S-m",Message::Move(Backward)), 215 | KeyBind::new("K-S-comma",Message::Move(Down)), 216 | KeyBind::new("K-S-period",Message::Move(Up)), 217 | KeyBind::new("K-S-slash",Message::Move(Forward)), 218 | 219 | ), 220 | } 221 | } 222 | 223 | impl Config { 224 | /// Create the Config from a json file 225 | pub fn load(_path: Option) -> ConfigLock { 226 | let mut path = if let Some(p) = _path.clone() { 227 | PathBuf::from(&p) 228 | } else { 229 | let mut path = home_dir().unwrap(); 230 | path.push(".galliumrc"); 231 | path 232 | }; 233 | let mut fopt = OpenOptions::new(); 234 | fopt.write(true).read(true); 235 | let mut conf_file = fopt.open(path).unwrap(); 236 | let mut buff = String::new(); 237 | conf_file.read_to_string(&mut buff); 238 | 239 | let dec_conf = match json::decode(&buff) { 240 | Ok(v) => v, 241 | Err(e) =>{ 242 | error!("Our config is corrupted!"); 243 | error!("Error: {:?}",e); 244 | default() 245 | } 246 | }; 247 | ConfigLock { 248 | conf: RwLock::new(dec_conf), 249 | path: _path 250 | } 251 | } 252 | pub fn setup(&mut self, serv: &mut XServer){ 253 | serv.clear_keys(); 254 | //Instance Kommand to a valid Key 255 | let mut kchord = self.kommand.chord.clone(); 256 | kchord.push_str("A"); //Make it a parsable KeyBind 257 | self.kommand.binding = Some(Key::create(kchord,serv)); 258 | //...And then add kontrol to the XServer cell (hacky!) 259 | *serv.kommand_mod.borrow_mut() = Some(self.kommand.unwrap().modifier.clone()); 260 | 261 | //Register the workspace hotkeys 262 | let numkey = ["1","2","3","4","5","6","7","8","9","0"]; 263 | for num in numkey.iter() { 264 | let mut k = Key::create(num.to_string(),serv); 265 | k.modifier = k.modifier | self.kommand.unwrap().modifier; 266 | serv.add_key(k); 267 | } 268 | for ref mut k in self.keys.iter_mut() { 269 | let p = Key::create(k.chord.clone(),serv); 270 | k.binding = Some(p); 271 | serv.add_key(p); 272 | } 273 | serv.grab_keys(); 274 | } 275 | 276 | pub fn reset(){ 277 | //Let's just roll back to the default 278 | //conf_file.truncate(0); 279 | let mut path = home_dir().unwrap(); 280 | path.push(".galliumrc"); 281 | let mut fopt = OpenOptions::new(); 282 | fopt.read(true).write(true).truncate(true); 283 | let mut conf_file = fopt.open(path).unwrap(); 284 | let mut buff = String::new(); 285 | { 286 | let mut pretty = json::Encoder::new_pretty(&mut buff); 287 | (&default()).encode(&mut pretty).unwrap() 288 | } 289 | conf_file.write(&buff.into_bytes()); 290 | conf_file.sync_data(); 291 | } 292 | 293 | //Wrap a config in a RWLock 294 | pub fn new() -> ConfigLock { 295 | ConfigLock { 296 | conf: RwLock::new(default()), 297 | //conf: RwLock::new(Config::initialize()) 298 | path: None 299 | } 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/cycle.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Index; 2 | pub struct Cycle { 3 | max: isize, 4 | slice: Vec> 5 | } 6 | 7 | impl Index for Cycle { 8 | type Output=Option; 9 | fn index(&self, index: isize) -> &Option { 10 | println!("Index {}",index); 11 | if self.max == 0 { //empty interval, return &None 12 | return &self.slice[0]; 13 | } 14 | if index == 0 { 15 | return &self.slice[0]; 16 | } 17 | let b_ind = self.max + ((index.abs()%self.max) * index.abs()/index); 18 | let b_ind = b_ind%self.max; 19 | return &self.slice[b_ind as usize]; 20 | } 21 | } 22 | impl Cycle { 23 | pub fn new(max: usize) -> Cycle { 24 | let mut v = Vec::new(); 25 | for i in 0..max { 26 | v.push(Some(i)); 27 | } 28 | v.push(None); 29 | Cycle { 30 | max: max as isize, 31 | slice: v 32 | } 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/deck.rs: -------------------------------------------------------------------------------- 1 | //A Vec that has a `current` member. 2 | #[derive(PartialEq)] 3 | pub struct Deck{ 4 | pub cards: Vec, //If you get weird mutability errors, RefCell 5 | pub index: Option 6 | } 7 | 8 | impl Deck{ 9 | pub fn new() -> Deck{ 10 | Deck { 11 | cards: Vec::new(), 12 | index: None 13 | } 14 | } 15 | 16 | pub fn select(&mut self, ind: usize){ 17 | if ind >= self.cards.len() { 18 | panic!("Selected a card index that doesn't exist"); 19 | } 20 | self.index = Some(ind) 21 | } 22 | pub fn current(&mut self) -> Option<&mut T>{ 23 | if self.index.is_none() || self.index.unwrap() >= self.cards.len() { 24 | return None 25 | } 26 | Some(&mut self.cards[self.index.unwrap()]) 27 | } 28 | 29 | pub fn push(&mut self,card: T){ 30 | self.cards.push(card); 31 | if self.index.is_none() { 32 | self.index = Some(self.cards.len()-1); 33 | } 34 | } 35 | pub fn pop(&mut self) -> Option{ 36 | let r = self.cards.pop(); 37 | if self.index.is_some() && self.cards.len() >= self.index.unwrap() { 38 | self.index = None; 39 | } 40 | Some(r.unwrap()) 41 | } 42 | pub fn swap(&mut self,pos1: usize,pos2: usize){ 43 | self.cards.swap(pos1,pos2); 44 | } 45 | //This is O(n) and re-allocates everything right of the index. It's bad. 46 | pub fn remove(&mut self,ind: usize) -> Option { 47 | let k = self.cards.remove(ind); 48 | // If we can avoid nuking index, then just select the next one in line 49 | if self.index.is_some() && self.index.unwrap() >= ind && self.cards.len() <= self.index.unwrap() { 50 | self.index = None 51 | } 52 | Some(k) 53 | } 54 | //Remove the element at index, but don't move self.index from it 55 | //If the index is None or would be OoB, return false 56 | pub fn forget(&mut self,ind: usize) -> bool { 57 | let old = self.index.clone(); 58 | let elem = self.remove(ind); 59 | if old.is_some() && self.index.is_none() { 60 | if old.unwrap() &[T] { 69 | &self.cards[..] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/gallium.rs: -------------------------------------------------------------------------------- 1 | //#![feature(libc)] 2 | #[macro_use] extern crate log; 3 | extern crate rustc_serialize; 4 | #[macro_use] extern crate bitflags; 5 | extern crate libc; 6 | extern crate xcb; 7 | extern crate x11; 8 | use config::{Message,Config,ConfigLock,Direction}; 9 | use window_manager::WindowManager; 10 | use layout::clamp; 11 | use deck::Deck; 12 | use cycle::Cycle; 13 | use xserver::{XServer,ServerEvent}; 14 | use key::Key; 15 | use std::process::Command; 16 | use gallium_log::GalliumLog; 17 | use xcb::*; 18 | 19 | pub mod cycle; 20 | pub mod deck; 21 | pub mod config; 22 | pub mod window_manager; 23 | pub mod key; 24 | pub mod xserver; 25 | pub mod layout; 26 | pub mod gallium_log; 27 | 28 | pub struct Gallium<'a> { 29 | config: ConfigLock, 30 | window_manager: WindowManager<'a>, 31 | window_server: XServer 32 | } 33 | 34 | impl<'a> Gallium<'a> { 35 | fn setup() -> Gallium<'a> { 36 | let mut window_server = unsafe { XServer::new() }; 37 | let mut config = Config::new(); 38 | let window_manager = WindowManager::new(&window_server,&config); 39 | 40 | Gallium { 41 | config: config, 42 | window_manager: window_manager, 43 | window_server: window_server 44 | } 45 | } 46 | 47 | fn start(mut self){ 48 | loop { 49 | trace!("Polling event..."); 50 | match self.window_server.get_event() { 51 | ServerEvent::MapRequest(mut window) => { 52 | //For now just adding to the current workspace 53 | let mut w = self.window_manager.workspaces.current().unwrap(); 54 | let screen = self.window_manager.screens.index.unwrap() as u32; 55 | let p = window.wind_ptr; 56 | let conf = self.config.current(); 57 | //w.layout.add(&mut window, &mut self.window_server); 58 | w.windows.push(window); 59 | w.refresh(&mut self.window_server, screen, conf); 60 | }, 61 | ServerEvent::KeyPress(key) => { 62 | self.dispatch_key(key); 63 | }, 64 | ServerEvent::DestroyNotify(wind_ptr) => { 65 | let screen = self.window_manager.screens.index.unwrap() as u32; 66 | for mut work in &mut self.window_manager.workspaces.cards[..] { 67 | for wind_ind in 0..work.windows.cards.len() { 68 | if work.windows.cards[wind_ind].wind_ptr == wind_ptr { 69 | debug!("Window {} removed from Workspace",wind_ind); 70 | //work.layout.remove(wind_ind, &mut self.window_server); 71 | work.windows.remove(wind_ind); 72 | work.refresh(&mut self.window_server, screen, self.config.current()); 73 | break; 74 | } 75 | } 76 | } 77 | }, 78 | ServerEvent::EnterNotify(wind_ptr) => { 79 | let conf = self.config.current(); 80 | if conf.follow_mouse == false { 81 | continue; 82 | } 83 | let screen = self.window_manager.screens.index.unwrap() as u32; 84 | let mut work = self.window_manager.workspaces.current().unwrap(); 85 | for wind_ind in 0..work.windows.cards.len() { 86 | if work.windows.cards[wind_ind].wind_ptr == wind_ptr { 87 | work.windows.select(wind_ind); 88 | work.refresh(&mut self.window_server, screen, conf); 89 | break; 90 | } 91 | } 92 | }, 93 | _ => { 94 | trace!("Unknown event"); 95 | } 96 | } 97 | } 98 | } 99 | 100 | fn dispatch_key(&mut self, key: Key){ 101 | debug!("Key press: {:?}",key); 102 | //TODO: figure out how I am going to handle multi-screen 103 | let screen = self.window_manager.screens.index.unwrap() as u32; 104 | for k in self.config.current().keys { if k.binding.unwrap() == key { 105 | match k.message { 106 | Message::Spawn(exe,args) => { 107 | info!("Spawning..."); 108 | let mut c = Command::new(exe); 109 | if args.len()>0 { 110 | let co = c.arg(args); 111 | co.spawn(); 112 | } 113 | else { 114 | c.spawn(); 115 | } 116 | }, 117 | Message::Master => { 118 | let mut work = self.window_manager.workspaces.current().unwrap(); 119 | //if wind.floating XRaiseWindow 120 | //else cards.forget and push(?) 121 | if let Some(ind) = work.windows.index { 122 | if work.windows.cards.len() > 0 { 123 | work.windows.cards.swap(0, ind); 124 | work.windows.select(0); 125 | } 126 | } 127 | work.refresh(&mut self.window_server, screen, self.config.current()); 128 | }, 129 | Message::Translate(dir) => { 130 | //swap adjecant windows 131 | //set new current 132 | let mut work = self.window_manager.workspaces.current().unwrap(); 133 | let off = match dir { 134 | Direction::Forward => 1, 135 | Direction::Backward => -1, 136 | _ => 0, 137 | }; 138 | work.windows.index.map(|i|{ 139 | let l = work.windows.cards.len(); 140 | let cycle = Cycle::new(l); 141 | work.windows.swap(i, cycle[(i as isize) + off].unwrap_or(i)); 142 | work.windows.index = cycle[(i as isize) + off]; 143 | }); 144 | work.refresh(&mut self.window_server, screen, self.config.current()); 145 | }, 146 | Message::Reload => { 147 | self.window_server.clear_keys(); 148 | // keep old path 149 | let mut new_conf = Config::load(self.config.path.clone()); 150 | new_conf.setup(&mut self.window_server); 151 | //replace the layouts for all the ones mapped 152 | //should really just replace for ones that are wrong 153 | { 154 | let ls = new_conf.current().workspaces; 155 | let wk = &mut self.window_manager.workspaces.cards; 156 | self.config = new_conf; 157 | ls.iter().zip(wk).map(|(lay,work)|{ 158 | info!("Layout for workspace {} is {:?}",lay.name,lay.layout); 159 | work.layout = layout::LayoutFactory(lay.layout); 160 | }); 161 | } 162 | 163 | let mut work = self.window_manager.workspaces.current().unwrap(); 164 | work.refresh(&mut self.window_server, screen, self.config.current()); 165 | info!("Config reloaded OK!"); 166 | }, 167 | Message::Quit => { 168 | unsafe { 169 | self.window_server.quit(k.binding.unwrap()); 170 | println!("Goodbye!"); 171 | libc::exit(0); 172 | } 173 | }, 174 | Message::Kill => { 175 | let mut work = self.window_manager.workspaces.current().unwrap(); 176 | { 177 | let mut wind = work.windows.current(); 178 | if wind.is_some() { 179 | unsafe { 180 | self.window_server.kill_window(wind.unwrap().wind_ptr); 181 | info!("Killed window"); 182 | } 183 | } 184 | } 185 | let wind_ind = work.windows.index; 186 | if wind_ind.is_some() { 187 | work.windows.forget(wind_ind.unwrap()); 188 | } 189 | work.refresh(&mut self.window_server, screen, self.config.current()); 190 | }, 191 | Message::Tile => { 192 | let mut work = self.window_manager.workspaces.current().unwrap(); 193 | let mut b = false; 194 | { 195 | let c = work.windows.current(); 196 | c.map(|wind| { 197 | wind.floating = !wind.floating; 198 | b = true; 199 | }); 200 | } 201 | if b { 202 | work.refresh(&mut self.window_server, screen, self.config.current()); 203 | } 204 | }, 205 | Message::Resize(dir) => { 206 | let mut work = self.window_manager.workspaces.current().unwrap(); 207 | let mut b = false; 208 | { 209 | let wind = work.windows.current(); 210 | wind.map(|wind| { 211 | if !wind.floating { 212 | return 213 | } 214 | let (x,y) = match dir { 215 | Direction::Backward => (-15,0), 216 | Direction::Forward => (15,0), 217 | Direction::Up => (0,-15), 218 | Direction::Down => (0,15) 219 | }; 220 | wind.size = ( 221 | clamp(0,(wind.size.0 as i32) + x,2048) as usize, 222 | clamp(0,(wind.size.1 as i32) + y,2048) as usize 223 | ); 224 | b = true; 225 | }); 226 | } 227 | if b { 228 | work.refresh(&mut self.window_server, screen, self.config.current()); 229 | } 230 | }, 231 | Message::Move(dir) => { 232 | let mut work = self.window_manager.workspaces.current().unwrap(); 233 | let mut b = false; 234 | { 235 | let wind = work.windows.current(); 236 | wind.map(|wind| { 237 | if !wind.floating { 238 | return 239 | } 240 | let (x,y) = match dir { 241 | Direction::Backward => (-15,0), 242 | Direction::Forward => (15,0), 243 | Direction::Up => (0,-15), 244 | Direction::Down => (0,15) 245 | }; 246 | wind.x = wind.x + x; 247 | wind.y = wind.y + y; 248 | b = true; 249 | }); 250 | } 251 | if b { 252 | work.refresh(&mut self.window_server, screen, self.config.current()); 253 | } 254 | }, 255 | Message::Focus(dir) => { 256 | let mut work = self.window_manager.workspaces.current().unwrap(); 257 | match dir { 258 | Direction::Backward => { 259 | let i = work.windows.index.unwrap_or(1); 260 | let l = work.windows.cards.len(); 261 | let cycle = Cycle::new(l); 262 | work.windows.index = cycle[(i as isize) - 1]; 263 | }, 264 | Direction::Forward => { 265 | let i = work.windows.index.unwrap_or(0); 266 | let l = work.windows.cards.len(); 267 | let cycle = Cycle::new(l); 268 | work.windows.index = cycle[(i as isize) + 1]; 269 | }, 270 | _ => () 271 | } 272 | work.refresh(&mut self.window_server, screen, self.config.current()); 273 | }, 274 | Message::Switch(dir) => { 275 | let w_l = self.window_manager.workspaces.cards.len(); 276 | let w_i = self.window_manager.workspaces.index.unwrap(); 277 | let cycle = Cycle::new(w_l); 278 | match dir { 279 | Direction::Backward => { 280 | self.window_manager 281 | .switch(&mut self.window_server, 282 | self.config.current(), 283 | cycle[(w_i as isize) - 1].unwrap() as u32); 284 | }, 285 | Direction::Forward => { 286 | self.window_manager 287 | .switch(&mut self.window_server, 288 | self.config.current(), 289 | cycle[(w_i as isize) + 1].unwrap() as u32); 290 | }, 291 | _ => () 292 | } 293 | }, 294 | //Message::Bring(dir) => { 295 | // let mang = self.window_manager; 296 | // let w_l = mang.workspaces.cards.len(); 297 | // let w_i = mang.workspaces.index.unwrap(); 298 | // let cycle = Cycle::new(w_l); 299 | // match dir { 300 | // Direction::Backward => { 301 | // win.map(|win|{ 302 | // }); 303 | // mang.switch(&mut self.window_server, 304 | // self.config.current(), 305 | // cycle[(w_i as isize) - 1].unwrap() as u32); 306 | // }, 307 | // Direction::Forward => { 308 | // win.map(|win|{ 309 | // }); 310 | // mang.switch(&mut self.window_server, 311 | // self.config.current(), 312 | // cycle[(w_i as isize) + 1].unwrap() as u32); 313 | // }, 314 | // _ => () 315 | // } 316 | //}, 317 | Message::Special(msg) => { 318 | let mut work = self.window_manager.workspaces.current().unwrap(); 319 | work.layout.message(msg); 320 | work.refresh(&mut self.window_server, screen, self.config.current()); 321 | }, 322 | Message::None => (), 323 | _ => warn!("Unknown key message!") 324 | } 325 | } 326 | } 327 | } 328 | } 329 | 330 | fn main(){ 331 | GalliumLog::init(); 332 | 333 | let mut init = false; 334 | let mut new_conf = None; 335 | for argument in std::env::args() { 336 | trace!("{}", argument); 337 | if argument[..].eq("--new") { 338 | Config::reset(); 339 | init = true; 340 | info!("New config installed!"); 341 | return; 342 | } 343 | if argument[0..3].eq("-c=") { 344 | info!("Using {:?} as config path",&argument[3..]); 345 | new_conf = Some(Config::load(Some(argument[3..].to_string()))); 346 | init = true; 347 | continue; 348 | } 349 | } 350 | 351 | let mut gl = Gallium::setup(); 352 | if init == false { 353 | new_conf = Some(Config::load(None)); 354 | } 355 | if let Some(mut new_conf) = new_conf { 356 | new_conf.setup(&mut gl.window_server); 357 | gl.config = new_conf; 358 | } 359 | info!("Gallium has been setup."); 360 | gl.start(); 361 | } 362 | -------------------------------------------------------------------------------- /src/gallium_log.rs: -------------------------------------------------------------------------------- 1 | extern crate log; 2 | use log::{Record, Level, LevelFilter, Metadata, SetLoggerError}; 3 | 4 | pub struct GalliumLog; 5 | 6 | impl log::Log for GalliumLog { 7 | fn enabled(&self, metadata: &Metadata) -> bool { 8 | metadata.level() != Level::Trace 9 | } 10 | 11 | fn log(&self, record: &Record) { 12 | if self.enabled(record.metadata()) { 13 | let mark = match record.level() { 14 | Level::Error => "!!!", 15 | Level::Warn => "!", 16 | Level::Info => ">", 17 | Level::Debug => "+", 18 | Level::Trace => "-", 19 | }; 20 | println!("[{}] {}", mark, record.args()); 21 | } 22 | } 23 | 24 | fn flush(&self) { } 25 | } 26 | 27 | static MY_LOGGER: GalliumLog = GalliumLog; 28 | impl GalliumLog { 29 | pub fn init() -> Result<(), SetLoggerError> { 30 | log::set_logger(&MY_LOGGER)?; 31 | log::set_max_level(LevelFilter::Debug); 32 | Ok(()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug,Formatter,Result}; 2 | use std::str::from_utf8; 3 | use x11::xlib::{KeyCode,KeySym}; 4 | use xserver::{XServer,keysym_to_string}; 5 | 6 | bitflags! { 7 | #[allow(non_upper_case_globals)] 8 | flags KeyMod: u32 { 9 | const SHIFT = 0b000000001, 10 | const LOCK = 0b000000010, 11 | const CONTROL = 0b000000100, 12 | const MOD1 = 0b000001000, 13 | const MOD2 = 0b000010000, 14 | const MOD3 = 0b000100000, 15 | const MOD4 = 0b001000000, 16 | const MOD5 = 0b010000000, 17 | const KOMMAND = 0b100000000, 18 | } 19 | } 20 | 21 | #[derive(Eq,PartialEq,Clone,Copy)] 22 | pub struct Key { 23 | pub code: KeyCode, 24 | pub sym: KeySym, 25 | pub modifier: KeyMod, 26 | } 27 | const LOOKUP: [(&'static str, KeyMod); 9] = [("S-",SHIFT), 28 | ("Lock-",LOCK), 29 | ("C-",CONTROL), 30 | ("M-",MOD1), 31 | ("M2-",MOD2), 32 | ("M3-",MOD3), 33 | ("M4-",MOD4), 34 | ("M5-",MOD5), 35 | ("K-",KOMMAND)]; 36 | impl Debug for Key { 37 | fn fmt(&self, f: &mut Formatter) -> Result { 38 | let (pref,x) = self.chord(); 39 | write!(f,"{}{}",pref,x) 40 | } 41 | } 42 | 43 | impl Key { 44 | pub fn parse(code: KeyCode, modifier: u32, serv: &XServer) -> Key { 45 | let mut sym = 0; 46 | sym = serv.keycode_to_keysym(code); 47 | let mut mo = match KeyMod::from_bits(modifier) { 48 | Some(v) => v, 49 | None => KeyMod::empty() 50 | }; 51 | // Replace the KOMMAND bit with what it's set to 52 | if (mo & KOMMAND).bits() != 0 { 53 | println!("Replace KOMMAND pls"); 54 | mo = mo | serv.kommand_mod.borrow().unwrap(); 55 | mo = mo & !KOMMAND; 56 | } 57 | Key { 58 | code: code, 59 | sym: sym, 60 | modifier: mo 61 | } 62 | } 63 | 64 | pub fn create(s: String, serv: &XServer) -> Key { 65 | let mut flag = KeyMod::empty(); 66 | let mut key = ""; 67 | for mut m in s.split('-') { 68 | let mut full = m.to_string(); 69 | full.push_str("-"); 70 | if full == "K-" { 71 | flag = flag | serv.kommand_mod.borrow().unwrap(); 72 | } 73 | else { 74 | let mut found = false; 75 | for &(c,k) in LOOKUP.iter() { 76 | if full == c { 77 | flag = flag | k; 78 | found = true; 79 | } 80 | } 81 | if !found { 82 | key = m 83 | } 84 | } 85 | } 86 | let mut sym = serv.string_to_keysym(&mut key.to_string()); 87 | let code = serv.keysym_to_keycode(sym); 88 | 89 | Key { 90 | code: code as KeyCode, 91 | sym: sym, 92 | modifier: flag 93 | } 94 | } 95 | 96 | pub fn chord(&self) -> (String,&'static str) { 97 | let mut pref = "".to_string(); 98 | for b in 0..8 { 99 | if self.modifier.contains(LOOKUP[b].1) { 100 | pref.push_str(LOOKUP[b].0); 101 | } 102 | } 103 | let cs = keysym_to_string(self.sym); 104 | 105 | let x = from_utf8(cs.to_bytes()).unwrap(); 106 | (pref,x) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | use xserver::{XServer}; 2 | use super::Gallium; 3 | use window_manager::{Workspace,Window}; 4 | use deck::Deck; 5 | use config::{Config,Direction,SpecialMsg}; 6 | extern crate core; 7 | use self::core::ops::IndexMut; 8 | 9 | //Why is this not provided by the stdlib? 10 | pub fn clamp(min: T, v: T, max:T) -> T { 11 | if max<=min { 12 | return min //kinda makes sense, and makes my life easier 13 | } 14 | if vmax { 18 | return max 19 | } 20 | v 21 | } 22 | 23 | //Accumulator-ish region, to keep current position book-keeping for layouts 24 | struct Region { 25 | x: usize, 26 | y: usize, 27 | size_x: usize, 28 | size_y: usize 29 | } 30 | 31 | pub trait Layout { 32 | fn apply(&self, screen: u32, xserv: &mut XServer, work: &mut Workspace, conf: &mut Config){ 33 | println!("Layout.apply unimplemented"); 34 | } 35 | fn message(&mut self, SpecialMsg){ 36 | println!("Layout.special unimplemented"); 37 | } 38 | } 39 | 40 | #[derive(Clone,Copy,PartialEq,RustcDecodable,RustcEncodable,Debug)] 41 | pub enum Layouts { 42 | Tall, 43 | Wide, 44 | Grid, 45 | Stacking, 46 | Full 47 | } 48 | pub fn LayoutFactory(form: Layouts) -> Box { 49 | let lay: Box = match form { 50 | Layouts::Tall => Box::new(TallLayout { 51 | master: 1, 52 | percent: 50.0 53 | }), 54 | Layouts::Full => Box::new(FullLayout), 55 | _ => panic!("Unimplemented layout form!") 56 | }; 57 | lay 58 | } 59 | 60 | //Placeholder layout for swapping 61 | pub struct HolderLayout; 62 | impl Layout for HolderLayout { } 63 | 64 | /* 65 | * Like spectrwm's tall layout. 66 | * Two columns, master and overflow. 67 | * Can increase the number of windows that are in master, 68 | * and can also shift the seperator between the columns. 69 | * Overflow is the remainder, split vertically for each window. 70 | */ 71 | pub struct TallLayout { 72 | //How many windows are in the master column. 73 | pub master: u16, 74 | //Percent of the screen that master takes up 75 | pub percent: f32 76 | } 77 | 78 | impl Layout for TallLayout { 79 | fn apply(&self, screen: u32, xserv: &mut XServer, work: &mut Workspace, config: &mut Config){ 80 | { 81 | let mut wind: &mut Vec<&mut Window> = &mut work.windows.cards[..] 82 | .iter_mut().filter(|wind| !wind.floating ).collect(); 83 | let pad = config.padding as usize; 84 | let (x,y) = (xserv.width(screen as u32) as usize - pad - pad, 85 | xserv.height(screen as u32) as usize - pad - pad); 86 | let space = config.spacing as usize; 87 | let mast = clamp(0,self.master as usize,wind.len()); 88 | debug!("Master {}",mast); 89 | if wind.len() == 0 { 90 | trace!("Empty window stack"); 91 | return; 92 | } 93 | let mut mast_size = clamp(space, 94 | ((x as f32)*(self.percent/100.0)).floor() as usize, 95 | x) - space; 96 | let mut leftover = x-(mast_size+space); 97 | if wind.len()<=mast { //Don't show a gap if we have no overflow 98 | leftover = 0; 99 | mast_size = x; 100 | } 101 | else if mast == 0 { //Or if we have no master 102 | mast_size = pad-space; //This is a hack. We can't have a space! 103 | leftover = x; 104 | } 105 | let mut reg = Region { x: pad, y: pad, size_x: mast_size, size_y: y }; 106 | for m in 0..mast { 107 | let ref mut w = wind.index_mut(m); 108 | //We are already offset by pad, dont need another space if we are the top 109 | let kill_s = if m==0 || m==(mast) { 0 } else { space }; 110 | //If the spacing doesn't divide evenly, stretch the top's Y 111 | let size_extra = if m==0 { (reg.size_y-(mast*space))%mast } else { 0 }; //Terrible 112 | let wind_y = (reg.size_y/mast) - kill_s + size_extra; 113 | reg.y = reg.y + kill_s; 114 | w.x = reg.x as isize; 115 | w.y = reg.y as isize; 116 | w.size = (reg.size_x as usize, wind_y as usize); 117 | w.shown = true; 118 | reg.y = reg.y + wind_y; 119 | xserv.refresh(w); 120 | } 121 | 122 | let mut reg = Region { x: (x-leftover)+space, y: pad, size_x: leftover, size_y: y }; 123 | let wlen = wind.len(); 124 | if wlen-mast as usize > 0 { 125 | let stack = wlen-mast as usize; 126 | for r in mast..wlen { 127 | let ref mut w = wind[r as usize]; 128 | let kill_s = if r==mast || r==(wlen) { 0 } else { space }; 129 | let size_extra = if r==mast { (reg.size_y-(stack*space))%stack } else { 0 }; //Terrible 130 | let wind_y = (reg.size_y/stack) - kill_s + size_extra; 131 | reg.y = reg.y + kill_s; 132 | w.x = reg.x as isize; 133 | w.y = reg.y as isize; 134 | w.size = (reg.size_x as usize,wind_y as usize); 135 | w.shown = true; 136 | reg.y = reg.y + wind_y; 137 | xserv.refresh(w); 138 | } 139 | } 140 | } // non-lexical borrows when 141 | for wind in work.windows.cards.iter_mut().filter(|wind| wind.floating) { 142 | wind.shown = true; 143 | xserv.refresh(wind); 144 | } 145 | work.windows.index.map(|wind|{ 146 | let ref mut wind = &mut work.windows.cards[wind]; 147 | wind.shown = true; 148 | xserv.refresh(wind); 149 | unsafe { xserv.bring_to_front(wind.wind_ptr); } // XX MAY BE HORRIBLY WRONG 150 | }); 151 | } 152 | 153 | fn message(&mut self, msg: SpecialMsg){ 154 | match msg { 155 | SpecialMsg::Add => if self.master < 5 { 156 | self.master+=1; 157 | }, 158 | SpecialMsg::Subtract => if self.master > 0 { 159 | self.master-=1; 160 | }, 161 | SpecialMsg::Shrink => { 162 | if self.percent > 5.0 { 163 | self.percent = (self.percent - 5.0).floor(); 164 | } 165 | }, 166 | SpecialMsg::Grow => { 167 | if self.percent < 95.0 { 168 | self.percent = (self.percent + 5.0).floor(); 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | pub struct FullLayout; 176 | 177 | impl Layout for FullLayout { 178 | fn apply(&self, screen: u32, xserv: &mut XServer, work: &mut Workspace, conf: &mut Config){ 179 | let pad = conf.padding as usize; 180 | let (x,y) = (xserv.width(screen as u32) as usize - pad - pad, 181 | xserv.height(screen as u32) as usize - pad - pad); 182 | let space = conf.spacing as usize; 183 | let focus = work.windows.index; 184 | let mut count = work.windows.cards.len(); 185 | for (ind,ref mut wind) in work.windows.cards.iter_mut().enumerate() { 186 | wind.x = (pad + (space * ind)) as isize; 187 | wind.y = (pad + (space * ind)) as isize; 188 | wind.size = (clamp(0,x - (space * count),x) as usize, 189 | clamp(0,y - (space * count),y) as usize); 190 | wind.shown = true; 191 | xserv.refresh(wind); 192 | } 193 | focus.map(|wind|{ 194 | let ref mut wind = &mut work.windows.cards[wind]; 195 | wind.shown = true; 196 | xserv.refresh(wind); 197 | unsafe { xserv.bring_to_front(wind.wind_ptr); } // XX MAY BE HORRIBLY WRONG 198 | }); 199 | } 200 | fn message(&mut self, msg: SpecialMsg){ 201 | println!("Layout.special unimplemented"); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/window_manager.rs: -------------------------------------------------------------------------------- 1 | use deck::Deck; 2 | use cycle::Cycle; 3 | use xserver::{XServer,XWindow,Screen,XDefaultScreenOfDisplay}; 4 | use layout::{Layout,TallLayout,HolderLayout,LayoutFactory}; 5 | use config::{Config,ConfigLock}; 6 | use std::mem::swap; 7 | use std::ptr; 8 | 9 | //WorkspaceManager is ~indirection~ 10 | //WorkspaceManager 11 | // -> many Displays 12 | // -> Workspaces mapped to Displays 13 | // -> Windows mapped to Workspaces 14 | pub struct Workspace<'a> { 15 | pub windows: Deck, //Is treated as a stack of the most recent windows 16 | pub layout: Box, 17 | master: Option<&'a Window>, 18 | name: String 19 | } 20 | impl<'a> Workspace<'a>{ 21 | pub fn refresh(&mut self, xserv: &mut XServer, screen: u32, mut config: Config){ 22 | trace!("Refresh"); 23 | //Swap self.layout so no dual-&mut 24 | let mut holder: Box = Box::new(HolderLayout); 25 | swap(&mut holder, &mut self.layout); 26 | holder.apply(screen, xserv, self, &mut config); 27 | //And restore it 28 | swap(&mut self.layout, &mut holder); 29 | let mast = self.windows.current().map(|x| x.wind_ptr ); 30 | for w in &self.windows.cards[..] { 31 | xserv.set_border_width(w.wind_ptr,config.border); 32 | if mast.is_some() && w.wind_ptr == mast.unwrap() { 33 | xserv.set_border_color(w.wind_ptr,config.focus_color.0 as u64); 34 | xserv.focus(w.wind_ptr); 35 | } 36 | else { 37 | xserv.set_border_color(w.wind_ptr,config.unfocus_color.0 as u64); 38 | } 39 | } 40 | } 41 | } 42 | 43 | pub struct Window { 44 | pub wind_ptr: XWindow, 45 | pub floating: bool, 46 | pub shown: bool, //not always true! just used with xserv.refresh(window) 47 | pub _mapped: bool, //used for xserv.map/unmap so it doesn't configure twice 48 | pub x: isize, 49 | pub y: isize, 50 | pub z: isize, 51 | pub size: (usize,usize) 52 | } 53 | 54 | pub struct Monitor { 55 | //Always one display, but mutable workspace + layout bound to it 56 | screen: Screen, 57 | pub workspace: u32, 58 | } 59 | 60 | pub struct WindowManager<'a> { 61 | pub screens: Deck, 62 | pub workspaces: Deck> 63 | } 64 | 65 | impl<'a> WindowManager<'a> { 66 | pub fn new(serv: &XServer, config: &ConfigLock) -> WindowManager<'a> { 67 | //TODO: Figure out how the heck XineramaScreenInfo is used for this 68 | //Preferably without having to recreate the Screen class to use it 69 | //let xine_enabled = unsafe { XineramaQueryExtension(serv.display) }; 70 | //let num_screens = 1; 71 | let mut screens = Deck::new(); 72 | let mut works = Deck::new(); 73 | /*if xine_enabled { 74 | debug!("Xinerama is enabled!"); 75 | let screens_: *const XineramaScreenInfo = unsafe { XineramaQueryScreens(serv.display, &num_screens) }; 76 | XFree(screens_); 77 | }*/ 78 | for wind_conf in config.current().workspaces { 79 | let wind_deck = Deck::new(); 80 | let work = Workspace { 81 | windows: wind_deck, 82 | layout: LayoutFactory(wind_conf.layout), 83 | master: None, 84 | name: wind_conf.name 85 | }; 86 | works.push(work); 87 | } 88 | works.select(0); 89 | let scr = Monitor { 90 | screen: unsafe { ptr::read(&*XDefaultScreenOfDisplay(serv.display.clone())) }, //Reused from XServer::new() :( 91 | workspace: 0 92 | }; 93 | screens.push(scr); 94 | WindowManager { 95 | screens: screens, 96 | workspaces: works 97 | } 98 | } 99 | pub fn switch(&mut self, xserv: &mut XServer, mut config: Config, ind: u32) { 100 | // All of these unwraps should be safe, we will never be at an empty workspace 101 | if ind as usize >= self.workspaces.cards.len() { 102 | panic!("Invalid workspace {} to switch to",ind); 103 | } 104 | //First, unmap all current windows 105 | for w in self.workspaces.current().unwrap().windows.cards.iter_mut() { 106 | w.shown = false; 107 | xserv.refresh(w); 108 | } 109 | //Switch workspaces 110 | self.workspaces.select(ind as usize); 111 | //And finally let the layout remap all the ones that it wants 112 | //(The layout shouldn't have to do anything fancy, since 113 | // xserv.refresh(window) will map the window) 114 | self.workspaces.current().unwrap().refresh(xserv,self.screens.index.unwrap() as u32,config); 115 | } 116 | } 117 | 118 | /* 119 | 120 | WindowManager has a list of screens and a list of workspaces. 121 | The current screen should be a member of this. 122 | Each screen has a workspace index into the list, too. 123 | 124 | */ 125 | -------------------------------------------------------------------------------- /src/xserver.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_upper_case_globals)] 2 | extern crate libc; 3 | use std::cell::RefCell; 4 | use libc::malloc; 5 | //pub use xlib::*; 6 | use window_manager::Window as GWindow; 7 | //pub use xlib::Window as XWindow; 8 | pub use key::{ 9 | Key, 10 | KeyMod, 11 | LOCK, 12 | }; 13 | use config::KeyBind; 14 | use std::ffi::{CString,CStr}; 15 | use std::ptr::{null_mut,read}; 16 | use std::mem::{zeroed,transmute}; 17 | use self::libc::{c_long,c_ulong,c_void,c_int}; 18 | pub use x11::xlib::*; 19 | //pub use xcb::xproto::*; 20 | use xcb::base::Connection; 21 | pub use x11::xlib::{Atom,KeyCode,KeySym,Screen,Window}; 22 | pub use x11::xlib::Window as XWindow; 23 | 24 | //Event types 25 | mod xevent { 26 | #![allow(non_upper_case_globals,dead_code)] 27 | pub const KeyPress: i32 = 2i32; 28 | pub const KeyRelease: i32 = 3i32; 29 | pub const ButtonPress: i32 = 4i32; 30 | pub const ButtonRelease: i32 = 5i32; 31 | pub const MotionNotify: i32 = 6i32; 32 | pub const EnterNotify: i32 = 7i32; 33 | pub const LeaveNotify: i32 = 8i32; 34 | pub const FocusIn: i32 = 9i32; 35 | pub const FocusOut: i32 = 10i32; 36 | pub const KeymapNotify: i32 = 11i32; 37 | pub const Expose: i32 = 12i32; 38 | pub const GraphicsExpose: i32 = 13i32; 39 | pub const NoExpose: i32 = 14i32; 40 | pub const VisibilityNotify: i32 = 15i32; 41 | pub const CreateNotify: i32 = 16i32; 42 | pub const DestroyNotify: i32 = 17i32; 43 | pub const UnmapNotify: i32 = 18i32; 44 | pub const MapNotify: i32 = 19i32; 45 | pub const MapRequest: i32 = 20i32; 46 | pub const ReparentNotify: i32 = 21i32; 47 | pub const ConfigureNotify: i32 = 22i32; 48 | pub const ConfigureRequest: i32 = 23i32; 49 | pub const GravityNotify: i32 = 24i32; 50 | pub const ResizeRequest: i32 = 25i32; 51 | pub const CirculateNotify: i32 = 26i32; 52 | pub const CirculateRequest: i32 = 27i32; 53 | pub const PropertyNotify: i32 = 28i32; 54 | pub const SelectionClear: i32 = 29i32; 55 | pub const SelectionRequest: i32 = 30i32; 56 | pub const SelectionNotify: i32 = 31i32; 57 | pub const ColormapNotify: i32 = 32i32; 58 | pub const ClientMessage: i32 = 33i32; 59 | pub const MappingNotify: i32 = 34i32; 60 | pub const GenericEvent: i32 = 35i32; 61 | } 62 | 63 | extern { 64 | pub fn XkbKeycodeToKeysym(dpy: *const Display, kc: KeyCode, group: usize, level: usize) -> KeySym; 65 | } 66 | 67 | //This is not going to be window server agnostic like wtftw is. Sorry, too hard 68 | //to tell what is server specific or not...Also I don't use Wayland 69 | pub struct XServer { 70 | pub conn: Connection, 71 | pub display: *mut Display, 72 | root: Window, 73 | event: *mut XEvent, 74 | keys: Vec, 75 | pub kommand_mod: RefCell> 76 | } 77 | 78 | //Actually just a wrapper around XEvents of the same name, but more info 79 | pub enum ServerEvent { 80 | ButtonPress((i32,i32)), 81 | KeyPress(Key), 82 | MapRequest(GWindow), 83 | DestroyNotify(Window), 84 | EnterNotify(Window), 85 | Unknown 86 | } 87 | 88 | pub fn keysym_to_string(sym: KeySym) -> &'static CStr { 89 | unsafe { 90 | let s: *const i8 = XKeysymToString(sym); 91 | &CStr::from_ptr(s) 92 | } 93 | } 94 | 95 | impl XServer { 96 | pub unsafe fn new() -> XServer { 97 | //TODO: Setup error handler 98 | let (conn, screen_num) = Connection::connect_with_xlib_display().unwrap(); 99 | let (disp, root) = { 100 | let setup = conn.get_setup(); 101 | let disp = conn.get_raw_dpy(); 102 | let screen = setup.roots().nth(screen_num as usize).unwrap(); 103 | 104 | debug!("XServer display: {:?}",disp); 105 | let root = XRootWindow(disp, screen_num); 106 | (disp, root) 107 | }; 108 | XSelectInput(disp,root,(SubstructureNotifyMask|SubstructureRedirectMask|EnterWindowMask)); 109 | XSync(disp,1); 110 | 111 | XServer { 112 | conn: conn, 113 | display: disp, 114 | root: root, 115 | event: malloc(256) as *mut XEvent,//unsafe { *malloc(size_of::() as *const XEvent) }, //Stolen from wtftw(ish) 116 | keys: Vec::new(), 117 | kommand_mod: RefCell::new(None) 118 | } 119 | } 120 | 121 | pub fn focus(&mut self, wind: Window) { 122 | unsafe { 123 | XSetInputFocus(self.display,wind,2 /*RevertToParent*/,0 /*CurrentTime*/); 124 | } 125 | } 126 | 127 | pub fn set_border_width(&mut self, wind: Window, width: u32) { 128 | unsafe { 129 | XSetWindowBorderWidth(self.display,wind,width); 130 | } 131 | } 132 | 133 | pub fn set_border_color(&mut self, wind: Window, color: c_ulong) { 134 | unsafe { 135 | XSetWindowBorder(self.display,wind,color); 136 | } 137 | } 138 | 139 | pub fn width(&mut self, screen: u32) -> u32 { 140 | unsafe { XDisplayWidth(self.display,screen as i32) as u32 } //why is this i32?? 141 | } 142 | pub fn height(&mut self, screen: u32) -> u32 { 143 | unsafe { XDisplayHeight(self.display,screen as i32) as u32 } //why is this i32?? 144 | } 145 | 146 | pub fn map(&mut self,wind: Window){ 147 | unsafe { 148 | XMapWindow(self.display,wind); 149 | //xcb_map_window(self.display, wind); 150 | XSelectInput(self.display,wind,(EnterWindowMask)); 151 | } 152 | } 153 | pub fn unmap(&mut self,wind: Window){ 154 | unsafe { 155 | XUnmapWindow(self.display,wind); 156 | } 157 | } 158 | 159 | pub fn refresh(&mut self,gwind: &mut GWindow){ 160 | unsafe { 161 | if gwind.shown && gwind._mapped == false { 162 | gwind._mapped = true; 163 | self.map(gwind.wind_ptr); 164 | } 165 | else if gwind.shown == false && gwind._mapped { 166 | gwind._mapped = false; 167 | self.unmap(gwind.wind_ptr); 168 | } 169 | 170 | if gwind._mapped { 171 | let (s_x,s_y) = gwind.size; 172 | XMoveResizeWindow(self.display, gwind.wind_ptr, gwind.x as i32, gwind.y as i32, s_x as u32, s_y as u32); 173 | } 174 | } 175 | } 176 | 177 | pub unsafe fn bring_to_front(&mut self, wind: Window){ 178 | //let's hope this is the correct way to do it? 179 | XConfigureWindow(self.display, wind, /* CWStackMode */ (1<<6), 180 | &mut XWindowChanges { 181 | x: 0, 182 | y: 0, 183 | width: 0, 184 | height: 0, 185 | border_width: 0, 186 | sibling: 0, 187 | stack_mode: 0 /* Above */ 188 | }); 189 | } 190 | 191 | pub unsafe fn kill_window(&mut self, wind: Window){ 192 | //Holy shit this is terrible 193 | let mut data: [c_long; 5] = zeroed(); 194 | let mut wmproto = self.get_atom("WM_PROTOCOLS"); 195 | let mut wmkill = self.get_atom("WM_DELETE_WINDOW"); 196 | data[0] = wmkill as c_long; 197 | data[1] = 0 as c_long; //CurrentTime 198 | struct XClientBullshit { 199 | _type: c_int, 200 | serial: c_ulong, 201 | send_event: c_int, 202 | display: *mut Display, 203 | window: Window, 204 | message_type: Atom, 205 | format: c_int, 206 | data: [c_long; 5], 207 | } 208 | 209 | let mut message = XClientBullshit { 210 | _type: ClientMessage, 211 | serial: 0, 212 | send_event: 0, 213 | display: self.display, 214 | window: wind, 215 | message_type: wmproto, 216 | format: 32, 217 | data: data, 218 | }; 219 | let mess_ptr = &mut message as *mut XClientBullshit as *mut XClientMessageEvent; 220 | XSendEvent(self.display, wind, 0 /*false*/, NoEventMask, (mess_ptr as *mut XEvent)); 221 | } 222 | 223 | pub unsafe fn quit(&mut self,qkey: Key){ 224 | let mut root_return: Window = zeroed(); 225 | let mut parent: Window = zeroed(); 226 | let mut children: *mut Window = zeroed(); 227 | let mut nchildren: u32 = 0; 228 | 229 | XQueryTree(self.display, self.root, &mut root_return, &mut parent, &mut children, &mut nchildren); 230 | if nchildren == 0 { 231 | return; 232 | } 233 | for ind in 0..nchildren as isize { 234 | let wind: Window = *children.offset(ind); 235 | self.kill_window(wind); //Kill them softly 236 | } 237 | 'stall: while nchildren > 0 { 238 | let ev = self.get_event(); 239 | match ev { 240 | ServerEvent::KeyPress(key) => { 241 | if key == qkey { //They pressed the Quit button while waiting 242 | //I shall not ask nicely a second time 243 | break 'stall; 244 | } 245 | }, 246 | _ => () 247 | } 248 | XQueryTree(self.display, self.root, &mut root_return, &mut parent, &mut children, &mut nchildren); 249 | } 250 | self.clear_keys(); 251 | } 252 | 253 | pub fn get_atom(&mut self, s: &str) -> Atom { 254 | unsafe { 255 | XInternAtom(self.display, CString::new(s).unwrap().as_ptr() as *mut i8, 1) 256 | } 257 | } 258 | 259 | fn get_event_as(&self) -> &T { //XEvent as XKeyPressedEvent 260 | let ev: *mut XEvent = self.event; 261 | unsafe { 262 | &*(ev as *mut T) 263 | } 264 | } 265 | 266 | pub fn get_event(&mut self) -> ServerEvent { 267 | trace!("Calling XNextEvent"); 268 | trace!("disp: {:?} event: {:?}", self.display, self.event); 269 | unsafe { 270 | XNextEvent(self.display, self.event); 271 | } 272 | trace!("XNextEvent passed"); 273 | let event_type = unsafe { (*self.event).get_type() }; 274 | match event_type { 275 | xevent::ButtonPress => unsafe { 276 | let ev = self.get_event_as::(); 277 | debug!("Pressed button {}",ev.state); 278 | ServerEvent::ButtonPress((ev.x,ev.y)) 279 | }, 280 | xevent::KeyPress => unsafe { 281 | let ev = self.get_event_as::(); 282 | //let m = KeyMod::from_bits(ev.state).unwrap_or(KeyMod::empty()); 283 | //ServerEvent::KeyPress(ev.keycode,m) 284 | let k = Key::parse(ev.keycode as KeyCode,ev.state,self); 285 | ServerEvent::KeyPress(k) 286 | }, 287 | xevent::EnterNotify => unsafe { 288 | let ev = self.get_event_as::(); 289 | ServerEvent::EnterNotify(ev.window) 290 | }, 291 | xevent::MapRequest => unsafe { 292 | let ev = self.get_event_as::(); 293 | debug!("Window added {}",ev.window); 294 | let w = GWindow { 295 | wind_ptr: ev.window, 296 | _mapped: false, 297 | shown: false, 298 | floating: false, 299 | x: 0, 300 | y: 0, 301 | z: 0, 302 | size: (0,0) 303 | }; 304 | ServerEvent::MapRequest(w) 305 | }, 306 | xevent::DestroyNotify => unsafe { 307 | let ev = self.get_event_as::(); 308 | debug!("Window destroyed {}",ev.window); 309 | ServerEvent::DestroyNotify(ev.window) 310 | }, 311 | _ => ServerEvent::Unknown 312 | } 313 | } 314 | 315 | pub fn add_key(&mut self, k: Key){ 316 | self.keys.push(k) 317 | } 318 | 319 | pub fn grab_keys(&mut self){ 320 | unsafe { 321 | XUngrabKey(self.display, 0 /*AnyKey*/, (1<<15) /*AnyModifier*/, self.root); 322 | } 323 | //Apparently this may have problems with NumLock. 324 | //Cross that bridge when we get to it. 325 | for key in self.keys.iter(){ 326 | unsafe { 327 | XGrabKey(self.display, key.code as i32, key.modifier.bits(), self.root, true as i32, GrabModeAsync, GrabModeAsync); 328 | XGrabKey(self.display, key.code as i32, (key.modifier | LOCK).bits(), self.root, true as i32, GrabModeAsync, GrabModeAsync); 329 | } 330 | } 331 | } 332 | 333 | pub fn clear_keys(&mut self){ 334 | self.keys.clear(); 335 | unsafe { 336 | XUngrabKey(self.display, 0 /*AnyKey*/, (1<<15) /*AnyModifier*/, self.root); 337 | } 338 | } 339 | 340 | pub fn string_to_keysym(&self,s: &mut String) -> KeySym { 341 | unsafe { 342 | let mut x = CString::new((*s.as_mut_vec()).clone()).unwrap(); 343 | let sym = XStringToKeysym(x.as_ptr() as *mut i8); 344 | if sym == 0 { //Invalid string! 345 | println!("{} is an invalid X11 keysym!",s); 346 | //panic!(format!("{} is an invalid X11 keysym!",s)); 347 | } 348 | sym 349 | } 350 | } 351 | 352 | pub fn keycode_to_keysym(&self,code: KeyCode) -> KeySym { 353 | unsafe { 354 | XkbKeycodeToKeysym(&*self.display, code, 0, 0) 355 | } 356 | } 357 | 358 | pub fn keysym_to_keycode(&self,sym: KeySym) -> KeyCode { 359 | unsafe { 360 | XKeysymToKeycode(self.display, sym) as KeyCode 361 | } 362 | } 363 | } 364 | --------------------------------------------------------------------------------