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