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