|  | @@ -0,0 +1,174 @@
 | 
	
		
			
				|  |  | +use ui;
 | 
	
		
			
				|  |  | +use ui::panel;
 | 
	
		
			
				|  |  | +use ui::x11;
 | 
	
		
			
				|  |  | +use xcb;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +use std::rc::Weak;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +const CLIENT_MESSAGE: u8 = xcb::CLIENT_MESSAGE | 0x80; // 0x80 flag for client messages
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +pub struct Tray {
 | 
	
		
			
				|  |  | +    panel: Weak<panel::Panel>,
 | 
	
		
			
				|  |  | +    children: Vec<xcb::Window>,
 | 
	
		
			
				|  |  | +    timestamp: xcb::Timestamp
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// I'M SO SORRY
 | 
	
		
			
				|  |  | +macro_rules! refs {
 | 
	
		
			
				|  |  | +    ( $slf:ident, $panel:ident, $conn:ident ) => {
 | 
	
		
			
				|  |  | +        let $panel = $slf.panel.upgrade().unwrap();
 | 
	
		
			
				|  |  | +        let $conn = &$panel.conn;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +impl Tray {
 | 
	
		
			
				|  |  | +    pub fn new(panel: Weak<panel::Panel>) -> Tray {
 | 
	
		
			
				|  |  | +        Tray {
 | 
	
		
			
				|  |  | +            panel: panel,
 | 
	
		
			
				|  |  | +            children: vec![],
 | 
	
		
			
				|  |  | +            timestamp: 0
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn take_selection(&mut self, timestamp: xcb::Timestamp) -> bool {
 | 
	
		
			
				|  |  | +        refs!(self, panel, conn);
 | 
	
		
			
				|  |  | +        let window = panel.window;
 | 
	
		
			
				|  |  | +        let selection = conn.atom(x11::_NET_SYSTEM_TRAY_S0);
 | 
	
		
			
				|  |  | +        xcb::set_selection_owner(conn, window, selection, timestamp);
 | 
	
		
			
				|  |  | +        let owner = xcb::get_selection_owner(conn, selection).get_reply().unwrap().owner();
 | 
	
		
			
				|  |  | +        let ok = owner == window;
 | 
	
		
			
				|  |  | +        if ok {
 | 
	
		
			
				|  |  | +            self.timestamp = timestamp;
 | 
	
		
			
				|  |  | +            let screen = conn.default_screen();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            let client_event = xcb::ClientMessageEvent::new(
 | 
	
		
			
				|  |  | +                32, // 32 bits (refers to data)
 | 
	
		
			
				|  |  | +                screen.root,
 | 
	
		
			
				|  |  | +                conn.atom(x11::MANAGER),
 | 
	
		
			
				|  |  | +                xcb::ClientMessageData::from_data32([timestamp, selection, window, 0, 0])
 | 
	
		
			
				|  |  | +            );
 | 
	
		
			
				|  |  | +            xcb::send_event(conn, false, screen.root, xcb::EVENT_MASK_STRUCTURE_NOTIFY, &client_event);
 | 
	
		
			
				|  |  | +            conn.flush();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        ok
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    fn x_pos(&self, index: usize) -> u32 {
 | 
	
		
			
				|  |  | +        refs!(self, panel, _conn);
 | 
	
		
			
				|  |  | +        let panel_width = panel.width as u32;
 | 
	
		
			
				|  |  | +        let tray_width = (index + 1) as u32 * ui::SIZE as u32;
 | 
	
		
			
				|  |  | +        panel_width - tray_width
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn adopt(&mut self, window: xcb::Window) {
 | 
	
		
			
				|  |  | +        refs!(self, panel, conn);
 | 
	
		
			
				|  |  | +        xcb::change_window_attributes(conn, window, &[
 | 
	
		
			
				|  |  | +            (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY)
 | 
	
		
			
				|  |  | +        ]);
 | 
	
		
			
				|  |  | +        xcb::reparent_window(conn, window, panel.window, self.x_pos(self.children.len()) as i16, 0);
 | 
	
		
			
				|  |  | +        xcb::map_window(conn, window);
 | 
	
		
			
				|  |  | +        self.force_size(window, None);
 | 
	
		
			
				|  |  | +        conn.flush();
 | 
	
		
			
				|  |  | +        self.children.push(window);
 | 
	
		
			
				|  |  | +        self.reposition();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn forget(&mut self, window: xcb::Window) {
 | 
	
		
			
				|  |  | +        self.children.retain(|child| *child != window);
 | 
	
		
			
				|  |  | +        self.reposition();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn force_size(&self, window: xcb::Window, dimensions: Option<(u16, u16)>) {
 | 
	
		
			
				|  |  | +        refs!(self, panel, conn);
 | 
	
		
			
				|  |  | +        let dimensions = dimensions.unwrap_or_else(|| {
 | 
	
		
			
				|  |  | +            let geometry = xcb::get_geometry(conn, window).get_reply().unwrap();
 | 
	
		
			
				|  |  | +            (geometry.width(), geometry.height())
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        if dimensions != (ui::SIZE, ui::SIZE) {
 | 
	
		
			
				|  |  | +            xcb::configure_window(conn, window, &[
 | 
	
		
			
				|  |  | +                (xcb::CONFIG_WINDOW_WIDTH as u16, ui::SIZE as u32),
 | 
	
		
			
				|  |  | +                (xcb::CONFIG_WINDOW_HEIGHT as u16, ui::SIZE as u32)
 | 
	
		
			
				|  |  | +            ]);
 | 
	
		
			
				|  |  | +            conn.flush();
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn reposition(&self) {
 | 
	
		
			
				|  |  | +        refs!(self, panel, conn);
 | 
	
		
			
				|  |  | +        for (index, child) in self.children.iter().enumerate() {
 | 
	
		
			
				|  |  | +            let window = *child;
 | 
	
		
			
				|  |  | +            xcb::configure_window(conn, window, &[
 | 
	
		
			
				|  |  | +                (xcb::CONFIG_WINDOW_X as u16, self.x_pos(index))
 | 
	
		
			
				|  |  | +            ]);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        conn.flush();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn finish(&mut self) {
 | 
	
		
			
				|  |  | +        refs!(self, panel, conn);
 | 
	
		
			
				|  |  | +        let screen = conn.default_screen();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        for child in self.children.iter() {
 | 
	
		
			
				|  |  | +            let window = *child;
 | 
	
		
			
				|  |  | +            xcb::change_window_attributes(conn, window, &[
 | 
	
		
			
				|  |  | +                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_NO_EVENT)
 | 
	
		
			
				|  |  | +            ]);
 | 
	
		
			
				|  |  | +            xcb::unmap_window(conn, window);
 | 
	
		
			
				|  |  | +            xcb::reparent_window(conn, window, screen.root, 0, 0);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        xcb::change_window_attributes(conn, panel.window, &[
 | 
	
		
			
				|  |  | +            (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY)
 | 
	
		
			
				|  |  | +        ]);
 | 
	
		
			
				|  |  | +        xcb::set_selection_owner(conn, xcb::NONE, conn.atom(x11::_NET_SYSTEM_TRAY_S0), self.timestamp);
 | 
	
		
			
				|  |  | +        conn.flush();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    pub fn handle_event(&mut self, event: &xcb::GenericEvent) -> bool {
 | 
	
		
			
				|  |  | +        refs!(self, panel, conn);
 | 
	
		
			
				|  |  | +        if panel.is_finishing() {
 | 
	
		
			
				|  |  | +            return false
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        match event.response_type() {
 | 
	
		
			
				|  |  | +            xcb::PROPERTY_NOTIFY if self.timestamp == 0 => {
 | 
	
		
			
				|  |  | +                let event: &xcb::PropertyNotifyEvent = xcb::cast_event(&event);
 | 
	
		
			
				|  |  | +                if !self.take_selection(event.time()) {
 | 
	
		
			
				|  |  | +                    println!("Could not take ownership of tray selection. Maybe another tray is also running?");
 | 
	
		
			
				|  |  | +                    return true
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            CLIENT_MESSAGE => {
 | 
	
		
			
				|  |  | +                let event: &xcb::ClientMessageEvent = xcb::cast_event(&event);
 | 
	
		
			
				|  |  | +                if event.type_() == conn.atom(x11::_NET_SYSTEM_TRAY_OPCODE) {
 | 
	
		
			
				|  |  | +                    let data = event.data().data32();
 | 
	
		
			
				|  |  | +                    let opcode = data[1];
 | 
	
		
			
				|  |  | +                    let window = data[2];
 | 
	
		
			
				|  |  | +                    if opcode == 0 {
 | 
	
		
			
				|  |  | +                        self.adopt(window);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            xcb::REPARENT_NOTIFY => {
 | 
	
		
			
				|  |  | +                let event: &xcb::ReparentNotifyEvent = xcb::cast_event(&event);
 | 
	
		
			
				|  |  | +                if event.parent() != panel.window {
 | 
	
		
			
				|  |  | +                    self.forget(event.window());
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            xcb::DESTROY_NOTIFY => {
 | 
	
		
			
				|  |  | +                let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event);
 | 
	
		
			
				|  |  | +                self.forget(event.window());
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            xcb::CONFIGURE_NOTIFY => {
 | 
	
		
			
				|  |  | +                let event: &xcb::ConfigureNotifyEvent = xcb::cast_event(&event);
 | 
	
		
			
				|  |  | +                let window = event.window();
 | 
	
		
			
				|  |  | +                if window != panel.window {
 | 
	
		
			
				|  |  | +                    self.force_size(window, Some((event.width(), event.height())));
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            xcb::SELECTION_CLEAR => {
 | 
	
		
			
				|  |  | +                self.finish();
 | 
	
		
			
				|  |  | +            },
 | 
	
		
			
				|  |  | +            _ => {}
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        false
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |