Browse Source

Implement tray

Thomas Dy 8 years ago
parent
commit
d6e9835b2f
4 changed files with 250 additions and 20 deletions
  1. 7 4
      src/ui/mod.rs
  2. 64 16
      src/ui/panel.rs
  3. 174 0
      src/ui/tray.rs
  4. 5 0
      src/ui/x11.rs

+ 7 - 4
src/ui/mod.rs

@@ -1,4 +1,5 @@
 mod panel;
+mod tray;
 mod x11;
 
 use chan;
@@ -6,12 +7,10 @@ use chan_signal;
 
 use std::thread;
 
+pub const SIZE: u16 = 20;
+
 pub fn ui_main(signal: chan::Receiver<chan_signal::Signal>) -> i32 {
     if let Some(conn) = x11::Connection::new() {
-
-        let panel = panel::Panel::new(&conn);
-        panel.create();
-
         let (tx, rx) = chan::sync(0);
         {
             let conn = conn.clone_connection();
@@ -25,10 +24,14 @@ pub fn ui_main(signal: chan::Receiver<chan_signal::Signal>) -> i32 {
             });
         }
 
+        let panel = panel::Panel::new(conn);
+        panel.create();
+
         loop {
             chan_select!(
                 rx.recv() -> event => {
                     if panel.handle_event(event.unwrap()) {
+                        println!("Exiting");
                         break;
                     }
                 },

+ 64 - 16
src/ui/panel.rs

@@ -1,24 +1,38 @@
 use xcb;
+use ui::tray;
 use ui::x11;
 
-pub struct Panel<'a> {
-    conn: &'a x11::Connection,
-    window: xcb::Window
+use std::cell::RefCell;
+use std::rc::Rc;
+
+pub struct Panel {
+    pub conn: x11::Connection,
+    pub window: xcb::Window,
+    pub width: u16,
+    tray: RefCell<Option<tray::Tray>>,
+    finishing: RefCell<bool>
 }
 
-impl<'a> Panel<'a> {
-    pub fn new(conn: &x11::Connection) -> Panel {
-        Panel {
+impl Panel {
+    pub fn new(conn: x11::Connection) -> Rc<Panel> {
+        let panel = Rc::new(Panel {
+            window: conn.generate_id(),
+            width: conn.default_screen().width,
             conn: conn,
-            window: conn.generate_id()
-        }
+            tray: RefCell::new(None),
+            finishing: RefCell::new(false)
+        });
+        let tray = tray::Tray::new(Rc::downgrade(&panel));
+        *panel.tray.borrow_mut() = Some(tray);
+        panel
     }
 
     pub fn create(&self) {
-        let screen = self.conn.default_screen();
+        let conn = &self.conn;
+        let screen = conn.default_screen();
 
         xcb::create_window(
-            self.conn,
+            conn,
             xcb::COPY_FROM_PARENT as u8,
             self.window,
             screen.root,
@@ -29,16 +43,50 @@ impl<'a> Panel<'a> {
             xcb::COPY_FROM_PARENT,
             &[
                 (xcb::CW_BACK_PIXEL, 0xFF000000),
-                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_PROPERTY_CHANGE)
+                (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_PROPERTY_CHANGE | xcb::EVENT_MASK_STRUCTURE_NOTIFY)
             ]
         );
-        xcb::map_window(self.conn, self.window);
-        self.conn.flush();
+        self.set_property(
+            conn.atom(x11::_NET_WM_WINDOW_TYPE),
+            xcb::ATOM_ATOM,
+            32,
+            &[conn.atom(x11::_NET_WM_WINDOW_TYPE_DOCK)]
+        );
+        xcb::map_window(conn, self.window);
+        conn.flush();
     }
 
-    pub fn handle_event(&self, _event: xcb::GenericEvent) -> bool {
-        false
+    pub fn set_property<T>(&self, name: xcb::Atom, type_: xcb::Atom, format: u8, data: &[T]) {
+        xcb::change_property(
+            &self.conn,
+            xcb::PROP_MODE_REPLACE as u8,
+            self.window,
+            name,
+            type_,
+            format,
+            data
+        );
     }
 
-    pub fn finish(&self) {}
+    pub fn handle_event(&self, event: xcb::GenericEvent) -> bool {
+        let tray = self.tray.borrow_mut().as_mut().unwrap().handle_event(&event);
+        if self.is_finishing() && event.response_type() == xcb::DESTROY_NOTIFY {
+            let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event);
+            if event.window() == self.window {
+                return true
+            }
+        }
+        tray
+    }
+
+    pub fn is_finishing(&self) -> bool {
+        *self.finishing.borrow()
+    }
+
+    pub fn finish(&self) {
+        *self.finishing.borrow_mut() = true;
+        self.tray.borrow_mut().as_mut().unwrap().finish();
+        xcb::destroy_window(&self.conn, self.window);
+        self.conn.flush();
+    }
 }

+ 174 - 0
src/ui/tray.rs

@@ -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
+    }
+}

+ 5 - 0
src/ui/x11.rs

@@ -16,7 +16,12 @@ macro_rules! atoms {
 
 atoms!(
     _NET_ACTIVE_WINDOW,
+    _NET_SYSTEM_TRAY_S0,
+    _NET_SYSTEM_TRAY_OPCODE,
     _NET_WM_NAME,
+    _NET_WM_WINDOW_TYPE,
+    _NET_WM_WINDOW_TYPE_DOCK,
+    MANAGER,
     UTF8_STRING
 );