hostx.c revision 05b261ec
1/*
2 * Xephyr - A kdrive X server thats runs in a host X window.
3 *          Authored by Matthew Allum <mallum@o-hand.com>
4 *
5 * Copyright � 2004 Nokia
6 *
7 * Permission to use, copy, modify, distribute, and sell this software and its
8 * documentation for any purpose is hereby granted without fee, provided that
9 * the above copyright notice appear in all copies and that both that
10 * copyright notice and this permission notice appear in supporting
11 * documentation, and that the name of Nokia not be used in
12 * advertising or publicity pertaining to distribution of the software without
13 * specific, written prior permission. Nokia makes no
14 * representations about the suitability of this software for any purpose.  It
15 * is provided "as is" without express or implied warranty.
16 *
17 * NOKIA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
18 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
19 * EVENT SHALL NOKIA BE LIABLE FOR ANY SPECIAL, INDIRECT OR
20 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
21 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
22 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
23 * PERFORMANCE OF THIS SOFTWARE.
24 */
25
26#include "hostx.h"
27
28#include <stdlib.h>
29#include <stdio.h>
30#include <unistd.h>
31#include <string.h> 		/* for memset */
32#include <time.h>
33
34#include <sys/ipc.h>
35#include <sys/shm.h>
36#include <sys/time.h>
37
38#include <X11/Xlib.h>
39#include <X11/Xutil.h>
40#include <X11/Xatom.h>
41#include <X11/keysym.h>
42#include <X11/extensions/XShm.h>
43
44/*
45 * All xlib calls go here, which gets built as its own .a .
46 * Mixing kdrive and xlib headers causes all sorts of types
47 * to get clobbered.
48 */
49
50struct EphyrHostXVars
51{
52  char           *server_dpy_name;
53  Display        *dpy;
54  int             screen;
55  Visual         *visual;
56  Window          win, winroot;
57  Window          win_pre_existing; 	/* Set via -parent option like xnest */
58  GC              gc;
59  int             depth;
60  int             server_depth;
61  XImage         *ximg;
62  int             win_width, win_height;
63  Bool            use_host_cursor;
64  Bool            use_fullscreen;
65  Bool            have_shm;
66
67  long            damage_debug_msec;
68
69  unsigned char  *fb_data;   	/* only used when host bpp != server bpp */
70  unsigned long   cmap[256];
71
72  XShmSegmentInfo shminfo;
73};
74
75/* memset ( missing> ) instead of below  */
76static EphyrHostXVars HostX = { "?", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
77
78static int            HostXWantDamageDebug = 0;
79
80extern EphyrKeySyms   ephyrKeySyms;
81
82extern int            monitorResolution;
83
84static void
85hostx_set_fullscreen_hint(void);
86
87/* X Error traps */
88
89static int trapped_error_code = 0;
90static int (*old_error_handler) (Display *d, XErrorEvent *e);
91
92#define host_depth_matches_server() (HostX.depth == HostX.server_depth)
93
94
95static int
96error_handler(Display     *display,
97              XErrorEvent *error)
98{
99  trapped_error_code = error->error_code;
100  return 0;
101}
102
103static void
104hostx_errors_trap(void)
105{
106  trapped_error_code = 0;
107  old_error_handler = XSetErrorHandler(error_handler);
108}
109
110static int
111hostx_errors_untrap(void)
112{
113  XSetErrorHandler(old_error_handler);
114  return trapped_error_code;
115}
116
117int
118hostx_want_screen_size(int *width, int *height)
119{
120 if (HostX.win_pre_existing != None
121     || HostX.use_fullscreen == True)
122    {
123      *width  = HostX.win_width;
124      *height = HostX.win_height;
125      return 1;
126    }
127
128 return 0;
129}
130
131void
132hostx_set_display_name(char *name)
133{
134  HostX.server_dpy_name = strdup(name);
135}
136
137void
138hostx_set_win_title(char *extra_text)
139{
140  char buf[256];
141
142  snprintf(buf, 256, "Xephyr on %s %s",
143	   HostX.server_dpy_name,
144	   (extra_text != NULL) ? extra_text : "");
145
146  XStoreName(HostX.dpy, HostX.win, buf);
147}
148
149int
150hostx_want_host_cursor(void)
151{
152  return HostX.use_host_cursor;
153}
154
155void
156hostx_use_host_cursor(void)
157{
158  HostX.use_host_cursor = True;
159}
160
161int
162hostx_want_preexisting_window(void)
163{
164  if (HostX.win_pre_existing)
165    return 1;
166  else
167    return 0;
168}
169
170void
171hostx_use_fullscreen(void)
172{
173  HostX.use_fullscreen = True;
174}
175
176int
177hostx_want_fullscreen(void)
178{
179  return HostX.use_fullscreen;
180}
181
182static void
183hostx_set_fullscreen_hint(void)
184{
185  Atom atom_WINDOW_STATE, atom_WINDOW_STATE_FULLSCREEN;
186
187  atom_WINDOW_STATE
188    = XInternAtom(HostX.dpy, "_NET_WM_STATE", False);
189  atom_WINDOW_STATE_FULLSCREEN
190    = XInternAtom(HostX.dpy, "_NET_WM_STATE_FULLSCREEN",False);
191
192  XChangeProperty(HostX.dpy, HostX.win,
193		  atom_WINDOW_STATE, XA_ATOM, 32,
194		  PropModeReplace,
195		  (unsigned char *)&atom_WINDOW_STATE_FULLSCREEN, 1);
196}
197
198void
199hostx_use_preexisting_window(unsigned long win_id)
200{
201  HostX.win_pre_existing = win_id;
202}
203
204static void
205hostx_toggle_damage_debug(void)
206{
207  HostXWantDamageDebug ^= 1;
208}
209
210void
211hostx_handle_signal(int signum)
212{
213  hostx_toggle_damage_debug();
214  EPHYR_DBG("Signal caught. Damage Debug:%i\n", HostXWantDamageDebug);
215}
216
217int
218hostx_init(void)
219{
220  XSetWindowAttributes  attr;
221  Cursor                empty_cursor;
222  Pixmap                cursor_pxm;
223  XColor                col;
224
225  attr.event_mask =
226    ButtonPressMask
227    |ButtonReleaseMask
228    |PointerMotionMask
229    |KeyPressMask
230    |KeyReleaseMask
231    |ExposureMask;
232
233  EPHYR_DBG("mark");
234
235  if ((HostX.dpy = XOpenDisplay(getenv("DISPLAY"))) == NULL)
236  {
237    fprintf(stderr, "\nXephyr cannot open host display. Is DISPLAY set?\n");
238    exit(1);
239  }
240
241  HostX.screen  = DefaultScreen(HostX.dpy);
242  HostX.winroot = RootWindow(HostX.dpy, HostX.screen);
243  HostX.gc      = XCreateGC(HostX.dpy, HostX.winroot, 0, NULL);
244  HostX.depth   = DefaultDepth(HostX.dpy, HostX.screen);
245  HostX.visual  = DefaultVisual(HostX.dpy, HostX.screen);
246
247  HostX.server_depth = HostX.depth;
248
249  if (HostX.win_pre_existing != None)
250    {
251      Status            result;
252      XWindowAttributes prewin_attr;
253
254      /* Get screen size from existing window */
255
256      hostx_errors_trap();
257
258      result = XGetWindowAttributes(HostX.dpy,
259				    HostX.win_pre_existing,
260				    &prewin_attr);
261
262
263      if (hostx_errors_untrap() || !result)
264	{
265	  fprintf(stderr, "\nXephyr -parent window' does not exist!\n");
266	  exit(1);
267	}
268
269      HostX.win_width  = prewin_attr.width;
270      HostX.win_height = prewin_attr.height;
271
272      HostX.win = XCreateWindow(HostX.dpy,
273				HostX.win_pre_existing,
274				0,0,HostX.win_width,HostX.win_height,
275				0,
276				CopyFromParent,
277				CopyFromParent,
278				CopyFromParent,
279				CWEventMask,
280				&attr);
281    }
282  else
283    {
284      HostX.win = XCreateWindow(HostX.dpy,
285				HostX.winroot,
286				0,0,100,100, /* will resize  */
287				0,
288			        CopyFromParent,
289				CopyFromParent,
290				CopyFromParent,
291				CWEventMask,
292				&attr);
293
294      hostx_set_win_title("( ctrl+shift grabs mouse and keyboard )");
295
296      if (HostX.use_fullscreen)
297	{
298	  HostX.win_width  = DisplayWidth(HostX.dpy, HostX.screen);
299	  HostX.win_height = DisplayHeight(HostX.dpy, HostX.screen);
300
301	  hostx_set_fullscreen_hint();
302	}
303    }
304
305
306  XParseColor(HostX.dpy, DefaultColormap(HostX.dpy,HostX.screen), "red", &col);
307  XAllocColor(HostX.dpy, DefaultColormap(HostX.dpy, HostX.screen), &col);
308  XSetForeground(HostX.dpy, HostX.gc, col.pixel);
309
310  if (!hostx_want_host_cursor())
311    {
312      /* Ditch the cursor, we provide our 'own' */
313      cursor_pxm = XCreatePixmap (HostX.dpy, HostX.winroot, 1, 1, 1);
314      memset (&col, 0, sizeof (col));
315      empty_cursor = XCreatePixmapCursor (HostX.dpy,
316					  cursor_pxm, cursor_pxm,
317					  &col, &col, 1, 1);
318      XDefineCursor (HostX.dpy, HostX.win, empty_cursor);
319      XFreePixmap (HostX.dpy, cursor_pxm);
320    }
321
322  HostX.ximg   = NULL;
323
324  /* Try to get share memory ximages for a little bit more speed */
325
326  if (!XShmQueryExtension(HostX.dpy) || getenv("XEPHYR_NO_SHM"))
327    {
328      fprintf(stderr, "\nXephyr unable to use SHM XImages\n");
329      HostX.have_shm = False;
330    }
331  else
332    {
333      /* Really really check we have shm - better way ?*/
334      XShmSegmentInfo shminfo;
335
336      HostX.have_shm = True;
337
338      shminfo.shmid=shmget(IPC_PRIVATE, 1, IPC_CREAT|0777);
339      shminfo.shmaddr=shmat(shminfo.shmid,0,0);
340      shminfo.readOnly=True;
341
342      hostx_errors_trap();
343
344      XShmAttach(HostX.dpy, &shminfo);
345      XSync(HostX.dpy, False);
346
347      if (hostx_errors_untrap())
348	{
349	  fprintf(stderr, "\nXephyr unable to use SHM XImages\n");
350	  HostX.have_shm = False;
351	}
352
353      shmdt(shminfo.shmaddr);
354      shmctl(shminfo.shmid, IPC_RMID, 0);
355    }
356
357  XFlush(HostX.dpy);
358
359  /* Setup the pause time between paints when debugging updates */
360
361  HostX.damage_debug_msec = 20000; /* 1/50 th of a second */
362
363  if (getenv("XEPHYR_PAUSE"))
364    {
365      HostX.damage_debug_msec = strtol(getenv("XEPHYR_PAUSE"), NULL, 0);
366      EPHYR_DBG("pause is %li\n", HostX.damage_debug_msec);
367    }
368
369  return 1;
370}
371
372int
373hostx_get_depth (void)
374{
375  return HostX.depth;
376}
377
378int
379hostx_get_server_depth (void)
380{
381  return HostX.server_depth;
382}
383
384void
385hostx_set_server_depth(int depth)
386{
387  HostX.server_depth = depth;
388}
389
390int
391hostx_get_bpp(void)
392{
393  if (host_depth_matches_server())
394    return  HostX.visual->bits_per_rgb;
395  else
396    return HostX.server_depth; 	/* XXX correct ? */
397}
398
399void
400hostx_get_visual_masks (CARD32 *rmsk,
401			CARD32 *gmsk,
402			CARD32 *bmsk)
403{
404  if (host_depth_matches_server())
405    {
406      *rmsk = HostX.visual->red_mask;
407      *gmsk = HostX.visual->green_mask;
408      *bmsk = HostX.visual->blue_mask;
409    }
410  else if (HostX.server_depth == 16)
411    {
412      /* Assume 16bpp 565 */
413      *rmsk = 0xf800;
414      *gmsk = 0x07e0;
415      *bmsk = 0x001f;
416    }
417  else
418    {
419      *rmsk = 0x0;
420      *gmsk = 0x0;
421      *bmsk = 0x0;
422    }
423}
424
425void
426hostx_set_cmap_entry(unsigned char idx,
427		     unsigned char r,
428		     unsigned char g,
429		     unsigned char b)
430{
431  /* XXX Will likely break for 8 on 16, not sure if this is correct */
432  HostX.cmap[idx] = (r << 16) | (g << 8) | (b);
433}
434
435/**
436 * hostx_screen_init creates the XImage that will contain the front buffer of
437 * the ephyr screen, and possibly offscreen memory.
438 *
439 * @param width width of the screen
440 * @param height height of the screen
441 * @param buffer_height  height of the rectangle to be allocated.
442 *
443 * hostx_screen_init() creates an XImage, using MIT-SHM if it's available.
444 * buffer_height can be used to create a larger offscreen buffer, which is used
445 * by fakexa for storing offscreen pixmap data.
446 */
447void*
448hostx_screen_init (int width, int height, int buffer_height)
449{
450  int         bitmap_pad;
451  Bool        shm_success = False;
452  XSizeHints *size_hints;
453
454  EPHYR_DBG("mark");
455
456  if (HostX.ximg != NULL)
457    {
458      /* Free up the image data if previously used
459       * i.ie called by server reset
460       */
461
462      if (HostX.have_shm)
463	{
464	  XShmDetach(HostX.dpy, &HostX.shminfo);
465	  XDestroyImage (HostX.ximg);
466	  shmdt(HostX.shminfo.shmaddr);
467	  shmctl(HostX.shminfo.shmid, IPC_RMID, 0);
468	}
469      else
470	{
471	  if (HostX.ximg->data)
472	    {
473	      free(HostX.ximg->data);
474	      HostX.ximg->data = NULL;
475	    }
476
477	  XDestroyImage(HostX.ximg);
478	}
479    }
480
481  if (HostX.have_shm)
482    {
483      HostX.ximg = XShmCreateImage(HostX.dpy, HostX.visual, HostX.depth,
484				   ZPixmap, NULL, &HostX.shminfo,
485				   width, buffer_height );
486
487      HostX.shminfo.shmid = shmget(IPC_PRIVATE,
488				   HostX.ximg->bytes_per_line * buffer_height,
489				   IPC_CREAT|0777);
490      HostX.shminfo.shmaddr = HostX.ximg->data = shmat(HostX.shminfo.shmid,
491						       0, 0);
492
493      if (HostX.ximg->data == (char *)-1)
494	{
495	  EPHYR_DBG("Can't attach SHM Segment, falling back to plain XImages");
496	  HostX.have_shm = False;
497	  XDestroyImage(HostX.ximg);
498	  shmctl(HostX.shminfo.shmid, IPC_RMID, 0);
499	}
500      else
501	{
502	  EPHYR_DBG("SHM segment attached");
503	  HostX.shminfo.readOnly = False;
504	  XShmAttach(HostX.dpy, &HostX.shminfo);
505	  shm_success = True;
506	}
507    }
508
509  if (!shm_success)
510    {
511      bitmap_pad = ( HostX.depth > 16 )? 32 : (( HostX.depth > 8 )? 16 : 8 );
512
513      HostX.ximg = XCreateImage( HostX.dpy,
514				 HostX.visual,
515				 HostX.depth,
516				 ZPixmap, 0, 0,
517				 width,
518				 buffer_height,
519				 bitmap_pad,
520				 0);
521
522      HostX.ximg->data = malloc( HostX.ximg->bytes_per_line * buffer_height );
523    }
524
525
526  XResizeWindow(HostX.dpy, HostX.win, width, height);
527
528  /* Ask the WM to keep our size static */
529  size_hints = XAllocSizeHints();
530  size_hints->max_width = size_hints->min_width = width;
531  size_hints->max_height = size_hints->min_height = height;
532  size_hints->flags = PMinSize|PMaxSize;
533  XSetWMNormalHints(HostX.dpy, HostX.win, size_hints);
534  XFree(size_hints);
535
536  XMapWindow(HostX.dpy, HostX.win);
537
538  XSync(HostX.dpy, False);
539
540  HostX.win_width  = width;
541  HostX.win_height = height;
542
543  if (host_depth_matches_server())
544    {
545      EPHYR_DBG("Host matches server");
546      return HostX.ximg->data;
547    }
548  else
549    {
550      EPHYR_DBG("server bpp %i", HostX.server_depth>>3);
551      HostX.fb_data = malloc(width*buffer_height*(HostX.server_depth>>3));
552      return HostX.fb_data;
553    }
554}
555
556void
557hostx_paint_rect(int sx,    int sy,
558		 int dx,    int dy,
559		 int width, int height)
560{
561  /*
562   *  Copy the image data updated by the shadow layer
563   *  on to the window
564   */
565
566  if (HostXWantDamageDebug)
567    {
568      hostx_paint_debug_rect(dx, dy, width, height);
569    }
570
571  /*
572   * If the depth of the ephyr server is less than that of the host,
573   * the kdrive fb does not point to the ximage data but to a buffer
574   * ( fb_data ), we shift the various bits from this onto the XImage
575   * so they match the host.
576   *
577   * Note, This code is pretty new ( and simple ) so may break on
578   *       endian issues, 32 bpp host etc.
579   *       Not sure if 8bpp case is right either.
580   *       ... and it will be slower than the matching depth case.
581   */
582
583  if (!host_depth_matches_server())
584    {
585      int            x,y,idx, bytes_per_pixel = (HostX.server_depth>>3);
586      unsigned char  r,g,b;
587      unsigned long  host_pixel;
588
589      for (y=sy; y<sy+height; y++)
590	for (x=sx; x<sx+width; x++)
591	  {
592	    idx = (HostX.win_width*y*bytes_per_pixel)+(x*bytes_per_pixel);
593
594	    switch (HostX.server_depth)
595	      {
596	      case 16:
597		{
598		  unsigned short pixel = *(unsigned short*)(HostX.fb_data+idx);
599
600		  r = ((pixel & 0xf800) >> 8);
601		  g = ((pixel & 0x07e0) >> 3);
602		  b = ((pixel & 0x001f) << 3);
603
604		  host_pixel = (r << 16) | (g << 8) | (b);
605
606		  XPutPixel(HostX.ximg, x, y, host_pixel);
607		  break;
608		}
609	      case 8:
610		{
611		  unsigned char pixel = *(unsigned char*)(HostX.fb_data+idx);
612		  XPutPixel(HostX.ximg, x, y, HostX.cmap[pixel]);
613		  break;
614		}
615	      default:
616		break;
617	      }
618	  }
619    }
620
621  if (HostX.have_shm)
622    {
623      XShmPutImage(HostX.dpy, HostX.win, HostX.gc, HostX.ximg,
624		       sx, sy, dx, dy, width, height, False);
625    }
626  else
627    {
628      XPutImage(HostX.dpy, HostX.win, HostX.gc, HostX.ximg,
629		sx, sy, dx, dy, width, height);
630    }
631
632  XSync(HostX.dpy, False);
633}
634
635void
636hostx_paint_debug_rect(int x,     int y,
637		       int width, int height)
638{
639  struct timespec tspec;
640
641  tspec.tv_sec  = HostX.damage_debug_msec / (1000000);
642  tspec.tv_nsec = (HostX.damage_debug_msec % 1000000) * 1000;
643
644  EPHYR_DBG("msec: %li tv_sec %li, tv_msec %li",
645	    HostX.damage_debug_msec, tspec.tv_sec, tspec.tv_nsec);
646
647  /* fprintf(stderr, "Xephyr updating: %i+%i %ix%i\n", x, y, width, height); */
648
649  XFillRectangle(HostX.dpy, HostX.win, HostX.gc, x, y, width,height);
650  XSync(HostX.dpy, False);
651
652  /* nanosleep seems to work better than usleep for me... */
653  nanosleep(&tspec, NULL);
654}
655
656void
657hostx_load_keymap(void)
658{
659  XID             *keymap;
660  int              host_width, min_keycode, max_keycode, width;
661  int              i,j;
662
663  XDisplayKeycodes(HostX.dpy, &min_keycode, &max_keycode);
664
665  EPHYR_DBG("min: %d, max: %d", min_keycode, max_keycode);
666
667  keymap = XGetKeyboardMapping(HostX.dpy,
668			       min_keycode,
669			       max_keycode - min_keycode + 1,
670			       &host_width);
671
672  /* Try and copy the hosts keymap into our keymap to avoid loads
673   * of messing around.
674   *
675   * kdrive cannot can have more than 4 keysyms per keycode
676   * so we only copy at most the first 4 ( xorg has 6 per keycode, XVNC 2 )
677   */
678  width = (host_width > 4) ? 4 : host_width;
679
680  ephyrKeySyms.map = (CARD32 *)calloc(sizeof(CARD32),
681                                      (max_keycode - min_keycode + 1) *
682                                      width);
683  if (!ephyrKeySyms.map)
684        return;
685
686  for (i=0; i<(max_keycode - min_keycode+1); i++)
687    for (j=0; j<width; j++)
688      ephyrKeySyms.map[(i*width)+j] = (CARD32) keymap[(i*host_width) + j];
689
690  EPHYR_DBG("keymap width, host:%d kdrive:%d", host_width, width);
691
692  ephyrKeySyms.minKeyCode  = min_keycode;
693  ephyrKeySyms.maxKeyCode  = max_keycode;
694  ephyrKeySyms.mapWidth    = width;
695
696  XFree(keymap);
697}
698
699int
700hostx_get_event(EphyrHostXEvent *ev)
701{
702  XEvent      xev;
703  static Bool grabbed;
704
705  if (XPending(HostX.dpy))
706    {
707      XNextEvent(HostX.dpy, &xev);
708
709      switch (xev.type)
710	{
711	case Expose:
712	  /* Not so great event compression, but works ok */
713	  while (XCheckTypedWindowEvent(HostX.dpy, xev.xexpose.window,
714					Expose, &xev));
715	  hostx_paint_rect(0, 0, 0, 0, HostX.win_width, HostX.win_height);
716	  return 0;
717
718	case MotionNotify:
719	  ev->type = EPHYR_EV_MOUSE_MOTION;
720	  ev->data.mouse_motion.x = xev.xmotion.x;
721	  ev->data.mouse_motion.y = xev.xmotion.y;
722	  return 1;
723
724	case ButtonPress:
725	  ev->type = EPHYR_EV_MOUSE_PRESS;
726	  ev->key_state = xev.xkey.state;
727	  /*
728	   * This is a bit hacky. will break for button 5 ( defined as 0x10 )
729           * Check KD_BUTTON defines in kdrive.h
730	   */
731	  ev->data.mouse_down.button_num = 1<<(xev.xbutton.button-1);
732	  return 1;
733
734	case ButtonRelease:
735	  ev->type = EPHYR_EV_MOUSE_RELEASE;
736	  ev->key_state = xev.xkey.state;
737	  ev->data.mouse_up.button_num = 1<<(xev.xbutton.button-1);
738	  return 1;
739
740	case KeyPress:
741	  {
742	    ev->type = EPHYR_EV_KEY_PRESS;
743	    ev->key_state = xev.xkey.state;
744	    ev->data.key_down.scancode = xev.xkey.keycode;
745	    return 1;
746	  }
747	case KeyRelease:
748
749	  if ((XKeycodeToKeysym(HostX.dpy,xev.xkey.keycode,0) == XK_Shift_L
750	       || XKeycodeToKeysym(HostX.dpy,xev.xkey.keycode,0) == XK_Shift_R)
751	      && (xev.xkey.state & ControlMask))
752	    {
753	      if (grabbed)
754		{
755		  XUngrabKeyboard (HostX.dpy, CurrentTime);
756		  XUngrabPointer (HostX.dpy, CurrentTime);
757		  grabbed = False;
758		  hostx_set_win_title("( ctrl+shift grabs mouse and keyboard )");
759		}
760	      else
761		{
762		  /* Attempt grab */
763		  if (XGrabKeyboard (HostX.dpy, HostX.win, True,
764				     GrabModeAsync,
765				     GrabModeAsync,
766				     CurrentTime) == 0)
767		    {
768		      if (XGrabPointer (HostX.dpy, HostX.win, True,
769					NoEventMask,
770					GrabModeAsync,
771					GrabModeAsync,
772					HostX.win, None, CurrentTime) == 0)
773			{
774			  grabbed = True;
775			  hostx_set_win_title("( ctrl+shift releases mouse and keyboard )");
776			}
777		      else 	/* Failed pointer grabm  ungrab keyboard */
778			XUngrabKeyboard (HostX.dpy, CurrentTime);
779		    }
780		}
781	    }
782
783	  /* Still send the release event even if above has happened
784           * server will get confused with just an up event.
785           * Maybe it would be better to just block shift+ctrls getting to
786           * kdrive all togeather.
787 	   */
788	  ev->type = EPHYR_EV_KEY_RELEASE;
789	  ev->key_state = xev.xkey.state;
790	  ev->data.key_up.scancode = xev.xkey.keycode;
791	  return 1;
792
793	default:
794	  break;
795
796	}
797    }
798  return 0;
799}
800
801