STR 2636. Add ability to get notifications whenever the
clipboard changes.

Index: fltk-1.3.2/configh.in
===================================================================
--- fltk-1.3.2.orig/configh.in
+++ fltk-1.3.2/configh.in
@@ -108,6 +108,14 @@
 #define USE_XDBE HAVE_XDBE
 
 /*
+ * HAVE_XFIXES:
+ *
+ * Do we have the X fixes extension?
+ */
+
+#define HAVE_XFIXES 0
+
+/*
  * __APPLE_QUARTZ__:
  *
  * All Apple implementations are now based on Quartz and Cocoa,
Index: fltk-1.3.2/src/CMakeLists.txt
===================================================================
--- fltk-1.3.2.orig/src/CMakeLists.txt
+++ fltk-1.3.2/src/CMakeLists.txt
@@ -239,6 +239,10 @@ if(HAVE_XINERAMA)
    target_link_libraries(fltk ${X11_Xinerama_LIB})
 endif(HAVE_XINERAMA)
 
+if(HAVE_XFIXES)
+   target_link_libraries(fltk ${X11_Xfixes_LIB})
+endif(HAVE_XFIXES)
+
 if(USE_XFT)
    target_link_libraries(fltk ${X11_Xft_LIB})
 endif(USE_XFT)
Index: fltk-1.3.2/src/Fl.cxx
===================================================================
--- fltk-1.3.2.orig/src/Fl.cxx
+++ fltk-1.3.2/src/Fl.cxx
@@ -435,6 +435,70 @@ static char in_idle;
 #endif
 
 ////////////////////////////////////////////////////////////////
+// Clipboard notifications
+
+struct Clipboard_Notify {
+  Fl_Clipboard_Notify_Handler handler;
+  void *data;
+  struct Clipboard_Notify *next;
+};
+
+static struct Clipboard_Notify *clip_notify_list = NULL;
+
+extern void fl_clipboard_notify_change(); // in Fl_<platform>.cxx
+
+void Fl::add_clipboard_notify(Fl_Clipboard_Notify_Handler h, void *data) {
+  struct Clipboard_Notify *node;
+
+  remove_clipboard_notify(h);
+
+  node = new Clipboard_Notify;
+
+  node->handler = h;
+  node->data = data;
+  node->next = clip_notify_list;
+
+  clip_notify_list = node;
+
+  fl_clipboard_notify_change();
+}
+
+void Fl::remove_clipboard_notify(Fl_Clipboard_Notify_Handler h) {
+  struct Clipboard_Notify *node, **prev;
+
+  node = clip_notify_list;
+  prev = &clip_notify_list;
+  while (node != NULL) {
+    if (node->handler == h) {
+      *prev = node->next;
+      delete node;
+
+      fl_clipboard_notify_change();
+
+      return;
+    }
+
+    prev = &node->next;
+    node = node->next;
+  }
+}
+
+bool fl_clipboard_notify_empty(void) {
+  return clip_notify_list == NULL;
+}
+
+void fl_trigger_clipboard_notify(int source) {
+  struct Clipboard_Notify *node, *next;
+
+  node = clip_notify_list;
+  while (node != NULL) {
+    next = node->next;
+    node->handler(source, node->data);
+    node = next;
+  }
+}
+
+////////////////////////////////////////////////////////////////
 // wait/run/check/ready:
 
 void (*Fl::idle)(); // see Fl::add_idle.cxx for the add/remove functions
@@ -1358,6 +1422,7 @@ int Fl::handle_(int e, Fl_Window* window
 // hide() destroys the X window, it does not do unmap!
 
 #if defined(WIN32)
+extern void fl_clipboard_notify_untarget(HWND wnd);
 extern void fl_update_clipboard(void);
 #elif USE_XFT
 extern void fl_destroy_xft_draw(Window);
@@ -1408,6 +1473,8 @@ void Fl_Window::hide() {
   // to destroy the window that owns the selection.
   if (GetClipboardOwner()==ip->xid)
     fl_update_clipboard();
+  // Make sure we unlink this window from the clipboard chain
+  fl_clipboard_notify_untarget(ip->xid);
   // Send a message to myself so that I'll get out of the event loop...
   PostMessage(ip->xid, WM_APP, 0, 0);
   if (ip->private_dc) fl_release_dc(ip->xid, ip->private_dc);
Index: fltk-1.3.2/src/Fl_cocoa.mm
===================================================================
--- fltk-1.3.2.orig/src/Fl_cocoa.mm
+++ fltk-1.3.2/src/Fl_cocoa.mm
@@ -88,6 +88,7 @@ static void createAppleMenu(void);
 static Fl_Region MacRegionMinusRect(Fl_Region r, int x,int y,int w,int h);
 static void cocoaMouseHandler(NSEvent *theEvent);
 static int calc_mac_os_version();
+static void clipboard_check(void);
 
 static Fl_Quartz_Graphics_Driver fl_quartz_driver;
 static Fl_Display_Device fl_quartz_display(&fl_quartz_driver);
@@ -1128,16 +1129,21 @@ void fl_open_callback(void (*cb)(const c
   fl_unlock_function();
   return reply;
 }
-/**
- * Cocoa organizes the Z depth of windows on a global priority. FLTK however
- * expects the window manager to organize Z level by application. The trickery
- * below will change Z order during activation and deactivation.
- */
 - (void)applicationDidBecomeActive:(NSNotification *)notify
 {
-  fl_lock_function();
   Fl_X *x;
   FLWindow *top = 0, *topModal = 0, *topNonModal = 0;
+
+  fl_lock_function();
+
+  // update clipboard status
+  clipboard_check();
+
+  /**
+   * Cocoa organizes the Z depth of windows on a global priority. FLTK however
+   * expects the window manager to organize Z level by application. The trickery
+   * below will change Z order during activation and deactivation.
+   */
   for (x = Fl_X::first;x;x = x->next) {
     FLWindow *cw = x->xid;
     Fl_Window *win = x->w;
@@ -2535,6 +2541,26 @@ static void allocatePasteboard() {
     PasteboardCreate(kPasteboardClipboard, &myPasteboard);
 }
 
+extern void fl_trigger_clipboard_notify(int source);
+
+void fl_clipboard_notify_change() {
+  // No need to do anything here...
+}
+
+static void clipboard_check(void)
+{
+  PasteboardSyncFlags flags;
+
+  allocatePasteboard();
+  flags = PasteboardSynchronize(myPasteboard);
+
+  if (!(flags & kPasteboardModified))
+    return;
+  if (flags & kPasteboardClientIsOwner)
+    return;
+
+  fl_trigger_clipboard_notify(1);
+}
 
 /*
  * create a selection
Index: fltk-1.3.2/src/Fl_win32.cxx
===================================================================
--- fltk-1.3.2.orig/src/Fl_win32.cxx
+++ fltk-1.3.2/src/Fl_win32.cxx
@@ -634,6 +634,38 @@ void Fl::paste(Fl_Widget &receiver, int
   }
 }
 
+static HWND clipboard_wnd = 0;
+static HWND next_clipboard_wnd = 0;
+
+static bool initial_clipboard = true;
+
+void fl_clipboard_notify_change() {
+  // No need to do anything here...
+}
+
+void fl_clipboard_notify_target(HWND wnd) {
+  if (clipboard_wnd)
+    return;
+
+  // We get one fake WM_DRAWCLIPBOARD immediately, which we therefore
+  // need to ignore.
+  initial_clipboard = true;
+
+  clipboard_wnd = wnd;
+  next_clipboard_wnd = SetClipboardViewer(wnd);
+}
+
+void fl_clipboard_notify_untarget(HWND wnd) {
+  if (wnd != clipboard_wnd)
+    return;
+
+  ChangeClipboardChain(wnd, next_clipboard_wnd);
+  clipboard_wnd = next_clipboard_wnd = 0;
+
+  if (Fl::first_window())
+    fl_clipboard_notify_target(fl_xid(Fl::first_window()));
+}
+
 ////////////////////////////////////////////////////////////////
 char fl_is_ime = 0;
 void fl_get_codepage()
@@ -1211,6 +1243,27 @@ static LRESULT CALLBACK WndProc(HWND hWn
     Fl::handle(FL_SCREEN_CONFIGURATION_CHANGED, NULL);
     return 0;
 
+  case WM_CHANGECBCHAIN:
+    if ((hWnd == clipboard_wnd) &&
+        (next_clipboard_wnd == (HWND)wParam)) {
+      next_clipboard_wnd = (HWND)lParam;
+      return 0;
+    }
+    break;
+
+  case WM_DRAWCLIPBOARD:
+    // When the clipboard moves between two FLTK windows,
+    // fl_i_own_selection will temporarily be false as we are
+    // processing this message. Hence the need to use fl_find().
+    if (!initial_clipboard && !fl_find(GetClipboardOwner()))
+      fl_trigger_clipboard_notify(1);
+    initial_clipboard = false;
+
+    if (next_clipboard_wnd)
+      SendMessage(next_clipboard_wnd, WM_DRAWCLIPBOARD, wParam, lParam);
+
+    return 0;
+
   default:
     if (Fl::handle(0,0)) return 0;
     break;
@@ -1647,6 +1700,8 @@ Fl_X* Fl_X::make(Fl_Window* w) {
   x->next = Fl_X::first;
   Fl_X::first = x;
 
+  fl_clipboard_notify_target(x->xid);
+
   x->wait_for_expose = 1;
   if (fl_show_iconic) {showit = 0; fl_show_iconic = 0;}
   if (showit) {
Index: fltk-1.3.2/src/Fl_x.cxx
===================================================================
--- fltk-1.3.2.orig/src/Fl_x.cxx
+++ fltk-1.3.2/src/Fl_x.cxx
@@ -53,6 +53,12 @@ static XRRUpdateConfiguration_type XRRUp
 static int randrEventBase;                  // base of RandR-defined events
 #endif
 
+#  if HAVE_XFIXES
+#  include <X11/extensions/Xfixes.h>
+static int xfixes_event_base = 0;
+static bool have_xfixes = false;
+#  endif
+
 static Fl_Xlib_Graphics_Driver fl_xlib_driver;
 static Fl_Display_Device fl_xlib_display(&fl_xlib_driver);
 Fl_Display_Device *Fl_Display_Device::_display = &fl_xlib_display;// the platform display
@@ -306,6 +312,9 @@ static Atom WM_PROTOCOLS;
 static Atom fl_MOTIF_WM_HINTS;
 static Atom TARGETS;
 static Atom CLIPBOARD;
+static Atom TIMESTAMP;
+static Atom PRIMARY_TIMESTAMP;
+static Atom CLIPBOARD_TIMESTAMP;
 Atom fl_XdndAware;
 Atom fl_XdndSelection;
 Atom fl_XdndEnter;
@@ -607,6 +616,9 @@ void fl_open_display(Display* d) {
   fl_MOTIF_WM_HINTS     = XInternAtom(d, "_MOTIF_WM_HINTS",     0);
   TARGETS               = XInternAtom(d, "TARGETS",             0);
   CLIPBOARD             = XInternAtom(d, "CLIPBOARD",           0);
+  TIMESTAMP             = XInternAtom(d, "TIMESTAMP",           0);
+  PRIMARY_TIMESTAMP     = XInternAtom(d, "PRIMARY_TIMESTAMP",   0);
+  CLIPBOARD_TIMESTAMP   = XInternAtom(d, "CLIPBOARD_TIMESTAMP", 0);
   fl_XdndAware          = XInternAtom(d, "XdndAware",           0);
   fl_XdndSelection      = XInternAtom(d, "XdndSelection",       0);
   fl_XdndEnter          = XInternAtom(d, "XdndEnter",           0);
@@ -653,6 +665,15 @@ void fl_open_display(Display* d) {
 #if !USE_COLORMAP
   Fl::visual(FL_RGB);
 #endif
+
+#if HAVE_XFIXES
+  int error_base;
+  if (XFixesQueryExtension(fl_display, &xfixes_event_base, &error_base))
+    have_xfixes = true;
+  else
+    have_xfixes = false;
+#endif
+
 #if USE_XRANDR
   void *libxrandr_addr = dlopen("libXrandr.so.2", RTLD_LAZY);
   if (!libxrandr_addr)  libxrandr_addr = dlopen("libXrandr.so", RTLD_LAZY);
@@ -841,6 +862,107 @@ void Fl::copy(const char *stuff, int len
 }
 
 ////////////////////////////////////////////////////////////////
+// Code for tracking clipboard changes:
+
+static Time primary_timestamp = -1;
+static Time clipboard_timestamp = -1;
+
+extern bool fl_clipboard_notify_empty(void);
+extern void fl_trigger_clipboard_notify(int source);
+
+static void poll_clipboard_owner(void) {
+  Window xid;
+
+#if HAVE_XFIXES
+  // No polling needed with Xfixes
+  if (have_xfixes)
+    return;
+#endif
+
+  // No one is interested, so no point polling
+  if (fl_clipboard_notify_empty())
+    return;
+
+  // We need a window for this to work
+  if (!Fl::first_window())
+    return;
+  xid = fl_xid(Fl::first_window());
+  if (!xid)
+    return;
+
+  // Request an update of the selection time for both the primary and
+  // clipboard selections. Magic continues when we get a SelectionNotify.
+  if (!fl_i_own_selection[0])
+    XConvertSelection(fl_display, XA_PRIMARY, TIMESTAMP, PRIMARY_TIMESTAMP,
+                      xid, fl_event_time);
+  if (!fl_i_own_selection[1])
+    XConvertSelection(fl_display, CLIPBOARD, TIMESTAMP, CLIPBOARD_TIMESTAMP,
+                      xid, fl_event_time);
+}
+
+static void clipboard_timeout(void *data)
+{
+  // No one is interested, so stop polling
+  if (fl_clipboard_notify_empty())
+    return;
+
+  poll_clipboard_owner();
+
+  Fl::repeat_timeout(0.5, clipboard_timeout);
+}
+
+static void handle_clipboard_timestamp(int clipboard, Time time)
+{
+  Time *timestamp;
+
+  timestamp = clipboard ? &clipboard_timestamp : &primary_timestamp;
+
+#if HAVE_XFIXES
+  if (!have_xfixes)
+#endif
+  {
+    // Initial scan, just store the value
+    if (*timestamp == (Time)-1) {
+      *timestamp = time;
+      return;
+    }
+  }
+
+  // Same selection
+  if (time == *timestamp)
+    return;
+
+  *timestamp = time;
+
+  // The clipboard change is the event that caused us to request
+  // the clipboard data, so use that time as the latest event.
+  if (time > fl_event_time)
+    fl_event_time = time;
+
+  // Something happened! Let's tell someone!
+  fl_trigger_clipboard_notify(clipboard);
+}
+
+void fl_clipboard_notify_change() {
+  // Reset the timestamps if we've going idle so that you don't
+  // get a bogus immediate trigger next time they're activated.
+  if (fl_clipboard_notify_empty()) {
+    primary_timestamp = -1;
+    clipboard_timestamp = -1;
+  } else {
+#if HAVE_XFIXES
+    if (!have_xfixes)
+#endif
+    {
+      poll_clipboard_owner();
+
+      if (!Fl::has_timeout(clipboard_timeout))
+        Fl::add_timeout(0.5, clipboard_timeout);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////
 
 const XEvent* fl_xevent; // the current x event
 ulong fl_event_time; // the last timestamp from an x event
@@ -1003,7 +1125,6 @@ int fl_handle(const XEvent& thisevent)
     return 0;
 
   case SelectionNotify: {
-    if (!fl_selection_requestor) return 0;
     static unsigned char* buffer = 0;
     if (buffer) {XFree(buffer); buffer = 0;}
     long bytesread = 0;
@@ -1019,6 +1140,19 @@ int fl_handle(const XEvent& thisevent)
                              bytesread/4, 65536, 1, 0,
                              &actual, &format, &count, &remaining,
                              &portion)) break; // quit on error
+
+      if ((fl_xevent->xselection.property == PRIMARY_TIMESTAMP) ||
+          (fl_xevent->xselection.property == CLIPBOARD_TIMESTAMP)) {
+        if (portion && format == 32 && count == 1) {
+          Time t = *(unsigned int*)portion;
+          if (fl_xevent->xselection.property == CLIPBOARD_TIMESTAMP)
+            handle_clipboard_timestamp(1, t);
+          else
+            handle_clipboard_timestamp(0, t);
+        }
+        return true;
+      }
+
       if (actual == TARGETS || actual == XA_ATOM) {
 	Atom type = XA_STRING;
 	for (unsigned i = 0; i<count; i++) {
@@ -1055,6 +1189,9 @@ int fl_handle(const XEvent& thisevent)
       buffer[bytesread] = 0;
       convert_crlf(buffer, bytesread);
     }
+
+    if (!fl_selection_requestor) return 0;
+
     Fl::e_text = buffer ? (char*)buffer : (char *)"";
     Fl::e_length = bytesread;
     int old_event = Fl::e_number;
@@ -1075,6 +1212,7 @@ int fl_handle(const XEvent& thisevent)
   case SelectionClear: {
     int clipboard = fl_xevent->xselectionclear.selection == CLIPBOARD;
     fl_i_own_selection[clipboard] = 0;
+    poll_clipboard_owner();
     return 1;}
 
   case SelectionRequest: {
@@ -1287,6 +1425,9 @@ int fl_handle(const XEvent& thisevent)
   case FocusIn:
     if (fl_xim_ic) XSetICFocus(fl_xim_ic);
     event = FL_FOCUS;
+    // If the user has toggled from another application to this one,
+    // then it's a good time to check for clipboard changes.
+    poll_clipboard_owner();
     break;
 
   case FocusOut:
@@ -1655,6 +1796,25 @@ int fl_handle(const XEvent& thisevent)
     }
   }
 
+#if HAVE_XFIXES
+  switch (xevent.type - xfixes_event_base) {
+  case XFixesSelectionNotify: {
+    // Someone feeding us bogus events?
+    if (!have_xfixes)
+      return true;
+
+    XFixesSelectionNotifyEvent *selection_notify = (XFixesSelectionNotifyEvent *)&xevent;
+
+    if ((selection_notify->selection == XA_PRIMARY) && !fl_i_own_selection[0])
+      handle_clipboard_timestamp(0, selection_notify->selection_timestamp);
+    else if ((selection_notify->selection == CLIPBOARD) && !fl_i_own_selection[1])
+      handle_clipboard_timestamp(1, selection_notify->selection_timestamp);
+
+    return true;
+    }
+  }
+#endif
+
   return Fl::handle(event, window);
 }
 
@@ -1974,6 +2134,16 @@ void Fl_X::make_xid(Fl_Window* win, XVis
     XChangeProperty(fl_display, xp->xid, net_wm_type, XA_ATOM, 32, PropModeReplace, (unsigned char*)&net_wm_type_kind, 1);
   }
 
+#if HAVE_XFIXES
+  // register for clipboard change notifications
+  if (have_xfixes && !win->parent()) {
+    XFixesSelectSelectionInput(fl_display, xp->xid, XA_PRIMARY,
+                               XFixesSetSelectionOwnerNotifyMask);
+    XFixesSelectSelectionInput(fl_display, xp->xid, CLIPBOARD,
+                               XFixesSetSelectionOwnerNotifyMask);
+  }
+#endif
+
   XMapWindow(fl_display, xp->xid);
   if (showit) {
     win->set_visible();
Index: fltk-1.3.2/configh.cmake.in
===================================================================
--- fltk-1.3.2.orig/configh.cmake.in
+++ fltk-1.3.2/configh.cmake.in
@@ -108,6 +108,14 @@
 #define USE_XDBE HAVE_XDBE
 
 /*
+ * HAVE_XFIXES:
+ *
+ * Do we have the X fixes extension?
+ */
+
+#cmakedefine01 HAVE_XFIXES
+
+/*
  * __APPLE_QUARTZ__:
  *
  * If __APPLE_QUARTZ__ is defined, FLTK will be
Index: fltk-1.3.2/FL/Fl.H
===================================================================
--- fltk-1.3.2.orig/FL/Fl.H
+++ fltk-1.3.2/FL/Fl.H
@@ -109,6 +109,9 @@ typedef int (*Fl_Args_Handler)(int argc,
     \see Fl::event_dispatch(Fl_Event_Dispatch) */
 typedef int (*Fl_Event_Dispatch)(int event, Fl_Window *w);
 
+/** Signature of add_clipboard_notify functions passed as parameters */
+typedef void (*Fl_Clipboard_Notify_Handler)(int source, void *data);
+
 /** @} */ /* group callback_functions */
 
 
@@ -746,6 +749,19 @@ public:
   */
   static void paste(Fl_Widget &receiver, int source /*=0*/); // platform dependent
   /**
+  FLTK will call the registered callback whenever there is a change to the
+  selection buffer or the clipboard. The source argument indicates which
+  of the two has changed. Only changes by other applications are reported.
+  \note Some systems require polling to monitor the clipboard and may
+  therefore have some delay in detecting changes.
+  */
+  static void add_clipboard_notify(Fl_Clipboard_Notify_Handler h, void *data = 0);
+  /**
+  Stop calling the specified callback when there are changes to the selection
+  buffer or the clipboard.
+  */
+  static void remove_clipboard_notify(Fl_Clipboard_Notify_Handler h);
+  /**
     Initiate a Drag And Drop operation. The selection buffer should be
     filled with relevant data before calling this method. FLTK will
     then initiate the system wide drag and drop handling. Dropped data
Index: fltk-1.3.2/CMakeLists.txt
===================================================================
--- fltk-1.3.2.orig/CMakeLists.txt
+++ fltk-1.3.2/CMakeLists.txt
@@ -515,6 +515,20 @@ else()
 endif(OPTION_USE_XINERAMA)
 
 #######################################################################
+if(X11_Xfixes_FOUND)
+   option(OPTION_USE_XFIXES "use lib XFIXES" ON)
+endif(X11_Xfixes_FOUND)
+
+if(OPTION_USE_XFIXES)
+   set(HAVE_XFIXES ${X11_Xfixes_FOUND})
+   include_directories(${X11_Xfixes_INCLUDE_PATH})
+   list(APPEND FLTK_LDLIBS -lXfixes)
+   set(FLTK_XFIXES_FOUND TRUE)
+else()
+   set(FLTK_XFIXES_FOUND FALSE)
+endif(OPTION_USE_XFIXES)
+
+#######################################################################
 if(X11_Xft_FOUND)
    option(OPTION_USE_XFT "use lib Xft" ON)
 endif(X11_Xft_FOUND)
Index: fltk-1.3.2/configure.in
===================================================================
--- fltk-1.3.2.orig/configure.in
+++ fltk-1.3.2/configure.in
@@ -997,6 +997,16 @@ case $uname_GUI in
 		LIBS="-lXext $LIBS")
 	fi
 
+	dnl Check for the Xfixes extension unless disabled...
+        AC_ARG_ENABLE(xfixes, [  --enable-xfixes       turn on Xfixes support [default=yes]])
+
+	if test x$enable_xfixes != xno; then
+	    AC_CHECK_HEADER(X11/extensions/Xfixes.h, AC_DEFINE(HAVE_XFIXES),,
+	        [#include <X11/Xlib.h>])
+	    AC_CHECK_LIB(Xfixes, XFixesQueryExtension,
+		LIBS="-lXfixes $LIBS")
+	fi
+
 	dnl Check for overlay visuals...
 	AC_PATH_PROG(XPROP, xprop)
 	AC_CACHE_CHECK(for X overlay visuals, ac_cv_have_overlay,
