1 /*
2 Copyright (C) 2002-2010 Karl J. Runge <runge@karlrunge.com>
3 All rights reserved.
4
5 This file is part of x11vnc.
6
7 x11vnc is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or (at
10 your option) any later version.
11
12 x11vnc is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with x11vnc; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
20 or see <http://www.gnu.org/licenses/>.
21
22 In addition, as a special exception, Karl J. Runge
23 gives permission to link the code of its release of x11vnc with the
24 OpenSSL project's "OpenSSL" library (or with modified versions of it
25 that use the same license as the "OpenSSL" library), and distribute
26 the linked executables. You must obey the GNU General Public License
27 in all respects for all of the code used other than "OpenSSL". If you
28 modify this file, you may extend this exception to your version of the
29 file, but you are not obligated to do so. If you do not wish to do
30 so, delete this exception statement from your version.
31 */
32
33 /* -- win_utils.c -- */
34
35 #include "x11vnc.h"
36 #include "xinerama.h"
37 #include "winattr_t.h"
38 #include "cleanup.h"
39 #include "xwrappers.h"
40 #include "connections.h"
41 #include "xrandr.h"
42 #include "macosx.h"
43
44 winattr_t *stack_list = NULL;
45 int stack_list_len = 0;
46 int stack_list_num = 0;
47
48
49 Window parent_window(Window win, char **name);
50 int valid_window(Window win, XWindowAttributes *attr_ret, int bequiet);
51 Bool xtranslate(Window src, Window dst, int src_x, int src_y, int *dst_x,
52 int *dst_y, Window *child, int bequiet);
53 int get_window_size(Window win, int *w, int *h);
54 void snapshot_stack_list(int free_only, double allowed_age);
55 int get_boff(void);
56 int get_bwin(void);
57 void update_stack_list(void);
58 Window query_pointer(Window start);
59 unsigned int mask_state(void);
60 int pick_windowid(unsigned long *num);
61 Window descend_pointer(int depth, Window start, char *name_info, int len);
62 void id_cmd(char *cmd);
63
64
parent_window(Window win,char ** name)65 Window parent_window(Window win, char **name) {
66 #if !NO_X11
67 Window r, parent;
68 Window *list;
69 XErrorHandler old_handler;
70 unsigned int nchild;
71 int rc;
72 #endif
73
74 if (name != NULL) {
75 *name = NULL;
76 }
77 RAWFB_RET(None)
78 #if NO_X11
79 nox11_exit(1);
80 if (!name || !win) {}
81 return None;
82 #else
83
84 old_handler = XSetErrorHandler(trap_xerror);
85 trapped_xerror = 0;
86 rc = XQueryTree_wr(dpy, win, &r, &parent, &list, &nchild);
87 XSetErrorHandler(old_handler);
88
89 if (! rc || trapped_xerror) {
90 trapped_xerror = 0;
91 return None;
92 }
93 trapped_xerror = 0;
94
95 if (list) {
96 XFree_wr(list);
97 }
98 if (parent && name) {
99 XFetchName(dpy, parent, name);
100 }
101 return parent;
102 #endif /* NO_X11 */
103 }
104
105 /* trapping utility to check for a valid window: */
valid_window(Window win,XWindowAttributes * attr_ret,int bequiet)106 int valid_window(Window win, XWindowAttributes *attr_ret, int bequiet) {
107 XWindowAttributes attr, *pattr;
108 #if !NO_X11
109 XErrorHandler old_handler;
110 int ok = 0;
111 #endif
112
113 if (attr_ret == NULL) {
114 pattr = &attr;
115 } else {
116 pattr = attr_ret;
117 }
118
119 if (win == None) {
120 return 0;
121 }
122
123 #ifdef MACOSX
124 if (macosx_console) {
125 return macosx_valid_window(win, attr_ret);
126 }
127 #endif
128
129 RAWFB_RET(0)
130
131 #if NO_X11
132 nox11_exit(1);
133 if (!win || !attr_ret || !bequiet) {}
134 return 0;
135 #else
136
137 old_handler = XSetErrorHandler(trap_xerror);
138 trapped_xerror = 0;
139 if (XGetWindowAttributes(dpy, win, pattr)) {
140 ok = 1;
141 }
142 if (trapped_xerror && trapped_xerror_event) {
143 if (! quiet && ! bequiet) {
144 rfbLog("valid_window: trapped XError: %s (0x%lx)\n",
145 xerror_string(trapped_xerror_event), win);
146 }
147 ok = 0;
148 }
149 XSetErrorHandler(old_handler);
150 trapped_xerror = 0;
151
152 return ok;
153 #endif /* NO_X11 */
154 }
155
xtranslate(Window src,Window dst,int src_x,int src_y,int * dst_x,int * dst_y,Window * child,int bequiet)156 Bool xtranslate(Window src, Window dst, int src_x, int src_y, int *dst_x,
157 int *dst_y, Window *child, int bequiet) {
158 XErrorHandler old_handler = NULL;
159 Bool ok = False;
160
161 RAWFB_RET(False)
162 #if NO_X11
163 nox11_exit(1);
164 if (!src || !dst || !src_x || !src_y || !dst_x || !dst_y || !child || !bequiet) {}
165 if (!old_handler || !ok) {}
166 return False;
167 #else
168
169 trapped_xerror = 0;
170 old_handler = XSetErrorHandler(trap_xerror);
171 if (XTranslateCoordinates(dpy, src, dst, src_x, src_y, dst_x,
172 dst_y, child)) {
173 ok = True;
174 }
175 if (trapped_xerror && trapped_xerror_event) {
176 if (! quiet && ! bequiet) {
177 rfbLog("xtranslate: trapped XError: %s (0x%lx)\n",
178 xerror_string(trapped_xerror_event), src);
179 }
180 ok = False;
181 }
182 XSetErrorHandler(old_handler);
183 trapped_xerror = 0;
184
185 return ok;
186 #endif /* NO_X11 */
187 }
188
get_window_size(Window win,int * w,int * h)189 int get_window_size(Window win, int *w, int *h) {
190 XWindowAttributes attr;
191 /* valid_window? */
192 if (valid_window(win, &attr, 1)) {
193 *w = attr.width;
194 *h = attr.height;
195 return 1;
196 } else {
197 return 0;
198 }
199 }
200
201 /*
202 * For use in the -wireframe stuff, save the stacking order of the direct
203 * children of the root window. Ideally done before we send ButtonPress
204 * to the X server.
205 */
snapshot_stack_list(int free_only,double allowed_age)206 void snapshot_stack_list(int free_only, double allowed_age) {
207 static double last_snap = 0.0, last_free = 0.0;
208 double now;
209 int num, rc, i, j;
210 unsigned int ui;
211 Window r, w;
212 Window *list;
213
214 if (! stack_list) {
215 stack_list = (winattr_t *) malloc(256*sizeof(winattr_t));
216 stack_list_num = 0;
217 stack_list_len = 256;
218 }
219
220 dtime0(&now);
221 if (free_only) {
222 /* we really don't free it, just reset to zero windows */
223 stack_list_num = 0;
224 last_free = now;
225 return;
226 }
227
228 if (stack_list_num && now < last_snap + allowed_age) {
229 return;
230 }
231
232 stack_list_num = 0;
233 last_free = now;
234
235 #ifdef MACOSX
236 if (! macosx_console) {
237 RAWFB_RET_VOID
238 }
239 #else
240 RAWFB_RET_VOID
241 #endif
242
243 #if NO_X11 && !defined(MACOSX)
244 num = rc = i = j = 0; /* compiler warnings */
245 ui = 0;
246 r = w = None;
247 list = NULL;
248 return;
249 #else
250
251 X_LOCK;
252 /* no need to trap error since rootwin */
253 rc = XQueryTree_wr(dpy, rootwin, &r, &w, &list, &ui);
254 num = (int) ui;
255
256 if (! rc) {
257 stack_list_num = 0;
258 last_free = now;
259 last_snap = 0.0;
260 X_UNLOCK;
261 return;
262 }
263
264 last_snap = now;
265 if (num > stack_list_len + blackouts) {
266 int n = 2*num;
267 free(stack_list);
268 stack_list = (winattr_t *) malloc(n*sizeof(winattr_t));
269 stack_list_len = n;
270 }
271 j = 0;
272 for (i=0; i<num; i++) {
273 stack_list[j].win = list[i];
274 stack_list[j].fetched = 0;
275 stack_list[j].valid = 0;
276 stack_list[j].time = now;
277 j++;
278 }
279 for (i=0; i<blackouts; i++) {
280 stack_list[j].win = get_boff() + 1;
281 stack_list[j].fetched = 1;
282 stack_list[j].valid = 1;
283 stack_list[j].x = blackr[i].x1;
284 stack_list[j].y = blackr[i].y1;
285 stack_list[j].width = blackr[i].x2 - blackr[i].x1;
286 stack_list[j].height = blackr[i].y2 - blackr[i].y1;
287 stack_list[j].time = now;
288 stack_list[j].map_state = IsViewable;
289 stack_list[j].rx = -1;
290 stack_list[j].ry = -1;
291 j++;
292
293 if (0) fprintf(stderr, "blackr: %d %dx%d+%d+%d\n", i,
294 stack_list[j-1].width, stack_list[j-1].height,
295 stack_list[j-1].x, stack_list[j-1].y);
296
297 }
298 stack_list_num = num + blackouts;
299 if (debug_wireframe > 1) {
300 fprintf(stderr, "snapshot_stack_list: num=%d len=%d\n",
301 stack_list_num, stack_list_len);
302 }
303
304 XFree_wr(list);
305 X_UNLOCK;
306 #endif /* NO_X11 */
307 }
308
get_boff(void)309 int get_boff(void) {
310 if (macosx_console) {
311 return 0x1000000;
312 } else {
313 return 0;
314 }
315 }
316
get_bwin(void)317 int get_bwin(void) {
318 return 10;
319 }
320
update_stack_list(void)321 void update_stack_list(void) {
322 int k;
323 double now;
324 XWindowAttributes attr;
325 int boff, bwin;
326
327 if (! stack_list) {
328 return;
329 }
330 if (! stack_list_num) {
331 return;
332 }
333
334 dtime0(&now);
335
336 boff = get_boff();
337 bwin = get_bwin();
338
339 X_LOCK;
340 for (k=0; k < stack_list_num; k++) {
341 Window win = stack_list[k].win;
342 if (win != None && boff <= (int) win && (int) win < boff + bwin) {
343 ; /* special, blackout */
344 } else if (!valid_window(win, &attr, 1)) {
345 stack_list[k].valid = 0;
346 } else {
347 stack_list[k].valid = 1;
348 stack_list[k].x = attr.x;
349 stack_list[k].y = attr.y;
350 stack_list[k].width = attr.width;
351 stack_list[k].height = attr.height;
352 stack_list[k].border_width = attr.border_width;
353 stack_list[k].depth = attr.depth;
354 stack_list[k].class = attr.class;
355 stack_list[k].backing_store = attr.backing_store;
356 stack_list[k].map_state = attr.map_state;
357
358 /* root_x, root_y not used for stack_list usage: */
359 stack_list[k].rx = -1;
360 stack_list[k].ry = -1;
361 }
362 stack_list[k].fetched = 1;
363 stack_list[k].time = now;
364 }
365 X_UNLOCK;
366 if (0) fprintf(stderr, "update_stack_list[%d]: %.4f %.4f\n", stack_list_num, now - x11vnc_start, dtime(&now));
367 }
368
query_pointer(Window start)369 Window query_pointer(Window start) {
370 int rx, ry;
371 #if !NO_X11
372 Window r, c; /* compiler warnings */
373 int wx, wy;
374 unsigned int mask;
375 #endif
376
377 #ifdef MACOSX
378 if (macosx_console) {
379 macosx_get_cursor_pos(&rx, &ry);
380 }
381 #endif
382
383 RAWFB_RET(None)
384
385 #if NO_X11
386 if (!start) { rx = ry = 0; }
387 return None;
388 #else
389 if (start == None) {
390 start = rootwin;
391 }
392 if (XQueryPointer_wr(dpy, start, &r, &c, &rx, &ry, &wx, &wy, &mask)) {
393 return c;
394 } else {
395 return None;
396 }
397 #endif /* NO_X11 */
398 }
399
mask_state(void)400 unsigned int mask_state(void) {
401 #if NO_X11
402 RAWFB_RET(0)
403 return 0;
404 #else
405 Window r, c;
406 int rx, ry, wx, wy;
407 unsigned int mask;
408
409 RAWFB_RET(0)
410
411 if (XQueryPointer_wr(dpy, rootwin, &r, &c, &rx, &ry, &wx, &wy, &mask)) {
412 return mask;
413 } else {
414 return 0;
415 }
416 #endif /* NO_X11 */
417 }
418
pick_windowid(unsigned long * num)419 int pick_windowid(unsigned long *num) {
420 char line[512];
421 int ok = 0, n = 0, msec = 10, secmax = 15;
422 FILE *p;
423
424 RAWFB_RET(0)
425
426 if (use_dpy) {
427 set_env("DISPLAY", use_dpy);
428 }
429 /* id */
430 if (no_external_cmds || !cmd_ok("id")) {
431 rfbLogEnable(1);
432 rfbLog("cannot run external commands in -nocmds mode:\n");
433 rfbLog(" \"%s\"\n", "xwininfo");
434 rfbLog(" exiting.\n");
435 clean_up_exit(1);
436 }
437 close_exec_fds();
438 p = popen("xwininfo", "r");
439
440 if (! p) {
441 return 0;
442 }
443
444 fprintf(stderr, "\n");
445 fprintf(stderr, " Please select the window for x11vnc to poll\n");
446 fprintf(stderr, " by clicking the mouse in that window.\n");
447 fprintf(stderr, "\n");
448
449 while (msec * n++ < 1000 * secmax) {
450 unsigned long tmp;
451 char *q;
452 fd_set set;
453 struct timeval tv;
454
455 if (screen && screen->clientHead) {
456 /* they may be doing the pointer-pick thru vnc: */
457 int nfds;
458 tv.tv_sec = 0;
459 tv.tv_usec = msec * 1000;
460 FD_ZERO(&set);
461 FD_SET(fileno(p), &set);
462
463 nfds = select(fileno(p)+1, &set, NULL, NULL, &tv);
464
465 if (nfds == 0 || nfds < 0) {
466 /*
467 * select timedout or error.
468 * note this rfbPE takes about 30ms too:
469 */
470 rfbPE(-1);
471 XFlush_wr(dpy);
472 continue;
473 }
474 }
475
476 if (fgets(line, 512, p) == NULL) {
477 break;
478 }
479 q = strstr(line, " id: 0x");
480 if (q) {
481 q += 5;
482 if (sscanf(q, "0x%lx ", &tmp) == 1) {
483 ok = 1;
484 *num = tmp;
485 fprintf(stderr, " Picked: 0x%lx\n\n", tmp);
486 break;
487 }
488 }
489 }
490 pclose(p);
491 return ok;
492 }
493
descend_pointer(int depth,Window start,char * name_info,int len)494 Window descend_pointer(int depth, Window start, char *name_info, int len) {
495 #if NO_X11
496 RAWFB_RET(None)
497 if (!depth || !start || !name_info || !len) {}
498 return None;
499 #else
500 Window r, c, clast = None;
501 int i, rx, ry, wx, wy;
502 int written = 0, filled = 0;
503 char *store = NULL;
504 unsigned int m;
505 static XClassHint *classhint = NULL;
506 static char *nm_cache = NULL;
507 static int nm_cache_len = 0;
508 static Window prev_start = None;
509
510 RAWFB_RET(None)
511
512 if (! classhint) {
513 classhint = XAllocClassHint();
514 }
515
516 if (! nm_cache) {
517 nm_cache = (char *) malloc(1024);
518 nm_cache_len = 1024;
519 nm_cache[0] = '\0';
520 }
521 if (name_info && nm_cache_len < len) {
522 if (nm_cache) {
523 free(nm_cache);
524 }
525 nm_cache_len = 2*len;
526 nm_cache = (char *) malloc(nm_cache_len);
527 }
528
529 if (name_info) {
530 if (start != None && start == prev_start) {
531 store = NULL;
532 strncpy(name_info, nm_cache, len);
533 } else {
534 store = name_info;
535 name_info[0] = '\0';
536 }
537 }
538
539 if (start != None) {
540 c = start;
541 if (name_info) {
542 prev_start = start;
543 }
544 } else {
545 c = rootwin;
546 }
547
548 for (i=0; i<depth; i++) {
549 clast = c;
550 if (store && ! filled) {
551 char *name;
552 if (XFetchName(dpy, clast, &name) && name != NULL) {
553 int l = strlen(name);
554 if (written + l+2 < len) {
555 strcat(store, "^^");
556 written += 2;
557 strcat(store, name);
558 written += l;
559 } else {
560 filled = 1;
561 }
562 XFree_wr(name);
563 }
564 }
565 if (store && classhint && ! filled) {
566 classhint->res_name = NULL;
567 classhint->res_class = NULL;
568 if (XGetClassHint(dpy, clast, classhint)) {
569 int l = 0;
570 if (classhint->res_class) {
571 l += strlen(classhint->res_class);
572 }
573 if (classhint->res_name) {
574 l += strlen(classhint->res_name);
575 }
576 if (written + l+4 < len) {
577 strcat(store, "##");
578 if (classhint->res_class) {
579 strcat(store,
580 classhint->res_class);
581 }
582 strcat(store, "++");
583 if (classhint->res_name) {
584 strcat(store,
585 classhint->res_name);
586 }
587 written += l+4;
588 } else {
589 filled = 1;
590 }
591 if (classhint->res_class) {
592 XFree_wr(classhint->res_class);
593 }
594 if (classhint->res_name) {
595 XFree_wr(classhint->res_name);
596 }
597 }
598 }
599 if (! XQueryPointer_wr(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &m)) {
600 break;
601 }
602 if (! c) {
603 break;
604 }
605 }
606 if (start != None && name_info) {
607 strncpy(nm_cache, name_info, nm_cache_len);
608 }
609
610 return clast;
611 #endif /* NO_X11 */
612 }
613
id_cmd(char * cmd)614 void id_cmd(char *cmd) {
615 int rc, dx = 0, dy = 0, dw = 0, dh = 0;
616 int x0, y0, w0, h0;
617 int x, y, w, h, do_move = 0, do_resize = 0;
618 int disp_x = DisplayWidth(dpy, scr);
619 int disp_y = DisplayHeight(dpy, scr);
620 Window win = subwin;
621 XWindowAttributes attr;
622 XErrorHandler old_handler = NULL;
623 Window twin;
624
625 if (!cmd || !strcmp(cmd, "")) {
626 return;
627 }
628 if (strstr(cmd, "win=") == cmd) {
629 if (! scan_hexdec(cmd + strlen("win="), &win)) {
630 rfbLog("id_cmd: incorrect win= hex/dec number: %s\n", cmd);
631 return;
632 } else {
633 char *q = strchr(cmd, ':');
634 if (!q) {
635 rfbLog("id_cmd: incorrect win=...: hex/dec number: %s\n", cmd);
636 return;
637 }
638 rfbLog("id_cmd:%s set window id to 0x%lx\n", cmd, win);
639 cmd = q+1;
640 }
641 }
642 if (!win) {
643 rfbLog("id_cmd:%s not in sub-window mode or no win=0xNNNN.\n", cmd);
644 return;
645 }
646 #if !NO_X11
647 X_LOCK;
648 if (!valid_window(win, &attr, 1)) {
649 X_UNLOCK;
650 return;
651 }
652 w0 = w = attr.width;
653 h0 = h = attr.height;
654 old_handler = XSetErrorHandler(trap_xerror);
655 trapped_xerror = 0;
656 XTranslateCoordinates(dpy, win, rootwin, 0, 0, &x, &y, &twin);
657 x0 = x;
658 y0 = y;
659 if (strstr(cmd, "move:") == cmd) {
660 if (sscanf(cmd, "move:%d%d", &dx, &dy) == 2) {
661 x = x + dx;
662 y = y + dy;
663 do_move = 1;
664 }
665 } else if (strstr(cmd, "resize:") == cmd) {
666 if (sscanf(cmd, "resize:%d%d", &dw, &dh) == 2) {
667 w = w + dw;
668 h = h + dh;
669 do_move = 1;
670 do_resize = 1;
671 }
672 } else if (strstr(cmd, "geom:") == cmd) {
673 if (parse_geom(cmd+strlen("geom:"), &w, &h, &x, &y, disp_x, disp_y)) {
674 do_move = 1;
675 do_resize = 1;
676 if (w <= 0) {
677 w = w0;
678 }
679 if (h <= 0) {
680 h = h0;
681 }
682 if (scaling && getenv("X11VNC_APPSHARE_ACTIVE")) {
683 x /= scale_fac_x;
684 y /= scale_fac_y;
685 }
686 }
687 } else if (!strcmp(cmd, "raise")) {
688 rc = XRaiseWindow(dpy, win);
689 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
690 } else if (!strcmp(cmd, "lower")) {
691 rc = XLowerWindow(dpy, win);
692 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
693 } else if (!strcmp(cmd, "map")) {
694 rc= XMapRaised(dpy, win);
695 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
696 } else if (!strcmp(cmd, "unmap")) {
697 rc= XUnmapWindow(dpy, win);
698 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
699 } else if (!strcmp(cmd, "iconify")) {
700 rc= XIconifyWindow(dpy, win, scr);
701 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
702 } else if (strstr(cmd, "wm_name:") == cmd) {
703 rc= XStoreName(dpy, win, cmd+strlen("wm_name:"));
704 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
705 } else if (strstr(cmd, "icon_name:") == cmd) {
706 rc= XSetIconName(dpy, win, cmd+strlen("icon_name:"));
707 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
708 } else if (!strcmp(cmd, "wm_delete")) {
709 XClientMessageEvent ev;
710 memset(&ev, 0, sizeof(ev));
711 ev.type = ClientMessage;
712 ev.send_event = True;
713 ev.display = dpy;
714 ev.window = win;
715 ev.message_type = XInternAtom(dpy, "WM_PROTOCOLS", False);
716 ev.format = 32;
717 ev.data.l[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
718 rc = XSendEvent(dpy, win, False, 0, (XEvent *) &ev);
719 rfbLog("id_cmd:%s rc=%d\n", cmd, rc);
720 } else {
721 rfbLog("id_cmd:%s unrecognized command.\n", cmd);
722 }
723 if (do_move || do_resize) {
724 if (w >= disp_x) {
725 w = disp_x - 4;
726 }
727 if (h >= disp_y) {
728 h = disp_y - 4;
729 }
730 if (w < 1) {
731 w = 1;
732 }
733 if (h < 1) {
734 h = 1;
735 }
736 if (x + w > disp_x) {
737 x = disp_x - w - 1;
738 }
739 if (y + h > disp_y) {
740 y = disp_y - h - 1;
741 }
742 if (x < 0) {
743 x = 1;
744 }
745 if (y < 0) {
746 y = 1;
747 }
748 rc = 0;
749 rc += XMoveWindow(dpy, win, x, y);
750 off_x = x;
751 off_y = y;
752
753 rc += XResizeWindow(dpy, win, w, h);
754
755 rfbLog("id_cmd:%s rc=%d dx=%d dy=%d dw=%d dh=%d %dx%d+%d+%d -> %dx%d+%d+%d\n",
756 cmd, rc, dx, dy, dw, dh, w0, h0, x0, y0, w, h, x, h);
757 }
758 XSync(dpy, False);
759 XSetErrorHandler(old_handler);
760 if (trapped_xerror) {
761 rfbLog("id_cmd:%s trapped_xerror.\n", cmd);
762 }
763 trapped_xerror = 0;
764 if (do_resize) {
765 rfbLog("id_cmd:%s calling check_xrandr_event.\n", cmd);
766 check_xrandr_event("id_cmd");
767 }
768 X_UNLOCK;
769 #endif
770 }
771
772