1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2
3 /* Asynchronous X property getting hack */
4
5 /*
6 * Copyright (C) 2002 Havoc Pennington
7 * Copyright (C) 1986, 1998 The Open Group
8 *
9 * Permission to use, copy, modify, distribute, and sell this software
10 * and its documentation for any purpose is hereby granted without
11 * fee, provided that the above copyright notice appear in all copies
12 * and that both that copyright notice and this permission notice
13 * appear in supporting documentation.
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN GROUP BE LIABLE FOR
22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 *
26 * Except as contained in this notice, the name of The Open Group shall not be
27 * used in advertising or otherwise to promote the sale, use or other dealings
28 * in this Software without prior written authorization from The Open Group.
29 */
30 #if HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33
34 #include <assert.h>
35
36 #undef DEBUG_SPEW
37 #ifdef DEBUG_SPEW
38 #include <stdio.h>
39 #endif
40
41 #include "async-getprop.h"
42
43 #define NEED_REPLIES
44 #include <X11/Xlibint.h>
45
46 #ifndef NULL
47 #define NULL ((void*)0)
48 #endif
49
50 typedef struct _ListNode ListNode;
51 typedef struct _AgPerDisplayData AgPerDisplayData;
52
53 struct _ListNode
54 {
55 ListNode *next;
56 };
57
58 struct _AgGetPropertyTask
59 {
60 ListNode node;
61
62 AgPerDisplayData *dd;
63 Window window;
64 Atom property;
65
66 unsigned long request_seq;
67 int error;
68
69 Atom actual_type;
70 int actual_format;
71
72 unsigned long n_items;
73 unsigned long bytes_after;
74 char *data;
75
76 Bool have_reply;
77 };
78
79 struct _AgPerDisplayData
80 {
81 ListNode node;
82 _XAsyncHandler async;
83
84 Display *display;
85 ListNode *pending_tasks;
86 ListNode *pending_tasks_tail;
87 ListNode *completed_tasks;
88 ListNode *completed_tasks_tail;
89 int n_tasks_pending;
90 int n_tasks_completed;
91 };
92
93 static ListNode *display_datas = NULL;
94 static ListNode *display_datas_tail = NULL;
95
96 static void
append_to_list(ListNode ** head,ListNode ** tail,ListNode * task)97 append_to_list (ListNode **head,
98 ListNode **tail,
99 ListNode *task)
100 {
101 task->next = NULL;
102
103 if (*tail == NULL)
104 {
105 assert (*head == NULL);
106 *head = task;
107 *tail = task;
108 }
109 else
110 {
111 (*tail)->next = task;
112 *tail = task;
113 }
114 }
115
116 static void
remove_from_list(ListNode ** head,ListNode ** tail,ListNode * task)117 remove_from_list (ListNode **head,
118 ListNode **tail,
119 ListNode *task)
120 {
121 ListNode *prev;
122 ListNode *node;
123
124 prev = NULL;
125 node = *head;
126 while (node != NULL)
127 {
128 if (node == task)
129 {
130 if (prev)
131 prev->next = node->next;
132 else
133 *head = node->next;
134
135 if (node == *tail)
136 *tail = prev;
137
138 break;
139 }
140
141 prev = node;
142 node = node->next;
143 }
144
145 /* can't remove what's not there */
146 assert (node != NULL);
147
148 node->next = NULL;
149 }
150
151 static void
move_to_completed(AgPerDisplayData * dd,AgGetPropertyTask * task)152 move_to_completed (AgPerDisplayData *dd,
153 AgGetPropertyTask *task)
154 {
155 remove_from_list (&dd->pending_tasks,
156 &dd->pending_tasks_tail,
157 &task->node);
158
159 append_to_list (&dd->completed_tasks,
160 &dd->completed_tasks_tail,
161 &task->node);
162
163 dd->n_tasks_pending -= 1;
164 dd->n_tasks_completed += 1;
165 }
166
167 static AgGetPropertyTask*
find_pending_by_request_sequence(AgPerDisplayData * dd,unsigned long request_seq)168 find_pending_by_request_sequence (AgPerDisplayData *dd,
169 unsigned long request_seq)
170 {
171 ListNode *node;
172
173 /* if the sequence is after our last pending task, we
174 * aren't going to find a match
175 */
176 {
177 AgGetPropertyTask *task = (AgGetPropertyTask*) dd->pending_tasks_tail;
178 if (task != NULL)
179 {
180 if (task->request_seq < request_seq)
181 return NULL;
182 else if (task->request_seq == request_seq)
183 return task; /* why not check this */
184 }
185 }
186
187 /* Generally we should get replies in the order we sent
188 * requests, so we should usually be using the task
189 * at the head of the list, if we use any task at all.
190 * I'm not sure this is 100% guaranteed, if it is,
191 * it would be a big speedup.
192 */
193
194 node = dd->pending_tasks;
195 while (node != NULL)
196 {
197 AgGetPropertyTask *task = (AgGetPropertyTask*) node;
198
199 if (task->request_seq == request_seq)
200 return task;
201
202 node = node->next;
203 }
204
205 return NULL;
206 }
207
208 static Bool
async_get_property_handler(Display * dpy,xReply * rep,char * buf,int len,XPointer data)209 async_get_property_handler (Display *dpy,
210 xReply *rep,
211 char *buf,
212 int len,
213 XPointer data)
214 {
215 xGetPropertyReply replbuf;
216 xGetPropertyReply *reply;
217 AgGetPropertyTask *task;
218 AgPerDisplayData *dd;
219 int bytes_read;
220
221 dd = (AgPerDisplayData*) data;
222
223 #if 0
224 printf ("%s: seeing request seq %ld buflen %d\n", __FUNCTION__,
225 dpy->last_request_read, len);
226 #endif
227
228 task = find_pending_by_request_sequence (dd, dpy->last_request_read);
229
230 if (task == NULL)
231 return False;
232
233 assert (dpy->last_request_read == task->request_seq);
234
235 task->have_reply = True;
236 move_to_completed (dd, task);
237
238 /* read bytes so far */
239 bytes_read = SIZEOF (xReply);
240
241 if (rep->generic.type == X_Error)
242 {
243 xError errbuf;
244
245 task->error = rep->error.errorCode;
246
247 #ifdef DEBUG_SPEW
248 printf ("%s: error code = %d (ignoring error, eating %d bytes, generic.length = %ld)\n",
249 __FUNCTION__, task->error, (SIZEOF (xError) - bytes_read),
250 rep->generic.length);
251 #endif
252
253 /* We return True (meaning we consumed the reply)
254 * because otherwise it would invoke the X error handler,
255 * and an async API is useless if you have to synchronously
256 * trap X errors. Also GetProperty can always fail, pretty
257 * much, so trapping errors is always what you want.
258 *
259 * We have to eat all the error reply data here.
260 * (kind of a charade as we know sizeof(xError) == sizeof(xReply))
261 *
262 * Passing discard = True seems to break things; I don't understand
263 * why, because there should be no extra data in an error reply,
264 * right?
265 */
266 _XGetAsyncReply (dpy, (char *)&errbuf, rep, buf, len,
267 (SIZEOF (xError) - bytes_read) >> 2, /* in 32-bit words */
268 False); /* really seems like it should be True */
269
270 return True;
271 }
272
273 #ifdef DEBUG_SPEW
274 printf ("%s: already read %d bytes reading %d more for total of %d; generic.length = %ld\n",
275 __FUNCTION__, bytes_read, (SIZEOF (xGetPropertyReply) - bytes_read) >> 2,
276 SIZEOF (xGetPropertyReply), rep->generic.length);
277 #endif
278
279 /* (kind of a silly as we know sizeof(xGetPropertyReply) == sizeof(xReply)) */
280 reply = (xGetPropertyReply *)
281 _XGetAsyncReply (dpy, (char *)&replbuf, rep, buf, len,
282 (SIZEOF (xGetPropertyReply) - bytes_read) >> 2, /* in 32-bit words */
283 False); /* False means expecting more data to follow,
284 * don't eat the rest of the reply
285 */
286
287 bytes_read = SIZEOF (xGetPropertyReply);
288
289 #ifdef DEBUG_SPEW
290 printf ("%s: have reply propertyType = %ld format = %d n_items = %ld\n",
291 __FUNCTION__, reply->propertyType, reply->format, reply->nItems);
292 #endif
293
294 assert (task->data == NULL);
295
296 /* This is all copied from XGetWindowProperty(). Not sure we should
297 * LockDisplay(). Not sure I'm passing the right args to
298 * XGetAsyncData(). Not sure about a lot of things.
299 */
300
301 /* LockDisplay (dpy); */
302
303 if (reply->propertyType != None)
304 {
305 long nbytes, netbytes;
306
307 /* this alignment macro from orbit2 */
308 #define ALIGN_VALUE(this, boundary) \
309 (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1)))
310
311 switch (reply->format)
312 {
313 /*
314 * One extra byte is malloced than is needed to contain the property
315 * data, but this last byte is null terminated and convenient for
316 * returning string properties, so the client doesn't then have to
317 * recopy the string to make it null terminated.
318 */
319 case 8:
320 nbytes = reply->nItems;
321 /* there's padding to word boundary */
322 netbytes = ALIGN_VALUE (nbytes, 4);
323 if (nbytes + 1 > 0 &&
324 (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
325 {
326 #ifdef DEBUG_SPEW
327 printf ("%s: already read %d bytes using %ld, more eating %ld more\n",
328 __FUNCTION__, bytes_read, nbytes, netbytes);
329 #endif
330 /* _XReadPad (dpy, (char *) task->data, netbytes); */
331 _XGetAsyncData (dpy, task->data, buf, len,
332 bytes_read, nbytes,
333 netbytes);
334 }
335 break;
336
337 case 16:
338 nbytes = reply->nItems * sizeof (short);
339 netbytes = reply->nItems << 1;
340 netbytes = ALIGN_VALUE (netbytes, 4); /* align to word boundary */
341 if (nbytes + 1 > 0 &&
342 (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
343 {
344 #ifdef DEBUG_SPEW
345 printf ("%s: already read %d bytes using %ld more, eating %ld more\n",
346 __FUNCTION__, bytes_read, nbytes, netbytes);
347 #endif
348 /* _XRead16Pad (dpy, (short *) task->data, netbytes); */
349 _XGetAsyncData (dpy, task->data, buf, len,
350 bytes_read, nbytes, netbytes);
351 }
352 break;
353
354 case 32:
355 /* NOTE buffer is in longs to match XGetWindowProperty() */
356 nbytes = reply->nItems * sizeof (long);
357 netbytes = reply->nItems << 2; /* wire size is always 32 bits though */
358 if (nbytes + 1 > 0 &&
359 (task->data = (char *) Xmalloc ((unsigned)nbytes + 1)))
360 {
361 #ifdef DEBUG_SPEW
362 printf ("%s: already read %d bytes using %ld more, eating %ld more\n",
363 __FUNCTION__, bytes_read, nbytes, netbytes);
364 #endif
365
366 /* We have to copy the XGetWindowProperty() crackrock
367 * and get format 32 as long even on 64-bit platforms.
368 */
369 if (sizeof (long) == 8)
370 {
371 char *netdata;
372 char *lptr;
373 char *end_lptr;
374
375 /* Store the 32-bit values in the end of the array */
376 netdata = task->data + nbytes / 2;
377
378 _XGetAsyncData (dpy, netdata, buf, len,
379 bytes_read, netbytes,
380 netbytes);
381
382 /* Now move the 32-bit values to the front */
383
384 lptr = task->data;
385 end_lptr = task->data + nbytes;
386 while (lptr != end_lptr)
387 {
388 *(long*) lptr = *(CARD32*) netdata;
389 lptr += sizeof (long);
390 netdata += sizeof (CARD32);
391 }
392 }
393 else
394 {
395 /* Here the wire format matches our actual format */
396 _XGetAsyncData (dpy, task->data, buf, len,
397 bytes_read, netbytes,
398 netbytes);
399 }
400 }
401 break;
402
403 default:
404 /*
405 * This part of the code should never be reached. If it is,
406 * the server sent back a property with an invalid format.
407 * This is a BadImplementation error.
408 *
409 * However this async GetProperty API doesn't report errors
410 * via the standard X mechanism, so don't do anything about
411 * it, other than store it in task->error.
412 */
413 {
414 #if 0
415 xError error;
416 #endif
417
418 task->error = BadImplementation;
419
420 #if 0
421 error.sequenceNumber = task->request_seq;
422 error.type = X_Error;
423 error.majorCode = X_GetProperty;
424 error.minorCode = 0;
425 error.errorCode = BadImplementation;
426
427 _XError (dpy, &error);
428 #endif
429 }
430
431 nbytes = netbytes = 0L;
432 break;
433 }
434
435 if (task->data == NULL)
436 {
437 task->error = BadAlloc;
438
439 #ifdef DEBUG_SPEW
440 printf ("%s: already read %d bytes eating %ld\n",
441 __FUNCTION__, bytes_read, netbytes);
442 #endif
443 /* _XEatData (dpy, (unsigned long) netbytes); */
444 _XGetAsyncData (dpy, NULL, buf, len,
445 bytes_read, 0, netbytes);
446
447 /* UnlockDisplay (dpy); */
448 return BadAlloc; /* not Success */
449 }
450
451 (task->data)[nbytes] = '\0';
452 }
453
454 #ifdef DEBUG_SPEW
455 printf ("%s: have data\n", __FUNCTION__);
456 #endif
457
458 task->actual_type = reply->propertyType;
459 task->actual_format = reply->format;
460 task->n_items = reply->nItems;
461 task->bytes_after = reply->bytesAfter;
462
463 /* UnlockDisplay (dpy); */
464
465 return True;
466 }
467
468 static AgPerDisplayData*
get_display_data(Display * display,Bool create)469 get_display_data (Display *display,
470 Bool create)
471 {
472 ListNode *node;
473 AgPerDisplayData *dd;
474
475 node = display_datas;
476 while (node != NULL)
477 {
478 dd = (AgPerDisplayData*) node;
479
480 if (dd->display == display)
481 return dd;
482
483 node = node->next;
484 }
485
486 if (!create)
487 return NULL;
488
489 dd = Xcalloc (1, sizeof (AgPerDisplayData));
490 if (dd == NULL)
491 return NULL;
492
493 dd->display = display;
494 dd->async.next = display->async_handlers;
495 dd->async.handler = async_get_property_handler;
496 dd->async.data = (XPointer) dd;
497 dd->display->async_handlers = &dd->async;
498
499 append_to_list (&display_datas,
500 &display_datas_tail,
501 &dd->node);
502
503 return dd;
504 }
505
506 static void
maybe_free_display_data(AgPerDisplayData * dd)507 maybe_free_display_data (AgPerDisplayData *dd)
508 {
509 if (dd->pending_tasks == NULL &&
510 dd->completed_tasks == NULL)
511 {
512 DeqAsyncHandler (dd->display, &dd->async);
513 remove_from_list (&display_datas, &display_datas_tail,
514 &dd->node);
515 XFree (dd);
516 }
517 }
518
519 LOCAL_SYMBOL AgGetPropertyTask*
ag_task_create(Display * dpy,Window window,Atom property,long offset,long length,Bool delete,Atom req_type)520 ag_task_create (Display *dpy,
521 Window window,
522 Atom property,
523 long offset,
524 long length,
525 Bool delete,
526 Atom req_type)
527 {
528 AgGetPropertyTask *task;
529 xGetPropertyReq *req;
530 AgPerDisplayData *dd;
531
532 /* Fire up our request */
533 LockDisplay (dpy);
534
535 dd = get_display_data (dpy, True);
536 if (dd == NULL)
537 {
538 UnlockDisplay (dpy);
539 return NULL;
540 }
541
542 GetReq (GetProperty, req);
543 req->window = window;
544 req->property = property;
545 req->type = req_type;
546 req->delete = delete;
547 req->longOffset = offset;
548 req->longLength = length;
549
550 /* Queue up our async task */
551 task = Xcalloc (1, sizeof (AgGetPropertyTask));
552 if (task == NULL)
553 {
554 UnlockDisplay (dpy);
555 return NULL;
556 }
557
558 task->dd = dd;
559 task->window = window;
560 task->property = property;
561 task->request_seq = dpy->request;
562
563 append_to_list (&dd->pending_tasks,
564 &dd->pending_tasks_tail,
565 &task->node);
566 dd->n_tasks_pending += 1;
567
568 UnlockDisplay (dpy);
569
570 SyncHandle ();
571
572 return task;
573 }
574
575 static void
free_task(AgGetPropertyTask * task)576 free_task (AgGetPropertyTask *task)
577 {
578 remove_from_list (&task->dd->completed_tasks,
579 &task->dd->completed_tasks_tail,
580 &task->node);
581 task->dd->n_tasks_completed -= 1;
582 maybe_free_display_data (task->dd);
583 XFree (task);
584 }
585
586 LOCAL_SYMBOL Status
ag_task_get_reply_and_free(AgGetPropertyTask * task,Atom * actual_type,int * actual_format,unsigned long * nitems,unsigned long * bytesafter,unsigned char ** prop)587 ag_task_get_reply_and_free (AgGetPropertyTask *task,
588 Atom *actual_type,
589 int *actual_format,
590 unsigned long *nitems,
591 unsigned long *bytesafter,
592 unsigned char **prop)
593 {
594 Display *dpy;
595
596 *prop = NULL;
597
598 dpy = task->dd->display; /* Xlib macros require a variable named "dpy" */
599
600 if (task->error != Success)
601 {
602 Status s = task->error;
603
604 free_task (task);
605
606 return s;
607 }
608
609 if (!task->have_reply)
610 {
611 free_task (task);
612
613 return BadAlloc; /* not Success */
614 }
615
616 *actual_type = task->actual_type;
617 *actual_format = task->actual_format;
618 *nitems = task->n_items;
619 *bytesafter = task->bytes_after;
620
621 *prop = (unsigned char*) task->data; /* pass out ownership of task->data */
622
623 SyncHandle ();
624
625 free_task (task);
626
627 return Success;
628 }
629
630 LOCAL_SYMBOL Bool
ag_task_have_reply(AgGetPropertyTask * task)631 ag_task_have_reply (AgGetPropertyTask *task)
632 {
633 return task->have_reply;
634 }
635
636 LOCAL_SYMBOL Atom
ag_task_get_property(AgGetPropertyTask * task)637 ag_task_get_property (AgGetPropertyTask *task)
638 {
639 return task->property;
640 }
641
642 LOCAL_SYMBOL Window
ag_task_get_window(AgGetPropertyTask * task)643 ag_task_get_window (AgGetPropertyTask *task)
644 {
645 return task->window;
646 }
647
648 LOCAL_SYMBOL Display*
ag_task_get_display(AgGetPropertyTask * task)649 ag_task_get_display (AgGetPropertyTask *task)
650 {
651 return task->dd->display;
652 }
653
654 LOCAL_SYMBOL AgGetPropertyTask*
ag_get_next_completed_task(Display * display)655 ag_get_next_completed_task (Display *display)
656 {
657 AgPerDisplayData *dd;
658
659 dd = get_display_data (display, False);
660
661 if (dd == NULL)
662 return NULL;
663
664 #ifdef DEBUG_SPEW
665 printf ("%d pending %d completed\n",
666 dd->n_tasks_pending,
667 dd->n_tasks_completed);
668 #endif
669
670 return (AgGetPropertyTask*) dd->completed_tasks;
671 }
672
673 LOCAL_SYMBOL void*
ag_Xmalloc(unsigned long bytes)674 ag_Xmalloc (unsigned long bytes)
675 {
676 return (void*) Xmalloc (bytes);
677 }
678
679 LOCAL_SYMBOL void*
ag_Xmalloc0(unsigned long bytes)680 ag_Xmalloc0 (unsigned long bytes)
681 {
682 return (void*) Xcalloc (bytes, 1);
683 }
684