tray.rs 10 KB

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