├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── atom.rs ├── main.rs └── tray.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rusttray" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "chan-signal 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "css-color-parser 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "xcb 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "bit-set" 14 | version = "0.2.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | dependencies = [ 17 | "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 18 | ] 19 | 20 | [[package]] 21 | name = "bit-vec" 22 | version = "0.4.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | 25 | [[package]] 26 | name = "chan" 27 | version = "0.1.18" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 31 | ] 32 | 33 | [[package]] 34 | name = "chan-signal" 35 | version = "0.1.6" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | dependencies = [ 38 | "bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 39 | "chan 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", 41 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "css-color-parser" 46 | version = "0.1.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | 49 | [[package]] 50 | name = "getopts" 51 | version = "0.2.14" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | 54 | [[package]] 55 | name = "lazy_static" 56 | version = "0.1.16" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | 59 | [[package]] 60 | name = "libc" 61 | version = "0.2.15" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | 64 | [[package]] 65 | name = "log" 66 | version = "0.3.6" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | 69 | [[package]] 70 | name = "rand" 71 | version = "0.3.14" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 75 | ] 76 | 77 | [[package]] 78 | name = "xcb" 79 | version = "0.7.5" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | dependencies = [ 82 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 84 | ] 85 | 86 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusttray" 3 | version = "0.1.0" 4 | authors = ["Thomas Dy "] 5 | 6 | [dependencies] 7 | chan = "0.1.18" 8 | chan-signal = "0.1.6" 9 | getopts = "0.2" 10 | css-color-parser = "*" 11 | 12 | [dependencies.xcb] 13 | version = "0.7.5" 14 | features = [ "thread" ] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rusttray 2 | 3 | A rudimentary system tray implemented in Rust. 4 | 5 | This is more of an experiment than something you should use on a daily basis. I 6 | primarily wrote it to understand how the system tray mechanics work in X. This 7 | doesn't actually implement all of the system tray specification but it works for 8 | most of the programs I use. 9 | 10 | rusttray only implements XEMBED style icons. The tray icons themselves perform 11 | the drawing and the tray only manages their sizes and positions. It does not 12 | draw icons by itself. In addition, balloon messages are not handled as well. 13 | 14 | ## How it works 15 | 16 | When starting, 17 | 18 | 1. Create a window 19 | 2. [Acquire a selection][1] to [`_NET_SYSTEM_TRAY_S0`][2] 20 | 3. Announce arrival as [manager][3] 21 | 4. Receive client messages to request docking 22 | 5. Reparent tray icon windows into our window 23 | 6. Map the tray icon windows 24 | 25 | When exiting, 26 | 27 | 1. Unmap tray icon windows 28 | 2. Reparent tray icon windows back to screen root 29 | 3. [Release the selection][4] 30 | 31 | It is important that the tray waits for the reparenting back to root to actually 32 | finish. If the tray exits before the tray windows are reparented, it will cause 33 | those applications to crash. 34 | 35 | [1]: https://tronche.com/gui/x/icccm/sec-2.html#s-2.1 36 | [2]: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html#locating 37 | [3]: https://tronche.com/gui/x/icccm/sec-2.html#s-2.8 38 | [4]: https://tronche.com/gui/x/icccm/sec-2.html#s-2.3 39 | -------------------------------------------------------------------------------- /src/atom.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::cell::RefCell; 3 | use xcb; 4 | 5 | macro_rules! atoms { 6 | ( $( $x:ident ),* ) => { 7 | #[allow(non_snake_case)] 8 | $(pub const $x: &'static str = stringify!($x);)* 9 | } 10 | } 11 | 12 | atoms!( 13 | _NET_SYSTEM_TRAY_S0, 14 | _NET_SYSTEM_TRAY_ORIENTATION, 15 | _NET_SYSTEM_TRAY_OPCODE, 16 | _NET_WM_WINDOW_TYPE, 17 | _NET_WM_WINDOW_TYPE_DOCK, 18 | MANAGER 19 | ); 20 | 21 | pub struct Atoms<'a> { 22 | conn: &'a xcb::Connection, 23 | cache: RefCell> 24 | } 25 | 26 | impl<'a> Atoms<'a> { 27 | pub fn new(conn: &xcb::Connection) -> Atoms { 28 | Atoms { 29 | conn: conn, 30 | cache: RefCell::new(HashMap::new()) 31 | } 32 | } 33 | 34 | pub fn get(&self, name: &str) -> xcb::Atom { 35 | let mut cache = self.cache.borrow_mut(); 36 | if cache.contains_key(name) { 37 | *cache.get(name).unwrap() 38 | } 39 | else { 40 | let atom = xcb::intern_atom(self.conn, false, name).get_reply().unwrap().atom(); 41 | cache.insert(name.to_string(), atom); 42 | atom 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | extern crate css_color_parser; 5 | extern crate getopts; 6 | extern crate xcb; 7 | 8 | mod atom; 9 | mod tray; 10 | 11 | use css_color_parser::Color; 12 | 13 | use std::env; 14 | use std::process; 15 | use std::thread; 16 | use std::sync::Arc; 17 | 18 | const PROGRAM: &'static str = "rusttray"; 19 | const EXIT_WRONG_ARGS: i32 = 1; 20 | const EXIT_FAILED_CONNECT: i32 = 10; 21 | const EXIT_FAILED_SELECT: i32 = 11; 22 | 23 | fn main() { 24 | process::exit(real_main()); 25 | } 26 | 27 | fn real_main() -> i32 { 28 | let signal = chan_signal::notify(&[chan_signal::Signal::INT, chan_signal::Signal::TERM]); 29 | let args: Vec = env::args().collect(); 30 | 31 | let mut opts = getopts::Options::new(); 32 | opts.optopt("i", "icon-size", "size of the tray icons, default 20", ""); 33 | opts.optopt("p", "position", "position of the tray, one of: top-left, top-right, bottom-left, bottom-right", ""); 34 | opts.optopt("b", "background", "background color of the tray", ""); 35 | opts.optflag("h", "help", "print this help menu"); 36 | let matches = match opts.parse(&args[1..]) { 37 | Ok(m) => m, 38 | Err(f) => panic!(f.to_string()) 39 | }; 40 | 41 | if matches.opt_present("h") { 42 | let brief = format!("Usage: {} [options]", PROGRAM); 43 | print!("{}", opts.usage(&brief)); 44 | return 0 45 | } 46 | let pos = matches.opt_str("p").unwrap_or("top-left".to_string()); 47 | let pos = match pos.as_ref() { 48 | "top-left" => tray::TOP_LEFT, 49 | "top-right" => tray::TOP_RIGHT, 50 | "bottom-left" => tray::BOTTOM_LEFT, 51 | "bottom-right" => tray::BOTTOM_RIGHT, 52 | _ => { 53 | println!("Invalid position specified."); 54 | return EXIT_WRONG_ARGS 55 | } 56 | }; 57 | let size = matches.opt_str("i"); 58 | let size = match size { 59 | Some(string) => match string.parse::() { 60 | Ok(size) => size, 61 | Err(e) => { 62 | println!("Invalid size specified, {}.", e.to_string()); 63 | return EXIT_WRONG_ARGS 64 | } 65 | }, 66 | None => 20 67 | }; 68 | let black = Color { r: 0, g: 0, b: 0, a: 1.0 }; 69 | let bg = matches.opt_str("b"); 70 | let bg = match bg { 71 | Some(color) => match color.parse::() { 72 | Ok(color) => color, 73 | Err(e) => { 74 | println!("Invalid color specified, {}.", e.to_string()); 75 | return EXIT_WRONG_ARGS 76 | } 77 | }, 78 | None => black 79 | }; 80 | let bg = ((bg.a * 255.0) as u32) << 24 | (bg.r as u32) << 16 | (bg.g as u32) << 8 | (bg.b as u32); 81 | 82 | if let Ok((conn, preferred)) = xcb::Connection::connect(None) { 83 | let conn = Arc::new(conn); 84 | let atoms = atom::Atoms::new(&conn); 85 | 86 | let mut tray = tray::Tray::new(&conn, &atoms, preferred as usize, size, pos, bg); 87 | 88 | if !tray.is_selection_available() { 89 | println!("Another system tray is already running"); 90 | return EXIT_FAILED_SELECT 91 | } 92 | 93 | let (tx, rx) = chan::sync(0); 94 | { 95 | let conn = conn.clone(); 96 | thread::spawn(move || { 97 | loop { 98 | match conn.wait_for_event() { 99 | Some(event) => { tx.send(event); }, 100 | None => { break; } 101 | } 102 | } 103 | }); 104 | } 105 | 106 | tray.create(); 107 | 108 | loop { 109 | chan_select!( 110 | rx.recv() -> event => { 111 | if let Some(code) = tray.handle_event(event.unwrap()) { 112 | return code 113 | } 114 | }, 115 | signal.recv() => { 116 | tray.finish(); 117 | } 118 | ); 119 | } 120 | } 121 | else { 122 | println!("Could not connect to X server!"); 123 | return EXIT_FAILED_CONNECT 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/tray.rs: -------------------------------------------------------------------------------- 1 | use atom; 2 | use xcb; 3 | 4 | pub enum HorizontalAlign { 5 | Left, 6 | Right 7 | } 8 | 9 | pub enum VerticalAlign { 10 | Top, 11 | Bottom 12 | } 13 | 14 | pub type Position = (VerticalAlign, HorizontalAlign); 15 | 16 | pub const TOP_LEFT: Position = (VerticalAlign::Top, HorizontalAlign::Left); 17 | pub const TOP_RIGHT: Position = (VerticalAlign::Top, HorizontalAlign::Right); 18 | pub const BOTTOM_LEFT: Position = (VerticalAlign::Bottom, HorizontalAlign::Left); 19 | pub const BOTTOM_RIGHT: Position = (VerticalAlign::Bottom, HorizontalAlign::Right); 20 | 21 | const CLIENT_MESSAGE: u8 = xcb::CLIENT_MESSAGE | 0x80; // 0x80 flag for client messages 22 | 23 | const SYSTEM_TRAY_REQUEST_DOCK: u32 = 0; 24 | const SYSTEM_TRAY_BEGIN_MESSAGE: u32 = 1; 25 | const SYSTEM_TRAY_CANCEL_MESSAGE: u32 = 2; 26 | 27 | pub struct Tray<'a> { 28 | conn: &'a xcb::Connection, 29 | atoms: &'a atom::Atoms<'a>, 30 | screen: usize, 31 | icon_size: u16, 32 | position: Position, 33 | bg: u32, 34 | window: xcb::Window, 35 | children: Vec, 36 | timestamp: xcb::Timestamp, 37 | finishing: bool 38 | } 39 | 40 | impl<'a> Tray<'a> { 41 | pub fn new<'b>( 42 | conn: &'b xcb::Connection, 43 | atoms: &'b atom::Atoms, 44 | screen: usize, 45 | icon_size: u16, 46 | position: Position, 47 | bg: u32 48 | ) -> Tray<'b> { 49 | Tray::<'b> { 50 | conn: conn, 51 | atoms: atoms, 52 | screen: screen, 53 | icon_size: icon_size, 54 | position: position, 55 | bg: bg, 56 | window: conn.generate_id(), 57 | children: vec![], 58 | timestamp: 0, 59 | finishing: false 60 | } 61 | } 62 | 63 | pub fn create(&self) { 64 | let setup = self.conn.get_setup(); 65 | let screen = setup.roots().nth(self.screen).unwrap(); 66 | 67 | xcb::create_window( 68 | &self.conn, 69 | xcb::COPY_FROM_PARENT as u8, 70 | self.window, 71 | screen.root(), 72 | 0, 0, 73 | self.icon_size, self.icon_size, 74 | 0, 75 | xcb::WINDOW_CLASS_INPUT_OUTPUT as u16, 76 | screen.root_visual(), 77 | &[ 78 | (xcb::CW_BACK_PIXEL, self.bg), 79 | (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_PROPERTY_CHANGE) 80 | ] 81 | ); 82 | self.set_property( 83 | self.atoms.get(atom::_NET_WM_WINDOW_TYPE), 84 | xcb::ATOM_ATOM, 85 | 32, 86 | &[self.atoms.get(atom::_NET_WM_WINDOW_TYPE_DOCK)] 87 | ); 88 | self.set_property( 89 | xcb::ATOM_WM_NAME, 90 | xcb::ATOM_STRING, 91 | 8, 92 | ::PROGRAM.as_bytes() 93 | ); 94 | self.set_property( 95 | xcb::ATOM_WM_CLASS, 96 | xcb::ATOM_STRING, 97 | 8, 98 | format!("{0}\0{0}", ::PROGRAM).as_bytes() 99 | ); 100 | self.set_property( 101 | self.atoms.get(atom::_NET_SYSTEM_TRAY_ORIENTATION), 102 | xcb::ATOM_CARDINAL, 103 | 32, 104 | &[0 as u32] // 0 is horizontal, 1 is vertical 105 | ); 106 | self.conn.flush(); 107 | } 108 | 109 | pub fn set_property(&self, name: xcb::Atom, type_: xcb::Atom, format: u8, data: &[T]) { 110 | xcb::change_property( 111 | self.conn, 112 | xcb::PROP_MODE_REPLACE as u8, 113 | self.window, 114 | name, 115 | type_, 116 | format, 117 | data 118 | ); 119 | } 120 | 121 | pub fn is_selection_available(&self) -> bool { 122 | let selection = self.atoms.get(atom::_NET_SYSTEM_TRAY_S0); 123 | let owner = xcb::get_selection_owner(self.conn, selection).get_reply().unwrap().owner(); 124 | owner == xcb::NONE 125 | } 126 | 127 | pub fn take_selection(&mut self, timestamp: xcb::Timestamp) -> bool { 128 | let selection = self.atoms.get(atom::_NET_SYSTEM_TRAY_S0); 129 | xcb::set_selection_owner(self.conn, self.window, selection, timestamp); 130 | let owner = xcb::get_selection_owner(self.conn, selection).get_reply().unwrap().owner(); 131 | let ok = owner == self.window; 132 | if ok { 133 | self.timestamp = timestamp; 134 | let setup = self.conn.get_setup(); 135 | let screen = setup.roots().nth(self.screen).unwrap(); 136 | 137 | let client_event = xcb::ClientMessageEvent::new( 138 | 32, // 32 bits (refers to data) 139 | screen.root(), 140 | self.atoms.get(atom::MANAGER), 141 | xcb::ClientMessageData::from_data32([timestamp, selection, self.window, 0, 0]) 142 | ); 143 | xcb::send_event(self.conn, false, screen.root(), xcb::EVENT_MASK_STRUCTURE_NOTIFY, &client_event); 144 | self.conn.flush(); 145 | } 146 | ok 147 | } 148 | 149 | pub fn adopt(&mut self, window: xcb::Window) { 150 | let offset = (self.children.len() as u16 * self.icon_size) as i16; 151 | xcb::change_window_attributes(self.conn, window, &[ 152 | (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY) 153 | ]); 154 | xcb::reparent_window(self.conn, window, self.window, offset, 0); 155 | xcb::map_window(self.conn, window); 156 | self.force_size(window, None); 157 | self.conn.flush(); 158 | self.children.push(window); 159 | self.reposition(); 160 | } 161 | 162 | pub fn forget(&mut self, window: xcb::Window) { 163 | self.children.retain(|child| *child != window); 164 | for (index, child) in self.children.iter().enumerate() { 165 | let window = *child; 166 | let xpos = index as u32 * self.icon_size as u32; 167 | xcb::configure_window(&self.conn, window, &[ 168 | (xcb::CONFIG_WINDOW_X as u16, xpos) 169 | ]); 170 | } 171 | self.reposition(); 172 | } 173 | 174 | pub fn force_size(&self, window: xcb::Window, dimensions: Option<(u16, u16)>) { 175 | let dimensions = dimensions.unwrap_or_else(|| { 176 | let geometry = xcb::get_geometry(self.conn, window).get_reply().unwrap(); 177 | (geometry.width(), geometry.height()) 178 | }); 179 | if dimensions != (self.icon_size, self.icon_size) { 180 | xcb::configure_window(self.conn, window, &[ 181 | (xcb::CONFIG_WINDOW_WIDTH as u16, self.icon_size as u32), 182 | (xcb::CONFIG_WINDOW_HEIGHT as u16, self.icon_size as u32) 183 | ]); 184 | self.conn.flush(); 185 | } 186 | } 187 | 188 | pub fn reposition(&self) { 189 | let width = self.children.len() as u16 * self.icon_size; 190 | if width > 0 { 191 | let setup = self.conn.get_setup(); 192 | let screen = setup.roots().nth(self.screen).unwrap(); 193 | 194 | let (ref valign, ref halign) = self.position; 195 | let y = match valign { 196 | &VerticalAlign::Top => 0, 197 | &VerticalAlign::Bottom => screen.height_in_pixels() - self.icon_size 198 | }; 199 | let x = match halign { 200 | &HorizontalAlign::Left => 0, 201 | &HorizontalAlign::Right => screen.width_in_pixels() - width 202 | }; 203 | xcb::configure_window(self.conn, self.window, &[ 204 | (xcb::CONFIG_WINDOW_X as u16, x as u32), 205 | (xcb::CONFIG_WINDOW_Y as u16, y as u32), 206 | (xcb::CONFIG_WINDOW_WIDTH as u16, width as u32) 207 | ]); 208 | xcb::map_window(self.conn, self.window); 209 | } 210 | else { 211 | xcb::unmap_window(self.conn, self.window); 212 | } 213 | self.conn.flush(); 214 | } 215 | 216 | pub fn finish(&mut self) { 217 | self.finishing = true; 218 | let setup = self.conn.get_setup(); 219 | let screen = setup.roots().nth(self.screen).unwrap(); 220 | let root = screen.root(); 221 | 222 | for child in self.children.iter() { 223 | let window = *child; 224 | xcb::change_window_attributes(self.conn, window, &[ 225 | (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_NO_EVENT) 226 | ]); 227 | xcb::unmap_window(self.conn, window); 228 | xcb::reparent_window(self.conn, window, root, 0, 0); 229 | } 230 | xcb::change_window_attributes(self.conn, self.window, &[ 231 | (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY) 232 | ]); 233 | xcb::destroy_window(self.conn, self.window); 234 | self.conn.flush(); 235 | } 236 | 237 | pub fn handle_event(&mut self, event: xcb::GenericEvent) -> Option { 238 | if self.finishing { 239 | self.handle_event_finishing(event) 240 | } 241 | else { 242 | self.handle_event_normal(event) 243 | } 244 | } 245 | 246 | fn handle_event_normal(&mut self, event: xcb::GenericEvent) -> Option { 247 | match event.response_type() { 248 | xcb::PROPERTY_NOTIFY if self.timestamp == 0 => { 249 | let event: &xcb::PropertyNotifyEvent = xcb::cast_event(&event); 250 | if !self.take_selection(event.time()) { 251 | println!("Could not take ownership of tray selection. Maybe another tray is also running?"); 252 | return Some(::EXIT_FAILED_SELECT) 253 | } 254 | }, 255 | CLIENT_MESSAGE => { 256 | let event: &xcb::ClientMessageEvent = xcb::cast_event(&event); 257 | if event.type_() == self.atoms.get(atom::_NET_SYSTEM_TRAY_OPCODE) { 258 | let data = event.data().data32(); 259 | let opcode = data[1]; 260 | let window = data[2]; 261 | match opcode { 262 | SYSTEM_TRAY_REQUEST_DOCK => { 263 | self.adopt(window); 264 | }, 265 | SYSTEM_TRAY_BEGIN_MESSAGE => {}, 266 | SYSTEM_TRAY_CANCEL_MESSAGE => {}, 267 | _ => { unreachable!("Invalid opcode") } 268 | } 269 | } 270 | }, 271 | xcb::REPARENT_NOTIFY => { 272 | let event: &xcb::ReparentNotifyEvent = xcb::cast_event(&event); 273 | if event.parent() != self.window { 274 | self.forget(event.window()); 275 | } 276 | }, 277 | xcb::DESTROY_NOTIFY => { 278 | let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event); 279 | self.forget(event.window()); 280 | }, 281 | xcb::CONFIGURE_NOTIFY => { 282 | let event: &xcb::ConfigureNotifyEvent = xcb::cast_event(&event); 283 | self.force_size(event.window(), Some((event.width(), event.height()))); 284 | }, 285 | xcb::SELECTION_CLEAR => { 286 | self.finish(); 287 | }, 288 | _ => {} 289 | } 290 | None 291 | } 292 | 293 | fn handle_event_finishing(&mut self, event: xcb::GenericEvent) -> Option { 294 | if event.response_type() == xcb::DESTROY_NOTIFY { 295 | let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event); 296 | if event.window() == self.window { 297 | return Some(0) 298 | } 299 | } 300 | None 301 | } 302 | } 303 | --------------------------------------------------------------------------------