X11 Work Bench Toolkit  1.0
clipboard_helper.c
Go to the documentation of this file.
1 
2 // //
3 // _ _ _ _ _ _ //
4 // ___ | |(_) _ __ | |__ ___ __ _ _ __ __| | | |__ ___ | | _ __ ___ _ __ ___ //
5 // / __|| || || '_ \ | '_ \ / _ \ / _` || '__|/ _` | | '_ \ / _ \| || '_ \ / _ \| '__|/ __| //
6 // | (__ | || || |_) || |_) || (_) || (_| || | | (_| | | | | || __/| || |_) || __/| | _| (__ //
7 // \___||_||_|| .__/ |_.__/ \___/ \__,_||_| \__,_|_____|_| |_| \___||_|| .__/ \___||_|(_)\___| //
8 // |_| |_____| |_| //
9 // //
10 // internal clipboard support //
11 // //
13 
14 /*****************************************************************************
15 
16  X11workbench - X11 programmer's 'work bench' application and toolkit
17  Copyright (c) 2010-2016 by Bob Frazier (aka 'Big Bad Bombastic Bob')
18  all rights reserved
19 
20  DISCLAIMER: The X11workbench application and toolkit software are supplied
21  'as-is', with no warranties, either implied or explicit.
22  Any claims to alleged functionality or features should be
23  considered 'preliminary', and might not function as advertised.
24 
25  BSD-like license:
26 
27  There is no restriction as to what you can do with this software, so long
28  as you include the above copyright notice and DISCLAIMER for any distributed
29  work that is equal to or derived from this one, along with this paragraph
30  that explains the terms of the license if the source is also being made
31  available. A "derived work" describes a work that uses a significant portion
32  of the source files or algorithms that are included with this one.
33  Specifically excluded from this are files that were generated by the software,
34  or anything that is included with the software that is part of another package
35  (such as files that were created or added during the 'configure' process).
36  Specifically included is the use of part or all of any of the X11 workbench
37  toolkit source or header files in your distributed application. If you do not
38  ship the source, the above copyright statement is still required to be placed
39  in a reasonably prominent place, such as documentation, splash screens, and/or
40  'about the application' dialog boxes.
41 
42  Use and distribution are in accordance with GPL, LGPL, and/or the above
43  BSD-like license. See COPYING and README files for more information.
44 
45 
46  Additional information at http://sourceforge.net/projects/X11workbench
47 
48 ******************************************************************************/
49 
50 
60 #include <stdio.h>
61 #include <stdlib.h>
62 #include <stdarg.h>
63 #include <limits.h>
64 #include <unistd.h>
65 #include <memory.h>
66 #include <string.h>
67 #include <strings.h>
68 #include <signal.h>
69 #include <time.h>
70 #include <fcntl.h>
71 #include <errno.h>
72 #include <sys/time.h>
73 
74 // TODO: determine if pthread is available
75 #include <pthread.h> /* currently required */
76 
77 #define _CLIPBOARD_HELPER_C /* to exclude certain things from window_helper.h like Atoms */
78 
79 #include "window_helper.h"
80 #include "pixmap_helper.h"
81 #include "conf_help.h"
82 #include "platform_helper.h"
83 #include "draw_text.h"
84 #include "text_object.h"
85 
86 
87 typedef struct _ClipboardTask_ // only for getting data; setting data done immediately
88 {
89  volatile struct _ClipboardTask_ *pNext; // singly linked list
90 
91  int fType; // get (0) or set (1) [others reserved]
92  void *pData; // 'WBAlloc'd data when 'get' completed
93  int cbLength; // length of buffer
94  Atom aSelection; // 'selection' to grab ('None' implies "CLIPBOARD" behavior)
95  Atom aType; // data type
96  int nFormat; // format of data (0, 8, 16, 32)
97  int nRefCount; // reference count (increment before 'owning' while global lock held)
98 
99  WB_COND cond; // condition to trigger when completed (and free when 'freed')
100 
101  // state-related things
102  int fState; // flag to indicate status
103  Atom aProp; // the property associated with this task
104  WB_UINT64 ullTime; // timestamp for various timeouts (reserved)
105 
106 } CLIPBOARD_TASK;
107 
108 
109 typedef struct _ClipboardData_ // for when I own the clipboard
110 {
111  struct _ClipboardData_ *pNext; // double-link list
112 
113  Atom aSelection; // 'selection' to grab ('None' implies "CLIPBOARD" behavior)
114  Atom aType; // type of data
115  int cbLength; // length of buffer
116  int nFormat; // format of data
117 
118  // cached data
119  Window wTo; // window that I'm currently sending to [for multi-requests]
120  int fState; // flag to indicate status
121 
122 
123  char aData[1]; // actual data goes here
124 
125 } CLIPBOARD_DATA;
126 
127 
128 static WB_THREAD hClipboardThread = (WB_THREAD)INVALID_HANDLE_VALUE;
129 static volatile int bClipboardQuitFlag = 0;
130 static volatile CLIPBOARD_TASK * volatile pCBTHead = NULL; // WBAlloc'd structures
131 static CLIPBOARD_DATA *pCBDHead = NULL; // both are linked lists
132 
133 static WB_MUTEX xClipboardMutex;
134 
135 static void * ClipboardThreadProc(void *);
136 
137 
138 
139 // WBInitClipboardSystem
140 // return NON-ZERO if clipboard system is initialized properly
141 int WBInitClipboardSystem(Display *pDisplay, const char *szDisplayName)
142 {
143 char *pDisplayName;
144 
145  // TODO: check 'hClipboardThread' and/or 'bClipboardQuitFlag'
146 
147  if(!szDisplayName ||
148  (pDisplay && pDisplay != WBGetDefaultDisplay())) // for now, this param is reserved
149  {
150  szDisplayName = GetStartupDisplayName();
151  if(!szDisplayName || !*szDisplayName)
152  {
153  szDisplayName = ":0.0"; // make sure it's SOMETHING
154  }
155  }
156 
157  pDisplayName = WBCopyString(szDisplayName);
158 
159  if(!pDisplayName)
160  {
161  WB_ERROR_PRINT("ERROR: in %s - NULL display name\n",__FUNCTION__);
162 
163  return 1; // error
164  }
165 
166  if(1) // TODO: check for initialization of mutex already
167  {
168  if(WBMutexCreate(&xClipboardMutex))
169  {
170  WB_ERROR_PRINT("ERROR: in %s - Unable to create mutex\n",__FUNCTION__);
171 
172  return 2; // error
173  }
174 // else
175 // {
176 // WB_ERROR_PRINT("TEMPORARY: %s - mutex created\n",__FUNCTION__);
177 // }
178  }
179 
180  // TODO: check if clipboard thread already started?
181 
182  bClipboardQuitFlag = 1; // initial value. when it's zero, I'm started. If < 0, error
183 
184  pCBTHead = NULL;
185  pCBDHead = NULL; // make sure in both cases
186 
187  hClipboardThread = WBThreadCreate(ClipboardThreadProc, pDisplayName);
188 
189  if(hClipboardThread == (WB_THREAD)INVALID_HANDLE_VALUE)
190  {
191  WBMutexFree(&xClipboardMutex);
192 
193  return 3;
194  }
195  else
196  {
197  while(WBThreadRunning(hClipboardThread) &&
198  bClipboardQuitFlag > 0)
199  {
200  usleep(100); // wait for my thread to initialize
201  }
202 
203  WBFree(pDisplayName); // ok to free it now
204 
205  if(bClipboardQuitFlag || !WBThreadRunning(hClipboardThread))
206  {
207  WBThreadWait(hClipboardThread); // wait for thread to exit (should be already)
208 
209  hClipboardThread = (WB_THREAD)NULL; // make sure
210 
211  WBMutexFree(&xClipboardMutex);
212 
213  return 4; // error
214  }
215  }
216 
217  WB_DEBUG_PRINT(DebugLevel_Light | DebugSubSystem_Init, "INFO: %s - Clipboard Thread started\n",__FUNCTION__);
218 
219  return 0; // everything's ok
220 }
221 
222 void WBExitClipboardSystem(Display *pDisplay)
223 {
224  if(pDisplay && pDisplay != WBGetDefaultDisplay())
225  {
226  WB_ERROR_PRINT("ERROR: invalid 'pDisplay' in WBExitClipboardSystem\n");
227  return;
228  }
229 
230  if(hClipboardThread != (WB_THREAD)INVALID_HANDLE_VALUE)
231  {
232  bClipboardQuitFlag = 1;
233 
234  WBThreadWait(hClipboardThread); // wait for thread to exit
235  }
236  else
237  {
238  WB_ERROR_PRINT("ERROR: in %s - hClipboardThread == INVALID_HANDLE_VALUE\n",__FUNCTION__);
239 
240  // continue anyway to free other resources...
241  }
242 
243  hClipboardThread = (WB_THREAD)INVALID_HANDLE_VALUE; // make sure
244 
245  // while mutex owned, free up the link lists
246 
247  while(pCBTHead) // unlikely but possible
248  {
249  volatile CLIPBOARD_TASK *pT = pCBTHead;
250  pCBTHead = pCBTHead->pNext;
251 
252  // since I'm not checking the ref count a crash COULD happen here
253  // the alternative is a MEMORY LEAK. Which is worse???
254  // The fact is that the calling thread should NOT allow this to be
255  // invoked until everything waiting on the clipboard has finished, so...
256 
257  WB_ERROR_PRINT("WARNING: %s - non-null 'clipboard task' being deleted (%p), ref count %d\n",
258  __FUNCTION__, pT, pT->nRefCount);
259 
260  if(pT->fType == 0 // only for 'get'
261  && pT->pData)
262  {
263  WBFree(pT->pData);
264  }
265 
266  WBCondFree((WB_COND *)&(pT->cond)); // assume it works (and signals anything waiting with an error)
267 
268  WBFree((void *)pT);
269  }
270 
271  while(pCBDHead) // this one is more likely to have data in it
272  {
273  CLIPBOARD_DATA *pD = pCBDHead;
274  pCBDHead = pCBDHead->pNext;
275 
276  WBFree(pD); // this one is very simple
277  }
278 
279 // pCBTHead = NULL;
280 // pCBDHead = NULL; // make sure in both cases
281 
282  if(1) // TODO: check for initialization of mutex already to avoid possible crashes
283  {
284  WBMutexFree(&xClipboardMutex);
285  }
286 
287  WB_DEBUG_PRINT(DebugLevel_Light | DebugSubSystem_Init, "INFO: %s - Clipboard Thread ended\n",__FUNCTION__);
288 }
289 
290 
291 #if 0 /* uncomment this if I need the functionality; otherwise, consider removing it */
292 // event check 'predicate' proc for selection events
293 static Bool __ClipboardThreadEventPredicate(Display *pDisplay, XEvent *pEvent, XPointer arg)
294 {
295  if(pEvent &&
296  (pEvent->type == SelectionNotify ||
297  pEvent->type == SelectionClear ||
298  pEvent->type == SelectionRequest))
299  {
300  return TRUE;
301  }
302  else
303  {
304  return FALSE;
305  }
306 }
307 #endif // 0
308 
309 
310 // a few utilities placed here to make the code read better
311 
312 void CleanupDoneList(CLIPBOARD_TASK **ppDoneList)
313 {
314 CLIPBOARD_TASK *pT;
315 
316  // MUST call this with the clipboard mutex owned
317 
318  pT = *ppDoneList; // makes me go through a loop before deleting one of the tasks
319 
320  if(pT)
321  {
322  // remove the first item 'pT' from the done list
323 
324  *ppDoneList = (CLIPBOARD_TASK *)pT->pNext;
325 
326  pT->pNext = NULL;
327 
328  if(!WBInterlockedDecrement((unsigned int *)&(pT->nRefCount))) // flags a delete when ref count is zero
329  {
330  // if the ref count is higher than zero, someone else must delete this instead
331 
332  if(!pT->fType && pT->pData) // it COULD happen...
333  {
334  WBFree(pT->pData);
335  pT->pData = NULL;
336  }
337 
338  WBCondFree((WB_COND *)&(pT->cond)); // assume it works
339 
340  WBFree((void *)pT); // do this while locked
341  }
342  }
343 }
344 
345 void AddNewItemToRunList(CLIPBOARD_TASK **ppRunList)
346 {
347 CLIPBOARD_TASK *pT, *pT2;
348 
349  // next, grab an item from the 'new' queue
350  pT = (CLIPBOARD_TASK *)pCBTHead;
351 
352  if(pT)
353  {
354  pCBTHead = pT->pNext;
355 
356  WBInterlockedIncrement((unsigned int *)&(pT->nRefCount)); // I own it now
357 
358  pT->pNext = NULL; // make sure
359 
360  // make sure the 'state' flag indicates zero [errors, aka < 0, will cause it to be deleted]
361  pT->fState = 0;
362  pT->aProp = None; // and also pre-assign this to 'none'
363 
364  // now 'walk' the running list and add it
365 
366  if(!*ppRunList)
367  {
368  *ppRunList = (CLIPBOARD_TASK *)pT;
369  }
370  else
371  {
372  pT2 = *ppRunList;
373 
374  while(pT2->pNext)
375  {
376  pT2 = (CLIPBOARD_TASK *)pT2->pNext;
377  }
378 
379  pT2->pNext = pT;
380  }
381  }
382 }
383 
384 
385 // static thread proc for clipboard
386 // display name is sent as const char *
387 // TODO: one thread per display??
388 
389 void * ClipboardThreadProc(void *pParam)
390 {
391 int iLen, iFactor;//, iErr;
392 const char *pDisplayName = (const char *)pParam;
393 Display *pDisplay = NULL;
394 volatile CLIPBOARD_TASK *pT;
395 CLIPBOARD_TASK *pT2, *pT3;
396 CLIPBOARD_TASK *pRunList = NULL; // what I'm running at the moment
397 CLIPBOARD_TASK *pDoneList = NULL; // things that get free'd up later
398 CLIPBOARD_DATA *pD, *pD2;
399 Window wWindow = None;
400 XEvent evt, evt2;
401 Atom aType;
402 // NOTE: Atoms in 'window_helper.h' won't be defined, see headers above
405 #ifdef X_HAVE_UTF8_STRING /* this indicates the extension is present */
406 Atom aUTF8_STRING;
407 #endif // X_HAVE_UTF8_STRING
408 //Atom aPRIMARY = XA_PRIMARY;
409 //Atom aSECONDARY = XA_SECONDARY;
410 //Atom aSTRING = XA_STRING;
411 //Atom aBITMAP = XA_BITMAP;
412 //Atom aDRAWABLE = XA_DRAWABLE;
413 //Atom aCOLORMAP = XA_COLORMAP;
414 //Atom aPIXMAP = XA_PIXMAP;
415 
416 
417  pDisplay = XOpenDisplay(pDisplayName);
418 
419  if(!pDisplay)
420  {
421  WB_ERROR_PRINT("%s - can't open display %s\n", __FUNCTION__, pDisplayName);
422 
423  bClipboardQuitFlag = -1;
424  goto exit_point;
425  }
426 
427  // atoms
428  aINCR = XInternAtom(pDisplay, "INCR", False);
429  aWBCLIP = XInternAtom(pDisplay, "WB_CLIP", False);
430  aCLIPBOARD = XInternAtom(pDisplay, "CLIPBOARD", False);
431  aTEXT = XInternAtom(pDisplay, "TEXT", False);
432  aC_STRING = XInternAtom(pDisplay, "C_STRING", False);
433  aCOMPOUND_TEXT = XInternAtom(pDisplay, "COMPOUND_TEXT", False);
434  aTARGETS = XInternAtom(pDisplay, "TARGETS", False);
435  aMULTIPLE = XInternAtom(pDisplay, "MULTIPLE", False);
436  aTIMESTAMP = XInternAtom(pDisplay, "TIMESTAMP", False);
437  aPIXEL = XInternAtom(pDisplay, "PIXEL", False);
438 
439 #ifdef X_HAVE_UTF8_STRING /* this indicates the extension is present */
440  aUTF8_STRING = XInternAtom(pDisplay, "UTF8_STRING", False);
441 #endif // X_HAVE_UTF8_STRING
442 
443  aNULL = XInternAtom(pDisplay, "NULL", False);
444 
445  // NOTE: these will actually match the ones allocated from the default display
446  // and later on, I will have to rely on that. But for now I have my own
447  // copies. A nice TODO would be to make sure they match.
448 
449  if(aINCR == None ||
450  aWBCLIP == None ||
451  aCLIPBOARD == None ||
452  aTEXT == None ||
453  aC_STRING == None ||
454  aCOMPOUND_TEXT == None ||
455  aTARGETS == None ||
456  aMULTIPLE == None ||
457  aTIMESTAMP == None ||
458  aPIXEL == None ||
459 #ifdef X_HAVE_UTF8_STRING /* this indicates the extension is present */
460  aUTF8_STRING == None || // TODO: should this one be optional?
461 #endif // X_HAVE_UTF8_STRING
462  aNULL == None)
463  {
464  WB_ERROR_PRINT("%s - error getting Atoms\n"
465  " aINCR=%d\n"
466  " aWBCLIP=%d\n"
467  " aCLIPBOARD=%d\n"
468  " aTEXT=%d\n"
469  " aC_STRING=%d\n"
470  " aCOMPOUND_TEXT=%d\n"
471  " aTARGETS=%d\n"
472  " aMULTIPLE=%d\n"
473  " aTIMESTAMP=%d\n"
474  " aPIXEL=%d\n"
475 #ifdef X_HAVE_UTF8_STRING /* this indicates the extension is present */
476  " aUTF8_STRING=%d\n"
477 #endif // X_HAVE_UTF8_STRING
478  " aNULL=%d\n", __FUNCTION__,
479  (int)aINCR, (int)aWBCLIP, (int)aCLIPBOARD, (int)aTEXT, (int)aC_STRING,
480  (int)aCOMPOUND_TEXT, (int)aTARGETS, (int)aMULTIPLE, (int)aTIMESTAMP, (int)aPIXEL,
481 #ifdef X_HAVE_UTF8_STRING /* this indicates the extension is present */
482  (int)aUTF8_STRING,
483 #endif // X_HAVE_UTF8_STRING
484  (int)aNULL);
485 
486  bClipboardQuitFlag = -1;
487  goto exit_point;
488  }
489 
490  // create thread's 'simple window' and enable property change/selection stuff
491 
492  wWindow = XCreateSimpleWindow(pDisplay, DefaultRootWindow(pDisplay), 0, 0, 1, 1, 0, 0, 0);
493 
494  if(wWindow == None)
495  {
496  bClipboardQuitFlag = -1;
497 
498  WB_ERROR_PRINT("%s - can't create simple window\n", __FUNCTION__);
499 
500  goto exit_point;
501  }
502 
503  XSelectInput(pDisplay, wWindow, PropertyChangeMask); // important - select property change input
504 
505  // wWindow is going to own every clipboard data thing
506 
507  XSync(pDisplay, 0); // make sure that the display is "in sync"
508 
509  bClipboardQuitFlag = 0; // to trigger "I am done initializing" and it's ok
510 
511  usleep(100000); // wait 0.1 seconds for everything to stabilize (TODO: is this needed here?)
512 
513  pDisplayName = NULL; // no longer valid (TODO: cache it for multiple display instances?)
514 
515  WB_DEBUG_PRINT(DebugLevel_Light | DebugSubSystem_Init, "INFO: %s - Clipboard Thread initialization complete\n",__FUNCTION__);
516 
517  // main handler loop
518  while(!bClipboardQuitFlag)
519  {
520  if(WBMutexLock(&xClipboardMutex, 1000)) // wait up to 1msec to lock
521  {
522  // well looks like I could NOT own the mutex
523 
524  usleep(100); // make sure I do this at least once in this loop
525  }
526  else
527  {
528  // clipboard task lock achieved, do some housekeeping
529 
530  // first clean up the "done" list (removes only the first item in it)
531  CleanupDoneList(&pDoneList);
532 
533  // next, grab an item from the 'new' queue and add it to the 'run' list
534  AddNewItemToRunList(&pRunList);
535 
536  // unlock the mutex now
537  WBMutexUnlock(&xClipboardMutex); // not locked now
538  }
539 
540  // now go through my 'run list' and work on whatever is here
541  pT2 = NULL; // this will be my 'prev' pointer for unlinking
542  pT = pRunList;
543 
544  // NOTE: because of the use of automatic variables rather than globals,
545  // I pretty much need to do this in one big freaking loop.
546  pD = pCBDHead;
547  while(pT)
548  {
549  int bDoneAndSignal = 0; // this flag tells me to place the item in the 'done queue' and signal the owner
550 
552  // PROCESS RUN LIST - CHECK 'CANCEL STATE' FIRST
554 
555  // check 'fState' - if it's negative, the task has been canceled or was an error I didn't catch before
556 
557  if((int)WBInterlockedRead((unsigned int *)&(pT->fState)) < 0) // error state - these will be moved to 'done list'
558  {
559  bDoneAndSignal = 1; // if marked 'error' the request was canceled and it needs to be deleted
560  // this will flow through and signal the caller
561  }
563  // CONTINUE NORMAL PROCESSING (NOT CANCELED)
565  else if(pT->fType == 0) // get clipboard data
566  {
568  // GET CLIPBOARD DATA - INITIALIZATION, 'state zero'
570 
571  if(pT->fState == 0) // state zero - just starting out
572  {
573  Window wOwn;
574  Atom aSelection;
575 
576  aSelection = pT->aSelection;
577  if(aSelection == None)
578  {
579  wOwn = XGetSelectionOwner(pDisplay, XA_PRIMARY);
580 
581  if(wOwn != None)
582  {
583  aSelection = XA_PRIMARY;
584  }
585  else
586  {
587  wOwn = XGetSelectionOwner(pDisplay, aCLIPBOARD);
588 
589  if(wOwn != None)
590  {
591  aSelection = aCLIPBOARD;
592  }
593  }
594 
595  if(wOwn != None)
596  {
597  pT->aSelection = aSelection;
598  }
599  }
600  else
601  {
602  wOwn = XGetSelectionOwner(pDisplay, aSelection);
603 
604 #ifndef NO_DEBUG
605  if(aSelection != XA_PRIMARY && aSelection != aCLIPBOARD)
606  {
607  char *p1 = WBGetAtomName(pDisplay, aSelection);
608  WB_ERROR_PRINT("TEMPORARY: %s - selection = %d \"%s\" owner = %u (%08xH)\n",
609  __FUNCTION__, (int)aSelection, p1, (int)wOwn, (int)wOwn);
610  if(p1)
611  {
612  WBFree(p1);
613  }
614  }
615 #endif // NO_DEBUG
616  }
617 
618  if(wOwn == wWindow) // it's me
619  {
620 // WB_ERROR_PRINT("TEMPORARY: %s - request clipboard and I own it\n", __FUNCTION__);
621 
622  // re-acquire the mutex
623 
624  if(!WBMutexLock(&xClipboardMutex, 100000L)) // re-lock, up to 0.1 seconds
625  {
626  // walk the data chain looking for a match
627  pD = pCBDHead;
628 
629  while(pD && pD->aSelection != aSelection)
630  {
631  pD = pD->pNext;
632  }
633 
634  if(!pD) // try 'soft match' instead
635  {
636  pD = pCBDHead;
637 
638  while(pD)
639  {
640  if(pD->aSelection == None &&
641  (aSelection == XA_PRIMARY ||
642  aSelection == aCLIPBOARD))
643  {
644  break;
645  }
646 
647  pD = pD->pNext;
648  }
649  }
650 
651  if(pD) // it will match
652  {
653  if(pD->nFormat == 16) // format determines element size
654  {
655  iFactor = 2;
656  }
657  else if(pD->nFormat == 32)
658  {
659  iFactor = 4;
660  }
661  else
662  {
663  iFactor = 1;
664  }
665 
666  iLen = iFactor * pD->cbLength; // actual length in bytes
667 
668  pT->pData = WBAlloc(iLen + iFactor);
669 
670  if(pT->pData)
671  {
672  memcpy(pT->pData, pD->aData, iLen);
673 
674  pT->cbLength = pD->cbLength;
675  pT->nFormat = pD->nFormat;
676 
677  bDoneAndSignal = 1; // put in 'done' queue, signal caller
678 
679 // WB_ERROR_PRINT("TEMPORARY: %s - return clipboard data %p len=%d (%d)\n",
680 // __FUNCTION__, pT->pData, pT->cbLength, iLen);
681  }
682  else
683  {
684  // TODO: set fState to -1 and set 'bDoneAndSignal?
685  // this could infinitely re-try if I don't...
686 
687  WB_ERROR_PRINT("TEMPORARY: %s - clipboard data is NULL\n", __FUNCTION__);
688 
689  goto null_data_me_own; // for now I'll do this
690  }
691  }
692  else
693  {
694 null_data_me_own:
695  WB_ERROR_PRINT("TEMPORARY: %s - return NULL clipboard data, owned by me\n", __FUNCTION__);
696 
697  // NULL data
698  pT->cbLength = 0;
699  pT->nFormat = 8;
700  pT->pData = NULL; // make sure
701 
702  bDoneAndSignal = 1; // put in 'done' queue, signal caller
703  }
704 
705  WBMutexUnlock(&xClipboardMutex); // not locked now
706 
707  // since I have a ref count it's safe to be unlocked here
708  // the data members I 'muck' with are reserved and don't need
709  // 'atomic' access between this thread and the caller
710  }
711  else
712  {
713  // TODO: set fState to -1 and set 'bDoneAndSignal?
714  // this could infinitely re-try if I don't...
715 
716  WB_ERROR_PRINT("TEMPORARY: %s line %d - unable to lock mutex\n", __FUNCTION__, __LINE__);
717 
718  pT->fState = -1;
719  bDoneAndSignal = 1; // well I'll do this for now
720  }
721  }
722  else if(wOwn == None) // nobody owns the clipboard
723  {
724  // NULL data
725  pT->cbLength = 0;
726  pT->nFormat = 8;
727  pT->pData = NULL; // make sure
728 
729  bDoneAndSignal = 1; // put in 'done' queue, signal caller
730  }
731  else
732  {
733  // I'm not the owner of the clipboard data. So I must make a request
734  // for the data. When it arrives, I need to hand it off to the caller
735  // and then clean myself up [this will happen asynchronously].
736 
737  // convert output to 'WB_CLIP' property on the window of my choice
738  pT->aProp = aWBCLIP; // for now always write to this one - later, custom per request?
739 
740  XConvertSelection(pDisplay, aSelection, pT->aType,
741  pT->aProp,
742  wWindow, CurrentTime);
743 
744  // NOTE: the function return does not indicate success/fail
745 
746 // WB_ERROR_PRINT("TEMPORARY: %s line %d - XConvertSelection, fState set to 1\n", __FUNCTION__, __LINE__);
747 
748  pT->fState = 1; // this tells me I'm waiting for the first reply
749  pT->ullTime = WBGetTimeIndex(); // the time I sent the message
750  }
751  }
752  else if(pT->fState == 1) // XConvertSelection sent, no reply yet
753  {
754  // TODO: if I am in state 1 and too much time has passed, do I want to cancel the
755  // request and signal a failure?? or do I want to re-issue the
756  // XConvertSelection request?
757 
758  // if((WBGetTimeIndex() - pT->ullTime) > some limit)
759  // { do something, re-send request, whatever }
760 
761  // NOTE: if the thing gets canceled by the caller, it will get a state of '-1'
762  }
763 
765  }
766  else if(pT->fType == 1) // assign clipboard
767  {
769  // SET CLIPBOARD DATA - copy data, assign to data list
771 
772  // NOTE: I don't need to worry about the state here, since all I'm going
773  // to do is set up a copy of the data, own the clipboard, and tell
774  // everyone/thing I completed the task. So it's almost synchronous
775 
776 
777  // COPY TO BUFFER OPERATION
778  // TODO: 'cut buffer' XA_STRING selection uses XStoreBuffer (etc.) directly
779 
780  // walk the data chain looking for a match, and remove it if it matches.
781  // (I'm going to replace it right away and the data and struct are together)
782 
783  pD = pCBDHead;
784  pD2 = NULL;
785 
786  while(pD)
787  {
788  if(pD->aSelection == pT->aSelection)
789  {
790  break; // direct match
791  }
792 
793  pD2 = pD;
794  pD = pD->pNext;
795  }
796 
797  if(!pD) // try 'soft match' instead
798  {
799  pD = pCBDHead;
800  pD2 = NULL;
801 
802  while(pD)
803  {
804  if(pD->aSelection == None &&
805  (pT->aSelection == XA_PRIMARY ||
806  pT->aSelection == aCLIPBOARD))
807  {
808  break;
809  }
810 
811  if(pT->aSelection == None &&
812  (pD->aSelection == XA_PRIMARY ||
813  pD->aSelection == aCLIPBOARD))
814  {
815  break;
816  }
817 
818  pD2 = pD;
819  pD = pD->pNext;
820  }
821  }
822 
823  if(pD) // matching data exists
824  {
825  if(pD2)
826  {
827  pD2->pNext = pD->pNext;
828  }
829  else
830  {
831  pCBDHead = pD->pNext;
832  }
833 
834  WBFree(pD); // done with it now
835  }
836 
837  // NOW, must create 'CLIPBOARD_DATA' struct with this stuff in it.
838 
839  if(!pT->pData || !pT->cbLength) // empty the clipboard/selection?
840  {
841  if(XGetSelectionOwner(pDisplay, pT->aSelection) == wWindow)
842  {
843  // empty the selection now (because I'm assigning 'NULL')
844 
845  if(pT->aSelection == None) // indicates 'clipboard'
846  {
847  XSetSelectionOwner(pDisplay, XA_PRIMARY, None, CurrentTime);
848  XSetSelectionOwner(pDisplay, aCLIPBOARD, None, CurrentTime); // so I do BOTH of them...
849  }
850  else
851  {
852  XSetSelectionOwner(pDisplay, pT->aSelection, None, CurrentTime);
853  }
854  }
855 
856 // WB_ERROR_PRINT("TEMPORARY: %s - empty clipboard assignment\n", __FUNCTION__);
857  }
858  else
859  {
860  int iTrueLen;
861 
862  if(pT->nFormat == 16)
863  {
864  iTrueLen = pT->cbLength * 2;
865  }
866  else if(pT->nFormat == 32)
867  {
868  iTrueLen = pT->cbLength * 4;
869  }
870  else
871  {
872  iTrueLen = pT->cbLength;
873  }
874 
875  pD = (CLIPBOARD_DATA *)WBAlloc(sizeof(*pD) + 2 + iTrueLen);
876 
877  if(!pD)
878  {
879  // TODO: indicate 'error' in assigning clipboard data
880 
881  XSetSelectionOwner(pDisplay, pT->aSelection, None, CurrentTime);
882  // regardless of who owns it, set it to 'None' on error
883  WB_ERROR_PRINT("%s - not enough memory for clipboard data - empty clipboard assignment\n", __FUNCTION__);
884  }
885  else
886  {
887  memcpy(pD->aData, pT->pData, iTrueLen);
888 
889  pD->aSelection = pT->aSelection;
890  pD->aType = pT->aType;
891  pD->nFormat = pT->nFormat;
892  pD->cbLength = pT->cbLength;
893 
894 // WB_ERROR_PRINT("TEMPORARY: %s - owning selection - length=%d (%d)\n", __FUNCTION__, pD->cbLength, iTrueLen);
895 
896  if(pD->aSelection != None)
897  {
898  char *p1 = WBGetAtomName(pDisplay, pD->aSelection);
899 
900  XSetSelectionOwner(pDisplay, pD->aSelection, wWindow, CurrentTime);
901 
902 // WB_ERROR_PRINT("TEMPORARY: %s - owned \"%s\"\n", __FUNCTION__, p1);
903  WBFree(p1);
904  }
905  else
906  {
907  XSetSelectionOwner(pDisplay, XA_PRIMARY, wWindow, CurrentTime);
908  XSetSelectionOwner(pDisplay, aCLIPBOARD, wWindow, CurrentTime);
909 
910 // WB_ERROR_PRINT("TEMPORARY: %s - owned XA_PRIMARY and CLIPBOARD\n", __FUNCTION__);
911  }
912 
913  // add 'pD' to my chain
914 
915  pD->pNext = NULL;
916 
917  if(!pCBDHead)
918  {
919  pCBDHead = pD;
920  }
921  else
922  {
923  pD2 = pCBDHead;
924 
925  while(pD2->pNext)
926  {
927  pD2 = pD2->pNext;
928  }
929 
930  pD2->pNext = pD;
931  }
932 
933 #if 0 /* this is a debug-only feature - remove when it's working properly */
934  {
935  char *p1;
936  WB_ERROR_PRINT("TEMPORARY: %s - walking clipboard data\n", __FUNCTION__);
937  pD = pCBDHead;
938 
939  while(pD)
940  {
941  int iLen;
942  if(pD->nFormat == 16)
943  {
944  iLen = 2 * pD->cbLength;
945  }
946  else if(pD->nFormat == 32)
947  {
948  iLen = 4 * pD->cbLength;
949  }
950  else
951  {
952  iLen = pD->cbLength;
953  }
954 
955  p1 = WBGetAtomName(pDisplay, pD->aSelection);
956  WBDebugPrint(" selection: \"%s\"\n", p1);
957  WBFree(p1);
958 
959  p1 = WBGetAtomName(pDisplay, pD->aType);
960  WBDebugPrint(" type: \"%s\"\n", p1);
961  WBFree(p1);
962 
963  WBDebugPrint(" format: %d\n", pD->nFormat);
964  WBDebugPrint(" length: %d (%d)\n", pD->cbLength, iLen);
965 
966  p1 = WBAlloc(iLen + 1);
967 
968  if(p1 && iLen)
969  {
970  memcpy(p1, pD->aData, iLen);
971  }
972  if(p1)
973  {
974  p1[iLen] = 0;
975  }
976 
977  WBDebugPrint("----------\n%s\n----------\n", p1);
978 
979  WBFree(p1);
980 
981  pD = pD->pNext;
982  }
983  }
984 #endif // 1
985  }
986 
987  XFlush(pDisplay);
988  }
989 
990  bDoneAndSignal = 1;
991 
993  }
994 
995  // flush and synchronize so that any events that "the above"
996  // might have created will be read to be processed right away
997 
998  XFlush(pDisplay); // make sure
999  XSync(pDisplay, 0); // same here
1000 
1001 
1002  if(bDoneAndSignal)
1003  {
1005  // signaling the completion of a task
1007 
1008 // WB_ERROR_PRINT("TEMPORARY: %s line %d - put thingy in done list, signal caller\n", __FUNCTION__, __LINE__);
1009 
1010  // after copying the data, remove it from the 'run' list
1011 
1012  if(pT2) // at THIS point, the 'prev' pointer for unlinking
1013  {
1014  pT2->pNext = pT->pNext;
1015  }
1016  else
1017  {
1018  pRunList = (CLIPBOARD_TASK *)pT->pNext;
1019  }
1020 
1021  pT->pNext = NULL;
1022 
1023  // now add it to the 'done' list
1024 
1025  if(!pDoneList)
1026  {
1027  pDoneList = (CLIPBOARD_TASK *)pT;
1028  }
1029  else
1030  {
1031  pT3 = pDoneList; // at THIS point, pT2 walks the 'done' list
1032 
1033  while(pT3->pNext)
1034  {
1035  pT3 = (CLIPBOARD_TASK *)pT3->pNext;
1036  }
1037 
1038  pT3->pNext = pT;
1039  }
1040 
1041  // must now signal the owner. Since I'm using the 'done list' to decrement
1042  // the reference count, the object will alays be 'good' here. So I just
1043  // need to signal the 'cond' object to get the owner to recognize I'm done
1044  // with the operation, and everything should be a-OK.
1045 
1046  WBCondSignal((WB_COND *)&(pT->cond)); // tell caller I'm done
1047 
1048  // now - leave pT2 'as-is' here but assign 'pT' to the 'pT2->pNext'
1049  if(pT2)
1050  {
1051  pT = pT2->pNext; // point to next item in list (after the one I moved)
1052  }
1053  else if(pRunList)
1054  {
1055  pT = pRunList->pNext;
1056  }
1057  else
1058  {
1059  pT = NULL; // end of loop
1060  }
1061 
1063  }
1064  else
1065  {
1067  // loop to next thingy - this is different than if I signaled
1068  // the caller, because I'm not pulling it out of the chain
1070 
1071  pT2 = (CLIPBOARD_TASK *)pT; // my new 'prev' pointer for unlinking (must re-assign here)
1072  pT = pT->pNext; // point to next item in list
1073  }
1074  }
1075 
1076  // check for the 'quit' flag before doing anything else
1077 
1078  if(bClipboardQuitFlag)
1079  {
1080  break;
1081  }
1082 
1084  // HANDLING X SERVER EVENTS
1086 
1087  // NEXT, look for selection events. If I find one directed at me, I must process
1088  // it according to whatever's currently going on.
1089 
1090  memset(&evt, 0, sizeof(evt));
1091 
1092  // read every available event, until I find one that I want
1093 
1094  while(XEventsQueued(pDisplay, QueuedAlready) > 0) // we have queued events!
1095  {
1096  // read through ALL of the incoming events until I find one
1097  // that I have to process. the others are 'eaten'
1098 
1099  XNextEvent(pDisplay, &evt);
1100 
1101  if(evt.type == SelectionClear ||
1102  evt.type == SelectionRequest ||
1103  evt.type == SelectionNotify)
1104  {
1105  WB_ERROR_PRINT("TEMPORARY: %s line %d - selection event found\n", __FUNCTION__, __LINE__);
1106  break;
1107  }
1108  else
1109  {
1110  WBDebugDumpEvent(&evt);
1111  WB_ERROR_PRINT("TEMPORARY: %s line %d - unrecognized event (ignoring)\n", __FUNCTION__, __LINE__);
1112  }
1113 
1114  evt.type = 0; // must do this in case I have no events left so next section is skipped
1115  }
1116 
1117 
1118  if(evt.type == SelectionClear ||
1119  evt.type == SelectionRequest ||
1120  evt.type == SelectionNotify)
1121  {
1122  int iFormat;
1123  unsigned long nItems, cbLeft;
1124  unsigned char *pBuf;
1125 
1126  WBDebugDumpEvent(&evt);
1127 
1128  // COPY TO BUFFER OPERATION
1129  // TODO: XA_STRING uses XStoreBuffer (etc.) directly, no owner, buffer 0
1130 
1131  // step 1: own selection using XSetSelectionOwner
1132  // step 2: wait for selection events using XNextEvent or similar
1133  // if I get 'SelectionClear', set 'clear' flag, continue processing incremental
1134  // step 3: on selection request, send the data, incrementally if needed (one message per loop)
1135  // (when selection is 'cleared' I can stop processing this)
1136 
1137  // COPY FROM CLIPBOARD OPERATION
1138  // when retrieving a selection, I'll be the one sending the SelectionRequest events
1139  // (this is typically done using 'XConvertSelection')
1140  // once retrieved, set appropriate flag in work unit, get next one [if any]
1141  // if I'm the clipboard owner, just copy the data as-is and mark 'success'
1142 
1143  // get data type in this order:
1144  // XA_CLIPBOARD(pDisplay) i.e. the atom 'CLIPBOARD' for the given display
1145  // XA_PRIMARY (this is the default for 'xclip')
1146  // XA_SECONDARY
1147  // XA_STRING
1148 
1149  // 'target' format will typically be
1150  // XA_UTF8_STRING(pDisplay) [the atom 'UTF8_STRING']
1151  // XA_STRING (usually when xselection.property == None in the SelectionEvent)
1152 
1153  // use XGetSelectionOwner to see who owns the selection
1154  // lock/unlock the X system before (and while) grabbing certain info
1155 
1156  // own the mutex and check for tasks, perform one per loop
1157 
1158  if(evt.type == SelectionClear)
1159  {
1161  // SELECTION CLEAR EVENT
1163 
1164  // I'm being asked NOT to own the clipboard, so first check that I'm still the owner
1165 
1166  Window wOwn = XGetSelectionOwner(pDisplay, evt.xselectionclear.selection);
1167 
1168  if(wOwn == wWindow) // do I still own it?
1169  {
1170  if(evt.xselectionclear.selection == aCLIPBOARD ||
1171  evt.xselectionclear.selection == XA_PRIMARY)
1172  {
1173  XSetSelectionOwner(pDisplay, XA_PRIMARY,
1174  None, CurrentTime); // no owner, now
1175  XSetSelectionOwner(pDisplay, aCLIPBOARD,
1176  None, CurrentTime); // no owner, now
1177  }
1178  else
1179  {
1180  XSetSelectionOwner(pDisplay, evt.xselectionclear.selection,
1181  None, CurrentTime); // no owner, now
1182  }
1183 
1184  XFlush(pDisplay);
1185  }
1186 
1187  pD = pCBDHead;
1188  pD2 = NULL;
1189 
1190  while(pD)
1191  {
1192  if(pD->aSelection == evt.xselectionclear.selection ||
1193  (pD->aSelection == None &&
1194  (evt.xselectionclear.selection == XA_PRIMARY ||
1195  evt.xselectionclear.selection == aCLIPBOARD)))
1196  {
1197  // remove this item from the chain
1198  if(!pD2)
1199  {
1200  pCBDHead = pD->pNext;
1201  }
1202  else
1203  {
1204  pD2->pNext = pD->pNext;
1205  }
1206 
1207  WBFree(pD); // free up the memory [that's all I need to do]
1208 
1209  pD = pD2; // this continues searching correctly
1210 
1211  if(!pD) // null means empty chain
1212  {
1213  break; // I am done
1214  }
1215  }
1216 
1217  pD2 = pD; // the new 'prev' item
1218  pD = pD->pNext; // "next" item in chain
1219  }
1220  }
1221  else if(evt.type == SelectionRequest) // copy FROM me
1222  {
1224  // SELECTION REQUEST EVENT
1226 
1227  pD = pCBDHead;
1228 
1229  while(pD && pD->aSelection != evt.xselectionrequest.selection)
1230  {
1231  pD = pD->pNext;
1232  }
1233 
1234  if(!pD) // try 'soft match' instead
1235  {
1236  pD = pCBDHead;
1237 
1238  while(pD)
1239  {
1240  if(pD->aSelection == None &&
1241  (evt.xselectionrequest.selection == XA_PRIMARY ||
1242  evt.xselectionrequest.selection == aCLIPBOARD))
1243  {
1244  break;
1245  }
1246 
1247  pD = pD->pNext;
1248  }
1249  }
1250 
1251  // send data to requestor
1252 
1253  if(evt.xselectionrequest.target == aTARGETS) // special target
1254  {
1255  Atom aT[2];
1256 
1257  aT[0] = aTARGETS;
1258  aT[1] = pD ? pD->aType : XA_STRING; // just do this for now
1259 
1260  XChangeProperty(pDisplay, evt.xselectionrequest.requestor,
1261  evt.xselectionrequest.property,
1262  XA_ATOM, 32, PropModeReplace,
1263  (void *)aT, sizeof(aT)/sizeof(aT[0]));
1264 
1265  WB_ERROR_PRINT("TEMPORARY: %s - sending TARGETS XChangeProperty\n", __FUNCTION__);
1266  }
1267  else if(pD &&
1268  (evt.xselectionrequest.target == pD->aType ||
1269  (evt.xselectionrequest.target == XA_STRING && pD->aType == aUTF8_STRING) ||
1270  (evt.xselectionrequest.target == aUTF8_STRING && pD->aType == XA_STRING)))
1271  {
1272  int nE = pD->cbLength;
1273 
1274  if(pD->nFormat == 16)
1275  {
1276  nE /= 2;
1277  }
1278  else if(pD->nFormat == 32)
1279  {
1280  nE /= 4;
1281  }
1282 
1283  // TODO: data conversion to/from UTF8 ??
1284 
1285 
1286  // TODO: handle 'chunking'
1287 
1288  XChangeProperty(pDisplay, evt.xselectionrequest.requestor,
1289  evt.xselectionrequest.property,
1290  pD->aType, pD->nFormat, PropModeReplace,
1291  (unsigned char *)pD->aData, nE);
1292 
1293  // TODO verify that XChangeProperty actually worked by using XSync
1294  // and testing for response messages. for now, just assume it works.
1295 
1296 
1297  WB_ERROR_PRINT("TEMPORARY: %s - sent XChangeProperty\n", __FUNCTION__);
1298  }
1299  else
1300  {
1301  // error - send 'None'
1302 
1303  XChangeProperty(pDisplay, evt.xselectionrequest.requestor,
1304  evt.xselectionrequest.property,
1305  None, 0, PropModeReplace,
1306  NULL, 0);
1307 
1308  WB_ERROR_PRINT("%s - sending 'None' for XChangeProperty\n", __FUNCTION__);
1309  }
1310 
1312  // EVENT REPLY TO REQUESTOR
1314 
1315  // now I need to reply to the message to say I did it
1316  // if I do _NOT_ do this in a timely manner, other applications
1317  // will be affected in a very... bad... way!
1318 
1319  memset(&evt2, 0, sizeof(evt2));
1320 
1321  evt2.xselection.type = SelectionNotify;
1322  evt2.xselection.display = evt.xselectionrequest.display;
1323  evt2.xselection.requestor = evt.xselectionrequest.requestor;
1324  evt2.xselection.property = evt.xselectionrequest.property;
1325  evt2.xselection.selection = evt.xselectionrequest.selection;
1326  evt2.xselection.target = evt.xselectionrequest.target;
1327  evt2.xselection.time = evt.xselectionrequest.time;
1328 
1329  XFlush(evt2.xselection.display);
1330 
1331  /* send the response event */
1332  XSendEvent(evt2.xselection.display, evt.xselectionrequest.requestor,
1333  0, 0, &evt2);
1334  XFlush(evt2.xselection.display);
1335 
1336  WB_ERROR_PRINT("TEMPORARY: %s - reply message sent\n", __FUNCTION__);
1337  }
1338  else if(evt.type == SelectionNotify)
1339  {
1341  // SELECTION NOTIFY EVENT
1343 
1344  int bDoneAndSignal = 0; // this flag tells me to place the item in the 'done queue' and signal the owner
1345 
1346  // now go through my 'run list' and work on whatever is here
1347  pT2 = NULL; // this will be my 'prev' pointer for unlinking
1348  pT = pRunList;
1349 
1350  while(pT)
1351  {
1352  WB_ERROR_PRINT("TEMPORARY: %s line %d - event loop for SelectionNotify\n", __FUNCTION__, __LINE__);
1353 
1354  if(pT->fType == 0 // getting clipboard/selection data
1355  && pT->aSelection == evt.xselection.selection // getting property from this selection
1356  && pT->aType == evt.xselection.target // and converting to THIS datatype
1357  && (pT->aProp == evt.xselection.property || // matching property name
1358  evt.xselection.property == None)) // or an error
1359  {
1360  pBuf = NULL; // make sure
1361 
1362  if(evt.xselection.property == None) // this means ERROR
1363  {
1364 #ifdef X_HAVE_UTF8_STRING /* this indicates the extension is present */
1365  if(pT->aType == aUTF8_STRING &&
1366  pT->fState == 1)
1367  {
1368  // fallback, re-issue the request but using XA_STRING instead of aUTF8_STRING
1369  pT->aType = XA_STRING; // change the type (will return this later)
1370 
1371  // ok here we go again, but an XA_STRINg this time
1372  XConvertSelection(pDisplay, pT->aSelection, pT->aType,
1373  pT->aProp,
1374  wWindow, CurrentTime);
1375  }
1376  else
1377 #endif // X_HAVE_UTF8_STRING
1378  {
1379  // assume this is an error condition
1380 
1381  WB_ERROR_PRINT("ERROR: %s line %d - Unable to do conversion\n", __FUNCTION__, __LINE__);
1382 
1383  pT->fState = -1; // an error
1384 
1385  bDoneAndSignal = 1;
1386  }
1387  }
1388  else if(pT->fState == 1) // state 1 - request sent, waiting for reply
1389  {
1390  // first, see what I have assigned to my property
1391 
1392  if(!XGetWindowProperty(pDisplay, wWindow, pT->aProp, 0, 0, False,
1393  AnyPropertyType, &aType, &iFormat, &nItems, &cbLeft, &pBuf))
1394  {
1395  if(pBuf)
1396  {
1397  XFree(pBuf);
1398  pBuf = NULL;
1399  }
1400 
1401  // is this the actual data, or a return that says "do it incrementally" ?
1402 
1403  if(aType == aINCR) // incremental
1404  {
1405  // begin incremental process by deleting the property
1406  XDeleteProperty(pDisplay, wWindow, pT->aProp);
1407  XFlush(pDisplay);
1408 
1409  // assign state 2, which will pick it up again as incremental
1410  pT->fState = 2;
1411  }
1412  else
1413  {
1414  int iLen = cbLeft; // the RAW length (in bytes)
1415 
1416  if(iFormat == 16)
1417  {
1418  pT->cbLength = cbLeft / 2;
1419  }
1420  else if(iFormat == 32)
1421  {
1422  pT->cbLength = cbLeft / 4;
1423  }
1424  else
1425  {
1426  pT->cbLength = cbLeft;
1427  }
1428 
1429  if(!XGetWindowProperty(pDisplay, wWindow, pT->aProp, 0, pT->cbLength, False,
1430  AnyPropertyType, &aType, &iFormat, &nItems, &cbLeft, &pBuf) &&
1431  pBuf)
1432  {
1433  pT->nFormat = iFormat;
1434  pT->aType = aType;
1435 
1436  if(nItems != pT->cbLength)
1437  {
1438  WB_ERROR_PRINT("WARNING: %s - nItems %ld does not match calculated length %d\n",
1439  __FUNCTION__, nItems, pT->cbLength);
1440  }
1441 
1442  pT->pData = WBAlloc(iLen + 4);
1443 
1444  if(pT->pData)
1445  {
1446  memcpy(pT->pData, pBuf, iLen);
1447  ((char *)pT->pData)[iLen] = 0; // make sure
1448 
1449  if(aType == XA_STRING || aType == aUTF8_STRING)
1450  {
1451  pT->nFormat = 8; // force it
1452  pT->cbLength = iLen + 1; // includes the terminating zero byte as part of the length
1453  }
1454 
1455  pT->fState = 3; // mark 'data complete' (for debugging, later, maybe)
1456 
1457  bDoneAndSignal = 1;
1458  }
1459  else
1460  {
1461  WB_ERROR_PRINT("ERROR: %s line %d - Unable to allocate pointer\n", __FUNCTION__, __LINE__);
1462 
1463  pT->fState = -1; // an error
1464 
1465  bDoneAndSignal = 1;
1466  }
1467  }
1468  else
1469  {
1470  WB_ERROR_PRINT("ERROR: %s line %d - Unable to get window property\n", __FUNCTION__, __LINE__);
1471 
1472  pT->fState = -1; // an error
1473 
1474  bDoneAndSignal = 1;
1475  }
1476 
1477  // regardless, delete the property I use to transfer data
1478 
1479  XDeleteProperty(pDisplay, wWindow, pT->aProp);
1480  XFlush(pDisplay);
1481  }
1482  }
1483  else // error condition
1484  {
1485  WB_ERROR_PRINT("ERROR: %s line %d - Unable to get window property\n", __FUNCTION__, __LINE__);
1486 
1487  pT->fState = -1; // an error
1488 
1489  bDoneAndSignal = 1;
1490  }
1491  }
1492  else if(pT->fState == 2) // incrementally reading the data
1493  {
1494  WB_ERROR_PRINT("WARNING: %s - INCREMENTAL not supported (yet)\n", __FUNCTION__);
1495 
1496  pT->fState = -1; // an error
1497 
1498  bDoneAndSignal = 1;
1499  }
1500 
1501  if(pBuf)
1502  {
1503  XFree(pBuf);
1504  }
1505  }
1506 
1507  if(bDoneAndSignal)
1508  {
1510  // TASK IS COMPLETE - SIGNAL THE CALLER
1512 
1513  WB_ERROR_PRINT("TEMPORARY: %s line %d - put thingy in done list, signal caller\n", __FUNCTION__, __LINE__);
1514 
1515  // after copying the data, remove it from the 'run' list
1516 
1517  if(pT2) // at THIS point, the 'prev' pointer for unlinking
1518  {
1519  pT2->pNext = pT->pNext;
1520  }
1521  else
1522  {
1523  pRunList = (CLIPBOARD_TASK *)pT->pNext;
1524  }
1525 
1526  pT->pNext = NULL;
1527 
1528  // now add it to the 'done' list
1529 
1530  if(!pDoneList)
1531  {
1532  pDoneList = (CLIPBOARD_TASK *)pT;
1533  }
1534  else
1535  {
1536  pT3 = pDoneList; // at THIS point, pT2 walks the 'done' list
1537 
1538  while(pT3->pNext)
1539  {
1540  pT3 = (CLIPBOARD_TASK *)pT3->pNext;
1541  }
1542 
1543  pT3->pNext = pT;
1544  }
1545 
1546  // must now signal the owner. Since I'm using the 'done list' to decrement
1547  // the reference count, the object will alays be 'good' here. So I just
1548  // need to signal the 'cond' object to get the owner to recognize I'm done
1549  // with the operation, and everything should be a-OK.
1550 
1551  WBCondSignal((WB_COND *)&(pT->cond)); // tell caller I'm done
1552 
1553  WB_ERROR_PRINT("TEMPORARY: %s line %d - signaled caller\n", __FUNCTION__, __LINE__);
1554 
1555  // now - leave pT2 'as-is' here but assign 'pT' to the 'pT2->pNext'
1556  if(pT2)
1557  {
1558  pT = pT2->pNext; // point to next item in list (after the one I moved)
1559  }
1560  else if(pRunList)
1561  {
1562  pT = pRunList->pNext;
1563  }
1564  else
1565  {
1566  pT = NULL; // end of loop
1567  }
1568  }
1569  else
1570  {
1571  // loop to next thingy
1572 
1573  pT2 = (CLIPBOARD_TASK *)pT; // my new 'prev' pointer for unlinking (must re-assign here)
1574  pT = pT->pNext; // point to next item in list
1575  }
1576  }
1577  }
1578  }
1579 
1580  // check for the 'quit' flag before doing anything else
1581 
1582  if(bClipboardQuitFlag)
1583  {
1584  break;
1585  }
1586 
1588  // end of loop
1590 
1591  if(!pCBTHead && // go ahead and test outside of a lock. if NULL, I need to sleep a bit
1592  XEventsQueued(pDisplay, QueuedAlready) <= 0) // no events
1593  {
1594  static unsigned long long ullLastTime = 0;
1595  unsigned long long ullTemp;
1596 
1597  // if nothing to do, sleep or something rather than spinning
1598  // this way if I have events to process, or if I have tasks to manage,
1599  // I can continue to cycle without delays
1600 
1601 // XFlush(pDisplay); // force flush just in case
1602 
1603 #ifdef HAVE_NANOSLEEP
1604  struct timespec tsp;
1605  tsp.tv_sec = 0;
1606  tsp.tv_nsec = 1000; // wait for 1 msec
1607 
1608  nanosleep(&tsp, NULL);
1609 #else // HAVE_NANOSLEEP
1610 
1611  usleep(1000); // but if I'm not busy, use a sleep state to limit CPU utilization in the thread
1612 #endif // HAVE_NANOSLEEP
1613 
1614  ullTemp = WBGetTimeIndex();
1615  if((ullTemp - ullLastTime) > 50000) // make sure it's more than 0.05 seconds, so I don't "spin"
1616  {
1617  ullLastTime = ullTemp;
1618 
1620  XSync(pDisplay, False); // force sync just in case
1622  }
1623  }
1624  }
1625 
1626  WB_ERROR_PRINT("TEMPORARY: %s line %d - exit from main thread loop\n", __FUNCTION__, __LINE__);
1627 
1628  // resource cleanup (which should be relatively simple)
1629  if(WBMutexLock(&xClipboardMutex, -1)) // wait forever to lock it (very important this succeeds)
1630  {
1631  WB_ERROR_PRINT("ERROR: %s - Clipboard Thread can't lock mutex on exit\n",__FUNCTION__);
1632  }
1633 
1634  while(pDoneList) // already signaled
1635  {
1636  pT = pDoneList;
1637  pDoneList = (CLIPBOARD_TASK *)pT->pNext;
1638 
1639  pT->pNext = NULL;
1640 
1641  if(!WBInterlockedDecrement((unsigned int *)&(pT->nRefCount))) // flags a delete when ref count is zero
1642  {
1643  if(!pT->fType && pT->pData) // it COULD happen...
1644  {
1645  WBFree(pT->pData);
1646  pT->pData = NULL;
1647  }
1648 
1649  WBCondFree((WB_COND *)&(pT->cond)); // assume it works
1650 
1651  WBFree((void *)pT); // do this while locked
1652  }
1653  }
1654 
1655  while(pRunList) // need to be signaled
1656  {
1657  pT = pRunList;
1658  pRunList = (CLIPBOARD_TASK *)pT->pNext;
1659 
1660  pT->pNext = NULL;
1661 
1662  WBCondSignal((WB_COND *)&(pT->cond)); // do this, regardless (won't wake up unless mutex unowned)
1663 
1664  if(!WBInterlockedDecrement((unsigned int *)&(pT->nRefCount))) // flags a delete when ref count is zero
1665  {
1666  if(!pT->fType && pT->pData) // it COULD happen...
1667  {
1668  WBFree(pT->pData);
1669  pT->pData = NULL;
1670  }
1671 
1672  WBCondFree((WB_COND *)&(pT->cond)); // assume it works
1673 
1674  WBFree((void *)pT); // do this while locked
1675  }
1676  }
1677 
1678  while(pCBTHead)
1679  {
1680  pT = pCBTHead;
1681  pCBTHead = pT->pNext;
1682 
1683  pT->pNext = NULL;
1684 
1685  WBCondSignal((WB_COND *)&(pT->cond)); // do this, regardless (won't wake up unless mutex unowned)
1686 
1687  if(!WBInterlockedDecrement((unsigned int *)&(pT->nRefCount))) // flags a delete when ref count is zero
1688  {
1689  // NOTE: unless it was mis-configured, these entries should NOT hve allocated 'pData', evar
1690 
1691  WBCondFree((WB_COND *)&(pT->cond)); // assume it works
1692 
1693  WBFree((void *)pT); // do this while locked
1694  }
1695  }
1696 
1697  XFlush(pDisplay);
1698  XSync(pDisplay, False);
1699 
1700  // and finally, unlock that mutex!
1701  WBMutexUnlock(&xClipboardMutex); // not locked now
1702 
1703  // now remove any data items I might be cacheing
1704 
1705  while(pCBDHead) // this one is likely to have data in it
1706  {
1707  CLIPBOARD_DATA *pD = pCBDHead;
1708  pCBDHead = pCBDHead->pNext;
1709 
1710  if(pDisplay && wWindow != None)
1711  {
1712  if(pD->aSelection != None &&
1713  XGetSelectionOwner(pDisplay, pD->aSelection) == wWindow)
1714  {
1715  XSetSelectionOwner(pDisplay, pD->aSelection, None, CurrentTime);
1716  }
1717  else if(pD->aSelection == None)
1718  {
1719  if(XGetSelectionOwner(pDisplay, aCLIPBOARD) == wWindow)
1720  {
1721  XSetSelectionOwner(pDisplay, aCLIPBOARD, None, CurrentTime);
1722  }
1723  else if(XGetSelectionOwner(pDisplay, XA_PRIMARY) == wWindow)
1724  {
1725  XSetSelectionOwner(pDisplay, XA_PRIMARY, None, CurrentTime);
1726  }
1727  }
1728  }
1729 
1730  WBFree(pD); // this one is very simple
1731  }
1732 
1733  // OK no longer owning the clipboard, so now it's time to
1734 
1735  if(pDisplay)
1736  {
1738  XFlush(pDisplay);
1740  }
1741 
1742 exit_point:
1743 
1744  if(wWindow != None)
1745  {
1747  XDestroyWindow(pDisplay, wWindow);
1749  }
1750 
1751  if(pDisplay)
1752  {
1754  XSync(pDisplay, FALSE); // try sync'ing first to avoid certain errors
1755  XCloseDisplay(pDisplay); // display is to be destroyed now
1757  }
1758 
1759  WB_DEBUG_PRINT(DebugLevel_Light | DebugSubSystem_Init, "INFO: %s - Clipboard Thread exit complete\n",__FUNCTION__);
1760 
1761  return NULL;
1762 }
1763 
1764 
1765 
1766 void * WBGetClipboardData(Display *pDisplay, Atom *paType, int *piFormat, unsigned long *pnData)
1767 {
1768  // TODO: find better way than using 'None' for the 'aSelection' parameter in order to get "that behavior"
1769 
1770  return WBGetSelectionData(pDisplay, None, paType, piFormat, pnData);
1771 }
1772 
1773 int WBSetClipboardData(Display *pDisplay, Atom aType, int iFormat, const void *pData, unsigned long nData)
1774 {
1775  // TODO: find better way than using 'None' for the 'aSelection' parameter in order to get "that behavior"
1776 
1777  return WBSetSelectionData(pDisplay, None, aType, iFormat, pData, nData);
1778 }
1779 
1780 void * WBGetSelectionData(Display *pDisplay, Atom aSelection, Atom *paType, int *piFormat, unsigned long *pnData)
1781 {
1782 void *pRval = NULL;
1783 volatile CLIPBOARD_TASK *pT, *pTask;
1784 Atom aType = None;
1785 int iFormat = 0;
1786 int iErr;
1787 
1788 
1789  if(paType)
1790  {
1791  aType = *paType;
1792  *paType = None; // this is actually my 'not found' indicator
1793  }
1794 
1795  if(piFormat)
1796  {
1797  iFormat = *piFormat;
1798  *piFormat = 0;
1799  }
1800 
1801  if(pnData)
1802  {
1803  *pnData = 0; // always
1804  }
1805 
1806  // for now, if 'pDisplay' isn't NULL or the default display, this will fail
1807  if(pDisplay && pDisplay != WBGetDefaultDisplay())
1808  {
1809  return NULL;
1810  }
1811 
1812  pTask = (CLIPBOARD_TASK *)WBAlloc(sizeof(*pTask));
1813  if(!pTask)
1814  {
1815  WB_ERROR_PRINT("%s - can't create 'CLIPBOARD_TASK' for clipboard task\n", __FUNCTION__);
1816 
1817  return NULL;
1818  }
1819 
1820  memset((void *)pTask, 0, sizeof(*pTask)); // always start by doing this
1821 
1822  pTask->fType = 0;
1823  pTask->pData = NULL; // always for 'get'
1824  pTask->cbLength = 0;
1825  pTask->aSelection = aSelection;
1826  pTask->aType = aType;
1827  pTask->nFormat = iFormat;
1828  pTask->nRefCount = 1;
1829  pTask->pNext = NULL;
1830 
1831  // create the 'cond' object
1832  if(WBCondCreate((WB_COND *)&(pTask->cond)))
1833  {
1834  WB_ERROR_PRINT("%s - can't create 'cond' for clipboard task\n", __FUNCTION__);
1835 
1836  goto exit_point0;
1837  }
1838 
1839  // own the global mutex and add this to the linked list. then wait on it.
1840 
1841  if(WBMutexLock(&xClipboardMutex, 200000L)) // 0.2 seconds
1842  {
1843  WB_ERROR_PRINT("%s - can't lock mutex for clipboard task\n", __FUNCTION__);
1844 
1845  goto exit_point;
1846  }
1847 
1848  // put it in the chain
1849 
1850  pT = pCBTHead;
1851 
1852  while(pT && pT->pNext)
1853  {
1854  pT = pT->pNext;
1855  }
1856 
1857  if(pT)
1858  {
1859  pT->pNext = pTask; // end of chain
1860  }
1861  else
1862  {
1863  pCBTHead = pTask; // head of chain
1864  }
1865 
1866  // wait for it to complete [TODO: do I need to do this?]
1867 
1868  iErr = WBCondWaitMutex((WB_COND *)&(pTask->cond), &xClipboardMutex, 200000L); // wait up to 0.2 sec for completion
1869 
1870  if(!iErr)
1871  {
1872 // WB_ERROR_PRINT("TEMPORARY: %s - completed wait\n", __FUNCTION__);
1873 
1874  pRval = pTask->pData; // always for 'get'
1875  pTask->pData = NULL; // so I don't accidentally free it, evar [assume WBAlloc from other thread is OK]
1876 
1877  if(paType)
1878  {
1879  *paType = pTask->aType;
1880  }
1881 
1882  if(piFormat)
1883  {
1884  *piFormat = pTask->nFormat;
1885  }
1886 
1887  if(pnData)
1888  {
1889  *pnData = pTask->cbLength;
1890  }
1891 
1892  // TODO: remove these extra checks later. also may add stuff to the main
1893  // thread loop to make sure that the 'done' list gets freed up at a reasonable rate.
1894 
1895  if(pTask->pNext)
1896  {
1897  WB_ERROR_PRINT("ERROR: %s - clipboard task %p has non-NULL 'pNext' on return (%p), refcount = %d\n",
1898  __FUNCTION__, pTask, pTask->pNext, pTask->nRefCount);
1899  }
1900  else if(pTask->nRefCount < 1 || pTask->nRefCount > 2) // should be 2, may be 1, anything else unexpected
1901  {
1902  WB_ERROR_PRINT("ERROR: %s - clipboard task %p has wrong refcount %d\n",
1903  __FUNCTION__, pTask, pTask->nRefCount);
1904  }
1905  else if(!pRval)
1906  {
1907  WB_ERROR_PRINT("TEMPORARY: %s - returning NULL\n", __FUNCTION__);
1908  }
1909 // else
1910 // {
1911 // WB_ERROR_PRINT("TEMPORARY: %s - returning %p\n", __FUNCTION__, pRval);
1912 // }
1913  }
1914  else
1915  {
1916  // some kind of error - unhook it from the chain. mutex is owned.
1917 
1918  WB_ERROR_PRINT("%s - %s error waiting on cond,mutex for completion\n", __FUNCTION__,
1919  (const char *)(iErr > 0 ? "timeout" : "unknown"));
1920 
1921 
1922  // the mutex is owned while I'm doing this, so it's ok
1923 
1924  pT = pCBTHead; // if it hasn't been processed yet, it's in this linked list
1925 
1926  while(pT && pT->pNext)
1927  {
1928  if(pT->pNext == pTask)
1929  {
1930  pT->pNext = pTask->pNext;
1931 
1932  break;
1933  }
1934  }
1935 
1936  // NOTE: if it wasn't in the 'pCBTHead' list, it's probably in the 'done' list and will be deleted.
1937  // but as a matter of course, while the mutex is locked, it's not going away. SO, I mark the
1938  // state with an 'error' flag.
1939 
1940  WBInterlockedExchange((unsigned int *)&(pTask->fState), -1); // changes state to 'error' so it will self-clean-up
1941  // in case that it wasn't in 'pCBTHead', which is likely
1942  }
1943 
1944  // at this point 'xClipboardMutex' is assumed to be LOCKED and I can go ahead and mess with the 'pTask' object
1945  // it should also be OUT of the 'pCBTHead' list, but may be in the 'done' list.
1946 
1947  if(!WBInterlockedDecrement((unsigned int *)&(pTask->nRefCount))) // flags a delete when ref count is zero
1948  {
1949 // WB_ERROR_PRINT("TEMPORARY: %s - ref count zero, deleting 'pTask'\n", __FUNCTION__);
1950 
1951  if(pTask->pData) // it COULD happen...
1952  {
1953  WBFree(pTask->pData);
1954  pTask->pData = NULL;
1955  }
1956 
1957  WBCondFree((WB_COND *)&(pTask->cond)); // assume it works
1958 
1959  WBFree((void *)pTask); // do this while locked
1960  }
1961 
1962  pTask = NULL; // so I don't try to re-use it
1963 
1964 // WB_ERROR_PRINT("TEMPORARY: %s - release mutex before exit\n", __FUNCTION__);
1965 
1966  WBMutexUnlock(&xClipboardMutex);
1967 
1968 exit_point:
1969 
1970  if(pTask)
1971  {
1972  WBCondFree((WB_COND *)&(pTask->cond)); // assume it works
1973  }
1974 
1975 exit_point0:
1976 
1977  if(pTask)
1978  {
1979  WBFree((void *)pTask); // TODO: on error, this might cause a crash... or not
1980  }
1981 
1982  return pRval; // for now (similar to an error return)
1983 }
1984 
1985 int WBSetSelectionData(Display *pDisplay, Atom aSelection, Atom aType, int iFormat, const void *pData, unsigned long nData)
1986 {
1987 int iRval = -1;
1988 volatile CLIPBOARD_TASK *pT, *pTask;
1989 
1990 
1991  // for now, if 'pDisplay' isn't NULL or the default display, this will fail
1992  if(pDisplay && pDisplay != WBGetDefaultDisplay())
1993  {
1994  return -1;
1995  }
1996 
1997  pTask = (CLIPBOARD_TASK *)WBAlloc(sizeof(*pTask));
1998  if(!pTask)
1999  {
2000  WB_ERROR_PRINT("%s - can't create 'CLIPBOARD_TASK' for clipboard task\n", __FUNCTION__);
2001 
2002  return -1;
2003  }
2004 
2005  memset((void *)pTask, 0, sizeof(*pTask)); // always start by doing this
2006 
2007  pTask->fType = 1;
2008  pTask->pData = (void *)pData; // will be treated as 'const'
2009  pTask->cbLength = nData;
2010  pTask->aSelection = aSelection;
2011  pTask->aType = aType;
2012  pTask->nFormat = iFormat;
2013  pTask->nRefCount = 1;
2014  pTask->pNext = NULL;
2015 
2016  // create the 'cond' object
2017  if(WBCondCreate((WB_COND *)&(pTask->cond)))
2018  {
2019  WB_ERROR_PRINT("%s - can't create 'cond' for clipboard task\n", __FUNCTION__);
2020 
2021  iRval = -2;
2022  goto exit_point0;
2023  }
2024 
2025  // own the global mutex and add this to the linked list. then wait on it.
2026 
2027  if(WBMutexLock(&xClipboardMutex, 200000L)) // up to 0.2 second for locking
2028  {
2029  WB_ERROR_PRINT("%s - can't lock mutex for clipboard task\n", __FUNCTION__);
2030 
2031  iRval = -3;
2032  goto exit_point;
2033  }
2034 
2035  // put pTask in the chain
2036 
2037  pT = pCBTHead;
2038 
2039  while(pT && pT->pNext)
2040  {
2041  pT = pT->pNext;
2042  }
2043 
2044  if(pT)
2045  {
2046  pT->pNext = pTask;
2047  }
2048  else
2049  {
2050  pCBTHead = pTask;
2051  }
2052 
2053  // wait for it to complete [TODO: do I need to do this?]
2054 
2055  if(WBCondWaitMutex((WB_COND *)&(pTask->cond), &xClipboardMutex, 200000L)) // wait up to 0.2 sec for completion
2056  {
2057  // some kind of error - unhook it from the chain. mutex is owned.
2058  WB_ERROR_PRINT("%s - error waiting on cond,mutex for completionk\n", __FUNCTION__);
2059 
2060  pT = pCBTHead; // if it hasn't been processed yet, it's in this linked list
2061 
2062  while(pT && pT->pNext)
2063  {
2064  if(pT->pNext == pTask)
2065  {
2066  pT->pNext = pTask->pNext;
2067 
2068  break;
2069  }
2070  }
2071 
2072  WBInterlockedExchange((unsigned int *)&(pTask->fState), -1); // changes state to 'error' so it will self-clean-up
2073  // in case that it wasn't in 'pCBTHead', which is likely
2074 
2075  iRval = -4;
2076  }
2077 
2078  // at this point 'xClipboardMutex' is assumed to be LOCKED
2079 
2080  if(!WBInterlockedDecrement((unsigned int *)&(pTask->nRefCount))) // flags a delete when ref count is zero
2081  {
2082  WBCondFree((WB_COND *)&(pTask->cond)); // assume it works
2083 
2084  WBFree((void *)pTask); // do this while locked
2085  }
2086 
2087  pTask = NULL; // so I don't try to re-use it
2088 
2089  WBMutexUnlock(&xClipboardMutex);
2090 
2091 exit_point:
2092 
2093  if(pTask)
2094  {
2095  WBCondFree((WB_COND *)&(pTask->cond)); // assume it works
2096  }
2097 
2098 exit_point0:
2099 
2100  if(pTask)
2101  {
2102  WBFree((void *)pTask); // TODO: on error, this might cause a crash... or not
2103  }
2104 
2105  return iRval;
2106 }
2107 
2108 
2109 
2110