Skip to content

Commit 5cde762

Browse files
committed
X11: Split off the event loop into a separate module
1 parent 385ba6b commit 5cde762

File tree

3 files changed

+314
-295
lines changed

3 files changed

+314
-295
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;

0 commit comments

Comments
 (0)