STR 2636. Add ability to get notifications whenever the

clipboard changes.


git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@9974 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
This commit is contained in:
Pierre Ossman 2013-09-11 12:54:40 +00:00
parent f410352c7c
commit 1d6cc80d59
10 changed files with 385 additions and 7 deletions

View File

@ -513,6 +513,20 @@ else()
set(FLTK_XINERAMA_FOUND FALSE)
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)

16
FL/Fl.H
View File

@ -109,6 +109,9 @@ typedef int (*Fl_Args_Handler)(int argc, char **argv, int &i);
\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 */
@ -749,6 +752,19 @@ public:
copy/cut/paste operations.
*/
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

View File

@ -107,6 +107,14 @@
#define USE_XDBE HAVE_XDBE
/*
* HAVE_XFIXES:
*
* Do we have the X fixes extension?
*/
#cmakedefine01 HAVE_XFIXES
/*
* __APPLE_QUARTZ__:
*

View File

@ -107,6 +107,14 @@
#define USE_XDBE HAVE_XDBE
/*
* HAVE_XFIXES:
*
* Do we have the X fixes extension?
*/
#define HAVE_XFIXES 0
/*
* __APPLE_QUARTZ__:
*

View File

@ -995,6 +995,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,

View File

@ -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)

View File

@ -434,6 +434,70 @@ static void run_checks()
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:
@ -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);

View File

@ -90,6 +90,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);
Fl_Display_Device *Fl_Display_Device::_display = new Fl_Display_Device(new Fl_Quartz_Graphics_Driver); // the platform display
@ -1109,16 +1110,21 @@ static void cocoaMouseHandler(NSEvent *theEvent)
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;
@ -2720,6 +2726,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

View File

@ -634,6 +634,38 @@ void Fl::paste(Fl_Widget &receiver, int clipboard) {
}
}
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 hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
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) {

View File

@ -53,6 +53,12 @@ static XRRUpdateConfiguration_type XRRUpdateConfiguration_f;
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;
@ -608,6 +617,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);
@ -655,6 +667,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);
@ -842,6 +863,107 @@ void Fl::copy(const char *stuff, int len, int clipboard) {
XSetSelectionOwner(fl_display, property, fl_message_window, fl_event_time);
}
////////////////////////////////////////////////////////////////
// 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
@ -1005,7 +1127,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;
@ -1021,6 +1142,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++) {
@ -1061,6 +1195,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;
@ -1081,6 +1218,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: {
@ -1295,6 +1433,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:
@ -1663,6 +1804,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);
}
@ -1982,6 +2142,16 @@ void Fl_X::make_xid(Fl_Window* win, XVisualInfo *visual, Colormap colormap)
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();