]> Pileus Git - ~andy/gtk/blob - gdk/x11/gxid.c
Initial revision
[~andy/gtk] / gdk / x11 / gxid.c
1 /* 
2  * gxid version 0.3
3  *
4  * Copyright 1997 Owen Taylor <owt1@cornell.edu>
5 */
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <signal.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <netinet/in.h>
15 #include <X11/Xlib.h>
16 #include <X11/extensions/XInput.h>
17
18 #include "gxid_proto.h"
19
20 /* #define DEBUG_CLIENTS  */
21 /* #define DEBUG_EVENTS */
22
23 char *program_name;
24 Display *dpy;
25 Window root_window;             /* default root window of dpy */
26 int port = 0;                   /* port to listen on */
27 int socket_fd = 0;              /* file descriptor of socket */
28 typedef struct GxidWindow_ GxidWindow;
29
30 typedef struct GxidDevice_ GxidDevice;
31 struct GxidDevice_ {
32   XID id;
33   int exclusive;
34   int ispointer;
35   
36   XDevice *xdevice;
37   int motionnotify_type;
38   int changenotify_type;
39 };
40
41 struct GxidWindow_ {
42   Window xwindow;
43   /* Immediate child of root that is ancestor of window */
44   Window root_child;
45   int num_devices;
46   GxidDevice **devices;
47 };
48
49 GxidDevice **devices = NULL;
50 int num_devices = 0;
51 GxidWindow **windows = NULL;
52 int num_windows = 0;
53
54 void
55 handler(int signal)
56 {
57   fprintf(stderr,"%s: dying on signal %d\n",program_name,signal);
58   if (socket_fd)
59     close(socket_fd);
60   exit(1);
61 }
62
63 void
64 init_socket()
65 {
66   struct sockaddr_in sin;
67
68   socket_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
69   if (socket_fd < 0)
70     {
71       fprintf (stderr, "%s: error getting socket\n",
72                program_name);
73       exit(1);
74     }
75   
76   sin.sin_family = AF_INET;
77   sin.sin_port = htons(port);
78   sin.sin_addr.s_addr = INADDR_ANY;
79   
80   if (bind(socket_fd,(struct sockaddr *)(&sin),
81            sizeof(struct sockaddr_in)) < 0)
82     {
83       fprintf (stderr,"%s: cannot bind to port %d\n",
84                program_name,port);
85       exit(1);
86     }
87
88   if (listen(socket_fd,5) < 0)
89     {
90       fprintf (stderr,"%s: error listening on socket\n",
91                program_name);
92       exit(1);
93     };
94 }
95
96 #define NUM_EVENTC 2
97 static void
98 enable_device(GxidDevice *dev)
99 {
100   XEventClass xevc[NUM_EVENTC];
101   int num_eventc = NUM_EVENTC;
102   int i,j;
103
104   if (!dev->xdevice) 
105     {
106       if (dev->ispointer) return;
107
108       dev->xdevice = XOpenDevice(dpy, dev->id);
109       if (!dev->xdevice) return;
110       
111       DeviceMotionNotify (dev->xdevice, dev->motionnotify_type,
112                           xevc[0]);
113       ChangeDeviceNotify (dev->xdevice, dev->changenotify_type,
114                           xevc[1]);
115
116       /* compress out zero event classes */
117       for (i=0,j=0;i<NUM_EVENTC;i++)
118         {
119           if (xevc[i]) {
120             xevc[j] = xevc[i];
121             j++;
122           }
123       }
124       num_eventc = j;
125       
126       XSelectExtensionEvent (dpy, root_window, xevc, num_eventc);
127     }
128 }
129
130 /* switch the core pointer from whatever it is now to something else,
131    return true on success, false otherwise */
132 static int
133 switch_core_pointer()
134 {
135   GxidDevice *old_pointer = 0;
136   GxidDevice *new_pointer = 0;
137   int result;
138   int i;
139
140   for (i=0;i<num_devices;i++)
141     {
142       if (devices[i]->ispointer)
143         old_pointer = devices[i];
144       else
145         if (!new_pointer && !devices[i]->exclusive)
146           new_pointer = devices[i];
147     }
148
149   if (!old_pointer || !new_pointer)
150     return 0;
151
152 #ifdef DEBUG_EVENTS
153   fprintf(stderr,"gxid: Switching core from %ld to %ld\n",
154          old_pointer->id,new_pointer->id);
155 #endif
156   result = XChangePointerDevice(dpy,new_pointer->xdevice, 0, 1);
157   if (result != Success)
158     {
159       fprintf(stderr,"gxid: Error %d switching core from %ld to %ld\n",
160               result, old_pointer->id, new_pointer->id);
161     }
162   else
163     {
164       new_pointer->ispointer = 1;
165       old_pointer->ispointer = 0;
166       if (!old_pointer->xdevice)
167         enable_device(old_pointer);
168     }
169
170   return 1;
171 }
172
173 void
174 disable_device(GxidDevice *dev)
175 {
176   if (dev->xdevice)
177     {
178       if (dev->ispointer)
179         return;
180       XCloseDevice(dpy,dev->xdevice);
181       dev->xdevice = 0;
182     }
183 }
184
185 GxidDevice *
186 init_device(XDeviceInfo *xdevice)
187 {
188   GxidDevice *dev = (GxidDevice *)malloc(sizeof(GxidDevice));
189   XAnyClassPtr class;
190   int num_axes, i;
191
192   dev->id = xdevice->id;
193   dev->exclusive = 0;
194   dev->xdevice = NULL;
195
196   dev->ispointer = (xdevice->use == IsXPointer);
197
198   /* step through the classes */
199
200   num_axes = 0;
201   class = xdevice->inputclassinfo;
202   for (i=0;i<xdevice->num_classes;i++) 
203     {
204       if (class->class == ValuatorClass) 
205         {
206           XValuatorInfo *xvi = (XValuatorInfo *)class;
207           num_axes = xvi->num_axes;
208         }
209       class = (XAnyClassPtr)(((char *)class) + class->length);
210     }
211
212   /* return NULL if insufficient axes */
213   if (num_axes < 2)
214     {
215       free((void *)dev);
216       return NULL;
217     }
218
219   if (!dev->ispointer)
220       enable_device(dev);
221   return dev;
222 }
223
224 void
225 init_xinput()
226 {
227   char **extensions;
228   XDeviceInfo   *xdevices;
229   int num_xdevices;
230   int num_extensions;
231   int i;
232
233   extensions = XListExtensions(dpy, &num_extensions);
234   for (i = 0; i < num_extensions &&
235          (strcmp(extensions[i], "XInputExtension") != 0); i++);
236   XFreeExtensionList(extensions);
237   if (i == num_extensions)      /* XInput extension not found */
238     {
239       fprintf(stderr,"XInput extension not found\n");
240       exit(1);
241     }
242
243   xdevices = XListInputDevices(dpy, &num_xdevices);
244   devices = (GxidDevice **)malloc(num_xdevices * sizeof(GxidDevice *));
245
246   num_devices = 0;
247   for(i=0; i<num_xdevices; i++)
248     {
249       GxidDevice *dev = init_device(&xdevices[i]);
250       if (dev)
251           devices[num_devices++] = dev;
252     }
253   XFreeDeviceList(xdevices);
254 }
255
256 /* If this routine needs fixing, the corresponding routine
257    in gdkinputgxi.h will need it too. */
258
259 Window
260 gxi_find_root_child(Display *dpy, Window w)
261 {
262   Window root,parent;
263   Window *children;
264   int nchildren;
265
266   parent = w;
267   do 
268     {
269       w = parent;
270       XQueryTree(dpy,w,&root,&parent,&children,&nchildren);
271       if (children) XFree(children);
272     } 
273   while (parent != root);
274   
275   return w;
276 }
277
278 int
279 handle_claim_device(GxidClaimDevice *msg)
280 {
281   int i,j;
282   XID devid = ntohl(msg->device);
283   XID winid = ntohl(msg->window);
284   int exclusive = ntohl(msg->exclusive);
285   GxidDevice *device = NULL;
286   GxidWindow *window = NULL;
287
288 #ifdef DEBUG_CLIENTS
289   fprintf(stderr,"device %ld claimed (window 0x%lx)\n",devid,winid);
290 #endif  
291
292   for (i=0;i<num_devices;i++)
293     {
294       if (devices[i]->id == devid)
295         {
296           device = devices[i];
297           break;
298         }
299     }
300   if (!device)
301     {
302       fprintf(stderr,"%s: Unknown device id %ld\n",program_name,devid);
303       return GXID_RETURN_ERROR;
304     }
305
306   if (device->exclusive)
307     {
308       /* already in use */
309       fprintf(stderr,
310               "%s: Device %ld already claimed in exclusive mode\n",
311               program_name,devid);
312       return GXID_RETURN_ERROR;
313     }
314
315   if (exclusive)
316     {
317       for (i=0;i<num_windows;i++)
318         {
319           for (j=0;j<windows[i]->num_devices;j++)
320             if (windows[i]->devices[j]->id == devid)
321               {
322                 /* already in use */
323                 fprintf(stderr,
324                         "%s: Can't establish exclusive use of device %ld\n",
325                         program_name,devid);
326                 return GXID_RETURN_ERROR;
327               }
328         }
329       if (device->ispointer)
330         if (!switch_core_pointer())
331           {
332             fprintf(stderr,
333                     "%s: Can't free up core pointer %ld\n",
334                     program_name,devid);
335             return GXID_RETURN_ERROR;
336           }
337
338       device->exclusive = 1;
339       disable_device(device);
340       XSelectInput(dpy,winid,StructureNotifyMask);
341     }
342   else                          /* !exclusive */
343     {
344       /* FIXME: this is a bit improper. We probably should do this only
345          when a window is first claimed. But we might be fooled if
346          an old client died without releasing it's windows. So until
347          we look for client-window closings, do it here 
348          
349          (We do look for closings now...)
350          */
351       
352       XSelectInput(dpy,winid,EnterWindowMask|StructureNotifyMask);
353     }
354
355   for (i=0;i<num_windows;i++)
356     {
357       if (windows[i]->xwindow == winid)
358         {
359           window = windows[i];
360           break;
361         }
362     }
363
364   /* Create window structure if no devices have been previously
365      claimed on it */
366   if (!window)
367     {
368       num_windows++;
369       windows = (GxidWindow **)realloc(windows,
370                                        sizeof(GxidWindow*)*num_windows);
371       window = (GxidWindow *)malloc(sizeof(GxidWindow));
372       windows[num_windows-1] = window;
373
374       window->xwindow = winid;
375       window->root_child = gxi_find_root_child(dpy,winid);
376       window->num_devices = 0;
377       window->devices = 0;
378     }
379
380   
381   for (i=0;i<window->num_devices;i++)
382     {
383       if (window->devices[i] == device)
384         return GXID_RETURN_OK;
385     }
386   
387   window->num_devices++;
388   window->devices = (GxidDevice **)realloc(window->devices,
389                                             sizeof(GxidDevice*)*num_devices);
390   /* we need add the device to the window */
391   window->devices[i] = device;
392
393   return GXID_RETURN_OK;
394 }
395
396 int
397 handle_release_device(GxidReleaseDevice *msg)
398 {
399   int i,j;
400   XID devid = ntohl(msg->device);
401   XID winid = ntohl(msg->window);
402
403   GxidDevice *device = NULL;
404
405 #ifdef DEBUG_CLIENTS
406   fprintf(stderr,"device %ld released (window 0x%lx)\n",devid,winid);
407 #endif  
408
409   for (i=0;i<num_devices;i++)
410     {
411       if (devices[i]->id == devid)
412         {
413           device = devices[i];
414           break;
415         }
416     }
417   if (!device)
418     {
419       fprintf(stderr,"%s: Unknown device id %ld\n",program_name,devid);
420       return GXID_RETURN_ERROR;
421     }
422
423   for (i=0;i<num_windows;i++)
424     {
425       GxidWindow *w = windows[i];
426       
427       if (w->xwindow == winid)
428         for (j=0;j<w->num_devices;j++)
429           if (w->devices[j]->id == devid)
430             {
431               if (j<w->num_devices-1)
432                 w->devices[j] = w->devices[w->num_devices-1];
433               w->num_devices--;
434
435               if (w->num_devices == 0)
436                 {
437                   if (i<num_windows-1)
438                     windows[i] = windows[num_windows-1];
439                   num_windows--;
440
441                   free((void *)w);
442                   /* FIXME: should we deselect input? But what
443                      what if window is already destroyed */
444                 }
445
446               if (device->exclusive)
447                 {
448                   device->exclusive = 0;
449                   enable_device(device);
450                 }
451               return GXID_RETURN_OK;
452             }
453     }
454   
455   /* device/window combination not found */
456   fprintf(stderr,
457           "%s: Device %ld not claimed for window 0x%lx\n",
458           program_name,devid,winid);
459   return GXID_RETURN_ERROR;
460 }
461
462 void
463 handle_connection()
464 {
465   GxidMessage msg;
466   GxidU32 type;
467   int length;
468   GxidI32 retval;
469
470   int conn_fd;
471   struct sockaddr_in sin;
472   int sin_length;
473   int count;
474
475   sin_length = sizeof(struct sockaddr_in);
476   conn_fd = accept(socket_fd,(struct sockaddr *)&sin,&sin_length);
477   if (conn_fd < 0)
478     {
479       fprintf(stderr,"%s: Error accepting connection\n",
480               program_name);
481       exit(1);
482     }
483
484   /* read type and length of message */
485
486   count = read(conn_fd,(char *)&msg,2*sizeof(GxidU32));
487   if (count != 2*sizeof(GxidU32))
488     {
489       fprintf(stderr,"%s: Error reading message header\n",
490               program_name);
491       close(conn_fd);
492       return;
493     }
494   type = ntohl(msg.any.type);
495   length = ntohl(msg.any.length);
496
497   /* read rest of message */
498
499   if (length > sizeof(GxidMessage)) 
500     {
501       fprintf(stderr,"%s: Bad message length\n",
502               program_name);
503       close(conn_fd);
504       return;
505     }
506
507   count = read(conn_fd,2*sizeof(GxidU32) + (char *)&msg,
508                     length - 2*sizeof(GxidU32));
509   if (count != length - 2*sizeof(GxidU32))
510     {
511       fprintf(stderr,"%s: Error reading message body\n",
512               program_name);
513       close(conn_fd);
514       return;
515     }
516
517   switch (type)
518     {
519     case GXID_CLAIM_DEVICE:
520       retval = handle_claim_device((GxidClaimDevice *)&msg);
521       break;
522     case GXID_RELEASE_DEVICE:
523       retval = handle_release_device((GxidReleaseDevice *)&msg);
524       break;
525     default:
526       fprintf(stderr,"%s: Unknown message type: %ld (ignoring)\n",
527               program_name,type);
528       close(conn_fd);
529       return;
530     }
531
532   count = write(conn_fd,&retval,sizeof(GxidI32));
533   if (count != sizeof(GxidI32))
534     {
535       fprintf(stderr,"%s: Error writing return code\n",
536               program_name);
537     }
538   
539   close(conn_fd);
540 }
541
542 void
543 handle_motion_notify(XDeviceMotionEvent *event)
544 {
545   int i,j;
546   GxidDevice *old_device = NULL;
547   GxidDevice *new_device = NULL;
548   Window w, root, child;
549   int root_x, root_y, x, y, mask;
550   
551   for (j=0;j<num_devices;j++)
552     {
553       if (devices[j]->ispointer)
554         old_device = devices[j];
555       if (devices[j]->id == event->deviceid)
556         new_device = devices[j];
557     }
558
559   if (new_device && !new_device->exclusive && !new_device->ispointer)
560     {
561       /* make sure we aren't stealing the pointer back from a slow
562          client */
563       child = root_window;
564       do
565         {
566           w = child;
567           /* FIXME: this fails disasterously if child vanishes between
568              calls. (Which is prone to happening since we get events
569              on root just as the client exits) */
570              
571           XQueryPointer(dpy,w,&root,&child,&root_x,&root_y,
572                         &x,&y,&mask);
573         }
574       while (child != None);
575
576       for (i=0;i<num_windows;i++)
577         if (windows[i]->xwindow == w)
578           for (j=0;j<windows[i]->num_devices;j++)
579             if (windows[i]->devices[j] == new_device)
580                 return;
581       
582       /* FIXME: do something smarter with axes */
583       XChangePointerDevice(dpy,new_device->xdevice, 0, 1);
584       new_device->ispointer = 1;
585       
586       old_device->ispointer = 0;
587       if (!old_device->xdevice)
588         enable_device(old_device);
589     }
590 }
591
592 void
593 handle_change_notify(XChangeDeviceNotifyEvent *event)
594 {
595   int j;
596   GxidDevice *old_device = NULL;
597   GxidDevice *new_device = NULL;
598
599
600   for (j=0;j<num_devices;j++)
601     {
602       if (devices[j]->ispointer)
603         old_device = devices[j];
604       if (devices[j]->id == event->deviceid)
605         new_device = devices[j];
606     }
607
608 #ifdef DEBUG_EVENTS
609   fprintf(stderr,"gxid: ChangeNotify event; old = %ld; new = %ld\n",
610           old_device->id, new_device->id);
611 #endif
612
613   if (old_device != new_device)
614     {
615       new_device->ispointer = 1;
616       
617       old_device->ispointer = 0;
618       if (!old_device->xdevice)
619         enable_device(old_device);
620     }
621 }
622
623 void
624 handle_enter_notify(XEnterWindowEvent *event, GxidWindow *window)
625 {
626   int i;
627   GxidDevice *old_pointer = NULL;
628   for (i=0;i<num_devices;i++)
629     {
630       if (devices[i]->ispointer)
631         {
632           old_pointer = devices[i];
633           break;
634         }
635     }
636
637 #ifdef DEBUG_EVENTS
638   fprintf(stderr,"gxid: Enter event; oldpointer = %ld\n",
639           old_pointer->id);
640 #endif
641
642   if (old_pointer)
643     for (i=0;i<window->num_devices;i++)
644       {
645         if (window->devices[i] == old_pointer)
646           {
647             switch_core_pointer();
648             break;
649           }
650       }
651 }
652
653 void
654 handle_destroy_notify(XDestroyWindowEvent *event)
655 {
656   int i,j;
657
658   for (i=0;i<num_windows;i++)
659     if (windows[i]->xwindow == event->window)
660       {
661         GxidWindow *w = windows[i];
662         
663         for (j=0;j<w->num_devices;j++)
664           {
665 #ifdef DEBUG_CLIENTS
666             fprintf(stderr,"device %ld released on destruction of window 0x%lx.\n",
667                     w->devices[j]->id,w->xwindow);
668 #endif  
669
670             if (w->devices[j]->exclusive)
671               {
672                 w->devices[j]->exclusive = 0;
673                 enable_device(devices[j]);
674               }
675           }
676         
677         if (i<num_windows-1)
678           windows[i] = windows[num_windows-1];
679         num_windows--;
680         
681         if (w->devices)
682           free((void *)w->devices);
683         free((void *)w);
684         /* FIXME: should we deselect input? But what
685            what if window is already destroyed */
686         
687         return;
688       }
689 }
690
691 void
692 handle_xevent()
693 {
694   int i;
695   XEvent event;
696         
697   XNextEvent (dpy, &event);
698
699 #ifdef DEBUG_EVENTS
700   fprintf(stderr,"Event - type = %d; window = 0x%lx\n",
701           event.type,event.xany.window);
702 #endif
703
704   if (event.type == ConfigureNotify)
705     {
706 #ifdef DEBUG_EVENTS
707       XConfigureEvent *xce = (XConfigureEvent *)&event;
708       fprintf(stderr," configureNotify: window = 0x%lx\n",xce->window);
709 #endif
710     }
711   else if (event.type == EnterNotify)
712     {
713       /* pointer entered a claimed window */
714       for (i=0;i<num_windows;i++)
715         {
716           if (event.xany.window == windows[i]->xwindow)
717             handle_enter_notify((XEnterWindowEvent *)&event,windows[i]);
718         }
719     }
720   else if (event.type == DestroyNotify)
721     {
722       /* A claimed window was destroyed */
723       for (i=0;i<num_windows;i++)
724         {
725           if (event.xany.window == windows[i]->xwindow)
726             handle_destroy_notify((XDestroyWindowEvent *)&event);
727         }
728     }
729   else
730     for (i=0;i<num_devices;i++)
731       {
732         if (event.type == devices[i]->motionnotify_type)
733           {
734             handle_motion_notify((XDeviceMotionEvent *)&event);
735             break;
736           }
737         else if (event.type == devices[i]->changenotify_type)
738           {
739             handle_change_notify((XChangeDeviceNotifyEvent *)&event);
740             break;
741           }
742       }
743 }
744
745 void 
746 usage()
747 {
748   fprintf(stderr,"Usage: %s [-d display] [-p --gxid-port port]\n",
749           program_name);
750   exit(1);
751 }
752
753 int
754 main(int argc, char **argv)
755 {
756   int i;
757   char *display_name = NULL;
758   fd_set readfds;
759
760   program_name = argv[0];
761
762   for (i=1;i<argc;i++)
763     {
764       if (!strcmp(argv[i],"-d"))
765         {
766             if (++i >= argc) usage();
767             display_name = argv[i];
768         }
769       else if (!strcmp(argv[i],"--gxid-port") ||
770                !strcmp(argv[i],"-p"))
771         {
772           if (++i >= argc) usage();
773           port = atoi(argv[i]);
774           break;
775         }
776       else
777         usage();
778     }
779
780   if (!port) 
781     {
782       char *t = getenv("GXID_PORT");
783       if (t)
784         port = atoi(t);
785       else
786         port = 6951;
787     }
788   /* set up a signal handler so we can clean up if killed */
789
790   signal(SIGTERM,handler);
791   signal(SIGINT,handler);
792   
793   /* initialize the X connection */
794   
795   dpy = XOpenDisplay (display_name);
796   if (!dpy)
797     {
798       fprintf (stderr, "%s:  unable to open display '%s'\n",
799                program_name, XDisplayName (display_name));
800       exit (1);
801     }
802   
803   root_window = DefaultRootWindow(dpy);
804
805   /* We'll want to do this in the future if we are to support
806      gxid monitoring visibility information for clients */
807 #if 0
808   XSelectInput(dpy,root_window,SubstructureNotifyMask);
809 #endif
810   init_xinput();
811   
812   /* set up our server connection */
813   
814   init_socket();
815   
816   /* main loop */
817
818   if (XPending(dpy))            /* this seems necessary to get things
819                                    in sync */
820     handle_xevent();
821   while (1) 
822     {
823
824       FD_ZERO(&readfds);
825       FD_SET(ConnectionNumber(dpy),&readfds);
826       FD_SET(socket_fd,&readfds);
827
828       if (select(8*sizeof(readfds),&readfds,
829                  (fd_set *)0,(fd_set *)0, (struct timeval *)0) < 0)
830         {
831           fprintf(stderr,"Error in select\n");
832           exit(1);
833         }
834
835       if (FD_ISSET(socket_fd,&readfds))
836         handle_connection(socket_fd);
837         
838       while (XPending(dpy))
839         handle_xevent();
840     }
841
842   XCloseDisplay (dpy);
843   exit (0);
844 }