1616
1717use std:: cell:: RefCell ;
1818use std:: collections:: HashMap ;
19- use std:: convert:: TryInto ;
19+ use std:: convert:: { TryFrom , TryInto } ;
20+ use std:: os:: unix:: io:: RawFd ;
2021use std:: rc:: Rc ;
22+ use std:: time:: { Duration , Instant } ;
2123
2224use anyhow:: { anyhow, Context , Error } ;
2325use x11rb:: connection:: Connection ;
@@ -30,6 +32,7 @@ use x11rb::xcb_ffi::XCBConnection;
3032use crate :: application:: AppHandler ;
3133
3234use super :: clipboard:: Clipboard ;
35+ use super :: util;
3336use super :: window:: Window ;
3437
3538#[ derive( Clone ) ]
@@ -41,6 +44,11 @@ pub(crate) struct Application {
4144 ///
4245 /// A display is a collection of screens.
4346 connection : Rc < XCBConnection > ,
47+ /// An `XCBConnection` is *technically* safe to use from other threads, but there are
48+ /// subtleties; see https://github.com/psychon/x11rb/blob/41ab6610f44f5041e112569684fc58cd6d690e57/src/event_loop_integration.rs.
49+ /// Let's just avoid the issue altogether. As far as public API is concerned, this causes
50+ /// `druid_shell::WindowHandle` to be `!Send` and `!Sync`.
51+ marker : std:: marker:: PhantomData < * mut XCBConnection > ,
4452 /// The default screen of the connected display.
4553 ///
4654 /// The connected display may also have additional screens.
@@ -62,6 +70,12 @@ pub(crate) struct Application {
6270 window_id : u32 ,
6371 /// The mutable `Application` state.
6472 state : Rc < RefCell < State > > ,
73+ /// The read end of the "idle pipe", a pipe that allows the event loop to be woken up from
74+ /// other threads.
75+ idle_read : RawFd ,
76+ /// The write end of the "idle pipe", a pipe that allows the event loop to be woken up from
77+ /// other threads.
78+ idle_write : RawFd ,
6579 /// The major opcode of the Present extension, if it is supported.
6680 present_opcode : Option < u8 > ,
6781}
@@ -92,6 +106,8 @@ impl Application {
92106 windows : HashMap :: new ( ) ,
93107 } ) ) ;
94108
109+ let ( idle_read, idle_write) = nix:: unistd:: pipe2 ( nix:: fcntl:: OFlag :: O_NONBLOCK ) ?;
110+
95111 let present_opcode = if std:: env:: var_os ( "DRUID_SHELL_DISABLE_X11_PRESENT" ) . is_some ( ) {
96112 // Allow disabling Present with an environment variable.
97113 None
@@ -110,7 +126,10 @@ impl Application {
110126 screen_num : screen_num as i32 ,
111127 window_id,
112128 state,
129+ idle_read,
130+ idle_write,
113131 present_opcode,
132+ marker : std:: marker:: PhantomData ,
114133 } )
115134 }
116135
@@ -350,23 +369,67 @@ impl Application {
350369 Ok ( false )
351370 }
352371
353- pub fn run ( self , _handler : Option < Box < dyn AppHandler > > ) {
372+ fn run_inner ( self ) -> Result < ( ) , Error > {
373+ // Try to figure out the refresh rate of the current screen. We run the idle loop at that
374+ // rate. The rate-limiting of the idle loop has two purposes:
375+ // - When the present extension is disabled, we paint in the idle loop. By limiting the
376+ // idle loop to the monitor's refresh rate, we aren't painting unnecessarily.
377+ // - By running idle commands at a limited rate, we limit spurious wake-ups: if the X11
378+ // connection is otherwise idle, we'll wake up at most once per frame, run *all* the
379+ // pending idle commands, and then go back to sleep.
380+ let refresh_rate = util:: refresh_rate ( self . connection ( ) , self . window_id ) . unwrap_or ( 60.0 ) ;
381+ let timeout = Duration :: from_millis ( ( 1000.0 / refresh_rate) as u64 ) ;
382+ let mut last_idle_time = Instant :: now ( ) ;
354383 loop {
355- if let Ok ( ev) = self . connection . wait_for_event ( ) {
384+ let next_idle_time = last_idle_time + timeout;
385+ self . connection . flush ( ) ?;
386+
387+ // Before we poll on the connection's file descriptor, check whether there are any
388+ // events ready. It could be that XCB has some events in its internal buffers because
389+ // of something that happened during the idle loop.
390+ let mut event = self . connection . poll_for_event ( ) ?;
391+
392+ if event. is_none ( ) {
393+ poll_with_timeout ( & self . connection , self . idle_read , next_idle_time)
394+ . context ( "Error while waiting for X11 connection" ) ?;
395+ }
396+
397+ while let Some ( ev) = event {
356398 match self . handle_event ( & ev) {
357399 Ok ( quit) => {
358400 if quit {
359- break ;
401+ return Ok ( ( ) ) ;
360402 }
361403 }
362404 Err ( e) => {
363405 log:: error!( "Error handling event: {:#}" , e) ;
364406 }
365407 }
408+ event = self . connection . poll_for_event ( ) ?;
409+ }
410+
411+ let now = Instant :: now ( ) ;
412+ if now >= next_idle_time {
413+ last_idle_time = now;
414+ drain_idle_pipe ( self . idle_read ) ?;
415+
416+ if let Ok ( state) = self . state . try_borrow ( ) {
417+ for w in state. windows . values ( ) {
418+ w. run_idle ( ) ;
419+ }
420+ } else {
421+ log:: error!( "In idle loop, application state already borrowed" ) ;
422+ }
366423 }
367424 }
368425 }
369426
427+ pub fn run ( self , _handler : Option < Box < dyn AppHandler > > ) {
428+ if let Err ( e) = self . run_inner ( ) {
429+ log:: error!( "{}" , e) ;
430+ }
431+ }
432+
370433 pub fn quit ( & self ) {
371434 if let Ok ( mut state) = self . state . try_borrow_mut ( ) {
372435 if !state. quitting {
@@ -380,7 +443,6 @@ impl Application {
380443 for window in state. windows . values ( ) {
381444 window. destroy ( ) ;
382445 }
383- log_x11 ! ( self . connection. flush( ) ) ;
384446 }
385447 }
386448 } else {
@@ -390,7 +452,12 @@ impl Application {
390452
391453 fn finalize_quit ( & self ) {
392454 log_x11 ! ( self . connection. destroy_window( self . window_id) ) ;
393- log_x11 ! ( self . connection. flush( ) ) ;
455+ if let Err ( e) = nix:: unistd:: close ( self . idle_read ) {
456+ log:: error!( "Error closing idle_read: {}" , e) ;
457+ }
458+ if let Err ( e) = nix:: unistd:: close ( self . idle_write ) {
459+ log:: error!( "Error closing idle_write: {}" , e) ;
460+ }
394461 }
395462
396463 pub fn clipboard ( & self ) -> Clipboard {
@@ -404,4 +471,93 @@ impl Application {
404471 log:: warn!( "Application::get_locale is currently unimplemented for X11 platforms. (defaulting to en-US)" ) ;
405472 "en-US" . into ( )
406473 }
474+
475+ pub ( crate ) fn idle_pipe ( & self ) -> RawFd {
476+ self . idle_write
477+ }
478+ }
479+
480+ /// Clears out our idle pipe; `idle_read` should be the reading end of a pipe that was opened with
481+ /// O_NONBLOCK.
482+ fn drain_idle_pipe ( idle_read : RawFd ) -> Result < ( ) , Error > {
483+ // Each write to the idle pipe adds one byte; it's unlikely that there will be much in it, but
484+ // read it 16 bytes at a time just in case.
485+ let mut read_buf = [ 0u8 ; 16 ] ;
486+ loop {
487+ match nix:: unistd:: read ( idle_read, & mut read_buf[ ..] ) {
488+ Err ( nix:: Error :: Sys ( nix:: errno:: Errno :: EINTR ) ) => { }
489+ // According to write(2), this is the outcome of reading an empty, O_NONBLOCK
490+ // pipe.
491+ Err ( nix:: Error :: Sys ( nix:: errno:: Errno :: EAGAIN ) ) => {
492+ break ;
493+ }
494+ Err ( e) => {
495+ return Err ( e) . context ( "Failed to read from idle pipe" ) ;
496+ }
497+ // According to write(2), this is the outcome of reading an O_NONBLOCK pipe
498+ // when the other end has been closed. This shouldn't happen to us because we
499+ // own both ends, but just in case.
500+ Ok ( 0 ) => {
501+ break ;
502+ }
503+ Ok ( _) => { }
504+ }
505+ }
506+ Ok ( ( ) )
507+ }
508+
509+ /// Returns when there is an event ready to read from `conn`, or we got signalled by another thread
510+ /// writing into our idle pipe and the `timeout` has passed.
511+ // This was taken, with minor modifications, from the xclock_utc example in the x11rb crate.
512+ // 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 > {
514+ use nix:: poll:: { poll, PollFd , PollFlags } ;
515+ use std:: os:: raw:: c_int;
516+ use std:: os:: unix:: io:: AsRawFd ;
517+
518+ let fd = conn. as_raw_fd ( ) ;
519+ let mut both_poll_fds = [
520+ PollFd :: new ( fd, PollFlags :: POLLIN ) ,
521+ PollFd :: new ( idle, PollFlags :: POLLIN ) ,
522+ ] ;
523+ let mut just_connection = [ PollFd :: new ( fd, PollFlags :: POLLIN ) ] ;
524+ let mut poll_fds = & mut both_poll_fds[ ..] ;
525+
526+ // We start with no timeout in the poll call. If we get something from the idle handler, we'll
527+ // start setting one.
528+ let mut poll_timeout = -1 ;
529+ loop {
530+ fn readable ( p : PollFd ) -> bool {
531+ p. revents ( )
532+ . unwrap_or_else ( PollFlags :: empty)
533+ . contains ( PollFlags :: POLLIN )
534+ } ;
535+
536+ match poll ( poll_fds, poll_timeout) {
537+ Ok ( _) => {
538+ if readable ( poll_fds[ 0 ] ) {
539+ // There is an X11 event ready to be handled.
540+ break ;
541+ }
542+ if poll_fds. len ( ) == 1 || readable ( poll_fds[ 1 ] ) {
543+ // Now that we got signalled, stop polling from the idle pipe and use a timeout
544+ // instead.
545+ poll_fds = & mut just_connection;
546+
547+ let now = Instant :: now ( ) ;
548+ if now >= timeout {
549+ break ;
550+ } else {
551+ poll_timeout = c_int:: try_from ( timeout. duration_since ( now) . as_millis ( ) )
552+ . unwrap_or ( c_int:: max_value ( ) )
553+ }
554+ }
555+ }
556+
557+ Err ( nix:: Error :: Sys ( nix:: errno:: Errno :: EINTR ) ) => { }
558+ Err ( e) => return Err ( e. into ( ) ) ,
559+ }
560+ }
561+
562+ Ok ( ( ) )
407563}
0 commit comments