Skip to content

Commit 45465c5

Browse files
authored
X11: Split off the event loop into a separate module (#183)
This PR splits off the X11 event loop logic into a separate module. It also changes the X11 implementation of the `Window` type to take only a shared reference to the inner type (`&WindowInner` instead of `&mut WindowInner`), bringing it in line with the other backends. This does not change any of the logic however, it only separates some of the window state from the event loop state, to make sure they don't step on each other's toes in the future (particularly around the WindowHandler). This is part of the effort to split up #174 into smaller pieces.
1 parent be3d72d commit 45465c5

File tree

5 files changed

+332
-307
lines changed

5 files changed

+332
-307
lines changed

src/x11/event_loop.rs

Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
use crate::x11::keyboard::{convert_key_press_event, convert_key_release_event, key_mods};
2+
use crate::x11::{ParentHandle, Window, WindowInner};
3+
use crate::{
4+
Event, MouseButton, MouseEvent, PhyPoint, PhySize, ScrollDelta, WindowEvent, WindowHandler,
5+
WindowInfo,
6+
};
7+
use std::error::Error;
8+
use std::os::fd::AsRawFd;
9+
use std::time::{Duration, Instant};
10+
use x11rb::connection::Connection;
11+
use x11rb::protocol::Event as XEvent;
12+
13+
pub(super) struct EventLoop {
14+
handler: Box<dyn WindowHandler>,
15+
window: WindowInner,
16+
parent_handle: Option<ParentHandle>,
17+
18+
new_physical_size: Option<PhySize>,
19+
frame_interval: Duration,
20+
event_loop_running: bool,
21+
}
22+
23+
impl EventLoop {
24+
pub fn new(
25+
window: WindowInner, handler: impl WindowHandler + 'static,
26+
parent_handle: Option<ParentHandle>,
27+
) -> Self {
28+
Self {
29+
window,
30+
handler: Box::new(handler),
31+
parent_handle,
32+
frame_interval: Duration::from_millis(15),
33+
event_loop_running: false,
34+
new_physical_size: None,
35+
}
36+
}
37+
38+
#[inline]
39+
fn drain_xcb_events(&mut self) -> Result<(), Box<dyn Error>> {
40+
// the X server has a tendency to send spurious/extraneous configure notify events when a
41+
// window is resized, and we need to batch those together and just send one resize event
42+
// when they've all been coalesced.
43+
self.new_physical_size = None;
44+
45+
while let Some(event) = self.window.xcb_connection.conn.poll_for_event()? {
46+
self.handle_xcb_event(event);
47+
}
48+
49+
if let Some(size) = self.new_physical_size.take() {
50+
self.window.window_info =
51+
WindowInfo::from_physical_size(size, self.window.window_info.scale());
52+
53+
let window_info = self.window.window_info;
54+
55+
self.handler.on_event(
56+
&mut crate::Window::new(Window { inner: &self.window }),
57+
Event::Window(WindowEvent::Resized(window_info)),
58+
);
59+
}
60+
61+
Ok(())
62+
}
63+
64+
// Event loop
65+
// FIXME: poll() acts fine on linux, sometimes funky on *BSD. XCB upstream uses a define to
66+
// switch between poll() and select() (the latter of which is fine on *BSD), and we should do
67+
// the same.
68+
pub fn run(&mut self) -> Result<(), Box<dyn Error>> {
69+
use nix::poll::*;
70+
71+
let xcb_fd = self.window.xcb_connection.conn.as_raw_fd();
72+
73+
let mut last_frame = Instant::now();
74+
self.event_loop_running = true;
75+
76+
while self.event_loop_running {
77+
// We'll try to keep a consistent frame pace. If the last frame couldn't be processed in
78+
// the expected frame time, this will throttle down to prevent multiple frames from
79+
// being queued up. The conditional here is needed because event handling and frame
80+
// drawing is interleaved. The `poll()` function below will wait until the next frame
81+
// can be drawn, or until the window receives an event. We thus need to manually check
82+
// if it's already time to draw a new frame.
83+
let next_frame = last_frame + self.frame_interval;
84+
if Instant::now() >= next_frame {
85+
self.handler.on_frame(&mut crate::Window::new(Window { inner: &self.window }));
86+
last_frame = Instant::max(next_frame, Instant::now() - self.frame_interval);
87+
}
88+
89+
let mut fds = [PollFd::new(xcb_fd, PollFlags::POLLIN)];
90+
91+
// Check for any events in the internal buffers
92+
// before going to sleep:
93+
self.drain_xcb_events()?;
94+
95+
// FIXME: handle errors
96+
poll(&mut fds, next_frame.duration_since(Instant::now()).subsec_millis() as i32)
97+
.unwrap();
98+
99+
if let Some(revents) = fds[0].revents() {
100+
if revents.contains(PollFlags::POLLERR) {
101+
panic!("xcb connection poll error");
102+
}
103+
104+
if revents.contains(PollFlags::POLLIN) {
105+
self.drain_xcb_events()?;
106+
}
107+
}
108+
109+
// Check if the parents's handle was dropped (such as when the host
110+
// requested the window to close)
111+
//
112+
// FIXME: This will need to be changed from just setting an atomic to somehow
113+
// synchronizing with the window being closed (using a synchronous channel, or
114+
// by joining on the event loop thread).
115+
if let Some(parent_handle) = &self.parent_handle {
116+
if parent_handle.parent_did_drop() {
117+
self.handle_must_close();
118+
self.window.close_requested.set(false);
119+
}
120+
}
121+
122+
// Check if the user has requested the window to close
123+
if self.window.close_requested.get() {
124+
self.handle_must_close();
125+
self.window.close_requested.set(false);
126+
}
127+
}
128+
129+
Ok(())
130+
}
131+
132+
fn handle_xcb_event(&mut self, event: XEvent) {
133+
// For all the keyboard and mouse events, you can fetch
134+
// `x`, `y`, `detail`, and `state`.
135+
// - `x` and `y` are the position inside the window where the cursor currently is
136+
// when the event happened.
137+
// - `detail` will tell you which keycode was pressed/released (for keyboard events)
138+
// or which mouse button was pressed/released (for mouse events).
139+
// For mouse events, here's what the value means (at least on my current mouse):
140+
// 1 = left mouse button
141+
// 2 = middle mouse button (scroll wheel)
142+
// 3 = right mouse button
143+
// 4 = scroll wheel up
144+
// 5 = scroll wheel down
145+
// 8 = lower side button ("back" button)
146+
// 9 = upper side button ("forward" button)
147+
// Note that you *will* get a "button released" event for even the scroll wheel
148+
// events, which you can probably ignore.
149+
// - `state` will tell you the state of the main three mouse buttons and some of
150+
// the keyboard modifier keys at the time of the event.
151+
// http://rtbo.github.io/rust-xcb/src/xcb/ffi/xproto.rs.html#445
152+
153+
match event {
154+
////
155+
// window
156+
////
157+
XEvent::ClientMessage(event) => {
158+
if event.format == 32
159+
&& event.data.as_data32()[0]
160+
== self.window.xcb_connection.atoms.WM_DELETE_WINDOW
161+
{
162+
self.handle_close_requested();
163+
}
164+
}
165+
166+
XEvent::ConfigureNotify(event) => {
167+
let new_physical_size = PhySize::new(event.width as u32, event.height as u32);
168+
169+
if self.new_physical_size.is_some()
170+
|| new_physical_size != self.window.window_info.physical_size()
171+
{
172+
self.new_physical_size = Some(new_physical_size);
173+
}
174+
}
175+
176+
////
177+
// mouse
178+
////
179+
XEvent::MotionNotify(event) => {
180+
let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32);
181+
let logical_pos = physical_pos.to_logical(&self.window.window_info);
182+
183+
self.handler.on_event(
184+
&mut crate::Window::new(Window { inner: &self.window }),
185+
Event::Mouse(MouseEvent::CursorMoved {
186+
position: logical_pos,
187+
modifiers: key_mods(event.state),
188+
}),
189+
);
190+
}
191+
192+
XEvent::EnterNotify(event) => {
193+
self.handler.on_event(
194+
&mut crate::Window::new(Window { inner: &self.window }),
195+
Event::Mouse(MouseEvent::CursorEntered),
196+
);
197+
// since no `MOTION_NOTIFY` event is generated when `ENTER_NOTIFY` is generated,
198+
// we generate a CursorMoved as well, so the mouse position from here isn't lost
199+
let physical_pos = PhyPoint::new(event.event_x as i32, event.event_y as i32);
200+
let logical_pos = physical_pos.to_logical(&self.window.window_info);
201+
self.handler.on_event(
202+
&mut crate::Window::new(Window { inner: &self.window }),
203+
Event::Mouse(MouseEvent::CursorMoved {
204+
position: logical_pos,
205+
modifiers: key_mods(event.state),
206+
}),
207+
);
208+
}
209+
210+
XEvent::LeaveNotify(_) => {
211+
self.handler.on_event(
212+
&mut crate::Window::new(Window { inner: &self.window }),
213+
Event::Mouse(MouseEvent::CursorLeft),
214+
);
215+
}
216+
217+
XEvent::ButtonPress(event) => match event.detail {
218+
4..=7 => {
219+
self.handler.on_event(
220+
&mut crate::Window::new(Window { inner: &self.window }),
221+
Event::Mouse(MouseEvent::WheelScrolled {
222+
delta: match event.detail {
223+
4 => ScrollDelta::Lines { x: 0.0, y: 1.0 },
224+
5 => ScrollDelta::Lines { x: 0.0, y: -1.0 },
225+
6 => ScrollDelta::Lines { x: -1.0, y: 0.0 },
226+
7 => ScrollDelta::Lines { x: 1.0, y: 0.0 },
227+
_ => unreachable!(),
228+
},
229+
modifiers: key_mods(event.state),
230+
}),
231+
);
232+
}
233+
detail => {
234+
let button_id = mouse_id(detail);
235+
self.handler.on_event(
236+
&mut crate::Window::new(Window { inner: &self.window }),
237+
Event::Mouse(MouseEvent::ButtonPressed {
238+
button: button_id,
239+
modifiers: key_mods(event.state),
240+
}),
241+
);
242+
}
243+
},
244+
245+
XEvent::ButtonRelease(event) => {
246+
if !(4..=7).contains(&event.detail) {
247+
let button_id = mouse_id(event.detail);
248+
self.handler.on_event(
249+
&mut crate::Window::new(Window { inner: &self.window }),
250+
Event::Mouse(MouseEvent::ButtonReleased {
251+
button: button_id,
252+
modifiers: key_mods(event.state),
253+
}),
254+
);
255+
}
256+
}
257+
258+
////
259+
// keys
260+
////
261+
XEvent::KeyPress(event) => {
262+
self.handler.on_event(
263+
&mut crate::Window::new(Window { inner: &self.window }),
264+
Event::Keyboard(convert_key_press_event(&event)),
265+
);
266+
}
267+
268+
XEvent::KeyRelease(event) => {
269+
self.handler.on_event(
270+
&mut crate::Window::new(Window { inner: &self.window }),
271+
Event::Keyboard(convert_key_release_event(&event)),
272+
);
273+
}
274+
275+
_ => {}
276+
}
277+
}
278+
279+
fn handle_close_requested(&mut self) {
280+
// FIXME: handler should decide whether window stays open or not
281+
self.handle_must_close();
282+
}
283+
284+
fn handle_must_close(&mut self) {
285+
self.handler.on_event(
286+
&mut crate::Window::new(Window { inner: &self.window }),
287+
Event::Window(WindowEvent::WillClose),
288+
);
289+
290+
self.event_loop_running = false;
291+
}
292+
}
293+
294+
fn mouse_id(id: u8) -> MouseButton {
295+
match id {
296+
1 => MouseButton::Left,
297+
2 => MouseButton::Middle,
298+
3 => MouseButton::Right,
299+
8 => MouseButton::Back,
300+
9 => MouseButton::Forward,
301+
id => MouseButton::Other(id),
302+
}
303+
}

src/x11/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ mod window;
55
pub use window::*;
66

77
mod cursor;
8+
mod event_loop;
89
mod keyboard;
910
mod visual_info;

src/x11/visual_info.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl WindowVisualConfig {
6363

6464
// For this 32-bit depth to work, you also need to define a color map and set a border
6565
// pixel: https://cgit.freedesktop.org/xorg/xserver/tree/dix/window.c#n818
66-
pub fn create_color_map(
66+
fn create_color_map(
6767
connection: &XcbConnection, visual_id: Visualid,
6868
) -> Result<Colormap, Box<dyn Error>> {
6969
let colormap = connection.conn.generate_id()?;

0 commit comments

Comments
 (0)