1 use anyhow::{bail, Context, Result};
2 use itertools::Itertools;
3 use log::debug;
4 use regex::Regex;
5 use std::ffi::CStr;
6 use std::iter;
7 use std::thread::sleep;
8 use std::time::{Duration, Instant};
9 use xcb::ffi::xcb_visualid_t;
10 
11 use crate::args::AppConfig;
12 use crate::{DesktopWindow, RenderWindow};
13 
14 /// Given a list of `current_hints` and a bunch of `hint_chars`, this finds a unique combination
15 /// of characters that doesn't yet exist in `current_hints`. `max_count` is the maximum possible
16 /// number of hints we need.
get_next_hint( current_hints: Vec<&String>, hint_chars: &str, max_count: usize, ) -> Result<String>17 pub fn get_next_hint(
18     current_hints: Vec<&String>,
19     hint_chars: &str,
20     max_count: usize,
21 ) -> Result<String> {
22     // Figure out which size we need.
23     let mut size_required = 1;
24     while hint_chars.len().pow(size_required) < max_count {
25         size_required += 1;
26     }
27     let mut ret = hint_chars
28         .chars()
29         .next()
30         .context("No hint_chars found")?
31         .to_string();
32     let it = iter::repeat(hint_chars.chars().rev())
33         .take(size_required as usize)
34         .multi_cartesian_product();
35     for c in it {
36         let folded = c.into_iter().collect();
37         if !current_hints.contains(&&folded) {
38             ret = folded;
39         }
40     }
41     debug!("Returning next hint: {}", ret);
42     Ok(ret)
43 }
44 
find_visual(conn: &xcb::Connection, visual: xcb_visualid_t) -> Option<xcb::Visualtype>45 pub fn find_visual(conn: &xcb::Connection, visual: xcb_visualid_t) -> Option<xcb::Visualtype> {
46     for screen in conn.get_setup().roots() {
47         for depth in screen.allowed_depths() {
48             for vis in depth.visuals() {
49                 if visual == vis.visual_id() {
50                     return Some(vis);
51                 }
52             }
53         }
54     }
55     None
56 }
57 
extents_for_text(text: &str, family: &str, size: f64) -> Result<cairo::TextExtents>58 pub fn extents_for_text(text: &str, family: &str, size: f64) -> Result<cairo::TextExtents> {
59     // Create a buffer image that should be large enough.
60     // TODO: Figure out the maximum size from the largest window on the desktop.
61     // For now we'll use made-up maximum values.
62     let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, 1024, 1024)
63         .context("Couldn't create ImageSurface")?;
64     let cr = cairo::Context::new(&surface).context("Couldn't create Cairo Surface")?;
65     cr.select_font_face(family, cairo::FontSlant::Normal, cairo::FontWeight::Normal);
66     cr.set_font_size(size);
67     cr.text_extents(text).context("Couldn't create TextExtents")
68 }
69 
70 /// Draw a `text` onto `rw`. In case any `current_hints` are already typed, it will draw those in a
71 /// different color to show that they were in fact typed.
draw_hint_text( rw: &RenderWindow, app_config: &AppConfig, text: &str, current_hints: &str, ) -> Result<()>72 pub fn draw_hint_text(
73     rw: &RenderWindow,
74     app_config: &AppConfig,
75     text: &str,
76     current_hints: &str,
77 ) -> Result<()> {
78     // Paint background.
79     rw.cairo_context.set_operator(cairo::Operator::Source);
80     rw.cairo_context.set_source_rgb(
81         app_config.bg_color.0,
82         app_config.bg_color.1,
83         app_config.bg_color.2,
84     );
85     rw.cairo_context.paint().context("Error trying to draw")?;
86     rw.cairo_context.set_operator(cairo::Operator::Over);
87 
88     rw.cairo_context.select_font_face(
89         &app_config.font.font_family,
90         cairo::FontSlant::Normal,
91         cairo::FontWeight::Normal,
92     );
93     rw.cairo_context.set_font_size(app_config.font.font_size);
94     rw.cairo_context.move_to(rw.draw_pos.0, rw.draw_pos.1);
95     if text.starts_with(current_hints) {
96         // Paint already selected chars.
97         rw.cairo_context.set_source_rgba(
98             app_config.text_color_alt.0,
99             app_config.text_color_alt.1,
100             app_config.text_color_alt.2,
101             app_config.text_color_alt.3,
102         );
103         for c in current_hints.chars() {
104             rw.cairo_context
105                 .show_text(&c.to_string())
106                 .context("Couldn't display text")?;
107         }
108     }
109 
110     // Paint unselected chars.
111     rw.cairo_context.set_source_rgba(
112         app_config.text_color.0,
113         app_config.text_color.1,
114         app_config.text_color.2,
115         app_config.text_color.3,
116     );
117     let re = Regex::new(&format!("^{}", current_hints)).unwrap();
118     for c in re.replace(text, "").chars() {
119         rw.cairo_context
120             .show_text(&c.to_string())
121             .context("Couldn't show text")?;
122     }
123     rw.cairo_context.target().flush();
124 
125     Ok(())
126 }
127 
128 /// Try to grab the keyboard until `timeout` is reached.
129 ///
130 /// Generally with X, I found that you can't grab global keyboard input without it failing
131 /// sometimes due to other clients grabbing it occasionally. Hence, we'll have to keep retrying
132 /// until we eventually succeed.
snatch_keyboard( conn: &xcb::Connection, screen: &xcb::Screen, timeout: Duration, ) -> Result<()>133 pub fn snatch_keyboard(
134     conn: &xcb::Connection,
135     screen: &xcb::Screen,
136     timeout: Duration,
137 ) -> Result<()> {
138     let now = Instant::now();
139     loop {
140         if now.elapsed() > timeout {
141             bail!("Couldn't grab keyboard input within {:?}", now.elapsed());
142         }
143         let grab_keyboard_cookie = xcb::xproto::grab_keyboard(
144             conn,
145             true,
146             screen.root(),
147             xcb::CURRENT_TIME,
148             xcb::GRAB_MODE_ASYNC as u8,
149             xcb::GRAB_MODE_ASYNC as u8,
150         );
151         let grab_keyboard_reply = grab_keyboard_cookie
152             .get_reply()
153             .context("Couldn't communicate with X")?;
154         if grab_keyboard_reply.status() == xcb::GRAB_STATUS_SUCCESS as u8 {
155             return Ok(());
156         }
157         sleep(Duration::from_millis(1));
158     }
159 }
160 
161 /// Try to grab the mouse until `timeout` is reached.
162 ///
163 /// Generally with X, I found that you can't grab global mouse input without it failing sometimes
164 /// due to other clients grabbing it occasionally. Hence, we'll have to keep retrying until we
165 /// eventually succeed.
snatch_mouse(conn: &xcb::Connection, screen: &xcb::Screen, timeout: Duration) -> Result<()>166 pub fn snatch_mouse(conn: &xcb::Connection, screen: &xcb::Screen, timeout: Duration) -> Result<()> {
167     let now = Instant::now();
168     loop {
169         if now.elapsed() > timeout {
170             bail!("Couldn't grab keyboard input within {:?}", now.elapsed());
171         }
172         let grab_pointer_cookie = xcb::xproto::grab_pointer(
173             conn,
174             true,
175             screen.root(),
176             xcb::EVENT_MASK_BUTTON_PRESS as u16,
177             xcb::GRAB_MODE_ASYNC as u8,
178             xcb::GRAB_MODE_ASYNC as u8,
179             xcb::NONE,
180             xcb::NONE,
181             xcb::CURRENT_TIME,
182         );
183         let grab_pointer_reply = grab_pointer_cookie
184             .get_reply()
185             .context("Couldn't communicate with X")?;
186         if grab_pointer_reply.status() == xcb::GRAB_STATUS_SUCCESS as u8 {
187             return Ok(());
188         }
189         sleep(Duration::from_millis(1));
190     }
191 }
192 
193 /// Sort list of `DesktopWindow`s by position.
194 ///
195 /// This sorts by column first and row second.
sort_by_pos(mut dws: Vec<DesktopWindow>) -> Vec<DesktopWindow>196 pub fn sort_by_pos(mut dws: Vec<DesktopWindow>) -> Vec<DesktopWindow> {
197     dws.sort_by_key(|w| w.pos.0);
198     dws.sort_by_key(|w| w.pos.1);
199     dws
200 }
201 
202 /// Returns true if `r1` and `r2` overlap.
intersects(r1: (i32, i32, i32, i32), r2: (i32, i32, i32, i32)) -> bool203 fn intersects(r1: (i32, i32, i32, i32), r2: (i32, i32, i32, i32)) -> bool {
204     let left_corner_inside = r1.0 < r2.0 + r2.2;
205     let right_corner_inside = r1.0 + r1.2 > r2.0;
206     let top_corner_inside = r1.1 < r2.1 + r2.3;
207     let bottom_corner_inside = r1.1 + r1.3 > r2.1;
208     left_corner_inside && right_corner_inside && top_corner_inside && bottom_corner_inside
209 }
210 
211 /// Finds overlaps and returns a list of those rects in the format (x, y, w, h).
find_overlaps( rws: Vec<&RenderWindow>, rect: (i32, i32, i32, i32), ) -> Vec<(i32, i32, i32, i32)>212 pub fn find_overlaps(
213     rws: Vec<&RenderWindow>,
214     rect: (i32, i32, i32, i32),
215 ) -> Vec<(i32, i32, i32, i32)> {
216     let mut overlaps = vec![];
217     for rw in rws {
218         if intersects(rw.rect, rect) {
219             overlaps.push(rw.rect);
220         }
221     }
222     overlaps
223 }
224 
225 /// Remove last pressed key from pressed keys
remove_last_key(pressed_keys: &mut String, kstr: &str)226 pub fn remove_last_key(pressed_keys: &mut String, kstr: &str) {
227     if pressed_keys.contains(kstr) {
228         pressed_keys.replace_range(pressed_keys.len() - kstr.len().., "");
229     }
230 }
231 
get_pressed_symbol(conn: &xcb::Connection, event: &xcb::base::GenericEvent) -> u32232 pub fn get_pressed_symbol(conn: &xcb::Connection, event: &xcb::base::GenericEvent) -> u32 {
233     let key_press: &xcb::KeyPressEvent = unsafe { xcb::cast_event(event) };
234     let syms = xcb_util::keysyms::KeySymbols::new(conn);
235     syms.press_lookup_keysym(key_press, 0)
236 }
237 
convert_to_string<'a>(symbol: u32) -> Result<&'a str>238 pub fn convert_to_string<'a>(symbol: u32) -> Result<&'a str> {
239     unsafe {
240         CStr::from_ptr(x11::xlib::XKeysymToString(symbol.into()))
241             .to_str()
242             .context("Couldn't create Rust string from C string")
243     }
244 }
245 
246 /// Struct helps to write sequence and check if it is found in list of exit sequences
247 #[derive(Debug, PartialEq)]
248 pub struct Sequence {
249     sequence: Vec<String>,
250 }
251 
252 impl Sequence {
new(string: Option<&str>) -> Sequence253     pub fn new(string: Option<&str>) -> Sequence {
254         match string {
255             Some(string) => {
256                 let mut vec: Vec<String> = Sequence::explode(string, "+");
257 
258                 Sequence::sort(&mut vec);
259 
260                 Sequence { sequence: vec }
261             }
262             None => Sequence {
263                 sequence: Vec::new(),
264             },
265         }
266     }
267 
explode(string: &str, separator: &str) -> Vec<String>268     fn explode(string: &str, separator: &str) -> Vec<String> {
269         string.split(separator).map(|s| s.to_string()).collect()
270     }
271 
272     /// Sort vector alphabetically
sort(vec: &mut Vec<String>)273     fn sort(vec: &mut Vec<String>) {
274         vec.sort_by(|a, b| a.to_lowercase().cmp(&b.to_lowercase()));
275     }
276 
remove(&mut self, key: &str)277     pub fn remove(&mut self, key: &str) {
278         self.sequence.retain(|x| x != key);
279     }
280 
push(&mut self, key: String)281     pub fn push(&mut self, key: String) {
282         self.sequence.push(key);
283         Sequence::sort(&mut self.sequence);
284     }
285 
286     /// Sequence is started if more than one key is pressed
is_started(&self) -> bool287     pub fn is_started(&self) -> bool {
288         self.sequence.len() > 1
289     }
290 }
291 
292 #[cfg(test)]
293 mod tests {
294     use super::*;
295 
296     #[test]
test_intersects()297     fn test_intersects() {
298         assert!(intersects((1905, 705, 31, 82), (1905, 723, 38, 64)));
299     }
300 
301     #[test]
test_no_intersect()302     fn test_no_intersect() {
303         assert!(!intersects((1905, 705, 31, 82), (2000, 723, 38, 64)));
304     }
305 
306     #[test]
test_sequences_equal()307     fn test_sequences_equal() {
308         let a = Sequence::new(Some("Control_L+Shift_L+a"));
309         let b = Sequence::new(Some("Control_L+a+Shift_L"));
310 
311         assert_eq!(a, b);
312 
313         let mut c = Sequence::new(None);
314 
315         c.push("Shift_L".to_owned());
316         c.push("Control_L".to_owned());
317         c.push("a".to_owned());
318 
319         assert_eq!(a, c);
320     }
321 
322     #[test]
test_sequences_not_equal()323     fn test_sequences_not_equal() {
324         let a = Sequence::new(Some("Control_L+Shift_L+a"));
325         let b = Sequence::new(Some("Control_L+a"));
326 
327         assert_ne!(a, b);
328 
329         let mut c = Sequence::new(None);
330 
331         c.push("Shift_L".to_owned());
332         c.push("a".to_owned());
333 
334         assert_ne!(a, c);
335     }
336 
337     #[test]
test_sequences_is_started()338     fn test_sequences_is_started() {
339         let mut sequence = Sequence::new(None);
340         assert!(!sequence.is_started());
341 
342         sequence.push("Control_L".to_owned());
343         assert!(!sequence.is_started());
344 
345         sequence.push("g".to_owned());
346         assert!(sequence.is_started());
347 
348         sequence.remove("g");
349 
350         assert!(!sequence.is_started());
351     }
352 }
353