tray.rs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. use atom;
  2. use xcb;
  3. pub enum HorizontalAlign {
  4. Left,
  5. Right
  6. }
  7. pub enum VerticalAlign {
  8. Top,
  9. Bottom
  10. }
  11. pub type Position = (VerticalAlign, HorizontalAlign);
  12. pub const TOP_LEFT: Position = (VerticalAlign::Top, HorizontalAlign::Left);
  13. pub const TOP_RIGHT: Position = (VerticalAlign::Top, HorizontalAlign::Right);
  14. pub const BOTTOM_LEFT: Position = (VerticalAlign::Bottom, HorizontalAlign::Left);
  15. pub const BOTTOM_RIGHT: Position = (VerticalAlign::Bottom, HorizontalAlign::Right);
  16. const CLIENT_MESSAGE: u8 = xcb::CLIENT_MESSAGE | 0x80;
  17. pub struct Tray<'a> {
  18. conn: &'a xcb::Connection,
  19. atoms: &'a atom::Atoms<'a>,
  20. screen: usize,
  21. icon_size: u16,
  22. position: Position,
  23. window: xcb::Window,
  24. children: Vec<xcb::Window>,
  25. timestamp: xcb::Timestamp,
  26. finishing: bool
  27. }
  28. impl<'a> Tray<'a> {
  29. pub fn new<'b>(
  30. conn: &'b xcb::Connection,
  31. atoms: &'b atom::Atoms,
  32. screen: usize,
  33. icon_size: u16,
  34. position: Position
  35. ) -> Tray<'b> {
  36. Tray::<'b> {
  37. conn: conn,
  38. atoms: atoms,
  39. screen: screen,
  40. icon_size: icon_size,
  41. position: position,
  42. window: conn.generate_id(),
  43. children: vec![],
  44. timestamp: 0,
  45. finishing: false
  46. }
  47. }
  48. pub fn create(&self) {
  49. let setup = self.conn.get_setup();
  50. let screen = setup.roots().nth(self.screen).unwrap();
  51. xcb::create_window(
  52. &self.conn,
  53. xcb::COPY_FROM_PARENT as u8,
  54. self.window,
  55. screen.root(),
  56. 0, 0,
  57. self.icon_size, self.icon_size,
  58. 0,
  59. xcb::WINDOW_CLASS_INPUT_OUTPUT as u16,
  60. screen.root_visual(),
  61. &[
  62. (xcb::CW_BACK_PIXEL, screen.black_pixel()),
  63. (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_PROPERTY_CHANGE)
  64. ]
  65. );
  66. self.set_property(
  67. self.atoms.get(atom::_NET_WM_WINDOW_TYPE),
  68. xcb::ATOM_ATOM,
  69. 32,
  70. &[self.atoms.get(atom::_NET_WM_WINDOW_TYPE_DOCK)]
  71. );
  72. self.set_property(
  73. xcb::ATOM_WM_NAME,
  74. xcb::ATOM_STRING,
  75. 8,
  76. ::PROGRAM.as_bytes()
  77. );
  78. self.set_property(
  79. xcb::ATOM_WM_CLASS,
  80. xcb::ATOM_STRING,
  81. 8,
  82. format!("{0}\0{0}", ::PROGRAM).as_bytes()
  83. );
  84. self.set_property(
  85. self.atoms.get(atom::_NET_SYSTEM_TRAY_ORIENTATION),
  86. xcb::ATOM_CARDINAL,
  87. 32,
  88. &[0 as u32] // 0 is horizontal, 1 is vertical
  89. );
  90. self.conn.flush();
  91. }
  92. pub fn set_property<T>(&self, name: xcb::Atom, type_: xcb::Atom, format: u8, data: &[T]) {
  93. xcb::change_property(
  94. self.conn,
  95. xcb::PROP_MODE_REPLACE as u8,
  96. self.window,
  97. name,
  98. type_,
  99. format,
  100. data
  101. );
  102. }
  103. pub fn is_selection_available(&self) -> bool {
  104. let selection = self.atoms.get(atom::_NET_SYSTEM_TRAY_S0);
  105. let owner = xcb::get_selection_owner(self.conn, selection).get_reply().unwrap().owner();
  106. owner == xcb::NONE
  107. }
  108. pub fn take_selection(&mut self, timestamp: xcb::Timestamp) -> bool {
  109. let selection = self.atoms.get(atom::_NET_SYSTEM_TRAY_S0);
  110. xcb::set_selection_owner(self.conn, self.window, selection, timestamp);
  111. let owner = xcb::get_selection_owner(self.conn, selection).get_reply().unwrap().owner();
  112. let ok = owner == self.window;
  113. if ok {
  114. self.timestamp = timestamp;
  115. let setup = self.conn.get_setup();
  116. let screen = setup.roots().nth(self.screen).unwrap();
  117. let client_event = xcb::ClientMessageEvent::new(
  118. 32, // 32 bits (refers to data)
  119. screen.root(),
  120. self.atoms.get(atom::MANAGER),
  121. xcb::ClientMessageData::from_data32([timestamp, selection, self.window, 0, 0])
  122. );
  123. xcb::send_event(self.conn, false, screen.root(), xcb::EVENT_MASK_STRUCTURE_NOTIFY, &client_event);
  124. self.conn.flush();
  125. }
  126. ok
  127. }
  128. pub fn adopt(&mut self, window: xcb::Window) {
  129. let offset = (self.children.len() as u16 * self.icon_size) as i16;
  130. xcb::change_window_attributes(self.conn, window, &[
  131. (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY)
  132. ]);
  133. xcb::reparent_window(self.conn, window, self.window, offset, 0);
  134. xcb::map_window(self.conn, window);
  135. self.force_size(window, None);
  136. self.conn.flush();
  137. self.children.push(window);
  138. self.reposition();
  139. }
  140. pub fn forget(&mut self, window: xcb::Window) {
  141. self.children.retain(|child| *child != window);
  142. self.reposition();
  143. }
  144. pub fn force_size(&self, window: xcb::Window, dimensions: Option<(u16, u16)>) {
  145. let dimensions = dimensions.unwrap_or_else(|| {
  146. let geometry = xcb::get_geometry(self.conn, window).get_reply().unwrap();
  147. (geometry.width(), geometry.height())
  148. });
  149. if dimensions != (self.icon_size, self.icon_size) {
  150. xcb::configure_window(self.conn, window, &[
  151. (xcb::CONFIG_WINDOW_WIDTH as u16, self.icon_size as u32),
  152. (xcb::CONFIG_WINDOW_HEIGHT as u16, self.icon_size as u32)
  153. ]);
  154. self.conn.flush();
  155. }
  156. }
  157. pub fn reposition(&self) {
  158. let width = self.children.len() as u16 * self.icon_size;
  159. if width > 0 {
  160. let setup = self.conn.get_setup();
  161. let screen = setup.roots().nth(self.screen).unwrap();
  162. let (ref valign, ref halign) = self.position;
  163. let y = match valign {
  164. &VerticalAlign::Top => 0,
  165. &VerticalAlign::Bottom => screen.height_in_pixels() - self.icon_size
  166. };
  167. let x = match halign {
  168. &HorizontalAlign::Left => 0,
  169. &HorizontalAlign::Right => screen.width_in_pixels() - width
  170. };
  171. xcb::configure_window(self.conn, self.window, &[
  172. (xcb::CONFIG_WINDOW_X as u16, x as u32),
  173. (xcb::CONFIG_WINDOW_Y as u16, y as u32),
  174. (xcb::CONFIG_WINDOW_WIDTH as u16, width as u32)
  175. ]);
  176. xcb::map_window(self.conn, self.window);
  177. }
  178. else {
  179. xcb::unmap_window(self.conn, self.window);
  180. }
  181. self.conn.flush();
  182. }
  183. pub fn finish(&mut self) {
  184. self.finishing = true;
  185. let setup = self.conn.get_setup();
  186. let screen = setup.roots().nth(self.screen).unwrap();
  187. let root = screen.root();
  188. for child in self.children.iter() {
  189. let window = *child;
  190. xcb::change_window_attributes(self.conn, window, &[
  191. (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_NO_EVENT)
  192. ]);
  193. xcb::unmap_window(self.conn, window);
  194. xcb::reparent_window(self.conn, window, root, 0, 0);
  195. }
  196. xcb::change_window_attributes(self.conn, self.window, &[
  197. (xcb::CW_EVENT_MASK, xcb::EVENT_MASK_STRUCTURE_NOTIFY)
  198. ]);
  199. xcb::destroy_window(self.conn, self.window);
  200. self.conn.flush();
  201. }
  202. pub fn handle_event(&mut self, event: xcb::GenericEvent) -> Option<i32> {
  203. if self.finishing {
  204. self.handle_event_finishing(event)
  205. }
  206. else {
  207. self.handle_event_normal(event)
  208. }
  209. }
  210. fn handle_event_normal(&mut self, event: xcb::GenericEvent) -> Option<i32> {
  211. match event.response_type() {
  212. xcb::PROPERTY_NOTIFY if self.timestamp == 0 => {
  213. let event: &xcb::PropertyNotifyEvent = xcb::cast_event(&event);
  214. if !self.take_selection(event.time()) {
  215. println!("Could not take ownership of tray selection. Maybe another tray is also running?");
  216. return Some(::EXIT_FAILED_SELECT)
  217. }
  218. },
  219. CLIENT_MESSAGE => {
  220. let event: &xcb::ClientMessageEvent = xcb::cast_event(&event);
  221. let data = event.data().data32();
  222. let window = data[2];
  223. self.adopt(window);
  224. },
  225. xcb::REPARENT_NOTIFY => {
  226. let event: &xcb::ReparentNotifyEvent = xcb::cast_event(&event);
  227. if event.parent() != self.window {
  228. self.forget(event.window());
  229. }
  230. },
  231. xcb::DESTROY_NOTIFY => {
  232. let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event);
  233. self.forget(event.window());
  234. },
  235. xcb::CONFIGURE_NOTIFY => {
  236. let event: &xcb::ConfigureNotifyEvent = xcb::cast_event(&event);
  237. self.force_size(event.window(), Some((event.width(), event.height())));
  238. },
  239. xcb::SELECTION_CLEAR => {
  240. self.finish();
  241. },
  242. _ => {}
  243. }
  244. None
  245. }
  246. fn handle_event_finishing(&mut self, event: xcb::GenericEvent) -> Option<i32> {
  247. if event.response_type() == xcb::DESTROY_NOTIFY {
  248. let event: &xcb::DestroyNotifyEvent = xcb::cast_event(&event);
  249. if event.window() == self.window {
  250. return Some(0)
  251. }
  252. }
  253. None
  254. }
  255. }