2 * Xephyr - A kdrive X server thats runs in a host X window.
3 * Authored by Matthew Allum <mallum@o-hand.com>
5 * Copyright © 2004 Nokia
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.
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.
31 #include <string.h> /* for memset */
39 #include <X11/Xutil.h>
40 #include <X11/Xatom.h>
41 #include <X11/keysym.h>
42 #include <X11/extensions/XShm.h>
45 * All xlib calls go here, which gets built as its own .a .
46 * Mixing kdrive and xlib headers causes all sorts of types
52 char *server_dpy_name
;
57 Window win_pre_existing
; /* Set via -parent option like xnest */
62 int win_width
, win_height
;
67 long damage_debug_msec
;
69 unsigned char *fb_data
; /* only used when host bpp != server bpp */
70 unsigned long cmap
[256];
72 XShmSegmentInfo shminfo
;
75 /* memset ( missing> ) instead of below */
76 static EphyrHostXVars HostX
= { "?", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
78 static int HostXWantDamageDebug
= 0;
80 extern EphyrKeySyms ephyrKeySyms
;
82 extern int monitorResolution
;
85 hostx_set_fullscreen_hint(void);
89 static int trapped_error_code
= 0;
90 static int (*old_error_handler
) (Display
*d
, XErrorEvent
*e
);
92 #define host_depth_matches_server() (HostX.depth == HostX.server_depth)
96 error_handler(Display
*display
,
99 trapped_error_code
= error
->error_code
;
104 hostx_errors_trap(void)
106 trapped_error_code
= 0;
107 old_error_handler
= XSetErrorHandler(error_handler
);
111 hostx_errors_untrap(void)
113 XSetErrorHandler(old_error_handler
);
114 return trapped_error_code
;
118 hostx_want_screen_size(int *width
, int *height
)
120 if (HostX
.win_pre_existing
!= None
121 || HostX
.use_fullscreen
== True
)
123 *width
= HostX
.win_width
;
124 *height
= HostX
.win_height
;
132 hostx_set_display_name(char *name
)
134 HostX
.server_dpy_name
= strdup(name
);
138 hostx_set_win_title(char *extra_text
)
142 snprintf(buf
, 256, "Xephyr on %s %s",
143 HostX
.server_dpy_name
,
144 (extra_text
!= NULL
) ? extra_text
: "");
146 XStoreName(HostX
.dpy
, HostX
.win
, buf
);
150 hostx_want_host_cursor(void)
152 return HostX
.use_host_cursor
;
156 hostx_use_host_cursor(void)
158 HostX
.use_host_cursor
= True
;
162 hostx_want_preexisting_window(void)
164 if (HostX
.win_pre_existing
)
171 hostx_use_fullscreen(void)
173 HostX
.use_fullscreen
= True
;
177 hostx_want_fullscreen(void)
179 return HostX
.use_fullscreen
;
183 hostx_set_fullscreen_hint(void)
185 Atom atom_WINDOW_STATE
, atom_WINDOW_STATE_FULLSCREEN
;
188 = XInternAtom(HostX
.dpy
, "_NET_WM_STATE", False
);
189 atom_WINDOW_STATE_FULLSCREEN
190 = XInternAtom(HostX
.dpy
, "_NET_WM_STATE_FULLSCREEN",False
);
192 XChangeProperty(HostX
.dpy
, HostX
.win
,
193 atom_WINDOW_STATE
, XA_ATOM
, 32,
195 (unsigned char *)&atom_WINDOW_STATE_FULLSCREEN
, 1);
199 hostx_use_preexisting_window(unsigned long win_id
)
201 HostX
.win_pre_existing
= win_id
;
205 hostx_toggle_damage_debug(void)
207 HostXWantDamageDebug
^= 1;
211 hostx_handle_signal(int signum
)
213 hostx_toggle_damage_debug();
214 EPHYR_DBG("Signal caught. Damage Debug:%i\n", HostXWantDamageDebug
);
220 XSetWindowAttributes attr
;
235 if ((HostX
.dpy
= XOpenDisplay(getenv("DISPLAY"))) == NULL
)
237 fprintf(stderr
, "\nXephyr cannot open host display. Is DISPLAY set?\n");
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
);
247 HostX
.server_depth
= HostX
.depth
;
249 if (HostX
.win_pre_existing
!= None
)
252 XWindowAttributes prewin_attr
;
254 /* Get screen size from existing window */
258 result
= XGetWindowAttributes(HostX
.dpy
,
259 HostX
.win_pre_existing
,
263 if (hostx_errors_untrap() || !result
)
265 fprintf(stderr
, "\nXephyr -parent window' does not exist!\n");
269 HostX
.win_width
= prewin_attr
.width
;
270 HostX
.win_height
= prewin_attr
.height
;
272 HostX
.win
= XCreateWindow(HostX
.dpy
,
273 HostX
.win_pre_existing
,
274 0,0,HostX
.win_width
,HostX
.win_height
,
284 HostX
.win
= XCreateWindow(HostX
.dpy
,
286 0,0,100,100, /* will resize */
294 hostx_set_win_title("( ctrl+shift grabs mouse and keyboard )");
296 if (HostX
.use_fullscreen
)
298 HostX
.win_width
= DisplayWidth(HostX
.dpy
, HostX
.screen
);
299 HostX
.win_height
= DisplayHeight(HostX
.dpy
, HostX
.screen
);
301 hostx_set_fullscreen_hint();
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
);
310 if (!hostx_want_host_cursor())
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
,
318 XDefineCursor (HostX
.dpy
, HostX
.win
, empty_cursor
);
319 XFreePixmap (HostX
.dpy
, cursor_pxm
);
324 /* Try to get share memory ximages for a little bit more speed */
326 if (!XShmQueryExtension(HostX
.dpy
) || getenv("XEPHYR_NO_SHM"))
328 fprintf(stderr
, "\nXephyr unable to use SHM XImages\n");
329 HostX
.have_shm
= False
;
333 /* Really really check we have shm - better way ?*/
334 XShmSegmentInfo shminfo
;
336 HostX
.have_shm
= True
;
338 shminfo
.shmid
=shmget(IPC_PRIVATE
, 1, IPC_CREAT
|0777);
339 shminfo
.shmaddr
=shmat(shminfo
.shmid
,0,0);
340 shminfo
.readOnly
=True
;
344 XShmAttach(HostX
.dpy
, &shminfo
);
345 XSync(HostX
.dpy
, False
);
347 if (hostx_errors_untrap())
349 fprintf(stderr
, "\nXephyr unable to use SHM XImages\n");
350 HostX
.have_shm
= False
;
353 shmdt(shminfo
.shmaddr
);
354 shmctl(shminfo
.shmid
, IPC_RMID
, 0);
359 /* Setup the pause time between paints when debugging updates */
361 HostX
.damage_debug_msec
= 20000; /* 1/50 th of a second */
363 if (getenv("XEPHYR_PAUSE"))
365 HostX
.damage_debug_msec
= strtol(getenv("XEPHYR_PAUSE"), NULL
, 0);
366 EPHYR_DBG("pause is %li\n", HostX
.damage_debug_msec
);
373 hostx_get_depth (void)
379 hostx_get_server_depth (void)
381 return HostX
.server_depth
;
385 hostx_set_server_depth(int depth
)
387 HostX
.server_depth
= depth
;
393 if (host_depth_matches_server())
394 return HostX
.visual
->bits_per_rgb
;
396 return HostX
.server_depth
; /* XXX correct ? */
400 hostx_get_visual_masks (CARD32
*rmsk
,
404 if (host_depth_matches_server())
406 *rmsk
= HostX
.visual
->red_mask
;
407 *gmsk
= HostX
.visual
->green_mask
;
408 *bmsk
= HostX
.visual
->blue_mask
;
410 else if (HostX
.server_depth
== 16)
412 /* Assume 16bpp 565 */
426 hostx_set_cmap_entry(unsigned char idx
,
431 /* XXX Will likely break for 8 on 16, not sure if this is correct */
432 HostX
.cmap
[idx
] = (r
<< 16) | (g
<< 8) | (b
);
436 * hostx_screen_init creates the XImage that will contain the front buffer of
437 * the ephyr screen, and possibly offscreen memory.
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.
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.
448 hostx_screen_init (int width
, int height
, int buffer_height
)
451 Bool shm_success
= False
;
452 XSizeHints
*size_hints
;
456 if (HostX
.ximg
!= NULL
)
458 /* Free up the image data if previously used
459 * i.ie called by server reset
464 XShmDetach(HostX
.dpy
, &HostX
.shminfo
);
465 XDestroyImage (HostX
.ximg
);
466 shmdt(HostX
.shminfo
.shmaddr
);
467 shmctl(HostX
.shminfo
.shmid
, IPC_RMID
, 0);
471 if (HostX
.ximg
->data
)
473 free(HostX
.ximg
->data
);
474 HostX
.ximg
->data
= NULL
;
477 XDestroyImage(HostX
.ximg
);
483 HostX
.ximg
= XShmCreateImage(HostX
.dpy
, HostX
.visual
, HostX
.depth
,
484 ZPixmap
, NULL
, &HostX
.shminfo
,
485 width
, buffer_height
);
487 HostX
.shminfo
.shmid
= shmget(IPC_PRIVATE
,
488 HostX
.ximg
->bytes_per_line
* buffer_height
,
490 HostX
.shminfo
.shmaddr
= HostX
.ximg
->data
= shmat(HostX
.shminfo
.shmid
,
493 if (HostX
.ximg
->data
== (char *)-1)
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);
502 EPHYR_DBG("SHM segment attached");
503 HostX
.shminfo
.readOnly
= False
;
504 XShmAttach(HostX
.dpy
, &HostX
.shminfo
);
511 bitmap_pad
= ( HostX
.depth
> 16 )? 32 : (( HostX
.depth
> 8 )? 16 : 8 );
513 HostX
.ximg
= XCreateImage( HostX
.dpy
,
522 HostX
.ximg
->data
= malloc( HostX
.ximg
->bytes_per_line
* buffer_height
);
526 XResizeWindow(HostX
.dpy
, HostX
.win
, width
, height
);
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
);
536 XMapWindow(HostX
.dpy
, HostX
.win
);
538 XSync(HostX
.dpy
, False
);
540 HostX
.win_width
= width
;
541 HostX
.win_height
= height
;
543 if (host_depth_matches_server())
545 EPHYR_DBG("Host matches server");
546 return HostX
.ximg
->data
;
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
;
557 hostx_paint_rect(int sx
, int sy
,
559 int width
, int height
)
562 * Copy the image data updated by the shadow layer
566 if (HostXWantDamageDebug
)
568 hostx_paint_debug_rect(dx
, dy
, width
, height
);
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.
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.
583 if (!host_depth_matches_server())
585 int x
,y
,idx
, bytes_per_pixel
= (HostX
.server_depth
>>3);
587 unsigned long host_pixel
;
589 for (y
=sy
; y
<sy
+height
; y
++)
590 for (x
=sx
; x
<sx
+width
; x
++)
592 idx
= (HostX
.win_width
*y
*bytes_per_pixel
)+(x
*bytes_per_pixel
);
594 switch (HostX
.server_depth
)
598 unsigned short pixel
= *(unsigned short*)(HostX
.fb_data
+idx
);
600 r
= ((pixel
& 0xf800) >> 8);
601 g
= ((pixel
& 0x07e0) >> 3);
602 b
= ((pixel
& 0x001f) << 3);
604 host_pixel
= (r
<< 16) | (g
<< 8) | (b
);
606 XPutPixel(HostX
.ximg
, x
, y
, host_pixel
);
611 unsigned char pixel
= *(unsigned char*)(HostX
.fb_data
+idx
);
612 XPutPixel(HostX
.ximg
, x
, y
, HostX
.cmap
[pixel
]);
623 XShmPutImage(HostX
.dpy
, HostX
.win
, HostX
.gc
, HostX
.ximg
,
624 sx
, sy
, dx
, dy
, width
, height
, False
);
628 XPutImage(HostX
.dpy
, HostX
.win
, HostX
.gc
, HostX
.ximg
,
629 sx
, sy
, dx
, dy
, width
, height
);
632 XSync(HostX
.dpy
, False
);
636 hostx_paint_debug_rect(int x
, int y
,
637 int width
, int height
)
639 struct timespec tspec
;
641 tspec
.tv_sec
= HostX
.damage_debug_msec
/ (1000000);
642 tspec
.tv_nsec
= (HostX
.damage_debug_msec
% 1000000) * 1000;
644 EPHYR_DBG("msec: %li tv_sec %li, tv_msec %li",
645 HostX
.damage_debug_msec
, tspec
.tv_sec
, tspec
.tv_nsec
);
647 /* fprintf(stderr, "Xephyr updating: %i+%i %ix%i\n", x, y, width, height); */
649 XFillRectangle(HostX
.dpy
, HostX
.win
, HostX
.gc
, x
, y
, width
,height
);
650 XSync(HostX
.dpy
, False
);
652 /* nanosleep seems to work better than usleep for me... */
653 nanosleep(&tspec
, NULL
);
657 hostx_load_keymap(void)
660 int host_width
, min_keycode
, max_keycode
, width
;
663 XDisplayKeycodes(HostX
.dpy
, &min_keycode
, &max_keycode
);
665 EPHYR_DBG("min: %d, max: %d", min_keycode
, max_keycode
);
667 keymap
= XGetKeyboardMapping(HostX
.dpy
,
669 max_keycode
- min_keycode
+ 1,
672 /* Try and copy the hosts keymap into our keymap to avoid loads
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 )
678 width
= (host_width
> 4) ? 4 : host_width
;
680 ephyrKeySyms
.map
= (CARD32
*)calloc(sizeof(CARD32
),
681 (max_keycode
- min_keycode
+ 1) *
683 if (!ephyrKeySyms
.map
)
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
];
690 EPHYR_DBG("keymap width, host:%d kdrive:%d", host_width
, width
);
692 ephyrKeySyms
.minKeyCode
= min_keycode
;
693 ephyrKeySyms
.maxKeyCode
= max_keycode
;
694 ephyrKeySyms
.mapWidth
= width
;
700 hostx_get_event(EphyrHostXEvent
*ev
)
705 if (XPending(HostX
.dpy
))
707 XNextEvent(HostX
.dpy
, &xev
);
712 /* Not so great event compression, but works ok */
713 while (XCheckTypedWindowEvent(HostX
.dpy
, xev
.xexpose
.window
,
715 hostx_paint_rect(0, 0, 0, 0, HostX
.win_width
, HostX
.win_height
);
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
;
725 ev
->type
= EPHYR_EV_MOUSE_PRESS
;
726 ev
->key_state
= xev
.xkey
.state
;
728 * This is a bit hacky. will break for button 5 ( defined as 0x10 )
729 * Check KD_BUTTON defines in kdrive.h
731 ev
->data
.mouse_down
.button_num
= 1<<(xev
.xbutton
.button
-1);
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);
742 ev
->type
= EPHYR_EV_KEY_PRESS
;
743 ev
->key_state
= xev
.xkey
.state
;
744 ev
->data
.key_down
.scancode
= xev
.xkey
.keycode
;
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
))
755 XUngrabKeyboard (HostX
.dpy
, CurrentTime
);
756 XUngrabPointer (HostX
.dpy
, CurrentTime
);
758 hostx_set_win_title("( ctrl+shift grabs mouse and keyboard )");
763 if (XGrabKeyboard (HostX
.dpy
, HostX
.win
, True
,
768 if (XGrabPointer (HostX
.dpy
, HostX
.win
, True
,
772 HostX
.win
, None
, CurrentTime
) == 0)
775 hostx_set_win_title("( ctrl+shift releases mouse and keyboard )");
777 else /* Failed pointer grabm ungrab keyboard */
778 XUngrabKeyboard (HostX
.dpy
, CurrentTime
);
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.
788 ev
->type
= EPHYR_EV_KEY_RELEASE
;
789 ev
->key_state
= xev
.xkey
.state
;
790 ev
->data
.key_up
.scancode
= xev
.xkey
.keycode
;