FLTK logo

[master] 29d9e31 - Consolidate timeout handling across platforms (#379)

FLTK matrix user chat room
(using Element browser app)   FLTK gitter user chat room   GitHub FLTK Project   FLTK News RSS Feed  
  FLTK Apps      FLTK Library      Forums      Links     Login 
 All Forums  |  Back to fltk.commit  ]
 
Previous Message ]Next Message ]

[master] 29d9e31 - Consolidate timeout handling across platforms (#379) "Albrecht Schlosser" Jan 31, 2022  
 
commit 29d9e31c51e6c11d6e33abf9bc4551afd9de3836
Author:     Albrecht Schlosser <albrechts.fltk@online.de>
AuthorDate: Mon Jan 31 22:27:17 2022 +0100
Commit:     GitHub <noreply@github.com>
CommitDate: Mon Jan 31 22:27:17 2022 +0100

    Consolidate timeout handling across platforms (#379)
    
    Add Fl_Timeout class
    
    Move platform independent code of Fl::wait() to main part
    
    - basic timeout handling
    - Fl::run_checks()
    - Fl::run_idle()
    - Fl::idle()
    - waiting time calculation (next timeout)
    - remove platform dependent "screen driver" stuff

 FL/Fl.H                                        |   3 +-
 src/CMakeLists.txt                             |   3 +-
 src/Fl.cxx                                     |  74 +++-
 src/Fl_Screen_Driver.H                         |   5 -
 src/Fl_Timeout.cxx                             | 456 +++++++++++++++++++++++++
 src/Fl_Timeout.h                               | 156 +++++++++
 src/Fl_add_idle.cxx                            |  11 +-
 src/Fl_cocoa.mm                                |  22 +-
 src/Fl_win32.cxx                               |  16 +-
 src/Makefile                                   |   3 +-
 src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.H     |   7 +-
 src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.cxx   | 142 +-------
 src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.H   |  10 +-
 src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.cxx | 145 +-------
 src/drivers/X11/Fl_X11_Screen_Driver.H         |   5 -
 src/drivers/X11/Fl_X11_Screen_Driver.cxx       | 161 +--------
 16 files changed, 719 insertions(+), 500 deletions(-)

diff --git FL/Fl.H FL/Fl.H
index 37ec2ca..a197885 100644
--- FL/Fl.H
+++ FL/Fl.H
@@ -1,7 +1,7 @@
 //
 // Main header file for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2021 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -510,6 +510,7 @@ int main() {
   static int  has_check(Fl_Timeout_Handler, void* = 0);
   static void remove_check(Fl_Timeout_Handler, void* = 0);
   // private
+  static void run_idle();
   static void run_checks();
   static void add_fd(int fd, int when, Fl_FD_Handler cb, void* = 0); // platform dependent
   static void add_fd(int fd, Fl_FD_Handler cb, void* = 0); // platform dependent
diff --git src/CMakeLists.txt src/CMakeLists.txt
index bca6d98..aa0eed1 100644
--- src/CMakeLists.txt
+++ src/CMakeLists.txt
@@ -1,7 +1,7 @@
 #
 # CMakeLists.txt to build the FLTK library using CMake (www.cmake.org)
 #
-# Copyright 1998-2021 by Bill Spitzak and others.
+# Copyright 1998-2022 by Bill Spitzak and others.
 #
 # This library is free software. Distribution and use rights are outlined in
 # the file "COPYING" which should have been included with this file.  If this
@@ -90,6 +90,7 @@ set (CPPFILES
   Fl_Text_Editor.cxx
   Fl_Tile.cxx
   Fl_Tiled_Image.cxx
+  Fl_Timeout.cxx
   Fl_Tooltip.cxx
   Fl_Tree.cxx
   Fl_Tree_Item_Array.cxx
diff --git src/Fl.cxx src/Fl.cxx
index db69fbd..a37e1a1 100644
--- src/Fl.cxx
+++ src/Fl.cxx
@@ -1,7 +1,7 @@
 //
 // Main event handling code for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2021 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -23,6 +23,7 @@
 #include "Fl_Screen_Driver.H"
 #include "Fl_Window_Driver.H"
 #include "Fl_System_Driver.H"
+#include "Fl_Timeout.h"
 #include <FL/Fl_Window.H>
 #include <FL/Fl_Tooltip.H>
 #include <FL/fl_draw.H>
@@ -228,24 +229,22 @@ int Fl::event_inside(const Fl_Widget *o) /*const*/ {
 }
 
 //
-//
-// timer support
-//
+// cross-platform timer support
 //
 
 void Fl::add_timeout(double time, Fl_Timeout_Handler cb, void *argp) {
-  Fl::screen_driver()->add_timeout(time, cb, argp);
+  Fl_Timeout::add_timeout(time, cb, argp);
 }
 
 void Fl::repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp) {
-  Fl::screen_driver()->repeat_timeout(time, cb, argp);
+  Fl_Timeout::repeat_timeout(time, cb, argp);
 }
 
 /**
  Returns true if the timeout exists and has not been called yet.
  */
 int Fl::has_timeout(Fl_Timeout_Handler cb, void *argp) {
-  return Fl::screen_driver()->has_timeout(cb, argp);
+  return Fl_Timeout::has_timeout(cb, argp);
 }
 
 /**
@@ -256,7 +255,7 @@ int Fl::has_timeout(Fl_Timeout_Handler cb, void *argp) {
         This may change in the future.
  */
 void Fl::remove_timeout(Fl_Timeout_Handler cb, void *argp) {
-  Fl::screen_driver()->remove_timeout(cb, argp);
+  Fl_Timeout::remove_timeout(cb, argp);
 }
 
 
@@ -429,10 +428,46 @@ void fl_trigger_clipboard_notify(int source) {
 }
 
 ////////////////////////////////////////////////////////////////
-// wait/run/check/ready:
+// idle/wait/run/check/ready:
 
 void (*Fl::idle)(); // see Fl::add_idle.cxx for the add/remove functions
 
+/*
+  Private, undocumented method to run idle callbacks.
+
+  FLTK guarantees that idle callbacks will never be called recursively,
+  i.e. while an idle callback is being executed.
+
+  This method should (must) be the only way to run idle callbacks to
+  ensure that the `in_idle' flag is respected.
+
+  Idle callbacks are executed whenever Fl::wait() is called and no events
+  are waiting to be serviced.
+
+  If Fl::idle is set (non-NULL) this points at a function that executes
+  the first idle callback and appends it to the end of the list of idle
+  callbacks. For details see static function call_idle() in Fl_add_idle.cxx.
+
+  If it is NULL then no idle callbacks are active and Fl::run_idle() returns
+  immediately.
+
+  Note: idle callbacks can be queued in nested FLTK event loops like
+  ```
+    while (win->shown())
+      Fl::wait();
+  ```
+  if an event (timeout or button click etc.) handler calls Fl::add_idle()
+  or even in Fl::flush() if a draw() method calls Fl::add_idle().
+*/
+void Fl::run_idle() {
+  static char in_idle;
+  if (Fl::idle && !in_idle) {
+    in_idle = 1;
+    Fl::idle();
+    in_idle = 0;
+  }
+}
+
 /**
  Waits a maximum of \p time_to_wait seconds or until "something happens".
 
@@ -444,8 +479,29 @@ void (*Fl::idle)(); // see Fl::add_idle.cxx for the add/remove functions
  occurs (this will happen on X11 if a signal happens).
 */
 double Fl::wait(double time_to_wait) {
+
+  // platform independent part:
+
   // delete all widgets that were listed during callbacks
   do_widget_deletion();
+
+  Fl_Timeout::do_timeouts(); // execute timer callbacks
+
+  Fl::run_checks();
+
+  Fl::run_idle();
+
+  // the idle function may turn off idle, we can then wait,
+  // or it leaves Fl::idle active and we set time_to_wait to 0
+  if (Fl::idle) {
+    time_to_wait = 0.0;
+  } else {
+    // limit time by next timer interval
+    time_to_wait = Fl_Timeout::time_to_wait(time_to_wait);
+  }
+
+  // platform dependent part:
+
   return screen_driver()->wait(time_to_wait);
 }
 
diff --git src/Fl_Screen_Driver.H src/Fl_Screen_Driver.H
index 027b300..1f6c80f 100644
--- src/Fl_Screen_Driver.H
+++ src/Fl_Screen_Driver.H
@@ -114,11 +114,6 @@ public:
   virtual void get_system_colors() {}
   /* the default implementation of get_system_scheme() may be enough */
   virtual const char *get_system_scheme();
-  // --- global timers
-  virtual void add_timeout(double, Fl_Timeout_Handler, void *) {}
-  virtual void repeat_timeout(double, Fl_Timeout_Handler, void *) {}
-  virtual int has_timeout(Fl_Timeout_Handler, void *) { return 0; }
-  virtual void remove_timeout(Fl_Timeout_Handler, void *) {}
 
   static int secret_input_character;
   /* Implement to indicate whether complex text input may involve marked text.
diff --git src/Fl_Timeout.cxx src/Fl_Timeout.cxx
new file mode 100644
index 0000000..fed9fba
--- /dev/null
+++ src/Fl_Timeout.cxx
@@ -0,0 +1,456 @@
+//
+// Timeout support functions for the Fast Light Tool Kit (FLTK).
+//
+// Author: Albrecht Schlosser
+// Copyright 2021-2022 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#include "Fl_Timeout.h"
+#include "Fl_System_Driver.H"
+
+#include <stdio.h>
+
+/**
+  \file Fl_Timeout.cxx
+*/
+
+// static class variables
+
+Fl_Timeout *Fl_Timeout::free_timeout = 0;
+Fl_Timeout *Fl_Timeout::first_timeout = 0;
+Fl_Timeout *Fl_Timeout::current_timeout = 0;
+
+#if FL_TIMEOUT_DEBUG
+static int num_timers = 0;    // DEBUG
+#endif
+
+// Internal timestamp, used for delta time calculation.
+// Note: FLTK naming convention is not used here to avoid potential conflicts
+// in the future.
+
+struct FlTimeStamp {
+  long sec;
+  long usec;
+};
+
+typedef struct FlTimeStamp FlTimeStamp_t;
+
+// Get a timestamp of type FlTimeStamp.
+
+// Depending on the system the resolution may be milliseconds or microseconds.
+// Under certain conditions (particularly on Windows) the value in member `sec'
+// may wrap around and does not represent a real time (maybe runtime of the system).
+// Function elapsed_time() below uses this to subtract two timestamps which is always
+// a correct delta time with milliseconds or microseconds resolution.
+
+// To do: Fl::system_driver()->gettime() was implemented for the Forms library and
+// has a limited resolution (on Windows: milliseconds). On POSIX platforms it uses
+// gettimeofday() with microsecond resolution.
+// A new function could use a better resolution on Windows with its multimedia
+// timers which requires a new dependency: winmm.lib (dll). This could be a future
+// improvement, maybe set as a build option or generally (requires Win95 or 98?).
+
+static void get_timestamp(FlTimeStamp_t *ts) {
+  time_t sec;
+  int usec;
+  Fl::system_driver()->gettime(&sec, &usec);
+  ts->sec = (long)sec;
+  ts->usec = usec;
+}
+
+// Returns 0 and initializes the "previous" timestamp when called for the first time.
+
+/*
+  Return the elapsed time since the last call in seconds.
+
+  The first call initializes the internal "previous" timestamp and returns 0.
+  This must only be called from Fl_Timeout::elapse_timeouts().
+
+  Todo: remove static variable in this function: previous time should be
+  maintained in the caller.
+
+  Return:  double  Elapsed time since the last call
+*/
+static double elapsed_time() {
+  static int first = 1;                 // initialization
+  static FlTimeStamp_t prev;            // previous timestamp
+  FlTimeStamp_t now;                    // current timestamp
+  double elapsed = 0.0;
+  get_timestamp(&now);
+  if (first) {
+    first = 0;
+  } else {
+    elapsed = double((now.sec - prev.sec) + (now.usec - prev.usec) / 1000000.);
+  }
+  prev = now;
+  return elapsed;
+}
+
+/**
+  Insert a timer entry into the active timer queue.
+
+  The base class Fl_Timeout inserts the timer as the first entry in
+  the queue of active timers. The default implementation is sufficient
+  for macOS and Windows.
+
+  Derived classes (e.g. Fl_Timeout) can override this method.
+  Currently the Posix timeout handling (Unix, Linux) does this so
+  the timer queue entries are ordered by due time.
+
+  \param[in]  t  Timer to be inserted (Fl_Timeout or derived class)
+*/
+void Fl_Timeout::insert() {
+  Fl_Timeout **p = (Fl_Timeout **)&first_timeout;
+  while (*p && (*p)->time <= time) {
+    p = (Fl_Timeout **)&((*p)->next);
+  }
+  next = *p;
+  *p = this;
+}
+
+/**
+  Returns whether the given timeout is active.
+
+  This returns whether a timeout handler already exists in the queue
+  of active timers.
+
+  If \p data == NULL only the Fl_Timeout_Handler \p cb must match to return
+  true, otherwise \p data must also match.
+
+  \note It is a restriction that there is no way to look for a timeout whose
+    \p data is NULL (zero). Therefore using 0 (zero, NULL) as the timeout
+    \p data value is discouraged, unless you're sure that you will never
+    need to use <kbd>Fl::has_timeout(callback, (void *)0);</kbd>.
+
+  Implements Fl::has_timeout(Fl_Timeout_Handler cb, void *data)
+
+  \param[in]  cb    Timer callback (must match)
+  \param[in]  data  Wildcard if NULL, must match otherwise
+
+  \returns      whether the timer was found in the queue
+  \retval   0   not found
+  \retval   1   found
+*/
+
+int Fl_Timeout::has_timeout(Fl_Timeout_Handler cb, void *data) {
+  for (Fl_Timeout *t = first_timeout; t; t = t->next) {
+    if (t->callback == cb && t->data == data)
+      return 1;
+  }
+  return 0;
+}
+
+void Fl_Timeout::add_timeout(double time, Fl_Timeout_Handler cb, void *data) {
+  elapse_timeouts();
+  Fl_Timeout *t = get(time, cb, data);
+  t->Fl_Timeout::insert();
+}
+
+void Fl_Timeout::repeat_timeout(double time, Fl_Timeout_Handler cb, void *data) {
+  elapse_timeouts();
+  Fl_Timeout *t = (Fl_Timeout *)get(time, cb, data);
+  Fl_Timeout *cur = current_timeout;
+  if (cur) {
+    t->time += cur->time;   // was: missed_timeout_by (always <= 0.0)
+  }
+  if (t->time < 0.0)
+    t->time = 0.001;        // at least 1 ms
+  t->insert();
+}
+
+/**
+  Remove a timeout callback. It is harmless to remove a timeout
+  callback that no longer exists.
+
+  \note This version removes all matching timeouts, not just the first one.
+    This may change in the future.
+
+  Implements Fl::remove_timeout(Fl_Timeout_Handler cb, void *data)
+*/
+void Fl_Timeout::remove_timeout(Fl_Timeout_Handler cb, void *data) {
+  for (Fl_Timeout** p = &first_timeout; *p;) {
+    Fl_Timeout* t = *p;
+    if (t->callback == cb && (t->data == data || !data)) {
+      *p = t->next;
+      t->next = free_timeout;
+      free_timeout = t;
+    } else {
+      p = &(t->next);
+    }
+  }
+}
+
+/**
+  Remove the timeout from the active timer queue and push it onto
+  the stack of currently running callbacks.
+
+  This becomes the current() timeout which can be used in
+  Fl::repeat_timeout().
+
+  \see Fl_Timeout::current()
+*/
+void Fl_Timeout::make_current() {
+  // printf("[%4d] Fl_Timeout::make_current(%p)\n", __LINE__, this);
+  // remove the timer entry from the active timer queue
+  for (Fl_Timeout** p = &first_timeout; *p;) {
+    Fl_Timeout* t = *p;
+    if (t == this) {
+      *p = t->next;
+      // push it to the current timer stack
+      t->next = current_timeout;
+      current_timeout = t;
+      break;
+    } else {
+      p = &(t->next);
+    }
+  }
+}
+
+/**
+  Remove the top-most timeout from the stack of currently running
+  timeout callbacks and insert it into the list of free timers.
+
+  This should always return a non-NULL value, otherwise there's a bug
+  in the library. Typical code in the library would look like:
+  \code
+    // The timeout \p Fl_Timeout *t has exired, run its callback
+    t->make_current();
+    (t->callback)(t->data);
+    t->release();
+  \endcode
+
+  \return   Fl_Timeout*   current timeout or NULL
+*/
+void Fl_Timeout::release() {
+  Fl_Timeout *t = current_timeout;
+  if (t) {
+
+    // The first timer in the "current" list *should* be 'this' but we
+    // check it to be sure. Issue an error message which should never appear.
+    // If it would happen we'd remove the wrong timer from the current timer
+    // list. This is not good but it doesn't really do harm.
+
+    if (t != this) {
+      Fl::error("*** Fl_Timeout::release() *** timer t (%p) != this (%p)\n", t, this);
+    }
+
+    // remove the timer from the list
+    current_timeout = t->next;
+  }
+  // put the timer into the list of free timers
+  t->next = free_timeout;
+  free_timeout = t;
+}
+
+/**
+  Returns the first (top-most) timeout from the current timeout stack.
+
+  This returns a pointer to the timeout but does not remove it from the
+  list of current timeouts. This should be the timeout that is currently
+  executing its callback.
+
+  \return   Fl_Timeout*   The current timeout whose callback is running.
+  \retval   NULL          if no callback is currently running.
+*/
+Fl_Timeout *Fl_Timeout::current() {
+  return current_timeout;
+}
+
+/**
+  Get an Fl_Timeout instance for further handling.
+
+  The timer object will be initialized with the input parameters
+  as given by Fl::add_timeout() or Fl::repeat_timeout().
+
+  Fl_Timeout objects are maintained in three queues:
+  - active timer queue
+  - list (stack, i.e. LIFO) of currently executing timer callbacks
+  - free timer entries.
+
+  When the FLTK program is launched all queues are empty. Whenever
+  a new timer object is required the get() method is called and a timer
+  object is either found in the queue of free timer entries or a new
+  timer object is created (operator new).
+
+  Active timer entries are inserted into the "active timer queue" until
+  they expire and their callback is called.
+
+  Before the callback is called the timer entry is inserted into the list
+  of current timers, i.e. it becomes the Fl_Timeout::current() timeout.
+  This can be used in Fl::repeat_timeout() to find out if and how long the
+  current timeout has been delayed.
+
+  When a timer is no longer used it is popped from the \p current list
+  and inserted into the "free timer" list so it can be reused later.
+
+  Timer queue entries are never returned to the system, there's no garbage
+  collection. The total number of timer objects is determined by the
+  largest number of concurrently active timers.
+
+  \param[in]  time  requested delta time
+  \param[in]  cb    timer callback
+  \param[in]  data  userdata for timer callback
+
+  \return  Fl_Timeout*  Timer entry
+
+  \see Fl::add_timeout(), Fl::repeat_timeout()
+*/
+
+Fl_Timeout *Fl_Timeout::get(double time, Fl_Timeout_Handler cb, void *data) {
+
+  Fl_Timeout *t = (Fl_Timeout *)free_timeout;
+  if (t) {
+    free_timeout = t->next;
+    t->next = 0;
+  } else {
+    t = new Fl_Timeout;
+#if FL_TIMEOUT_DEBUG
+    num_timers++;                 // DEBUG: count allocated timers
+#endif
+  }
+
+  memset(t, 0, sizeof(*t));
+  t->delay(time);
+  t->callback = cb;
+  t->data = data;
+  return t;
+}
+
+/**
+  Elapse all timers w/o calling their callbacks.
+
+  All timer values are adjusted by the delta time since the last call.
+  This method does \b NOT call timer callbacks if timers are expired.
+
+  This must be called before new timers are added to the timer queue to make
+  sure that the next timer decrement does not count down too much time.
+
+  \see Fl_Timeout::do_timeouts()
+*/
+void Fl_Timeout::elapse_timeouts() {
+  double elapsed = elapsed_time();
+  // printf("elapse_timeouts: elapsed = %9.6f\n", double(elapsed)/1000000.);
+
+  if (elapsed > 0.0) {
+
+    // active timers
+
+    for (Fl_Timeout* t = first_timeout; t; t = t->next) {
+      t->time -= elapsed;
+    }
+
+    // "current" timers, i.e. timers being serviced
+
+    for (Fl_Timeout* t = current_timeout; t; t = t->next) {
+      t->time -= elapsed;
+    }
+  }
+}
+
+/**
+  Elapse timers and call their callbacks if any timers are expired.
+*/
+void Fl_Timeout::do_timeouts() {
+  if (first_timeout) {
+    Fl_Timeout::elapse_timeouts();
+    Fl_Timeout *t;
+    while ((t = Fl_Timeout::first_timeout)) {
+      if (t->time > 0) break;
+      // make this timeout the "current" timeout
+      t->make_current();
+      // now it is safe for the callback to do add_timeout:
+      t->callback(t->data);
+      // release the timer entry
+      t->release();
+
+      // Elapse timers (again) because the callback may have used a
+      // significant amount of time. This is optional though.
+
+      Fl_Timeout::elapse_timeouts();
+    }
+  }
+}
+
+/**
+  Returns the delay in seconds until the next timer expires,
+  limited by \p ttw.
+
+  This function calculates the time to wait for the FLTK event queue
+  processing, depending on the given value \p ttw.
+
+  If at least one timer is active and its timeout value is smaller than
+  \p ttw then this value is returned. Fl::wait() will wait no longer than
+  until the next timer expires.
+
+  If no timer is active this returns the input value \p ttw unchanged.
+
+  If at least one timer is expired this returns 0.0 so the event processing
+  does not wait.
+
+  \param[in]  ttw   time to wait from Fl::wait() etc. (upper limit)
+
+  \return  delay until next timeout or 0.0 (see description)
+*/
+double Fl_Timeout::time_to_wait(double ttw) {
+  Fl_Timeout *t = first_timeout;
+  if (!t) return ttw;
+  double tdelay = t->delay();
+if (tdelay < 0.0)
+    return 0.0;
+  if (tdelay < ttw)
+    return tdelay;
+  return ttw;
+}
+
+
+// Write some statistics to stdout for debugging
+
+#if FL_TIMEOUT_DEBUG
+
+void Fl_Timeout::debug(int level) {
+
+  printf("\nFl_Timeout::debug: number of allocated timers = %d\n", num_timers);
+
+  int active = 0;
+  Fl_Timeout *t = first_timeout;
+  while (t) {
+    active++;
+    t = t->next;
+  }
+
+  int current = 0;
+  t = current_timeout;
+  while (t) {
+    current++;
+    t = t->next;
+  }
+
+  int free = 0;
+  t = free_timeout;
+  while (t) {
+    free++;
+    t = t->next;
+  }
+
+  printf("Fl_Timeout::debug: active: %d, current: %d, free: %d\n\n", active, current, free);
+
+  t = first_timeout;
+  int n = 0;
+  while (t) {
+    printf("Active timer %3d: time = %10.6f sec\n", n+1, t->delay());
+    t = t->next;
+    n++;
+  }
+} // Fl_Timeout::debug(int)
+
+#endif // FL_TIMEOUT_DEBUG
diff --git src/Fl_Timeout.h src/Fl_Timeout.h
new file mode 100644
index 0000000..de41106
--- /dev/null
+++ src/Fl_Timeout.h
@@ -0,0 +1,156 @@
+//
+// Header for timeout support functions for the Fast Light Tool Kit (FLTK).
+//
+// Author: Albrecht Schlosser
+// Copyright 2021-2022 by Bill Spitzak and others.
+//
+// This library is free software. Distribution and use rights are outlined in
+// the file "COPYING" which should have been included with this file.  If this
+// file is missing or damaged, see the license at:
+//
+//     https://www.fltk.org/COPYING.php
+//
+// Please see the following page on how to report bugs and issues:
+//
+//     https://www.fltk.org/bugs.php
+//
+
+#ifndef _src_Fl_Timeout_h_
+#define _src_Fl_Timeout_h_
+
+#include <FL/Fl.H>
+
+#define FL_TIMEOUT_DEBUG 0        // 1 = include debugging features, 0 = no
+
+/** \file
+  Fl_Timeout handling.
+
+  This file contains implementations of:
+
+  - Fl::add_timeout()
+  - Fl::repeat_timeout()
+  - Fl::remove_timeout()
+  - Fl::has_timeout()
+
+  and related methods of class Fl_Timeout.
+*/
+
+/**
+  Class Fl_Timeout handles all timeout related functions.
+
+  All code is platform independent except retrieving a timestamp
+  which requires calling a system driver function and potentially
+  results in different timer resolutions (from milliseconds to
+  microseconds).
+*/
+class Fl_Timeout {
+
+protected:
+
+  Fl_Timeout *next;             // ** Link to next timeout
+  Fl_Timeout_Handler callback;  // the user's callback
+  void *data;                   // the user's callback data
+  double time;                  // delay until timeout
+
+  // constructor
+  Fl_Timeout() {
+    next = 0;
+    callback = 0;
+    data = 0;
+    time = 0;
+  }
+
+  ~Fl_Timeout() {}
+
+  static Fl_Timeout *get(double time, Fl_Timeout_Handler cb, void *data);
+
+  // insert this timer into the active timer queue, sorted by expiration time
+  void insert();
+
+  // remove this timer from the active timer queue and
+  // add it to the "current" timer stack
+  void make_current();
+
+  // remove this timer from the current timer stack and
+  // add it to the list of free timers
+  void release();
+
+  /** Get the timer's delay in seconds. */
+  double delay() {
+    return time;
+  }
+
+  /** Set the timer's delay in seconds. */
+  void delay(double t) {
+    time = t;
+  }
+
+public:
+  // Returns whether the given timeout is active.
+  static int has_timeout(Fl_Timeout_Handler cb, void *data);
+
+  // Add or remove timeouts
+
+  static void add_timeout(double time, Fl_Timeout_Handler cb, void *data);
+  static void repeat_timeout(double time, Fl_Timeout_Handler cb, void *data);
+  static void remove_timeout(Fl_Timeout_Handler cb, void *data);
+
+  // Elapse timeouts, i.e. calculate new delay time of all timers.
+  // This does not call the timer callbacks.
+  static void elapse_timeouts();
+
+  // Elapse timeouts and call timer callbacks.
+  static void do_timeouts();
+
+  // Return the delay in seconds until the next timer expires.
+  static double time_to_wait(double ttw);
+
+#if FL_TIMEOUT_DEBUG
+  // Write some statistics to stdout
+  static void debug(int level = 1);
+#endif
+
+protected:
+
+  static Fl_Timeout *current();
+
+  /**
+    List of active timeouts.
+
+    These timeouts can be triggered when due, which calls their callbacks.
+    The lifetime of a timeout:
+    - active, in this queue
+    - callback running, in queue \p current_timeout
+    - done, in list of free timeouts, ready to be reused.
+  */
+  static Fl_Timeout *first_timeout;
+
+  /**
+    List of free timeouts after use.
+    Timeouts can be reused many times.
+  */
+  static Fl_Timeout *free_timeout;
+
+  /**
+    The list of current timeouts is used to store the timeout whose callback
+    is called while the callback is executed. This is used like a stack, the
+    current timeout is pushed to the front of the list and once the callback
+    is finished, that timeout is removed and entered into the free list.
+
+    Background: Fl::repeat_timeout() needs to know which timeout triggered it
+    and the exact schedule time and/or the delay of that timeout, i.e. how
+    long the scheduled time was missed before the callback was called.
+    A static, global variable is not sufficient since the user code can call
+    other functions, e.g. dialogs, that run a nested event loop which can
+    run another timeout callback. Hence this list of "current" timeouts is
+    used like a stack (last in, first out).
+
+    \see Fl_Timeout::push()                 Member function (method)
+    \see Fl_Timeout *Fl_Timeout::pop()      Static function
+    \see Fl_Timeout *Fl_Timeout::current()  TStatic function
+  */
+  static Fl_Timeout *current_timeout;   // list of "current" timeouts
+
+}; // class Fl_Timeout
+
+#endif // _src_Fl_Timeout_h_
diff --git src/Fl_add_idle.cxx src/Fl_add_idle.cxx
index f5d588c..1f1673f 100644
--- src/Fl_add_idle.cxx
+++ src/Fl_add_idle.cxx
@@ -1,7 +1,7 @@
 //
 // Idle routine support for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2010 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -32,6 +32,13 @@ static idle_cb* first;
 static idle_cb* last;
 static idle_cb* freelist;
 
+// The function call_idle()
+// - removes the first idle callback from the front of the list (ring)
+// - adds it as the last entry and
+// - calls the idle callback.
+// The idle callback may remove itself from the list of idle callbacks
+// by calling Fl::remove_idle()
+
 static void call_idle() {
   idle_cb* p = first;
   last = p; first = p->next;
@@ -42,7 +49,7 @@ static void call_idle() {
   Adds a callback function that is called every time by Fl::wait() and also
   makes it act as though the timeout is zero (this makes Fl::wait() return
   immediately, so if it is in a loop it is called repeatedly, and thus the
-  idle fucntion is called repeatedly).  The idle function can be used to get
+  idle function is called repeatedly).  The idle function can be used to get
   background processing done.
 
   You can have multiple idle callbacks. To remove an idle callback use
diff --git src/Fl_cocoa.mm src/Fl_cocoa.mm
index 52680a8..d9f6e0f 100644
--- src/Fl_cocoa.mm
+++ src/Fl_cocoa.mm
@@ -1,7 +1,7 @@
 //
-// MacOS-Cocoa specific code for the Fast Light Tool Kit (FLTK).
+// macOS-Cocoa specific code for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2021 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -27,6 +27,7 @@ extern "C" {
 #include <FL/platform.H>
 #include "Fl_Window_Driver.H"
 #include "Fl_Screen_Driver.H"
+#include "Fl_Timeout.h"
 #include <FL/Fl_Window.H>
 #include <FL/Fl_Tooltip.H>
 #include <FL/Fl_Printer.H>
@@ -749,6 +750,10 @@ static int do_queued_events( double time = 0.0 )
     dataready.StartThread();
   }
 
+  // Elapse timeouts and calculate waiting time
+  Fl_Timeout::elapse_timeouts();
+  time = Fl_Timeout::time_to_wait(time);
+
   fl_unlock_function();
   NSEvent *event = [NSApp nextEventMatchingMask:NSAnyEventMask
                                       untilDate:[NSDate dateWithTimeIntervalSinceNow:time]
@@ -773,22 +778,11 @@ double Fl_Cocoa_Screen_Driver::wait(double time_to_wait)
   if (dropped_files_list) { // when the list of dropped files is not empty, open one and remove it from list
     drain_dropped_files_list();
   }
-  Fl::run_checks();
-  static int in_idle = 0;
   NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-  if (Fl::idle) {
-    if (!in_idle) {
-      in_idle = 1;
-      Fl::idle();
-      in_idle = 0;
-    }
-    // the idle function may turn off idle, we can then wait:
-    if (Fl::idle) time_to_wait = 0.0;
-  }
   if (fl_mac_os_version < 101100) NSDisableScreenUpdates(); // 10.3 Makes updates to all windows appear as a single event
   Fl::flush();
   if (fl_mac_os_version < 101100) NSEnableScreenUpdates(); // 10.3
-  if (Fl::idle && !in_idle) // 'idle' may have been set within flush()
+  if (Fl::idle) // 'idle' may have been set within flush()
     time_to_wait = 0.0;
   int retval = do_queued_events(time_to_wait);
 
diff --git src/Fl_win32.cxx src/Fl_win32.cxx
index 5ee2fe4..b57b81a 100644
--- src/Fl_win32.cxx
+++ src/Fl_win32.cxx
@@ -1,7 +1,7 @@
 //
 // Windows-specific code for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2021 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -54,6 +54,7 @@ void fl_cleanup_dc_list(void);
 #include <FL/platform.H>
 #include "Fl_Window_Driver.H"
 #include "Fl_Screen_Driver.H"
+#include "Fl_Timeout.h"
 #include <FL/Fl_Graphics_Driver.H> // for fl_graphics_driver
 #include "drivers/WinAPI/Fl_WinAPI_Window_Driver.H"
 #include "drivers/WinAPI/Fl_WinAPI_System_Driver.H"
@@ -417,16 +418,6 @@ double Fl_WinAPI_Screen_Driver::wait(double time_to_wait) {
 
   int have_message = 0;
 
-  Fl::run_checks();
-
-  // idle processing
-  static char in_idle;
-  if (Fl::idle && !in_idle) {
-    in_idle = 1;
-    Fl::idle();
-    in_idle = 0;
-  }
-
   if (nfds) {
     // For Windows we need to poll for socket input FIRST, since
     // the event queue is not something we can select() on...
@@ -469,6 +460,9 @@ double Fl_WinAPI_Screen_Driver::wait(double time_to_wait) {
   fl_unlock_function();
 
   time_to_wait = (time_to_wait > 10000 ? 10000 : time_to_wait);
+
+  time_to_wait = Fl_Timeout::time_to_wait(time_to_wait);
+
   int t_msec = (int)(time_to_wait * 1000.0 + 0.5);
   MsgWaitForMultipleObjects(0, NULL, FALSE, t_msec, QS_ALLINPUT);
 
diff --git src/Makefile src/Makefile
index 6df9160..f98ca02 100644
--- src/Makefile
+++ src/Makefile
@@ -1,7 +1,7 @@
 #
 # Library Makefile for the Fast Light Tool Kit (FLTK).
 #
-# Copyright 1998-2021 by Bill Spitzak and others.
+# Copyright 1998-2022 by Bill Spitzak and others.
 #
 # This library is free software. Distribution and use rights are outlined in
 # the file "COPYING" which should have been included with this file.  If this
@@ -92,6 +92,7 @@ CPPFILES = \
 	Fl_Text_Editor.cxx \
 	Fl_Tile.cxx \
 	Fl_Tiled_Image.cxx \
+	Fl_Timeout.cxx \
 	Fl_Tree.cxx \
 	Fl_Tree_Item.cxx \
 	Fl_Tree_Item_Array.cxx \
diff --git src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.H src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.H
index dcd5c4f..7dd6fc1 100644
--- src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.H
+++ src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.H
@@ -2,7 +2,7 @@
 // Definition of Apple Cocoa Screen interface
 // for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 2010-2018 by Bill Spitzak and others.
+// Copyright 2010-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -80,11 +80,6 @@ public:
   virtual void grab(Fl_Window* win);
   // --- global colors
   virtual void get_system_colors();
-  // --- global timers
-  virtual void add_timeout(double time, Fl_Timeout_Handler cb, void *argp);
-  virtual void repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp);
-  virtual int has_timeout(Fl_Timeout_Handler cb, void *argp);
-  virtual void remove_timeout(Fl_Timeout_Handler cb, void *argp);
   virtual int has_marked_text() const;
   static void reset_marked_text();
   static void insertion_point_location(int x, int y, int height);
diff --git src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.cxx src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.cxx
index 1a36149..e213e5f 100644
--- src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.cxx
+++ src/drivers/Cocoa/Fl_Cocoa_Screen_Driver.cxx
@@ -1,7 +1,7 @@
 //
 // Definition of Apple Cocoa Screen interface.
 //
-// Copyright 1998-2018 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -401,143 +401,3 @@ Fl_RGB_Image *Fl_Cocoa_Screen_Driver::read_win_rectangle(int X, int Y, int w, in
   rgb->alloc_array = 1;
   return rgb;
 }
-
-//
-// MacOS X timers
-//
-
-struct MacTimeout {
-  Fl_Timeout_Handler callback;
-  void* data;
-  CFRunLoopTimerRef timer;
-  char pending;
-  CFAbsoluteTime next_timeout; // scheduled time for this timer
-};
-static MacTimeout* mac_timers;
-static int mac_timer_alloc;
-static int mac_timer_used;
-static MacTimeout* current_timer;  // the timer that triggered its callback function
-
-static void realloc_timers()
-{
-  if (mac_timer_alloc == 0) {
-    mac_timer_alloc = 8;
-    fl_open_display(); // needed because the timer creates an event
-  }
-  mac_timer_alloc *= 2;
-  MacTimeout* new_timers = new MacTimeout[mac_timer_alloc];
-  memset(new_timers, 0, sizeof(MacTimeout)*mac_timer_alloc);
-  memcpy(new_timers, mac_timers, sizeof(MacTimeout) * mac_timer_used);
-  if (current_timer) {
-    MacTimeout* newCurrent = new_timers + (current_timer - mac_timers);
-    current_timer = newCurrent;
-  }
-  MacTimeout* delete_me = mac_timers;
-  mac_timers = new_timers;
-  delete [] delete_me;
-}
-
-static void delete_timer(MacTimeout& t)
-{
-  if (t.timer) {
-    CFRunLoopRemoveTimer(CFRunLoopGetCurrent(),
-                         t.timer,
-                         kCFRunLoopDefaultMode);
-    CFRelease(t.timer);
-    memset(&t, 0, sizeof(MacTimeout));
-  }
-}
-
-static void do_timer(CFRunLoopTimerRef timer, void* data)
-{
-  fl_lock_function();
-  fl_intptr_t timerId = (fl_intptr_t)data;
-  current_timer = &mac_timers[timerId];
-  current_timer->pending = 0;
-  (current_timer->callback)(current_timer->data);
-  if (current_timer && current_timer->pending == 0)
-    delete_timer(*current_timer);
-  current_timer = NULL;
-
-  Fl_Cocoa_Screen_Driver::breakMacEventLoop();
-  fl_unlock_function();
-}
-
-void Fl_Cocoa_Screen_Driver::add_timeout(double time, Fl_Timeout_Handler cb, void* data)
-{
-  // always create a new timer entry
-  fl_intptr_t timer_id = -1;
-  // find an empty slot in the timer array
-  for (int i = 0; i < mac_timer_used; ++i) {
-    if ( !mac_timers[i].timer ) {
-      timer_id = i;
-      break;
-    }
-  }
-  // if there was no empty slot, append a new timer
-  if (timer_id == -1) {
-    // make space if needed
-    if (mac_timer_used == mac_timer_alloc) {
-      realloc_timers();
-    }
-    timer_id = mac_timer_used++;
-  }
-  // now install a brand new timer
-  MacTimeout& t = mac_timers[timer_id];
-  CFRunLoopTimerContext context = {0, (void*)timer_id, NULL,NULL,NULL};
-  CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault,
-                                                    CFAbsoluteTimeGetCurrent() + time,
-                                                    1E30,
-                                                    0,
-                                                    0,
-                                                    do_timer,
-                                                    &context
-                                                    );
-  if (timerRef) {
-    CFRunLoopAddTimer(CFRunLoopGetCurrent(),
-                      timerRef,
-                      kCFRunLoopDefaultMode);
-    t.callback = cb;
-    t.data     = data;
-    t.timer    = timerRef;
-    t.pending  = 1;
-    t.next_timeout = CFRunLoopTimerGetNextFireDate(timerRef);
-  }
-}
-
-void Fl_Cocoa_Screen_Driver::repeat_timeout(double time, Fl_Timeout_Handler cb, void* data)
-{
-  if (!current_timer) {
-    add_timeout(time, cb, data);
-    return;
-  }
-  // k = how many times 'time' seconds after the last scheduled timeout until the future
-  double k = ceil( (CFAbsoluteTimeGetCurrent() - current_timer->next_timeout) / time);
-  if (k < 1) k = 1;
-  current_timer->next_timeout += k * time;
-  CFRunLoopTimerSetNextFireDate(current_timer->timer, current_timer->next_timeout );
-  current_timer->callback = cb;
-  current_timer->data = data;
-  current_timer->pending = 1;
-}
-
-int Fl_Cocoa_Screen_Driver::has_timeout(Fl_Timeout_Handler cb, void* data)
-{
-  for (int i = 0; i < mac_timer_used; ++i) {
-    MacTimeout& t = mac_timers[i];
-    if (t.callback == cb  &&  t.data == data && t.pending) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-void Fl_Cocoa_Screen_Driver::remove_timeout(Fl_Timeout_Handler cb, void* data)
-{
-  for (int i = 0; i < mac_timer_used; ++i) {
-    MacTimeout& t = mac_timers[i];
-    if (t.callback == cb  && ( t.data == data || data == NULL)) {
-      delete_timer(t);
-    }
-  }
-}
diff --git src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.H src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.H
index 9bd8485..c621f7e 100644
--- src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.H
+++ src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.H
@@ -1,8 +1,7 @@
 //
-// Definition of Windows screen interface
-// for the Fast Light Tool Kit (FLTK).
+// Windows screen interface for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 2010-2018 by Bill Spitzak and others.
+// Copyright 2010-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -67,11 +66,6 @@ public:
   virtual void grab(Fl_Window* win);
   // --- global colors
   virtual void get_system_colors();
-  // --- global timers
-  virtual void add_timeout(double time, Fl_Timeout_Handler cb, void *argp);
-  virtual void repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp);
-  virtual int has_timeout(Fl_Timeout_Handler cb, void *argp);
-  virtual void remove_timeout(Fl_Timeout_Handler cb, void *argp);
   virtual int dnd(int unused);
   virtual int compose(int &del);
   virtual Fl_RGB_Image *read_win_rectangle(int X, int Y, int w, int h, Fl_Window *win, bool may_capture_subwins, bool *did_capture_subwins);
diff --git src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.cxx src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.cxx
index 5747f16..489e7fe 100644
--- src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.cxx
+++ src/drivers/WinAPI/Fl_WinAPI_Screen_Driver.cxx
@@ -1,7 +1,7 @@
 //
 // Windows screen interface for the Fast Light Tool Kit (FLTK).
 //
-// Copyright 1998-2018 by Bill Spitzak and others.
+// Copyright 1998-2022 by Bill Spitzak and others.
 //
 // This library is free software. Distribution and use rights are outlined in
 // the file "COPYING" which should have been included with this file.  If this
@@ -328,149 +328,6 @@ void Fl_WinAPI_Screen_Driver::get_system_colors()
 }
 
 
-// ---- timers
-
-
-struct Win32Timer
-{
-  UINT_PTR handle;
-  Fl_Timeout_Handler callback;
-  void *data;
-};
-static Win32Timer* win32_timers;
-static int win32_timer_alloc;
-static int win32_timer_used;
-static HWND s_TimerWnd;
-
-
-static void realloc_timers()
-{
-  if (win32_timer_alloc == 0) {
-    win32_timer_alloc = 8;
-  }
-  win32_timer_alloc *= 2;
-  Win32Timer* new_timers = new Win32Timer[win32_timer_alloc];
-  memset(new_timers, 0, sizeof(Win32Timer) * win32_timer_used);
-  memcpy(new_timers, win32_timers, sizeof(Win32Timer) * win32_timer_used);
-  Win32Timer* delete_me = win32_timers;
-  win32_timers = new_timers;
-  delete [] delete_me;
-}
-
-
-static void delete_timer(Win32Timer& t)
-{
-  KillTimer(s_TimerWnd, t.handle);
-  memset(&t, 0, sizeof(Win32Timer));
-}
-
-
-static LRESULT CALLBACK s_TimerProc(HWND hwnd, UINT msg,
-                                    WPARAM wParam, LPARAM lParam)
-{
-  switch (msg) {
-    case WM_TIMER:
-    {
-      unsigned int id = (unsigned) (wParam - 1);
-      if (id < (unsigned int)win32_timer_used && win32_timers[id].handle) {
-        Fl_Timeout_Handler cb   = win32_timers[id].callback;
-        void*              data = win32_timers[id].data;
-        delete_timer(win32_timers[id]);
-        if (cb) {
-          (*cb)(data);
-        }
-      }
-    }
-      return 0;
-
-    default:
-      break;
-  }
-  return DefWindowProc(hwnd, msg, wParam, lParam);
-}
-
-
-void Fl_WinAPI_Screen_Driver::add_timeout(double time, Fl_Timeout_Handler cb, void* data)
-{
-  repeat_timeout(time, cb, data);
-}
-
-
-void Fl_WinAPI_Screen_Driver::repeat_timeout(double time, Fl_Timeout_Handler cb, void* data)
-{
-  int timer_id = -1;
-  for (int i = 0;  i < win32_timer_used;  ++i) {
-    if ( !win32_timers[i].handle ) {
-      timer_id = i;
-      break;
-    }
-  }
-  if (timer_id == -1) {
-    if (win32_timer_used == win32_timer_alloc) {
-      realloc_timers();
-    }
-    timer_id = win32_timer_used++;
-  }
-  unsigned int elapsed = (unsigned int)(time * 1000);
-
-  if ( !s_TimerWnd ) {
-    const char* timer_class = "FLTimer";
-    WNDCLASSEX wc;
-    memset(&wc, 0, sizeof(wc));
-    wc.cbSize = sizeof (wc);
-    wc.style = CS_CLASSDC;
-    wc.lpfnWndProc = (WNDPROC)s_TimerProc;
-    wc.hInstance = fl_display;
-    wc.lpszClassName = timer_class;
-    /*ATOM atom =*/ RegisterClassEx(&wc);
-    // create a zero size window to handle timer events
-    s_TimerWnd = CreateWindowEx(WS_EX_LEFT | WS_EX_TOOLWINDOW,
-                                timer_class, "",
-                                WS_POPUP,
-                                0, 0, 0, 0,
-                                NULL, NULL, fl_display, NULL);
-    // just in case this OS won't let us create a 0x0 size window:
-    if (!s_TimerWnd)
-      s_TimerWnd = CreateWindowEx(WS_EX_LEFT | WS_EX_TOOLWINDOW,
-                                  timer_class, "",
-                                  WS_POPUP,
-                                  0, 0, 1, 1,
-                                  NULL, NULL, fl_display, NULL);
-    ShowWindow(s_TimerWnd, SW_SHOWNOACTIVATE);
-  }
-
-  win32_timers[timer_id].callback = cb;
-  win32_timers[timer_id].data     = data;
-
-  win32_timers[timer_id].handle =
-  SetTimer(s_TimerWnd, timer_id + 1, elapsed, NULL);
-}
-
-
-int Fl_WinAPI_Screen_Driver::has_timeout(Fl_Timeout_Handler cb, void* data)
-{
-  for (int i = 0;  i < win32_timer_used;  ++i) {
-    Win32Timer& t = win32_timers[i];
-    if (t.handle  &&  t.callback == cb  &&  t.data == data) {
-      return 1;
-    }
-  }
-  return 0;
-}
-
-
-void Fl_WinAPI_Screen_Driver::remove_timeout(Fl_Timeout_Handler cb, void* data)
-{
-  int i;
-  for (i = 0;  i < win32_timer_used;  ++i) {
-    Win32Timer& t = win32_timers[i];
-    if (t.handle  &&  t.callback == cb  &&
-        (t.data == data  ||  data == NULL)) {
-      delete_timer(t);
-    }
-  }
-}
-
 int Fl_WinAPI_Screen_Driver::compose(int &del) {
   unsigned char ascii = (unsigned char)Fl::e_text[0];
   int condition = (Fl::e_state & (FL_ALT | FL_META)) && !(ascii & 128) ;
diff --git src/drivers/X11/Fl_X11_Screen_Driver.H src/drivers/X11/Fl_X11_Screen_Driver.H
index 7d665db..c8676f3 100644
--- src/drivers/X11/Fl_X11_Screen_Driver.H
+++ src/drivers/X11/Fl_X11_Screen_Driver.H
@@ -85,11 +85,6 @@ public:
   virtual int parse_color(const char* p, uchar& r, uchar& g, uchar& b);
   virtual void get_system_colors();
   virtual const char *get_system_scheme();
-  // --- global timers
-  virtual void add_timeout(double time, Fl_Timeout_Handler cb, void *argp);
-  virtual void repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp);
-  virtual int has_timeout(Fl_Timeout_Handler cb, void *argp);
-  virtual void remove_timeout(Fl_Timeout_Handler cb, void *argp);
   virtual int dnd(int unused);
   virtual int compose(int &del);
   virtual void compose_reset();
diff --git src/drivers/X11/Fl_X11_Screen_Driver.cxx src/drivers/X11/Fl_X11_Screen_Driver.cxx
index beb30df..3e7edf8 100644
--- src/drivers/X11/Fl_X11_Screen_Driver.cxx
+++ src/drivers/X11/Fl_X11_Screen_Driver.cxx
@@ -31,6 +31,8 @@
 #include <FL/filename.H>
 #include <sys/time.h>
 
+#include "../../Fl_Timeout.h"
+
 #if HAVE_XINERAMA
 #  include <X11/extensions/Xinerama.h>
 #endif
@@ -55,52 +57,6 @@ extern const char *fl_bg;
 extern const char *fl_bg2;
 // end of extern additions workaround
 
-//
-// X11 timers
-//
-
-
-////////////////////////////////////////////////////////////////////////
-// Timeouts are stored in a sorted list (*first_timeout), so only the
-// first one needs to be checked to see if any should be called.
-// Allocated, but unused (free) Timeout structs are stored in another
-// linked list (*free_timeout).
-
-struct Timeout {
-  double time;
-  void (*cb)(void*);
-  void* arg;
-  Timeout* next;
-};
-static Timeout* first_timeout, *free_timeout;
-
-// I avoid the overhead of getting the current time when we have no
-// timeouts by setting this flag instead of getting the time.
-// In this case calling elapse_timeouts() does nothing, but records
-// the current time, and the next call will actually elapse time.
-static char reset_clock = 1;
-
-static void elapse_timeouts() {
-  static struct timeval prevclock;
-  struct timeval newclock;
-  gettimeofday(&newclock, NULL);
-  double elapsed = newclock.tv_sec - prevclock.tv_sec +
-    (newclock.tv_usec - prevclock.tv_usec)/1000000.0;
-  prevclock.tv_sec = newclock.tv_sec;
-  prevclock.tv_usec = newclock.tv_usec;
-  if (reset_clock) {
-    reset_clock = 0;
-  } else if (elapsed > 0) {
-    for (Timeout* t = first_timeout; t; t = t->next) t->time -= elapsed;
-  }
-}
-
-
-// Continuously-adjusted error value, this is a number <= 0 for how late
-// we were at calling the last timeout. This appears to make repeat_timeout
-// very accurate even when processing takes a significant portion of the
-// time interval:
-static double missed_timeout_by;
 
 /**
  Creates a driver that manages all screen and display related calls.
@@ -411,39 +367,6 @@ void Fl_X11_Screen_Driver::flush()
 
 double Fl_X11_Screen_Driver::wait(double time_to_wait)
 {
-  static char in_idle;
-
-  if (first_timeout) {
-    elapse_timeouts();
-    Timeout *t;
-    while ((t = first_timeout)) {
-      if (t->time > 0) break;
-      // The first timeout in the array has expired.
-      missed_timeout_by = t->time;
-      // We must remove timeout from array before doing the callback:
-      void (*cb)(void*) = t->cb;
-      void *argp = t->arg;
-      first_timeout = t->next;
-      t->next = free_timeout;
-      free_timeout = t;
-      // Now it is safe for the callback to do add_timeout:
-      cb(argp);
-    }
-  } else {
-    reset_clock = 1; // we are not going to check the clock
-  }
-  Fl::run_checks();
-  if (Fl::idle) {
-    if (!in_idle) {
-      in_idle = 1;
-      Fl::idle();
-      in_idle = 0;
-    }
-    // the idle function may turn off idle, we can then wait:
-    if (Fl::idle) time_to_wait = 0.0;
-  }
-  if (first_timeout && first_timeout->time < time_to_wait)
-    time_to_wait = first_timeout->time;
   if (time_to_wait <= 0.0) {
     // do flush second so that the results of events are visible:
     int ret = this->poll_or_select_with_delay(0.0);
@@ -452,25 +375,20 @@ double Fl_X11_Screen_Driver::wait(double time_to_wait)
   } else {
     // do flush first so that user sees the display:
     Fl::flush();
-    if (Fl::idle && !in_idle) // 'idle' may have been set within flush()
+    if (Fl::idle) // 'idle' may have been set within flush()
       time_to_wait = 0.0;
-    else if (first_timeout && first_timeout->time < time_to_wait) {
-      // another timeout may have been queued within flush(), see STR #3188
-      time_to_wait = first_timeout->time >= 0.0 ? first_timeout->time : 0.0;
+    else {
+      Fl_Timeout::elapse_timeouts();
+      time_to_wait = Fl_Timeout::time_to_wait(time_to_wait);
     }
     return this->poll_or_select_with_delay(time_to_wait);
   }
 }
 
 
-int Fl_X11_Screen_Driver::ready()
-{
-  if (first_timeout) {
-    elapse_timeouts();
-    if (first_timeout->time <= 0) return 1;
-  } else {
-    reset_clock = 1;
-  }
+int Fl_X11_Screen_Driver::ready() {
+  Fl_Timeout::elapse_timeouts();
+  if (Fl_Timeout::time_to_wait(1.0) <= 0.0) return 1;
   return this->poll_or_select();
 }
 
@@ -598,67 +516,6 @@ const char *Fl_X11_Screen_Driver::get_system_scheme()
   return s;
 }
 
-// ######################   *FIXME*   ########################
-// ######################   *FIXME*   ########################
-// ######################   *FIXME*   ########################
-
-
-//
-// X11 timers
-//
-
-void Fl_X11_Screen_Driver::add_timeout(double time, Fl_Timeout_Handler cb, void *argp) {
-  elapse_timeouts();
-  missed_timeout_by = 0;
-  repeat_timeout(time, cb, argp);
-}
-
-void Fl_X11_Screen_Driver::repeat_timeout(double time, Fl_Timeout_Handler cb, void *argp) {
-  time += missed_timeout_by; if (time < -.05) time = 0;
-  Timeout* t = free_timeout;
-  if (t) {
-      free_timeout = t->next;
-  } else {
-      t = new Timeout;
-  }
-  t->time = time;
-  t->cb = cb;
-  t->arg = argp;
-  // insert-sort the new timeout:
-  Timeout** p = &first_timeout;
-  while (*p && (*p)->time <= time) p = &((*p)->next);
-  t->next = *p;
-  *p = t;
-}
-
-/**
-  Returns true if the timeout exists and has not been called yet.
-*/
-int Fl_X11_Screen_Driver::has_timeout(Fl_Timeout_Handler cb, void *argp) {
-  for (Timeout* t = first_timeout; t; t = t->next)
-    if (t->cb == cb && t->arg == argp) return 1;
-  return 0;
-}
-
-/**
-  Removes a timeout callback. It is harmless to remove a timeout
-  callback that no longer exists.
-
-  \note This version removes all matching timeouts, not just the first one.
-        This may change in the future.
-*/
-void Fl_X11_Screen_Driver::remove_timeout(Fl_Timeout_Handler cb, void *argp) {
-  for (Timeout** p = &first_timeout; *p;) {
-    Timeout* t = *p;
-    if (t->cb == cb && (t->arg == argp || !argp)) {
-      *p = t->next;
-      t->next = free_timeout;
-      free_timeout = t;
-    } else {
-      p = &(t->next);
-    }
-  }
-}
 
 int Fl_X11_Screen_Driver::compose(int& del) {
   int condition;
Direct Link to Message ]
 
     
Previous Message ]Next Message ]
 
 

Comments are owned by the poster. All other content is copyright 1998-2024 by Bill Spitzak and others. This project is hosted by The FLTK Team. Please report site problems to 'erco@seriss.com'.