1 // This is (quite loosely) based on the original xeyes
2 
3 extern crate x11rb;
4 
5 use x11rb::connection::{Connection, RequestConnection as _};
6 use x11rb::errors::{ConnectionError, ReplyOrIdError};
7 use x11rb::protocol::shape::{self, ConnectionExt as _};
8 use x11rb::protocol::xproto::*;
9 use x11rb::protocol::Event;
10 use x11rb::wrapper::ConnectionExt as _;
11 use x11rb::COPY_DEPTH_FROM_PARENT;
12 
13 const PUPIL_SIZE: i16 = 50;
14 const EYE_SIZE: i16 = 50;
15 
16 // Draw the big background of the eyes
draw_eyes<C: Connection>( conn: &C, win_id: Window, black: Gcontext, white: Gcontext, window_size: (u16, u16), ) -> Result<(), ConnectionError>17 fn draw_eyes<C: Connection>(
18     conn: &C,
19     win_id: Window,
20     black: Gcontext,
21     white: Gcontext,
22     window_size: (u16, u16),
23 ) -> Result<(), ConnectionError> {
24     // Draw the black outlines
25     let mut arc1 = Arc {
26         x: 0,
27         y: 0,
28         width: window_size.0 / 2,
29         height: window_size.1,
30         angle1: 0,
31         angle2: 360 * 64,
32     };
33     let mut arc2 = arc1;
34     arc2.x = arc2.width as _;
35     conn.poly_fill_arc(win_id, black, &[arc1, arc2])?;
36 
37     // Draw the white inner part
38     for mut arc in [&mut arc1, &mut arc2].iter_mut() {
39         arc.x += EYE_SIZE;
40         arc.y += EYE_SIZE;
41         arc.width -= 2 * EYE_SIZE as u16;
42         arc.height -= 2 * EYE_SIZE as u16;
43     }
44     conn.poly_fill_arc(win_id, white, &[arc1, arc2])?;
45 
46     Ok(())
47 }
48 
49 // Draw the pupils inside the eye
draw_pupils<C: Connection>( conn: &C, win_id: Window, gc: Gcontext, ((x1, y1), (x2, y2)): ((i16, i16), (i16, i16)), ) -> Result<(), ConnectionError>50 fn draw_pupils<C: Connection>(
51     conn: &C,
52     win_id: Window,
53     gc: Gcontext,
54     ((x1, y1), (x2, y2)): ((i16, i16), (i16, i16)),
55 ) -> Result<(), ConnectionError> {
56     // Transform center to top left corner
57     let (x1, y1) = (x1 - PUPIL_SIZE / 2, y1 - PUPIL_SIZE / 2);
58     let (x2, y2) = (x2 - PUPIL_SIZE / 2, y2 - PUPIL_SIZE / 2);
59 
60     let arc1 = Arc {
61         x: x1,
62         y: y1,
63         width: PUPIL_SIZE as _,
64         height: PUPIL_SIZE as _,
65         angle1: 90 * 64,
66         angle2: 360 * 64,
67     };
68     let mut arc2 = arc1;
69     arc2.x = x2;
70     arc2.y = y2;
71 
72     // Do the drawing
73     conn.poly_fill_arc(win_id, gc, &[arc1, arc2])?;
74     Ok(())
75 }
76 
77 // Given two points, return their squared distance
distance_squared(p1: (f64, f64), p2: (f64, f64)) -> f6478 fn distance_squared(p1: (f64, f64), p2: (f64, f64)) -> f64 {
79     let dx = p1.0 - p2.0;
80     let dy = p1.1 - p2.1;
81     dx * dx + dy * dy
82 }
83 
84 // Compute the position of a pupil inside of the given area.
compute_pupil(area: (i16, i16, i16, i16), mouse: (i16, i16)) -> (i16, i16)85 fn compute_pupil(area: (i16, i16, i16, i16), mouse: (i16, i16)) -> (i16, i16) {
86     // What is the center of the eye?
87     let center_x = area.0 + area.2 / 2;
88     let center_y = area.1 + area.3 / 2;
89     let (w, h) = (f64::from(area.2) / 2.0, f64::from(area.3) / 2.0);
90 
91     // Is the mouse exactly on the center?
92     if (center_x, center_y) == mouse {
93         return mouse;
94     }
95 
96     let center = (f64::from(center_x), f64::from(center_y));
97     let mouse = (f64::from(mouse.0), f64::from(mouse.1));
98 
99     // Calculate the offset of the mouse position from the center
100     let diff = (mouse.0 - center.0, mouse.1 - center.1);
101 
102     // An eclipse is described by this equation, where the angle 'a' is varied over all values, but
103     // does not actually describe the angle from the center due to the different scaling in x and y
104     // direction.
105     //
106     //  x = w * cos(a)
107     //  y = h * sin(a)
108     //
109     // With tan(a) = sin(a)/cos(a), we get
110     //
111     //  tan(a) * x = w * sin(a) => sin(a) = tan(a) * x / w
112     //  y = h * sin(a)          => sin(a) = y / h
113     //
114     // and thus
115     //
116     //   tan(a) * x / w = y / h
117     //
118     // which we can rearrange to
119     //
120     //   tan(a) = (y * w) / (x * h)
121     //
122     // And thus, the angle we are looking for is:
123     //
124     //   a = arctan((y * w) / (x * h))
125     //
126     // However, due to tan() being the way it is, we actually need:
127     let angle = (diff.1 * w).atan2(diff.0 * h);
128 
129     // Now compute the corresponding point on the ellipse (relative to the center)
130     let (cx, cy) = (w * angle.cos(), h * angle.sin());
131 
132     // ...and also compute the actual point
133     let (x, y) = ((center.0 + cx) as _, (center.1 + cy) as _);
134 
135     // Return the point that is closer to the center
136     if distance_squared(center, mouse) < distance_squared(center, (x, y)) {
137         (mouse.0 as _, mouse.1 as _)
138     } else {
139         (x as _, y as _)
140     }
141 }
142 
143 // Compute the position of both pupils.
compute_pupils(window_size: (u16, u16), mouse_position: (i16, i16)) -> ((i16, i16), (i16, i16))144 fn compute_pupils(window_size: (u16, u16), mouse_position: (i16, i16)) -> ((i16, i16), (i16, i16)) {
145     let border = PUPIL_SIZE + EYE_SIZE;
146     let half_width = window_size.0 as i16 / 2;
147     let width = half_width - 2 * border;
148     let height = window_size.1 as i16 - 2 * border;
149 
150     (
151         compute_pupil((border, border, width, height), mouse_position),
152         compute_pupil((border + half_width, border, width, height), mouse_position),
153     )
154 }
155 
156 struct FreePixmap<'c, C: Connection>(&'c C, Pixmap);
157 impl<C: Connection> Drop for FreePixmap<'_, C> {
drop(&mut self)158     fn drop(&mut self) {
159         self.0.free_pixmap(self.1).unwrap();
160     }
161 }
162 struct FreeGC<'c, C: Connection>(&'c C, Gcontext);
163 impl<C: Connection> Drop for FreeGC<'_, C> {
drop(&mut self)164     fn drop(&mut self) {
165         self.0.free_gc(self.1).unwrap();
166     }
167 }
168 
create_pixmap_wrapper<C: Connection>( conn: &C, depth: u8, drawable: Drawable, size: (u16, u16), ) -> Result<FreePixmap<C>, ReplyOrIdError>169 fn create_pixmap_wrapper<C: Connection>(
170     conn: &C,
171     depth: u8,
172     drawable: Drawable,
173     size: (u16, u16),
174 ) -> Result<FreePixmap<C>, ReplyOrIdError> {
175     let pixmap = conn.generate_id()?;
176     conn.create_pixmap(depth, pixmap, drawable, size.0, size.1)?;
177     Ok(FreePixmap(conn, pixmap))
178 }
179 
shape_window<C: Connection>( conn: &C, win_id: Window, window_size: (u16, u16), ) -> Result<(), ReplyOrIdError>180 fn shape_window<C: Connection>(
181     conn: &C,
182     win_id: Window,
183     window_size: (u16, u16),
184 ) -> Result<(), ReplyOrIdError> {
185     // Create a pixmap for the shape
186     let pixmap = create_pixmap_wrapper(conn, 1, win_id, window_size)?;
187 
188     // Fill the pixmap with what will indicate "transparent"
189     let gc = create_gc_with_foreground(conn, pixmap.1, 0)?;
190     let _free_gc = FreeGC(conn, gc);
191 
192     let rect = Rectangle {
193         x: 0,
194         y: 0,
195         width: window_size.0,
196         height: window_size.1,
197     };
198     conn.poly_fill_rectangle(pixmap.1, gc, &[rect])?;
199 
200     // Draw the eyes as "not transparent"
201     let values = ChangeGCAux::new().foreground(1);
202     conn.change_gc(gc, &values)?;
203     draw_eyes(conn, pixmap.1, gc, gc, window_size)?;
204 
205     // Set the shape of the window
206     conn.shape_mask(shape::SO::SET, shape::SK::BOUNDING, win_id, 0, 0, pixmap.1)?;
207     Ok(())
208 }
209 
setup_window<C: Connection>( conn: &C, screen: &Screen, window_size: (u16, u16), wm_protocols: Atom, wm_delete_window: Atom, ) -> Result<Window, ReplyOrIdError>210 fn setup_window<C: Connection>(
211     conn: &C,
212     screen: &Screen,
213     window_size: (u16, u16),
214     wm_protocols: Atom,
215     wm_delete_window: Atom,
216 ) -> Result<Window, ReplyOrIdError> {
217     let win_id = conn.generate_id()?;
218     let win_aux = CreateWindowAux::new()
219         .event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY | EventMask::POINTER_MOTION)
220         .background_pixel(screen.white_pixel);
221 
222     conn.create_window(
223         COPY_DEPTH_FROM_PARENT,
224         win_id,
225         screen.root,
226         0,
227         0,
228         window_size.0,
229         window_size.1,
230         0,
231         WindowClass::INPUT_OUTPUT,
232         0,
233         &win_aux,
234     )?;
235 
236     let title = "xeyes";
237     conn.change_property8(
238         PropMode::REPLACE,
239         win_id,
240         AtomEnum::WM_NAME,
241         AtomEnum::STRING,
242         title.as_bytes(),
243     )
244     .unwrap();
245     conn.change_property32(
246         PropMode::REPLACE,
247         win_id,
248         wm_protocols,
249         AtomEnum::ATOM,
250         &[wm_delete_window],
251     )
252     .unwrap();
253 
254     conn.map_window(win_id).unwrap();
255 
256     Ok(win_id)
257 }
258 
create_gc_with_foreground<C: Connection>( conn: &C, win_id: Window, foreground: u32, ) -> Result<Gcontext, ReplyOrIdError>259 fn create_gc_with_foreground<C: Connection>(
260     conn: &C,
261     win_id: Window,
262     foreground: u32,
263 ) -> Result<Gcontext, ReplyOrIdError> {
264     let gc = conn.generate_id()?;
265     let gc_aux = CreateGCAux::new()
266         .graphics_exposures(0)
267         .foreground(foreground);
268     conn.create_gc(gc, win_id, &gc_aux)?;
269     Ok(gc)
270 }
271 
main()272 fn main() {
273     let (conn, screen_num) = x11rb::connect(None).expect("Failed to connect to the X11 server");
274 
275     // The following is only needed for start_timeout_thread(), which is used for 'tests'
276     let conn1 = std::sync::Arc::new(conn);
277     let conn = &*conn1;
278 
279     let screen = &conn.setup().roots[screen_num];
280 
281     let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS").unwrap();
282     let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW").unwrap();
283 
284     let mut window_size = (700, 500);
285     let has_shape = conn
286         .extension_information(shape::X11_EXTENSION_NAME)
287         .expect("failed to get extension information")
288         .is_some();
289     let (wm_protocols, wm_delete_window) = (
290         wm_protocols.reply().unwrap().atom,
291         wm_delete_window.reply().unwrap().atom,
292     );
293     let win_id = setup_window(conn, screen, window_size, wm_protocols, wm_delete_window).unwrap();
294     let mut pixmap = create_pixmap_wrapper(conn, screen.root_depth, win_id, window_size).unwrap();
295 
296     let black_gc = create_gc_with_foreground(conn, win_id, screen.black_pixel).unwrap();
297     let white_gc = create_gc_with_foreground(conn, win_id, screen.white_pixel).unwrap();
298 
299     conn.flush().unwrap();
300 
301     let mut need_repaint = false;
302     let mut need_reshape = false;
303     let mut mouse_position = (0, 0);
304 
305     util::start_timeout_thread(conn1.clone(), win_id);
306 
307     loop {
308         let event = conn.wait_for_event().unwrap();
309         let mut event_option = Some(event);
310         while let Some(event) = event_option {
311             match event {
312                 Event::Expose(event) => {
313                     if event.count == 0 {
314                         need_repaint = true;
315                     }
316                 }
317                 Event::ConfigureNotify(event) => {
318                     window_size = (event.width, event.height);
319                     pixmap = create_pixmap_wrapper(conn, screen.root_depth, win_id, window_size)
320                         .unwrap();
321                     need_reshape = true;
322                 }
323                 Event::MotionNotify(event) => {
324                     mouse_position = (event.event_x, event.event_y);
325                     need_repaint = true;
326                 }
327                 Event::MapNotify(_) => {
328                     need_reshape = true;
329                 }
330                 Event::ClientMessage(event) => {
331                     let data = event.data.as_data32();
332                     if event.format == 32 && event.window == win_id && data[0] == wm_delete_window {
333                         println!("Window was asked to close");
334                         return;
335                     }
336                 }
337                 Event::Error(error) => {
338                     println!("Unknown error {:?}", error);
339                 }
340                 event => {
341                     println!("Unknown event {:?}", event);
342                 }
343             }
344 
345             event_option = conn.poll_for_event().unwrap();
346         }
347 
348         if need_reshape && has_shape {
349             shape_window(conn, win_id, window_size).unwrap();
350             need_reshape = false;
351         }
352         if need_repaint {
353             // Draw new pupils
354             let pos = compute_pupils(window_size, mouse_position);
355             draw_eyes(conn, pixmap.1, black_gc, white_gc, window_size).unwrap();
356             draw_pupils(conn, pixmap.1, black_gc, pos).unwrap();
357 
358             // Copy drawing from pixmap to window
359             conn.copy_area(
360                 pixmap.1,
361                 win_id,
362                 white_gc,
363                 0,
364                 0,
365                 0,
366                 0,
367                 window_size.0,
368                 window_size.1,
369             )
370             .unwrap();
371 
372             conn.flush().unwrap();
373             need_repaint = false;
374         }
375     }
376 }
377 
378 include!("integration_test_util/util.rs");
379