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