From b0daa444cc2fdda15b8b82497d17f80e7d75f8ad Mon Sep 17 00:00:00 2001 From: runge Date: Tue, 3 May 2005 02:05:51 +0000 Subject: [PATCH] x11vnc: -scrollcopyrect/RECORD, etc. configure.ac: customizations for x11vnc pkg --- ChangeLog | 6 + configure.ac | 41 + x11vnc/ChangeLog | 11 + x11vnc/README | 258 +- x11vnc/tkx11vnc | 7 + x11vnc/tkx11vnc.h | 7 + x11vnc/x11vnc.1 | 160 +- x11vnc/x11vnc.c | 24467 ++++++++++++++++++++++++-------------------- 8 files changed, 13902 insertions(+), 11055 deletions(-) diff --git a/ChangeLog b/ChangeLog index fed5a68..90c0cf8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2005-05-02 Karl Runge + * configure.ac: fatal error for x11vnc package if no X present + * configure.ac: give warnings and info about missing libjpeg/libz + * x11vnc: X RECORD heuristics to detect scrolls: -scrollcopyrect, + build customizations, bandwidth/latency estimates. + 2005-04-27 Johannes E. Schindelin * clear requested region (long standing TODO, pointed out by Karl) diff --git a/configure.ac b/configure.ac index cd31883..9f2299d 100644 --- a/configure.ac +++ b/configure.ac @@ -52,6 +52,7 @@ AH_TEMPLATE(HAVE_LIBXRANDR, [XRANDR extension build environment present]) AH_TEMPLATE(HAVE_LIBXFIXES, [XFIXES extension build environment present]) AH_TEMPLATE(HAVE_LIBXDAMAGE, [XDAMAGE extension build environment present]) AH_TEMPLATE(HAVE_LIBXTRAP, [DEC-XTRAP extension build environment present]) +AH_TEMPLATE(HAVE_RECORD, [RECORD extension build environment present]) if test "$X_CFLAGS" != "-DX_DISPLAY_MISSING"; then AC_CHECK_LIB(X11, XGetImage, HAVE_X="true", HAVE_X="false", @@ -74,6 +75,11 @@ if test "$X_CFLAGS" != "-DX_DISPLAY_MISSING"; then [AC_DEFINE(HAVE_XTEST)], , $X_LIBS $X_PRELIBS -lX11 $X_EXTRA_LIBS) + AC_CHECK_LIB(Xtst, XRecordEnableContextAsync, + X_PRELIBS="$X_PRELIBS -lXtst" + [AC_DEFINE(HAVE_RECORD)], , + $X_LIBS $X_PRELIBS -lX11 $X_EXTRA_LIBS) + # we use XTRAP on X11R5, or user can set X11VNC_USE_XTRAP if test ! -z "$X11VNC_USE_XTRAP" -o -z "$HAVE_XTESTGRABCONTROL"; then AC_CHECK_LIB(XTrap, XETrapSetGrabServer, @@ -131,6 +137,19 @@ fi AC_SUBST(X_LIBS) AM_CONDITIONAL(HAVE_X, test $HAVE_X != "false") +if test "$PACKAGE_NAME" = "x11vnc" -a $HAVE_X = "false"; then + AC_MSG_ERROR([ +========================================================================== +a working X window system build environment is required to build x11vnc. +Make sure any required X development packages are installed. If they are +installed in non-standard locations, one can use the --x-includes=DIR +and --x-libraries=DIR configure options or set the CPPFLAGS and LDFLAGS +environment variables to indicate where the X window system header files +and libraries may be found. +========================================================================== +]) +fi + # Checks for libraries. AC_ARG_WITH(jpeg, @@ -168,6 +187,17 @@ if test "x$with_jpeg" != "xno"; then LDFLAGS="$saved_LDFLAGS" fi fi + if test "$PACKAGE_NAME" = "x11vnc"; then + if test "x$HAVE_JPEGLIB_H" != "xtrue"; then + AC_MSG_WARN([ +libjpeg compression library was not found: this may lead to reduced performance +especially over slow links. If libjpeg is in a non-standard location +use --with-jpeg=DIR to indicate the header file is in DIR/include/jpeglib.h +and the library in DIR/lib/libjpeg.a. A copy of libjpeg may be obtained +from: ftp://ftp.uu.net/graphics/jpeg/ +]) + fi + fi fi AC_ARG_WITH(libz, @@ -198,6 +228,17 @@ if test "x$with_zlib" != "xno" -a "x$with_libz" != "xno"; then LDFLAGS="$saved_LDFLAGS" fi fi + if test "$PACKAGE_NAME" = "x11vnc"; then + if test "x$HAVE_ZLIB_H" != "xtrue"; then + AC_MSG_WARN([ +libz compression library was not found: this may lead to reduced performance, +especially over slow links. If libz is in a non-standard location +use --with-zlib=DIR to indicate the header file is in DIR/include/zlib.h +and the library in DIR/lib/libz.a. A copy of libz may be obtained +from: http://www.gzip.org/zlib/ +]) + fi + fi fi AC_CHECK_HEADER(pthread.h, HAVE_PTHREAD_H="true") diff --git a/x11vnc/ChangeLog b/x11vnc/ChangeLog index dffbb81..5a28e34 100644 --- a/x11vnc/ChangeLog +++ b/x11vnc/ChangeLog @@ -1,3 +1,14 @@ +2005-05-02 Karl Runge + * initial support for using RECORD to detect some types of window + scrolls. This is "-scrollcopyrect" mode, use -noscrollcopyrect + to disable. Much tuning and painting error repair still required. + * more build time customizations: REMOTE_DEFAULT, REMOTE_CONTROL, + EXTERNAL_COMMANDS, NOREPEAT, WIREFRAME*, SCROLL*, ... + * added bandwidth and latency measurements. + * added XListHosts to -privremote check. + * debug_* remote-control variables. + * removed OLD_TREE stuff. + 2005-04-19 Karl Runge * somewhat safer remote-control defaults, and addnl options for more safe operation: -privremote, -safer, -nocmds, -unsafe diff --git a/x11vnc/README b/x11vnc/README index 74b92ad..13a0977 100644 --- a/x11vnc/README +++ b/x11vnc/README @@ -1,5 +1,5 @@ -x11vnc README file Date: Tue Apr 19 16:26:20 EDT 2005 +x11vnc README file Date: Mon May 2 21:41:13 EDT 2005 The following information is taken from these URLs: @@ -527,7 +527,9 @@ make Here is what is shaping up to be [52]the release notes for 0.7.2.. Note that the [53]X DAMAGE feature will be on by default and so I am - interested if that causes any problems. Thanks! + interested if that causes any problems. I'd also like to have the new + "wireframe" move/resize stuff on by default as well, let me know of + any issues you find. Thanks! _________________________________________________________________ Some Notes: @@ -576,11 +578,10 @@ make note below) Options: x11vnc has (far too) many features that may be activated - via its [57]command line options. Useful options are -nap to use fewer - resources (it sleeps more between polls when activity is low) and - -rfbauth passwd-file to use VNC password protection (the vncpasswd or - storepasswd programs, or the x11vnc [58]-storepasswd option can be - used to create the password file). + via its [57]command line options. Useful options are, e.g., -scale to + do server-side scaling, and -rfbauth passwd-file to use VNC password + protection (the vncpasswd or storepasswd programs, or the x11vnc + [58]-storepasswd option can be used to create the password file). Algorithm: How does x11vnc do it? Rather brute-forcedly: it continuously polls the X11 framebuffer for changes using @@ -588,18 +589,18 @@ make which rectangular regions of the framebuffer have changed, and libvncserver compresses the changes and sends them off to any connected VNC viewers. A number of applications do similar things, - such as x0rfbserver, krfb, x0vncserver. x11vnc uses a 32 x 32 pixel - tile model (the desktop is decomposed into roughly 1000 such tiles), - where changed tiles are found by pseudo-randomly polling 1 pixel tall - horizontal scanlines. This is a surprisingly effective algorithm for - finding changed regions. For keyboard and mouse user input the XTEST - extension is used to pass the input events to the X server. To detect - XBell "beeps" the XKEYBOARD extension is used. If available, the - XFIXES extension is used to retrieve the current mouse cursor shape. - Also, if available the X DAMAGE extension is used to receive hints - from the X server where modified regions on the screen are. This - greatly reduces the system load when not much is changing on the - screen and also improves how quickly the screen is updated. + such as x0rfbserver, krfb, x0vncserver, vino. x11vnc uses a 32 x 32 + pixel tile model (the desktop is decomposed into roughly 1000 such + tiles), where changed tiles are found by pseudo-randomly polling 1 + pixel tall horizontal scanlines. This is a surprisingly effective + algorithm for finding changed regions. For keyboard and mouse user + input the XTEST extension is used to pass the input events to the X + server. To detect XBell "beeps" the XKEYBOARD extension is used. If + available, the XFIXES extension is used to retrieve the current mouse + cursor shape. Also, if available the X DAMAGE extension is used to + receive hints from the X server where modified regions on the screen + are. This greatly reduces the system load when not much is changing on + the screen and also improves how quickly the screen is updated. Barbershop mirrors effect: What if x11vnc is started up, and vncviewer is then started up on the same machine and displayed on the @@ -1375,7 +1376,9 @@ display :0 unintendeds. Perhaps this is of use in remote access for an embedded application, etc... - Let us know if more build-time customizations would be useful. + Let us know if more build-time customizations would be useful. Look + near the top of the source file for any additional customization + macros. If the system does not have the XTEST XTestGrabControl interface (some early X11R5 systems have XTEST but not this interface), then configure @@ -2761,18 +2764,20 @@ ied) polling and updates will be suspended and only an animated "wireframe" (a rectangle outline drawn where the moved/resized window would be) is shown. When the window move/resize stops, it returns to normal - processing: you should just see the window appear in the new position. - This spares you from interacting with a "lurching" window during all - of the intermediate steps. (the lurching is due to [297]slow video - card read rates) - - The mode is currently on be default because most people are inflicted + processing: you should only see the window appear in the new position. + This spares you from interacting with a "lurching" window between all + of the intermediate steps. BTW the lurching is due to [297]slow video + card read rates. A displacement, even a small one, of a large window + requires a non-negligible amount of time, a good fraction of a second, + to read in from the hardware framebuffer. + + The mode is currently on by default because most people are inflicted with the problem. It can be disabled with the [298]-nowireframe option. Why might one want to turn off the wireframing? Since x11vnc is merely guessing when windows are being moved/resized, it may guess poorly for your window-manager or desktop, or even for the way you move the pointer. If your window-manager or desktop already does its - own wireframing then this mode is a waste of time or could do the + own wireframing then this mode is a waste of time and could do the wrong thing occasionally. There may be other reasons the new mode feels unnatural. If you have very expensive video hardware (SGI) or are using an in-RAM video framebuffer (SunRay, ShadowFB, Xvfb), the @@ -2785,16 +2790,16 @@ ied) are not fool proof: x11vnc is sometimes tricked and so you'll occasionally see the lurching opaque move and rarely something even worse. First it assumes that the move/resize will occur with a mouse - button pressed and held down (of course this is only mostly true). - Next it will only consider a window for wireframing if the mouse - pointer is initially "close enough" to the edges of the window frame, - e.g. you have grabbed the title bar or a resizer edge (this + button pressed, held down and dragged (of course this is only mostly + true). Next it will only consider a window for wireframing if the + mouse pointer is initially "close enough" to the edges of the window + frame, e.g. you have grabbed the title bar or a resizer edge (this requirement can be disabled). If these are true, it will wait an amount of time to see if the window starts moving or resizing. If it - does, it starts drawing the wireframe "animation" of where the window - would be. If the mouse button is released, or a timeout occurs, it - goes back to the standard mode to allow the framebuffer changes to - propagate to the viewers. + does, it starts drawing the wireframe "outline" of where the window + would be. When the mouse button is released, or a timeout occurs, it + goes back to the standard mode to allow the actual framebuffer changes + to propagate to the viewers. These parameters can be tweaked: * Color/Shade of the wireframe. @@ -2836,6 +2841,12 @@ ied) data is translated! Use -nowirecopyrect if this yields undesirable effects for your desktop. + Also, the CopyRect encoding may give incorrect results under -scale + (depending on the scale factor the CopyRect operation is often only + approximate: the correctly scaled framebuffer will be slightly + different from the translated one). Use -nowirecopyrect if this or + other painting errors are unacceptable. + Q-51: Does x11vnc support the X DAMAGE Xserver extension to find modified regions of the screen quickly and efficiently? @@ -4222,7 +4233,7 @@ x11vnc: a VNC server for real X displays Here are all of x11vnc command line options: % x11vnc -opts (see below for -help long descriptions) -x11vnc: allow VNC connections to real X11 displays. 0.7.2 lastmod: 2005-04-19 +x11vnc: allow VNC connections to real X11 displays. 0.7.2 lastmod: 2005-05-02 x11vnc options: -display disp -auth file @@ -4266,21 +4277,23 @@ x11vnc options: -xwarppointer -buttonmap string -nodragging -wireframe [str] -nowireframe -wirecopyrect mode - -nowirecopyrect -pointer_mode n - -input_skip n -speeds rd,bw,lat - -debug_pointer -debug_keyboard - -defer time -wait time - -nap -nonap - -sb time -noxdamage - -xd_area A -xd_mem f - -sigpipe string -threads - -nothreads -fs f - -gaps n -grow n - -fuzz n -snapfb - -rawfb string -pipeinput cmd - -gui [gui-opts] -remote command - -query variable -sync - -noremote -unsafe + -nowirecopyrect -scrollcopyrect mode + -noscrollcopyrect -scr_area n + -pointer_mode n -input_skip n + -speeds rd,bw,lat -debug_pointer + -debug_keyboard -defer time + -wait time -nap + -nonap -sb time + -noxdamage -xd_area A + -xd_mem f -sigpipe string + -threads -nothreads + -fs f -gaps n + -grow n -fuzz n + -snapfb -rawfb string + -pipeinput cmd -gui [gui-opts] + -remote command -query variable + -sync -noremote + -yesremote -unsafe -safer -privremote -nocmds -deny_all @@ -4309,7 +4322,7 @@ libvncserver options: % x11vnc -help -x11vnc: allow VNC connections to real X11 displays. 0.7.2 lastmod: 2005-04-19 +x11vnc: allow VNC connections to real X11 displays. 0.7.2 lastmod: 2005-05-02 Typical usage is: @@ -5009,13 +5022,16 @@ Options: heuristics and may not always work: it depends on your window manager and even how you move things around. See -pointer_mode below for discussion of the "bogging - down" problem this tries to avoid. Default: -wireframe + down" problem this tries to avoid. + Default: -wireframe + + Shorter aliases: -wf [str] and -nowf The value "str" is optional and, of course, is packed with many tunable parameters for this scheme: Format: shade,linewidth,percent,T+B+L+R,t1+t2+t3+t4 - Default: 0xff,3,0,32+8+8+8,0.15+0.35+4.0+0.1 + Default: 0xff,3,0,32+8+8+8,0.15+0.30+5.0+0.125 If you leave nothing between commas: ",," the default value is used. If you don't specify enough commas, @@ -5047,16 +5063,67 @@ Options: link this might be a better choice: 0.25+0.6+6.0+0.15 -wirecopyrect mode Since the -wireframe mechanism evidently tracks moving --nowirecopyrect windows, a speedup can be obtained by telling the VNC - viewers to locally copy the translated window region. - This is the VNC CopyRect encoding: the framebuffer - update doesn't need to send the actual new image data. +-nowirecopyrect windows accurately, a speedup can be obtained by + telling the VNC viewers to locally copy the translated + window region. This is the VNC CopyRect encoding: + the framebuffer update doesn't need to send the actual + new image data. + + Shorter aliases: -wcr [mode] and -nowcr + "mode" can be "never" (same as -nowirecopyrect) to never try the copyrect, "top" means only do it if the window was not covered by any other windows, and "always" means to translate the orginally unobscured region (this may look odd as the remaining pieces come - in, but helps on a slow link) Default: always + in, but helps on a slow link). Default: "always" + + Note: there can be painting errors when using -scale + so CopyRect is skipped when scaling unless you specify + -wirecopyrect on the command line or by remote-control. + +-scrollcopyrect mode Like -wirecopyrect, but use heuristics to try to guess +-noscrollcopyrect if a window has scrolled its contents (either vertically + or horizontally). This requires the RECORD X extension + to "snoop" on X applications (currently for certain + XCopyArea and XConfigureWindow X protocol requests). + Examples: Hitting in a terminal window when the + cursor was at the bottom, the text scrolls up one line. + Hitting arrow in a web browser window, the web + page scrolls up a small amount. + + Shorter aliases: -scr [mode] and -noscr + + This scheme will not always detect scrolls, but when + it does there is a nice speedup from using the VNC + CopyRect encoding (see -wirecopyrect). The speedup + is both in reduced network traffic and reduced X + framebuffer polling/copying. On the other hand, + it may induce undesired transients (e.g. a terminal + cursor being scrolled up when it should not be) or other + painting errors. These are automatically repaired in a + short period of time. If this is unacceptable disable + the feature with -noscrollcopyrect. + + "mode" can be "never" (same as -noscrollcopyrect) + to never try the copyrect, "keys" means to try it + in response to keystrokes only, "mouse" means to + try it in response to mouse events only, "always" + means to do both. Default: "always" + + Note: there can be painting errors when using + -scale so CopyRect is skipped when scaling unless + you specify -scrollcopyrect on the command line or + by remote-control. + +-scr_area n Set the minimum area in pixels for a rectangle + to be considered for the -scrollcopyrect detection + scheme. This is to avoid wasting the effort on small + rectangles that would be quickly updated the normal way. + E.g. suppose an app updated the position of its skinny + scrollbar first and then shifted the large panel + it controlled. We want to be sure to skip the small + scrollbar and get the large panel. Default: 60000 -pointer_mode n Various pointer motion update schemes. "-pm" is an alias. The problem is pointer motion can cause @@ -5503,11 +5570,16 @@ Options: buttonmap:str set -buttonmap "str", empty to disable dragging disable -nodragging mode. nodragging enable -nodragging mode. - wireframe enable -wireframe mode. - nowireframe disable -wireframe mode. + wireframe enable -wireframe mode. same as "wf" + nowireframe disable -wireframe mode. same as "nowf" wireframe:str enable -wireframe mode string. wireframe_mode:str enable -wireframe mode string. - wirecopyrect:str set -wirecopyrect string. + wirecopyrect:str set -wirecopyrect string. same as "wcr: +" + scrollcopyrect:str set -scrollcopyrect string. same "scr +" + noscrollcopyrect disable -scrollcopyrect mode. "noscr" + scr_area:n set -scr_area to n pointer_mode:n set -pointer_mode to n. same as "pm" input_skip:n set -input_skip to n. speeds:str set -speeds to str. @@ -5550,6 +5622,15 @@ Options: dontdisconnect enable -dontdisconnect mode. nodontdisconnect disable -dontdisconnect mode. (may interfere with other options) + debug_xevents enable debugging X events. + nodebug_xevents disable debugging X events. + debug_xdamage enable debugging X DAMAGE mechanism. + nodebug_xdamage disable debugging X DAMAGE mechanism. + debug_wireframe enable debugging wireframe mechanism. + nodebug_wireframe disable debugging wireframe mechanism. + debug_scroll enable debugging scrollcopy mechanism. + nodebug_scroll disable debugging scrollcopy mechanism. + noremote disable the -remote command processing, it cannot be turned back on. @@ -5606,13 +5687,14 @@ Options: add_keysyms noadd_keysyms clear_mods noclear_mods clear_keys noclear_keys remap repeat norepeat fb nofb bell nobell sel nosel primary noprimary - cursorshape nocursorshape cursorpos nocursorpos - cursor show_cursor noshow_cursor nocursor arrow - xfixes noxfixes xdamage noxdamage xd_area xd_mem - alphacut alphafrac alpharemove noalpharemove alphablend - noalphablend xwarp xwarppointer noxwarp noxwarppointer - buttonmap dragging nodragging wireframe_mode - wireframe nowireframe wirecopyrect nowirecopyrect + cursorshape nocursorshape cursorpos nocursorpos cursor + show_cursor noshow_cursor nocursor arrow xfixes noxfixes + xdamage noxdamage xd_area xd_mem alphacut alphafrac + alpharemove noalpharemove alphablend noalphablend + xwarp xwarppointer noxwarp noxwarppointer buttonmap + dragging nodragging wireframe_mode wireframe wf + nowireframe nowf wirecopyrect wcr nowirecopyrect nowcr + scr_area scrollcopyrect scr noscrollcopyrect noscr pointer_mode pm input_skip input client_input speeds debug_pointer dp nodebug_pointer nodp debug_keyboard dk nodebug_keyboard nodk deferupdate defer wait rfbwait @@ -5622,19 +5704,23 @@ Options: noalwaysshared nevershared noalwaysshared dontdisconnect nodontdisconnect desktop noremote - aro= debug_xevents debug_xdamage display vncdisplay - desktopname http_url auth users rootshift clipshift - scale_str scaled_x scaled_y scale_numer scale_denom - scale_fac scaling_blend scaling_nomult4 scaling_pad - scaling_interpolate inetd privremote unsafe safer - nocmds passwdfile using_shm logfile o flag rc norc h - help V version lastmod bg sigpipe threads pipeinput - clients client_count pid ext_xtest ext_xtrap ext_xkb - ext_xshm ext_xinerama ext_overlay ext_xfixes ext_xdamage - ext_xrandr rootwin num_buttons button_mask mouse_x - mouse_y bpp depth indexed_color dpy_x dpy_y wdpy_x - wdpy_y off_x off_y cdpy_x cdpy_y coff_x coff_y rfbauth - passwd + aro= debug_xevents nodebug_xevents debug_xevents + debug_xdamage nodebug_xdamage debug_xdamage + debug_wireframe nodebug_wireframe debug_wireframe + debug_scroll nodebug_scroll debug_scroll display + vncdisplay desktopname http_url auth users rootshift + clipshift scale_str scaled_x scaled_y scale_numer + scale_denom scale_fac scaling_blend scaling_nomult4 + scaling_pad scaling_interpolate inetd privremote + unsafe safer nocmds passwdfile using_shm logfile + o flag rc norc h help V version lastmod bg sigpipe + threads readrate netrate netlatency pipeinput clients + client_count pid ext_xtest ext_xtrap ext_xrecord + ext_xkb ext_xshm ext_xinerama ext_overlay ext_xfixes + ext_xdamage ext_xrandr rootwin num_buttons button_mask + mouse_x mouse_y bpp depth indexed_color dpy_x dpy_y + wdpy_x wdpy_y off_x off_y cdpy_x cdpy_y coff_x coff_y + rfbauth passwd -sync By default -remote commands are run asynchronously, that is, the request is posted and the program immediately @@ -5656,6 +5742,8 @@ Options: taken place. -noremote Do not process any remote control commands or queries. +-yesremote Do process remote control commands or queries. + Default: -yesremote A note about security wrt remote control commands. If someone can connect to the X display and change @@ -5685,10 +5773,10 @@ Options: -safer Equivalent to: -novncconnect -noremote and prohibiting -gui and the -connect file. Shuts off communcation channels. --privremote Perform some sanity checks and only allow remote-control +-privremote Perform some sanity checks and disable remote-control commands if it appears that the X DISPLAY and/or - connectfile cannot be accessed by other users. (not - complete, does not check for empty access control list) + connectfile can be accessed by other users. Once + remote-control is disabled it cannot be turned back on. -nocmds No external commands (e.g. system(3), popen(3), exec(3)) will be run. diff --git a/x11vnc/tkx11vnc b/x11vnc/tkx11vnc index 0ed781b..5128d52 100755 --- a/x11vnc/tkx11vnc +++ b/x11vnc/tkx11vnc @@ -189,6 +189,11 @@ Debugging =GA tail-logfile quiet -- + debug_xevents + debug_xdamage + debug_wireframe + debug_scroll + -- =GA show-start-cmd =DG debug_gui @@ -232,6 +237,8 @@ Tuning wireframe wireframe_mode: =-C:never,top,always wirecopyrect: + =-C:never,keys,mouse,always scrollcopyrect: + scr_area: -- noshm flipbyteorder diff --git a/x11vnc/tkx11vnc.h b/x11vnc/tkx11vnc.h index b00fc1d..a92a212 100644 --- a/x11vnc/tkx11vnc.h +++ b/x11vnc/tkx11vnc.h @@ -195,6 +195,11 @@ " =GA tail-logfile\n" " quiet\n" " --\n" +" debug_xevents\n" +" debug_xdamage\n" +" debug_wireframe\n" +" debug_scroll\n" +" --\n" " =GA show-start-cmd\n" " =DG debug_gui\n" "\n" @@ -238,6 +243,8 @@ " wireframe\n" " wireframe_mode:\n" " =-C:never,top,always wirecopyrect:\n" +" =-C:never,keys,mouse,always scrollcopyrect:\n" +" scr_area:\n" " --\n" " noshm\n" " flipbyteorder\n" diff --git a/x11vnc/x11vnc.1 b/x11vnc/x11vnc.1 index 2acc247..2086491 100644 --- a/x11vnc/x11vnc.1 +++ b/x11vnc/x11vnc.1 @@ -1,8 +1,8 @@ .\" This file was automatically generated from x11vnc -help output. -.TH X11VNC "1" "April 2005" "x11vnc " "User Commands" +.TH X11VNC "1" "May 2005" "x11vnc " "User Commands" .SH NAME x11vnc - allow VNC connections to real X11 displays - version: 0.7.2, lastmod: 2005-04-19 + version: 0.7.2, lastmod: 2005-05-02 .SH SYNOPSIS .B x11vnc [OPTION]... @@ -956,13 +956,16 @@ the full opaque window. This is based completely on heuristics and may not always work: it depends on your window manager and even how you move things around. See \fB-pointer_mode\fR below for discussion of the "bogging -down" problem this tries to avoid. Default: \fB-wireframe\fR +down" problem this tries to avoid. +Default: \fB-wireframe\fR +.IP +Shorter aliases: \fB-wf\fR [str] and \fB-nowf\fR .IP The value "str" is optional and, of course, is packed with many tunable parameters for this scheme: .IP Format: shade,linewidth,percent,T+B+L+R,t1+t2+t3+t4 -Default: 0xff,3,0,32+8+8+8,0.15+0.35+4.0+0.1 +Default: 0xff,3,0,32+8+8+8,0.15+0.30+5.0+0.125 .IP If you leave nothing between commas: ",," the default value is used. If you don't specify enough commas, @@ -996,16 +999,71 @@ link this might be a better choice: 0.25+0.6+6.0+0.15 \fB-wirecopyrect\fR \fImode,\fR \fB-nowirecopyrect\fR .IP Since the \fB-wireframe\fR mechanism evidently tracks moving -windows, a speedup can be obtained by telling the VNC -viewers to locally copy the translated window region. -This is the VNC CopyRect encoding: the framebuffer -update doesn't need to send the actual new image data. +windows accurately, a speedup can be obtained by +telling the VNC viewers to locally copy the translated +window region. This is the VNC CopyRect encoding: +the framebuffer update doesn't need to send the actual +new image data. +.IP +Shorter aliases: \fB-wcr\fR [mode] and \fB-nowcr\fR +.IP "mode" can be "never" (same as \fB-nowirecopyrect)\fR to never try the copyrect, "top" means only do it if the window was not covered by any other windows, and "always" means to translate the orginally unobscured region (this may look odd as the remaining pieces come -in, but helps on a slow link) Default: always +in, but helps on a slow link). Default: "always" +.IP +Note: there can be painting errors when using \fB-scale\fR +so CopyRect is skipped when scaling unless you specify +\fB-wirecopyrect\fR on the command line or by remote-control. +.PP +\fB-scrollcopyrect\fR \fImode,\fR \fB-noscrollcopyrect\fR +.IP +Like \fB-wirecopyrect,\fR but use heuristics to try to guess +if a window has scrolled its contents (either vertically +or horizontally). This requires the RECORD X extension +to "snoop" on X applications (currently for certain +XCopyArea and XConfigureWindow X protocol requests). +Examples: Hitting in a terminal window when the +cursor was at the bottom, the text scrolls up one line. +Hitting arrow in a web browser window, the web +page scrolls up a small amount. +.IP +Shorter aliases: \fB-scr\fR [mode] and \fB-noscr\fR +.IP +This scheme will not always detect scrolls, but when +it does there is a nice speedup from using the VNC +CopyRect encoding (see \fB-wirecopyrect).\fR The speedup +is both in reduced network traffic and reduced X +framebuffer polling/copying. On the other hand, +it may induce undesired transients (e.g. a terminal +cursor being scrolled up when it should not be) or other +painting errors. These are automatically repaired in a +short period of time. If this is unacceptable disable +the feature with \fB-noscrollcopyrect.\fR +.IP +"mode" can be "never" (same as \fB-noscrollcopyrect)\fR +to never try the copyrect, "keys" means to try it +in response to keystrokes only, "mouse" means to +try it in response to mouse events only, "always" +means to do both. Default: "always" +.IP +Note: there can be painting errors when using +\fB-scale\fR so CopyRect is skipped when scaling unless +you specify \fB-scrollcopyrect\fR on the command line or +by remote-control. +.PP +\fB-scr_area\fR \fIn\fR +.IP +Set the minimum area in pixels for a rectangle +to be considered for the \fB-scrollcopyrect\fR detection +scheme. This is to avoid wasting the effort on small +rectangles that would be quickly updated the normal way. +E.g. suppose an app updated the position of its skinny +scrollbar first and then shifted the large panel +it controlled. We want to be sure to skip the small +scrollbar and get the large panel. Default: 60000 .PP \fB-pointer_mode\fR \fIn\fR .IP @@ -1633,15 +1691,21 @@ dragging disable \fB-nodragging\fR mode. .IP nodragging enable \fB-nodragging\fR mode. .IP -wireframe enable \fB-wireframe\fR mode. +wireframe enable \fB-wireframe\fR mode. same as "wf" .IP -nowireframe disable \fB-wireframe\fR mode. +nowireframe disable \fB-wireframe\fR mode. same as "nowf" .IP wireframe:str enable \fB-wireframe\fR mode string. .IP wireframe_mode:str enable \fB-wireframe\fR mode string. .IP -wirecopyrect:str set \fB-wirecopyrect\fR string. +wirecopyrect:str set \fB-wirecopyrect\fR string. same as "wcr:" +.IP +scrollcopyrect:str set \fB-scrollcopyrect\fR string. same "scr" +.IP +noscrollcopyrect disable \fB-scrollcopyrect__mode_.\fR "noscr" +.IP +scr_area:n set \fB-scr_area\fR to n .IP pointer_mode:n set \fB-pointer_mode\fR to n. same as "pm" .IP @@ -1721,6 +1785,23 @@ dontdisconnect enable \fB-dontdisconnect\fR mode. nodontdisconnect disable \fB-dontdisconnect\fR mode. (may interfere with other options) .IP +debug_xevents enable debugging X events. +.IP +nodebug_xevents disable debugging X events. +.IP +debug_xdamage enable debugging X DAMAGE mechanism. +.IP +nodebug_xdamage disable debugging X DAMAGE mechanism. +.IP +debug_wireframe enable debugging wireframe mechanism. +.IP +nodebug_wireframe disable debugging wireframe mechanism. +.IP +debug_scroll enable debugging scrollcopy mechanism. +.IP +nodebug_scroll disable debugging scrollcopy mechanism. +.IP +.IP noremote disable the \fB-remote\fR command processing, it cannot be turned back on. .IP @@ -1795,13 +1876,14 @@ noquiet modtweak nomodtweak xkb noxkb skip_keycodes add_keysyms noadd_keysyms clear_mods noclear_mods clear_keys noclear_keys remap repeat norepeat fb nofb bell nobell sel nosel primary noprimary -cursorshape nocursorshape cursorpos nocursorpos -cursor show_cursor noshow_cursor nocursor arrow -xfixes noxfixes xdamage noxdamage xd_area xd_mem -alphacut alphafrac alpharemove noalpharemove alphablend -noalphablend xwarp xwarppointer noxwarp noxwarppointer -buttonmap dragging nodragging wireframe_mode -wireframe nowireframe wirecopyrect nowirecopyrect +cursorshape nocursorshape cursorpos nocursorpos cursor +show_cursor noshow_cursor nocursor arrow xfixes noxfixes +xdamage noxdamage xd_area xd_mem alphacut alphafrac +alpharemove noalpharemove alphablend noalphablend +xwarp xwarppointer noxwarp noxwarppointer buttonmap +dragging nodragging wireframe_mode wireframe wf +nowireframe nowf wirecopyrect wcr nowirecopyrect nowcr +scr_area scrollcopyrect scr noscrollcopyrect noscr pointer_mode pm input_skip input client_input speeds debug_pointer dp nodebug_pointer nodp debug_keyboard dk nodebug_keyboard nodk deferupdate defer wait rfbwait @@ -1811,19 +1893,23 @@ httpdir enablehttpproxy noenablehttpproxy alwaysshared noalwaysshared nevershared noalwaysshared dontdisconnect nodontdisconnect desktop noremote .IP -aro= debug_xevents debug_xdamage display vncdisplay -desktopname http_url auth users rootshift clipshift -scale_str scaled_x scaled_y scale_numer scale_denom -scale_fac scaling_blend scaling_nomult4 scaling_pad -scaling_interpolate inetd privremote unsafe safer -nocmds passwdfile using_shm logfile o flag rc norc h -help V version lastmod bg sigpipe threads pipeinput -clients client_count pid ext_xtest ext_xtrap ext_xkb -ext_xshm ext_xinerama ext_overlay ext_xfixes ext_xdamage -ext_xrandr rootwin num_buttons button_mask mouse_x -mouse_y bpp depth indexed_color dpy_x dpy_y wdpy_x -wdpy_y off_x off_y cdpy_x cdpy_y coff_x coff_y rfbauth -passwd +aro= debug_xevents nodebug_xevents debug_xevents +debug_xdamage nodebug_xdamage debug_xdamage +debug_wireframe nodebug_wireframe debug_wireframe +debug_scroll nodebug_scroll debug_scroll display +vncdisplay desktopname http_url auth users rootshift +clipshift scale_str scaled_x scaled_y scale_numer +scale_denom scale_fac scaling_blend scaling_nomult4 +scaling_pad scaling_interpolate inetd privremote +unsafe safer nocmds passwdfile using_shm logfile +o flag rc norc h help V version lastmod bg sigpipe +threads readrate netrate netlatency pipeinput clients +client_count pid ext_xtest ext_xtrap ext_xrecord +ext_xkb ext_xshm ext_xinerama ext_overlay ext_xfixes +ext_xdamage ext_xrandr rootwin num_buttons button_mask +mouse_x mouse_y bpp depth indexed_color dpy_x dpy_y +wdpy_x wdpy_y off_x off_y cdpy_x cdpy_y coff_x coff_y +rfbauth passwd .PP \fB-sync\fR .IP @@ -1846,9 +1932,11 @@ if the x11vnc takes longer than that to process the requests the requestor will think that a failure has taken place. .PP -\fB-noremote\fR +\fB-noremote,\fR \fB-yesremote\fR .IP Do not process any remote control commands or queries. +Do process remote control commands or queries. +Default: \fB-yesremote\fR .IP A note about security wrt remote control commands. If someone can connect to the X display and change @@ -1886,10 +1974,10 @@ channels. .PP \fB-privremote\fR .IP -Perform some sanity checks and only allow remote-control +Perform some sanity checks and disable remote-control commands if it appears that the X DISPLAY and/or -connectfile cannot be accessed by other users. (not -complete, does not check for empty access control list) +connectfile can be accessed by other users. Once +remote-control is disabled it cannot be turned back on. .PP \fB-nocmds\fR .IP diff --git a/x11vnc/x11vnc.c b/x11vnc/x11vnc.c index e09d9ca..092c236 100644 --- a/x11vnc/x11vnc.c +++ b/x11vnc/x11vnc.c @@ -127,60 +127,9 @@ /* -- x11vnc.h -- */ -/* - *************************************************************************** - * if you are inserting this file, x11vnc.c into an old CVS tree you - * may need to set OLD_TREE to 1. Or use -DOLD_TREE=1 in CPPFLAGS. - * See below for LibVNCServer 0.7 tips. - */ - -#ifndef OLD_TREE -#define OLD_TREE 0 -#endif -#if OLD_TREE - -/* BEGIN OLD TREE */ - -/* - * if you have a very old tree (LibVNCServer 0.6) and get errors these may - * be need to be uncommented. LibVNCServer <= 0.5 is no longer supported. - * note the maxRectsPerUpdate below is a hack that may break some usage. -#define oldCursorX cursorX -#define oldCursorY cursorY -#define thisHost rfbThisHost -#define framebufferUpdateMessagesSent rfbFramebufferUpdateMessagesSent -#define bytesSent rfbBytesSent -#define rawBytesEquivalent rfbRawBytesEquivalent -#define progressiveSliceHeight maxRectsPerUpdate - */ - -/* - * If you are building in an older libvncserver tree with this newer - * x11vnc.c file using OLD_TREE=1 you may need to set some of these lines - * since your older libvncserver configure is not setting them. - * - * For the features LIBVNCSERVER_HAVE_LIBXINERAMA and - * LIBVNCSERVER_HAVE_XFIXES you may also need to add - * -lXinerama or -lXfixes, respectively, to the linking line, e.g. - * by setting them in LD_FLAGS before running configure. - */ - -#define LIBVNCSERVER_HAVE_XSHM 1 -#define LIBVNCSERVER_HAVE_XTEST 1 -#define LIBVNCSERVER_HAVE_XTESTGRABCONTROL 1 - -#define LIBVNCSERVER_HAVE_PWD_H 1 -#define LIBVNCSERVER_HAVE_SYS_WAIT_H 1 -#define LIBVNCSERVER_HAVE_UTMPX_H 1 -#define LIBVNCSERVER_HAVE_MMAP 1 - -/* -#define LIBVNCSERVER_HAVE_LIBXINERAMA 1 -#define LIBVNCSERVER_HAVE_XFIXES 1 -#define LIBVNCSERVER_HAVE_LIBXDAMAGE 1 - */ - -/* END OLD TREE */ +/* TODO: autoconf */ +#if 1 +#define LIBVNCSERVER_HAVE_RECORD 1 #endif /****************************************************************************/ @@ -206,63 +155,41 @@ #include /****************************************************************************/ -#if OLD_TREE -/* - * This is another transient for building in older libvncserver trees, - * due to the API change: - */ -#define dontDisconnect rfbDontDisconnect -#define neverShared rfbNeverShared -#define alwaysShared rfbAlwaysShared -#define clientHead rfbClientHead -#define serverFormat rfbServerFormat -#define port rfbPort -#define listenSock rfbListenSock -#define deferUpdateTime rfbDeferUpdateTime -#define authPasswdData rfbAuthPasswdData -#define rfbEncryptAndStorePasswd vncEncryptAndStorePasswd -#define maxClientWait rfbMaxClientWait -#define rfbHttpInitSockets httpInitSockets - -#define RFBUNDRAWCURSOR(s) if (s) {rfbUndrawCursor(s);} -#else /* OLD_TREE */ -#define RFBUNDRAWCURSOR(s) -#endif /* !OLD_TREE */ + +/* Build-time customization via CPPFLAGS. */ + /* - * To get a clean build in a LibVNCServer 0.7 source tree no need for - * OLD_TREE, you just need to either download the forgotten tkx11vnc.h - * file or run: + * Summary of options to include in CPPFLAGS for custom builds: * - * echo 'char gui_code[] = "";' > tkx11vnc.h + * -DSHARED to have the vnc display shared by default. + * -DFOREVER to have -forever on by default. + * -DNOREPEAT=0 to have -repeat on by default. * - * (this disables the gui) and uncomment this line: -#define rfbSetCursor(a, b) rfbSetCursor((a), (b), FALSE) - */ - -/* - * To get a clean build on LibVNCServer 0.7.1 no need for OLD_TREE, - * just uncomment this line (note the maxRectsPerUpdate below is a hack - * that may break some usage): + * -DREMOTE_DEFAULT=0 to disable remote-control on by default (-yesremote). + * -DREMOTE_CONTROL=0 to disable remote-control mechanism completely. + * -DEXTERNAL_COMMANDS=0 to disable the running of all external commands. * -#define listenInterface maxRectsPerUpdate + * -DHARDWIRE_PASSWD=... hardwired passwords, quoting necessary. + * -DHARDWIRE_VIEWPASSWD=... * - */ -/****************************************************************************/ - - -/* Build-time customization via CPPFLAGS. */ - -/* - * -DX11VNC_SHARED to have the vnc display shared by default. - */ - -/* - * -DX11VNC_FOREVER to have -forever on by default. + * -DWIREFRAME=0 to have -nowireframe as the default. + * -DWIREFRAME_COPYRECT=0 to have -nowirecopyrect as the default. + * -DSCROLL_COPYRECT=0 to have -noscrollcopyrect as the default. + * -DXDAMAGE=0 to have -noxdamage as the default. + * + * -DPOINTER_MODE_DEFAULT={0,1,2,3,4,5} set default -pointer_mode. + * -DBOLDLY_CLOSE_DISPLAY=0 to not close X DISPLAY under -rawfb. + * -DSMALL_FOOTPRINT=1 for smaller binary size (no help, no gui, etc) + * use 2 or 3 for even smaller footprint. + * + * Set these in CPPFLAGS before running configure. E.g.: + * + * % env CPPFLAGS="-DFOREVER -DREMOTE_CONTROL=0" ./configure + * % make */ /* * This can be used to disable the remote control mechanism. - * E.g. -DREMOTE_CONTROL=0 in CPPFLAGS. */ #ifndef REMOTE_CONTROL #define REMOTE_CONTROL 1 @@ -334,6 +261,11 @@ XETC *trap_ctx = NULL; #endif int xtrap_base_event_type = 0; +#if LIBVNCSERVER_HAVE_RECORD +#include +#include +#endif + #if LIBVNCSERVER_HAVE_XKEYBOARD #include #endif @@ -423,8 +355,11 @@ int alt_arrow = 1; int xfixes_base_event_type = 0; +#ifndef XDAMAGE +#define XDAMAGE 1 +#endif +int use_xdamage = XDAMAGE; /* use the xdamage rects for scanline hints */ int xdamage_present = 0; -int use_xdamage = 1; /* just use the xdamage rects. for scanline hints */ #if LIBVNCSERVER_HAVE_LIBXDAMAGE #include Damage xdamage = 0; @@ -436,7 +371,7 @@ int xdamage_tile_count; /* date +'lastmod: %Y-%m-%d' */ -char lastmod[] = "0.7.2 lastmod: 2005-04-19"; +char lastmod[] = "0.7.2 lastmod: 2005-05-02"; int hack_val = 0; /* X display info */ @@ -504,7 +439,6 @@ unsigned short main_red_max, main_green_max, main_blue_max; unsigned short main_red_shift, main_green_shift, main_blue_shift; /* struct with client specific data: */ -#define RATE_SAMPLES 5 #define CILEN 10 typedef struct _ClientData { int uid; @@ -522,11 +456,9 @@ typedef struct _ClientData { double timer; double send_cmp_rate; double send_raw_rate; - int set_cmp_bytes; - int set_raw_bytes; - double cmp_samp[RATE_SAMPLES]; - double raw_samp[RATE_SAMPLES]; - int sample; + double latency; + int cmp_bytes_sent; + int raw_bytes_sent; } ClientData; /* scaling parameters */ @@ -560,6 +492,7 @@ unsigned char *tile_has_xdamage_diff, *tile_row_has_xdamage_diff; /* times of recent events */ time_t last_event, last_input, last_client = 0; +double servertime_diff = 0.0; /* last client to move pointer */ rfbClientPtr last_pointer_client = NULL; @@ -574,7 +507,9 @@ int button_change_x, button_change_y; int got_user_input = 0; int got_pointer_input = 0; int got_keyboard_input = 0; +int urgent_update = 0; int last_keyboard_input = 0; +rfbKeySym last_keysym = 0; int fb_copy_in_progress = 0; int drag_in_progress = 0; int shut_down = 0; @@ -649,6 +584,7 @@ void initialize_watch_bell(void); void initialize_xinerama(void); void initialize_xfixes(void); void initialize_xdamage(void); +int valid_window(Window, XWindowAttributes *); void create_xdamage_if_needed(void); void destroy_xdamage_if_needed(void); void initialize_xrandr(void); @@ -686,12 +622,21 @@ int check_pipeinput(void); void cursor_position(int, int); void parse_wireframe(void); +void parse_scroll_copyrect(void); void set_wirecopyrect_mode(char *); +void set_scrollcopyrect_mode(char *); +int try_copyrect(Window, int, int, int, int, int, int, int *); +void do_copyregion(sraRegionPtr, int, int); +int direct_fb_copy(int, int, int, int, int); +int get_wm_frame_pos(int *, int *, int *, int *, int *, int *, Window *); +int near_wm_edge(int, int, int, int, int, int); +int near_scrollbar_edge(int, int, int, int, int, int); void read_vnc_connect_prop(void); void set_vnc_connect_prop(char *); +void fb_push(void); char *process_remote_cmd(char *, int); -void rfbPE(rfbScreenInfoPtr, long); -void rfbCFD(rfbScreenInfoPtr, long); +void rfbPE(long); +void rfbCFD(long); int scan_for_updates(int); void set_colormap(int); void set_offset(void); @@ -728,8 +673,10 @@ int get_raw_rate(void); int get_read_rate(void); int get_net_rate(void); int get_net_latency(void); +int get_latency(void); void measure_send_rates(int); int fb_update_sent(int *); +void snapshot_stack_list(int, double); int get_remote_port(int sock); int get_local_port(int sock); @@ -771,29 +718,42 @@ char *solid_default = "cyan4"; char *speeds_str = NULL; /* -speeds TBD */ int measure_speeds = 1; int speeds_net_rate = 0; +int speeds_net_rate_measured = 0; int speeds_net_latency = 0; +int speeds_net_latency_measured = 0; int speeds_read_rate = 0; +int speeds_read_rate_measured = 0; char *rc_rcfile = NULL; /* -rc */ int rc_norc = 0; int opts_bg = 0; -#ifndef X11VNC_SHARED +#ifndef SHARED int shared = 0; /* share vnc display. */ #else int shared = 1; #endif -#ifndef X11VNC_FOREVER +#ifndef FOREVER int connect_once = 1; /* disconnect after first connection session. */ #else int connect_once = 0; #endif int deny_all = 0; /* global locking of new clients */ -int accept_remote_cmds = 1; /* -noremote */ +#ifndef REMOTE_DEFAULT +#define REMOTE_DEFAULT 1 +#endif +int accept_remote_cmds = REMOTE_DEFAULT; /* -noremote */ int safe_remote_only = 1; /* -unsafe */ int priv_remote = 0; /* -privremote */ int more_safe = 0; /* -safer */ +#ifndef EXTERNAL_COMMANDS +#define EXTERNAL_COMMANDS 1 +#endif +#if EXTERNAL_COMMANDS int no_external_cmds = 0; /* -nocmds */ +#else +int no_external_cmds = 1; /* cannot be turned back on. */ +#endif int started_as_root = 0; int host_lookup = 1; char *users_list = NULL; /* -users */ @@ -829,6 +789,8 @@ int subwin_wait_mapped = 0; int debug_xevents = 0; /* -R debug_xevents:1 */ int debug_xdamage = 0; /* -R debug_xdamage:1 or 2 ... */ +int debug_wireframe = 0; +int debug_wireframe2 = 0; int xtrap_input = 0; /* -xtrap for user input insertion */ int xinerama = 0; /* -xinerama */ @@ -844,6 +806,10 @@ char *pad_geometry = NULL; time_t pad_geometry_time; int use_snapfb = 0; +Display *rdpy_data = 0; /* Data connection for RECORD */ +Display *rdpy_ctrl = 0; /* Control connection for RECORD */ +int use_xrecord = 0; + char *client_connect = NULL; /* strings for -connect option */ char *client_connect_file = NULL; int vnc_connect = 1; /* -vncconnect option */ @@ -861,15 +827,47 @@ int show_dragging = 1; /* process mouse movement events */ int wireframe = WIREFRAME; /* try to emulate wireframe wm moves */ /* shade,linewidth,percent,T+B+L+R,t1+t2+t3+t4 */ #ifndef WIREFRAME_PARMS -#define WIREFRAME_PARMS "0xff,3,0,32+8+8+8,0.15+0.35+4.0+0.1" +#define WIREFRAME_PARMS "0xff,3,0,32+8+8+8,0.15+0.30+5.0+0.125" #endif char *wireframe_str = NULL; char *wireframe_copyrect = NULL; +#ifndef WIREFRAME_COPYRECT +#define WIREFRAME_COPYRECT 1 +#endif +#if WIREFRAME_COPYRECT char *wireframe_copyrect_default = "always"; +#else +char *wireframe_copyrect_default = "never"; +#endif int wireframe_in_progress = 0; Window *stack_list = NULL; int stack_num = 0; -int no_autorepeat = 1; /* turn off autorepeat with clients */ + +/* T+B+L+R,tkey+presist_key,tmouse+persist_mouse */ +#ifndef SCROLL_COPYRECT_PARMS +#define SCROLL_COPYRECT_PARMS "0+64+32+32,0.02+0.4,0.08+0.4" +#endif +char *scroll_copyrect_str = NULL; +#ifndef SCROLL_COPYRECT +#define SCROLL_COPYRECT 1 +#endif +char *scroll_copyrect = NULL; +#if SCROLL_COPYRECT +#if 1 +char *scroll_copyrect_default = "always"; /* -scrollcopyrect */ +#else +char *scroll_copyrect_default = "keys"; +#endif +#else +char *scroll_copyrect_default = "never"; +#endif +int scrollcopyrect_min_area = 60000; /* minimum rectangle area */ +int debug_scroll = 0; + +#ifndef NOREPEAT +#define NOREPEAT 1 +#endif +int no_autorepeat = NOREPEAT; /* turn off autorepeat with clients */ int no_repeat_countdown = 2; int watch_bell = 1; /* watch for the bell using XKEYBOARD */ int sound_bell = 1; /* actually send it */ @@ -925,6 +923,7 @@ int overlay_cursor = 1; int xshm_present = 0; int xtest_present = 0; int xtrap_present = 0; +int xrecord_present = 0; int xkb_present = 0; int xinerama_present = 0; @@ -947,6 +946,8 @@ int got_nevershared = 0; int got_cursorpos = 0; int got_pointer_mode = -1; int got_noviewonly = 0; +int got_wirecopyrect = 0; +int got_scrollcopyrect = 0; /* threaded vs. non-threaded (default) */ #if LIBVNCSERVER_X11VNC_THREADED && ! defined(X11VNC_THREADED) @@ -1013,6 +1014,14 @@ int nabs(int n) { } } +double dabs(double x) { + if (x < 0.0) { + return -x; + } else { + return x; + } +} + void lowercase(char *str) { char *p; if (str == NULL) { @@ -1136,7 +1145,7 @@ int pick_windowid(unsigned long *num) { * select timedout or error. * note this rfbPE takes about 30ms too: */ - rfbPE(screen, -1); + rfbPE(-1); XFlush(dpy); continue; } @@ -1952,6 +1961,30 @@ char *host2ip(char *host) { return str; } +char *raw2host(char *raw, int len) { + char *str; +#if LIBVNCSERVER_HAVE_NETDB_H && LIBVNCSERVER_HAVE_NETINET_IN_H + struct hostent *hp; + + if (! host_lookup) { + return strdup("unknown"); + } + + hp = gethostbyaddr(raw, len, AF_INET); + if (!hp) { + return strdup(inet_ntoa(*((struct in_addr *)raw))); + } + str = strdup(hp->h_name); +#else + str = strdup("unknown"); +#endif + return str; +} + +char *raw2ip(char *raw) { + return strdup(inet_ntoa(*((struct in_addr *)raw))); +} + char *ip2host(char *ip) { char *str; #if LIBVNCSERVER_HAVE_NETDB_H && LIBVNCSERVER_HAVE_NETINET_IN_H @@ -2558,15 +2591,6 @@ Bool XTestCompareCursorWithWindow_wr(Display* dpy, Window w, Cursor cursor) { #endif } -/* how to handle old tree for this w/o OLD_TREE? */ -#if 0 -#if LIBVNCSERVER_HAVE_XTEST -#ifndef LIBVNCSERVER_HAVE_XTESTGRABCONTROL -#define LIBVNCSERVER_HAVE_XTESTGRABCONTROL 1 -#endif -#endif -#endif - Bool XTestQueryExtension_wr(Display *dpy, int *ev, int *er, int *maj, int *min) { #if LIBVNCSERVER_HAVE_XTEST @@ -2642,3970 +2666,4395 @@ int XTRAP_GrabControl_wr(Display *dpy, Bool impervious) { return 0; } -void disable_grabserver(void) { +void disable_grabserver(Display *dpy) { int ok = 0; + static int didmsg = 0; if (! xtrap_input) { if (XTestGrabControl_wr(dpy, True)) { XTRAP_GrabControl_wr(dpy, False); - rfbLog("GrabServer control via XTEST.\n"); + if (! didmsg) { + rfbLog("GrabServer control via XTEST.\n"); + didmsg = 1; + } ok = 1; } else { if (XTRAP_GrabControl_wr(dpy, True)) { ok = 1; - rfbLog("Using DEC-XTRAP for protection from " - "XGrabServer.\n"); + if (! didmsg) { + rfbLog("Using DEC-XTRAP for protection" + " from XGrabServer.\n"); + didmsg = 1; + } } } } else { if (XTRAP_GrabControl_wr(dpy, True)) { XTestGrabControl_wr(dpy, False); - rfbLog("GrabServer control via DEC-XTRAP.\n"); + if (! didmsg) { + rfbLog("GrabServer control via DEC-XTRAP.\n"); + didmsg = 1; + } ok = 1; } else { if (XTestGrabControl_wr(dpy, True)) { ok = 1; - rfbLog("DEC-XTRAP XGrabServer protection not " - "available, using XTEST.\n"); + if (! didmsg) { + rfbLog("DEC-XTRAP XGrabServer " + "protection not available, " + "using XTEST.\n"); + didmsg = 1; + } } } } - if (! ok) { + if (! ok && ! didmsg) { rfbLog("No XTEST or DEC-XTRAP protection from XGrabServer.\n"); rfbLog("Deadlock if your window manager calls XGrabServer!!\n"); } } +Bool XRecordQueryVersion_wr(Display *dpy, int *maj, int *min) { +#if LIBVNCSERVER_HAVE_RECORD + return XRecordQueryVersion(dpy, maj, min); +#else + return False; +#endif +} -/* -- cleanup.c -- */ -/* - * Exiting and error handling routines - */ +#if LIBVNCSERVER_HAVE_RECORD +XRecordRange *rr_CA; +XRecordRange *rr_CW; +XRecordRange *rr_scroll[10]; +XRecordContext rc_scroll; +XRecordClientSpec rcs_scroll; +#endif -static int exit_flag = 0; -int exit_sig = 0; +int xrecording = 0; +int xrecord_set_by_keys = 0; +int xrecord_set_by_mouse = 0; +Window xrecord_focus_window = None; +Window xrecord_wm_window = None; -void clean_shm(int quick) { - int i, cnt = 0; +void initialize_xrecord(void) { + use_xrecord = 0; + if (! xrecord_present) { + return; + } +#if LIBVNCSERVER_HAVE_RECORD + rr_CA = XRecordAllocRange(); + rr_CW = XRecordAllocRange(); + if (! rr_CA || ! rr_CW) { + return; + } + /* protocol request ranges: */ + rr_CA->core_requests.first = X_CopyArea; + rr_CA->core_requests.last = X_CopyArea; + + rr_CW->core_requests.first = X_ConfigureWindow; + rr_CW->core_requests.last = X_ConfigureWindow; + + /* open a 2nd control connection to DISPLAY: */ + rdpy_ctrl = XOpenDisplay(DisplayString(dpy)); + XSync(dpy, True); + XSync(rdpy_ctrl, True); + /* open datalink connection to DISPLAY: */ + rdpy_data = XOpenDisplay(DisplayString(dpy)); + if (!rdpy_ctrl || ! rdpy_data) { + return; + } + disable_grabserver(rdpy_ctrl); + disable_grabserver(rdpy_data); + use_xrecord = 1; +#endif +} - if (raw_fb) quick = 1; /* raw_fb hack */ +int xrecord_skip_keysym(rfbKeySym keysym) { + KeySym sym = (KeySym) keysym; - /* - * to avoid deadlock, etc, under quick=1 we just delete the shm - * areas and leave the X stuff hanging. - */ - if (quick) { - shm_delete(&scanline_shm); - shm_delete(&fullscreen_shm); - shm_delete(&snaprect_shm); - } else { - shm_clean(&scanline_shm, scanline); - shm_clean(&fullscreen_shm, fullscreen); - shm_clean(&snaprect_shm, snaprect); + if (IsModifierKey(sym)) { + return 1; } + return 0; +} - /* - * Here we have to clean up quite a few shm areas for all - * the possible tile row runs (40 for 1280), not as robust - * as one might like... sometimes need to run ipcrm(1). - */ - for(i=1; i<=ntiles_x; i++) { - if (i > tile_shm_count) { - break; - } - if (quick) { - shm_delete(&tile_row_shm[i]); - } else { - shm_clean(&tile_row_shm[i], tile_row[i]); - } - cnt++; - if (single_copytile_count && i >= single_copytile_count) { - break; - } +int xrecord_skip_button(int new, int old) { + return 0; +} + +int xrecord_scroll_keysym(rfbKeySym keysym) { + KeySym sym = (KeySym) keysym; + /* X11/keysymdef.h */ + + if (sym == XK_Return || sym == XK_KP_Enter || sym == XK_Linefeed) { + return 1; /* Enter */ } - if (!quiet) { - rfbLog("deleted %d tile_row polling images.\n", cnt); + if (sym==XK_Up || sym==XK_KP_Up || sym==XK_Down || sym==XK_KP_Down) { + return 1; /* U/D arrows */ } + if (sym == XK_Left || sym == XK_KP_Left || sym == XK_Right || + sym == XK_KP_Right) { + return 1; /* L/R arrows */ + } + if (sym == XK_J || sym == XK_j || sym == XK_K || sym == XK_k) { + return 1; /* vi */ + } + if (sym == XK_N || sym == XK_n || sym == XK_P || sym == XK_p) { + return 1; /* emacs */ + } + return 0; } -/* - * Normal exiting - */ -void clean_up_exit (int ret) { - exit_flag = 1; +typedef struct scroll_event { + Window win, frame; + int dx, dy; + int x, y, w, h, t; + int win_x, win_y, win_w, win_h; + int new_x, new_y, new_w, new_h; +} scroll_event_t; + +#define SCR_EV_MAX 128 +scroll_event_t scr_ev[SCR_EV_MAX]; +int scr_ev_cnt = 0; + +XID xrecord_seq = 0; +double xrecord_start = 0.0; + +#if LIBVNCSERVER_HAVE_RECORD +void record_CA(XPointer ptr, XRecordInterceptData *rec_data) { + xCopyAreaReq *req; + Window src = None, dst = None, c; + XWindowAttributes attr; + int src_x, src_y, dst_x, dst_y, rx, ry; + int good = 1, dx, dy, k=0, i; + unsigned int w, h; + int dba = 0, db = debug_scroll; - /* remove the shm areas: */ - clean_shm(0); +//dba = 1; - if (! dpy) exit(ret); /* raw_rb hack */ + if (dba || db) { + if (rec_data->category == XRecordFromClient) { + req = (xCopyAreaReq *) rec_data->data; + if (req->reqType == X_CopyArea) { + src = req->srcDrawable; + dst = req->dstDrawable; + } + } + } - /* X keyboard cleanups */ - delete_added_keycodes(); +if (dba || db) fprintf(stderr, "record_CA-%d id_base: 0x%lx ptr: 0x%lx " + "seq: 0x%lx rc: 0x%lx cat: %d swapped: %d 0x%lx/0x%lx\n", k++, + rec_data->id_base, (unsigned long) ptr, xrecord_seq, rc_scroll, + rec_data->category, rec_data->client_swapped, src, dst); - if (clear_mods == 1) { - clear_modifiers(0); - } else if (clear_mods == 2) { - clear_keys(); + if (! xrecording) { + return; } +if (db) fprintf(stderr, "record_CA-%d\n", k++); - if (no_autorepeat) { - autorepeat(1); + if (rec_data->id_base == 0) { + return; } - if (use_solid_bg) { - solid_bg(1); +if (db) fprintf(stderr, "record_CA-%d\n", k++); + + if ((XID) ptr != xrecord_seq) { + return; } - X_LOCK; - XTestDiscard_wr(dpy); -#if LIBVNCSERVER_HAVE_LIBXDAMAGE - if (xdamage) { - XDamageDestroy(dpy, xdamage); +if (db) fprintf(stderr, "record_CA-%d\n", k++); + + if (rec_data->category != XRecordFromClient) { + return; } -#endif -#if LIBVNCSERVER_HAVE_LIBXTRAP - if (trap_ctx) { - XEFreeTC(trap_ctx); +if (db) fprintf(stderr, "record_CA-%d\n", k++); + + req = (xCopyAreaReq *) rec_data->data; + + if (req->reqType != X_CopyArea) { + return; } -#endif - XCloseDisplay(dpy); - X_UNLOCK; +if (db) fprintf(stderr, "record_CA-%d\n", k++); - fflush(stderr); - exit(ret); -} +/* -/* X11 error handlers */ +xterm, gnome-terminal, others. + +Note we miss the X_ImageText8 that clears the block cursor. So there is a +short period of time with a painting error: two cursors, one above the other. + + X_ImageText8 + draw: 0x8c00017 nChars: 1, gc: 0x8c00013, x: 101, y: 585, chars=' ' + X_ClearArea + window: 0x8c00018, x: 2, y: 217, w: 10, h: 5 + X_FillPoly + draw: 0x8c00018 gc: 0x8c0000a, shape: 0, coordMode: 0, + X_FillPoly + draw: 0x8c00018 gc: 0x8c0000b, shape: 0, coordMode: 0, + X_CopyArea + src: 0x8c00017, dst: 0x8c00017, gc: 0x8c00013, srcX: 17, srcY: 15, dstX: 17, dstY: 2, w: 480, h: 572 + X_ChangeWindowAttributes + X_ClearArea + window: 0x8c00017, x: 17, y: 574, w: 480, h: 13 + X_ChangeWindowAttributes -static XErrorHandler Xerror_def; -static XIOErrorHandler XIOerr_def; -XErrorEvent *trapped_xerror_event; -int trapped_xerror = 0; -int trapped_xioerror = 0; -int trapped_getimage_xerror = 0; + */ -int trap_xerror(Display *d, XErrorEvent *error) { - trapped_xerror = 1; - trapped_xerror_event = error; - return 0; -} + src = req->srcDrawable; + dst = req->dstDrawable; + src_x = req->srcX; + src_y = req->srcY; + dst_x = req->dstX; + dst_y = req->dstY; + w = req->width; + h = req->height; -int trap_xioerror(Display *d) { - trapped_xioerror = 1; - return 0; -} + if (w*h < scrollcopyrect_min_area) { + good = 0; + } else if (!src || !dst) { + good = 0; + } else if (src != dst) { + good = 0; + } -int trap_getimage_xerror(Display *d, XErrorEvent *error) { - trapped_getimage_xerror = 1; - trapped_xerror_event = error; - return 0; -} + dx = dst_x - src_x; + dy = dst_y - src_y; -void interrupted(int); + if (dx != 0 && dy != 0) { + good = 0; + } -static int Xerror(Display *d, XErrorEvent *error) { - X_UNLOCK; - interrupted(0); - return (*Xerror_def)(d, error); -} + if (! good) { + return; + } +if (db) fprintf(stderr, "record_CA-%d\n", k++); -static int XIOerr(Display *d) { - X_UNLOCK; - interrupted(-1); - return (*XIOerr_def)(d); -} + if (! valid_window(src, &attr)) { + return; + } +if (db) fprintf(stderr, "record_CA-%d\n", k++); -char *xerrors[] = { - "Success", - "BadRequest", - "BadValue", - "BadWindow", - "BadPixmap", - "BadAtom", - "BadCursor", - "BadFont", - "BadMatch", - "BadDrawable", - "BadAccess", - "BadAlloc", - "BadColor", - "BadGC", - "BadIDChoice", - "BadName", - "BadLength", - "BadImplementation", - "unknown" -}; -int xerrors_max = BadImplementation; + if (attr.map_state != IsViewable) { + return; + } -char *xerror_string(XErrorEvent *error) { - int index = -1; - if (error) { - index = (int) error->error_code; + XTranslateCoordinates(dpy, src, rootwin, 0, 0, &rx, &ry, &c); + +if (dba || db) fprintf(stderr, "record_CA-%d *FOUND: src: 0x%lx dx: %d dy: %d " + "x: %d y: %d w: %d h: %d st: %.4f\n", k++, src, dx, dy, src_x, + src_y, w, h, (double) rec_data->server_time/1000.0); + + if (scr_ev_cnt >= SCR_EV_MAX) { + return; } - if (0 <= index && index <= xerrors_max) { - return xerrors[index]; - } else { - return xerrors[xerrors_max+1]; + + i = scr_ev_cnt; + + scr_ev[i].win = src; + scr_ev[i].frame = None; + scr_ev[i].dx = dx; + scr_ev[i].dy = dy; + scr_ev[i].x = rx + dst_x; + scr_ev[i].y = ry + dst_y; + scr_ev[i].w = w; + scr_ev[i].h = h; + scr_ev[i].t = (int) rec_data->server_time; + scr_ev[i].win_x = rx; + scr_ev[i].win_y = ry; + scr_ev[i].win_w = attr.width; + scr_ev[i].win_h = attr.height; + scr_ev[i].new_x = 0; + scr_ev[i].new_y = 0; + scr_ev[i].new_w = 0; + scr_ev[i].new_h = 0; + + if (dx == 0) { + if (dy > 0) { + scr_ev[i].new_x = rx + src_x; + scr_ev[i].new_y = ry + src_y; + scr_ev[i].new_w = w; + scr_ev[i].new_h = dy; + } else { + scr_ev[i].new_x = rx + src_x; + scr_ev[i].new_y = ry + dst_y + h; + scr_ev[i].new_w = w; + scr_ev[i].new_h = -dy; + } + } else if (dy == 0) { + if (dx > 0) { + scr_ev[i].new_x = rx + src_x; + scr_ev[i].new_y = rx + src_y; + scr_ev[i].new_w = dx; + scr_ev[i].new_h = h; + } else { + scr_ev[i].new_x = rx + dst_x + w; + scr_ev[i].new_y = ry + src_y; + scr_ev[i].new_w = -dx; + scr_ev[i].new_h = h; + } } + + scr_ev_cnt++; } -/* - * General problem handler - */ -void interrupted (int sig) { - exit_sig = sig; - if (exit_flag) { - exit_flag++; - if (use_threads) { - usleep2(250 * 1000); - } else if (exit_flag <= 2) { - return; +typedef struct cw_event { + Window win; + int x, y, w, h; +} cw_event_t; + +#define MAX_CW 128 +cw_event_t cw_events[MAX_CW]; + +void record_CW(XPointer ptr, XRecordInterceptData *rec_data) { + xConfigureWindowReq *req; + Window win = None, c; + Window src = None, dst = None; + XWindowAttributes attr; + int absent = 0x100000; + int src_x, src_y, dst_x, dst_y, rx, ry; + int good = 1, dx, dy, k=0, i, j, match, list[3]; + int f_x, f_y, f_w, f_h; + int x, y, w, h; + int x0, y0, w0, h0, x1, y1, w1, h1, x2, y2, w2, h2; + static int index = 0; + unsigned long vals[4]; + unsigned tmask; + char *data; + int dba = 0, db = debug_scroll; + +//dba = 1; +//db = 1; + + if (1 || db) { + if (rec_data->category == XRecordFromClient) { + req = (xConfigureWindowReq *) rec_data->data; + if (req->reqType == X_ConfigureWindow) { + src = req->window; + } } - exit(4); } - exit_flag++; - if (sig == 0) { - fprintf(stderr, "caught X11 error:\n"); - } else if (sig == -1) { - fprintf(stderr, "caught XIO error:\n"); - } else { - fprintf(stderr, "caught signal: %d\n", sig); + +if (dba || db) fprintf(stderr, "record_CW-%d id_base: 0x%lx ptr: 0x%lx " + "seq: 0x%lx rc: 0x%lx cat: %d swapped: %d 0x%lx/0x%lx\n", k++, + rec_data->id_base, (unsigned long) ptr, xrecord_seq, rc_scroll, + rec_data->category, rec_data->client_swapped, src, dst); + + + if (! xrecording) { + return; } - if (sig == SIGINT) { - shut_down = 1; +if (db) fprintf(stderr, "record_CW-%d\n", k++); + + if ((XID) ptr != xrecord_seq) { return; } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - X_UNLOCK; + if (rec_data->id_base == 0) { + return; + } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - /* remove the shm areas with quick=1: */ - clean_shm(1); + if (rec_data->category == XRecordStartOfData) { + index = 0; + return; + } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - if (sig == -1) { - /* not worth trying any more cleanup, X server probably gone */ - exit(3); + if (rec_data->category != XRecordFromClient) { + return; } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - /* X keyboard cleanups */ - delete_added_keycodes(); + if (rec_data->client_swapped) { + return; + } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - if (clear_mods == 1) { - clear_modifiers(0); - } else if (clear_mods == 2) { - clear_keys(); + req = (xConfigureWindowReq *) rec_data->data; + + if (req->reqType != X_ConfigureWindow) { + return; } - if (no_autorepeat) { - autorepeat(1); +if (db) fprintf(stderr, "record_CW-%d\n", k++); + + tmask = req->mask; + + tmask &= ~CWX; + tmask &= ~CWY; + tmask &= ~CWWidth; + tmask &= ~CWHeight; + + if (tmask) { + /* require no more than these 4 flags */ + return; } - if (use_solid_bg) { - solid_bg(1); +if (db) fprintf(stderr, "record_CW-%d\n", k++); + + f_x = req->mask & CWX; + f_y = req->mask & CWY; + f_w = req->mask & CWWidth; + f_h = req->mask & CWHeight; + + if (! f_x || ! f_y) { + if (f_w && f_h) { + ; /* netscape 4.x style */ + } else { + return; + } } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - if (sig) { - exit(2); + if ( (f_w && !f_h) || (!f_w && f_h) ) { + return; + } +if (db) fprintf(stderr, "record_CW-%d\n", k++); + + for (i=0; i<4; i++) { + vals[i] = 0; } -} -/* trapping utility to check for a valid window: */ -int valid_window(Window win, XWindowAttributes *attr_ret) { - XErrorHandler old_handler; - XWindowAttributes attr, *pattr; - int ok = 0; + data = (char *)req; + data += sz_xConfigureWindowReq; - if (attr_ret == NULL) { - pattr = &attr; - } else { - pattr = attr_ret; + for (i=0; ilength; i++) { + unsigned long v; + v = *( (unsigned long *) data); + vals[i] = v; + data += 4; } - trapped_xerror = 0; - old_handler = XSetErrorHandler(trap_xerror); - if (XGetWindowAttributes(dpy, win, pattr)) { - ok = 1; + + if (index >= MAX_CW) { + int i, j; + + /* FIXME, circular, etc. */ + for (i=0; i<2; i++) { + j = MAX_CW - 2 + i; + cw_events[i].win = cw_events[j].win; + cw_events[i].x = cw_events[j].x; + cw_events[i].y = cw_events[j].y; + cw_events[i].w = cw_events[j].w; + cw_events[i].h = cw_events[j].h; + index = 2; + } } - if (trapped_xerror && trapped_xerror_event && ! quiet) { - rfbLog("trapped XError: %s (0x%lx)\n", - xerror_string(trapped_xerror_event), win); + + if (! f_x && ! f_y) { + /* netscape 4.x style CWWidth,CWHeight */ + vals[2] = vals[0]; + vals[3] = vals[1]; + vals[0] = 0; + vals[1] = 0; } - XSetErrorHandler(old_handler); - trapped_xerror = 0; - - return ok; -} -int wait_until_mapped(Window win) { - int ms = 50, waittime = 30; - time_t start = time(0); - XWindowAttributes attr; + cw_events[index].win = req->window; - while (1) { - if (! valid_window(win, NULL)) { - if (time(0) > start + waittime) { - return 0; - } - usleep(ms * 1000); - continue; - } - if (! XGetWindowAttributes(dpy, win, &attr)) { - return 0; - } - if (attr.map_state == IsViewable) { - return 1; - } - usleep(ms * 1000); + if (! f_x) { + cw_events[index].x = absent; + } else { + cw_events[index].x = (int) vals[0]; + } + if (! f_y) { + cw_events[index].y = absent; + } else { + cw_events[index].y = (int) vals[1]; } - return 0; -} -int get_window_size(Window win, int *x, int *y) { - XWindowAttributes attr; - /* valid_window? */ - if (XGetWindowAttributes(dpy, win, &attr)) { - *x = attr.width; - *y = attr.height; - return 1; + if (! f_w) { + cw_events[index].w = absent; } else { - return 0; + cw_events[index].w = (int) vals[2]; + } + if (! f_h) { + cw_events[index].h = absent; + } else { + cw_events[index].h = (int) vals[3]; } -} -/* signal handlers */ -void initialize_signals(void) { - signal(SIGHUP, interrupted); - signal(SIGINT, interrupted); - signal(SIGQUIT, interrupted); - signal(SIGABRT, interrupted); - signal(SIGTERM, interrupted); - signal(SIGBUS, interrupted); - signal(SIGSEGV, interrupted); - signal(SIGFPE, interrupted); + x = cw_events[index].x; + y = cw_events[index].y; + w = cw_events[index].w; + h = cw_events[index].h; + win = cw_events[index].win; - if (!sigpipe || *sigpipe == '\0' || !strcmp(sigpipe, "skip")) { - ; - } else if (!strcmp(sigpipe, "ignore")) { -#ifdef SIG_IGN - signal(SIGPIPE, SIG_IGN); -#endif - } else if (!strcmp(sigpipe, "exit")) { - rfbLog("initialize_signals: will exit on SIGPIPE\n"); - signal(SIGPIPE, interrupted); +if (dba || db) fprintf(stderr, " record_CW ind: %d win: 0x%lx x: %d y: %d w: %d h: %d\n", + index, win, x, y, w, h); + + index++; + + if (index < 3) { + good = 0; + } else if (w != absent && h != absent && + w*h < scrollcopyrect_min_area) { + good = 0; } - X_LOCK; - Xerror_def = XSetErrorHandler(Xerror); - XIOerr_def = XSetIOErrorHandler(XIOerr); - X_UNLOCK; -} + if (! good) { + return; + } +if (db) fprintf(stderr, "record_CW-%d\n", k++); -/* -- connections.c -- */ -/* - * routines for handling incoming, outgoing, etc connections - */ + match = 0; + for (j=index - 1; j >= 0; j--) { + if (cw_events[j].win == win) { + list[match++] = j; + } + if (match >= 3) { + break; + } + } + + if (match != 3) { + return; + } +if (db) fprintf(stderr, "record_CW-%d\n", k++); /* - * check that all clients are in RFB_NORMAL state + +Mozilla: + +Up arrow: window moves down a bit (dy > 0): + + X_ConfigureWindow + length: 7, window: 0x2e000cd, mask: 0xf, v0 0, v1 -18, v2 760, v3 906, v4 327692, v5 48234701, v6 3, + CW-mask: CWX,CWY,CWWidth,CWHeight, + X_ConfigureWindow + length: 5, window: 0x2e000cd, mask: 0x3, v0 0, v1 0, v2 506636, v3 48234701, v4 48234511, + CW-mask: CWX,CWY, + X_ConfigureWindow + length: 7, window: 0x2e000cd, mask: 0xf, v0 0, v1 0, v2 760, v3 888, v4 65579, v5 0, v6 108009, + CW-mask: CWX,CWY,CWWidth,CWHeight, + +Down arrow: window moves up a bit (dy < 0): + + X_ConfigureWindow + length: 7, window: 0x2e000cd, mask: 0xf, v0 0, v1 0, v2 760, v3 906, v4 327692, v5 48234701, v6 262147, + CW-mask: CWX,CWY,CWWidth,CWHeight, + X_ConfigureWindow + length: 5, window: 0x2e000cd, mask: 0x3, v0 0, v1 -18, v2 506636, v3 48234701, v4 48234511, + CW-mask: CWX,CWY, + X_ConfigureWindow + length: 7, window: 0x2e000cd, mask: 0xf, v0 0, v1 0, v2 760, v3 888, v4 96555, v5 48265642, v6 48265262, + CW-mask: CWX,CWY,CWWidth,CWHeight, + + +Netscape 4.x + +Up arrow: +71.76142 0.01984 X_ConfigureWindow + length: 7, window: 0x9800488, mask: 0xf, v0 0, v1 -15, v2 785, v3 882, v4 327692, v5 159384712, v6 1769484, + CW-mask: CWX,CWY,CWWidth,CWHeight, +71.76153 0.00011 X_ConfigureWindow + length: 5, window: 0x9800488, mask: 0xc, v0 785, v1 867, v2 329228, v3 159384712, v4 159383555, + CW-mask: CWWidth,CWHeight, + XXX,XXX +71.76157 0.00003 X_ConfigureWindow + length: 5, window: 0x9800488, mask: 0x3, v0 0, v1 0, v2 131132, v3 159385313, v4 328759, + CW-mask: CWX,CWY, + XXX,XXX + +Down arrow: +72.93147 0.01990 X_ConfigureWindow + length: 5, window: 0x9800488, mask: 0xc, v0 785, v1 882, v2 328972, v3 159384712, v4 159383555, + CW-mask: CWWidth,CWHeight, + XXX,XXX +72.93156 0.00009 X_ConfigureWindow + length: 5, window: 0x9800488, mask: 0x3, v0 0, v1 -15, v2 458764, v3 159384712, v4 159383567, + CW-mask: CWX,CWY, +72.93160 0.00004 X_ConfigureWindow + length: 7, window: 0x9800488, mask: 0xf, v0 0, v1 0, v2 785, v3 867, v4 131132, v5 159385335, v6 328759, + CW-mask: CWX,CWY,CWWidth,CWHeight, + + +sadly, probably need to handle some more... + */ -int all_clients_initialized(void) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int ok = 1; + x0 = cw_events[list[2]].x; + y0 = cw_events[list[2]].y; + w0 = cw_events[list[2]].w; + h0 = cw_events[list[2]].h; - if (! screen) { - return ok; - } + x1 = cw_events[list[1]].x; + y1 = cw_events[list[1]].y; + w1 = cw_events[list[1]].w; + h1 = cw_events[list[1]].h; - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->state != RFB_NORMAL) { - ok = 0; - break; + x2 = cw_events[list[0]].x; + y2 = cw_events[list[0]].y; + w2 = cw_events[list[0]].w; + h2 = cw_events[list[0]].h; + + /* see NS4 XXX's above: */ + if (w2 == absent || h2 == absent) { + /* up arrow */ + if (w2 == absent) { + w2 = w1; + } + if (h2 == absent) { + h2 = h1; + } + } + if (x1 == absent || y1 == absent) { + /* up arrow */ + if (x1 == absent) { + x1 = x2; + } + if (y1 == absent) { + y1 = y2; + } + } + if (x0 == absent || y0 == absent) { + /* down arrow */ + if (x0 == absent) { + /* hmmm... what to do */ + x0 = x2; + } + if (y0 == absent) { + y0 = y2; } } - rfbReleaseClientIterator(iter); - return ok; -} +if (dba) fprintf(stderr, "%d/%d/%d/%d %d/%d/%d/%d %d/%d/%d/%d\n", x0, y0, w0, h0, x1, y1, w1, h1, x2, y2, w2, h2); -char *list_clients(void) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - char *list, tmp[32]; - int count = 0; + dy = y1 - y0; + dx = x1 - x0; - if (!screen) { - return strdup(""); + src_x = x2; + src_y = y2; + w = w2; + h = h2; + + /* check w and h before we modify them */ + if (w <= 0 || h <= 0) { + good = 0; + } else if (w == absent || h == absent) { + good = 0; + } + if (! good) { + return; } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - count++; + if (dy > 0) { + h -= dy; + } else { + h += dy; + src_y -= dy; + } + if (dx > 0) { + w -= dx; + } else { + w += dx; + src_x -= dx; } - rfbReleaseClientIterator(iter); - /* - * each client: - * ::::::, - * 8+1+16+1+5+1+24+1+256+1+5+1+1+1 - * 123.123.123.123:60000/0x11111111-rw, - * so count+1 * 400 must cover it. - */ - list = (char *) malloc((count+1)*400); - - list[0] = '\0'; + dst_x = src_x + dx; + dst_y = src_y + dy; - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - ClientData *cd = (ClientData *) cl->clientData; - if (*list != '\0') { - strcat(list, ","); - } - sprintf(tmp, "0x%x:", cd->uid); - strcat(list, tmp); - strcat(list, cl->host); - strcat(list, ":"); - sprintf(tmp, "%d:", cd->client_port); - strcat(list, tmp); - if (*(cd->username) == '\0') { - char *s = ident_username(cl); - if (s) free(s); - } - strcat(list, cd->username); - strcat(list, ":"); - strcat(list, cd->hostname); - strcat(list, ":"); - strcat(list, cd->input); - strcat(list, ":"); - sprintf(tmp, "%d", cd->login_viewonly); - strcat(list, tmp); + if (x0 == absent || x1 == absent || x2 == absent) { + good = 0; + } else if (y0 == absent || y1 == absent || y2 == absent) { + good = 0; + } else if (dx != 0 && dy != 0) { + good = 0; + } else if (w0 - w2 != nabs(dx)) { + good = 0; + } else if (h0 - h2 != nabs(dy)) { + good = 0; } - rfbReleaseClientIterator(iter); - return list; -} -/* count number of clients supporting NewFBSize */ -int new_fb_size_clients(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int count = 0; - - if (! s) { - return 0; + if (! good) { + return; } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->useNewFBSize) { - count++; - } + /* geometry OK, now check with the X server: */ + if (! valid_window(win, &attr)) { + return; } - rfbReleaseClientIterator(iter); - return count; -} - -void close_all_clients(void) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; +if (db) fprintf(stderr, "record_CW-%d\n", k++); - if (! screen) { + if (attr.map_state != IsViewable) { return; } +if (db) fprintf(stderr, "record_CW-%d\n", k++); - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - rfbCloseClient(cl); - rfbClientConnectionGone(cl); - } - rfbReleaseClientIterator(iter); -} + XTranslateCoordinates(dpy, win, rootwin, 0, 0, &rx, &ry, &c); -rfbClientPtr *client_match(char *str) { - rfbClientIteratorPtr iter; - rfbClientPtr cl, *cl_list; - int i, n, host_warn = 0, hex_warn = 0; +if (dba || db) fprintf(stderr, "record_CW-%d *FOUND: win: 0x%lx dx: %d dy: %d " + "x: %d y: %d w: %d h: %d st: %.4f\n", k++, win, dx, dy, src_x, src_y, + w, h, (double) rec_data->server_time/1000.0); - n = client_count + 10; - cl_list = (rfbClientPtr *) malloc(n * sizeof(rfbClientPtr)); - - i = 0; - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (strstr(str, "0x") == str) { - int id; - ClientData *cd = (ClientData *) cl->clientData; - if (sscanf(str, "0x%x", &id) != 1) { - if (hex_warn++) { - continue; - } - rfbLog("skipping bad client hex id: %s\n", str); - continue; - } - if ( cd->uid == id) { - cl_list[i++] = cl; - } + if (scr_ev_cnt >= SCR_EV_MAX) { + return; + } + + i = scr_ev_cnt; + + scr_ev[i].win = win; + scr_ev[i].frame = None; + scr_ev[i].dx = dx; + scr_ev[i].dy = dy; + scr_ev[i].x = rx + dst_x; + scr_ev[i].y = ry + dst_y; + scr_ev[i].w = w; + scr_ev[i].h = h; + scr_ev[i].t = (int) rec_data->server_time; + scr_ev[i].win_x = rx; + scr_ev[i].win_y = ry; + scr_ev[i].win_w = attr.width; + scr_ev[i].win_h = attr.height; + scr_ev[i].new_x = 0; + scr_ev[i].new_y = 0; + scr_ev[i].new_w = 0; + scr_ev[i].new_h = 0; + + if (dx == 0) { + if (dy > 0) { + scr_ev[i].new_x = rx + src_x; + scr_ev[i].new_y = ry + src_y; + scr_ev[i].new_w = w; + scr_ev[i].new_h = dy; } else { - char *rstr = str; - if (! dotted_ip(str)) { - rstr = host2ip(str); - if (rstr == NULL || *rstr == '\0') { - if (host_warn++) { - continue; - } - rfbLog("skipping bad lookup: \"%s\"\n", - str); - continue; - } - rfbLog("lookup: %s -> %s\n", str, rstr); - } - if (!strcmp(rstr, cl->host)) { - cl_list[i++] = cl; - } - if (rstr != str) { - free(rstr); - } - } - if (i >= n - 1) { - break; + scr_ev[i].new_x = rx + src_x; + scr_ev[i].new_y = ry + dst_y + h; + scr_ev[i].new_w = w; + scr_ev[i].new_h = -dy; + } + } else if (dy == 0) { + if (dx > 0) { + scr_ev[i].new_x = rx + src_x; + scr_ev[i].new_y = rx + src_y; + scr_ev[i].new_w = dx; + scr_ev[i].new_h = h; + } else { + scr_ev[i].new_x = rx + dst_x + w; + scr_ev[i].new_y = ry + src_y; + scr_ev[i].new_w = -dx; + scr_ev[i].new_h = h; } } - rfbReleaseClientIterator(iter); - cl_list[i] = NULL; + /* indicate we have a new one */ + scr_ev_cnt++; - return cl_list; + index = 0; } -void close_clients(char *str) { - rfbClientPtr *cl_list, *cp; +void record_switch(XPointer ptr, XRecordInterceptData *rec_data) { + xReq *req; - if (!strcmp(str, "all") || !strcmp(str, "*")) { - close_all_clients(); - return; +//fprintf(stderr, "XRecordFreeData-0: %p\n", rec_data); + /* should handle control msgs, start/stop/etc */ + if (rec_data->category == XRecordStartOfData) { + record_CW(ptr, rec_data); + } else if (rec_data->category == XRecordEndOfData) { + ; + } else if (rec_data->category == XRecordClientStarted) { + ; + } else if (rec_data->category == XRecordClientDied) { + ; + } else if (rec_data->category == XRecordFromServer) { + ; } - if (! screen) { + if (rec_data->category != XRecordFromClient) { +//fprintf(stderr, "XRecordFreeData-1: %p\n", rec_data); + XRecordFreeData(rec_data); return; } - - cl_list = client_match(str); - cp = cl_list; - while (*cp) { - rfbCloseClient(*cp); - rfbClientConnectionGone(*cp); - cp++; + req = (xReq *) rec_data->data; + + if (req->reqType == X_CopyArea) { + record_CA(ptr, rec_data); + } else if (req->reqType == X_ConfigureWindow) { + record_CW(ptr, rec_data); + } else { + ; } - free(cl_list); +//fprintf(stderr, "XRecordFreeData-2: %p\n", rec_data); + XRecordFreeData(rec_data); } +#endif -void set_client_input(char *str) { - rfbClientPtr *cl_list, *cp; - char *p, *val; +void xrecord_watch(int start) { + Window focus, wm, r, c, clast; + int rx, ry, wx, wy; + int depth = 0, i; + unsigned int m; + int db = debug_scroll; + int do_shutdown = 0; + +//db = 1; + +#if LIBVNCSERVER_HAVE_RECORD + if (! start) { + xrecording = 0; + if (! rc_scroll) { + xrecord_focus_window = None; + xrecord_wm_window = None; + rcs_scroll = 0; + return; + } + if (do_shutdown) { +if (db) fprintf(stderr, "=== shutdown-scroll 0x%lx\n", rc_scroll); + X_LOCK; + if (! XRecordDisableContext(rdpy_ctrl, rc_scroll)) { + rfbLog("XRecordDisableContext(rc_scroll)" + " failed.\n"); + } + if (! XRecordFreeContext(rdpy_ctrl, rc_scroll)) { + rfbLog("XRecordFreeContext(rc_scroll)" + " failed.\n"); + } + XRecordProcessReplies(rdpy_data); + X_UNLOCK; + rc_scroll = 0; + } else { + if (rcs_scroll) { +if (db) fprintf(stderr, "=== disab-scroll 0x%lx 0x%lx\n", rc_scroll, rcs_scroll); + X_LOCK; + rcs_scroll = XRecordCurrentClients; + XRecordUnregisterClients(rdpy_ctrl, rc_scroll, + &rcs_scroll, 1); + XRecordDisableContext(rdpy_ctrl, rc_scroll); + XFlush(rdpy_ctrl); + XRecordProcessReplies(rdpy_data); + X_UNLOCK; + } + } + /* + * XXX if we do a XFlush(rdpy_ctrl) here we get: + * - /* str is "match:value" */ + X Error of failed request: XRecordBadContext + Major opcode of failed request: 145 (RECORD) + Minor opcode of failed request: 5 (XRecordEnableContext) + Context in failed request: 0x2200013 + Serial number of failed request: 29 + Current serial number in output stream: 29 - if (! screen) { + * + * need to figure out what is going on... since it may lead + * infrequent failures. + */ + xrecord_focus_window = None; + xrecord_wm_window = None; + rcs_scroll = 0; return; } - p = strchr(str, ':'); - if (! p) { + if (xrecording) { return; } - *p = '\0'; - p++; - val = short_kmb(p); - - cl_list = client_match(str); - cp = cl_list; - while (*cp) { - ClientData *cd = (ClientData *) (*cp)->clientData; - cd->input[0] = '\0'; - strcat(cd->input, "_"); - strcat(cd->input, val); - cp++; + if (do_shutdown && rc_scroll) { + static int didmsg = 0; + /* should not happen... */ + if (!didmsg) { + rfbLog("warning: do_shutdown && rc_scroll\n"); + didmsg = 1; + } + xrecord_watch(0); } - free(val); - free(cl_list); -} + xrecording = 0; + xrecord_focus_window = None; + xrecord_wm_window = None; + xrecord_set_by_keys = 0; + xrecord_set_by_mouse = 0; -void set_child_info(void) { - char pid[16]; - /* set up useful environment for child process */ - sprintf(pid, "%d", (int) getpid()); - set_env("X11VNC_PID", pid); - if (program_name) { - /* e.g. for remote control -R */ - set_env("X11VNC_PROG", program_name); - } - if (program_cmdline) { - set_env("X11VNC_CMDLINE", program_cmdline); - } - if (raw_fb_str) { - set_env("X11VNC_RAWFB_STR", raw_fb_str); - } else { - set_env("X11VNC_RAWFB_STR", ""); - } -} + X_LOCK; -/* - * utility to run a user supplied command setting some RFB_ env vars. - * used by, e.g., accept_client() and client_gone() - */ -static int run_user_command(char *cmd, rfbClientPtr client, char *mode) { - char *old_display = NULL; - char *addr = client->host; - char str[100]; - int rc; - ClientData *cd = (ClientData *) client->clientData; + /* get the window with focus and mouse pointer: */ + clast = None; + focus = None; + wm = None; - if (addr == NULL || addr[0] == '\0') { - addr = "unknown-host"; - } + XGetInputFocus(dpy, &focus, &i); - /* set RFB_CLIENT_ID to semi unique id for command to use */ - if (cd && cd->uid) { - sprintf(str, "0x%x", cd->uid); + XQueryPointer(dpy, rootwin, &r, &wm, &rx, &ry, &wx, &wy, &m); + if (wm) { + c = wm; } else { - /* not accepted yet: */ - sprintf(str, "0x%x", clients_served); + c = rootwin; + } + for (i=0; i<3; i++) { + /* descend a bit to avoid wm frames: */ + clast = c; + if (! XQueryPointer(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &m)) { + break; + } + if (! c) { + break; + } + depth++; + } + if (!clast || clast == rootwin) { +if (db) fprintf(stderr, "--- xrecord_watch: skip.\n"); + X_UNLOCK; + return; } - set_env("RFB_CLIENT_ID", str); - /* set RFB_CLIENT_IP to IP addr for command to use */ - set_env("RFB_CLIENT_IP", addr); + /* set protocol request ranges: */ + rr_scroll[0] = rr_CA; + rr_scroll[1] = rr_CW; - /* set RFB_X11VNC_PID to our pid for command to use */ - sprintf(str, "%d", (int) getpid()); - set_env("RFB_X11VNC_PID", str); + if (! rc_scroll) { + /* do_shutdown case or first time in */ + rcs_scroll = (XRecordClientSpec) clast; + rc_scroll = XRecordCreateContext(rdpy_ctrl, 0, &rcs_scroll, 1, + rr_scroll, 2); - /* set RFB_CLIENT_PORT to peer port for command to use */ - if (cd && cd->client_port > 0) { - sprintf(str, "%d", cd->client_port); - } else { - sprintf(str, "%d", get_remote_port(client->sock)); + } else if (! do_shutdown) { + if (rcs_scroll) { +if (db) fprintf(stderr, "=2= unreg-scroll 0x%lx 0x%lx\n", rc_scroll, rcs_scroll); + rcs_scroll = XRecordCurrentClients; + XRecordUnregisterClients(rdpy_ctrl, rc_scroll, + &rcs_scroll, 1); + } + + rcs_scroll = (XRecordClientSpec) clast; +if (db) fprintf(stderr, "=-= reg-scroll 0x%lx 0x%lx\n", rc_scroll, rcs_scroll); + + if (!XRecordRegisterClients(rdpy_ctrl, rc_scroll, 0, + &rcs_scroll, 1, rr_scroll, 2)) { + static time_t last_err = 0; + time_t now = time(0); + if (now > last_err + 60) { + rfbLog("failed to register client 0x%lx with" + " X RECORD context rc_scroll.\n", clast); + last_err = now; + } + /* continue on for now... */ + } } - set_env("RFB_CLIENT_PORT", str); + XFlush(rdpy_ctrl); - set_env("RFB_MODE", mode); +if (db) fprintf(stderr, "rc_scroll: 0x%lx\n", rc_scroll); - /* - * now do RFB_SERVER_IP and RFB_SERVER_PORT (i.e. us!) - * This will establish a 5-tuple (including tcp) the external - * program can potentially use to work out the virtual circuit - * for this connection. - */ - if (cd && cd->server_ip) { - set_env("RFB_SERVER_IP", cd->server_ip); - } else { - char *sip = get_local_host(client->sock); - set_env("RFB_SERVER_IP", sip); - free(sip); + if (! rc_scroll) { + X_UNLOCK; + use_xrecord = 0; + rfbLog("failed to create X RECORD context rc_scroll.\n"); + rfbLog(" switching to -noscrollcopyrect mode.\n"); + return; } - if (cd && cd->server_port > 0) { - sprintf(str, "%d", cd->server_port); - } else { - sprintf(str, "%d", get_local_port(client->sock)); + xrecord_focus_window = focus; + if (! xrecord_focus_window) { + xrecord_focus_window = clast; } - set_env("RFB_SERVER_PORT", str); - - /* - * Better set DISPLAY to the one we are polling, if they - * want something trickier, they can handle on their own - * via environment, etc. - */ - if (getenv("DISPLAY")) { - old_display = strdup(getenv("DISPLAY")); + xrecord_wm_window = wm; + if (! xrecord_wm_window) { + xrecord_wm_window = clast; } - if (raw_fb && ! dpy) { /* raw_fb hack */ - set_env("DISPLAY", "rawfb"); - } else { - set_env("DISPLAY", DisplayString(dpy)); + xrecording = 1; + xrecord_seq++; + xrecord_start = 0.0; + dtime(&xrecord_start); + + if (!XRecordEnableContextAsync(rdpy_data, rc_scroll, record_switch, + (XPointer) xrecord_seq)) { + static time_t last_err = 0; + time_t now = time(0); + if (now > last_err + 60) { + rfbLog("failed to enable RECORD context " + "rc_scroll.\n"); + last_err = now; + } + /* continue on for now... */ } + XFlush(rdpy_data); + X_UNLOCK; +#endif +} - /* - * work out the number of clients (have to use client_count - * since there is deadlock in rfbGetClientIterator) - */ - sprintf(str, "%d", client_count); - set_env("RFB_CLIENT_COUNT", str); +/* -- cleanup.c -- */ +/* + * Exiting and error handling routines + */ - if (no_external_cmds) { - rfbLog("cannot run external commands in -nocmds mode:\n"); - rfbLog(" \"%s\"\n", cmd); - rfbLog(" exiting.\n"); - clean_up_exit(1); - } - rfbLog("running command:\n"); - rfbLog(" %s\n", cmd); +static int exit_flag = 0; +int exit_sig = 0; - /* XXX need to close port 5900, etc.. */ - rc = system(cmd); +void clean_shm(int quick) { + int i, cnt = 0; - if (rc >= 256) { - rc = rc/256; - } - rfbLog("command returned: %d\n", rc); + if (raw_fb) quick = 1; /* raw_fb hack */ - if (old_display) { - set_env("DISPLAY", old_display); - free(old_display); + /* + * to avoid deadlock, etc, under quick=1 we just delete the shm + * areas and leave the X stuff hanging. + */ + if (quick) { + shm_delete(&scanline_shm); + shm_delete(&fullscreen_shm); + shm_delete(&snaprect_shm); + } else { + shm_clean(&scanline_shm, scanline); + shm_clean(&fullscreen_shm, fullscreen); + shm_clean(&snaprect_shm, snaprect); } - return rc; + /* + * Here we have to clean up quite a few shm areas for all + * the possible tile row runs (40 for 1280), not as robust + * as one might like... sometimes need to run ipcrm(1). + */ + for(i=1; i<=ntiles_x; i++) { + if (i > tile_shm_count) { + break; + } + if (quick) { + shm_delete(&tile_row_shm[i]); + } else { + shm_clean(&tile_row_shm[i], tile_row[i]); + } + cnt++; + if (single_copytile_count && i >= single_copytile_count) { + break; + } + } + if (!quiet) { + rfbLog("deleted %d tile_row polling images.\n", cnt); + } } /* - * callback for when a client disconnects + * Normal exiting */ -static void client_gone(rfbClientPtr client) { +void clean_up_exit (int ret) { + exit_flag = 1; - client_count--; - if (client_count < 0) client_count = 0; + /* remove the shm areas: */ + clean_shm(0); - rfbLog("client_count: %d\n", client_count); + if (! dpy) exit(ret); /* raw_rb hack */ - if (no_autorepeat && client_count == 0) { + /* X keyboard cleanups */ + delete_added_keycodes(); + + if (clear_mods == 1) { + clear_modifiers(0); + } else if (clear_mods == 2) { + clear_keys(); + } + + if (no_autorepeat) { autorepeat(1); } - if (use_solid_bg && client_count == 0) { + if (use_solid_bg) { solid_bg(1); } - if (gone_cmd && *gone_cmd != '\0') { - rfbLog("client_gone: using cmd for: %s\n", client->host); - run_user_command(gone_cmd, client, "gone"); + X_LOCK; + XTestDiscard_wr(dpy); +#if LIBVNCSERVER_HAVE_LIBXDAMAGE + if (xdamage) { + XDamageDestroy(dpy, xdamage); } - - if (client->clientData) { - ClientData *cd = (ClientData *) client->clientData; - if (cd) { - if (cd->server_ip) { - free(cd->server_ip); - } - if (cd->hostname) { - free(cd->hostname); - } - if (cd->username) { - free(cd->username); - } - } - free(client->clientData); +#endif +#if LIBVNCSERVER_HAVE_LIBXTRAP + if (trap_ctx) { + XEFreeTC(trap_ctx); } +#endif +#if LIBVNCSERVER_HAVE_RECORD + /* XXX currently blocks: */ +#if 0 + if (rdpy_ctrl && rc_scroll) XRecordDisableContext(rdpy_ctrl, rc_scroll); + if (rdpy_data) XCloseDisplay(rdpy_data); + if (rdpy_ctrl) XCloseDisplay(rdpy_ctrl); +#endif +#endif + XCloseDisplay(dpy); + X_UNLOCK; - if (inetd) { - rfbLog("viewer exited.\n"); - clean_up_exit(0); - } - if (connect_once) { - /* - * This non-exit is done for a bad passwd to be consistent - * with our RFB_CLIENT_REFUSE behavior in new_client() (i.e. - * we disconnect after 1 successful connection). - */ - if ((client->state == RFB_PROTOCOL_VERSION || - client->state == RFB_AUTHENTICATION) && accepted_client) { - rfbLog("connect_once: bad password or early " - "disconnect.\n"); - rfbLog("connect_once: waiting for next connection.\n"); - accepted_client = 0; - return; - } - if (shared && client_count > 0) { - rfbLog("connect_once: other shared clients still " - "connected, not exiting.\n"); - return; - } + fflush(stderr); + exit(ret); +} - rfbLog("viewer exited.\n"); - clean_up_exit(0); - } +/* X11 error handlers */ + +static XErrorHandler Xerror_def; +static XIOErrorHandler XIOerr_def; +XErrorEvent *trapped_xerror_event; +int trapped_xerror = 0; +int trapped_xioerror = 0; +int trapped_getimage_xerror = 0; + +int trap_xerror(Display *d, XErrorEvent *error) { + trapped_xerror = 1; + trapped_xerror_event = error; + return 0; } -/* - * Simple routine to limit access via string compare. A power user will - * want to compile libvncserver with libwrap support and use /etc/hosts.allow. - */ -static int check_access(char *addr) { - int allowed = 0; - char *p, *list; +int trap_xioerror(Display *d) { + trapped_xioerror = 1; + return 0; +} - if (deny_all) { - rfbLog("check_access: new connections are currently " - "blocked.\n"); - return 0; - } - if (addr == NULL || *addr == '\0') { - rfbLog("check_access: denying empty host IP address string.\n"); - return 0; - } +int trap_getimage_xerror(Display *d, XErrorEvent *error) { + trapped_getimage_xerror = 1; + trapped_xerror_event = error; + return 0; +} - if (allow_list == NULL) { - /* set to "" to possibly append allow_once */ - allow_list = strdup(""); - } - if (*allow_list == '\0' && allow_once == NULL) { - /* no constraints, accept it */ - return 1; - } +void interrupted(int); - if (strchr(allow_list, '/')) { - /* a file of IP addresess or prefixes */ - int len, len2 = 0; - struct stat sbuf; - FILE *in; - char line[1024], *q; +static int Xerror(Display *d, XErrorEvent *error) { + X_UNLOCK; + interrupted(0); + return (*Xerror_def)(d, error); +} - if (stat(allow_list, &sbuf) != 0) { - rfbLog("check_access: failure stating file: %s\n", - allow_list); - rfbLogPerror("stat"); - clean_up_exit(1); - } - len = sbuf.st_size + 1; /* 1 more for '\0' at end */ - if (allow_once) { - len2 = strlen(allow_once) + 2; - len += len2; - } - list = malloc(len); - list[0] = '\0'; - - in = fopen(allow_list, "r"); - if (in == NULL) { - rfbLog("check_access: cannot open: %s\n", allow_list); - rfbLogPerror("fopen"); - clean_up_exit(1); - } - while (fgets(line, 1024, in) != NULL) { - if ( (q = strchr(line, '#')) != NULL) { - *q = '\0'; - } - if (strlen(list) + strlen(line) >= len - len2) { - /* file grew since our stat() */ - break; - } - strcat(list, line); - } - fclose(in); - if (allow_once) { - strcat(list, "\n"); - strcat(list, allow_once); - strcat(list, "\n"); - } - } else { - int len = strlen(allow_list) + 1; - if (allow_once) { - len += strlen(allow_once) + 1; - } - list = malloc(len); - list[0] = '\0'; - strcat(list, allow_list); - if (allow_once) { - strcat(list, ","); - strcat(list, allow_once); - } - } +static int XIOerr(Display *d) { + X_UNLOCK; + interrupted(-1); + return (*XIOerr_def)(d); +} - if (allow_once) { - free(allow_once); - allow_once = NULL; - } - - p = strtok(list, ", \t\n\r"); - while (p) { - char *chk, *q, *r = NULL; - if (*p == '\0') { - p = strtok(NULL, ", \t\n\r"); - continue; - } - if (! dotted_ip(p)) { - r = host2ip(p); - if (r == NULL || *r == '\0') { - rfbLog("check_access: bad lookup \"%s\"\n", p); - p = strtok(NULL, ", \t\n\r"); - continue; - } - rfbLog("check_access: lookup %s -> %s\n", p, r); - chk = r; - } else { - chk = p; - } +char *xerrors[] = { + "Success", + "BadRequest", + "BadValue", + "BadWindow", + "BadPixmap", + "BadAtom", + "BadCursor", + "BadFont", + "BadMatch", + "BadDrawable", + "BadAccess", + "BadAlloc", + "BadColor", + "BadGC", + "BadIDChoice", + "BadName", + "BadLength", + "BadImplementation", + "unknown" +}; +int xerrors_max = BadImplementation; - q = strstr(addr, chk); - if (chk[strlen(chk)-1] != '.') { - if (!strcmp(addr, chk)) { - if (chk != p) { - rfbLog("check_access: client %s " - "matches host %s=%s\n", addr, - chk, p); - } else { - rfbLog("check_access: client %s " - "matches host %s\n", addr, chk); - } - allowed = 1; - } else if(!strcmp(chk, "localhost") && - !strcmp(addr, "127.0.0.1")) { - allowed = 1; - } - } else if (q == addr) { - rfbLog("check_access: client %s matches pattern %s\n", - addr, chk); - allowed = 1; - } - p = strtok(NULL, ", \t\n\r"); - if (r) { - free(r); - } - if (allowed) { - break; - } +char *xerror_string(XErrorEvent *error) { + int index = -1; + if (error) { + index = (int) error->error_code; + } + if (0 <= index && index <= xerrors_max) { + return xerrors[index]; + } else { + return xerrors[xerrors_max+1]; } - free(list); - return allowed; } /* - * x11vnc's first (and only) visible widget: accept/reject dialog window. - * We go through this pain to avoid dependency on libXt... + * General problem handler */ -static int ugly_accept_window(char *addr, char *userhost, int X, int Y, - int timeout, char *mode) { - -#define t2x2_width 16 -#define t2x2_height 16 -static char t2x2_bits[] = { - 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, - 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, - 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33}; - - Window awin; - GC gc; - XSizeHints hints; - XGCValues values; - static XFontStruct *font_info = NULL; - static Pixmap ico = 0; - unsigned long valuemask = 0; - static char dash_list[] = {20, 40}; - int list_length = sizeof(dash_list); +void interrupted (int sig) { + exit_sig = sig; + if (exit_flag) { + exit_flag++; + if (use_threads) { + usleep2(250 * 1000); + } else if (exit_flag <= 2) { + return; + } + exit(4); + } + exit_flag++; + if (sig == 0) { + fprintf(stderr, "caught X11 error:\n"); + } else if (sig == -1) { + fprintf(stderr, "caught XIO error:\n"); + } else { + fprintf(stderr, "caught signal: %d\n", sig); + } + if (sig == SIGINT) { + shut_down = 1; + return; + } - Atom wm_protocols; - Atom wm_delete_window; + X_UNLOCK; - XEvent ev; - long evmask = ExposureMask | KeyPressMask | ButtonPressMask - | StructureNotifyMask; - double waited = 0.0; + /* remove the shm areas with quick=1: */ + clean_shm(1); - /* strings and geometries y/n */ - KeyCode key_y, key_n, key_v; - char strh[100]; - char stri[100]; - char str1_b[] = "To accept: press \"y\" or click the \"Yes\" button"; - char str2_b[] = "To reject: press \"n\" or click the \"No\" button"; - char str3_b[] = "View only: press \"v\" or click the \"View\" button"; - char str1_m[] = "To accept: click the \"Yes\" button"; - char str2_m[] = "To reject: click the \"No\" button"; - char str3_m[] = "View only: click the \"View\" button"; - char str1_k[] = "To accept: press \"y\""; - char str2_k[] = "To reject: press \"n\""; - char str3_k[] = "View only: press \"v\""; - char *str1, *str2, *str3; - char str_y[] = "Yes"; - char str_n[] = "No"; - char str_v[] = "View"; - int x, y, w = 345, h = 175, ret = 0; - int X_sh = 20, Y_sh = 30, dY = 20; - int Ye_x = 20, Ye_y = 0, Ye_w = 45, Ye_h = 20; - int No_x = 75, No_y = 0, No_w = 45, No_h = 20; - int Vi_x = 130, Vi_y = 0, Vi_w = 45, Vi_h = 20; + if (sig == -1) { + /* not worth trying any more cleanup, X server probably gone */ + exit(3); + } - if (raw_fb && ! dpy) return 0; /* raw_fb hack */ + /* X keyboard cleanups */ + delete_added_keycodes(); - if (!strcmp(mode, "mouse_only")) { - str1 = str1_m; - str2 = str2_m; - str3 = str3_m; - } else if (!strcmp(mode, "key_only")) { - str1 = str1_k; - str2 = str2_k; - str3 = str3_k; - h -= dY; - } else { - str1 = str1_b; - str2 = str2_b; - str3 = str3_b; - } - if (view_only) { - h -= dY; + if (clear_mods == 1) { + clear_modifiers(0); + } else if (clear_mods == 2) { + clear_keys(); } - - /* XXX handle coff_x/coff_y? */ - if (X < -dpy_x) { - x = (dpy_x - w)/2; /* large negative: center */ - if (x < 0) x = 0; - } else if (X < 0) { - x = dpy_x + X - w; /* from lower right */ - } else { - x = X; /* from upper left */ + if (no_autorepeat) { + autorepeat(1); } - - if (Y < -dpy_y) { - y = (dpy_y - h)/2; - if (y < 0) y = 0; - } else if (Y < 0) { - y = dpy_y + Y - h; - } else { - y = Y; + if (use_solid_bg) { + solid_bg(1); } - X_LOCK; + if (sig) { + exit(2); + } +} - awin = XCreateSimpleWindow(dpy, window, x, y, w, h, 4, - BlackPixel(dpy, scr), WhitePixel(dpy, scr)); +/* trapping utility to check for a valid window: */ +int valid_window(Window win, XWindowAttributes *attr_ret) { + XErrorHandler old_handler; + XWindowAttributes attr, *pattr; + int ok = 0; - wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); - wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False); - XSetWMProtocols(dpy, awin, &wm_delete_window, 1); + if (attr_ret == NULL) { + pattr = &attr; + } else { + pattr = attr_ret; + } - if (! ico) { - ico = XCreateBitmapFromData(dpy, awin, t2x2_bits, t2x2_width, - t2x2_height); + trapped_xerror = 0; + old_handler = XSetErrorHandler(trap_xerror); + if (XGetWindowAttributes(dpy, win, pattr)) { + ok = 1; } - - hints.flags = PPosition | PSize | PMinSize; - hints.x = x; - hints.y = y; - hints.width = w; - hints.height = h; - hints.min_width = w; - hints.min_height = h; - - XSetStandardProperties(dpy, awin, "new x11vnc client", "x11vnc query", - ico, NULL, 0, &hints); - - XSelectInput(dpy, awin, evmask); - - if (! font_info && (font_info = XLoadQueryFont(dpy, "fixed")) == NULL) { - rfbLog("ugly_accept_window: cannot locate font fixed.\n"); - X_UNLOCK; - clean_up_exit(1); + if (trapped_xerror && trapped_xerror_event && ! quiet) { + rfbLog("trapped XError: %s (0x%lx)\n", + xerror_string(trapped_xerror_event), win); } + XSetErrorHandler(old_handler); + trapped_xerror = 0; + + return ok; +} - gc = XCreateGC(dpy, awin, valuemask, &values); - XSetFont(dpy, gc, font_info->fid); - XSetForeground(dpy, gc, BlackPixel(dpy, scr)); - XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter); - XSetDashes(dpy, gc, 0, dash_list, list_length); - - XMapWindow(dpy, awin); - XFlush(dpy); - - snprintf(strh, 100, "x11vnc: accept connection from %s?", addr); - snprintf(stri, 100, " (%s)", userhost); - key_y = XKeysymToKeycode(dpy, XStringToKeysym("y")); - key_n = XKeysymToKeycode(dpy, XStringToKeysym("n")); - key_v = XKeysymToKeycode(dpy, XStringToKeysym("v")); +int wait_until_mapped(Window win) { + int ms = 50, waittime = 30; + time_t start = time(0); + XWindowAttributes attr; while (1) { - int out = -1, x, y, tw, k; - - if (XCheckWindowEvent(dpy, awin, evmask, &ev)) { - ; /* proceed to handling */ - } else if (XCheckTypedEvent(dpy, ClientMessage, &ev)) { - ; /* proceed to handling */ - } else { - int ms = 100; /* sleep a bit */ - usleep(ms * 1000); - waited += ((double) ms)/1000.; - if (timeout && (int) waited >= timeout) { - rfbLog("accept_client: popup timed out after " - "%d seconds.\n", timeout); - out = 0; - ev.type = 0; - } else { - continue; + if (! valid_window(win, NULL)) { + if (time(0) > start + waittime) { + return 0; } + usleep(ms * 1000); + continue; } + if (! XGetWindowAttributes(dpy, win, &attr)) { + return 0; + } + if (attr.map_state == IsViewable) { + return 1; + } + usleep(ms * 1000); + } + return 0; +} - switch(ev.type) { - case Expose: - while (XCheckTypedEvent(dpy, Expose, &ev)) { - ; - } - k=0; - - /* instructions */ - XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, - strh, strlen(strh)); - XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, - stri, strlen(stri)); - XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, - str1, strlen(str1)); - XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, - str2, strlen(str2)); - if (! view_only) { - XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, - str3, strlen(str3)); - } - - if (!strcmp(mode, "key_only")) { - break; - } - - /* buttons */ - Ye_y = Y_sh+k*dY; - No_y = Y_sh+k*dY; - Vi_y = Y_sh+k*dY; - XDrawRectangle(dpy, awin, gc, Ye_x, Ye_y, Ye_w, Ye_h); - XDrawRectangle(dpy, awin, gc, No_x, No_y, No_w, No_h); - if (! view_only) { - XDrawRectangle(dpy, awin, gc, Vi_x, Vi_y, - Vi_w, Vi_h); - } +int get_window_size(Window win, int *x, int *y) { + XWindowAttributes attr; + /* valid_window? */ + if (XGetWindowAttributes(dpy, win, &attr)) { + *x = attr.width; + *y = attr.height; + return 1; + } else { + return 0; + } +} - tw = XTextWidth(font_info, str_y, strlen(str_y)); - tw = (Ye_w - tw)/2; - if (tw < 0) tw = 1; - XDrawString(dpy, awin, gc, Ye_x+tw, Ye_y+Ye_h-5, - str_y, strlen(str_y)); +/* signal handlers */ +void initialize_signals(void) { + signal(SIGHUP, interrupted); + signal(SIGINT, interrupted); + signal(SIGQUIT, interrupted); + signal(SIGABRT, interrupted); + signal(SIGTERM, interrupted); + signal(SIGBUS, interrupted); + signal(SIGSEGV, interrupted); + signal(SIGFPE, interrupted); - tw = XTextWidth(font_info, str_n, strlen(str_n)); - tw = (No_w - tw)/2; - if (tw < 0) tw = 1; - XDrawString(dpy, awin, gc, No_x+tw, No_y+No_h-5, - str_n, strlen(str_n)); + if (!sigpipe || *sigpipe == '\0' || !strcmp(sigpipe, "skip")) { + ; + } else if (!strcmp(sigpipe, "ignore")) { +#ifdef SIG_IGN + signal(SIGPIPE, SIG_IGN); +#endif + } else if (!strcmp(sigpipe, "exit")) { + rfbLog("initialize_signals: will exit on SIGPIPE\n"); + signal(SIGPIPE, interrupted); + } - if (! view_only) { - tw = XTextWidth(font_info, str_v, - strlen(str_v)); - tw = (Vi_w - tw)/2; - if (tw < 0) tw = 1; - XDrawString(dpy, awin, gc, Vi_x+tw, - Vi_y+Vi_h-5, str_v, strlen(str_v)); - } + X_LOCK; + Xerror_def = XSetErrorHandler(Xerror); + XIOerr_def = XSetIOErrorHandler(XIOerr); + X_UNLOCK; +} - break; +/* -- connections.c -- */ +/* + * routines for handling incoming, outgoing, etc connections + */ - case ClientMessage: - if (ev.xclient.message_type == wm_protocols && - ev.xclient.data.l[0] == wm_delete_window) { - out = 0; - } - break; +/* + * check that all clients are in RFB_NORMAL state + */ +int all_clients_initialized(void) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int ok = 1; - case ButtonPress: - x = ev.xbutton.x; - y = ev.xbutton.y; - if (!strcmp(mode, "key_only")) { - ; - } else if (x > No_x && x < No_x+No_w && y > No_y - && y < No_y+No_h) { - out = 0; - } else if (x > Ye_x && x < Ye_x+Ye_w && y > Ye_y - && y < Ye_y+Ye_h) { - out = 1; - } else if (! view_only && x > Vi_x && x < Vi_x+Vi_w - && y > Vi_y && y < Vi_y+Ye_h) { - out = 2; - } - break; + if (! screen) { + return ok; + } - case KeyPress: - if (!strcmp(mode, "mouse_only")) { - ; - } else if (ev.xkey.keycode == key_y) { - out = 1; - } else if (ev.xkey.keycode == key_n) { - out = 0; - } else if (! view_only && ev.xkey.keycode == key_v) { - out = 2; - } - break; - default: - break; - } - if (out != -1) { - ret = out; - XSelectInput(dpy, awin, 0); - XUnmapWindow(dpy, awin); - XFreeGC(dpy, gc); - XDestroyWindow(dpy, awin); - XFlush(dpy); + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (cl->state != RFB_NORMAL) { + ok = 0; break; } } - X_UNLOCK; + rfbReleaseClientIterator(iter); - return ret; + return ok; } -/* - * process a "yes:0,no:*,view:3" type action list comparing to command - * return code rc. * means the default action with no other match. - */ -static int action_match(char *action, int rc) { - char *p, *q, *s = strdup(action); - int cases[4], i, result; - char *labels[4]; +char *list_clients(void) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + char *list, tmp[32]; + int count = 0; - labels[1] = "yes"; - labels[2] = "no"; - labels[3] = "view"; + if (!screen) { + return strdup(""); + } - rfbLog("accept_client: process action line: %s\n", - action); - - for (i=1; i <= 3; i++) { - cases[i] = -2; + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + count++; } + rfbReleaseClientIterator(iter); - p = strtok(s, ","); - while (p) { - if ((q = strchr(p, ':')) != NULL) { - int in, k; - *q = '\0'; - q++; - if (strstr(p, "yes") == p) { - k = 1; - } else if (strstr(p, "no") == p) { - k = 2; - } else if (strstr(p, "view") == p) { - k = 3; - } else { - rfbLog("bad action line: %s\n", action); - clean_up_exit(1); - } - if (*q == '*') { - cases[k] = -1; - } else if (sscanf(q, "%d", &in) == 1) { - if (in < 0) { - rfbLog("bad action line: %s\n", action); - clean_up_exit(1); - } - cases[k] = in; - } else { - rfbLog("bad action line: %s\n", action); - clean_up_exit(1); - } - } else { - rfbLog("bad action line: %s\n", action); - clean_up_exit(1); - } - p = strtok(NULL, ","); - } - free(s); + /* + * each client: + * ::::::, + * 8+1+16+1+5+1+24+1+256+1+5+1+1+1 + * 123.123.123.123:60000/0x11111111-rw, + * so count+1 * 400 must cover it. + */ + list = (char *) malloc((count+1)*400); + + list[0] = '\0'; - result = -1; - for (i=1; i <= 3; i++) { - if (cases[i] == -1) { - rfbLog("accept_client: default action is case=%d %s\n", - i, labels[i]); - result = i; - break; + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + ClientData *cd = (ClientData *) cl->clientData; + if (*list != '\0') { + strcat(list, ","); } - } - if (result == -1) { - rfbLog("accept_client: no default action\n"); - } - for (i=1; i <= 3; i++) { - if (cases[i] >= 0 && cases[i] == rc) { - rfbLog("accept_client: matched action is case=%d %s\n", - i, labels[i]); - result = i; - break; + sprintf(tmp, "0x%x:", cd->uid); + strcat(list, tmp); + strcat(list, cl->host); + strcat(list, ":"); + sprintf(tmp, "%d:", cd->client_port); + strcat(list, tmp); + if (*(cd->username) == '\0') { + char *s = ident_username(cl); + if (s) free(s); } + strcat(list, cd->username); + strcat(list, ":"); + strcat(list, cd->hostname); + strcat(list, ":"); + strcat(list, cd->input); + strcat(list, ":"); + sprintf(tmp, "%d", cd->login_viewonly); + strcat(list, tmp); } - if (result < 0) { - rfbLog("no action match: %s rc=%d set to no\n", action, rc); - result = 2; - } - return result; + rfbReleaseClientIterator(iter); + return list; } -/* - * Simple routine to prompt the user on the X display whether an incoming - * client should be allowed to connect or not. If a gui is involved it - * will be running in the environment/context of the X11 DISPLAY. - * - * The command supplied via -accept is run as is (i.e. no string - * substitution) with the RFB_CLIENT_IP environment variable set to the - * incoming client's numerical IP address. - * - * If the external command exits with 0 the client is accepted, otherwise - * the client is rejected. - * - * Some builtins are provided: - * - * xmessage: use homebrew xmessage(1) for the external command. - * popup: use internal X widgets for prompting. - * - */ -static int accept_client(rfbClientPtr client) { - - char xmessage[200], *cmd = NULL; - char *addr = client->host; - char *action = NULL; +/* count number of clients supporting NewFBSize */ +int new_fb_size_clients(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int count = 0; - if (accept_cmd == NULL || *accept_cmd == '\0') { - return 1; /* no command specified, so we accept */ + if (! s) { + return 0; } - if (addr == NULL || addr[0] == '\0') { - addr = "unknown-host"; + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (cl->useNewFBSize) { + count++; + } } + rfbReleaseClientIterator(iter); + return count; +} - if (strstr(accept_cmd, "popup") == accept_cmd) { - /* use our builtin popup button */ +void close_all_clients(void) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; - /* (popup|popupkey|popupmouse)[+-X+-Y][:timeout] */ + if (! screen) { + return; + } - int ret, timeout = 120; - int x = -64000, y = -64000; - char *p, *mode; - char *userhost = ident_username(client); + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + rfbCloseClient(cl); + rfbClientConnectionGone(cl); + } + rfbReleaseClientIterator(iter); +} - /* extract timeout */ - if ((p = strchr(accept_cmd, ':')) != NULL) { - int in; - if (sscanf(p+1, "%d", &in) == 1) { - timeout = in; +rfbClientPtr *client_match(char *str) { + rfbClientIteratorPtr iter; + rfbClientPtr cl, *cl_list; + int i, n, host_warn = 0, hex_warn = 0; + + n = client_count + 10; + cl_list = (rfbClientPtr *) malloc(n * sizeof(rfbClientPtr)); + + i = 0; + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (strstr(str, "0x") == str) { + int id; + ClientData *cd = (ClientData *) cl->clientData; + if (sscanf(str, "0x%x", &id) != 1) { + if (hex_warn++) { + continue; + } + rfbLog("skipping bad client hex id: %s\n", str); + continue; } - } - /* extract geometry */ - if ((p = strpbrk(accept_cmd, "+-")) != NULL) { - int x1, y1; - if (sscanf(p, "+%d+%d", &x1, &y1) == 2) { - x = x1; - y = y1; - } else if (sscanf(p, "+%d-%d", &x1, &y1) == 2) { - x = x1; - y = -y1; - } else if (sscanf(p, "-%d+%d", &x1, &y1) == 2) { - x = -x1; - y = y1; - } else if (sscanf(p, "-%d-%d", &x1, &y1) == 2) { - x = -x1; - y = -y1; + if ( cd->uid == id) { + cl_list[i++] = cl; } - } - - /* find mode: mouse, key, or both */ - if (strstr(accept_cmd, "popupmouse") == accept_cmd) { - mode = "mouse_only"; - } else if (strstr(accept_cmd, "popupkey") == accept_cmd) { - mode = "key_only"; } else { - mode = "both"; - } - - rfbLog("accept_client: using builtin popup for: %s\n", addr); - if ((ret = ugly_accept_window(addr, userhost, x, y, timeout, - mode))) { - free(userhost); - if (ret == 2) { - rfbLog("accept_client: viewonly: %s\n", addr); - client->viewOnly = TRUE; + char *rstr = str; + if (! dotted_ip(str)) { + rstr = host2ip(str); + if (rstr == NULL || *rstr == '\0') { + if (host_warn++) { + continue; + } + rfbLog("skipping bad lookup: \"%s\"\n", + str); + continue; + } + rfbLog("lookup: %s -> %s\n", str, rstr); } - rfbLog("accept_client: popup accepted: %s\n", addr); - return 1; - } else { - free(userhost); - rfbLog("accept_client: popup rejected: %s\n", addr); - return 0; + if (!strcmp(rstr, cl->host)) { + cl_list[i++] = cl; + } + if (rstr != str) { + free(rstr); + } + } + if (i >= n - 1) { + break; } + } + rfbReleaseClientIterator(iter); - } else if (!strcmp(accept_cmd, "xmessage")) { - /* make our own command using xmessage(1) */ + cl_list[i] = NULL; - if (view_only) { - sprintf(xmessage, "xmessage -buttons yes:0,no:2 -center" - " 'x11vnc: accept connection from %s?'", addr); - } else { - sprintf(xmessage, "xmessage -buttons yes:0,no:2," - "view-only:3 -center" " 'x11vnc: accept connection" - " from %s?'", addr); - action = "yes:0,no:*,view:3"; - } - cmd = xmessage; - - } else { - /* use the user supplied command: */ + return cl_list; +} - cmd = accept_cmd; +void close_clients(char *str) { + rfbClientPtr *cl_list, *cp; - /* extract any action prefix: yes:N,no:M,view:K */ - if (strstr(accept_cmd, "yes:") == accept_cmd) { - char *p; - if ((p = strpbrk(accept_cmd, " \t")) != NULL) { - int i; - cmd = p; - p = accept_cmd; - for (i=0; i<200; i++) { - if (*p == ' ' || *p == '\t') { - xmessage[i] = '\0'; - break; - } - xmessage[i] = *p; - p++; - } - xmessage[200-1] = '\0'; - action = xmessage; - } - } + if (!strcmp(str, "all") || !strcmp(str, "*")) { + close_all_clients(); + return; } - if (cmd) { - int rc; - - rfbLog("accept_client: using cmd for: %s\n", addr); - rc = run_user_command(cmd, client, "accept"); - - if (action) { - int result; - - if (rc < 0) { - rfbLog("accept_client: cannot use negative " - "rc: %d, action %s\n", rc, action); - result = 2; - } else { - result = action_match(action, rc); - } - - if (result == 1) { - rc = 0; - } else if (result == 2) { - rc = 1; - } else if (result == 3) { - rc = 0; - rfbLog("accept_client: viewonly: %s\n", addr); - client->viewOnly = TRUE; - } else { - rc = 1; /* NOTREACHED */ - } - } - - if (rc == 0) { - rfbLog("accept_client: accepted: %s\n", addr); - return 1; - } else { - rfbLog("accept_client: rejected: %s\n", addr); - return 0; - } - } else { - rfbLog("accept_client: no command, rejecting %s\n", addr); - return 0; + if (! screen) { + return; } + + cl_list = client_match(str); - return 0; /* NOTREACHED */ + cp = cl_list; + while (*cp) { + rfbCloseClient(*cp); + rfbClientConnectionGone(*cp); + cp++; + } + free(cl_list); } -/* - * For the -connect option: periodically read the file looking for - * a connect string. If one is found set client_connect to it. - */ -static void check_connect_file(char *file) { - FILE *in; - char line[VNC_CONNECT_MAX], host[VNC_CONNECT_MAX]; - static int first_warn = 1, truncate_ok = 1; - static time_t last_time = 0; - time_t now = time(0); +void set_client_input(char *str) { + rfbClientPtr *cl_list, *cp; + char *p, *val; - if (last_time == 0) { - last_time = now; - } - if (now - last_time < 1) { - /* check only once a second */ - return; - } - last_time = now; + /* str is "match:value" */ - if (! truncate_ok) { - /* check if permissions changed */ - if (access(file, W_OK) == 0) { - truncate_ok = 1; - } else { - return; - } + if (! screen) { + return; } - in = fopen(file, "r"); - if (in == NULL) { - if (first_warn) { - rfbLog("check_connect_file: fopen failure: %s\n", file); - rfbLogPerror("fopen"); - first_warn = 0; - } + p = strchr(str, ':'); + if (! p) { return; } + *p = '\0'; + p++; + val = short_kmb(p); + + cl_list = client_match(str); - if (fgets(line, VNC_CONNECT_MAX, in) != NULL) { - if (sscanf(line, "%s", host) == 1) { - if (strlen(host) > 0) { - char *str = strdup(host); - if (strlen(str) > 38) { - char trim[100]; - trim[0] = '\0'; - strncat(trim, str, 38); - rfbLog("read connect file: %s ...\n", - trim); - } else { - rfbLog("read connect file: %s\n", str); - } - client_connect = str; - } - } + cp = cl_list; + while (*cp) { + ClientData *cd = (ClientData *) (*cp)->clientData; + cd->input[0] = '\0'; + strcat(cd->input, "_"); + strcat(cd->input, val); + cp++; } - fclose(in); - /* truncate file */ - in = fopen(file, "w"); - if (in != NULL) { - fclose(in); + free(val); + free(cl_list); +} + +void set_child_info(void) { + char pid[16]; + /* set up useful environment for child process */ + sprintf(pid, "%d", (int) getpid()); + set_env("X11VNC_PID", pid); + if (program_name) { + /* e.g. for remote control -R */ + set_env("X11VNC_PROG", program_name); + } + if (program_cmdline) { + set_env("X11VNC_CMDLINE", program_cmdline); + } + if (raw_fb_str) { + set_env("X11VNC_RAWFB_STR", raw_fb_str); } else { - /* disable if we cannot truncate */ - rfbLog("check_connect_file: could not truncate %s, " - "disabling checking.\n", file); - truncate_ok = 0; + set_env("X11VNC_RAWFB_STR", ""); } } /* - * Do a reverse connect for a single "host" or "host:port" + * utility to run a user supplied command setting some RFB_ env vars. + * used by, e.g., accept_client() and client_gone() */ -static int do_reverse_connect(char *str) { - rfbClientPtr cl; - char *host, *p; - int rport = 5500, len = strlen(str); +static int run_user_command(char *cmd, rfbClientPtr client, char *mode) { + char *old_display = NULL; + char *addr = client->host; + char str[100]; + int rc; + ClientData *cd = (ClientData *) client->clientData; - if (len < 1) { - return 0; - } - if (len > 1024) { - rfbLog("reverse_connect: string too long: %d bytes\n", len); - return 0; - } - if (!screen) { - rfbLog("reverse_connect: screen not setup yet.\n"); - return 0; + if (addr == NULL || addr[0] == '\0') { + addr = "unknown-host"; } - /* copy in to host */ - host = (char *) malloc((size_t) len+1); - if (! host) { - rfbLog("reverse_connect: could not malloc string %d\n", len); - return 0; + /* set RFB_CLIENT_ID to semi unique id for command to use */ + if (cd && cd->uid) { + sprintf(str, "0x%x", cd->uid); + } else { + /* not accepted yet: */ + sprintf(str, "0x%x", clients_served); } - strncpy(host, str, len); - host[len] = '\0'; + set_env("RFB_CLIENT_ID", str); - /* extract port, if any */ - if ((p = strchr(host, ':')) != NULL) { - rport = atoi(p+1); - *p = '\0'; - } + /* set RFB_CLIENT_IP to IP addr for command to use */ + set_env("RFB_CLIENT_IP", addr); - cl = rfbReverseConnection(screen, host, rport); - free(host); + /* set RFB_X11VNC_PID to our pid for command to use */ + sprintf(str, "%d", (int) getpid()); + set_env("RFB_X11VNC_PID", str); - if (cl == NULL) { - rfbLog("reverse_connect: %s failed\n", str); - return 0; + /* set RFB_CLIENT_PORT to peer port for command to use */ + if (cd && cd->client_port > 0) { + sprintf(str, "%d", cd->client_port); } else { - rfbLog("reverse_connect: %s/%s OK\n", str, cl->host); - return 1; + sprintf(str, "%d", get_remote_port(client->sock)); } -} + set_env("RFB_CLIENT_PORT", str); -/* - * Break up comma separated list of hosts and call do_reverse_connect() - */ -static void reverse_connect(char *str) { - char *p, *tmp = strdup(str); - int sleep_between_host = 300; - int sleep_min = 1500, sleep_max = 4500, n_max = 5; - int n, tot, t, dt = 100, cnt = 0; + set_env("RFB_MODE", mode); - p = strtok(tmp, ", \t\r\n"); - while (p) { - if ((n = do_reverse_connect(p)) != 0) { - rfbPE(screen, -1); - } - cnt += n; + /* + * now do RFB_SERVER_IP and RFB_SERVER_PORT (i.e. us!) + * This will establish a 5-tuple (including tcp) the external + * program can potentially use to work out the virtual circuit + * for this connection. + */ + if (cd && cd->server_ip) { + set_env("RFB_SERVER_IP", cd->server_ip); + } else { + char *sip = get_local_host(client->sock); + set_env("RFB_SERVER_IP", sip); + free(sip); + } - p = strtok(NULL, ", \t\r\n"); - if (p) { - t = 0; - while (t < sleep_between_host) { - usleep(dt * 1000); - rfbPE(screen, -1); - t += dt; - } - } + if (cd && cd->server_port > 0) { + sprintf(str, "%d", cd->server_port); + } else { + sprintf(str, "%d", get_local_port(client->sock)); } - free(tmp); + set_env("RFB_SERVER_PORT", str); - if (cnt == 0) { - return; + /* + * Better set DISPLAY to the one we are polling, if they + * want something trickier, they can handle on their own + * via environment, etc. + */ + if (getenv("DISPLAY")) { + old_display = strdup(getenv("DISPLAY")); + } + + if (raw_fb && ! dpy) { /* raw_fb hack */ + set_env("DISPLAY", "rawfb"); + } else { + set_env("DISPLAY", DisplayString(dpy)); } /* - * XXX: we need to process some of the initial handshaking - * events, otherwise the client can get messed up (why??) - * so we send rfbProcessEvents() all over the place. + * work out the number of clients (have to use client_count + * since there is deadlock in rfbGetClientIterator) */ + sprintf(str, "%d", client_count); + set_env("RFB_CLIENT_COUNT", str); - n = cnt; - if (n >= n_max) { - n = n_max; - } - t = sleep_max - sleep_min; - tot = sleep_min + ((n-1) * t) / (n_max-1); + if (no_external_cmds) { + rfbLog("cannot run external commands in -nocmds mode:\n"); + rfbLog(" \"%s\"\n", cmd); + rfbLog(" exiting.\n"); + clean_up_exit(1); + } + rfbLog("running command:\n"); + rfbLog(" %s\n", cmd); - t = 0; - while (t < tot) { - rfbPE(screen, -1); - usleep(dt * 1000); - t += dt; + /* XXX need to close port 5900, etc.. */ + rc = system(cmd); + + if (rc >= 256) { + rc = rc/256; + } + rfbLog("command returned: %d\n", rc); + + if (old_display) { + set_env("DISPLAY", old_display); + free(old_display); } + + return rc; } /* - * Routines for monitoring the VNC_CONNECT property for changes. - * The vncconnect(1) will set it on our X display. + * callback for when a client disconnects */ -void set_vnc_connect_prop(char *str) { - XChangeProperty(dpy, rootwin, vnc_connect_prop, XA_STRING, 8, - PropModeReplace, (unsigned char *)str, strlen(str)); -} +static void client_gone(rfbClientPtr client) { -void read_vnc_connect_prop(void) { - Atom type; - int format, slen, dlen; - unsigned long nitems = 0, bytes_after = 0; - unsigned char* data = NULL; + client_count--; + if (client_count < 0) client_count = 0; - vnc_connect_str[0] = '\0'; - slen = 0; + rfbLog("client_count: %d\n", client_count); - if (! vnc_connect || vnc_connect_prop == None) { - /* not active or problem with VNC_CONNECT atom */ - return; + if (no_autorepeat && client_count == 0) { + autorepeat(1); + } + if (use_solid_bg && client_count == 0) { + solid_bg(1); + } + if (gone_cmd && *gone_cmd != '\0') { + rfbLog("client_gone: using cmd for: %s\n", client->host); + run_user_command(gone_cmd, client, "gone"); } - /* read the property value into vnc_connect_str: */ - do { - if (XGetWindowProperty(dpy, DefaultRootWindow(dpy), - vnc_connect_prop, nitems/4, VNC_CONNECT_MAX/16, False, - AnyPropertyType, &type, &format, &nitems, &bytes_after, - &data) == Success) { - - dlen = nitems * (format/8); - if (slen + dlen > VNC_CONNECT_MAX) { - /* too big */ - rfbLog("warning: truncating large VNC_CONNECT" - " string > %d bytes.\n", VNC_CONNECT_MAX); - XFree(data); - break; + if (client->clientData) { + ClientData *cd = (ClientData *) client->clientData; + if (cd) { + if (cd->server_ip) { + free(cd->server_ip); + } + if (cd->hostname) { + free(cd->hostname); + } + if (cd->username) { + free(cd->username); } - memcpy(vnc_connect_str+slen, data, dlen); - slen += dlen; - vnc_connect_str[slen] = '\0'; - XFree(data); } - } while (bytes_after > 0); - - vnc_connect_str[VNC_CONNECT_MAX] = '\0'; - if (strlen(vnc_connect_str) > 38) { - char trim[100]; - trim[0] = '\0'; - strncat(trim, vnc_connect_str, 38); - rfbLog("read VNC_CONNECT: %s ...\n", trim); - - } else { - rfbLog("read VNC_CONNECT: %s\n", vnc_connect_str); + free(client->clientData); } -} -/* - * check if client_connect has been set, if so make the reverse connections. - */ -static void send_client_connect(void) { - if (client_connect != NULL) { - char *str = client_connect; - if (strstr(str, "cmd=") == str || strstr(str, "qry=") == str) { - process_remote_cmd(client_connect, 0); - } else if (strstr(str, "ans=") || strstr(str, "aro=") == str) { - ; - } else if (strstr(str, "ack=") == str) { - ; - } else { - reverse_connect(client_connect); + if (inetd) { + rfbLog("viewer exited.\n"); + clean_up_exit(0); + } + if (connect_once) { + /* + * This non-exit is done for a bad passwd to be consistent + * with our RFB_CLIENT_REFUSE behavior in new_client() (i.e. + * we disconnect after 1 successful connection). + */ + if ((client->state == RFB_PROTOCOL_VERSION || + client->state == RFB_AUTHENTICATION) && accepted_client) { + rfbLog("connect_once: bad password or early " + "disconnect.\n"); + rfbLog("connect_once: waiting for next connection.\n"); + accepted_client = 0; + return; } - free(client_connect); - client_connect = NULL; + if (shared && client_count > 0) { + rfbLog("connect_once: other shared clients still " + "connected, not exiting.\n"); + return; + } + + rfbLog("viewer exited.\n"); + clean_up_exit(0); } } /* - * monitor the various input methods + * Simple routine to limit access via string compare. A power user will + * want to compile libvncserver with libwrap support and use /etc/hosts.allow. */ -void check_connect_inputs(void) { - - /* flush any already set: */ - send_client_connect(); +static int check_access(char *addr) { + int allowed = 0; + char *p, *list; - /* connect file: */ - if (client_connect_file != NULL) { - check_connect_file(client_connect_file); + if (deny_all) { + rfbLog("check_access: new connections are currently " + "blocked.\n"); + return 0; } - send_client_connect(); - - /* VNC_CONNECT property (vncconnect program) */ - if (vnc_connect && *vnc_connect_str != '\0') { - client_connect = strdup(vnc_connect_str); - vnc_connect_str[0] = '\0'; + if (addr == NULL || *addr == '\0') { + rfbLog("check_access: denying empty host IP address string.\n"); + return 0; } - send_client_connect(); -} - -/* - * libvncserver callback for when a new client connects - */ -enum rfbNewClientAction new_client(rfbClientPtr client) { - ClientData *cd; - int i; - last_event = last_input = time(0); - if (inetd) { - /* - * Set this so we exit as soon as connection closes, - * otherwise client_gone is only called after RFB_CLIENT_ACCEPT - */ - client->clientGoneHook = client_gone; + if (allow_list == NULL) { + /* set to "" to possibly append allow_once */ + allow_list = strdup(""); + } + if (*allow_list == '\0' && allow_once == NULL) { + /* no constraints, accept it */ + return 1; } - clients_served++; + if (strchr(allow_list, '/')) { + /* a file of IP addresess or prefixes */ + int len, len2 = 0; + struct stat sbuf; + FILE *in; + char line[1024], *q; - if (connect_once) { - if (screen->dontDisconnect && screen->neverShared) { - if (! shared && accepted_client) { - rfbLog("denying additional client: %s\n", - client->host); - return(RFB_CLIENT_REFUSE); + if (stat(allow_list, &sbuf) != 0) { + rfbLog("check_access: failure stating file: %s\n", + allow_list); + rfbLogPerror("stat"); + clean_up_exit(1); + } + len = sbuf.st_size + 1; /* 1 more for '\0' at end */ + if (allow_once) { + len2 = strlen(allow_once) + 2; + len += len2; + } + list = malloc(len); + list[0] = '\0'; + + in = fopen(allow_list, "r"); + if (in == NULL) { + rfbLog("check_access: cannot open: %s\n", allow_list); + rfbLogPerror("fopen"); + clean_up_exit(1); + } + while (fgets(line, 1024, in) != NULL) { + if ( (q = strchr(line, '#')) != NULL) { + *q = '\0'; + } + if (strlen(list) + strlen(line) >= len - len2) { + /* file grew since our stat() */ + break; } + strcat(list, line); + } + fclose(in); + if (allow_once) { + strcat(list, "\n"); + strcat(list, allow_once); + strcat(list, "\n"); + } + } else { + int len = strlen(allow_list) + 1; + if (allow_once) { + len += strlen(allow_once) + 1; + } + list = malloc(len); + list[0] = '\0'; + strcat(list, allow_list); + if (allow_once) { + strcat(list, ","); + strcat(list, allow_once); } } - if (! check_access(client->host)) { - rfbLog("denying client: %s does not match %s\n", client->host, - allow_list ? allow_list : "(null)" ); - return(RFB_CLIENT_REFUSE); + + if (allow_once) { + free(allow_once); + allow_once = NULL; } - if (! accept_client(client)) { - rfbLog("denying client: %s local user rejected connection.\n", - client->host); - rfbLog("denying client: accept_cmd=\"%s\"\n", - accept_cmd ? accept_cmd : "(null)" ); - return(RFB_CLIENT_REFUSE); + + p = strtok(list, ", \t\n\r"); + while (p) { + char *chk, *q, *r = NULL; + if (*p == '\0') { + p = strtok(NULL, ", \t\n\r"); + continue; + } + if (! dotted_ip(p)) { + r = host2ip(p); + if (r == NULL || *r == '\0') { + rfbLog("check_access: bad lookup \"%s\"\n", p); + p = strtok(NULL, ", \t\n\r"); + continue; + } + rfbLog("check_access: lookup %s -> %s\n", p, r); + chk = r; + } else { + chk = p; + } + + q = strstr(addr, chk); + if (chk[strlen(chk)-1] != '.') { + if (!strcmp(addr, chk)) { + if (chk != p) { + rfbLog("check_access: client %s " + "matches host %s=%s\n", addr, + chk, p); + } else { + rfbLog("check_access: client %s " + "matches host %s\n", addr, chk); + } + allowed = 1; + } else if(!strcmp(chk, "localhost") && + !strcmp(addr, "127.0.0.1")) { + allowed = 1; + } + } else if (q == addr) { + rfbLog("check_access: client %s matches pattern %s\n", + addr, chk); + allowed = 1; + } + p = strtok(NULL, ", \t\n\r"); + if (r) { + free(r); + } + if (allowed) { + break; + } } + free(list); + return allowed; +} - client->clientData = (void *) calloc(sizeof(ClientData), 1); - cd = (ClientData *) client->clientData; +/* + * x11vnc's first (and only) visible widget: accept/reject dialog window. + * We go through this pain to avoid dependency on libXt... + */ +static int ugly_accept_window(char *addr, char *userhost, int X, int Y, + int timeout, char *mode) { - cd->uid = clients_served; +#define t2x2_width 16 +#define t2x2_height 16 +static char t2x2_bits[] = { + 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, + 0x33, 0x33, 0x33, 0x33, 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33, + 0xff, 0xff, 0xff, 0xff, 0x33, 0x33, 0x33, 0x33}; - cd->client_port = get_remote_port(client->sock); - cd->server_port = get_local_port(client->sock); - cd->server_ip = get_local_host(client->sock); - cd->hostname = ip2host(client->host); - cd->username = strdup(""); + Window awin; + GC gc; + XSizeHints hints; + XGCValues values; + static XFontStruct *font_info = NULL; + static Pixmap ico = 0; + unsigned long valuemask = 0; + static char dash_list[] = {20, 40}; + int list_length = sizeof(dash_list); - cd->input[0] = '-'; - cd->login_viewonly = -1; + Atom wm_protocols; + Atom wm_delete_window; - client->clientGoneHook = client_gone; - client_count++; + XEvent ev; + long evmask = ExposureMask | KeyPressMask | ButtonPressMask + | StructureNotifyMask; + double waited = 0.0; - if (no_autorepeat && client_count == 1 && ! view_only) { - /* - * first client, turn off X server autorepeat - * XXX handle dynamic change of view_only and per-client. - */ - autorepeat(0); + /* strings and geometries y/n */ + KeyCode key_y, key_n, key_v; + char strh[100]; + char stri[100]; + char str1_b[] = "To accept: press \"y\" or click the \"Yes\" button"; + char str2_b[] = "To reject: press \"n\" or click the \"No\" button"; + char str3_b[] = "View only: press \"v\" or click the \"View\" button"; + char str1_m[] = "To accept: click the \"Yes\" button"; + char str2_m[] = "To reject: click the \"No\" button"; + char str3_m[] = "View only: click the \"View\" button"; + char str1_k[] = "To accept: press \"y\""; + char str2_k[] = "To reject: press \"n\""; + char str3_k[] = "View only: press \"v\""; + char *str1, *str2, *str3; + char str_y[] = "Yes"; + char str_n[] = "No"; + char str_v[] = "View"; + int x, y, w = 345, h = 175, ret = 0; + int X_sh = 20, Y_sh = 30, dY = 20; + int Ye_x = 20, Ye_y = 0, Ye_w = 45, Ye_h = 20; + int No_x = 75, No_y = 0, No_w = 45, No_h = 20; + int Vi_x = 130, Vi_y = 0, Vi_w = 45, Vi_h = 20; + + if (raw_fb && ! dpy) return 0; /* raw_fb hack */ + + if (!strcmp(mode, "mouse_only")) { + str1 = str1_m; + str2 = str2_m; + str3 = str3_m; + } else if (!strcmp(mode, "key_only")) { + str1 = str1_k; + str2 = str2_k; + str3 = str3_k; + h -= dY; + } else { + str1 = str1_b; + str2 = str2_b; + str3 = str3_b; } - if (use_solid_bg && client_count == 1) { - solid_bg(0); + if (view_only) { + h -= dY; } - if (pad_geometry) { - install_padded_fb(pad_geometry); + /* XXX handle coff_x/coff_y? */ + if (X < -dpy_x) { + x = (dpy_x - w)/2; /* large negative: center */ + if (x < 0) x = 0; + } else if (X < 0) { + x = dpy_x + X - w; /* from lower right */ + } else { + x = X; /* from upper left */ } - - for (i=0; icmp_samp[i] = 5000; /* 56k modem */ - cd->raw_samp[i] = 50000; + + if (Y < -dpy_y) { + y = (dpy_y - h)/2; + if (y < 0) y = 0; + } else if (Y < 0) { + y = dpy_y + Y - h; + } else { + y = Y; } - cd->sample = 0; - - accepted_client = 1; - last_client = time(0); - return(RFB_CLIENT_ACCEPT); -} + X_LOCK; -void check_new_clients(void) { - static int last_count = 0; - rfbClientIteratorPtr iter; - rfbClientPtr cl; - - if (client_count == last_count) { - return; - } + awin = XCreateSimpleWindow(dpy, window, x, y, w, h, 4, + BlackPixel(dpy, scr), WhitePixel(dpy, scr)); - if (! all_clients_initialized()) { - return; - } + wm_protocols = XInternAtom(dpy, "WM_PROTOCOLS", False); + wm_delete_window = XInternAtom(dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(dpy, awin, &wm_delete_window, 1); - last_count = client_count; - if (! client_count) { - return; - } - if (! screen) { - return; + if (! ico) { + ico = XCreateBitmapFromData(dpy, awin, t2x2_bits, t2x2_width, + t2x2_height); } - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - ClientData *cd = (ClientData *) cl->clientData; + hints.flags = PPosition | PSize | PMinSize; + hints.x = x; + hints.y = y; + hints.width = w; + hints.height = h; + hints.min_width = w; + hints.min_height = h; - if (cd->login_viewonly < 0) { - /* this is a general trigger to initialize things */ - if (cl->viewOnly) { - cd->login_viewonly = 1; - if (allowed_input_view_only) { - cl->viewOnly = FALSE; - cd->input[0] = '\0'; - strncpy(cd->input, - allowed_input_view_only, CILEN); - } - } else { - cd->login_viewonly = 0; - if (allowed_input_normal) { - cd->input[0] = '\0'; - strncpy(cd->input, - allowed_input_normal, CILEN); - } - } - } + XSetStandardProperties(dpy, awin, "new x11vnc client", "x11vnc query", + ico, NULL, 0, &hints); + + XSelectInput(dpy, awin, evmask); + + if (! font_info && (font_info = XLoadQueryFont(dpy, "fixed")) == NULL) { + rfbLog("ugly_accept_window: cannot locate font fixed.\n"); + X_UNLOCK; + clean_up_exit(1); } - rfbReleaseClientIterator(iter); -} -/* -- keyboard.c -- */ -/* - * Routine to retreive current state keyboard. 1 means down, 0 up. - */ -static void get_keystate(int *keystate) { - int i, k; - char keys[32]; - - /* n.b. caller decides to X_LOCK or not. */ - XQueryKeymap(dpy, keys); - for (i=0; i<32; i++) { - char c = keys[i]; + gc = XCreateGC(dpy, awin, valuemask, &values); + XSetFont(dpy, gc, font_info->fid); + XSetForeground(dpy, gc, BlackPixel(dpy, scr)); + XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter); + XSetDashes(dpy, gc, 0, dash_list, list_length); - for (k=0; k < 8; k++) { - if (c & 0x1) { - keystate[8*i + k] = 1; + XMapWindow(dpy, awin); + XFlush(dpy); + + snprintf(strh, 100, "x11vnc: accept connection from %s?", addr); + snprintf(stri, 100, " (%s)", userhost); + key_y = XKeysymToKeycode(dpy, XStringToKeysym("y")); + key_n = XKeysymToKeycode(dpy, XStringToKeysym("n")); + key_v = XKeysymToKeycode(dpy, XStringToKeysym("v")); + + while (1) { + int out = -1, x, y, tw, k; + + if (XCheckWindowEvent(dpy, awin, evmask, &ev)) { + ; /* proceed to handling */ + } else if (XCheckTypedEvent(dpy, ClientMessage, &ev)) { + ; /* proceed to handling */ + } else { + int ms = 100; /* sleep a bit */ + usleep(ms * 1000); + waited += ((double) ms)/1000.; + if (timeout && (int) waited >= timeout) { + rfbLog("accept_client: popup timed out after " + "%d seconds.\n", timeout); + out = 0; + ev.type = 0; } else { - keystate[8*i + k] = 0; + continue; } - c = c >> 1; } - } -} -/* - * Try to KeyRelease any non-Lock modifiers that are down. - */ -void clear_modifiers(int init) { - static KeyCode keycodes[256]; - static KeySym keysyms[256]; - static char *keystrs[256]; - static int kcount = 0, first = 1; - int keystate[256]; - int i, j, minkey, maxkey, syms_per_keycode; - KeySym *keymap; - KeySym keysym; - KeyCode keycode; + switch(ev.type) { + case Expose: + while (XCheckTypedEvent(dpy, Expose, &ev)) { + ; + } + k=0; - /* n.b. caller decides to X_LOCK or not. */ - if (first) { - /* - * we store results in static arrays, to aid interrupted - * case, but modifiers could have changed during session... - */ - XDisplayKeycodes(dpy, &minkey, &maxkey); + /* instructions */ + XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, + strh, strlen(strh)); + XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, + stri, strlen(stri)); + XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, + str1, strlen(str1)); + XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, + str2, strlen(str2)); + if (! view_only) { + XDrawString(dpy, awin, gc, X_sh, Y_sh+(k++)*dY, + str3, strlen(str3)); + } - keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), - &syms_per_keycode); + if (!strcmp(mode, "key_only")) { + break; + } - for (i = minkey; i <= maxkey; i++) { - for (j = 0; j < syms_per_keycode; j++) { - keysym = keymap[ (i - minkey) * syms_per_keycode + j ]; - if (keysym == NoSymbol || ! ismodkey(keysym)) { - continue; + /* buttons */ + Ye_y = Y_sh+k*dY; + No_y = Y_sh+k*dY; + Vi_y = Y_sh+k*dY; + XDrawRectangle(dpy, awin, gc, Ye_x, Ye_y, Ye_w, Ye_h); + XDrawRectangle(dpy, awin, gc, No_x, No_y, No_w, No_h); + if (! view_only) { + XDrawRectangle(dpy, awin, gc, Vi_x, Vi_y, + Vi_w, Vi_h); } - keycode = XKeysymToKeycode(dpy, keysym); - if (keycode == NoSymbol) { - continue; + + tw = XTextWidth(font_info, str_y, strlen(str_y)); + tw = (Ye_w - tw)/2; + if (tw < 0) tw = 1; + XDrawString(dpy, awin, gc, Ye_x+tw, Ye_y+Ye_h-5, + str_y, strlen(str_y)); + + tw = XTextWidth(font_info, str_n, strlen(str_n)); + tw = (No_w - tw)/2; + if (tw < 0) tw = 1; + XDrawString(dpy, awin, gc, No_x+tw, No_y+No_h-5, + str_n, strlen(str_n)); + + if (! view_only) { + tw = XTextWidth(font_info, str_v, + strlen(str_v)); + tw = (Vi_w - tw)/2; + if (tw < 0) tw = 1; + XDrawString(dpy, awin, gc, Vi_x+tw, + Vi_y+Vi_h-5, str_v, strlen(str_v)); } - keycodes[kcount] = keycode; - keysyms[kcount] = keysym; - keystrs[kcount] = strdup(XKeysymToString(keysym)); - kcount++; - } - } - XFree((void *) keymap); - first = 0; - } - if (init) { - return; - } - - get_keystate(keystate); - for (i=0; i < kcount; i++) { - keysym = keysyms[i]; - keycode = keycodes[i]; - if (! keystate[(int) keycode]) { - continue; - } + break; - if (clear_mods) { - rfbLog("clear_modifiers: up: %-10s (0x%x) " - "keycode=0x%x\n", keystrs[i], keysym, keycode); + case ClientMessage: + if (ev.xclient.message_type == wm_protocols && + ev.xclient.data.l[0] == wm_delete_window) { + out = 0; + } + break; + + case ButtonPress: + x = ev.xbutton.x; + y = ev.xbutton.y; + if (!strcmp(mode, "key_only")) { + ; + } else if (x > No_x && x < No_x+No_w && y > No_y + && y < No_y+No_h) { + out = 0; + } else if (x > Ye_x && x < Ye_x+Ye_w && y > Ye_y + && y < Ye_y+Ye_h) { + out = 1; + } else if (! view_only && x > Vi_x && x < Vi_x+Vi_w + && y > Vi_y && y < Vi_y+Ye_h) { + out = 2; + } + break; + + case KeyPress: + if (!strcmp(mode, "mouse_only")) { + ; + } else if (ev.xkey.keycode == key_y) { + out = 1; + } else if (ev.xkey.keycode == key_n) { + out = 0; + } else if (! view_only && ev.xkey.keycode == key_v) { + out = 2; + } + break; + default: + break; + } + if (out != -1) { + ret = out; + XSelectInput(dpy, awin, 0); + XUnmapWindow(dpy, awin); + XFreeGC(dpy, gc); + XDestroyWindow(dpy, awin); + XFlush(dpy); + break; } - XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime); } - XFlush(dpy); + X_UNLOCK; + + return ret; } /* - * Attempt to set all keys to Up position. Can mess up typing at the - * physical keyboard so use with caution. + * process a "yes:0,no:*,view:3" type action list comparing to command + * return code rc. * means the default action with no other match. */ -void clear_keys(void) { - int k, keystate[256]; - - /* n.b. caller decides to X_LOCK or not. */ - get_keystate(keystate); - for (k=0; k<256; k++) { - if (keystate[k]) { - KeyCode keycode = (KeyCode) k; - rfbLog("clear_keys: keycode=%d\n", keycode); - XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime); - } - } - XFlush(dpy); -} - -/* - * Kludge for -norepeat option: we turn off keystroke autorepeat in - * the X server when clients are connected. This may annoy people at - * the physical display. We do this because 'key down' and 'key up' - * user input events may be separated by 100s of ms due to screen fb - * processing or link latency, thereby inducing the X server to apply - * autorepeat when it should not. Since the *client* is likely doing - * keystroke autorepeating as well, it kind of makes sense to shut it - * off if no one is at the physical display... - */ -int get_autorepeat_state(void) { - XKeyboardState kstate; - X_LOCK; - XGetKeyboardControl(dpy, &kstate); - X_UNLOCK; - return kstate.global_auto_repeat; -} - -void autorepeat(int restore) { - int global_auto_repeat; - XKeyboardControl kctrl; - static int save_auto_repeat = -1; - - if (raw_fb && ! dpy) return; /* raw_fb hack */ +static int action_match(char *action, int rc) { + char *p, *q, *s = strdup(action); + int cases[4], i, result; + char *labels[4]; - if (restore) { - if (save_auto_repeat < 0) { - return; /* nothing to restore */ - } - global_auto_repeat = get_autorepeat_state(); - X_LOCK; - /* read state and skip restore if equal (e.g. no clients) */ - if (global_auto_repeat == save_auto_repeat) { - X_UNLOCK; - return; - } + labels[1] = "yes"; + labels[2] = "no"; + labels[3] = "view"; - kctrl.auto_repeat_mode = save_auto_repeat; - XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl); - XFlush(dpy); - X_UNLOCK; + rfbLog("accept_client: process action line: %s\n", + action); - rfbLog("Restored X server key autorepeat to: %d\n", - save_auto_repeat); - } else { - global_auto_repeat = get_autorepeat_state(); - save_auto_repeat = global_auto_repeat; + for (i=1; i <= 3; i++) { + cases[i] = -2; + } - X_LOCK; - kctrl.auto_repeat_mode = AutoRepeatModeOff; - XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl); - XFlush(dpy); - X_UNLOCK; + p = strtok(s, ","); + while (p) { + if ((q = strchr(p, ':')) != NULL) { + int in, k; + *q = '\0'; + q++; + if (strstr(p, "yes") == p) { + k = 1; + } else if (strstr(p, "no") == p) { + k = 2; + } else if (strstr(p, "view") == p) { + k = 3; + } else { + rfbLog("bad action line: %s\n", action); + clean_up_exit(1); + } + if (*q == '*') { + cases[k] = -1; + } else if (sscanf(q, "%d", &in) == 1) { + if (in < 0) { + rfbLog("bad action line: %s\n", action); + clean_up_exit(1); + } + cases[k] = in; + } else { + rfbLog("bad action line: %s\n", action); + clean_up_exit(1); + } + } else { + rfbLog("bad action line: %s\n", action); + clean_up_exit(1); + } + p = strtok(NULL, ","); + } + free(s); - rfbLog("Disabled X server key autorepeat.\n"); - if (no_repeat_countdown >= 0) { - rfbLog(" you can run the command: 'xset r on' (%d " - "times)\n", no_repeat_countdown+1); - rfbLog(" to force it back on.\n"); + result = -1; + for (i=1; i <= 3; i++) { + if (cases[i] == -1) { + rfbLog("accept_client: default action is case=%d %s\n", + i, labels[i]); + result = i; + break; + } + } + if (result == -1) { + rfbLog("accept_client: no default action\n"); + } + for (i=1; i <= 3; i++) { + if (cases[i] >= 0 && cases[i] == rc) { + rfbLog("accept_client: matched action is case=%d %s\n", + i, labels[i]); + result = i; + break; } } + if (result < 0) { + rfbLog("no action match: %s rc=%d set to no\n", action, rc); + result = 2; + } + return result; } -static KeySym added_keysyms[0x100]; - -int add_keysym(KeySym keysym) { - int minkey, maxkey, syms_per_keycode; - int kc, n, ret = 0; - static int first = 1; - KeySym *keymap; +/* + * Simple routine to prompt the user on the X display whether an incoming + * client should be allowed to connect or not. If a gui is involved it + * will be running in the environment/context of the X11 DISPLAY. + * + * The command supplied via -accept is run as is (i.e. no string + * substitution) with the RFB_CLIENT_IP environment variable set to the + * incoming client's numerical IP address. + * + * If the external command exits with 0 the client is accepted, otherwise + * the client is rejected. + * + * Some builtins are provided: + * + * xmessage: use homebrew xmessage(1) for the external command. + * popup: use internal X widgets for prompting. + * + */ +static int accept_client(rfbClientPtr client) { - if (raw_fb && ! dpy) return 0; /* raw_fb hack */ + char xmessage[200], *cmd = NULL; + char *addr = client->host; + char *action = NULL; - if (first) { - for (n=0; n < 0x100; n++) { - added_keysyms[n] = NoSymbol; - } - first = 0; - } - if (keysym == NoSymbol) { - return 0; + if (accept_cmd == NULL || *accept_cmd == '\0') { + return 1; /* no command specified, so we accept */ } - /* there can be a race before MappingNotify */ - for (n=0; n < 0x100; n++) { - if (added_keysyms[n] == keysym) { - return n; - } + + if (addr == NULL || addr[0] == '\0') { + addr = "unknown-host"; } - XDisplayKeycodes(dpy, &minkey, &maxkey); - keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), - &syms_per_keycode); + if (strstr(accept_cmd, "popup") == accept_cmd) { + /* use our builtin popup button */ - for (kc = minkey+1; kc <= maxkey; kc++) { - int i, is_empty = 1; - char *str; - KeySym new[8]; + /* (popup|popupkey|popupmouse)[+-X+-Y][:timeout] */ - for (n=0; n < syms_per_keycode; n++) { - if (keymap[ (kc-minkey) * syms_per_keycode + n] - != NoSymbol) { - is_empty = 0; - break; + int ret, timeout = 120; + int x = -64000, y = -64000; + char *p, *mode; + char *userhost = ident_username(client); + + /* extract timeout */ + if ((p = strchr(accept_cmd, ':')) != NULL) { + int in; + if (sscanf(p+1, "%d", &in) == 1) { + timeout = in; } } - if (! is_empty) { - continue; + /* extract geometry */ + if ((p = strpbrk(accept_cmd, "+-")) != NULL) { + int x1, y1; + if (sscanf(p, "+%d+%d", &x1, &y1) == 2) { + x = x1; + y = y1; + } else if (sscanf(p, "+%d-%d", &x1, &y1) == 2) { + x = x1; + y = -y1; + } else if (sscanf(p, "-%d+%d", &x1, &y1) == 2) { + x = -x1; + y = y1; + } else if (sscanf(p, "-%d-%d", &x1, &y1) == 2) { + x = -x1; + y = -y1; + } } - for (i=0; i<8; i++) { - new[i] = NoSymbol; - } - if (add_keysyms == 2) { - new[0] = keysym; + /* find mode: mouse, key, or both */ + if (strstr(accept_cmd, "popupmouse") == accept_cmd) { + mode = "mouse_only"; + } else if (strstr(accept_cmd, "popupkey") == accept_cmd) { + mode = "key_only"; } else { - for(i=0; i < syms_per_keycode; i++) { - new[i] = keysym; - if (i >= 7) break; + mode = "both"; + } + + rfbLog("accept_client: using builtin popup for: %s\n", addr); + if ((ret = ugly_accept_window(addr, userhost, x, y, timeout, + mode))) { + free(userhost); + if (ret == 2) { + rfbLog("accept_client: viewonly: %s\n", addr); + client->viewOnly = TRUE; } + rfbLog("accept_client: popup accepted: %s\n", addr); + return 1; + } else { + free(userhost); + rfbLog("accept_client: popup rejected: %s\n", addr); + return 0; } - XChangeKeyboardMapping(dpy, kc, syms_per_keycode, - new, 1); + } else if (!strcmp(accept_cmd, "xmessage")) { + /* make our own command using xmessage(1) */ - str = XKeysymToString(keysym); - rfbLog("added missing keysym to X display: %03d 0x%x \"%s\"\n", - kc, keysym, str ? str : "null"); + if (view_only) { + sprintf(xmessage, "xmessage -buttons yes:0,no:2 -center" + " 'x11vnc: accept connection from %s?'", addr); + } else { + sprintf(xmessage, "xmessage -buttons yes:0,no:2," + "view-only:3 -center" " 'x11vnc: accept connection" + " from %s?'", addr); + action = "yes:0,no:*,view:3"; + } + cmd = xmessage; + + } else { + /* use the user supplied command: */ - XFlush(dpy); - added_keysyms[kc] = keysym; - ret = kc; - break; - } - XFree(keymap); - return ret; -} + cmd = accept_cmd; -void delete_keycode(KeyCode kc) { - int minkey, maxkey, syms_per_keycode, i; - KeySym *keymap; - KeySym ksym, new[8]; - char *str; + /* extract any action prefix: yes:N,no:M,view:K */ + if (strstr(accept_cmd, "yes:") == accept_cmd) { + char *p; + if ((p = strpbrk(accept_cmd, " \t")) != NULL) { + int i; + cmd = p; + p = accept_cmd; + for (i=0; i<200; i++) { + if (*p == ' ' || *p == '\t') { + xmessage[i] = '\0'; + break; + } + xmessage[i] = *p; + p++; + } + xmessage[200-1] = '\0'; + action = xmessage; + } + } + } - if (raw_fb && ! dpy) return; /* raw_fb hack */ + if (cmd) { + int rc; - XDisplayKeycodes(dpy, &minkey, &maxkey); - keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), - &syms_per_keycode); + rfbLog("accept_client: using cmd for: %s\n", addr); + rc = run_user_command(cmd, client, "accept"); - for (i=0; i<8; i++) { - new[i] = NoSymbol; - } - XChangeKeyboardMapping(dpy, kc, syms_per_keycode, new, 1); + if (action) { + int result; - ksym = XKeycodeToKeysym(dpy, kc, 0); - str = XKeysymToString(ksym); - rfbLog("deleted keycode from X display: %03d 0x%x \"%s\"\n", - kc, ksym, str ? str : "null"); + if (rc < 0) { + rfbLog("accept_client: cannot use negative " + "rc: %d, action %s\n", rc, action); + result = 2; + } else { + result = action_match(action, rc); + } - XFree(keymap); - XFlush(dpy); -} + if (result == 1) { + rc = 0; + } else if (result == 2) { + rc = 1; + } else if (result == 3) { + rc = 0; + rfbLog("accept_client: viewonly: %s\n", addr); + client->viewOnly = TRUE; + } else { + rc = 1; /* NOTREACHED */ + } + } -void delete_added_keycodes(void) { - int kc; - for (kc = 0; kc < 0x100; kc++) { - if (added_keysyms[kc] != NoSymbol) { - delete_keycode(kc); - added_keysyms[kc] = NoSymbol; + if (rc == 0) { + rfbLog("accept_client: accepted: %s\n", addr); + return 1; + } else { + rfbLog("accept_client: rejected: %s\n", addr); + return 0; } + } else { + rfbLog("accept_client: no command, rejecting %s\n", addr); + return 0; } -} - -/* - * The following is for an experimental -remap option to allow the user - * to remap keystrokes. It is currently confusing wrt modifiers... - */ -typedef struct keyremap { - KeySym before; - KeySym after; - int isbutton; - struct keyremap *next; -} keyremap_t; -static keyremap_t *keyremaps = NULL; + return 0; /* NOTREACHED */ +} /* - * process the -remap string (file or mapping string) + * For the -connect option: periodically read the file looking for + * a connect string. If one is found set client_connect to it. */ -void initialize_remap(char *infile) { +static void check_connect_file(char *file) { FILE *in; - char *p, *q, line[256], str1[256], str2[256]; - int i; - KeySym ksym1, ksym2; - keyremap_t *remap, *current; + char line[VNC_CONNECT_MAX], host[VNC_CONNECT_MAX]; + static int first_warn = 1, truncate_ok = 1; + static time_t last_time = 0; + time_t now = time(0); - if (keyremaps != NULL) { - /* free last remapping */ - keyremap_t *next_remap, *curr_remap = keyremaps; - while (curr_remap != NULL) { - next_remap = curr_remap->next; - free(curr_remap); - curr_remap = next_remap; - } - keyremaps = NULL; + if (last_time == 0) { + last_time = now; } - if (infile == NULL || *infile == '\0') { - /* just unset remapping */ + if (now - last_time < 1) { + /* check only once a second */ return; } + last_time = now; - in = fopen(infile, "r"); + if (! truncate_ok) { + /* check if permissions changed */ + if (access(file, W_OK) == 0) { + truncate_ok = 1; + } else { + return; + } + } + + in = fopen(file, "r"); if (in == NULL) { - /* assume cmd line key1-key2,key3-key4 */ - if (! strchr(infile, '-') || (in = tmpfile()) == NULL) { - rfbLog("remap: cannot open: %s\n", infile); + if (first_warn) { + rfbLog("check_connect_file: fopen failure: %s\n", file); rfbLogPerror("fopen"); - clean_up_exit(1); - } - p = infile; - while (*p) { - if (*p == '-') { - fprintf(in, " "); - } else if (*p == ',' || *p == ' ' || *p == '\t') { - fprintf(in, "\n"); - } else { - fprintf(in, "%c", *p); - } - p++; + first_warn = 0; } - fprintf(in, "\n"); - fflush(in); - rewind(in); + return; } - while (fgets(line, 256, in) != NULL) { - int isbtn = 0; - p = lblanks(line); - if (*p == '\0') { - continue; - } - if (strchr(line, '#')) { - continue; - } - if ( (q = strchr(line, '-')) != NULL) { - /* allow Keysym1-Keysym2 notation */ - *q = ' '; - } - - if (sscanf(line, "%s %s", str1, str2) != 2) { - rfbLog("remap: bad line: %s\n", line); - fclose(in); - clean_up_exit(1); - } - if (sscanf(str1, "0x%x", &i) == 1) { - ksym1 = (KeySym) i; - } else { - ksym1 = XStringToKeysym(str1); - } - if (sscanf(str2, "0x%x", &i) == 1) { - ksym2 = (KeySym) i; - } else { - ksym2 = XStringToKeysym(str2); - } - if (ksym2 == NoSymbol) { - int i; - if (sscanf(str2, "Button%d", &i) == 1) { - ksym2 = (KeySym) i; - isbtn = 1; + + if (fgets(line, VNC_CONNECT_MAX, in) != NULL) { + if (sscanf(line, "%s", host) == 1) { + if (strlen(host) > 0) { + char *str = strdup(host); + if (strlen(str) > 38) { + char trim[100]; + trim[0] = '\0'; + strncat(trim, str, 38); + rfbLog("read connect file: %s ...\n", + trim); + } else { + rfbLog("read connect file: %s\n", str); + } + client_connect = str; } } - if (ksym1 == NoSymbol || ksym2 == NoSymbol) { - rfbLog("warning: skipping bad remap line: %s", line); - continue; - } - remap = (keyremap_t *) malloc((size_t) sizeof(keyremap_t)); - remap->before = ksym1; - remap->after = ksym2; - remap->isbutton = isbtn; - remap->next = NULL; - rfbLog("remapping: (%s, 0x%x) -> (%s, 0x%x) isbtn=%d\n", str1, - ksym1, str2, ksym2, isbtn); - if (keyremaps == NULL) { - keyremaps = remap; - } else { - current->next = remap; - } - current = remap; } fclose(in); -} -/* - * preliminary support for using the Xkb (XKEYBOARD) extension for handling - * user input. inelegant, slow, and incomplete currently... but initial - * tests show it is useful for some setups. - */ -typedef struct keychar { - KeyCode code; - int group; - int level; -} keychar_t; - -/* max number of key groups and shift levels we consider */ -#define GRP 4 -#define LVL 8 -static int lvl_max, grp_max, kc_min, kc_max; -static KeySym xkbkeysyms[0x100][GRP][LVL]; -static unsigned int xkbstate[0x100][GRP][LVL]; -static unsigned int xkbignore[0x100][GRP][LVL]; -static unsigned int xkbmodifiers[0x100][GRP][LVL]; -static int multi_key[0x100], mode_switch[0x100], skipkeycode[0x100]; -static int shift_keys[0x100]; - -#if !LIBVNCSERVER_HAVE_XKEYBOARD - -/* empty functions for no xkb */ -static void initialize_xkb_modtweak(void) {} -static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, - rfbClientPtr client) { + /* truncate file */ + in = fopen(file, "w"); + if (in != NULL) { + fclose(in); + } else { + /* disable if we cannot truncate */ + rfbLog("check_connect_file: could not truncate %s, " + "disabling checking.\n", file); + truncate_ok = 0; + } } -#else - -/* sets up all the keymapping info via Xkb API */ - -static void initialize_xkb_modtweak(void) { - KeySym ks; - int kc, grp, lvl, k; - unsigned int state; - /* - * Here is a guide: - -Workarounds arrays: + * Do a reverse connect for a single "host" or "host:port" + */ +static int do_reverse_connect(char *str) { + rfbClientPtr cl; + char *host, *p; + int rport = 5500, len = strlen(str); -multi_key[] indicates which keycodes have Multi_key (Compose) - bound to them. -mode_switch[] indicates which keycodes have Mode_switch (AltGr) - bound to them. -shift_keys[] indicates which keycodes have Shift bound to them. -skipkeycode[] indicates which keycodes are to be skipped - for any lookups from -skip_keycodes option. + if (len < 1) { + return 0; + } + if (len > 1024) { + rfbLog("reverse_connect: string too long: %d bytes\n", len); + return 0; + } + if (!screen) { + rfbLog("reverse_connect: screen not setup yet.\n"); + return 0; + } -Groups and Levels, here is an example: - - ^ -------- - | L2 | A AE | - shift | | - level L1 | a ae | - -------- - G1 G2 - - group -> + /* copy in to host */ + host = (char *) malloc((size_t) len+1); + if (! host) { + rfbLog("reverse_connect: could not malloc string %d\n", len); + return 0; + } + strncpy(host, str, len); + host[len] = '\0'; -Traditionally this it all a key could do. L1 vs. L2 selected via Shift -and G1 vs. G2 selected via Mode_switch. Up to 4 Keysyms could be bound -to a key. See initialize_modtweak() for an example of using that type -of keymap from XGetKeyboardMapping(). + /* extract port, if any */ + if ((p = strchr(host, ':')) != NULL) { + rport = atoi(p+1); + *p = '\0'; + } -Xkb gives us up to 4 groups and 63 shift levels per key, with the -situation being potentially different for each key. This is complicated, -and I don't claim to understand it all, but in the following we just think -of ranging over the group and level indices as covering all of the cases. -This gives us an accurate view of the keymap. The main tricky part -is mapping between group+level and modifier state. + cl = rfbReverseConnection(screen, host, rport); + free(host); -On current linux/XFree86 setups (Xkb is enabled by default) the -information from XGetKeyboardMapping() (evidently the compat map) -is incomplete and inaccurate, so we are really forced to use the -Xkb API. + if (cl == NULL) { + rfbLog("reverse_connect: %s failed\n", str); + return 0; + } else { + rfbLog("reverse_connect: %s/%s OK\n", str, cl->host); + return 1; + } +} -xkbkeysyms[] For a (keycode,group,level) holds the KeySym (0 for none) -xkbstate[] For a (keycode,group,level) holds the corresponding - modifier state needed to get that KeySym -xkbignore[] For a (keycode,group,level) which modifiers can be - ignored (the 0 bits can be ignored). -xkbmodifiers[] For the KeySym bound to this (keycode,group,level) store - the modifier mask. - * +/* + * Break up comma separated list of hosts and call do_reverse_connect() */ +static void reverse_connect(char *str) { + char *p, *tmp = strdup(str); + int sleep_between_host = 300; + int sleep_min = 1500, sleep_max = 4500, n_max = 5; + int n, tot, t, dt = 100, cnt = 0; - /* initialize all the arrays: */ - for (kc = 0; kc < 0x100; kc++) { - multi_key[kc] = 0; - mode_switch[kc] = 0; - skipkeycode[kc] = 0; - shift_keys[kc] = 0; + p = strtok(tmp, ", \t\r\n"); + while (p) { + if ((n = do_reverse_connect(p)) != 0) { + rfbPE(-1); + } + cnt += n; - for (grp = 0; grp < GRP; grp++) { - for (lvl = 0; lvl < LVL; lvl++) { - xkbkeysyms[kc][grp][lvl] = NoSymbol; - xkbmodifiers[kc][grp][lvl] = -1; - xkbstate[kc][grp][lvl] = -1; + p = strtok(NULL, ", \t\r\n"); + if (p) { + t = 0; + while (t < sleep_between_host) { + usleep(dt * 1000); + rfbPE(-1); + t += dt; } } } + free(tmp); - /* - * the array is 256*LVL*GRP, but we can make the searched region - * smaller by computing the actual ranges. - */ - lvl_max = 0; - grp_max = 0; - kc_max = 0; - kc_min = 0x100; + if (cnt == 0) { + return; + } /* - * loop over all possible (keycode, group, level) triples - * and record what we find for it: + * XXX: we need to process some of the initial handshaking + * events, otherwise the client can get messed up (why??) + * so we send rfbProcessEvents() all over the place. */ - if (debug_keyboard > 1) { - rfbLog("initialize_xkb_modtweak: XKB keycode -> keysyms " - "mapping info:\n"); + + n = cnt; + if (n >= n_max) { + n = n_max; + } + t = sleep_max - sleep_min; + tot = sleep_min + ((n-1) * t) / (n_max-1); + + t = 0; + while (t < tot) { + rfbPE(-1); + usleep(dt * 1000); + t += dt; } - for (kc = 0; kc < 0x100; kc++) { - for (grp = 0; grp < GRP; grp++) { - for (lvl = 0; lvl < LVL; lvl++) { - unsigned int ms, mods; - int state_save = -1, mods_save; - KeySym ks2; +} - /* look up the Keysym, if any */ - ks = XkbKeycodeToKeysym(dpy, kc, grp, lvl); - xkbkeysyms[kc][grp][lvl] = ks; +/* + * Routines for monitoring the VNC_CONNECT property for changes. + * The vncconnect(1) will set it on our X display. + */ +void set_vnc_connect_prop(char *str) { + XChangeProperty(dpy, rootwin, vnc_connect_prop, XA_STRING, 8, + PropModeReplace, (unsigned char *)str, strlen(str)); +} - /* if no Keysym, on to next */ - if (ks == NoSymbol) { - continue; - } - /* - * for various workarounds, note where these special - * keys are bound to. - */ - if (ks == XK_Multi_key) { - multi_key[kc] = lvl+1; - } - if (ks == XK_Mode_switch) { - mode_switch[kc] = lvl+1; - } - if (ks == XK_Shift_L || ks == XK_Shift_R) { - shift_keys[kc] = lvl+1; - } +void read_vnc_connect_prop(void) { + Atom type; + int format, slen, dlen; + unsigned long nitems = 0, bytes_after = 0; + unsigned char* data = NULL; - /* - * record maximum extent for group/level indices - * and keycode range: - */ - if (grp > grp_max) { - grp_max = grp; - } - if (lvl > lvl_max) { - lvl_max = lvl; - } - if (kc > kc_max) { - kc_max = kc; - } - if (kc < kc_min) { - kc_min = kc; - } + vnc_connect_str[0] = '\0'; + slen = 0; - /* - * lookup on *keysym* (i.e. not kc, grp, lvl) - * and get the modifier mask. this is 0 for - * most keysyms, only non zero for modifiers. - */ - ms = XkbKeysymToModifiers(dpy, ks); - xkbmodifiers[kc][grp][lvl] = ms; + if (! vnc_connect || vnc_connect_prop == None) { + /* not active or problem with VNC_CONNECT atom */ + return; + } - /* - * Amusing heuristic (may have bugs). There are - * 8 modifier bits, so 256 possible modifier - * states. We loop over all of them for this - * keycode (simulating Key "events") and ask - * XkbLookupKeySym to tell us the Keysym. Once it - * matches the Keysym we have for this (keycode, - * group, level), gotten via XkbKeycodeToKeysym() - * above, we then (hopefully...) know that state - * of modifiers needed to generate this keysym. - * - * Yes... keep your fingers crossed. - * - * Note that many of the 256 states give the - * Keysym, we just need one, and we take the - * first one found. - */ - state = 0; - while(state < 256) { - if (XkbLookupKeySym(dpy, kc, state, &mods, - &ks2)) { - - /* save these for workaround below */ - if (state_save == -1) { - state_save = state; - mods_save = mods; - } - if (ks2 == ks) { - /* - * zero the irrelevant bits - * by anding with mods. - */ - xkbstate[kc][grp][lvl] - = state & mods; - /* - * also remember the irrelevant - * bits since it is handy. - */ - xkbignore[kc][grp][lvl] = mods; - - break; - } - } - state++; - } - if (xkbstate[kc][grp][lvl] == -1 && grp == 1) { - /* - * Hack on Solaris 9 for Mode_switch - * for Group2 characters. We force the - * Mode_switch modifier bit on. - * XXX Need to figure out better what is - * happening here. Is compat on somehow?? - */ - unsigned int ms2; - ms2 = XkbKeysymToModifiers(dpy, XK_Mode_switch); - - xkbstate[kc][grp][lvl] - = (state_save & mods_save) | ms2; - - xkbignore[kc][grp][lvl] = mods_save | ms2; - } + /* read the property value into vnc_connect_str: */ + do { + if (XGetWindowProperty(dpy, DefaultRootWindow(dpy), + vnc_connect_prop, nitems/4, VNC_CONNECT_MAX/16, False, + AnyPropertyType, &type, &format, &nitems, &bytes_after, + &data) == Success) { - if (debug_keyboard > 1) { - fprintf(stderr, " %03d G%d L%d mod=%s ", - kc, grp+1, lvl+1, bitprint(ms, 8)); - fprintf(stderr, "state=%s ", - bitprint(xkbstate[kc][grp][lvl], 8)); - fprintf(stderr, "ignore=%s ", - bitprint(xkbignore[kc][grp][lvl], 8)); - fprintf(stderr, " ks=0x%08lx \"%s\"\n", - ks, XKeysymToString(ks)); + dlen = nitems * (format/8); + if (slen + dlen > VNC_CONNECT_MAX) { + /* too big */ + rfbLog("warning: truncating large VNC_CONNECT" + " string > %d bytes.\n", VNC_CONNECT_MAX); + XFree(data); + break; } + memcpy(vnc_connect_str+slen, data, dlen); + slen += dlen; + vnc_connect_str[slen] = '\0'; + XFree(data); } - } + } while (bytes_after > 0); + + vnc_connect_str[VNC_CONNECT_MAX] = '\0'; + if (strlen(vnc_connect_str) > 38) { + char trim[100]; + trim[0] = '\0'; + strncat(trim, vnc_connect_str, 38); + rfbLog("read VNC_CONNECT: %s ...\n", trim); + + } else { + rfbLog("read VNC_CONNECT: %s\n", vnc_connect_str); } +} - /* - * process the user supplied -skip_keycodes string. - * This is presumably a list if "ghost" keycodes, the X server - * thinks they exist, but they do not. ghosts can lead to - * ambiguities in the reverse map: Keysym -> KeyCode + Modstate, - * so if we can ignore them so much the better. Presumably the - * user can never generate them from the physical keyboard. - * There may be other reasons to deaden some keys. - */ - if (skip_keycodes != NULL) { - char *p, *str = strdup(skip_keycodes); - p = strtok(str, ", \t\n\r"); - while (p) { - k = 1; - if (sscanf(p, "%d", &k) != 1 || k < 0 || k >= 0x100) { - rfbLog("bad skip_keycodes: %s %s\n", - skip_keycodes, p); - clean_up_exit(1); - } - skipkeycode[k] = 1; - p = strtok(NULL, ", \t\n\r"); +/* + * check if client_connect has been set, if so make the reverse connections. + */ +static void send_client_connect(void) { + if (client_connect != NULL) { + char *str = client_connect; + if (strstr(str, "cmd=") == str || strstr(str, "qry=") == str) { + process_remote_cmd(client_connect, 0); + } else if (strstr(str, "ans=") || strstr(str, "aro=") == str) { + ; + } else if (strstr(str, "ack=") == str) { + ; + } else { + reverse_connect(client_connect); } - free(str); - } - if (debug_keyboard > 1) { - fprintf(stderr, "grp_max=%d lvl_max=%d\n", grp_max, lvl_max); + free(client_connect); + client_connect = NULL; } } /* - * Called on user keyboard input. Try to solve the reverse mapping - * problem: KeySym (from VNC client) => KeyCode(s) to press to generate - * it. The one-to-many KeySym => KeyCode mapping makes it difficult, as - * does working out what changes to the modifier keypresses are needed. + * monitor the various input methods */ -static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, - rfbClientPtr client) { - - int kc, grp, lvl, i; - int kc_f[0x100], grp_f[0x100], lvl_f[0x100], state_f[0x100], found; - unsigned int state; +void check_connect_inputs(void) { - /* these are used for finding modifiers, etc */ - XkbStateRec kbstate; - int got_kbstate = 0; - int Kc_f, Grp_f, Lvl_f; + /* flush any already set: */ + send_client_connect(); - X_LOCK; + /* connect file: */ + if (client_connect_file != NULL) { + check_connect_file(client_connect_file); + } + send_client_connect(); - if (debug_keyboard) { - char *str = XKeysymToString(keysym); + /* VNC_CONNECT property (vncconnect program) */ + if (vnc_connect && *vnc_connect_str != '\0') { + client_connect = strdup(vnc_connect_str); + vnc_connect_str[0] = '\0'; + } + send_client_connect(); +} - if (debug_keyboard > 1) fprintf(stderr, "\n"); +/* + * libvncserver callback for when a new client connects + */ +enum rfbNewClientAction new_client(rfbClientPtr client) { + ClientData *cd; + double tmr = 0.0; - rfbLog("xkb_tweak_keyboard: %s keysym=0x%x \"%s\"\n", - down ? "down" : "up", (int) keysym, str ? str : "null"); - } + last_event = last_input = time(0); - /* - * set everything to not-yet-found. - * these "found" arrays (*_f) let us dyanamically consider the - * one-to-many Keysym -> Keycode issue. we set the size at 256, - * but of course only very few will be found. - */ - for (i = 0; i < 0x100; i++) { - kc_f[i] = -1; - grp_f[i] = -1; - lvl_f[i] = -1; - state_f[i] = -1; + if (inetd) { + /* + * Set this so we exit as soon as connection closes, + * otherwise client_gone is only called after RFB_CLIENT_ACCEPT + */ + client->clientGoneHook = client_gone; } - found = 0; - - /* - * loop over all (keycode, group, level) triples looking for - * matching keysyms. Amazingly this isn't slow (but maybe if - * you type really fast...). Hash lookup into a linked list of - * (keycode,grp,lvl) triples would be the way to improve this - * in the future if needed. - */ - for (kc = kc_min; kc <= kc_max; kc++) { - for (grp = 0; grp < grp_max+1; grp++) { - for (lvl = 0; lvl < lvl_max+1; lvl++) { - if (keysym != xkbkeysyms[kc][grp][lvl]) { - continue; - } - /* got a keysym match */ - state = xkbstate[kc][grp][lvl]; - if (debug_keyboard > 1) { - fprintf(stderr, " got match kc=%03d=0x%02x G%d" - " L%d ks=0x%x \"%s\" (basesym: \"%s\")\n", - kc, kc, grp+1, lvl+1, keysym, - XKeysymToString(keysym), XKeysymToString( - XKeycodeToKeysym(dpy, kc, 0))); - fprintf(stderr, " need state: %s\n", - bitprint(state, 8)); - fprintf(stderr, " ignorable : %s\n", - bitprint(xkbignore[kc][grp][lvl], 8)); - } + clients_served++; - /* save it if state is OK and not told to skip */ - if (state == -1) { - continue; - } - if (skipkeycode[kc] && debug_keyboard) { - fprintf(stderr, " xxx skipping keycode: %d " - "G%d/L%d\n", kc, grp+1, lvl+1); - } - if (skipkeycode[kc]) { - continue; - } - if (found > 0 && kc == kc_f[found-1]) { - /* ignore repeats for same keycode */ - continue; + if (connect_once) { + if (screen->dontDisconnect && screen->neverShared) { + if (! shared && accepted_client) { + rfbLog("denying additional client: %s\n", + client->host); + return(RFB_CLIENT_REFUSE); } - kc_f[found] = kc; - grp_f[found] = grp; - lvl_f[found] = lvl; - state_f[found] = state; - found++; } - } + } + if (! check_access(client->host)) { + rfbLog("denying client: %s does not match %s\n", client->host, + allow_list ? allow_list : "(null)" ); + return(RFB_CLIENT_REFUSE); + } + if (! accept_client(client)) { + rfbLog("denying client: %s local user rejected connection.\n", + client->host); + rfbLog("denying client: accept_cmd=\"%s\"\n", + accept_cmd ? accept_cmd : "(null)" ); + return(RFB_CLIENT_REFUSE); } -#define PKBSTATE \ - fprintf(stderr, " --- current mod state:\n"); \ - fprintf(stderr, " mods : %s\n", bitprint(kbstate.mods, 8)); \ - fprintf(stderr, " base_mods : %s\n", bitprint(kbstate.base_mods, 8)); \ - fprintf(stderr, " latch_mods: %s\n", bitprint(kbstate.latched_mods, 8)); \ - fprintf(stderr, " lock_mods : %s\n", bitprint(kbstate.locked_mods, 8)); \ - fprintf(stderr, " compat : %s\n", bitprint(kbstate.compat_state, 8)); - - /* - * Now get the current state of the keyboard from the X server. - * This seems to be the safest way to go as opposed to our - * keeping track of the modifier state on our own. Again, - * this is fortunately not too slow. - */ + client->clientData = (void *) calloc(sizeof(ClientData), 1); + cd = (ClientData *) client->clientData; - if (debug_keyboard > 1) { - /* get state early for debug output */ - XkbGetState(dpy, XkbUseCoreKbd, &kbstate); - got_kbstate = 1; - PKBSTATE - } + cd->uid = clients_served; - if (!found && add_keysyms && keysym && ! IsModifierKey(keysym)) { - int new_kc = add_keysym(keysym); - if (new_kc != 0) { - found = 1; - kc_f[0] = new_kc; - grp_f[0] = 0; - lvl_f[0] = 0; - state_f[0] = 0; - } + cd->client_port = get_remote_port(client->sock); + cd->server_port = get_local_port(client->sock); + cd->server_ip = get_local_host(client->sock); + cd->hostname = ip2host(client->host); + cd->username = strdup(""); + + cd->input[0] = '-'; + cd->login_viewonly = -1; + + client->clientGoneHook = client_gone; + client_count++; + + if (no_autorepeat && client_count == 1 && ! view_only) { + /* + * first client, turn off X server autorepeat + * XXX handle dynamic change of view_only and per-client. + */ + autorepeat(0); + } + if (use_solid_bg && client_count == 1) { + solid_bg(0); } - if (!found && debug_keyboard) { - char *str = XKeysymToString(keysym); - fprintf(stderr, " *** NO key found for: 0x%x %s " - "*keystroke ignored*\n", keysym, str ? str : "null"); + if (pad_geometry) { + install_padded_fb(pad_geometry); } - if (!found) { - X_UNLOCK; + + dtime(&tmr); + cd->timer = tmr; + cd->send_cmp_rate = 0.0; + cd->send_raw_rate = 0.0; + cd->latency = 0.0; + cd->cmp_bytes_sent = 0; + cd->raw_bytes_sent = 0; + + accepted_client = 1; + last_client = time(0); + + return(RFB_CLIENT_ACCEPT); +} + +void check_new_clients(void) { + static int last_count = 0; + rfbClientIteratorPtr iter; + rfbClientPtr cl; + + if (client_count == last_count) { return; } - /* - * we could optimize here if found > 1 - * e.g. minimize lvl or grp, or other things to give - * "safest" scenario to simulate the keystrokes. - * but for now we just take the first one we found. - */ - Kc_f = kc_f[0]; - Grp_f = grp_f[0]; - Lvl_f = lvl_f[0]; - state = state_f[0]; + if (! all_clients_initialized()) { + return; + } - if (debug_keyboard && found > 1) { - int l; - char *str; - fprintf(stderr, " *** found more than one keycode: "); - for (l = 0; l < found; l++) { - fprintf(stderr, "%03d ", kc_f[l]); - } - for (l = 0; l < found; l++) { - str = XKeysymToString(XKeycodeToKeysym(dpy,kc_f[l],0)); - fprintf(stderr, " \"%s\"", str ? str : "null"); - } - fprintf(stderr, ", using first one: %03d\n", Kc_f); + last_count = client_count; + if (! client_count) { + return; + } + if (! screen) { + return; } - if (down) { - /* - * need to set up the mods for tweaking and other workarounds - */ - int needmods[8], sentmods[8], Ilist[8], keystate[256]; - int involves_multi_key, shift_is_down; - int i, j, b, curr, need; - unsigned int ms; - KeySym ks; - Bool dn; + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + ClientData *cd = (ClientData *) cl->clientData; - if (! got_kbstate) { - /* get the current modifier state if we haven't yet */ - XkbGetState(dpy, XkbUseCoreKbd, &kbstate); + if (cd->login_viewonly < 0) { + /* this is a general trigger to initialize things */ + if (cl->viewOnly) { + cd->login_viewonly = 1; + if (allowed_input_view_only) { + cl->viewOnly = FALSE; + cd->input[0] = '\0'; + strncpy(cd->input, + allowed_input_view_only, CILEN); + } + } else { + cd->login_viewonly = 0; + if (allowed_input_normal) { + cd->input[0] = '\0'; + strncpy(cd->input, + allowed_input_normal, CILEN); + } + } } + } + rfbReleaseClientIterator(iter); +} - /* - * needmods[] whether or not that modifier bit needs - * something done to it. - * < 0 means no, - * 0 means needs to go up. - * 1 means needs to go down. - * - * -1, -2, -3 are used for debugging info to indicate - * why nothing needs to be done with the modifier, see below. - * - * sentmods[] is the corresponding keycode to use - * to acheive the needmods[] requirement for the bit. - */ +/* -- keyboard.c -- */ +/* + * Routine to retreive current state keyboard. 1 means down, 0 up. + */ +static void get_keystate(int *keystate) { + int i, k; + char keys[32]; + + /* n.b. caller decides to X_LOCK or not. */ + XQueryKeymap(dpy, keys); + for (i=0; i<32; i++) { + char c = keys[i]; - for (i=0; i<8; i++) { - needmods[i] = -1; - sentmods[i] = 0; + for (k=0; k < 8; k++) { + if (c & 0x1) { + keystate[8*i + k] = 1; + } else { + keystate[8*i + k] = 0; + } + c = c >> 1; } + } +} + +/* + * Try to KeyRelease any non-Lock modifiers that are down. + */ +void clear_modifiers(int init) { + static KeyCode keycodes[256]; + static KeySym keysyms[256]; + static char *keystrs[256]; + static int kcount = 0, first = 1; + int keystate[256]; + int i, j, minkey, maxkey, syms_per_keycode; + KeySym *keymap; + KeySym keysym; + KeyCode keycode; + /* n.b. caller decides to X_LOCK or not. */ + if (first) { /* - * Loop over the 8 modifier bits and check if the current - * setting is what we need it to be or whether it should - * be changed (by us sending some keycode event) - * - * If nothing needs to be done to it record why: - * -1 the modifier bit is ignored. - * -2 the modifier bit is ignored, but is correct anyway. - * -3 the modifier bit is correct. + * we store results in static arrays, to aid interrupted + * case, but modifiers could have changed during session... */ + XDisplayKeycodes(dpy, &minkey, &maxkey); - b = 0x1; - for (i=0; i<8; i++) { - curr = b & kbstate.mods; - need = b & state; + keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), + &syms_per_keycode); - if (! (b & xkbignore[Kc_f][Grp_f][Lvl_f])) { - /* irrelevant modifier bit */ - needmods[i] = -1; - if (curr == need) needmods[i] = -2; - } else if (curr == need) { - /* already correct */ - needmods[i] = -3; - } else if (! curr && need) { - /* need it down */ - needmods[i] = 1; - } else if (curr && ! need) { - /* need it up */ - needmods[i] = 0; + for (i = minkey; i <= maxkey; i++) { + for (j = 0; j < syms_per_keycode; j++) { + keysym = keymap[ (i - minkey) * syms_per_keycode + j ]; + if (keysym == NoSymbol || ! ismodkey(keysym)) { + continue; } - - b = b << 1; + keycode = XKeysymToKeycode(dpy, keysym); + if (keycode == NoSymbol) { + continue; + } + keycodes[kcount] = keycode; + keysyms[kcount] = keysym; + keystrs[kcount] = strdup(XKeysymToString(keysym)); + kcount++; + } } + XFree((void *) keymap); + first = 0; + } + if (init) { + return; + } + + get_keystate(keystate); + for (i=0; i < kcount; i++) { + keysym = keysyms[i]; + keycode = keycodes[i]; - /* - * Again we dynamically probe the X server for information, - * this time for the state of all the keycodes. Useful - * info, and evidently is not too slow... - */ - get_keystate(keystate); - - /* - * We try to determine if Shift is down (since that can - * screw up ISO_Level3_Shift manipulations). - */ - shift_is_down = 0; + if (! keystate[(int) keycode]) { + continue; + } - for (kc = kc_min; kc <= kc_max; kc++) { - if (skipkeycode[kc] && debug_keyboard) { - fprintf(stderr, " xxx skipping keycode: " - "%d\n", kc); - } - if (skipkeycode[kc]) { - continue; - } - if (shift_keys[kc] && keystate[kc]) { - shift_is_down = kc; - break; - } + if (clear_mods) { + rfbLog("clear_modifiers: up: %-10s (0x%x) " + "keycode=0x%x\n", keystrs[i], keysym, keycode); } + XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime); + } + XFlush(dpy); +} - /* - * Now loop over the modifier bits and try to deduce the - * keycode presses/release require to match the desired - * state. - */ - for (i=0; i<8; i++) { - if (needmods[i] < 0 && debug_keyboard > 1) { - int k = -needmods[i] - 1; - char *words[] = {"ignorable", - "bitset+ignorable", "bitset"}; - fprintf(stderr, " +++ needmods: mod=%d is " - "OK (%s)\n", i, words[k]); - } - if (needmods[i] < 0) { - continue; - } +/* + * Attempt to set all keys to Up position. Can mess up typing at the + * physical keyboard so use with caution. + */ +void clear_keys(void) { + int k, keystate[256]; + + /* n.b. caller decides to X_LOCK or not. */ + get_keystate(keystate); + for (k=0; k<256; k++) { + if (keystate[k]) { + KeyCode keycode = (KeyCode) k; + rfbLog("clear_keys: keycode=%d\n", keycode); + XTestFakeKeyEvent_wr(dpy, keycode, False, CurrentTime); + } + } + XFlush(dpy); +} + +/* + * Kludge for -norepeat option: we turn off keystroke autorepeat in + * the X server when clients are connected. This may annoy people at + * the physical display. We do this because 'key down' and 'key up' + * user input events may be separated by 100s of ms due to screen fb + * processing or link latency, thereby inducing the X server to apply + * autorepeat when it should not. Since the *client* is likely doing + * keystroke autorepeating as well, it kind of makes sense to shut it + * off if no one is at the physical display... + */ +int get_autorepeat_state(void) { + XKeyboardState kstate; + X_LOCK; + XGetKeyboardControl(dpy, &kstate); + X_UNLOCK; + return kstate.global_auto_repeat; +} - b = 1 << i; +void autorepeat(int restore) { + int global_auto_repeat; + XKeyboardControl kctrl; + static int save_auto_repeat = -1; - if (debug_keyboard > 1) { - fprintf(stderr, " +++ needmods: mod=%d %s " - "need it to be: %d %s\n", i, bitprint(b, 8), - needmods[i], needmods[i] ? "down" : "up"); - } + if (raw_fb && ! dpy) return; /* raw_fb hack */ - /* - * Again, an inefficient loop, this time just - * looking for modifiers... - */ - for (kc = kc_min; kc <= kc_max; kc++) { - for (grp = 0; grp < grp_max+1; grp++) { - for (lvl = 0; lvl < lvl_max+1; lvl++) { - int skip = 1, dbmsg = 0; + if (restore) { + if (save_auto_repeat < 0) { + return; /* nothing to restore */ + } + global_auto_repeat = get_autorepeat_state(); + X_LOCK; + /* read state and skip restore if equal (e.g. no clients) */ + if (global_auto_repeat == save_auto_repeat) { + X_UNLOCK; + return; + } - ms = xkbmodifiers[kc][grp][lvl]; - if (! ms || ms != b) { - continue; - } + kctrl.auto_repeat_mode = save_auto_repeat; + XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl); + XFlush(dpy); + X_UNLOCK; - if (skipkeycode[kc] && debug_keyboard) { - fprintf(stderr, " xxx skipping " - "keycode: %d G%d/L%d\n", - kc, grp+1, lvl+1); - } - if (skipkeycode[kc]) { - continue; - } + rfbLog("Restored X server key autorepeat to: %d\n", + save_auto_repeat); + } else { + global_auto_repeat = get_autorepeat_state(); + save_auto_repeat = global_auto_repeat; - ks = xkbkeysyms[kc][grp][lvl]; - if (! ks) { - continue; - } + X_LOCK; + kctrl.auto_repeat_mode = AutoRepeatModeOff; + XChangeKeyboardControl(dpy, KBAutoRepeatMode, &kctrl); + XFlush(dpy); + X_UNLOCK; - if (ks == XK_Shift_L) { - skip = 0; - } else if (ks == XK_Shift_R) { - skip = 0; - } else if (ks == XK_Mode_switch) { - skip = 0; - } else if (ks == XK_ISO_Level3_Shift) { - skip = 0; - } - /* - * Alt, Meta, Control, Super, - * Hyper, Num, Caps are skipped. - * - * XXX need more work on Locks, - * and non-standard modifiers. - * (e.g. XF86_Next_VMode using - * Ctrl+Alt) - */ - if (debug_keyboard > 1) { - char *str = XKeysymToString(ks); - int kt = keystate[kc]; - fprintf(stderr, " === for " - "mod=%s found kc=%03d/G%d" - "/L%d it is %d %s skip=%d " - "(%s)\n", bitprint(b,8), kc, - grp+1, lvl+1, kt, kt ? - "down" : "up ", skip, - str ? str : "null"); - } + rfbLog("Disabled X server key autorepeat.\n"); + if (no_repeat_countdown >= 0) { + rfbLog(" you can run the command: 'xset r on' (%d " + "times)\n", no_repeat_countdown+1); + rfbLog(" to force it back on.\n"); + } + } +} - if (! skip && needmods[i] != - keystate[kc] && sentmods[i] == 0) { - sentmods[i] = kc; - dbmsg = 1; - } +static KeySym added_keysyms[0x100]; - if (debug_keyboard > 1 && dbmsg) { - int nm = needmods[i]; - fprintf(stderr, " >>> we " - "choose kc=%03d=0x%02x to " - "change it to: %d %s\n", kc, - kc, nm, nm ? "down" : "up"); - } - - } - } - } +int add_keysym(KeySym keysym) { + int minkey, maxkey, syms_per_keycode; + int kc, n, ret = 0; + static int first = 1; + KeySym *keymap; + + if (raw_fb && ! dpy) return 0; /* raw_fb hack */ + + if (first) { + for (n=0; n < 0x100; n++) { + added_keysyms[n] = NoSymbol; } - for (i=0; i<8; i++) { - /* - * reverse order is useful for tweaking - * ISO_Level3_Shift before Shift, but assumes they - * are in that order (i.e. Shift is first bit). - */ - int reverse = 1; - if (reverse) { - Ilist[i] = 7 - i; - } else { - Ilist[i] = i; - } + first = 0; + } + if (keysym == NoSymbol) { + return 0; + } + /* there can be a race before MappingNotify */ + for (n=0; n < 0x100; n++) { + if (added_keysyms[n] == keysym) { + return n; } + } - /* - * check to see if Multi_key is bound to one of the Mods - * we have to tweak - */ - involves_multi_key = 0; - for (j=0; j<8; j++) { - i = Ilist[j]; - if (sentmods[i] == 0) continue; - dn = (Bool) needmods[i]; - if (!dn) continue; - if (multi_key[sentmods[i]]) { - involves_multi_key = i+1; - } - } + XDisplayKeycodes(dpy, &minkey, &maxkey); + keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), + &syms_per_keycode); - if (involves_multi_key && shift_is_down && needmods[0] < 0) { - /* - * Workaround for Multi_key and shift. - * Assumes Shift is bit 1 (needmods[0]) - */ - if (debug_keyboard) { - fprintf(stderr, " ^^^ trying to avoid " - "inadvertent Multi_key from Shift " - "(doing %03d up now)\n", shift_is_down); - } - XTestFakeKeyEvent_wr(dpy, shift_is_down, False, - CurrentTime); - } else { - involves_multi_key = 0; - } + for (kc = minkey+1; kc <= maxkey; kc++) { + int i, is_empty = 1; + char *str; + KeySym new[8]; - for (j=0; j<8; j++) { - /* do the Mod ups */ - i = Ilist[j]; - if (sentmods[i] == 0) continue; - dn = (Bool) needmods[i]; - if (dn) continue; - XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime); + for (n=0; n < syms_per_keycode; n++) { + if (keymap[ (kc-minkey) * syms_per_keycode + n] + != NoSymbol) { + is_empty = 0; + break; + } } - for (j=0; j<8; j++) { - /* next, do the Mod downs */ - i = Ilist[j]; - if (sentmods[i] == 0) continue; - dn = (Bool) needmods[i]; - if (!dn) continue; - XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime); + if (! is_empty) { + continue; } - if (involves_multi_key) { - /* - * Reverse workaround for Multi_key and shift. - */ - if (debug_keyboard) { - fprintf(stderr, " vvv trying to avoid " - "inadvertent Multi_key from Shift " - "(doing %03d down now)\n", shift_is_down); + for (i=0; i<8; i++) { + new[i] = NoSymbol; + } + if (add_keysyms == 2) { + new[0] = keysym; + } else { + for(i=0; i < syms_per_keycode; i++) { + new[i] = keysym; + if (i >= 7) break; } - XTestFakeKeyEvent_wr(dpy, shift_is_down, True, - CurrentTime); } - /* - * With the above modifier work done, send the actual keycode: - */ - XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime); - - /* - * Now undo the modifier work: - */ - for (j=7; j>=0; j--) { - /* reverse Mod downs we did */ - i = Ilist[j]; - if (sentmods[i] == 0) continue; - dn = (Bool) needmods[i]; - if (!dn) continue; - XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn, - CurrentTime); - } - for (j=7; j>=0; j--) { - /* finally reverse the Mod ups we did */ - i = Ilist[j]; - if (sentmods[i] == 0) continue; - dn = (Bool) needmods[i]; - if (dn) continue; - XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn, - CurrentTime); - } + XChangeKeyboardMapping(dpy, kc, syms_per_keycode, + new, 1); - } else { /* for up case, hopefully just need to pop it up: */ + str = XKeysymToString(keysym); + rfbLog("added missing keysym to X display: %03d 0x%x \"%s\"\n", + kc, keysym, str ? str : "null"); - XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime); + XFlush(dpy); + added_keysyms[kc] = keysym; + ret = kc; + break; } - X_UNLOCK; + XFree(keymap); + return ret; } -#endif -/* - * For tweaking modifiers wrt the Alt-Graph key, etc. - */ -#define LEFTSHIFT 1 -#define RIGHTSHIFT 2 -#define ALTGR 4 -static char mod_state = 0; +void delete_keycode(KeyCode kc) { + int minkey, maxkey, syms_per_keycode, i; + KeySym *keymap; + KeySym ksym, new[8]; + char *str; -static char modifiers[0x100]; -static KeyCode keycodes[0x100]; -static KeyCode left_shift_code, right_shift_code, altgr_code, iso_level3_code; + if (raw_fb && ! dpy) return; /* raw_fb hack */ -/* workaround for X11R5, Latin 1 only */ -#ifndef XConvertCase -#define XConvertCase(sym, lower, upper) \ -*(lower) = sym; \ -*(upper) = sym; \ -if (sym >> 8 == 0) { \ - if ((sym >= XK_A) && (sym <= XK_Z)) \ - *(lower) += (XK_a - XK_A); \ - else if ((sym >= XK_a) && (sym <= XK_z)) \ - *(upper) -= (XK_a - XK_A); \ - else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis)) \ - *(lower) += (XK_agrave - XK_Agrave); \ - else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis)) \ - *(upper) -= (XK_agrave - XK_Agrave); \ - else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn)) \ - *(lower) += (XK_oslash - XK_Ooblique); \ - else if ((sym >= XK_oslash) && (sym <= XK_thorn)) \ - *(upper) -= (XK_oslash - XK_Ooblique); \ -} -#endif + XDisplayKeycodes(dpy, &minkey, &maxkey); + keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), + &syms_per_keycode); -char *short_kmb(char *str) { - int i, saw_k = 0, saw_m = 0, saw_b = 0, n = 10; - char *p, tmp[10]; - - for (i=0; inext; + free(curr_remap); + curr_remap = next_remap; + } + keyremaps = NULL; } - if (allowed_input_view_only) { - free(allowed_input_view_only); + if (infile == NULL || *infile == '\0') { + /* just unset remapping */ + return; } - if (! allowed_input_str) { - allowed_input_normal = strdup("KMB"); - allowed_input_view_only = strdup(""); - } else { - char *p, *str = strdup(allowed_input_str); - p = strchr(str, ','); - if (p) { - allowed_input_view_only = strdup(p+1); - *p = '\0'; - allowed_input_normal = strdup(str); - } else { - allowed_input_normal = strdup(str); - allowed_input_view_only = strdup(""); + in = fopen(infile, "r"); + if (in == NULL) { + /* assume cmd line key1-key2,key3-key4 */ + if (! strchr(infile, '-') || (in = tmpfile()) == NULL) { + rfbLog("remap: cannot open: %s\n", infile); + rfbLogPerror("fopen"); + clean_up_exit(1); } - free(str); + p = infile; + while (*p) { + if (*p == '-') { + fprintf(in, " "); + } else if (*p == ',' || *p == ' ' || *p == '\t') { + fprintf(in, "\n"); + } else { + fprintf(in, "%c", *p); + } + p++; + } + fprintf(in, "\n"); + fflush(in); + rewind(in); } - - /* shorten them */ - str = short_kmb(allowed_input_normal); - free(allowed_input_normal); - allowed_input_normal = str; - - str = short_kmb(allowed_input_view_only); - free(allowed_input_view_only); - allowed_input_view_only = str; - - if (screen) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - ClientData *cd = (ClientData *) cl->clientData; - - if (cd->input[0] == '=') { - ; /* custom setting */ - } else if (cd->login_viewonly) { - if (*allowed_input_view_only != '\0') { - cl->viewOnly = FALSE; - cd->input[0] = '\0'; - strncpy(cd->input, - allowed_input_view_only, CILEN); - } else { - cl->viewOnly = TRUE; - } - } else { - if (allowed_input_normal) { - cd->input[0] = '\0'; - strncpy(cd->input, - allowed_input_normal, CILEN); - } + while (fgets(line, 256, in) != NULL) { + int isbtn = 0; + p = lblanks(line); + if (*p == '\0') { + continue; + } + if (strchr(line, '#')) { + continue; + } + if ( (q = strchr(line, '-')) != NULL) { + /* allow Keysym1-Keysym2 notation */ + *q = ' '; + } + + if (sscanf(line, "%s %s", str1, str2) != 2) { + rfbLog("remap: bad line: %s\n", line); + fclose(in); + clean_up_exit(1); + } + if (sscanf(str1, "0x%x", &i) == 1) { + ksym1 = (KeySym) i; + } else { + ksym1 = XStringToKeysym(str1); + } + if (sscanf(str2, "0x%x", &i) == 1) { + ksym2 = (KeySym) i; + } else { + ksym2 = XStringToKeysym(str2); + } + if (ksym2 == NoSymbol) { + int i; + if (sscanf(str2, "Button%d", &i) == 1) { + ksym2 = (KeySym) i; + isbtn = 1; } } - rfbReleaseClientIterator(iter); + if (ksym1 == NoSymbol || ksym2 == NoSymbol) { + rfbLog("warning: skipping bad remap line: %s", line); + continue; + } + remap = (keyremap_t *) malloc((size_t) sizeof(keyremap_t)); + remap->before = ksym1; + remap->after = ksym2; + remap->isbutton = isbtn; + remap->next = NULL; + rfbLog("remapping: (%s, 0x%x) -> (%s, 0x%x) isbtn=%d\n", str1, + ksym1, str2, ksym2, isbtn); + if (keyremaps == NULL) { + keyremaps = remap; + } else { + current->next = remap; + } + current = remap; } + fclose(in); } -void initialize_keyboard_and_pointer(void) { - - if (raw_fb && ! dpy) return; /* raw_fb hack */ +/* + * preliminary support for using the Xkb (XKEYBOARD) extension for handling + * user input. inelegant, slow, and incomplete currently... but initial + * tests show it is useful for some setups. + */ +typedef struct keychar { + KeyCode code; + int group; + int level; +} keychar_t; - if (use_modifier_tweak) { - initialize_modtweak(); - } - if (remap_file != NULL) { - initialize_remap(remap_file); - } +/* max number of key groups and shift levels we consider */ +#define GRP 4 +#define LVL 8 +static int lvl_max, grp_max, kc_min, kc_max; +static KeySym xkbkeysyms[0x100][GRP][LVL]; +static unsigned int xkbstate[0x100][GRP][LVL]; +static unsigned int xkbignore[0x100][GRP][LVL]; +static unsigned int xkbmodifiers[0x100][GRP][LVL]; +static int multi_key[0x100], mode_switch[0x100], skipkeycode[0x100]; +static int shift_keys[0x100]; - initialize_pointer_map(pointer_remap); +#if !LIBVNCSERVER_HAVE_XKEYBOARD - clear_modifiers(1); - if (clear_mods == 1) { - clear_modifiers(0); - } +/* empty functions for no xkb */ +static void initialize_xkb_modtweak(void) {} +static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, + rfbClientPtr client) { } -void initialize_modtweak(void) { - KeySym keysym, *keymap; - int i, j, minkey, maxkey, syms_per_keycode; +#else - if (use_xkb_modtweak) { - initialize_xkb_modtweak(); - return; - } - memset(modifiers, -1, sizeof(modifiers)); - for (i=0; i<0x100; i++) { - keycodes[i] = NoSymbol; - } +/* sets up all the keymapping info via Xkb API */ - X_LOCK; - XDisplayKeycodes(dpy, &minkey, &maxkey); +static void initialize_xkb_modtweak(void) { + KeySym ks; + int kc, grp, lvl, k; + unsigned int state; - keymap = XGetKeyboardMapping(dpy, minkey, (maxkey - minkey + 1), - &syms_per_keycode); +/* + * Here is a guide: - /* handle alphabetic char with only one keysym (no upper + lower) */ - for (i = minkey; i <= maxkey; i++) { - KeySym lower, upper; - /* 2nd one */ - keysym = keymap[(i - minkey) * syms_per_keycode + 1]; - if (keysym != NoSymbol) { - continue; - } - /* 1st one */ - keysym = keymap[(i - minkey) * syms_per_keycode + 0]; - if (keysym == NoSymbol) { - continue; - } - XConvertCase(keysym, &lower, &upper); - if (lower != upper) { - keymap[(i - minkey) * syms_per_keycode + 0] = lower; - keymap[(i - minkey) * syms_per_keycode + 1] = upper; - } - } - for (i = minkey; i <= maxkey; i++) { - if (debug_keyboard) { - if (i == minkey) { - rfbLog("initialize_modtweak: keycode -> " - "keysyms mapping info:\n"); - } - fprintf(stderr, " %03d ", i); - } - for (j = 0; j < syms_per_keycode; j++) { - if (debug_keyboard) { - char *sym; - sym = XKeysymToString(XKeycodeToKeysym(dpy, - i, j)); - fprintf(stderr, "%-18s ", sym ? sym : "null"); - if (j == syms_per_keycode - 1) { - fprintf(stderr, "\n"); - } - } - if (j >= 4) { - /* - * Something wacky in the keymapping. - * Ignore these non Shift/AltGr chords - * for now... - */ - continue; - } - keysym = keymap[ (i - minkey) * syms_per_keycode + j ]; - if ( keysym >= ' ' && keysym < 0x100 - && i == XKeysymToKeycode(dpy, keysym) ) { - keycodes[keysym] = i; - modifiers[keysym] = j; - } - } - } +Workarounds arrays: - left_shift_code = XKeysymToKeycode(dpy, XK_Shift_L); - right_shift_code = XKeysymToKeycode(dpy, XK_Shift_R); - altgr_code = XKeysymToKeycode(dpy, XK_Mode_switch); - iso_level3_code = NoSymbol; -#ifdef XK_ISO_Level3_Shift - iso_level3_code = XKeysymToKeycode(dpy, XK_ISO_Level3_Shift); -#endif +multi_key[] indicates which keycodes have Multi_key (Compose) + bound to them. +mode_switch[] indicates which keycodes have Mode_switch (AltGr) + bound to them. +shift_keys[] indicates which keycodes have Shift bound to them. +skipkeycode[] indicates which keycodes are to be skipped + for any lookups from -skip_keycodes option. - XFree ((void *) keymap); +Groups and Levels, here is an example: + + ^ -------- + | L2 | A AE | + shift | | + level L1 | a ae | + -------- + G1 G2 + + group -> - X_UNLOCK; -} +Traditionally this it all a key could do. L1 vs. L2 selected via Shift +and G1 vs. G2 selected via Mode_switch. Up to 4 Keysyms could be bound +to a key. See initialize_modtweak() for an example of using that type +of keymap from XGetKeyboardMapping(). -/* - * does the actual tweak: +Xkb gives us up to 4 groups and 63 shift levels per key, with the +situation being potentially different for each key. This is complicated, +and I don't claim to understand it all, but in the following we just think +of ranging over the group and level indices as covering all of the cases. +This gives us an accurate view of the keymap. The main tricky part +is mapping between group+level and modifier state. + +On current linux/XFree86 setups (Xkb is enabled by default) the +information from XGetKeyboardMapping() (evidently the compat map) +is incomplete and inaccurate, so we are really forced to use the +Xkb API. + +xkbkeysyms[] For a (keycode,group,level) holds the KeySym (0 for none) +xkbstate[] For a (keycode,group,level) holds the corresponding + modifier state needed to get that KeySym +xkbignore[] For a (keycode,group,level) which modifiers can be + ignored (the 0 bits can be ignored). +xkbmodifiers[] For the KeySym bound to this (keycode,group,level) store + the modifier mask. + * */ -static void tweak_mod(signed char mod, rfbBool down) { - rfbBool is_shift = mod_state & (LEFTSHIFT|RIGHTSHIFT); - Bool dn = (Bool) down; - KeyCode altgr = altgr_code; - if (mod < 0) { - if (debug_keyboard) { - rfbLog("tweak_mod: Skip: down=%d index=%d\n", down, - (int) mod); + /* initialize all the arrays: */ + for (kc = 0; kc < 0x100; kc++) { + multi_key[kc] = 0; + mode_switch[kc] = 0; + skipkeycode[kc] = 0; + shift_keys[kc] = 0; + + for (grp = 0; grp < GRP; grp++) { + for (lvl = 0; lvl < LVL; lvl++) { + xkbkeysyms[kc][grp][lvl] = NoSymbol; + xkbmodifiers[kc][grp][lvl] = -1; + xkbstate[kc][grp][lvl] = -1; + } } - return; - } - if (debug_keyboard) { - rfbLog("tweak_mod: Start: down=%d index=%d mod_state=0x%x" - " is_shift=%d\n", down, (int) mod, (int) mod_state, - is_shift); } - if (use_iso_level3 && iso_level3_code) { - altgr = iso_level3_code; + /* + * the array is 256*LVL*GRP, but we can make the searched region + * smaller by computing the actual ranges. + */ + lvl_max = 0; + grp_max = 0; + kc_max = 0; + kc_min = 0x100; + + /* + * loop over all possible (keycode, group, level) triples + * and record what we find for it: + */ + if (debug_keyboard > 1) { + rfbLog("initialize_xkb_modtweak: XKB keycode -> keysyms " + "mapping info:\n"); } + for (kc = 0; kc < 0x100; kc++) { + for (grp = 0; grp < GRP; grp++) { + for (lvl = 0; lvl < LVL; lvl++) { + unsigned int ms, mods; + int state_save = -1, mods_save; + KeySym ks2; - X_LOCK; - if (is_shift && mod != 1) { - if (mod_state & LEFTSHIFT) { - XTestFakeKeyEvent_wr(dpy, left_shift_code, !dn, CurrentTime); - } - if (mod_state & RIGHTSHIFT) { - XTestFakeKeyEvent_wr(dpy, right_shift_code, !dn, CurrentTime); + /* look up the Keysym, if any */ + ks = XkbKeycodeToKeysym(dpy, kc, grp, lvl); + xkbkeysyms[kc][grp][lvl] = ks; + + /* if no Keysym, on to next */ + if (ks == NoSymbol) { + continue; + } + /* + * for various workarounds, note where these special + * keys are bound to. + */ + if (ks == XK_Multi_key) { + multi_key[kc] = lvl+1; + } + if (ks == XK_Mode_switch) { + mode_switch[kc] = lvl+1; + } + if (ks == XK_Shift_L || ks == XK_Shift_R) { + shift_keys[kc] = lvl+1; + } + + /* + * record maximum extent for group/level indices + * and keycode range: + */ + if (grp > grp_max) { + grp_max = grp; + } + if (lvl > lvl_max) { + lvl_max = lvl; + } + if (kc > kc_max) { + kc_max = kc; + } + if (kc < kc_min) { + kc_min = kc; + } + + /* + * lookup on *keysym* (i.e. not kc, grp, lvl) + * and get the modifier mask. this is 0 for + * most keysyms, only non zero for modifiers. + */ + ms = XkbKeysymToModifiers(dpy, ks); + xkbmodifiers[kc][grp][lvl] = ms; + + /* + * Amusing heuristic (may have bugs). There are + * 8 modifier bits, so 256 possible modifier + * states. We loop over all of them for this + * keycode (simulating Key "events") and ask + * XkbLookupKeySym to tell us the Keysym. Once it + * matches the Keysym we have for this (keycode, + * group, level), gotten via XkbKeycodeToKeysym() + * above, we then (hopefully...) know that state + * of modifiers needed to generate this keysym. + * + * Yes... keep your fingers crossed. + * + * Note that many of the 256 states give the + * Keysym, we just need one, and we take the + * first one found. + */ + state = 0; + while(state < 256) { + if (XkbLookupKeySym(dpy, kc, state, &mods, + &ks2)) { + + /* save these for workaround below */ + if (state_save == -1) { + state_save = state; + mods_save = mods; + } + if (ks2 == ks) { + /* + * zero the irrelevant bits + * by anding with mods. + */ + xkbstate[kc][grp][lvl] + = state & mods; + /* + * also remember the irrelevant + * bits since it is handy. + */ + xkbignore[kc][grp][lvl] = mods; + + break; + } + } + state++; + } + if (xkbstate[kc][grp][lvl] == -1 && grp == 1) { + /* + * Hack on Solaris 9 for Mode_switch + * for Group2 characters. We force the + * Mode_switch modifier bit on. + * XXX Need to figure out better what is + * happening here. Is compat on somehow?? + */ + unsigned int ms2; + ms2 = XkbKeysymToModifiers(dpy, XK_Mode_switch); + + xkbstate[kc][grp][lvl] + = (state_save & mods_save) | ms2; + + xkbignore[kc][grp][lvl] = mods_save | ms2; + } + + if (debug_keyboard > 1) { + fprintf(stderr, " %03d G%d L%d mod=%s ", + kc, grp+1, lvl+1, bitprint(ms, 8)); + fprintf(stderr, "state=%s ", + bitprint(xkbstate[kc][grp][lvl], 8)); + fprintf(stderr, "ignore=%s ", + bitprint(xkbignore[kc][grp][lvl], 8)); + fprintf(stderr, " ks=0x%08lx \"%s\"\n", + ks, XKeysymToString(ks)); + } + } } } - if ( ! is_shift && mod == 1 ) { - XTestFakeKeyEvent_wr(dpy, left_shift_code, dn, CurrentTime); - } - if ( altgr && (mod_state & ALTGR) && mod != 2 ) { - XTestFakeKeyEvent_wr(dpy, altgr, !dn, CurrentTime); - } - if ( altgr && ! (mod_state & ALTGR) && mod == 2 ) { - XTestFakeKeyEvent_wr(dpy, altgr, dn, CurrentTime); + + /* + * process the user supplied -skip_keycodes string. + * This is presumably a list if "ghost" keycodes, the X server + * thinks they exist, but they do not. ghosts can lead to + * ambiguities in the reverse map: Keysym -> KeyCode + Modstate, + * so if we can ignore them so much the better. Presumably the + * user can never generate them from the physical keyboard. + * There may be other reasons to deaden some keys. + */ + if (skip_keycodes != NULL) { + char *p, *str = strdup(skip_keycodes); + p = strtok(str, ", \t\n\r"); + while (p) { + k = 1; + if (sscanf(p, "%d", &k) != 1 || k < 0 || k >= 0x100) { + rfbLog("bad skip_keycodes: %s %s\n", + skip_keycodes, p); + clean_up_exit(1); + } + skipkeycode[k] = 1; + p = strtok(NULL, ", \t\n\r"); + } + free(str); } - X_UNLOCK; - if (debug_keyboard) { - rfbLog("tweak_mod: Finish: down=%d index=%d mod_state=0x%x" - " is_shift=%d\n", down, (int) mod, (int) mod_state, - is_shift); + if (debug_keyboard > 1) { + fprintf(stderr, "grp_max=%d lvl_max=%d\n", grp_max, lvl_max); } } /* - * tweak the modifier under -modtweak + * Called on user keyboard input. Try to solve the reverse mapping + * problem: KeySym (from VNC client) => KeyCode(s) to press to generate + * it. The one-to-many KeySym => KeyCode mapping makes it difficult, as + * does working out what changes to the modifier keypresses are needed. */ -static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym, +static void xkb_tweak_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { - KeyCode k; - int tweak = 0; - if (use_xkb_modtweak) { - xkb_tweak_keyboard(down, keysym, client); - return; - } + int kc, grp, lvl, i; + int kc_f[0x100], grp_f[0x100], lvl_f[0x100], state_f[0x100], found; + unsigned int state; + + /* these are used for finding modifiers, etc */ + XkbStateRec kbstate; + int got_kbstate = 0; + int Kc_f, Grp_f, Lvl_f; + + X_LOCK; + if (debug_keyboard) { - rfbLog("modifier_tweak_keyboard: %s keysym=0x%x\n", - down ? "down" : "up", (int) keysym); - } + char *str = XKeysymToString(keysym); -#define ADJUSTMOD(sym, state) \ - if (keysym == sym) { \ - if (down) { \ - mod_state |= state; \ - } else { \ - mod_state &= ~state; \ - } \ - } + if (debug_keyboard > 1) fprintf(stderr, "\n"); - ADJUSTMOD(XK_Shift_L, LEFTSHIFT) - ADJUSTMOD(XK_Shift_R, RIGHTSHIFT) - ADJUSTMOD(XK_Mode_switch, ALTGR) + rfbLog("xkb_tweak_keyboard: %s keysym=0x%x \"%s\"\n", + down ? "down" : "up", (int) keysym, str ? str : "null"); + } - if ( down && keysym >= ' ' && keysym < 0x100 ) { - tweak = 1; - tweak_mod(modifiers[keysym], True); - k = keycodes[keysym]; - } else { - X_LOCK; - k = XKeysymToKeycode(dpy, (KeySym) keysym); - X_UNLOCK; - } - if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) { - int new_kc = add_keysym(keysym); - if (new_kc) { - k = new_kc; - } - } - if (debug_keyboard) { - rfbLog("modifier_tweak_keyboard: KeySym 0x%x \"%s\" -> " - "KeyCode 0x%x%s\n", (int) keysym, XKeysymToString(keysym), - (int) k, k ? "" : " *ignored*"); + /* + * set everything to not-yet-found. + * these "found" arrays (*_f) let us dyanamically consider the + * one-to-many Keysym -> Keycode issue. we set the size at 256, + * but of course only very few will be found. + */ + for (i = 0; i < 0x100; i++) { + kc_f[i] = -1; + grp_f[i] = -1; + lvl_f[i] = -1; + state_f[i] = -1; } - if ( k != NoSymbol ) { - X_LOCK; - XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime); - X_UNLOCK; - } + found = 0; - if ( tweak ) { - tweak_mod(modifiers[keysym], False); - } -} + /* + * loop over all (keycode, group, level) triples looking for + * matching keysyms. Amazingly this isn't slow (but maybe if + * you type really fast...). Hash lookup into a linked list of + * (keycode,grp,lvl) triples would be the way to improve this + * in the future if needed. + */ + for (kc = kc_min; kc <= kc_max; kc++) { + for (grp = 0; grp < grp_max+1; grp++) { + for (lvl = 0; lvl < lvl_max+1; lvl++) { + if (keysym != xkbkeysyms[kc][grp][lvl]) { + continue; + } + /* got a keysym match */ + state = xkbstate[kc][grp][lvl]; -typedef struct allowed_input { - int keystroke; - int motion; - int button; -} allowed_input_t; + if (debug_keyboard > 1) { + fprintf(stderr, " got match kc=%03d=0x%02x G%d" + " L%d ks=0x%x \"%s\" (basesym: \"%s\")\n", + kc, kc, grp+1, lvl+1, keysym, + XKeysymToString(keysym), XKeysymToString( + XKeycodeToKeysym(dpy, kc, 0))); + fprintf(stderr, " need state: %s\n", + bitprint(state, 8)); + fprintf(stderr, " ignorable : %s\n", + bitprint(xkbignore[kc][grp][lvl], 8)); + } -void get_allowed_input(rfbClientPtr client, allowed_input_t *input) { - ClientData *cd; - char *str; + /* save it if state is OK and not told to skip */ + if (state == -1) { + continue; + } + if (skipkeycode[kc] && debug_keyboard) { + fprintf(stderr, " xxx skipping keycode: %d " + "G%d/L%d\n", kc, grp+1, lvl+1); + } + if (skipkeycode[kc]) { + continue; + } + if (found > 0 && kc == kc_f[found-1]) { + /* ignore repeats for same keycode */ + continue; + } + kc_f[found] = kc; + grp_f[found] = grp; + lvl_f[found] = lvl; + state_f[found] = state; + found++; + } + } + } - input->keystroke = 0; - input->motion = 0; - input->button = 0; +#define PKBSTATE \ + fprintf(stderr, " --- current mod state:\n"); \ + fprintf(stderr, " mods : %s\n", bitprint(kbstate.mods, 8)); \ + fprintf(stderr, " base_mods : %s\n", bitprint(kbstate.base_mods, 8)); \ + fprintf(stderr, " latch_mods: %s\n", bitprint(kbstate.latched_mods, 8)); \ + fprintf(stderr, " lock_mods : %s\n", bitprint(kbstate.locked_mods, 8)); \ + fprintf(stderr, " compat : %s\n", bitprint(kbstate.compat_state, 8)); - if (! client) { - return; - } + /* + * Now get the current state of the keyboard from the X server. + * This seems to be the safest way to go as opposed to our + * keeping track of the modifier state on our own. Again, + * this is fortunately not too slow. + */ - cd = (ClientData *) client->clientData; - - if (cd->input[0] != '-') { - str = cd->input; - } else if (client->viewOnly) { - if (allowed_input_view_only) { - str = allowed_input_view_only; - } else { - str = ""; - } - } else { - if (allowed_input_normal) { - str = allowed_input_normal; - } else { - str = "KMB"; - } + if (debug_keyboard > 1) { + /* get state early for debug output */ + XkbGetState(dpy, XkbUseCoreKbd, &kbstate); + got_kbstate = 1; + PKBSTATE } - while (*str) { - if (*str == 'K') { - input->keystroke = 1; - } else if (*str == 'M') { - input->motion = 1; - } else if (*str == 'B') { - input->button = 1; + if (!found && add_keysyms && keysym && ! IsModifierKey(keysym)) { + int new_kc = add_keysym(keysym); + if (new_kc != 0) { + found = 1; + kc_f[0] = new_kc; + grp_f[0] = 0; + lvl_f[0] = 0; + state_f[0] = 0; } - str++; } -} - -/* for -pipeinput mode */ -void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { - int can_input = 0, uid; - allowed_input_t input; - char *name; - ClientData *cd = (ClientData *) client->clientData; - if (pipeinput_fh == NULL) { + if (!found && debug_keyboard) { + char *str = XKeysymToString(keysym); + fprintf(stderr, " *** NO key found for: 0x%x %s " + "*keystroke ignored*\n", keysym, str ? str : "null"); + } + if (!found) { + X_UNLOCK; return; } - if (! view_only) { - get_allowed_input(client, &input); - if (input.motion || input.button) { - can_input = 1; /* XXX distinguish later */ + /* + * we could optimize here if found > 1 + * e.g. minimize lvl or grp, or other things to give + * "safest" scenario to simulate the keystrokes. + * but for now we just take the first one we found. + */ + Kc_f = kc_f[0]; + Grp_f = grp_f[0]; + Lvl_f = lvl_f[0]; + state = state_f[0]; + + if (debug_keyboard && found > 1) { + int l; + char *str; + fprintf(stderr, " *** found more than one keycode: "); + for (l = 0; l < found; l++) { + fprintf(stderr, "%03d ", kc_f[l]); } + for (l = 0; l < found; l++) { + str = XKeysymToString(XKeycodeToKeysym(dpy,kc_f[l],0)); + fprintf(stderr, " \"%s\"", str ? str : "null"); + } + fprintf(stderr, ", using first one: %03d\n", Kc_f); } - uid = cd->uid; - if (! can_input) { - uid = -uid; - } - - X_LOCK; - name = XKeysymToString(keysym); - X_UNLOCK; - fprintf(pipeinput_fh, "Keysym %d %d %u %s %s\n", uid, down, - keysym, name, down ? "KeyPress" : "KeyRelease"); + if (down) { + /* + * need to set up the mods for tweaking and other workarounds + */ + int needmods[8], sentmods[8], Ilist[8], keystate[256]; + int involves_multi_key, shift_is_down; + int i, j, b, curr, need; + unsigned int ms; + KeySym ks; + Bool dn; - fflush(pipeinput_fh); - check_pipeinput(); -} + if (! got_kbstate) { + /* get the current modifier state if we haven't yet */ + XkbGetState(dpy, XkbUseCoreKbd, &kbstate); + } -/* - * key event handler. See the above functions for contortions for - * running under -modtweak. - */ -static rfbClientPtr last_keyboard_client = NULL; + /* + * needmods[] whether or not that modifier bit needs + * something done to it. + * < 0 means no, + * 0 means needs to go up. + * 1 means needs to go down. + * + * -1, -2, -3 are used for debugging info to indicate + * why nothing needs to be done with the modifier, see below. + * + * sentmods[] is the corresponding keycode to use + * to acheive the needmods[] requirement for the bit. + */ -void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { - KeyCode k; - int isbutton = 0; - allowed_input_t input; + for (i=0; i<8; i++) { + needmods[i] = -1; + sentmods[i] = 0; + } - if (debug_keyboard) { - char *str; - X_LOCK; - str = XKeysymToString(keysym); - rfbLog("keyboard(%s, 0x%x \"%s\")\n", down ? "down":"up", - (int) keysym, str ? str : "null"); - X_UNLOCK; - } + /* + * Loop over the 8 modifier bits and check if the current + * setting is what we need it to be or whether it should + * be changed (by us sending some keycode event) + * + * If nothing needs to be done to it record why: + * -1 the modifier bit is ignored. + * -2 the modifier bit is ignored, but is correct anyway. + * -3 the modifier bit is correct. + */ - if (pipeinput_fh != NULL) { - pipe_keyboard(down, keysym, client); - if (! pipeinput_tee) { - if (! view_only || raw_fb) { /* raw_fb hack */ - last_keyboard_client = client; - last_event = last_input = time(0); - got_user_input++; - got_keyboard_input++; + b = 0x1; + for (i=0; i<8; i++) { + curr = b & kbstate.mods; + need = b & state; + + if (! (b & xkbignore[Kc_f][Grp_f][Lvl_f])) { + /* irrelevant modifier bit */ + needmods[i] = -1; + if (curr == need) needmods[i] = -2; + } else if (curr == need) { + /* already correct */ + needmods[i] = -3; + } else if (! curr && need) { + /* need it down */ + needmods[i] = 1; + } else if (curr && ! need) { + /* need it up */ + needmods[i] = 0; } - return; + + b = b << 1; } - } - if (view_only) { - return; - } - get_allowed_input(client, &input); - if (! input.keystroke) { - return; - } + /* + * Again we dynamically probe the X server for information, + * this time for the state of all the keycodes. Useful + * info, and evidently is not too slow... + */ + get_keystate(keystate); - last_keyboard_client = client; - last_event = last_input = time(0); - got_user_input++; - got_keyboard_input++; - - if (raw_fb && ! dpy) return; /* raw_fb hack */ + /* + * We try to determine if Shift is down (since that can + * screw up ISO_Level3_Shift manipulations). + */ + shift_is_down = 0; - if (keyremaps) { - keyremap_t *remap = keyremaps; - while (remap != NULL) { - if (remap->before == keysym) { - keysym = remap->after; - isbutton = remap->isbutton; - if (debug_keyboard) { - X_LOCK; - rfbLog("keyboard(): remapping keysym: " - "0x%x \"%s\" -> 0x%x \"%s\"\n", - (int) remap->before, - XKeysymToString(remap->before), - (int) remap->after, - remap->isbutton ? "button" : - XKeysymToString(remap->after)); - X_UNLOCK; - } + for (kc = kc_min; kc <= kc_max; kc++) { + if (skipkeycode[kc] && debug_keyboard) { + fprintf(stderr, " xxx skipping keycode: " + "%d\n", kc); + } + if (skipkeycode[kc]) { + continue; + } + if (shift_keys[kc] && keystate[kc]) { + shift_is_down = kc; break; } - remap = remap->next; - } - } - - if (isbutton) { - int button = (int) keysym; - if (! down) { - return; /* nothing to send */ - } - if (debug_keyboard) { - rfbLog("keyboard(): remapping keystroke to button %d" - " click\n", button); - } - if (button < 1 || button > num_buttons) { - rfbLog("keyboard(): ignoring mouse button out of " - "bounds: %d\n", button); - return; } - X_LOCK; - XTestFakeButtonEvent_wr(dpy, button, True, CurrentTime); - XTestFakeButtonEvent_wr(dpy, button, False, CurrentTime); - XFlush(dpy); - X_UNLOCK; - return; - } - - if (use_modifier_tweak) { - modifier_tweak_keyboard(down, keysym, client); - X_LOCK; - XFlush(dpy); - X_UNLOCK; - return; - } - - X_LOCK; - k = XKeysymToKeycode(dpy, (KeySym) keysym); + /* + * Now loop over the modifier bits and try to deduce the + * keycode presses/release require to match the desired + * state. + */ + for (i=0; i<8; i++) { + if (needmods[i] < 0 && debug_keyboard > 1) { + int k = -needmods[i] - 1; + char *words[] = {"ignorable", + "bitset+ignorable", "bitset"}; + fprintf(stderr, " +++ needmods: mod=%d is " + "OK (%s)\n", i, words[k]); + } + if (needmods[i] < 0) { + continue; + } - if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) { - int new_kc = add_keysym(keysym); - if (new_kc) { - k = new_kc; - } - } - if (debug_keyboard) { - char *str = XKeysymToString(keysym); - rfbLog("keyboard(): KeySym 0x%x \"%s\" -> KeyCode 0x%x%s\n", - (int) keysym, str ? str : "null", (int) k, - k ? "" : " *ignored*"); - } + b = 1 << i; - if ( k != NoSymbol ) { - XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime); - XFlush(dpy); - } + if (debug_keyboard > 1) { + fprintf(stderr, " +++ needmods: mod=%d %s " + "need it to be: %d %s\n", i, bitprint(b, 8), + needmods[i], needmods[i] ? "down" : "up"); + } - X_UNLOCK; -} + /* + * Again, an inefficient loop, this time just + * looking for modifiers... + */ + for (kc = kc_min; kc <= kc_max; kc++) { + for (grp = 0; grp < grp_max+1; grp++) { + for (lvl = 0; lvl < lvl_max+1; lvl++) { + int skip = 1, dbmsg = 0; -/* -- pointer.c -- */ -/* - * pointer event (motion and button click) handling routines. - */ -typedef struct ptrremap { - KeySym keysym; - KeyCode keycode; - int end; - int button; - int down; - int up; -} prtremap_t; + ms = xkbmodifiers[kc][grp][lvl]; + if (! ms || ms != b) { + continue; + } -MUTEX(pointerMutex); -#define MAX_BUTTONS 5 -#define MAX_BUTTON_EVENTS 50 -static prtremap_t pointer_map[MAX_BUTTONS+1][MAX_BUTTON_EVENTS]; + if (skipkeycode[kc] && debug_keyboard) { + fprintf(stderr, " xxx skipping " + "keycode: %d G%d/L%d\n", + kc, grp+1, lvl+1); + } + if (skipkeycode[kc]) { + continue; + } -/* - * For parsing the -buttonmap sections, e.g. "4" or ":Up+Up+Up:" - */ -static void buttonparse(int from, char **s) { - char *q; - int to, i; - int modisdown[256]; + ks = xkbkeysyms[kc][grp][lvl]; + if (! ks) { + continue; + } - q = *s; + if (ks == XK_Shift_L) { + skip = 0; + } else if (ks == XK_Shift_R) { + skip = 0; + } else if (ks == XK_Mode_switch) { + skip = 0; + } else if (ks == XK_ISO_Level3_Shift) { + skip = 0; + } + /* + * Alt, Meta, Control, Super, + * Hyper, Num, Caps are skipped. + * + * XXX need more work on Locks, + * and non-standard modifiers. + * (e.g. XF86_Next_VMode using + * Ctrl+Alt) + */ + if (debug_keyboard > 1) { + char *str = XKeysymToString(ks); + int kt = keystate[kc]; + fprintf(stderr, " === for " + "mod=%s found kc=%03d/G%d" + "/L%d it is %d %s skip=%d " + "(%s)\n", bitprint(b,8), kc, + grp+1, lvl+1, kt, kt ? + "down" : "up ", skip, + str ? str : "null"); + } - for (i=0; i<256; i++) { - modisdown[i] = 0; - } + if (! skip && needmods[i] != + keystate[kc] && sentmods[i] == 0) { + sentmods[i] = kc; + dbmsg = 1; + } - if (*q == ':') { - /* :sym1+sym2+...+symN: format */ - int l = 0, n = 0; - char list[1000]; - char *t, *kp = q + 1; - KeyCode kcode; - - while (*(kp+l) != ':' && *(kp+l) != '\0') { - /* loop to the matching ':' */ - l++; - if (l >= 1000) { - rfbLog("buttonparse: keysym list too long: " - "%s\n", q); - break; + if (debug_keyboard > 1 && dbmsg) { + int nm = needmods[i]; + fprintf(stderr, " >>> we " + "choose kc=%03d=0x%02x to " + "change it to: %d %s\n", kc, + kc, nm, nm ? "down" : "up"); + } + + } + } } } - *(kp+l) = '\0'; - strncpy(list, kp, l); - list[l] = '\0'; - rfbLog("remap button %d using \"%s\"\n", from, list); - - /* loop over tokens separated by '+' */ - t = strtok(list, "+"); - while (t) { - KeySym ksym; - int i; - if (n >= MAX_BUTTON_EVENTS - 20) { - rfbLog("buttonparse: too many button map " - "events: %s\n", list); - break; - } - if (sscanf(t, "0x%x", &i) == 1) { - ksym = (KeySym) i; /* hex value */ + for (i=0; i<8; i++) { + /* + * reverse order is useful for tweaking + * ISO_Level3_Shift before Shift, but assumes they + * are in that order (i.e. Shift is first bit). + */ + int reverse = 1; + if (reverse) { + Ilist[i] = 7 - i; } else { - X_LOCK; - ksym = XStringToKeysym(t); /* string value */ - X_UNLOCK; + Ilist[i] = i; } - if (ksym == NoSymbol) { - /* see if Button "keysym" was used: */ - if (sscanf(t, "Button%d", &i) == 1) { - rfbLog(" event %d: button %d\n", - from, n+1, i); - if (i == 0) i = -1; /* bah */ - pointer_map[from][n].keysym = NoSymbol; - pointer_map[from][n].keycode = NoSymbol; - pointer_map[from][n].button = i; - pointer_map[from][n].end = 0; - pointer_map[from][n].down = 0; - pointer_map[from][n].up = 0; - } else { - rfbLog("buttonparse: ignoring unknown " - "keysym: %s\n", t); - n--; - } - } else { - /* - * XXX may not work with -modtweak or -xkb - */ - X_LOCK; - kcode = XKeysymToKeycode(dpy, ksym); + } - pointer_map[from][n].keysym = ksym; - pointer_map[from][n].keycode = kcode; - pointer_map[from][n].button = 0; - pointer_map[from][n].end = 0; - if (! ismodkey(ksym) ) { - /* do both down then up */ - pointer_map[from][n].down = 1; - pointer_map[from][n].up = 1; - } else { - if (modisdown[kcode]) { - pointer_map[from][n].down = 0; - pointer_map[from][n].up = 1; - modisdown[kcode] = 0; - } else { - pointer_map[from][n].down = 1; - pointer_map[from][n].up = 0; - modisdown[kcode] = 1; - } - } - rfbLog(" event %d: keysym %s (0x%x) -> " - "keycode 0x%x down=%d up=%d\n", n+1, - XKeysymToString(ksym), ksym, kcode, - pointer_map[from][n].down, - pointer_map[from][n].up); - X_UNLOCK; + /* + * check to see if Multi_key is bound to one of the Mods + * we have to tweak + */ + involves_multi_key = 0; + for (j=0; j<8; j++) { + i = Ilist[j]; + if (sentmods[i] == 0) continue; + dn = (Bool) needmods[i]; + if (!dn) continue; + if (multi_key[sentmods[i]]) { + involves_multi_key = i+1; } - t = strtok(NULL, "+"); - n++; } - /* we must release any modifiers that are still down: */ - for (i=0; i<256; i++) { - kcode = (KeyCode) i; - if (n >= MAX_BUTTON_EVENTS) { - rfbLog("buttonparse: too many button map " - "events: %s\n", list); - break; + if (involves_multi_key && shift_is_down && needmods[0] < 0) { + /* + * Workaround for Multi_key and shift. + * Assumes Shift is bit 1 (needmods[0]) + */ + if (debug_keyboard) { + fprintf(stderr, " ^^^ trying to avoid " + "inadvertent Multi_key from Shift " + "(doing %03d up now)\n", shift_is_down); } - if (modisdown[kcode]) { - pointer_map[from][n].keysym = NoSymbol; - pointer_map[from][n].keycode = kcode; - pointer_map[from][n].button = 0; - pointer_map[from][n].end = 0; - pointer_map[from][n].down = 0; - pointer_map[from][n].up = 1; - modisdown[kcode] = 0; - n++; + XTestFakeKeyEvent_wr(dpy, shift_is_down, False, + CurrentTime); + } else { + involves_multi_key = 0; + } + + for (j=0; j<8; j++) { + /* do the Mod ups */ + i = Ilist[j]; + if (sentmods[i] == 0) continue; + dn = (Bool) needmods[i]; + if (dn) continue; + XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime); + } + for (j=0; j<8; j++) { + /* next, do the Mod downs */ + i = Ilist[j]; + if (sentmods[i] == 0) continue; + dn = (Bool) needmods[i]; + if (!dn) continue; + XTestFakeKeyEvent_wr(dpy, sentmods[i], dn, CurrentTime); + } + + if (involves_multi_key) { + /* + * Reverse workaround for Multi_key and shift. + */ + if (debug_keyboard) { + fprintf(stderr, " vvv trying to avoid " + "inadvertent Multi_key from Shift " + "(doing %03d down now)\n", shift_is_down); } + XTestFakeKeyEvent_wr(dpy, shift_is_down, True, + CurrentTime); } - /* advance the source pointer position */ - (*s) += l+2; - } else { - /* single digit format */ - char str[2]; - str[0] = *q; - str[1] = '\0'; + /* + * With the above modifier work done, send the actual keycode: + */ + XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime); - to = atoi(str); - if (to < 1) { - rfbLog("skipping invalid remap button \"%d\" for button" - " %d from string \"%s\"\n", - to, from, str); - } else { - rfbLog("remap button %d using \"%s\"\n", from, str); - rfbLog(" button: %d -> %d\n", from, to); - pointer_map[from][0].keysym = NoSymbol; - pointer_map[from][0].keycode = NoSymbol; - pointer_map[from][0].button = to; - pointer_map[from][0].end = 0; - pointer_map[from][0].down = 0; - pointer_map[from][0].up = 0; + /* + * Now undo the modifier work: + */ + for (j=7; j>=0; j--) { + /* reverse Mod downs we did */ + i = Ilist[j]; + if (sentmods[i] == 0) continue; + dn = (Bool) needmods[i]; + if (!dn) continue; + XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn, + CurrentTime); } - /* advance the source pointer position */ - (*s)++; + for (j=7; j>=0; j--) { + /* finally reverse the Mod ups we did */ + i = Ilist[j]; + if (sentmods[i] == 0) continue; + dn = (Bool) needmods[i]; + if (dn) continue; + XTestFakeKeyEvent_wr(dpy, sentmods[i], !dn, + CurrentTime); + } + + } else { /* for up case, hopefully just need to pop it up: */ + + XTestFakeKeyEvent_wr(dpy, Kc_f, (Bool) down, CurrentTime); } + X_UNLOCK; } +#endif /* - * process the -buttonmap string + * For tweaking modifiers wrt the Alt-Graph key, etc. */ -void initialize_pointer_map(char *pointer_remap) { - unsigned char map[MAX_BUTTONS]; - int i, k; - /* - * This routine counts the number of pointer buttons on the X - * server (to avoid problems, even crashes, if a client has more - * buttons). And also initializes any pointer button remapping - * from -buttonmap option. - */ - - X_LOCK; - num_buttons = XGetPointerMapping(dpy, map, MAX_BUTTONS); - X_UNLOCK; +#define LEFTSHIFT 1 +#define RIGHTSHIFT 2 +#define ALTGR 4 +static char mod_state = 0; - if (num_buttons < 0) { - num_buttons = 0; - } +static char modifiers[0x100]; +static KeyCode keycodes[0x100]; +static KeyCode left_shift_code, right_shift_code, altgr_code, iso_level3_code; - /* FIXME: should use info in map[] */ - for (i=1; i<= MAX_BUTTONS; i++) { - for (k=0; k < MAX_BUTTON_EVENTS; k++) { - pointer_map[i][k].end = 1; - } - pointer_map[i][0].keysym = NoSymbol; - pointer_map[i][0].keycode = NoSymbol; - pointer_map[i][0].button = i; - pointer_map[i][0].end = 0; - pointer_map[i][0].down = 0; - pointer_map[i][0].up = 0; +/* workaround for X11R5, Latin 1 only */ +#ifndef XConvertCase +#define XConvertCase(sym, lower, upper) \ +*(lower) = sym; \ +*(upper) = sym; \ +if (sym >> 8 == 0) { \ + if ((sym >= XK_A) && (sym <= XK_Z)) \ + *(lower) += (XK_a - XK_A); \ + else if ((sym >= XK_a) && (sym <= XK_z)) \ + *(upper) -= (XK_a - XK_A); \ + else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis)) \ + *(lower) += (XK_agrave - XK_Agrave); \ + else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis)) \ + *(upper) -= (XK_agrave - XK_Agrave); \ + else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn)) \ + *(lower) += (XK_oslash - XK_Ooblique); \ + else if ((sym >= XK_oslash) && (sym <= XK_thorn)) \ + *(upper) -= (XK_oslash - XK_Ooblique); \ +} +#endif + +char *short_kmb(char *str) { + int i, saw_k = 0, saw_m = 0, saw_b = 0, n = 10; + char *p, tmp[10]; + + for (i=0; iclientData; + + if (cd->input[0] == '=') { + ; /* custom setting */ + } else if (cd->login_viewonly) { + if (*allowed_input_view_only != '\0') { + cl->viewOnly = FALSE; + cd->input[0] = '\0'; + strncpy(cd->input, + allowed_input_view_only, CILEN); + } else { + cl->viewOnly = TRUE; + } + } else { + if (allowed_input_normal) { + cd->input[0] = '\0'; + strncpy(cd->input, + allowed_input_normal, CILEN); + } } } - free(remap); - } -} - -/* - * For use in the -wireframe stuff, save the stacking order of the direct - * children of the root window. Ideally done before we send ButtonPress - * to the X server. - */ -void snapshot_stack_list(void) { - Window r, w; - if (stack_list) { - XFree(stack_list); - stack_list = NULL; - stack_num = 0; - } - XSync(dpy, False); - if (! XQueryTree(dpy, rootwin, &r, &w, &stack_list, &stack_num)) { - stack_list = NULL; - stack_num = 0; + rfbReleaseClientIterator(iter); } } -/* - * Send a pointer position event to the X server. - */ -static void update_x11_pointer_position(int x, int y) { +void initialize_keyboard_and_pointer(void) { if (raw_fb && ! dpy) return; /* raw_fb hack */ - X_LOCK; - if (use_xwarppointer) { - /* - * off_x and off_y not needed with XWarpPointer since - * window is used: - */ - XWarpPointer(dpy, None, window, 0, 0, 0, 0, x + coff_x, - y + coff_y); - } else { - XTestFakeMotionEvent_wr(dpy, scr, x + off_x + coff_x, - y + off_y + coff_y, CurrentTime); + if (use_modifier_tweak) { + initialize_modtweak(); + } + if (remap_file != NULL) { + initialize_remap(remap_file); } - X_UNLOCK; - - cursor_x = x; - cursor_y = y; - - /* record the x, y position for the rfb screen as well. */ - cursor_position(x, y); - /* change the cursor shape if necessary */ - set_cursor(x, y, get_which_cursor()); + initialize_pointer_map(pointer_remap); - last_event = last_input = time(0); + clear_modifiers(1); + if (clear_mods == 1) { + clear_modifiers(0); + } } -/* - * Send a pointer button event to the X server. - */ -static void update_x11_pointer_mask(int mask) { - int i, mb; - - last_event = last_input = time(0); +void initialize_modtweak(void) { + KeySym keysym, *keymap; + int i, j, minkey, maxkey, syms_per_keycode; - if (raw_fb && ! dpy) return; /* raw_fb hack */ + if (use_xkb_modtweak) { + initialize_xkb_modtweak(); + return; + } + memset(modifiers, -1, sizeof(modifiers)); + for (i=0; i<0x100; i++) { + keycodes[i] = NoSymbol; + } X_LOCK; - if (mask && !button_mask) { - /* button down, snapshot the stacking list before flushing */ - if (wireframe && !wireframe_in_progress && - strcmp(wireframe_copyrect, "never")) { - snapshot_stack_list(); - } - } + XDisplayKeycodes(dpy, &minkey, &maxkey); - /* look for buttons that have be clicked or released: */ - for (i=0; i < MAX_BUTTONS; i++) { - if ( (button_mask & (1< " - "0x%x button: %d\n", button_mask, mask,i+1); - } - for (k=0; k < MAX_BUTTON_EVENTS; k++) { - int bmask = (mask & (1< " + "keysyms mapping info:\n"); } - - if (pointer_map[i+1][k].button) { - /* sent button up or down */ - mb = pointer_map[i+1][k].button; - if ((num_buttons && mb > num_buttons) - || mb < 1) { - rfbLog("ignoring mouse button out of " - "bounds: %d>%d mask: 0x%x -> 0x%x\n", - mb, num_buttons, button_mask, mask); - continue; - } - if (debug_pointer) { - rfbLog("pointer(): sending button %d" - " %s (event %d)\n", mb, bmask - ? "down" : "up", k+1); - } - XTestFakeButtonEvent_wr(dpy, mb, (mask & (1<= 4) { + /* + * Something wacky in the keymapping. + * Ignore these non Shift/AltGr chords + * for now... + */ + continue; + } + keysym = keymap[ (i - minkey) * syms_per_keycode + j ]; + if ( keysym >= ' ' && keysym < 0x100 + && i == XKeysymToKeycode(dpy, keysym) ) { + keycodes[keysym] = i; + modifiers[keysym] = j; + } } - } } + left_shift_code = XKeysymToKeycode(dpy, XK_Shift_L); + right_shift_code = XKeysymToKeycode(dpy, XK_Shift_R); + altgr_code = XKeysymToKeycode(dpy, XK_Mode_switch); + iso_level3_code = NoSymbol; +#ifdef XK_ISO_Level3_Shift + iso_level3_code = XKeysymToKeycode(dpy, XK_ISO_Level3_Shift); +#endif + + XFree ((void *) keymap); X_UNLOCK; +} - /* - * Remember the button state for next time and also for the - * -nodragging case: - */ - button_mask_prev = button_mask; - button_mask = mask; +/* + * does the actual tweak: + */ +static void tweak_mod(signed char mod, rfbBool down) { + rfbBool is_shift = mod_state & (LEFTSHIFT|RIGHTSHIFT); + Bool dn = (Bool) down; + KeyCode altgr = altgr_code; + + if (mod < 0) { + if (debug_keyboard) { + rfbLog("tweak_mod: Skip: down=%d index=%d\n", down, + (int) mod); + } + return; + } + if (debug_keyboard) { + rfbLog("tweak_mod: Start: down=%d index=%d mod_state=0x%x" + " is_shift=%d\n", down, (int) mod, (int) mod_state, + is_shift); + } + + if (use_iso_level3 && iso_level3_code) { + altgr = iso_level3_code; + } + + X_LOCK; + if (is_shift && mod != 1) { + if (mod_state & LEFTSHIFT) { + XTestFakeKeyEvent_wr(dpy, left_shift_code, !dn, CurrentTime); + } + if (mod_state & RIGHTSHIFT) { + XTestFakeKeyEvent_wr(dpy, right_shift_code, !dn, CurrentTime); + } + } + if ( ! is_shift && mod == 1 ) { + XTestFakeKeyEvent_wr(dpy, left_shift_code, dn, CurrentTime); + } + if ( altgr && (mod_state & ALTGR) && mod != 2 ) { + XTestFakeKeyEvent_wr(dpy, altgr, !dn, CurrentTime); + } + if ( altgr && ! (mod_state & ALTGR) && mod == 2 ) { + XTestFakeKeyEvent_wr(dpy, altgr, dn, CurrentTime); + } + X_UNLOCK; + if (debug_keyboard) { + rfbLog("tweak_mod: Finish: down=%d index=%d mod_state=0x%x" + " is_shift=%d\n", down, (int) mod, (int) mod_state, + is_shift); + } } -/* for -pipeinput */ +/* + * tweak the modifier under -modtweak + */ +static void modifier_tweak_keyboard(rfbBool down, rfbKeySym keysym, + rfbClientPtr client) { + KeyCode k; + int tweak = 0; + + if (use_xkb_modtweak) { + xkb_tweak_keyboard(down, keysym, client); + return; + } + if (debug_keyboard) { + rfbLog("modifier_tweak_keyboard: %s keysym=0x%x\n", + down ? "down" : "up", (int) keysym); + } +#define ADJUSTMOD(sym, state) \ + if (keysym == sym) { \ + if (down) { \ + mod_state |= state; \ + } else { \ + mod_state &= ~state; \ + } \ + } -void pipe_pointer(int mask, int x, int y, rfbClientPtr client) { + ADJUSTMOD(XK_Shift_L, LEFTSHIFT) + ADJUSTMOD(XK_Shift_R, RIGHTSHIFT) + ADJUSTMOD(XK_Mode_switch, ALTGR) + + if ( down && keysym >= ' ' && keysym < 0x100 ) { + tweak = 1; + tweak_mod(modifiers[keysym], True); + k = keycodes[keysym]; + } else { + X_LOCK; + k = XKeysymToKeycode(dpy, (KeySym) keysym); + X_UNLOCK; + } + if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) { + int new_kc = add_keysym(keysym); + if (new_kc) { + k = new_kc; + } + } + if (debug_keyboard) { + rfbLog("modifier_tweak_keyboard: KeySym 0x%x \"%s\" -> " + "KeyCode 0x%x%s\n", (int) keysym, XKeysymToString(keysym), + (int) k, k ? "" : " *ignored*"); + } + if ( k != NoSymbol ) { + X_LOCK; + XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime); + X_UNLOCK; + } + + if ( tweak ) { + tweak_mod(modifiers[keysym], False); + } +} + +typedef struct allowed_input { + int keystroke; + int motion; + int button; +} allowed_input_t; + +void get_allowed_input(rfbClientPtr client, allowed_input_t *input) { + ClientData *cd; + char *str; + + input->keystroke = 0; + input->motion = 0; + input->button = 0; + + if (! client) { + return; + } + + cd = (ClientData *) client->clientData; + + if (cd->input[0] != '-') { + str = cd->input; + } else if (client->viewOnly) { + if (allowed_input_view_only) { + str = allowed_input_view_only; + } else { + str = ""; + } + } else { + if (allowed_input_normal) { + str = allowed_input_normal; + } else { + str = "KMB"; + } + } + + while (*str) { + if (*str == 'K') { + input->keystroke = 1; + } else if (*str == 'M') { + input->motion = 1; + } else if (*str == 'B') { + input->button = 1; + } + str++; + } +} + +/* for -pipeinput mode */ +void pipe_keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { int can_input = 0, uid; allowed_input_t input; + char *name; ClientData *cd = (ClientData *) client->clientData; - char hint[MAX_BUTTONS * 20]; if (pipeinput_fh == NULL) { return; @@ -6622,88 +7071,46 @@ void pipe_pointer(int mask, int x, int y, rfbClientPtr client) { uid = -uid; } - hint[0] = '\0'; - if (mask == button_mask) { - strcat(hint, "None"); - } else { - int i, old, new, m = 1, cnt = 0; - for (i=0; i= 0) { - static int show_motion = -1; - if (show_motion == -1) { - if (getenv("X11VNC_DB_NOMOTION")) { - show_motion = 0; - } else { - show_motion = 1; - } - } - if (show_motion) { - rfbLog("pointer(mask: 0x%x, x:%4d, y:%4d)\n", - mask, x, y); - } - } +void keyboard(rfbBool down, rfbKeySym keysym, rfbClientPtr client) { + KeyCode k; + int isbutton = 0; + allowed_input_t input; - if (scaling) { - /* map from rfb size to X11 size: */ - x = ((double) x / scaled_x) * dpy_x; - x = nfix(x, dpy_x); - y = ((double) y / scaled_y) * dpy_y; - y = nfix(y, dpy_y); + if (debug_keyboard) { + char *str; + X_LOCK; + str = XKeysymToString(keysym); + rfbLog("keyboard(%s, 0x%x \"%s\")\n", down ? "down":"up", + (int) keysym, str ? str : "null"); + X_UNLOCK; } - if (pipeinput_fh != NULL && mask >= 0) { - pipe_pointer(mask, x, y, client); + if (pipeinput_fh != NULL) { + pipe_keyboard(down, keysym, client); if (! pipeinput_tee) { if (! view_only || raw_fb) { /* raw_fb hack */ + last_keyboard_client = client; + last_event = last_input = time(0); + last_keysym = keysym; got_user_input++; got_keyboard_input++; - last_pointer_client = client; - } - if (view_only && raw_fb) { - /* raw_fb hack track button state */ - button_mask_prev = button_mask; - button_mask = mask; } return; } @@ -6713,1960 +7120,2742 @@ void pointer(int mask, int x, int y, rfbClientPtr client) { return; } get_allowed_input(client, &input); - if (! input.motion && ! input.button) { + if (! input.keystroke) { return; } - if (mask >= 0) { - /* - * mask = -1 is a special case call from scan_for_updates() - * to flush the event queue; there is no real pointer event. - */ - got_user_input++; - got_pointer_input++; - last_pointer_client = client; - } - - /* - * The following is hopefully an improvement wrt response during - * pointer user input (window drags) for the threaded case. - * See check_user_input() for the more complicated things we do - * in the non-threaded case. - */ - if (use_threads && pointer_mode != 1) { -# define NEV 32 - /* storage for the event queue */ - static int mutex_init = 0; - static int nevents = 0; - static int ev[NEV][3]; - int i; - /* timer things */ - static double dt = 0.0, tmr = 0.0, maxwait = 0.4; - - if (! mutex_init) { - INIT_MUTEX(pointerMutex); - mutex_init = 1; - } - LOCK(pointerMutex); - - /* - * If the framebuffer is being copied in another thread - * (scan_for_updates()), we will queue up to 32 pointer - * events for later. The idea is by delaying these input - * events, the screen is less likely to change during the - * copying period, and so will give rise to less window - * "tearing". - * - * Tearing is not completely eliminated because we do - * not suspend work in the other libvncserver threads. - * Maybe that is a possibility with a mutex... - */ - if (fb_copy_in_progress && mask >= 0) { - /* - * mask = -1 is an all-clear signal from - * scan_for_updates(). - * - * dt is a timer in seconds; we only queue for so long. - */ - dt += dtime(&tmr); + last_keyboard_client = client; + last_event = last_input = time(0); + last_keysym = keysym; + got_user_input++; + got_keyboard_input++; + + if (raw_fb && ! dpy) return; /* raw_fb hack */ - if (nevents < NEV && dt < maxwait) { - i = nevents++; - ev[i][0] = mask; - ev[i][1] = x; - ev[i][2] = y; - if (! input.button) { - ev[i][0] = -1; - } - if (! input.motion) { - ev[i][1] = -1; - ev[i][2] = -1; - } - UNLOCK(pointerMutex); - if (debug_pointer) { - rfbLog("pointer(): deferring event " - "%d\n", i); + if (keyremaps) { + keyremap_t *remap = keyremaps; + while (remap != NULL) { + if (remap->before == keysym) { + keysym = remap->after; + isbutton = remap->isbutton; + if (debug_keyboard) { + X_LOCK; + rfbLog("keyboard(): remapping keysym: " + "0x%x \"%s\" -> 0x%x \"%s\"\n", + (int) remap->before, + XKeysymToString(remap->before), + (int) remap->after, + remap->isbutton ? "button" : + XKeysymToString(remap->after)); + X_UNLOCK; } - return; + break; } + remap = remap->next; } + } - /* time to send the queue */ - for (i=0; i= 0) { - update_x11_pointer_position(ev[i][1], ev[i][2]); - } - if (ev[i][0] >= 0) { - update_x11_pointer_mask(ev[i][0]); - } + if (isbutton) { + int button = (int) keysym; + if (! down) { + return; /* nothing to send */ } - if (nevents && dt > maxwait) { - X_LOCK; - if (dpy) { /* raw_fb hack */ - XFlush(dpy); - } - X_UNLOCK; + if (debug_keyboard) { + rfbLog("keyboard(): remapping keystroke to button %d" + " click\n", button); } - nevents = 0; /* reset everything */ - tmr = 0.0; - dt = 0.0; - dtime(&tmr); - - UNLOCK(pointerMutex); - } - if (mask < 0) { /* -1 just means flush the event queue */ - if (debug_pointer > 1) { - rfbLog("pointer(): flush only.\n"); + if (button < 1 || button > num_buttons) { + rfbLog("keyboard(): ignoring mouse button out of " + "bounds: %d\n", button); + return; } + X_LOCK; + XTestFakeButtonEvent_wr(dpy, button, True, CurrentTime); + XTestFakeButtonEvent_wr(dpy, button, False, CurrentTime); + XFlush(dpy); + X_UNLOCK; return; } - /* update the X display with the event: */ - if (input.motion) { - update_x11_pointer_position(x, y); - sent = 1; - } - if (input.button) { - if (mask != button_mask) { - button_change_x = cursor_x; - button_change_y = cursor_y; + if (use_xrecord && ! xrecording && down) { + if (scaling && ! got_scrollcopyrect) { + ; + } else if (!strcmp(scroll_copyrect, "never")) { + ; + } else if (!strcmp(scroll_copyrect, "mouse")) { + ; + } else if (! xrecord_skip_keysym(keysym)) { + snapshot_stack_list(0, 0.25); + xrecord_watch(1); + xrecord_set_by_keys = 1; } - update_x11_pointer_mask(mask); - sent = 1; } - if (nofb && sent) { - /* - * nofb is for, e.g. Win2VNC, where fastest pointer - * updates are desired. - */ + if (use_modifier_tweak) { + modifier_tweak_keyboard(down, keysym, client); X_LOCK; XFlush(dpy); X_UNLOCK; + return; } -} -int check_pipeinput(void) { - if (! pipeinput_fh) { - return 1; + X_LOCK; + + k = XKeysymToKeycode(dpy, (KeySym) keysym); + + if (k == NoSymbol && add_keysyms && ! IsModifierKey(keysym)) { + int new_kc = add_keysym(keysym); + if (new_kc) { + k = new_kc; + } } - if (ferror(pipeinput_fh)) { - rfbLog("pipeinput pipe has ferror. %p\n", pipeinput_fh); - - if (pipeinput_opts && strstr(pipeinput_opts, "reopen")) { - rfbLog("restarting -pipeinput pipe...\n"); - initialize_pipeinput(); - if (pipeinput_fh) { - return 1; - } else { - return 0; - } - } else { - rfbLog("closing -pipeinput pipe...\n"); - pclose(pipeinput_fh); - pipeinput_fh = NULL; - return 0; - } + if (debug_keyboard) { + char *str = XKeysymToString(keysym); + rfbLog("keyboard(): KeySym 0x%x \"%s\" -> KeyCode 0x%x%s\n", + (int) keysym, str ? str : "null", (int) k, + k ? "" : " *ignored*"); } - return 1; + + if ( k != NoSymbol ) { + XTestFakeKeyEvent_wr(dpy, k, (Bool) down, CurrentTime); + XFlush(dpy); + } + + X_UNLOCK; } -void initialize_pipeinput(void) { - char *p; +/* -- pointer.c -- */ +/* + * pointer event (motion and button click) handling routines. + */ +typedef struct ptrremap { + KeySym keysym; + KeyCode keycode; + int end; + int button; + int down; + int up; +} prtremap_t; - if (pipeinput_fh != NULL) { - rfbLog("closing pipeinput stream: %p\n", pipeinput_fh); - pclose(pipeinput_fh); - pipeinput_fh = NULL; - } +MUTEX(pointerMutex); +#define MAX_BUTTONS 5 +#define MAX_BUTTON_EVENTS 50 +static prtremap_t pointer_map[MAX_BUTTONS+1][MAX_BUTTON_EVENTS]; - pipeinput_tee = 0; - if (pipeinput_opts) { - free(pipeinput_opts); - pipeinput_opts = NULL; - } +/* + * For parsing the -buttonmap sections, e.g. "4" or ":Up+Up+Up:" + */ +static void buttonparse(int from, char **s) { + char *q; + int to, i; + int modisdown[256]; - if (! pipeinput_str) { - return; + q = *s; + + for (i=0; i<256; i++) { + modisdown[i] = 0; } - /* look for options: tee, reopen, ... */ - p = strchr(pipeinput_str, ':'); - if (p != NULL) { - char *str, *opt, *q; - int got = 0; - *p = '\0'; - str = strdup(pipeinput_str); - opt = strdup(pipeinput_str); - *p = ':'; - q = strtok(str, ","); - while (q) { - if (!strcmp(q, "key") || !strcmp(q, "keycodes")) { - got = 1; + if (*q == ':') { + /* :sym1+sym2+...+symN: format */ + int l = 0, n = 0; + char list[1000]; + char *t, *kp = q + 1; + KeyCode kcode; + + while (*(kp+l) != ':' && *(kp+l) != '\0') { + /* loop to the matching ':' */ + l++; + if (l >= 1000) { + rfbLog("buttonparse: keysym list too long: " + "%s\n", q); + break; } - if (!strcmp(q, "reopen")) { - got = 1; + } + *(kp+l) = '\0'; + strncpy(list, kp, l); + list[l] = '\0'; + rfbLog("remap button %d using \"%s\"\n", from, list); + + /* loop over tokens separated by '+' */ + t = strtok(list, "+"); + while (t) { + KeySym ksym; + int i; + if (n >= MAX_BUTTON_EVENTS - 20) { + rfbLog("buttonparse: too many button map " + "events: %s\n", list); + break; } - if (!strcmp(q, "tee")) { - pipeinput_tee = 1; - got = 1; + if (sscanf(t, "0x%x", &i) == 1) { + ksym = (KeySym) i; /* hex value */ + } else { + X_LOCK; + ksym = XStringToKeysym(t); /* string value */ + X_UNLOCK; } - q = strtok(NULL, ","); + if (ksym == NoSymbol) { + /* see if Button "keysym" was used: */ + if (sscanf(t, "Button%d", &i) == 1) { + rfbLog(" event %d: button %d\n", + from, n+1, i); + if (i == 0) i = -1; /* bah */ + pointer_map[from][n].keysym = NoSymbol; + pointer_map[from][n].keycode = NoSymbol; + pointer_map[from][n].button = i; + pointer_map[from][n].end = 0; + pointer_map[from][n].down = 0; + pointer_map[from][n].up = 0; + } else { + rfbLog("buttonparse: ignoring unknown " + "keysym: %s\n", t); + n--; + } + } else { + /* + * XXX may not work with -modtweak or -xkb + */ + X_LOCK; + kcode = XKeysymToKeycode(dpy, ksym); + + pointer_map[from][n].keysym = ksym; + pointer_map[from][n].keycode = kcode; + pointer_map[from][n].button = 0; + pointer_map[from][n].end = 0; + if (! ismodkey(ksym) ) { + /* do both down then up */ + pointer_map[from][n].down = 1; + pointer_map[from][n].up = 1; + } else { + if (modisdown[kcode]) { + pointer_map[from][n].down = 0; + pointer_map[from][n].up = 1; + modisdown[kcode] = 0; + } else { + pointer_map[from][n].down = 1; + pointer_map[from][n].up = 0; + modisdown[kcode] = 1; + } + } + rfbLog(" event %d: keysym %s (0x%x) -> " + "keycode 0x%x down=%d up=%d\n", n+1, + XKeysymToString(ksym), ksym, kcode, + pointer_map[from][n].down, + pointer_map[from][n].up); + X_UNLOCK; + } + t = strtok(NULL, "+"); + n++; } - if (got) { - pipeinput_opts = opt; - } else { - free(opt); + + /* we must release any modifiers that are still down: */ + for (i=0; i<256; i++) { + kcode = (KeyCode) i; + if (n >= MAX_BUTTON_EVENTS) { + rfbLog("buttonparse: too many button map " + "events: %s\n", list); + break; + } + if (modisdown[kcode]) { + pointer_map[from][n].keysym = NoSymbol; + pointer_map[from][n].keycode = kcode; + pointer_map[from][n].button = 0; + pointer_map[from][n].end = 0; + pointer_map[from][n].down = 0; + pointer_map[from][n].up = 1; + modisdown[kcode] = 0; + n++; + } } - free(str); - p++; + + /* advance the source pointer position */ + (*s) += l+2; } else { - p = pipeinput_str; + /* single digit format */ + char str[2]; + str[0] = *q; + str[1] = '\0'; + + to = atoi(str); + if (to < 1) { + rfbLog("skipping invalid remap button \"%d\" for button" + " %d from string \"%s\"\n", + to, from, str); + } else { + rfbLog("remap button %d using \"%s\"\n", from, str); + rfbLog(" button: %d -> %d\n", from, to); + pointer_map[from][0].keysym = NoSymbol; + pointer_map[from][0].keycode = NoSymbol; + pointer_map[from][0].button = to; + pointer_map[from][0].end = 0; + pointer_map[from][0].down = 0; + pointer_map[from][0].up = 0; + } + /* advance the source pointer position */ + (*s)++; } +} - set_child_info(); - if (no_external_cmds) { - rfbLog("cannot run external commands in -nocmds mode:\n"); - rfbLog(" \"%s\"\n", p); - rfbLog(" exiting.\n"); - clean_up_exit(1); +/* + * process the -buttonmap string + */ +void initialize_pointer_map(char *pointer_remap) { + unsigned char map[MAX_BUTTONS]; + int i, k; + /* + * This routine counts the number of pointer buttons on the X + * server (to avoid problems, even crashes, if a client has more + * buttons). And also initializes any pointer button remapping + * from -buttonmap option. + */ + + X_LOCK; + num_buttons = XGetPointerMapping(dpy, map, MAX_BUTTONS); + X_UNLOCK; + + if (num_buttons < 0) { + num_buttons = 0; } - rfbLog("pipeinput: starting: \"%s\"...\n", p); - pipeinput_fh = popen(p, "w"); - if (! pipeinput_fh) { - rfbLog("popen(\"%s\", \"w\") failed.\n", p); - rfbLogPerror("popen"); - rfbLog("Disabling -pipeinput mode.\n"); - return; + /* FIXME: should use info in map[] */ + for (i=1; i<= MAX_BUTTONS; i++) { + for (k=0; k < MAX_BUTTON_EVENTS; k++) { + pointer_map[i][k].end = 1; + } + pointer_map[i][0].keysym = NoSymbol; + pointer_map[i][0].keycode = NoSymbol; + pointer_map[i][0].button = i; + pointer_map[i][0].end = 0; + pointer_map[i][0].down = 0; + pointer_map[i][0].up = 0; } - fprintf(pipeinput_fh, "%s", -"# \n" -"# Format of the -pipeinput stream:\n" -"# --------------------------------\n" -"#\n" -"# Lines like these beginning with '#' are to be ignored.\n" -"#\n" -"# Pointer events (mouse motion and button clicks) come in the form:\n" -"#\n" -"#\n" -"# Pointer \n" -"#\n" -"#\n" -"# The is a decimal integer uniquely identifying the client\n" -"# that generated the event. If it is negative that means this event\n" -"# would have been discarded since the client was viewonly.\n" -"#\n" -"# and are decimal integers reflecting the position on the screen\n" -"# the event took place at.\n" -"#\n" -"# is the button mask indicating the button press state, as normal\n" -"# 0 means no buttons pressed, 1 means button 1 is down 3 (11) means buttons\n" -"# 1 and 2 are down, etc.\n" -"#\n" -"# is a string containing no spaces and may be ignored.\n" -"# It contains some interpretation about what has happened.\n" -"# It can be:\n" -"#\n" -"# None (nothing to report)\n" -"# ButtonPress-N (this event will cause button-1 to be pressed) \n" -"# ButtonRelease-N (this event will cause button-1 to be released) \n" -"#\n" -"# if two more more buttons change state in one event they are listed\n" -"# separated by commas.\n" -"#\n" -"# One might parse a Pointer line with:\n" -"#\n" -"# int client, x, y, mask; char *hint;\n" -"# sscanf(line, \"Pointer %d %d %d %s\", &client, &x, &y, &mask, &hint);\n" -"#\n" -"#\n" -"# Keysym events (keyboard presses and releases) come in the form:\n" -"#\n" -"#\n" -"# Keysym \n" -"#\n" -"#\n" -"# The is as with Pointer.\n" -"#\n" -"# is a decimal either 1 or 0 indicating KeyPress or KeyRelease,\n" -"# respectively.\n" -"#\n" -"# is a decimal integer incidating the Keysym of the event.\n" -"#\n" -"# is the corresponding Keysym name.\n" -"#\n" -"# See the file /usr/include/X11/keysymdef.h for the mappings.\n" -"# You basically remove the leading 'XK_' prefix from the macro name in\n" -"# that file to get the Keysym name.\n" -"#\n" -"# One might parse a Keysym line with:\n" -"#\n" -"# int client, down, keysym; char *name, *hint;\n" -"# sscanf(line, \"Keysym %d %d %s %s\", &client, &down, &keysym, &name, &hint);\n" -"#\n" -"# The value is currently just None, KeyPress, or KeyRelease.\n" -"#\n" -"# In the future will provide a hint for the sequence of KeyCodes\n" -"# (i.e. keyboard scancodes) that x11vnc would inject to an X display to\n" -"# simulate the Keysym.\n" -"#\n" -"# You see, some Keysyms will require more than one injected Keycode to\n" -"# generate the symbol. E.g. the Keysym \"ampersand\" going down usually\n" -"# requires a Shift key going down, then the key with the \"&\" on it going\n" -"# down, and, perhaps, the Shift key going up (that is how x11vnc does it).\n" -"#\n" -"# The Keysym => Keycode(s) stuff gets pretty messy. Hopefully the Keysym\n" -"# info will be enough for most purposes (having identical keyboards on\n" -"# both sides helps).\n" -"#\n" -"# Here comes your stream. The following token will always indicate the\n" -"# end of this informational text:\n" -"# END_OF_TOP\n" -); - fflush(pipeinput_fh); - if (raw_fb_str) { - /* the pipe program may actually create the fb */ - sleep(1); + if (pointer_remap && *pointer_remap != '\0') { + /* -buttonmap, format is like: 12-21=2 */ + char *p, *q, *remap = strdup(pointer_remap); + int n; + + if ((p = strchr(remap, '=')) != NULL) { + /* undocumented max button number */ + n = atoi(p+1); + *p = '\0'; + if (n < num_buttons || num_buttons == 0) { + num_buttons = n; + } else { + rfbLog("warning: increasing number of mouse " + "buttons from %d to %d\n", num_buttons, n); + num_buttons = n; + } + } + if ((q = strchr(remap, '-')) != NULL) { + /* + * The '-' separates the 'from' and 'to' lists, + * then it is kind of like tr(1). + */ + char str[2]; + int from; + + rfbLog("remapping pointer buttons using string:\n"); + rfbLog(" \"%s\"\n", remap); + + p = remap; + q++; + i = 0; + str[1] = '\0'; + while (*p != '-') { + str[0] = *p; + from = atoi(str); + buttonparse(from, &q); + p++; + } + } + free(remap); } } -/* -- xkb_bell.c -- */ /* - * Bell event handling. Requires XKEYBOARD extension. + * For use in the -wireframe stuff, save the stacking order of the direct + * children of the root window. Ideally done before we send ButtonPress + * to the X server. */ -int xkb_base_event_type = 0; +void snapshot_stack_list(int free_only, double allowed_age) { + static double last_snap = 0.0, last_sync = 0.0, last_free = 0.0; + double now = 0.0, xsync_max = 0.25; + Window r, w; -#if LIBVNCSERVER_HAVE_XKEYBOARD -/* - * check for XKEYBOARD, set up xkb_base_event_type - */ -void initialize_xkb(void) { - int ir, reason; - int op, ev, er, maj, min; - - if (xkbcompat) { - xkb_present = 0; - } else if (! XkbQueryExtension(dpy, &op, &ev, &er, &maj, &min)) { - if (! quiet) { - rfbLog("warning: XKEYBOARD extension not present.\n"); + dtime(&now); + if (free_only) { + if (stack_list) { + X_LOCK; + XFree(stack_list); + X_UNLOCK; + stack_list = NULL; + stack_num = 0; + last_free = now; } - xkb_present = 0; - } else { - xkb_present = 1; + return; } - if (! xkb_present) { + if (stack_list && now < last_snap + allowed_age) { return; } - if (! XkbOpenDisplay(DisplayString(dpy), &xkb_base_event_type, &ir, - NULL, NULL, &reason) ) { - if (! quiet) { - rfbLog("warning: disabling XKEYBOARD. XkbOpenDisplay" - " failed.\n"); - } - xkb_base_event_type = 0; - xkb_present = 0; + if (stack_list) { + XFree(stack_list); + stack_list = NULL; + stack_num = 0; + last_free = now; } -} -void initialize_watch_bell(void) { - if (! xkb_present) { - if (! quiet) { - rfbLog("warning: disabling bell. XKEYBOARD ext. " - "not present.\n"); - } - watch_bell = 0; - sound_bell = 0; - return; + X_LOCK; + if (now > last_sync + xsync_max) { + XSync(dpy, False); + last_sync = now; } - XkbSelectEvents(dpy, XkbUseCoreKbd, XkbBellNotifyMask, 0); - - if (! watch_bell) { - return; - } - if (! XkbSelectEvents(dpy, XkbUseCoreKbd, XkbBellNotifyMask, - XkbBellNotifyMask) ) { - if (! quiet) { - rfbLog("warning: disabling bell. XkbSelectEvents" - " failed.\n"); - } - watch_bell = 0; - sound_bell = 0; + if (! XQueryTree(dpy, rootwin, &r, &w, &stack_list, &stack_num)) { + stack_list = NULL; + stack_num = 0; + last_free = now; + last_snap = 0.0; + } else { + last_snap = now; } + X_UNLOCK; } /* - * We call this periodically to process any bell events that have - * taken place. + * Send a pointer position event to the X server. */ -void check_bell_event(void) { - XEvent xev; - XkbAnyEvent *xkb_ev; - int got_bell = 0; +static void update_x11_pointer_position(int x, int y) { - if (! xkb_base_event_type) { - return; - } + if (raw_fb && ! dpy) return; /* raw_fb hack */ - /* caller does X_LOCK */ - if (! XCheckTypedEvent(dpy, xkb_base_event_type, &xev)) { - return; - } - if (! watch_bell) { - /* we return here to avoid xkb events piling up */ - return; + X_LOCK; + if (use_xwarppointer) { + /* + * off_x and off_y not needed with XWarpPointer since + * window is used: + */ + XWarpPointer(dpy, None, window, 0, 0, 0, 0, x + coff_x, + y + coff_y); + } else { + XTestFakeMotionEvent_wr(dpy, scr, x + off_x + coff_x, + y + off_y + coff_y, CurrentTime); } + X_UNLOCK; - xkb_ev = (XkbAnyEvent *) &xev; - if (xkb_ev->xkb_type == XkbBellNotify) { - got_bell = 1; - } + cursor_x = x; + cursor_y = y; - if (got_bell && sound_bell) { - if (! all_clients_initialized()) { - rfbLog("check_bell_event: not sending bell: " - "uninitialized clients\n"); - } else { - if (screen && client_count) { - rfbSendBell(screen); - } - } - } -} -#else -void initialize_watch_bell(void) { - watch_bell = 0; - sound_bell = 0; + /* record the x, y position for the rfb screen as well. */ + cursor_position(x, y); + + /* change the cursor shape if necessary */ + set_cursor(x, y, get_which_cursor()); + + last_event = last_input = time(0); } -void check_bell_event(void) {} -#endif -/* -- xrandr.h -- */ +/* + * Send a pointer button event to the X server. + */ +static void update_x11_pointer_mask(int mask) { + int i, mb; + int xr_mouse = 1; + int snapped = 0; -time_t last_subwin_trap = 0; -int subwin_trap_count = 0; + last_event = last_input = time(0); -XErrorHandler old_getimage_handler; -#define XRANDR_SET_TRAP_RET(x,y) \ - if (subwin || xrandr) { \ - trapped_getimage_xerror = 0; \ - old_getimage_handler = XSetErrorHandler(trap_getimage_xerror); \ - if (check_xrandr_event(y)) { \ - trapped_getimage_xerror = 0; \ - XSetErrorHandler(old_getimage_handler); \ - return(x); \ - } \ - } -#define XRANDR_CHK_TRAP_RET(x,y) \ - if (subwin || xrandr) { \ - if (trapped_getimage_xerror) { \ - if (subwin) { \ - static int last = 0; \ - subwin_trap_count++; \ - if (time(0) > last_subwin_trap + 60) { \ - rfbLog("trapped GetImage xerror" \ - " in SUBWIN mode. [%d]\n", \ - subwin_trap_count); \ - last_subwin_trap = time(0); \ - last = subwin_trap_count; \ - } \ - if (subwin_trap_count - last > 30) { \ - /* window probably iconified */ \ - usleep(1000*1000); \ - } \ - } else { \ - rfbLog("trapped GetImage xerror" \ - " in XRANDR mode.\n"); \ - } \ - trapped_getimage_xerror = 0; \ - XSetErrorHandler(old_getimage_handler); \ - check_xrandr_event(y); \ - X_UNLOCK; \ - return(x); \ - } \ + if (raw_fb && ! dpy) return; /* raw_fb hack */ + + if (scaling && ! got_scrollcopyrect) { + xr_mouse = 0; + } else if (!strcmp(scroll_copyrect, "never")) { + xr_mouse = 0; + } else if (!strcmp(scroll_copyrect, "keys")) { + xr_mouse = 0; + } else if (xrecord_skip_button(mask, button_mask)) { + xr_mouse = 0; } -/* -- xrandr.c -- */ + if (mask && use_xrecord && ! xrecording && xr_mouse) { + static int px, py, x, y, w, h, ok; + Window frame; + int skip = 0; -void initialize_xrandr(void) { - if (xrandr_present) { -#if LIBVNCSERVER_HAVE_LIBXRANDR - Rotation rot; + if (!button_mask) { + if (get_wm_frame_pos(&px, &py, &x, &y, &w, &h, + &frame)) { + ok = 1; + } else { + ok = 0; + } + } + if (ok) { + if (! near_scrollbar_edge(x, y, w, h, px, py)) { + skip = 1; + } + if (near_wm_edge(x, y, w, h, px, py)) { + /* step out of wireframe's way */ + skip = 1; + } + } - xrandr_width = XDisplayWidth(dpy, scr); - xrandr_height = XDisplayHeight(dpy, scr); - XRRRotations(dpy, scr, &rot); - xrandr_rotation = (int) rot; - if (xrandr) { - XRRSelectInput(dpy, rootwin, RRScreenChangeNotifyMask); - } else { - XRRSelectInput(dpy, rootwin, 0); + if (! skip) { + snapshot_stack_list(0, 0.25); + snapped = 1; + xrecord_watch(1); + if (button_mask) { + xrecord_set_by_mouse = 1; + } else { + xrecord_set_by_mouse = 2; + } } -#endif - } else if (xrandr) { - rfbLog("-xrandr mode specified, but no RANDR support on\n"); - rfbLog(" display or in client library. Disabling -xrandr " - "mode.\n"); - xrandr = 0; } -} -void handle_xrandr_change(int, int); + if (mask && !button_mask) { + /* button down, snapshot the stacking list before flushing */ + if (wireframe && !wireframe_in_progress && + strcmp(wireframe_copyrect, "never")) { + if (! snapped) { + snapshot_stack_list(0, 0.0); + } + } + } -int handle_subwin_resize(char *msg) { - int new_x, new_y; - int i, check = 10, ms = 250; /* 2.5 secs total... */ + X_LOCK; + /* look for buttons that have be clicked or released: */ + for (i=0; i < MAX_BUTTONS; i++) { + if ( (button_mask & (1< " + "0x%x button: %d\n", button_mask, mask,i+1); + } + for (k=0; k < MAX_BUTTON_EVENTS; k++) { + int bmask = (mask & (1< num_buttons) + || mb < 1) { + rfbLog("ignoring mouse button out of " + "bounds: %d>%d mask: 0x%x -> 0x%x\n", + mb, num_buttons, button_mask, mask); + continue; + } + if (debug_pointer) { + rfbLog("pointer(): sending button %d" + " %s (event %d)\n", mb, bmask + ? "down" : "up", k+1); + } + XTestFakeButtonEvent_wr(dpy, mb, (mask & (1< %d, y: %d -> %d\n", - subwin, wdpy_x, new_x, wdpy_y, new_y); - rfbLog("calling handle_xrandr_change() for resizing\n"); X_UNLOCK; - handle_xrandr_change(new_x, new_y); - return 1; + + /* + * Remember the button state for next time and also for the + * -nodragging case: + */ + button_mask_prev = button_mask; + button_mask = mask; } -int known_xrandr_mode(char *); +/* for -pipeinput */ -void handle_xrandr_change(int new_x, int new_y) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - /* sanity check xrandr_mode */ - if (! xrandr_mode) { - xrandr_mode = strdup("default"); - } else if (! known_xrandr_mode(xrandr_mode)) { - free(xrandr_mode); - xrandr_mode = strdup("default"); - } - rfbLog("xrandr_mode: %s\n", xrandr_mode); - if (!strcmp(xrandr_mode, "exit")) { - close_all_clients(); - rfbLog(" shutting down due to XRANDR event.\n"); - clean_up_exit(0); +void pipe_pointer(int mask, int x, int y, rfbClientPtr client) { + int can_input = 0, uid; + allowed_input_t input; + ClientData *cd = (ClientData *) client->clientData; + char hint[MAX_BUTTONS * 20]; + + if (pipeinput_fh == NULL) { + return; } - if (!strcmp(xrandr_mode, "newfbsize") && screen) { - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->useNewFBSize) { - continue; - } - rfbLog(" closing client %s (no useNewFBSize" - " support).\n", cl->host); - rfbCloseClient(cl); - rfbClientConnectionGone(cl); + + if (! view_only) { + get_allowed_input(client, &input); + if (input.motion || input.button) { + can_input = 1; /* XXX distinguish later */ } - rfbReleaseClientIterator(iter); } - - /* default, resize, and newfbsize create a new fb: */ - rfbLog("check_xrandr_event: trying to create new framebuffer...\n"); - if (new_x < wdpy_x || new_y < wdpy_y) { - check_black_fb(); - } - do_new_fb(1); - rfbLog("check_xrandr_event: fb WxH: %dx%d\n", wdpy_x, wdpy_y); -} - -int check_xrandr_event(char *msg) { - XEvent xev; - if (subwin) { - return handle_subwin_resize(msg); - } -#if LIBVNCSERVER_HAVE_LIBXRANDR - if (! xrandr || ! xrandr_present) { - return 0; + uid = cd->uid; + if (! can_input) { + uid = -uid; } - if (xrandr_base_event_type && XCheckTypedEvent(dpy, - xrandr_base_event_type + RRScreenChangeNotify, &xev)) { - int do_change; - XRRScreenChangeNotifyEvent *rev; - - rev = (XRRScreenChangeNotifyEvent *) &xev; - rfbLog("check_xrandr_event():\n"); - rfbLog("Detected XRANDR event at location '%s':\n", msg); - rfbLog(" serial: %d\n", (int) rev->serial); - rfbLog(" timestamp: %d\n", (int) rev->timestamp); - rfbLog(" cfg_timestamp: %d\n", (int) rev->config_timestamp); - rfbLog(" size_id: %d\n", (int) rev->size_index); - rfbLog(" sub_pixel: %d\n", (int) rev->subpixel_order); - rfbLog(" rotation: %d\n", (int) rev->rotation); - rfbLog(" width: %d\n", (int) rev->width); - rfbLog(" height: %d\n", (int) rev->height); - rfbLog(" mwidth: %d mm\n", (int) rev->mwidth); - rfbLog(" mheight: %d mm\n", (int) rev->mheight); - rfbLog("\n"); - rfbLog("check_xrandr_event: previous WxH: %dx%d\n", - wdpy_x, wdpy_y); - if (wdpy_x == rev->width && wdpy_y == rev->height && - xrandr_rotation == (int) rev->rotation) { - rfbLog("check_xrandr_event: no change detected.\n"); - do_change = 0; - } else { - do_change = 1; - } - xrandr_width = rev->width; - xrandr_height = rev->height; - xrandr_timestamp = rev->timestamp; - xrandr_cfg_time = rev->config_timestamp; - xrandr_rotation = (int) rev->rotation; + hint[0] = '\0'; + if (mask == button_mask) { + strcat(hint, "None"); + } else { + int i, old, new, m = 1, cnt = 0; + for (i=0; iwidth, rev->height); + if (old == new) { + continue; + } + if (hint[0] != '\0') { + strcat(hint, ","); + } + if (new && ! old) { + sprintf(s, "ButtonPress-%d", i+1); + cnt++; + } else if (! new && old) { + sprintf(s, "ButtonRelease-%d", i+1); + cnt++; + } + strcat(hint, s); + } + if (! cnt) { + strcpy(hint, "None"); } - rfbLog("check_xrandr_event: current WxH: %dx%d\n", - XDisplayWidth(dpy, scr), XDisplayHeight(dpy, scr)); - rfbLog("check_xrandr_event(): returning control to" - " caller...\n"); - return do_change; - } -#endif - return 0; -} - -int known_xrandr_mode(char *s) { -/* - * default: - * resize: the default - * exit: shutdown clients and exit. - * newfbsize: shutdown clients that do not support NewFBSize encoding. - */ - if (strcmp(s, "default") && strcmp(s, "resize") && - strcmp(s, "exit") && strcmp(s, "newfbsize")) { - return 0; - } else { - return 1; } -} -int known_sigpipe_mode(char *s) { -/* - * skip, ignore, exit - */ - if (strcmp(s, "skip") && strcmp(s, "ignore") && - strcmp(s, "exit")) { - return 0; - } else { - return 1; - } + fprintf(pipeinput_fh, "Pointer %d %d %d %d %s\n", uid, x, y, + mask, hint); + fflush(pipeinput_fh); + check_pipeinput(); } -/* -- selection.c -- */ -/* - * Selection/Cutbuffer/Clipboard handlers. - */ - -static int own_selection = 0; /* whether we currently own PRIMARY or not */ -static int set_cutbuffer = 0; /* to avoid bouncing the CutText right back */ -static int sel_waittime = 15; /* some seconds to skip before first send */ -static Window selwin; /* special window for our selection */ - /* - * This is where we keep our selection: the string sent TO us from VNC - * clients, and the string sent BY us to requesting X11 clients. + * Actual callback from libvncserver when it gets a pointer event. + * This may queue pointer events rather than sending them immediately + * to the X server. (see update_x11_pointer*()) */ -static char *xcut_str = NULL; +void pointer(int mask, int x, int y, rfbClientPtr client) { + allowed_input_t input; + int sent = 0; -/* - * Our callbacks instruct us to check for changes in the cutbuffer - * and PRIMARY selection on the local X11 display. - * - * We store the new cutbuffer and/or PRIMARY selection data in this - * constant sized array selection_str[]. - * TODO: check if malloc does not cause performance issues (esp. WRT - * SelectionNotify handling). - */ -#define PROP_MAX (131072L) -static char selection_str[PROP_MAX+1]; + if (debug_pointer && mask >= 0) { + static int show_motion = -1; + if (show_motion == -1) { + if (getenv("X11VNC_DB_NOMOTION")) { + show_motion = 0; + } else { + show_motion = 1; + } + } + if (show_motion) { + rfbLog("pointer(mask: 0x%x, x:%4d, y:%4d)\n", + mask, x, y); + } + } -/* - * An X11 (not VNC) client on the local display has requested the selection - * from us (because we are the current owner). - * - * n.b.: our caller already has the X_LOCK. - */ -static void selection_request(XEvent *ev) { - XSelectionEvent notify_event; - XSelectionRequestEvent *req_event; - XErrorHandler old_handler; - unsigned int length; - unsigned char *data; -#ifndef XA_LENGTH - unsigned long XA_LENGTH = XInternAtom(dpy, "LENGTH", True); -#endif + if (scaling) { + /* map from rfb size to X11 size: */ + x = ((double) x / scaled_x) * dpy_x; + x = nfix(x, dpy_x); + y = ((double) y / scaled_y) * dpy_y; + y = nfix(y, dpy_y); + } - req_event = &(ev->xselectionrequest); - notify_event.type = SelectionNotify; - notify_event.display = req_event->display; - notify_event.requestor = req_event->requestor; - notify_event.selection = req_event->selection; - notify_event.target = req_event->target; - notify_event.time = req_event->time; + if (pipeinput_fh != NULL && mask >= 0) { + pipe_pointer(mask, x, y, client); + if (! pipeinput_tee) { + if (! view_only || raw_fb) { /* raw_fb hack */ + got_user_input++; + got_keyboard_input++; + last_pointer_client = client; + } + if (view_only && raw_fb) { + /* raw_fb hack track button state */ + button_mask_prev = button_mask; + button_mask = mask; + } + return; + } + } - if (req_event->property == None) { - notify_event.property = req_event->target; - } else { - notify_event.property = req_event->property; + if (view_only) { + return; } - if (xcut_str) { - length = strlen(xcut_str); - } else { - length = 0; + get_allowed_input(client, &input); + if (! input.motion && ! input.button) { + return; + } + if (mask >= 0) { + /* + * mask = -1 is a special case call from scan_for_updates() + * to flush the event queue; there is no real pointer event. + */ + got_user_input++; + got_pointer_input++; + last_pointer_client = client; } - /* the window may have gone away, so trap errors */ - trapped_xerror = 0; - old_handler = XSetErrorHandler(trap_xerror); - - if (ev->xselectionrequest.target == XA_LENGTH) { - /* length request */ + /* + * The following is hopefully an improvement wrt response during + * pointer user input (window drags) for the threaded case. + * See check_user_input() for the more complicated things we do + * in the non-threaded case. + */ + if (use_threads && pointer_mode != 1) { +# define NEV 32 + /* storage for the event queue */ + static int mutex_init = 0; + static int nevents = 0; + static int ev[NEV][3]; + int i; + /* timer things */ + static double dt = 0.0, tmr = 0.0, maxwait = 0.4; - XChangeProperty(ev->xselectionrequest.display, - ev->xselectionrequest.requestor, - ev->xselectionrequest.property, - ev->xselectionrequest.target, 32, PropModeReplace, - (unsigned char *) &length, sizeof(unsigned int)); + if (! mutex_init) { + INIT_MUTEX(pointerMutex); + mutex_init = 1; + } - } else { - /* data request */ + LOCK(pointerMutex); - data = (unsigned char *)xcut_str; + /* + * If the framebuffer is being copied in another thread + * (scan_for_updates()), we will queue up to 32 pointer + * events for later. The idea is by delaying these input + * events, the screen is less likely to change during the + * copying period, and so will give rise to less window + * "tearing". + * + * Tearing is not completely eliminated because we do + * not suspend work in the other libvncserver threads. + * Maybe that is a possibility with a mutex... + */ + if (fb_copy_in_progress && mask >= 0) { + /* + * mask = -1 is an all-clear signal from + * scan_for_updates(). + * + * dt is a timer in seconds; we only queue for so long. + */ + dt += dtime(&tmr); - XChangeProperty(ev->xselectionrequest.display, - ev->xselectionrequest.requestor, - ev->xselectionrequest.property, - ev->xselectionrequest.target, 8, PropModeReplace, - data, length); + if (nevents < NEV && dt < maxwait) { + i = nevents++; + ev[i][0] = mask; + ev[i][1] = x; + ev[i][2] = y; + if (! input.button) { + ev[i][0] = -1; + } + if (! input.motion) { + ev[i][1] = -1; + ev[i][2] = -1; + } + UNLOCK(pointerMutex); + if (debug_pointer) { + rfbLog("pointer(): deferring event " + "%d\n", i); + } + return; + } + } + + /* time to send the queue */ + for (i=0; i= 0) { + update_x11_pointer_position(ev[i][1], ev[i][2]); + } + if (ev[i][0] >= 0) { + update_x11_pointer_mask(ev[i][0]); + } + } + if (nevents && dt > maxwait) { + X_LOCK; + if (dpy) { /* raw_fb hack */ + XFlush(dpy); + } + X_UNLOCK; + } + nevents = 0; /* reset everything */ + tmr = 0.0; + dt = 0.0; + dtime(&tmr); + + UNLOCK(pointerMutex); + } + if (mask < 0) { /* -1 just means flush the event queue */ + if (debug_pointer > 1) { + rfbLog("pointer(): flush only.\n"); + } + return; } - if (! trapped_xerror) { - XSendEvent(req_event->display, req_event->requestor, False, 0, - (XEvent *)¬ify_event); - } - if (trapped_xerror) { - rfbLog("selection_request: ignored XError while sending " - "PRIMARY selection to 0x%x.\n", req_event->requestor); + /* update the X display with the event: */ + if (input.motion) { + update_x11_pointer_position(x, y); + sent = 1; + } + if (input.button) { + if (mask != button_mask) { + button_change_x = cursor_x; + button_change_y = cursor_y; + } + update_x11_pointer_mask(mask); + sent = 1; } - XSetErrorHandler(old_handler); - trapped_xerror = 0; - XFlush(dpy); + if (nofb && sent) { + /* + * nofb is for, e.g. Win2VNC, where fastest pointer + * updates are desired. + */ + X_LOCK; + XFlush(dpy); + X_UNLOCK; + } } -/* - * CUT_BUFFER0 property on the local display has changed, we read and - * store it and send it out to any connected VNC clients. - * - * n.b.: our caller already has the X_LOCK. - */ -static void cutbuffer_send(void) { - Atom type; - int format, slen, dlen; - unsigned long nitems = 0, bytes_after = 0; - unsigned char* data = NULL; +int check_pipeinput(void) { + if (! pipeinput_fh) { + return 1; + } + if (ferror(pipeinput_fh)) { + rfbLog("pipeinput pipe has ferror. %p\n", pipeinput_fh); + + if (pipeinput_opts && strstr(pipeinput_opts, "reopen")) { + rfbLog("restarting -pipeinput pipe...\n"); + initialize_pipeinput(); + if (pipeinput_fh) { + return 1; + } else { + return 0; + } + } else { + rfbLog("closing -pipeinput pipe...\n"); + pclose(pipeinput_fh); + pipeinput_fh = NULL; + return 0; + } + } + return 1; +} - selection_str[0] = '\0'; - slen = 0; +void initialize_pipeinput(void) { + char *p; - /* read the property value into selection_str: */ - do { - if (XGetWindowProperty(dpy, DefaultRootWindow(dpy), - XA_CUT_BUFFER0, nitems/4, PROP_MAX/16, False, - AnyPropertyType, &type, &format, &nitems, &bytes_after, - &data) == Success) { + if (pipeinput_fh != NULL) { + rfbLog("closing pipeinput stream: %p\n", pipeinput_fh); + pclose(pipeinput_fh); + pipeinput_fh = NULL; + } - dlen = nitems * (format/8); - if (slen + dlen > PROP_MAX) { - /* too big */ - rfbLog("warning: truncating large CUT_BUFFER0" - " selection > %d bytes.\n", PROP_MAX); - XFree(data); - break; + pipeinput_tee = 0; + if (pipeinput_opts) { + free(pipeinput_opts); + pipeinput_opts = NULL; + } + + if (! pipeinput_str) { + return; + } + + /* look for options: tee, reopen, ... */ + p = strchr(pipeinput_str, ':'); + if (p != NULL) { + char *str, *opt, *q; + int got = 0; + *p = '\0'; + str = strdup(pipeinput_str); + opt = strdup(pipeinput_str); + *p = ':'; + q = strtok(str, ","); + while (q) { + if (!strcmp(q, "key") || !strcmp(q, "keycodes")) { + got = 1; } - memcpy(selection_str+slen, data, dlen); - slen += dlen; - selection_str[slen] = '\0'; - XFree(data); + if (!strcmp(q, "reopen")) { + got = 1; + } + if (!strcmp(q, "tee")) { + pipeinput_tee = 1; + got = 1; + } + q = strtok(NULL, ","); } - } while (bytes_after > 0); - - selection_str[PROP_MAX] = '\0'; + if (got) { + pipeinput_opts = opt; + } else { + free(opt); + } + free(str); + p++; + } else { + p = pipeinput_str; + } - if (! all_clients_initialized()) { - rfbLog("cutbuffer_send: no send: uninitialized clients\n"); - return; /* some clients initializing, cannot send */ + set_child_info(); + if (no_external_cmds) { + rfbLog("cannot run external commands in -nocmds mode:\n"); + rfbLog(" \"%s\"\n", p); + rfbLog(" exiting.\n"); + clean_up_exit(1); } + rfbLog("pipeinput: starting: \"%s\"...\n", p); + pipeinput_fh = popen(p, "w"); - /* now send it to any connected VNC clients (rfbServerCutText) */ - if (!screen) { + if (! pipeinput_fh) { + rfbLog("popen(\"%s\", \"w\") failed.\n", p); + rfbLogPerror("popen"); + rfbLog("Disabling -pipeinput mode.\n"); return; } - rfbSendServerCutText(screen, selection_str, strlen(selection_str)); -} -/* - * "callback" for our SelectionNotify polling. We try to determine if - * the PRIMARY selection has changed (checking length and first CHKSZ bytes) - * and if it has we store it and send it off to any connected VNC clients. - * - * n.b.: our caller already has the X_LOCK. - * - * TODO: if we were willing to use libXt, we could perhaps get selection - * timestamps to speed up the checking... XtGetSelectionValue(). - * - * Also: XFIXES has XFixesSelectSelectionInput(). - */ -#define CHKSZ 32 -static void selection_send(XEvent *ev) { - Atom type; - int format, slen, dlen, oldlen, newlen, toobig = 0; - static int err = 0, sent_one = 0; - char before[CHKSZ], after[CHKSZ]; - unsigned long nitems = 0, bytes_after = 0; - unsigned char* data = NULL; + fprintf(pipeinput_fh, "%s", +"# \n" +"# Format of the -pipeinput stream:\n" +"# --------------------------------\n" +"#\n" +"# Lines like these beginning with '#' are to be ignored.\n" +"#\n" +"# Pointer events (mouse motion and button clicks) come in the form:\n" +"#\n" +"#\n" +"# Pointer \n" +"#\n" +"#\n" +"# The is a decimal integer uniquely identifying the client\n" +"# that generated the event. If it is negative that means this event\n" +"# would have been discarded since the client was viewonly.\n" +"#\n" +"# and are decimal integers reflecting the position on the screen\n" +"# the event took place at.\n" +"#\n" +"# is the button mask indicating the button press state, as normal\n" +"# 0 means no buttons pressed, 1 means button 1 is down 3 (11) means buttons\n" +"# 1 and 2 are down, etc.\n" +"#\n" +"# is a string containing no spaces and may be ignored.\n" +"# It contains some interpretation about what has happened.\n" +"# It can be:\n" +"#\n" +"# None (nothing to report)\n" +"# ButtonPress-N (this event will cause button-1 to be pressed) \n" +"# ButtonRelease-N (this event will cause button-1 to be released) \n" +"#\n" +"# if two more more buttons change state in one event they are listed\n" +"# separated by commas.\n" +"#\n" +"# One might parse a Pointer line with:\n" +"#\n" +"# int client, x, y, mask; char *hint;\n" +"# sscanf(line, \"Pointer %d %d %d %s\", &client, &x, &y, &mask, &hint);\n" +"#\n" +"#\n" +"# Keysym events (keyboard presses and releases) come in the form:\n" +"#\n" +"#\n" +"# Keysym \n" +"#\n" +"#\n" +"# The is as with Pointer.\n" +"#\n" +"# is a decimal either 1 or 0 indicating KeyPress or KeyRelease,\n" +"# respectively.\n" +"#\n" +"# is a decimal integer incidating the Keysym of the event.\n" +"#\n" +"# is the corresponding Keysym name.\n" +"#\n" +"# See the file /usr/include/X11/keysymdef.h for the mappings.\n" +"# You basically remove the leading 'XK_' prefix from the macro name in\n" +"# that file to get the Keysym name.\n" +"#\n" +"# One might parse a Keysym line with:\n" +"#\n" +"# int client, down, keysym; char *name, *hint;\n" +"# sscanf(line, \"Keysym %d %d %s %s\", &client, &down, &keysym, &name, &hint);\n" +"#\n" +"# The value is currently just None, KeyPress, or KeyRelease.\n" +"#\n" +"# In the future will provide a hint for the sequence of KeyCodes\n" +"# (i.e. keyboard scancodes) that x11vnc would inject to an X display to\n" +"# simulate the Keysym.\n" +"#\n" +"# You see, some Keysyms will require more than one injected Keycode to\n" +"# generate the symbol. E.g. the Keysym \"ampersand\" going down usually\n" +"# requires a Shift key going down, then the key with the \"&\" on it going\n" +"# down, and, perhaps, the Shift key going up (that is how x11vnc does it).\n" +"#\n" +"# The Keysym => Keycode(s) stuff gets pretty messy. Hopefully the Keysym\n" +"# info will be enough for most purposes (having identical keyboards on\n" +"# both sides helps).\n" +"#\n" +"# Here comes your stream. The following token will always indicate the\n" +"# end of this informational text:\n" +"# END_OF_TOP\n" +); + fflush(pipeinput_fh); + if (raw_fb_str) { + /* the pipe program may actually create the fb */ + sleep(1); + } +} - /* - * remember info about our last value of PRIMARY (or CUT_BUFFER0) - * so we can check for any changes below. - */ - oldlen = strlen(selection_str); - strncpy(before, selection_str, CHKSZ); +/* -- xkb_bell.c -- */ +/* + * Bell event handling. Requires XKEYBOARD extension. + */ +int xkb_base_event_type = 0; - selection_str[0] = '\0'; - slen = 0; +#if LIBVNCSERVER_HAVE_XKEYBOARD +/* + * check for XKEYBOARD, set up xkb_base_event_type + */ +void initialize_xkb(void) { + int ir, reason; + int op, ev, er, maj, min; + + if (xkbcompat) { + xkb_present = 0; + } else if (! XkbQueryExtension(dpy, &op, &ev, &er, &maj, &min)) { + if (! quiet) { + rfbLog("warning: XKEYBOARD extension not present.\n"); + } + xkb_present = 0; + } else { + xkb_present = 1; + } - /* read in the current value of PRIMARY: */ - do { - if (XGetWindowProperty(dpy, ev->xselection.requestor, - ev->xselection.property, nitems/4, PROP_MAX/16, True, - AnyPropertyType, &type, &format, &nitems, &bytes_after, - &data) == Success) { + if (! xkb_present) { + return; + } - dlen = nitems * (format/8); - if (slen + dlen > PROP_MAX) { - /* too big */ - toobig = 1; - XFree(data); - if (err) { /* cut down on messages */ - break; - } else { - err = 5; - } - rfbLog("warning: truncating large PRIMARY" - " selection > %d bytes.\n", PROP_MAX); - break; - } - memcpy(selection_str+slen, data, dlen); - slen += dlen; - selection_str[slen] = '\0'; - XFree(data); + if (! XkbOpenDisplay(DisplayString(dpy), &xkb_base_event_type, &ir, + NULL, NULL, &reason) ) { + if (! quiet) { + rfbLog("warning: disabling XKEYBOARD. XkbOpenDisplay" + " failed.\n"); } - } while (bytes_after > 0); + xkb_base_event_type = 0; + xkb_present = 0; + } +} - if (! toobig) { - err = 0; - } else if (err) { - err--; +void initialize_watch_bell(void) { + if (! xkb_present) { + if (! quiet) { + rfbLog("warning: disabling bell. XKEYBOARD ext. " + "not present.\n"); + } + watch_bell = 0; + sound_bell = 0; + return; } - if (! sent_one) { - /* try to force a send first time in */ - oldlen = -1; - sent_one = 1; + XkbSelectEvents(dpy, XkbUseCoreKbd, XkbBellNotifyMask, 0); + + if (! watch_bell) { + return; + } + if (! XkbSelectEvents(dpy, XkbUseCoreKbd, XkbBellNotifyMask, + XkbBellNotifyMask) ) { + if (! quiet) { + rfbLog("warning: disabling bell. XkbSelectEvents" + " failed.\n"); + } + watch_bell = 0; + sound_bell = 0; } +} - /* look for changes in the new value */ - newlen = strlen(selection_str); - strncpy(after, selection_str, CHKSZ); +/* + * We call this periodically to process any bell events that have + * taken place. + */ +void check_bell_event(void) { + XEvent xev; + XkbAnyEvent *xkb_ev; + int got_bell = 0; - if (oldlen == newlen && strncmp(before, after, CHKSZ) == 0) { - /* evidently no change */ + if (! xkb_base_event_type) { return; } - if (newlen == 0) { - /* do not bother sending a null string out */ + + /* caller does X_LOCK */ + if (! XCheckTypedEvent(dpy, xkb_base_event_type, &xev)) { + return; + } + if (! watch_bell) { + /* we return here to avoid xkb events piling up */ return; } - if (! all_clients_initialized()) { - rfbLog("selection_send: no send: uninitialized clients\n"); - return; /* some clients initializing, cannot send */ + xkb_ev = (XkbAnyEvent *) &xev; + if (xkb_ev->xkb_type == XkbBellNotify) { + got_bell = 1; } - /* now send it to any connected VNC clients (rfbServerCutText) */ - if (!screen) { - return; + if (got_bell && sound_bell) { + if (! all_clients_initialized()) { + rfbLog("check_bell_event: not sending bell: " + "uninitialized clients\n"); + } else { + if (screen && client_count) { + rfbSendBell(screen); + } + } } - rfbSendServerCutText(screen, selection_str, newlen); } +#else +void initialize_watch_bell(void) { + watch_bell = 0; + sound_bell = 0; +} +void check_bell_event(void) {} +#endif -/* -- xevents.c -- */ +/* -- xrandr.h -- */ -void initialize_vnc_connect_prop() { - vnc_connect_str[0] = '\0'; - vnc_connect_prop = XInternAtom(dpy, "VNC_CONNECT", False); -} +time_t last_subwin_trap = 0; +int subwin_trap_count = 0; -void initialize_xevents(void) { - static int did_xselect_input = 0; - static int did_xcreate_simple_window = 0; - static int did_vnc_connect_prop = 0; - static int did_xfixes = 0; - static int did_xdamage = 0; - static int did_xrandr = 0; - - if ((watch_selection || vnc_connect) && !did_xselect_input) { - /* - * register desired event(s) for notification. - * PropertyChangeMask is for CUT_BUFFER0 changes. - * XXX: does this cause a flood of other stuff? - */ - X_LOCK; - XSelectInput(dpy, rootwin, PropertyChangeMask); - X_UNLOCK; - did_xselect_input = 1; +XErrorHandler old_getimage_handler; +#define XRANDR_SET_TRAP_RET(x,y) \ + if (subwin || xrandr) { \ + trapped_getimage_xerror = 0; \ + old_getimage_handler = XSetErrorHandler(trap_getimage_xerror); \ + if (check_xrandr_event(y)) { \ + trapped_getimage_xerror = 0; \ + XSetErrorHandler(old_getimage_handler); \ + return(x); \ + } \ + } +#define XRANDR_CHK_TRAP_RET(x,y) \ + if (subwin || xrandr) { \ + if (trapped_getimage_xerror) { \ + if (subwin) { \ + static int last = 0; \ + subwin_trap_count++; \ + if (time(0) > last_subwin_trap + 60) { \ + rfbLog("trapped GetImage xerror" \ + " in SUBWIN mode. [%d]\n", \ + subwin_trap_count); \ + last_subwin_trap = time(0); \ + last = subwin_trap_count; \ + } \ + if (subwin_trap_count - last > 30) { \ + /* window probably iconified */ \ + usleep(1000*1000); \ + } \ + } else { \ + rfbLog("trapped GetImage xerror" \ + " in XRANDR mode.\n"); \ + } \ + trapped_getimage_xerror = 0; \ + XSetErrorHandler(old_getimage_handler); \ + check_xrandr_event(y); \ + X_UNLOCK; \ + return(x); \ + } \ } - if (watch_selection && !did_xcreate_simple_window) { - /* create fake window for our selection ownership, etc */ - X_LOCK; - selwin = XCreateSimpleWindow(dpy, rootwin, 0, 0, 1, 1, 0, 0, 0); - X_UNLOCK; - did_xcreate_simple_window = 1; +/* -- xrandr.c -- */ + +void initialize_xrandr(void) { + if (xrandr_present) { +#if LIBVNCSERVER_HAVE_LIBXRANDR + Rotation rot; + + xrandr_width = XDisplayWidth(dpy, scr); + xrandr_height = XDisplayHeight(dpy, scr); + XRRRotations(dpy, scr, &rot); + xrandr_rotation = (int) rot; + if (xrandr) { + XRRSelectInput(dpy, rootwin, RRScreenChangeNotifyMask); + } else { + XRRSelectInput(dpy, rootwin, 0); + } +#endif + } else if (xrandr) { + rfbLog("-xrandr mode specified, but no RANDR support on\n"); + rfbLog(" display or in client library. Disabling -xrandr " + "mode.\n"); + xrandr = 0; } +} - if (xrandr && !did_xrandr) { - initialize_xrandr(); - did_xrandr = 1; +void handle_xrandr_change(int, int); + +int handle_subwin_resize(char *msg) { + int new_x, new_y; + int i, check = 10, ms = 250; /* 2.5 secs total... */ + + if (! subwin) { + return 0; /* hmmm... */ } - if (vnc_connect && !did_vnc_connect_prop) { - initialize_vnc_connect_prop(); - did_vnc_connect_prop = 1; + if (! valid_window(subwin, NULL)) { + rfbLog("subwin 0x%lx went away!\n", subwin); + X_UNLOCK; + clean_up_exit(1); } - if (xfixes_present && use_xfixes && !did_xfixes) { - initialize_xfixes(); - did_xfixes = 1; + if (! get_window_size(subwin, &new_x, &new_y)) { + rfbLog("could not get size of subwin 0x%lx\n", subwin); + X_UNLOCK; + clean_up_exit(1); } - if (xdamage_present && !did_xdamage) { - initialize_xdamage(); - did_xdamage = 1; + if (wdpy_x == new_x && wdpy_y == new_y) { + /* no change */ + return 0; } -} -void print_xevent_bases(void) { - fprintf(stderr, "X event bases: xkb=%d, xtest=%d, xrandr=%d, " - "xfixes=%d, xdamage=%d, xtrap=%d\n", xkb_base_event_type, - xtest_base_event_type, xrandr_base_event_type, - xfixes_base_event_type, xdamage_base_event_type, - xtrap_base_event_type); - fprintf(stderr, " MapNotify=%d, ClientMsg=%d PropNotify=%d " - "SelNotify=%d, SelRequest=%d\n", MappingNotify, ClientMessage, - PropertyNotify, SelectionNotify, SelectionRequest); - fprintf(stderr, " SelClear=%d, Expose=%d\n", SelectionClear, Expose); + /* window may still be changing (e.g. drag resize) */ + for (i=0; i < check; i++) { + int newer_x, newer_y; + usleep(ms * 1000); + + if (! get_window_size(subwin, &newer_x, &newer_y)) { + rfbLog("could not get size of subwin 0x%lx\n", subwin); + clean_up_exit(1); + } + if (new_x == newer_x && new_y == newer_y) { + /* go for it... */ + break; + } else { + rfbLog("subwin 0x%lx still changing size...\n", subwin); + new_x = newer_x; + new_y = newer_y; + } + } + + rfbLog("subwin 0x%lx new size: x: %d -> %d, y: %d -> %d\n", + subwin, wdpy_x, new_x, wdpy_y, new_y); + rfbLog("calling handle_xrandr_change() for resizing\n"); + + X_UNLOCK; + handle_xrandr_change(new_x, new_y); + return 1; } -/* - * This routine is periodically called to check for selection related - * and other X11 events and respond to them as needed. - */ -void check_xevents(void) { - XEvent xev; - int have_clients = 0; - static int sent_some_sel = 0; - static time_t last_request = 0; - static time_t last_call = 0; - static time_t last_bell = 0; - static time_t last_init_check = 0; - static time_t last_sync = 0; - time_t now = time(0); +int known_xrandr_mode(char *); - if (raw_fb && ! dpy) return; /* raw_fb hack */ +void handle_xrandr_change(int new_x, int new_y) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; - if (now > last_init_check+1) { - last_init_check = now; - initialize_xevents(); + /* sanity check xrandr_mode */ + if (! xrandr_mode) { + xrandr_mode = strdup("default"); + } else if (! known_xrandr_mode(xrandr_mode)) { + free(xrandr_mode); + xrandr_mode = strdup("default"); } - - if (screen && screen->clientHead) { - have_clients = 1; + rfbLog("xrandr_mode: %s\n", xrandr_mode); + if (!strcmp(xrandr_mode, "exit")) { + close_all_clients(); + rfbLog(" shutting down due to XRANDR event.\n"); + clean_up_exit(0); } - X_LOCK; - /* - * There is a bug where we have to wait before sending text to - * the client... so instead of sending right away we wait a - * the few seconds. - */ - if (have_clients && watch_selection && !sent_some_sel - && now > last_client + sel_waittime) { - if (XGetSelectionOwner(dpy, XA_PRIMARY) == None) { - cutbuffer_send(); + if (!strcmp(xrandr_mode, "newfbsize") && screen) { + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (cl->useNewFBSize) { + continue; + } + rfbLog(" closing client %s (no useNewFBSize" + " support).\n", cl->host); + rfbCloseClient(cl); + rfbClientConnectionGone(cl); } - sent_some_sel = 1; + rfbReleaseClientIterator(iter); } - if (! have_clients) { - /* - * If we don't have clients we can miss the X server - * going away until a client connects. - */ - static time_t last_X_ping = 0; - if (now > last_X_ping + 5) { - last_X_ping = now; - XGetSelectionOwner(dpy, XA_PRIMARY); - } + + /* default, resize, and newfbsize create a new fb: */ + rfbLog("check_xrandr_event: trying to create new framebuffer...\n"); + if (new_x < wdpy_x || new_y < wdpy_y) { + check_black_fb(); } + do_new_fb(1); + rfbLog("check_xrandr_event: fb WxH: %dx%d\n", wdpy_x, wdpy_y); +} - if (no_autorepeat && have_clients && no_repeat_countdown) { - static time_t last_check = 0; - if (now > last_check + 1 && ! view_only) { - last_check = now; - X_UNLOCK; - if (get_autorepeat_state() != 0) { - int n = no_repeat_countdown - 1; - if (n >= 0) { - rfbLog("Battling with something for " - "-norepeat!! (%d resets left)\n",n); - } else { - rfbLog("Battling with something for " - "-norepeat!!\n"); - } - if (no_repeat_countdown > 0) { - no_repeat_countdown--; - } - autorepeat(1); - autorepeat(0); - } - X_LOCK; - } +int check_xrandr_event(char *msg) { + XEvent xev; + if (subwin) { + return handle_subwin_resize(msg); } - - if (now > last_call+1) { - /* we only check these once a second or so. */ - int n = 0; - while (XCheckTypedEvent(dpy, MappingNotify, &xev)) { - XRefreshKeyboardMapping((XMappingEvent *) &xev); - n++; - } - if (n && use_modifier_tweak) { - X_UNLOCK; - initialize_modtweak(); - X_LOCK; - } - if (xtrap_base_event_type) { - int base = xtrap_base_event_type; - while (XCheckTypedEvent(dpy, base, &xev)) { - ; - } - } - if (xtest_base_event_type) { - int base = xtest_base_event_type; - while (XCheckTypedEvent(dpy, base, &xev)) { - ; - } - } - /* - * we can get ClientMessage from our XSendEvent() call in - * selection_request(). - */ - while (XCheckTypedEvent(dpy, ClientMessage, &xev)) { - ; - } +#if LIBVNCSERVER_HAVE_LIBXRANDR + if (! xrandr || ! xrandr_present) { + return 0; } + if (xrandr_base_event_type && XCheckTypedEvent(dpy, + xrandr_base_event_type + RRScreenChangeNotify, &xev)) { + int do_change; + XRRScreenChangeNotifyEvent *rev; - /* check for CUT_BUFFER0 and VNC_CONNECT changes: */ - if (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { - if (xev.type == PropertyNotify) { - if (xev.xproperty.atom == XA_CUT_BUFFER0) { - /* - * Go retrieve CUT_BUFFER0 and send it. - * - * set_cutbuffer is a flag to try to avoid - * processing our own cutbuffer changes. - */ - if (have_clients && watch_selection - && ! set_cutbuffer) { - cutbuffer_send(); - sent_some_sel = 1; - } - set_cutbuffer = 0; - } else if (vnc_connect && vnc_connect_prop != None - && xev.xproperty.atom == vnc_connect_prop) { - - /* - * Go retrieve VNC_CONNECT string. - */ - read_vnc_connect_prop(); - } + rev = (XRRScreenChangeNotifyEvent *) &xev; + rfbLog("check_xrandr_event():\n"); + rfbLog("Detected XRANDR event at location '%s':\n", msg); + rfbLog(" serial: %d\n", (int) rev->serial); + rfbLog(" timestamp: %d\n", (int) rev->timestamp); + rfbLog(" cfg_timestamp: %d\n", (int) rev->config_timestamp); + rfbLog(" size_id: %d\n", (int) rev->size_index); + rfbLog(" sub_pixel: %d\n", (int) rev->subpixel_order); + rfbLog(" rotation: %d\n", (int) rev->rotation); + rfbLog(" width: %d\n", (int) rev->width); + rfbLog(" height: %d\n", (int) rev->height); + rfbLog(" mwidth: %d mm\n", (int) rev->mwidth); + rfbLog(" mheight: %d mm\n", (int) rev->mheight); + rfbLog("\n"); + rfbLog("check_xrandr_event: previous WxH: %dx%d\n", + wdpy_x, wdpy_y); + if (wdpy_x == rev->width && wdpy_y == rev->height && + xrandr_rotation == (int) rev->rotation) { + rfbLog("check_xrandr_event: no change detected.\n"); + do_change = 0; + } else { + do_change = 1; } - } -#if LIBVNCSERVER_HAVE_LIBXRANDR - if (xrandr) { - check_xrandr_event("check_xevents"); - } -#endif -#if LIBVNCSERVER_HAVE_LIBXFIXES - if (xfixes_present && use_xfixes && xfixes_base_event_type) { - if (XCheckTypedEvent(dpy, xfixes_base_event_type + - XFixesCursorNotify, &xev)) { - got_xfixes_cursor_notify++; + xrandr_width = rev->width; + xrandr_height = rev->height; + xrandr_timestamp = rev->timestamp; + xrandr_cfg_time = rev->config_timestamp; + xrandr_rotation = (int) rev->rotation; + + rfbLog("check_xrandr_event: updating config...\n"); + XRRUpdateConfiguration(&xev); + + if (do_change) { + X_UNLOCK; + handle_xrandr_change(rev->width, rev->height); } + rfbLog("check_xrandr_event: current WxH: %dx%d\n", + XDisplayWidth(dpy, scr), XDisplayHeight(dpy, scr)); + rfbLog("check_xrandr_event(): returning control to" + " caller...\n"); + return do_change; } #endif + return 0; +} - /* check for our PRIMARY request notification: */ - if (watch_primary) { - if (XCheckTypedEvent(dpy, SelectionNotify, &xev)) { - if (xev.type == SelectionNotify && - xev.xselection.requestor == selwin && - xev.xselection.selection == XA_PRIMARY && - xev.xselection.property != None && - xev.xselection.target == XA_STRING) { +int known_xrandr_mode(char *s) { +/* + * default: + * resize: the default + * exit: shutdown clients and exit. + * newfbsize: shutdown clients that do not support NewFBSize encoding. + */ + if (strcmp(s, "default") && strcmp(s, "resize") && + strcmp(s, "exit") && strcmp(s, "newfbsize")) { + return 0; + } else { + return 1; + } +} - /* go retrieve PRIMARY and check it */ - if (now > last_client + sel_waittime - || sent_some_sel) { - selection_send(&xev); - } - } - } - if (now > last_request) { - /* - * Every second or two, request PRIMARY, unless we - * already own it or there is no owner or we have - * no clients. - * TODO: even at this low rate we should look into - * and performance problems in odds cases, etc. - */ - last_request = now; - if (! own_selection && have_clients && - XGetSelectionOwner(dpy, XA_PRIMARY) != None) { - XConvertSelection(dpy, XA_PRIMARY, XA_STRING, - XA_STRING, selwin, CurrentTime); - } - } +int known_sigpipe_mode(char *s) { +/* + * skip, ignore, exit + */ + if (strcmp(s, "skip") && strcmp(s, "ignore") && + strcmp(s, "exit")) { + return 0; + } else { + return 1; } +} - if (own_selection) { - /* we own PRIMARY, see if someone requested it: */ - if (XCheckTypedEvent(dpy, SelectionRequest, &xev)) { - if (xev.type == SelectionRequest && - xev.xselectionrequest.selection == XA_PRIMARY) { - selection_request(&xev); - } - } +/* -- selection.c -- */ +/* + * Selection/Cutbuffer/Clipboard handlers. + */ - /* we own PRIMARY, see if we no longer own it: */ - if (XCheckTypedEvent(dpy, SelectionClear, &xev)) { - if (xev.type == SelectionClear && - xev.xselectionclear.selection == XA_PRIMARY) { +static int own_selection = 0; /* whether we currently own PRIMARY or not */ +static int set_cutbuffer = 0; /* to avoid bouncing the CutText right back */ +static int sel_waittime = 15; /* some seconds to skip before first send */ +static Window selwin; /* special window for our selection */ - own_selection = 0; - if (xcut_str) { - free(xcut_str); - xcut_str = NULL; - } - } - } - } +/* + * This is where we keep our selection: the string sent TO us from VNC + * clients, and the string sent BY us to requesting X11 clients. + */ +static char *xcut_str = NULL; - if (watch_bell || now > last_bell+1) { - last_bell = now; - check_bell_event(); - } +/* + * Our callbacks instruct us to check for changes in the cutbuffer + * and PRIMARY selection on the local X11 display. + * + * We store the new cutbuffer and/or PRIMARY selection data in this + * constant sized array selection_str[]. + * TODO: check if malloc does not cause performance issues (esp. WRT + * SelectionNotify handling). + */ +#define PROP_MAX (131072L) +static char selection_str[PROP_MAX+1]; -#ifndef DEBUG_XEVENTS -#define DEBUG_XEVENTS 1 +/* + * An X11 (not VNC) client on the local display has requested the selection + * from us (because we are the current owner). + * + * n.b.: our caller already has the X_LOCK. + */ +static void selection_request(XEvent *ev) { + XSelectionEvent notify_event; + XSelectionRequestEvent *req_event; + XErrorHandler old_handler; + unsigned int length; + unsigned char *data; +#ifndef XA_LENGTH + unsigned long XA_LENGTH = XInternAtom(dpy, "LENGTH", True); #endif -#if DEBUG_XEVENTS - if (debug_xevents) { - static time_t last_check = 0; - static time_t reminder = 0; - static int freq = 0; - - if (! freq) { - if (getenv("X11VNC_REMINDER_RATE")) { - freq = atoi(getenv("X11VNC_REMINDER_RATE")); - } else { - freq = 300; - } - } - if (now > last_check + 1) { - int ev_type_max = 300, ev_size = 400; - XEvent xevs[400]; - int i, tot = XEventsQueued(dpy, QueuedAlready); + req_event = &(ev->xselectionrequest); + notify_event.type = SelectionNotify; + notify_event.display = req_event->display; + notify_event.requestor = req_event->requestor; + notify_event.selection = req_event->selection; + notify_event.target = req_event->target; + notify_event.time = req_event->time; - if (reminder == 0 || (tot && now > reminder + freq)) { - print_xevent_bases(); - reminder = now; - } - last_check = now; - - if (tot) { - fprintf(stderr, "Total events queued: %d\n", - tot); - } - for (i=1; i= ev_size) { - break; - } - } - if (n) { - fprintf(stderr, " %d%s events of type" - " %d queued\n", n, - (n >= ev_size) ? "+" : "", i); - } - for (k=n-1; k >= 0; k--) { - XPutBackEvent(dpy, xevs+k); - } - } - } + if (req_event->property == None) { + notify_event.property = req_event->target; + } else { + notify_event.property = req_event->property; } -#endif - - if (now > last_sync + 1200) { - /* kludge for any remaining event leaks */ - int bugout = use_xdamage ? 500 : 50; - if (last_sync != 0) { - int qlen = XEventsQueued(dpy, QueuedAlready); - if (qlen >= bugout) { - rfbLog("event leak: %d queued, " - " calling XSync(dpy, True)\n", qlen); - rfbLog(" for diagnostics run: 'x11vnc -R" - " debug_xevents:1'\n"); - XSync(dpy, True); - } - } - last_sync = now; + if (xcut_str) { + length = strlen(xcut_str); + } else { + length = 0; } - X_UNLOCK; - last_call = now; -} + /* the window may have gone away, so trap errors */ + trapped_xerror = 0; + old_handler = XSetErrorHandler(trap_xerror); -/* - * hook called when a VNC client sends us some "XCut" text (rfbClientCutText). - */ -void xcut_receive(char *text, int len, rfbClientPtr cl) { - allowed_input_t input; + if (ev->xselectionrequest.target == XA_LENGTH) { + /* length request */ - if (raw_fb && ! dpy) return; /* raw_fb hack */ + XChangeProperty(ev->xselectionrequest.display, + ev->xselectionrequest.requestor, + ev->xselectionrequest.property, + ev->xselectionrequest.target, 32, PropModeReplace, + (unsigned char *) &length, sizeof(unsigned int)); - if (!watch_selection) { - return; - } - if (view_only) { - return; - } - if (text == NULL || len == 0) { - return; - } - get_allowed_input(cl, &input); - if (!input.keystroke && !input.motion && !input.button) { - /* maybe someday KMBC for cut text... */ - return; - } + } else { + /* data request */ - X_LOCK; + data = (unsigned char *)xcut_str; - /* associate this text with PRIMARY (and SECONDARY...) */ - if (! own_selection) { - own_selection = 1; - /* we need to grab the PRIMARY selection */ - XSetSelectionOwner(dpy, XA_PRIMARY, selwin, CurrentTime); - XFlush(dpy); + XChangeProperty(ev->xselectionrequest.display, + ev->xselectionrequest.requestor, + ev->xselectionrequest.property, + ev->xselectionrequest.target, 8, PropModeReplace, + data, length); } - /* duplicate the text string for our own use. */ - if (xcut_str != NULL) { - free(xcut_str); + if (! trapped_xerror) { + XSendEvent(req_event->display, req_event->requestor, False, 0, + (XEvent *)¬ify_event); + } + if (trapped_xerror) { + rfbLog("selection_request: ignored XError while sending " + "PRIMARY selection to 0x%x.\n", req_event->requestor); } - xcut_str = (unsigned char *) - malloc((size_t) (len+1) * sizeof(unsigned char)); - strncpy(xcut_str, text, len); - xcut_str[len] = '\0'; /* make sure null terminated */ + XSetErrorHandler(old_handler); + trapped_xerror = 0; - /* copy this text to CUT_BUFFER0 as well: */ - XChangeProperty(dpy, rootwin, XA_CUT_BUFFER0, XA_STRING, 8, - PropModeReplace, (unsigned char *) text, len); XFlush(dpy); - - X_UNLOCK; - - set_cutbuffer = 1; } -/* -- remote.c -- */ - /* - * for the wild-n-crazy -remote/-R interface. + * CUT_BUFFER0 property on the local display has changed, we read and + * store it and send it out to any connected VNC clients. + * + * n.b.: our caller already has the X_LOCK. */ -int send_remote_cmd(char *cmd, int query, int wait) { - FILE *in = NULL; - - if (client_connect_file) { - in = fopen(client_connect_file, "w"); - if (in == NULL) { - fprintf(stderr, "send_remote_cmd: could not open " - "connect file \"%s\" for writing\n", - client_connect_file); - perror("fopen"); - return 1; - } - } else if (vnc_connect_prop == None) { - initialize_vnc_connect_prop(); - if (vnc_connect_prop == None) { - fprintf(stderr, "send_remote_cmd: could not obtain " - "VNC_CONNECT X property\n"); - return 1; - } - } +static void cutbuffer_send(void) { + Atom type; + int format, slen, dlen; + unsigned long nitems = 0, bytes_after = 0; + unsigned char* data = NULL; - if (in != NULL) { - fprintf(stderr, "sending remote command: \"%s\"\nvia connect" - " file: %s\n", cmd, client_connect_file); - fprintf(in, "%s\n", cmd); - fclose(in); - } else { - fprintf(stderr, "sending remote command: \"%s\" via VNC_CONNECT" - " X property.\n", cmd); - set_vnc_connect_prop(cmd); - XFlush(dpy); - } + selection_str[0] = '\0'; + slen = 0; - if (query || wait) { - char line[VNC_CONNECT_MAX]; - int rc=1, i=0, max=70, ms_sl=50; + /* read the property value into selection_str: */ + do { + if (XGetWindowProperty(dpy, DefaultRootWindow(dpy), + XA_CUT_BUFFER0, nitems/4, PROP_MAX/16, False, + AnyPropertyType, &type, &format, &nitems, &bytes_after, + &data) == Success) { - if (!strcmp(cmd, "cmd=stop")) { - max = 20; - } - for (i=0; i PROP_MAX) { + /* too big */ + rfbLog("warning: truncating large CUT_BUFFER0" + " selection > %d bytes.\n", PROP_MAX); + XFree(data); break; } + memcpy(selection_str+slen, data, dlen); + slen += dlen; + selection_str[slen] = '\0'; + XFree(data); } - if (rc) { - fprintf(stderr, "error: could not connect to " - "an x11vnc server at %s (rc=%d)\n", - client_connect_file ? client_connect_file - : DisplayString(dpy), rc); - } - return rc; - } - return 0; -} + } while (bytes_after > 0); -int do_remote_query(char *remote_cmd, char *query_cmd, int remote_sync) { - char *rcmd = NULL, *qcmd = NULL; - int rc = 1; + selection_str[PROP_MAX] = '\0'; - if (remote_cmd) { - rcmd = (char *)malloc(strlen(remote_cmd) + 5); - strcpy(rcmd, "cmd="); - strcat(rcmd, remote_cmd); + if (! all_clients_initialized()) { + rfbLog("cutbuffer_send: no send: uninitialized clients\n"); + return; /* some clients initializing, cannot send */ } - if (query_cmd) { - qcmd = (char *)malloc(strlen(query_cmd) + 5); - strcpy(qcmd, "qry="); - strcat(qcmd, query_cmd); + + /* now send it to any connected VNC clients (rfbServerCutText) */ + if (!screen) { + return; } - - if (rcmd && qcmd) { - rc = send_remote_cmd(rcmd, 0, 1); - if (rc) { - free(rcmd); - free(qcmd); - return(rc); - } - rc = send_remote_cmd(qcmd, 1, 1); - } else if (rcmd) { - rc = send_remote_cmd(rcmd, 0, remote_sync); - free(rcmd); - } else if (qcmd) { - rc = send_remote_cmd(qcmd, 1, 1); - free(qcmd); - } - return rc; + rfbSendServerCutText(screen, selection_str, strlen(selection_str)); } -char *add_item(char *instr, char *item) { - char *p, *str; - int len, saw_item = 0; +/* + * "callback" for our SelectionNotify polling. We try to determine if + * the PRIMARY selection has changed (checking length and first CHKSZ bytes) + * and if it has we store it and send it off to any connected VNC clients. + * + * n.b.: our caller already has the X_LOCK. + * + * TODO: if we were willing to use libXt, we could perhaps get selection + * timestamps to speed up the checking... XtGetSelectionValue(). + * + * Also: XFIXES has XFixesSelectSelectionInput(). + */ +#define CHKSZ 32 +static void selection_send(XEvent *ev) { + Atom type; + int format, slen, dlen, oldlen, newlen, toobig = 0; + static int err = 0, sent_one = 0; + char before[CHKSZ], after[CHKSZ]; + unsigned long nitems = 0, bytes_after = 0; + unsigned char* data = NULL; - if (! instr || *instr == '\0') { - str = strdup(item); - return str; - } - len = strlen(instr) + 1 + strlen(item) + 1; - str = (char *)malloc(len); - str[0] = '\0'; + /* + * remember info about our last value of PRIMARY (or CUT_BUFFER0) + * so we can check for any changes below. + */ + oldlen = strlen(selection_str); + strncpy(before, selection_str, CHKSZ); - /* n.b. instr will be modified; caller replaces with returned string */ - p = strtok(instr, ","); - while (p) { - if (!strcmp(p, item)) { - if (saw_item) { - p = strtok(NULL, ","); - continue; + selection_str[0] = '\0'; + slen = 0; + + /* read in the current value of PRIMARY: */ + do { + if (XGetWindowProperty(dpy, ev->xselection.requestor, + ev->xselection.property, nitems/4, PROP_MAX/16, True, + AnyPropertyType, &type, &format, &nitems, &bytes_after, + &data) == Success) { + + dlen = nitems * (format/8); + if (slen + dlen > PROP_MAX) { + /* too big */ + toobig = 1; + XFree(data); + if (err) { /* cut down on messages */ + break; + } else { + err = 5; + } + rfbLog("warning: truncating large PRIMARY" + " selection > %d bytes.\n", PROP_MAX); + break; } - saw_item = 1; - } else if (*p == '\0') { - p = strtok(NULL, ","); - continue; - } - if (str[0]) { - strcat(str, ","); + memcpy(selection_str+slen, data, dlen); + slen += dlen; + selection_str[slen] = '\0'; + XFree(data); } - strcat(str, p); - p = strtok(NULL, ","); + } while (bytes_after > 0); + + if (! toobig) { + err = 0; + } else if (err) { + err--; } - if (! saw_item) { - if (str[0]) { - strcat(str, ","); - } - strcat(str, item); + + if (! sent_one) { + /* try to force a send first time in */ + oldlen = -1; + sent_one = 1; } - return str; -} -char *delete_item(char *instr, char *item) { - char *p, *str; - int len; + /* look for changes in the new value */ + newlen = strlen(selection_str); + strncpy(after, selection_str, CHKSZ); - if (! instr || *instr == '\0') { - str = strdup(""); - return str; + if (oldlen == newlen && strncmp(before, after, CHKSZ) == 0) { + /* evidently no change */ + return; } - len = strlen(instr) + 1; - str = (char *)malloc(len); - str[0] = '\0'; - - /* n.b. instr will be modified; caller replaces with returned string */ - p = strtok(instr, ","); - while (p) { - if (!strcmp(p, item) || *p == '\0') { - p = strtok(NULL, ","); - continue; - } - if (str[0]) { - strcat(str, ","); - } - strcat(str, p); - p = strtok(NULL, ","); + if (newlen == 0) { + /* do not bother sending a null string out */ + return; } - return str; -} -void if_8bpp_do_new_fb(void) { - if (bpp == 8) { - do_new_fb(0); - } else { - rfbLog(" bpp(%d) is not 8bpp, not resetting fb\n", bpp); + if (! all_clients_initialized()) { + rfbLog("selection_send: no send: uninitialized clients\n"); + return; /* some clients initializing, cannot send */ } -} -void check_black_fb(void) { + /* now send it to any connected VNC clients (rfbServerCutText) */ if (!screen) { return; } - if (new_fb_size_clients(screen) != client_count) { - rfbLog("trying to send a black fb for non-newfbsize" - " clients %d != %d\n", client_count, - new_fb_size_clients(screen)); - push_black_screen(4); - } + rfbSendServerCutText(screen, selection_str, newlen); } -int check_httpdir(void) { - if (http_dir) { - return 1; - } else { - char *prog = NULL, *httpdir, *q; - struct stat sbuf; - int len; +/* -- xevents.c -- */ - rfbLog("check_httpdir: trying to guess httpdir...\n"); - if (program_name[0] == '/') { - prog = strdup(program_name); - } else { - char cwd[1024]; - getcwd(cwd, 1024); - len = strlen(cwd) + 1 + strlen(program_name) + 1; - prog = (char *) malloc(len); - snprintf(prog, len, "%s/%s", cwd, program_name); - if (stat(prog, &sbuf) != 0) { - char *path = strdup(getenv("PATH")); - char *p, *base; - base = strrchr(program_name, '/'); - if (base) { - base++; - } else { - base = program_name; - } - - p = strtok(path, ":"); - while(p) { - free(prog); - len = strlen(p) + 1 + strlen(base) + 1; - prog = (char *) malloc(len); - snprintf(prog, len, "%s/%s", p, base); - if (stat(prog, &sbuf) == 0) { - break; - } - p = strtok(NULL, ":"); - } - free(path); - } - } +void initialize_vnc_connect_prop() { + vnc_connect_str[0] = '\0'; + vnc_connect_prop = XInternAtom(dpy, "VNC_CONNECT", False); +} + +void initialize_xevents(void) { + static int did_xselect_input = 0; + static int did_xcreate_simple_window = 0; + static int did_vnc_connect_prop = 0; + static int did_xfixes = 0; + static int did_xdamage = 0; + static int did_xrandr = 0; + + if ((watch_selection || vnc_connect) && !did_xselect_input) { /* - * /path/to/bin/x11vnc - * /path/to/bin/../share/x11vnc/classes - * 12345678901234567 + * register desired event(s) for notification. + * PropertyChangeMask is for CUT_BUFFER0 changes. + * XXX: does this cause a flood of other stuff? */ - if ((q = strrchr(prog, '/')) == NULL) { - rfbLog("check_httpdir: bad program path: %s\n", prog); - free(prog); - return 0; - } + X_LOCK; + XSelectInput(dpy, rootwin, PropertyChangeMask); + X_UNLOCK; + did_xselect_input = 1; + } + if (watch_selection && !did_xcreate_simple_window) { + /* create fake window for our selection ownership, etc */ - len = strlen(prog) + 17 + 1; - *q = '\0'; - httpdir = (char *) malloc(len); - snprintf(httpdir, len, "%s/../share/x11vnc/classes", prog); - free(prog); + X_LOCK; + selwin = XCreateSimpleWindow(dpy, rootwin, 0, 0, 1, 1, 0, 0, 0); + X_UNLOCK; + did_xcreate_simple_window = 1; + } - if (stat(httpdir, &sbuf) == 0) { - /* good enough for me */ - rfbLog("check_httpdir: guessed directory:\n"); - rfbLog(" %s\n", httpdir); - http_dir = httpdir; - return 1; - } else { - /* try some hardwires: */ - if (stat("/usr/local/share/x11vnc/classes", - &sbuf) == 0) { - http_dir = - strdup("/usr/local/share/x11vnc/classes"); - return 1; - } - if (stat("/usr/share/x11vnc/classes", &sbuf) == 0) { - http_dir = strdup("/usr/share/x11vnc/classes"); - return 1; - } - rfbLog("check_httpdir: bad guess:\n"); - rfbLog(" %s\n", httpdir); - return 0; - } + if (xrandr && !did_xrandr) { + initialize_xrandr(); + did_xrandr = 1; } -} - -void http_connections(int on) { - if (!screen) { - return; + if (vnc_connect && !did_vnc_connect_prop) { + initialize_vnc_connect_prop(); + did_vnc_connect_prop = 1; } - if (on) { - rfbLog("http_connections: turning on http service.\n"); - screen->httpInitDone = FALSE; - screen->httpDir = http_dir; - if (check_httpdir()) { - rfbHttpInitSockets(screen); - } - } else { - rfbLog("http_connections: turning off http service.\n"); - if (screen->httpListenSock > -1) { - close(screen->httpListenSock); - } - screen->httpListenSock = -1; - screen->httpDir = NULL; + if (xfixes_present && use_xfixes && !did_xfixes) { + initialize_xfixes(); + did_xfixes = 1; + } + if (xdamage_present && !did_xdamage) { + initialize_xdamage(); + did_xdamage = 1; } } -void reset_httpport(int old, int new) { - int hp = new; - if (hp < 0) { - rfbLog("reset_httpport: bad httpport: %d\n", hp); - } else if (hp == old) { - rfbLog("reset_httpport: unchanged httpport: %d\n", hp); - } else if (inetd) { - rfbLog("reset_httpport: cannot set httpport: %d" - " in inetd.\n", hp); - } else if (screen) { - screen->httpPort = hp; - screen->httpInitDone = FALSE; - if (screen->httpListenSock > -1) { - close(screen->httpListenSock); - } - rfbLog("reset_httpport: setting httpport %d -> %d.\n", - old == -1 ? hp : old, hp); - rfbHttpInitSockets(screen); - } +void print_xevent_bases(void) { + fprintf(stderr, "X event bases: xkb=%d, xtest=%d, xrandr=%d, " + "xfixes=%d, xdamage=%d, xtrap=%d\n", xkb_base_event_type, + xtest_base_event_type, xrandr_base_event_type, + xfixes_base_event_type, xdamage_base_event_type, + xtrap_base_event_type); + fprintf(stderr, " MapNotify=%d, ClientMsg=%d PropNotify=%d " + "SelNotify=%d, SelRequest=%d\n", MappingNotify, ClientMessage, + PropertyNotify, SelectionNotify, SelectionRequest); + fprintf(stderr, " SelClear=%d, Expose=%d\n", SelectionClear, Expose); } -void reset_rfbport(int old, int new) { - int rp = new; - if (rp < 0) { - rfbLog("reset_rfbport: bad rfbport: %d\n", rp); - } else if (rp == old) { - rfbLog("reset_rfbport: unchanged rfbport: %d\n", rp); - } else if (inetd) { - rfbLog("reset_rfbport: cannot set rfbport: %d" - " in inetd.\n", rp); - } else if (screen) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int maxfd; - if (rp == 0) { - screen->autoPort = TRUE; - } else { - screen->autoPort = FALSE; - } - screen->port = rp; - screen->socketInitDone = FALSE; +void sync_tod_with_servertime() { + static Atom servertime = None; + XEvent xev; + char diff[64]; + static int seq = 0; + int i; - if (screen->listenSock > -1) { - close(screen->listenSock); - } + if (! servertime) { + servertime = XInternAtom(dpy, "X11VNC_SERVERTIME_DIFF", False); + } + if (! servertime) { + return; + } - rfbLog("reset_rfbport: setting rfbport %d -> %d.\n", - old == -1 ? rp : old, rp); - rfbInitSockets(screen); + XSync(dpy, False); + while (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { + ; + } - maxfd = screen->maxFd; - if (screen->udpSock > 0 && screen->udpSock > maxfd) { - maxfd = screen->udpSock; - } - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->sock > -1) { - FD_SET(cl->sock, &(screen->allFds)); - if (cl->sock > maxfd) { - maxfd = cl->sock; + snprintf(diff, 64, "%.6f/%08d", servertime_diff, seq++); + XChangeProperty(dpy, rootwin, servertime, XA_STRING, 8, + PropModeReplace, (unsigned char *) diff, strlen(diff)); + XSync(dpy, False); + + for (i=0; i<10; i++) { + int k, got = 0; + + for (k = 0; k<5; k++) { + while (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { + if (xev.xproperty.atom == servertime) { + double now = 0.0, stime; + + dtime(&now); + stime = (double) xev.xproperty.time; + stime = stime/1000.0; + servertime_diff = now - stime; + if (0) rfbLog("set servertime_diff: " + "%.6f\n", servertime_diff); + got = 1; } } } - rfbReleaseClientIterator(iter); - - screen->maxFd = maxfd; - - set_vnc_desktop_name(); + if (got) { + break; + } + usleep(1000); } } /* - * Do some sanity checking of the permissions on the XAUTHORITY and the - * -connect file. This is -privremote. What should be done is check - * for an empty host access list, currently we lazily do not bring in - * libXau yet. + * This routine is periodically called to check for selection related + * and other X11 events and respond to them as needed. */ -int remote_control_access_ok(void) { - struct stat sbuf; +void check_xevents(void) { + XEvent xev; + int have_clients = 0; + static int sent_some_sel = 0; + static time_t last_request = 0; + static time_t last_call = 0; + static time_t last_bell = 0; + static time_t last_init_check = 0; + static time_t last_sync = 0; + static time_t last_time_sync = 0; + time_t now = time(0); - if (client_connect_file) { - if (stat(client_connect_file, &sbuf) == 0) { - if (sbuf.st_mode & S_IWOTH) { - rfbLog("connect file is writable by others.\n"); - rfbLog(" %s\n", client_connect_file); - return 0; - } - if (sbuf.st_mode & S_IWGRP) { - rfbLog("connect file is writable by group.\n"); - rfbLog(" %s\n", client_connect_file); - return 0; - } - } + if (raw_fb && ! dpy) return; /* raw_fb hack */ + + if (now > last_init_check+1) { + last_init_check = now; + initialize_xevents(); } - if (dpy) { - char tmp[1000]; - char *home, *xauth; - char *dpy_str = DisplayString(dpy); - Display *dpy2; + if (screen && screen->clientHead) { + have_clients = 1; + } - home = get_home_dir(); - if (getenv("XAUTHORITY") != NULL) { - xauth = getenv("XAUTHORITY"); - } else if (home) { - int len = 1000 - strlen("/.Xauthority") - 1; - strncpy(tmp, home, len); - strcat(tmp, "/.Xauthority"); - xauth = tmp; - } else { - rfbLog("cannot determine default XAUTHORITY.\n"); - return 0; + X_LOCK; + /* + * There is a bug where we have to wait before sending text to + * the client... so instead of sending right away we wait a + * the few seconds. + */ + if (have_clients && watch_selection && !sent_some_sel + && now > last_client + sel_waittime) { + if (XGetSelectionOwner(dpy, XA_PRIMARY) == None) { + cutbuffer_send(); } - if (home) { - free(home); + sent_some_sel = 1; + } + if (! have_clients) { + /* + * If we don't have clients we can miss the X server + * going away until a client connects. + */ + static time_t last_X_ping = 0; + if (now > last_X_ping + 5) { + last_X_ping = now; + XGetSelectionOwner(dpy, XA_PRIMARY); } - if (stat(xauth, &sbuf) == 0) { - if (sbuf.st_mode & S_IWOTH) { - rfbLog("XAUTHORITY is writable by others!!\n"); - rfbLog(" %s\n", xauth); - return 0; - } - if (sbuf.st_mode & S_IWGRP) { - rfbLog("XAUTHORITY is writable by group!!\n"); - rfbLog(" %s\n", xauth); - return 0; - } - if (sbuf.st_mode & S_IROTH) { - rfbLog("XAUTHORITY is readable by others.\n"); - rfbLog(" %s\n", xauth); - return 0; - } - if (sbuf.st_mode & S_IRGRP) { - rfbLog("XAUTHORITY is readable by group.\n"); - rfbLog(" %s\n", xauth); - return 0; + } + + if (no_autorepeat && have_clients && no_repeat_countdown) { + static time_t last_check = 0; + if (now > last_check + 1 && ! view_only) { + last_check = now; + X_UNLOCK; + if (get_autorepeat_state() != 0) { + int n = no_repeat_countdown - 1; + if (n >= 0) { + rfbLog("Battling with something for " + "-norepeat!! (%d resets left)\n",n); + } else { + rfbLog("Battling with something for " + "-norepeat!!\n"); + } + if (no_repeat_countdown > 0) { + no_repeat_countdown--; + } + autorepeat(1); + autorepeat(0); } + X_LOCK; } + } - if (getenv("XAUTHORITY")) { - xauth = strdup(getenv("XAUTHORITY")); - } else { - xauth = NULL; + if (now > last_call+1) { + /* we only check these once a second or so. */ + int n = 0; + while (XCheckTypedEvent(dpy, MappingNotify, &xev)) { + XRefreshKeyboardMapping((XMappingEvent *) &xev); + n++; } - set_env("XAUTHORITY", "/impossible/xauthfile"); - - fprintf(stderr, "\nChecking if display %s requires " - "XAUTHORITY\n", dpy_str); - fprintf(stderr, " (ignore any Xlib: errors that follow)\n"); - dpy2 = XOpenDisplay(dpy_str); - - if (xauth) { - set_env("XAUTHORITY", xauth); - free(xauth); - } else { - xauth = getenv("XAUTHORITY"); - if (xauth) { - *(xauth-2) = '_'; /* yow */ + if (n && use_modifier_tweak) { + X_UNLOCK; + initialize_modtweak(); + X_LOCK; + } + if (xtrap_base_event_type) { + int base = xtrap_base_event_type; + while (XCheckTypedEvent(dpy, base, &xev)) { + ; } } - if (dpy2) { - rfbLog("XAUTHORITY is not required on display.\n"); - rfbLog(" %s\n", DisplayString(dpy)); - XCloseDisplay(dpy2); - return 0; + if (xtest_base_event_type) { + int base = xtest_base_event_type; + while (XCheckTypedEvent(dpy, base, &xev)) { + ; + } + } + /* + * we can get ClientMessage from our XSendEvent() call in + * selection_request(). + */ + while (XCheckTypedEvent(dpy, ClientMessage, &xev)) { + ; } } - return 1; -} -/* - * Huge, ugly switch to handle all remote commands and queries - * -remote/-R and -query/-Q. - */ -char *process_remote_cmd(char *cmd, int stringonly) { -#if REMOTE_CONTROL - char *p = cmd; - char *co = ""; - char buf[VNC_CONNECT_MAX]; - int bufn = VNC_CONNECT_MAX; - int query = 0; - static char *prev_cursors_mode = NULL; - static int first = 1; + /* check for CUT_BUFFER0 and VNC_CONNECT changes: */ + if (XCheckTypedEvent(dpy, PropertyNotify, &xev)) { + if (xev.type == PropertyNotify) { + if (xev.xproperty.atom == XA_CUT_BUFFER0) { + /* + * Go retrieve CUT_BUFFER0 and send it. + * + * set_cutbuffer is a flag to try to avoid + * processing our own cutbuffer changes. + */ + if (have_clients && watch_selection + && ! set_cutbuffer) { + cutbuffer_send(); + sent_some_sel = 1; + } + set_cutbuffer = 0; + } else if (vnc_connect && vnc_connect_prop != None + && xev.xproperty.atom == vnc_connect_prop) { + + /* + * Go retrieve VNC_CONNECT string. + */ + read_vnc_connect_prop(); + } + } + } - if (! accept_remote_cmds) { - rfbLog("remote commands disabled: %s\n", cmd); - return NULL; + if (now > last_time_sync + 30) { + sync_tod_with_servertime(); + last_time_sync = now; } - if (first && priv_remote) { - if (! remote_control_access_ok()) { - rfbLog("disabling remote commands in -privremote " - "mode.\n"); - accept_remote_cmds = 0; - return NULL; +#if LIBVNCSERVER_HAVE_LIBXRANDR + if (xrandr) { + check_xrandr_event("check_xevents"); + } +#endif +#if LIBVNCSERVER_HAVE_LIBXFIXES + if (xfixes_present && use_xfixes && xfixes_base_event_type) { + if (XCheckTypedEvent(dpy, xfixes_base_event_type + + XFixesCursorNotify, &xev)) { + got_xfixes_cursor_notify++; } } - first = 0; +#endif - strcpy(buf, ""); - if (strstr(cmd, "cmd=") == cmd) { - p += strlen("cmd="); - } else if (strstr(cmd, "qry=") == cmd) { - query = 1; - if (strchr(cmd, ',')) { - /* comma separated batch mode */ - char *s, *q, *res; - char tmp[512]; - strcpy(buf, ""); - s = strdup(cmd + strlen("qry=")); - q = strtok(s, ","); - while (q) { - strcpy(tmp, "qry="); - strncat(tmp, q, 500); - res = process_remote_cmd(tmp, 1); - if (res && strlen(buf)+strlen(res) - >= VNC_CONNECT_MAX - 1) { - rfbLog("overflow in process_remote_cmd:" - " %s -- %s\n", buf, res); - free(res); - break; - } - if (res) { - strcat(buf, res); - free(res); - } - q = strtok(NULL, ","); - if (q) { - strcat(buf, ","); + /* check for our PRIMARY request notification: */ + if (watch_primary) { + if (XCheckTypedEvent(dpy, SelectionNotify, &xev)) { + if (xev.type == SelectionNotify && + xev.xselection.requestor == selwin && + xev.xselection.selection == XA_PRIMARY && + xev.xselection.property != None && + xev.xselection.target == XA_STRING) { + + /* go retrieve PRIMARY and check it */ + if (now > last_client + sel_waittime + || sent_some_sel) { + selection_send(&xev); } } - free(s); - goto qry; } - p += strlen("qry="); - } else { - rfbLog("ignoring malformed command: %s\n", cmd); - goto done; + if (now > last_request) { + /* + * Every second or two, request PRIMARY, unless we + * already own it or there is no owner or we have + * no clients. + * TODO: even at this low rate we should look into + * and performance problems in odds cases, etc. + */ + last_request = now; + if (! own_selection && have_clients && + XGetSelectionOwner(dpy, XA_PRIMARY) != None) { + XConvertSelection(dpy, XA_PRIMARY, XA_STRING, + XA_STRING, selwin, CurrentTime); + } + } } - /* always call like: COLON_CHECK("foobar:") */ -#define COLON_CHECK(str) \ - if (strstr(p, str) != p) { \ - co = ":"; \ - if (! query) { \ - goto done; \ - } \ - } else { \ - char *q = strchr(p, ':'); \ - if (query && q != NULL) { \ - *(q+1) = '\0'; \ - } \ - } + if (own_selection) { + /* we own PRIMARY, see if someone requested it: */ + if (XCheckTypedEvent(dpy, SelectionRequest, &xev)) { + if (xev.type == SelectionRequest && + xev.xselectionrequest.selection == XA_PRIMARY) { + selection_request(&xev); + } + } -#define NOTAPP \ - if (query) { \ - if (strchr(p, ':')) { \ - snprintf(buf, bufn, "ans=%sN/A", p); \ - } else { \ - snprintf(buf, bufn, "ans=%s:N/A", p); \ - } \ - goto qry; \ + /* we own PRIMARY, see if we no longer own it: */ + if (XCheckTypedEvent(dpy, SelectionClear, &xev)) { + if (xev.type == SelectionClear && + xev.xselectionclear.selection == XA_PRIMARY) { + + own_selection = 0; + if (xcut_str) { + free(xcut_str); + xcut_str = NULL; + } + } + } } -#define NOTAPPRO \ - if (query) { \ - if (strchr(p, ':')) { \ - snprintf(buf, bufn, "aro=%sN/A", p); \ - } else { \ - snprintf(buf, bufn, "aro=%s:N/A", p); \ - } \ - goto qry; \ + if (watch_bell || now > last_bell+1) { + last_bell = now; + check_bell_event(); } -/* - * Maybe add: passwdfile logfile bg rfbauth passwd... - */ - if (!strcmp(p, "stop") || !strcmp(p, "quit") || - !strcmp(p, "exit") || !strcmp(p, "shutdown")) { - NOTAPP - close_all_clients(); - rfbLog("remote_cmd: setting shut_down flag\n"); - shut_down = 1; +#ifndef DEBUG_XEVENTS +#define DEBUG_XEVENTS 1 +#endif +#if DEBUG_XEVENTS + if (debug_xevents) { + static time_t last_check = 0; + static time_t reminder = 0; + static int freq = 0; - } else if (!strcmp(p, "ping")) { - query = 1; - if (rfb_desktop_name) { - snprintf(buf, bufn, "ans=%s:%s", p, rfb_desktop_name); - } else { - snprintf(buf, bufn, "ans=%s:%s", p, "unknown"); + if (! freq) { + if (getenv("X11VNC_REMINDER_RATE")) { + freq = atoi(getenv("X11VNC_REMINDER_RATE")); + } else { + freq = 300; + } } - goto qry; - } else if (!strcmp(p, "blacken") || !strcmp(p, "zero")) { - NOTAPP - push_black_screen(4); - } else if (!strcmp(p, "refresh")) { - NOTAPP - refresh_screen(); - } else if (!strcmp(p, "reset")) { - NOTAPP - do_new_fb(1); - } else if (strstr(p, "zero:") == p) { /* skip-cmd-list */ - int x1, y1, x2, y2; - NOTAPP - p += strlen("zero:"); - if (sscanf(p, "%d,%d,%d,%d", &x1, &y1, &x2, &y2) == 4) { + if (now > last_check + 1) { + int ev_type_max = 300, ev_size = 400; + XEvent xevs[400]; + int i, tot = XEventsQueued(dpy, QueuedAlready); + + if (reminder == 0 || (tot && now > reminder + freq)) { + print_xevent_bases(); + reminder = now; + } + last_check = now; + + if (tot) { + fprintf(stderr, "Total events queued: %d\n", + tot); + } + for (i=1; i= ev_size) { + break; + } + } + if (n) { + fprintf(stderr, " %d%s events of type" + " %d queued\n", n, + (n >= ev_size) ? "+" : "", i); + } + for (k=n-1; k >= 0; k--) { + XPutBackEvent(dpy, xevs+k); + } + } + } + } +#endif + + if (now > last_sync + 1200) { + /* kludge for any remaining event leaks */ + int bugout = use_xdamage ? 500 : 50; + if (last_sync != 0) { + int qlen = XEventsQueued(dpy, QueuedAlready); + if (qlen >= bugout) { + rfbLog("event leak: %d queued, " + " calling XSync(dpy, True)\n", qlen); + rfbLog(" for diagnostics run: 'x11vnc -R" + " debug_xevents:1'\n"); + XSync(dpy, True); + } + } + last_sync = now; + } + X_UNLOCK; + + last_call = now; +} + +/* + * hook called when a VNC client sends us some "XCut" text (rfbClientCutText). + */ +void xcut_receive(char *text, int len, rfbClientPtr cl) { + allowed_input_t input; + + if (raw_fb && ! dpy) return; /* raw_fb hack */ + + if (!watch_selection) { + return; + } + if (view_only) { + return; + } + if (text == NULL || len == 0) { + return; + } + get_allowed_input(cl, &input); + if (!input.keystroke && !input.motion && !input.button) { + /* maybe someday KMBC for cut text... */ + return; + } + + X_LOCK; + + /* associate this text with PRIMARY (and SECONDARY...) */ + if (! own_selection) { + own_selection = 1; + /* we need to grab the PRIMARY selection */ + XSetSelectionOwner(dpy, XA_PRIMARY, selwin, CurrentTime); + XFlush(dpy); + } + + /* duplicate the text string for our own use. */ + if (xcut_str != NULL) { + free(xcut_str); + } + xcut_str = (unsigned char *) + malloc((size_t) (len+1) * sizeof(unsigned char)); + strncpy(xcut_str, text, len); + xcut_str[len] = '\0'; /* make sure null terminated */ + + /* copy this text to CUT_BUFFER0 as well: */ + XChangeProperty(dpy, rootwin, XA_CUT_BUFFER0, XA_STRING, 8, + PropModeReplace, (unsigned char *) text, len); + XFlush(dpy); + + X_UNLOCK; + + set_cutbuffer = 1; +} + +/* -- remote.c -- */ + +/* + * for the wild-n-crazy -remote/-R interface. + */ +int send_remote_cmd(char *cmd, int query, int wait) { + FILE *in = NULL; + + if (client_connect_file) { + in = fopen(client_connect_file, "w"); + if (in == NULL) { + fprintf(stderr, "send_remote_cmd: could not open " + "connect file \"%s\" for writing\n", + client_connect_file); + perror("fopen"); + return 1; + } + } else if (vnc_connect_prop == None) { + initialize_vnc_connect_prop(); + if (vnc_connect_prop == None) { + fprintf(stderr, "send_remote_cmd: could not obtain " + "VNC_CONNECT X property\n"); + return 1; + } + } + + if (in != NULL) { + fprintf(stderr, "sending remote command: \"%s\"\nvia connect" + " file: %s\n", cmd, client_connect_file); + fprintf(in, "%s\n", cmd); + fclose(in); + } else { + fprintf(stderr, "sending remote command: \"%s\" via VNC_CONNECT" + " X property.\n", cmd); + set_vnc_connect_prop(cmd); + XFlush(dpy); + } + + if (query || wait) { + char line[VNC_CONNECT_MAX]; + int rc=1, i=0, max=70, ms_sl=50; + + if (!strcmp(cmd, "cmd=stop")) { + max = 20; + } + for (i=0; ihttpInitDone = FALSE; + screen->httpDir = http_dir; + if (check_httpdir()) { + rfbHttpInitSockets(screen); + } + } else { + rfbLog("http_connections: turning off http service.\n"); + if (screen->httpListenSock > -1) { + close(screen->httpListenSock); + } + screen->httpListenSock = -1; + screen->httpDir = NULL; + } +} + +void reset_httpport(int old, int new) { + int hp = new; + if (hp < 0) { + rfbLog("reset_httpport: bad httpport: %d\n", hp); + } else if (hp == old) { + rfbLog("reset_httpport: unchanged httpport: %d\n", hp); + } else if (inetd) { + rfbLog("reset_httpport: cannot set httpport: %d" + " in inetd.\n", hp); + } else if (screen) { + screen->httpPort = hp; + screen->httpInitDone = FALSE; + if (screen->httpListenSock > -1) { + close(screen->httpListenSock); + } + rfbLog("reset_httpport: setting httpport %d -> %d.\n", + old == -1 ? hp : old, hp); + rfbHttpInitSockets(screen); + } +} + +void reset_rfbport(int old, int new) { + int rp = new; + if (rp < 0) { + rfbLog("reset_rfbport: bad rfbport: %d\n", rp); + } else if (rp == old) { + rfbLog("reset_rfbport: unchanged rfbport: %d\n", rp); + } else if (inetd) { + rfbLog("reset_rfbport: cannot set rfbport: %d" + " in inetd.\n", rp); + } else if (screen) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int maxfd; + if (rp == 0) { + screen->autoPort = TRUE; + } else { + screen->autoPort = FALSE; + } + screen->port = rp; + screen->socketInitDone = FALSE; + + if (screen->listenSock > -1) { + close(screen->listenSock); + } + + rfbLog("reset_rfbport: setting rfbport %d -> %d.\n", + old == -1 ? rp : old, rp); + rfbInitSockets(screen); + + maxfd = screen->maxFd; + if (screen->udpSock > 0 && screen->udpSock > maxfd) { + maxfd = screen->udpSock; + } + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (cl->sock > -1) { + FD_SET(cl->sock, &(screen->allFds)); + if (cl->sock > maxfd) { + maxfd = cl->sock; + } + } + } + rfbReleaseClientIterator(iter); + + screen->maxFd = maxfd; + + set_vnc_desktop_name(); + } +} + +/* + * Do some sanity checking of the permissions on the XAUTHORITY and the + * -connect file. This is -privremote. What should be done is check + * for an empty host access list, currently we lazily do not bring in + * libXau yet. + */ +int remote_control_access_ok(void) { + struct stat sbuf; + + if (client_connect_file) { + if (stat(client_connect_file, &sbuf) == 0) { + if (sbuf.st_mode & S_IWOTH) { + rfbLog("connect file is writable by others.\n"); + rfbLog(" %s\n", client_connect_file); + return 0; + } + if (sbuf.st_mode & S_IWGRP) { + rfbLog("connect file is writable by group.\n"); + rfbLog(" %s\n", client_connect_file); + return 0; + } + } + } + + if (dpy) { + char tmp[1000]; + char *home, *xauth; + char *dpy_str = DisplayString(dpy); + Display *dpy2; + XHostAddress *xha; + Bool enabled; + int n; + + home = get_home_dir(); + if (getenv("XAUTHORITY") != NULL) { + xauth = getenv("XAUTHORITY"); + } else if (home) { + int len = 1000 - strlen("/.Xauthority") - 1; + strncpy(tmp, home, len); + strcat(tmp, "/.Xauthority"); + xauth = tmp; + } else { + rfbLog("cannot determine default XAUTHORITY.\n"); + return 0; + } + if (home) { + free(home); + } + if (stat(xauth, &sbuf) == 0) { + if (sbuf.st_mode & S_IWOTH) { + rfbLog("XAUTHORITY is writable by others!!\n"); + rfbLog(" %s\n", xauth); + return 0; + } + if (sbuf.st_mode & S_IWGRP) { + rfbLog("XAUTHORITY is writable by group!!\n"); + rfbLog(" %s\n", xauth); + return 0; + } + if (sbuf.st_mode & S_IROTH) { + rfbLog("XAUTHORITY is readable by others.\n"); + rfbLog(" %s\n", xauth); + return 0; + } + if (sbuf.st_mode & S_IRGRP) { + rfbLog("XAUTHORITY is readable by group.\n"); + rfbLog(" %s\n", xauth); + return 0; + } + } + + xha = XListHosts(dpy, &n, &enabled); + if (! enabled) { + rfbLog("X access control is disabled, X clients can\n"); + rfbLog(" connect from any host. Run 'xhost -'\n"); + return 0; + } + if (xha) { + int i; + rfbLog("The following hosts can connect w/o X11 " + "auth:\n"); + for (i=0; i 1) { @@ -11940,7 +13305,6 @@ void setup_cursors(void) { first = 0; if (screen) { - RFBUNDRAWCURSOR(screen); screen->cursor = NULL; LOCK(screen->cursorMutex); } @@ -12515,7 +13879,6 @@ rfbCursorPtr pixels2curs(unsigned long *pixels, int w, int h, c->cleanupRichSource = FALSE; c->richSource = rich; -#if !OLD_TREE if (alpha_blend && !indexed_color) { c->alphaSource = alpha; c->alphaPreMultiplied = TRUE; @@ -12523,8 +13886,6 @@ rfbCursorPtr pixels2curs(unsigned long *pixels, int w, int h, free(alpha); c->alphaSource = NULL; } -#endif - return c; } @@ -12618,8 +13979,6 @@ int get_xfixes_cursor(int init) { } } - RFBUNDRAWCURSOR(screen); - /* we need to create the cursor and overwrite oldest */ use = oldest; if (cursors[use]->rfb) { @@ -12627,11 +13986,9 @@ int get_xfixes_cursor(int init) { if (cursors[use]->rfb->richSource) { free(cursors[use]->rfb->richSource); } -#if !OLD_TREE if (cursors[use]->rfb->alphaSource) { free(cursors[use]->rfb->alphaSource); } -#endif if (cursors[use]->rfb->source) { free(cursors[use]->rfb->source); } @@ -12828,6428 +14185,7446 @@ int get_which_cursor(void) { } } } - return which; + return which; +} + +void set_cursor_was_changed(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + + if (! s) { + return; + } + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + cl->cursorWasChanged = TRUE; + } + rfbReleaseClientIterator(iter); +} + +void set_cursor_was_moved(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + + if (! s) { + return; + } + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + cl->cursorWasMoved = TRUE; + } + rfbReleaseClientIterator(iter); +} + +void restore_cursor_shape_updates(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int count = 0; + + if (! s || ! s->clientHead) { + return; + } + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + int changed = 0; + ClientData *cd = (ClientData *) cl->clientData; + + if (cd->had_cursor_shape_updates) { + rfbLog("restoring enableCursorShapeUpdates for client" + " 0x%x\n", cl); + cl->enableCursorShapeUpdates = TRUE; + changed = 1; + } + if (cd->had_cursor_pos_updates) { + rfbLog("restoring enableCursorPosUpdates for client" + " 0x%x\n", cl); + cl->enableCursorPosUpdates = TRUE; + changed = 1; + } + if (changed) { + cl->cursorWasChanged = TRUE; + count++; + } + } + rfbReleaseClientIterator(iter); +} + +void disable_cursor_shape_updates(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + + if (! s || ! s->clientHead) { + return; + } + + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + ClientData *cd; + cd = (ClientData *) cl->clientData; + + if (cl->enableCursorShapeUpdates) { + cd->had_cursor_shape_updates = 1; + } + if (cl->enableCursorPosUpdates) { + cd->had_cursor_pos_updates = 1; + } + + cl->enableCursorShapeUpdates = FALSE; + cl->enableCursorPosUpdates = FALSE; + cl->cursorWasChanged = FALSE; + } + rfbReleaseClientIterator(iter); +} + +int cursor_shape_updates_clients(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int count = 0; + + if (! s) { + return 0; + } + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (cl->enableCursorShapeUpdates) { + count++; + } + } + rfbReleaseClientIterator(iter); + return count; +} + +int cursor_pos_updates_clients(rfbScreenInfoPtr s) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int count = 0; + + if (! s) { + return 0; + } + iter = rfbGetClientIterator(s); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (cl->enableCursorPosUpdates) { + count++; + } + } + rfbReleaseClientIterator(iter); + return count; +} + +/* + * Record rfb cursor position screen->cursorX, etc (a la defaultPtrAddEvent()) + * Then set up for sending rfbCursorPosUpdates back + * to clients that understand them. This seems to be TightVNC specific. + */ +void cursor_position(int x, int y) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + int cnt = 0, nonCursorPosUpdates_clients = 0; + int x_in = x, y_in = y; + + /* x and y are current positions of X11 pointer on the X11 display */ + if (!screen) { + return; + } + + if (scaling) { + x = ((double) x / dpy_x) * scaled_x; + x = nfix(x, scaled_x); + y = ((double) y / dpy_y) * scaled_y; + y = nfix(y, scaled_y); + } + + if (x == screen->cursorX && y == screen->cursorY) { + return; + } + + LOCK(screen->cursorMutex); + screen->cursorX = x; + screen->cursorY = y; + UNLOCK(screen->cursorMutex); + + iter = rfbGetClientIterator(screen); + while( (cl = rfbClientIteratorNext(iter)) ) { + if (! cl->enableCursorPosUpdates) { + nonCursorPosUpdates_clients++; + continue; + } + if (! cursor_pos_updates) { + continue; + } + if (cl == last_pointer_client) { + /* + * special case if this client was the last one to + * send a pointer position. + */ + if (x_in == cursor_x && y_in == cursor_y) { + cl->cursorWasMoved = FALSE; + } else { + /* an X11 app evidently warped the pointer */ + if (debug_pointer) { + rfbLog("cursor_position: warp " + "detected dx=%3d dy=%3d\n", + cursor_x - x, cursor_y - y); + } + cl->cursorWasMoved = TRUE; + cnt++; + } + } else { + cl->cursorWasMoved = TRUE; + cnt++; + } + } + rfbReleaseClientIterator(iter); + + if (debug_pointer && cnt) { + rfbLog("cursor_position: sent position x=%3d y=%3d to %d" + " clients\n", x, y, cnt); + } +} + +void set_rfb_cursor(int which) { + + if (! show_cursor) { + return; + } + if (! screen) { + return; + } + + if (!cursors[which] || !cursors[which]->rfb) { + rfbLog("non-existent cursor: which=%d\n", which); + return; + } else { + rfbSetCursor(screen, cursors[which]->rfb); + } +} + +void set_no_cursor(void) { + set_rfb_cursor(CURS_EMPTY); +} + +void set_cursor(int x, int y, int which) { + static int last = -1; + if (which < 0) { + which = last; + } + if (last < 0 || which != last) { + set_rfb_cursor(which); + } + last = which; +} + +/* + * routine called periodically to update cursor aspects, this catches + * warps and cursor shape changes. + */ +void check_x11_pointer(void) { + Window root_w, child_w; + rfbBool ret; + int root_x, root_y, win_x, win_y; + int x, y; + unsigned int mask; + + if (raw_fb && ! dpy) return; /* raw_fb hack */ + + X_LOCK; + ret = XQueryPointer(dpy, rootwin, &root_w, &child_w, &root_x, &root_y, + &win_x, &win_y, &mask); + X_UNLOCK; + + if (! ret) { + return; + } + if (debug_pointer) { + static int last_x = -1, last_y = -1; + if (root_x != last_x || root_y != last_y) { + rfbLog("XQueryPointer: x:%4d, y:%4d)\n", + root_x, root_y); + } + last_x = root_x; + last_y = root_y; + } + + /* offset subtracted since XQueryPointer relative to rootwin */ + x = root_x - off_x - coff_x; + y = root_y - off_y - coff_y; + + /* record the cursor position in the rfb screen */ + cursor_position(x, y); + + /* change the cursor shape if necessary */ + set_cursor(x, y, get_which_cursor()); } -#if OLD_TREE +/* -- screen.c -- */ /* - * Some utilities for marking the little cursor patch region as - * modified, etc. + * X11 and rfb display/screen related routines */ -void mark_cursor_patch_modified(rfbScreenInfoPtr s, int old) { - int curx, cury, xhot, yhot, w, h; - int x1, x2, y1, y2; - if (! s || ! s->cursor) { - return; +/* + * Some handling of 8bpp PseudoColor colormaps. Called for initializing + * the clients and dynamically if -flashcmap is specified. + */ +#define NCOLOR 256 +void set_colormap(int reset) { + static int first = 1; + static XColor color[NCOLOR], prev[NCOLOR]; + Colormap cmap; + Visual *vis; + int i, ncells, diffs = 0; + + if (reset) { + first = 1; + if (screen->colourMap.data.shorts) { + free(screen->colourMap.data.shorts); + } } - if (old) { - /* use oldCursor pos */ - curx = s->oldCursorX; - cury = s->oldCursorY; - } else { - curx = s->cursorX; - cury = s->cursorY; + if (first) { + screen->colourMap.count = NCOLOR; + screen->serverFormat.trueColour = FALSE; + screen->colourMap.is16 = TRUE; + screen->colourMap.data.shorts = (unsigned short*) + malloc(3*sizeof(short) * NCOLOR); } - - xhot = s->cursor->xhot; - yhot = s->cursor->yhot; - w = s->cursor->width; - h = s->cursor->height; - x1 = curx - xhot; - x2 = x1 + w; - x1 = nfix(x1, s->width); - x2 = nfix(x2, s->width); + for (i=0; i < NCOLOR; i++) { + prev[i].red = color[i].red; + prev[i].green = color[i].green; + prev[i].blue = color[i].blue; + } - y1 = cury - yhot; - y2 = y1 + h; - y1 = nfix(y1, s->height); - y2 = nfix(y2, s->height); + X_LOCK; - rfbMarkRectAsModified(s, x1, y1, x1+x2, y1+y2); -} -#endif + cmap = DefaultColormap(dpy, scr); + ncells = CellsOfScreen(ScreenOfDisplay(dpy, scr)); + vis = default_visual; -void set_cursor_was_changed(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; + if (subwin) { + XWindowAttributes attr; - if (! s) { - return; + if (XGetWindowAttributes(dpy, window, &attr)) { + cmap = attr.colormap; + vis = attr.visual; + ncells = vis->map_entries; + } } - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - cl->cursorWasChanged = TRUE; + + if (ncells != NCOLOR) { + if (first && ! quiet) { + rfbLog("set_colormap: number of cells is %d " + "instead of %d.\n", ncells, NCOLOR); + } + if (! shift_cmap) { + screen->colourMap.count = ncells; + } } - rfbReleaseClientIterator(iter); -} -void set_cursor_was_moved(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; + if (flash_cmap && ! first) { + XWindowAttributes attr; + Window r, c; + int rx, ry, wx, wy, tries = 0; + unsigned int m; - if (! s) { - return; + c = window; + while (c && tries++ < 16) { + /* XXX XQueryTree somehow? */ + XQueryPointer(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &m); + if (c && XGetWindowAttributes(dpy, c, &attr)) { + if (attr.colormap && attr.map_installed) { + cmap = attr.colormap; + vis = attr.visual; + ncells = vis->map_entries; + break; + } + } else { + break; + } + } } - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - cl->cursorWasMoved = TRUE; + if (ncells > NCOLOR && ! quiet) { + rfbLog("set_colormap: big problem: ncells=%d > %d\n", + ncells, NCOLOR); } - rfbReleaseClientIterator(iter); -} -void restore_cursor_shape_updates(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int count = 0; - - if (! s || ! s->clientHead) { - return; + if (vis->class == TrueColor || vis->class == DirectColor) { + /* + * Kludge to make 8bpp TrueColor & DirectColor be like + * the StaticColor map. The ncells = 8 is "8 per subfield" + * mentioned in xdpyinfo. Looks OK... perhaps fortuitously. + */ + if (ncells == 8 && ! shift_cmap) { + ncells = NCOLOR; + } } - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - int changed = 0; - ClientData *cd = (ClientData *) cl->clientData; - if (cd->had_cursor_shape_updates) { - rfbLog("restoring enableCursorShapeUpdates for client" - " 0x%x\n", cl); - cl->enableCursorShapeUpdates = TRUE; - changed = 1; - } - if (cd->had_cursor_pos_updates) { - rfbLog("restoring enableCursorPosUpdates for client" - " 0x%x\n", cl); - cl->enableCursorPosUpdates = TRUE; - changed = 1; - } - if (changed) { - cl->cursorWasChanged = TRUE; - count++; - } + for (i=0; i < ncells; i++) { + color[i].pixel = i; + color[i].pad = 0; } - rfbReleaseClientIterator(iter); -} -void disable_cursor_shape_updates(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; + XQueryColors(dpy, cmap, color, ncells); - if (! s || ! s->clientHead) { - return; - } + X_UNLOCK; - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - ClientData *cd; - cd = (ClientData *) cl->clientData; + for(i = ncells - 1; i >= 0; i--) { + int k = i + shift_cmap; - if (cl->enableCursorShapeUpdates) { - cd->had_cursor_shape_updates = 1; + screen->colourMap.data.shorts[i*3+0] = color[i].red; + screen->colourMap.data.shorts[i*3+1] = color[i].green; + screen->colourMap.data.shorts[i*3+2] = color[i].blue; + + if (prev[i].red != color[i].red || + prev[i].green != color[i].green || + prev[i].blue != color[i].blue ) { + diffs++; } - if (cl->enableCursorPosUpdates) { - cd->had_cursor_pos_updates = 1; + + if (shift_cmap && k >= 0 && k < NCOLOR) { + /* kludge to copy the colors to higher pixel values */ + screen->colourMap.data.shorts[k*3+0] = color[i].red; + screen->colourMap.data.shorts[k*3+1] = color[i].green; + screen->colourMap.data.shorts[k*3+2] = color[i].blue; } - - cl->enableCursorShapeUpdates = FALSE; - cl->enableCursorPosUpdates = FALSE; - cl->cursorWasChanged = FALSE; } - rfbReleaseClientIterator(iter); + + if (diffs && ! first) { + if (! all_clients_initialized()) { + rfbLog("set_colormap: warning: sending cmap " + "with uninitialized clients.\n"); + } + if (shift_cmap) { + rfbSetClientColourMaps(screen, 0, NCOLOR); + } else { + rfbSetClientColourMaps(screen, 0, ncells); + } + } + + first = 0; } -int cursor_shape_updates_clients(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int count = 0; +void debug_colormap(XImage *fb) { + static int debug_cmap = -1; + int i, k, histo[NCOLOR]; - if (! s) { - return 0; - } - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->enableCursorShapeUpdates) { - count++; + if (debug_cmap < 0) { + if (getenv("DEBUG_CMAP") != NULL) { + debug_cmap = 1; + } else { + debug_cmap = 0; } } - rfbReleaseClientIterator(iter); - return count; -} + if (! debug_cmap) { + return; + } + if (! fb) { + return; + } + if (fb->bits_per_pixel > 8) { + return; + } -int cursor_pos_updates_clients(rfbScreenInfoPtr s) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int count = 0; + for (i=0; i < NCOLOR; i++) { + histo[i] = 0; + } + for (k = 0; k < fb->width * fb->height; k++) { + unsigned char n; + char c = *(fb->data + k); - if (! s) { - return 0; + n = (unsigned char) c; + histo[n]++; } - iter = rfbGetClientIterator(s); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (cl->enableCursorPosUpdates) { - count++; + fprintf(stderr, "\nColormap histogram for current screen contents:\n"); + for (i=0; i < NCOLOR; i++) { + unsigned short r = screen->colourMap.data.shorts[i*3+0]; + unsigned short g = screen->colourMap.data.shorts[i*3+1]; + unsigned short b = screen->colourMap.data.shorts[i*3+2]; + + fprintf(stderr, " %03d: %7d %04x/%04x/%04x", i, histo[i], + r, g, b); + if ((i+1) % 2 == 0) { + fprintf(stderr, "\n"); } } - rfbReleaseClientIterator(iter); - return count; + fprintf(stderr, "\n"); } /* - * Record rfb cursor position screen->cursorX, etc (a la defaultPtrAddEvent()) - * Then set up for sending rfbCursorPosUpdates back - * to clients that understand them. This seems to be TightVNC specific. + * Experimental mode to force the visual of the window instead of querying + * it. Used for testing, overriding some rare cases (win2vnc), and for + * -overlay . Input string can be a decimal or 0x hex or something like + * TrueColor or TrueColor:24 to force a depth as well. + * + * visual_id and possibly visual_depth are set. */ -void cursor_position(int x, int y) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; - int cnt = 0, nonCursorPosUpdates_clients = 0; - int x_in = x, y_in = y; -#if OLD_TREE - int x_old, y_old; -#endif - - /* x and y are current positions of X11 pointer on the X11 display */ - if (!screen) { - return; - } +void set_visual(char *str) { + int vis, vdepth, defdepth = DefaultDepth(dpy, scr); + XVisualInfo vinfo; + char *p, *vstring = strdup(str); - if (scaling) { - x = ((double) x / dpy_x) * scaled_x; - x = nfix(x, scaled_x); - y = ((double) y / dpy_y) * scaled_y; - y = nfix(y, scaled_y); - } + visual_id = (VisualID) 0; + visual_depth = 0; - if (x == screen->cursorX && y == screen->cursorY) { + if (!strcmp(vstring, "ignore") || !strcmp(vstring, "default") + || !strcmp(vstring, "")) { + free(vstring); return; } -#if OLD_TREE - x_old = screen->oldCursorX; - y_old = screen->oldCursorY; - - if (screen->cursorIsDrawn) { - rfbUndrawCursor(screen); + /* set visual depth */ + if ((p = strchr(vstring, ':')) != NULL) { + visual_depth = atoi(p+1); + *p = '\0'; + vdepth = visual_depth; + } else { + vdepth = defdepth; } - - LOCK(screen->cursorMutex); - if (! screen->cursorIsDrawn) { - screen->cursorX = x; - screen->cursorY = y; + if (! quiet) { + fprintf(stderr, "\nVisual Info:\n"); + fprintf(stderr, " set_visual(\"%s\")\n", str); + fprintf(stderr, " visual_depth: %d\n", vdepth); } - UNLOCK(screen->cursorMutex); -#else - LOCK(screen->cursorMutex); - screen->cursorX = x; - screen->cursorY = y; - UNLOCK(screen->cursorMutex); -#endif - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (! cl->enableCursorPosUpdates) { - nonCursorPosUpdates_clients++; - continue; - } - if (! cursor_pos_updates) { - continue; - } - if (cl == last_pointer_client) { - /* - * special case if this client was the last one to - * send a pointer position. - */ - if (x_in == cursor_x && y_in == cursor_y) { - cl->cursorWasMoved = FALSE; - } else { - /* an X11 app evidently warped the pointer */ - if (debug_pointer) { - rfbLog("cursor_position: warp " - "detected dx=%3d dy=%3d\n", - cursor_x - x, cursor_y - y); - } - cl->cursorWasMoved = TRUE; - cnt++; + /* set visual id number */ + if (strcmp(vstring, "StaticGray") == 0) { + vis = StaticGray; + } else if (strcmp(vstring, "GrayScale") == 0) { + vis = GrayScale; + } else if (strcmp(vstring, "StaticColor") == 0) { + vis = StaticColor; + } else if (strcmp(vstring, "PseudoColor") == 0) { + vis = PseudoColor; + } else if (strcmp(vstring, "TrueColor") == 0) { + vis = TrueColor; + } else if (strcmp(vstring, "DirectColor") == 0) { + vis = DirectColor; + } else { + int v_in; + if (sscanf(vstring, "0x%x", &v_in) != 1) { + if (sscanf(vstring, "%d", &v_in) == 1) { + visual_id = (VisualID) v_in; + return; } - } else { - cl->cursorWasMoved = TRUE; - cnt++; + rfbLog("bad -visual arg: %s\n", vstring); + X_UNLOCK; + clean_up_exit(1); } + visual_id = (VisualID) v_in; + free(vstring); + return; } - rfbReleaseClientIterator(iter); -#if OLD_TREE - if (nonCursorPosUpdates_clients && show_cursor) { - if (x_old != x || y_old != y) { - mark_cursor_patch_modified(screen, 0); - } + if (! quiet) fprintf(stderr, " visual: %d\n", vis); + if (XMatchVisualInfo(dpy, scr, visual_depth, vis, &vinfo)) { + ; + } else if (XMatchVisualInfo(dpy, scr, defdepth, vis, &vinfo)) { + ; + } else { + rfbLog("could not find visual: %s\n", vstring); + X_UNLOCK; + clean_up_exit(1); } -#endif + free(vstring); - if (debug_pointer && cnt) { - rfbLog("cursor_position: sent position x=%3d y=%3d to %d" - " clients\n", x, y, cnt); - } + /* set numerical visual id. */ + visual_id = vinfo.visualid; } -#if !OLD_TREE -void set_rfb_cursor(int which) { +void set_nofb_params(void) { + use_xfixes = 0; + use_xdamage = 0; - if (! show_cursor) { - return; - } - if (! screen) { - return; + use_solid_bg = 0; + overlay = 0; + overlay_cursor = 0; + + using_shm = 0; + single_copytile = 1; + + take_naps = 0; + measure_speeds = 0; + + show_cursor = 0; + show_multiple_cursors = 0; + cursor_shape_updates = 0; + if (! got_cursorpos) { + cursor_pos_updates = 0; } - - if (!cursors[which] || !cursors[which]->rfb) { - rfbLog("non-existent cursor: which=%d\n", which); - return; - } else { - rfbSetCursor(screen, cursors[which]->rfb); + + if (! quiet) { + rfbLog("disabling: xfixes, xdamage, solid, overlay, shm,\n"); + rfbLog(" noonetile, nap, cursor, %scursorshape\n", + got_cursorpos ? "" : "cursorpos, " ); + rfbLog(" in -nofb mode.\n"); } } -#else +char *raw_fb_orig_dpy = NULL; -void set_rfb_cursor(int which) { +void set_raw_fb_params(int restore) { + static int first = 1; + static int vo0, us0, sm0, ws0, wp0, wb0, na0, tn0; + static int xr0, sb0; + static char *mc0; - if (! show_cursor) { - return; + /* + * set turn off a bunch of parameters not compatible with + * -rawfb mode: 1) ignoring the X server 2) ignoring user input. + */ + + if (first) { + /* at least save the initial settings... */ + vo0 = view_only; + ws0 = watch_selection; + wp0 = watch_primary; + wb0 = watch_bell; + na0 = no_autorepeat; + sb0 = use_solid_bg; + + us0 = use_snapfb; + sm0 = using_shm; + tn0 = take_naps; + xr0 = xrandr; + mc0 = multiple_cursors_mode; + + first = 0; } - if (! screen) { + + if (restore) { + view_only = vo0; + watch_selection = ws0; + watch_primary = wp0; + watch_bell = wb0; + no_autorepeat = na0; + use_solid_bg = sb0; + + use_snapfb = us0; + using_shm = sm0; + take_naps = tn0; + xrandr = xr0; + multiple_cursors_mode = mc0; + + if (! dpy && raw_fb_orig_dpy) { + dpy = XOpenDisplay(raw_fb_orig_dpy); + if (dpy) { + rfbLog("reopened DISPLAY: %s\n", + raw_fb_orig_dpy); + } else { + rfbLog("WARNING: failed to reopen " + "DISPLAY: %s\n", raw_fb_orig_dpy); + } + } return; } - - if (screen->cursor) { - int all_are_cursor_pos = 1; - rfbClientIteratorPtr iter; - rfbClientPtr cl; - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - if (! cl->enableCursorPosUpdates) { - all_are_cursor_pos = 0; - } - if (! cl->enableCursorShapeUpdates) { - all_are_cursor_pos = 0; - } + rfbLog("set_raw_fb_params: modifying settings for -rawfb mode.\n"); + + if (got_noviewonly) { + /* + * The user input parameters are not unset under + * -noviewonly... this usage should be very rare + * (i.e. rawfb but also send user input to the X + * display, most likely using /dev/fb0 for some reason...) + */ + rfbLog("rawfb: -noviewonly mode: still sending mouse and\n"); + rfbLog("rawfb: keyboard input to the X DISPLAY!!\n"); + } else { + /* Normal case: */ + if (! view_only) { + rfbLog("rawfb: setting view_only\n"); + view_only = 1; } - rfbReleaseClientIterator(iter); - - if (! all_are_cursor_pos) { - mark_cursor_patch_modified(screen, 1); + if (watch_selection) { + rfbLog("rawfb: turning off watch_selection\n"); + watch_selection = 0; + } + if (watch_primary) { + rfbLog("rawfb: turning off watch_primary\n"); + watch_primary = 0; } + if (watch_bell) { + rfbLog("rawfb: turning off watch_bell\n"); + watch_bell = 0; + } + if (no_autorepeat) { + rfbLog("rawfb: turning off no_autorepeat\n"); + no_autorepeat = 0; + } + if (use_solid_bg) { + rfbLog("rawfb: turning off use_solid_bg\n"); + use_solid_bg = 0; + } + multiple_cursors_mode = strdup("arrow"); } - - if (!cursors[which] || !cursors[which]->rfb) { - rfbLog("non-existent cursor: which=%d\n", which); - return; - } else { - rfbSetCursor(screen, cursors[which]->rfb, FALSE); + if (use_snapfb) { + rfbLog("rawfb: turning off use_snapfb\n"); + use_snapfb = 0; } - - if (screen->underCursorBuffer == NULL && - screen->underCursorBufferLen != 0) { - LOCK(screen->cursorMutex); - screen->underCursorBufferLen = 0; - UNLOCK(screen->cursorMutex); + if (using_shm) { + rfbLog("rawfb: turning off using_shm\n"); + using_shm = 0; } - set_cursor_was_changed(screen); -} -#endif - -void set_no_cursor(void) { - RFBUNDRAWCURSOR(screen); - set_rfb_cursor(CURS_EMPTY); -} - -void set_cursor(int x, int y, int which) { - static int last = -1; - if (which < 0) { - which = last; + if (take_naps) { + rfbLog("rawfb: turning off take_naps\n"); + take_naps = 0; } - if (last < 0 || which != last) { - set_rfb_cursor(which); + if (xrandr) { + rfbLog("rawfb: turning off xrandr\n"); + xrandr = 0; } - last = which; } /* - * routine called periodically to update cursor aspects, this catches - * warps and cursor shape changes. + * Presumably under -nofb the clients will never request the framebuffer. + * However, we have gotten such a request... so let's just give them + * the current view on the display. n.b. x2vnc and perhaps win2vnc + * requests a 1x1 pixel for some workaround so sadly this evidently + * nearly always happens. */ -void check_x11_pointer(void) { - Window root_w, child_w; - rfbBool ret; - int root_x, root_y, win_x, win_y; - int x, y; - unsigned int mask; - - if (raw_fb && ! dpy) return; /* raw_fb hack */ +void nofb_hook(rfbClientPtr cl) { + XImage *fb; + rfbLog("framebuffer requested in -nofb mode by client %s\n", cl->host); + /* ignore xrandr */ + fb = XGetImage_wr(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, ZPixmap); + main_fb = fb->data; + rfb_fb = main_fb; + screen->frameBuffer = rfb_fb; + screen->displayHook = NULL; +} - X_LOCK; - ret = XQueryPointer(dpy, rootwin, &root_w, &child_w, &root_x, &root_y, - &win_x, &win_y, &mask); - X_UNLOCK; +void do_new_fb(int reset_mem) { + XImage *fb; + char *old_main = main_fb; + char *old_rfb = rfb_fb; - if (! ret) { - return; - } - if (debug_pointer) { - static int last_x = -1, last_y = -1; - if (root_x != last_x || root_y != last_y) { - rfbLog("XQueryPointer: x:%4d, y:%4d)\n", - root_x, root_y); - } - last_x = root_x; - last_y = root_y; + /* for threaded we really should lock libvncserver out. */ + if (use_threads) { + rfbLog("warning: changing framebuffers while threaded may\n"); + rfbLog(" not work, do not use -threads if problems arise.\n"); } - /* offset subtracted since XQueryPointer relative to rootwin */ - x = root_x - off_x - coff_x; - y = root_y - off_y - coff_y; - - /* record the cursor position in the rfb screen */ - cursor_position(x, y); - - /* change the cursor shape if necessary */ - set_cursor(x, y, get_which_cursor()); -} + if (reset_mem == 1) { + /* reset_mem == 2 is a hack for changing users... */ + clean_shm(0); + free_tiles(); + } -/* -- screen.c -- */ -/* - * X11 and rfb display/screen related routines - */ + fb = initialize_xdisplay_fb(); -/* - * Some handling of 8bpp PseudoColor colormaps. Called for initializing - * the clients and dynamically if -flashcmap is specified. - */ -#define NCOLOR 256 -void set_colormap(int reset) { - static int first = 1; - static XColor color[NCOLOR], prev[NCOLOR]; - Colormap cmap; - Visual *vis; - int i, ncells, diffs = 0; + initialize_screen(NULL, NULL, fb); - if (reset) { - first = 1; - if (screen->colourMap.data.shorts) { - free(screen->colourMap.data.shorts); - } + if (reset_mem) { + initialize_tiles(); + initialize_blackouts_and_xinerama(); + initialize_polling_images(); } - if (first) { - screen->colourMap.count = NCOLOR; - screen->serverFormat.trueColour = FALSE; - screen->colourMap.is16 = TRUE; - screen->colourMap.data.shorts = (unsigned short*) - malloc(3*sizeof(short) * NCOLOR); + if (old_main != old_rfb && old_main) { + free(old_main); } - - for (i=0; i < NCOLOR; i++) { - prev[i].red = color[i].red; - prev[i].green = color[i].green; - prev[i].blue = color[i].blue; + if (old_rfb) { + free(old_rfb); } + fb0 = fb; +} - X_LOCK; +void remove_fake_fb(void) { + if (! screen) { + return; + } + rfbLog("removing fake fb: 0x%x\n", fake_fb); - cmap = DefaultColormap(dpy, scr); - ncells = CellsOfScreen(ScreenOfDisplay(dpy, scr)); - vis = default_visual; + do_new_fb(1); - if (subwin) { - XWindowAttributes attr; + /* + * fake_fb is freed in do_new_fb(), but we set to NULL here to + * indicate it is gone. + */ + fake_fb = NULL; +} - if (XGetWindowAttributes(dpy, window, &attr)) { - cmap = attr.colormap; - vis = attr.visual; - ncells = vis->map_entries; - } +void install_fake_fb(int w, int h, int bpp) { + int bpc; + if (! screen) { + return; } - - if (ncells != NCOLOR) { - if (first && ! quiet) { - rfbLog("set_colormap: number of cells is %d " - "instead of %d.\n", ncells, NCOLOR); - } - if (! shift_cmap) { - screen->colourMap.count = ncells; - } + if (fake_fb) { + free(fake_fb); + } + fake_fb = (char *) calloc(w*h*bpp/8, 1); + if (! fake_fb) { + rfbLog("could not create fake fb: %dx%d %d\n", w, h, bpp); + return; } + bpc = guess_bits_per_color(bpp); + rfbLog("installing fake fb: %dx%d %d\n", w, h, bpp); + rfbLog("rfbNewFramebuffer(0x%x, 0x%x, %d, %d, %d, %d, %d)\n", + screen, fake_fb, w, h, bpc, 1, bpp/8); - if (flash_cmap && ! first) { - XWindowAttributes attr; - Window r, c; - int rx, ry, wx, wy, tries = 0; - unsigned int m; + rfbNewFramebuffer(screen, fake_fb, w, h, bpc, 1, bpp/8); +} - c = window; - while (c && tries++ < 16) { - /* XXX XQueryTree somehow? */ - XQueryPointer(dpy, c, &r, &c, &rx, &ry, &wx, &wy, &m); - if (c && XGetWindowAttributes(dpy, c, &attr)) { - if (attr.colormap && attr.map_installed) { - cmap = attr.colormap; - vis = attr.visual; - ncells = vis->map_entries; - break; - } - } else { - break; - } - } - } - if (ncells > NCOLOR && ! quiet) { - rfbLog("set_colormap: big problem: ncells=%d > %d\n", - ncells, NCOLOR); +void check_padded_fb(void) { + if (! fake_fb) { + return; } - - if (vis->class == TrueColor || vis->class == DirectColor) { - /* - * Kludge to make 8bpp TrueColor & DirectColor be like - * the StaticColor map. The ncells = 8 is "8 per subfield" - * mentioned in xdpyinfo. Looks OK... perhaps fortuitously. - */ - if (ncells == 8 && ! shift_cmap) { - ncells = NCOLOR; - } + if (time(0) > pad_geometry_time+1 && all_clients_initialized()) { + remove_fake_fb(); } +} - for (i=0; i < ncells; i++) { - color[i].pixel = i; - color[i].pad = 0; +void install_padded_fb(char *geom) { + int w, h; + int ok = 1; + if (! geom || *geom == '\0') { + ok = 0; + } else if (sscanf(geom, "%dx%d", &w, &h) != 2) { + ok = 0; } + w = nabs(w); + h = nabs(h); - XQueryColors(dpy, cmap, color, ncells); - - X_UNLOCK; - - for(i = ncells - 1; i >= 0; i--) { - int k = i + shift_cmap; - - screen->colourMap.data.shorts[i*3+0] = color[i].red; - screen->colourMap.data.shorts[i*3+1] = color[i].green; - screen->colourMap.data.shorts[i*3+2] = color[i].blue; - - if (prev[i].red != color[i].red || - prev[i].green != color[i].green || - prev[i].blue != color[i].blue ) { - diffs++; - } + if (w < 5) w = 5; + if (h < 5) h = 5; - if (shift_cmap && k >= 0 && k < NCOLOR) { - /* kludge to copy the colors to higher pixel values */ - screen->colourMap.data.shorts[k*3+0] = color[i].red; - screen->colourMap.data.shorts[k*3+1] = color[i].green; - screen->colourMap.data.shorts[k*3+2] = color[i].blue; - } + if (!ok) { + rfbLog("skipping invalid pad geometry: '%s'\n", NONUL(geom)); + return; } + install_fake_fb(w, h, bpp); + pad_geometry_time = time(0); +} - if (diffs && ! first) { - if (! all_clients_initialized()) { - rfbLog("set_colormap: warning: sending cmap " - "with uninitialized clients.\n"); - } - if (shift_cmap) { - rfbSetClientColourMaps(screen, 0, NCOLOR); - } else { - rfbSetClientColourMaps(screen, 0, ncells); - } +void initialize_snap_fb(void) { + if (snap_fb) { + free(snap_fb); } - - first = 0; + snap = XGetImage_wr(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, + ZPixmap); + snap_fb = snap->data; } -void debug_colormap(XImage *fb) { - static int debug_cmap = -1; - int i, k, histo[NCOLOR]; - if (debug_cmap < 0) { - if (getenv("DEBUG_CMAP") != NULL) { - debug_cmap = 1; - } else { - debug_cmap = 0; +XImage *initialize_raw_fb(void) { + char *str, *q; + int w, h, b, shmid = 0; + unsigned long rm = 0, gm = 0, bm = 0; + static XImage ximage_struct; /* n.b.: not (XImage *) */ + int closedpy = 1, i, m; + + if (raw_fb_addr || raw_fb_seek) { + if (raw_fb_shm) { + shmdt(raw_fb_addr); +#if LIBVNCSERVER_HAVE_MMAP + } else if (raw_fb_mmap) { + munmap(raw_fb_addr, raw_fb_mmap); + if (raw_fb_fd >= 0) { + close(raw_fb_fd); + } +#endif + } else if (raw_fb_seek) { + if (raw_fb_fd >= 0) { + close(raw_fb_fd); + } } + raw_fb_addr = NULL; } - if (! debug_cmap) { - return; - } - if (! fb) { - return; - } - if (fb->bits_per_pixel > 8) { - return; + if (! raw_fb_str) { + return NULL; } - for (i=0; i < NCOLOR; i++) { - histo[i] = 0; - } - for (k = 0; k < fb->width * fb->height; k++) { - unsigned char n; - char c = *(fb->data + k); - n = (unsigned char) c; - histo[n]++; - } - fprintf(stderr, "\nColormap histogram for current screen contents:\n"); - for (i=0; i < NCOLOR; i++) { - unsigned short r = screen->colourMap.data.shorts[i*3+0]; - unsigned short g = screen->colourMap.data.shorts[i*3+1]; - unsigned short b = screen->colourMap.data.shorts[i*3+2]; + if ( (q = strstr(raw_fb_str, "setup:")) == raw_fb_str) { + FILE *pipe; + char line[1024], *t; - fprintf(stderr, " %03d: %7d %04x/%04x/%04x", i, histo[i], - r, g, b); - if ((i+1) % 2 == 0) { - fprintf(stderr, "\n"); + set_child_info(); + q += strlen("setup:"); + if (no_external_cmds) { + rfbLog("cannot run external commands in -nocmds " + "mode:\n"); + rfbLog(" \"%s\"\n", q); + rfbLog(" exiting.\n"); + clean_up_exit(1); } - } - fprintf(stderr, "\n"); -} + rfbLog("running command to setup rawfb: %s\n", q); + pipe = popen(q, "r"); + if (! pipe) { + rfbLog("popen of setup command failed.\n"); + rfbLogPerror("popen"); + clean_up_exit(1); + } + line[0] = '\0'; + if (fgets(line, 1024, pipe) == NULL) { + rfbLog("read of setup command failed.\n"); + clean_up_exit(1); + } + pclose(pipe); + str = strdup(line); + t = str; + while (*t != '\0') { + if (*t == '\n') { + *t = '\0'; + } + t++; + } + rfbLog("setup command returned: %s\n", str); -/* - * Experimental mode to force the visual of the window instead of querying - * it. Used for testing, overriding some rare cases (win2vnc), and for - * -overlay . Input string can be a decimal or 0x hex or something like - * TrueColor or TrueColor:24 to force a depth as well. - * - * visual_id and possibly visual_depth are set. - */ -void set_visual(char *str) { - int vis, vdepth, defdepth = DefaultDepth(dpy, scr); - XVisualInfo vinfo; - char *p, *vstring = strdup(str); + } else { + str = strdup(raw_fb_str); + } - visual_id = (VisualID) 0; - visual_depth = 0; + /* + * uppercase means do not close the display (e.g. for remote control) + */ + if (strstr(str, "SHM:") == str) { + closedpy = 0; + str[0] = 's'; str[1] = 'h'; str[2] = 'm'; + } else if (strstr(str, "MAP:") == str) { + closedpy = 0; + str[0] = 'm'; str[1] = 'a'; str[2] = 'p'; + } else if (strstr(str, "MMAP:") == str) { + closedpy = 0; + str[0] = 'm'; str[1] = 'm'; str[2] = 'a'; str[3] = 'p'; + } else if (strstr(str, "FILE:") == str) { + str[0] = 'f'; str[1] = 'i'; str[2] = 'l'; str[3] = 'e'; + closedpy = 0; + } - if (!strcmp(vstring, "ignore") || !strcmp(vstring, "default") - || !strcmp(vstring, "")) { - free(vstring); - return; + if (closedpy && !view_only && got_noviewonly) { + rfbLog("not closing X DISPLAY under -noviewonly option.\n"); + closedpy = 0; + if (! window) { + window = rootwin; + } } - /* set visual depth */ - if ((p = strchr(vstring, ':')) != NULL) { - visual_depth = atoi(p+1); - *p = '\0'; - vdepth = visual_depth; - } else { - vdepth = defdepth; + if (! raw_fb_orig_dpy && dpy) { + raw_fb_orig_dpy = strdup(DisplayString(dpy)); } - if (! quiet) { - fprintf(stderr, "\nVisual Info:\n"); - fprintf(stderr, " set_visual(\"%s\")\n", str); - fprintf(stderr, " visual_depth: %d\n", vdepth); +#ifndef BOLDLY_CLOSE_DISPLAY +#define BOLDLY_CLOSE_DISPLAY 1 +#endif +#if BOLDLY_CLOSE_DISPLAY + if (closedpy) { + if (dpy) { + rfbLog("closing X DISPLAY: %s in rawfb mode.\n", + DisplayString(dpy)); + XCloseDisplay(dpy); /* yow! */ + } + dpy = 0; } +#endif - /* set visual id number */ - if (strcmp(vstring, "StaticGray") == 0) { - vis = StaticGray; - } else if (strcmp(vstring, "GrayScale") == 0) { - vis = GrayScale; - } else if (strcmp(vstring, "StaticColor") == 0) { - vis = StaticColor; - } else if (strcmp(vstring, "PseudoColor") == 0) { - vis = PseudoColor; - } else if (strcmp(vstring, "TrueColor") == 0) { - vis = TrueColor; - } else if (strcmp(vstring, "DirectColor") == 0) { - vis = DirectColor; - } else { - int v_in; - if (sscanf(vstring, "0x%x", &v_in) != 1) { - if (sscanf(vstring, "%d", &v_in) == 1) { - visual_id = (VisualID) v_in; - return; - } - rfbLog("bad -visual arg: %s\n", vstring); - X_UNLOCK; - clean_up_exit(1); + /* + * -rawfb shm:163938442@640x480x32:ff/ff00/ff0000+3000 + * -rawfb map:/path/to/file@640x480x32:ff/ff00/ff0000 + * -rawfb file:/path/to/file@640x480x32:ff/ff00/ff0000 + */ + + raw_fb_offset = 0; + + /* +O offset */ + if ((q = strrchr(str, '+')) != NULL) { + if (sscanf(q, "+%d", &raw_fb_offset) == 1) { + *q = '\0'; + } else { + raw_fb_offset = 0; } - visual_id = (VisualID) v_in; - free(vstring); - return; } - - if (! quiet) fprintf(stderr, " visual: %d\n", vis); - if (XMatchVisualInfo(dpy, scr, visual_depth, vis, &vinfo)) { - ; - } else if (XMatchVisualInfo(dpy, scr, defdepth, vis, &vinfo)) { - ; - } else { - rfbLog("could not find visual: %s\n", vstring); - X_UNLOCK; + /* :R/G/B masks */ + if ((q = strrchr(str, ':')) != NULL) { + if (sscanf(q, ":%lx/%lx/%lx", &rm, &gm, &bm) == 3) { + *q = '\0'; + } else if (sscanf(q, ":0x%lx/0x%lx/0x%lx", &rm, &gm, &bm)== 3) { + *q = '\0'; + } else if (sscanf(q, ":%lu/%lu/%lu", &rm, &gm, &bm) == 3) { + *q = '\0'; + } else { + rm = 0; + gm = 0; + bm = 0; + } + } + if ((q = strrchr(str, '@')) == NULL) { + rfbLog("invalid rawfb str: %s\n", str); clean_up_exit(1); } - free(vstring); + /* @WxHxB */ + if (sscanf(q, "@%dx%dx%d", &w, &h, &b) != 3) { + rfbLog("invalid rawfb str: %s\n", str); + clean_up_exit(1); + } + *q = '\0'; - /* set numerical visual id. */ - visual_id = vinfo.visualid; -} + if (strstr(str, "shm:") != str && strstr(str, "mmap:") != str && + strstr(str, "map:") != str && strstr(str, "file:") != str) { + /* hmmm, not following directions, see if map: applies */ + struct stat sbuf; + if (stat(str, &sbuf) == 0) { + char *new; + int len = strlen("map:") + strlen(str) + 1; + rfbLog("no type prefix: %s\n", raw_fb_str); + rfbLog(" but file exists, so assuming: map:%s\n", + raw_fb_str); + new = (char *)malloc(len); + strcpy(new, "map:"); + strcat(new, str); + free(str); + str = new; + } + } -void set_nofb_params(void) { - use_xfixes = 0; - use_xdamage = 0; + dpy_x = wdpy_x = w; + dpy_y = wdpy_y = h; + off_x = 0; + off_y = 0; - use_solid_bg = 0; - overlay = 0; - overlay_cursor = 0; + raw_fb_shm = 0; + raw_fb_mmap = 0; + raw_fb_seek = 0; + raw_fb_fd = -1; + raw_fb_addr = NULL; - using_shm = 0; - single_copytile = 1; + if (sscanf(str, "shm:%d", &shmid) == 1) { + /* shm:N */ + raw_fb_addr = (char *) shmat(shmid, 0, SHM_RDONLY); + if (! raw_fb_addr) { + rfbLog("failed to attach to shm: %d, %s\n", shmid, str); + rfbLogPerror("shmat"); + clean_up_exit(1); + } + raw_fb_shm = 1; + rfbLog("rawfb: shm: %d W: %d H: %d B: %d addr: %p\n", + shmid, w, h, b, raw_fb_addr); - take_naps = 0; - measure_speeds = 0; + } else if (strstr(str, "map:") == str || strstr(str, "mmap:") == str + || strstr(str, "file:") == str) { + /* map:/path/... or file:/path */ + int fd, do_mmap = 1, size; + struct stat sbuf; - show_cursor = 0; - show_multiple_cursors = 0; - cursor_shape_updates = 0; - if (! got_cursorpos) { - cursor_pos_updates = 0; - } + if (*str == 'f') { + do_mmap = 0; + } + q = strchr(str, ':'); + q++; - if (! quiet) { - rfbLog("disabling: xfixes, xdamage, solid, overlay, shm,\n"); - rfbLog(" noonetile, nap, cursor, %scursorshape\n", - got_cursorpos ? "" : "cursorpos, " ); - rfbLog(" in -nofb mode.\n"); - } -} + fd = open(q, O_RDONLY); + if (fd < 0) { + rfbLog("failed to open file: %s, %s\n", q, str); + rfbLogPerror("open"); + clean_up_exit(1); + } + raw_fb_fd = fd; -char *raw_fb_orig_dpy = NULL; + size = w*h*b/8 + raw_fb_offset; + if (fstat(fd, &sbuf) == 0) { + if (S_ISREG(sbuf.st_mode)) { + if (0) size = sbuf.st_size; + } else { + rfbLog("raw fb is non-regular file: %s\n", q); + } + } -void set_raw_fb_params(int restore) { - static int first = 1; - static int vo0, us0, sm0, ws0, wp0, wb0, na0, tn0; - static int xr0, sb0; - static char *mc0; + if (do_mmap) { +#if LIBVNCSERVER_HAVE_MMAP + raw_fb_addr = mmap(0, size, PROT_READ, MAP_SHARED, + fd, 0); - /* - * set turn off a bunch of parameters not compatible with - * -rawfb mode: 1) ignoring the X server 2) ignoring user input. - */ - - if (first) { - /* at least save the initial settings... */ - vo0 = view_only; - ws0 = watch_selection; - wp0 = watch_primary; - wb0 = watch_bell; - na0 = no_autorepeat; - sb0 = use_solid_bg; + if (raw_fb_addr == MAP_FAILED || raw_fb_addr == NULL) { + rfbLog("failed to mmap file: %s, %s\n", q, str); + rfbLog(" raw_fb_addr: %p\n", raw_fb_addr); + rfbLogPerror("mmap"); + clean_up_exit(1); + } + raw_fb_mmap = size; - us0 = use_snapfb; - sm0 = using_shm; - tn0 = take_naps; - xr0 = xrandr; - mc0 = multiple_cursors_mode; + rfbLog("rawfb: mmap file: %s\n", q); + rfbLog(" w: %d h: %d b: %d addr: %p sz: %d\n", w, h, + b, raw_fb_addr, size); +#else + rfbLog("mmap(2) not supported on system, using" + " slower lseek(2)\n"); + raw_fb_seek = size; +#endif + } else { + raw_fb_seek = size; - first = 0; + rfbLog("rawfb: seek file: %s\n", q); + rfbLog(" W: %d H: %d B: %d sz: %d\n", w, h, b, size); + } + } else { + rfbLog("invalid rawfb str: %s\n", str); + clean_up_exit(1); } - if (restore) { - view_only = vo0; - watch_selection = ws0; - watch_primary = wp0; - watch_bell = wb0; - no_autorepeat = na0; - use_solid_bg = sb0; - - use_snapfb = us0; - using_shm = sm0; - take_naps = tn0; - xrandr = xr0; - multiple_cursors_mode = mc0; - - if (! dpy && raw_fb_orig_dpy) { - dpy = XOpenDisplay(raw_fb_orig_dpy); - if (dpy) { - rfbLog("reopened DISPLAY: %s\n", - raw_fb_orig_dpy); - } else { - rfbLog("WARNING: failed to reopen " - "DISPLAY: %s\n", raw_fb_orig_dpy); - } - } - return; + if (! raw_fb_image) { + raw_fb_image = &ximage_struct; } - rfbLog("set_raw_fb_params: modifying settings for -rawfb mode.\n"); + initialize_clipshift(); - if (got_noviewonly) { - /* - * The user input parameters are not unset under - * -noviewonly... this usage should be very rare - * (i.e. rawfb but also send user input to the X - * display, most likely using /dev/fb0 for some reason...) - */ - rfbLog("rawfb: -noviewonly mode: still sending mouse and\n"); - rfbLog("rawfb: keyboard input to the X DISPLAY!!\n"); - } else { - /* Normal case: */ - if (! view_only) { - rfbLog("rawfb: setting view_only\n"); - view_only = 1; - } - if (watch_selection) { - rfbLog("rawfb: turning off watch_selection\n"); - watch_selection = 0; - } - if (watch_primary) { - rfbLog("rawfb: turning off watch_primary\n"); - watch_primary = 0; + raw_fb = (char *) malloc(dpy_x * dpy_y * b/8); + raw_fb_image->data = raw_fb; + raw_fb_image->format = ZPixmap; + raw_fb_image->width = dpy_x; + raw_fb_image->height = dpy_y; + raw_fb_image->bits_per_pixel = b; + raw_fb_image->bytes_per_line = dpy_x*b/8; + + if (rm == 0 && gm == 0 && bm == 0) { + /* guess masks... */ + if (b == 24 || b == 32) { + rm = 0xff0000; + gm = 0x00ff00; + bm = 0x0000ff; + } else if (b == 16) { + rm = 0xf800; + gm = 0x07e0; + bm = 0x001f; + } else if (b == 8) { + rm = 0x07; + gm = 0x38; + bm = 0xc0; } - if (watch_bell) { - rfbLog("rawfb: turning off watch_bell\n"); - watch_bell = 0; + } + + raw_fb_image->red_mask = rm; + raw_fb_image->green_mask = gm; + raw_fb_image->blue_mask = bm; + + raw_fb_image->depth = 0; + m = 1; + for (i=0; i<32; i++) { + if (rm & m) { + raw_fb_image->depth++; } - if (no_autorepeat) { - rfbLog("rawfb: turning off no_autorepeat\n"); - no_autorepeat = 0; + if (gm & m) { + raw_fb_image->depth++; } - if (use_solid_bg) { - rfbLog("rawfb: turning off use_solid_bg\n"); - use_solid_bg = 0; + if (bm & m) { + raw_fb_image->depth++; } - multiple_cursors_mode = strdup("arrow"); - } - if (use_snapfb) { - rfbLog("rawfb: turning off use_snapfb\n"); - use_snapfb = 0; + m = m << 1; } - if (using_shm) { - rfbLog("rawfb: turning off using_shm\n"); - using_shm = 0; + if (! raw_fb_image->depth) { + raw_fb_image->depth = (b == 32) ? 24 : b; } - if (take_naps) { - rfbLog("rawfb: turning off take_naps\n"); - take_naps = 0; + + if (clipshift) { + memset(raw_fb, 0xff, dpy_x * dpy_y * b/8); + } else if (raw_fb_addr) { + memcpy(raw_fb, raw_fb_addr + raw_fb_offset, dpy_x*dpy_y*b/8); + } else { + memset(raw_fb, 0xff, dpy_x * dpy_y * b/8); } - if (xrandr) { - rfbLog("rawfb: turning off xrandr\n"); - xrandr = 0; + + rfbLog("rawfb: raw_fb %p\n", raw_fb); + + free(str); + + return raw_fb_image; +} + +void initialize_clipshift(void) { + clipshift = 0; + cdpy_x = cdpy_y = coff_x = coff_y = 0; + if (clip_str) { + int w, h, x, y, bad = 0; + if (parse_geom(clip_str, &w, &h, &x, &y, wdpy_x, wdpy_y)) { + if (x < 0) { + x = 0; + } + if (y < 0) { + y = 0; + } + if (x + w > wdpy_x) { + w = wdpy_x - x; + } + if (y + h > wdpy_y) { + h = wdpy_y - y; + } + if (w <= 0 || h <= 0) { + bad = 1; + } + } else { + bad = 1; + } + if (bad) { + rfbLog("skipping invalid -clip WxH+X+Y: %s\n", + clip_str); + } else { + /* OK, change geom behind everyone's back... */ + cdpy_x = w; + cdpy_y = h; + coff_x = x; + coff_y = y; + + clipshift = 1; + + dpy_x = cdpy_x; + dpy_y = cdpy_y; + } } } /* - * Presumably under -nofb the clients will never request the framebuffer. - * However, we have gotten such a request... so let's just give them - * the current view on the display. n.b. x2vnc and perhaps win2vnc - * requests a 1x1 pixel for some workaround so sadly this evidently - * nearly always happens. + * initialize a fb for the X display */ -void nofb_hook(rfbClientPtr cl) { +XImage *initialize_xdisplay_fb(void) { XImage *fb; - rfbLog("framebuffer requested in -nofb mode by client %s\n", cl->host); - /* ignore xrandr */ - fb = XGetImage_wr(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, ZPixmap); - main_fb = fb->data; - rfb_fb = main_fb; - screen->frameBuffer = rfb_fb; - screen->displayHook = NULL; -} + char *vis_str = visual_str; + int try = 0, subwin_tries = 3; + XErrorHandler old_handler; + int subwin_bs; -void do_new_fb(int reset_mem) { - XImage *fb; - char *old_main = main_fb; - char *old_rfb = rfb_fb; + if (raw_fb_str) { + return initialize_raw_fb(); + } - /* for threaded we really should lock libvncserver out. */ - if (use_threads) { - rfbLog("warning: changing framebuffers while threaded may\n"); - rfbLog(" not work, do not use -threads if problems arise.\n"); + X_LOCK; + if (subwin) { + if (subwin_wait_mapped) { + wait_until_mapped(subwin); + } + if (!valid_window((Window) subwin, NULL)) { + rfbLog("invalid sub-window: 0x%lx\n", subwin); + X_UNLOCK; + clean_up_exit(1); + } } + + if (overlay) { + /* + * ideally we'd like to not have to cook up the + * visual variables but rather let it all come out + * of XReadScreen(), however there is no way to get + * a default visual out of it, so we pretend -visual + * TrueColor:NN was supplied with NN usually 24. + */ + char str[32]; + Window twin = subwin ? subwin : rootwin; + XImage *xi; - if (reset_mem == 1) { - /* reset_mem == 2 is a hack for changing users... */ - clean_shm(0); - free_tiles(); + xi = xreadscreen(dpy, twin, 0, 0, 8, 8, False); + sprintf(str, "TrueColor:%d", xi->depth); + if (xi->depth != 24 && ! quiet) { + rfbLog("warning: overlay image has depth %d " + "instead of 24.\n", xi->depth); + } + XDestroyImage(xi); + if (visual_str != NULL && ! quiet) { + rfbLog("warning: replacing '-visual %s' by '%s' " + "for use with -overlay\n", visual_str, str); + } + vis_str = strdup(str); } - fb = initialize_xdisplay_fb(); + if (vis_str != NULL) { + set_visual(vis_str); + if (vis_str != visual_str) { + free(vis_str); + } + } - initialize_screen(NULL, NULL, fb); + /* set up parameters for subwin or non-subwin cases: */ - if (reset_mem) { - initialize_tiles(); - initialize_blackouts_and_xinerama(); - initialize_polling_images(); - } + if (! subwin) { + /* full screen */ + window = rootwin; + dpy_x = wdpy_x = DisplayWidth(dpy, scr); + dpy_y = wdpy_y = DisplayHeight(dpy, scr); + off_x = 0; + off_y = 0; + /* this may be overridden via visual_id below */ + default_visual = DefaultVisual(dpy, scr); + } else { + /* single window */ + XWindowAttributes attr; - if (old_main != old_rfb && old_main) { - free(old_main); + window = (Window) subwin; + if (! XGetWindowAttributes(dpy, window, &attr)) { + rfbLog("invalid window: 0x%lx\n", window); + X_UNLOCK; + clean_up_exit(1); + } + dpy_x = wdpy_x = attr.width; + dpy_y = wdpy_y = attr.height; + + subwin_bs = attr.backing_store; + + /* this may be overridden via visual_id below */ + default_visual = attr.visual; + + X_UNLOCK; + set_offset(); + X_LOCK; } - if (old_rfb) { - free(old_rfb); + + initialize_clipshift(); + + /* initialize depth to reasonable value, visual_id may override */ + depth = DefaultDepth(dpy, scr); + + if (visual_id) { + int n; + XVisualInfo vinfo_tmpl, *vinfo; + + /* + * we are in here from -visual or -overlay options + * visual_id and visual_depth were set in set_visual(). + */ + + vinfo_tmpl.visualid = visual_id; + vinfo = XGetVisualInfo(dpy, VisualIDMask, &vinfo_tmpl, &n); + if (vinfo == NULL || n == 0) { + rfbLog("could not match visual_id: 0x%x\n", + (int) visual_id); + X_UNLOCK; + clean_up_exit(1); + } + default_visual = vinfo->visual; + depth = vinfo->depth; + if (visual_depth) { + /* force it from -visual MooColor:NN */ + depth = visual_depth; + } + if (! quiet) { + fprintf(stderr, " initialize_xdisplay_fb()\n"); + fprintf(stderr, " Visual*: %p\n", vinfo->visual); + fprintf(stderr, " visualid: 0x%x\n", + (int) vinfo->visualid); + fprintf(stderr, " screen: %d\n", vinfo->screen); + fprintf(stderr, " depth: %d\n", vinfo->depth); + fprintf(stderr, " class: %d\n", vinfo->class); + fprintf(stderr, " red_mask: 0x%08lx %s\n", + vinfo->red_mask, bitprint(vinfo->red_mask, 32)); + fprintf(stderr, " green_mask: 0x%08lx %s\n", + vinfo->green_mask, bitprint(vinfo->green_mask, 32)); + fprintf(stderr, " blue_mask: 0x%08lx %s\n", + vinfo->blue_mask, bitprint(vinfo->blue_mask, 32)); + fprintf(stderr, " cmap_size: %d\n", + vinfo->colormap_size); + fprintf(stderr, " bits b/rgb: %d\n", + vinfo->bits_per_rgb); + fprintf(stderr, "\n"); + } + XFree(vinfo); } - fb0 = fb; -} -void remove_fake_fb(void) { - if (! screen) { - return; + if (! quiet) { + rfbLog("default visual ID: 0x%x\n", + (int) XVisualIDFromVisual(default_visual)); } - rfbLog("removing fake fb: 0x%x\n", fake_fb); - - do_new_fb(1); - /* - * fake_fb is freed in do_new_fb(), but we set to NULL here to - * indicate it is gone. - */ - fake_fb = NULL; -} + again: + if (subwin) { + int shift = 0; + int subwin_x, subwin_y; + int disp_x = DisplayWidth(dpy, scr); + int disp_y = DisplayHeight(dpy, scr); + Window twin; + /* subwins can be a dicey if they are changing size... */ + XTranslateCoordinates(dpy, window, rootwin, 0, 0, &subwin_x, + &subwin_y, &twin); + if (subwin_x + wdpy_x > disp_x) { + shift = 1; + subwin_x = disp_x - wdpy_x - 3; + } + if (subwin_y + wdpy_y > disp_y) { + shift = 1; + subwin_y = disp_y - wdpy_y - 3; + } + if (subwin_x < 0) { + shift = 1; + subwin_x = 1; + } + if (subwin_y < 0) { + shift = 1; + subwin_y = 1; + } -void install_fake_fb(int w, int h, int bpp) { - int bpc; - if (! screen) { - return; - } - if (fake_fb) { - free(fake_fb); - } - fake_fb = (char *) calloc(w*h*bpp/8, 1); - if (! fake_fb) { - rfbLog("could not create fake fb: %dx%d %d\n", w, h, bpp); - return; + trapped_xerror = 0; + old_handler = XSetErrorHandler(trap_xerror); + if (shift) { + XMoveWindow(dpy, window, subwin_x, subwin_y); + } + XMapRaised(dpy, window); + XRaiseWindow(dpy, window); + XFlush(dpy); } - bpc = guess_bits_per_color(bpp); - rfbLog("installing fake fb: %dx%d %d\n", w, h, bpp); - rfbLog("rfbNewFramebuffer(0x%x, 0x%x, %d, %d, %d, %d, %d)\n", - screen, fake_fb, w, h, bpc, 1, bpp/8); + try++; - rfbNewFramebuffer(screen, fake_fb, w, h, bpc, 1, bpp/8); -} + if (nofb) { + /* + * For -nofb we do not allocate the framebuffer, so we + * can save a few MB of memory. + */ + fb = XCreateImage_wr(dpy, default_visual, depth, ZPixmap, + 0, NULL, dpy_x, dpy_y, BitmapPad(dpy), 0); -void check_padded_fb(void) { - if (! fake_fb) { - return; - } - if (time(0) > pad_geometry_time+1 && all_clients_initialized()) { - remove_fake_fb(); - } -} + } else if (visual_id) { + /* + * we need to call XCreateImage to supply the visual + */ + fb = XCreateImage_wr(dpy, default_visual, depth, ZPixmap, + 0, NULL, dpy_x, dpy_y, BitmapPad(dpy), 0); + fb->data = (char *) malloc(fb->bytes_per_line * fb->height); -void install_padded_fb(char *geom) { - int w, h; - int ok = 1; - if (! geom || *geom == '\0') { - ok = 0; - } else if (sscanf(geom, "%dx%d", &w, &h) != 2) { - ok = 0; + } else { + fb = XGetImage_wr(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, + ZPixmap); + if (! quiet) { + rfbLog("Read initial data from X display into" + " framebuffer.\n"); + } } - w = nabs(w); - h = nabs(h); - if (w < 5) w = 5; - if (h < 5) h = 5; + if (subwin) { + XSetErrorHandler(old_handler); + if (trapped_xerror) { + rfbLog("trapped GetImage at SUBWIN creation.\n"); + if (try < subwin_tries) { + usleep(250 * 1000); + if (!get_window_size(window, &wdpy_x, &wdpy_y)) { + rfbLog("could not get size of subwin " + "0x%lx\n", subwin); + X_UNLOCK; + clean_up_exit(1); + } + goto again; + } + } + trapped_xerror = 0; - if (!ok) { - rfbLog("skipping invalid pad geometry: '%s'\n", NONUL(geom)); - return; + } else if (! fb && try == 1) { + /* try once more */ + usleep(250 * 1000); + goto again; } - install_fake_fb(w, h, bpp); - pad_geometry_time = time(0); -} + if (use_snapfb) { + initialize_snap_fb(); + } + X_UNLOCK; -void initialize_snap_fb(void) { - if (snap_fb) { - free(snap_fb); + if (fb->bits_per_pixel == 24 && ! quiet) { + rfbLog("warning: 24 bpp may have poor performance.\n"); } - snap = XGetImage_wr(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, - ZPixmap); - snap_fb = snap->data; + return fb; } +void parse_scale_string(char *str, double *factor, int *scaling, int *blend, + int *nomult4, int *pad, int *interpolate, int *numer, int *denom) { -XImage *initialize_raw_fb(void) { - char *str, *q; - int w, h, b, shmid = 0; - unsigned long rm = 0, gm = 0, bm = 0; - static XImage ximage_struct; /* n.b.: not (XImage *) */ - int closedpy = 1, i, m; + int m, n; + char *p, *tstr; + double f; + + *factor = 1.0; + *scaling = 0; + *blend = 1; + *nomult4 = 0; + *pad = 0; + *interpolate = 0; + *numer = 0, *denom = 0; + + if (str == NULL || str[0] == '\0') { + return; + } + tstr = strdup(str); - if (raw_fb_addr || raw_fb_seek) { - if (raw_fb_shm) { - shmdt(raw_fb_addr); -#if LIBVNCSERVER_HAVE_MMAP - } else if (raw_fb_mmap) { - munmap(raw_fb_addr, raw_fb_mmap); - if (raw_fb_fd >= 0) { - close(raw_fb_fd); + if ( (p = strchr(tstr, ':')) != NULL) { + /* options */ + if (strstr(p+1, "nb") != NULL) { + *blend = 0; + } + if (strstr(p+1, "fb") != NULL) { + *blend = 2; + } + if (strstr(p+1, "n4") != NULL) { + *nomult4 = 1; + } + if (strstr(p+1, "in") != NULL) { + *interpolate = 1; + } + if (strstr(p+1, "pad") != NULL) { + *pad = 1; + } + *p = '\0'; + } + if (strchr(tstr, '.') != NULL) { + double test, diff, eps = 1.0e-7; + if (sscanf(tstr, "%lf", &f) != 1) { + rfbLog("bad -scale arg: %s\n", tstr); + clean_up_exit(1); + } + *factor = (double) f; + /* look for common fractions from small ints: */ + for (n=2; n<=10; n++) { + for (m=1; m= 0) { - close(raw_fb_fd); + if (*denom) { + break; } } - raw_fb_addr = NULL; - } - if (! raw_fb_str) { - return NULL; - } - - - if ( (q = strstr(raw_fb_str, "setup:")) == raw_fb_str) { - FILE *pipe; - char line[1024], *t; - - set_child_info(); - q += strlen("setup:"); - if (no_external_cmds) { - rfbLog("cannot run external commands in -nocmds " - "mode:\n"); - rfbLog(" \"%s\"\n", q); - rfbLog(" exiting.\n"); + if (*factor < 0.01) { + rfbLog("-scale factor too small: %f\n", scale_fac); clean_up_exit(1); } - rfbLog("running command to setup rawfb: %s\n", q); - pipe = popen(q, "r"); - if (! pipe) { - rfbLog("popen of setup command failed.\n"); - rfbLogPerror("popen"); + } else { + if (sscanf(tstr, "%d/%d", &m, &n) != 2) { + if (sscanf(tstr, "%d", &m) != 1) { + rfbLog("bad -scale arg: %s\n", tstr); + clean_up_exit(1); + } else { + /* e.g. -scale 1 or -scale 2 */ + n = 1; + } + } + if (n <= 0 || m <=0) { + rfbLog("bad -scale arg: %s\n", tstr); clean_up_exit(1); } - line[0] = '\0'; - if (fgets(line, 1024, pipe) == NULL) { - rfbLog("read of setup command failed.\n"); + *factor = ((double) m)/ n; + if (*factor < 0.01) { + rfbLog("-scale factor too small: %f\n", *factor); clean_up_exit(1); } - pclose(pipe); - str = strdup(line); - t = str; - while (*t != '\0') { - if (*t == '\n') { - *t = '\0'; - } - t++; + *numer = m; + *denom = n; + } + if (*factor == 1.0) { + if (! quiet) { + rfbLog("scaling disabled for factor %f\n", *factor); } - rfbLog("setup command returned: %s\n", str); - } else { - str = strdup(raw_fb_str); + *scaling = 1; } + free(tstr); +} - /* - * uppercase means do not close the display (e.g. for remote control) - */ - if (strstr(str, "SHM:") == str) { - closedpy = 0; - str[0] = 's'; str[1] = 'h'; str[2] = 'm'; - } else if (strstr(str, "MAP:") == str) { - closedpy = 0; - str[0] = 'm'; str[1] = 'a'; str[2] = 'p'; - } else if (strstr(str, "MMAP:") == str) { - closedpy = 0; - str[0] = 'm'; str[1] = 'm'; str[2] = 'a'; str[3] = 'p'; - } else if (strstr(str, "FILE:") == str) { - str[0] = 'f'; str[1] = 'i'; str[2] = 'l'; str[3] = 'e'; - closedpy = 0; +int scale_round(int len, double fac) { + double eps = 0.000001; + + len = (int) (len * fac + eps); + if (len < 1) { + len = 1; } + return len; +} - if (closedpy && !view_only && got_noviewonly) { - rfbLog("not closing X DISPLAY under -noviewonly option.\n"); - closedpy = 0; - if (! window) { - window = rootwin; +void setup_scaling(int *width_in, int *height_in) { + int width = *width_in; + int height = *height_in; + + parse_scale_string(scale_str, &scale_fac, &scaling, &scaling_blend, + &scaling_nomult4, &scaling_pad, &scaling_interpolate, + &scale_numer, &scale_denom); + + if (scaling) { + width = scale_round(width, scale_fac); + height = scale_round(height, scale_fac); + if (scale_denom && scaling_pad) { + /* it is not clear this padding is useful anymore */ + rfbLog("width %% denom: %d %% %d = %d\n", width, + scale_denom, width % scale_denom); + rfbLog("height %% denom: %d %% %d = %d\n", height, + scale_denom, height % scale_denom); + if (width % scale_denom != 0) { + int w = width; + w += scale_denom - (w % scale_denom); + if (!scaling_nomult4 && w % 4 != 0) { + /* need to make mult of 4 as well */ + int c = 0; + while (w % 4 != 0 && c++ <= 5) { + w += scale_denom; + } + } + width = w; + rfbLog("padded width to: %d (mult of %d%s\n", + width, scale_denom, !scaling_nomult4 ? + " and 4)" : ")"); + } + if (height % scale_denom != 0) { + height += scale_denom - (height % scale_denom); + rfbLog("padded height to: %d (mult of %d)\n", + height, scale_denom); + } } - } + if (!scaling_nomult4 && width % 4 != 0 && width > 2) { + /* reset width to be multiple of 4 */ + int width0 = width; + if ((width+1) % 4 == 0) { + width = width+1; + } else if ((width-1) % 4 == 0) { + width = width-1; + } else if ((width+2) % 4 == 0) { + width = width+2; + } + rfbLog("reset scaled width %d -> %d to be a multiple of" + " 4 (to\n", width0, width); + rfbLog("make vncviewers happy). use -scale m/n:n4 to " + "disable.\n"); + } + scaled_x = width; + scaled_y = height; - if (! raw_fb_orig_dpy && dpy) { - raw_fb_orig_dpy = strdup(DisplayString(dpy)); + *width_in = width; + *height_in = height; } -#ifndef BOLDLY_CLOSE_DISPLAY -#define BOLDLY_CLOSE_DISPLAY 1 -#endif -#if BOLDLY_CLOSE_DISPLAY - if (closedpy) { - if (dpy) { - rfbLog("closing X DISPLAY: %s in rawfb mode.\n", - DisplayString(dpy)); - XCloseDisplay(dpy); /* yow! */ - } - dpy = 0; +} + +/* + * initialize the rfb framebuffer/screen + */ +void initialize_screen(int *argc, char **argv, XImage *fb) { + int have_masks = 0; + int width = fb->width; + int height = fb->height; + int create_screen = screen ? 0 : 1; + int bits_per_color; + + main_bytes_per_line = fb->bytes_per_line; + + setup_scaling(&width, &height); + + + if (scaling) { + rfbLog("scaling screen: %dx%d -> %dx%d scale_fac=%.5f\n", + fb->width, fb->height, scaled_x, scaled_y, scale_fac); + + rfb_bytes_per_line = (main_bytes_per_line / fb->width) * width; + } else { + rfb_bytes_per_line = main_bytes_per_line; } -#endif /* - * -rawfb shm:163938442@640x480x32:ff/ff00/ff0000+3000 - * -rawfb map:/path/to/file@640x480x32:ff/ff00/ff0000 - * -rawfb file:/path/to/file@640x480x32:ff/ff00/ff0000 + * These are just hints wrt pixel format just to let + * rfbGetScreen/rfbNewFramebuffer proceed with reasonable + * defaults. We manually set them in painful detail below. */ + bits_per_color = guess_bits_per_color(fb->bits_per_pixel); - raw_fb_offset = 0; - - /* +O offset */ - if ((q = strrchr(str, '+')) != NULL) { - if (sscanf(q, "+%d", &raw_fb_offset) == 1) { - *q = '\0'; - } else { - raw_fb_offset = 0; + /* n.b. samplesPerPixel (set = 1 here) seems to be unused. */ + if (create_screen) { + screen = rfbGetScreen(argc, argv, width, height, + bits_per_color, 1, (int) fb->bits_per_pixel/8); + if (screen && http_dir) { + http_connections(1); } + } else { + /* set set frameBuffer member below. */ + rfbLog("rfbNewFramebuffer(0x%x, 0x%x, %d, %d, %d, %d, %d)\n", + screen, NULL, width, height, + bits_per_color, 1, fb->bits_per_pixel/8); + + /* these are probably overwritten, but just to be safe: */ + screen->bitsPerPixel = fb->bits_per_pixel; + screen->depth = fb->depth; + + rfbNewFramebuffer(screen, NULL, width, height, + bits_per_color, 1, (int) fb->bits_per_pixel/8); } - /* :R/G/B masks */ - if ((q = strrchr(str, ':')) != NULL) { - if (sscanf(q, ":%lx/%lx/%lx", &rm, &gm, &bm) == 3) { - *q = '\0'; - } else if (sscanf(q, ":0x%lx/0x%lx/0x%lx", &rm, &gm, &bm)== 3) { - *q = '\0'; - } else if (sscanf(q, ":%lu/%lu/%lu", &rm, &gm, &bm) == 3) { - *q = '\0'; - } else { - rm = 0; - gm = 0; - bm = 0; + if (! screen) { + int i; + rfbLog("\n"); + rfbLog("failed to create rfb screen.\n"); + for (i=0; i< *argc; i++) { + rfbLog("\t[%d] %s\n", i, argv[i]); } - } - if ((q = strrchr(str, '@')) == NULL) { - rfbLog("invalid rawfb str: %s\n", str); - clean_up_exit(1); - } - /* @WxHxB */ - if (sscanf(q, "@%dx%dx%d", &w, &h, &b) != 3) { - rfbLog("invalid rawfb str: %s\n", str); clean_up_exit(1); } - *q = '\0'; - if (strstr(str, "shm:") != str && strstr(str, "mmap:") != str && - strstr(str, "map:") != str && strstr(str, "file:") != str) { - /* hmmm, not following directions, see if map: applies */ - struct stat sbuf; - if (stat(str, &sbuf) == 0) { - char *new; - int len = strlen("map:") + strlen(str) + 1; - rfbLog("no type prefix: %s\n", raw_fb_str); - rfbLog(" but file exists, so assuming: map:%s\n", - raw_fb_str); - new = (char *)malloc(len); - strcpy(new, "map:"); - strcat(new, str); - free(str); - str = new; + if (create_screen && *argc != 1) { + int i; + rfbLog("*** unrecognized option(s) ***\n"); + for (i=1; i< *argc; i++) { + rfbLog("\t[%d] %s\n", i, argv[i]); } + rfbLog("For a list of options run: x11vnc -help\n"); + rfbLog("\n"); + rfbLog("Here is a list of removed or obsolete" + " options:\n"); + rfbLog("\n"); + rfbLog("removed: -hints, -nohints\n"); + rfbLog("removed: -cursorposall\n"); + rfbLog("\n"); + rfbLog("renamed: -old_copytile, use -onetile\n"); + rfbLog("renamed: -mouse, use -cursor\n"); + rfbLog("renamed: -mouseX, use -cursor X\n"); + rfbLog("renamed: -X, use -cursor X\n"); + rfbLog("renamed: -nomouse, use -nocursor\n"); + rfbLog("renamed: -old_pointer, use -pointer_mode 1\n"); + + clean_up_exit(1); } - dpy_x = wdpy_x = w; - dpy_y = wdpy_y = h; - off_x = 0; - off_y = 0; + /* set up format from scratch: */ + screen->paddedWidthInBytes = rfb_bytes_per_line; + screen->serverFormat.bitsPerPixel = fb->bits_per_pixel; + screen->serverFormat.depth = fb->depth; + screen->serverFormat.trueColour = TRUE; - raw_fb_shm = 0; - raw_fb_mmap = 0; - raw_fb_seek = 0; - raw_fb_fd = -1; - raw_fb_addr = NULL; + screen->serverFormat.redShift = 0; + screen->serverFormat.greenShift = 0; + screen->serverFormat.blueShift = 0; + screen->serverFormat.redMax = 0; + screen->serverFormat.greenMax = 0; + screen->serverFormat.blueMax = 0; - if (sscanf(str, "shm:%d", &shmid) == 1) { - /* shm:N */ - raw_fb_addr = (char *) shmat(shmid, 0, SHM_RDONLY); - if (! raw_fb_addr) { - rfbLog("failed to attach to shm: %d, %s\n", shmid, str); - rfbLogPerror("shmat"); - clean_up_exit(1); - } - raw_fb_shm = 1; - rfbLog("rawfb: shm: %d W: %d H: %d B: %d addr: %p\n", - shmid, w, h, b, raw_fb_addr); + /* these main_* formats are used generally. */ + main_red_shift = 0; + main_green_shift = 0; + main_blue_shift = 0; + main_red_max = 0; + main_green_max = 0; + main_blue_max = 0; + main_red_mask = fb->red_mask; + main_green_mask = fb->green_mask; + main_blue_mask = fb->blue_mask; - } else if (strstr(str, "map:") == str || strstr(str, "mmap:") == str - || strstr(str, "file:") == str) { - /* map:/path/... or file:/path */ - int fd, do_mmap = 1, size; - struct stat sbuf; - if (*str == 'f') { - do_mmap = 0; - } - q = strchr(str, ':'); - q++; + have_masks = ((fb->red_mask|fb->green_mask|fb->blue_mask) != 0); + if (force_indexed_color) { + have_masks = 0; + } - fd = open(q, O_RDONLY); - if (fd < 0) { - rfbLog("failed to open file: %s, %s\n", q, str); - rfbLogPerror("open"); - clean_up_exit(1); + if (! have_masks && screen->serverFormat.bitsPerPixel == 8 + && dpy && CellsOfScreen(ScreenOfDisplay(dpy, scr))) { + /* indexed color */ + if (!quiet) { + rfbLog("X display %s is 8bpp indexed color\n", + DisplayString(dpy)); + if (! flash_cmap && ! overlay) { + rfbLog("\n"); + rfbLog("In 8bpp PseudoColor mode if you " + "experience color\n"); + rfbLog("problems you may want to enable " + "following the\n"); + rfbLog("changing colormap by using the " + "-flashcmap option.\n"); + rfbLog("\n"); + } } - raw_fb_fd = fd; - - size = w*h*b/8 + raw_fb_offset; - if (fstat(fd, &sbuf) == 0) { - if (S_ISREG(sbuf.st_mode)) { - if (0) size = sbuf.st_size; + screen->serverFormat.trueColour = FALSE; + indexed_color = 1; + set_colormap(1); + debug_colormap(fb); + } else { + /* + * general case, we call it truecolor, but could be direct + * color, static color, etc.... + */ + if (! quiet) { + if (raw_fb) { + rfbLog("Raw fb at addr %p is %dbpp depth=%d " + "true color\n", raw_fb_addr, + fb->bits_per_pixel, fb->depth); } else { - rfbLog("raw fb is non-regular file: %s\n", q); + rfbLog("X display %s is %dbpp depth=%d true " + "color\n", DisplayString(dpy), + fb->bits_per_pixel, fb->depth); } } - if (do_mmap) { -#if LIBVNCSERVER_HAVE_MMAP - raw_fb_addr = mmap(0, size, PROT_READ, MAP_SHARED, - fd, 0); + indexed_color = 0; - if (raw_fb_addr == MAP_FAILED || raw_fb_addr == NULL) { - rfbLog("failed to mmap file: %s, %s\n", q, str); - rfbLog(" raw_fb_addr: %p\n", raw_fb_addr); - rfbLogPerror("mmap"); - clean_up_exit(1); + /* convert masks to bit shifts and max # colors */ + if (fb->red_mask) { + while (! (fb->red_mask + & (1 << screen->serverFormat.redShift))) { + screen->serverFormat.redShift++; } - raw_fb_mmap = size; - - rfbLog("rawfb: mmap file: %s\n", q); - rfbLog(" w: %d h: %d b: %d addr: %p sz: %d\n", w, h, - b, raw_fb_addr, size); -#else - rfbLog("mmap(2) not supported on system, using" - " slower lseek(2)\n"); - raw_fb_seek = size; -#endif - } else { - raw_fb_seek = size; - - rfbLog("rawfb: seek file: %s\n", q); - rfbLog(" W: %d H: %d B: %d sz: %d\n", w, h, b, size); } - } else { - rfbLog("invalid rawfb str: %s\n", str); - clean_up_exit(1); - } - - if (! raw_fb_image) { - raw_fb_image = &ximage_struct; - } - - initialize_clipshift(); + if (fb->green_mask) { + while (! (fb->green_mask + & (1 << screen->serverFormat.greenShift))) { + screen->serverFormat.greenShift++; + } + } + if (fb->blue_mask) { + while (! (fb->blue_mask + & (1 << screen->serverFormat.blueShift))) { + screen->serverFormat.blueShift++; + } + } + screen->serverFormat.redMax + = fb->red_mask >> screen->serverFormat.redShift; + screen->serverFormat.greenMax + = fb->green_mask >> screen->serverFormat.greenShift; + screen->serverFormat.blueMax + = fb->blue_mask >> screen->serverFormat.blueShift; - raw_fb = (char *) malloc(dpy_x * dpy_y * b/8); - raw_fb_image->data = raw_fb; - raw_fb_image->format = ZPixmap; - raw_fb_image->width = dpy_x; - raw_fb_image->height = dpy_y; - raw_fb_image->bits_per_pixel = b; - raw_fb_image->bytes_per_line = dpy_x*b/8; + main_red_max = screen->serverFormat.redMax; + main_green_max = screen->serverFormat.greenMax; + main_blue_max = screen->serverFormat.blueMax; - if (rm == 0 && gm == 0 && bm == 0) { - /* guess masks... */ - if (b == 24 || b == 32) { - rm = 0xff0000; - gm = 0x00ff00; - bm = 0x0000ff; - } else if (b == 16) { - rm = 0xf800; - gm = 0x07e0; - bm = 0x001f; - } else if (b == 8) { - rm = 0x07; - gm = 0x38; - bm = 0xc0; - } + main_red_shift = screen->serverFormat.redShift; + main_green_shift = screen->serverFormat.greenShift; + main_blue_shift = screen->serverFormat.blueShift; } - raw_fb_image->red_mask = rm; - raw_fb_image->green_mask = gm; - raw_fb_image->blue_mask = bm; - - raw_fb_image->depth = 0; - m = 1; - for (i=0; i<32; i++) { - if (rm & m) { - raw_fb_image->depth++; +#if !SMALL_FOOTPRINT + if (!quiet) { + fprintf(stderr, "\n"); + fprintf(stderr, "FrameBuffer Info:\n"); + fprintf(stderr, " width: %d\n", fb->width); + fprintf(stderr, " height: %d\n", fb->height); + fprintf(stderr, " scaled_width: %d\n", width); + fprintf(stderr, " scaled_height: %d\n", height); + fprintf(stderr, " indexed_color: %d\n", indexed_color); + fprintf(stderr, " bits_per_pixel: %d\n", fb->bits_per_pixel); + fprintf(stderr, " depth: %d\n", fb->depth); + fprintf(stderr, " red_mask: 0x%08lx %s\n", fb->red_mask, + bitprint(fb->red_mask, 32)); + fprintf(stderr, " green_mask: 0x%08lx %s\n", fb->green_mask, + bitprint(fb->green_mask, 32)); + fprintf(stderr, " blue_mask: 0x%08lx %s\n", fb->blue_mask, + bitprint(fb->blue_mask, 32)); + fprintf(stderr, " red: max: %3d shift: %2d\n", + main_red_max, main_red_shift); + fprintf(stderr, " green: max: %3d shift: %2d\n", + main_green_max, main_green_shift); + fprintf(stderr, " blue: max: %3d shift: %2d\n", + main_blue_max, main_blue_shift); + fprintf(stderr, " mainfb_bytes_per_line: %d\n", + main_bytes_per_line); + fprintf(stderr, " rfb_fb_bytes_per_line: %d\n", + rfb_bytes_per_line); + switch(fb->format) { + case XYBitmap: + fprintf(stderr, " format: XYBitmap\n"); break; + case XYPixmap: + fprintf(stderr, " format: XYPixmap\n"); break; + case ZPixmap: + fprintf(stderr, " format: ZPixmap\n"); break; + default: + fprintf(stderr, " format: %d\n", fb->format); break; } - if (gm & m) { - raw_fb_image->depth++; + switch(fb->byte_order) { + case LSBFirst: + fprintf(stderr, " byte_order: LSBFirst\n"); break; + case MSBFirst: + fprintf(stderr, " byte_order: MSBFirst\n"); break; + default: + fprintf(stderr, " byte_order: %d\n", fb->byte_order); + break; } - if (bm & m) { - raw_fb_image->depth++; + fprintf(stderr, " bitmap_pad: %d\n", fb->bitmap_pad); + fprintf(stderr, " bitmap_unit: %d\n", fb->bitmap_unit); + switch(fb->bitmap_bit_order) { + case LSBFirst: + fprintf(stderr, " bitmap_bit_order: LSBFirst\n"); break; + case MSBFirst: + fprintf(stderr, " bitmap_bit_order: MSBFirst\n"); break; + default: + fprintf(stderr, " bitmap_bit_order: %d\n", + fb->bitmap_bit_order); break; } - m = m << 1; } - if (! raw_fb_image->depth) { - raw_fb_image->depth = (b == 32) ? 24 : b; + if (overlay && ! quiet) { + rfbLog("\n"); + rfbLog("Overlay mode enabled: If you experience color\n"); + rfbLog("problems when popup menus are on the screen, try\n"); + rfbLog("disabling SaveUnders in your X server, one way is\n"); + rfbLog("to start the X server with the '-su' option, e.g.:\n"); + rfbLog("Xsun -su ... see Xserver(1), xinit(1) for more info.\n"); + rfbLog("\n"); } - - if (clipshift) { - memset(raw_fb, 0xff, dpy_x * dpy_y * b/8); - } else if (raw_fb_addr) { - memcpy(raw_fb, raw_fb_addr + raw_fb_offset, dpy_x*dpy_y*b/8); +#endif + /* nofb is for pointer/keyboard only handling. */ + if (nofb) { + main_fb = NULL; + rfb_fb = main_fb; + screen->displayHook = nofb_hook; } else { - memset(raw_fb, 0xff, dpy_x * dpy_y * b/8); - } - - rfbLog("rawfb: raw_fb %p\n", raw_fb); - - free(str); - - return raw_fb_image; -} - -void initialize_clipshift(void) { - clipshift = 0; - cdpy_x = cdpy_y = coff_x = coff_y = 0; - if (clip_str) { - int w, h, x, y, bad = 0; - if (parse_geom(clip_str, &w, &h, &x, &y, wdpy_x, wdpy_y)) { - if (x < 0) { - x = 0; - } - if (y < 0) { - y = 0; - } - if (x + w > wdpy_x) { - w = wdpy_x - x; - } - if (y + h > wdpy_y) { - h = wdpy_y - y; - } - if (w <= 0 || h <= 0) { - bad = 1; - } + main_fb = fb->data; + if (scaling) { + rfb_fb = (char *) malloc(rfb_bytes_per_line * height); + memset(rfb_fb, 0, rfb_bytes_per_line * height); } else { - bad = 1; + rfb_fb = main_fb; } - if (bad) { - rfbLog("skipping invalid -clip WxH+X+Y: %s\n", - clip_str); - } else { - /* OK, change geom behind everyone's back... */ - cdpy_x = w; - cdpy_y = h; - coff_x = x; - coff_y = y; + } + screen->frameBuffer = rfb_fb; + if (!quiet) { + fprintf(stderr, " main_fb: %p\n", main_fb); + fprintf(stderr, " rfb_fb: %p\n", rfb_fb); + fprintf(stderr, "\n"); + } - clipshift = 1; + bpp = screen->serverFormat.bitsPerPixel; + depth = screen->serverFormat.depth; - dpy_x = cdpy_x; - dpy_y = cdpy_y; - } - } -} + /* may need, bpp, main_red_max, etc. */ + parse_wireframe(); + parse_scroll_copyrect(); -/* - * initialize a fb for the X display - */ -XImage *initialize_xdisplay_fb(void) { - XImage *fb; - char *vis_str = visual_str; - int try = 0, subwin_tries = 3; - XErrorHandler old_handler; - int subwin_bs; + setup_cursors_and_push(); - if (raw_fb_str) { - return initialize_raw_fb(); + if (scaling) { + mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0); } - X_LOCK; - if (subwin) { - if (subwin_wait_mapped) { - wait_until_mapped(subwin); - } - if (!valid_window((Window) subwin, NULL)) { - rfbLog("invalid sub-window: 0x%lx\n", subwin); - X_UNLOCK; - clean_up_exit(1); - } - } - - if (overlay) { + if (! create_screen) { + rfbClientIteratorPtr iter; + rfbClientPtr cl; + /* - * ideally we'd like to not have to cook up the - * visual variables but rather let it all come out - * of XReadScreen(), however there is no way to get - * a default visual out of it, so we pretend -visual - * TrueColor:NN was supplied with NN usually 24. + * since bits_per_color above may have been approximate, + * try to reset the individual translation tables... + * we do not seem to need this with rfbGetScreen()... */ - char str[32]; - Window twin = subwin ? subwin : rootwin; - XImage *xi; - - xi = xreadscreen(dpy, twin, 0, 0, 8, 8, False); - sprintf(str, "TrueColor:%d", xi->depth); - if (xi->depth != 24 && ! quiet) { - rfbLog("warning: overlay image has depth %d " - "instead of 24.\n", xi->depth); - } - XDestroyImage(xi); - if (visual_str != NULL && ! quiet) { - rfbLog("warning: replacing '-visual %s' by '%s' " - "for use with -overlay\n", visual_str, str); - } - vis_str = strdup(str); - } - - if (vis_str != NULL) { - set_visual(vis_str); - if (vis_str != visual_str) { - free(vis_str); + if (!quiet) rfbLog("calling setTranslateFunction()...\n"); + iter = rfbGetClientIterator(screen); + while ((cl = rfbClientIteratorNext(iter)) != NULL) { + screen->setTranslateFunction(cl); } + rfbReleaseClientIterator(iter); + if (!quiet) rfbLog(" done.\n"); + do_copy_screen = 1; + + /* done for framebuffer change case */ + return; } - /* set up parameters for subwin or non-subwin cases: */ - - if (! subwin) { - /* full screen */ - window = rootwin; - dpy_x = wdpy_x = DisplayWidth(dpy, scr); - dpy_y = wdpy_y = DisplayHeight(dpy, scr); - off_x = 0; - off_y = 0; - /* this may be overridden via visual_id below */ - default_visual = DefaultVisual(dpy, scr); - } else { - /* single window */ - XWindowAttributes attr; + /* + * the rest is screen server initialization, etc, only needed + * at screen creation time. + */ - window = (Window) subwin; - if (! XGetWindowAttributes(dpy, window, &attr)) { - rfbLog("invalid window: 0x%lx\n", window); - X_UNLOCK; + /* called from inetd, we need to treat stdio as our socket */ + if (inetd) { + int fd = dup(0); + if (fd < 3) { + rfbErr("dup(0) = %d failed.\n", fd); + rfbLogPerror("dup"); clean_up_exit(1); } - dpy_x = wdpy_x = attr.width; - dpy_y = wdpy_y = attr.height; - - subwin_bs = attr.backing_store; + fclose(stdin); + fclose(stdout); + /* we keep stderr for logging */ + screen->inetdSock = fd; + screen->port = 0; - /* this may be overridden via visual_id below */ - default_visual = attr.visual; + } else if (! got_rfbport) { + screen->autoPort = TRUE; + } - X_UNLOCK; - set_offset(); - X_LOCK; + if (! got_nevershared && ! got_alwaysshared) { + if (shared) { + screen->alwaysShared = TRUE; + } else { + screen->dontDisconnect = TRUE; + screen->neverShared = TRUE; + } + } + /* XXX the following is based on libvncserver defaults. */ + if (screen->deferUpdateTime == 5) { + /* XXX will be fixed someday */ + screen->deferUpdateTime = defer_update; } - initialize_clipshift(); + /* event callbacks: */ + screen->newClientHook = new_client; + screen->kbdAddEvent = keyboard; + screen->ptrAddEvent = pointer; + screen->setXCutText = xcut_receive; - /* initialize depth to reasonable value, visual_id may override */ - depth = DefaultDepth(dpy, scr); + rfbInitServer(screen); - if (visual_id) { - int n; - XVisualInfo vinfo_tmpl, *vinfo; + if (viewonly_passwd) { + /* append the view only passwd after the normal passwd */ + char **passwds_new = malloc(3*sizeof(char**)); + char **passwds_old = (char **) screen->authPasswdData; + passwds_new[0] = passwds_old[0]; + passwds_new[1] = viewonly_passwd; + passwds_new[2] = NULL; + screen->authPasswdData = (void*) passwds_new; + } +} - /* - * we are in here from -visual or -overlay options - * visual_id and visual_depth were set in set_visual(). - */ +/* -- solid.c -- */ - vinfo_tmpl.visualid = visual_id; - vinfo = XGetVisualInfo(dpy, VisualIDMask, &vinfo_tmpl, &n); - if (vinfo == NULL || n == 0) { - rfbLog("could not match visual_id: 0x%x\n", - (int) visual_id); - X_UNLOCK; - clean_up_exit(1); - } - default_visual = vinfo->visual; - depth = vinfo->depth; - if (visual_depth) { - /* force it from -visual MooColor:NN */ - depth = visual_depth; - } - if (! quiet) { - fprintf(stderr, " initialize_xdisplay_fb()\n"); - fprintf(stderr, " Visual*: %p\n", vinfo->visual); - fprintf(stderr, " visualid: 0x%x\n", - (int) vinfo->visualid); - fprintf(stderr, " screen: %d\n", vinfo->screen); - fprintf(stderr, " depth: %d\n", vinfo->depth); - fprintf(stderr, " class: %d\n", vinfo->class); - fprintf(stderr, " red_mask: 0x%08lx %s\n", - vinfo->red_mask, bitprint(vinfo->red_mask, 32)); - fprintf(stderr, " green_mask: 0x%08lx %s\n", - vinfo->green_mask, bitprint(vinfo->green_mask, 32)); - fprintf(stderr, " blue_mask: 0x%08lx %s\n", - vinfo->blue_mask, bitprint(vinfo->blue_mask, 32)); - fprintf(stderr, " cmap_size: %d\n", - vinfo->colormap_size); - fprintf(stderr, " bits b/rgb: %d\n", - vinfo->bits_per_rgb); - fprintf(stderr, "\n"); +void usr_bin_path(int restore) { + static char *oldpath = NULL; + char *newpath; + char addpath[] = "/usr/bin:/bin:"; + + if (restore) { + if (oldpath) { + set_env("PATH", oldpath); + free(oldpath); + oldpath = NULL; } - XFree(vinfo); + return; } - if (! quiet) { - rfbLog("default visual ID: 0x%x\n", - (int) XVisualIDFromVisual(default_visual)); + if (getenv("PATH")) { + oldpath = strdup(getenv("PATH")); + } else { + oldpath = strdup("/usr/bin"); } + newpath = (char *) malloc(strlen(oldpath) + strlen(addpath) + 1); + newpath[0] = '\0'; + strcat(newpath, addpath); + strcat(newpath, oldpath); + set_env("PATH", newpath); + free(newpath); +} - again: - if (subwin) { - int shift = 0; - int subwin_x, subwin_y; - int disp_x = DisplayWidth(dpy, scr); - int disp_y = DisplayHeight(dpy, scr); - Window twin; - /* subwins can be a dicey if they are changing size... */ - XTranslateCoordinates(dpy, window, rootwin, 0, 0, &subwin_x, - &subwin_y, &twin); - if (subwin_x + wdpy_x > disp_x) { - shift = 1; - subwin_x = disp_x - wdpy_x - 3; - } - if (subwin_y + wdpy_y > disp_y) { - shift = 1; - subwin_y = disp_y - wdpy_y - 3; - } - if (subwin_x < 0) { - shift = 1; - subwin_x = 1; - } - if (subwin_y < 0) { - shift = 1; - subwin_y = 1; - } +int dt_cmd(char *cmd) { + int rc; - trapped_xerror = 0; - old_handler = XSetErrorHandler(trap_xerror); - if (shift) { - XMoveWindow(dpy, window, subwin_x, subwin_y); - } - XMapRaised(dpy, window); - XRaiseWindow(dpy, window); - XFlush(dpy); + if (!cmd || *cmd == '\0') { + return 0; } - try++; - if (nofb) { - /* - * For -nofb we do not allocate the framebuffer, so we - * can save a few MB of memory. - */ - fb = XCreateImage_wr(dpy, default_visual, depth, ZPixmap, - 0, NULL, dpy_x, dpy_y, BitmapPad(dpy), 0); + if (no_external_cmds) { + rfbLog("cannot run external commands in -nocmds mode:\n"); + rfbLog(" \"%s\"\n", cmd); + rfbLog(" dt_cmd: returning 1\n"); + return 1; + } - } else if (visual_id) { - /* - * we need to call XCreateImage to supply the visual - */ - fb = XCreateImage_wr(dpy, default_visual, depth, ZPixmap, - 0, NULL, dpy_x, dpy_y, BitmapPad(dpy), 0); - fb->data = (char *) malloc(fb->bytes_per_line * fb->height); + rfbLog("running command:\n %s\n", cmd); + usr_bin_path(0); + rc = system(cmd); + usr_bin_path(1); - } else { - fb = XGetImage_wr(dpy, window, 0, 0, dpy_x, dpy_y, AllPlanes, - ZPixmap); - if (! quiet) { - rfbLog("Read initial data from X display into" - " framebuffer.\n"); - } + if (rc >= 256) { + rc = rc/256; } + return rc; +} - if (subwin) { - XSetErrorHandler(old_handler); - if (trapped_xerror) { - rfbLog("trapped GetImage at SUBWIN creation.\n"); - if (try < subwin_tries) { - usleep(250 * 1000); - if (!get_window_size(window, &wdpy_x, &wdpy_y)) { - rfbLog("could not get size of subwin " - "0x%lx\n", subwin); - X_UNLOCK; - clean_up_exit(1); - } - goto again; - } - } - trapped_xerror = 0; +char *cmd_output(char *cmd) { + FILE *p; + static char output[50000]; + char line[1024]; + int rc; - } else if (! fb && try == 1) { - /* try once more */ - usleep(250 * 1000); - goto again; - } - if (use_snapfb) { - initialize_snap_fb(); + if (!cmd || *cmd == '\0') { + return ""; } - X_UNLOCK; - if (fb->bits_per_pixel == 24 && ! quiet) { - rfbLog("warning: 24 bpp may have poor performance.\n"); + if (no_external_cmds) { + rfbLog("cannot run external commands in -nocmds mode:\n"); + rfbLog(" \"%s\"\n", cmd); + rfbLog(" cmd_output: null string.\n"); + return ""; } - return fb; -} -void parse_scale_string(char *str, double *factor, int *scaling, int *blend, - int *nomult4, int *pad, int *interpolate, int *numer, int *denom) { + rfbLog("running pipe:\n %s\n", cmd); + usr_bin_path(0); + p = popen(cmd, "r"); + usr_bin_path(1); - int m, n; - char *p, *tstr; - double f; + output[0] = '\0'; - *factor = 1.0; - *scaling = 0; - *blend = 1; - *nomult4 = 0; - *pad = 0; - *interpolate = 0; - *numer = 0, *denom = 0; + while (fgets(line, 1024, p) != NULL) { + if (strlen(output) + strlen(line) + 1 < 50000) { + strcat(output, line); + } + } + rc = pclose(p); + return(output); +} - if (str == NULL || str[0] == '\0') { +void solid_root(char *color) { + Window expose; + static XImage *image = NULL; + Pixmap pixmap; + XGCValues gcv; + GC gc; + XSetWindowAttributes swa; + Visual visual; + unsigned long mask, pixel; + XColor cdef; + Colormap cmap; + + if (subwin || window != rootwin) { + rfbLog("cannot set subwin to solid color, must be rootwin\n"); return; } - tstr = strdup(str); - - if ( (p = strchr(tstr, ':')) != NULL) { - /* options */ - if (strstr(p+1, "nb") != NULL) { - *blend = 0; - } - if (strstr(p+1, "fb") != NULL) { - *blend = 2; - } - if (strstr(p+1, "n4") != NULL) { - *nomult4 = 1; - } - if (strstr(p+1, "in") != NULL) { - *interpolate = 1; - } - if (strstr(p+1, "pad") != NULL) { - *pad = 1; - } - *p = '\0'; - } - if (strchr(tstr, '.') != NULL) { - double test, diff, eps = 1.0e-7; - if (sscanf(tstr, "%lf", &f) != 1) { - rfbLog("bad -scale arg: %s\n", tstr); - clean_up_exit(1); - } - *factor = (double) f; - /* look for common fractions from small ints: */ - for (n=2; n<=10; n++) { - for (m=1; m 8 || strcmp(color, solid_default)) { + cmap = DefaultColormap (dpy, scr); + if (XParseColor(dpy, cmap, color, &cdef) && + XAllocColor(dpy, cmap, &cdef)) { + pixel = cdef.pixel; + } else { + rfbLog("error parsing/allocing color: %s\n", color); } - } else { - *scaling = 1; } - free(tstr); + + rfbLog("setting solid background...\n"); + XSetWindowBackground(dpy, window, pixel); + XMapWindow(dpy, expose); + XSync(dpy, False); + XDestroyWindow(dpy, expose); } -int scale_round(int len, double fac) { - double eps = 0.000001; - - len = (int) (len * fac + eps); - if (len < 1) { - len = 1; +void solid_cde(char *color) { + int wsmax = 16; + static XImage *image[16]; + static Window ws_wins[16]; + static int nws = -1; + + Window expose; + Pixmap pixmap; + XGCValues gcv; + GC gc; + XSetWindowAttributes swa; + Visual visual; + unsigned long mask, pixel; + XColor cdef; + Colormap cmap; + int n; + + if (subwin || window != rootwin) { + rfbLog("cannot set subwin to solid color, must be rootwin\n"); + return; } - return len; -} -void setup_scaling(int *width_in, int *height_in) { - int width = *width_in; - int height = *height_in; + /* create the "clear" window just for generating exposures */ + swa.override_redirect = True; + swa.backing_store = NotUseful; + swa.save_under = False; + swa.background_pixmap = None; + visual.visualid = CopyFromParent; + mask = (CWOverrideRedirect|CWBackingStore|CWSaveUnder|CWBackPixmap); + expose = XCreateWindow(dpy, window, 0, 0, wdpy_x, wdpy_y, 0, depth, + InputOutput, &visual, mask, &swa); - parse_scale_string(scale_str, &scale_fac, &scaling, &scaling_blend, - &scaling_nomult4, &scaling_pad, &scaling_interpolate, - &scale_numer, &scale_denom); + if (! color) { + /* restore the backdrop windows from the XImage snapshots */ - if (scaling) { - width = scale_round(width, scale_fac); - height = scale_round(height, scale_fac); - if (scale_denom && scaling_pad) { - /* it is not clear this padding is useful anymore */ - rfbLog("width %% denom: %d %% %d = %d\n", width, - scale_denom, width % scale_denom); - rfbLog("height %% denom: %d %% %d = %d\n", height, - scale_denom, height % scale_denom); - if (width % scale_denom != 0) { - int w = width; - w += scale_denom - (w % scale_denom); - if (!scaling_nomult4 && w % 4 != 0) { - /* need to make mult of 4 as well */ - int c = 0; - while (w % 4 != 0 && c++ <= 5) { - w += scale_denom; - } - } - width = w; - rfbLog("padded width to: %d (mult of %d%s\n", - width, scale_denom, !scaling_nomult4 ? - " and 4)" : ")"); + for (n=0; n < nws; n++) { + Window twin; + + if (! image[n]) { + continue; } - if (height % scale_denom != 0) { - height += scale_denom - (height % scale_denom); - rfbLog("padded height to: %d (mult of %d)\n", - height, scale_denom); + + twin = ws_wins[n]; + if (! twin) { + twin = rootwin; } - } - if (!scaling_nomult4 && width % 4 != 0 && width > 2) { - /* reset width to be multiple of 4 */ - int width0 = width; - if ((width+1) % 4 == 0) { - width = width+1; - } else if ((width-1) % 4 == 0) { - width = width-1; - } else if ((width+2) % 4 == 0) { - width = width+2; + if (! valid_window(twin, NULL)) { + continue; } - rfbLog("reset scaled width %d -> %d to be a multiple of" - " 4 (to\n", width0, width); - rfbLog("make vncviewers happy). use -scale m/n:n4 to " - "disable.\n"); - } - scaled_x = width; - scaled_y = height; - - *width_in = width; - *height_in = height; - } -} -/* - * initialize the rfb framebuffer/screen - */ -void initialize_screen(int *argc, char **argv, XImage *fb) { - int have_masks = 0; - int width = fb->width; - int height = fb->height; - int create_screen = screen ? 0 : 1; - int bits_per_color; - - main_bytes_per_line = fb->bytes_per_line; + pixmap = XCreatePixmap(dpy, twin, wdpy_x, wdpy_y, + depth); + + /* draw the image to a pixmap: */ + gcv.function = GXcopy; + gcv.plane_mask = AllPlanes; + gc = XCreateGC(dpy, twin, GCFunction|GCPlaneMask, &gcv); - setup_scaling(&width, &height); - + XPutImage(dpy, pixmap, gc, image[n], 0, 0, 0, 0, + wdpy_x, wdpy_y); - if (scaling) { - rfbLog("scaling screen: %dx%d -> %dx%d scale_fac=%.5f\n", - fb->width, fb->height, scaled_x, scaled_y, scale_fac); + gcv.foreground = gcv.background = BlackPixel(dpy, scr); + gc = XCreateGC(dpy, twin, GCForeground|GCBackground, + &gcv); - rfb_bytes_per_line = (main_bytes_per_line / fb->width) * width; - } else { - rfb_bytes_per_line = main_bytes_per_line; + rfbLog("restoring CDE ws%d snapshot to 0x%lx\n", + n, twin); + /* set the pixmap as the bg: */ + XSetWindowBackgroundPixmap(dpy, twin, pixmap); + XFreePixmap(dpy, pixmap); + XClearWindow(dpy, twin); + XFlush(dpy); + } + + /* generate exposures */ + XMapWindow(dpy, expose); + XSync(dpy, False); + XDestroyWindow(dpy, expose); + return; } - /* - * These are just hints wrt pixel format just to let - * rfbGetScreen/rfbNewFramebuffer proceed with reasonable - * defaults. We manually set them in painful detail below. - */ - bits_per_color = guess_bits_per_color(fb->bits_per_pixel); - - /* n.b. samplesPerPixel (set = 1 here) seems to be unused. */ - if (create_screen) { - screen = rfbGetScreen(argc, argv, width, height, - bits_per_color, 1, (int) fb->bits_per_pixel/8); - if (screen && http_dir) { - http_connections(1); - } - } else { - /* set set frameBuffer member below. */ - rfbLog("rfbNewFramebuffer(0x%x, 0x%x, %d, %d, %d, %d, %d)\n", - screen, NULL, width, height, - bits_per_color, 1, fb->bits_per_pixel/8); + if (nws < 0) { + /* need to retrieve snapshots of the ws backgrounds: */ + Window iwin, wm_win; + XSetWindowAttributes iswa; + Atom dt_list, wm_info, type; + int format; + unsigned long length, after; + unsigned char *data; + unsigned int * dp; - /* these are probably overwritten, but just to be safe: */ - screen->bitsPerPixel = fb->bits_per_pixel; - screen->depth = fb->depth; + nws = 0; - rfbNewFramebuffer(screen, NULL, width, height, - bits_per_color, 1, (int) fb->bits_per_pixel/8); - } - if (! screen) { - int i; - rfbLog("\n"); - rfbLog("failed to create rfb screen.\n"); - for (i=0; i< *argc; i++) { - rfbLog("\t[%d] %s\n", i, argv[i]); + /* extract the hidden wm properties about backdrops: */ + + wm_info = XInternAtom(dpy, "_MOTIF_WM_INFO", True); + if (wm_info == None) { + return; } - clean_up_exit(1); - } -/* - * This ifdef is a transient for source compatibility for people who download - * the x11vnc.c file by itself and plop it down into their libvncserver tree. - * Remove at some point. BTW, this assumes no usage of earlier "0.7pre". - */ -#if OLD_TREE && defined(LIBVNCSERVER_VERSION) - if (strcmp(LIBVNCSERVER_VERSION, "0.6")) -#endif - { - if (create_screen && *argc != 1) { - int i; - rfbLog("*** unrecognized option(s) ***\n"); - for (i=1; i< *argc; i++) { - rfbLog("\t[%d] %s\n", i, argv[i]); - } - rfbLog("For a list of options run: x11vnc -help\n"); - rfbLog("\n"); - rfbLog("Here is a list of removed or obsolete" - " options:\n"); - rfbLog("\n"); - rfbLog("removed: -hints, -nohints\n"); - rfbLog("removed: -cursorposall\n"); - rfbLog("\n"); - rfbLog("renamed: -old_copytile, use -onetile\n"); - rfbLog("renamed: -mouse, use -cursor\n"); - rfbLog("renamed: -mouseX, use -cursor X\n"); - rfbLog("renamed: -X, use -cursor X\n"); - rfbLog("renamed: -nomouse, use -nocursor\n"); - rfbLog("renamed: -old_pointer, use -pointer_mode 1\n"); - - clean_up_exit(1); + XGetWindowProperty(dpy, rootwin, wm_info, 0L, 10L, False, + AnyPropertyType, &type, &format, &length, &after, &data); + + /* + * xprop -notype -root _MOTIF_WM_INFO + * _MOTIF_WM_INFO = 0x2, 0x580028 + */ + + if (length < 2 || format != 32 || after != 0) { + return; } - } - /* set up format from scratch: */ - screen->paddedWidthInBytes = rfb_bytes_per_line; - screen->serverFormat.bitsPerPixel = fb->bits_per_pixel; - screen->serverFormat.depth = fb->depth; - screen->serverFormat.trueColour = TRUE; + dp = (unsigned int *) data; + wm_win = (Window) *(dp+1); /* 2nd item. */ - screen->serverFormat.redShift = 0; - screen->serverFormat.greenShift = 0; - screen->serverFormat.blueShift = 0; - screen->serverFormat.redMax = 0; - screen->serverFormat.greenMax = 0; - screen->serverFormat.blueMax = 0; - /* these main_* formats are used generally. */ - main_red_shift = 0; - main_green_shift = 0; - main_blue_shift = 0; - main_red_max = 0; - main_green_max = 0; - main_blue_max = 0; - main_red_mask = fb->red_mask; - main_green_mask = fb->green_mask; - main_blue_mask = fb->blue_mask; + dt_list = XInternAtom(dpy, "_DT_WORKSPACE_LIST", True); + if (dt_list == None) { + return; + } + XGetWindowProperty(dpy, wm_win, dt_list, 0L, 10L, False, + AnyPropertyType, &type, &format, &length, &after, &data); - have_masks = ((fb->red_mask|fb->green_mask|fb->blue_mask) != 0); - if (force_indexed_color) { - have_masks = 0; - } + nws = length; - if (! have_masks && screen->serverFormat.bitsPerPixel == 8 - && dpy && CellsOfScreen(ScreenOfDisplay(dpy, scr))) { - /* indexed color */ - if (!quiet) { - rfbLog("X display %s is 8bpp indexed color\n", - DisplayString(dpy)); - if (! flash_cmap && ! overlay) { - rfbLog("\n"); - rfbLog("In 8bpp PseudoColor mode if you " - "experience color\n"); - rfbLog("problems you may want to enable " - "following the\n"); - rfbLog("changing colormap by using the " - "-flashcmap option.\n"); - rfbLog("\n"); - } + if (nws > wsmax) { + nws = wsmax; } - screen->serverFormat.trueColour = FALSE; - indexed_color = 1; - set_colormap(1); - debug_colormap(fb); - } else { - /* - * general case, we call it truecolor, but could be direct - * color, static color, etc.... - */ - if (! quiet) { - if (raw_fb) { - rfbLog("Raw fb at addr %p is %dbpp depth=%d " - "true color\n", raw_fb_addr, - fb->bits_per_pixel, fb->depth); - } else { - rfbLog("X display %s is %dbpp depth=%d true " - "color\n", DisplayString(dpy), - fb->bits_per_pixel, fb->depth); - } + if (nws < 0) { + nws = 0; } - indexed_color = 0; - - /* convert masks to bit shifts and max # colors */ - if (fb->red_mask) { - while (! (fb->red_mask - & (1 << screen->serverFormat.redShift))) { - screen->serverFormat.redShift++; - } + rfbLog("special CDE win: 0x%lx, %d workspaces\n", wm_win, nws); + if (nws == 0) { + return; } - if (fb->green_mask) { - while (! (fb->green_mask - & (1 << screen->serverFormat.greenShift))) { - screen->serverFormat.greenShift++; + + for (n=0; nblue_mask) { - while (! (fb->blue_mask - & (1 << screen->serverFormat.blueShift))) { - screen->serverFormat.blueShift++; + XGetWindowProperty(dpy, wm_win, ws_atom, 0L, 100L, + False, AnyPropertyType, &type, &format, &length, + &after, &data); + + if (format != 8 || after != 0) { + continue; } - } - screen->serverFormat.redMax - = fb->red_mask >> screen->serverFormat.redShift; - screen->serverFormat.greenMax - = fb->green_mask >> screen->serverFormat.greenShift; - screen->serverFormat.blueMax - = fb->blue_mask >> screen->serverFormat.blueShift; + /* + * xprop -notype -id wm_win + * _DT_WORKSPACE_INFO_ws0 = "One", "3", "0x2f2f4a", + * "0x63639c", "0x103", "1", "0x58044e" + */ - main_red_max = screen->serverFormat.redMax; - main_green_max = screen->serverFormat.greenMax; - main_blue_max = screen->serverFormat.blueMax; + cnt = 0; + twin = 0x0; + for (i=0; iserverFormat.redShift; - main_green_shift = screen->serverFormat.greenShift; - main_blue_shift = screen->serverFormat.blueShift; - } + if (! twin) { + twin = rootwin; + } -#if !SMALL_FOOTPRINT - if (!quiet) { - fprintf(stderr, "\n"); - fprintf(stderr, "FrameBuffer Info:\n"); - fprintf(stderr, " width: %d\n", fb->width); - fprintf(stderr, " height: %d\n", fb->height); - fprintf(stderr, " scaled_width: %d\n", width); - fprintf(stderr, " scaled_height: %d\n", height); - fprintf(stderr, " indexed_color: %d\n", indexed_color); - fprintf(stderr, " bits_per_pixel: %d\n", fb->bits_per_pixel); - fprintf(stderr, " depth: %d\n", fb->depth); - fprintf(stderr, " red_mask: 0x%08lx %s\n", fb->red_mask, - bitprint(fb->red_mask, 32)); - fprintf(stderr, " green_mask: 0x%08lx %s\n", fb->green_mask, - bitprint(fb->green_mask, 32)); - fprintf(stderr, " blue_mask: 0x%08lx %s\n", fb->blue_mask, - bitprint(fb->blue_mask, 32)); - fprintf(stderr, " red: max: %3d shift: %2d\n", - main_red_max, main_red_shift); - fprintf(stderr, " green: max: %3d shift: %2d\n", - main_green_max, main_green_shift); - fprintf(stderr, " blue: max: %3d shift: %2d\n", - main_blue_max, main_blue_shift); - fprintf(stderr, " mainfb_bytes_per_line: %d\n", - main_bytes_per_line); - fprintf(stderr, " rfb_fb_bytes_per_line: %d\n", - rfb_bytes_per_line); - switch(fb->format) { - case XYBitmap: - fprintf(stderr, " format: XYBitmap\n"); break; - case XYPixmap: - fprintf(stderr, " format: XYPixmap\n"); break; - case ZPixmap: - fprintf(stderr, " format: ZPixmap\n"); break; - default: - fprintf(stderr, " format: %d\n", fb->format); break; - } - switch(fb->byte_order) { - case LSBFirst: - fprintf(stderr, " byte_order: LSBFirst\n"); break; - case MSBFirst: - fprintf(stderr, " byte_order: MSBFirst\n"); break; - default: - fprintf(stderr, " byte_order: %d\n", fb->byte_order); - break; - } - fprintf(stderr, " bitmap_pad: %d\n", fb->bitmap_pad); - fprintf(stderr, " bitmap_unit: %d\n", fb->bitmap_unit); - switch(fb->bitmap_bit_order) { - case LSBFirst: - fprintf(stderr, " bitmap_bit_order: LSBFirst\n"); break; - case MSBFirst: - fprintf(stderr, " bitmap_bit_order: MSBFirst\n"); break; - default: - fprintf(stderr, " bitmap_bit_order: %d\n", - fb->bitmap_bit_order); break; + XGetWindowAttributes(dpy, twin, &attr); + if (twin != rootwin) { + if (attr.map_state != IsViewable) { + XMapWindow(dpy, twin); + } + XRaiseWindow(dpy, twin); + } + XSync(dpy, False); + + /* create image window: */ + iswa.override_redirect = True; + iswa.backing_store = NotUseful; + iswa.save_under = False; + iswa.background_pixmap = ParentRelative; + visual.visualid = CopyFromParent; + + iwin = XCreateWindow(dpy, twin, 0, 0, wdpy_x, wdpy_y, + 0, depth, InputOutput, &visual, mask, &iswa); + + rfbLog("snapshotting CDE backdrop ws%d 0x%lx -> " + "0x%lx ...\n", n, twin, iwin); + XMapWindow(dpy, iwin); + XSync(dpy, False); + + image[n] = XGetImage(dpy, iwin, 0, 0, wdpy_x, wdpy_y, + AllPlanes, ZPixmap); + XSync(dpy, False); + XDestroyWindow(dpy, iwin); + if (twin != rootwin) { + XLowerWindow(dpy, twin); + if (attr.map_state != IsViewable) { + XUnmapWindow(dpy, twin); + } + } } } - if (overlay && ! quiet) { - rfbLog("\n"); - rfbLog("Overlay mode enabled: If you experience color\n"); - rfbLog("problems when popup menus are on the screen, try\n"); - rfbLog("disabling SaveUnders in your X server, one way is\n"); - rfbLog("to start the X server with the '-su' option, e.g.:\n"); - rfbLog("Xsun -su ... see Xserver(1), xinit(1) for more info.\n"); - rfbLog("\n"); + if (nws == 0) { + return; } -#endif - /* nofb is for pointer/keyboard only handling. */ - if (nofb) { - main_fb = NULL; - rfb_fb = main_fb; - screen->displayHook = nofb_hook; - } else { - main_fb = fb->data; - if (scaling) { - rfb_fb = (char *) malloc(rfb_bytes_per_line * height); - memset(rfb_fb, 0, rfb_bytes_per_line * height); + + /* use black for low colors or failure */ + pixel = BlackPixel(dpy, scr); + if (depth > 8 || strcmp(color, solid_default)) { + cmap = DefaultColormap (dpy, scr); + if (XParseColor(dpy, cmap, color, &cdef) && + XAllocColor(dpy, cmap, &cdef)) { + pixel = cdef.pixel; } else { - rfb_fb = main_fb; + rfbLog("error parsing/allocing color: %s\n", color); } } - screen->frameBuffer = rfb_fb; - if (!quiet) { - fprintf(stderr, " main_fb: %p\n", main_fb); - fprintf(stderr, " rfb_fb: %p\n", rfb_fb); - fprintf(stderr, "\n"); - } - - bpp = screen->serverFormat.bitsPerPixel; - depth = screen->serverFormat.depth; - - /* may need, bpp, main_red_max, etc. */ - parse_wireframe(); - setup_cursors_and_push(); + rfbLog("setting solid backgrounds...\n"); - if (scaling) { - mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0); + for (n=0; n < nws; n++) { + Window twin = ws_wins[n]; + if (image[n] == NULL) { + continue; + } + if (! twin) { + twin = rootwin; + } + XSetWindowBackground(dpy, twin, pixel); } + XMapWindow(dpy, expose); + XSync(dpy, False); + XDestroyWindow(dpy, expose); +} - if (! create_screen) { - rfbClientIteratorPtr iter; - rfbClientPtr cl; +void solid_gnome(char *color) { + char get_color[] = "gconftool-2 --get " + "/desktop/gnome/background/primary_color"; + char set_color[] = "gconftool-2 --set " + "/desktop/gnome/background/primary_color --type string '%s'"; + char get_option[] = "gconftool-2 --get " + "/desktop/gnome/background/picture_options"; + char set_option[] = "gconftool-2 --set " + "/desktop/gnome/background/picture_options --type string '%s'"; + static char *orig_color = NULL; + static char *orig_option = NULL; + char *cmd; + + if (! color) { + if (! orig_color) { + orig_color = strdup("#FFFFFF"); + } + if (! orig_option) { + orig_option = strdup("stretched"); + } + if (strstr(orig_color, "'") != NULL) { + rfbLog("bad color: %s\n", orig_color); + return; + } + if (strstr(orig_option, "'") != NULL) { + rfbLog("bad option: %s\n", orig_option); + return; + } + cmd = (char *)malloc(strlen(set_option) - 2 + + strlen(orig_option) + 1); + sprintf(cmd, set_option, orig_option); + dt_cmd(cmd); + free(cmd); + cmd = (char *)malloc(strlen(set_color) - 2 + + strlen(orig_color) + 1); + sprintf(cmd, set_color, orig_color); + dt_cmd(cmd); + free(cmd); + return; + } - /* - * since bits_per_color above may have been approximate, - * try to reset the individual translation tables... - * we do not seem to need this with rfbGetScreen()... - */ - if (!quiet) rfbLog("calling setTranslateFunction()...\n"); - iter = rfbGetClientIterator(screen); - while ((cl = rfbClientIteratorNext(iter)) != NULL) { - screen->setTranslateFunction(cl); + if (! orig_color) { + char *q; + orig_color = strdup(cmd_output(get_color)); + if (*orig_color == '\0') { + orig_color = strdup("#FFFFFF"); } - rfbReleaseClientIterator(iter); - if (!quiet) rfbLog(" done.\n"); - do_copy_screen = 1; - - /* done for framebuffer change case */ + if ((q = strchr(orig_color, '\n')) != NULL) { + *q = '\0'; + } + } + if (! orig_option) { + char *q; + orig_option = strdup(cmd_output(get_option)); + if (*orig_option == '\0') { + orig_option = strdup("stretched"); + } + if ((q = strchr(orig_option, '\n')) != NULL) { + *q = '\0'; + } + } + if (strstr(color, "'") != NULL) { + rfbLog("bad color: %s\n", color); return; } + cmd = (char *)malloc(strlen(set_color) + strlen(color) + 1); + sprintf(cmd, set_color, color); + dt_cmd(cmd); + free(cmd); - /* - * the rest is screen server initialization, etc, only needed - * at screen creation time. - */ + cmd = (char *)malloc(strlen(set_option) + strlen("none") + 1); + sprintf(cmd, set_option, "none"); + dt_cmd(cmd); + free(cmd); +} - /* called from inetd, we need to treat stdio as our socket */ - if (inetd) { - int fd = dup(0); - if (fd < 3) { - rfbErr("dup(0) = %d failed.\n", fd); - rfbLogPerror("dup"); - clean_up_exit(1); - } - fclose(stdin); - fclose(stdout); - /* we keep stderr for logging */ - screen->inetdSock = fd; - screen->port = 0; +void solid_kde(char *color) { + char set_color[] = + "dcop --user '%s' kdesktop KBackgroundIface setColor '%s' 1"; + char bg_off[] = + "dcop --user '%s' kdesktop KBackgroundIface setBackgroundEnabled 0"; + char bg_on[] = + "dcop --user '%s' kdesktop KBackgroundIface setBackgroundEnabled 1"; + char *cmd, *user = NULL; + int len; - } else if (! got_rfbport) { - screen->autoPort = TRUE; + user = get_user_name(); + if (strstr(user, "'") != NULL) { + rfbLog("bad user: %s\n", user); + free(user); + return; } - if (! got_nevershared && ! got_alwaysshared) { - if (shared) { - screen->alwaysShared = TRUE; - } else { - screen->dontDisconnect = TRUE; - screen->neverShared = TRUE; - } + if (! color) { + len = strlen(bg_on) + strlen(user) + 1; + cmd = (char *)malloc(len); + sprintf(cmd, bg_on, user); + dt_cmd(cmd); + free(cmd); + free(user); + + return; } - /* XXX the following is based on libvncserver defaults. */ - if (screen->deferUpdateTime == 5) { - /* XXX will be fixed someday */ - screen->deferUpdateTime = defer_update; + + if (strstr(color, "'") != NULL) { + rfbLog("bad color: %s\n", color); + return; } - /* event callbacks: */ - screen->newClientHook = new_client; - screen->kbdAddEvent = keyboard; - screen->ptrAddEvent = pointer; - screen->setXCutText = xcut_receive; + len = strlen(set_color) + strlen(user) + strlen(color) + 1; + cmd = (char *)malloc(len); + sprintf(cmd, set_color, user, color); + dt_cmd(cmd); + free(cmd); - rfbInitServer(screen); + len = strlen(bg_off) + strlen(user) + 1; + cmd = (char *)malloc(len); + sprintf(cmd, bg_off, user); + dt_cmd(cmd); + free(cmd); + free(user); +} - if (viewonly_passwd) { - /* append the view only passwd after the normal passwd */ - char **passwds_new = malloc(3*sizeof(char**)); - char **passwds_old = (char **) screen->authPasswdData; - passwds_new[0] = passwds_old[0]; - passwds_new[1] = viewonly_passwd; - passwds_new[2] = NULL; - screen->authPasswdData = (void*) passwds_new; +char *guess_desktop() { + Atom prop; + prop = XInternAtom(dpy, "XFCE_DESKTOP_WINDOW", True); + if (prop != None) { + return "xfce"; + } + prop = XInternAtom(dpy, "_QT_DESKTOP_PROPERTIES", True); + if (prop != None) { + return "kde"; + } + prop = XInternAtom(dpy, "NAUTILUS_DESKTOP_WINDOW_ID", True); + if (prop != None) { + return "gnome"; + } + prop = XInternAtom(dpy, "_MOTIF_WM_INFO", True); + if (prop != None) { + prop = XInternAtom(dpy, "_DT_WORKSPACE_LIST", True); + if (prop != None) { + return "cde"; + } } + return "root"; } -/* -- solid.c -- */ +void solid_bg(int restore) { + static int desktop = -1; + static int solid_on = 0; + static char *prev_str; + char *dtname, *color; -void usr_bin_path(int restore) { - static char *oldpath = NULL; - char *newpath; - char addpath[] = "/usr/bin:/bin:"; + if (started_as_root == 1 && users_list) { + /* we are still root, don't try. */ + return; + } if (restore) { - if (oldpath) { - set_env("PATH", oldpath); - free(oldpath); - oldpath = NULL; + if (! solid_on) { + return; + } + if (desktop == 0) { + solid_root(NULL); + } else if (desktop == 1) { + solid_gnome(NULL); + } else if (desktop == 2) { + solid_kde(NULL); + } else if (desktop == 3) { + solid_cde(NULL); } + solid_on = 0; return; } - - if (getenv("PATH")) { - oldpath = strdup(getenv("PATH")); + if (! solid_str) { + return; + } + if (solid_on && !strcmp(prev_str, solid_str)) { + return; + } + if (strstr(solid_str, "guess:") == solid_str + || !strchr(solid_str, ':')) { + dtname = guess_desktop(); + rfbLog("guessed desktop: %s\n", dtname); } else { - oldpath = strdup("/usr/bin"); + if (strstr(solid_str, "gnome:") == solid_str) { + dtname = "gnome"; + } else if (strstr(solid_str, "kde:") == solid_str) { + dtname = "kde"; + } else if (strstr(solid_str, "cde:") == solid_str) { + dtname = "cde"; + } else { + dtname = "root"; + } } - newpath = (char *) malloc(strlen(oldpath) + strlen(addpath) + 1); - newpath[0] = '\0'; - strcat(newpath, addpath); - strcat(newpath, oldpath); - set_env("PATH", newpath); - free(newpath); -} -int dt_cmd(char *cmd) { - int rc; - - if (!cmd || *cmd == '\0') { - return 0; + color = strchr(solid_str, ':'); + if (! color) { + color = solid_str; + } else { + color++; + if (*color == '\0') { + color = solid_default; + } } - - if (no_external_cmds) { - rfbLog("cannot run external commands in -nocmds mode:\n"); - rfbLog(" \"%s\"\n", cmd); - rfbLog(" dt_cmd: returning 1\n"); - return 1; + if (!strcmp(dtname, "gnome")) { + desktop = 1; + solid_gnome(color); + } else if (!strcmp(dtname, "kde")) { + desktop = 2; + solid_kde(color); + } else if (!strcmp(dtname, "cde")) { + desktop = 3; + solid_cde(color); + } else { + desktop = 0; + solid_root(color); } - - rfbLog("running command:\n %s\n", cmd); - usr_bin_path(0); - rc = system(cmd); - usr_bin_path(1); - - if (rc >= 256) { - rc = rc/256; + if (prev_str) { + free(prev_str); } - return rc; + prev_str = strdup(solid_str); + solid_on = 1; } -char *cmd_output(char *cmd) { - FILE *p; - static char output[50000]; - char line[1024]; - int rc; +/* -- xinerama.c -- */ +/* + * routines related to xinerama and blacking out rectangles + */ - if (!cmd || *cmd == '\0') { - return ""; - } +/* blacked-out region (-blackout, -xinerama) */ +typedef struct bout { + int x1, y1, x2, y2; +} blackout_t; +#define BO_MAX 16 +typedef struct tbout { + blackout_t bo[BO_MAX]; /* hardwired max rectangles. */ + int cover; + int count; +} tile_blackout_t; - if (no_external_cmds) { - rfbLog("cannot run external commands in -nocmds mode:\n"); - rfbLog(" \"%s\"\n", cmd); - rfbLog(" cmd_output: null string.\n"); - return ""; - } +#define BLACKR_MAX 100 +blackout_t blackr[BLACKR_MAX]; /* hardwired max blackouts */ +tile_blackout_t *tile_blackout; +int blackouts = 0; - rfbLog("running pipe:\n %s\n", cmd); - usr_bin_path(0); - p = popen(cmd, "r"); - usr_bin_path(1); +/* + * Take a comma separated list of geometries: WxH+X+Y and register them as + * rectangles to black out from the screen. + */ +void initialize_blackouts(char *list) { + char *p, *blist = strdup(list); + int x, y, X, Y, h, w, t; - output[0] = '\0'; + blackouts = 0; - while (fgets(line, 1024, p) != NULL) { - if (strlen(output) + strlen(line) + 1 < 50000) { - strcat(output, line); + p = strtok(blist, ", \t"); + while (p) { + if (! parse_geom(p, &w, &h, &x, &y, dpy_x, dpy_y)) { + if (*p != '\0') { + rfbLog("skipping invalid geometry: %s\n", p); + } + p = strtok(NULL, ", \t"); + continue; + } + w = nabs(w); + h = nabs(h); + x = nfix(x, dpy_x); + y = nfix(y, dpy_y); + X = x + w; + Y = y + h; + X = nfix(X, dpy_x); + Y = nfix(Y, dpy_y); + if (x > X) { + t = X; X = x; x = t; + } + if (y > Y) { + t = Y; Y = y; y = t; + } + if (x < 0 || x > dpy_x || y < 0 || y > dpy_y || + X < 0 || X > dpy_x || Y < 0 || Y > dpy_y || + x == X || y == Y) { + rfbLog("skipping invalid blackout geometry: %s x=" + "%d-%d,y=%d-%d,w=%d,h=%d\n", p, x, X, y, Y, w, h); + } else { + rfbLog("blackout rect: %s: x=%d-%d y=%d-%d\n", p, + x, X, y, Y); + + /* + * note that the black out is x1 <= x but x < x2 + * for the region. i.e. the x2, y2 are outside + * by 1 pixel. + */ + blackr[blackouts].x1 = x; + blackr[blackouts].y1 = y; + blackr[blackouts].x2 = X; + blackr[blackouts].y2 = Y; + blackouts++; + if (blackouts >= BLACKR_MAX) { + rfbLog("too many blackouts: %d\n", blackouts); + break; + } } + p = strtok(NULL, ", \t"); } - rc = pclose(p); - return(output); + free(blist); } -void solid_root(char *color) { - Window expose; - static XImage *image = NULL; - Pixmap pixmap; - XGCValues gcv; - GC gc; - XSetWindowAttributes swa; - Visual visual; - unsigned long mask, pixel; - XColor cdef; - Colormap cmap; - - if (subwin || window != rootwin) { - rfbLog("cannot set subwin to solid color, must be rootwin\n"); +/* + * Now that all blackout rectangles have been constructed, see what overlap + * they have with the tiles in the system. If a tile is touched by a + * blackout, record information. + */ +void blackout_tiles(void) { + int tx, ty; + int debug_bo = 0; + if (! blackouts) { return; } + if (getenv("DEBUG_BLACKOUT") != NULL) { + debug_bo = 1; + } + + /* + * to simplify things drop down to single copy mode, no vcr, etc... + */ + single_copytile = 1; + + /* loop over all tiles. */ + for (ty=0; ty < ntiles_y; ty++) { + for (tx=0; tx < ntiles_x; tx++) { + sraRegionPtr tile_reg, black_reg; + sraRect rect; + sraRectangleIterator *iter; + int n, b, x1, y1, x2, y2, cnt; - /* create the "clear" window just for generating exposures */ - swa.override_redirect = True; - swa.backing_store = NotUseful; - swa.save_under = False; - swa.background_pixmap = None; - visual.visualid = CopyFromParent; - mask = (CWOverrideRedirect|CWBackingStore|CWSaveUnder|CWBackPixmap); - expose = XCreateWindow(dpy, window, 0, 0, wdpy_x, wdpy_y, 0, depth, - InputOutput, &visual, mask, &swa); + /* tile number and coordinates: */ + n = tx + ty * ntiles_x; + x1 = tx * tile_x; + y1 = ty * tile_y; + x2 = x1 + tile_x; + y2 = y1 + tile_y; + if (x2 > dpy_x) { + x2 = dpy_x; + } + if (y2 > dpy_y) { + y2 = dpy_y; + } - if (! color) { - /* restore the root window from the XImage snapshot */ - pixmap = XCreatePixmap(dpy, window, wdpy_x, wdpy_y, depth); + /* make regions for the tile and the blackouts: */ + black_reg = (sraRegionPtr) sraRgnCreate(); + tile_reg = (sraRegionPtr) sraRgnCreateRect(x1, y1, + x2, y2); - if (! image) { - /* whoops */ - XDestroyWindow(dpy, expose); - rfbLog("no root snapshot available.\n"); - return; - } + tile_blackout[n].cover = 0; + tile_blackout[n].count = 0; - - /* draw the image to a pixmap: */ - gcv.function = GXcopy; - gcv.plane_mask = AllPlanes; - gc = XCreateGC(dpy, window, GCFunction|GCPlaneMask, &gcv); + /* union of blackouts */ + for (b=0; b < blackouts; b++) { + sraRegionPtr tmp_reg = (sraRegionPtr) + sraRgnCreateRect(blackr[b].x1, + blackr[b].y1, blackr[b].x2, blackr[b].y2); - XPutImage(dpy, pixmap, gc, image, 0, 0, 0, 0, wdpy_x, wdpy_y); + sraRgnOr(black_reg, tmp_reg); + sraRgnDestroy(tmp_reg); + } - gcv.foreground = gcv.background = BlackPixel(dpy, scr); - gc = XCreateGC(dpy, window, GCForeground|GCBackground, &gcv); + if (! sraRgnAnd(black_reg, tile_reg)) { + /* + * no intersection for this tile, so we + * are done. + */ + sraRgnDestroy(black_reg); + sraRgnDestroy(tile_reg); + continue; + } - rfbLog("restoring root snapshot...\n"); - /* set the pixmap as the bg: */ - XSetWindowBackgroundPixmap(dpy, window, pixmap); - XFreePixmap(dpy, pixmap); - XClearWindow(dpy, window); - XFlush(dpy); - - /* generate exposures */ - XMapWindow(dpy, expose); - XSync(dpy, False); - XDestroyWindow(dpy, expose); - return; - } + /* + * loop over rectangles that make up the blackout + * region: + */ + cnt = 0; + iter = sraRgnGetIterator(black_reg); + while (sraRgnIteratorNext(iter, &rect)) { - if (! image) { - /* need to retrieve a snapshot of the root background: */ - Window iwin; - XSetWindowAttributes iswa; + /* make sure x1 < x2 and y1 < y2 */ + if (rect.x1 > rect.x2) { + int tmp = rect.x2; + rect.x2 = rect.x1; + rect.x1 = tmp; + } + if (rect.y1 > rect.y2) { + int tmp = rect.y2; + rect.y2 = rect.y1; + rect.y1 = tmp; + } - /* create image window: */ - iswa.override_redirect = True; - iswa.backing_store = NotUseful; - iswa.save_under = False; - iswa.background_pixmap = ParentRelative; + /* store coordinates */ + tile_blackout[n].bo[cnt].x1 = rect.x1; + tile_blackout[n].bo[cnt].y1 = rect.y1; + tile_blackout[n].bo[cnt].x2 = rect.x2; + tile_blackout[n].bo[cnt].y2 = rect.y2; - iwin = XCreateWindow(dpy, window, 0, 0, wdpy_x, wdpy_y, 0, - depth, InputOutput, &visual, mask, &iswa); + /* note if the tile is completely obscured */ + if (rect.x1 == x1 && rect.y1 == y1 && + rect.x2 == x2 && rect.y2 == y2) { + tile_blackout[n].cover = 2; + if (debug_bo) { + fprintf(stderr, "full: %d=%d,%d" + " (%d-%d) (%d-%d)\n", + n, tx, ty, x1, x2, y1, y2); + } + } else { + tile_blackout[n].cover = 1; + if (debug_bo) { + fprintf(stderr, "part: %d=%d,%d" + " (%d-%d) (%d-%d)\n", + n, tx, ty, x1, x2, y1, y2); + } + } - rfbLog("snapshotting background...\n"); + if (++cnt >= BO_MAX) { + rfbLog("too many blackout rectangles " + "for tile %d=%d,%d.\n", n, tx, ty); + break; + } + } - XMapWindow(dpy, iwin); - XSync(dpy, False); - image = XGetImage(dpy, iwin, 0, 0, wdpy_x, wdpy_y, AllPlanes, - ZPixmap); - XSync(dpy, False); - XDestroyWindow(dpy, iwin); - } + sraRgnReleaseIterator(iter); + sraRgnDestroy(black_reg); + sraRgnDestroy(tile_reg); - /* use black for low colors or failure */ - pixel = BlackPixel(dpy, scr); - if (depth > 8 || strcmp(color, solid_default)) { - cmap = DefaultColormap (dpy, scr); - if (XParseColor(dpy, cmap, color, &cdef) && - XAllocColor(dpy, cmap, &cdef)) { - pixel = cdef.pixel; - } else { - rfbLog("error parsing/allocing color: %s\n", color); + tile_blackout[n].count = cnt; + if (debug_bo && cnt > 1) { + rfbLog("warning: multiple region overlaps[%d] " + "for tile %d=%d,%d.\n", cnt, n, tx, ty); + } } } - - rfbLog("setting solid background...\n"); - XSetWindowBackground(dpy, window, pixel); - XMapWindow(dpy, expose); - XSync(dpy, False); - XDestroyWindow(dpy, expose); } -void solid_cde(char *color) { - int wsmax = 16; - static XImage *image[16]; - static Window ws_wins[16]; - static int nws = -1; +void initialize_xinerama (void) { +#if !LIBVNCSERVER_HAVE_LIBXINERAMA + rfbLog("Xinerama: Library libXinerama is not available to determine\n"); + rfbLog("Xinerama: the head geometries, consider using -blackout\n"); + rfbLog("Xinerama: if the screen is non-rectangular.\n"); +#else + XineramaScreenInfo *sc, *xineramas; + sraRegionPtr black_region, tmp_region; + sraRectangleIterator *iter; + sraRect rect; + char *bstr, *tstr; + int ev, er, i, n, rcnt; - Window expose; - Pixmap pixmap; - XGCValues gcv; - GC gc; - XSetWindowAttributes swa; - Visual visual; - unsigned long mask, pixel; - XColor cdef; - Colormap cmap; - int n; + if (raw_fb && ! dpy) return; /* raw_fb hack */ - if (subwin || window != rootwin) { - rfbLog("cannot set subwin to solid color, must be rootwin\n"); + if (! XineramaQueryExtension(dpy, &ev, &er)) { + rfbLog("Xinerama: disabling: display does not support it.\n"); + xinerama = 0; + xinerama_present = 0; return; } + if (! XineramaIsActive(dpy)) { + /* n.b. change to XineramaActive(dpy, window) someday */ + rfbLog("Xinerama: disabling: not active on display.\n"); + xinerama = 0; + xinerama_present = 0; + return; + } + xinerama_present = 1; - /* create the "clear" window just for generating exposures */ - swa.override_redirect = True; - swa.backing_store = NotUseful; - swa.save_under = False; - swa.background_pixmap = None; - visual.visualid = CopyFromParent; - mask = (CWOverrideRedirect|CWBackingStore|CWSaveUnder|CWBackPixmap); - expose = XCreateWindow(dpy, window, 0, 0, wdpy_x, wdpy_y, 0, depth, - InputOutput, &visual, mask, &swa); + /* n.b. change to XineramaGetData() someday */ + xineramas = XineramaQueryScreens(dpy, &n); + rfbLog("Xinerama: number of sub-screens: %d\n", n); - if (! color) { - /* restore the backdrop windows from the XImage snapshots */ + if (n == 1) { + rfbLog("Xinerama: no blackouts needed (only one" + " sub-screen)\n"); + XFree(xineramas); + return; /* must be OK w/o change */ + } - for (n=0; n < nws; n++) { - Window twin; + black_region = sraRgnCreateRect(0, 0, dpy_x, dpy_y); + + sc = xineramas; + for (i=0; ix_org; + y = sc->y_org; + w = sc->width; + h = sc->height; + + tmp_region = sraRgnCreateRect(x, y, x + w, y + h); + + sraRgnSubtract(black_region, tmp_region); + sraRgnDestroy(tmp_region); + sc++; + } + XFree(xineramas); + + if (sraRgnEmpty(black_region)) { + rfbLog("Xinerama: no blackouts needed (screen fills" + " rectangle)\n"); + sraRgnDestroy(black_region); + return; + } - if (! image[n]) { - continue; - } + /* max len is 10000x10000+10000+10000 (23 chars) per geometry */ + rcnt = (int) sraRgnCountRects(black_region); + bstr = (char *) malloc(30 * (rcnt+1) * sizeof(char)); + tstr = (char *) malloc(30 * sizeof(char)); + bstr[0] = '\0'; - twin = ws_wins[n]; - if (! twin) { - twin = rootwin; - } - if (! valid_window(twin, NULL)) { - continue; - } + iter = sraRgnGetIterator(black_region); + while (sraRgnIteratorNext(iter, &rect)) { + int x, y, w, h; - pixmap = XCreatePixmap(dpy, twin, wdpy_x, wdpy_y, - depth); - - /* draw the image to a pixmap: */ - gcv.function = GXcopy; - gcv.plane_mask = AllPlanes; - gc = XCreateGC(dpy, twin, GCFunction|GCPlaneMask, &gcv); + /* make sure x1 < x2 and y1 < y2 */ + if (rect.x1 > rect.x2) { + int tmp = rect.x2; + rect.x2 = rect.x1; + rect.x1 = tmp; + } + if (rect.y1 > rect.y2) { + int tmp = rect.y2; + rect.y2 = rect.y1; + rect.y1 = tmp; + } + x = rect.x1; + y = rect.y1; + w = rect.x2 - x; + h = rect.y2 - y; + sprintf(tstr, "%dx%d+%d+%d,", w, h, x, y); + strcat(bstr, tstr); + } + initialize_blackouts(bstr); - XPutImage(dpy, pixmap, gc, image[n], 0, 0, 0, 0, - wdpy_x, wdpy_y); + free(bstr); + free(tstr); +#endif +} - gcv.foreground = gcv.background = BlackPixel(dpy, scr); - gc = XCreateGC(dpy, twin, GCForeground|GCBackground, - &gcv); +void initialize_blackouts_and_xinerama(void) { + if (blackout_str != NULL) { + initialize_blackouts(blackout_str); + } + if (xinerama) { + initialize_xinerama(); + } + if (blackouts) { + blackout_tiles(); + /* schedule a copy_screen(), now is too early. */ + do_copy_screen = 1; + } +} - rfbLog("restoring CDE ws%d snapshot to 0x%lx\n", - n, twin); - /* set the pixmap as the bg: */ - XSetWindowBackgroundPixmap(dpy, twin, pixmap); - XFreePixmap(dpy, pixmap); - XClearWindow(dpy, twin); - XFlush(dpy); +void push_sleep(n) { + int i; + for (i=0; i dpy_x) { + return; + } + if (y1 < 0 || y2 <= y1 || y2 > dpy_y) { + return; + } + if (! main_fb) { + return; + } - /* extract the hidden wm properties about backdrops: */ + dst = main_fb + y1 * main_bytes_per_line + x1 * pixelsize; + line = y1; + while (line++ < y2) { + memset(dst, fill, (size_t) (x2 - x1) * pixelsize); + dst += main_bytes_per_line; + } +} - wm_info = XInternAtom(dpy, "_MOTIF_WM_INFO", True); - if (wm_info == None) { - return; - } +/* -- scan.c -- */ +/* + * routines for scanning and reading the X11 display for changes, and + * for doing all the tile work (shm, etc). + */ - XGetWindowProperty(dpy, rootwin, wm_info, 0L, 10L, False, - AnyPropertyType, &type, &format, &length, &after, &data); +/* array to hold the hints: */ +static hint_t *hint_list; - /* - * xprop -notype -root _MOTIF_WM_INFO - * _MOTIF_WM_INFO = 0x2, 0x580028 - */ +/* nap state */ +static int nap_ok = 0, nap_diff_count = 0; - if (length < 2 || format != 32 || after != 0) { - return; - } +static int scan_count = 0; /* indicates which scan pattern we are on */ +static int scan_in_progress = 0; - dp = (unsigned int *) data; - wm_win = (Window) *(dp+1); /* 2nd item. */ +typedef struct tile_change_region { + /* start and end lines, along y, of the changed area inside a tile. */ + unsigned short first_line, last_line; + /* info about differences along edges. */ + unsigned short left_diff, right_diff; + unsigned short top_diff, bot_diff; +} region_t; +/* array to hold the tiles region_t-s. */ +static region_t *tile_region; - dt_list = XInternAtom(dpy, "_DT_WORKSPACE_LIST", True); - if (dt_list == None) { - return; - } - XGetWindowProperty(dpy, wm_win, dt_list, 0L, 10L, False, - AnyPropertyType, &type, &format, &length, &after, &data); +/* + * setup tile numbers and allocate the tile and hint arrays: + */ +void initialize_tiles(void) { - nws = length; + ntiles_x = (dpy_x - 1)/tile_x + 1; + ntiles_y = (dpy_y - 1)/tile_y + 1; + ntiles = ntiles_x * ntiles_y; - if (nws > wsmax) { - nws = wsmax; - } - if (nws < 0) { - nws = 0; - } + tile_has_diff = (unsigned char *) + malloc((size_t) (ntiles * sizeof(unsigned char))); + tile_has_xdamage_diff = (unsigned char *) + malloc((size_t) (ntiles * sizeof(unsigned char))); + tile_row_has_xdamage_diff = (unsigned char *) + malloc((size_t) (ntiles_y * sizeof(unsigned char))); + tile_tried = (unsigned char *) + malloc((size_t) (ntiles * sizeof(unsigned char))); + tile_copied = (unsigned char *) + malloc((size_t) (ntiles * sizeof(unsigned char))); + tile_blackout = (tile_blackout_t *) + malloc((size_t) (ntiles * sizeof(tile_blackout_t))); + tile_region = (region_t *) malloc((size_t) (ntiles * sizeof(region_t))); - rfbLog("special CDE win: 0x%lx, %d workspaces\n", wm_win, nws); - if (nws == 0) { - return; - } + tile_row = (XImage **) + malloc((size_t) ((ntiles_x + 1) * sizeof(XImage *))); + tile_row_shm = (XShmSegmentInfo *) + malloc((size_t) ((ntiles_x + 1) * sizeof(XShmSegmentInfo))); - for (n=0; nbyte_order == LSBFirst) { + order = "MSBFirst"; + xim->byte_order = MSBFirst; + xim->bitmap_bit_order = MSBFirst; + } else { + order = "LSBFirst"; + xim->byte_order = LSBFirst; + xim->bitmap_bit_order = LSBFirst; + } + return order; +} - XGetWindowAttributes(dpy, twin, &attr); - if (twin != rootwin) { - if (attr.map_state != IsViewable) { - XMapWindow(dpy, twin); - } - XRaiseWindow(dpy, twin); - } - XSync(dpy, False); - - /* create image window: */ - iswa.override_redirect = True; - iswa.backing_store = NotUseful; - iswa.save_under = False; - iswa.background_pixmap = ParentRelative; - visual.visualid = CopyFromParent; +/* + * set up an XShm image, or if not using shm just create the XImage. + */ +static int shm_create(XShmSegmentInfo *shm, XImage **ximg_ptr, int w, int h, + char *name) { - iwin = XCreateWindow(dpy, twin, 0, 0, wdpy_x, wdpy_y, - 0, depth, InputOutput, &visual, mask, &iswa); + XImage *xim; + static int reported_flip = 0; - rfbLog("snapshotting CDE backdrop ws%d 0x%lx -> " - "0x%lx ...\n", n, twin, iwin); - XMapWindow(dpy, iwin); - XSync(dpy, False); + shm->shmid = -1; + shm->shmaddr = (char *) -1; + *ximg_ptr = NULL; - image[n] = XGetImage(dpy, iwin, 0, 0, wdpy_x, wdpy_y, - AllPlanes, ZPixmap); - XSync(dpy, False); - XDestroyWindow(dpy, iwin); - if (twin != rootwin) { - XLowerWindow(dpy, twin); - if (attr.map_state != IsViewable) { - XUnmapWindow(dpy, twin); - } - } - } - } - if (nws == 0) { - return; + if (nofb) { + return 1; } - /* use black for low colors or failure */ - pixel = BlackPixel(dpy, scr); - if (depth > 8 || strcmp(color, solid_default)) { - cmap = DefaultColormap (dpy, scr); - if (XParseColor(dpy, cmap, color, &cdef) && - XAllocColor(dpy, cmap, &cdef)) { - pixel = cdef.pixel; - } else { - rfbLog("error parsing/allocing color: %s\n", color); - } - } + X_LOCK; - rfbLog("setting solid backgrounds...\n"); + if (! using_shm) { + /* we only need the XImage created */ + xim = XCreateImage_wr(dpy, default_visual, depth, ZPixmap, + 0, NULL, w, h, raw_fb ? 32 : BitmapPad(dpy), 0); - for (n=0; n < nws; n++) { - Window twin = ws_wins[n]; - if (image[n] == NULL) { - continue; - } - if (! twin) { - twin = rootwin; - } - XSetWindowBackground(dpy, twin, pixel); - } - XMapWindow(dpy, expose); - XSync(dpy, False); - XDestroyWindow(dpy, expose); -} + X_UNLOCK; -void solid_gnome(char *color) { - char get_color[] = "gconftool-2 --get " - "/desktop/gnome/background/primary_color"; - char set_color[] = "gconftool-2 --set " - "/desktop/gnome/background/primary_color --type string '%s'"; - char get_option[] = "gconftool-2 --get " - "/desktop/gnome/background/picture_options"; - char set_option[] = "gconftool-2 --set " - "/desktop/gnome/background/picture_options --type string '%s'"; - static char *orig_color = NULL; - static char *orig_option = NULL; - char *cmd; - - if (! color) { - if (! orig_color) { - orig_color = strdup("#FFFFFF"); - } - if (! orig_option) { - orig_option = strdup("stretched"); + if (xim == NULL) { + rfbErr("XCreateImage(%s) failed.\n", name); + return 0; } - if (strstr(orig_color, "'") != NULL) { - rfbLog("bad color: %s\n", orig_color); - return; + xim->data = (char *) malloc(xim->bytes_per_line * xim->height); + if (xim->data == NULL) { + rfbErr("XCreateImage(%s) data malloc failed.\n", name); + return 0; } - if (strstr(orig_option, "'") != NULL) { - rfbLog("bad option: %s\n", orig_option); - return; + if (flip_byte_order) { + char *order = flip_ximage_byte_order(xim); + if (! reported_flip && ! quiet) { + rfbLog("Changing XImage byte order" + " to %s\n", order); + reported_flip = 1; + } } - cmd = (char *)malloc(strlen(set_option) - 2 + - strlen(orig_option) + 1); - sprintf(cmd, set_option, orig_option); - dt_cmd(cmd); - free(cmd); - cmd = (char *)malloc(strlen(set_color) - 2 + - strlen(orig_color) + 1); - sprintf(cmd, set_color, orig_color); - dt_cmd(cmd); - free(cmd); - return; - } - if (! orig_color) { - char *q; - orig_color = strdup(cmd_output(get_color)); - if (*orig_color == '\0') { - orig_color = strdup("#FFFFFF"); - } - if ((q = strchr(orig_color, '\n')) != NULL) { - *q = '\0'; - } + *ximg_ptr = xim; + return 1; } - if (! orig_option) { - char *q; - orig_option = strdup(cmd_output(get_option)); - if (*orig_option == '\0') { - orig_option = strdup("stretched"); - } - if ((q = strchr(orig_option, '\n')) != NULL) { - *q = '\0'; - } + + xim = XShmCreateImage_wr(dpy, default_visual, depth, ZPixmap, NULL, + shm, w, h); + + if (xim == NULL) { + rfbErr("XShmCreateImage(%s) failed.\n", name); + X_UNLOCK; + return 0; } - if (strstr(color, "'") != NULL) { - rfbLog("bad color: %s\n", color); - return; + + *ximg_ptr = xim; + +#if LIBVNCSERVER_HAVE_XSHM + shm->shmid = shmget(IPC_PRIVATE, + xim->bytes_per_line * xim->height, IPC_CREAT | 0777); + + if (shm->shmid == -1) { + rfbErr("shmget(%s) failed.\n", name); + rfbLogPerror("shmget"); + + XDestroyImage(xim); + *ximg_ptr = NULL; + + X_UNLOCK; + return 0; } - cmd = (char *)malloc(strlen(set_color) + strlen(color) + 1); - sprintf(cmd, set_color, color); - dt_cmd(cmd); - free(cmd); - cmd = (char *)malloc(strlen(set_option) + strlen("none") + 1); - sprintf(cmd, set_option, "none"); - dt_cmd(cmd); - free(cmd); -} + shm->shmaddr = xim->data = (char *) shmat(shm->shmid, 0, 0); -void solid_kde(char *color) { - char set_color[] = - "dcop --user '%s' kdesktop KBackgroundIface setColor '%s' 1"; - char bg_off[] = - "dcop --user '%s' kdesktop KBackgroundIface setBackgroundEnabled 0"; - char bg_on[] = - "dcop --user '%s' kdesktop KBackgroundIface setBackgroundEnabled 1"; - char *cmd, *user = NULL; - int len; + if (shm->shmaddr == (char *)-1) { + rfbErr("shmat(%s) failed.\n", name); + rfbLogPerror("shmat"); - user = get_user_name(); - if (strstr(user, "'") != NULL) { - rfbLog("bad user: %s\n", user); - free(user); - return; - } + XDestroyImage(xim); + *ximg_ptr = NULL; - if (! color) { - len = strlen(bg_on) + strlen(user) + 1; - cmd = (char *)malloc(len); - sprintf(cmd, bg_on, user); - dt_cmd(cmd); - free(cmd); - free(user); + shmctl(shm->shmid, IPC_RMID, 0); + shm->shmid = -1; - return; + X_UNLOCK; + return 0; } - if (strstr(color, "'") != NULL) { - rfbLog("bad color: %s\n", color); - return; - } + shm->readOnly = False; - len = strlen(set_color) + strlen(user) + strlen(color) + 1; - cmd = (char *)malloc(len); - sprintf(cmd, set_color, user, color); - dt_cmd(cmd); - free(cmd); + if (! XShmAttach_wr(dpy, shm)) { + rfbErr("XShmAttach(%s) failed.\n", name); + XDestroyImage(xim); + *ximg_ptr = NULL; - len = strlen(bg_off) + strlen(user) + 1; - cmd = (char *)malloc(len); - sprintf(cmd, bg_off, user); - dt_cmd(cmd); - free(cmd); - free(user); + shmdt(shm->shmaddr); + shm->shmaddr = (char *) -1; + + shmctl(shm->shmid, IPC_RMID, 0); + shm->shmid = -1; + + X_UNLOCK; + return 0; + } +#endif + + X_UNLOCK; + return 1; } -char *guess_desktop() { - Atom prop; - prop = XInternAtom(dpy, "XFCE_DESKTOP_WINDOW", True); - if (prop != None) { - return "xfce"; +void shm_delete(XShmSegmentInfo *shm) { +#if LIBVNCSERVER_HAVE_XSHM + if (shm != NULL && shm->shmaddr != (char *) -1) { + shmdt(shm->shmaddr); } - prop = XInternAtom(dpy, "_QT_DESKTOP_PROPERTIES", True); - if (prop != None) { - return "kde"; + if (shm != NULL && shm->shmid != -1) { + shmctl(shm->shmid, IPC_RMID, 0); } - prop = XInternAtom(dpy, "NAUTILUS_DESKTOP_WINDOW_ID", True); - if (prop != None) { - return "gnome"; +#endif +} + +void shm_clean(XShmSegmentInfo *shm, XImage *xim) { + + X_LOCK; +#if LIBVNCSERVER_HAVE_XSHM + if (shm != NULL && shm->shmid != -1 && dpy) { /* raw_fb hack */ + XShmDetach_wr(dpy, shm); } - prop = XInternAtom(dpy, "_MOTIF_WM_INFO", True); - if (prop != None) { - prop = XInternAtom(dpy, "_DT_WORKSPACE_LIST", True); - if (prop != None) { - return "cde"; - } +#endif + if (xim != NULL) { + XDestroyImage(xim); + xim = NULL; } - return "root"; + X_UNLOCK; + + shm_delete(shm); } -void solid_bg(int restore) { - static int desktop = -1; - static int solid_on = 0; - static char *prev_str; - char *dtname, *color; +void initialize_polling_images(void) { + int i, MB = 1024 * 1024; - if (started_as_root == 1 && users_list) { - /* we are still root, don't try. */ - return; + /* set all shm areas to "none" before trying to create any */ + scanline_shm.shmid = -1; + scanline_shm.shmaddr = (char *) -1; + scanline = NULL; + fullscreen_shm.shmid = -1; + fullscreen_shm.shmaddr = (char *) -1; + fullscreen = NULL; + snaprect_shm.shmid = -1; + snaprect_shm.shmaddr = (char *) -1; + snaprect = NULL; + for (i=1; i<=ntiles_x; i++) { + tile_row_shm[i].shmid = -1; + tile_row_shm[i].shmaddr = (char *) -1; + tile_row[i] = NULL; } - if (restore) { - if (! solid_on) { - return; - } - if (desktop == 0) { - solid_root(NULL); - } else if (desktop == 1) { - solid_gnome(NULL); - } else if (desktop == 2) { - solid_kde(NULL); - } else if (desktop == 3) { - solid_cde(NULL); - } - solid_on = 0; - return; + /* the scanline (e.g. 1280x1) shared memory area image: */ + + if (! shm_create(&scanline_shm, &scanline, dpy_x, 1, "scanline")) { + clean_up_exit(1); } - if (! solid_str) { - return; + + /* + * the fullscreen (e.g. 1280x1024/fs_factor) shared memory area image: + * (we cut down the size of the shm area to try avoid and shm segment + * limits, e.g. the default 1MB on Solaris) + */ + if (UT.sysname && strstr(UT.sysname, "Linux")) { + set_fs_factor(10 * MB); + } else { + set_fs_factor(1 * MB); } - if (solid_on && !strcmp(prev_str, solid_str)) { - return; + if (fs_frac >= 1.0) { + fs_frac = 1.1; + fs_factor = 0; } - if (strstr(solid_str, "guess:") == solid_str - || !strchr(solid_str, ':')) { - dtname = guess_desktop(); - rfbLog("guessed desktop: %s\n", dtname); + if (! fs_factor) { + rfbLog("warning: fullscreen updates are disabled.\n"); } else { - if (strstr(solid_str, "gnome:") == solid_str) { - dtname = "gnome"; - } else if (strstr(solid_str, "kde:") == solid_str) { - dtname = "kde"; - } else if (strstr(solid_str, "cde:") == solid_str) { - dtname = "cde"; - } else { - dtname = "root"; - } + if (! shm_create(&fullscreen_shm, &fullscreen, dpy_x, + dpy_y/fs_factor, "fullscreen")) { + clean_up_exit(1); + } + } + if (use_snapfb) { + if (! fs_factor) { + rfbLog("warning: disabling -snapfb mode.\n"); + use_snapfb = 0; + } else if (! shm_create(&snaprect_shm, &snaprect, dpy_x, + dpy_y/fs_factor, "snaprect")) { + clean_up_exit(1); + } } - color = strchr(solid_str, ':'); - if (! color) { - color = solid_str; - } else { - color++; - if (*color == '\0') { - color = solid_default; + /* + * for copy_tiles we need a lot of shared memory areas, one for + * each possible run length of changed tiles. 32 for 1024x768 + * and 40 for 1280x1024, etc. + */ + + tile_shm_count = 0; + for (i=1; i<=ntiles_x; i++) { + if (! shm_create(&tile_row_shm[i], &tile_row[i], tile_x * i, + tile_y, "tile_row")) { + if (i == 1) { + clean_up_exit(1); + } + rfbLog("shm: Error creating shared memory tile-row for" + " len=%d,\n", i); + rfbLog("shm: reverting to -onetile mode. If this" + " problem persists\n"); + rfbLog("shm: try using the -onetile or -noshm options" + " to limit\n"); + rfbLog("shm: shared memory usage, or run ipcrm(1)" + " to manually\n"); + rfbLog("shm: delete unattached shm segments.\n"); + single_copytile_count = i; + } + tile_shm_count++; + if (single_copytile && i >= 1) { + /* only need 1x1 tiles */ + break; } } - if (!strcmp(dtname, "gnome")) { - desktop = 1; - solid_gnome(color); - } else if (!strcmp(dtname, "kde")) { - desktop = 2; - solid_kde(color); - } else if (!strcmp(dtname, "cde")) { - desktop = 3; - solid_cde(color); - } else { - desktop = 0; - solid_root(color); - } - if (prev_str) { - free(prev_str); + if (!quiet) { + if (using_shm) { + rfbLog("created %d tile_row shm polling images.\n", + tile_shm_count); + } else { + rfbLog("created %d tile_row polling images.\n", + tile_shm_count); + } } - prev_str = strdup(solid_str); - solid_on = 1; } -/* -- xinerama.c -- */ /* - * routines related to xinerama and blacking out rectangles + * A hint is a rectangular region built from 1 or more adjacent tiles + * glued together. Ultimately, this information in a single hint is sent + * to libvncserver rather than sending each tile separately. */ +static void create_tile_hint(int x, int y, int th, hint_t *hint) { + int w = dpy_x - x; + int h = dpy_y - y; -/* blacked-out region (-blackout, -xinerama) */ -typedef struct bout { - int x1, y1, x2, y2; -} blackout_t; -#define BO_MAX 16 -typedef struct tbout { - blackout_t bo[BO_MAX]; /* hardwired max rectangles. */ - int cover; - int count; -} tile_blackout_t; + if (w > tile_x) { + w = tile_x; + } + if (h > th) { + h = th; + } -#define BLACKR_MAX 100 -blackout_t blackr[BLACKR_MAX]; /* hardwired max blackouts */ -tile_blackout_t *tile_blackout; -int blackouts = 0; + hint->x = x; + hint->y = y; + hint->w = w; + hint->h = h; +} + +static void extend_tile_hint(int x, int y, int th, hint_t *hint) { + int w = dpy_x - x; + int h = dpy_y - y; + + if (w > tile_x) { + w = tile_x; + } + if (h > th) { + h = th; + } + + if (hint->x > x) { /* extend to the left */ + hint->w += hint->x - x; + hint->x = x; + } + if (hint->y > y) { /* extend upward */ + hint->h += hint->y - y; + hint->y = y; + } + + if (hint->x + hint->w < x + w) { /* extend to the right */ + hint->w = x + w - hint->x; + } + if (hint->y + hint->h < y + h) { /* extend downward */ + hint->h = y + h - hint->y; + } +} + +static void save_hint(hint_t hint, int loc) { + /* simply copy it to the global array for later use. */ + hint_list[loc].x = hint.x; + hint_list[loc].y = hint.y; + hint_list[loc].w = hint.w; + hint_list[loc].h = hint.h; +} /* - * Take a comma separated list of geometries: WxH+X+Y and register them as - * rectangles to black out from the screen. + * Glue together horizontal "runs" of adjacent changed tiles into one big + * rectangle change "hint" to be passed to the vnc machinery. */ -void initialize_blackouts(char *list) { - char *p, *blist = strdup(list); - int x, y, X, Y, h, w, t; +static void hint_updates(void) { + hint_t hint; + int x, y, i, n, ty, th; + int hint_count = 0, in_run = 0; - blackouts = 0; + for (y=0; y < ntiles_y; y++) { + for (x=0; x < ntiles_x; x++) { + n = x + y * ntiles_x; - p = strtok(blist, ", \t"); - while (p) { - if (! parse_geom(p, &w, &h, &x, &y, dpy_x, dpy_y)) { - if (*p != '\0') { - rfbLog("skipping invalid geometry: %s\n", p); + if (tile_has_diff[n]) { + ty = tile_region[n].first_line; + th = tile_region[n].last_line - ty + 1; + if (! in_run) { + create_tile_hint( x * tile_x, + y * tile_y + ty, th, &hint); + in_run = 1; + } else { + extend_tile_hint( x * tile_x, + y * tile_y + ty, th, &hint); + } + } else { + if (in_run) { + /* end of a row run of altered tiles: */ + save_hint(hint, hint_count++); + in_run = 0; + } } - p = strtok(NULL, ", \t"); - continue; - } - w = nabs(w); - h = nabs(h); - x = nfix(x, dpy_x); - y = nfix(y, dpy_y); - X = x + w; - Y = y + h; - X = nfix(X, dpy_x); - Y = nfix(Y, dpy_y); - if (x > X) { - t = X; X = x; x = t; } - if (y > Y) { - t = Y; Y = y; y = t; + if (in_run) { /* save the last row run */ + save_hint(hint, hint_count++); + in_run = 0; } - if (x < 0 || x > dpy_x || y < 0 || y > dpy_y || - X < 0 || X > dpy_x || Y < 0 || Y > dpy_y || - x == X || y == Y) { - rfbLog("skipping invalid blackout geometry: %s x=" - "%d-%d,y=%d-%d,w=%d,h=%d\n", p, x, X, y, Y, w, h); - } else { - rfbLog("blackout rect: %s: x=%d-%d y=%d-%d\n", p, - x, X, y, Y); + } - /* - * note that the black out is x1 <= x but x < x2 - * for the region. i.e. the x2, y2 are outside - * by 1 pixel. - */ - blackr[blackouts].x1 = x; - blackr[blackouts].y1 = y; - blackr[blackouts].x2 = X; - blackr[blackouts].y2 = Y; - blackouts++; - if (blackouts >= BLACKR_MAX) { - rfbLog("too many blackouts: %d\n", blackouts); - break; - } - } - p = strtok(NULL, ", \t"); + + for (i=0; i < hint_count; i++) { + /* pass update info to vnc: */ + mark_hint(hint_list[i]); } - free(blist); } /* - * Now that all blackout rectangles have been constructed, see what overlap - * they have with the tiles in the system. If a tile is touched by a - * blackout, record information. + * kludge, simple ceil+floor for non-negative doubles: + */ +#define CEIL(x) ( (double) ((int) (x)) == (x) ? \ + (double) ((int) (x)) : (double) ((int) (x) + 1) ) +#define FLOOR(x) ( (double) ((int) (x)) ) + +/* + * Scaling: + * + * For shrinking, a destination (scaled) pixel will correspond to more + * than one source (i.e. main fb) pixel. Think of an x-y plane made with + * graph paper. Each unit square in the graph paper (i.e. collection of + * points (x,y) such that N < x < N+1 and M < y < M+1, N and M integers) + * corresponds to one pixel in the unscaled fb. There is a solid + * color filling the inside of such a square. A scaled pixel has width + * 1/scale_fac, e.g. for "-scale 3/4" the width of the scaled pixel + * is 1.333. The area of this scaled pixel is 1.333 * 1.333 (so it + * obviously overlaps more than one source pixel, each which have area 1). + * + * We take the weight an unscaled pixel (source) contributes to a + * scaled pixel (destination) as simply proportional to the overlap area + * between the two pixels. One can then think of the value of the scaled + * pixel as an integral over the portion of the graph paper it covers. + * The thing being integrated is the color value of the unscaled source. + * That color value is constant over a graph paper square (source pixel), + * and changes discontinuously from one unit square to the next. + * + +Here is an example for -scale 3/4, the solid lines are the source pixels +(graph paper unit squares), while the dotted lines denote the scaled +pixels (destination pixels): + + 0 1 4/3 2 8/3 3 4=12/3 + |---------|--.------|------.--|---------|. + | | . | . | |. + | A | . B | . | |. + | | . | . | |. + | | . | . | |. + 1 |---------|--.------|------.--|---------|. + 4/3|.........|.........|.........|.........|. + | | . | . | |. + | C | . D | . | |. + | | . | . | |. + 2 |---------|--.------|------.--|---------|. + | | . | . | |. + | | . | . | |. + 8/3|.........|.........|.........|.........|. + | | . | . | |. + 3 |---------|--.------|------.--|---------|. + +So we see the first scaled pixel (0 < x < 4/3 and 0 < y < 4/3) mostly +overlaps with unscaled source pixel "A". The integration (averaging) +weights for this scaled pixel are: + + A 1 + B 1/3 + C 1/3 + D 1/9 + + * + * The Red, Green, and Blue color values must be averaged over separately + * otherwise you can get a complete mess (except in solid regions), + * because high order bits are averaged differently from the low order bits. + * + * So the algorithm is roughly: + * + * - Given as input a rectangle in the unscaled source fb with changes, + * find the rectangle of pixels this affects in the scaled destination fb. + * + * - For each of the affected scaled (dest) pixels, determine all of the + * unscaled (source) pixels it overlaps with. + * + * - Average those unscaled source values together, weighted by the area + * overlap with the destination pixel. Average R, G, B separately. + * + * - Take this average value and convert to a valid pixel value if + * necessary (e.g. rounding, shifting), and then insert it into the + * destination framebuffer as the pixel value. + * + * - On to the next destination pixel... + * + * ======================================================================== + * + * For expanding, e.g. -scale 1.1 (which we don't think people will do + * very often... or at least so we hope, the framebuffer can become huge) + * the situation is reversed and the destination pixel is smaller than a + * "graph paper" unit square (source pixel). Some destination pixels + * will be completely within a single unscaled source pixel. + * + * What we do here is a simple 4 point interpolation scheme: + * + * Let P00 be the source pixel closest to the destination pixel but with + * x and y values less than or equal to those of the destination pixel. + * (for simplicity, think of the upper left corner of a pixel defining the + * x,y location of the pixel, the center would work just as well). So it + * is the source pixel immediately to the upper left of the destination + * pixel. Let P10 be the source pixel one to the right of P00. Let P01 + * be one down from P00. And let P11 be one down and one to the right + * of P00. They form a 2x2 square we will interpolate inside of. + * + * Let V00, V10, V01, and V11 be the color values of those 4 source + * pixels. Let dx be the displacement along x the destination pixel is + * from P00. Note: 0 <= dx < 1 by definition of P00. Similarly let + * dy be the displacement along y. The weighted average for the + * interpolation is: + * + * V_ave = V00 * (1 - dx) * (1 - dy) + * + V10 * dx * (1 - dy) + * + V01 * (1 - dx) * dy + * + V11 * dx * dy + * + * Note that the weights (1-dx)*(1-dy) + dx*(1-dy) + (1-dx)*dy + dx*dy + * automatically add up to 1. It is also nice that all the weights are + * positive (unsigned char stays unsigned char). The above formula can + * be motivated by doing two 1D interpolations along x: + * + * VA = V00 * (1 - dx) + V10 * dx + * VB = V01 * (1 - dx) + V11 * dx + * + * and then interpolating VA and VB along y: + * + * V_ave = VA * (1 - dy) + VB * dy + * + * VA + * v |<-dx->| + * -- V00 ------ V10 + * dy | | + * -- | o...|... "o" denotes the position of the desired + * ^ | . | . destination pixel relative to the P00 + * | . | . source pixel. + * V10 ----.- V11 . + * ........ + * | + * VB + * + * + * Of course R, G, B averages are done separately as in the shrinking + * case. This gives reasonable results, and the implementation for + * shrinking can simply be used with different choices for weights for + * the loop over the 4 pixels. + */ + +void scale_rect(double factor, int blend, int interpolate, int Bpp, + char *src_fb, int src_bytes_per_line, char *dst_fb, int dst_bytes_per_line, + int Nx, int Ny, int nx, int ny, int X1, int Y1, int X2, int Y2, int mark) { +/* + * Notation: + * "i" an x pixel index in the destination (scaled) framebuffer + * "j" a y pixel index in the destination (scaled) framebuffer + * "I" an x pixel index in the source (un-scaled, i.e. main) framebuffer + * "J" a y pixel index in the source (un-scaled, i.e. main) framebuffer + * + * Similarly for nx, ny, Nx, Ny, etc. Lowercase: dest, Uppercase: source. */ -void blackout_tiles(void) { - int tx, ty; - int debug_bo = 0; - if (! blackouts) { - return; - } - if (getenv("DEBUG_BLACKOUT") != NULL) { - debug_bo = 1; - } - - /* - * to simplify things drop down to single copy mode, no vcr, etc... - */ - single_copytile = 1; - - /* loop over all tiles. */ - for (ty=0; ty < ntiles_y; ty++) { - for (tx=0; tx < ntiles_x; tx++) { - sraRegionPtr tile_reg, black_reg; - sraRect rect; - sraRectangleIterator *iter; - int n, b, x1, y1, x2, y2, cnt; - - /* tile number and coordinates: */ - n = tx + ty * ntiles_x; - x1 = tx * tile_x; - y1 = ty * tile_y; - x2 = x1 + tile_x; - y2 = y1 + tile_y; - if (x2 > dpy_x) { - x2 = dpy_x; - } - if (y2 > dpy_y) { - y2 = dpy_y; - } + int i, j, i1, i2, j1, j2; /* indices for scaled fb (dest) */ + int I, J, I1, I2, J1, J2; /* indices for main fb (source) */ - /* make regions for the tile and the blackouts: */ - black_reg = (sraRegionPtr) sraRgnCreate(); - tile_reg = (sraRegionPtr) sraRgnCreateRect(x1, y1, - x2, y2); + double w, wx, wy, wtot; /* pixel weights */ - tile_blackout[n].cover = 0; - tile_blackout[n].count = 0; + double x1, y1, x2, y2; /* x-y coords for destination pixels edges */ + double dx, dy; /* size of destination pixel */ + double ddx, ddy; /* for interpolation expansion */ - /* union of blackouts */ - for (b=0; b < blackouts; b++) { - sraRegionPtr tmp_reg = (sraRegionPtr) - sraRgnCreateRect(blackr[b].x1, - blackr[b].y1, blackr[b].x2, blackr[b].y2); + char *src, *dest; /* pointers to the two framebuffers */ - sraRgnOr(black_reg, tmp_reg); - sraRgnDestroy(tmp_reg); - } - if (! sraRgnAnd(black_reg, tile_reg)) { - /* - * no intersection for this tile, so we - * are done. - */ - sraRgnDestroy(black_reg); - sraRgnDestroy(tile_reg); - continue; - } + unsigned short us; + unsigned char uc; + unsigned int ui; - /* - * loop over rectangles that make up the blackout - * region: - */ - cnt = 0; - iter = sraRgnGetIterator(black_reg); - while (sraRgnIteratorNext(iter, &rect)) { + int use_noblend_shortcut = 1; + int shrink; /* whether shrinking or expanding */ + static int constant_weights = -1, mag_int = -1; + static int last_Nx = -1, last_Ny = -1, cnt = 0; + static double last_factor = -1.0; + int b, k; + double pixave[4]; /* for averaging pixel values */ - /* make sure x1 < x2 and y1 < y2 */ - if (rect.x1 > rect.x2) { - int tmp = rect.x2; - rect.x2 = rect.x1; - rect.x1 = tmp; - } - if (rect.y1 > rect.y2) { - int tmp = rect.y2; - rect.y2 = rect.y1; - rect.y1 = tmp; - } + if (factor <= 1.0) { + shrink = 1; + } else { + shrink = 0; + } - /* store coordinates */ - tile_blackout[n].bo[cnt].x1 = rect.x1; - tile_blackout[n].bo[cnt].y1 = rect.y1; - tile_blackout[n].bo[cnt].x2 = rect.x2; - tile_blackout[n].bo[cnt].y2 = rect.y2; + /* + * N.B. width and height (real numbers) of a scaled pixel. + * both are > 1 (e.g. 1.333 for -scale 3/4) + * they should also be equal but we don't assume it. + * + * This new way is probably the best we can do, take the inverse + * of the scaling factor to double precision. + */ + dx = 1.0/factor; + dy = 1.0/factor; - /* note if the tile is completely obscured */ - if (rect.x1 == x1 && rect.y1 == y1 && - rect.x2 == x2 && rect.y2 == y2) { - tile_blackout[n].cover = 2; - if (debug_bo) { - fprintf(stderr, "full: %d=%d,%d" - " (%d-%d) (%d-%d)\n", - n, tx, ty, x1, x2, y1, y2); - } - } else { - tile_blackout[n].cover = 1; - if (debug_bo) { - fprintf(stderr, "part: %d=%d,%d" - " (%d-%d) (%d-%d)\n", - n, tx, ty, x1, x2, y1, y2); - } - } + /* + * There is some speedup if the pixel weights are constant, so + * let's special case these. + * + * If scale = 1/n and n divides Nx and Ny, the pixel weights + * are constant (e.g. 1/2 => equal on 2x2 square). + */ + if (factor != last_factor || Nx != last_Nx || Ny != last_Ny) { + constant_weights = -1; + mag_int = -1; + last_Nx = Nx; + last_Ny = Ny; + last_factor = factor; + } - if (++cnt >= BO_MAX) { - rfbLog("too many blackout rectangles " - "for tile %d=%d,%d.\n", n, tx, ty); - break; - } - } + if (constant_weights < 0) { + int n = 0; - sraRgnReleaseIterator(iter); - sraRgnDestroy(black_reg); - sraRgnDestroy(tile_reg); + constant_weights = 0; + mag_int = 0; - tile_blackout[n].count = cnt; - if (debug_bo && cnt > 1) { - rfbLog("warning: multiple region overlaps[%d] " - "for tile %d=%d,%d.\n", cnt, n, tx, ty); + for (i = 2; i<=128; i++) { + double test = ((double) 1)/ i; + double diff, eps = 1.0e-7; + diff = factor - test; + if (-eps < diff && diff < eps) { + n = i; + break; + } + } + if (! blend || ! shrink || interpolate) { + ; + } else if (n != 0) { + if (Nx % n == 0 && Ny % n == 0) { + static int didmsg = 0; + if (mark && ! didmsg) { + didmsg = 1; + rfbLog("scale_and_mark_rect: using " + "constant pixel weight speedup " + "for 1/%d\n", n); + } + constant_weights = 1; } } - } -} - -void initialize_xinerama (void) { -#if !LIBVNCSERVER_HAVE_LIBXINERAMA - rfbLog("Xinerama: Library libXinerama is not available to determine\n"); - rfbLog("Xinerama: the head geometries, consider using -blackout\n"); - rfbLog("Xinerama: if the screen is non-rectangular.\n"); -#else - XineramaScreenInfo *sc, *xineramas; - sraRegionPtr black_region, tmp_region; - sraRectangleIterator *iter; - sraRect rect; - char *bstr, *tstr; - int ev, er, i, n, rcnt; - - if (raw_fb && ! dpy) return; /* raw_fb hack */ - if (! XineramaQueryExtension(dpy, &ev, &er)) { - rfbLog("Xinerama: disabling: display does not support it.\n"); - xinerama = 0; - xinerama_present = 0; - return; + n = 0; + for (i = 2; i<=32; i++) { + double test = (double) i; + double diff, eps = 1.0e-7; + diff = factor - test; + if (-eps < diff && diff < eps) { + n = i; + break; + } + } + if (! blend && factor > 1.0 && n) { + mag_int = n; + } } - if (! XineramaIsActive(dpy)) { - /* n.b. change to XineramaActive(dpy, window) someday */ - rfbLog("Xinerama: disabling: not active on display.\n"); - xinerama = 0; - xinerama_present = 0; - return; - } - xinerama_present = 1; - /* n.b. change to XineramaGetData() someday */ - xineramas = XineramaQueryScreens(dpy, &n); - rfbLog("Xinerama: number of sub-screens: %d\n", n); - - if (n == 1) { - rfbLog("Xinerama: no blackouts needed (only one" - " sub-screen)\n"); - XFree(xineramas); - return; /* must be OK w/o change */ + if (mark && factor > 1.0 && blend) { + /* + * kludge: correct for interpolating blurring leaking + * up or left 1 destination pixel. + */ + if (X1 > 0) X1--; + if (Y1 > 0) Y1--; } - black_region = sraRgnCreateRect(0, 0, dpy_x, dpy_y); + /* + * find the extent of the change the input rectangle induces in + * the scaled framebuffer. + */ - sc = xineramas; - for (i=0; ix_org; - y = sc->y_org; - w = sc->width; - h = sc->height; + /* Left edges: find largest i such that i * dx <= X1 */ + i1 = FLOOR(X1/dx); - tmp_region = sraRgnCreateRect(x, y, x + w, y + h); + /* Right edges: find smallest i such that (i+1) * dx >= X2+1 */ + i2 = CEIL( (X2+1)/dx ) - 1; - sraRgnSubtract(black_region, tmp_region); - sraRgnDestroy(tmp_region); - sc++; - } - XFree(xineramas); + /* To be safe, correct any overflows: */ + i1 = nfix(i1, nx); + i2 = nfix(i2, nx) + 1; /* add 1 to make a rectangle upper boundary */ - if (sraRgnEmpty(black_region)) { - rfbLog("Xinerama: no blackouts needed (screen fills" - " rectangle)\n"); - sraRgnDestroy(black_region); - return; - } + /* Repeat above for y direction: */ + j1 = FLOOR(Y1/dy); + j2 = CEIL( (Y2+1)/dy ) - 1; - /* max len is 10000x10000+10000+10000 (23 chars) per geometry */ - rcnt = (int) sraRgnCountRects(black_region); - bstr = (char *) malloc(30 * (rcnt+1) * sizeof(char)); - tstr = (char *) malloc(30 * sizeof(char)); - bstr[0] = '\0'; + j1 = nfix(j1, ny); + j2 = nfix(j2, ny) + 1; - iter = sraRgnGetIterator(black_region); - while (sraRgnIteratorNext(iter, &rect)) { - int x, y, w, h; + /* special case integer magnification with no blending */ + if (mark && ! blend && mag_int && Bpp != 3) { + int jmin, jmax, imin, imax; - /* make sure x1 < x2 and y1 < y2 */ - if (rect.x1 > rect.x2) { - int tmp = rect.x2; - rect.x2 = rect.x1; - rect.x1 = tmp; - } - if (rect.y1 > rect.y2) { - int tmp = rect.y2; - rect.y2 = rect.y1; - rect.y1 = tmp; + /* outer loop over *source* pixels */ + for (J=Y1; J < Y2; J++) { + jmin = J * mag_int; + jmax = jmin + mag_int; + for (I=X1; I < X2; I++) { + /* extract value */ + src = src_fb + J*src_bytes_per_line + I*Bpp; + if (Bpp == 4) { + ui = *((unsigned int *)src); + } else if (Bpp == 2) { + us = *((unsigned short *)src); + } else if (Bpp == 1) { + uc = *((unsigned char *)src); + } + imin = I * mag_int; + imax = imin + mag_int; + /* inner loop over *dest* pixels */ + for (j=jmin; j Ny - 1) { + /* can go over with dy = 1/scale_fac */ + y1 = Ny - 1; + } + y2 = y1 + dy; /* bottom edge */ -void push_sleep(n) { - int i; - for (i=0; i Nx - 1) { + /* can go over with dx = 1/scale_fac */ + x1 = Nx - 1; + } + x2 = x1 + dx; /* right edge */ -/* - * Fill the framebuffer with zero for the prescribed rectangle - */ -void zero_fb(int x1, int y1, int x2, int y2) { - int pixelsize = bpp/8; - int line, fill = 0; - char *dst; - - if (x1 < 0 || x2 <= x1 || x2 > dpy_x) { - return; - } - if (y1 < 0 || y2 <= y1 || y2 > dpy_y) { - return; - } - if (! main_fb) { - return; - } + cnt++; - dst = main_fb + y1 * main_bytes_per_line + x1 * pixelsize; - line = y1; - while (line++ < y2) { - memset(dst, fill, (size_t) (x2 - x1) * pixelsize); - dst += main_bytes_per_line; - } -} + /* Find main fb indices covered by this dest pixel: */ + I1 = (int) FLOOR(x1); + if (I1 >= Nx) I1 = Nx - 1; -/* -- scan.c -- */ -/* - * routines for scanning and reading the X11 display for changes, and - * for doing all the tile work (shm, etc). - */ + if (! blend && use_noblend_shortcut) { + /* + * The noblend case involves no weights, + * and 1 pixel, so just copy the value + * directly. + */ + src = src_fb + J1*src_bytes_per_line + I1*Bpp; + if (Bpp == 4) { + *((unsigned int *)dest) + = *((unsigned int *)src); + } else if (Bpp == 2) { + *((unsigned short *)dest) + = *((unsigned short *)src); + } else if (Bpp == 1) { + *(dest) = *(src); + } else if (Bpp == 3) { + /* rare case */ + for (k=0; k<=2; k++) { + *(dest+k) = *(src+k); + } + } + dest += Bpp; + continue; + } + + if (shrink && ! interpolate) { + I2 = (int) CEIL(x2) - 1; + if (I2 >= Nx) I2 = Nx - 1; + } else { + I2 = I1 + 1; /* simple interpolation */ + ddx = x1 - I1; + } -/* array to hold the hints: */ -static hint_t *hint_list; + /* Zero out accumulators for next pixel average: */ + for (b=0; b<4; b++) { + pixave[b] = 0.0; /* for RGB weighted sums */ + } -/* nap state */ -static int nap_ok = 0, nap_diff_count = 0; + /* + * wtot is for accumulating the total weight. + * It should always sum to 1/(scale_fac * scale_fac). + */ + wtot = 0.0; -static int scan_count = 0; /* indicates which scan pattern we are on */ -static int scan_in_progress = 0; + /* + * Loop over source pixels covered by this dest pixel. + * + * These "extra" loops over "J" and "I" make + * the cache/cacheline performance unclear. + * For example, will the data brought in from + * src for j, i, and J=0 still be in the cache + * after the J > 0 data have been accessed and + * we are at j, i+1, J=0? The stride in J is + * main_bytes_per_line, and so ~4 KB. + * + * Typical case when shrinking are 2x2 loop, so + * just two lines to worry about. + */ + for (J=J1; J<=J2; J++) { + /* see comments for I, x1, x2, etc. below */ + if (constant_weights) { + ; + } else if (! blend) { + if (J != J1) { + continue; + } + wy = 1.0; -typedef struct tile_change_region { - /* start and end lines, along y, of the changed area inside a tile. */ - unsigned short first_line, last_line; - /* info about differences along edges. */ - unsigned short left_diff, right_diff; - unsigned short top_diff, bot_diff; -} region_t; + /* interpolation scheme: */ + } else if (! shrink || interpolate) { + if (J >= Ny) { + continue; + } else if (J == J1) { + wy = 1.0 - ddy; + } else if (J != J1) { + wy = ddy; + } -/* array to hold the tiles region_t-s. */ -static region_t *tile_region; + /* integration scheme: */ + } else if (J < y1) { + wy = J+1 - y1; + } else if (J+1 > y2) { + wy = y2 - J; + } else { + wy = 1.0; + } + src = src_fb + J*src_bytes_per_line + I1*Bpp; -/* - * setup tile numbers and allocate the tile and hint arrays: - */ -void initialize_tiles(void) { + for (I=I1; I<=I2; I++) { - ntiles_x = (dpy_x - 1)/tile_x + 1; - ntiles_y = (dpy_y - 1)/tile_y + 1; - ntiles = ntiles_x * ntiles_y; + /* Work out the weight: */ - tile_has_diff = (unsigned char *) - malloc((size_t) (ntiles * sizeof(unsigned char))); - tile_has_xdamage_diff = (unsigned char *) - malloc((size_t) (ntiles * sizeof(unsigned char))); - tile_row_has_xdamage_diff = (unsigned char *) - malloc((size_t) (ntiles_y * sizeof(unsigned char))); - tile_tried = (unsigned char *) - malloc((size_t) (ntiles * sizeof(unsigned char))); - tile_copied = (unsigned char *) - malloc((size_t) (ntiles * sizeof(unsigned char))); - tile_blackout = (tile_blackout_t *) - malloc((size_t) (ntiles * sizeof(tile_blackout_t))); - tile_region = (region_t *) malloc((size_t) (ntiles * sizeof(region_t))); + if (constant_weights) { + ; + } else if (! blend) { + /* + * Ugh, PseudoColor colormap is + * bad news, to avoid random + * colors just take the first + * pixel. Or user may have + * specified :nb to fraction. + * The :fb will force blending + * for this case. + */ + if (I != I1) { + continue; + } + wx = 1.0; - tile_row = (XImage **) - malloc((size_t) ((ntiles_x + 1) * sizeof(XImage *))); - tile_row_shm = (XShmSegmentInfo *) - malloc((size_t) ((ntiles_x + 1) * sizeof(XShmSegmentInfo))); + /* interpolation scheme: */ + } else if (! shrink || interpolate) { + if (I >= Nx) { + continue; /* off edge */ + } else if (I == I1) { + wx = 1.0 - ddx; + } else if (I != I1) { + wx = ddx; + } - /* there will never be more hints than tiles: */ - hint_list = (hint_t *) malloc((size_t) (ntiles * sizeof(hint_t))); -} + /* integration scheme: */ + } else if (I < x1) { + /* + * source left edge (I) to the + * left of dest left edge (x1): + * fractional weight + */ + wx = I+1 - x1; + } else if (I+1 > x2) { + /* + * source right edge (I+1) to the + * right of dest right edge (x2): + * fractional weight + */ + wx = x2 - I; + } else { + /* + * source edges (I and I+1) completely + * inside dest edges (x1 and x2): + * full weight + */ + wx = 1.0; + } -void free_tiles(void) { - if (tile_has_diff) { - free(tile_has_diff); - tile_has_diff = NULL; - } - if (tile_has_xdamage_diff) { - free(tile_has_xdamage_diff); - tile_has_xdamage_diff = NULL; - } - if (tile_row_has_xdamage_diff) { - free(tile_row_has_xdamage_diff); - tile_row_has_xdamage_diff = NULL; - } - if (tile_tried) { - free(tile_tried); - tile_tried = NULL; - } - if (tile_copied) { - free(tile_copied); - tile_copied = NULL; - } - if (tile_blackout) { - free(tile_blackout); - tile_blackout = NULL; - } - if (tile_region) { - free(tile_region); - tile_region = NULL; - } - if (tile_row) { - free(tile_row); - tile_row = NULL; - } - if (tile_row_shm) { - free(tile_row_shm); - tile_row_shm = NULL; - } - if (hint_list) { - free(hint_list); - hint_list = NULL; - } -} + w = wx * wy; + wtot += w; -/* - * silly function to factor dpy_y until fullscreen shm is not bigger than max. - * should always work unless dpy_y is a large prime or something... under - * failure fs_factor remains 0 and no fullscreen updates will be tried. - */ -static int fs_factor = 0; + /* + * We average the unsigned char value + * instead of char value: otherwise + * the minimum (char 0) is right next + * to the maximum (char -1)! This way + * they are spread between 0 and 255. + */ + if (Bpp == 4) { + /* unroll the loops, can give 20% */ + pixave[0] += w * + ((unsigned char) *(src )); + pixave[1] += w * + ((unsigned char) *(src+1)); + pixave[2] += w * + ((unsigned char) *(src+2)); + pixave[3] += w * + ((unsigned char) *(src+3)); + } else if (Bpp == 2) { + /* + * 16bpp: trickier with green + * split over two bytes, so we + * use the masks: + */ + us = *((unsigned short *) src); + pixave[0] += w*(us & main_red_mask); + pixave[1] += w*(us & main_green_mask); + pixave[2] += w*(us & main_blue_mask); + } else if (Bpp == 1) { + pixave[0] += w * + ((unsigned char) *(src)); + } else { + for (b=0; bbyte_order == LSBFirst) { - order = "MSBFirst"; - xim->byte_order = MSBFirst; - xim->bitmap_bit_order = MSBFirst; - } else { - order = "LSBFirst"; - xim->byte_order = LSBFirst; - xim->bitmap_bit_order = LSBFirst; + markit: + if (mark) { + mark_rect_as_modified(i1, j1, i2, j2, 1); } - return order; } -/* - * set up an XShm image, or if not using shm just create the XImage. - */ -static int shm_create(XShmSegmentInfo *shm, XImage **ximg_ptr, int w, int h, - char *name) { - - XImage *xim; - static int reported_flip = 0; - - shm->shmid = -1; - shm->shmaddr = (char *) -1; - *ximg_ptr = NULL; +static void scale_and_mark_rect(int X1, int Y1, int X2, int Y2) { - if (nofb) { - return 1; + if (!screen || !rfb_fb || !main_fb) { + return; } - - X_LOCK; - - if (! using_shm) { - /* we only need the XImage created */ - xim = XCreateImage_wr(dpy, default_visual, depth, ZPixmap, - 0, NULL, w, h, raw_fb ? 32 : BitmapPad(dpy), 0); - - X_UNLOCK; - - if (xim == NULL) { - rfbErr("XCreateImage(%s) failed.\n", name); - return 0; - } - xim->data = (char *) malloc(xim->bytes_per_line * xim->height); - if (xim->data == NULL) { - rfbErr("XCreateImage(%s) data malloc failed.\n", name); - return 0; - } - if (flip_byte_order) { - char *order = flip_ximage_byte_order(xim); - if (! reported_flip && ! quiet) { - rfbLog("Changing XImage byte order" - " to %s\n", order); - reported_flip = 1; + if (! screen->serverFormat.trueColour) { + /* + * PseudoColor colormap... blending leads to random colors. + * User can override with ":fb" + */ + if (scaling_blend == 1) { + /* :fb option sets it to 2 */ + if (default_visual->class == StaticGray) { + /* + * StaticGray can be blended OK, otherwise + * user can disable with :nb + */ + ; + } else { + scaling_blend = 0; } } - - *ximg_ptr = xim; - return 1; - } - - xim = XShmCreateImage_wr(dpy, default_visual, depth, ZPixmap, NULL, - shm, w, h); - - if (xim == NULL) { - rfbErr("XShmCreateImage(%s) failed.\n", name); - X_UNLOCK; - return 0; } - *ximg_ptr = xim; - -#if LIBVNCSERVER_HAVE_XSHM - shm->shmid = shmget(IPC_PRIVATE, - xim->bytes_per_line * xim->height, IPC_CREAT | 0777); - - if (shm->shmid == -1) { - rfbErr("shmget(%s) failed.\n", name); - rfbLogPerror("shmget"); + scale_rect(scale_fac, scaling_blend, scaling_interpolate, bpp/8, + main_fb, main_bytes_per_line, rfb_fb, rfb_bytes_per_line, + dpy_x, dpy_y, scaled_x, scaled_y, X1, Y1, X2, Y2, 1); +} - XDestroyImage(xim); - *ximg_ptr = NULL; +void mark_rect_as_modified(int x1, int y1, int x2, int y2, int force) { - X_UNLOCK; - return 0; + if (damage_time != 0) { + /* + * This is not XDAMAGE, rather a hack for testing + * where we allow the framebuffer to be corrupted for + * damage_delay seconds. + */ + int debug = 0; + if (time(0) > damage_time + damage_delay) { + if (! quiet) { + rfbLog("damaging turned off.\n"); + } + damage_time = 0; + damage_delay = 0; + } else { + if (debug) { + rfbLog("damaging viewer fb by not marking " + "rect: %d,%d,%d,%d\n", x1, y1, x2, y2); + } + return; + } } - shm->shmaddr = xim->data = (char *) shmat(shm->shmid, 0, 0); - - if (shm->shmaddr == (char *)-1) { - rfbErr("shmat(%s) failed.\n", name); - rfbLogPerror("shmat"); - - XDestroyImage(xim); - *ximg_ptr = NULL; - - shmctl(shm->shmid, IPC_RMID, 0); - shm->shmid = -1; - - X_UNLOCK; - return 0; + if (rfb_fb == main_fb || force) { + rfbMarkRectAsModified(screen, x1, y1, x2, y2); + } else if (scaling) { + scale_and_mark_rect(x1, y1, x2, y2); } +} - shm->readOnly = False; - - if (! XShmAttach_wr(dpy, shm)) { - rfbErr("XShmAttach(%s) failed.\n", name); - XDestroyImage(xim); - *ximg_ptr = NULL; - - shmdt(shm->shmaddr); - shm->shmaddr = (char *) -1; - - shmctl(shm->shmid, IPC_RMID, 0); - shm->shmid = -1; - - X_UNLOCK; - return 0; - } -#endif +/* + * Notifies libvncserver of a changed hint rectangle. + */ +void mark_hint(hint_t hint) { + int x = hint.x; + int y = hint.y; + int w = hint.w; + int h = hint.h; - X_UNLOCK; - return 1; + mark_rect_as_modified(x, y, x + w, y + h, 0); } -void shm_delete(XShmSegmentInfo *shm) { -#if LIBVNCSERVER_HAVE_XSHM - if (shm != NULL && shm->shmaddr != (char *) -1) { - shmdt(shm->shmaddr); - } - if (shm != NULL && shm->shmid != -1) { - shmctl(shm->shmid, IPC_RMID, 0); - } -#endif -} +/* + * copy_tiles() gives a slight improvement over copy_tile() since + * adjacent runs of tiles are done all at once there is some savings + * due to contiguous memory access. Not a great speedup, but in some + * cases it can be up to 2X. Even more on a SunRay or ShadowFB where + * no graphics hardware is involved in the read. Generally, graphics + * devices are optimized for write, not read, so we are limited by the + * read bandwidth, sometimes only 5 MB/sec on otherwise fast hardware. + */ +static int *first_line = NULL, *last_line; +static unsigned short *left_diff, *right_diff; -void shm_clean(XShmSegmentInfo *shm, XImage *xim) { +static int copy_tiles(int tx, int ty, int nt) { + int x, y, line; + int size_x, size_y, width1, width2; + int off, len, n, dw, dx, t; + int w1, w2, dx1, dx2; /* tmps for normal and short tiles */ + int pixelsize = bpp/8; + int first_min, last_max; - X_LOCK; -#if LIBVNCSERVER_HAVE_XSHM - if (shm != NULL && shm->shmid != -1 && dpy) { /* raw_fb hack */ - XShmDetach_wr(dpy, shm); - } -#endif - if (xim != NULL) { - XDestroyImage(xim); - xim = NULL; + char *src, *dst, *s_src, *s_dst, *m_src, *m_dst; + char *h_src, *h_dst; + if (! first_line) { + /* allocate arrays first time in. */ + int n = ntiles_x + 1; + first_line = (int *) malloc((size_t) (n * sizeof(int))); + last_line = (int *) malloc((size_t) (n * sizeof(int))); + left_diff = (unsigned short *) + malloc((size_t) (n * sizeof(unsigned short))); + right_diff = (unsigned short *) + malloc((size_t) (n * sizeof(unsigned short))); } - X_UNLOCK; - shm_delete(shm); -} + x = tx * tile_x; + y = ty * tile_y; -void initialize_polling_images(void) { - int i, MB = 1024 * 1024; + size_x = dpy_x - x; + if ( size_x > tile_x * nt ) { + size_x = tile_x * nt; + width1 = tile_x; + width2 = tile_x; + } else { + /* short tile */ + width1 = tile_x; /* internal tile */ + width2 = size_x - (nt - 1) * tile_x; /* right hand tile */ + } - /* set all shm areas to "none" before trying to create any */ - scanline_shm.shmid = -1; - scanline_shm.shmaddr = (char *) -1; - scanline = NULL; - fullscreen_shm.shmid = -1; - fullscreen_shm.shmaddr = (char *) -1; - fullscreen = NULL; - snaprect_shm.shmid = -1; - snaprect_shm.shmaddr = (char *) -1; - snaprect = NULL; - for (i=1; i<=ntiles_x; i++) { - tile_row_shm[i].shmid = -1; - tile_row_shm[i].shmaddr = (char *) -1; - tile_row[i] = NULL; + size_y = dpy_y - y; + if ( size_y > tile_y ) { + size_y = tile_y; } - /* the scanline (e.g. 1280x1) shared memory area image: */ + n = tx + ty * ntiles_x; /* number of the first tile */ - if (! shm_create(&scanline_shm, &scanline, dpy_x, 1, "scanline")) { - clean_up_exit(1); + if (blackouts && tile_blackout[n].cover == 2) { + /* + * If there are blackouts and this tile is completely covered + * no need to poll screen or do anything else.. + * n.b. we are int single copy_tile mode: nt=1 + */ + tile_has_diff[n] = 0; + return(0); } - /* - * the fullscreen (e.g. 1280x1024/fs_factor) shared memory area image: - * (we cut down the size of the shm area to try avoid and shm segment - * limits, e.g. the default 1MB on Solaris) - */ - if (UT.sysname && strstr(UT.sysname, "Linux")) { - set_fs_factor(10 * MB); - } else { - set_fs_factor(1 * MB); - } - if (fs_frac >= 1.0) { - fs_frac = 1.1; - fs_factor = 0; - } - if (! fs_factor) { - rfbLog("warning: fullscreen updates are disabled.\n"); - } else { - if (! shm_create(&fullscreen_shm, &fullscreen, dpy_x, - dpy_y/fs_factor, "fullscreen")) { - clean_up_exit(1); - } - } - if (use_snapfb) { - if (! fs_factor) { - rfbLog("warning: disabling -snapfb mode.\n"); - use_snapfb = 0; - } else if (! shm_create(&snaprect_shm, &snaprect, dpy_x, - dpy_y/fs_factor, "snaprect")) { - clean_up_exit(1); - } - } + X_LOCK; + XRANDR_SET_TRAP_RET(-1, "copy_tile-set"); + /* read in the whole tile run at once: */ + copy_image(tile_row[nt], x, y, size_x, size_y); + XRANDR_CHK_TRAP_RET(-1, "copy_tile-chk"); - /* - * for copy_tiles we need a lot of shared memory areas, one for - * each possible run length of changed tiles. 32 for 1024x768 - * and 40 for 1280x1024, etc. - */ + X_UNLOCK; - tile_shm_count = 0; - for (i=1; i<=ntiles_x; i++) { - if (! shm_create(&tile_row_shm[i], &tile_row[i], tile_x * i, - tile_y, "tile_row")) { - if (i == 1) { - clean_up_exit(1); + if (blackouts && tile_blackout[n].cover == 1) { + /* + * If there are blackouts and this tile is partially covered + * we should re-black-out the portion. + * n.b. we are int single copy_tile mode: nt=1 + */ + int x1, x2, y1, y2, b; + int w, s, fill = 0; + + for (b=0; b < tile_blackout[n].count; b++) { + char *b_dst = tile_row[nt]->data; + + x1 = tile_blackout[n].bo[b].x1 - x; + y1 = tile_blackout[n].bo[b].y1 - y; + x2 = tile_blackout[n].bo[b].x2 - x; + y2 = tile_blackout[n].bo[b].y2 - y; + + w = (x2 - x1) * pixelsize; + s = x1 * pixelsize; + + for (line = 0; line < size_y; line++) { + if (y1 <= line && line < y2) { + memset(b_dst + s, fill, (size_t) w); + } + b_dst += tile_row[nt]->bytes_per_line; } - rfbLog("shm: Error creating shared memory tile-row for" - " len=%d,\n", i); - rfbLog("shm: reverting to -onetile mode. If this" - " problem persists\n"); - rfbLog("shm: try using the -onetile or -noshm options" - " to limit\n"); - rfbLog("shm: shared memory usage, or run ipcrm(1)" - " to manually\n"); - rfbLog("shm: delete unattached shm segments.\n"); - single_copytile_count = i; - } - tile_shm_count++; - if (single_copytile && i >= 1) { - /* only need 1x1 tiles */ - break; - } - } - if (!quiet) { - if (using_shm) { - rfbLog("created %d tile_row shm polling images.\n", - tile_shm_count); - } else { - rfbLog("created %d tile_row polling images.\n", - tile_shm_count); } } -} -/* - * A hint is a rectangular region built from 1 or more adjacent tiles - * glued together. Ultimately, this information in a single hint is sent - * to libvncserver rather than sending each tile separately. - */ -static void create_tile_hint(int x, int y, int th, hint_t *hint) { - int w = dpy_x - x; - int h = dpy_y - y; + src = tile_row[nt]->data; + dst = main_fb + y * main_bytes_per_line + x * pixelsize; - if (w > tile_x) { - w = tile_x; - } - if (h > th) { - h = th; + s_src = src; + s_dst = dst; + + for (t=1; t <= nt; t++) { + first_line[t] = -1; } - hint->x = x; - hint->y = y; - hint->w = w; - hint->h = h; -} + /* find the first line with difference: */ + w1 = width1 * pixelsize; + w2 = width2 * pixelsize; -static void extend_tile_hint(int x, int y, int th, hint_t *hint) { - int w = dpy_x - x; - int h = dpy_y - y; + /* foreach line: */ + for (line = 0; line < size_y; line++) { + /* foreach horizontal tile: */ + for (t=1; t <= nt; t++) { + if (first_line[t] != -1) { + continue; + } - if (w > tile_x) { - w = tile_x; - } - if (h > th) { - h = th; + off = (t-1) * w1; + if (t == nt) { + len = w2; /* possible short tile */ + } else { + len = w1; + } + + if (memcmp(s_dst + off, s_src + off, len)) { + first_line[t] = line; + } + } + s_src += tile_row[nt]->bytes_per_line; + s_dst += main_bytes_per_line; } - if (hint->x > x) { /* extend to the left */ - hint->w += hint->x - x; - hint->x = x; + /* see if there were any differences for any tile: */ + first_min = -1; + for (t=1; t <= nt; t++) { + tile_tried[n+(t-1)] = 1; + if (first_line[t] != -1) { + if (first_min == -1 || first_line[t] < first_min) { + first_min = first_line[t]; + } + } } - if (hint->y > y) { /* extend upward */ - hint->h += hint->y - y; - hint->y = y; + if (first_min == -1) { + /* no tile has a difference, note this and get out: */ + for (t=1; t <= nt; t++) { + tile_has_diff[n+(t-1)] = 0; + } + return(0); + } else { + /* + * at least one tile has a difference. make sure info + * is recorded (e.g. sometimes we guess tiles and they + * came in with tile_has_diff 0) + */ + for (t=1; t <= nt; t++) { + if (first_line[t] == -1) { + tile_has_diff[n+(t-1)] = 0; + } else { + tile_has_diff[n+(t-1)] = 1; + } + } } - if (hint->x + hint->w < x + w) { /* extend to the right */ - hint->w = x + w - hint->x; - } - if (hint->y + hint->h < y + h) { /* extend downward */ - hint->h = y + h - hint->y; + m_src = src + (tile_row[nt]->bytes_per_line * size_y); + m_dst = dst + (main_bytes_per_line * size_y); + + for (t=1; t <= nt; t++) { + last_line[t] = first_line[t]; } -} -static void save_hint(hint_t hint, int loc) { - /* simply copy it to the global array for later use. */ - hint_list[loc].x = hint.x; - hint_list[loc].y = hint.y; - hint_list[loc].w = hint.w; - hint_list[loc].h = hint.h; -} + /* find the last line with difference: */ + w1 = width1 * pixelsize; + w2 = width2 * pixelsize; + + /* foreach line: */ + for (line = size_y - 1; line > first_min; line--) { -/* - * Glue together horizontal "runs" of adjacent changed tiles into one big - * rectangle change "hint" to be passed to the vnc machinery. - */ -static void hint_updates(void) { - hint_t hint; - int x, y, i, n, ty, th; - int hint_count = 0, in_run = 0; + m_src -= tile_row[nt]->bytes_per_line; + m_dst -= main_bytes_per_line; - for (y=0; y < ntiles_y; y++) { - for (x=0; x < ntiles_x; x++) { - n = x + y * ntiles_x; + /* foreach tile: */ + for (t=1; t <= nt; t++) { + if (first_line[t] == -1 + || last_line[t] != first_line[t]) { + /* tile has no changes or already done */ + continue; + } - if (tile_has_diff[n]) { - ty = tile_region[n].first_line; - th = tile_region[n].last_line - ty + 1; - if (! in_run) { - create_tile_hint( x * tile_x, - y * tile_y + ty, th, &hint); - in_run = 1; - } else { - extend_tile_hint( x * tile_x, - y * tile_y + ty, th, &hint); - } + off = (t-1) * w1; + if (t == nt) { + len = w2; /* possible short tile */ } else { - if (in_run) { - /* end of a row run of altered tiles: */ - save_hint(hint, hint_count++); - in_run = 0; - } + len = w1; + } + if (memcmp(m_dst + off, m_src + off, len)) { + last_line[t] = line; } } - if (in_run) { /* save the last row run */ - save_hint(hint, hint_count++); - in_run = 0; + } + + /* + * determine the farthest down last changed line + * will be used below to limit our memcpy() to the framebuffer. + */ + last_max = -1; + for (t=1; t <= nt; t++) { + if (first_line[t] == -1) { + continue; + } + if (last_max == -1 || last_line[t] > last_max) { + last_max = last_line[t]; } } - - for (i=0; i < hint_count; i++) { - /* pass update info to vnc: */ - mark_hint(hint_list[i]); + /* look for differences on left and right hand edges: */ + for (t=1; t <= nt; t++) { + left_diff[t] = 0; + right_diff[t] = 0; } -} -/* - * kludge, simple ceil+floor for non-negative doubles: - */ -#define CEIL(x) ( (double) ((int) (x)) == (x) ? \ - (double) ((int) (x)) : (double) ((int) (x) + 1) ) -#define FLOOR(x) ( (double) ((int) (x)) ) + h_src = src; + h_dst = dst; -/* - * Scaling: - * - * For shrinking, a destination (scaled) pixel will correspond to more - * than one source (i.e. main fb) pixel. Think of an x-y plane made with - * graph paper. Each unit square in the graph paper (i.e. collection of - * points (x,y) such that N < x < N+1 and M < y < M+1, N and M integers) - * corresponds to one pixel in the unscaled fb. There is a solid - * color filling the inside of such a square. A scaled pixel has width - * 1/scale_fac, e.g. for "-scale 3/4" the width of the scaled pixel - * is 1.333. The area of this scaled pixel is 1.333 * 1.333 (so it - * obviously overlaps more than one source pixel, each which have area 1). - * - * We take the weight an unscaled pixel (source) contributes to a - * scaled pixel (destination) as simply proportional to the overlap area - * between the two pixels. One can then think of the value of the scaled - * pixel as an integral over the portion of the graph paper it covers. - * The thing being integrated is the color value of the unscaled source. - * That color value is constant over a graph paper square (source pixel), - * and changes discontinuously from one unit square to the next. - * + w1 = width1 * pixelsize; + w2 = width2 * pixelsize; -Here is an example for -scale 3/4, the solid lines are the source pixels -(graph paper unit squares), while the dotted lines denote the scaled -pixels (destination pixels): + dx1 = (width1 - tile_fuzz) * pixelsize; + dx2 = (width2 - tile_fuzz) * pixelsize; + dw = tile_fuzz * pixelsize; - 0 1 4/3 2 8/3 3 4=12/3 - |---------|--.------|------.--|---------|. - | | . | . | |. - | A | . B | . | |. - | | . | . | |. - | | . | . | |. - 1 |---------|--.------|------.--|---------|. - 4/3|.........|.........|.........|.........|. - | | . | . | |. - | C | . D | . | |. - | | . | . | |. - 2 |---------|--.------|------.--|---------|. - | | . | . | |. - | | . | . | |. - 8/3|.........|.........|.........|.........|. - | | . | . | |. - 3 |---------|--.------|------.--|---------|. + /* foreach line: */ + for (line = 0; line < size_y; line++) { + /* foreach tile: */ + for (t=1; t <= nt; t++) { + if (first_line[t] == -1) { + /* tile has no changes at all */ + continue; + } -So we see the first scaled pixel (0 < x < 4/3 and 0 < y < 4/3) mostly -overlaps with unscaled source pixel "A". The integration (averaging) -weights for this scaled pixel are: + off = (t-1) * w1; + if (t == nt) { + dx = dx2; /* possible short tile */ + if (dx <= 0) { + break; + } + } else { + dx = dx1; + } - A 1 - B 1/3 - C 1/3 - D 1/9 + if (! left_diff[t] && memcmp(h_dst + off, + h_src + off, dw)) { + left_diff[t] = 1; + } + if (! right_diff[t] && memcmp(h_dst + off + dx, + h_src + off + dx, dw) ) { + right_diff[t] = 1; + } + } + h_src += tile_row[nt]->bytes_per_line; + h_dst += main_bytes_per_line; + } - * - * The Red, Green, and Blue color values must be averaged over separately - * otherwise you can get a complete mess (except in solid regions), - * because high order bits are averaged differently from the low order bits. - * - * So the algorithm is roughly: - * - * - Given as input a rectangle in the unscaled source fb with changes, - * find the rectangle of pixels this affects in the scaled destination fb. - * - * - For each of the affected scaled (dest) pixels, determine all of the - * unscaled (source) pixels it overlaps with. - * - * - Average those unscaled source values together, weighted by the area - * overlap with the destination pixel. Average R, G, B separately. - * - * - Take this average value and convert to a valid pixel value if - * necessary (e.g. rounding, shifting), and then insert it into the - * destination framebuffer as the pixel value. - * - * - On to the next destination pixel... - * - * ======================================================================== - * - * For expanding, e.g. -scale 1.1 (which we don't think people will do - * very often... or at least so we hope, the framebuffer can become huge) - * the situation is reversed and the destination pixel is smaller than a - * "graph paper" unit square (source pixel). Some destination pixels - * will be completely within a single unscaled source pixel. - * - * What we do here is a simple 4 point interpolation scheme: - * - * Let P00 be the source pixel closest to the destination pixel but with - * x and y values less than or equal to those of the destination pixel. - * (for simplicity, think of the upper left corner of a pixel defining the - * x,y location of the pixel, the center would work just as well). So it - * is the source pixel immediately to the upper left of the destination - * pixel. Let P10 be the source pixel one to the right of P00. Let P01 - * be one down from P00. And let P11 be one down and one to the right - * of P00. They form a 2x2 square we will interpolate inside of. - * - * Let V00, V10, V01, and V11 be the color values of those 4 source - * pixels. Let dx be the displacement along x the destination pixel is - * from P00. Note: 0 <= dx < 1 by definition of P00. Similarly let - * dy be the displacement along y. The weighted average for the - * interpolation is: - * - * V_ave = V00 * (1 - dx) * (1 - dy) - * + V10 * dx * (1 - dy) - * + V01 * (1 - dx) * dy - * + V11 * dx * dy - * - * Note that the weights (1-dx)*(1-dy) + dx*(1-dy) + (1-dx)*dy + dx*dy - * automatically add up to 1. It is also nice that all the weights are - * positive (unsigned char stays unsigned char). The above formula can - * be motivated by doing two 1D interpolations along x: - * - * VA = V00 * (1 - dx) + V10 * dx - * VB = V01 * (1 - dx) + V11 * dx - * - * and then interpolating VA and VB along y: - * - * V_ave = VA * (1 - dy) + VB * dy - * - * VA - * v |<-dx->| - * -- V00 ------ V10 - * dy | | - * -- | o...|... "o" denotes the position of the desired - * ^ | . | . destination pixel relative to the P00 - * | . | . source pixel. - * V10 ----.- V11 . - * ........ - * | - * VB - * - * - * Of course R, G, B averages are done separately as in the shrinking - * case. This gives reasonable results, and the implementation for - * shrinking can simply be used with different choices for weights for - * the loop over the 4 pixels. - */ + /* now finally copy the difference to the rfb framebuffer: */ + s_src = src + tile_row[nt]->bytes_per_line * first_min; + s_dst = dst + main_bytes_per_line * first_min; + + for (line = first_min; line <= last_max; line++) { + /* for I/O speed we do not do this tile by tile */ + memcpy(s_dst, s_src, size_x * pixelsize); + s_src += tile_row[nt]->bytes_per_line; + s_dst += main_bytes_per_line; + } + + /* record all the info in the region array for this tile: */ + for (t=1; t <= nt; t++) { + int s = t - 1; + + if (first_line[t] == -1) { + /* tile unchanged */ + continue; + } + tile_region[n+s].first_line = first_line[t]; + tile_region[n+s].last_line = last_line[t]; + + tile_region[n+s].top_diff = 0; + tile_region[n+s].bot_diff = 0; + if ( first_line[t] < tile_fuzz ) { + tile_region[n+s].top_diff = 1; + } + if ( last_line[t] > (size_y - 1) - tile_fuzz ) { + tile_region[n+s].bot_diff = 1; + } + + tile_region[n+s].left_diff = left_diff[t]; + tile_region[n+s].right_diff = right_diff[t]; + + tile_copied[n+s] = 1; + } + + return(1); +} -void scale_rect(double factor, int blend, int interpolate, int Bpp, - char *src_fb, int src_bytes_per_line, char *dst_fb, int dst_bytes_per_line, - int Nx, int Ny, int nx, int ny, int X1, int Y1, int X2, int Y2, int mark) { /* - * Notation: - * "i" an x pixel index in the destination (scaled) framebuffer - * "j" a y pixel index in the destination (scaled) framebuffer - * "I" an x pixel index in the source (un-scaled, i.e. main) framebuffer - * "J" a y pixel index in the source (un-scaled, i.e. main) framebuffer + * The copy_tile() call in the loop below copies the changed tile into + * the rfb framebuffer. Note that copy_tile() sets the tile_region + * struct to have info about the y-range of the changed region and also + * whether the tile edges contain diffs (within distance tile_fuzz). * - * Similarly for nx, ny, Nx, Ny, etc. Lowercase: dest, Uppercase: source. + * We use this tile_region info to try to guess if the downward and right + * tiles will have diffs. These tiles will be checked later in the loop + * (since y+1 > y and x+1 > x). + * + * See copy_tiles_backward_pass() for analogous checking upward and + * left tiles. */ - int i, j, i1, i2, j1, j2; /* indices for scaled fb (dest) */ - int I, J, I1, I2, J1, J2; /* indices for main fb (source) */ +static int copy_all_tiles(void) { + int x, y, n, m; + int diffs = 0, ct; - double w, wx, wy, wtot; /* pixel weights */ + for (y=0; y < ntiles_y; y++) { + for (x=0; x < ntiles_x; x++) { + n = x + y * ntiles_x; - double x1, y1, x2, y2; /* x-y coords for destination pixels edges */ - double dx, dy; /* size of destination pixel */ - double ddx, ddy; /* for interpolation expansion */ + if (tile_has_diff[n]) { + ct = copy_tiles(x, y, 1); + if (ct < 0) return ct; /* fatal */ + } + if (! tile_has_diff[n]) { + /* + * n.b. copy_tiles() may have detected + * no change and reset tile_has_diff to 0. + */ + continue; + } + diffs++; - char *src, *dest; /* pointers to the two framebuffers */ + /* neighboring tile downward: */ + if ( (y+1) < ntiles_y && tile_region[n].bot_diff) { + m = x + (y+1) * ntiles_x; + if (! tile_has_diff[m]) { + tile_has_diff[m] = 2; + } + } + /* neighboring tile to right: */ + if ( (x+1) < ntiles_x && tile_region[n].right_diff) { + m = (x+1) + y * ntiles_x; + if (! tile_has_diff[m]) { + tile_has_diff[m] = 2; + } + } + } + } + return diffs; +} +/* + * Routine analogous to copy_all_tiles() above, but for horizontal runs + * of adjacent changed tiles. + */ +static int copy_all_tile_runs(void) { + int x, y, n, m, i; + int diffs = 0, ct; + int in_run = 0, run = 0; + int ntave = 0, ntcnt = 0; - unsigned short us; - unsigned char uc; - unsigned int ui; + for (y=0; y < ntiles_y; y++) { + for (x=0; x < ntiles_x + 1; x++) { + n = x + y * ntiles_x; - int use_noblend_shortcut = 1; - int shrink; /* whether shrinking or expanding */ - static int constant_weights = -1, mag_int = -1; - static int last_Nx = -1, last_Ny = -1, cnt = 0; - static double last_factor = -1.0; - int b, k; - double pixave[4]; /* for averaging pixel values */ + if (x != ntiles_x && tile_has_diff[n]) { + in_run = 1; + run++; + } else { + if (! in_run) { + in_run = 0; + run = 0; + continue; + } + ct = copy_tiles(x - run, y, run); + if (ct < 0) return ct; /* fatal */ - if (factor <= 1.0) { - shrink = 1; - } else { - shrink = 0; - } + ntcnt++; + ntave += run; + diffs += run; - /* - * N.B. width and height (real numbers) of a scaled pixel. - * both are > 1 (e.g. 1.333 for -scale 3/4) - * they should also be equal but we don't assume it. - * - * This new way is probably the best we can do, take the inverse - * of the scaling factor to double precision. - */ - dx = 1.0/factor; - dy = 1.0/factor; + /* neighboring tile downward: */ + for (i=1; i <= run; i++) { + if ((y+1) < ntiles_y + && tile_region[n-i].bot_diff) { + m = (x-i) + (y+1) * ntiles_x; + if (! tile_has_diff[m]) { + tile_has_diff[m] = 2; + } + } + } - /* - * There is some speedup if the pixel weights are constant, so - * let's special case these. - * - * If scale = 1/n and n divides Nx and Ny, the pixel weights - * are constant (e.g. 1/2 => equal on 2x2 square). - */ - if (factor != last_factor || Nx != last_Nx || Ny != last_Ny) { - constant_weights = -1; - mag_int = -1; - last_Nx = Nx; - last_Ny = Ny; - last_factor = factor; + /* neighboring tile to right: */ + if (((x-1)+1) < ntiles_x + && tile_region[n-1].right_diff) { + m = ((x-1)+1) + y * ntiles_x; + if (! tile_has_diff[m]) { + tile_has_diff[m] = 2; + } + + /* note that this starts a new run */ + in_run = 1; + run = 1; + } else { + in_run = 0; + run = 0; + } + } + } } + return diffs; +} - if (constant_weights < 0) { - int n = 0; +/* + * Here starts a bunch of heuristics to guess/detect changed tiles. + * They are: + * copy_tiles_backward_pass, fill_tile_gaps/gap_try, grow_islands/island_try + */ - constant_weights = 0; - mag_int = 0; +/* + * Try to predict whether the upward and/or leftward tile has been modified. + * copy_all_tiles() has already done downward and rightward tiles. + */ +static int copy_tiles_backward_pass(void) { + int x, y, n, m; + int diffs = 0, ct; - for (i = 2; i<=128; i++) { - double test = ((double) 1)/ i; - double diff, eps = 1.0e-7; - diff = factor - test; - if (-eps < diff && diff < eps) { - n = i; - break; + for (y = ntiles_y - 1; y >= 0; y--) { + for (x = ntiles_x - 1; x >= 0; x--) { + n = x + y * ntiles_x; /* number of this tile */ + + if (! tile_has_diff[n]) { + continue; + } + + m = x + (y-1) * ntiles_x; /* neighboring tile upward */ + + if (y >= 1 && ! tile_has_diff[m] && tile_region[n].top_diff) { + if (! tile_tried[m]) { + tile_has_diff[m] = 2; + ct = copy_tiles(x, y-1, 1); + if (ct < 0) return ct; /* fatal */ } } - if (! blend || ! shrink || interpolate) { - ; - } else if (n != 0) { - if (Nx % n == 0 && Ny % n == 0) { - static int didmsg = 0; - if (mark && ! didmsg) { - didmsg = 1; - rfbLog("scale_and_mark_rect: using " - "constant pixel weight speedup " - "for 1/%d\n", n); - } - constant_weights = 1; + + m = (x-1) + y * ntiles_x; /* neighboring tile to left */ + + if (x >= 1 && ! tile_has_diff[m] && tile_region[n].left_diff) { + if (! tile_tried[m]) { + tile_has_diff[m] = 2; + ct = copy_tiles(x-1, y, 1); + if (ct < 0) return ct; /* fatal */ } } + } + } + for (n=0; n < ntiles; n++) { + if (tile_has_diff[n]) { + diffs++; + } + } + return diffs; +} + +static int copy_tiles_additional_pass(void) { + int x, y, n; + int diffs = 0, ct; - n = 0; - for (i = 2; i<=32; i++) { - double test = (double) i; - double diff, eps = 1.0e-7; - diff = factor - test; - if (-eps < diff && diff < eps) { - n = i; - break; + for (y=0; y < ntiles_y; y++) { + for (x=0; x < ntiles_x; x++) { + n = x + y * ntiles_x; /* number of this tile */ + + if (! tile_has_diff[n]) { + continue; } - } - if (! blend && factor > 1.0 && n) { - mag_int = n; + if (tile_copied[n]) { + continue; + } + + ct = copy_tiles(x, y, 1); + if (ct < 0) return ct; /* fatal */ } } - - if (mark && factor > 1.0 && blend) { - /* - * kludge: correct for interpolating blurring leaking - * up or left 1 destination pixel. - */ - if (X1 > 0) X1--; - if (Y1 > 0) Y1--; + for (n=0; n < ntiles; n++) { + if (tile_has_diff[n]) { + diffs++; + } } + return diffs; +} - /* - * find the extent of the change the input rectangle induces in - * the scaled framebuffer. - */ +static int gap_try(int x, int y, int *run, int *saw, int along_x) { + int n, m, i, xt, yt, ct; - /* Left edges: find largest i such that i * dx <= X1 */ - i1 = FLOOR(X1/dx); + n = x + y * ntiles_x; - /* Right edges: find smallest i such that (i+1) * dx >= X2+1 */ - i2 = CEIL( (X2+1)/dx ) - 1; + if (! tile_has_diff[n]) { + if (*saw) { + (*run)++; /* extend the gap run. */ + } + return 0; + } + if (! *saw || *run == 0 || *run > gaps_fill) { + *run = 0; /* unacceptable run. */ + *saw = 1; + return 0; + } - /* To be safe, correct any overflows: */ - i1 = nfix(i1, nx); - i2 = nfix(i2, nx) + 1; /* add 1 to make a rectangle upper boundary */ + for (i=1; i <= *run; i++) { /* iterate thru the run. */ + if (along_x) { + xt = x - i; + yt = y; + } else { + xt = x; + yt = y - i; + } - /* Repeat above for y direction: */ - j1 = FLOOR(Y1/dy); - j2 = CEIL( (Y2+1)/dy ) - 1; + m = xt + yt * ntiles_x; + if (tile_tried[m]) { /* do not repeat tiles */ + continue; + } - j1 = nfix(j1, ny); - j2 = nfix(j2, ny) + 1; + ct = copy_tiles(xt, yt, 1); + if (ct < 0) return ct; /* fatal */ + } + *run = 0; + *saw = 1; + return 1; +} - /* special case integer magnification with no blending */ - if (mark && ! blend && mag_int && Bpp != 3) { - int jmin, jmax, imin, imax; +/* + * Look for small gaps of unchanged tiles that may actually contain changes. + * E.g. when paging up and down in a web broswer or terminal there can + * be a distracting delayed filling in of such gaps. gaps_fill is the + * tweak parameter that sets the width of the gaps that are checked. + * + * BTW, grow_islands() is actually pretty successful at doing this too... + */ +static int fill_tile_gaps(void) { + int x, y, run, saw; + int n, diffs = 0, ct; - /* outer loop over *source* pixels */ - for (J=Y1; J < Y2; J++) { - jmin = J * mag_int; - jmax = jmin + mag_int; - for (I=X1; I < X2; I++) { - /* extract value */ - src = src_fb + J*src_bytes_per_line + I*Bpp; - if (Bpp == 4) { - ui = *((unsigned int *)src); - } else if (Bpp == 2) { - us = *((unsigned short *)src); - } else if (Bpp == 1) { - uc = *((unsigned char *)src); - } - imin = I * mag_int; - imax = imin + mag_int; - /* inner loop over *dest* pixels */ - for (j=jmin; j Ny - 1) { - /* can go over with dy = 1/scale_fac */ - y1 = Ny - 1; + for (n=0; n < ntiles; n++) { + if (tile_has_diff[n]) { + diffs++; } - y2 = y1 + dy; /* bottom edge */ + } + return diffs; +} - /* Find main fb indices covered by this dest pixel: */ - J1 = (int) FLOOR(y1); - J1 = nfix(J1, Ny); +static int island_try(int x, int y, int u, int v, int *run) { + int n, m, ct; - if (shrink && ! interpolate) { - J2 = (int) CEIL(y2) - 1; - J2 = nfix(J2, Ny); - } else { - J2 = J1 + 1; /* simple interpolation */ - ddy = y1 - J1; - } + n = x + y * ntiles_x; + m = u + v * ntiles_x; - /* destination char* pointer: */ - dest = dst_fb + j*dst_bytes_per_line + i1*Bpp; - - for (i=i1; i Nx - 1) { - /* can go over with dx = 1/scale_fac */ - x1 = Nx - 1; - } - x2 = x1 + dx; /* right edge */ + if (tile_has_diff[n] && ! tile_has_diff[m]) { + /* found a discontinuity */ - cnt++; + if (tile_tried[m]) { + return 0; + } else if (*run < grow_fill) { + return 0; + } - /* Find main fb indices covered by this dest pixel: */ - I1 = (int) FLOOR(x1); - if (I1 >= Nx) I1 = Nx - 1; + ct = copy_tiles(u, v, 1); + if (ct < 0) return ct; /* fatal */ + } + return 1; +} - if (! blend && use_noblend_shortcut) { - /* - * The noblend case involves no weights, - * and 1 pixel, so just copy the value - * directly. - */ - src = src_fb + J1*src_bytes_per_line + I1*Bpp; - if (Bpp == 4) { - *((unsigned int *)dest) - = *((unsigned int *)src); - } else if (Bpp == 2) { - *((unsigned short *)dest) - = *((unsigned short *)src); - } else if (Bpp == 1) { - *(dest) = *(src); - } else if (Bpp == 3) { - /* rare case */ - for (k=0; k<=2; k++) { - *(dest+k) = *(src+k); - } - } - dest += Bpp; - continue; - } - - if (shrink && ! interpolate) { - I2 = (int) CEIL(x2) - 1; - if (I2 >= Nx) I2 = Nx - 1; - } else { - I2 = I1 + 1; /* simple interpolation */ - ddx = x1 - I1; - } +/* + * Scan looking for discontinuities in tile_has_diff[]. Try to extend + * the boundary of the discontinuity (i.e. make the island larger). + * Vertical scans are skipped since they do not seem to yield much... + */ +static int grow_islands(void) { + int x, y, n, run; + int diffs = 0, ct; + + /* + * n.b. the way we scan here should keep an extension going, + * and so also fill in gaps effectively... + */ - /* Zero out accumulators for next pixel average: */ - for (b=0; b<4; b++) { - pixave[b] = 0.0; /* for RGB weighted sums */ - } + /* left to right: */ + for (y=0; y < ntiles_y; y++) { + run = 0; + for (x=0; x <= ntiles_x - 2; x++) { + ct = island_try(x, y, x+1, y, &run); + if (ct < 0) return ct; /* fatal */ + } + } + /* right to left: */ + for (y=0; y < ntiles_y; y++) { + run = 0; + for (x = ntiles_x - 1; x >= 1; x--) { + ct = island_try(x, y, x-1, y, &run); + if (ct < 0) return ct; /* fatal */ + } + } + for (n=0; n < ntiles; n++) { + if (tile_has_diff[n]) { + diffs++; + } + } + return diffs; +} - /* - * wtot is for accumulating the total weight. - * It should always sum to 1/(scale_fac * scale_fac). - */ - wtot = 0.0; +/* + * Fill the framebuffer with zeros for each blackout region + */ +static void blackout_regions(void) { + int i; + for (i=0; i < blackouts; i++) { + zero_fb(blackr[i].x1, blackr[i].y1, blackr[i].x2, blackr[i].y2); + } +} - /* - * Loop over source pixels covered by this dest pixel. - * - * These "extra" loops over "J" and "I" make - * the cache/cacheline performance unclear. - * For example, will the data brought in from - * src for j, i, and J=0 still be in the cache - * after the J > 0 data have been accessed and - * we are at j, i+1, J=0? The stride in J is - * main_bytes_per_line, and so ~4 KB. - * - * Typical case when shrinking are 2x2 loop, so - * just two lines to worry about. - */ - for (J=J1; J<=J2; J++) { - /* see comments for I, x1, x2, etc. below */ - if (constant_weights) { - ; - } else if (! blend) { - if (J != J1) { - continue; - } - wy = 1.0; +/* + * copy the whole X screen to the rfb framebuffer. For a large enough + * number of changed tiles, this is faster than tiles scheme at retrieving + * the info from the X server. Bandwidth to client and compression time + * are other issues... use -fs 1.0 to disable. + */ +int copy_screen(void) { + int pixelsize = bpp/8; + char *fbp; + int i, y, block_size; - /* interpolation scheme: */ - } else if (! shrink || interpolate) { - if (J >= Ny) { - continue; - } else if (J == J1) { - wy = 1.0 - ddy; - } else if (J != J1) { - wy = ddy; - } + if (! fs_factor) { + return 0; + } - /* integration scheme: */ - } else if (J < y1) { - wy = J+1 - y1; - } else if (J+1 > y2) { - wy = y2 - J; - } else { - wy = 1.0; - } + block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize); - src = src_fb + J*src_bytes_per_line + I1*Bpp; + if (! main_fb) { + return 0; + } + fbp = main_fb; + y = 0; - for (I=I1; I<=I2; I++) { + X_LOCK; - /* Work out the weight: */ + /* screen may be too big for 1 shm area, so broken into fs_factor */ + for (i=0; i < fs_factor; i++) { + XRANDR_SET_TRAP_RET(-1, "copy_screen-set"); + copy_image(fullscreen, 0, y, 0, 0); + XRANDR_CHK_TRAP_RET(-1, "copy_screen-chk"); - if (constant_weights) { - ; - } else if (! blend) { - /* - * Ugh, PseudoColor colormap is - * bad news, to avoid random - * colors just take the first - * pixel. Or user may have - * specified :nb to fraction. - * The :fb will force blending - * for this case. - */ - if (I != I1) { - continue; - } - wx = 1.0; + memcpy(fbp, fullscreen->data, (size_t) block_size); - /* interpolation scheme: */ - } else if (! shrink || interpolate) { - if (I >= Nx) { - continue; /* off edge */ - } else if (I == I1) { - wx = 1.0 - ddx; - } else if (I != I1) { - wx = ddx; - } + y += dpy_y / fs_factor; + fbp += block_size; + } - /* integration scheme: */ - } else if (I < x1) { - /* - * source left edge (I) to the - * left of dest left edge (x1): - * fractional weight - */ - wx = I+1 - x1; - } else if (I+1 > x2) { - /* - * source right edge (I+1) to the - * right of dest right edge (x2): - * fractional weight - */ - wx = x2 - I; - } else { - /* - * source edges (I and I+1) completely - * inside dest edges (x1 and x2): - * full weight - */ - wx = 1.0; - } + X_UNLOCK; - w = wx * wy; - wtot += w; + if (blackouts) { + blackout_regions(); + } - /* - * We average the unsigned char value - * instead of char value: otherwise - * the minimum (char 0) is right next - * to the maximum (char -1)! This way - * they are spread between 0 and 255. - */ - if (Bpp == 4) { - /* unroll the loops, can give 20% */ - pixave[0] += w * - ((unsigned char) *(src )); - pixave[1] += w * - ((unsigned char) *(src+1)); - pixave[2] += w * - ((unsigned char) *(src+2)); - pixave[3] += w * - ((unsigned char) *(src+3)); - } else if (Bpp == 2) { - /* - * 16bpp: trickier with green - * split over two bytes, so we - * use the masks: - */ - us = *((unsigned short *) src); - pixave[0] += w*(us & main_red_mask); - pixave[1] += w*(us & main_green_mask); - pixave[2] += w*(us & main_blue_mask); - } else if (Bpp == 1) { - pixave[0] += w * - ((unsigned char) *(src)); - } else { - for (b=0; bdata, (size_t) block_size); + + y += dpy_y / fs_factor; + fbp += block_size; } - if (! screen->serverFormat.trueColour) { - /* - * PseudoColor colormap... blending leads to random colors. - * User can override with ":fb" - */ - if (scaling_blend == 1) { - /* :fb option sets it to 2 */ - if (default_visual->class == StaticGray) { - /* - * StaticGray can be blended OK, otherwise - * user can disable with :nb - */ - ; - } else { - scaling_blend = 0; - } - } + + X_UNLOCK; + dt = dtime(&dt); + if (first) { + rfbLog("copy_snap: time for -snapfb snapshot: %.3f sec\n", dt); + first = 0; } - scale_rect(scale_fac, scaling_blend, scaling_interpolate, bpp/8, - main_fb, main_bytes_per_line, rfb_fb, rfb_bytes_per_line, - dpy_x, dpy_y, scaled_x, scaled_y, X1, Y1, X2, Y2, 1); + return 0; } -void mark_rect_as_modified(int x1, int y1, int x2, int y2, int force) { - if (damage_time != 0) { - /* - * This is not XDAMAGE, rather a hack for testing - * where we allow the framebuffer to be corrupted for - * damage_delay seconds. - */ - int debug = 0; - if (time(0) > damage_time + damage_delay) { - if (! quiet) { - rfbLog("damaging turned off.\n"); - } - damage_time = 0; - damage_delay = 0; - } else { - if (debug) { - rfbLog("damaging viewer fb by not marking " - "rect: %d,%d,%d,%d\n", x1, y1, x2, y2); - } - return; +/* + * Utilities for managing the "naps" to cut down on amount of polling. + */ +static void nap_set(int tile_cnt) { + int nap_in = nap_ok; + + if (scan_count == 0) { + /* roll up check for all NSCAN scans */ + nap_ok = 0; + if (naptile && nap_diff_count < 2 * NSCAN * naptile) { + /* "2" is a fudge to permit a bit of bg drawing */ + nap_ok = 1; + } + nap_diff_count = 0; + } + if (nap_ok && ! nap_in && use_xdamage) { + if (XD_skip > 0.8 * XD_tot) { + /* X DAMAGE is keeping load low, so skip nap */ + nap_ok = 0; } } - if (rfb_fb == main_fb || force) { - rfbMarkRectAsModified(screen, x1, y1, x2, y2); - } else if (scaling) { - scale_and_mark_rect(x1, y1, x2, y2); + if (show_cursor) { + /* kludge for the up to 4 tiles the mouse patch could occupy */ + if ( tile_cnt > 4) { + last_event = time(0); + } + } else if (tile_cnt != 0) { + last_event = time(0); } } /* - * Notifies libvncserver of a changed hint rectangle. + * split up a long nap to improve the wakeup time */ -void mark_hint(hint_t hint) { - int x = hint.x; - int y = hint.y; - int w = hint.w; - int h = hint.h; +static void nap_sleep(int ms, int split) { + int i, input = got_user_input; - mark_rect_as_modified(x, y, x + w, y + h, 0); + for (i=0; i tile_x * nt ) { - size_x = tile_x * nt; - width1 = tile_x; - width2 = tile_x; - } else { - /* short tile */ - width1 = tile_x; /* internal tile */ - width2 = size_x - (nt - 1) * tile_x; /* right hand tile */ - } + if (screen_blank > 0) { + int dt = (int) (now - last_event); + int ms = 1500; - size_y = dpy_y - y; - if ( size_y > tile_y ) { - size_y = tile_y; + /* if no activity, pause here for a second or so. */ + if (dt > screen_blank) { + nap_sleep(ms, 8); + return; + } + } + if (naptile && nap_ok && tile_cnt < naptile) { + int ms = napfac * waitms; + ms = ms > napmax ? napmax : ms; + if (now - last_input <= 2) { + nap_ok = 0; + } else { + nap_sleep(ms, 1); + } } +} - n = tx + ty * ntiles_x; /* number of the first tile */ +/* + * This is called to avoid a ~20 second timeout in libvncserver. + * May no longer be needed. + */ +static void ping_clients(int tile_cnt) { + static time_t last_send = 0; + time_t now = time(0); - if (blackouts && tile_blackout[n].cover == 2) { - /* - * If there are blackouts and this tile is completely covered - * no need to poll screen or do anything else.. - * n.b. we are int single copy_tile mode: nt=1 - */ - tile_has_diff[n] = 0; - return(0); + if (rfbMaxClientWait < 20000) { + rfbMaxClientWait = 20000; + rfbLog("reset rfbMaxClientWait to %d ms.\n", + rfbMaxClientWait); } + if (tile_cnt) { + last_send = now; + } else if (now - last_send > 1) { + /* Send small heartbeat to client */ + mark_rect_as_modified(0, 0, 1, 1, 1); + last_send = now; + } +} - X_LOCK; - XRANDR_SET_TRAP_RET(-1, "copy_tile-set"); - /* read in the whole tile run at once: */ - copy_image(tile_row[nt], x, y, size_x, size_y); - XRANDR_CHK_TRAP_RET(-1, "copy_tile-chk"); - - X_UNLOCK; +/* + * scan_display() wants to know if this tile can be skipped due to + * blackout regions: (no data compare is done, just a quick geometric test) + */ +static int blackout_line_skip(int n, int x, int y, int rescan, + int *tile_count) { + + if (tile_blackout[n].cover == 2) { + tile_has_diff[n] = 0; + return 1; /* skip it */ - if (blackouts && tile_blackout[n].cover == 1) { - /* - * If there are blackouts and this tile is partially covered - * we should re-black-out the portion. - * n.b. we are int single copy_tile mode: nt=1 - */ - int x1, x2, y1, y2, b; - int w, s, fill = 0; + } else if (tile_blackout[n].cover == 1) { + int w, x1, y1, x2, y2, b, hit = 0; + if (x + NSCAN > dpy_x) { + w = dpy_x - x; + } else { + w = NSCAN; + } for (b=0; b < tile_blackout[n].count; b++) { - char *b_dst = tile_row[nt]->data; - x1 = tile_blackout[n].bo[b].x1 - x; - y1 = tile_blackout[n].bo[b].y1 - y; - x2 = tile_blackout[n].bo[b].x2 - x; - y2 = tile_blackout[n].bo[b].y2 - y; - - w = (x2 - x1) * pixelsize; - s = x1 * pixelsize; - - for (line = 0; line < size_y; line++) { - if (y1 <= line && line < y2) { - memset(b_dst + s, fill, (size_t) w); - } - b_dst += tile_row[nt]->bytes_per_line; + /* n.b. these coords are in full display space: */ + x1 = tile_blackout[n].bo[b].x1; + x2 = tile_blackout[n].bo[b].x2; + y1 = tile_blackout[n].bo[b].y1; + y2 = tile_blackout[n].bo[b].y2; + + if (x2 - x1 < w) { + /* need to cover full width */ + continue; + } + if (y1 <= y && y < y2) { + hit = 1; + break; + } + } + if (hit) { + if (! rescan) { + tile_has_diff[n] = 0; + } else { + *tile_count += tile_has_diff[n]; } + return 1; /* skip */ } } + return 0; /* do not skip */ +} - src = tile_row[nt]->data; - dst = main_fb + y * main_bytes_per_line + x * pixelsize; +static int blackout_line_cmpskip(int n, int x, int y, char *dst, char *src, + int w, int pixelsize) { - s_src = src; - s_dst = dst; + int i, x1, y1, x2, y2, b, hit = 0; + int beg = -1, end = -1; - for (t=1; t <= nt; t++) { - first_line[t] = -1; + if (tile_blackout[n].cover == 0) { + return 0; /* 0 means do not skip it. */ + } else if (tile_blackout[n].cover == 2) { + return 1; /* 1 means skip it. */ } - /* find the first line with difference: */ - w1 = width1 * pixelsize; - w2 = width2 * pixelsize; - - /* foreach line: */ - for (line = 0; line < size_y; line++) { - /* foreach horizontal tile: */ - for (t=1; t <= nt; t++) { - if (first_line[t] != -1) { - continue; - } + /* tile has partial coverage: */ - off = (t-1) * w1; - if (t == nt) { - len = w2; /* possible short tile */ - } else { - len = w1; - } - - if (memcmp(s_dst + off, s_src + off, len)) { - first_line[t] = line; - } + for (i=0; i < w * pixelsize; i++) { + if (*(dst+i) != *(src+i)) { + beg = i/pixelsize; /* beginning difference */ + break; } - s_src += tile_row[nt]->bytes_per_line; - s_dst += main_bytes_per_line; } - - /* see if there were any differences for any tile: */ - first_min = -1; - for (t=1; t <= nt; t++) { - tile_tried[n+(t-1)] = 1; - if (first_line[t] != -1) { - if (first_min == -1 || first_line[t] < first_min) { - first_min = first_line[t]; - } + for (i = w * pixelsize - 1; i >= 0; i--) { + if (*(dst+i) != *(src+i)) { + end = i/pixelsize; /* ending difference */ + break; } } - if (first_min == -1) { - /* no tile has a difference, note this and get out: */ - for (t=1; t <= nt; t++) { - tile_has_diff[n+(t-1)] = 0; + if (beg < 0 || end < 0) { + /* problem finding range... */ + return 0; + } + + /* loop over blackout rectangles: */ + for (b=0; b < tile_blackout[n].count; b++) { + + /* y in full display space: */ + y1 = tile_blackout[n].bo[b].y1; + y2 = tile_blackout[n].bo[b].y2; + + /* x relative to tile origin: */ + x1 = tile_blackout[n].bo[b].x1 - x; + x2 = tile_blackout[n].bo[b].x2 - x; + + if (y1 > y || y >= y2) { + continue; } - return(0); - } else { - /* - * at least one tile has a difference. make sure info - * is recorded (e.g. sometimes we guess tiles and they - * came in with tile_has_diff 0) - */ - for (t=1; t <= nt; t++) { - if (first_line[t] == -1) { - tile_has_diff[n+(t-1)] = 0; - } else { - tile_has_diff[n+(t-1)] = 1; - } + if (x1 <= beg && end <= x2) { + hit = 1; + break; } } + if (hit) { + return 1; + } else { + return 0; + } +} - m_src = src + (tile_row[nt]->bytes_per_line * size_y); - m_dst = dst + (main_bytes_per_line * size_y); - - for (t=1; t <= nt; t++) { - last_line[t] = first_line[t]; +/* + * For the subwin case follows the window if it is moved. + */ +void set_offset(void) { + Window w; + if (! subwin) { + return; } + X_LOCK; + XTranslateCoordinates(dpy, window, rootwin, 0, 0, &off_x, &off_y, &w); + X_UNLOCK; +} - /* find the last line with difference: */ - w1 = width1 * pixelsize; - w2 = width2 * pixelsize; +/* + * Loop over 1-pixel tall horizontal scanlines looking for changes. + * Record the changes in tile_has_diff[]. Scanlines in the loop are + * equally spaced along y by NSCAN pixels, but have a slightly random + * starting offset ystart ( < NSCAN ) from scanlines[]. + */ +static int scan_display(int ystart, int rescan) { + char *src, *dst; + int pixelsize = bpp/8; + int x, y, w, n; + int tile_count = 0; + int nodiffs = 0, diff_hint; - /* foreach line: */ - for (line = size_y - 1; line > first_min; line--) { + y = ystart; - m_src -= tile_row[nt]->bytes_per_line; - m_dst -= main_bytes_per_line; + if (! main_fb) { + rfbLog("scan_display: no main_fb!\n"); + return 0; + } - /* foreach tile: */ - for (t=1; t <= nt; t++) { - if (first_line[t] == -1 - || last_line[t] != first_line[t]) { - /* tile has no changes or already done */ - continue; - } + while (y < dpy_y) { - off = (t-1) * w1; - if (t == nt) { - len = w2; /* possible short tile */ - } else { - len = w1; - } - if (memcmp(m_dst + off, m_src + off, len)) { - last_line[t] = line; + if (use_xdamage) { + XD_tot++; + if (xdamage_hint_skip(y)) { + XD_skip++; + y += NSCAN; + continue; } } - } - - /* - * determine the farthest down last changed line - * will be used below to limit our memcpy() to the framebuffer. - */ - last_max = -1; - for (t=1; t <= nt; t++) { - if (first_line[t] == -1) { - continue; - } - if (last_max == -1 || last_line[t] > last_max) { - last_max = last_line[t]; - } - } - /* look for differences on left and right hand edges: */ - for (t=1; t <= nt; t++) { - left_diff[t] = 0; - right_diff[t] = 0; - } + /* grab the horizontal scanline from the display: */ + X_LOCK; + XRANDR_SET_TRAP_RET(-1, "scan_display-set"); + copy_image(scanline, 0, y, 0, 0); + XRANDR_CHK_TRAP_RET(-1, "scan_display-chk"); + X_UNLOCK; - h_src = src; - h_dst = dst; + /* for better memory i/o try the whole line at once */ + src = scanline->data; + dst = main_fb + y * main_bytes_per_line; - w1 = width1 * pixelsize; - w2 = width2 * pixelsize; + if (! memcmp(dst, src, main_bytes_per_line)) { + /* no changes anywhere in scan line */ + nodiffs = 1; + if (! rescan) { + y += NSCAN; + continue; + } + } - dx1 = (width1 - tile_fuzz) * pixelsize; - dx2 = (width2 - tile_fuzz) * pixelsize; - dw = tile_fuzz * pixelsize; + x = 0; + while (x < dpy_x) { + n = (x/tile_x) + (y/tile_y) * ntiles_x; + diff_hint = 0; - /* foreach line: */ - for (line = 0; line < size_y; line++) { - /* foreach tile: */ - for (t=1; t <= nt; t++) { - if (first_line[t] == -1) { - /* tile has no changes at all */ - continue; + if (blackouts) { + if (blackout_line_skip(n, x, y, rescan, + &tile_count)) { + x += NSCAN; + continue; + } + } + + if (rescan) { + if (nodiffs || tile_has_diff[n]) { + tile_count += tile_has_diff[n]; + x += NSCAN; + continue; + } + } else if (xdamage_tile_count && + tile_has_xdamage_diff[n]) { + tile_has_xdamage_diff[n] = 2; + diff_hint = 1; } - off = (t-1) * w1; - if (t == nt) { - dx = dx2; /* possible short tile */ - if (dx <= 0) { - break; - } + /* set ptrs to correspond to the x offset: */ + src = scanline->data + x * pixelsize; + dst = main_fb + y * main_bytes_per_line + x * pixelsize; + + /* compute the width of data to be compared: */ + if (x + NSCAN > dpy_x) { + w = dpy_x - x; } else { - dx = dx1; + w = NSCAN; } - if (! left_diff[t] && memcmp(h_dst + off, - h_src + off, dw)) { - left_diff[t] = 1; - } - if (! right_diff[t] && memcmp(h_dst + off + dx, - h_src + off + dx, dw) ) { - right_diff[t] = 1; + if (diff_hint || memcmp(dst, src, w * pixelsize)) { + /* found a difference, record it: */ + if (! blackouts) { + tile_has_diff[n] = 1; + tile_count++; + } else { + if (blackout_line_cmpskip(n, x, y, + dst, src, w, pixelsize)) { + tile_has_diff[n] = 0; + } else { + tile_has_diff[n] = 1; + tile_count++; + } + } } + x += NSCAN; } - h_src += tile_row[nt]->bytes_per_line; - h_dst += main_bytes_per_line; + y += NSCAN; } + return tile_count; +} - /* now finally copy the difference to the rfb framebuffer: */ - s_src = src + tile_row[nt]->bytes_per_line * first_min; - s_dst = dst + main_bytes_per_line * first_min; - for (line = first_min; line <= last_max; line++) { - /* for I/O speed we do not do this tile by tile */ - memcpy(s_dst, s_src, size_x * pixelsize); - s_src += tile_row[nt]->bytes_per_line; - s_dst += main_bytes_per_line; +/* + * toplevel for the scanning, rescanning, and applying the heuristics. + * returns number of changed tiles. + */ +int scan_for_updates(int count_only) { + int i, tile_count, tile_diffs; + int old_copy_tile; + double frac1 = 0.1; /* tweak parameter to try a 2nd scan_display() */ + double frac2 = 0.35; /* or 3rd */ + double frac3 = 0.02; /* do scan_display() again after copy_tiles() */ + for (i=0; i < ntiles; i++) { + tile_has_diff[i] = 0; + tile_has_xdamage_diff[i] = 0; + tile_tried[i] = 0; + tile_copied[i] = 0; } + for (i=0; i < ntiles_y; i++) { + /* could be useful, currently not used */ + tile_row_has_xdamage_diff[i] = 0; + } + xdamage_tile_count = 0; - /* record all the info in the region array for this tile: */ - for (t=1; t <= nt; t++) { - int s = t - 1; + /* + * n.b. this program has only been tested so far with + * tile_x = tile_y = NSCAN = 32! + */ - if (first_line[t] == -1) { - /* tile unchanged */ - continue; - } - tile_region[n+s].first_line = first_line[t]; - tile_region[n+s].last_line = last_line[t]; + if (!count_only) { + scan_count++; + scan_count %= NSCAN; - tile_region[n+s].top_diff = 0; - tile_region[n+s].bot_diff = 0; - if ( first_line[t] < tile_fuzz ) { - tile_region[n+s].top_diff = 1; + /* some periodic maintenance */ + if (subwin) { + set_offset(); /* follow the subwindow */ } - if ( last_line[t] > (size_y - 1) - tile_fuzz ) { - tile_region[n+s].bot_diff = 1; + if (indexed_color && scan_count % 4 == 0) { + /* check for changed colormap */ + set_colormap(0); + } + if (use_xdamage) { + collect_xdamage(scan_count); } - - tile_region[n+s].left_diff = left_diff[t]; - tile_region[n+s].right_diff = right_diff[t]; - - tile_copied[n+s] = 1; } - return(1); -} +#define SCAN_FATAL(x) \ + if (x < 0) { \ + scan_in_progress = 0; \ + fb_copy_in_progress = 0; \ + return 0; \ + } -/* - * The copy_tile() call in the loop below copies the changed tile into - * the rfb framebuffer. Note that copy_tile() sets the tile_region - * struct to have info about the y-range of the changed region and also - * whether the tile edges contain diffs (within distance tile_fuzz). - * - * We use this tile_region info to try to guess if the downward and right - * tiles will have diffs. These tiles will be checked later in the loop - * (since y+1 > y and x+1 > x). - * - * See copy_tiles_backward_pass() for analogous checking upward and - * left tiles. - */ -static int copy_all_tiles(void) { - int x, y, n, m; - int diffs = 0, ct; + /* scan with the initial y to the jitter value from scanlines: */ + scan_in_progress = 1; + tile_count = scan_display(scanlines[scan_count], 0); + SCAN_FATAL(tile_count); - for (y=0; y < ntiles_y; y++) { - for (x=0; x < ntiles_x; x++) { - n = x + y * ntiles_x; + if (count_only) { + scan_in_progress = 0; + fb_copy_in_progress = 0; + return tile_count; + } - if (tile_has_diff[n]) { - ct = copy_tiles(x, y, 1); - if (ct < 0) return ct; /* fatal */ - } - if (! tile_has_diff[n]) { - /* - * n.b. copy_tiles() may have detected - * no change and reset tile_has_diff to 0. - */ + if (xdamage_tile_count) { + /* pick up "known" damaged tiles we missed in scan_display() */ + for (i=0; i < ntiles; i++) { + if (tile_has_diff[i]) { continue; } - diffs++; - - /* neighboring tile downward: */ - if ( (y+1) < ntiles_y && tile_region[n].bot_diff) { - m = x + (y+1) * ntiles_x; - if (! tile_has_diff[m]) { - tile_has_diff[m] = 2; - } - } - /* neighboring tile to right: */ - if ( (x+1) < ntiles_x && tile_region[n].right_diff) { - m = (x+1) + y * ntiles_x; - if (! tile_has_diff[m]) { - tile_has_diff[m] = 2; - } + if (tile_has_xdamage_diff[i] == 1) { + tile_has_xdamage_diff[i] = 2; + tile_has_diff[i] = 1; + tile_count++; } } } - return diffs; -} -/* - * Routine analogous to copy_all_tiles() above, but for horizontal runs - * of adjacent changed tiles. - */ -static int copy_all_tile_runs(void) { - int x, y, n, m, i; - int diffs = 0, ct; - int in_run = 0, run = 0; - int ntave = 0, ntcnt = 0; + nap_set(tile_count); - for (y=0; y < ntiles_y; y++) { - for (x=0; x < ntiles_x + 1; x++) { - n = x + y * ntiles_x; + if (fs_factor && frac1 >= fs_frac) { + /* make frac1 < fs_frac if fullscreen updates are enabled */ + frac1 = fs_frac/2.0; + } - if (x != ntiles_x && tile_has_diff[n]) { - in_run = 1; - run++; - } else { - if (! in_run) { - in_run = 0; - run = 0; - continue; - } - ct = copy_tiles(x - run, y, run); - if (ct < 0) return ct; /* fatal */ + if (tile_count > frac1 * ntiles) { + /* + * many tiles have changed, so try a rescan (since it should + * be short compared to the many upcoming copy_tiles() calls) + */ - ntcnt++; - ntave += run; - diffs += run; + /* this check is done to skip the extra scan_display() call */ + if (! fs_factor || tile_count <= fs_frac * ntiles) { + int cp, tile_count_old = tile_count; + + /* choose a different y shift for the 2nd scan: */ + cp = (NSCAN - scan_count) % NSCAN; - /* neighboring tile downward: */ - for (i=1; i <= run; i++) { - if ((y+1) < ntiles_y - && tile_region[n-i].bot_diff) { - m = (x-i) + (y+1) * ntiles_x; - if (! tile_has_diff[m]) { - tile_has_diff[m] = 2; - } - } - } + tile_count = scan_display(scanlines[cp], 1); + SCAN_FATAL(tile_count); - /* neighboring tile to right: */ - if (((x-1)+1) < ntiles_x - && tile_region[n-1].right_diff) { - m = ((x-1)+1) + y * ntiles_x; - if (! tile_has_diff[m]) { - tile_has_diff[m] = 2; - } - - /* note that this starts a new run */ - in_run = 1; - run = 1; - } else { - in_run = 0; - run = 0; - } + if (tile_count >= (1 + frac2) * tile_count_old) { + /* on a roll... do a 3rd scan */ + cp = (NSCAN - scan_count + 7) % NSCAN; + tile_count = scan_display(scanlines[cp], 1); + SCAN_FATAL(tile_count); + } + } + scan_in_progress = 0; + + /* + * At some number of changed tiles it is better to just + * copy the full screen at once. I.e. time = c1 + m * r1 + * where m is number of tiles, r1 is the copy_tiles() + * time, and c1 is the scan_display() time: for some m + * it crosses the full screen update time. + * + * We try to predict that crossover with the fs_frac + * fudge factor... seems to be about 1/2 the total number + * of tiles. n.b. this ignores network bandwidth, + * compression time etc... + * + * Use -fs 1.0 to disable on slow links. + */ + if (fs_factor && tile_count > fs_frac * ntiles) { + int cs; + fb_copy_in_progress = 1; + cs = copy_screen(); + fb_copy_in_progress = 0; + SCAN_FATAL(cs); + if (use_threads && pointer_mode != 1) { + pointer(-1, 0, 0, NULL); } + nap_check(tile_count); + return tile_count; } } - return diffs; -} - -/* - * Here starts a bunch of heuristics to guess/detect changed tiles. - * They are: - * copy_tiles_backward_pass, fill_tile_gaps/gap_try, grow_islands/island_try - */ - -/* - * Try to predict whether the upward and/or leftward tile has been modified. - * copy_all_tiles() has already done downward and rightward tiles. - */ -static int copy_tiles_backward_pass(void) { - int x, y, n, m; - int diffs = 0, ct; + scan_in_progress = 0; - for (y = ntiles_y - 1; y >= 0; y--) { - for (x = ntiles_x - 1; x >= 0; x--) { - n = x + y * ntiles_x; /* number of this tile */ + /* copy all tiles with differences from display to rfb framebuffer: */ + fb_copy_in_progress = 1; - if (! tile_has_diff[n]) { - continue; - } + if (single_copytile || tile_shm_count < ntiles_x) { + /* + * Old way, copy I/O one tile at a time. + */ + old_copy_tile = 1; + } else { + /* + * New way, does runs of horizontal tiles at once. + * Note that below, for simplicity, the extra tile finding + * (e.g. copy_tiles_backward_pass) is done the old way. + */ + old_copy_tile = 0; + } + if (old_copy_tile) { + tile_diffs = copy_all_tiles(); + } else { + tile_diffs = copy_all_tile_runs(); + } + SCAN_FATAL(tile_diffs); - m = x + (y-1) * ntiles_x; /* neighboring tile upward */ + /* + * This backward pass for upward and left tiles complements what + * was done in copy_all_tiles() for downward and right tiles. + */ + tile_diffs = copy_tiles_backward_pass(); + SCAN_FATAL(tile_diffs); - if (y >= 1 && ! tile_has_diff[m] && tile_region[n].top_diff) { - if (! tile_tried[m]) { - tile_has_diff[m] = 2; - ct = copy_tiles(x, y-1, 1); - if (ct < 0) return ct; /* fatal */ - } - } + if (tile_diffs > frac3 * ntiles) { + /* + * we spent a lot of time in those copy_tiles, run + * another scan, maybe more of the screen changed. + */ + int cp = (NSCAN - scan_count + 13) % NSCAN; - m = (x-1) + y * ntiles_x; /* neighboring tile to left */ + scan_in_progress = 1; + tile_count = scan_display(scanlines[cp], 1); + SCAN_FATAL(tile_count); + scan_in_progress = 0; - if (x >= 1 && ! tile_has_diff[m] && tile_region[n].left_diff) { - if (! tile_tried[m]) { - tile_has_diff[m] = 2; - ct = copy_tiles(x-1, y, 1); - if (ct < 0) return ct; /* fatal */ - } - } - } + tile_diffs = copy_tiles_additional_pass(); + SCAN_FATAL(tile_diffs); } - for (n=0; n < ntiles; n++) { - if (tile_has_diff[n]) { - diffs++; - } + + /* Given enough tile diffs, try the islands: */ + if (grow_fill && tile_diffs > 4) { + tile_diffs = grow_islands(); } - return diffs; -} + SCAN_FATAL(tile_diffs); -static int copy_tiles_additional_pass(void) { - int x, y, n; - int diffs = 0, ct; + /* Given enough tile diffs, try the gaps: */ + if (gaps_fill && tile_diffs > 4) { + tile_diffs = fill_tile_gaps(); + } + SCAN_FATAL(tile_diffs); - for (y=0; y < ntiles_y; y++) { - for (x=0; x < ntiles_x; x++) { - n = x + y * ntiles_x; /* number of this tile */ + fb_copy_in_progress = 0; + if (use_threads && pointer_mode != 1) { + /* + * tell the pointer handler it can process any queued + * pointer events: + */ + pointer(-1, 0, 0, NULL); + } - if (! tile_has_diff[n]) { - continue; - } - if (tile_copied[n]) { - continue; + if (blackouts) { + /* ignore any diffs in completely covered tiles */ + int x, y, n; + for (y=0; y < ntiles_y; y++) { + for (x=0; x < ntiles_x; x++) { + n = x + y * ntiles_x; + if (tile_blackout[n].cover == 2) { + tile_has_diff[n] = 0; + } } - - ct = copy_tiles(x, y, 1); - if (ct < 0) return ct; /* fatal */ } } - for (n=0; n < ntiles; n++) { - if (tile_has_diff[n]) { - diffs++; - } + + hint_updates(); /* use krfb/x0rfbserver hints algorithm */ + + /* Work around threaded rfbProcessClientMessage() calls timeouts */ + if (use_threads) { + ping_clients(tile_diffs); } - return diffs; + + + nap_check(tile_diffs); + return tile_diffs; } -static int gap_try(int x, int y, int *run, int *saw, int along_x) { - int n, m, i, xt, yt, ct; +/* -- gui.c -- */ +#if SMALL_FOOTPRINT +char gui_code[] = ""; +#else +#include "tkx11vnc.h" +#endif - n = x + y * ntiles_x; +void run_gui(char *gui_xdisplay, int connect_to_x11vnc, int simple_gui, + pid_t parent) { + char *x11vnc_xdisplay = NULL; + char extra_path[] = ":/usr/local/bin:/usr/bin/X11:/usr/sfw/bin" + ":/usr/X11R6/bin:/usr/openwin/bin:/usr/dt/bin"; + char cmd[100]; + char *wish = NULL, *orig_path, *full_path, *tpath, *p; + char *old_xauth = NULL; + int try_max = 4, sleep = 300; + pid_t mypid = getpid(); + FILE *pipe, *tmpf; - if (! tile_has_diff[n]) { - if (*saw) { - (*run)++; /* extend the gap run. */ - } - return 0; + if (*gui_code == '\0') { + rfbLog("gui not available in this program.\n"); + exit(0); } - if (! *saw || *run == 0 || *run > gaps_fill) { - *run = 0; /* unacceptable run. */ - *saw = 1; - return 0; + if (getenv("DISPLAY") != NULL) { + /* worst case */ + x11vnc_xdisplay = strdup(getenv("DISPLAY")); } - - for (i=1; i <= *run; i++) { /* iterate thru the run. */ - if (along_x) { - xt = x - i; - yt = y; + if (use_dpy) { + /* better */ + x11vnc_xdisplay = strdup(use_dpy); + } + if (connect_to_x11vnc) { + int rc, i; + rfbLogEnable(0); + if (! client_connect_file) { + if (getenv("XAUTHORITY") != NULL) { + old_xauth = strdup(getenv("XAUTHORITY")); + } else { + old_xauth = strdup(""); + } + dpy = XOpenDisplay(x11vnc_xdisplay); + if (! dpy && auth_file) { + set_env("XAUTHORITY", auth_file); + dpy = XOpenDisplay(x11vnc_xdisplay); + } + if (! dpy && ! x11vnc_xdisplay) { + /* worstest case */ + x11vnc_xdisplay = strdup(":0"); + dpy = XOpenDisplay(x11vnc_xdisplay); + } + if (! dpy) { + fprintf(stderr, "gui: could not open x11vnc " + "display: %s\n", NONUL(x11vnc_xdisplay)); + exit(1); + } + scr = DefaultScreen(dpy); + rootwin = RootWindow(dpy, scr); + initialize_vnc_connect_prop(); + } + usleep(1000*1000); + fprintf(stderr, "\n"); + for (i=0; i 0) { + ; /* parent */ + } else if (p == -1) { + fprintf(stderr, "could not fork\n"); + perror("fork"); + clean_up_exit(1); + } else { + run_gui(gui_xdisplay, connect_to_x11vnc, simple_gui, + parent); + exit(1); } +#else + fprintf(stderr, "system does not support fork: start " + "x11vnc in the gui.\n"); + start_x11vnc = 0; +#endif } - /* right to left: */ - for (y=0; y < ntiles_y; y++) { - run = 0; - for (x = ntiles_x - 1; x >= 1; x--) { - ct = island_try(x, y, x-1, y, &run); - if (ct < 0) return ct; /* fatal */ - } + if (!start_x11vnc) { + run_gui(gui_xdisplay, connect_to_x11vnc, simple_gui, 0); + exit(1); } - for (n=0; n < ntiles; n++) { - if (tile_has_diff[n]) { - diffs++; - } + if (old_xauth) { + set_env("XAUTHORITY", old_xauth); } - return diffs; } +/* -- userinput.c -- */ /* - * Fill the framebuffer with zeros for each blackout region + * user input handling heuristics */ -static void blackout_regions(void) { - int i; - for (i=0; i < blackouts; i++) { - zero_fb(blackr[i].x1, blackr[i].y1, blackr[i].x2, blackr[i].y2); - } -} /* - * copy the whole X screen to the rfb framebuffer. For a large enough - * number of changed tiles, this is faster than tiles scheme at retrieving - * the info from the X server. Bandwidth to client and compression time - * are other issues... use -fs 1.0 to disable. + * For -wireframe: find the direct child of rootwin that has the + * pointer, assume that is the WM frame that contains the application + * (i.e. wm reparents the app toplevel) return frame position and size + * if successful. */ -int copy_screen(void) { - int pixelsize = bpp/8; - char *fbp; - int i, y, block_size; - if (! fs_factor) { +int get_wm_frame_pos(int *px, int *py, int *x, int *y, int *w, int *h, Window *win) { + Window r, c; + XWindowAttributes attr; + Bool ret; + int rootx, rooty, wx, wy; + unsigned int mask; + + ret = XQueryPointer(dpy, rootwin, &r, &c, &rootx, &rooty, &wx, &wy, &mask); + + *win = c; + + /* current pointer position is returned too */ + *px = rootx; + *py = rooty; + + if (!ret || ! c || c == rootwin) { + /* no immediate child */ return 0; } - block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize); - - if (! main_fb) { + /* child window position and size */ + if (! valid_window(c, &attr)) { return 0; } - fbp = main_fb; - y = 0; +#if 0 + XTranslateCoordinates(dpy, c, rootwin, 0, 0, &rootx, &rooty, &c2); + *x = rootx; + *y = rooty; +#endif + *x = attr.x; + *y = attr.y; + *w = attr.width; + *h = attr.height; - X_LOCK; + return 1; +} - /* screen may be too big for 1 shm area, so broken into fs_factor */ - for (i=0; i < fs_factor; i++) { - XRANDR_SET_TRAP_RET(-1, "copy_screen-set"); - copy_image(fullscreen, 0, y, 0, 0); - XRANDR_CHK_TRAP_RET(-1, "copy_screen-chk"); +static int defer_update_nofb = 6; /* defer a shorter time under -nofb */ - memcpy(fbp, fullscreen->data, (size_t) block_size); +int scrollcopyrect_top, scrollcopyrect_bot; +int scrollcopyrect_left, scrollcopyrect_right; +double scr_key_time, scr_key_persist, scr_mouse_time, scr_mouse_persist; - y += dpy_y / fs_factor; - fbp += block_size; +void parse_scroll_copyrect_str(char *scr) { + char *p, *str; + int i; + char *part[10]; + + for (i=0; i<10; i++) { + part[i] = NULL; } - X_UNLOCK; + if (scr == NULL || *scr == '\0') { + return; + } - if (blackouts) { - blackout_regions(); + str = strdup(scr); + + p = strtok(str, ","); + i = 0; + while (p) { + part[i++] = strdup(p); + p = strtok(NULL, ","); } + free(str); - mark_rect_as_modified(0, 0, dpy_x, dpy_y, 0); - return 0; -} -int copy_snap(void) { - int pixelsize = bpp/8; - char *fbp; - int i, y, block_size; - double dt = 0.0; - static int first = 1; + /* + * Top, Bottom, Left, Right tolerances for scrollbar locations. + */ + if ((str = part[0]) != NULL) { + int t, b, l, r; + if (sscanf(str, "%d+%d+%d+%d", &t, &b, &l, &r) == 4) { + scrollcopyrect_top = t; + scrollcopyrect_bot = b; + scrollcopyrect_left = l; + scrollcopyrect_right = r; + } + free(str); + } - if (! fs_factor) { - return 0; + /* key scrolling timing heuristics. */ + if ((str = part[1]) != NULL) { + double t1, t2; + if (sscanf(str, "%lf+%lf", &t1, &t2) == 2) { + scr_key_time = t1; + scr_key_persist = t2; + } + free(str); } - block_size = (dpy_x * (dpy_y/fs_factor) * pixelsize); + /* mouse scrolling timing heuristics. */ + if ((str = part[2]) != NULL) { + double t1, t2; + if (sscanf(str, "%lf+%lf", &t1, &t2) == 2) { + scr_mouse_time = t1; + scr_mouse_persist = t2; + } + free(str); + } +} - if (! snap_fb || ! snap || ! snaprect) { - return 0; +void parse_scroll_copyrect(void) { + parse_scroll_copyrect_str(SCROLL_COPYRECT_PARMS); + if (! scroll_copyrect_str) { + scroll_copyrect_str = strdup(SCROLL_COPYRECT_PARMS); } - fbp = snap_fb; - y = 0; + parse_scroll_copyrect_str(scroll_copyrect_str); +} - dtime(&dt); - X_LOCK; +/* +WIREFRAME_PARMS "0xff,2,0,30+6+6+6,0.05+0.3+2.0,8" +shade,linewidth,percent,T+B+L+R,t1+t2+t3 + */ +#define LW_MAX 8 +unsigned long wireframe_shade; +int wireframe_lw; +double wireframe_frac; +int wireframe_top, wireframe_bot, wireframe_left, wireframe_right; +double wireframe_t1, wireframe_t2, wireframe_t3, wireframe_t4; - /* screen may be too big for 1 shm area, so broken into fs_factor */ - for (i=0; i < fs_factor; i++) { - XRANDR_SET_TRAP_RET(-1, "copy_snap-set"); - copy_image(snaprect, 0, y, 0, 0); - XRANDR_CHK_TRAP_RET(-1, "copy_snap-chk"); +/* + * Parse the gory -wireframe string for parameters. + */ +void parse_wireframe_str(char *wf) { + char *p, *str; + int i; + char *part[10]; - memcpy(fbp, snaprect->data, (size_t) block_size); + for (i=0; i<10; i++) { + part[i] = NULL; + } - y += dpy_y / fs_factor; - fbp += block_size; + if (wf == NULL || *wf == '\0') { + return; } - X_UNLOCK; - dt = dtime(&dt); - if (first) { - rfbLog("copy_snap: time for -snapfb snapshot: %.3f sec\n", dt); - first = 0; + str = strdup(wf); + + /* leading ",", make it start with ignorable string "z" */ + if (*str == ',') { + char *tmp = (char *) malloc(strlen(str)+2); + strcpy(tmp, "z"); + strcat(tmp, str); + free(str); + str = tmp; } - return 0; -} + p = strtok(str, ","); + i = 0; + while (p) { + part[i++] = strdup(p); + p = strtok(NULL, ","); + } + free(str); -/* - * Utilities for managing the "naps" to cut down on amount of polling. - */ -static void nap_set(int tile_cnt) { - int nap_in = nap_ok; + /* Wireframe shade, color, RGB: */ + if ((str = part[0]) != NULL) { + unsigned long n; + int r, g, b, ok = 0; + XColor cdef; + Colormap cmap; + if (dpy && (bpp == 32 || bpp == 16)) { + cmap = DefaultColormap (dpy, scr); + if (XParseColor(dpy, cmap, str, &cdef) && + XAllocColor(dpy, cmap, &cdef)) { + r = cdef.red >> 8; + g = cdef.green >> 8; + b = cdef.blue >> 8; + if (r == 0 && g == 0) { + g = 1; /* need to be > 255 */ + } + n = 0; + n |= (r << main_red_shift); + n |= (g << main_green_shift); + n |= (b << main_blue_shift); + wireframe_shade = n; + ok = 1; + } + } + if (ok) { + ; + } else if (sscanf(str, "0x%lx", &n) == 1) { + wireframe_shade = n; + } else if (sscanf(str, "%ld", &n) == 1) { + wireframe_shade = n; + } else if (sscanf(str, "%lx", &n) == 1) { + wireframe_shade = n; + } + free(str); + } - if (scan_count == 0) { - /* roll up check for all NSCAN scans */ - nap_ok = 0; - if (naptile && nap_diff_count < 2 * NSCAN * naptile) { - /* "2" is a fudge to permit a bit of bg drawing */ - nap_ok = 1; + /* linewidth: # of pixels wide for the wireframe lines */ + if ((str = part[1]) != NULL) { + int n; + if (sscanf(str, "%d", &n) == 1) { + wireframe_lw = n; + if (wireframe_lw < 1) { + wireframe_lw = 1; + } + if (wireframe_lw > LW_MAX) { + wireframe_lw = LW_MAX; + } + } + free(str); + } + + /* percentage cutoff for opaque move/resize (like WM's) */ + if ((str = part[2]) != NULL) { + if (*str == '\0') { + ; + } else if (strchr(str, '.')) { + wireframe_frac = atof(str); + } else { + wireframe_frac = ((double) atoi(str))/100.0; } - nap_diff_count = 0; + free(str); } - if (nap_ok && ! nap_in && use_xdamage) { - if (XD_skip > 0.8 * XD_tot) { - /* X DAMAGE is keeping load low, so skip nap */ - nap_ok = 0; + + /* + * Top, Bottom, Left, Right tolerances to guess the wm frame is + * being grabbed (Top is traditionally bigger, i.e. titlebar): + */ + if ((str = part[3]) != NULL) { + int t, b, l, r; + if (sscanf(str, "%d+%d+%d+%d", &t, &b, &l, &r) == 4) { + wireframe_top = t; + wireframe_bot = b; + wireframe_left = l; + wireframe_right = r; } + free(str); } - if (show_cursor) { - /* kludge for the up to 4 tiles the mouse patch could occupy */ - if ( tile_cnt > 4) { - last_event = time(0); + /* check_wireframe() timing heuristics. */ + if ((str = part[4]) != NULL) { + double t1, t2, t3, t4; + if (sscanf(str, "%lf+%lf+%lf+%lf", &t1, &t2, &t3, &t4) == 4) { + wireframe_t1 = t1; + wireframe_t2 = t2; + wireframe_t3 = t3; + wireframe_t4 = t4; } - } else if (tile_cnt != 0) { - last_event = time(0); + free(str); } } /* - * split up a long nap to improve the wakeup time + * First parse the defaults and apply any user supplied ones (may be a subset) */ -static void nap_sleep(int ms, int split) { - int i, input = got_user_input; - - for (i=0; i 0) { - int dt = (int) (now - last_event); - int ms = 1500; - - /* if no activity, pause here for a second or so. */ - if (dt > screen_blank) { - nap_sleep(ms, 8); - return; +void set_wirecopyrect_mode(char *str) { + char *orig = wireframe_copyrect; + if (str == NULL || *str == '\0') { + wireframe_copyrect = strdup(wireframe_copyrect_default); + } else if (!strcmp(str, "always") || !strcmp(str, "all")) { + wireframe_copyrect = strdup("always"); + } else if (!strcmp(str, "top")) { + wireframe_copyrect = strdup("top"); + } else if (!strcmp(str, "never") || !strcmp(str, "none")) { + wireframe_copyrect = strdup("never"); + } else { + if (! wireframe_copyrect) { + wireframe_copyrect = strdup(wireframe_copyrect_default); } + rfbLog("unknown -wirecopyrect mode: %s, using: %s\n", str, + wireframe_copyrect); } - if (naptile && nap_ok && tile_cnt < naptile) { - int ms = napfac * waitms; - ms = ms > napmax ? napmax : ms; - if (now - last_input <= 2) { - nap_ok = 0; - } else { - nap_sleep(ms, 1); - } + if (orig) { + free(orig); } } /* - * This is called to avoid a ~20 second timeout in libvncserver. - * May no longer be needed. + * Set scroll_copyrect based on desired mode. */ -static void ping_clients(int tile_cnt) { - static time_t last_send = 0; - time_t now = time(0); - - if (rfbMaxClientWait < 20000) { - rfbMaxClientWait = 20000; - rfbLog("reset rfbMaxClientWait to %d ms.\n", - rfbMaxClientWait); +void set_scrollcopyrect_mode(char *str) { + char *orig = scroll_copyrect; + if (str == NULL || *str == '\0') { + scroll_copyrect = strdup(scroll_copyrect_default); + } else if (!strcmp(str, "always") || !strcmp(str, "all") || + !strcmp(str, "both")) { + scroll_copyrect = strdup("always"); + } else if (!strcmp(str, "keys") || !strcmp(str, "keyboard")) { + scroll_copyrect = strdup("keys"); + } else if (!strcmp(str, "mouse") || !strcmp(str, "pointer")) { + scroll_copyrect = strdup("mouse"); + } else if (!strcmp(str, "never") || !strcmp(str, "none")) { + scroll_copyrect = strdup("never"); + } else { + if (! scroll_copyrect) { + scroll_copyrect = strdup(scroll_copyrect_default); + } + rfbLog("unknown -scrollcopyrect mode: %s, using: %s\n", str, + scroll_copyrect); } - if (tile_cnt) { - last_send = now; - } else if (now - last_send > 1) { - /* Send small heartbeat to client */ - mark_rect_as_modified(0, 0, 1, 1, 1); - last_send = now; + if (orig) { + free(orig); } } +typedef struct saveline { + int x0, y0, x1, y1; + int shift; + int vert; + int saved; + char *data; +} saveline_t; + /* - * scan_display() wants to know if this tile can be skipped due to - * blackout regions: (no data compare is done, just a quick geometric test) + * Draw the wireframe box onto the framebuffer. Saves the real + * framebuffer data to some storage lines. Restores previous lines. + * use restore = 1 to clean up (done with animation). + * This works with -scale. */ -static int blackout_line_skip(int n, int x, int y, int rescan, - int *tile_count) { - - if (tile_blackout[n].cover == 2) { - tile_has_diff[n] = 0; - return 1; /* skip it */ - - } else if (tile_blackout[n].cover == 1) { - int w, x1, y1, x2, y2, b, hit = 0; - if (x + NSCAN > dpy_x) { - w = dpy_x - x; - } else { - w = NSCAN; - } - - for (b=0; b < tile_blackout[n].count; b++) { - - /* n.b. these coords are in full display space: */ - x1 = tile_blackout[n].bo[b].x1; - x2 = tile_blackout[n].bo[b].x2; - y1 = tile_blackout[n].bo[b].y1; - y2 = tile_blackout[n].bo[b].y2; +void draw_box(int x, int y, int w, int h, int restore) { + int x0, y0, x1, y1, i, pixelsize = bpp/8; + char *dst, *src; + static saveline_t *save[4]; + static int first = 1, len = 0; + int max = dpy_x > dpy_y ? dpy_x : dpy_y; + int sz, lw = wireframe_lw; + unsigned long shade = wireframe_shade; + int color = 0; + unsigned short us; + unsigned long ul; - if (x2 - x1 < w) { - /* need to cover full width */ - continue; - } - if (y1 <= y && y < y2) { - hit = 1; - break; - } - } - if (hit) { - if (! rescan) { - tile_has_diff[n] = 0; - } else { - *tile_count += tile_has_diff[n]; + if (max > len) { + /* create/resize storage lines: */ + for (i=0; i<4; i++) { + len = max; + if (! first && save[i]) { + if (save[i]->data) { + free(save[i]->data); + } + free(save[i]); } - return 1; /* skip */ - } - } - return 0; /* do not skip */ -} - -static int blackout_line_cmpskip(int n, int x, int y, char *dst, char *src, - int w, int pixelsize) { - - int i, x1, y1, x2, y2, b, hit = 0; - int beg = -1, end = -1; - - if (tile_blackout[n].cover == 0) { - return 0; /* 0 means do not skip it. */ - } else if (tile_blackout[n].cover == 2) { - return 1; /* 1 means skip it. */ - } - - /* tile has partial coverage: */ + save[i] = (saveline_t *) malloc(sizeof(saveline_t)); + save[i]->saved = 0; + sz = (LW_MAX+1)*len*pixelsize; + save[i]->data = (char *)malloc(sz); - for (i=0; i < w * pixelsize; i++) { - if (*(dst+i) != *(src+i)) { - beg = i/pixelsize; /* beginning difference */ - break; - } - } - for (i = w * pixelsize - 1; i >= 0; i--) { - if (*(dst+i) != *(src+i)) { - end = i/pixelsize; /* ending difference */ - break; - } - } - if (beg < 0 || end < 0) { - /* problem finding range... */ - return 0; + /* + * Four types of lines: + * 0) top horizontal + * 1) bottom horizontal + * 2) left vertical + * 3) right vertical + * + * shift means shifted by width or height. + */ + if (i == 0) { + save[i]->vert = 0; + save[i]->shift = 0; + } else if (i == 1) { + save[i]->vert = 0; + save[i]->shift = 1; + } else if (i == 2) { + save[i]->vert = 1; + save[i]->shift = 0; + } else if (i == 3) { + save[i]->vert = 1; + save[i]->shift = 1; + } + } } + first = 0; - /* loop over blackout rectangles: */ - for (b=0; b < tile_blackout[n].count; b++) { - - /* y in full display space: */ - y1 = tile_blackout[n].bo[b].y1; - y2 = tile_blackout[n].bo[b].y2; - - /* x relative to tile origin: */ - x1 = tile_blackout[n].bo[b].x1 - x; - x2 = tile_blackout[n].bo[b].x2 - x; + /* + * restore any saved lines. see below for algorithm and + * how x0, etc. are used. we just reverse those steps. + */ + for (i=0; i<4; i++) { + int s = save[i]->shift; + int yu, y_min = -1, y_max = -1; + int y_start, y_stop, y_step; - if (y1 > y || y >= y2) { + if (! save[i]->saved) { continue; } - if (x1 <= beg && end <= x2) { - hit = 1; - break; + x0 = save[i]->x0; + y0 = save[i]->y0; + x1 = save[i]->x1; + y1 = save[i]->y1; + if (save[i]->vert) { + y_start = y0+lw; + y_stop = y1-lw; + y_step = lw*pixelsize; + } else { + y_start = y0 - s*lw; + y_stop = y_start + lw; + y_step = max*pixelsize; } + for (yu = y_start; yu < y_stop; yu++) { + if (x0 == x1) { + continue; + } + if (yu < 0 || yu >= dpy_y) { + continue; + } + if (y_min < 0 || yu < y_min) { + y_min = yu; + } + if (y_max < 0 || yu > y_max) { + y_max = yu; + } + src = save[i]->data + (yu-y_start)*y_step; + dst = main_fb + yu*main_bytes_per_line + + x0*pixelsize; + memcpy(dst, src, (x1-x0)*pixelsize); + } + if (y_min >= 0) { + mark_rect_as_modified(x0, y_min, x1, y_max+1, 0); + } + save[i]->saved = 0; } - if (hit) { - return 1; - } else { - return 0; - } -} -/* - * For the subwin case follows the window if it is moved. - */ -void set_offset(void) { - Window w; - if (! subwin) { + if (restore) { return; } - X_LOCK; - XTranslateCoordinates(dpy, window, rootwin, 0, 0, &off_x, &off_y, &w); - X_UNLOCK; -} - -/* - * Loop over 1-pixel tall horizontal scanlines looking for changes. - * Record the changes in tile_has_diff[]. Scanlines in the loop are - * equally spaced along y by NSCAN pixels, but have a slightly random - * starting offset ystart ( < NSCAN ) from scanlines[]. - */ -static int scan_display(int ystart, int rescan) { - char *src, *dst; - int pixelsize = bpp/8; - int x, y, w, n; - int tile_count = 0; - int nodiffs = 0, diff_hint; - y = ystart; +if (0) fprintf(stderr, " DrawBox: %dx%d+%d+%d\n", w, h, x, y); - if (! main_fb) { - rfbLog("scan_display: no main_fb!\n"); - return 0; + /* + * work out shade/color for the wireframe line, could be a color + * for 16bpp or 24bpp. + */ + if (shade > 255) { + if (pixelsize == 2) { + us = (unsigned short) (shade & 0xffff); + color = 1; + } else if (pixelsize == 4) { + ul = (unsigned long) shade; + color = 1; + } else { + shade = shade % 256; + } } - while (y < dpy_y) { + for (i=0; i<4; i++) { + int s = save[i]->shift; + int yu, y_min = -1, y_max = -1; + int yblack = -1, xblack1 = -1, xblack2 = -1; + int y_start, y_stop, y_step; - if (use_xdamage) { - XD_tot++; - if (xdamage_hint_skip(y)) { - XD_skip++; - y += NSCAN; - continue; - } - } + if (save[i]->vert) { + /* + * make the narrow x's be on the screen, let + * the y's hang off (not drawn). + */ + save[i]->x0 = x0 = nfix(x + s*w - s*lw, dpy_x); + save[i]->y0 = y0 = y; + save[i]->x1 = x1 = nfix(x + s*w - s*lw + lw, dpy_x); + save[i]->y1 = y1 = y + h; - /* grab the horizontal scanline from the display: */ - X_LOCK; - XRANDR_SET_TRAP_RET(-1, "scan_display-set"); - copy_image(scanline, 0, y, 0, 0); - XRANDR_CHK_TRAP_RET(-1, "scan_display-chk"); - X_UNLOCK; + /* + * start and stop a linewidth away from true edge, + * to avoid interfering with horizontal lines. + */ + y_start = y0+lw; + y_stop = y1-lw; + y_step = lw*pixelsize; - /* for better memory i/o try the whole line at once */ - src = scanline->data; - dst = main_fb + y * main_bytes_per_line; + /* draw a black pixel for the border if lw > 1 */ + if (s) { + xblack1 = x1-1; + } else { + xblack1 = x0; + } + } else { + /* + * make the wide x's be on the screen, let the y's + * hang off (not drawn). + */ + save[i]->x0 = x0 = nfix(x, dpy_x); + save[i]->y0 = y0 = y + s*h; + save[i]->x1 = x1 = nfix(x + w, dpy_x); + save[i]->y1 = y1 = y0 + lw; + y_start = y0 - s*lw; + y_stop = y_start + lw; + y_step = max*pixelsize; - if (! memcmp(dst, src, main_bytes_per_line)) { - /* no changes anywhere in scan line */ - nodiffs = 1; - if (! rescan) { - y += NSCAN; - continue; + /* draw a black pixels for the border if lw > 1 */ + if (s) { + yblack = y_stop - 1; + } else { + yblack = y_start; } + xblack1 = x0; + xblack2 = x1-1; } - x = 0; - while (x < dpy_x) { - n = (x/tile_x) + (y/tile_y) * ntiles_x; - diff_hint = 0; - - if (blackouts) { - if (blackout_line_skip(n, x, y, rescan, - &tile_count)) { - x += NSCAN; - continue; - } + /* now loop over the allowed y's for either case */ + for (yu = y_start; yu < y_stop; yu++) { + if (x0 == x1) { + continue; + } + if (yu < 0 || yu >= dpy_y) { + continue; } - if (rescan) { - if (nodiffs || tile_has_diff[n]) { - tile_count += tile_has_diff[n]; - x += NSCAN; - continue; - } - } else if (xdamage_tile_count && - tile_has_xdamage_diff[n]) { - tile_has_xdamage_diff[n] = 2; - diff_hint = 1; + /* record min and max y's for marking rectangle: */ + if (y_min < 0 || yu < y_min) { + y_min = yu; + } + if (y_max < 0 || yu > y_max) { + y_max = yu; } - /* set ptrs to correspond to the x offset: */ - src = scanline->data + x * pixelsize; - dst = main_fb + y * main_bytes_per_line + x * pixelsize; + /* save fb data for this line: */ + save[i]->saved = 1; + src = main_fb + yu*main_bytes_per_line + + x0*pixelsize; + dst = save[i]->data + (yu-y_start)*y_step; + memcpy(dst, src, (x1-x0)*pixelsize); - /* compute the width of data to be compared: */ - if (x + NSCAN > dpy_x) { - w = dpy_x - x; + /* apply the shade/color to make the wireframe line: */ + if (! color) { + memset(src, shade, (x1-x0)*pixelsize); } else { - w = NSCAN; - } - - if (diff_hint || memcmp(dst, src, w * pixelsize)) { - /* found a difference, record it: */ - if (! blackouts) { - tile_has_diff[n] = 1; - tile_count++; - } else { - if (blackout_line_cmpskip(n, x, y, - dst, src, w, pixelsize)) { - tile_has_diff[n] = 0; - } else { - tile_has_diff[n] = 1; - tile_count++; + char *csrc = src; + unsigned short *usp; + unsigned long *ulp; + int k; + for (k=0; k < x1 - x0; k++) { + if (pixelsize == 4) { + ulp = (unsigned long *)csrc; + *ulp = ul; + } else if (pixelsize == 2) { + usp = (unsigned short *)csrc; + *usp = us; } + csrc += pixelsize; + } + } + + /* apply black border for lw >= 2 */ + if (lw > 1) { + if (yu == yblack) { + memset(src, 0, (x1-x0)*pixelsize); + } + if (xblack1 >= 0) { + src = src + (xblack1 - x0)*pixelsize; + memset(src, 0, pixelsize); + } + if (xblack2 >= 0) { + src = src + (xblack2 - x0)*pixelsize; + memset(src, 0, pixelsize); } } - x += NSCAN; } - y += NSCAN; + /* mark it for sending: */ + if (save[i]->saved) { + mark_rect_as_modified(x0, y_min, x1, y_max+1, 0); + } } - return tile_count; } +int direct_fb_copy(int x1, int y1, int x2, int y2, int mark) { + char *src, *dst; + int y, pixelsize = bpp/8; + int xmin = -1, xmax = -1, ymin = -1, ymax = -1; + int do_cmp = 0; -/* - * toplevel for the scanning, rescanning, and applying the heuristics. - * returns number of changed tiles. - */ -int scan_for_updates(int count_only) { - int i, tile_count, tile_diffs; - int old_copy_tile; - double frac1 = 0.1; /* tweak parameter to try a 2nd scan_display() */ - double frac2 = 0.35; /* or 3rd */ - double frac3 = 0.02; /* do scan_display() again after copy_tiles() */ - for (i=0; i < ntiles; i++) { - tile_has_diff[i] = 0; - tile_has_xdamage_diff[i] = 0; - tile_tried[i] = 0; - tile_copied[i] = 0; - } - for (i=0; i < ntiles_y; i++) { - /* could be useful, currently not used */ - tile_row_has_xdamage_diff[i] = 0; - } - xdamage_tile_count = 0; - - /* - * n.b. this program has only been tested so far with - * tile_x = tile_y = NSCAN = 32! - */ - - if (!count_only) { - scan_count++; - scan_count %= NSCAN; + x1 = nfix(x1, dpy_x); + y1 = nfix(y1, dpy_y); + x2 = nfix(x2, dpy_x); + y2 = nfix(y2, dpy_y); - /* some periodic maintenance */ - if (subwin) { - set_offset(); /* follow the subwindow */ - } - if (indexed_color && scan_count % 4 == 0) { - /* check for changed colormap */ - set_colormap(0); - } - if (use_xdamage) { - collect_xdamage(scan_count); - } + if (x1 == x2) { + return 1; } - -#define SCAN_FATAL(x) \ - if (x < 0) { \ - scan_in_progress = 0; \ - fb_copy_in_progress = 0; \ - return 0; \ + if (y1 == y2) { + return 1; } - /* scan with the initial y to the jitter value from scanlines: */ - scan_in_progress = 1; - tile_count = scan_display(scanlines[scan_count], 0); - SCAN_FATAL(tile_count); + X_LOCK; + for (y = y1; y < y2; y++) { + XRANDR_SET_TRAP_RET(0, "direct_fb_copy-set"); + copy_image(scanline, x1, y, x2 - x1, 1); + XRANDR_CHK_TRAP_RET(0, "direct_fb_copy-chk"); + + src = scanline->data; + dst = main_fb + y * main_bytes_per_line + x1 * pixelsize; - if (count_only) { - scan_in_progress = 0; - fb_copy_in_progress = 0; - return tile_count; - } + if (do_cmp == 0 || !mark) { + memcpy(dst, src, (x2 - x1)*pixelsize); - if (xdamage_tile_count) { - /* pick up "known" damaged tiles we missed in scan_display() */ - for (i=0; i < ntiles; i++) { - if (tile_has_diff[i]) { - continue; + } else if (do_cmp == 1) { + if (memcmp(dst, src, (x2 - x1)*pixelsize)) { + if (ymin == -1 || y < ymin) { + ymin = y; + } + if (ymax == -1 || y > ymax) { + ymax = y; + } + memcpy(dst, src, (x2 - x1)*pixelsize); } - if (tile_has_xdamage_diff[i] == 1) { - tile_has_xdamage_diff[i] = 2; - tile_has_diff[i] = 1; - tile_count++; + + } else if (do_cmp == 2) { + int n, shift, xlo, xhi, k, block = 32; + char *dst2, *src2; + + for (k=0; k*block < (x2 - x1); k++) { + shift = k*block; + xlo = x1 + shift; + xhi = xlo + block; + if (xhi > x2) { + xhi = x2; + } + n = xhi - xlo; + if (n < 1) { + continue; + } + src2 = src + shift*pixelsize; + dst2 = dst + shift*pixelsize; + if (memcmp(dst2, src2, n*pixelsize)) { + if (ymin == -1 || y < ymin) { + ymin = y; + } + if (ymax == -1 || y > ymax) { + ymax = y; + } + if (xmin == -1 || xlo < xmin) { + xmin = xlo; + } + if (xmax == -1 || xhi > xmax) { + xmax = xhi; + } + memcpy(dst2, src2, n*pixelsize); + } } } } + X_UNLOCK; - nap_set(tile_count); - - if (fs_factor && frac1 >= fs_frac) { - /* make frac1 < fs_frac if fullscreen updates are enabled */ - frac1 = fs_frac/2.0; + if (do_cmp == 0) { + xmin = x1; + ymin = y1; + xmax = x2; + ymax = y2; + } else if (do_cmp == 1) { + xmin = x1; + xmax = x2; } - if (tile_count > frac1 * ntiles) { - /* - * many tiles have changed, so try a rescan (since it should - * be short compared to the many upcoming copy_tiles() calls) - */ + if (xmin < 0 || ymin < 0 || xmax < 0 || xmin < 0) { + /* no diffs */ + return 1; + } - /* this check is done to skip the extra scan_display() call */ - if (! fs_factor || tile_count <= fs_frac * ntiles) { - int cp, tile_count_old = tile_count; - - /* choose a different y shift for the 2nd scan: */ - cp = (NSCAN - scan_count) % NSCAN; + if (xmax < x2) { + xmax++; + } + if (ymax < y2) { + ymax++; + } - tile_count = scan_display(scanlines[cp], 1); - SCAN_FATAL(tile_count); + if (mark) { + mark_rect_as_modified(xmin, ymin, xmax, ymax, 1); + } + return 1; +} - if (tile_count >= (1 + frac2) * tile_count_old) { - /* on a roll... do a 3rd scan */ - cp = (NSCAN - scan_count + 7) % NSCAN; - tile_count = scan_display(scanlines[cp], 1); - SCAN_FATAL(tile_count); - } - } - scan_in_progress = 0; +#define PUSH_TEST(n) \ +if (n) { \ + double dt = 0.0, tm = 0.0; dtime(&tm); \ + fprintf(stderr, "PUSH---\n"); \ + while (dt < 2.0) { rfbPE(50000); dt += dtime(&tm); } \ + fprintf(stderr, "---PUSH\n"); \ +} + +int push_scr_ev(double bdpush) { + Window frame, win, win0; + int x, y, w, h, wx, wy, ww, wh, dx, dy; + int x0, y0, w0, h0; + int nx, ny, nw, nh; + int dret = 1, obscured; + int ev, ev_tot = scr_ev_cnt; + double st, dnow = 0.0; + int db = 0, rrate = get_read_rate(); + sraRegionPtr backfill, whole, tmpregion; + XWindowAttributes attr; - /* - * At some number of changed tiles it is better to just - * copy the full screen at once. I.e. time = c1 + m * r1 - * where m is number of tiles, r1 is the copy_tiles() - * time, and c1 is the scan_display() time: for some m - * it crosses the full screen update time. - * - * We try to predict that crossover with the fs_frac - * fudge factor... seems to be about 1/2 the total number - * of tiles. n.b. this ignores network bandwidth, - * compression time etc... - * - * Use -fs 1.0 to disable on slow links. - */ - if (fs_factor && tile_count > fs_frac * ntiles) { - int cs; - fb_copy_in_progress = 1; - cs = copy_screen(); - fb_copy_in_progress = 0; - SCAN_FATAL(cs); - if (use_threads && pointer_mode != 1) { - pointer(-1, 0, 0, NULL); - } - nap_check(tile_count); - return tile_count; - } - } - scan_in_progress = 0; +//db = 1; - /* copy all tiles with differences from display to rfb framebuffer: */ - fb_copy_in_progress = 1; + dtime(&dnow); - if (single_copytile || tile_shm_count < ntiles_x) { - /* - * Old way, copy I/O one tile at a time. - */ - old_copy_tile = 1; - } else { - /* - * New way, does runs of horizontal tiles at once. - * Note that below, for simplicity, the extra tile finding - * (e.g. copy_tiles_backward_pass) is done the old way. - */ - old_copy_tile = 0; - } - if (old_copy_tile) { - tile_diffs = copy_all_tiles(); - } else { - tile_diffs = copy_all_tile_runs(); + if (ev_tot == 0) { + return dret; } - SCAN_FATAL(tile_diffs); + backfill = sraRgnCreate(); + whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y); - /* - * This backward pass for upward and left tiles complements what - * was done in copy_all_tiles() for downward and right tiles. - */ - tile_diffs = copy_tiles_backward_pass(); - SCAN_FATAL(tile_diffs); + win0 = scr_ev[0].win; + x0 = scr_ev[0].win_x; + y0 = scr_ev[0].win_y; + w0 = scr_ev[0].win_w; + h0 = scr_ev[0].win_h; - if (tile_diffs > frac3 * ntiles) { - /* - * we spent a lot of time in those copy_tiles, run - * another scan, maybe more of the screen changed. - */ - int cp = (NSCAN - scan_count + 13) % NSCAN; + for (ev=0; ev < ev_tot; ev++) { + + x = scr_ev[ev].x; + y = scr_ev[ev].y; + w = scr_ev[ev].w; + h = scr_ev[ev].h; + dx = scr_ev[ev].dx; + dy = scr_ev[ev].dy; + win = scr_ev[ev].win; + wx = scr_ev[ev].win_x; + wy = scr_ev[ev].win_y; + ww = scr_ev[ev].win_w; + wh = scr_ev[ev].win_h; + nx = scr_ev[ev].new_x; + ny = scr_ev[ev].new_y; + nw = scr_ev[ev].new_w; + nh = scr_ev[ev].new_h; + st = (double) scr_ev[ev].t/1000.0; + + if (dabs((dnow - servertime_diff) - st) > 0.15) { +if (db) fprintf(stderr, "push_scr_ev: TOO OLD: %.4f\n", (dnow - servertime_diff) - st); + dret = 0; + break; + } else { +if (db) fprintf(stderr, "push_scr_ev: AGE: %.4f\n", (dnow - servertime_diff) - st); + } + if (win != win0) { +if (db) fprintf(stderr, "push_scr_ev: DIFF WIN: 0x%lx != 0x%lx\n", win, win0); + dret = 0; + break; + } + if (wx != x0 || wy != y0) { +if (db) fprintf(stderr, "push_scr_ev: WIN SHIFT: %d %d, %d %d", wx, x0, wy, y0); + dret = 0; + break; + } + if (ww != w0 || wh != h0) { +if (db) fprintf(stderr, "push_scr_ev: WIN RESIZE: %d %d, %d %d", ww, w0, wh, h0); + dret = 0; + break; + } + if (w < 1 || h < 1 || ww < 1 || wh < 1) { +if (db) fprintf(stderr, "push_scr_ev: NEGATIVE h/w: %d %d %d %d\n", w, h, ww, wh); + dret = 0; + break; + } - scan_in_progress = 1; - tile_count = scan_display(scanlines[cp], 1); - SCAN_FATAL(tile_count); - scan_in_progress = 0; +if (db) fprintf(stderr, "push_scr_ev: got: %d x: %4d y: %3d" + " w: %4d h: %3d dx: %d dy: %d %dx%d+%d+%d win: 0x%lx\n", + ev, x, y, w, h, dx, dy, w, h, x, y, win); - tile_diffs = copy_tiles_additional_pass(); - SCAN_FATAL(tile_diffs); - } +if (db) fprintf(stderr, "------------ got: %d x: %4d y: %3d" + " w: %4d h: %3d %dx%d+%d+%d\n", + ev, wx, wy, ww, wh, ww, wh, wx, wy); - /* Given enough tile diffs, try the islands: */ - if (grow_fill && tile_diffs > 4) { - tile_diffs = grow_islands(); - } - SCAN_FATAL(tile_diffs); +if (db) fprintf(stderr, "------------ got: %d x: %4d y: %3d" + " w: %4d h: %3d %dx%d+%d+%d\n", + ev, nx, ny, nw, nh, nw, nh, nx, ny); - /* Given enough tile diffs, try the gaps: */ - if (gaps_fill && tile_diffs > 4) { - tile_diffs = fill_tile_gaps(); - } - SCAN_FATAL(tile_diffs); + frame = None; + if (xrecord_wm_window) { + frame = xrecord_wm_window; + } + if (! frame) { + Window r; + int rx, ry, wx, wy; + unsigned int m; + if (! XQueryPointer(dpy, rootwin, &r, &frame, + &rx, &ry, &wx, &wy, &m)) { + frame = None; + } + } + if (! frame) { + frame = win; + } - fb_copy_in_progress = 0; - if (use_threads && pointer_mode != 1) { - /* - * tell the pointer handler it can process any queued - * pointer events: - */ - pointer(-1, 0, 0, NULL); - } + if (try_copyrect(frame, x, y, w, h, dx, dy, &obscured)) { + fb_push(); + urgent_update = 1; +PUSH_TEST(0); - if (blackouts) { - /* ignore any diffs in completely covered tiles */ - int x, y, n; - for (y=0; y < ntiles_y; y++) { - for (x=0; x < ntiles_x; x++) { - n = x + y * ntiles_x; - if (tile_blackout[n].cover == 2) { - tile_has_diff[n] = 0; - } - } + } else { + dret = 0; + break; } - } - hint_updates(); /* use krfb/x0rfbserver hints algorithm */ + if (ev > 0) { + sraRgnOffset(backfill, dx, dy); + sraRgnAnd(backfill, whole); + } - /* Work around threaded rfbProcessClientMessage() calls timeouts */ - if (use_threads) { - ping_clients(tile_diffs); + tmpregion = sraRgnCreateRect(nx, ny, nx + nw, ny + nh); + sraRgnAnd(tmpregion, whole); + sraRgnOr(backfill, tmpregion); + sraRgnDestroy(tmpregion); } + if (dret != 0) { + double est, win_area = 0.0, area = 0.0; + sraRectangleIterator *iter; + sraRect rect; + int tx1, ty1, tx2, ty2; + double tm = 0.0, dt = 0.0; - nap_check(tile_diffs); - return tile_diffs; -} + tmpregion = sraRgnCreateRect(x0, y0, x0 + w0, y0 + h0); + sraRgnAnd(tmpregion, whole); -/* -- gui.c -- */ -#if OLD_TREE || SMALL_FOOTPRINT -char gui_code[] = ""; -#else -#include "tkx11vnc.h" -#endif + sraRgnAnd(backfill, tmpregion); -void run_gui(char *gui_xdisplay, int connect_to_x11vnc, int simple_gui, - pid_t parent) { - char *x11vnc_xdisplay = NULL; - char extra_path[] = ":/usr/local/bin:/usr/bin/X11:/usr/sfw/bin" - ":/usr/X11R6/bin:/usr/openwin/bin:/usr/dt/bin"; - char cmd[100]; - char *wish = NULL, *orig_path, *full_path, *tpath, *p; - char *old_xauth = NULL; - int try_max = 4, sleep = 300; - pid_t mypid = getpid(); - FILE *pipe, *tmpf; + iter = sraRgnGetIterator(tmpregion); + while (sraRgnIteratorNext(iter, &rect)) { + tx1 = rect.x1; + ty1 = rect.y1; + tx2 = rect.x2; + ty2 = rect.y2; - if (*gui_code == '\0') { - rfbLog("gui not available in this program.\n"); - exit(0); - } - if (getenv("DISPLAY") != NULL) { - /* worst case */ - x11vnc_xdisplay = strdup(getenv("DISPLAY")); - } - if (use_dpy) { - /* better */ - x11vnc_xdisplay = strdup(use_dpy); - } - if (connect_to_x11vnc) { - int rc, i; - rfbLogEnable(0); - if (! client_connect_file) { - if (getenv("XAUTHORITY") != NULL) { - old_xauth = strdup(getenv("XAUTHORITY")); - } else { - old_xauth = strdup(""); - } - dpy = XOpenDisplay(x11vnc_xdisplay); - if (! dpy && auth_file) { - set_env("XAUTHORITY", auth_file); - dpy = XOpenDisplay(x11vnc_xdisplay); - } - if (! dpy && ! x11vnc_xdisplay) { - /* worstest case */ - x11vnc_xdisplay = strdup(":0"); - dpy = XOpenDisplay(x11vnc_xdisplay); - } - if (! dpy) { - fprintf(stderr, "gui: could not open x11vnc " - "display: %s\n", NONUL(x11vnc_xdisplay)); - exit(1); - } - scr = DefaultScreen(dpy); - rootwin = RootWindow(dpy, scr); - initialize_vnc_connect_prop(); - } - usleep(1000*1000); - fprintf(stderr, "\n"); - for (i=0; i 0.85 * win_area) { +if (db) fprintf(stderr, " AREA_TOO_MUCH"); + dret = 0; + } else if (est > 0.6) { +if (db) fprintf(stderr, " EST_TOO_LARGE"); + dret = 0; + } else if (area <= 0.0) { + ; } else { - fprintf(stderr, "gui: could not connect to: '%s', try" - " again manually.\n", x11vnc_xdisplay); + dtime(&tm); + iter = sraRgnGetIterator(backfill); + while (sraRgnIteratorNext(iter, &rect)) { + tx1 = rect.x1; + ty1 = rect.y1; + tx2 = rect.x2; + ty2 = rect.y2; + tx1 = nfix(tx1, dpy_x); + ty1 = nfix(ty1, dpy_y); + tx2 = nfix(tx2, dpy_x); + ty2 = nfix(ty2, dpy_y); + + dtime(&tm); +if (db) fprintf(stderr, " DFC(%d,%d-%d,%d)", tx1, ty1, tx2, ty2); + direct_fb_copy(tx1, ty1, tx2, ty2, 1); + dt = dtime(&tm); + fb_push(); +PUSH_TEST(0); + } + dt = dtime(&tm); + sraRgnReleaseIterator(iter); +if (db) fprintf(stderr, " dt: %.4f", dt); + } - if (client_connect_file) { - set_env("X11VNC_CONNECT_FILE", client_connect_file); +if (db && dret) fprintf(stderr, " **** dret=%d", dret); +if (db && !dret) fprintf(stderr, " ---- dret=%d", dret); +if (db) fprintf(stderr, "\n"); + } + +if (db || bdpush > 0.0) fprintf(stderr, "BDPUSH-TIME: %.3f 0x%lx\n", bdpush, xrecord_wm_window); + + if (bdpush > 0.0 && xrecord_wm_window != None && + valid_window(xrecord_wm_window, &attr)) { + + double wm_area = 0.0, win_area = 0.0, d_area; + sraRectangleIterator *iter; + sraRect rect; + sraRegionPtr frame; + int tx1, ty1, tx2, ty2; + + /* wm frame: */ + tx1 = attr.x; + ty1 = attr.y; + tx2 = attr.x + attr.width; + ty2 = attr.y + attr.height; + + frame = sraRgnCreateRect(tx1, ty1, tx2, ty2); + sraRgnAnd(frame, whole); + + iter = sraRgnGetIterator(frame); + while (sraRgnIteratorNext(iter, &rect)) { + tx1 = rect.x1; + ty1 = rect.y1; + tx2 = rect.x2; + ty2 = rect.y2; + + wm_area += (tx2 - tx1)*(ty2 - ty1); } - if (dpy) { - XCloseDisplay(dpy); + sraRgnReleaseIterator(iter); + + /* scrolling window: */ + tmpregion = sraRgnCreateRect(x0, y0, x0 + w0, y0 + h0); + sraRgnAnd(tmpregion, whole); + + iter = sraRgnGetIterator(tmpregion); + while (sraRgnIteratorNext(iter, &rect)) { + tx1 = rect.x1; + ty1 = rect.y1; + tx2 = rect.x2; + ty2 = rect.y2; + + win_area += (tx2 - tx1)*(ty2 - ty1); } - if (old_xauth) { - if (*old_xauth == '\0') { - /* wasn't set, hack it out if it is now */ - char *xauth = getenv("XAUTHORITY"); - if (xauth) { - *(xauth-2) = '_'; /* yow */ - } - } else { - set_env("XAUTHORITY", old_xauth); + sraRgnReleaseIterator(iter); + + d_area = wm_area - win_area; + sraRgnSubtract(frame, tmpregion); + sraRgnDestroy(tmpregion); + +if (db) fprintf(stderr, "d_area: %.4f wm_area: %.4f\n", d_area, wm_area); + + if (d_area >= 0.0 && d_area < bdpush * wm_area && + !sraRgnEmpty(frame)) { + double dt = 0.0, dm = 0.0; + dtime(&dm); + iter = sraRgnGetIterator(frame); + while (sraRgnIteratorNext(iter, &rect)) { + tx1 = rect.x1; + ty1 = rect.y1; + tx2 = rect.x2; + ty2 = rect.y2; + direct_fb_copy(tx1, ty1, tx2, ty2, 1); + fb_push(); + dt += dtime(&dm); +if (db) fprintf(stderr, " BDP(%d,%d-%d,%d) dt: %.4f\n", tx1, ty1, tx2, ty2, dt); } - free(old_xauth); + sraRgnReleaseIterator(iter); } + sraRgnDestroy(frame); } - orig_path = getenv("PATH"); - if (! orig_path) { - orig_path = strdup("/bin:/usr/bin:/usr/bin/X11"); + sraRgnDestroy(backfill); + sraRgnDestroy(whole); + return dret; +} +/* + * Wrapper to apply the rfbDoCopyRegion taking into account if scaling + * is being done. Note that copyrect under the scaling case is often + * only approximate. + */ +void do_copyregion(sraRegionPtr region, int dx, int dy) { + sraRectangleIterator *iter; + sraRect rect; + int Bpp = bpp/8; + int x1, y1, x2, y2, w, stride; + int sx1, sy1, sx2, sy2, sdx, sdy; + char *dst, *src; + + if (!scaling || rfb_fb == main_fb) { + rfbDoCopyRegion(screen, region, dx, dy); + return; } - full_path = (char *) malloc(strlen(orig_path)+strlen(extra_path)+1); - strcpy(full_path, orig_path); - strcat(full_path, extra_path); - tpath = strdup(full_path); - p = strtok(tpath, ":"); + stride = dpy_x * Bpp; - while (p) { - char *try; - struct stat sbuf; - char *wishes[] = {"wish", "wish8.3", "wish8.4", "wish8.5", - "wish8.0"}; - int nwishes = 3, i; + iter = sraRgnGetIterator(region); + while(sraRgnIteratorNext(iter, &rect)) { + int j; - try = (char *)malloc(strlen(p) + 1 + strlen("wish8.4") + 1); - for (i=0; iy1; j--) { + memmove(dst, src, w); + dst -= stride; + src -= stride; } } - free(try); - if (wish) { + + sx1 = ((double) x1 / dpy_x) * scaled_x; + sy1 = ((double) y1 / dpy_y) * scaled_y; + sx2 = ((double) x2 / dpy_x) * scaled_x; + sy2 = ((double) y2 / dpy_y) * scaled_y; + sdx = ((double) dx / dpy_x) * scaled_x; + sdy = ((double) dy / dpy_y) * scaled_y; + + rfbDoCopyRect(screen, sx1, sy1, sx2, sy2, sdx, sdy); + } + sraRgnReleaseIterator(iter); +} + +void fb_push0(int first_ms, int loop_ms, int loop_max) { + int t; + fb_update_sent(NULL); + rfbPE(1000 * first_ms); /* long select */ + for (t=0; trequestedRegion); + *mod += sraRgnCountRects(cl->modifiedRegion); + *cpy += sraRgnCountRects(cl->copyRegion); } + rfbReleaseClientIterator(i); +} - sprintf(cmd, "%s -", wish); - tmpf = tmpfile(); - if (tmpf == NULL) { - /* if no tmpfile, use a pipe */ - pipe = popen(cmd, "w"); - if (! pipe) { - fprintf(stderr, "could not run: %s\n", cmd); - perror("popen"); - } - fprintf(pipe, "%s", gui_code); - pclose(pipe); - } else { - /* - * we prefer a tmpfile since then this x11vnc process - * will then be gone, otherwise the x11vnc program text - * will still be in use. - */ - int n = fileno(tmpf); - fprintf(tmpf, "%s", gui_code); - fflush(tmpf); - rewind(tmpf); - dup2(n, 0); - close(n); - execlp(wish, wish, "-", (char *) NULL); - fprintf(stderr, "could not exec wish: %s -\n", wish); - perror("execlp"); +void fb_push(void) { + char *httpdir = screen->httpDir; + int defer = screen->deferUpdateTime; + int i, req0, mod0, cpy0, req1, mod1, cpy1; + int db = 0; +//db = 1; + + screen->httpDir = NULL; + screen->deferUpdateTime = 0; + + get_client_regions(&req0, &mod0, &cpy0); + + rfbPE(0); + + screen->httpDir = httpdir; + screen->deferUpdateTime = defer; + + get_client_regions(&req1, &mod1, &cpy1); +if (db) fprintf(stderr, "\nFB_push: req: %d/%d mod: %d/%d cpy: %d/%d\n", + req0, req1, mod0, mod1, cpy0, cpy1); + + for (i = 0; i < 0; i++) { + get_client_regions(&req0, &mod0, &cpy0); + rfbCFD(1000); + get_client_regions(&req1, &mod1, &cpy1); +if (db) fprintf(stderr, "-------: req: %d/%d mod: %d/%d cpy: %d/%d\n", + req0, req1, mod0, mod1, cpy0, cpy1); } - exit(0); } -void do_gui(char *opts) { - char *s, *p; - char *old_xauth = NULL; - char *gui_xdisplay = NULL; - int start_x11vnc = 1; - int connect_to_x11vnc = 0; - int simple_gui = 0; - Display *test_dpy; +/* + * utility routine for CopyRect of the window (but not CopyRegion) + */ +int crfix(int x, int dx, int Lx) { + /* adjust x so that copy source is on screen */ + if (dx > 0) { + if (x-dx < 0) { + /* off on the left */ + x = dx; + } + } else { + if (x-dx >= Lx) { + /* off on the right */ + x = Lx + dx - 1; + } + } + return x; +} - if (opts) { - s = strdup(opts); - } else { - s = strdup(""); +int check_xrecord_keys(void) { + double spin = 0.0, tm = 0.0; + int gk, gk0, extra_keys = 0, ret = 0; + int db = debug_scroll; + int get_out = 1, got_one = 0, flush1 = 0, flush2 = 0; + static double last_key_scroll = 0.0; + static double persist_start = 0.0; + double this_scroll, scroll_persist = scr_key_persist; + double spin_fac = 1.0, scroll_fac = 2.0; + double max_spin, tnow = 0.0; + +//db = 1; + + if (got_keyboard_input) { + get_out = 0; + } + + dtime(&tnow); + if (tnow < last_key_scroll + scroll_persist) { + get_out = 0; } - if (use_dpy) { - /* worst case */ - gui_xdisplay = strdup(use_dpy); - + if (get_out) { + persist_start = 0.0; + xrecord_watch(0); + return 0; } - if (getenv("DISPLAY") != NULL) { - /* better */ - gui_xdisplay = strdup(getenv("DISPLAY")); + +if (db) fprintf(stderr, "xrecord_set_by_mouse: %d\n", xrecord_set_by_mouse); +if (db) fprintf(stderr, "xrecord_set_by_keys: %d\n", xrecord_set_by_keys); + + max_spin = scr_key_time; + + if (tnow < last_key_scroll + scroll_persist) { + max_spin = 1.25*(tnow - last_key_scroll); + } else if (xrecord_scroll_keysym(last_keysym)) { + spin_fac = scroll_fac; } - p = strtok(s, ","); + gk = gk0 = got_keyboard_input; + dtime(&tm); - while(p) { - if (*p == '\0') { - ; - } else if (strchr(p, ':') != NULL) { - /* best */ - gui_xdisplay = strdup(p); - } else if (!strcmp(p, "wait")) { - start_x11vnc = 0; - connect_to_x11vnc = 0; - } else if (!strcmp(p, "conn") || !strcmp(p, "connect")) { - start_x11vnc = 0; - connect_to_x11vnc = 1; - } else if (!strcmp(p, "ez") || !strcmp(p, "simple")) { - simple_gui = 1; +if (db) fprintf(stderr, "check_xrecord: LOOP: scr_ev_cnt: %d max: %.3f\n", + scr_ev_cnt, max_spin); + + while (1) { + + if (scr_ev_cnt) { + got_one = 1; + this_scroll = 0.0; + dtime(&this_scroll); + break; + } + + X_LOCK; + XFlush(dpy); + flush1 = 1; + X_UNLOCK; + + if (use_threads) { + usleep(1000); } else { - fprintf(stderr, "unrecognized gui opt: %s\n", p); + rfbCFD(1000); } - - p = strtok(NULL, ","); - } - free(s); - if (start_x11vnc) { - connect_to_x11vnc = 1; - } + spin += dtime(&tm); - if (! gui_xdisplay) { - fprintf(stderr, "error: cannot determine X DISPLAY for gui" - " to display on.\n"); - exit(1); - } - test_dpy = XOpenDisplay(gui_xdisplay); - if (! test_dpy && auth_file) { - if (getenv("XAUTHORITY") != NULL) { - old_xauth = strdup(getenv("XAUTHORITY")); + if (spin >= max_spin * spin_fac) { +if (db) fprintf(stderr, "check_xrecord: SPIN-OUT: %.3f/%.3f\n", spin, + max_spin * spin_fac); + break; } - set_env("XAUTHORITY", auth_file); - test_dpy = XOpenDisplay(gui_xdisplay); - } - if (! test_dpy) { - if (! old_xauth && getenv("XAUTHORITY") != NULL) { - old_xauth = strdup(getenv("XAUTHORITY")); + + if (got_keyboard_input > gk) { + gk = got_keyboard_input; + if (xrecord_scroll_keysym(last_keysym)) { + spin_fac = scroll_fac; + } + extra_keys++; +if (db) fprintf(stderr, "check_xrecord: more keys: %d %.3f\n", extra_keys, + spin); + flush2 = 1; } - set_env("XAUTHORITY", ""); - test_dpy = XOpenDisplay(gui_xdisplay); - } - if (! test_dpy) { - fprintf(stderr, "error: cannot connect to gui X DISPLAY: %s\n", - gui_xdisplay); - exit(1); - } - XCloseDisplay(test_dpy); - if (start_x11vnc) { -#if LIBVNCSERVER_HAVE_FORK - /* fork into the background now */ - int p; - pid_t parent = getpid(); - if ((p = fork()) > 0) { - ; /* parent */ - } else if (p == -1) { - fprintf(stderr, "could not fork\n"); - perror("fork"); - clean_up_exit(1); - } else { - run_gui(gui_xdisplay, connect_to_x11vnc, simple_gui, - parent); - exit(1); + X_LOCK; + if (flush2) { + XFlush(dpy); } -#else - fprintf(stderr, "system does not support fork: start " - "x11vnc in the gui.\n"); - start_x11vnc = 0; -#endif - } - if (!start_x11vnc) { - run_gui(gui_xdisplay, connect_to_x11vnc, simple_gui, 0); - exit(1); - } - if (old_xauth) { - set_env("XAUTHORITY", old_xauth); + XRecordProcessReplies(rdpy_data); + X_UNLOCK; } -} - -/* -- userinput.c -- */ -/* - * user input handling heuristics - */ -/* - * For -wireframe: find the direct child of rootwin that has the - * pointer, assume that is the WM frame that contains the application - * (i.e. wm reparents the app toplevel) return frame position and size - * if successful. - */ +if (db) fprintf(stderr, " f1: %d f2: %d spin: %.4f\n", flush1, flush2, spin); + /* since we've flushed it, we might as well avoid -input_skip */ + if (flush1 || flush2) { + got_keyboard_input = 0; + got_pointer_input = 0; + } -int get_wm_frame_pos(int *px, int *py, int *x, int *y, int *w, int *h, Window *win) { - Window r, c; - XWindowAttributes attr; - Bool ret; - int rootx, rooty, wx, wy; - unsigned int mask; - - ret = XQueryPointer(dpy, rootwin, &r, &c, &rootx, &rooty, &wx, &wy, &mask); + if (scr_ev_cnt) { + int dret; + double bdpush = 0.0; + static double last_border_push = 0.0; - *win = c; + if (persist_start > 0.0 && + this_scroll > last_border_push + 1.00) { + bdpush = 0.0; + last_border_push = this_scroll; + } + dret = push_scr_ev(bdpush); + ret = 1 + dret; + scr_ev_cnt = 0; + } - /* current pointer position is returned too */ - *px = rootx; - *py = rooty; + if (xrecording) { + if (ret < 2) { + xrecord_watch(0); + } + } - if (!ret || ! c || c == rootwin) { - /* no immediate child */ - return 0; + if (this_scroll > 0.0) { + last_key_scroll = this_scroll; } - /* child window position and size */ - if (! valid_window(c, &attr)) { - return 0; + if (ret == 2) { + if (persist_start == 0.0) { + dtime(&persist_start); + } + } else { + persist_start = 0.0; } -#if 0 - XTranslateCoordinates(dpy, c, rootwin, 0, 0, &rootx, &rooty, &c2); - *x = rootx; - *y = rooty; -#endif - *x = attr.x; - *y = attr.y; - *w = attr.width; - *h = attr.height; - return 1; + return ret; } -static int defer_update_nofb = 6; /* defer a shorter time under -nofb */ -static Window maybe_scrolling = 0; +int check_xrecord_mouse(void) { + double spin = 0.0, tm = 0.0; + int gp, gp0, ret = 0; + int db = debug_scroll; + int flush1 = 0, flush2 = 0; + int get_out = 1, got_one = 0; + static double last_mouse_scroll = 0.0; + static double persist_start = 0.0; + double this_scroll, scroll_persist = scr_mouse_persist; + double spin_fac = 1.0; + double max_spin, tnow = 0.0; + +//db = 1; + + if (button_mask) { + get_out = 0; + } + dtime(&tnow); + if (tnow < last_mouse_scroll + scroll_persist) { + get_out = 0; + } -/* -WIREFRAME_PARMS "0xff,2,0,30+6+6+6,0.05+0.3+2.0,8" -shade,linewidth,percent,T+B+L+R,t1+t2+t3 - */ -#define LW_MAX 8 -unsigned long wireframe_shade; -int wireframe_lw; -double wireframe_frac; -int wireframe_top, wireframe_bot, wireframe_left, wireframe_right; -double wireframe_t1, wireframe_t2, wireframe_t3, wireframe_t4; + if (get_out) { + persist_start = 0.0; + xrecord_watch(0); + return 0; + } -/* - * Parse the gory -wireframe string for parameters. - */ -void parse_wireframe_str(char *wf) { - char *p, *str; - int i; - char *part[10]; +if (db) fprintf(stderr, "xrecord_set_by_mouse: %d\n", xrecord_set_by_mouse); +if (db) fprintf(stderr, "xrecord_set_by_keys: %d\n", xrecord_set_by_keys); - for (i=0; i<10; i++) { - part[i] = NULL; - } + max_spin = scr_mouse_time; - if (wf == NULL || *wf == '\0') { - return; + if (tnow < last_mouse_scroll + scroll_persist) { + max_spin = 1.25*(tnow - last_mouse_scroll); } - str = strdup(wf); + gp = gp0 = got_pointer_input; + dtime(&tm); - /* leading ",", make it start with ignorable string "z" */ - if (*str == ',') { - char *tmp = (char *) malloc(strlen(str)+2); - strcpy(tmp, "z"); - strcat(tmp, str); - free(str); - str = tmp; - } +if (db) fprintf(stderr, "check_xrecord: LOOP: scr_ev_cnt: %d max: %.3f\n", + scr_ev_cnt, max_spin); - p = strtok(str, ","); - i = 0; - while (p) { - part[i++] = strdup(p); - p = strtok(NULL, ","); - } - free(str); + while (1) { + if (scr_ev_cnt) { + got_one = 1; + this_scroll = 0.0; + dtime(&this_scroll); + break; + } - /* Wireframe shade, color, RGB: */ - if ((str = part[0]) != NULL) { - unsigned long n; - int r, g, b, ok = 0; - XColor cdef; - Colormap cmap; - if (dpy && (bpp == 32 || bpp == 16)) { - cmap = DefaultColormap (dpy, scr); - if (XParseColor(dpy, cmap, str, &cdef) && - XAllocColor(dpy, cmap, &cdef)) { - r = cdef.red >> 8; - g = cdef.green >> 8; - b = cdef.blue >> 8; - if (r == 0 && g == 0) { - g = 1; /* need to be > 255 */ - } - n = 0; - n |= (r << main_red_shift); - n |= (g << main_green_shift); - n |= (b << main_blue_shift); - wireframe_shade = n; - ok = 1; - } + X_LOCK; + XFlush(dpy); + flush1 = 1; + X_UNLOCK; + + if (use_threads) { + usleep(1000); + } else { + rfbCFD(1000); } - if (ok) { - ; - } else if (sscanf(str, "0x%lx", &n) == 1) { - wireframe_shade = n; - } else if (sscanf(str, "%ld", &n) == 1) { - wireframe_shade = n; - } else if (sscanf(str, "%lx", &n) == 1) { - wireframe_shade = n; + spin += dtime(&tm); + + if (spin >= max_spin * spin_fac) { +if (db) fprintf(stderr, "check_xrecord: SPIN-OUT: %.3f/%.3f\n", spin, + max_spin * spin_fac); + break; + } + if (got_pointer_input > gp) { + gp = got_pointer_input; + flush2 = 1; } - free(str); - } - /* linewidth: # of pixels wide for the wireframe lines */ - if ((str = part[1]) != NULL) { - int n; - if (sscanf(str, "%d", &n) == 1) { - wireframe_lw = n; - if (wireframe_lw < 1) { - wireframe_lw = 1; - } - if (wireframe_lw > LW_MAX) { - wireframe_lw = LW_MAX; - } + X_LOCK; + if (flush2) { + XFlush(dpy); + } + XRecordProcessReplies(rdpy_data); + X_UNLOCK; + if (! button_mask) { + break; } - free(str); } - /* percentage cutoff for opaque move/resize (like WM's) */ - if ((str = part[2]) != NULL) { - if (*str == '\0') { - ; - } else if (strchr(str, '.')) { - wireframe_frac = atof(str); - } else { - wireframe_frac = ((double) atoi(str))/100.0; - } - free(str); +if (db) fprintf(stderr, " f1: %d f2: %d\n", flush1, flush2); + /* since we've flushed it, we might as well avoid -input_skip */ + if (flush1 || flush2) { + got_keyboard_input = 0; + got_pointer_input = 0; } - /* - * Top, Bottom, Left, Right tolerances to guess the wm frame is - * being grabbed (Top is traditionally bigger, i.e. titlebar): - */ - if ((str = part[3]) != NULL) { - int t, b, l, r; - if (sscanf(str, "%d+%d+%d+%d", &t, &b, &l, &r) == 4) { - wireframe_top = t; - wireframe_bot = b; - wireframe_left = l; - wireframe_right = r; + if (scr_ev_cnt) { + int dret; + double bdpush = 0.0; + static double last_border_push = 0.0; + + if (persist_start > 0.0 && + this_scroll > last_border_push + 1.00) { + bdpush = 0.0; + last_border_push = this_scroll; } - free(str); + dret = push_scr_ev(bdpush); + if (! button_mask) { + dret = 0; + } + ret = 1 + dret; + scr_ev_cnt = 0; } - /* check_wireframe() timing heuristics. */ - if ((str = part[4]) != NULL) { - double t1, t2, t3, t4; - if (sscanf(str, "%lf+%lf+%lf+%lf", &t1, &t2, &t3, &t4) == 4) { - wireframe_t1 = t1; - wireframe_t2 = t2; - wireframe_t3 = t3; - wireframe_t4 = t4; + if (xrecording) { + if (ret < 2) { + xrecord_watch(0); } - free(str); } -} -/* - * First parse the defaults and apply any user supplied ones (may be a subset) - */ -void parse_wireframe(void) { - parse_wireframe_str(WIREFRAME_PARMS); - if (! wireframe_str) { - wireframe_str = strdup(WIREFRAME_PARMS); + if (this_scroll > 0.0) { + last_mouse_scroll = this_scroll; } - parse_wireframe_str(wireframe_str); -} -/* - * Set wireframe_copyrect based on desired mode. - */ -void set_wirecopyrect_mode(char *str) { - char *orig = wireframe_copyrect; - if (str == NULL || *str == '\0') { - wireframe_copyrect = strdup(wireframe_copyrect_default); - } else if (!strcmp(str, "always") || !strcmp(str, "all")) { - wireframe_copyrect = strdup("always"); - } else if (!strcmp(str, "top")) { - wireframe_copyrect = strdup("top"); - } else if (!strcmp(str, "never") || !strcmp(str, "none")) { - wireframe_copyrect = strdup("never"); - } else { - if (! wireframe_copyrect) { - wireframe_copyrect = strdup(wireframe_copyrect_default); + if (ret == 2) { + if (persist_start == 0.0) { + dtime(&persist_start); } - rfbLog("unknown -wirecopyrect mode: %s, using: %s\n", str, - wireframe_copyrect); - } - if (orig) { - free(orig); + } else { + persist_start = 0.0; } -} -typedef struct saveline { - int x0, y0, x1, y1; - int shift; - int vert; - int saved; - char *data; -} saveline_t; + return ret; +} -/* - * Draw the wireframe box onto the framebuffer. Saves the real - * framebuffer data to some storage lines. Restores previous lines. - * use restore = 1 to clean up (done with animation). - * This works with -scale. - */ -void draw_box(int x, int y, int w, int h, int restore) { - int x0, y0, x1, y1, i, pixelsize = bpp/8; - char *dst, *src; - static saveline_t *save[4]; - static int first = 1, len = 0; - int max = dpy_x > dpy_y ? dpy_x : dpy_y; - int sz, lw = wireframe_lw; - unsigned long shade = wireframe_shade; - int color = 0; - unsigned short us; - unsigned long ul; +int check_xrecord(void) { + int watch_keys = 0, watch_mouse = 0; - if (max > len) { - /* create/resize storage lines: */ - for (i=0; i<4; i++) { - len = max; - if (! first && save[i]) { - if (save[i]->data) { - free(save[i]->data); - } - free(save[i]); - } - save[i] = (saveline_t *) malloc(sizeof(saveline_t)); - save[i]->saved = 0; - sz = (LW_MAX+1)*len*pixelsize; - save[i]->data = (char *)malloc(sz); + if (! use_xrecord) { + return 0; + } + if (scaling && ! got_scrollcopyrect) { + return 0; + } - /* - * Four types of lines: - * 0) top horizontal - * 1) bottom horizontal - * 2) left vertical - * 3) right vertical - * - * shift means shifted by width or height. - */ - if (i == 0) { - save[i]->vert = 0; - save[i]->shift = 0; - } else if (i == 1) { - save[i]->vert = 0; - save[i]->shift = 1; - } else if (i == 2) { - save[i]->vert = 1; - save[i]->shift = 0; - } else if (i == 3) { - save[i]->vert = 1; - save[i]->shift = 1; - } - } + if (! xrecording) { + return 0; } - first = 0; - /* - * restore any saved lines. see below for algorithm and - * how x0, etc. are used. we just reverse those steps. - */ - for (i=0; i<4; i++) { - int s = save[i]->shift; - int yu, y_min = -1, y_max = -1; - int y_start, y_stop, y_step; + if (!strcmp(scroll_copyrect, "always")) { + watch_keys = 1; + watch_mouse = 1; + } else if (!strcmp(scroll_copyrect, "keys")) { + watch_keys = 1; + } else if (!strcmp(scroll_copyrect, "mouse")) { + watch_mouse = 1; + } - if (! save[i]->saved) { - continue; - } - x0 = save[i]->x0; - y0 = save[i]->y0; - x1 = save[i]->x1; - y1 = save[i]->y1; - if (save[i]->vert) { - y_start = y0+lw; - y_stop = y1-lw; - y_step = lw*pixelsize; - } else { - y_start = y0 - s*lw; - y_stop = y_start + lw; - y_step = max*pixelsize; - } - for (yu = y_start; yu < y_stop; yu++) { - if (x0 == x1) { - continue; - } - if (yu < 0 || yu >= dpy_y) { - continue; - } - if (y_min < 0 || yu < y_min) { - y_min = yu; - } - if (y_max < 0 || yu > y_max) { - y_max = yu; - } - src = save[i]->data + (yu-y_start)*y_step; - dst = main_fb + yu*main_bytes_per_line + - x0*pixelsize; - memcpy(dst, src, (x1-x0)*pixelsize); - } - if (y_min >= 0) { - mark_rect_as_modified(x0, y_min, x1, y_max+1, 0); - } - save[i]->saved = 0; + if (watch_mouse && button_mask && xrecord_set_by_mouse) { + return check_xrecord_mouse(); + } else if (watch_keys && xrecord_set_by_keys) { + return check_xrecord_keys(); + } else { + return 0; } +} - if (restore) { - return; +#define DB_SET \ + int db = 0; \ + int db2 = 0; \ + if (debug_wireframe == 1) { \ + db = 1; \ + } \ + if (debug_wireframe == 2) { \ + db2 = 1; \ + } \ + if (debug_wireframe == 3) { \ + db = 1; \ + db2 = 1; \ } -if (0) fprintf(stderr, " DrawBox: %dx%d+%d+%d\n", w, h, x, y); +int try_copyrect(Window frame, int x, int y, int w, int h, int dx, int dy, + int *obscured) { + static int dt_bad = 0; + static time_t dt_bad_check = 0; + int x1, y1, x2, y2, sent_copyrect = 0; + DB_SET + + *obscured = 0; /* - * work out shade/color for the wireframe line, could be a color - * for 16bpp or 24bpp. + * XXX KDE and xfce do some weird things with the + * stacking, it does not match XQueryTree. Work around + * it for now by CopyRect-ing the *whole* on-screen + * rectangle (whether obscured or not!) */ - if (shade > 255) { - if (pixelsize == 2) { - us = (unsigned short) (shade & 0xffff); - color = 1; - } else if (pixelsize == 4) { - ul = (unsigned long) shade; - color = 1; + if (time(0) > dt_bad_check + 5) { + char *dt = guess_desktop(); + if (!strcmp(dt, "kde")) { + dt_bad = 1; + } else if (!strcmp(dt, "xfce")) { + dt_bad = 1; } else { - shade = shade % 256; + dt_bad = 0; } + dt_bad_check = time(0); } - for (i=0; i<4; i++) { - int s = save[i]->shift; - int yu, y_min = -1, y_max = -1; - int yblack = -1, xblack1 = -1, xblack2 = -1; - int y_start, y_stop, y_step; + if (dt_bad) { + sraRegionPtr rect; + /* send the whole thing... */ + x1 = crfix(nfix(x, dpy_x), dx, dpy_x); + y1 = crfix(nfix(y, dpy_y), dy, dpy_y); + x2 = crfix(nfix(x+w, dpy_x), dx, dpy_x); + y2 = crfix(nfix(y+h, dpy_y), dy, dpy_y); - if (save[i]->vert) { - /* - * make the narrow x's be on the screen, let - * the y's hang off (not drawn). - */ - save[i]->x0 = x0 = nfix(x + s*w - s*lw, dpy_x); - save[i]->y0 = y0 = y; - save[i]->x1 = x1 = nfix(x + s*w - s*lw + lw, dpy_x); - save[i]->y1 = y1 = y + h; + rect = sraRgnCreateRect(x1, y1, x2, y2); + do_copyregion(rect, dx, dy); + sraRgnDestroy(rect); + + sent_copyrect = 1; + *obscured = 1; /* set to avoid an aggressive push */ + + } else if (stack_list) { + int k, tx1, tx2, ty1, ty2; + sraRegionPtr moved_win, tmp_win; + int saw_me = 0; + int orig_x, orig_y; + XWindowAttributes attr; + + orig_x = x - dx; + orig_y = y - dy; + + tx1 = nfix(orig_x, dpy_x); + ty1 = nfix(orig_y, dpy_y); + tx2 = nfix(orig_x+w, dpy_x); + ty2 = nfix(orig_y+h, dpy_y); + +if (db2) fprintf(stderr, "moved_win: %4d %3d, %4d %3d 0x%lx ---\n", + tx1, ty1, tx2, ty2, frame); + + moved_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); + + X_LOCK; + + /* + * loop over the stack, top to bottom until we + * find our wm frame: + */ + for (k = stack_num - 1; k >= 0; k--) { + Window swin = stack_list[k]; + if (swin == frame) { +if (db2) { +saw_me = 1; +fprintf(stderr, " ----------\n"); +} else { + break; +} + } + + /* skip some unwanted cases: */ + if (swin == None) { + continue; + } + if (!valid_window(swin, &attr)) { + continue; + } + if (attr.map_state != IsViewable) { + continue; + } /* - * start and stop a linewidth away from true edge, - * to avoid interfering with horizontal lines. + * first subtract any overlap from the initial + * window rectangle */ - y_start = y0+lw; - y_stop = y1-lw; - y_step = lw*pixelsize; - /* draw a black pixel for the border if lw > 1 */ - if (s) { - xblack1 = x1-1; - } else { - xblack1 = x0; + /* clip the window to the visible screen: */ + tx1 = nfix(attr.x, dpy_x); + ty1 = nfix(attr.y, dpy_y); + tx2 = nfix(attr.x + attr.width, dpy_x); + ty2 = nfix(attr.y + attr.height, dpy_y); + +if (db2) fprintf(stderr, " tmp_win-1: %4d %3d, %4d %3d 0x%lx\n", + tx1, ty1, tx2, ty2, swin); +if (db2 && saw_me) continue; + + /* see if window clips us: */ + tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); + if (sraRgnAnd(tmp_win, moved_win)) { + *obscured = 1; +if (db2) fprintf(stderr, " : clips it.\n"); } - } else { + sraRgnDestroy(tmp_win); + + /* subtract it from our region: */ + tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); + sraRgnSubtract(moved_win, tmp_win); + sraRgnDestroy(tmp_win); + /* - * make the wide x's be on the screen, let the y's - * hang off (not drawn). + * next, subtract from the initial window rectangle + * anything that woul + * window rectangle */ - save[i]->x0 = x0 = nfix(x, dpy_x); - save[i]->y0 = y0 = y + s*h; - save[i]->x1 = x1 = nfix(x + w, dpy_x); - save[i]->y1 = y1 = y0 + lw; - y_start = y0 - s*lw; - y_stop = y_start + lw; - y_step = max*pixelsize; - /* draw a black pixels for the border if lw > 1 */ - if (s) { - yblack = y_stop - 1; - } else { - yblack = y_start; - } - xblack1 = x0; - xblack2 = x1-1; + /* clip the window to the visible screen: */ + tx1 = nfix(attr.x - dx, dpy_x); + ty1 = nfix(attr.y - dy, dpy_y); + tx2 = nfix(attr.x - dx + attr.width, dpy_x); + ty2 = nfix(attr.y - dy + attr.height, dpy_y); + +if (db2) fprintf(stderr, " tmp_win-2: %4d %3d, %4d %3d 0x%lx\n", + tx1, ty1, tx2, ty2, swin); +if (db2 && saw_me) continue; + + /* subtract it from our region: */ + tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); + sraRgnSubtract(moved_win, tmp_win); + sraRgnDestroy(tmp_win); } + X_UNLOCK; - /* now loop over the allowed y's for either case */ - for (yu = y_start; yu < y_stop; yu++) { - if (x0 == x1) { - continue; - } - if (yu < 0 || yu >= dpy_y) { - continue; - } + if (*obscured && !strcmp(wireframe_copyrect, "top")) { + ; /* cannot send CopyRegion */ + } else if (! sraRgnEmpty(moved_win)) { + sraRegionPtr whole, shifted_region; - /* record min and max y's for marking rectangle: */ - if (y_min < 0 || yu < y_min) { - y_min = yu; - } - if (y_max < 0 || yu > y_max) { - y_max = yu; - } + whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y); + shifted_region = sraRgnCreateRgn(moved_win); + sraRgnOffset(shifted_region, dx, dy); + sraRgnAnd(shifted_region, whole); - /* save fb data for this line: */ - save[i]->saved = 1; - src = main_fb + yu*main_bytes_per_line + - x0*pixelsize; - dst = save[i]->data + (yu-y_start)*y_step; - memcpy(dst, src, (x1-x0)*pixelsize); + sraRgnDestroy(whole); - /* apply the shade/color to make the wireframe line: */ - if (! color) { - memset(src, shade, (x1-x0)*pixelsize); - } else { - char *csrc = src; - unsigned short *usp; - unsigned long *ulp; - int k; - for (k=0; k < x1 - x0; k++) { - if (pixelsize == 4) { - ulp = (unsigned long *)csrc; - *ulp = ul; - } else if (pixelsize == 2) { - usp = (unsigned short *)csrc; - *usp = us; - } - csrc += pixelsize; - } + /* now send the CopyRegion: */ + if (! sraRgnEmpty(shifted_region)) { +if (db2) fprintf(stderr, "do_copyregion: %d %d %d %d dx: %d dy: %d\n", tx1, ty1, tx2, ty2, dx, dy); + do_copyregion(shifted_region, dx, dy); + sent_copyrect = 1; } + sraRgnDestroy(shifted_region); + } + sraRgnDestroy(moved_win); + } + return sent_copyrect; +} + +int near_wm_edge(int x, int y, int w, int h, int px, int py) { + /* heuristics: */ + int wf_t = wireframe_top; + int wf_b = wireframe_bot; + int wf_l = wireframe_left; + int wf_r = wireframe_right; - /* apply black border for lw >= 2 */ - if (lw > 1) { - if (yu == yblack) { - memset(src, 0, (x1-x0)*pixelsize); - } - if (xblack1 >= 0) { - src = src + (xblack1 - x0)*pixelsize; - memset(src, 0, pixelsize); - } - if (xblack2 >= 0) { - src = src + (xblack2 - x0)*pixelsize; - memset(src, 0, pixelsize); - } - } + int near_edge = 0; + + if (wf_t || wf_b || wf_l || wf_r) { + if (nabs(y - py) < wf_t) { + near_edge = 1; } - /* mark it for sending: */ - if (save[i]->saved) { - mark_rect_as_modified(x0, y_min, x1, y_max+1, 0); + if (nabs(y + h - py) < wf_b) { + near_edge = 1; + } + if (nabs(x - px) < wf_l) { + near_edge = 1; } + if (nabs(x + w - px) < wf_r) { + near_edge = 1; + } + } else { + /* all zero; always "near" edge: */ + near_edge = 1; } + return near_edge; } -/* - * utility routine for CopyRect of the window (but not CopyRegion) - */ -int crfix(int x, int dx, int Lx) { - /* adjust x so that copy source is on screen */ - if (dx > 0) { - if (x-dx < 0) { - /* off on the left */ - x = dx; +int near_scrollbar_edge(int x, int y, int w, int h, int px, int py) { + /* heuristics: */ + int sb_t = scrollcopyrect_top; + int sb_b = scrollcopyrect_bot; + int sb_l = scrollcopyrect_left; + int sb_r = scrollcopyrect_right; + + int near_edge = 0; + + if (sb_t || sb_b || sb_l || sb_r) { + if (nabs(y - py) < sb_t) { + near_edge = 1; } - } else { - if (x-dx >= Lx) { - /* off on the right */ - x = Lx + dx - 1; + if (nabs(y + h - py) < sb_b) { + near_edge = 1; + } + if (nabs(x - px) < sb_l) { + near_edge = 1; } + if (nabs(x + w - px) < sb_r) { + near_edge = 1; + } + } else { + /* all zero; always "near" edge: */ + near_edge = 1; } - return x; + return near_edge; } /* @@ -19267,7 +21642,7 @@ int crfix(int x, int dx, int Lx) { * reduces actual XShmGetImage work (i.e. if we correctly predicted how * the X fb has changed. * - * -scale doesn't work under -wirecopyrect, but the wireframe does. + * -scale doesn't always work under -wirecopyrect, but the wireframe does. * * testing of this mode under -threads is incomplete. * @@ -19285,27 +21660,22 @@ int check_wireframe(void) { int orig_px, orig_py, orig_x, orig_y, orig_w, orig_h; int px, py, x, y, w, h; int box_x, box_y, box_w, box_h; - int orig_cursor_x, orig_cursor_y, g, g_in; + int orig_cursor_x, orig_cursor_y, g; int already_down = 0, win_gone = 0, win_unmapped = 0; double spin = 0.0, tm = 0.0, last_ptr, last_draw; int frame_changed = 0, drew_box = 0, got_2nd_pointer = 0; - int break_reason = 0; + int special_t1 = 0, break_reason = 0; static double first_dt_ave = 0.0; static int first_dt_cnt = 0; static time_t last_save_stacklist = 0; /* heuristics: */ - int wf_t = wireframe_top; - int wf_b = wireframe_bot; - int wf_l = wireframe_left; - int wf_r = wireframe_right; double first_event_spin = wireframe_t1; double frame_changed_spin = wireframe_t2; double max_spin = wireframe_t3; double min_draw = wireframe_t4; - - int db = 0; - int db2 = 0; + int near_edge; + DB_SET if (subwin) { return 0; /* don't even bother for -id case */ @@ -19317,7 +21687,7 @@ int check_wireframe(void) { return 0; /* need ptr input, e.g. button down, motion */ } -if (db || db2) fprintf(stderr, "\n*** button down!! x: %d y: %d\n", cursor_x, cursor_y); +if (db) fprintf(stderr, "\n*** button down!! x: %d y: %d\n", cursor_x, cursor_y); /* * Query where the pointer is and which child of the root @@ -19333,10 +21703,6 @@ if (db) fprintf(stderr, "NO get_wm_frame_pos: 0x%lx\n", frame); X_UNLOCK; if (db) fprintf(stderr, "a: %d wf: %.3f A: %d\n", w*h, wireframe_frac, (dpy_x*dpy_y)); - if (nabs(x + w - px) < 35) { - maybe_scrolling = frame; /* not yet used... */ - } - /* * apply the percentage size criterion (allow opaque moves for * small windows) @@ -19351,24 +21717,11 @@ if (db) fprintf(stderr, " frame: x: %d y: %d w: %d h: %d px: %d py: %d fr * see if the pointer is within range of the assumed wm frame * decorations on the edge of the window. */ - if (wf_t || wf_b || wf_l || wf_r) { - int near_edge = 0; - if (nabs(y - py) < wf_t) { - near_edge = 1; - } - if (nabs(y + h - py) < wf_b) { - near_edge = 1; - } - if (nabs(x - px) < wf_l) { - near_edge = 1; - } - if (nabs(x + w - px) < wf_r) { - near_edge = 1; - } - if (! near_edge) { -if (db) fprintf(stderr, "INTERIOR %d %d %d %d\n", wf_t, wf_b, wf_l, wf_r); - return 0; - } + + near_edge = near_wm_edge(x, y, w, h, px, py); + if (! near_edge) { +if (db) fprintf(stderr, "INTERIOR\n"); + return 0; } wireframe_in_progress = 1; @@ -19376,6 +21729,27 @@ if (db) fprintf(stderr, "INTERIOR %d %d %d %d\n", wf_t, wf_b, wf_l, wf_r); if (button_mask_prev) { already_down = 1; } + + if (! wireframe_str || !strcmp(wireframe_str, WIREFRAME_PARMS)) { + int latency = get_net_latency(); + int netrate = get_net_rate(); + static int didmsg = 0; + + if (latency > 100 || netrate < 10) { /* 100ms, 10KB/sec */ + /* slow link, e.g. dialup, increase timeouts: */ + first_event_spin *= 2.0; + frame_changed_spin *= 2.0; + max_spin *= 2.0; + min_draw *= 1.5; + if (! didmsg) { + rfbLog("increased wireframe timeouts for " + "slow network connection.\n"); + rfbLog("netrate: %d KB/sec, latency: %d ms\n", + netrate, latency); + didmsg = 1; + } + } + } /* * pointer() should have snapped the stacking list for us, if @@ -19384,6 +21758,7 @@ if (db) fprintf(stderr, "INTERIOR %d %d %d %d\n", wf_t, wf_b, wf_l, wf_r); */ if (strcmp(wireframe_copyrect, "never")) { if (already_down) { + double age = 0.0; /* * see if we can reuse the stack list (pause * with button down) @@ -19396,26 +21771,18 @@ if (db) fprintf(stderr, "INTERIOR %d %d %d %d\n", wf_t, wf_b, wf_l, wf_r); break; } } - if (! got_me) { - last_save_stacklist = 0; + if (got_me) { + age = 1.0; } + snapshot_stack_list(0, age); } - if (stack_list && time(0) > last_save_stacklist + 1) { - /* stack_list too old */ - X_LOCK; - XFree(stack_list); - stack_list = NULL; - stack_num = 0; - X_UNLOCK; - } - } else if (! stack_list) { - /* ! already_down, might as well get a copy */ - X_LOCK; - snapshot_stack_list(); - X_UNLOCK; + } + if (! stack_list) { + snapshot_stack_list(0, 0.0); } } + /* store initial parameters, we look for changes in them */ orig_frame = frame; orig_px = px; /* pointer position */ @@ -19444,7 +21811,7 @@ if (db) fprintf(stderr, "INTERIOR %d %d %d %d\n", wf_t, wf_b, wf_l, wf_r); pointer(-1, 0, 0, NULL); } - g = g_in = got_pointer_input; + g = got_pointer_input; while (1) { @@ -19456,28 +21823,37 @@ if (db) fprintf(stderr, "INTERIOR %d %d %d %d\n", wf_t, wf_b, wf_l, wf_r); if (use_threads) { usleep(1000); } else if (drew_box) { - rfbPE(screen, 1000); + rfbPE(1000); } else { - rfbCFD(screen, 1000); + rfbCFD(1000); } spin += dtime(&tm); /* check for any timeouts: */ if (frame_changed) { + double delay; /* max time we play this game: */ if (spin > max_spin) { if (db || db2) fprintf(stderr, " SPIN-OUT-MAX: %.3f\n", spin); break_reason = 1; break; } - /* pointer events slowing down: */ - if (spin > last_ptr + frame_changed_spin) { + /* watch for pointer events slowing down: */ + if (special_t1) { + delay = max_spin; + } else { + delay = 2.0* frame_changed_spin; + if (spin > 3.0 * frame_changed_spin) { + delay = 1.5 * delay; + } + } + if (spin > last_ptr + delay) { if (db || db2) fprintf(stderr, " SPIN-OUT-NOT-FAST: %.3f\n", spin); break_reason = 2; break; } - } else if(got_2nd_pointer) { + } else if (got_2nd_pointer) { /* * pointer is moving, max time we wait for wm * move or resize to be detected @@ -19502,6 +21878,10 @@ if (db) fprintf(stderr, " ++pointer event!! [%02d] dt: %.3f x: %d y: %d mas g = got_pointer_input; + X_LOCK; + XFlush(dpy); + X_UNLOCK; + /* periodically try to let the wm get moving: */ if (!frame_changed && got_2nd_pointer % 4 == 0) { #if 0 @@ -19622,7 +22002,7 @@ if (db) fprintf(stderr, "FRAME MOVE 1st-dt: %.3f\n", first_dt_ave/n); ! drew_box) { draw_box(x, y, w, h, 0); drew_box = 1; - rfbPE(screen, 1000); + rfbPE(1000); last_draw = spin; } } @@ -19648,216 +22028,62 @@ if (db || db2) fprintf(stderr, "NO button_mask\n"); if (! drew_box) { /* nice try, but no move or resize detected. cleanup. */ if (stack_list) { - X_LOCK; - XFree(stack_list); - X_UNLOCK; - stack_list = NULL; - stack_num = 0; - } - wireframe_in_progress = 0; - return 0; - } - - /* remove the wireframe */ - draw_box(0, 0, 0, 0, 1); - - dx = x - orig_x; - dy = y - orig_y; - - /* - * see if we can apply CopyRect or CopyRegion to the change: - */ - if (!scaling && w == orig_w && h == orig_h && (dx != 0 || dy != 0) && - !win_gone && !win_unmapped && strcmp(wireframe_copyrect, "never")) { - - int x1, y1, x2, y2, t, tmax = 50; - int spin_ms = (int) (spin * 1000 * 1000); - int sent_copyrect = 0; - int obscured = 0; - static int dt_bad = 0; - static time_t dt_bad_check = 0; - - /* - * set a timescale comparable to the spin time, - * but not too short or too long. - */ - if (spin_ms < 30) { - spin_ms = 30; - } else if (spin_ms > 400) { - spin_ms = 400; - } - - /* try to flush the wireframe removal: */ - fb_update_sent(NULL); - rfbPE(screen, spin_ms/3); /* long select */ - for (t=0; t dt_bad_check + 5) { - if (!strcmp(guess_desktop(), "kde")) { - dt_bad = 1; - } else if (!strcmp(guess_desktop(), "xfce")) { - dt_bad = 1; - } else { - dt_bad = 0; - } - dt_bad_check = time(0); - } - - if (dt_bad) { - /* send the whole thing... */ - x1 = crfix(nfix(x, dpy_x), dx, dpy_x); - y1 = crfix(nfix(y, dpy_y), dy, dpy_y); - x2 = crfix(nfix(x+w, dpy_x), dx, dpy_x); - y2 = crfix(nfix(y+h, dpy_y), dy, dpy_y); - - rfbDoCopyRect(screen, x1, y1, x2, y2, dx, dy); - - sent_copyrect = 1; - obscured = 1; /* set to avoid an aggressive push */ - -if (db2) fprintf(stderr, "CopyRect: x1: %d y1: %d x2: %d y2: %d dx: %d dy: %d spin: %.3f\n", x1, y1, x2, y2, dx, dy, spin); - - } else if (stack_list) { - int k, tx1, tx2, ty1, ty2; - sraRegionPtr moved_win, tmp_win; - int saw_me = 0; - - tx1 = nfix(orig_x, dpy_x); - ty1 = nfix(orig_y, dpy_y); - tx2 = nfix(orig_x+w, dpy_x); - ty2 = nfix(orig_y+h, dpy_y); - -if (db2) fprintf(stderr, "moved_win: %4d %3d, %4d %3d 0x%lx ---\n", tx1, ty1, tx2, ty2, frame); - - moved_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); - - X_LOCK; - - /* - * loop over the stack, top to bottom until we - * find our wm frame: - */ - for (k = stack_num - 1; k >= 0; k--) { - Window win = stack_list[k]; - if (win == frame) { -if (db2) { - saw_me = 1; - fprintf(stderr, " ----------\n"); -} else { - break; -} - } - - /* skip some unwanted cases: */ - if (win == None) { - continue; - } - if (!valid_window(win, &attr)) { - continue; - } - if (attr.map_state != IsViewable) { - continue; - } + X_LOCK; + XFree(stack_list); + X_UNLOCK; + stack_list = NULL; + stack_num = 0; + } + wireframe_in_progress = 0; + return 0; + } - /* clip the window to the visable screen: */ - tx1 = nfix(attr.x, dpy_x); - ty1 = nfix(attr.y, dpy_y); - tx2 = nfix(attr.x + attr.width, dpy_x); - ty2 = nfix(attr.y + attr.height, dpy_y); + /* remove the wireframe */ + draw_box(0, 0, 0, 0, 1); -if (db2) fprintf(stderr, " tmp_win: %4d %3d, %4d %3d 0x%lx\n", tx1, ty1, tx2, ty2, win); -if (db2 && saw_me) continue; + dx = x - orig_x; + dy = y - orig_y; - /* see if window clips us: */ - tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); - if (sraRgnAnd(tmp_win, moved_win)) { - obscured = 1; -if (db2) fprintf(stderr, " : clips it.\n"); - } - sraRgnDestroy(tmp_win); + /* + * see if we can apply CopyRect or CopyRegion to the change: + */ + if (!strcmp(wireframe_copyrect, "never")) { + ; + } else if (win_gone || win_unmapped) { + ; + } else if (scaling && ! got_wirecopyrect) { + ; + } else if (w != orig_w || h != orig_h) { + ; + } else if (dx == 0 && dy == 0) { + ; + } else { + int spin_ms = (int) (spin * 1000 * 1000); + int obscured, sent_copyrect = 0; - /* subtract it from our region: */ - tmp_win = sraRgnCreateRect(tx1, ty1, tx2, ty2); - sraRgnSubtract(moved_win, tmp_win); - sraRgnDestroy(tmp_win); - } - X_UNLOCK; + /* + * set a timescale comparable to the spin time, + * but not too short or too long. + */ + if (spin_ms < 30) { + spin_ms = 30; + } else if (spin_ms > 400) { + spin_ms = 400; + } - if (obscured && !strcmp(wireframe_copyrect, "top")) { - ; /* cannot send CopyRegion */ - } else if (! sraRgnEmpty(moved_win)) { - sraRectangleIterator *iter; - sraRegionPtr whole, shifted_region; - sraRect rect; + /* try to flush the wireframe removal: */ + fb_push(); - /* - * prepare for CopyRegion, apply dx and - * dy to each rectangle in the region. - * keep only the part on the screen. - */ - whole = sraRgnCreateRect(0, 0, dpy_x, dpy_y); - shifted_region = sraRgnCreate(); - -if (db2) fprintf(stderr, "\n"); - - iter = sraRgnGetIterator(moved_win); - while (sraRgnIteratorNext(iter, &rect)) { - tx1 = rect.x1 + dx; - ty1 = rect.y1 + dy; - tx2 = rect.x2 + dx; - ty2 = rect.y2 + dy; - -if (db2) fprintf(stderr, " shf_win: %4d %3d, %4d %3d\n", tx1, ty1, tx2, ty2); - - tmp_win = sraRgnCreateRect(tx1, ty1, - tx2, ty2); - sraRgnAnd(tmp_win, whole); - if (! sraRgnEmpty(tmp_win)) { - sraRgnOr(shifted_region, - tmp_win); - } - sraRgnDestroy(tmp_win); - } - sraRgnReleaseIterator(iter); - sraRgnDestroy(whole); - - /* now send the CopyRegion: */ - if (! sraRgnEmpty(shifted_region)) { - rfbDoCopyRegion(screen, shifted_region, - dx, dy); - sent_copyrect = 1; -if (db2) fprintf(stderr, " rfbDoCopyRegion\n"); - } - sraRgnDestroy(shifted_region); - } - sraRgnDestroy(moved_win); - } + /* try to send a clipped copyrect of translation: */ + sent_copyrect = try_copyrect(frame, x, y, w, h, dx, dy, + &obscured); if (sent_copyrect) { - /* try to push the changes to viewers: */ - fb_update_sent(NULL); - rfbPE(screen, spin_ms/3); if (! obscured) { - for (t=0; t g) { @@ -20055,9 +22280,9 @@ static void check_user_input3(double dt) { int mcut = miss_max; if (show_multiple_cursors) { - rfbPE(screen, 1000); + rfbPE(1000); } else { - rfbCFD(screen, 1000); + rfbCFD(1000); } X_LOCK; XFlush(dpy); @@ -20136,9 +22361,9 @@ static void check_user_input3(double dt) { for (i=0; i g) { @@ -20212,7 +22437,7 @@ static void check_user_input4(double dt, double dtr, int tile_diffs) { drag_in_progress = 1; } - rfbCFD(screen, rfb_wait_ms * 1000); + rfbCFD(rfb_wait_ms * 1000); dtm = dtime(&tm); spin += dtm; @@ -20272,7 +22497,7 @@ static void check_user_input4(double dt, double dtr, int tile_diffs) { if (ginput >= 2) { /* try for a couple more quick ones */ for (i=0; i<2; i++) { - rfbCFD(screen, rfb_wait_ms * 1000); + rfbCFD(rfb_wait_ms * 1000); } } @@ -20385,7 +22610,7 @@ static void check_user_input5(double dt, double dtr, int tile_diffs) { } /* damn, they didn't push our frame! */ iter++; - rfbPE(screen, rfb_wait_ms * 1000); + rfbPE(rfb_wait_ms * 1000); push_spin += dtime(&tp); } @@ -20443,7 +22668,7 @@ static void check_user_input5(double dt, double dtr, int tile_diffs) { } /* turn libvncserver crank to process events: */ - rfbCFD(screen, rfb_wait_ms * 1000); + rfbCFD(rfb_wait_ms * 1000); dtm = dtime(&tm); spin += dtm; @@ -20507,7 +22732,7 @@ static void check_user_input5(double dt, double dtr, int tile_diffs) { if (ginput >= 2) { /* try for a couple more quick ones */ for (i=0; i<2; i++) { - rfbCFD(screen, rfb_wait_ms * 1000); + rfbCFD(rfb_wait_ms * 1000); } } drag_in_progress = 0; @@ -20517,7 +22742,15 @@ static int check_user_input(double dt, double dtr, int tile_diffs, int *cnt) { if (raw_fb && ! dpy) return 0; /* raw_fb hack */ - maybe_scrolling = None; + if (use_xrecord) { + int rc = check_xrecord(); + if (rc == 1) { + return 0; + } else if (rc == 2) { +if (0) fprintf(stderr, " CXR: check_user_input ret 1\n"); + return 1; + } + } if (wireframe) { if (check_wireframe()) { @@ -20593,67 +22826,90 @@ double dtime(double *t_old) { void measure_display_hook(rfbClientPtr cl) { ClientData *cd = (ClientData *) cl->clientData; +#if 0 +double tm = 0.0; +dtime(&tm); +fprintf(stderr, " MDH: %.4f\n", tm); +#endif cd->timer = 0.0; dtime(&cd->timer); } -void measure_send_rates_init(void) { - int i, bs, rbs; +int get_rate(int which) { rfbClientIteratorPtr iter; rfbClientPtr cl; - - screen->displayHook = measure_display_hook; - + int irate, irate_min = 1; /* 1 KB/sec */ + int irate_max = 100000; /* 100 MB/sec */ + double slowest = -1.0, rate; + static double save_rate = 10000.0; /* 10000.0 B/sec */ + iter = rfbGetClientIterator(screen); while( (cl = rfbClientIteratorNext(iter)) ) { ClientData *cd = (ClientData *) cl->clientData; - bs = 0; - for (i=0; ibytesSent[i]; + + if (which == 0) { + rate = cd->send_cmp_rate; + } else { + rate = cd->send_raw_rate; + } + if (slowest == -1.0 || rate < slowest) { + slowest = rate; } - rbs = cl->rawBytesEquivalent; - cd->set_cmp_bytes = bs; - cd->set_raw_bytes = rbs; - cd->timer = -1.0; } rfbReleaseClientIterator(iter); + + if (slowest == -1.0) { + slowest = save_rate; + } else { + save_rate = slowest; + } + + irate = (int) (slowest/1000.0); + if (irate < irate_min) { + irate = irate_min; + } + if (irate > irate_max) { + irate = irate_max; + } + + return irate; } -int get_rate(int which) { +int get_latency(void) { rfbClientIteratorPtr iter; rfbClientPtr cl; - int i, samples = RATE_SAMPLES; - double dslowest = -1.0, dsum; + int ilat, ilat_min = 1; /* 1 ms */ + int ilat_max = 2000; /* 2 sec */ + double slowest = -1.0, lat; + static double save_lat = 0.020; /* 20 ms */ iter = rfbGetClientIterator(screen); while( (cl = rfbClientIteratorNext(iter)) ) { ClientData *cd = (ClientData *) cl->clientData; - - dsum = 0.0; - for (i=0; icmp_samp[i]; - } else { - dsum += cd->raw_samp[i]; - } - } - dsum = dsum / samples; - if (dsum > dslowest) { - dslowest = dsum; - } + lat = cd->latency; + if (slowest == -1.0 || lat > slowest) { + slowest = lat; + } } rfbReleaseClientIterator(iter); - if (dslowest < 0.0) { - if (which == 0) { - dslowest = 5000.0; - } else { - dslowest = 50000.0; - } + if (slowest == -1.0) { + slowest = save_lat; + } else { + save_lat = slowest; + } + + ilat = (int) (slowest * 1000.0); + if (ilat < ilat_min) { + ilat = ilat_min; } - return (int) dslowest; + if (ilat > ilat_max) { + ilat = ilat_max; + } + + return ilat; } int get_cmp_rate(void) { @@ -20665,24 +22921,26 @@ int get_raw_rate(void) { } void initialize_speeds(void) { - char *s, *p; + char *s, *s_in, *p; int i; speeds_read_rate = 0; speeds_net_rate = 0; speeds_net_latency = 0; if (! speeds_str || *speeds_str == '\0') { - return; + s_in = strdup(""); + } else { + s_in = strdup(speeds_str); } - if (!strcmp(speeds_str, "modem")) { + if (!strcmp(s_in, "modem")) { s = strdup("6,4,200"); - } else if (!strcmp(speeds_str, "dsl")) { + } else if (!strcmp(s_in, "dsl")) { s = strdup("6,100,50"); - } else if (!strcmp(speeds_str, "lan")) { + } else if (!strcmp(s_in, "lan")) { s = strdup("6,5000,1"); } else { - s = strdup(speeds_str); + s = strdup(s_in); } p = strtok(s, ","); @@ -20703,12 +22961,40 @@ void initialize_speeds(void) { p = strtok(NULL, ","); } free(s); + free(s_in); + + if (! speeds_read_rate) { + int n = 0; + double dt, timer = 0.0; + dtime(&timer); + if (fullscreen) { + copy_image(fullscreen, 0, 0, 0, 0); + n = fullscreen->bytes_per_line * fullscreen->height; + } else if (scanline) { + copy_image(scanline, 0, 0, 0, 0); + n = scanline->bytes_per_line * scanline->height; + } + dt = dtime(&timer); + if (n && dt > 0.0) { + double rate = ((double) n) / dt; + speeds_read_rate_measured = (int) (rate/1000000.0); + if (speeds_read_rate_measured < 1) { + speeds_read_rate_measured = 1; + } else { + rfbLog("fb read rate: %d MB/sec\n", + speeds_read_rate_measured); + } + } + } } int get_read_rate(void) { if (speeds_read_rate) { return speeds_read_rate; } + if (speeds_read_rate_measured) { + return speeds_read_rate_measured; + } return 0; } @@ -20716,6 +23002,12 @@ int get_net_rate(void) { if (speeds_net_rate) { return speeds_net_rate; } + if (! speeds_net_rate_measured) { + speeds_net_rate_measured = get_cmp_rate(); + } + if (speeds_net_rate_measured) { + return speeds_net_rate_measured; + } return 0; } @@ -20723,143 +23015,319 @@ int get_net_latency(void) { if (speeds_net_latency) { return speeds_net_latency; } + if (! speeds_net_latency_measured) { + speeds_net_latency_measured = get_latency(); + } + if (speeds_net_latency_measured) { + return speeds_net_latency_measured; + } return 0; } void measure_send_rates(int init) { - int i, j, nclient = 0; - int min_width = 200; - double dt, cmp_rate, raw_rate; - rfbClientPtr id[100]; - double dts[100], dts_sorted[100], dtmp; - int sorted[100], did[100], best; + double cmp_rate, raw_rate; + static double now, start = 0.0; + static rfbDisplayHookPtr orig_display_hook = NULL; + double cmp_max = 1.0e+08; /* 100 MB/sec */ + double cmp_min = 1000.0; /* 9600baud */ + double lat_max = 5.0; /* 5 sec */ + double lat_min = .0005; /* 0.5 ms */ + int min_cmp = 10000, nclients; rfbClientIteratorPtr iter; rfbClientPtr cl; + int db = 0; if (! measure_speeds) { return; } - if (init) { - measure_send_rates_init(); + if (speeds_net_rate && speeds_net_latency) { return; } + if (! orig_display_hook) { + orig_display_hook = screen->displayHook; + } + + if (start == 0.0) { + dtime(&start); + } + now = 0.0; + dtime(&now); + now = now - start; + + nclients = 0; + iter = rfbGetClientIterator(screen); while( (cl = rfbClientIteratorNext(iter)) ) { - double tmp2; + int defer, i, cbs, rbs; + char *httpdir; + double dt, dt1 = 0.0, dt2, dt3; + double tm, spin_max = 15.0, spin_lat_max = 1.5; + int got_t2 = 0, got_t3 = 0; ClientData *cd = (ClientData *) cl->clientData; - tmp2 = 0.0; - dtime(&tmp2); -if (init) { - continue; -} - if (cd->timer <= 0.0) { + + if (cd->send_cmp_rate > 0.0) { continue; } - dt = dtime(&cd->timer); - cd->timer = dt; - if (nclient < 100) { - id[nclient] = cl; - dts[nclient] = dt; - nclient++; + nclients++; + + cbs = 0; + for (i=0; ibytesSent[i]; } - } - rfbReleaseClientIterator(iter); -if (init) { - return; -} + rbs = cl->rawBytesEquivalent; - for (i=0; i dtmp) { - best = j; - dtmp = dts[j]; - } - } - did[best] = 1; - sorted[i] = best; - dts_sorted[i] = dts[best]; - } - + if (init) { + double tmr = 0.0; - iter = rfbGetClientIterator(screen); - while( (cl = rfbClientIteratorNext(iter)) ) { - int db, dbr, cbs, rbs; - ClientData *cd = (ClientData *) cl->clientData; +if (db) fprintf(stderr, "%d client num rects req: %d mod: %d cbs: %d " + "rbs: %d dt1: %.3f t: %.3f\n", init, + (int) sraRgnCountRects(cl->requestedRegion), + (int) sraRgnCountRects(cl->modifiedRegion), cbs, rbs, dt1, now); - dt = cd->timer; - if (dt <= 0.0) { + dtime(&tmr); + cd->timer = tmr; + cd->cmp_bytes_sent = cbs; + cd->raw_bytes_sent = rbs; continue; } - if (nclient > 1) { - for (i=0; itimer); + +if (db) fprintf(stderr, "%d client num rects req: %d mod: %d cbs: %d " + "rbs: %d dt1: %.3f t: %.3f\n", init, + (int) sraRgnCountRects(cl->requestedRegion), + (int) sraRgnCountRects(cl->modifiedRegion), cbs, rbs, dt1, now); + + if (dt1 <= 0.0) { + continue; + } + + cbs = cbs - cd->cmp_bytes_sent; + rbs = rbs - cd->raw_bytes_sent; + + if (cbs < min_cmp) { + continue; + } + + rfbPE(1000); + if (sraRgnCountRects(cl->modifiedRegion)) { + rfbPE(1000); + } + + defer = screen->deferUpdateTime; + httpdir = screen->httpDir; + screen->deferUpdateTime = 0; + screen->httpDir = NULL; + + /* mark a small rectangle: */ + mark_rect_as_modified(0, 0, 16, 16, 1); + + tm = 0.0; + dtime(&tm); + + dt2 = 0.0; + dt3 = 0.0; + + if (dt1 < 0.25) { + /* try to cut it down to avoid long pauses. */ + spin_max = 5.0; + } + + /* when req1 = 1 mod1 == 0, end of 2nd part of bulk transfer */ + while (1) { + int req0, req1, mod0, mod1; + req0 = sraRgnCountRects(cl->requestedRegion); + mod0 = sraRgnCountRects(cl->modifiedRegion); + if (use_threads) { + usleep(1000); + } else { + if (mod0) { + rfbPE(1000); + } else { + rfbCFD(1000); } - for (j=0; j spin_max) { + break; + } + req1 = sraRgnCountRects(cl->requestedRegion); + mod1 = sraRgnCountRects(cl->modifiedRegion); + +if (db) fprintf(stderr, "dt2 calc: num rects req: %d/%d mod: %d/%d " + "fbu-sent: %d dt: %.4f dt2: %.4f tm: %.4f\n", + req0, req1, mod0, mod1, cl->framebufferUpdateMessagesSent, dt, dt2, tm); + if (req1 != 0 && mod1 == 0) { + got_t2 = 1; + break; + } + } + + if (! got_t2) { + dt2 = 0.0; + } else { + int tr, trm = 3; + double dts[10]; + + /* + * Note: since often select(2) cannot sleep + * less than 1/HZ (e.g. 10ms), the resolution + * of the latency may be messed up by something + * of this order. Effect may occur on both ends, + * i.e. the viewer may not respond immediately. + */ + + for (tr = 0; tr < trm; tr++) { + usleep(5000); + + /* mark a 2nd small rectangle: */ + mark_rect_as_modified(0, 0, 16, 16, 1); + i = 0; + tm = 0.0; + dtime(&tm); + dt3 = 0.0; + + /* + * when req1 > 0 and mod1 == 0, we say + * that is the "ping" time. + */ + while (1) { + int req0, req1, mod0, mod1; + + req0 = sraRgnCountRects( + cl->requestedRegion); + mod0 = sraRgnCountRects( + cl->modifiedRegion); + + if (i == 0) { + rfbPE(0); + } else { + if (use_threads) { + usleep(1000); + } else { + /* try to get it all */ + rfbCFD(1000*1000); } } + dt = dtime(&tm); + i++; + + dt3 += dt; + if (dt3 > spin_lat_max) { + break; + } + req1 = sraRgnCountRects( + cl->requestedRegion); + + mod1 = sraRgnCountRects( + cl->modifiedRegion); + +if (db) fprintf(stderr, "dt3 calc: num rects req: %d/%d mod: %d/%d " + "fbu-sent: %d dt: %.4f dt3: %.4f tm: %.4f\n", + req0, req1, mod0, mod1, cl->framebufferUpdateMessagesSent, dt, dt3, tm); + + if (req1 != 0 && mod1 == 0) { + dts[got_t3++] = dt3; + break; + } + } + } + + if (! got_t3) { + dt3 = 0.0; + } else { + if (got_t3 == 1) { + dt3 = dts[0]; + } else if (got_t3 == 2) { + dt3 = dts[1]; + } else { + if (dts[2] >= 0.0) { + double rat = dts[1]/dts[2]; + if (rat > 0.5 && rat < 2.0) { + dt3 = dts[1]+dts[2]; + dt3 *= 0.5; + } else { + dt3 = dts[1]; + } + } else { + dt3 = dts[1]; + } } - break; } } - if (dt <= 0.0) { - continue; + + screen->deferUpdateTime = defer; + screen->httpDir = httpdir; + + dt = dt1 + dt2; + + + if (dt3 <= dt2/2.0) { + /* guess only 1/2 a ping for reply... */ + dt = dt - dt3/2.0; } - cbs = 0; - for (i=0; ibytesSent[i]; + cmp_rate = cbs/dt; + raw_rate = rbs/dt; + + if (cmp_rate > cmp_max) { + cmp_rate = cmp_max; + } + if (cmp_rate <= cmp_min) { + cmp_rate = cmp_min; } - rbs = cl->rawBytesEquivalent; - db = cbs - cd->set_cmp_bytes; - dbr = rbs - cd->set_raw_bytes; - cmp_rate = db/dt; - raw_rate = dbr/dt; - if (dbr > min_width * min_width * bpp/8) { - cd->sample++; - if (cd->sample >= RATE_SAMPLES) { - cd->sample = 0; - } - i = cd->sample; - cd->cmp_samp[i] = cmp_rate; - cd->raw_samp[i] = raw_rate; + cd->send_cmp_rate = cmp_rate; + cd->send_raw_rate = raw_rate; + + if (dt3 > lat_max) { + dt3 = lat_max; + } + if (dt3 <= lat_min) { + dt3 = lat_min; } + + cd->latency = dt3; + + rfbLog("client %d network rate %.1f KB/sec (%.1f eff KB/sec)\n", + cd->uid, cmp_rate/1000.0, raw_rate/1000.0); + rfbLog("client %d latency: %.1f ms\n", cd->uid, 1000.0*dt3); + rfbLog("dt1: %.4f, dt2: %.4f dt3: %.4f bytes: %d\n", + dt1, dt2, dt3, cbs); } rfbReleaseClientIterator(iter); + + if (init) { + if (nclients) { + screen->displayHook = measure_display_hook; + } + } else { + screen->displayHook = orig_display_hook; + } } /* * utility wrapper to call rfbProcessEvents * checks that we are not in threaded mode. */ -void rfbPE(rfbScreenInfoPtr scr, long usec) { - if (! scr) { +void rfbPE(long usec) { + if (! screen) { return; } if (! use_threads) { - rfbProcessEvents(scr, usec); + rfbProcessEvents(screen, usec); } } -void rfbCFD(rfbScreenInfoPtr scr, long usec) { - if (! scr) { +void rfbCFD(long usec) { + if (! screen) { return; } if (! use_threads) { - rfbCheckFds(scr, usec); + rfbCheckFds(screen, usec); } } @@ -20880,11 +23348,14 @@ static void watch_loop(void) { got_user_input = 0; got_pointer_input = 0; got_keyboard_input = 0; + urgent_update = 0; if (! use_threads) { double tm = 0.0; dtime(&tm); - rfbPE(screen, -1); + measure_send_rates(1); + rfbPE(-1); + measure_send_rates(0); dtr = dtime(&tm); fb_update_sent(NULL); @@ -20895,6 +23366,7 @@ static void watch_loop(void) { if (screen && screen->clientHead && check_user_input(dt, dtr, tile_diffs, &cnt)) { /* true means loop back for more input */ +if (0) fprintf(stderr, "watch_loop: LOOP-BACK\n"); continue; } } else if (use_threads && wireframe && button_mask) { @@ -20905,23 +23377,25 @@ static void watch_loop(void) { clean_up_exit(0); } - if (do_copy_screen) { - do_copy_screen = 0; - copy_screen(); - } + if (! urgent_update) { + if (do_copy_screen) { + do_copy_screen = 0; + copy_screen(); + } - check_new_clients(); - check_xevents(); - check_connect_inputs(); - check_padded_fb(); - check_xdamage_state(); - if (started_as_root) { - check_switched_user(); - } + check_new_clients(); + check_xevents(); + check_connect_inputs(); + check_padded_fb(); + check_xdamage_state(); + if (started_as_root) { + check_switched_user(); + } - if (first_conn_timeout < 0) { - start = time(0); - first_conn_timeout = -first_conn_timeout; + if (first_conn_timeout < 0) { + start = time(0); + first_conn_timeout = -first_conn_timeout; + } } if (! screen || ! screen->clientHead) { @@ -20962,7 +23436,6 @@ static void watch_loop(void) { double tm = 0.0; dtime(&tm); - RFBUNDRAWCURSOR(screen); if (use_snapfb) { int t, tries = 5; copy_snap(); @@ -20973,6 +23446,9 @@ static void watch_loop(void) { tile_diffs = scan_for_updates(0); } dt = dtime(&tm); +if (0 && tile_diffs > 4) { +fprintf(stderr, "TILES: %d dt: %.4f t: %.4f\n", tile_diffs, dt, tm); +} check_x11_pointer(); } @@ -21701,7 +24177,10 @@ static void print_help(int mode) { " heuristics and may not always work: it depends on your\n" " window manager and even how you move things around.\n" " See -pointer_mode below for discussion of the \"bogging\n" -" down\" problem this tries to avoid. Default: %s\n" +" down\" problem this tries to avoid.\n" +" Default: %s\n" +"\n" +" Shorter aliases: -wf [str] and -nowf\n" "\n" " The value \"str\" is optional and, of course, is\n" " packed with many tunable parameters for this scheme:\n" @@ -21739,16 +24218,67 @@ static void print_help(int mode) { " link this might be a better choice: 0.25+0.6+6.0+0.15\n" "\n" "-wirecopyrect mode Since the -wireframe mechanism evidently tracks moving\n" -"-nowirecopyrect windows, a speedup can be obtained by telling the VNC\n" -" viewers to locally copy the translated window region.\n" -" This is the VNC CopyRect encoding: the framebuffer\n" -" update doesn't need to send the actual new image data.\n" +"-nowirecopyrect windows accurately, a speedup can be obtained by\n" +" telling the VNC viewers to locally copy the translated\n" +" window region. This is the VNC CopyRect encoding:\n" +" the framebuffer update doesn't need to send the actual\n" +" new image data.\n" +"\n" +" Shorter aliases: -wcr [mode] and -nowcr\n" +"\n" " \"mode\" can be \"never\" (same as -nowirecopyrect)\n" " to never try the copyrect, \"top\" means only do it if\n" " the window was not covered by any other windows, and\n" " \"always\" means to translate the orginally unobscured\n" " region (this may look odd as the remaining pieces come\n" -" in, but helps on a slow link) Default: %s\n" +" in, but helps on a slow link). Default: \"%s\"\n" +"\n" +" Note: there can be painting errors when using -scale\n" +" so CopyRect is skipped when scaling unless you specify\n" +" -wirecopyrect on the command line or by remote-control.\n" +"\n" +"-scrollcopyrect mode Like -wirecopyrect, but use heuristics to try to guess\n" +"-noscrollcopyrect if a window has scrolled its contents (either vertically\n" +" or horizontally). This requires the RECORD X extension\n" +" to \"snoop\" on X applications (currently for certain\n" +" XCopyArea and XConfigureWindow X protocol requests).\n" +" Examples: Hitting in a terminal window when the\n" +" cursor was at the bottom, the text scrolls up one line.\n" +" Hitting arrow in a web browser window, the web\n" +" page scrolls up a small amount.\n" +"\n" +" Shorter aliases: -scr [mode] and -noscr\n" +"\n" +" This scheme will not always detect scrolls, but when\n" +" it does there is a nice speedup from using the VNC\n" +" CopyRect encoding (see -wirecopyrect). The speedup\n" +" is both in reduced network traffic and reduced X\n" +" framebuffer polling/copying. On the other hand,\n" +" it may induce undesired transients (e.g. a terminal\n" +" cursor being scrolled up when it should not be) or other\n" +" painting errors. These are automatically repaired in a\n" +" short period of time. If this is unacceptable disable\n" +" the feature with -noscrollcopyrect.\n" +"\n" +" \"mode\" can be \"never\" (same as -noscrollcopyrect)\n" +" to never try the copyrect, \"keys\" means to try it\n" +" in response to keystrokes only, \"mouse\" means to\n" +" try it in response to mouse events only, \"always\"\n" +" means to do both. Default: \"%s\"\n" +"\n" +" Note: there can be painting errors when using\n" +" -scale so CopyRect is skipped when scaling unless\n" +" you specify -scrollcopyrect on the command line or\n" +" by remote-control.\n" +"\n" +"-scr_area n Set the minimum area in pixels for a rectangle\n" +" to be considered for the -scrollcopyrect detection\n" +" scheme. This is to avoid wasting the effort on small\n" +" rectangles that would be quickly updated the normal way.\n" +" E.g. suppose an app updated the position of its skinny\n" +" scrollbar first and then shifted the large panel\n" +" it controlled. We want to be sure to skip the small\n" +" scrollbar and get the large panel. Default: %d\n" "\n" "-pointer_mode n Various pointer motion update schemes. \"-pm\" is\n" " an alias. The problem is pointer motion can cause\n" @@ -22204,11 +24734,14 @@ static void print_help(int mode) { " buttonmap:str set -buttonmap \"str\", empty to disable\n" " dragging disable -nodragging mode.\n" " nodragging enable -nodragging mode.\n" -" wireframe enable -wireframe mode.\n" -" nowireframe disable -wireframe mode.\n" +" wireframe enable -wireframe mode. same as \"wf\"\n" +" nowireframe disable -wireframe mode. same as \"nowf\"\n" " wireframe:str enable -wireframe mode string.\n" " wireframe_mode:str enable -wireframe mode string.\n" -" wirecopyrect:str set -wirecopyrect string.\n" +" wirecopyrect:str set -wirecopyrect string. same as \"wcr:\"\n" +" scrollcopyrect:str set -scrollcopyrect string. same \"scr\"\n" +" noscrollcopyrect disable -scrollcopyrect mode. \"noscr\"\n" +" scr_area:n set -scr_area to n\n" " pointer_mode:n set -pointer_mode to n. same as \"pm\"\n" " input_skip:n set -input_skip to n.\n" " speeds:str set -speeds to str.\n" @@ -22251,6 +24784,15 @@ static void print_help(int mode) { " dontdisconnect enable -dontdisconnect mode.\n" " nodontdisconnect disable -dontdisconnect mode.\n" " (may interfere with other options)\n" +" debug_xevents enable debugging X events.\n" +" nodebug_xevents disable debugging X events.\n" +" debug_xdamage enable debugging X DAMAGE mechanism.\n" +" nodebug_xdamage disable debugging X DAMAGE mechanism.\n" +" debug_wireframe enable debugging wireframe mechanism.\n" +" nodebug_wireframe disable debugging wireframe mechanism.\n" +" debug_scroll enable debugging scrollcopy mechanism.\n" +" nodebug_scroll disable debugging scrollcopy mechanism.\n" +"\n" " noremote disable the -remote command processing,\n" " it cannot be turned back on.\n" "\n" @@ -22307,13 +24849,14 @@ static void print_help(int mode) { " add_keysyms noadd_keysyms clear_mods noclear_mods\n" " clear_keys noclear_keys remap repeat norepeat\n" " fb nofb bell nobell sel nosel primary noprimary\n" -" cursorshape nocursorshape cursorpos nocursorpos\n" -" cursor show_cursor noshow_cursor nocursor arrow\n" -" xfixes noxfixes xdamage noxdamage xd_area xd_mem\n" -" alphacut alphafrac alpharemove noalpharemove alphablend\n" -" noalphablend xwarp xwarppointer noxwarp noxwarppointer\n" -" buttonmap dragging nodragging wireframe_mode\n" -" wireframe nowireframe wirecopyrect nowirecopyrect\n" +" cursorshape nocursorshape cursorpos nocursorpos cursor\n" +" show_cursor noshow_cursor nocursor arrow xfixes noxfixes\n" +" xdamage noxdamage xd_area xd_mem alphacut alphafrac\n" +" alpharemove noalpharemove alphablend noalphablend\n" +" xwarp xwarppointer noxwarp noxwarppointer buttonmap\n" +" dragging nodragging wireframe_mode wireframe wf\n" +" nowireframe nowf wirecopyrect wcr nowirecopyrect nowcr\n" +" scr_area scrollcopyrect scr noscrollcopyrect noscr\n" " pointer_mode pm input_skip input client_input speeds\n" " debug_pointer dp nodebug_pointer nodp debug_keyboard dk\n" " nodebug_keyboard nodk deferupdate defer wait rfbwait\n" @@ -22323,19 +24866,23 @@ static void print_help(int mode) { " noalwaysshared nevershared noalwaysshared dontdisconnect\n" " nodontdisconnect desktop noremote\n" "\n" -" aro= debug_xevents debug_xdamage display vncdisplay\n" -" desktopname http_url auth users rootshift clipshift\n" -" scale_str scaled_x scaled_y scale_numer scale_denom\n" -" scale_fac scaling_blend scaling_nomult4 scaling_pad\n" -" scaling_interpolate inetd privremote unsafe safer\n" -" nocmds passwdfile using_shm logfile o flag rc norc h\n" -" help V version lastmod bg sigpipe threads pipeinput\n" -" clients client_count pid ext_xtest ext_xtrap ext_xkb\n" -" ext_xshm ext_xinerama ext_overlay ext_xfixes ext_xdamage\n" -" ext_xrandr rootwin num_buttons button_mask mouse_x\n" -" mouse_y bpp depth indexed_color dpy_x dpy_y wdpy_x\n" -" wdpy_y off_x off_y cdpy_x cdpy_y coff_x coff_y rfbauth\n" -" passwd\n" +" aro= debug_xevents nodebug_xevents debug_xevents\n" +" debug_xdamage nodebug_xdamage debug_xdamage\n" +" debug_wireframe nodebug_wireframe debug_wireframe\n" +" debug_scroll nodebug_scroll debug_scroll display\n" +" vncdisplay desktopname http_url auth users rootshift\n" +" clipshift scale_str scaled_x scaled_y scale_numer\n" +" scale_denom scale_fac scaling_blend scaling_nomult4\n" +" scaling_pad scaling_interpolate inetd privremote\n" +" unsafe safer nocmds passwdfile using_shm logfile\n" +" o flag rc norc h help V version lastmod bg sigpipe\n" +" threads readrate netrate netlatency pipeinput clients\n" +" client_count pid ext_xtest ext_xtrap ext_xrecord\n" +" ext_xkb ext_xshm ext_xinerama ext_overlay ext_xfixes\n" +" ext_xdamage ext_xrandr rootwin num_buttons button_mask\n" +" mouse_x mouse_y bpp depth indexed_color dpy_x dpy_y\n" +" wdpy_x wdpy_y off_x off_y cdpy_x cdpy_y coff_x coff_y\n" +" rfbauth passwd\n" "\n" "-sync By default -remote commands are run asynchronously, that\n" " is, the request is posted and the program immediately\n" @@ -22357,6 +24904,8 @@ static void print_help(int mode) { " taken place.\n" "\n" "-noremote Do not process any remote control commands or queries.\n" +"-yesremote Do process remote control commands or queries.\n" +" Default: %s\n" "\n" " A note about security wrt remote control commands.\n" " If someone can connect to the X display and change\n" @@ -22386,10 +24935,10 @@ static void print_help(int mode) { "-safer Equivalent to: -novncconnect -noremote and prohibiting\n" " -gui and the -connect file. Shuts off communcation\n" " channels.\n" -"-privremote Perform some sanity checks and only allow remote-control\n" +"-privremote Perform some sanity checks and disable remote-control\n" " commands if it appears that the X DISPLAY and/or\n" -" connectfile cannot be accessed by other users. (not\n" -" complete, does not check for empty access control list)\n" +" connectfile can be accessed by other users. Once\n" +" remote-control is disabled it cannot be turned back on.\n" "-nocmds No external commands (e.g. system(3), popen(3), exec(3))\n" " will be run.\n" "\n" @@ -22441,6 +24990,8 @@ static void print_help(int mode) { wireframe ? "-wireframe":"-nowireframe", WIREFRAME_PARMS, wireframe_copyrect_default, + scroll_copyrect_default, + scrollcopyrect_min_area, pointer_mode_max, pointer_mode, ui_skip, defer_update, @@ -22453,6 +25004,7 @@ static void print_help(int mode) { gaps_fill, grow_fill, tile_fuzz, + accept_remote_cmds ? "-yesremote":"-noremote", "" ); @@ -23134,6 +25686,8 @@ int main(int argc, char* argv[]) { } else if (!strcmp(arg, "-arrow")) { CHECK_ARGC alt_arrow = atoi(argv[++i]); + } else if (!strcmp(arg, "-xfixes")) { + use_xfixes = 1; } else if (!strcmp(arg, "-noxfixes")) { use_xfixes = 0; } else if (!strcmp(arg, "-alphacut")) { @@ -23160,7 +25714,8 @@ int main(int argc, char* argv[]) { pointer_remap = strdup(argv[++i]); } else if (!strcmp(arg, "-nodragging")) { show_dragging = 0; - } else if (!strcmp(arg, "-wireframe")) { + } else if (!strcmp(arg, "-wireframe") + || !strcmp(arg, "-wf")) { wireframe = 1; if (i < argc-1) { char *s = argv[i+1]; @@ -23168,13 +25723,32 @@ int main(int argc, char* argv[]) { wireframe_str = strdup(argv[++i]); } } - } else if (!strcmp(arg, "-nowireframe")) { + } else if (!strcmp(arg, "-nowireframe") + || !strcmp(arg, "-nowf")) { wireframe = 0; - } else if (!strcmp(arg, "-wirecopyrect")) { + } else if (!strcmp(arg, "-wirecopyrect") + || !strcmp(arg, "-wcr")) { CHECK_ARGC set_wirecopyrect_mode(argv[++i]); - } else if (!strcmp(arg, "-nowirecopyrect")) { + got_wirecopyrect = 1; + } else if (!strcmp(arg, "-nowirecopyrect") + || !strcmp(arg, "-nowf")) { set_wirecopyrect_mode("never"); + } else if (!strcmp(arg, "-scrollcopyrect") + || !strcmp(arg, "-scr")) { + CHECK_ARGC + set_scrollcopyrect_mode(argv[++i]); + got_scrollcopyrect = 1; + } else if (!strcmp(arg, "-noscrollcopyrect") + || !strcmp(arg, "-noscr")) { + set_scrollcopyrect_mode("never"); + } else if (!strcmp(arg, "-scr_area")) { + int tn; + CHECK_ARGC + tn = atoi(argv[++i]); + if (tn >= 0) { + scrollcopyrect_min_area = tn; + } } else if (!strcmp(arg, "-pointer_mode") || !strcmp(arg, "-pm")) { char *p, *s; @@ -23219,6 +25793,8 @@ int main(int argc, char* argv[]) { } else if (!strcmp(arg, "-sb")) { CHECK_ARGC screen_blank = atoi(argv[++i]); + } else if (!strcmp(arg, "-xdamage")) { + use_xdamage = 1; } else if (!strcmp(arg, "-noxdamage")) { use_xdamage = 0; } else if (!strcmp(arg, "-xd_area")) { @@ -23280,7 +25856,7 @@ int main(int argc, char* argv[]) { } } } else if (!strcmp(arg, "-remote") || !strcmp(arg, "-R") - || !strcmp(arg, "-r")) { + || !strcmp(arg, "-r") || !strcmp(arg, "-remote-control")) { CHECK_ARGC i++; if (!strcmp(argv[i], "ping")) { @@ -23299,6 +25875,8 @@ int main(int argc, char* argv[]) { remote_sync = 1; } else if (!strcmp(arg, "-noremote")) { accept_remote_cmds = 0; + } else if (!strcmp(arg, "-yesremote")) { + accept_remote_cmds = 1; } else if (!strcmp(arg, "-unsafe")) { safe_remote_only = 0; } else if (!strcmp(arg, "-privremote")) { @@ -23562,6 +26140,9 @@ int main(int argc, char* argv[]) { if (! wireframe_copyrect) { set_wirecopyrect_mode(NULL); } + if (! scroll_copyrect) { + set_scrollcopyrect_mode(NULL); + } /* increase rfbwait if threaded */ if (use_threads && ! got_rfbwait) { @@ -23852,6 +26433,14 @@ int main(int argc, char* argv[]) { exit(rc); } + if (priv_remote) { + if (! remote_control_access_ok()) { + rfbLog("** Disabling remote commands in -privremote " + "mode.\n"); + accept_remote_cmds = 0; + } + } + #if LIBVNCSERVER_HAVE_LIBXFIXES if (! XFixesQueryExtension(dpy, &xfixes_base_event_type, &er)) { if (! quiet) { @@ -24016,7 +26605,16 @@ int main(int argc, char* argv[]) { * input is not processed) we tell the server to process our * requests during all grabs: */ - disable_grabserver(); + disable_grabserver(dpy); + + /* check for RECORD */ + if (! XRecordQueryVersion_wr(dpy, &maj, &min)) { + xrecord_present = 0; + } else { + xrecord_present = 1; + } + + initialize_xrecord(); /* check for OS with small shm limits */ if (using_shm && ! single_copytile) { @@ -24175,3 +26773,4 @@ int main(int argc, char* argv[]) { #undef argv } +