Skip to content

Commit cffde32

Browse files
authored
Implement timers for the X11 platform (#1096)
Fixes-half-of: #934 Signed-off-by: Uli Schlachter <[email protected]>
1 parent c8c909a commit cffde32

File tree

4 files changed

+136
-17
lines changed

4 files changed

+136
-17
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ You can find its changes [documented below](#060---2020-06-01).
3636
- X11: Support idle callbacks. ([#1072] by [@jneem])
3737
- GTK: Don't interrupt `KeyEvent.repeat` when releasing another key. ([#1081] by [@raphlinus])
3838
- X11: Set some more common window properties. ([#1097] by [@psychon])
39+
- X11: Support timers. ([#1096] by [@psychon])
3940

4041
### Visual
4142

@@ -362,6 +363,7 @@ Last release without a changelog :(
362363
[#1075]: https://github.com/linebender/druid/pull/1075
363364
[#1076]: https://github.com/linebender/druid/pull/1076
364365
[#1081]: https://github.com/linebender/druid/pull/1081
366+
[#1096]: https://github.com/linebender/druid/pull/1096
365367
[#1097]: https://github.com/linebender/druid/pull/1097
366368

367369
[Unreleased]: https://github.com/linebender/druid/compare/v0.6.0...master

druid-shell/src/platform/x11/application.rs

+58-10
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,18 @@ impl Application {
381381
let timeout = Duration::from_millis((1000.0 / refresh_rate) as u64);
382382
let mut last_idle_time = Instant::now();
383383
loop {
384+
// Figure out when the next wakeup needs to happen
385+
let next_timeout = if let Ok(state) = self.state.try_borrow() {
386+
state.windows
387+
.values()
388+
.filter_map(|w| w.next_timeout())
389+
.min()
390+
} else {
391+
log::error!("Getting next timeout, application state already borrowed");
392+
None
393+
};
384394
let next_idle_time = last_idle_time + timeout;
395+
385396
self.connection.flush()?;
386397

387398
// Before we poll on the connection's file descriptor, check whether there are any
@@ -390,7 +401,7 @@ impl Application {
390401
let mut event = self.connection.poll_for_event()?;
391402

392403
if event.is_none() {
393-
poll_with_timeout(&self.connection, self.idle_read, next_idle_time)
404+
poll_with_timeout(&self.connection, self.idle_read, next_timeout, next_idle_time)
394405
.context("Error while waiting for X11 connection")?;
395406
}
396407

@@ -409,6 +420,17 @@ impl Application {
409420
}
410421

411422
let now = Instant::now();
423+
if let Some(timeout) = next_timeout {
424+
if timeout <= now {
425+
if let Ok(state) = self.state.try_borrow() {
426+
for w in state.windows.values() {
427+
w.run_timers(now);
428+
}
429+
} else {
430+
log::error!("In timer loop, application state already borrowed");
431+
}
432+
}
433+
}
412434
if now >= next_idle_time {
413435
last_idle_time = now;
414436
drain_idle_pipe(self.idle_read)?;
@@ -510,11 +532,13 @@ fn drain_idle_pipe(idle_read: RawFd) -> Result<(), Error> {
510532
/// writing into our idle pipe and the `timeout` has passed.
511533
// This was taken, with minor modifications, from the xclock_utc example in the x11rb crate.
512534
// https://github.com/psychon/x11rb/blob/a6bd1453fd8e931394b9b1f2185fad48b7cca5fe/examples/xclock_utc.rs
513-
fn poll_with_timeout(conn: &Rc<XCBConnection>, idle: RawFd, timeout: Instant) -> Result<(), Error> {
535+
fn poll_with_timeout(conn: &Rc<XCBConnection>, idle: RawFd, timer_timeout: Option<Instant>, idle_timeout: Instant) -> Result<(), Error> {
514536
use nix::poll::{poll, PollFd, PollFlags};
515537
use std::os::raw::c_int;
516538
use std::os::unix::io::AsRawFd;
517539

540+
let mut now = Instant::now();
541+
let earliest_timeout = idle_timeout.min(timer_timeout.unwrap_or(idle_timeout));
518542
let fd = conn.as_raw_fd();
519543
let mut both_poll_fds = [
520544
PollFd::new(fd, PollFlags::POLLIN),
@@ -525,36 +549,60 @@ fn poll_with_timeout(conn: &Rc<XCBConnection>, idle: RawFd, timeout: Instant) ->
525549

526550
// We start with no timeout in the poll call. If we get something from the idle handler, we'll
527551
// start setting one.
528-
let mut poll_timeout = -1;
552+
let mut honor_idle_timeout = false;
529553
loop {
530554
fn readable(p: PollFd) -> bool {
531555
p.revents()
532556
.unwrap_or_else(PollFlags::empty)
533557
.contains(PollFlags::POLLIN)
534558
};
535559

560+
// Compute the deadline for when poll() has to wakeup
561+
let deadline = if honor_idle_timeout {
562+
Some(earliest_timeout)
563+
} else {
564+
timer_timeout
565+
};
566+
// ...and convert the deadline into an argument for poll()
567+
let poll_timeout = if let Some(deadline) = deadline {
568+
if deadline <= now {
569+
break;
570+
} else {
571+
let millis = c_int::try_from(deadline.duration_since(now).as_millis())
572+
.unwrap_or(c_int::max_value() - 1);
573+
// The above .as_millis() rounds down. This means we would wake up before the
574+
// deadline is reached. Add one to 'simulate' rounding up instead.
575+
millis + 1
576+
}
577+
} else {
578+
// No timeout
579+
-1
580+
};
581+
536582
match poll(poll_fds, poll_timeout) {
537583
Ok(_) => {
538584
if readable(poll_fds[0]) {
539585
// There is an X11 event ready to be handled.
540586
break;
541587
}
588+
now = Instant::now();
589+
if timer_timeout.is_some() && now >= timer_timeout.unwrap() {
590+
break;
591+
}
542592
if poll_fds.len() == 1 || readable(poll_fds[1]) {
543593
// Now that we got signalled, stop polling from the idle pipe and use a timeout
544594
// instead.
545595
poll_fds = &mut just_connection;
546-
547-
let now = Instant::now();
548-
if now >= timeout {
596+
honor_idle_timeout = true;
597+
if now >= idle_timeout {
549598
break;
550-
} else {
551-
poll_timeout = c_int::try_from(timeout.duration_since(now).as_millis())
552-
.unwrap_or(c_int::max_value())
553599
}
554600
}
555601
}
556602

557-
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {}
603+
Err(nix::Error::Sys(nix::errno::Errno::EINTR)) => {
604+
now = Instant::now();
605+
}
558606
Err(e) => return Err(e.into()),
559607
}
560608
}

druid-shell/src/platform/x11/util.rs

+41
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414

1515
//! Miscellaneous utility functions for working with X11.
1616
17+
use std::cmp::Ordering;
1718
use std::rc::Rc;
19+
use std::time::Instant;
1820

1921
use anyhow::{anyhow, Error};
2022
use x11rb::protocol::randr::{ConnectionExt, ModeFlag};
2123
use x11rb::protocol::xproto::{Screen, Visualtype, Window};
2224
use x11rb::xcb_ffi::XCBConnection;
2325

26+
use crate::window::TimerToken;
27+
2428
// See: https://github.com/rtbo/rust-xcb/blob/master/examples/randr_screen_modes.rs
2529
pub fn refresh_rate(conn: &Rc<XCBConnection>, window_id: Window) -> Option<f64> {
2630
let try_refresh_rate = || -> Result<f64, Error> {
@@ -87,3 +91,40 @@ macro_rules! log_x11 {
8791
}
8892
};
8993
}
94+
95+
/// A timer is a deadline (`std::Time::Instant`) and a `TimerToken`.
96+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97+
pub(crate) struct Timer {
98+
deadline: Instant,
99+
token: TimerToken,
100+
}
101+
102+
impl Timer {
103+
pub(crate) fn new(deadline: Instant) -> Self {
104+
let token = TimerToken::next();
105+
Self { deadline, token }
106+
}
107+
108+
pub(crate) fn deadline(&self) -> Instant {
109+
self.deadline
110+
}
111+
112+
pub(crate) fn token(&self) -> TimerToken {
113+
self.token
114+
}
115+
}
116+
117+
impl Ord for Timer {
118+
/// Ordering is so that earliest deadline sorts first
119+
// "Earliest deadline first" that a std::collections::BinaryHeap will have the earliest timer
120+
// at its head, which is just what is needed for timer management.
121+
fn cmp(&self, other: &Self) -> Ordering {
122+
self.deadline.cmp(&other.deadline).reverse()
123+
}
124+
}
125+
126+
impl PartialOrd for Timer {
127+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
128+
Some(self.cmp(other))
129+
}
130+
}

druid-shell/src/platform/x11/window.rs

+35-7
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
use std::any::Any;
1818
use std::cell::RefCell;
1919
use std::convert::{TryFrom, TryInto};
20+
use std::collections::BinaryHeap;
2021
use std::os::unix::io::RawFd;
2122
use std::rc::{Rc, Weak};
2223
use std::sync::{Arc, Mutex};
24+
use std::time::Instant;
2325

2426
use anyhow::{anyhow, Context, Error};
2527
use cairo::{XCBConnection as CairoXCBConnection, XCBDrawable, XCBSurface, XCBVisualType};
@@ -47,7 +49,7 @@ use crate::window::{IdleToken, Text, TimerToken, WinHandler};
4749
use super::application::Application;
4850
use super::keycodes;
4951
use super::menu::Menu;
50-
use super::util;
52+
use super::util::{self, Timer};
5153

5254
/// A version of XCB's `xcb_visualtype_t` struct. This was copied from the [example] in x11rb; it
5355
/// is used to interoperate with cairo.
@@ -296,6 +298,7 @@ impl WindowBuilder {
296298
cairo_surface,
297299
atoms,
298300
state,
301+
timer_queue: Mutex::new(BinaryHeap::new()),
299302
idle_queue: Arc::new(Mutex::new(Vec::new())),
300303
idle_pipe: self.app.idle_pipe(),
301304
present_data: RefCell::new(present_data),
@@ -350,6 +353,8 @@ pub(crate) struct Window {
350353
cairo_surface: RefCell<XCBSurface>,
351354
atoms: WindowAtoms,
352355
state: RefCell<WindowState>,
356+
/// Timers, sorted by "earliest deadline first"
357+
timer_queue: Mutex<BinaryHeap<Timer>>,
353358
idle_queue: Arc<Mutex<Vec<IdleKind>>>,
354359
// Writing to this wakes up the event loop, so that it can run idle handlers.
355360
idle_pipe: RawFd,
@@ -1004,6 +1009,27 @@ impl Window {
10041009
}
10051010
}
10061011
}
1012+
1013+
pub(crate) fn next_timeout(&self) -> Option<Instant> {
1014+
if let Some(timer) = self.timer_queue.lock().unwrap().peek() {
1015+
Some(timer.deadline())
1016+
} else {
1017+
None
1018+
}
1019+
}
1020+
1021+
pub(crate) fn run_timers(&self, now: Instant) {
1022+
while let Some(deadline) = self.next_timeout() {
1023+
if deadline > now {
1024+
break;
1025+
}
1026+
// Remove the timer and get the token
1027+
let token = self.timer_queue.lock().unwrap().pop().unwrap().token();
1028+
if let Ok(mut handler_borrow) = self.handler.try_borrow_mut() {
1029+
handler_borrow.timer(token);
1030+
}
1031+
}
1032+
}
10071033
}
10081034

10091035
impl Buffers {
@@ -1339,12 +1365,14 @@ impl WindowHandle {
13391365
Text::new()
13401366
}
13411367

1342-
pub fn request_timer(&self, _deadline: std::time::Instant) -> TimerToken {
1343-
// TODO(x11/timers): implement WindowHandle::request_timer
1344-
// This one might be tricky, since there's not really any timers to hook into in X11.
1345-
// Might have to code up our own Timer struct, running in its own thread?
1346-
log::warn!("WindowHandle::resizeable is currently unimplemented for X11 platforms.");
1347-
TimerToken::INVALID
1368+
pub fn request_timer(&self, deadline: Instant) -> TimerToken {
1369+
if let Some(w) = self.window.upgrade() {
1370+
let timer = Timer::new(deadline);
1371+
w.timer_queue.lock().unwrap().push(timer);
1372+
timer.token()
1373+
} else {
1374+
TimerToken::INVALID
1375+
}
13481376
}
13491377

13501378
pub fn set_cursor(&mut self, _cursor: &Cursor) {

0 commit comments

Comments
 (0)