Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 1 | /* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. |
| 2 | * |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 3 | * This is free software; you can redistribute it and/or modify |
| 4 | * it under the terms of the GNU General Public License as published by |
| 5 | * the Free Software Foundation; either version 2 of the License, or |
| 6 | * (at your option) any later version. |
| 7 | * |
| 8 | * This software is distributed in the hope that it will be useful, |
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | * GNU General Public License for more details. |
| 12 | * |
| 13 | * You should have received a copy of the GNU General Public License |
| 14 | * along with this software; if not, write to the Free Software |
| 15 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
| 16 | * USA. |
| 17 | */ |
| 18 | // |
| 19 | // XserverDesktop.cxx |
| 20 | // |
| 21 | |
| 22 | #include <stdio.h> |
| 23 | #include <strings.h> |
| 24 | #include <unistd.h> |
| 25 | #include <pwd.h> |
| 26 | #include <sys/types.h> |
| 27 | #include <sys/stat.h> |
| 28 | #include <fcntl.h> |
| 29 | #include <sys/utsname.h> |
| 30 | #include <network/TcpSocket.h> |
| 31 | #include <rfb/Exception.h> |
| 32 | #include <rfb/VNCServerST.h> |
| 33 | #include <rfb/HTTPServer.h> |
| 34 | #include <rfb/LogWriter.h> |
| 35 | #include <rfb/Configuration.h> |
| 36 | #include "XserverDesktop.h" |
| 37 | #include "vncExtInit.h" |
| 38 | |
| 39 | extern "C" { |
| 40 | #define public c_public |
| 41 | #define class c_class |
| 42 | |
| 43 | // windowTable is in globals.h in XFree 4, but not in XFree 3 unfortunately |
| 44 | extern WindowPtr *WindowTable; |
| 45 | extern char *display; |
| 46 | |
| 47 | #include "inputstr.h" |
| 48 | #include "servermd.h" |
| 49 | #include "colormapst.h" |
| 50 | #include "resource.h" |
| 51 | #include "cursorstr.h" |
| 52 | #include "windowstr.h" |
| 53 | #define XK_CYRILLIC |
| 54 | #include "keysym.h" |
| 55 | #undef public |
| 56 | #undef class |
| 57 | } |
| 58 | |
| 59 | using namespace rfb; |
| 60 | using namespace network; |
| 61 | |
| 62 | static LogWriter vlog("XserverDesktop"); |
| 63 | |
| 64 | rfb::IntParameter deferUpdateTime("DeferUpdate", |
| 65 | "Time in milliseconds to defer updates",40); |
| 66 | |
| 67 | rfb::BoolParameter alwaysSetDeferUpdateTimer("AlwaysSetDeferUpdateTimer", |
| 68 | "Always reset the defer update timer on every change",false); |
| 69 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 70 | IntParameter queryConnectTimeout("QueryConnectTimeout", |
| 71 | "Number of seconds to show the Accept Connection dialog before " |
| 72 | "rejecting the connection", |
| 73 | 10); |
| 74 | |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 75 | static KeyCode KeysymToKeycode(KeySymsPtr keymap, KeySym ks, int* col); |
| 76 | |
| 77 | static rdr::U8 reverseBits[] = { |
| 78 | 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, |
| 79 | 0x30, 0xb0, 0x70, 0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, |
| 80 | 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04, 0x84, 0x44, 0xc4, |
| 81 | 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, |
| 82 | 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, |
| 83 | 0x3c, 0xbc, 0x7c, 0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, |
| 84 | 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a, 0x8a, 0x4a, 0xca, |
| 85 | 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, |
| 86 | 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, |
| 87 | 0x36, 0xb6, 0x76, 0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, |
| 88 | 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, 0x01, 0x81, 0x41, 0xc1, |
| 89 | 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, |
| 90 | 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, |
| 91 | 0x39, 0xb9, 0x79, 0xf9, 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, |
| 92 | 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, 0x0d, 0x8d, 0x4d, 0xcd, |
| 93 | 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, |
| 94 | 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, |
| 95 | 0x33, 0xb3, 0x73, 0xf3, 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, |
| 96 | 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, 0x07, 0x87, 0x47, 0xc7, |
| 97 | 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, |
| 98 | 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, |
| 99 | 0x3f, 0xbf, 0x7f, 0xff |
| 100 | }; |
| 101 | |
| 102 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 103 | class FileHTTPServer : public rfb::HTTPServer { |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 104 | public: |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 105 | FileHTTPServer(XserverDesktop* d) : desktop(d) {} |
| 106 | virtual ~FileHTTPServer() {} |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 107 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 108 | virtual rdr::InStream* getFile(const char* name, const char** contentType, |
| 109 | int* contentLength, time_t* lastModified) |
| 110 | { |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 111 | if (name[0] != '/' || strstr(name, "..") != 0) { |
| 112 | vlog.info("http request was for invalid file name"); |
| 113 | return 0; |
| 114 | } |
| 115 | |
| 116 | if (strcmp(name, "/") == 0) name = "/index.vnc"; |
| 117 | |
| 118 | CharArray httpDirStr(httpDir.getData()); |
| 119 | CharArray fname(strlen(httpDirStr.buf)+strlen(name)+1); |
| 120 | sprintf(fname.buf, "%s%s", httpDirStr.buf, name); |
| 121 | int fd = open(fname.buf, O_RDONLY); |
| 122 | if (fd < 0) return 0; |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 123 | rdr::InStream* is = new rdr::FdInStream(fd, -1, 0, true); |
| 124 | *contentType = guessContentType(name, *contentType); |
| 125 | if (strlen(name) > 4 && strcasecmp(&name[strlen(name)-4], ".vnc") == 0) { |
| 126 | is = new rdr::SubstitutingInStream(is, desktop, 20); |
| 127 | *contentType = "text/html"; |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 128 | } else { |
| 129 | struct stat st; |
| 130 | if (fstat(fd, &st) == 0) { |
| 131 | *contentLength = st.st_size; |
| 132 | *lastModified = st.st_mtime; |
| 133 | } |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 134 | } |
| 135 | return is; |
| 136 | } |
| 137 | |
| 138 | XserverDesktop* desktop; |
| 139 | }; |
| 140 | |
| 141 | |
| 142 | XserverDesktop::XserverDesktop(ScreenPtr pScreen_, |
| 143 | network::TcpListener* listener_, |
| 144 | network::TcpListener* httpListener_, |
| 145 | const char* name, void* fbptr) |
| 146 | : pScreen(pScreen_), deferredUpdateTimer(0), dummyTimer(0), |
| 147 | server(0), httpServer(0), |
| 148 | listener(listener_), httpListener(httpListener_), |
| 149 | cmap(0), deferredUpdateTimerSet(false), |
| 150 | grabbing(false), ignoreHooks_(false), directFbptr(fbptr != 0), |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 151 | oldButtonMask(0), |
| 152 | queryConnectId(0) |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 153 | { |
| 154 | int i; |
| 155 | format.depth = pScreen->rootDepth; |
| 156 | for (i = 0; i < screenInfo.numPixmapFormats; i++) { |
| 157 | if (screenInfo.formats[i].depth == format.depth) { |
| 158 | format.bpp = screenInfo.formats[i].bitsPerPixel; |
| 159 | break; |
| 160 | } |
| 161 | } |
| 162 | if (i == screenInfo.numPixmapFormats) { |
| 163 | fprintf(stderr,"no pixmap format for root depth???\n"); |
| 164 | abort(); |
| 165 | } |
| 166 | format.bigEndian = (screenInfo.imageByteOrder == MSBFirst); |
| 167 | |
| 168 | VisualPtr vis; |
| 169 | for (i = 0; i < pScreen->numVisuals; i++) { |
| 170 | if (pScreen->visuals[i].vid == pScreen->rootVisual) { |
| 171 | vis = &pScreen->visuals[i]; |
| 172 | break; |
| 173 | } |
| 174 | } |
| 175 | if (i == pScreen->numVisuals) { |
| 176 | fprintf(stderr,"no visual rec for root visual???\n"); |
| 177 | abort(); |
| 178 | } |
| 179 | format.trueColour = (vis->c_class == TrueColor); |
| 180 | if (!format.trueColour && format.bpp != 8) |
| 181 | throw rfb::Exception("X server uses unsupported visual"); |
| 182 | format.redShift = ffs(vis->redMask) - 1; |
| 183 | format.greenShift = ffs(vis->greenMask) - 1; |
| 184 | format.blueShift = ffs(vis->blueMask) - 1; |
| 185 | format.redMax = vis->redMask >> format.redShift; |
| 186 | format.greenMax = vis->greenMask >> format.greenShift; |
| 187 | format.blueMax = vis->blueMask >> format.blueShift; |
| 188 | |
| 189 | width_ = pScreen->width; |
| 190 | height_ = pScreen->height; |
| 191 | if (fbptr) |
| 192 | data = (rdr::U8*)fbptr; |
| 193 | else |
| 194 | data = new rdr::U8[pScreen->width * pScreen->height * (format.bpp/8)]; |
| 195 | colourmap = this; |
| 196 | |
| 197 | serverReset(pScreen); |
| 198 | |
| 199 | server = new VNCServerST(name, this); |
| 200 | server->setPixelBuffer(this); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 201 | server->setQueryConnectionHandler(this); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 202 | |
| 203 | if (httpListener) |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 204 | httpServer = new FileHTTPServer(this); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 205 | } |
| 206 | |
| 207 | XserverDesktop::~XserverDesktop() |
| 208 | { |
| 209 | if (!directFbptr) |
| 210 | delete [] data; |
| 211 | TimerFree(deferredUpdateTimer); |
| 212 | TimerFree(dummyTimer); |
| 213 | delete httpServer; |
| 214 | delete server; |
| 215 | } |
| 216 | |
| 217 | void XserverDesktop::serverReset(ScreenPtr pScreen_) |
| 218 | { |
| 219 | pScreen = pScreen_; |
| 220 | XID* ids = new XID[pScreen->maxInstalledCmaps]; |
| 221 | int nmaps = (*pScreen->ListInstalledColormaps)(pScreen, ids); |
| 222 | cmap = (ColormapPtr)LookupIDByType(ids[0], RT_COLORMAP); |
| 223 | delete [] ids; |
| 224 | } |
| 225 | |
| 226 | char* XserverDesktop::substitute(const char* varName) |
| 227 | { |
| 228 | if (strcmp(varName, "$$") == 0) { |
| 229 | return rfb::strDup("$"); |
| 230 | } |
| 231 | if (strcmp(varName, "$PORT") == 0) { |
| 232 | char* str = new char[10]; |
| 233 | sprintf(str, "%d", listener ? listener->getMyPort() : 0); |
| 234 | return str; |
| 235 | } |
| 236 | if (strcmp(varName, "$WIDTH") == 0) { |
| 237 | char* str = new char[10]; |
| 238 | sprintf(str, "%d", width()); |
| 239 | return str; |
| 240 | } |
| 241 | if (strcmp(varName, "$HEIGHT") == 0) { |
| 242 | char* str = new char[10]; |
| 243 | sprintf(str, "%d", height()); |
| 244 | return str; |
| 245 | } |
| 246 | if (strcmp(varName, "$APPLETWIDTH") == 0) { |
| 247 | char* str = new char[10]; |
| 248 | sprintf(str, "%d", width()); |
| 249 | return str; |
| 250 | } |
| 251 | if (strcmp(varName, "$APPLETHEIGHT") == 0) { |
| 252 | char* str = new char[10]; |
| 253 | sprintf(str, "%d", height() + 32); |
| 254 | return str; |
| 255 | } |
| 256 | if (strcmp(varName, "$DESKTOP") == 0) { |
| 257 | return rfb::strDup(server->getName()); |
| 258 | } |
| 259 | if (strcmp(varName, "$DISPLAY") == 0) { |
| 260 | struct utsname uts; |
| 261 | uname(&uts); |
| 262 | char* str = new char[256]; |
| 263 | strncat(str, uts.nodename, 240); |
| 264 | strcat(str, ":"); |
| 265 | strncat(str, display, 10); |
| 266 | return str; |
| 267 | } |
| 268 | if (strcmp(varName, "$USER") == 0) { |
| 269 | struct passwd* user = getpwuid(getuid()); |
| 270 | return rfb::strDup(user ? user->pw_name : "?"); |
| 271 | } |
| 272 | return 0; |
| 273 | } |
| 274 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 275 | rfb::VNCServerST::queryResult |
| 276 | XserverDesktop::queryConnection(network::Socket* sock, |
| 277 | const char* userName, |
| 278 | char** reason) { |
| 279 | if (queryConnectId) { |
| 280 | *reason = strDup("Another connection is currently being queried."); |
| 281 | return rfb::VNCServerST::REJECT; |
| 282 | } |
| 283 | queryConnectAddress.replaceBuf(sock->getPeerAddress()); |
| 284 | if (!userName) |
| 285 | userName = "(anonymous)"; |
| 286 | queryConnectUsername.replaceBuf(strDup(userName)); |
| 287 | queryConnectId = sock; |
| 288 | vncQueryConnect(this, sock); |
| 289 | return rfb::VNCServerST::PENDING; |
| 290 | } |
| 291 | |
| 292 | |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 293 | void XserverDesktop::setColormap(ColormapPtr cmap_) |
| 294 | { |
| 295 | if (cmap != cmap_) { |
| 296 | cmap = cmap_; |
| 297 | setColourMapEntries(0, 0); |
| 298 | } |
| 299 | } |
| 300 | |
| 301 | void XserverDesktop::setColourMapEntries(ColormapPtr pColormap, int ndef, |
| 302 | xColorItem* pdef) |
| 303 | { |
| 304 | if (cmap != pColormap || ndef <= 0) return; |
| 305 | |
| 306 | int first = pdef[0].pixel; |
| 307 | int n = 1; |
| 308 | |
| 309 | for (int i = 1; i < ndef; i++) { |
| 310 | if (first + n == pdef[i].pixel) { |
| 311 | n++; |
| 312 | } else { |
| 313 | setColourMapEntries(first, n); |
| 314 | first = pdef[i].pixel; |
| 315 | n = 1; |
| 316 | } |
| 317 | } |
| 318 | setColourMapEntries(first, n); |
| 319 | } |
| 320 | |
| 321 | void XserverDesktop::setColourMapEntries(int firstColour, int nColours) |
| 322 | { |
| 323 | try { |
| 324 | server->setColourMapEntries(firstColour, nColours); |
| 325 | } catch (rdr::Exception& e) { |
| 326 | vlog.error("XserverDesktop::setColourMapEntries: %s",e.str()); |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | void XserverDesktop::bell() |
| 331 | { |
| 332 | server->bell(); |
| 333 | } |
| 334 | |
| 335 | void XserverDesktop::serverCutText(const char* str, int len) |
| 336 | { |
| 337 | try { |
| 338 | server->serverCutText(str, len); |
| 339 | } catch (rdr::Exception& e) { |
| 340 | vlog.error("XserverDesktop::serverCutText: %s",e.str()); |
| 341 | } |
| 342 | } |
| 343 | |
| 344 | void XserverDesktop::setCursor(CursorPtr cursor) |
| 345 | { |
| 346 | try { |
| 347 | int w = cursor->bits->width; |
| 348 | int h = cursor->bits->height; |
| 349 | rdr::U8* cursorData = new rdr::U8[w * h * (getPF().bpp / 8)]; |
| 350 | |
| 351 | xColorItem fg, bg; |
| 352 | fg.red = cursor->foreRed; |
| 353 | fg.green = cursor->foreGreen; |
| 354 | fg.blue = cursor->foreBlue; |
| 355 | FakeAllocColor(cmap, &fg); |
| 356 | bg.red = cursor->backRed; |
| 357 | bg.green = cursor->backGreen; |
| 358 | bg.blue = cursor->backBlue; |
| 359 | FakeAllocColor(cmap, &bg); |
| 360 | FakeFreeColor(cmap, fg.pixel); |
| 361 | FakeFreeColor(cmap, bg.pixel); |
| 362 | |
| 363 | int xMaskBytesPerRow = BitmapBytePad(w); |
| 364 | |
| 365 | for (int y = 0; y < h; y++) { |
| 366 | for (int x = 0; x < w; x++) { |
| 367 | int byte = y * xMaskBytesPerRow + x / 8; |
| 368 | #if (BITMAP_BIT_ORDER == MSBFirst) |
| 369 | int bit = 7 - x % 8; |
| 370 | #else |
| 371 | int bit = x % 8; |
| 372 | #endif |
| 373 | switch (getPF().bpp) { |
| 374 | case 8: |
| 375 | ((rdr::U8*)cursorData)[y * w + x] |
| 376 | = (cursor->bits->source[byte] & (1 << bit)) ? fg.pixel : bg.pixel; |
| 377 | break; |
| 378 | case 16: |
| 379 | ((rdr::U16*)cursorData)[y * w + x] |
| 380 | = (cursor->bits->source[byte] & (1 << bit)) ? fg.pixel : bg.pixel; |
| 381 | break; |
| 382 | case 32: |
| 383 | ((rdr::U32*)cursorData)[y * w + x] |
| 384 | = (cursor->bits->source[byte] & (1 << bit)) ? fg.pixel : bg.pixel; |
| 385 | break; |
| 386 | } |
| 387 | } |
| 388 | } |
| 389 | |
| 390 | int rfbMaskBytesPerRow = (w + 7) / 8; |
| 391 | |
| 392 | rdr::U8* cursorMask = new rdr::U8[rfbMaskBytesPerRow * h]; |
| 393 | |
| 394 | for (int j = 0; j < h; j++) { |
| 395 | for (int i = 0; i < rfbMaskBytesPerRow; i++) |
| 396 | #if (BITMAP_BIT_ORDER == MSBFirst) |
| 397 | cursorMask[j * rfbMaskBytesPerRow + i] |
| 398 | = cursor->bits->mask[j * xMaskBytesPerRow + i]; |
| 399 | #else |
| 400 | cursorMask[j * rfbMaskBytesPerRow + i] |
| 401 | = reverseBits[cursor->bits->mask[j * xMaskBytesPerRow + i]]; |
| 402 | #endif |
| 403 | } |
| 404 | |
| 405 | server->setCursor(cursor->bits->width, cursor->bits->height, |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 406 | Point(cursor->bits->xhot, cursor->bits->yhot), |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 407 | cursorData, cursorMask); |
| 408 | server->tryUpdate(); |
| 409 | delete [] cursorData; |
| 410 | delete [] cursorMask; |
| 411 | } catch (rdr::Exception& e) { |
| 412 | vlog.error("XserverDesktop::setCursor: %s",e.str()); |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | static void printRegion(RegionPtr reg) |
| 417 | { |
| 418 | int nrects = REGION_NUM_RECTS(reg); |
| 419 | |
| 420 | fprintf(stderr,"Region num rects %2d extents %3d,%3d %3dx%3d\n",nrects, |
| 421 | (REGION_EXTENTS(pScreen,reg))->x1, |
| 422 | (REGION_EXTENTS(pScreen,reg))->y1, |
| 423 | (REGION_EXTENTS(pScreen,reg))->x2-(REGION_EXTENTS(pScreen,reg))->x1, |
| 424 | (REGION_EXTENTS(pScreen,reg))->y2-(REGION_EXTENTS(pScreen,reg))->y1); |
| 425 | |
| 426 | for (int i = 0; i < nrects; i++) { |
| 427 | fprintf(stderr," rect %3d,%3d %3dx%3d\n", |
| 428 | REGION_RECTS(reg)[i].x1, |
| 429 | REGION_RECTS(reg)[i].y1, |
| 430 | REGION_RECTS(reg)[i].x2-REGION_RECTS(reg)[i].x1, |
| 431 | REGION_RECTS(reg)[i].y2-REGION_RECTS(reg)[i].y1); |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | CARD32 XserverDesktop::deferredUpdateTimerCallback(OsTimerPtr timer, |
| 436 | CARD32 now, pointer arg) |
| 437 | { |
| 438 | XserverDesktop* desktop = (XserverDesktop*)arg; |
| 439 | desktop->deferredUpdateTimerSet = false; |
| 440 | try { |
| 441 | desktop->server->tryUpdate(); |
| 442 | } catch (rdr::Exception& e) { |
| 443 | vlog.error("XserverDesktop::deferredUpdateTimerCallback: %s",e.str()); |
| 444 | } |
| 445 | return 0; |
| 446 | } |
| 447 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 448 | void XserverDesktop::deferUpdate() |
| 449 | { |
| 450 | if (deferUpdateTime != 0) { |
| 451 | if (!deferredUpdateTimerSet || alwaysSetDeferUpdateTimer) { |
| 452 | deferredUpdateTimerSet = true; |
| 453 | deferredUpdateTimer = TimerSet(deferredUpdateTimer, 0, |
| 454 | deferUpdateTime, |
| 455 | deferredUpdateTimerCallback, this); |
| 456 | } |
| 457 | } else { |
| 458 | server->tryUpdate(); |
| 459 | } |
| 460 | } |
| 461 | |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 462 | void XserverDesktop::add_changed(RegionPtr reg) |
| 463 | { |
| 464 | if (ignoreHooks_) return; |
| 465 | if (grabbing) return; |
| 466 | try { |
| 467 | rfb::Region rfbReg; |
| 468 | rfbReg.setExtentsAndOrderedRects((ShortRect*)REGION_EXTENTS(pScreen, reg), |
| 469 | REGION_NUM_RECTS(reg), |
| 470 | (ShortRect*)REGION_RECTS(reg)); |
| 471 | server->add_changed(rfbReg); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 472 | deferUpdate(); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 473 | } catch (rdr::Exception& e) { |
| 474 | vlog.error("XserverDesktop::add_changed: %s",e.str()); |
| 475 | } |
| 476 | } |
| 477 | |
| 478 | void XserverDesktop::add_copied(RegionPtr dst, int dx, int dy) |
| 479 | { |
| 480 | if (ignoreHooks_) return; |
| 481 | if (grabbing) return; |
| 482 | try { |
| 483 | rfb::Region rfbReg; |
| 484 | rfbReg.setExtentsAndOrderedRects((ShortRect*)REGION_EXTENTS(pScreen, dst), |
| 485 | REGION_NUM_RECTS(dst), |
| 486 | (ShortRect*)REGION_RECTS(dst)); |
| 487 | server->add_copied(rfbReg, rfb::Point(dx, dy)); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 488 | deferUpdate(); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 489 | } catch (rdr::Exception& e) { |
| 490 | vlog.error("XserverDesktop::add_copied: %s",e.str()); |
| 491 | } |
| 492 | } |
| 493 | |
| 494 | void XserverDesktop::positionCursor() |
| 495 | { |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 496 | if (!cursorPos.equals(oldCursorPos)) { |
| 497 | oldCursorPos = cursorPos; |
| 498 | (*pScreen->SetCursorPosition) (pScreen, cursorPos.x, cursorPos.y, FALSE); |
| 499 | server->setCursorPos(cursorPos); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 500 | server->tryUpdate(); |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | void XserverDesktop::blockHandler(fd_set* fds) |
| 505 | { |
| 506 | try { |
| 507 | ScreenPtr screenWithCursor = GetCurrentRootWindow()->drawable.pScreen; |
| 508 | if (screenWithCursor == pScreen) { |
| 509 | int x, y; |
| 510 | GetSpritePosition(&x, &y); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 511 | if (x != cursorPos.x || y != cursorPos.y) { |
| 512 | cursorPos = oldCursorPos = Point(x, y); |
| 513 | server->setCursorPos(cursorPos); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 514 | server->tryUpdate(); |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | if (listener) |
| 519 | FD_SET(listener->getFd(), fds); |
| 520 | if (httpListener) |
| 521 | FD_SET(httpListener->getFd(), fds); |
| 522 | |
| 523 | std::list<Socket*> sockets; |
| 524 | server->getSockets(&sockets); |
| 525 | std::list<Socket*>::iterator i; |
| 526 | for (i = sockets.begin(); i != sockets.end(); i++) { |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 527 | int fd = (*i)->getFd(); |
| 528 | if ((*i)->isShutdown()) { |
| 529 | vlog.debug("client gone, sock %d",fd); |
| 530 | server->removeSocket(*i); |
| 531 | vncClientGone(fd); |
| 532 | delete (*i); |
| 533 | } else { |
| 534 | FD_SET(fd, fds); |
| 535 | } |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 536 | } |
| 537 | if (httpServer) { |
| 538 | httpServer->getSockets(&sockets); |
| 539 | for (i = sockets.begin(); i != sockets.end(); i++) { |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 540 | int fd = (*i)->getFd(); |
| 541 | if ((*i)->isShutdown()) { |
| 542 | vlog.debug("http client gone, sock %d",fd); |
| 543 | httpServer->removeSocket(*i); |
| 544 | delete (*i); |
| 545 | } else { |
| 546 | FD_SET(fd, fds); |
| 547 | } |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 548 | } |
| 549 | } |
| 550 | } catch (rdr::Exception& e) { |
| 551 | vlog.error("XserverDesktop::blockHandler: %s",e.str()); |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | static CARD32 dummyTimerCallback(OsTimerPtr timer, CARD32 now, pointer arg) { |
| 556 | return 0; |
| 557 | } |
| 558 | |
| 559 | void XserverDesktop::wakeupHandler(fd_set* fds, int nfds) |
| 560 | { |
| 561 | try { |
| 562 | if (nfds >= 1) { |
| 563 | |
| 564 | if (listener) { |
| 565 | if (FD_ISSET(listener->getFd(), fds)) { |
| 566 | FD_CLR(listener->getFd(), fds); |
| 567 | Socket* sock = listener->accept(); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 568 | server->addSocket(sock); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 569 | vlog.debug("new client, sock %d",sock->getFd()); |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | if (httpListener) { |
| 574 | if (FD_ISSET(httpListener->getFd(), fds)) { |
| 575 | FD_CLR(httpListener->getFd(), fds); |
| 576 | Socket* sock = httpListener->accept(); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 577 | httpServer->addSocket(sock); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 578 | vlog.debug("new http client, sock %d",sock->getFd()); |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | std::list<Socket*> sockets; |
| 583 | server->getSockets(&sockets); |
| 584 | std::list<Socket*>::iterator i; |
| 585 | for (i = sockets.begin(); i != sockets.end(); i++) { |
| 586 | int fd = (*i)->getFd(); |
| 587 | if (FD_ISSET(fd, fds)) { |
| 588 | FD_CLR(fd, fds); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 589 | server->processSocketEvent(*i); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 590 | } |
| 591 | } |
| 592 | |
| 593 | if (httpServer) { |
| 594 | httpServer->getSockets(&sockets); |
| 595 | for (i = sockets.begin(); i != sockets.end(); i++) { |
| 596 | int fd = (*i)->getFd(); |
| 597 | if (FD_ISSET(fd, fds)) { |
| 598 | FD_CLR(fd, fds); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 599 | httpServer->processSocketEvent(*i); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 600 | } |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | positionCursor(); |
| 605 | } |
| 606 | |
| 607 | int timeout = server->checkTimeouts(); |
| 608 | if (timeout > 0) { |
| 609 | // set a dummy timer just so we are guaranteed be called again next time. |
| 610 | dummyTimer = TimerSet(dummyTimer, 0, timeout, |
| 611 | dummyTimerCallback, 0); |
| 612 | } |
| 613 | |
| 614 | } catch (rdr::Exception& e) { |
| 615 | vlog.error("XserverDesktop::wakeupHandler: %s",e.str()); |
| 616 | } |
| 617 | } |
| 618 | |
| 619 | void XserverDesktop::addClient(Socket* sock, bool reverse) |
| 620 | { |
| 621 | vlog.debug("new client, sock %d reverse %d",sock->getFd(),reverse); |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 622 | server->addSocket(sock, reverse); |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 623 | } |
| 624 | |
| 625 | void XserverDesktop::disconnectClients() |
| 626 | { |
| 627 | vlog.debug("disconnecting all clients"); |
| 628 | return server->closeClients("Disconnection from server end"); |
| 629 | } |
| 630 | |
| 631 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 632 | int XserverDesktop::getQueryTimeout(void* opaqueId, |
| 633 | const char** address, |
| 634 | const char** username) |
| 635 | { |
| 636 | if (opaqueId && queryConnectId == opaqueId) { |
| 637 | vlog.info("address=%s, username=%s, timeout=%d", |
| 638 | queryConnectAddress.buf, queryConnectUsername.buf, |
| 639 | (int)queryConnectTimeout); |
| 640 | if (address) *address = queryConnectAddress.buf; |
| 641 | if (username) *username = queryConnectUsername.buf; |
| 642 | return queryConnectTimeout; |
| 643 | } |
| 644 | return 0; |
| 645 | } |
| 646 | |
| 647 | void XserverDesktop::approveConnection(void* opaqueId, bool accept, |
| 648 | const char* rejectMsg) |
| 649 | { |
| 650 | if (queryConnectId == opaqueId) { |
| 651 | server->approveConnection((network::Socket*)opaqueId, accept, rejectMsg); |
| 652 | queryConnectId = 0; |
| 653 | } |
| 654 | } |
| 655 | |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 656 | /////////////////////////////////////////////////////////////////////////// |
| 657 | // |
| 658 | // SDesktop callbacks |
| 659 | |
| 660 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 661 | void XserverDesktop::pointerEvent(const Point& pos, int buttonMask) |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 662 | { |
| 663 | xEvent ev; |
| 664 | DevicePtr dev = LookupPointerDevice(); |
| 665 | |
| 666 | // SetCursorPosition seems to be very expensive (at least on XFree86 3.3.6 |
| 667 | // for S3), so we delay calling it until positionCursor() is called at the |
| 668 | // end of processing a load of RFB. |
| 669 | //(*pScreen->SetCursorPosition) (pScreen, pos.x, pos.y, FALSE); |
| 670 | |
| 671 | NewCurrentScreen(pScreen, pos.x, pos.y); |
| 672 | |
| 673 | ev.u.u.type = MotionNotify; |
| 674 | ev.u.u.detail = 0; |
| 675 | ev.u.keyButtonPointer.rootX = pos.x; |
| 676 | ev.u.keyButtonPointer.rootY = pos.y; |
| 677 | ev.u.keyButtonPointer.time = GetTimeInMillis(); |
| 678 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 679 | if (!pos.equals(cursorPos)) |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 680 | (*dev->processInputProc)(&ev, (DeviceIntPtr)dev, 1); |
| 681 | |
| 682 | for (int i = 0; i < 5; i++) { |
| 683 | if ((buttonMask ^ oldButtonMask) & (1<<i)) { |
Peter Ã…strand | 0aafa76 | 2005-03-10 14:45:22 +0000 | [diff] [blame] | 684 | // Do not use the pointer mapping. Treat VNC buttons as logical |
| 685 | // buttons. |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 686 | ev.u.u.detail = i + 1; |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 687 | ev.u.u.type = (buttonMask & (1<<i)) ? ButtonPress : ButtonRelease; |
| 688 | (*dev->processInputProc)(&ev, (DeviceIntPtr)dev, 1); |
| 689 | } |
| 690 | } |
| 691 | |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 692 | cursorPos = pos; |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 693 | oldButtonMask = buttonMask; |
| 694 | } |
| 695 | |
| 696 | void XserverDesktop::clientCutText(const char* str, int len) |
| 697 | { |
| 698 | vncClientCutText(str, len); |
| 699 | } |
| 700 | |
| 701 | void XserverDesktop::grabRegion(const rfb::Region& region) |
| 702 | { |
| 703 | if (directFbptr) return; |
| 704 | if (!pScreen->GetImage) { |
| 705 | vlog.error("VNC error: pScreen->GetImage == 0"); |
| 706 | return; |
| 707 | } |
| 708 | |
| 709 | grabbing = true; |
| 710 | |
| 711 | int bytesPerPixel = format.bpp/8; |
| 712 | int bytesPerRow = pScreen->width * bytesPerPixel; |
| 713 | |
| 714 | std::vector<rfb::Rect> rects; |
| 715 | std::vector<rfb::Rect>::iterator i; |
| 716 | region.get_rects(&rects); |
| 717 | for (i = rects.begin(); i != rects.end(); i++) { |
| 718 | for (int y = i->tl.y; y < i->br.y; y++) { |
| 719 | (*pScreen->GetImage) ((DrawablePtr)WindowTable[pScreen->myNum], |
| 720 | i->tl.x, y, i->width(), 1, |
| 721 | ZPixmap, (unsigned long)~0L, |
| 722 | ((char*)data |
| 723 | + y * bytesPerRow + i->tl.x * bytesPerPixel)); |
| 724 | } |
| 725 | } |
| 726 | grabbing = false; |
| 727 | } |
| 728 | |
| 729 | void XserverDesktop::lookup(int index, int* r, int* g, int* b) |
| 730 | { |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 731 | if ((cmap->c_class | DynamicClass) == DirectColor) { |
| 732 | VisualPtr v = cmap->pVisual; |
| 733 | *r = cmap->red [(index & v->redMask ) >> v->offsetRed ].co.local.red; |
| 734 | *g = cmap->green[(index & v->greenMask) >> v->offsetGreen].co.local.green; |
| 735 | *b = cmap->blue [(index & v->blueMask ) >> v->offsetBlue ].co.local.blue; |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 736 | } else { |
Constantin Kaplinsky | 95f6f7a | 2006-04-17 04:17:23 +0000 | [diff] [blame^] | 737 | EntryPtr pent; |
| 738 | pent = (EntryPtr)&cmap->red[index]; |
| 739 | if (pent->fShared) { |
| 740 | *r = pent->co.shco.red->color; |
| 741 | *g = pent->co.shco.green->color; |
| 742 | *b = pent->co.shco.blue->color; |
| 743 | } else { |
| 744 | *r = pent->co.local.red; |
| 745 | *g = pent->co.local.green; |
| 746 | *b = pent->co.local.blue; |
| 747 | } |
Constantin Kaplinsky | 47ed8d3 | 2004-10-08 09:43:57 +0000 | [diff] [blame] | 748 | } |
| 749 | } |
| 750 | |
| 751 | // |
| 752 | // Keyboard handling |
| 753 | // |
| 754 | |
| 755 | #define IS_PRESSED(keyc, keycode) \ |
| 756 | ((keyc)->down[(keycode) >> 3] & (1 << ((keycode) & 7))) |
| 757 | |
| 758 | // ModifierState is a class which helps simplify generating a "fake" press |
| 759 | // or release of shift, ctrl, alt, etc. An instance of the class is created |
| 760 | // for every modifier which may need to be pressed or released. Then either |
| 761 | // press() or release() may be called to make sure that the corresponding keys |
| 762 | // are in the right state. The destructor of the class automatically reverts |
| 763 | // to the previous state. Each modifier may have multiple keys associated with |
| 764 | // it, so in the case of a fake release, this may involve releasing more than |
| 765 | // one key. |
| 766 | |
| 767 | class ModifierState { |
| 768 | public: |
| 769 | ModifierState(DeviceIntPtr dev_, int modIndex_) |
| 770 | : dev(dev_), modIndex(modIndex_), nKeys(0), keys(0), pressed(false) |
| 771 | { |
| 772 | } |
| 773 | ~ModifierState() { |
| 774 | for (int i = 0; i < nKeys; i++) |
| 775 | generateXKeyEvent(keys[i], !pressed); |
| 776 | delete [] keys; |
| 777 | } |
| 778 | void press() { |
| 779 | KeyClassPtr keyc = dev->key; |
| 780 | if (!(keyc->state & (1<<modIndex))) { |
| 781 | tempKeyEvent(keyc->modifierKeyMap[modIndex * keyc->maxKeysPerModifier], |
| 782 | true); |
| 783 | pressed = true; |
| 784 | } |
| 785 | } |
| 786 | void release() { |
| 787 | KeyClassPtr keyc = dev->key; |
| 788 | if (keyc->state & (1<<modIndex)) { |
| 789 | for (int k = 0; k < keyc->maxKeysPerModifier; k++) { |
| 790 | int keycode |
| 791 | = keyc->modifierKeyMap[modIndex * keyc->maxKeysPerModifier + k]; |
| 792 | if (keycode && IS_PRESSED(keyc, keycode)) |
| 793 | tempKeyEvent(keycode, false); |
| 794 | } |
| 795 | } |
| 796 | } |
| 797 | private: |
| 798 | void tempKeyEvent(int keycode, bool down) { |
| 799 | if (keycode) { |
| 800 | if (!keys) keys = new int[dev->key->maxKeysPerModifier]; |
| 801 | keys[nKeys++] = keycode; |
| 802 | generateXKeyEvent(keycode, down); |
| 803 | } |
| 804 | } |
| 805 | void generateXKeyEvent(int keycode, bool down) { |
| 806 | xEvent ev; |
| 807 | ev.u.u.type = down ? KeyPress : KeyRelease; |
| 808 | ev.u.u.detail = keycode; |
| 809 | ev.u.keyButtonPointer.time = GetTimeInMillis(); |
| 810 | (*dev->c_public.processInputProc)(&ev, dev, 1); |
| 811 | vlog.debug("fake keycode %d %s", keycode, down ? "down" : "up"); |
| 812 | } |
| 813 | DeviceIntPtr dev; |
| 814 | int modIndex; |
| 815 | int nKeys; |
| 816 | int* keys; |
| 817 | bool pressed; |
| 818 | }; |
| 819 | |
| 820 | |
| 821 | // altKeysym is a table of alternative keysyms which have the same meaning. |
| 822 | |
| 823 | struct altKeysym_t { |
| 824 | KeySym a, b; |
| 825 | }; |
| 826 | |
| 827 | altKeysym_t altKeysym[] = { |
| 828 | { XK_Shift_L, XK_Shift_R }, |
| 829 | { XK_Control_L, XK_Control_R }, |
| 830 | { XK_Meta_L, XK_Meta_R }, |
| 831 | { XK_Alt_L, XK_Alt_R }, |
| 832 | { XK_Super_L, XK_Super_R }, |
| 833 | { XK_Hyper_L, XK_Hyper_R }, |
| 834 | { XK_KP_Space, XK_space }, |
| 835 | { XK_KP_Tab, XK_Tab }, |
| 836 | { XK_KP_Enter, XK_Return }, |
| 837 | { XK_KP_F1, XK_F1 }, |
| 838 | { XK_KP_F2, XK_F2 }, |
| 839 | { XK_KP_F3, XK_F3 }, |
| 840 | { XK_KP_F4, XK_F4 }, |
| 841 | { XK_KP_Home, XK_Home }, |
| 842 | { XK_KP_Left, XK_Left }, |
| 843 | { XK_KP_Up, XK_Up }, |
| 844 | { XK_KP_Right, XK_Right }, |
| 845 | { XK_KP_Down, XK_Down }, |
| 846 | { XK_KP_Page_Up, XK_Page_Up }, |
| 847 | { XK_KP_Page_Down, XK_Page_Down }, |
| 848 | { XK_KP_End, XK_End }, |
| 849 | { XK_KP_Begin, XK_Begin }, |
| 850 | { XK_KP_Insert, XK_Insert }, |
| 851 | { XK_KP_Delete, XK_Delete }, |
| 852 | { XK_KP_Equal, XK_equal }, |
| 853 | { XK_KP_Multiply, XK_asterisk }, |
| 854 | { XK_KP_Add, XK_plus }, |
| 855 | { XK_KP_Separator, XK_comma }, |
| 856 | { XK_KP_Subtract, XK_minus }, |
| 857 | { XK_KP_Decimal, XK_period }, |
| 858 | { XK_KP_Divide, XK_slash }, |
| 859 | { XK_KP_0, XK_0 }, |
| 860 | { XK_KP_1, XK_1 }, |
| 861 | { XK_KP_2, XK_2 }, |
| 862 | { XK_KP_3, XK_3 }, |
| 863 | { XK_KP_4, XK_4 }, |
| 864 | { XK_KP_5, XK_5 }, |
| 865 | { XK_KP_6, XK_6 }, |
| 866 | { XK_KP_7, XK_7 }, |
| 867 | { XK_KP_8, XK_8 }, |
| 868 | { XK_KP_9, XK_9 }, |
| 869 | }; |
| 870 | |
| 871 | // keyEvent() - work out the best keycode corresponding to the keysym sent by |
| 872 | // the viewer. This is non-trivial because we can't assume much about the |
| 873 | // local keyboard layout. We must also find out which column of the keyboard |
| 874 | // mapping the keysym is in, and alter the shift state appropriately. Column 0 |
| 875 | // means both shift and "mode_switch" (AltGr) must be released, column 1 means |
| 876 | // shift must be pressed and mode_switch released, column 2 means shift must be |
| 877 | // released and mode_switch pressed, and column 3 means both shift and |
| 878 | // mode_switch must be pressed. |
| 879 | |
| 880 | void XserverDesktop::keyEvent(rdr::U32 keysym, bool down) |
| 881 | { |
| 882 | if (keysym == XK_Caps_Lock) { |
| 883 | vlog.debug("Ignoring caps lock"); |
| 884 | return; |
| 885 | } |
| 886 | DeviceIntPtr dev = (DeviceIntPtr)LookupKeyboardDevice(); |
| 887 | KeyClassPtr keyc = dev->key; |
| 888 | KeySymsPtr keymap = &keyc->curKeySyms; |
| 889 | |
| 890 | // find which modifier Mode_switch is on. |
| 891 | int modeSwitchMapIndex = 0; |
| 892 | for (int i = 3; i < 8; i++) { |
| 893 | for (int k = 0; k < keyc->maxKeysPerModifier; k++) { |
| 894 | int keycode = keyc->modifierKeyMap[i * keyc->maxKeysPerModifier + k]; |
| 895 | for (int j = 0; j < keymap->mapWidth; j++) { |
| 896 | if (keycode != 0 && |
| 897 | keymap->map[(keycode - keymap->minKeyCode) |
| 898 | * keymap->mapWidth + j] == XK_Mode_switch) |
| 899 | { |
| 900 | modeSwitchMapIndex = i; |
| 901 | break; |
| 902 | } |
| 903 | } |
| 904 | } |
| 905 | } |
| 906 | |
| 907 | int col = 0; |
| 908 | if (keyc->state & (1<<ShiftMapIndex)) col |= 1; |
| 909 | if (modeSwitchMapIndex && (keyc->state & (1<<modeSwitchMapIndex))) col |= 2; |
| 910 | |
| 911 | int kc = KeysymToKeycode(keymap, keysym, &col); |
| 912 | |
| 913 | // Sort out the "shifted Tab" mess. If we are sent a shifted Tab, generate a |
| 914 | // local shifted Tab regardless of what the "shifted Tab" keysym is on the |
| 915 | // local keyboard (it might be Tab, ISO_Left_Tab or HP's private BackTab |
| 916 | // keysym, and quite possibly some others too). We never get ISO_Left_Tab |
| 917 | // here because it's already been translated in VNCSConnectionST. |
| 918 | if (keysym == XK_Tab && (keyc->state & (1<<ShiftMapIndex))) |
| 919 | col |= 1; |
| 920 | |
| 921 | if (kc == 0) { |
| 922 | // Not a direct match in the local keyboard mapping. Check for alternative |
| 923 | // keysyms with the same meaning. |
| 924 | for (int i = 0; i < sizeof(altKeysym) / sizeof(altKeysym_t); i++) { |
| 925 | if (keysym == altKeysym[i].a) |
| 926 | kc = KeysymToKeycode(keymap, altKeysym[i].b, &col); |
| 927 | else if (keysym == altKeysym[i].b) |
| 928 | kc = KeysymToKeycode(keymap, altKeysym[i].a, &col); |
| 929 | if (kc) break; |
| 930 | } |
| 931 | } |
| 932 | |
| 933 | if (kc == 0) { |
| 934 | // Last resort - dynamically add a new key to the keyboard mapping. |
| 935 | for (kc = keymap->maxKeyCode; kc >= keymap->minKeyCode; kc--) { |
| 936 | if (!keymap->map[(kc - keymap->minKeyCode) * keymap->mapWidth]) { |
| 937 | keymap->map[(kc - keymap->minKeyCode) * keymap->mapWidth] = keysym; |
| 938 | col = 0; |
| 939 | SendMappingNotify(MappingKeyboard, kc, 1, serverClient); |
| 940 | vlog.info("Added unknown keysym 0x%x to keycode %d",keysym,kc); |
| 941 | break; |
| 942 | } |
| 943 | } |
| 944 | if (kc < keymap->minKeyCode) { |
| 945 | vlog.info("Keyboard mapping full - ignoring unknown keysym 0x%x",keysym); |
| 946 | return; |
| 947 | } |
| 948 | } |
| 949 | |
| 950 | // See if it's a modifier key. If so, then don't do any auto-repeat, because |
| 951 | // the X server will translate each press into a release followed by a press. |
| 952 | for (int i = 0; i < 8; i++) { |
| 953 | for (int k = 0; k < keyc->maxKeysPerModifier; k++) { |
| 954 | if (kc == keyc->modifierKeyMap[i * keyc->maxKeysPerModifier + k] && |
| 955 | IS_PRESSED(keyc,kc) && down) |
| 956 | return; |
| 957 | } |
| 958 | } |
| 959 | |
| 960 | ModifierState shift(dev, ShiftMapIndex); |
| 961 | ModifierState modeSwitch(dev, modeSwitchMapIndex); |
| 962 | if (down) { |
| 963 | if (col & 1) |
| 964 | shift.press(); |
| 965 | else |
| 966 | shift.release(); |
| 967 | if (modeSwitchMapIndex) { |
| 968 | if (col & 2) |
| 969 | modeSwitch.press(); |
| 970 | else |
| 971 | modeSwitch.release(); |
| 972 | } |
| 973 | } |
| 974 | vlog.debug("keycode %d %s", kc, down ? "down" : "up"); |
| 975 | xEvent ev; |
| 976 | ev.u.u.type = down ? KeyPress : KeyRelease; |
| 977 | ev.u.u.detail = kc; |
| 978 | ev.u.keyButtonPointer.time = GetTimeInMillis(); |
| 979 | (*dev->c_public.processInputProc)(&ev, dev, 1); |
| 980 | } |
| 981 | |
| 982 | |
| 983 | void XConvertCase(KeySym sym, KeySym *lower, KeySym *upper) |
| 984 | { |
| 985 | *lower = sym; |
| 986 | *upper = sym; |
| 987 | switch(sym >> 8) { |
| 988 | case 0: /* Latin 1 */ |
| 989 | if ((sym >= XK_A) && (sym <= XK_Z)) |
| 990 | *lower += (XK_a - XK_A); |
| 991 | else if ((sym >= XK_a) && (sym <= XK_z)) |
| 992 | *upper -= (XK_a - XK_A); |
| 993 | else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis)) |
| 994 | *lower += (XK_agrave - XK_Agrave); |
| 995 | else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis)) |
| 996 | *upper -= (XK_agrave - XK_Agrave); |
| 997 | else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn)) |
| 998 | *lower += (XK_oslash - XK_Ooblique); |
| 999 | else if ((sym >= XK_oslash) && (sym <= XK_thorn)) |
| 1000 | *upper -= (XK_oslash - XK_Ooblique); |
| 1001 | break; |
| 1002 | case 1: /* Latin 2 */ |
| 1003 | /* Assume the KeySym is a legal value (ignore discontinuities) */ |
| 1004 | if (sym == XK_Aogonek) |
| 1005 | *lower = XK_aogonek; |
| 1006 | else if (sym >= XK_Lstroke && sym <= XK_Sacute) |
| 1007 | *lower += (XK_lstroke - XK_Lstroke); |
| 1008 | else if (sym >= XK_Scaron && sym <= XK_Zacute) |
| 1009 | *lower += (XK_scaron - XK_Scaron); |
| 1010 | else if (sym >= XK_Zcaron && sym <= XK_Zabovedot) |
| 1011 | *lower += (XK_zcaron - XK_Zcaron); |
| 1012 | else if (sym == XK_aogonek) |
| 1013 | *upper = XK_Aogonek; |
| 1014 | else if (sym >= XK_lstroke && sym <= XK_sacute) |
| 1015 | *upper -= (XK_lstroke - XK_Lstroke); |
| 1016 | else if (sym >= XK_scaron && sym <= XK_zacute) |
| 1017 | *upper -= (XK_scaron - XK_Scaron); |
| 1018 | else if (sym >= XK_zcaron && sym <= XK_zabovedot) |
| 1019 | *upper -= (XK_zcaron - XK_Zcaron); |
| 1020 | else if (sym >= XK_Racute && sym <= XK_Tcedilla) |
| 1021 | *lower += (XK_racute - XK_Racute); |
| 1022 | else if (sym >= XK_racute && sym <= XK_tcedilla) |
| 1023 | *upper -= (XK_racute - XK_Racute); |
| 1024 | break; |
| 1025 | case 2: /* Latin 3 */ |
| 1026 | /* Assume the KeySym is a legal value (ignore discontinuities) */ |
| 1027 | if (sym >= XK_Hstroke && sym <= XK_Hcircumflex) |
| 1028 | *lower += (XK_hstroke - XK_Hstroke); |
| 1029 | else if (sym >= XK_Gbreve && sym <= XK_Jcircumflex) |
| 1030 | *lower += (XK_gbreve - XK_Gbreve); |
| 1031 | else if (sym >= XK_hstroke && sym <= XK_hcircumflex) |
| 1032 | *upper -= (XK_hstroke - XK_Hstroke); |
| 1033 | else if (sym >= XK_gbreve && sym <= XK_jcircumflex) |
| 1034 | *upper -= (XK_gbreve - XK_Gbreve); |
| 1035 | else if (sym >= XK_Cabovedot && sym <= XK_Scircumflex) |
| 1036 | *lower += (XK_cabovedot - XK_Cabovedot); |
| 1037 | else if (sym >= XK_cabovedot && sym <= XK_scircumflex) |
| 1038 | *upper -= (XK_cabovedot - XK_Cabovedot); |
| 1039 | break; |
| 1040 | case 3: /* Latin 4 */ |
| 1041 | /* Assume the KeySym is a legal value (ignore discontinuities) */ |
| 1042 | if (sym >= XK_Rcedilla && sym <= XK_Tslash) |
| 1043 | *lower += (XK_rcedilla - XK_Rcedilla); |
| 1044 | else if (sym >= XK_rcedilla && sym <= XK_tslash) |
| 1045 | *upper -= (XK_rcedilla - XK_Rcedilla); |
| 1046 | else if (sym == XK_ENG) |
| 1047 | *lower = XK_eng; |
| 1048 | else if (sym == XK_eng) |
| 1049 | *upper = XK_ENG; |
| 1050 | else if (sym >= XK_Amacron && sym <= XK_Umacron) |
| 1051 | *lower += (XK_amacron - XK_Amacron); |
| 1052 | else if (sym >= XK_amacron && sym <= XK_umacron) |
| 1053 | *upper -= (XK_amacron - XK_Amacron); |
| 1054 | break; |
| 1055 | case 6: /* Cyrillic */ |
| 1056 | /* Assume the KeySym is a legal value (ignore discontinuities) */ |
| 1057 | if (sym >= XK_Serbian_DJE && sym <= XK_Serbian_DZE) |
| 1058 | *lower -= (XK_Serbian_DJE - XK_Serbian_dje); |
| 1059 | else if (sym >= XK_Serbian_dje && sym <= XK_Serbian_dze) |
| 1060 | *upper += (XK_Serbian_DJE - XK_Serbian_dje); |
| 1061 | else if (sym >= XK_Cyrillic_YU && sym <= XK_Cyrillic_HARDSIGN) |
| 1062 | *lower -= (XK_Cyrillic_YU - XK_Cyrillic_yu); |
| 1063 | else if (sym >= XK_Cyrillic_yu && sym <= XK_Cyrillic_hardsign) |
| 1064 | *upper += (XK_Cyrillic_YU - XK_Cyrillic_yu); |
| 1065 | break; |
| 1066 | case 7: /* Greek */ |
| 1067 | /* Assume the KeySym is a legal value (ignore discontinuities) */ |
| 1068 | if (sym >= XK_Greek_ALPHAaccent && sym <= XK_Greek_OMEGAaccent) |
| 1069 | *lower += (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent); |
| 1070 | else if (sym >= XK_Greek_alphaaccent && sym <= XK_Greek_omegaaccent && |
| 1071 | sym != XK_Greek_iotaaccentdieresis && |
| 1072 | sym != XK_Greek_upsilonaccentdieresis) |
| 1073 | *upper -= (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent); |
| 1074 | else if (sym >= XK_Greek_ALPHA && sym <= XK_Greek_OMEGA) |
| 1075 | *lower += (XK_Greek_alpha - XK_Greek_ALPHA); |
| 1076 | else if (sym >= XK_Greek_alpha && sym <= XK_Greek_omega && |
| 1077 | sym != XK_Greek_finalsmallsigma) |
| 1078 | *upper -= (XK_Greek_alpha - XK_Greek_ALPHA); |
| 1079 | break; |
| 1080 | } |
| 1081 | } |
| 1082 | |
| 1083 | static KeySym KeyCodetoKeySym(KeySymsPtr keymap, int keycode, int col) |
| 1084 | { |
| 1085 | register int per = keymap->mapWidth; |
| 1086 | register KeySym *syms; |
| 1087 | KeySym lsym, usym; |
| 1088 | |
| 1089 | if ((col < 0) || ((col >= per) && (col > 3)) || |
| 1090 | (keycode < keymap->minKeyCode) || (keycode > keymap->maxKeyCode)) |
| 1091 | return NoSymbol; |
| 1092 | |
| 1093 | syms = &keymap->map[(keycode - keymap->minKeyCode) * per]; |
| 1094 | if (col < 4) { |
| 1095 | if (col > 1) { |
| 1096 | while ((per > 2) && (syms[per - 1] == NoSymbol)) |
| 1097 | per--; |
| 1098 | if (per < 3) |
| 1099 | col -= 2; |
| 1100 | } |
| 1101 | if ((per <= (col|1)) || (syms[col|1] == NoSymbol)) { |
| 1102 | XConvertCase(syms[col&~1], &lsym, &usym); |
| 1103 | if (!(col & 1)) |
| 1104 | return lsym; |
| 1105 | // I'm commenting out this logic because it's incorrect even though it |
| 1106 | // was copied from the Xlib sources. The X protocol book quite clearly |
| 1107 | // states that where a group consists of element 1 being a non-alphabetic |
| 1108 | // keysym and element 2 being NoSymbol that you treat the second element |
| 1109 | // as being the same as the first. This also tallies with the behaviour |
| 1110 | // produced by the installed Xlib on my linux box (I believe this is |
| 1111 | // because it uses some XKB code rather than the original Xlib code - |
| 1112 | // compare XKBBind.c with KeyBind.c in lib/X11). |
| 1113 | // else if (usym == lsym) |
| 1114 | // return NoSymbol; |
| 1115 | else |
| 1116 | return usym; |
| 1117 | } |
| 1118 | } |
| 1119 | return syms[col]; |
| 1120 | } |
| 1121 | |
| 1122 | // KeysymToKeycode() - find the keycode and column corresponding to the given |
| 1123 | // keysym. The value of col passed in should be the column determined from the |
| 1124 | // current shift state. If the keysym can be found in that column we prefer |
| 1125 | // that to finding it in a different column (which would require fake events to |
| 1126 | // alter the shift state). |
| 1127 | |
| 1128 | static KeyCode KeysymToKeycode(KeySymsPtr keymap, KeySym ks, int* col) |
| 1129 | { |
| 1130 | register int i, j; |
| 1131 | |
| 1132 | j = *col; |
| 1133 | for (i = keymap->minKeyCode; i <= keymap->maxKeyCode; i++) { |
| 1134 | if (KeyCodetoKeySym(keymap, i, j) == ks) |
| 1135 | return i; |
| 1136 | } |
| 1137 | |
| 1138 | for (j = 0; j < keymap->mapWidth; j++) { |
| 1139 | for (i = keymap->minKeyCode; i <= keymap->maxKeyCode; i++) { |
| 1140 | if (KeyCodetoKeySym(keymap, i, j) == ks) { |
| 1141 | *col = j; |
| 1142 | return i; |
| 1143 | } |
| 1144 | } |
| 1145 | } |
| 1146 | return 0; |
| 1147 | } |