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