1use std::env::{var, var_os};
11use std::ffi::OsStr;
12use std::fs::File;
13use std::io::{BufRead, BufReader, Error as IOError};
14use std::path::{Path, PathBuf};
15
16static CORE_CURSORS: &[(&str, u16)] = &[
17 ("X_cursor", 0),
18 ("arrow", 1),
19 ("based_arrow_down", 2),
20 ("based_arrow_up", 3),
21 ("boat", 4),
22 ("bogosity", 5),
23 ("bottom_left_corner", 6),
24 ("bottom_right_corner", 7),
25 ("bottom_side", 8),
26 ("bottom_tee", 9),
27 ("box_spiral", 10),
28 ("center_ptr", 11),
29 ("circle", 12),
30 ("clock", 13),
31 ("coffee_mug", 14),
32 ("cross", 15),
33 ("cross_reverse", 16),
34 ("crosshair", 17),
35 ("diamond_cross", 18),
36 ("dot", 19),
37 ("dotbox", 20),
38 ("double_arrow", 21),
39 ("draft_large", 22),
40 ("draft_small", 23),
41 ("draped_box", 24),
42 ("exchange", 25),
43 ("fleur", 26),
44 ("gobbler", 27),
45 ("gumby", 28),
46 ("hand1", 29),
47 ("hand2", 30),
48 ("heart", 31),
49 ("icon", 32),
50 ("iron_cross", 33),
51 ("left_ptr", 34),
52 ("left_side", 35),
53 ("left_tee", 36),
54 ("leftbutton", 37),
55 ("ll_angle", 38),
56 ("lr_angle", 39),
57 ("man", 40),
58 ("middlebutton", 41),
59 ("mouse", 42),
60 ("pencil", 43),
61 ("pirate", 44),
62 ("plus", 45),
63 ("question_arrow", 46),
64 ("right_ptr", 47),
65 ("right_side", 48),
66 ("right_tee", 49),
67 ("rightbutton", 50),
68 ("rtl_logo", 51),
69 ("sailboat", 52),
70 ("sb_down_arrow", 53),
71 ("sb_h_double_arrow", 54),
72 ("sb_left_arrow", 55),
73 ("sb_right_arrow", 56),
74 ("sb_up_arrow", 57),
75 ("sb_v_double_arrow", 58),
76 ("shuttle", 59),
77 ("sizing", 60),
78 ("spider", 61),
79 ("spraycan", 62),
80 ("star", 63),
81 ("target", 64),
82 ("tcross", 65),
83 ("top_left_arrow", 66),
84 ("top_left_corner", 67),
85 ("top_right_corner", 68),
86 ("top_side", 69),
87 ("top_tee", 70),
88 ("trek", 71),
89 ("ul_angle", 72),
90 ("umbrella", 73),
91 ("ur_angle", 74),
92 ("watch", 75),
93 ("xterm", 76),
94];
95
96fn cursor_shape_to_id(name: &str) -> Option<u16> {
100 CORE_CURSORS
101 .iter()
102 .filter(|&(name2, _)| name == *name2)
103 .map(|&(_, id)| id)
104 .next()
105}
106
107#[derive(Debug)]
109pub(crate) enum Error {
110 NoHomeDir,
112
113 NothingFound,
115}
116
117#[derive(Debug)]
119pub(crate) enum Cursor<F> {
120 CoreChar(u16),
122
123 File(F),
125}
126
127fn parse_inherits(filename: &Path) -> Result<Vec<String>, IOError> {
129 let file = File::open(filename)?;
130 parse_inherits_impl(&mut BufReader::new(file))
131}
132
133fn parse_inherits_impl(input: &mut impl BufRead) -> Result<Vec<String>, IOError> {
135 let mut buffer = Vec::new();
136 loop {
137 buffer.clear();
138
139 if 0 == input.read_until(b'\n', &mut buffer)? {
141 return Ok(Default::default());
143 }
144
145 if buffer.last() == Some(&b'\n') {
147 let _ = buffer.pop();
148 }
149
150 let begin = b"Inherits";
151 if buffer.starts_with(begin) {
152 let mut result = Vec::new();
153
154 let mut to_parse = &buffer[begin.len()..];
155
156 fn skip_while(mut slice: &[u8], f: impl Fn(u8) -> bool) -> &[u8] {
157 while !slice.is_empty() && f(slice[0]) {
158 slice = &slice[1..]
159 }
160 slice
161 }
162
163 to_parse = skip_while(to_parse, |c| c == b' ');
165
166 if to_parse.first() == Some(&b'=') {
168 to_parse = &to_parse[1..];
169
170 fn should_skip(c: u8) -> bool {
171 matches!(c, b' ' | b'\t' | b'\n' | b';' | b',')
172 }
173
174 for mut part in to_parse.split(|&x| x == b':') {
176 part = skip_while(part, should_skip);
178
179 while let Some((&last, rest)) = part.split_last() {
181 if !should_skip(last) {
182 break;
183 }
184 part = rest;
185 }
186 if !part.is_empty() {
187 if let Ok(part) = std::str::from_utf8(part) {
188 result.push(part.to_string());
189 }
190 }
191 }
192 }
193 return Ok(result);
194 }
195 }
196}
197
198#[cfg(test)]
199mod test_parse_inherits {
200 use super::parse_inherits_impl;
201 use std::io::Cursor;
202
203 #[test]
204 fn parse_inherits_successful() {
205 let input =
206 b"Hi\nInherits = \t ; whatever ;,::; stuff : i s ,: \tthis \t \nInherits=ignored\n";
207 let mut input = Cursor::new(&input[..]);
208 let result = parse_inherits_impl(&mut input).unwrap();
209 assert_eq!(result, vec!["whatever", "stuff", "i s", "this"]);
210 }
211}
212
213pub(crate) fn find_cursor(theme: &str, name: &str) -> Result<Cursor<File>, Error> {
215 let home = match var_os("HOME") {
216 Some(home) => home,
217 None => return Err(Error::NoHomeDir),
218 };
219 let cursor_path = var("XCURSOR_PATH").unwrap_or_else(|_| {
220 "~/.icons:/usr/share/icons:/usr/share/pixmaps:/usr/X11R6/lib/X11/icons".into()
221 });
222 let open_cursor = |file: &Path| File::open(file);
223 find_cursor_impl(
224 &home,
225 &cursor_path,
226 theme,
227 name,
228 open_cursor,
229 parse_inherits,
230 )
231}
232
233fn find_cursor_impl<F, G, H>(
234 home: &OsStr,
235 cursor_path: &str,
236 theme: &str,
237 name: &str,
238 mut open_cursor: G,
239 mut parse_inherits: H,
240) -> Result<Cursor<F>, Error>
241where
242 G: FnMut(&Path) -> Result<F, IOError>,
243 H: FnMut(&Path) -> Result<Vec<String>, IOError>,
244{
245 if theme == "core" {
246 if let Some(id) = cursor_shape_to_id(name) {
247 return Ok(Cursor::CoreChar(id));
248 }
249 }
250
251 let cursor_path = cursor_path.split(':').collect::<Vec<_>>();
252
253 let mut next_inherits = Vec::new();
254 let mut last_inherits = vec![theme.into()];
255 while !last_inherits.is_empty() {
256 for theme in last_inherits {
257 for path in &cursor_path {
258 let mut theme_dir = PathBuf::new();
260 if let Some(mut path) = path.strip_prefix('~') {
262 theme_dir.push(home);
263 if path.chars().next().map(std::path::is_separator) == Some(true) {
265 path = &path[1..];
266 }
267 theme_dir.push(path);
268 } else {
269 theme_dir.push(path);
270 }
271 theme_dir.push(&theme);
272
273 let mut cursor_file = theme_dir.clone();
275 cursor_file.push("cursors");
276 cursor_file.push(name);
277 if let Ok(file) = open_cursor(&cursor_file) {
278 return Ok(Cursor::File(file));
279 }
280
281 let mut index = theme_dir;
283 index.push("index.theme");
284 if let Ok(res) = parse_inherits(&index) {
285 next_inherits.extend(res);
286 }
287 }
288 }
289
290 last_inherits = next_inherits;
291 next_inherits = Vec::new();
292 }
293
294 Err(Error::NothingFound)
295}
296
297#[cfg(all(test, unix))]
299mod test_find_cursor {
300 use super::{find_cursor_impl, Cursor, Error};
301 use crate::errors::ConnectionError;
302 use std::io::{Error as IOError, ErrorKind};
303 use std::path::Path;
304
305 #[test]
306 fn core_cursor() {
307 let cb1 = |_: &Path| -> Result<(), _> { unimplemented!() };
308 let cb2 = |_: &Path| unimplemented!();
309 match find_cursor_impl("unused".as_ref(), "unused", "core", "heart", cb1, cb2).unwrap() {
310 Cursor::CoreChar(31) => {}
311 e => panic!("Unexpected result {:?}", e),
312 }
313 }
314
315 #[test]
316 fn nothing_found() {
317 let mut opened = Vec::new();
318 let mut inherit_parsed = Vec::new();
319 let cb1 = |path: &Path| -> Result<(), _> {
320 opened.push(path.to_str().unwrap().to_owned());
321 Err(IOError::new(
322 ErrorKind::Other,
323 ConnectionError::UnknownError,
324 ))
325 };
326 let cb2 = |path: &Path| {
327 inherit_parsed.push(path.to_str().unwrap().to_owned());
328 Ok(Vec::new())
329 };
330 match find_cursor_impl(
331 "home".as_ref(),
332 "path:~/some/:/entries",
333 "theme",
334 "theCursor",
335 cb1,
336 cb2,
337 ) {
338 Err(Error::NothingFound) => {}
339 e => panic!("Unexpected result {:?}", e),
340 }
341 assert_eq!(
342 opened,
343 &[
344 "path/theme/cursors/theCursor",
345 "home/some/theme/cursors/theCursor",
346 "/entries/theme/cursors/theCursor",
347 ]
348 );
349 assert_eq!(
350 inherit_parsed,
351 &[
352 "path/theme/index.theme",
353 "home/some/theme/index.theme",
354 "/entries/theme/index.theme",
355 ]
356 );
357 }
358
359 #[test]
360 fn inherit() {
361 let mut opened = Vec::new();
362 let cb1 = |path: &Path| -> Result<(), _> {
363 opened.push(path.to_str().unwrap().to_owned());
364 Err(IOError::new(
365 ErrorKind::Other,
366 ConnectionError::UnknownError,
367 ))
368 };
369 let cb2 = |path: &Path| {
370 if path.starts_with("base/theTheme") {
371 Ok(vec!["inherited".into()])
372 } else if path.starts_with("path/inherited") {
373 Ok(vec!["theEnd".into()])
374 } else {
375 Ok(vec![])
376 }
377 };
378 match find_cursor_impl(
379 "home".as_ref(),
380 "path:base:tail",
381 "theTheme",
382 "theCursor",
383 cb1,
384 cb2,
385 ) {
386 Err(Error::NothingFound) => {}
387 e => panic!("Unexpected result {:?}", e),
388 }
389 assert_eq!(
390 opened,
391 &[
392 "path/theTheme/cursors/theCursor",
393 "base/theTheme/cursors/theCursor",
394 "tail/theTheme/cursors/theCursor",
395 "path/inherited/cursors/theCursor",
396 "base/inherited/cursors/theCursor",
397 "tail/inherited/cursors/theCursor",
398 "path/theEnd/cursors/theCursor",
399 "base/theEnd/cursors/theCursor",
400 "tail/theEnd/cursors/theCursor",
401 ]
402 );
403 }
404}