diff options
author | Constantin Kaplinsky <const@tightvnc.com> | 2004-10-08 09:43:57 +0000 |
---|---|---|
committer | Constantin Kaplinsky <const@tightvnc.com> | 2004-10-08 09:43:57 +0000 |
commit | 47ed8d321c32c6b741cff1f4ff686165c4f269f4 (patch) | |
tree | da413648adbff4ff10c8ee26124673f8e7cf238a /rfb_win32 | |
parent | 266bb36cd47555280fffd3aab1ed86683e26d748 (diff) | |
download | tigervnc-47ed8d321c32c6b741cff1f4ff686165c4f269f4.tar.gz tigervnc-47ed8d321c32c6b741cff1f4ff686165c4f269f4.zip |
Initial revision
git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@2 3789f03b-4d11-0410-bbf8-ca57d06f2519
Diffstat (limited to 'rfb_win32')
58 files changed, 8934 insertions, 0 deletions
diff --git a/rfb_win32/AboutDialog.cxx b/rfb_win32/AboutDialog.cxx new file mode 100644 index 00000000..efb15c00 --- /dev/null +++ b/rfb_win32/AboutDialog.cxx @@ -0,0 +1,49 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <rfb_win32/AboutDialog.h> +#include <rfb_win32/Win32Util.h> +#include <rfb_win32/TCharArray.h> +#include <rfb/LogWriter.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("AboutDialog"); + +AboutDialog AboutDialog::instance; + + +AboutDialog::AboutDialog() : Dialog(GetModuleHandle(0)) { +} + +bool AboutDialog::showDialog() { + return Dialog::showDialog(MAKEINTRESOURCE(DialogId)); +} + +void AboutDialog::initDialog() { + // Set the build time field + SetWindowText(GetDlgItem(handle, BuildTime), TStr(buildTime)); + + // Get our executable's version info + FileVersionInfo verInfo; + + SetWindowText(GetDlgItem(handle, Version), verInfo.getVerString(_T("FileVersion"))); + SetWindowText(GetDlgItem(handle, Copyright), verInfo.getVerString(_T("LegalCopyright"))); + SetWindowText(GetDlgItem(handle, Description), verInfo.getVerString(_T("FileDescription"))); +} diff --git a/rfb_win32/AboutDialog.h b/rfb_win32/AboutDialog.h new file mode 100644 index 00000000..cb1713d4 --- /dev/null +++ b/rfb_win32/AboutDialog.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- AboutDialog.h + +#ifndef __RFB_WIN32_ABOUT_DIALOG_H__ +#define __RFB_WIN32_ABOUT_DIALOG_H__ + +#include <rfb_win32/Dialog.h> +#include <rfb/util.h> + +extern const char* buildTime; + +namespace rfb { + + namespace win32 { + + class AboutDialog : Dialog { + public: + AboutDialog(); + virtual bool showDialog(); + virtual void initDialog(); + + static AboutDialog instance; + + typedef WORD LabelId; + static const LabelId DialogId; // Resource ID of the About dialog + static const LabelId BuildTime; // Resource ID of the BuildTime label in the dialog + static const LabelId Version; // etc... + static const LabelId Copyright; + static const LabelId Description; + protected: + WORD dialogId; + }; + + }; + +}; + +#endif diff --git a/rfb_win32/CKeyboard.cxx b/rfb_win32/CKeyboard.cxx new file mode 100644 index 00000000..ad852a0e --- /dev/null +++ b/rfb_win32/CKeyboard.cxx @@ -0,0 +1,259 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <map> + +#define XK_MISCELLANY +#define XK_LATIN1 +#define XK_CURRENCY +#include <rfb/keysymdef.h> + +#include <rfb_win32/CKeyboard.h> +#include <rfb/CMsgWriter.h> +#include <rfb/LogWriter.h> +#include <rfb_win32/OSVersion.h> +#include "keymap.h" + +using namespace rfb; + +static LogWriter vlog("CKeyboard"); + + +// Client-side RFB keyboard event sythesis + +class CKeymapper { + +public: + CKeymapper() + { + for (int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) { + int extendedVkey = keymap[i].vk + (keymap[i].extended ? 256 : 0); + if (keysymMap.find(extendedVkey) == keysymMap.end()) { + keysymMap[extendedVkey] = keymap[i].keysym; + } + } + } + + // lookup() tries to find a match for vkey with the extended flag. We check + // first for an exact match including the extended flag, then try without the + // extended flag. + rdr::U32 lookup(int extendedVkey) { + if (keysymMap.find(extendedVkey) != keysymMap.end()) + return keysymMap[extendedVkey]; + if (keysymMap.find(extendedVkey ^ 256) != keysymMap.end()) + return keysymMap[extendedVkey ^ 256]; + return 0; + } + +private: + std::map<int,rdr::U32> keysymMap; +} ckeymapper; + + +class ModifierKeyReleaser { +public: + ModifierKeyReleaser(CMsgWriter* writer_, int vkCode, bool extended) + : writer(writer_), extendedVkey(vkCode + (extended ? 256 : 0)), + keysym(0) + {} + void release(std::map<int,rdr::U32>* downKeysym) { + if (downKeysym->find(extendedVkey) != downKeysym->end()) { + keysym = (*downKeysym)[extendedVkey]; + vlog.debug("fake release extendedVkey 0x%x, keysym 0x%x", + extendedVkey, keysym); + writer->writeKeyEvent(keysym, false); + } + } + ~ModifierKeyReleaser() { + if (keysym) { + vlog.debug("fake press extendedVkey 0x%x, keysym 0x%x", + extendedVkey, keysym); + writer->writeKeyEvent(keysym, true); + } + } + CMsgWriter* writer; + int extendedVkey; + rdr::U32 keysym; +}; + +// IS_PRINTABLE_LATIN1 tests if a character is either a printable latin1 +// character, or 128, which is the Euro symbol on Windows. +#define IS_PRINTABLE_LATIN1(c) (((c) >= 32 && (c) <= 126) || (c) == 128 || \ + ((c) >= 160 && (c) <= 255)) + +void win32::CKeyboard::keyEvent(CMsgWriter* writer, rdr::U8 vkey, + rdr::U32 flags, bool down) +{ + bool extended = (flags & 0x1000000); + int extendedVkey = vkey + (extended ? 256 : 0); + + // If it's a release, just release whichever keysym corresponded to the same + // key being pressed, regardless of how it would be interpreted in the + // current keyboard state. + if (!down) { + releaseKey(writer, extendedVkey); + return; + } + + // We should always pass every down event to ToAscii() otherwise it can get + // out of sync. + + // XXX should we pass CapsLock, ScrollLock or NumLock to ToAscii - they + // actually alter the lock state on the keyboard? + + BYTE keystate[256]; + GetKeyboardState(keystate); + rdr::U8 chars[2]; + + int nchars = ToAscii(vkey, 0, keystate, (WORD*)&chars, 0); + + // See if it's in the Windows VK code -> X keysym map. We do this before + // looking at the result of ToAscii so that e.g. we recognise that it's + // XK_KP_Add rather than '+'. + + rdr::U32 keysym = ckeymapper.lookup(extendedVkey); + if (keysym) { + vlog.debug("mapped key: extendedVkey 0x%x", extendedVkey); + pressKey(writer, extendedVkey, keysym); + return; + } + + if (nchars < 0) { + // Dead key - the next call to ToAscii() will give us either the accented + // character or two characters. + vlog.debug("ToAscii dead key (1): extendedVkey 0x%x", extendedVkey); + return; + } + + if (nchars > 0 && IS_PRINTABLE_LATIN1(chars[0])) { + // Got a printable latin1 character. We must release Control and Alt + // (AltGr) if they were both pressed, so that the latin1 character is seen + // without them by the VNC server. + ModifierKeyReleaser lctrl(writer, VK_CONTROL, 0); + ModifierKeyReleaser rctrl(writer, VK_CONTROL, 1); + ModifierKeyReleaser lalt(writer, VK_MENU, 0); + ModifierKeyReleaser ralt(writer, VK_MENU, 1); + + if ((keystate[VK_CONTROL] & 0x80) && (keystate[VK_MENU] & 0x80)) { + lctrl.release(&downKeysym); + rctrl.release(&downKeysym); + lalt.release(&downKeysym); + ralt.release(&downKeysym); + } + + for (int i = 0; i < nchars; i++) { + vlog.debug("ToAscii key (1): extendedVkey 0x%x", extendedVkey); + if (chars[i] == 128) { // special hack for euro! + pressKey(writer, extendedVkey, XK_EuroSign); + } else { + pressKey(writer, extendedVkey, chars[i]); + } + } + return; + } + + // Either no chars were generated, or something outside the printable + // character range. Try ToAscii() without the Control and Alt keys down to + // see if that yields an ordinary character. + + keystate[VK_CONTROL] = keystate[VK_LCONTROL] = keystate[VK_RCONTROL] = 0; + keystate[VK_MENU] = keystate[VK_LMENU] = keystate[VK_RMENU] = 0; + + nchars = ToAscii(vkey, 0, keystate, (WORD*)&chars, 0); + + if (nchars < 0) { + // So it would be a dead key if neither control nor alt were pressed. + // However, we know that at least one of control and alt must be pressed. + // We can't leave it at this stage otherwise the next call to ToAscii() + // with a valid character will get wrongly interpreted in the context of + // this bogus dead key. Working on the assumption that a dead key followed + // by space usually returns the dead character itself, try calling ToAscii + // with VK_SPACE. + vlog.debug("ToAscii dead key (2): extendedVkey 0x%x", extendedVkey); + nchars = ToAscii(VK_SPACE, 0, keystate, (WORD*)&chars, 0); + if (nchars < 0) { + vlog.debug("ToAscii dead key (3): extendedVkey 0x%x - giving up!", + extendedVkey); + return; + } + } + + if (nchars > 0 && IS_PRINTABLE_LATIN1(chars[0])) { + for (int i = 0; i < nchars; i++) { + vlog.debug("ToAscii key (2) (no ctrl/alt): extendedVkey 0x%x", + extendedVkey); + if (chars[i] == 128) { // special hack for euro! + pressKey(writer, extendedVkey, XK_EuroSign); + } else { + pressKey(writer, extendedVkey, chars[i]); + } + } + return; + } + + vlog.debug("no chars regardless of control and alt: extendedVkey 0x%x", + extendedVkey); +} + +// releaseAllKeys() - write key release events to the server for all keys +// that are currently regarded as being down. +void win32::CKeyboard::releaseAllKeys(CMsgWriter* writer) { + std::map<int,rdr::U32>::iterator i, next_i; + for (i=downKeysym.begin(); i!=downKeysym.end(); i=next_i) { + next_i = i; next_i++; + writer->writeKeyEvent((*i).second, false); + downKeysym.erase(i); + } +} + +// releaseKey() - write a key up event to the server, but only if we've +// actually sent a key down event for the given key. The key up event always +// contains the same keysym we used in the key down event, regardless of what +// it would look up as using the current keyboard state. +void win32::CKeyboard::releaseKey(CMsgWriter* writer, int extendedVkey) +{ + if (downKeysym.find(extendedVkey) != downKeysym.end()) { + vlog.debug("release extendedVkey 0x%x, keysym 0x%x", + extendedVkey, downKeysym[extendedVkey]); + writer->writeKeyEvent(downKeysym[extendedVkey], false); + downKeysym.erase(extendedVkey); + } +} + +// pressKey() - write a key down event to the server, and record which keysym +// was sent as corresponding to the given extendedVkey. The only tricky bit is +// that if we are trying to press an extendedVkey which is already marked as +// down but with a different keysym, then we need to release the old keysym +// first. This can happen in two cases: (a) when a single key press results in +// more than one character, and (b) when shift is released while another key is +// autorepeating. +void win32::CKeyboard::pressKey(CMsgWriter* writer, int extendedVkey, + rdr::U32 keysym) +{ + if (downKeysym.find(extendedVkey) != downKeysym.end()) { + if (downKeysym[extendedVkey] != keysym) { + vlog.debug("release extendedVkey 0x%x, keysym 0x%x", + extendedVkey, downKeysym[extendedVkey]); + writer->writeKeyEvent(downKeysym[extendedVkey], false); + } + } + vlog.debug("press extendedVkey 0x%x, keysym 0x%x", + extendedVkey, keysym); + writer->writeKeyEvent(keysym, true); + downKeysym[extendedVkey] = keysym; +} diff --git a/rfb_win32/CKeyboard.h b/rfb_win32/CKeyboard.h new file mode 100644 index 00000000..10346ffc --- /dev/null +++ b/rfb_win32/CKeyboard.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- CKeyboard.h +// +// Client-side keyboard handling for Win32 + +#ifndef __RFB_WIN32_CKEYBOARD_H__ +#define __RFB_WIN32_CKEYBOARD_H__ + +#include <rdr/types.h> +#include <map> + +namespace rfb { + + class CMsgWriter; + + namespace win32 { + + class CKeyboard { + public: + void keyEvent(CMsgWriter* writer, rdr::U8 vkey, rdr::U32 flags, + bool down); + void releaseAllKeys(CMsgWriter* writer); + const std::map<int,rdr::U32>& pressedKeys() const {return downKeysym;}; + bool keyPressed(int k) const {return downKeysym.find(k)!=downKeysym.end();} + private: + void win32::CKeyboard::releaseKey(CMsgWriter* writer, int extendedVkey); + void win32::CKeyboard::pressKey(CMsgWriter* writer, int extendedVkey, + rdr::U32 keysym); + std::map<int,rdr::U32> downKeysym; + }; + + }; // win32 + +}; // rfb + +#endif // __RFB_WIN32_CKEYBOARD_H__ diff --git a/rfb_win32/CPointer.cxx b/rfb_win32/CPointer.cxx new file mode 100644 index 00000000..1cab662a --- /dev/null +++ b/rfb_win32/CPointer.cxx @@ -0,0 +1,188 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb/LogWriter.h> +#include <rfb_win32/CPointer.h> + +using namespace rfb; +using namespace win32; + +static LogWriter vlog("CPointer"); + + +CPointer::CPointer() : currButtonMask(0), intervalQueued(false), threeEmulating(false) { +} + +CPointer::~CPointer() { + intervalTimer.stop(); + threeTimer.stop(); +} + + +void CPointer::pointerEvent(CMsgWriter* writer, int x, int y, int buttonMask) { + // + // - Duplicate Event Filtering + // + + bool maskChanged = buttonMask != currButtonMask; + bool posChanged = !Point(x, y).equals(currPos); + if (!(posChanged || maskChanged)) + return; + + // Pass on the event to the event-interval handler + threePointerEvent(writer, x, y, buttonMask); + + // Save the position and mask + currPos = Point(x, y); + currButtonMask = buttonMask; +} + + +inline abs(int x) {return x>0 ? x : 0;} + +int emulate3Mask(int buttonMask) { + // - Release left & right and press middle + vlog.debug("emulate3: active"); + buttonMask &= ~5; + buttonMask |= 2; + return buttonMask; +} + +void CPointer::threePointerEvent(CMsgWriter* writer, int x, int y, int buttonMask) { + // + // - 3-Button Mouse Emulation + // + + if (emulate3) { + + bool leftChanged = (buttonMask & 1) != (currButtonMask & 1); + bool rightChanged = (buttonMask & 4) != (currButtonMask & 4); + + if (leftChanged || rightChanged) { + // - One of left or right have changed + + if ((buttonMask & 5) == 1 || (buttonMask & 5) == 4) { + // - One is up, one is down. Start a timer, so that if it + // expires then we know we should actually send this event + vlog.debug("emulate3: start timer"); + threeTimer.start(100); + threePos = Point(x, y); + threeMask = buttonMask; + return; + + } else if (threeTimer.isActive()) { + // - Both are up or both are down, and we were timing for an emulation event + // Stop the timer and flush the stored event + vlog.debug("emulate3: stop timer (state)"); + threeTimer.stop(); + if (threeEmulating == ((buttonMask & 5) == 5)) + intervalPointerEvent(writer, threePos.x, threePos.y, threeMask); + else + threeEmulating = ((buttonMask & 5) == 5); + } + + } else { + + if (threeTimer.isActive()) { + // - We are timing for an emulation event + + if (abs(threePos.x - x) <= 4 || abs(threePos.y - y) <= 4) { + // If the mouse has moved too far since the button-change event then flush + vlog.debug("emulate3: stop timer (moved)"); + threeTimer.stop(); + intervalPointerEvent(writer, threePos.x, threePos.y, threeMask); + + } else { + // Otherwise, we ignore the new event + return; + } + } + + } + + // - If neither left nor right are down, stop emulating + if ((buttonMask & 5) == 0) + threeEmulating = false; + + // - If emulating, release left & right and press middle + if (threeEmulating) + buttonMask = emulate3Mask(buttonMask); + + } + + // - Let the event pass through to the next stage of processing + intervalPointerEvent(writer, x, y, buttonMask); +} + +void CPointer::intervalPointerEvent(CMsgWriter* writer, int x, int y, int buttonMask) { + // + // - Pointer Event Interval + // + vlog.write(101, "ptrEvent: %d,%d (%lx)", x, y, buttonMask); + + // Send the event immediately if we haven't sent one for a while + bool sendNow = !intervalTimer.isActive(); + + if (intervalMask != buttonMask) { + // If the buttons have changed then flush queued events and send now + sendNow = true; + if (intervalQueued) + writer->writePointerEvent(intervalPos.x, intervalPos.y, intervalMask); + intervalQueued = false; + } + + if (!sendNow) { + // If we're not sending now then just queue the event + intervalQueued = true; + intervalPos = Point(x, y); + intervalMask = buttonMask; + } else { + // Start the interval timer if required, and send the event + intervalQueued = false; + intervalMask = buttonMask; + if (pointerEventInterval) + intervalTimer.start(pointerEventInterval); + writer->writePointerEvent(x, y, buttonMask); + } +} + +void CPointer::handleTimer(CMsgWriter* writer, int timerId) { + if (timerId == intervalTimer.getId()) { + // Pointer interval has expired - send any queued events + if (intervalQueued) { + writer->writePointerEvent(intervalPos.x, intervalPos.y, intervalMask); + intervalQueued = false; + } else { + intervalTimer.stop(); + } + + } else if (timerId = threeTimer.getId()) { + // 3-Button emulation timer has expired - send what we've got + vlog.debug("emulate3: timeout"); + threeTimer.stop(); + + // If emulating, release left & right and press middle + if (threeEmulating) + threeMask = emulate3Mask(threeMask); + + intervalPointerEvent(writer, threePos.x, threePos.y, threeMask); + } +} diff --git a/rfb_win32/CPointer.h b/rfb_win32/CPointer.h new file mode 100644 index 00000000..f111de74 --- /dev/null +++ b/rfb_win32/CPointer.h @@ -0,0 +1,76 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- CPointer.h +// +// Client-side pointer event handling for Win32 + +#ifndef __RFB_WIN32_CPOINTER_H__ +#define __RFB_WIN32_CPOINTER_H__ + +#include <rdr/Exception.h> +#include <rfb/Configuration.h> +#include <rfb/CMsgWriter.h> +#include <rfb/Rect.h> +#include <rfb_win32/IntervalTimer.h> + +namespace rfb { + + class CMsgWriter; + + namespace win32 { + + class CPointer { + public: + CPointer(); + ~CPointer(); + + void pointerEvent(CMsgWriter* writer, int x, int y, int buttonMask); + void handleTimer(CMsgWriter* writer, int timerId); + + void setHWND(HWND w) {intervalTimer.setHWND(w); threeTimer.setHWND(w);} + void setIntervalTimerId(int id) {intervalTimer.setId(id);} + void set3ButtonTimerId(int id) {threeTimer.setId(id);} + + void enableEmulate3(bool enable) {emulate3 = enable;} + void enableInterval(int millis) {pointerEventInterval = millis;} + private: + Point currPos; + int currButtonMask; + + bool emulate3; + int pointerEventInterval; + + void intervalPointerEvent(CMsgWriter* writer, int x, int y, int buttonMask); + IntervalTimer intervalTimer; + bool intervalQueued; + Point intervalPos; + int intervalMask; + + void threePointerEvent(CMsgWriter* writer, int x, int y, int buttonMask); + IntervalTimer threeTimer; + Point threePos; + int threeMask; + bool threeEmulating; + }; + + }; // win32 + +}; // rfb + +#endif // __RFB_WIN32_CPOINTER_H__ diff --git a/rfb_win32/CleanDesktop.cxx b/rfb_win32/CleanDesktop.cxx new file mode 100644 index 00000000..9fb83478 --- /dev/null +++ b/rfb_win32/CleanDesktop.cxx @@ -0,0 +1,255 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- CleanDesktop.cxx + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <wininet.h> +#include <shlobj.h> + +#include <rfb_win32/CleanDesktop.h> +#include <rfb_win32/CurrentUser.h> +#include <rfb_win32/Registry.h> +#include <rfb/LogWriter.h> +#include <rdr/Exception.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("CleanDesktop"); + + +struct ActiveDesktop { + ActiveDesktop() : handle(0) { + // - Contact Active Desktop + HRESULT result = CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, + IID_IActiveDesktop, (PVOID*)&handle); + if (result != S_OK) + throw rdr::SystemException("failed to contact Active Desktop", result); + } + ~ActiveDesktop() { + if (handle) + handle->Release(); + } + bool enable(bool enable_) { + // - Get the current Active Desktop options + COMPONENTSOPT adOptions; + memset(&adOptions, 0, sizeof(adOptions)); + adOptions.dwSize = sizeof(adOptions); + + HRESULT result = handle->GetDesktopItemOptions(&adOptions, 0); + if (result != S_OK) { + vlog.error("failed to get Active Desktop options: %d", result); + return false; + } + + // - If Active Desktop is active, disable it + if ((adOptions.fActiveDesktop==0) != (enable_==0)) { + if (enable_) + vlog.debug("enabling Active Desktop"); + else + vlog.debug("disabling Active Desktop"); + + adOptions.fActiveDesktop = enable_; + result = handle->SetDesktopItemOptions(&adOptions, 0); + if (result != S_OK) { + vlog.error("failed to disable ActiveDesktop: %d", result); + return false; + } + handle->ApplyChanges(AD_APPLY_REFRESH); + return true; + } + return false; + } + IActiveDesktop* handle; +}; + + +DWORD SysParamsInfo(UINT action, UINT param, PVOID ptr, UINT ini) { + DWORD r = ERROR_SUCCESS; + if (!SystemParametersInfo(action, param, ptr, ini)) { + r = GetLastError(); + vlog.info("SPI error: %d", r); + } + return r; +} + + +CleanDesktop::CleanDesktop() : restoreActiveDesktop(false), restoreWallpaper(false), restoreEffects(false) { + CoInitialize(0); +} + +CleanDesktop::~CleanDesktop() { + enableEffects(); + enablePattern(); + enableWallpaper(); + CoUninitialize(); +} + +void CleanDesktop::disableWallpaper() { + try { + ImpersonateCurrentUser icu; + + vlog.debug("disable desktop wallpaper/Active Desktop"); + + // -=- First attempt to remove the wallpaper using Active Desktop + try { + ActiveDesktop ad; + if (ad.enable(false)) + restoreActiveDesktop = true; + } catch (rdr::Exception& e) { + vlog.error(e.str()); + } + + // -=- Switch of normal wallpaper and notify apps + SysParamsInfo(SPI_SETDESKWALLPAPER, 0, "", SPIF_SENDCHANGE); + restoreWallpaper = true; + + } catch (rdr::Exception& e) { + vlog.info(e.str()); + } +} + +void CleanDesktop::enableWallpaper() { + try { + ImpersonateCurrentUser icu; + + if (restoreActiveDesktop) { + vlog.debug("restore Active Desktop"); + + // -=- First attempt to re-enable Active Desktop + try { + ActiveDesktop ad; + ad.enable(true); + restoreActiveDesktop = false; + } catch (rdr::Exception& e) { + vlog.error(e.str()); + } + } + + if (restoreWallpaper) { + vlog.debug("restore desktop wallpaper"); + + // -=- Then restore the standard wallpaper if required + SysParamsInfo(SPI_SETDESKWALLPAPER, 0, NULL, SPIF_SENDCHANGE); + restoreWallpaper = false; + } + + } catch (rdr::Exception& e) { + vlog.info(e.str()); + } +} + + +void CleanDesktop::disablePattern() { + try { + ImpersonateCurrentUser icu; + + vlog.debug("disable desktop pattern"); + SysParamsInfo(SPI_SETDESKPATTERN, 0, "", SPIF_SENDCHANGE); + restorePattern = true; + + } catch (rdr::Exception& e) { + vlog.info(e.str()); + } +} + +void CleanDesktop::enablePattern() { + try { + if (restorePattern) { + ImpersonateCurrentUser icu; + + vlog.debug("restoring pattern..."); + + TCharArray pattern; + if (osVersion.isPlatformWindows) { + RegKey cfgKey; + cfgKey.openKey(HKEY_CURRENT_USER, _T("Control Panel\\Desktop")); + pattern.buf = cfgKey.getString(_T("Pattern")); + } + SysParamsInfo(SPI_SETDESKPATTERN, 0, pattern.buf, SPIF_SENDCHANGE); + restorePattern = false; + } + + } catch (rdr::Exception& e) { + vlog.info(e.str()); + } +} + + +void CleanDesktop::disableEffects() { +#if (WINVER >= 0x500) + try { + ImpersonateCurrentUser icu; + + vlog.debug("disable desktop effects"); + + SysParamsInfo(SPI_SETFONTSMOOTHING, FALSE, 0, SPIF_SENDCHANGE); + if (SysParamsInfo(SPI_GETUIEFFECTS, 0, &uiEffects, 0) == ERROR_CALL_NOT_IMPLEMENTED) { + SysParamsInfo(SPI_GETCOMBOBOXANIMATION, 0, &comboBoxAnim, 0); + SysParamsInfo(SPI_GETGRADIENTCAPTIONS, 0, &gradientCaptions, 0); + SysParamsInfo(SPI_GETHOTTRACKING, 0, &hotTracking, 0); + SysParamsInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &listBoxSmoothScroll, 0); + SysParamsInfo(SPI_GETMENUANIMATION, 0, &menuAnim, 0); + SysParamsInfo(SPI_SETCOMBOBOXANIMATION, 0, FALSE, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETGRADIENTCAPTIONS, 0, FALSE, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETHOTTRACKING, 0, FALSE, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETLISTBOXSMOOTHSCROLLING, 0, FALSE, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETMENUANIMATION, 0, FALSE, SPIF_SENDCHANGE); + } else { + SysParamsInfo(SPI_SETUIEFFECTS, 0, FALSE, SPIF_SENDCHANGE); + } + restoreEffects = true; + + } catch (rdr::Exception& e) { + vlog.info(e.str()); + } +#else + vlog.info("disableffects not implemented"); +#endif +} + +void CleanDesktop::enableEffects() { + try { + if (restoreEffects) { +#if (WINVER >= 0x500) + ImpersonateCurrentUser icu; + + vlog.debug("restore desktop effects"); + + RegKey desktopCfg; + desktopCfg.openKey(HKEY_CURRENT_USER, _T("Control Panel\\Desktop")); + SysParamsInfo(SPI_SETFONTSMOOTHING, desktopCfg.getInt(_T("FontSmoothing"), 0) != 0, 0, SPIF_SENDCHANGE); + if (SysParamsInfo(SPI_SETUIEFFECTS, 0, (void*)uiEffects, SPIF_SENDCHANGE) == ERROR_CALL_NOT_IMPLEMENTED) { + SysParamsInfo(SPI_SETCOMBOBOXANIMATION, 0, (void*)comboBoxAnim, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETGRADIENTCAPTIONS, 0, (void*)gradientCaptions, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETHOTTRACKING, 0, (void*)hotTracking, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETLISTBOXSMOOTHSCROLLING, 0, (void*)listBoxSmoothScroll, SPIF_SENDCHANGE); + SysParamsInfo(SPI_SETMENUANIMATION, 0, (void*)menuAnim, SPIF_SENDCHANGE); + } + restoreEffects = false; +#else + vlog.info("enableEffects not implemented"); +#endif + } + + } catch (rdr::Exception& e) { + vlog.info(e.str()); + } +} diff --git a/rfb_win32/CleanDesktop.h b/rfb_win32/CleanDesktop.h new file mode 100644 index 00000000..73f41534 --- /dev/null +++ b/rfb_win32/CleanDesktop.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- CleanDesktop.h + +#ifndef __RFB_WIN32_CLEANDESKTOP_H__ +#define __RFB_WIN32_CLEANDESKTOP_H__ + +#include <rfb_win32/TCharArray.h> + +namespace rfb { + + namespace win32 { + + class CleanDesktop { + public: + CleanDesktop(); + ~CleanDesktop(); + + void disableWallpaper(); + void enableWallpaper(); + + void disablePattern(); + void enablePattern(); + + void disableEffects(); + void enableEffects(); + + private: + bool restoreActiveDesktop; + bool restoreWallpaper; + bool restorePattern; + bool restoreEffects; + BOOL uiEffects; + BOOL comboBoxAnim, gradientCaptions, hotTracking, listBoxSmoothScroll, menuAnim; + }; + + }; // win32 + +}; // rfb + +#endif // __RFB_WIN32_CLEANDESKTOP_H__ diff --git a/rfb_win32/Clipboard.cxx b/rfb_win32/Clipboard.cxx new file mode 100644 index 00000000..96d1e942 --- /dev/null +++ b/rfb_win32/Clipboard.cxx @@ -0,0 +1,199 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Clipboard.cxx + +#include <rfb_win32/Clipboard.h> +#include <rfb_win32/WMShatter.h> +#include <rfb/util.h> + +#include <rfb/LogWriter.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("Clipboard"); + + +// +// -=- CR/LF handlers +// + +char* +dos2unix(const char* text) { + int len = strlen(text)+1; + char* unix = new char[strlen(text)+1]; + int i, j=0; + for (i=0; i<len; i++) { + if (text[i] != '\x0d') + unix[j++] = text[i]; + } + return unix; +} + +char* +unix2dos(const char* text) { + int len = strlen(text)+1; + char* dos = new char[strlen(text)*2+1]; + int i, j=0; + for (i=0; i<len; i++) { + if (text[i] == '\x0a') + dos[j++] = '\x0d'; + dos[j++] = text[i]; + } + return dos; +} + + +// +// -=- ASCII filter (in-place) +// + +void +removeNonAsciiChars(char* text) { + int len = strlen(text); + int i=0, j=0; + for (; i<len; i++) { + if ((text[i] >= 1) && (text[i] <= 127)) + text[j++] = text[i]; + } + text[j] = 0; +} + +// +// -=- Clipboard object +// + +Clipboard::Clipboard() + : MsgWindow(_T("Clipboard")), notifier(0), next_window(0) { + next_window = SetClipboardViewer(getHandle()); + vlog.debug("registered clipboard handler"); +} + +Clipboard::~Clipboard() { + vlog.debug("removing %x from chain (next is %x)", getHandle(), next_window); + ChangeClipboardChain(getHandle(), next_window); +} + +LRESULT +Clipboard::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + + case WM_CHANGECBCHAIN: + vlog.debug("change clipboard chain (%x, %x)", wParam, lParam); + if ((HWND) wParam == next_window) + next_window = (HWND) lParam; + else if (next_window != 0) + SendMessage(next_window, msg, wParam, lParam); + else + vlog.error("bad clipboard chain change!"); + break; + + case WM_DRAWCLIPBOARD: + { + HWND owner = GetClipboardOwner(); + if (owner == getHandle()) { + vlog.debug("local clipboard changed by me"); + } else { + vlog.debug("local clipboard changed by %x", owner); + + // Open the clipboard + if (OpenClipboard(getHandle())) { + // Get the clipboard data + HGLOBAL cliphandle = GetClipboardData(CF_TEXT); + if (cliphandle) { + char* clipdata = (char*) GlobalLock(cliphandle); + + // Notify clients + if (notifier) { + if (!clipdata) { + notifier->notifyClipboardChanged(0, 0); + } else { + CharArray unix_text; + unix_text.buf = dos2unix(clipdata); + removeNonAsciiChars(unix_text.buf); + notifier->notifyClipboardChanged(unix_text.buf, strlen(unix_text.buf)); + } + } else { + vlog.debug("no clipboard notifier registered"); + } + + // Release the buffer and close the clipboard + GlobalUnlock(cliphandle); + } + + CloseClipboard(); + } + } + } + if (next_window) + SendMessage(next_window, msg, wParam, lParam); + return 0; + + }; + return MsgWindow::processMessage(msg, wParam, lParam); +}; + +void +Clipboard::setClipText(const char* text) { + HANDLE clip_handle = 0; + + try { + + // - Firstly, we must open the clipboard + if (!OpenClipboard(getHandle())) + throw rdr::SystemException("unable to open Win32 clipboard", GetLastError()); + + // - Pre-process the supplied clipboard text into DOS format + CharArray dos_text; + dos_text.buf = unix2dos(text); + removeNonAsciiChars(dos_text.buf); + int dos_text_len = strlen(dos_text.buf); + + // - Allocate global memory for the data + clip_handle = ::GlobalAlloc(GMEM_MOVEABLE, dos_text_len+1); + + char* data = (char*) GlobalLock(clip_handle); + memcpy(data, dos_text.buf, dos_text_len+1); + data[dos_text_len] = 0; + GlobalUnlock(clip_handle); + + // - Next, we must clear out any existing data + if (!EmptyClipboard()) + throw rdr::SystemException("unable to empty Win32 clipboard", GetLastError()); + + // - Set the new clipboard data + if (!SetClipboardData(CF_TEXT, clip_handle)) + throw rdr::SystemException("unable to set Win32 clipboard", GetLastError()); + clip_handle = 0; + + vlog.debug("set clipboard"); + } catch (rdr::Exception& e) { + vlog.debug(e.str()); + } + + // - Close the clipboard + if (!CloseClipboard()) + vlog.debug("unable to close Win32 clipboard: %u", GetLastError()); + else + vlog.debug("closed clipboard"); + if (clip_handle) { + vlog.debug("freeing clipboard handle"); + GlobalFree(clip_handle); + } +} diff --git a/rfb_win32/Clipboard.h b/rfb_win32/Clipboard.h new file mode 100644 index 00000000..57297e1e --- /dev/null +++ b/rfb_win32/Clipboard.h @@ -0,0 +1,66 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Clipboard.h +// +// The Clipboard is used to set the system clipboard, and to get callbacks +// when the system clipboard has changed. + +#ifndef __RFB_WIN32_CLIPBOARD_H__ +#define __RFB_WIN32_CLIPBOARD_H__ + +#include <rfb/SDesktop.h> +#include <rfb/Threading.h> +#include <rfb_win32/MsgWindow.h> +#include <rfb_win32/DeviceFrameBuffer.h> + +namespace rfb { + + namespace win32 { + + class Clipboard : MsgWindow { + public: + + // -=- Abstract base class for callback recipients + class Notifier { + public: + virtual void notifyClipboardChanged(const char* text, int len) = 0; + }; + + Clipboard(); + ~Clipboard(); + + // - Set the notifier to use + void setNotifier(Notifier* cbn) {notifier = cbn;} + + // - Set the clipboard contents + void setClipText(const char* text); + + protected: + // - Internal MsgWindow callback + virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam); + + Notifier* notifier; + HWND next_window; + }; + + }; + +}; + +#endif // __RFB_WIN32_CLIPBOARD_H__ diff --git a/rfb_win32/CurrentUser.cxx b/rfb_win32/CurrentUser.cxx new file mode 100644 index 00000000..1a244853 --- /dev/null +++ b/rfb_win32/CurrentUser.cxx @@ -0,0 +1,93 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Currentuser.cxx + +#include <windows.h> +#include <lmcons.h> +#include <rfb_win32/CurrentUser.h> +#include <rfb_win32/Service.h> +#include <rfb_win32/OSVersion.h> + +using namespace rfb; +using namespace win32; + + +CurrentUserToken::CurrentUserToken() : isValid_(false) { + // - If the OS doesn't support security, ignore it + if (!isServiceProcess()) { + // - Running in User-Mode - just get our current token + if (!OpenProcessToken(GetCurrentProcess(), GENERIC_ALL, &h)) { + DWORD err = GetLastError(); + if (err != ERROR_CALL_NOT_IMPLEMENTED) + throw rdr::SystemException("OpenProcessToken failed", GetLastError()); + } + isValid_ = true; + } else { + // - Under XP/2003 and above, we can just ask the operating system + typedef BOOL (WINAPI *WTSQueryUserToken_proto)(ULONG, PHANDLE); + DynamicFn<WTSQueryUserToken_proto> _WTSQueryUserToken(_T("wtsapi32.dll"), "WTSQueryUserToken"); + if (_WTSQueryUserToken.isValid()) { + (*_WTSQueryUserToken)(-1, &h); + isValid_ = true; + } else { + // - Under NT/2K we have to resort to a nasty hack... + HWND tray = FindWindow(_T("Shell_TrayWnd"), 0); + if (!tray) + return; + DWORD processId = 0; + GetWindowThreadProcessId(tray, &processId); + if (!processId) + return; + Handle process = OpenProcess(MAXIMUM_ALLOWED, FALSE, processId); + if (!process.h) + return; + OpenProcessToken(process, MAXIMUM_ALLOWED, &h); + isValid_ = true; + } + } +} + + +ImpersonateCurrentUser::ImpersonateCurrentUser() { + RegCloseKey(HKEY_CURRENT_USER); + if (!isServiceProcess()) + return; + if (!token.isValid()) + throw rdr::Exception("CurrentUserToken is not valid"); + if (!ImpersonateLoggedOnUser(token)) { + DWORD err = GetLastError(); + if (err != ERROR_CALL_NOT_IMPLEMENTED) + throw rdr::SystemException("Failed to impersonate user", GetLastError()); + } +} + +ImpersonateCurrentUser::~ImpersonateCurrentUser() { + if (!RevertToSelf()) { + DWORD err = GetLastError(); + if (err != ERROR_CALL_NOT_IMPLEMENTED) + exit(err); + } +} + + +UserName::UserName() : TCharArray(UNLEN+1) { + DWORD len = UNLEN+1; + if (!GetUserName(buf, &len)) + throw rdr::SystemException("GetUserName failed", GetLastError()); +} diff --git a/rfb_win32/CurrentUser.h b/rfb_win32/CurrentUser.h new file mode 100644 index 00000000..469946b6 --- /dev/null +++ b/rfb_win32/CurrentUser.h @@ -0,0 +1,78 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// CurrentUser.h + +// Helper class providing the session's logged on username, if +// a user is logged on. Also allows processes running under +// XP/2K3 etc to masquerade as the logged on user for security +// purposes + +#ifndef __RFB_WIN32_CURRENT_USER_H__ +#define __RFB_WIN32_CURRENT_USER_H__ + +#include <rfb_win32/Service.h> +#include <rfb_win32/OSVersion.h> +#include <rfb_win32/Win32Util.h> + +namespace rfb { + + namespace win32 { + + // CurrentUserToken + // h == 0 + // if platform is not NT *or* unable to get token + // *or* if process is hosting service. + // h != 0 + // if process is hosting service *and* + // if platform is NT *and* able to get token. + // isValid() == true + // if platform is not NT *or* token is valid. + + struct CurrentUserToken : public Handle { + CurrentUserToken(); + bool isValid() const {return isValid_;}; + private: + bool isValid_; + }; + + // ImpersonateCurrentUser + // Throws an exception on failure. + // Succeeds (trivially) if process is not running as service. + // Fails if CurrentUserToken is not valid. + // Fails if platform is NT AND cannot impersonate token. + // Succeeds otherwise. + + struct ImpersonateCurrentUser { + ImpersonateCurrentUser(); + ~ImpersonateCurrentUser(); + CurrentUserToken token; + }; + + // UserName + // Returns the name of the user the thread is currently running as. + + struct UserName : public TCharArray { + UserName(); + }; + + } + +} + +#endif diff --git a/rfb_win32/DIBSectionBuffer.cxx b/rfb_win32/DIBSectionBuffer.cxx new file mode 100644 index 00000000..21743768 --- /dev/null +++ b/rfb_win32/DIBSectionBuffer.cxx @@ -0,0 +1,221 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#include <rfb/LogWriter.h> + +#include <rfb_win32/DIBSectionBuffer.h> +#include <rfb_win32/Win32Util.h> + + +using namespace rfb; +using namespace win32; + +static LogWriter vlog("DIBSection"); + + +DIBSectionBuffer::DIBSectionBuffer(HWND window_) + : bitmap(0), device(0), window(window_) { + memset(&format, 0, sizeof(format)); + memset(palette, 0, sizeof(palette)); +} + +DIBSectionBuffer::DIBSectionBuffer(HDC device_) + : bitmap(0), window(0), device(device_) { + memset(&format, 0, sizeof(format)); + memset(palette, 0, sizeof(palette)); +} + +DIBSectionBuffer::~DIBSectionBuffer() { + if (bitmap) + DeleteObject(bitmap); +} + + +void DIBSectionBuffer::setPF(const PixelFormat& pf) { + if (memcmp(&getPF(), &pf, sizeof(pf)) == 0) { + vlog.debug("pixel format unchanged by setPF()"); + return; + } + format = pf; + recreateBuffer(); + if ((pf.bpp <= 8) && pf.trueColour) { + vlog.debug("creating %d-bit TrueColour palette", pf.depth); + for (int i=0; i < (1<<(pf.depth)); i++) { + palette[i].b = ((((i >> pf.blueShift) & pf.blueMax) * 65535) + pf.blueMax/2) / pf.blueMax; + palette[i].g = ((((i >> pf.greenShift) & pf.greenMax) * 65535) + pf.greenMax/2) / pf.greenMax; + palette[i].r = ((((i >> pf.redShift) & pf.redMax) * 65535) + pf.redMax/2) / pf.redMax; + } + refreshPalette(); + } +} + +void DIBSectionBuffer::setSize(int w, int h) { + if (width_ == w && height_ == h) { + vlog.debug("size unchanged by setSize()"); + return; + } + width_ = w; + height_ = h; + recreateBuffer(); +} + + +// * copyPaletteToDIB MUST NEVER be called on a truecolour DIB! * + +void copyPaletteToDIB(Colour palette[256], HDC wndDC, HBITMAP dib) { + BitmapDC dibDC(wndDC, dib); + RGBQUAD rgb[256]; + for (unsigned int i=0;i<256;i++) { + rgb[i].rgbRed = palette[i].r >> 8; + rgb[i].rgbGreen = palette[i].g >> 8; + rgb[i].rgbBlue = palette[i].b >> 8; + } + if (!SetDIBColorTable(dibDC, 0, 256, (RGBQUAD*) rgb)) + throw rdr::SystemException("unable to SetDIBColorTable", GetLastError()); +} + +inline void initMaxAndShift(DWORD mask, int* max, int* shift) { + for ((*shift) = 0; (mask & 1) == 0; (*shift)++) mask >>= 1; + (*max) = (rdr::U16)mask; +} + +void DIBSectionBuffer::recreateBuffer() { + HBITMAP new_bitmap = 0; + rdr::U8* new_data = 0; + + if (width_ && height_ && (format.depth != 0)) { + BitmapInfo bi; + memset(&bi, 0, sizeof(bi)); + // *** wrong? + UINT iUsage = format.trueColour ? DIB_RGB_COLORS : DIB_PAL_COLORS; + // *** + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biBitCount = format.bpp; + bi.bmiHeader.biSizeImage = (format.bpp / 8) * width_ * height_; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biWidth = width_; + bi.bmiHeader.biHeight = -height_; + bi.bmiHeader.biCompression = (format.bpp > 8) ? BI_BITFIELDS : BI_RGB; + bi.mask.red = format.redMax << format.redShift; + bi.mask.green = format.greenMax << format.greenShift; + bi.mask.blue = format.blueMax << format.blueShift; + + vlog.debug("area=%d, bpp=%d", width_ * height_, format.bpp); + + // Create a DIBSection to draw into + if (device) + new_bitmap = ::CreateDIBSection(device, (BITMAPINFO*)&bi.bmiHeader, iUsage, + (void**)&new_data, NULL, 0); + else + new_bitmap = ::CreateDIBSection(WindowDC(window), (BITMAPINFO*)&bi.bmiHeader, iUsage, + (void**)&new_data, NULL, 0); + + if (!new_bitmap) { + int err = GetLastError(); + throw rdr::SystemException("unable to create DIB section", err); + } + + vlog.debug("recreateBuffer()"); + } else { + vlog.debug("one of area or format not set"); + } + + if (new_bitmap && bitmap) { + vlog.debug("preserving bitmap contents"); + + // Copy the contents across + if (device) { + if (format.bpp <= 8) + copyPaletteToDIB(palette, device, new_bitmap); + BitmapDC src_dev(device, bitmap); + BitmapDC dest_dev(device, new_bitmap); + BitBlt(dest_dev, 0, 0, width_, height_, src_dev, 0, 0, SRCCOPY); + } else { + WindowDC wndDC(window); + if (format.bpp <= 8) + copyPaletteToDIB(palette, wndDC, new_bitmap); + BitmapDC src_dev(wndDC, bitmap); + BitmapDC dest_dev(wndDC, new_bitmap); + BitBlt(dest_dev, 0, 0, width_, height_, src_dev, 0, 0, SRCCOPY); + } + } + + if (bitmap) { + // Delete the old bitmap + DeleteObject(bitmap); + bitmap = 0; + data = 0; + } + + if (new_bitmap) { + // Set up the new bitmap + bitmap = new_bitmap; + data = new_data; + + // Determine the *actual* DIBSection format + DIBSECTION ds; + if (!GetObject(bitmap, sizeof(ds), &ds)) + throw rdr::SystemException("GetObject", GetLastError()); + + // Correct the "stride" of the DIB + // *** This code DWORD aligns each row - is that right??? + stride = width_; + int bytesPerRow = stride * format.bpp/8; + if (bytesPerRow % 4) { + bytesPerRow += 4 - (bytesPerRow % 4); + stride = (bytesPerRow * 8) / format.bpp; + vlog.info("adjusting DIB stride: %d to %d", width_, stride); + } + + // Calculate the PixelFormat for the DIB + format.bigEndian = 0; + format.bpp = format.depth = ds.dsBm.bmBitsPixel; + format.trueColour = format.trueColour || format.bpp > 8; + if (format.bpp > 8) { + + // Get the truecolour format used by the DIBSection + initMaxAndShift(ds.dsBitfields[0], &format.redMax, &format.redShift); + initMaxAndShift(ds.dsBitfields[1], &format.greenMax, &format.greenShift); + initMaxAndShift(ds.dsBitfields[2], &format.blueMax, &format.blueShift); + + // Calculate the effective depth + format.depth = 0; + Pixel bits = ds.dsBitfields[0] | ds.dsBitfields[1] | ds.dsBitfields[2]; + while (bits) { + format.depth++; + bits = bits >> 1; + } + } else { + // Set the DIBSection's palette + refreshPalette(); + } + } +} + +void DIBSectionBuffer::refreshPalette() { + if (format.bpp > 8) { + vlog.error("refresh palette called for truecolour DIB"); + return; + } + vlog.debug("refreshing palette"); + if (device) + copyPaletteToDIB(palette, device, bitmap); + else + copyPaletteToDIB(palette, WindowDC(window), bitmap); +} + + diff --git a/rfb_win32/DIBSectionBuffer.h b/rfb_win32/DIBSectionBuffer.h new file mode 100644 index 00000000..51e2da31 --- /dev/null +++ b/rfb_win32/DIBSectionBuffer.h @@ -0,0 +1,87 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- DIBSectionBuffer.h + +// A DIBSectionBuffer acts much like a standard PixelBuffer, but is associated +// with a particular window on-screen and can be drawn into that window if +// required, using the standard Win32 drawing operations. + +#ifndef __RFB_WIN32_DIB_SECTION_BUFFER_H__ +#define __RFB_WIN32_DIB_SECTION_BUFFER_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb/PixelBuffer.h> +#include <rfb/Region.h> +#include <rfb/ColourMap.h> +#include <rfb/Exception.h> + +namespace rfb { + + namespace win32 { + + // + // -=- DIBSectionBuffer + // + + class DIBSectionBuffer : public FullFramePixelBuffer, ColourMap { + public: + DIBSectionBuffer(HWND window); + DIBSectionBuffer(HDC device); + virtual ~DIBSectionBuffer(); + + virtual void setPF(const PixelFormat &pf); + virtual void setSize(int w, int h); + + virtual int getStride() const {return stride;} + + virtual ColourMap* getColourMap() const {return (ColourMap*)this;} + + // - ColourMap interface + virtual void lookup(int index, int* r, int *g, int* b) { + *r = palette[index].r; + *g = palette[index].g; + *b = palette[index].b; + } + + // Custom colourmap interface + void setColour(int index, int r, int g, int b) { + palette[index].r = r; + palette[index].g = g; + palette[index].b = b; + } + void refreshPalette(); + + // *** virtual void copyRect(const Rect &dest, const Point &move_by_delta); + public: + HBITMAP bitmap; + protected: + void recreateBuffer(); + Colour palette[256]; + int stride; + HWND window; + HDC device; + }; + + }; + +}; + +#endif // __RFB_WIN32_DIB_SECTION_BUFFER_H__ diff --git a/rfb_win32/DeviceFrameBuffer.cxx b/rfb_win32/DeviceFrameBuffer.cxx new file mode 100644 index 00000000..a4d10211 --- /dev/null +++ b/rfb_win32/DeviceFrameBuffer.cxx @@ -0,0 +1,298 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- DeviceFrameBuffer.cxx +// +// The DeviceFrameBuffer class encapsulates the pixel data of the system +// display. + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <assert.h> + +#include <vector> + +#include <rfb/LogWriter.h> +#include <rfb/Exception.h> +#include <rdr/types.h> +#include <rfb/VNCServer.h> +#include <rfb_win32/DeviceFrameBuffer.h> +#include <rfb_win32/Win32Util.h> +#include <rfb_win32/OSVersion.h> + +namespace rfb { + +namespace win32 { + +static LogWriter vlog("FrameBuffer"); + + +// -=- DeviceFrameBuffer class + +DeviceFrameBuffer::DeviceFrameBuffer(HDC deviceContext, const Rect& wRect) + : DIBSectionBuffer(deviceContext), device(deviceContext), cursorBm(deviceContext), + ignoreGrabErrors(false) +{ + + // -=- Firstly, let's check that the device has suitable capabilities + + int capabilities = GetDeviceCaps(device, RASTERCAPS); + if (!(capabilities & RC_BITBLT)) { + throw Exception("device does not support BitBlt"); + } + if (!(capabilities & RC_DI_BITMAP)) { + throw Exception("device does not support GetDIBits"); + } + /* + if (GetDeviceCaps(device, PLANES) != 1) { + throw Exception("device does not support planar displays"); + } + */ + + // -=- Get the display dimensions and pixel format + + // Get the display dimensions + RECT cr; + if (!GetClipBox(device, &cr)) + throw rdr::SystemException("GetClipBox", GetLastError()); + deviceCoords = Rect(cr.left, cr.top, cr.right, cr.bottom); + if (!wRect.is_empty()) + deviceCoords = wRect.translate(deviceCoords.tl); + int w = deviceCoords.width(); + int h = deviceCoords.height(); + + // We can't handle uneven widths :( + if (w % 2) w--; + + // Configure the underlying DIB to match the device + DIBSectionBuffer::setPF(DeviceContext::getPF(device)); + DIBSectionBuffer::setSize(w, h); + + // Configure the cursor buffer + cursorBm.setPF(format); + + // Set up a palette if required + if (!format.trueColour) + updateColourMap(); +} + +DeviceFrameBuffer::~DeviceFrameBuffer() { +} + + +void +DeviceFrameBuffer::setPF(const PixelFormat &pf) { + throw Exception("setPF not supported"); +} + +void +DeviceFrameBuffer::setSize(int w, int h) { + throw Exception("setSize not supported"); +} + + +#ifndef CAPTUREBLT +#define CAPTUREBLT 0x40000000 +#endif + +void +DeviceFrameBuffer::grabRect(const Rect &rect) { + BitmapDC tmpDC(device, bitmap); + + // Map the rectangle coords from VNC Desktop-relative to device relative - usually (0,0) + Point src = desktopToDevice(rect.tl); + + // Note: Microsoft's documentation lies directly about CAPTUREBLT and claims it works on 98/ME + // If you try CAPTUREBLT on 98 then you get blank output... + if (!::BitBlt(tmpDC, rect.tl.x, rect.tl.y, rect.width(), rect.height(), device, src.x, src.y, + osVersion.isPlatformNT ? CAPTUREBLT | SRCCOPY : SRCCOPY)) { + if (ignoreGrabErrors) + vlog.error("BitBlt failed:%ld", GetLastError()); + else + throw rdr::SystemException("BitBlt failed", GetLastError()); + } +} + +void +DeviceFrameBuffer::grabRegion(const Region &rgn) { + std::vector<Rect> rects; + std::vector<Rect>::const_iterator i; + rgn.get_rects(&rects); + for(i=rects.begin(); i!=rects.end(); i++) { + grabRect(*i); + } + ::GdiFlush(); +} + + +void copyDevicePaletteToDIB(HDC dc, DIBSectionBuffer* dib) { + // - Fetch the system palette for the framebuffer + PALETTEENTRY syspalette[256]; + UINT entries = ::GetSystemPaletteEntries(dc, 0, 256, syspalette); + + if (entries == 0) { + vlog.info("resorting to standard 16 colour palette"); + for (unsigned int i=0;i<256;i++) { + int v = (i%16) >= 8 ? 127 : 255; + syspalette[i].peRed = i & 1 ? v : 0; + syspalette[i].peGreen = i & 2 ? v : 0; + syspalette[i].peBlue = i & 4 ? v : 0; + } + } else { + vlog.info("framebuffer has %u palette entries", entries); + } + + // - Update the bitmap's stored copy of the palette + for (unsigned int i=0;i<256;i++) { + int r, g, b; + r = (syspalette[i].peRed << 8) + 0x80; + g = (syspalette[i].peGreen << 8) + 0x80; + b = (syspalette[i].peBlue << 8) + 0x80; + dib->setColour(i, r, g, b); + } + + // - Update the DIB section to use the palette + dib->refreshPalette(); +} + + +void DeviceFrameBuffer::setCursor(HCURSOR hCursor, VNCServer* server) +{ + // - If hCursor is null then there is no cursor - clear the old one + + if (hCursor == 0) { + server->setCursor(0, 0, 0, 0, 0, 0); + return; + } + + try { + + // - Get the size and other details about the cursor. + + IconInfo iconInfo((HICON)hCursor); + + BITMAP maskInfo; + if (!GetObject(iconInfo.hbmMask, sizeof(BITMAP), &maskInfo)) + throw rdr::SystemException("GetObject() failed", GetLastError()); + + assert(maskInfo.bmPlanes == 1 && maskInfo.bmBitsPixel == 1); + + // - Create the cursor pixel buffer and mask storage + // NB: The cursor pixel buffer is NOT used here. Instead, we + // pass the cursorBm.data pointer directly, to save overhead. + + cursor.setSize(maskInfo.bmWidth, maskInfo.bmHeight); + cursor.setPF(format); + cursor.hotspot = Point(iconInfo.xHotspot, iconInfo.yHotspot); + + // - Get the AND and XOR masks. There is only an XOR mask if this is not a + // colour cursor. + + if (!iconInfo.hbmColor) + cursor.setSize(cursor.width(), cursor.height() / 2); + rdr::U8Array mask(maskInfo.bmWidthBytes * maskInfo.bmHeight); + rdr::U8* xorMask = mask.buf + cursor.height() * maskInfo.bmWidthBytes; + + if (!GetBitmapBits(iconInfo.hbmMask, + maskInfo.bmWidthBytes * maskInfo.bmHeight, mask.buf)) + throw rdr::SystemException("GetBitmapBits failed", GetLastError()); + + // Configure the cursor bitmap + cursorBm.setSize(cursor.width(), cursor.height()); + + // Copy the palette into it if required + if (format.bpp <= 8) + copyDevicePaletteToDIB(device, &cursorBm); + + // Draw the cursor into the bitmap + BitmapDC dc(device, cursorBm.bitmap); + if (!DrawIconEx(dc, 0, 0, hCursor, 0, 0, 0, NULL, DI_NORMAL | DI_COMPAT)) + throw rdr::SystemException("unable to render cursor", GetLastError()); + + // Replace any XORed pixels with xorColour, because RFB doesn't support + // XORing of cursors. XORing is used for the I-beam cursor, which is most + // often used over a white background, but also sometimes over a black + // background. We set the XOR'd pixels to black, then draw a white outline + // around the whole cursor. + + // *** should we replace any pixels not set in mask to zero, to ensure + // that irrelevant data doesn't screw compression? + + bool doOutline = false; + if (!iconInfo.hbmColor) { + Pixel xorColour = format.pixelFromRGB(0, 0, 0, cursorBm.getColourMap()); + for (int y = 0; y < cursor.height(); y++) { + bool first = true; + for (int x = 0; x < cursor.width(); x++) { + int byte = y * maskInfo.bmWidthBytes + x / 8; + int bit = 7 - x % 8; + if ((mask.buf[byte] & (1 << bit)) && (xorMask[byte] & (1 << bit))) + { + mask.buf[byte] &= ~(1 << bit); + + switch (format.bpp) { + case 8: + ((rdr::U8*)cursorBm.data)[y * cursor.width() + x] = xorColour; break; + case 16: + ((rdr::U16*)cursorBm.data)[y * cursor.width() + x] = xorColour; break; + case 32: + ((rdr::U32*)cursorBm.data)[y * cursor.width() + x] = xorColour; break; + } + + doOutline = true; + } + } + } + } + + // Finally invert the AND mask so it's suitable for RFB and pack it into + // the minimum number of bytes per row. + + int maskBytesPerRow = (cursor.width() + 7) / 8; + + for (int j = 0; j < cursor.height(); j++) { + for (int i = 0; i < maskBytesPerRow; i++) + cursor.mask.buf[j * maskBytesPerRow + i] + = ~mask.buf[j * maskInfo.bmWidthBytes + i]; + } + + if (doOutline) { + vlog.debug("drawing cursor outline!"); + memcpy(cursor.data, cursorBm.data, cursor.dataLen()); + cursor.drawOutline(format.pixelFromRGB(0xffff, 0xffff, 0xffff, cursorBm.getColourMap())); + memcpy(cursorBm.data, cursor.data, cursor.dataLen()); + } + + server->setCursor(cursor.width(), cursor.height(), + cursor.hotspot.x, cursor.hotspot.y, + cursorBm.data, cursor.mask.buf); + } catch (rdr::Exception& e) { + vlog.error(e.str()); + } +} + + +void +DeviceFrameBuffer::updateColourMap() { + if (!format.trueColour) + copyDevicePaletteToDIB(device, this); +} + +}; // namespace win32 + +}; // namespace rfb diff --git a/rfb_win32/DeviceFrameBuffer.h b/rfb_win32/DeviceFrameBuffer.h new file mode 100644 index 00000000..5e97b222 --- /dev/null +++ b/rfb_win32/DeviceFrameBuffer.h @@ -0,0 +1,121 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- DeviceFrameBuffer.h +// +// The DeviceFrameBuffer class encapsulates the pixel data of a supplied +// Device Context Handle (HDC) + +// *** THIS INTERFACE NEEDS TIDYING TO SEPARATE COORDINATE SYSTEMS BETTER *** + +#ifndef __RFB_WIN32_DEVICE_FRAME_BUFFER_H__ +#define __RFB_WIN32_DEVICE_FRAME_BUFFER_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb_win32/DIBSectionBuffer.h> +#include <rfb/Cursor.h> +#include <rfb/Region.h> +#include <rfb/Exception.h> + +namespace rfb { + + class VNCServer; + + namespace win32 { + + // -=- DeviceFrameBuffer interface + + // DeviceFrameBuffer is passed an HDC referring to a window or to + // the entire display. It may also be passed a rectangle specifying + // the Device-relative coordinates of the actual rectangle to treat + // as the desktop. + + // Coordinate systems start getting really annoying here. There are + // three different "origins" to which coordinates might be relative: + // + // Desktop - VNC coordinates, top-left always (0,0) + // Device - DC coordinates. Top-left *usually (0,0) but could be other. + // Window - coordinates relative to the specified sub-rectangle within + // the supplied DC. + // Screen - Coordinates relative to the entire Windows virtual screen. + // The virtual screen includes all monitors that are part of + // the Windows desktop. + + // The data member is made to point to an internal mirror of the + // current display data. Individual rectangles or regions of the + // buffer can be brought up to date by calling the grab functions. + + class DeviceFrameBuffer : public DIBSectionBuffer { + public: + DeviceFrameBuffer(HDC deviceContext, const Rect& area_=Rect()); + virtual ~DeviceFrameBuffer(); + + // - FrameBuffer overrides + + virtual void grabRect(const Rect &rect); + virtual void grabRegion(const Region ®ion); + + // - DIBSectionBuffer overrides + + virtual void setPF(const PixelFormat& pf); + virtual void setSize(int w, int h); + + // - DeviceFrameBuffer specific methods + + void setCursor(HCURSOR c, VNCServer* server); + void updateColourMap(); + + // Set whether grabRect should ignore errors or throw exceptions + // Only set this if you are sure you'll capture the errors some other way! + void setIgnoreGrabErrors(bool ie) {ignoreGrabErrors=ie;} + + protected: + // Translate supplied Desktop coordinates into Device-relative coordinates + // This translation may have been affected at start-time by the supplied sub-rect. + Point desktopToDevice(const Point p) const {return p.translate(deviceCoords.tl);} + + HDC device; + DIBSectionBuffer cursorBm; + Cursor cursor; + Rect deviceCoords; + bool ignoreGrabErrors; + }; + + // -=- createDisplayDeviceFrameBuffer + // createDisplayDeviceFrameBuffer must be passed the name of a display device, + // and will return a new FrameBuffer object attached to that display + // device. + // If the device name is not specified then the default display is + // returned. + + DeviceFrameBuffer *createDisplayDeviceFrameBuffer(const char *device=0); + + // -=- createDisplayFrameBuffers + // Creates a set of framebuffers, one for each available display + // device. + + typedef std::vector<DeviceFrameBuffer *> DeviceFrameBuffers; + void createDisplayDeviceFrameBuffers(DeviceFrameBuffers *fbs); + + }; + +}; + +#endif // __RFB_WIN32_DEVICE_FRAME_BUFFER_H__ diff --git a/rfb_win32/Dialog.cxx b/rfb_win32/Dialog.cxx new file mode 100644 index 00000000..157cf5fe --- /dev/null +++ b/rfb_win32/Dialog.cxx @@ -0,0 +1,353 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Dialog.cxx + +// Base-class for any Dialog classes we might require + +#include <rfb_win32/Dialog.h> +#include <rfb_win32/TCharArray.h> +#include <rfb/LogWriter.h> +#include <rdr/Exception.h> +#include <rfb_win32/Win32Util.h> +#ifdef _DIALOG_CAPTURE +#include <rfb_win32/DeviceFrameBuffer.h> +#include <extra/LoadBMP.cxx> +#endif + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter dlog("Dialog"); +static LogWriter plog("PropSheet"); + + +Dialog::Dialog(HINSTANCE inst_) +: inst(inst_), alreadyShowing(false), handle(0) +{ +} + +Dialog::~Dialog() +{ +} + + +bool Dialog::showDialog(const TCHAR* resource, HWND owner) +{ + if (alreadyShowing) return false; + handle = 0; + alreadyShowing = true; + INT_PTR result = DialogBoxParam(inst, resource, owner, + staticDialogProc, (LPARAM)this); + if (result<0) + throw rdr::SystemException("DialogBoxParam failed", GetLastError()); + alreadyShowing = false; + return (result == 1); +} + + +bool Dialog::isItemChecked(int id) { + return SendMessage(GetDlgItem(handle, id), BM_GETCHECK, 0, 0) == BST_CHECKED; +} +int Dialog::getItemInt(int id) { + BOOL trans; + int result = GetDlgItemInt(handle, id, &trans, TRUE); + if (!trans) + throw rdr::Exception("unable to read dialog Int"); + return result; +} +TCHAR* Dialog::getItemString(int id) { + TCharArray tmp(256); + if (!GetDlgItemText(handle, id, tmp.buf, 256)) + tmp.buf[0] = 0; + return tmp.takeBuf(); +} + +void Dialog::setItemChecked(int id, bool state) { + dlog.debug("bool[%d]=%d", id, (int)state); + SendMessage(GetDlgItem(handle, id), BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0); +} +void Dialog::setItemInt(int id, int value) { + dlog.debug("int[%d]=%d", id, value); + SetDlgItemInt(handle, id, value, TRUE); +} +void Dialog::setItemString(int id, const TCHAR* s) { + dlog.debug("string[%d]=%s", id, (const char*)CStr(s)); + SetDlgItemText(handle, id, s); +} + + +void Dialog::enableItem(int id, bool state) { + dlog.debug("enable[%d]=%d", id, (int)state); + EnableWindow(GetDlgItem(handle, id), state); +} + + + + +BOOL CALLBACK Dialog::staticDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_INITDIALOG) + SetWindowLong(hwnd, GWL_USERDATA, (LONG)lParam); + + LONG self = GetWindowLong(hwnd, GWL_USERDATA); + if (!self) return FALSE; + + return ((Dialog*)self)->dialogProc(hwnd, msg, wParam, lParam); +} + +BOOL Dialog::dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + + case WM_INITDIALOG: + handle = hwnd; + initDialog(); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (onOk()) { + EndDialog(hwnd, 1); + return TRUE; + } + return FALSE; + case IDCANCEL: + EndDialog(hwnd, 0); + return TRUE; + default: + return onCommand(LOWORD(wParam), HIWORD(wParam)); + }; + + case WM_HELP: + return onHelp(((HELPINFO*)lParam)->iCtrlId); + + } + + return FALSE; +} + + +PropSheetPage::PropSheetPage(HINSTANCE inst, const TCHAR* id) : Dialog(inst), propSheet(0) { + page.dwSize = sizeof(page); + page.dwFlags = 0; // PSP_USECALLBACK; + page.hInstance = inst; + page.pszTemplate = id; + page.pfnDlgProc = staticPageProc; + page.lParam = (LPARAM)this; + page.pfnCallback = 0; // staticPageProc; +} + +PropSheetPage::~PropSheetPage() { +} + + +BOOL CALLBACK PropSheetPage::staticPageProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + if (msg == WM_INITDIALOG) + SetWindowLong(hwnd, GWL_USERDATA, ((PROPSHEETPAGE*)lParam)->lParam); + + LONG self = GetWindowLong(hwnd, GWL_USERDATA); + if (!self) return FALSE; + + return ((PropSheetPage*)self)->dialogProc(hwnd, msg, wParam, lParam); +} + +BOOL PropSheetPage::dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + + case WM_INITDIALOG: + handle = hwnd; + initDialog(); + return TRUE; + + case WM_NOTIFY: + switch (((NMHDR*)lParam)->code) { + case PSN_APPLY: + onOk(); + return FALSE; + }; + return FALSE; + + case WM_COMMAND: + return onCommand(LOWORD(wParam), HIWORD(wParam)); + + case WM_HELP: + return onHelp(((HELPINFO*)lParam)->iCtrlId); + + } + + return FALSE; +} + + +PropSheet::PropSheet(HINSTANCE inst_, const TCHAR* title_, std::list<PropSheetPage*> pages_, HICON icon_) +: title(tstrDup(title_)), inst(inst_), pages(pages_), alreadyShowing(0), handle(0), icon(icon_) { +} + +PropSheet::~PropSheet() { +} + + +bool PropSheet::showPropSheet(HWND owner, bool showApply, bool showCtxtHelp, bool capture) { + if (alreadyShowing) return false; + alreadyShowing = true; + int count = pages.size(); + + HPROPSHEETPAGE* hpages = new HPROPSHEETPAGE[count]; + try { + // Create the PropertSheet page GDI objects. + std::list<PropSheetPage*>::iterator pspi; + int i = 0; + for (pspi=pages.begin(); pspi!=pages.end(); pspi++) { + hpages[i] = CreatePropertySheetPage(&((*pspi)->page)); + (*pspi)->setPropSheet(this); + i++; + } + + // Initialise and create the PropertySheet itself + PROPSHEETHEADER header; + header.dwSize = PROPSHEETHEADER_V1_SIZE; + header.dwFlags = PSH_MODELESS | (showApply ? 0 : PSH_NOAPPLYNOW) /*| (showCtxtHelp ? 0 : PSH_NOCONTEXTHELP)*/; + header.hwndParent = owner; + header.hInstance = inst; + header.pszCaption = title.buf; + header.nPages = count; + header.nStartPage = 0; + header.phpage = hpages; + if (icon) { + header.hIcon = icon; + header.dwFlags |= PSH_USEHICON; + } + + handle = (HWND)PropertySheet(&header); + if ((handle == 0) || (handle == (HWND)-1)) + throw rdr::SystemException("PropertySheet failed", GetLastError()); + centerWindow(handle, owner); + plog.info("created %lx", handle); + +#if (WINVER >= 0x0500) +#ifdef _DIALOG_CAPTURE + // *** NOT TESTED + if (capture) { + plog.info("capturing \"%s\"", (const char*)CStr(title.buf)); + char* tmpdir = getenv("TEMP"); + HDC dc = GetWindowDC(handle); + DeviceFrameBuffer fb(dc); + int i=0; + while (true) { + int id = PropSheet_IndexToId(handle, i); + if (!id) break; + PropSheet_SetCurSelByID(handle, id); + MSG msg; + while (PeekMessage(&msg, handle, 0, 0, PM_REMOVE)) { + if (!PropSheet_IsDialogMessage(handle, &msg)) + DispatchMessage(&msg); + } + fb.grabRect(fb.getRect()); + char filename[256]; + sprintf(filename, "%s\\capture%d.bmp", tmpdir, i); + saveBMP(filename, &fb); + i++; + } + ReleaseDC(handle, dc); + } else { +#endif +#endif + try { + if (owner) + EnableWindow(owner, FALSE); + // Run the PropertySheet + MSG msg; + while (GetMessage(&msg, 0, 0, 0)) { + if (!PropSheet_IsDialogMessage(handle, &msg)) + DispatchMessage(&msg); + if (!PropSheet_GetCurrentPageHwnd(handle)) + break; + } + if (owner) + EnableWindow(owner, TRUE); + } catch (...) { + if (owner) + EnableWindow(owner, TRUE); + throw; + } +#if (WINVER >= 0x0500) +#ifdef _DIALOG_CAPTURE + } +#endif +#endif + + plog.info("finished %lx", handle); + + DestroyWindow(handle); + handle = 0; + alreadyShowing = false; + + // Clear up the pages' GDI objects + for (pspi=pages.begin(); pspi!=pages.end(); pspi++) + (*pspi)->setPropSheet(0); + delete [] hpages; hpages = 0; + + return true; + } catch (rdr::Exception) { + alreadyShowing = false; + + std::list<PropSheetPage*>::iterator pspi; + for (pspi=pages.begin(); pspi!=pages.end(); pspi++) + (*pspi)->setPropSheet(0); + delete [] hpages; hpages = 0; + + throw; + } +} + +void PropSheet::reInitPages() { + plog.debug("reInitPages %lx", handle); + std::list<PropSheetPage*>::iterator pspi; + for (pspi=pages.begin(); pspi!=pages.end(); pspi++) { + if ((*pspi)->handle) + (*pspi)->initDialog(); + } +} + +bool PropSheet::commitPages() { + plog.debug("commitPages %lx", handle); + bool result = true; + std::list<PropSheetPage*>::iterator pspi; + for (pspi=pages.begin(); pspi!=pages.end(); pspi++) { + if ((*pspi)->handle) + result = result && (*pspi)->onOk(); + } + return result; +} + + +void PropSheetPage::setChanged(bool changed) { + if (propSheet) { + plog.debug("setChanged[%lx(%lx)]=%d", handle, propSheet->handle, (int)changed); + if (changed) + PropSheet_Changed(propSheet->handle, handle); + else + PropSheet_UnChanged(propSheet->handle, handle); + } +} diff --git a/rfb_win32/Dialog.h b/rfb_win32/Dialog.h new file mode 100644 index 00000000..d46133a0 --- /dev/null +++ b/rfb_win32/Dialog.h @@ -0,0 +1,159 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- RegConfig.h + +// Class which monitors the registry and reads in the registry settings +// whenever they change, or are added or removed. + +#ifndef __RFB_WIN32_DIALOG_H__ +#define __RFB_WIN32_DIALOG_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <prsht.h> +#include <list> +#include <rfb_win32/TCharArray.h> + +namespace rfb { + + namespace win32 { + + // Dialog - A simple Win32 Dialog box. A derived class of Dialog overrides the + // initDialog(), command() and ok() methods to take appropriate action. A + // simple dialog box can be displayed by creating a Dialog object and calling + // show(). + + class Dialog { + public: + + Dialog(HINSTANCE inst); + virtual ~Dialog(); + + // showDialog() displays the dialog box. It returns when it has been dismissed, + // returning true if "OK" was pressed, false otherwise. The resource + // argument identifies the dialog resource (often a MAKEINTRESOURCE macro + // expansion), and owner is an optional window handle - the corresponding + // window is disabled while the dialog box is displayed. + + bool showDialog(const TCHAR* resource, HWND owner=0); + + // initDialog() is called upon receipt of the WM_INITDIALOG message. + + virtual void initDialog() {} + + // onCommand() is called upon receipt of a WM_COMMAND message item other than IDOK + // or IDCANCEL. It should return true if the command has been handled. + + virtual bool onCommand(int item, int cmd) { return false; } + + // onHelp() is called upon receipt of a WM_MENU message. This indicates that + // context-specific help should be displayed, for a dialog control, for example. + // It should return true if the command has been handled. + + virtual bool onHelp(int item) { return false; } + + // onOk() is called when the OK button is pressed. The hwnd argument is the + // dialog box's window handle. + + virtual bool onOk() { return true; } + + // Read the states of items + bool isItemChecked(int id); + int getItemInt(int id); + TCHAR* getItemString(int id); // Recipient owns string storage + + // Set the states of items + void setItemChecked(int id, bool state); + void setItemInt(int id, int value); + void setItemString(int id, const TCHAR* s); + + // enableItem is used to grey out an item, making it inaccessible, or to + // re-enable it. + void enableItem(int id, bool state); + + protected: + static BOOL CALLBACK staticDialogProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam); + virtual BOOL dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + HINSTANCE inst; + HWND handle; + bool alreadyShowing; + }; + + // PropertySheetPage + // Class used to define property pages within a PropertySheet. + // Each page is associated with a particular dialog resource, indicated by + // the "id" parameter supplied to the constructor. + + class PropSheetPage; + + class PropSheet { + public: + PropSheet(HINSTANCE inst, const TCHAR* title, std::list<PropSheetPage*> pages, HICON icon=0); + virtual ~PropSheet(); + + // Display the PropertySheet + bool showPropSheet(HWND owner, bool showApply = false, bool showCtxtHelp = false, bool capture=false); + + // Calls initDialog again for each page that has already had it called. + // Note: If a page hasn't been seen yet, it won't have been called. + // Note: This must only be called while the property sheet is visible. + void reInitPages(); + + // Calls onOk for each page that has had initDialog called, and returns + // false if any one of them returns false, or true otherwise. ALL the + // onOk() methods will be called, even if one of them fails. + // Note: If a page hasn't been seen yet, it won't have been called. + // Note: This must only be called while the property sheet is visible. + bool commitPages(); + + friend class PropSheetPage; + + protected: + HWND owner; + HICON icon; + std::list<PropSheetPage*> pages; + HINSTANCE inst; + TCharArray title; + HWND handle; + bool alreadyShowing; + }; + + class PropSheetPage : public Dialog { + public: + PropSheetPage(HINSTANCE inst, const TCHAR* id); + virtual ~PropSheetPage(); + + void setChanged(bool changed); + + friend class PropSheet; + + protected: + void setPropSheet(PropSheet* ps) {propSheet = ps;}; + static BOOL CALLBACK staticPageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + virtual BOOL dialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + PROPSHEETPAGE page; + PropSheet* propSheet; + }; + + }; + +}; + +#endif // __RFB_WIN32_DIALOG_H__ diff --git a/rfb_win32/IntervalTimer.h b/rfb_win32/IntervalTimer.h new file mode 100644 index 00000000..645d0ee9 --- /dev/null +++ b/rfb_win32/IntervalTimer.h @@ -0,0 +1,70 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- IntervalTimer.h +// +// Simple wrapper for standard Win32 timers + +#ifndef __RFB_WIN32_INTERVAL_TIMER_H__ +#define __RFB_WIN32_INTERVAL_TIMER_H__ + +namespace rfb { + + namespace win32 { + + struct IntervalTimer { + IntervalTimer(HWND hwnd_, int id_) + : active(false), hwnd(hwnd_), id(id_) { + } + IntervalTimer() : active(false), hwnd(0), id(0) { + } + ~IntervalTimer() { + stop(); + } + + void start(int interval_) { + if (!active || interval_ != interval) { + interval = interval_; + if (!SetTimer(hwnd, id, interval, 0)) + throw rdr::SystemException("SetTimer", GetLastError()); + active = true; + } + } + void stop() { + if (active) + KillTimer(hwnd, id); + active = false; + } + + void setHWND(HWND hwnd_) {hwnd=hwnd_;} + void setId(int id_) {id = id_;} + int getId() const {return id;} + bool isActive() const {return active;} + + private: + HWND hwnd; + int id; + bool active; + int interval; + }; + + }; // win32 + +}; // rfb + +#endif // __RFB_WIN32_INTERVAL_TIMER_H__ diff --git a/rfb_win32/LaunchProcess.cxx b/rfb_win32/LaunchProcess.cxx new file mode 100644 index 00000000..0a48d60c --- /dev/null +++ b/rfb_win32/LaunchProcess.cxx @@ -0,0 +1,92 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- LaunchProcess.cxx + +#include <rfb_win32/LaunchProcess.h> +#include <rfb_win32/Win32Util.h> + +#include <rfb/util.h> + +using namespace rfb; +using namespace win32; + + +LaunchProcess::LaunchProcess(const TCHAR* exeName_, const TCHAR* params_) +: exeName(tstrDup(exeName_)), params(tstrDup(params_)) { + memset(&procInfo, 0, sizeof(procInfo)); +} + +LaunchProcess::~LaunchProcess() { + await(); +} + + +void LaunchProcess::start(HANDLE userToken) { + if (procInfo.hProcess && (WaitForSingleObject(procInfo.hProcess, 0) != WAIT_OBJECT_0)) + return; + await(); + + // - Create storage for the process startup information + STARTUPINFO sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + sinfo.cb = sizeof(sinfo); + + // - Concoct a suitable command-line + TCharArray exePath; + if (!tstrContains(exeName.buf, _T('\\'))) { + ModuleFileName filename; + TCharArray path; splitPath(filename.buf, &path.buf, 0); + exePath.buf = new TCHAR[_tcslen(path.buf) + _tcslen(exeName.buf) + 2]; + _stprintf(exePath.buf, _T("%s\\%s"), path.buf, exeName.buf); + } else { + exePath.buf = tstrDup(exeName.buf); + } + + // - Start the VNC server + // Note: We specify the exe's precise path in the ApplicationName parameter, + // AND include the name as the first part of the CommandLine parameter, + // because CreateProcess doesn't make ApplicationName argv[0] in C programs. + TCharArray cmdLine(_tcslen(exeName.buf) + 3 + _tcslen(params.buf) + 1); + _stprintf(cmdLine.buf, _T("\"%s\" %s"), exeName.buf, params.buf); +#ifdef _DEBUG + DWORD flags = CREATE_NEW_CONSOLE; +#else + DWORD flags = CREATE_NO_WINDOW; +#endif + BOOL success; + if (userToken) + success = CreateProcessAsUser(userToken, exePath.buf, cmdLine.buf, 0, 0, FALSE, flags, 0, 0, &sinfo, &procInfo); + else + success = CreateProcess(exePath.buf, cmdLine.buf, 0, 0, FALSE, flags, 0, 0, &sinfo, &procInfo); + if (!success) + throw rdr::SystemException("unable to launch process", GetLastError()); + + // Wait for it to finish initialising + WaitForInputIdle(procInfo.hProcess, 15000); +} + +void LaunchProcess::await() { + if (!procInfo.hProcess) + return; + WaitForSingleObject(procInfo.hProcess, INFINITE); + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + memset(&procInfo, 0, sizeof(procInfo)); +} + diff --git a/rfb_win32/LaunchProcess.h b/rfb_win32/LaunchProcess.h new file mode 100644 index 00000000..6fd34e9c --- /dev/null +++ b/rfb_win32/LaunchProcess.h @@ -0,0 +1,59 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- LaunchProcess.h + +// Helper class to launch a names process from the same directory as +// the current process executable resides in. + +#ifndef __RFB_WIN32_LAUNCHPROCESS_H__ +#define __RFB_WIN32_LAUNCHPROCESS_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb_win32/TCharArray.h> + +namespace rfb { + + namespace win32 { + + class LaunchProcess { + public: + LaunchProcess(const TCHAR* exeName_, const TCHAR* params); + ~LaunchProcess(); + + // If userToken is 0 then starts as current user, otherwise + // starts as the specified user. userToken must be a primary token. + void start(HANDLE userToken); + + // Wait for the process to quit, and close the handles to it. + void await(); + + PROCESS_INFORMATION procInfo; + protected: + TCharArray exeName; + TCharArray params; + }; + + + }; + +}; + +#endif diff --git a/rfb_win32/MsgWindow.cxx b/rfb_win32/MsgWindow.cxx new file mode 100644 index 00000000..519d6ab9 --- /dev/null +++ b/rfb_win32/MsgWindow.cxx @@ -0,0 +1,116 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- MsgWindow.cxx + +#include <rfb_win32/MsgWindow.h> +#include <rfb_win32/WMShatter.h> +#include <rfb/LogWriter.h> +#include <rdr/Exception.h> +#include <malloc.h> +#include <tchar.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("MsgWindow"); + +// +// -=- MsgWindowClass +// + +class MsgWindowClass { +public: + MsgWindowClass(); + ~MsgWindowClass(); + ATOM classAtom; + HINSTANCE instance; +}; + +LRESULT CALLBACK MsgWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) { + LRESULT result; + + if (msg == WM_CREATE) + SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams); + else if (msg == WM_DESTROY) + SetWindowLong(wnd, GWL_USERDATA, 0); + MsgWindow* _this = (MsgWindow*) GetWindowLong(wnd, GWL_USERDATA); + if (!_this) { + vlog.info("null _this in %x, message %x", wnd, msg); + return SafeDefWindowProc(wnd, msg, wParam, lParam); + } + + try { + result = _this->processMessage(msg, wParam, lParam); + } catch (rdr::Exception& e) { + vlog.error("untrapped: %s", e.str()); + } + + return result; +}; + +MsgWindowClass::MsgWindowClass() : classAtom(0) { + WNDCLASS wndClass; + wndClass.style = 0; + wndClass.lpfnWndProc = MsgWindowProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = instance = GetModuleHandle(0); + wndClass.hIcon = 0; + wndClass.hCursor = 0; + wndClass.hbrBackground = 0; + wndClass.lpszMenuName = 0; + wndClass.lpszClassName = _T("rfb::win32::MsgWindowClass"); + classAtom = RegisterClass(&wndClass); + if (!classAtom) { + throw rdr::SystemException("unable to register MsgWindow window class", GetLastError()); + } +} + +MsgWindowClass::~MsgWindowClass() { + if (classAtom) { + UnregisterClass((const TCHAR*)classAtom, instance); + } +} + +MsgWindowClass baseClass; + +// +// -=- MsgWindow +// + +MsgWindow::MsgWindow(const TCHAR* name_) : name(tstrDup(name_)), handle(0) { + vlog.debug("creating window \"%s\"", (const char*)CStr(name.buf)); + handle = CreateWindow((const TCHAR*)baseClass.classAtom, name.buf, WS_OVERLAPPED, + 0, 0, 10, 10, 0, 0, baseClass.instance, this); + if (!handle) { + throw rdr::SystemException("unable to create WMNotifier window instance", GetLastError()); + } + vlog.debug("created window \"%s\" (%x)", (const char*)CStr(name.buf), handle); +} + +MsgWindow::~MsgWindow() { + if (handle) + DestroyWindow(handle); + vlog.debug("destroyed window \"%s\" (%x)", (const char*)CStr(name.buf), handle); +} + +LRESULT +MsgWindow::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + return SafeDefWindowProc(getHandle(), msg, wParam, lParam); +}
\ No newline at end of file diff --git a/rfb_win32/MsgWindow.h b/rfb_win32/MsgWindow.h new file mode 100644 index 00000000..94baca38 --- /dev/null +++ b/rfb_win32/MsgWindow.h @@ -0,0 +1,55 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- MsgWindow.h + +// Base-class for any hidden message-handling windows used in the rfb::win32 +// implementation. + +#ifndef __RFB_WIN32_MSG_WINDOW_H__ +#define __RFB_WIN32_MSG_WINDOW_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb_win32/TCharArray.h> + +namespace rfb { + + namespace win32 { + + class MsgWindow { + public: + MsgWindow(const TCHAR* _name); + virtual ~MsgWindow(); + + const TCHAR* getName() {return name.buf;} + HWND getHandle() const {return handle;} + + virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam); + + protected: + TCharArray name; + HWND handle; + }; + + }; + +}; + +#endif // __RFB_WIN32_MSG_WINDOW_H__ diff --git a/rfb_win32/OSVersion.cxx b/rfb_win32/OSVersion.cxx new file mode 100644 index 00000000..1976098f --- /dev/null +++ b/rfb_win32/OSVersion.cxx @@ -0,0 +1,47 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- OSVersion.cxx + +#include <rfb_win32/OSVersion.h> +#include <rdr/Exception.h> +#include <tchar.h> + +using namespace rfb; +using namespace win32; + + +OSVersionInfo::OSVersionInfo() { + // Get OS Version Info + ZeroMemory(static_cast<OSVERSIONINFO*>(this), sizeof(this)); + dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (!GetVersionEx(this)) + throw rdr::SystemException("unable to get system version info", GetLastError()); + + // Set the special extra flags + isPlatformNT = dwPlatformId == VER_PLATFORM_WIN32_NT; + isPlatformWindows = dwPlatformId == VER_PLATFORM_WIN32_WINDOWS; + + cannotSwitchDesktop = isPlatformNT && (dwMajorVersion==4) && + ((_tcscmp(szCSDVersion, _T("")) == 0) || + (_tcscmp(szCSDVersion, _T("Service Pack 1")) == 0) || + (_tcscmp(szCSDVersion, _T("Service Pack 2")) == 0)); + +} + +OSVersionInfo rfb::win32::osVersion; diff --git a/rfb_win32/OSVersion.h b/rfb_win32/OSVersion.h new file mode 100644 index 00000000..1d529431 --- /dev/null +++ b/rfb_win32/OSVersion.h @@ -0,0 +1,54 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- OSVersion.h + +// Operating system version info. +// GetVersionInfo is called once at process initialisation, and any +// extra flags (such as isWinNT) are calculated and saved at that +// point. It is assumed that the OS Version seldom changes during a +// program's execution... + +#ifndef __RFB_WIN32_OS_VERSION_H__ +#define __RFB_WIN32_OS_VERSION_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +namespace rfb { + + namespace win32 { + + extern struct OSVersionInfo : OSVERSIONINFO { + OSVersionInfo(); + + // Is the OS one of the NT family (NT 3.51, NT4.0, 2K, XP, etc.)? + bool isPlatformNT; + // Is one of the Windows family? + bool isPlatformWindows; + + // Is this OS one of those that blue-screens when grabbing another desktop (NT4 pre SP3)? + bool cannotSwitchDesktop; + + } osVersion; + + }; + +}; + +#endif // __RFB_WIN32_OS_VERSION_H__ diff --git a/rfb_win32/RegConfig.cxx b/rfb_win32/RegConfig.cxx new file mode 100644 index 00000000..fcb309b5 --- /dev/null +++ b/rfb_win32/RegConfig.cxx @@ -0,0 +1,151 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- RegConfig.cxx + +#include <malloc.h> + +#include <rfb_win32/RegConfig.h> +#include <rfb/LogWriter.h> +#include <rfb/util.h> +#include <rdr/HexOutStream.h> + +using namespace rfb; +using namespace rfb::win32; + + +static LogWriter vlog("RegConfig"); + + +class rfb::win32::RegReaderThread : public Thread { +public: + RegReaderThread(RegistryReader& reader, const HKEY key); + ~RegReaderThread(); + virtual void run(); + virtual Thread* join(); +protected: + RegistryReader& reader; + RegKey key; + HANDLE event; +}; + +RegReaderThread::RegReaderThread(RegistryReader& reader_, const HKEY key_) : Thread("RegConfig"), reader(reader_), key(key_) { +} + +RegReaderThread::~RegReaderThread() { +} + +void +RegReaderThread::run() { + vlog.debug("RegReaderThread started"); + while (key) { + // - Wait for changes + vlog.debug("waiting for changes"); + key.awaitChange(true, REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET); + + // - Load settings + RegistryReader::loadRegistryConfig(key); + + // - Notify specified thread of changes + if (reader.notifyThread) + PostThreadMessage(reader.notifyThread->getThreadId(), + reader.notifyMsg.message, + reader.notifyMsg.wParam, + reader.notifyMsg.lParam); + else if (reader.notifyWindow) + PostMessage(reader.notifyWindow, + reader.notifyMsg.message, + reader.notifyMsg.wParam, + reader.notifyMsg.lParam); + } +} + +Thread* +RegReaderThread::join() { + RegKey old_key = key; + key.close(); + if ((HKEY)old_key) { + // *** Closing the key doesn't always seem to work + // Writing to it always will, instead... + vlog.debug("closing key"); + old_key.setString(_T("dummy"), _T("")); + } + return Thread::join(); +} + + +RegistryReader::RegistryReader() : thread(0), notifyThread(0) { + memset(¬ifyMsg, 0, sizeof(notifyMsg)); +} + +RegistryReader::~RegistryReader() { + if (thread) delete thread->join(); +} + +bool RegistryReader::setKey(const HKEY rootkey, const TCHAR* keyname) { + if (thread) delete thread->join(); + thread = 0; + + RegKey key; + try { + key.createKey(rootkey, keyname); + loadRegistryConfig(key); + } catch (rdr::Exception& e) { + vlog.debug(e.str()); + return false; + } + thread = new RegReaderThread(*this, key); + if (thread) thread->start(); + return true; +} + +void +RegistryReader::loadRegistryConfig(RegKey& key) { + DWORD i = 0; + try { + while (1) { + TCharArray name = tstrDup(key.getValueName(i++)); + if (!name.buf) break; + TCharArray value = key.getRepresentation(name.buf); + if (!value.buf || !Configuration::setParam(CStr(name.buf), CStr(value.buf))) + vlog.info("unable to process %s", CStr(name.buf)); + } + } catch (rdr::SystemException& e) { + if (e.err != 6) + vlog.error(e.str()); + } +} + +bool RegistryReader::setNotifyThread(Thread* thread, UINT winMsg, WPARAM wParam, LPARAM lParam) { + notifyMsg.message = winMsg; + notifyMsg.wParam = wParam; + notifyMsg.lParam = lParam; + notifyThread = thread; + notifyWindow = 0; + return true; +} + +bool RegistryReader::setNotifyWindow(HWND window, UINT winMsg, WPARAM wParam, LPARAM lParam) { + notifyMsg.message = winMsg; + notifyMsg.wParam = wParam; + notifyMsg.lParam = lParam; + notifyWindow = window; + notifyThread = 0; + return true; +} + diff --git a/rfb_win32/RegConfig.h b/rfb_win32/RegConfig.h new file mode 100644 index 00000000..3fced85e --- /dev/null +++ b/rfb_win32/RegConfig.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- RegConfig.h + +// Class which monitors the registry and reads in the registry settings +// whenever they change, or are added or removed. + +#ifndef __RFB_WIN32_REG_CONFIG_H__ +#define __RFB_WIN32_REG_CONFIG_H__ + +#include <rfb/Threading.h> +#include <rfb/Configuration.h> + +#include <rfb_win32/Registry.h> + +namespace rfb { + + namespace win32 { + + class RegistryReader { + public: + RegistryReader(); + ~RegistryReader(); + bool setKey(const HKEY rootkey, const TCHAR* keyname); + bool setNotifyThread(Thread* thread, UINT winMsg, WPARAM wParam=0, LPARAM lParam=0); + bool setNotifyWindow(HWND window, UINT winMsg, WPARAM wParam=0, LPARAM lParam=0); + static void loadRegistryConfig(RegKey& key); + protected: + friend class RegReaderThread; + Thread* thread; + Thread* notifyThread; + HWND notifyWindow; + MSG notifyMsg; + }; + + }; + +}; + +#endif // __RFB_WIN32_REG_CONFIG_H__ diff --git a/rfb_win32/Registry.cxx b/rfb_win32/Registry.cxx new file mode 100644 index 00000000..de9238f7 --- /dev/null +++ b/rfb_win32/Registry.cxx @@ -0,0 +1,272 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Registry.cxx + +#include <rfb/LogWriter.h> +#include <rfb_win32/Registry.h> +#include <rdr/MemOutStream.h> +#include <rdr/HexOutstream.h> +#include <rdr/HexInStream.h> +#include <rfb_win32/Security.h> + +#include <stdlib.h> + +// These flags are required to control access control inheritance, +// but are not defined by VC6's headers. These definitions comes +// from the Microsoft Platform SDK. +#ifndef PROTECTED_DACL_SECURITY_INFORMATION +#define PROTECTED_DACL_SECURITY_INFORMATION (0x80000000L) +#endif +#ifndef UNPROTECTED_DACL_SECURITY_INFORMATION +#define UNPROTECTED_DACL_SECURITY_INFORMATION (0x20000000L) +#endif + + +using namespace rfb; +using namespace rfb::win32; + + +static LogWriter vlog("Registry"); + + +RegKey::RegKey() : key(0), freeKey(false), valueNameBufLen(0) {} + +RegKey::RegKey(const HKEY k) : key(0), freeKey(false), valueNameBufLen(0) { + LONG result = RegOpenKeyEx(k, 0, 0, KEY_ALL_ACCESS, &key); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegOpenKeyEx(HKEY)", result); + vlog.debug("duplicated %x to %x", k, key); + freeKey = true; +} + +RegKey::RegKey(const RegKey& k) : key(0), freeKey(false), valueNameBufLen(0) { + LONG result = RegOpenKeyEx(k.key, 0, 0, KEY_ALL_ACCESS, &key); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegOpenKeyEx(RegKey&)", result); + vlog.debug("duplicated %x to %x", k.key, key); + freeKey = true; +} + +RegKey::~RegKey() { + close(); +} + + +void RegKey::setHKEY(HKEY k, bool fK) { + close(); + freeKey = fK; + key = k; +} + + +bool RegKey::createKey(const RegKey& root, const TCHAR* name) { + close(); + LONG result = RegCreateKey(root.key, name, &key); + if (result != ERROR_SUCCESS) { + vlog.error("RegCreateKey(%x, %s): %x", root.key, name, result); + throw rdr::SystemException("RegCreateKeyEx", result); + } + freeKey = true; + return true; +} + +void RegKey::openKey(const RegKey& root, const TCHAR* name, bool readOnly) { + close(); + LONG result = RegOpenKeyEx(root.key, name, 0, readOnly ? KEY_READ : KEY_ALL_ACCESS, &key); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegOpenKeyEx (open)", result); + freeKey = true; +} + +void RegKey::setDACL(const PACL acl, bool inherit) { + DWORD result; + typedef DWORD (WINAPI *_SetSecurityInfo_proto) (HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION, PSID, PSID, PACL, PACL); + DynamicFn<_SetSecurityInfo_proto> _SetSecurityInfo(_T("advapi32.dll"), "SetSecurityInfo"); + if (!_SetSecurityInfo.isValid()) + throw rdr::SystemException("RegKey::setDACL failed", ERROR_CALL_NOT_IMPLEMENTED); + if ((result = (*_SetSecurityInfo)(key, SE_REGISTRY_KEY, + DACL_SECURITY_INFORMATION | + (inherit ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), + 0, 0, acl, 0)) != ERROR_SUCCESS) + throw rdr::SystemException("RegKey::setDACL failed", result); +} + +void RegKey::close() { + if (freeKey) { + RegCloseKey(key); + key = 0; + } +} + +void RegKey::deleteKey(const TCHAR* name) const { + LONG result = RegDeleteKey(key, name); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegDeleteKey", result); +} + +void RegKey::deleteValue(const TCHAR* name) const { + LONG result = RegDeleteValue(key, name); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegDeleteValue", result); +} + +void RegKey::awaitChange(bool watchSubTree, DWORD filter) const { + LONG result = RegNotifyChangeKeyValue(key, watchSubTree, filter, 0, FALSE); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegNotifyChangeKeyValue", result); +} + + +RegKey::operator HKEY() const {return key;} + + +void RegKey::setExpandString(const TCHAR* valname, const TCHAR* value) const { + LONG result = RegSetValueEx(key, valname, 0, REG_EXPAND_SZ, (const BYTE*)value, (_tcslen(value)+1)*sizeof(TCHAR)); + if (result != ERROR_SUCCESS) throw rdr::SystemException("setExpandString", result); +} + +void RegKey::setString(const TCHAR* valname, const TCHAR* value) const { + LONG result = RegSetValueEx(key, valname, 0, REG_SZ, (const BYTE*)value, (_tcslen(value)+1)*sizeof(TCHAR)); + if (result != ERROR_SUCCESS) throw rdr::SystemException("setString", result); +} + +void RegKey::setBinary(const TCHAR* valname, const void* value, int length) const { + LONG result = RegSetValueEx(key, valname, 0, REG_BINARY, (const BYTE*)value, length); + if (result != ERROR_SUCCESS) throw rdr::SystemException("setBinary", result); +} + +void RegKey::setInt(const TCHAR* valname, int value) const { + LONG result = RegSetValueEx(key, valname, 0, REG_DWORD, (const BYTE*)&value, sizeof(value)); + if (result != ERROR_SUCCESS) throw rdr::SystemException("setInt", result); +} + +void RegKey::setBool(const TCHAR* valname, bool value) const { + setInt(valname, value ? 1 : 0); +} + +TCHAR* RegKey::getString(const TCHAR* valname) const {return getRepresentation(valname);} +TCHAR* RegKey::getString(const TCHAR* valname, const TCHAR* def) const { + try { + return getString(valname); + } catch(rdr::Exception) { + return tstrDup(def); + } +} + +void RegKey::getBinary(const TCHAR* valname, void** data, int* length) const { + TCharArray hex = getRepresentation(valname); + if (!rdr::HexInStream::hexStrToBin(CStr(hex.buf), (char**)data, length)) + throw rdr::Exception("getBinary failed"); +} +void RegKey::getBinary(const TCHAR* valname, void** data, int* length, void* def, int deflen) const { + try { + getBinary(valname, data, length); + } catch(rdr::Exception) { + if (deflen) { + *data = new char[deflen]; + memcpy(*data, def, deflen); + } else + *data = 0; + *length = deflen; + } +} + +int RegKey::getInt(const TCHAR* valname) const { + TCharArray tmp = getRepresentation(valname); + return _ttoi(tmp.buf); +} +int RegKey::getInt(const TCHAR* valname, int def) const { + try { + return getInt(valname); + } catch(rdr::Exception) { + return def; + } +} + +bool RegKey::getBool(const TCHAR* valname) const { + return getInt(valname) > 0; +} +bool RegKey::getBool(const TCHAR* valname, bool def) const { + return getInt(valname, def ? 1 : 0) > 0; +} + +TCHAR* RegKey::getRepresentation(const TCHAR* valname) const { + DWORD type, length; + LONG result = RegQueryValueEx(key, valname, 0, &type, 0, &length); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("get registry value length", result); + CharArray data(length); + result = RegQueryValueEx(key, valname, 0, &type, (BYTE*)data.buf, &length); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("get registry value", result); + + switch (type) { + case REG_BINARY: + { + TCharArray hex = rdr::HexOutStream::binToHexStr(data.buf, length); + return hex.takeBuf(); + } + case REG_SZ: + if (length) { + // We must terminate the string, just to be sure. Stupid Win32... + int len = length/sizeof(TCHAR); + TCharArray str(len+1); + memcpy(str.buf, data.buf, length); + str.buf[len] = 0; + return str.takeBuf(); + } else { + return tstrDup(_T("")); + } + case REG_DWORD: + { + TCharArray tmp(16); + _stprintf(tmp.buf, _T("%u"), *((DWORD*)data.buf)); + return tmp.takeBuf(); + } + default: + throw rdr::Exception("unsupported registry type"); + } +} + +bool RegKey::isValue(const TCHAR* valname) const { + try { + TCharArray tmp = getRepresentation(valname); + return true; + } catch(rdr::Exception) { + return false; + } +} + +const TCHAR* RegKey::getValueName(int i) { + DWORD maxValueNameLen; + LONG result = RegQueryInfoKey(key, 0, 0, 0, 0, 0, 0, 0, &maxValueNameLen, 0, 0, 0); + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegQueryInfoKey", result); + if (valueNameBufLen < maxValueNameLen + 1) { + valueNameBufLen = maxValueNameLen + 1; + delete [] valueName.buf; + valueName.buf = new TCHAR[valueNameBufLen]; + } + DWORD length = valueNameBufLen; + result = RegEnumValue(key, i, valueName.buf, &length, NULL, 0, 0, 0); + if (result == ERROR_NO_MORE_ITEMS) return 0; + if (result != ERROR_SUCCESS) + throw rdr::SystemException("RegEnumValue", result); + return valueName.buf; +} diff --git a/rfb_win32/Registry.h b/rfb_win32/Registry.h new file mode 100644 index 00000000..1998c497 --- /dev/null +++ b/rfb_win32/Registry.h @@ -0,0 +1,111 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +// -=- Registry.h + +// C++ wrappers around the Win32 Registry APIs + +#ifndef __RFB_WIN32_REGISTRY_H__ +#define __RFB_WIN32_REGISTRY_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb_win32/Security.h> +#include <rfb/util.h> + +namespace rfb { + + namespace win32 { + + class RegKey { + public: + // No key open + RegKey(); + + // Duplicate the specified existing key + RegKey(const HKEY k); + RegKey(const RegKey& k); + + // Calls close() internally + ~RegKey(); + + void setHKEY(HKEY key, bool freeKey); + protected: + HKEY operator=(const RegKey& k); + HKEY operator=(HKEY k); + public: + + // Returns true if key was created, false if already existed + bool createKey(const RegKey& root, const TCHAR* name); + + // Opens key if it exists, or raises an exception if not + void openKey(const RegKey& root, const TCHAR* name, bool readOnly=false); + + // Set the (discretionary) access control list for the key + void setDACL(const PACL acl, bool inheritFromParent=true); + + // Closes current key, if required + void close(); + + // Delete a subkey/value + void deleteKey(const TCHAR* name) const; + void deleteValue(const TCHAR* name) const; + + + void awaitChange(bool watchSubTree, DWORD filter) const; + + void setExpandString(const TCHAR* valname, const TCHAR* s) const; + void setString(const TCHAR* valname, const TCHAR* s) const; + void setBinary(const TCHAR* valname, const void* data, int length) const; + void setInt(const TCHAR* valname, int i) const; + void setBool(const TCHAR* valname, bool b) const; + + TCHAR* getString(const TCHAR* valname) const; + TCHAR* getString(const TCHAR* valname, const TCHAR* def) const; + + void getBinary(const TCHAR* valname, void** data, int* length) const; + void getBinary(const TCHAR* valname, void** data, int* length, void* def, int deflength) const; + + int getInt(const TCHAR* valname) const; + int getInt(const TCHAR* valname, int def) const; + + bool getBool(const TCHAR* valname) const; + bool getBool(const TCHAR* valname, bool def) const; + + TCHAR* getRepresentation(const TCHAR* valname) const; + + bool isValue(const TCHAR* valname) const; + + // Get the name of value number "i" + // If there are fewer than "i" values then return 0 + // NAME IS OWNED BY RegKey OBJECT! + const TCHAR* getValueName(int i); + + operator HKEY() const; + protected: + HKEY key; + bool freeKey; + TCharArray valueName; + DWORD valueNameBufLen; + }; + + }; + +}; + +#endif // __RFB_WIN32_REG_CONFIG_H__ diff --git a/rfb_win32/SDisplay.cxx b/rfb_win32/SDisplay.cxx new file mode 100644 index 00000000..6fa3ff0e --- /dev/null +++ b/rfb_win32/SDisplay.cxx @@ -0,0 +1,612 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- SDisplay.cxx +// +// The SDisplay class encapsulates a particular system display. + +#include <assert.h> + +#include <rfb_win32/SDisplay.h> +#include <rfb_win32/Service.h> +#include <rfb_win32/WMShatter.h> +#include <rfb_win32/osVersion.h> +#include <rfb_win32/Win32Util.h> +#include <rfb_win32/IntervalTimer.h> +#include <rfb_win32/CleanDesktop.h> + +#include <rfb/util.h> +#include <rfb/LogWriter.h> +#include <rfb/Exception.h> + +#include <rfb/Configuration.h> + +using namespace rdr; +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("SDisplay"); + +// - SDisplay-specific configuration options + +BoolParameter rfb::win32::SDisplay::use_hooks("UseHooks", + "Set hooks in the operating system to capture display updates more efficiently", true); +BoolParameter rfb::win32::SDisplay::disableLocalInputs("DisableLocalInputs", + "Disable local keyboard and pointer input while the server is in use", false); +StringParameter rfb::win32::SDisplay::disconnectAction("DisconnectAction", + "Action to perform when all clients have disconnected. (None, Lock, Logoff)", "None"); + +BoolParameter rfb::win32::SDisplay::removeWallpaper("RemoveWallpaper", + "Remove the desktop wallpaper when the server in in use.", false); +BoolParameter rfb::win32::SDisplay::removePattern("RemovePattern", + "Remove the desktop background pattern when the server in in use.", false); +BoolParameter rfb::win32::SDisplay::disableEffects("DisableEffects", + "Disable desktop user interface effects when the server is in use.", false); + + +// - WM_TIMER ID values + +#define TIMER_CURSOR 1 +#define TIMER_UPDATE 2 +#define TIMER_UPDATE_AND_POLL 3 + + +// -=- Polling settings + +const int POLLING_SEGMENTS = 16; + +const int FG_POLLING_FPS = 20; +const int FG_POLLING_FS_INTERVAL = 1000 / FG_POLLING_FPS; +const int FG_POLLING_INTERVAL = FG_POLLING_FS_INTERVAL / POLLING_SEGMENTS; + +const int BG_POLLING_FS_INTERVAL = 5000; +const int BG_POLLING_INTERVAL = BG_POLLING_FS_INTERVAL / POLLING_SEGMENTS; + + +////////////////////////////////////////////////////////////////////////////// +// +// SDisplayCore +// + +// The SDisplay Core object is created by SDisplay's start() method +// and deleted by its stop() method. +// The Core must be created in the current input desktop in order +// to operate - SDisplay is responsible for ensuring that. +// The structures contained in the Core are manipulated directly +// by the SDisplay, which is also responsible for detecting when +// a desktop-switch is required. + +class rfb::win32::SDisplayCore : public MsgWindow { +public: + SDisplayCore(SDisplay* display); + ~SDisplayCore(); + + void setPixelBuffer(DeviceFrameBuffer* pb_); + + bool isRestartRequired(); + + virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam); + + // -=- Timers + IntervalTimer pollTimer; + IntervalTimer cursorTimer; + + // -=- Input handling + rfb::win32::SPointer ptr; + rfb::win32::SKeyboard kbd; + rfb::win32::Clipboard clipboard; + + // -=- Hook handling objects used outside thread run() method + WMCopyRect wm_copyrect; + WMPoller wm_poller; + WMCursor cursor; + WMMonitor wm_monitor; + WMHooks wm_hooks; + WMBlockInput wm_input; + + // -=- Tidying the desktop + CleanDesktop cleanDesktop; + bool isWallpaperRemoved; + bool isPatternRemoved; + bool areEffectsDisabled; + + // -=- Full screen polling + int poll_next_y; + int poll_y_increment; + + // Are we using hooks? + bool use_hooks; + bool using_hooks; + + // State of the display object + SDisplay* display; +}; + +SDisplayCore::SDisplayCore(SDisplay* display_) +: MsgWindow(_T("SDisplayCore")), display(display_), + using_hooks(0), use_hooks(rfb::win32::SDisplay::use_hooks), + isWallpaperRemoved(rfb::win32::SDisplay::removeWallpaper), + isPatternRemoved(rfb::win32::SDisplay::removePattern), + areEffectsDisabled(rfb::win32::SDisplay::disableEffects), + pollTimer(getHandle(), TIMER_UPDATE_AND_POLL), + cursorTimer(getHandle(), TIMER_CURSOR) { + setPixelBuffer(display->pb); +} + +SDisplayCore::~SDisplayCore() { +} + +void SDisplayCore::setPixelBuffer(DeviceFrameBuffer* pb) { + poll_y_increment = (display->pb->height()+POLLING_SEGMENTS-1)/POLLING_SEGMENTS; + poll_next_y = display->screenRect.tl.y; + wm_hooks.setClipRect(display->screenRect); + wm_copyrect.setClipRect(display->screenRect); + wm_poller.setClipRect(display->screenRect); +} + + +bool SDisplayCore::isRestartRequired() { + // - We must restart the SDesktop if: + // 1. We are no longer in the input desktop. + // 2. The use_hooks setting has changed. + + // - Check that we are in the input desktop + if (rfb::win32::desktopChangeRequired()) + return true; + + // - Check that the hooks setting hasn't changed + // NB: We can't just check using_hooks because that can be false + // because they failed, even though use_hooks is true! + if (use_hooks != rfb::win32::SDisplay::use_hooks) + return true; + + // - Check that the desktop optimisation settings haven't changed + // This isn't very efficient, but it shouldn't change very often! + if ((isWallpaperRemoved != rfb::win32::SDisplay::removeWallpaper) || + (isPatternRemoved != rfb::win32::SDisplay::removePattern) || + (areEffectsDisabled != rfb::win32::SDisplay::disableEffects)) + return true; + + return false; +} + +LRESULT SDisplayCore::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + + case WM_TIMER: + + if (display->server && display->server->clientsReadyForUpdate()) { + + // - Check that the SDesktop doesn't need restarting + if (isRestartRequired()) { + display->restart(); + return 0; + } + + // - Action depends on the timer message type + switch (wParam) { + + // POLL THE SCREEN + case TIMER_UPDATE_AND_POLL: + // Handle window dragging, polling of consoles, etc. + while (wm_poller.processEvent()) {} + + // Poll the next strip of the screen (in Screen coordinates) + { + Rect pollrect = display->screenRect; + if (poll_next_y >= pollrect.br.y) { + // Yes. Reset the counter and return + poll_next_y = pollrect.tl.y; + } else { + // No. Poll the next section + pollrect.tl.y = poll_next_y; + poll_next_y += poll_y_increment; + pollrect.br.y = min(poll_next_y, pollrect.br.y); + display->add_changed(pollrect); + } + } + break; + + case TIMER_CURSOR: + display->triggerUpdate(); + break; + + }; + + } + return 0; + + }; + + return MsgWindow::processMessage(msg, wParam, lParam); +} + +////////////////////////////////////////////////////////////////////////////// +// +// SDisplay +// + +// -=- Constructor/Destructor + +SDisplay::SDisplay(const TCHAR* devName) + : server(0), change_tracker(true), pb(0), + deviceName(tstrDup(devName)), device(0), releaseDevice(false), + core(0), statusLocation(0) +{ + updateEvent.h = CreateEvent(0, TRUE, FALSE, 0); +} + +SDisplay::~SDisplay() +{ + // XXX when the VNCServer has been deleted with clients active, stop() + // doesn't get called - this ought to be fixed in VNCServerST. In any event, + // we should never call any methods on VNCServer once we're being deleted. + // This is because it is supposed to be guaranteed that the SDesktop exists + // throughout the lifetime of the VNCServer. So if we're being deleted, then + // the VNCServer ought not to exist and therefore we shouldn't invoke any + // methods on it. Setting server to zero here ensures that stop() doesn't + // call setPixelBuffer(0) on the server. + server = 0; + if (core) stop(); +} + + +// -=- SDesktop interface + +void SDisplay::start(VNCServer* vs) +{ + vlog.debug("starting"); + server = vs; + + // Switch to the current input desktop + // *** + if (rfb::win32::desktopChangeRequired()) { + if (!rfb::win32::changeDesktop()) + throw rdr::Exception("unable to switch into input desktop"); + } + + // Clear the change tracker + change_tracker.clear(); + + // Create the framebuffer object + recreatePixelBuffer(); + + // Create the SDisplayCore + core = new SDisplayCore(this); + assert(core); + + // Start display monitor and clipboard handler + core->wm_monitor.setNotifier(this); + core->clipboard.setNotifier(this); + + // Apply desktop optimisations + if (removePattern) + core->cleanDesktop.disablePattern(); + if (removeWallpaper) + core->cleanDesktop.disableWallpaper(); + if (disableEffects) + core->cleanDesktop.disableEffects(); + + // Start hooks + core->wm_hooks.setClipRect(screenRect); + if (core->use_hooks) { + // core->wm_hooks.setDiagnosticRange(0, 0x400-1); + core->using_hooks = core->wm_hooks.setUpdateTracker(this); + if (!core->using_hooks) + vlog.debug("hook subsystem failed to initialise"); + } + + // Set up timers + core->pollTimer.start(core->using_hooks ? BG_POLLING_INTERVAL : FG_POLLING_INTERVAL); + core->cursorTimer.start(10); + + // Register an interest in faked copyrect events + core->wm_copyrect.setUpdateTracker(&change_tracker); + core->wm_copyrect.setClipRect(screenRect); + + // Polling of particular windows on the desktop + core->wm_poller.setUpdateTracker(&change_tracker); + core->wm_poller.setClipRect(screenRect); + + vlog.debug("started"); + + if (statusLocation) *statusLocation = true; +} + +void SDisplay::stop() +{ + vlog.debug("stopping"); + if (core) { + // If SDisplay was actually active then perform the disconnect action + CharArray action = disconnectAction.getData(); + if (stricmp(action.buf, "Logoff") == 0) { + ExitWindowsEx(EWX_LOGOFF, 0); + } else if (stricmp(action.buf, "Lock") == 0) { + typedef BOOL (WINAPI *_LockWorkStation_proto)(); + DynamicFn<_LockWorkStation_proto> _LockWorkStation(_T("user32.dll"), "LockWorkStation"); + if (_LockWorkStation.isValid()) + (*_LockWorkStation)(); + else + ExitWindowsEx(EWX_LOGOFF, 0); + } + } + delete core; + core = 0; + delete pb; + pb = 0; + if (device) { + if (releaseDevice) + ReleaseDC(0, device); + else + DeleteDC(device); + } + device = 0; + if (server) + server->setPixelBuffer(0); + + server = 0; + vlog.debug("stopped"); + + if (statusLocation) *statusLocation = false; +} + +void SDisplay::restart() { + vlog.debug("restarting"); + // Close down the hooks + delete core; + core = 0; + try { + // Re-start the hooks if possible + start(server); + vlog.debug("restarted"); + } catch (rdr::Exception& e) { + // If start() fails then we MUST disconnect all clients, + // to cause the server to stop using the desktop. + // Otherwise, the SDesktop is in an inconsistent state + // and the server will crash + server->closeClients(e.str()); + } +} + + +void SDisplay::pointerEvent(const Point& pos, rdr::U8 buttonmask) { + if (pb->getRect().contains(pos)) { + Point screenPos = pos.translate(screenRect.tl); + core->ptr.pointerEvent(screenPos, buttonmask); + } +} + +void SDisplay::keyEvent(rdr::U32 key, bool down) { + core->kbd.keyEvent(key, down); +} + +void SDisplay::clientCutText(const char* text, int len) { + CharArray clip_sz(len+1); + memcpy(clip_sz.buf, text, len); + clip_sz.buf[len] = 0; + core->clipboard.setClipText(clip_sz.buf); +} + + +void SDisplay::framebufferUpdateRequest() +{ + triggerUpdate(); +} + +Point SDisplay::getFbSize() { + bool startAndStop = !core; + // If not started, do minimal initialisation to get desktop size. + if (startAndStop) recreatePixelBuffer(); + Point result = Point(pb->width(), pb->height()); + // Destroy the initialised structures. + if (startAndStop) stop(); + return result; +} + + +void +SDisplay::add_changed(const Region& rgn) { + change_tracker.add_changed(rgn); + triggerUpdate(); +} + +void +SDisplay::add_copied(const Region& dest, const Point& delta) { + change_tracker.add_copied(dest, delta); + triggerUpdate(); +} + + +void +SDisplay::notifyClipboardChanged(const char* text, int len) { + vlog.debug("clipboard text changed"); + if (server) + server->serverCutText(text, len); +} + + +void +SDisplay::notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt) { + switch (evt) { + case WMMonitor::Notifier::DisplaySizeChanged: + vlog.debug("desktop size changed"); + recreatePixelBuffer(); + break; + case WMMonitor::Notifier::DisplayPixelFormatChanged: + vlog.debug("desktop format changed"); + recreatePixelBuffer(); + break; + case WMMonitor::Notifier::DisplayColourMapChanged: + vlog.debug("desktop colourmap changed"); + pb->updateColourMap(); + if (server) + server->setColourMapEntries(); + break; + default: + vlog.error("unknown display event received"); + } +} + +bool +SDisplay::processEvent(HANDLE event) { + if (event == updateEvent) { + vlog.info("processEvent"); + ResetEvent(updateEvent); + + // - If the SDisplay isn't even started then quit now + if (!core) { + vlog.error("not start()ed"); + return true; + } + + // - Ensure that the disableLocalInputs flag is respected + core->wm_input.blockInputs(SDisplay::disableLocalInputs); + + // - Only process updates if the server is ready + if (server && server->clientsReadyForUpdate()) { + bool try_update = false; + + // - Check that the SDesktop doesn't need restarting + if (core->isRestartRequired()) { + restart(); + return true; + } + + // *** window dragging can be improved - more frequent, more cunning about updates + while (core->wm_copyrect.processEvent()) {} + + // Ensure the cursor is up to date + WMCursor::Info info = core->cursor.getCursorInfo(); + if (old_cursor != info) { + // Update the cursor shape if the visibility has changed + bool set_cursor = info.visible != old_cursor.visible; + // OR if the cursor is visible and the shape has changed. + set_cursor |= info.visible && (old_cursor.cursor != info.cursor); + + // Update the cursor shape + if (set_cursor) + pb->setCursor(info.visible ? info.cursor : 0, server); + + // Update the cursor position + // NB: First translate from Screen coordinates to Desktop + Point desktopPos = info.position.translate(screenRect.tl.negate()); + server->setCursorPos(desktopPos.x, desktopPos.y); + try_update = true; + + old_cursor = info; + } + + // Flush any changes to the server + try_update = flushChangeTracker() || try_update; + if (try_update) + server->tryUpdate(); + } + } else { + CloseHandle(event); + return false; + } + return true; +} + + +// -=- Protected methods + +void +SDisplay::recreatePixelBuffer() { + vlog.debug("attaching to device %s", deviceName); + + // Open the specified display device + HDC new_device; + if (deviceName.buf) { + new_device = ::CreateDC(_T("DISPLAY"), deviceName.buf, NULL, NULL); + releaseDevice = false; + } else { + // If no device is specified, open entire screen. + // Doing this with CreateDC creates problems on multi-monitor systems. + new_device = ::GetDC(0); + releaseDevice = true; + } + if (!new_device) + throw SystemException("cannot open the display", GetLastError()); + + // Get the coordinates of the entire virtual display + Rect newScreenRect; + { + WindowDC rootDC(0); + RECT r; + if (!GetClipBox(rootDC, &r)) + throw rdr::SystemException("GetClipBox", GetLastError()); + newScreenRect = Rect(r.left, r.top, r.right, r.bottom); + } + + // Create a DeviceFrameBuffer attached to it + DeviceFrameBuffer* new_buffer = new DeviceFrameBuffer(new_device); + + // Has anything actually changed about the screen or the buffer? + if (!pb || + (!newScreenRect.equals(screenRect)) || + (!new_buffer->getPF().equal(pb->getPF()))) + { + // Yes. Update the buffer state. + screenRect = newScreenRect; + vlog.debug("creating pixel buffer for device"); + + // Flush any existing changes to the server + flushChangeTracker(); + + // Replace the old PixelBuffer + if (pb) delete pb; + if (device) DeleteDC(device); + pb = new_buffer; + device = new_device; + + // Initialise the pixels + pb->grabRegion(pb->getRect()); + + // Prevent future grabRect operations from throwing exceptions + pb->setIgnoreGrabErrors(true); + + // Update the SDisplayCore if required + if (core) + core->setPixelBuffer(pb); + + // Inform the server of the changes + if (server) + server->setPixelBuffer(pb); + + } else { + delete new_buffer; + DeleteDC(new_device); + } +} + +bool SDisplay::flushChangeTracker() { + if (change_tracker.is_empty()) + return false; + // Translate the update coordinates from Screen coords to Desktop + change_tracker.translate(screenRect.tl.negate()); + // Flush the updates through + change_tracker.get_update(*server); + change_tracker.clear(); + return true; +} + +void SDisplay::triggerUpdate() { + if (core) + SetEvent(updateEvent); +} diff --git a/rfb_win32/SDisplay.h b/rfb_win32/SDisplay.h new file mode 100644 index 00000000..c4c08bf3 --- /dev/null +++ b/rfb_win32/SDisplay.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- SDisplay.h +// +// The SDisplay class encapsulates a system display. + +// *** THIS INTERFACE NEEDS TIDYING TO SEPARATE COORDINATE SYSTEMS BETTER *** + +#ifndef __RFB_SDISPLAY_H__ +#define __RFB_SDISPLAY_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#include <rfb/SDesktop.h> +#include <rfb/UpdateTracker.h> +#include <rfb/Configuration.h> +#include <rfb/util.h> + +#include <winsock2.h> +#include <rfb_win32/Win32Util.h> +#include <rfb_win32/SocketManager.h> +#include <rfb_win32/DeviceFrameBuffer.h> +#include <rfb_win32/SInput.h> +#include <rfb_win32/Clipboard.h> +#include <rfb_win32/MsgWindow.h> +#include <rfb_win32/WMCursor.h> +#include <rfb_win32/WMHooks.h> +#include <rfb_win32/WMNotifier.h> +#include <rfb_win32/WMWindowCopyRect.h> +#include <rfb_win32/WMPoller.h> + +namespace rfb { + + namespace win32 { + + // + // -=- SDisplay + // + + class SDisplayCore; + + class SDisplay : public SDesktop, + WMMonitor::Notifier, + Clipboard::Notifier, + UpdateTracker, + public SocketManager::EventHandler + { + public: + SDisplay(const TCHAR* device=0); + virtual ~SDisplay(); + + // -=- SDesktop interface + + virtual void start(VNCServer* vs); + virtual void stop(); + virtual void pointerEvent(const Point& pos, rdr::U8 buttonmask); + virtual void keyEvent(rdr::U32 key, bool down); + virtual void clientCutText(const char* str, int len); + virtual void framebufferUpdateRequest(); + virtual Point getFbSize(); + + // -=- UpdateTracker + + virtual void add_changed(const Region& rgn); + virtual void add_copied(const Region& dest, const Point& delta); + + // -=- Clipboard + + virtual void notifyClipboardChanged(const char* text, int len); + + // -=- Display events + + virtual void notifyDisplayEvent(WMMonitor::Notifier::DisplayEventType evt); + + // -=- EventHandler interface + + HANDLE getUpdateEvent() {return updateEvent;} + virtual bool processEvent(HANDLE event); + + // -=- Notification of whether or not SDisplay is started + + void setStatusLocation(bool* status) {statusLocation = status;} + + friend class SDisplayCore; + + static BoolParameter use_hooks; + static BoolParameter disableLocalInputs; + static StringParameter disconnectAction; + static BoolParameter removeWallpaper; + static BoolParameter removePattern; + static BoolParameter disableEffects; + + protected: + void restart(); + void recreatePixelBuffer(); + bool flushChangeTracker(); // true if flushed, false if empty + + void triggerUpdate(); + + VNCServer* server; + + // -=- Display pixel buffer + DeviceFrameBuffer* pb; + TCharArray deviceName; + HDC device; + bool releaseDevice; + + // -=- The coordinates of Window's entire virtual Screen + Rect screenRect; + + // -=- All changes are collected in Display coords and merged + SimpleUpdateTracker change_tracker; + + // -=- Internal SDisplay implementation + SDisplayCore* core; + + // -=- Cursor + WMCursor::Info old_cursor; + Region old_cursor_region; + Point cursor_renderpos; + + // -=- Event signalled to trigger an update to be flushed + Handle updateEvent; + + // -=- Where to write the active/inactive indicator to + bool* statusLocation; + }; + + } +} + +#endif // __RFB_SDISPLAY_H__ diff --git a/rfb_win32/SInput.cxx b/rfb_win32/SInput.cxx new file mode 100644 index 00000000..457a8619 --- /dev/null +++ b/rfb_win32/SInput.cxx @@ -0,0 +1,459 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- SInput.cxx +// +// A number of routines that accept VNC input event data and perform +// the appropriate actions under Win32 + +#define XK_MISCELLANY +#define XK_LATIN1 +#define XK_CURRENCY +#include <rfb/keysymdef.h> + +// * Force the windows headers to include all the SendInput stuff +#define _WIN32_WINNT 0x401 + +#include <rfb_win32/SInput.h> +#include <rfb_win32/Service.h> +#include <rfb/LogWriter.h> +#include <rfb_win32/OSVersion.h> +#include <rfb_win32/Win32Util.h> +#include "keymap.h" + +using namespace rfb; + +static LogWriter vlog("SInput"); + + +typedef UINT (WINAPI *_SendInput_proto)(UINT, LPINPUT, int); +static win32::DynamicFn<_SendInput_proto> _SendInput(_T("user32.dll"), "SendInput"); + +// +// -=- Pointer implementation for Win32 +// + +static DWORD buttonDownMapping[8] = { + MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_RIGHTDOWN, + MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0 +}; + +static DWORD buttonUpMapping[8] = { + MOUSEEVENTF_LEFTUP, MOUSEEVENTF_MIDDLEUP, MOUSEEVENTF_RIGHTUP, + MOUSEEVENTF_WHEEL, MOUSEEVENTF_WHEEL, 0, 0, 0 +}; + +static DWORD buttonDataMapping[8] = { + 0, 0, 0, 120, -120, 0, 0, 0 +}; + +win32::SPointer::SPointer() + : last_buttonmask(0) +{ +} + +void +win32::SPointer::pointerEvent(const Point& pos, rdr::U8 buttonmask) +{ + // - We are specifying absolute coordinates + DWORD flags = MOUSEEVENTF_ABSOLUTE; + + // - Has the pointer moved since the last event? + if (!last_position.equals(pos)) + flags |= MOUSEEVENTF_MOVE; + + // - If the system swaps left and right mouse buttons then we must + // swap them here to negate the effect, so that we do the actual + // action we mean to do + if (::GetSystemMetrics(SM_SWAPBUTTON)) { + bool leftDown = buttonmask & 1; + bool rightDown = buttonmask & 4; + buttonmask = (buttonmask & ~(1 | 4)); + if (leftDown) buttonmask |= 4; + if (rightDown) buttonmask |= 1; + } + + DWORD data = 0; + for (int i = 0; i < 8; i++) { + if ((buttonmask & (1<<i)) != (last_buttonmask & (1<<i))) { + if (buttonmask & (1<<i)) { + flags |= buttonDownMapping[i]; + if (buttonDataMapping[i]) { + if (data) vlog.info("warning - two buttons set mouse_event data field"); + data = buttonDataMapping[i]; + } + } else { + flags |= buttonUpMapping[i]; + } + } + } + + last_position = pos; + last_buttonmask = buttonmask; + + Rect primaryDisplay(0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN)); + if (primaryDisplay.contains(pos)) { + // mouse_event wants coordinates specified as a proportion of the + // primary display's size, scaled to the range 0 to 65535 + Point scaled; + scaled.x = (pos.x * 65535) / (primaryDisplay.width()-1); + scaled.y = (pos.y * 65535) / (primaryDisplay.height()-1); + ::mouse_event(flags, scaled.x, scaled.y, data, 0); + } else { + // The event lies outside the primary monitor. Under Win2K, we can just use + // SendInput, which allows us to provide coordinates scaled to the virtual desktop. + // SendInput is available on all multi-monitor-aware platforms. +#ifdef SM_CXVIRTUALSCREEN + if (osVersion.isPlatformNT) { + if (!_SendInput.isValid()) + throw rdr::Exception("SendInput not available"); + INPUT evt; + evt.type = INPUT_MOUSE; + Point vPos(pos.x-GetSystemMetrics(SM_XVIRTUALSCREEN), + pos.y-GetSystemMetrics(SM_YVIRTUALSCREEN)); + evt.mi.dx = (vPos.x * 65535) / (GetSystemMetrics(SM_CXVIRTUALSCREEN)-1); + evt.mi.dy = (vPos.y * 65535) / (GetSystemMetrics(SM_CYVIRTUALSCREEN)-1); + evt.mi.dwFlags = flags | MOUSEEVENTF_VIRTUALDESK; + evt.mi.dwExtraInfo = 0; + evt.mi.mouseData = data; + evt.mi.time = 0; + if ((*_SendInput)(1, &evt, sizeof(evt)) != 1) + throw rdr::SystemException("SendInput", GetLastError()); + } else { + // Under Win9x, this is not addressable by either mouse_event or SendInput + // *** STUPID KLUDGY HACK *** + POINT cursorPos; GetCursorPos(&cursorPos); + ULONG oldSpeed, newSpeed = 10; + ULONG mouseInfo[3]; + if (flags & MOUSEEVENTF_MOVE) { + flags &= ~MOUSEEVENTF_ABSOLUTE; + SystemParametersInfo(SPI_GETMOUSE, 0, &mouseInfo, 0); + SystemParametersInfo(SPI_GETMOUSESPEED, 0, &oldSpeed, 0); + vlog.debug("SPI_GETMOUSE %d, %d, %d, speed %d", mouseInfo[0], mouseInfo[1], mouseInfo[2], oldSpeed); + ULONG idealMouseInfo[] = {10, 0, 0}; + SystemParametersInfo(SPI_SETMOUSESPEED, 0, &newSpeed, 0); + SystemParametersInfo(SPI_SETMOUSE, 0, &idealMouseInfo, 0); + } + ::mouse_event(flags, pos.x-cursorPos.x, pos.y-cursorPos.y, data, 0); + if (flags & MOUSEEVENTF_MOVE) { + SystemParametersInfo(SPI_SETMOUSE, 0, &mouseInfo, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, &oldSpeed, 0); + } + } +#endif + } +} + +// +// -=- Keyboard implementation +// + +BoolParameter rfb::win32::SKeyboard::deadKeyAware("DeadKeyAware", + "Whether to assume the viewer has already interpreted dead key sequences " + "into latin-1 characters", true); + +static bool oneShift; + +// The keysymToAscii table transforms a couple of awkward keysyms into their +// ASCII equivalents. +struct keysymToAscii_t { + rdr::U32 keysym; + rdr::U8 ascii; +}; + +keysymToAscii_t keysymToAscii[] = { + { XK_KP_Space, ' ' }, + { XK_KP_Equal, '=' }, +}; + +rdr::U8 latin1DeadChars[] = { + XK_grave, XK_acute, XK_asciicircum, XK_diaeresis, XK_degree, XK_cedilla, + XK_asciitilde +}; + +struct latin1ToDeadChars_t { + rdr::U8 latin1Char; + rdr::U8 deadChar; + rdr::U8 baseChar; +}; + +latin1ToDeadChars_t latin1ToDeadChars[] = { + + { XK_Agrave, XK_grave, XK_A }, + { XK_Egrave, XK_grave, XK_E }, + { XK_Igrave, XK_grave, XK_I }, + { XK_Ograve, XK_grave, XK_O }, + { XK_Ugrave, XK_grave, XK_U }, + { XK_agrave, XK_grave, XK_a }, + { XK_egrave, XK_grave, XK_e }, + { XK_igrave, XK_grave, XK_i }, + { XK_ograve, XK_grave, XK_o}, + { XK_ugrave, XK_grave, XK_u }, + + { XK_Aacute, XK_acute, XK_A }, + { XK_Eacute, XK_acute, XK_E }, + { XK_Iacute, XK_acute, XK_I }, + { XK_Oacute, XK_acute, XK_O }, + { XK_Uacute, XK_acute, XK_U }, + { XK_Yacute, XK_acute, XK_Y }, + { XK_aacute, XK_acute, XK_a }, + { XK_eacute, XK_acute, XK_e }, + { XK_iacute, XK_acute, XK_i }, + { XK_oacute, XK_acute, XK_o}, + { XK_uacute, XK_acute, XK_u }, + { XK_yacute, XK_acute, XK_y }, + + { XK_Acircumflex, XK_asciicircum, XK_A }, + { XK_Ecircumflex, XK_asciicircum, XK_E }, + { XK_Icircumflex, XK_asciicircum, XK_I }, + { XK_Ocircumflex, XK_asciicircum, XK_O }, + { XK_Ucircumflex, XK_asciicircum, XK_U }, + { XK_acircumflex, XK_asciicircum, XK_a }, + { XK_ecircumflex, XK_asciicircum, XK_e }, + { XK_icircumflex, XK_asciicircum, XK_i }, + { XK_ocircumflex, XK_asciicircum, XK_o}, + { XK_ucircumflex, XK_asciicircum, XK_u }, + + { XK_Adiaeresis, XK_diaeresis, XK_A }, + { XK_Ediaeresis, XK_diaeresis, XK_E }, + { XK_Idiaeresis, XK_diaeresis, XK_I }, + { XK_Odiaeresis, XK_diaeresis, XK_O }, + { XK_Udiaeresis, XK_diaeresis, XK_U }, + { XK_adiaeresis, XK_diaeresis, XK_a }, + { XK_ediaeresis, XK_diaeresis, XK_e }, + { XK_idiaeresis, XK_diaeresis, XK_i }, + { XK_odiaeresis, XK_diaeresis, XK_o}, + { XK_udiaeresis, XK_diaeresis, XK_u }, + { XK_ydiaeresis, XK_diaeresis, XK_y }, + + { XK_Aring, XK_degree, XK_A }, + { XK_aring, XK_degree, XK_a }, + + { XK_Ccedilla, XK_cedilla, XK_C }, + { XK_ccedilla, XK_cedilla, XK_c }, + + { XK_Atilde, XK_asciitilde, XK_A }, + { XK_Ntilde, XK_asciitilde, XK_N }, + { XK_Otilde, XK_asciitilde, XK_O }, + { XK_atilde, XK_asciitilde, XK_a }, + { XK_ntilde, XK_asciitilde, XK_n }, + { XK_otilde, XK_asciitilde, XK_o }, +}; + +// doKeyboardEvent wraps the system keybd_event function and attempts to find +// the appropriate scancode corresponding to the supplied virtual keycode. + +inline void doKeyboardEvent(BYTE vkCode, DWORD flags) { + vlog.debug("vkCode 0x%x flags 0x%x", vkCode, flags); + keybd_event(vkCode, MapVirtualKey(vkCode, 0), flags, 0); +} + +// KeyStateModifier is a class which helps simplify generating a "fake" press +// or release of shift, ctrl, alt, etc. An instance of the class is created +// for every key which may need to be pressed or released. Then either press() +// or release() may be called to make sure that the corresponding key is in the +// right state. The destructor of the class automatically reverts to the +// previous state. + +class KeyStateModifier { +public: + KeyStateModifier(int vkCode_, int flags_=0) + : vkCode(vkCode_), flags(flags_), pressed(false), released(false) + {} + void press() { + if (!(GetAsyncKeyState(vkCode) & 0x8000)) { + doKeyboardEvent(vkCode, flags); + pressed = true; + } + } + void release() { + if (GetAsyncKeyState(vkCode) & 0x8000) { + doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP); + released = true; + } + } + ~KeyStateModifier() { + if (pressed) { + doKeyboardEvent(vkCode, flags | KEYEVENTF_KEYUP); + } else if (released) { + doKeyboardEvent(vkCode, flags); + } + } + int vkCode; + int flags; + bool pressed; + bool released; +}; + + +// doKeyEventWithModifiers() generates a key event having first "pressed" or +// "released" the shift, ctrl or alt modifiers if necessary. + +void doKeyEventWithModifiers(BYTE vkCode, BYTE modifierState, bool down) +{ + KeyStateModifier ctrl(VK_CONTROL); + KeyStateModifier alt(VK_MENU); + KeyStateModifier shift(VK_SHIFT); + + if (down) { + if (modifierState & 2) ctrl.press(); + if (modifierState & 4) alt.press(); + if (modifierState & 1) { + shift.press(); + } else { + shift.release(); + } + } + doKeyboardEvent(vkCode, down ? 0 : KEYEVENTF_KEYUP); +} + + +win32::SKeyboard::SKeyboard() +{ + oneShift = rfb::win32::osVersion.isPlatformWindows; + for (int i = 0; i < sizeof(keymap) / sizeof(keymap_t); i++) { + vkMap[keymap[i].keysym] = keymap[i].vk; + extendedMap[keymap[i].keysym] = keymap[i].extended; + } + + // Find dead characters for the current keyboard layout + // XXX how could we handle the keyboard layout changing? + BYTE keystate[256]; + memset(keystate, 0, 256); + for (int j = 0; j < sizeof(latin1DeadChars); j++) { + SHORT s = VkKeyScan(latin1DeadChars[j]); + if (s != -1) { + BYTE vkCode = LOBYTE(s); + BYTE modifierState = HIBYTE(s); + keystate[VK_SHIFT] = (modifierState & 1) ? 0x80 : 0; + keystate[VK_CONTROL] = (modifierState & 2) ? 0x80 : 0; + keystate[VK_MENU] = (modifierState & 4) ? 0x80 : 0; + rdr::U8 chars[2]; + int nchars = ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0); + if (nchars < 0) { + vlog.debug("Found dead key 0x%x '%c'", + latin1DeadChars[j], latin1DeadChars[j]); + deadChars.push_back(latin1DeadChars[j]); + ToAscii(vkCode, 0, keystate, (WORD*)&chars, 0); + } + } + } +} + + +void win32::SKeyboard::keyEvent(rdr::U32 keysym, bool down) +{ + for (int i = 0; i < sizeof(keysymToAscii) / sizeof(keysymToAscii_t); i++) { + if (keysymToAscii[i].keysym == keysym) { + keysym = keysymToAscii[i].ascii; + break; + } + } + + if ((keysym >= 32 && keysym <= 126) || + (keysym >= 160 && keysym <= 255)) + { + // ordinary Latin-1 character + + if (deadKeyAware) { + // Detect dead chars and generate the dead char followed by space so + // that we'll end up with the original char. + for (int i = 0; i < deadChars.size(); i++) { + if (keysym == deadChars[i]) { + SHORT dc = VkKeyScan(keysym); + if (dc != -1) { + if (down) { + vlog.info("latin-1 dead key: 0x%x vkCode 0x%x mod 0x%x " + "followed by space", keysym, LOBYTE(dc), HIBYTE(dc)); + doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true); + doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false); + doKeyEventWithModifiers(VK_SPACE, 0, true); + doKeyEventWithModifiers(VK_SPACE, 0, false); + } + return; + } + } + } + } + + SHORT s = VkKeyScan(keysym); + if (s == -1) { + if (down) { + // not a single keypress - try synthesizing dead chars. + for (int j = 0; + j < sizeof(latin1ToDeadChars) / sizeof(latin1ToDeadChars_t); + j++) { + if (keysym == latin1ToDeadChars[j].latin1Char) { + for (int i = 0; i < deadChars.size(); i++) { + if (deadChars[i] == latin1ToDeadChars[j].deadChar) { + SHORT dc = VkKeyScan(latin1ToDeadChars[j].deadChar); + SHORT bc = VkKeyScan(latin1ToDeadChars[j].baseChar); + if (dc != -1 && bc != -1) { + vlog.info("latin-1 key: 0x%x dead key vkCode 0x%x mod 0x%x " + "followed by vkCode 0x%x mod 0x%x", + keysym, LOBYTE(dc), HIBYTE(dc), + LOBYTE(bc), HIBYTE(bc)); + doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), true); + doKeyEventWithModifiers(LOBYTE(dc), HIBYTE(dc), false); + doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), true); + doKeyEventWithModifiers(LOBYTE(bc), HIBYTE(bc), false); + return; + } + break; + } + } + break; + } + } + vlog.info("ignoring unrecognised Latin-1 keysym 0x%x",keysym); + } + return; + } + + BYTE vkCode = LOBYTE(s); + BYTE modifierState = HIBYTE(s); + vlog.debug("latin-1 key: 0x%x vkCode 0x%x mod 0x%x down %d", + keysym, vkCode, modifierState, down); + doKeyEventWithModifiers(vkCode, modifierState, down); + + } else { + + // see if it's a recognised keyboard key, otherwise ignore it + + if (vkMap.find(keysym) == vkMap.end()) { + vlog.info("ignoring unknown keysym 0x%x",keysym); + return; + } + BYTE vkCode = vkMap[keysym]; + DWORD flags = 0; + if (extendedMap[keysym]) flags |= KEYEVENTF_EXTENDEDKEY; + if (!down) flags |= KEYEVENTF_KEYUP; + + vlog.debug("keyboard key: keysym 0x%x vkCode 0x%x ext %d down %d", + keysym, vkCode, extendedMap[keysym], down); + if (down && (vkCode == VK_DELETE) && + ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) && + ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0)) + { + rfb::win32::emulateCtrlAltDel(); + return; + } + + doKeyboardEvent(vkCode, flags); + } +} diff --git a/rfb_win32/SInput.h b/rfb_win32/SInput.h new file mode 100644 index 00000000..dcd779ef --- /dev/null +++ b/rfb_win32/SInput.h @@ -0,0 +1,70 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Input.h +// +// A number of routines that accept VNC-style input event data and perform +// the appropriate actions under Win32 + +#ifndef __RFB_WIN32_INPUT_H__ +#define __RFB_WIN32_INPUT_H__ + +#include <rfb/Rect.h> +#include <rfb/Configuration.h> +#include <rdr/types.h> +#include <map> +#include <vector> + +namespace rfb { + + class CMsgWriter; + + namespace win32 { + + // -=- Pointer event handling + + class SPointer { + public: + SPointer(); + // - Create a pointer event at a the given coordinates, with the + // specified button state. The event must be specified using + // Screen coordinates. + void pointerEvent(const Point& pos, rdr::U8 buttonmask); + protected: + Point last_position; + rdr::U8 last_buttonmask; + }; + + // -=- Keyboard event handling + + class SKeyboard { + public: + SKeyboard(); + void keyEvent(rdr::U32 key, bool down); + static BoolParameter deadKeyAware; + private: + std::map<rdr::U32,rdr::U8> vkMap; + std::map<rdr::U32,bool> extendedMap; + std::vector<rdr::U8> deadChars; + }; + + }; // win32 + +}; // rfb + +#endif // __RFB_WIN32_INPUT_H__ diff --git a/rfb_win32/Security.h b/rfb_win32/Security.h new file mode 100644 index 00000000..d92e314f --- /dev/null +++ b/rfb_win32/Security.h @@ -0,0 +1,198 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// Security.h + +// Wrapper classes for a few Windows NT security structures/functions +// that are used by VNC + +#ifndef __RFB_WIN32_SECURITY_H__ +#define __RFB_WIN32_SECURITY_H__ + +#include <rdr/types.h> +#include <rdr/Exception.h> +#include <rfb_win32/Win32Util.h> +#include <rfb_win32/TCharArray.h> + +#include <lmcons.h> +#include <Accctrl.h> +#include <aclapi.h> + +#include <list> + +namespace rfb { + + namespace win32 { + + struct Trustee : public TRUSTEE { + Trustee(const TCHAR* name, + TRUSTEE_FORM form=TRUSTEE_IS_NAME, + TRUSTEE_TYPE type=TRUSTEE_IS_UNKNOWN) + { + pMultipleTrustee = 0; + MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + TrusteeForm = form; + TrusteeType = type; + ptstrName = (TCHAR*)name; + } + }; + + struct ExplicitAccess : public EXPLICIT_ACCESS { + ExplicitAccess(const TCHAR* name, + TRUSTEE_FORM type, + DWORD perms, + ACCESS_MODE mode, + DWORD inherit=0) + { + Trustee = rfb::win32::Trustee(name, type); + grfAccessPermissions = perms; + grfAccessMode = mode; + grfInheritance = inherit; + } + }; + + // Helper class for building access control lists + struct AccessEntries { + AccessEntries() : entries(0), entry_count(0) {} + ~AccessEntries() {delete [] entries;} + void allocMinEntries(int count) { + if (count > entry_count) { + EXPLICIT_ACCESS* new_entries = new EXPLICIT_ACCESS[entry_count+1]; + if (entries) { + memcpy(new_entries, entries, sizeof(EXPLICIT_ACCESS) * entry_count); + delete entries; + } + entries = new_entries; + } + } + void addEntry(const TCHAR* trusteeName, + DWORD permissions, + ACCESS_MODE mode) + { + allocMinEntries(entry_count+1); + ZeroMemory(&entries[entry_count], sizeof(EXPLICIT_ACCESS)); + entries[entry_count] = ExplicitAccess(trusteeName, TRUSTEE_IS_NAME, permissions, mode); + entry_count++; + } + void addEntry(const PSID sid, DWORD permissions, ACCESS_MODE mode) { + allocMinEntries(entry_count+1); + ZeroMemory(&entries[entry_count], sizeof(EXPLICIT_ACCESS)); + entries[entry_count] = ExplicitAccess((TCHAR*)sid, TRUSTEE_IS_SID, permissions, mode); + entry_count++; + } + + EXPLICIT_ACCESS* entries; + int entry_count; + }; + + // Helper class for handling SIDs + struct Sid { + Sid() : sid(0) {} + Sid(PSID sid_) : sid(sid_) {} + ~Sid() { + if (sid) FreeSid(sid); + } + operator PSID() const {return sid;} + PSID operator=(const PSID sid_) { + if (sid) FreeSid(sid); + sid = sid_; + } + + static PSID Administrators() { + PSID sid = 0; + SID_IDENTIFIER_AUTHORITY ntAuth = SECURITY_NT_AUTHORITY; + if (!AllocateAndInitializeSid(&ntAuth, 2, + SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid)) + throw rdr::SystemException("Sid::Administrators", GetLastError()); + return sid; + } + static PSID SYSTEM() { + PSID sid = 0; + SID_IDENTIFIER_AUTHORITY ntAuth = SECURITY_NT_AUTHORITY; + if (!AllocateAndInitializeSid(&ntAuth, 1, + SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, + &sid)) + throw rdr::SystemException("Sid::SYSTEM", GetLastError()); + return sid; + } + + protected: + PSID sid; + }; + + // Helper class for handling & freeing ACLs + struct AccessControlList : public LocalMem { + AccessControlList(int size) : LocalMem(size) {} + AccessControlList(PACL acl_=0) : LocalMem(acl_) {} + operator PACL() {return (PACL)ptr;} + }; + + // Create a new ACL based on supplied entries and, if supplied, existing ACL + static PACL CreateACL(const AccessEntries& ae, PACL existing_acl=0) { + typedef DWORD (WINAPI *_SetEntriesInAcl_proto) (ULONG, PEXPLICIT_ACCESS, PACL, PACL*); +#ifdef UNICODE + const char* fnName = "SetEntriesInAclW"; +#else + const char* fnName = "SetEntriesInAclA"; +#endif + DynamicFn<_SetEntriesInAcl_proto> _SetEntriesInAcl(_T("advapi32.dll"), fnName); + if (!_SetEntriesInAcl.isValid()) + throw rdr::SystemException("CreateACL failed; no SetEntriesInAcl", ERROR_CALL_NOT_IMPLEMENTED); + PACL new_dacl; + DWORD result; + if ((result = (*_SetEntriesInAcl)(ae.entry_count, ae.entries, existing_acl, &new_dacl)) != ERROR_SUCCESS) + throw rdr::SystemException("SetEntriesInAcl", result); + return new_dacl; + } + + // Helper class for memory-management of self-relative SecurityDescriptors + struct SecurityDescriptorPtr : LocalMem { + SecurityDescriptorPtr(int size) : LocalMem(size) {} + SecurityDescriptorPtr(PSECURITY_DESCRIPTOR sd_=0) : LocalMem(sd_) {} + PSECURITY_DESCRIPTOR takeSD() {return takePtr();} + }; + + // Create a new self-relative Security Descriptor, owned by SYSTEM/Administrators, + // with the supplied DACL and no SACL. The returned value can be assigned + // to a SecurityDescriptorPtr to be managed. + static PSECURITY_DESCRIPTOR CreateSdWithDacl(const PACL dacl) { + SECURITY_DESCRIPTOR absSD; + if (!InitializeSecurityDescriptor(&absSD, SECURITY_DESCRIPTOR_REVISION)) + throw rdr::SystemException("InitializeSecurityDescriptor", GetLastError()); + Sid owner(Sid::SYSTEM()); + if (!SetSecurityDescriptorOwner(&absSD, owner, FALSE)) + throw rdr::SystemException("SetSecurityDescriptorOwner", GetLastError()); + Sid group(Sid::Administrators()); + if (!SetSecurityDescriptorGroup(&absSD, group, FALSE)) + throw rdr::SystemException("SetSecurityDescriptorGroupp", GetLastError()); + if (!SetSecurityDescriptorDacl(&absSD, TRUE, dacl, FALSE)) + throw rdr::SystemException("SetSecurityDescriptorDacl", GetLastError()); + DWORD sdSize = GetSecurityDescriptorLength(&absSD); + SecurityDescriptorPtr sd(sdSize); + if (!MakeSelfRelativeSD(&absSD, sd, &sdSize)) + throw rdr::SystemException("MakeSelfRelativeSD", GetLastError()); + return sd.takeSD(); + } + + } + +} + +#endif diff --git a/rfb_win32/Service.cxx b/rfb_win32/Service.cxx new file mode 100644 index 00000000..b00c2900 --- /dev/null +++ b/rfb_win32/Service.cxx @@ -0,0 +1,638 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Service.cxx + +#include <rfb_win32/Service.h> +#include <rfb_win32/MsgWindow.h> +#include <rfb_win32/Registry.h> +#include <rfb_win32/Win32Util.h> +#include <rfb_win32/OSVersion.h> +#include <rfb/Threading.h> +#include <rfb/LogWriter.h> +#include <rfb/util.h> +#include <rdr/Exception.h> + +#include <logmessages/messages.h> + +using namespace rdr; +using namespace rfb; +using namespace win32; + +static LogWriter vlog("Service"); + + +// - Internal service implementation functions + +Service* service = 0; + +VOID WINAPI serviceHandler(DWORD control) { + vlog.debug("service control %u", control); + switch (control) { + case SERVICE_CONTROL_INTERROGATE: + service->setStatus(); + break; + case SERVICE_CONTROL_PARAMCHANGE: + service->readParams(); + break; + case SERVICE_CONTROL_SHUTDOWN: + service->osShuttingDown(); + break; + case SERVICE_CONTROL_STOP: + service->setStatus(SERVICE_STOP_PENDING); + service->stop(); + break; + } +} + + +// -=- Message window derived class used under Win9x to implement stopService + +#define WM_SMSG_SERVICE_STOP WM_USER + +class ServiceMsgWindow : public MsgWindow { +public: + ServiceMsgWindow(const TCHAR* name) : MsgWindow(name) {} + LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_SMSG_SERVICE_STOP: + service->stop(); + return TRUE; + } + return MsgWindow::processMessage(msg, wParam, lParam); + } + + static const TCHAR* baseName; +}; + +const TCHAR* ServiceMsgWindow::baseName = _T("ServiceWindow:"); + + +// -=- Service main procedure, used under WinNT/2K/XP by the SCM + +VOID WINAPI serviceProc(DWORD dwArgc, LPTSTR* lpszArgv) { + vlog.debug("entering %s serviceProc", service->getName()); + service->status_handle = RegisterServiceCtrlHandler(service->getName(), serviceHandler); + if (!service->status_handle) { + vlog.error("unable to register service control handler"); + return; + } + service->setStatus(SERVICE_START_PENDING); + vlog.debug("entering %s serviceMain", service->getName()); + service->status.dwWin32ExitCode = service->serviceMain(dwArgc, lpszArgv); + vlog.debug("leaving %s serviceMain", service->getName()); + service->setStatus(SERVICE_STOPPED); +} + + +// -=- Service + +Service::Service(const TCHAR* name_) : name(name_) { + vlog.debug("Service"); + status_handle = 0; + status.dwControlsAccepted = SERVICE_CONTROL_INTERROGATE | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = 0; + status.dwWaitHint = 30000; + status.dwCurrentState = SERVICE_STOPPED; +} + +void +Service::start() { + if (osVersion.isPlatformNT) { + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = (TCHAR*)name; + entry[0].lpServiceProc = serviceProc; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + vlog.debug("entering dispatcher"); + if (!SetProcessShutdownParameters(0x100, 0)) + vlog.error("unable to set shutdown parameters: %d", GetLastError()); + service = this; + if (!StartServiceCtrlDispatcher(entry)) + throw SystemException("unable to start service", GetLastError()); + } else { + + // - Create the service window, so the service can be stopped + TCharArray wndName(_tcslen(getName()) + _tcslen(ServiceMsgWindow::baseName) + 1); + _tcscpy(wndName.buf, ServiceMsgWindow::baseName); + _tcscat(wndName.buf, getName()); + ServiceMsgWindow service_window(wndName.buf); + + // - Locate the RegisterServiceProcess function + typedef DWORD (WINAPI * _RegisterServiceProcess_proto)(DWORD, DWORD); + DynamicFn<_RegisterServiceProcess_proto> _RegisterServiceProcess(_T("kernel32.dll"), "RegisterServiceProcess"); + if (!_RegisterServiceProcess.isValid()) + throw Exception("unable to find RegisterServiceProcess"); + + // - Run the service + (*_RegisterServiceProcess)(NULL, 1); + service = this; + serviceMain(0, 0); + (*_RegisterServiceProcess)(NULL, 0); + } +} + +void +Service::setStatus() { + setStatus(status.dwCurrentState); +} + +void +Service::setStatus(DWORD state) { + if (!osVersion.isPlatformNT) + return; + if (status_handle == 0) { + vlog.debug("warning - cannot setStatus"); + return; + } + status.dwCurrentState = state; + status.dwCheckPoint++; + if (!SetServiceStatus(status_handle, &status)) { + status.dwWin32ExitCode = GetLastError(); + vlog.error("unable to set service status:%u", status.dwWin32ExitCode); + stop(); + } + vlog.debug("set status to %u(%u)", state, status.dwCheckPoint); +} + +Service::~Service() { + vlog.debug("~Service"); + service = 0; +} + + +// Find out whether this process is running as the WinVNC service +bool thisIsService() { + return service && (service->status.dwCurrentState != SERVICE_STOPPED); +} + + +// -=- Desktop handling code + +// Switch the current thread to the specified desktop +static bool +switchToDesktop(HDESK desktop) { + HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId()); + if (!SetThreadDesktop(desktop)) { + vlog.debug("switchToDesktop failed:%u", GetLastError()); + return false; + } + if (!CloseDesktop(old_desktop)) + vlog.debug("unable to close old desktop:%u", GetLastError()); + return true; +} + +// Determine whether the thread's current desktop is the input one +static bool +inputDesktopSelected() { + HDESK current = GetThreadDesktop(GetCurrentThreadId()); + HDESK input = OpenInputDesktop(0, FALSE, + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE); + if (!input) { + vlog.debug("unable to OpenInputDesktop(1):%u", GetLastError()); + return false; + } + + DWORD size; + char currentname[256]; + char inputname[256]; + + if (!GetUserObjectInformation(current, UOI_NAME, currentname, 256, &size)) { + vlog.debug("unable to GetUserObjectInformation(1):%u", GetLastError()); + CloseDesktop(input); + return false; + } + if (!GetUserObjectInformation(input, UOI_NAME, inputname, 256, &size)) { + vlog.debug("unable to GetUserObjectInformation(2):%u", GetLastError()); + CloseDesktop(input); + return false; + } + if (!CloseDesktop(input)) + vlog.debug("unable to close input desktop:%u", GetLastError()); + + // *** vlog.debug("current=%s, input=%s", currentname, inputname); + bool result = strcmp(currentname, inputname) == 0; + return result; +} + +// Switch the current thread into the input desktop +static bool +selectInputDesktop() { + // - Open the input desktop + HDESK desktop = OpenInputDesktop(0, FALSE, + DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE); + if (!desktop) { + vlog.debug("unable to OpenInputDesktop(2):%u", GetLastError()); + return false; + } + + // - Switch into it + if (!switchToDesktop(desktop)) { + CloseDesktop(desktop); + return false; + } + + // *** + DWORD size = 256; + char currentname[256]; + if (GetUserObjectInformation(desktop, UOI_NAME, currentname, 256, &size)) { + vlog.debug("switched to %s", currentname); + } + // *** + + vlog.debug("switched to input desktop"); + + return true; +} + + +// -=- Access points to desktop-switching routines + +bool +rfb::win32::desktopChangeRequired() { + if (!osVersion.isPlatformNT) + return false; + + return !inputDesktopSelected(); +} + +bool +rfb::win32::changeDesktop() { + if (!osVersion.isPlatformNT) + return true; + if (osVersion.cannotSwitchDesktop) + return false; + + return selectInputDesktop(); +} + + +// -=- Ctrl-Alt-Del emulation + +class CADThread : public Thread { +public: + CADThread() : Thread("CtrlAltDel Emulator"), result(false) {} + virtual void run() { + HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId()); + + if (switchToDesktop(OpenDesktop(_T("Winlogon"), 0, FALSE, DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW | + DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL | + DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP | GENERIC_WRITE))) { + PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, MAKELONG(MOD_ALT | MOD_CONTROL, VK_DELETE)); + switchToDesktop(old_desktop); + result = true; + } + } + bool result; +}; + +bool +rfb::win32::emulateCtrlAltDel() { + if (!osVersion.isPlatformNT) + return false; + + CADThread* cad_thread = new CADThread(); + vlog.debug("emulate Ctrl-Alt-Del"); + if (cad_thread) { + cad_thread->start(); + cad_thread->join(); + bool result = cad_thread->result; + delete cad_thread; + return result; + } + return false; +} + + +// -=- Application Event Log target Logger class + +class Logger_EventLog : public Logger { +public: + Logger_EventLog(const TCHAR* srcname) : Logger("EventLog") { + eventlog = RegisterEventSource(NULL, srcname); + if (!eventlog) + printf("Unable to open event log:%ld\n", GetLastError()); + } + ~Logger_EventLog() { + if (eventlog) + DeregisterEventSource(eventlog); + } + + virtual void write(int level, const char *logname, const char *message) { + if (!eventlog) return; + TStr log(logname), msg(message); + const TCHAR* strings[] = {log, msg}; + WORD type = EVENTLOG_INFORMATION_TYPE; + if (level == 0) type = EVENTLOG_ERROR_TYPE; + if (!ReportEvent(eventlog, type, 0, VNC4LogMessage, NULL, 2, 0, strings, NULL)) { + // *** It's not at all clear what is the correct behaviour if this fails... + printf("ReportEvent failed:%ld\n", GetLastError()); + } + } + +protected: + HANDLE eventlog; +}; + +static Logger_EventLog* logger = 0; + +bool rfb::win32::initEventLogLogger(const TCHAR* srcname) { + if (logger) + return false; + if (osVersion.isPlatformNT) { + logger = new Logger_EventLog(srcname); + logger->registerLogger(); + return true; + } else { + return false; + } +} + + +// -=- Registering and unregistering the service + +bool rfb::win32::registerService(const TCHAR* name, const TCHAR* desc, + int argc, const char* argv[]) { + + // - Initialise the default service parameters + const TCHAR* defaultcmdline; + if (osVersion.isPlatformNT) + defaultcmdline = _T("-service"); + else + defaultcmdline = _T("-noconsole -service"); + + // - Get the full pathname of our executable + ModuleFileName buffer; + + // - Calculate the command-line length + int cmdline_len = _tcslen(buffer.buf) + 4; + int i; + for (i=0; i<argc; i++) { + cmdline_len += strlen(argv[i]) + 3; + } + + // - Add the supplied extra parameters to the command line + TCharArray cmdline(cmdline_len+_tcslen(defaultcmdline)); + _stprintf(cmdline.buf, _T("\"%s\" %s"), buffer.buf, defaultcmdline); + for (i=0; i<argc; i++) { + _tcscat(cmdline.buf, _T(" \"")); + _tcscat(cmdline.buf, TStr(argv[i])); + _tcscat(cmdline.buf, _T("\"")); + } + + // - Register the service + + if (osVersion.isPlatformNT) { + + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + + ServiceHandle service = CreateService(scm, + name, desc, SC_MANAGER_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, + cmdline.buf, NULL, NULL, NULL, NULL, NULL); + if (!service) + throw rdr::SystemException("unable to create service", GetLastError()); + + // - Register the event log source + RegKey hk, hk2; + + hk2.createKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application")); + hk.createKey(hk2, name); + + for (i=_tcslen(buffer.buf); i>0; i--) { + if (buffer.buf[i] == _T('\\')) { + buffer.buf[i+1] = 0; + break; + } + } + + const TCHAR* dllFilename = _T("logmessages.dll"); + TCharArray dllPath(_tcslen(buffer.buf) + _tcslen(dllFilename) + 1); + _tcscpy(dllPath.buf, buffer.buf); + _tcscat(dllPath.buf, dllFilename); + + hk.setExpandString(_T("EventMessageFile"), dllPath.buf); + hk.setInt(_T("TypesSupported"), EVENTLOG_ERROR_TYPE | EVENTLOG_INFORMATION_TYPE); + + } else { + + RegKey services; + services.createKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices")); + services.setString(name, cmdline.buf); + + } + + Sleep(500); + + return true; +} + +bool rfb::win32::unregisterService(const TCHAR* name) { + if (osVersion.isPlatformNT) { + + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Create the service + ServiceHandle service = OpenService(scm, name, SC_MANAGER_ALL_ACCESS); + if (!service) + throw rdr::SystemException("unable to locate the service", GetLastError()); + if (!DeleteService(service)) + throw rdr::SystemException("unable to remove the service", GetLastError()); + + // - Register the event log source + RegKey hk; + hk.openKey(HKEY_LOCAL_MACHINE, _T("SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application")); + hk.deleteKey(name); + + } else { + + RegKey services; + services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices")); + services.deleteValue(name); + + } + + Sleep(500); + + return true; +} + + +// -=- Starting and stopping the service + +HWND findServiceWindow(const TCHAR* name) { + TCharArray wndName(_tcslen(ServiceMsgWindow::baseName)+_tcslen(name)+1); + _tcscpy(wndName.buf, ServiceMsgWindow::baseName); + _tcscat(wndName.buf, name); + vlog.debug("searching for %s window", CStr(wndName.buf)); + return FindWindow(0, wndName.buf); +} + +bool rfb::win32::startService(const TCHAR* name) { + + if (osVersion.isPlatformNT) { + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Locate the service + ServiceHandle service = OpenService(scm, name, SERVICE_START); + if (!service) + throw rdr::SystemException("unable to open the service", GetLastError()); + + // - Start the service + if (!StartService(service, 0, NULL)) + throw rdr::SystemException("unable to start the service", GetLastError()); + } else { + // - Check there is no service window + if (findServiceWindow(name)) + throw rdr::Exception("the service is already running"); + + // - Find the RunServices registry key + RegKey services; + services.openKey(HKEY_LOCAL_MACHINE, _T("Software\\Microsoft\\Windows\\CurrentVersion\\RunServices")); + + // - Read the command-line from it + TCharArray cmdLine = services.getString(name); + + // - Start the service + PROCESS_INFORMATION proc_info; + STARTUPINFO startup_info; + ZeroMemory(&startup_info, sizeof(startup_info)); + startup_info.cb = sizeof(startup_info); + if (!CreateProcess(0, cmdLine.buf, 0, 0, FALSE, CREATE_NEW_CONSOLE, 0, 0, &startup_info, &proc_info)) { + throw SystemException("unable to start service", GetLastError()); + } + } + + Sleep(500); + + return true; +} + +bool rfb::win32::stopService(const TCHAR* name) { + if (osVersion.isPlatformNT) { + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Locate the service + ServiceHandle service = OpenService(scm, name, SERVICE_STOP); + if (!service) + throw rdr::SystemException("unable to open the service", GetLastError()); + + // - Start the service + SERVICE_STATUS status; + if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) + throw rdr::SystemException("unable to stop the service", GetLastError()); + + } else { + // - Find the service window + HWND service_window = findServiceWindow(name); + if (!service_window) + throw Exception("unable to locate running service"); + + // Tell it to quit + vlog.debug("sending service stop request"); + if (!SendMessage(service_window, WM_SMSG_SERVICE_STOP, 0, 0)) + throw Exception("unable to stop service"); + + // Check it's quitting... + DWORD process_id = 0; + HANDLE process = 0; + if (!GetWindowThreadProcessId(service_window, &process_id)) + throw SystemException("unable to verify service has quit", GetLastError()); + process = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, FALSE, process_id); + if (!process) + throw SystemException("unable to obtain service handle", GetLastError()); + int retries = 5; + vlog.debug("checking status"); + while (retries-- && (WaitForSingleObject(process, 1000) != WAIT_OBJECT_0)) {} + if (!retries) { + vlog.debug("failed to quit - terminating"); + // May not have quit because of silly Win9x registry watching bug.. + if (!TerminateProcess(process, 1)) + throw SystemException("unable to terminate process!", GetLastError()); + throw Exception("service failed to quit - called TerminateProcess"); + } + } + + Sleep(500); + + return true; +} + +void rfb::win32::printServiceStatus(const TCHAR* name) { + if (osVersion.isPlatformNT) { + // - Open the SCM + ServiceHandle scm = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); + if (!scm) + throw rdr::SystemException("unable to open Service Control Manager", GetLastError()); + + // - Locate the service + ServiceHandle service = OpenService(scm, name, SERVICE_INTERROGATE); + if (!service) + throw rdr::SystemException("unable to open the service", GetLastError()); + + // - Get the service status + SERVICE_STATUS status; + if (!ControlService(service, SERVICE_CONTROL_INTERROGATE, (SERVICE_STATUS*)&status)) + throw rdr::SystemException("unable to query the service", GetLastError()); + + printf("Service is in the "); + switch (status.dwCurrentState) { + case SERVICE_RUNNING: printf("running"); break; + case SERVICE_STOPPED: printf("stopped"); break; + case SERVICE_STOP_PENDING: printf("stop pending"); break; + default: printf("unknown (%lu)", status.dwCurrentState); break; + }; + printf(" state.\n"); + + } else { + HWND service_window = findServiceWindow(name); + printf("Service is in the "); + if (!service_window) printf("stopped"); + else printf("running"); + printf(" state.\n"); + } +} + + +bool rfb::win32::isServiceProcess() { + return service != 0; +}
\ No newline at end of file diff --git a/rfb_win32/Service.h b/rfb_win32/Service.h new file mode 100644 index 00000000..164381a1 --- /dev/null +++ b/rfb_win32/Service.h @@ -0,0 +1,123 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Service.h +// +// Win32 service-mode code. +// Derive your service from this code and let it handle the annoying Win32 +// service API. +// The underlying implementation takes care of the differences between +// Windows NT and Windows 95 based systems + +#ifndef __RFB_WIN32_SERVICE_H__ +#define __RFB_WIN32_SERVICE_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +namespace rfb { + + namespace win32 { + + // + // -=- Service + // + + // Application base-class for services. + + class Service { + public: + + Service(const TCHAR* name_); + virtual ~Service(); + + const TCHAR* getName() {return name;} + SERVICE_STATUS& getStatus() {return status;} + + void setStatus(DWORD status); + void setStatus(); + + // - Start the service, having initialised it + void start(); + + // - Service main procedure - override to implement a service + virtual DWORD serviceMain(int argc, TCHAR* argv[]) = 0; + + // - Service control notifications + + // To get notified when the OS is shutting down + virtual void osShuttingDown() = 0; + + // To get notified when the service parameters change + virtual void readParams() = 0; + + // To cause the serviceMain() routine to return + virtual void stop() = 0; + + public: + SERVICE_STATUS_HANDLE status_handle; + SERVICE_STATUS status; + protected: + const TCHAR* name; + }; + + class ServiceHandle { + public: + ServiceHandle(SC_HANDLE h) : handle(h) {} + ~ServiceHandle() {CloseServiceHandle(handle);} + operator SC_HANDLE() const {return handle;} + protected: + SC_HANDLE handle; + }; + + // -=- Routines used by desktop back-end code to manage desktops/window stations + + // Returns false under Win9x + bool desktopChangeRequired(); + + // Returns true under Win9x + bool changeDesktop(); + + // -=- Routines used by the SInput Keyboard class to emulate Ctrl-Alt-Del + // Returns false under Win9x + bool emulateCtrlAltDel(); + + // -=- Routines to initialise the Event Log target Logger + // Returns false under Win9x + bool initEventLogLogger(const TCHAR* srcname); + + // -=- Routines to register/unregister the service + // These routines also take care of registering the required + // event source information, etc. + // *** should really accept TCHAR argv + + bool registerService(const TCHAR* name, const TCHAR* desc, int argc, const char* argv[]); + bool unregisterService(const TCHAR* name); + + bool startService(const TCHAR* name); + bool stopService(const TCHAR* name); + void printServiceStatus(const TCHAR* name); + + // -=- Routine to determine whether the host process is running a service + bool isServiceProcess(); + + }; + +}; + +#endif // __RFB_WIN32_SERVICE_NT_H__ diff --git a/rfb_win32/SocketManager.cxx b/rfb_win32/SocketManager.cxx new file mode 100644 index 00000000..6ebd5c0a --- /dev/null +++ b/rfb_win32/SocketManager.cxx @@ -0,0 +1,246 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- SocketManager.cxx + +#define WIN32_LEAN_AND_MEAN +#include <winsock2.h> +#include <assert.h> + +#include <rfb/LogWriter.h> +#include <rfb_win32/SocketManager.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("SocketManager"); + + +// -=- SocketManager + +SocketManager::SocketManager() : sockets(0), events(0), nSockets(0), nAvail(0) { +} + +SocketManager::~SocketManager() { + for (int i=0; i<nSockets; i++) { + if (!sockets[i].is_event) + WSACloseEvent(events[i]); + } + delete [] events; + delete [] sockets; +} + + +void SocketManager::addListener(network::SocketListener* sock_, network::SocketServer* srvr) { + WSAEVENT event = WSACreateEvent(); + assert(event != WSA_INVALID_EVENT); + addListener(sock_, event, srvr); +} + +void SocketManager::addSocket(network::Socket* sock_, network::SocketServer* srvr) { + WSAEVENT event = WSACreateEvent(); + assert(event != WSA_INVALID_EVENT); + addSocket(sock_, event, srvr); +} + + +BOOL SocketManager::getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg) { + while (true) { + // First check for idle timeout + + network::SocketServer* server = 0; + int timeout = 0; + for (int i=0; i<nSockets; i++) { + if (!sockets[i].is_event && + sockets[i].server != server) { + server = sockets[i].server; + int t = server->checkTimeouts(); + if (t > 0 && (timeout == 0 || t < timeout)) + timeout = t; + } + } + if (timeout == 0) + timeout = INFINITE; + + // - Network IO is less common than messages - process it first + DWORD result; + if (nSockets) { + result = WaitForMultipleObjects(nSockets, events, FALSE, 0); + if (result == WAIT_TIMEOUT) { + if (PeekMessage(msg, hwnd, minMsg, maxMsg, PM_REMOVE)) + return msg->message != WM_QUIT; + + result = MsgWaitForMultipleObjects(nSockets, events, FALSE, timeout, + QS_ALLINPUT); + if (result == WAIT_OBJECT_0 + nSockets) { + if (PeekMessage(msg, hwnd, minMsg, maxMsg, PM_REMOVE)) + return msg->message != WM_QUIT; + continue; + } + } + } else + return GetMessage(msg, hwnd, minMsg, maxMsg); + + if ((result >= WAIT_OBJECT_0) && (result < (WAIT_OBJECT_0 + nSockets))) { + int index = result - WAIT_OBJECT_0; + + // - Process a socket event + + if (sockets[index].is_event) { + // Process a general Win32 event + // NB: The handler must reset the event! + + if (!sockets[index].handler->processEvent(events[index])) { + removeSocket(index); + continue; + } + } else if (sockets[index].is_conn) { + // Process data from an active connection + + // Cancel event notification for this socket + if (WSAEventSelect(sockets[index].fd, events[index], 0) == SOCKET_ERROR) + vlog.info("unable to disable WSAEventSelect:%u", WSAGetLastError()); + + // Reset the event object + WSAResetEvent(events[index]); + + // Call the socket server to process the event + if (!sockets[index].server->processSocketEvent(sockets[index].sock.conn)) { + removeSocket(index); + continue; + } + + // Re-instate the required socket event + // If the read event is still valid, the event object gets set here + if (WSAEventSelect(sockets[index].fd, events[index], FD_READ | FD_CLOSE) == SOCKET_ERROR) + throw rdr::SystemException("unable to re-enable WSAEventSelect:%u", WSAGetLastError()); + + } else { + // Accept an incoming connection + vlog.debug("accepting incoming connection"); + + // What kind of event is this? + WSANETWORKEVENTS network_events; + WSAEnumNetworkEvents(sockets[index].fd, events[index], &network_events); + if (network_events.lNetworkEvents & FD_ACCEPT) { + network::Socket* new_sock = sockets[index].sock.listener->accept(); + if (new_sock) { + sockets[index].server->addClient(new_sock); + addSocket(new_sock, sockets[index].server); + } + } else if (network_events.lNetworkEvents & FD_CLOSE) { + vlog.info("deleting listening socket"); + network::SocketListener* s = sockets[index].sock.listener; + removeSocket(index); + delete s; + } else { + vlog.error("unknown network event for listener"); + } + + } + } else if (result == WAIT_FAILED) { + throw rdr::SystemException("unable to wait for events", GetLastError()); + } + } +} + + +void SocketManager::resizeArrays(int numSockets) { + if (nAvail >= numSockets) return; + while (nAvail < numSockets) + nAvail = max(16, nAvail*2); + + SocketInfo* newinfo = new SocketInfo[nAvail]; + HANDLE* newevents = new HANDLE[nAvail]; + for (int i=0; i<nSockets; i++) { + newinfo[i] = sockets[i]; + newevents[i] = events[i]; + } + delete [] sockets; + delete [] events; + sockets = newinfo; + events = newevents; +} + +void SocketManager::addSocket(network::Socket* sock, HANDLE event, network::SocketServer* server) { + resizeArrays(nSockets+1); + + sockets[nSockets].sock.conn = sock; + sockets[nSockets].fd = sock->getFd(); + sockets[nSockets].server = server; + events[nSockets] = event; + sockets[nSockets].is_conn = true; + sockets[nSockets].is_event = false; + + if (WSAEventSelect(sock->getFd(), event, FD_READ | FD_CLOSE) == SOCKET_ERROR) + throw rdr::SystemException("unable to select on socket", WSAGetLastError()); + nSockets++; +} + +void SocketManager::addListener(network::SocketListener* sock, HANDLE event, network::SocketServer* server) { + resizeArrays(nSockets+1); + + sockets[nSockets].sock.listener = sock; + sockets[nSockets].fd = sock->getFd(); + sockets[nSockets].server = server; + events[nSockets] = event; + sockets[nSockets].is_conn = false; + sockets[nSockets].is_event = false; + + if (WSAEventSelect(sock->getFd(), event, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR) + throw rdr::SystemException("unable to select on listener", WSAGetLastError()); + nSockets++; +} + +void SocketManager::remListener(network::SocketListener* sock) { + for (int index=0; index<nSockets; index++) { + if (!sockets[index].is_conn && + !sockets[index].is_event) { + vlog.debug("removing listening socket"); + removeSocket(index); + delete sock; + } + } +} + +void SocketManager::addEvent(HANDLE event, EventHandler* ecb) { + resizeArrays(nSockets+1); + + sockets[nSockets].handler = ecb; + events[nSockets] = event; + sockets[nSockets].is_conn = false; + sockets[nSockets].is_event = true; + + nSockets++; +} + +void SocketManager::removeSocket(int index) { + if (index >= nSockets) + throw rdr::Exception("attempting to remove unregistered socket"); + + if (!sockets[index].is_event) + WSACloseEvent(events[index]); + + for (int i=index; i<nSockets-1; i++) { + sockets[i] = sockets[i+1]; + events[i] = events[i+1]; + } + + nSockets--; +} + diff --git a/rfb_win32/SocketManager.h b/rfb_win32/SocketManager.h new file mode 100644 index 00000000..791370f2 --- /dev/null +++ b/rfb_win32/SocketManager.h @@ -0,0 +1,107 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- SocketManager.h + +// Socket manager class for Win32. +// Passed a network::SocketListener and a network::SocketServer when +// constructed. Uses WSAAsyncSelect to get notifications of network +// connection attempts. When an incoming connection is received, +// the manager will call network::SocketServer::addClient(). If +// addClient returns true then the manager registers interest in +// network events on that socket, and calls +// network::SocketServer::processSocketEvent(). + +#ifndef __RFB_WIN32_SOCKET_MGR_H__ +#define __RFB_WIN32_SOCKET_MGR_H__ + +#include <list> + +#include <network/Socket.h> +#include <rfb_win32/MsgWindow.h> + +namespace rfb { + + namespace win32 { + + class SocketManager { + public: + SocketManager(); + virtual ~SocketManager(); + + // Add a listening socket. Incoming connections will be added to the supplied + // SocketServer. + void addListener(network::SocketListener* sock_, network::SocketServer* srvr); + + // Remove and delete a listening socket. + void remListener(network::SocketListener* sock); + + // Add an already-connected socket. Socket events will cause the supplied + // SocketServer to be called. The socket must ALREADY BE REGISTERED with + // the SocketServer. + void addSocket(network::Socket* sock_, network::SocketServer* srvr); + + // Add a Win32 event & handler for it to the SocketManager + // This event will be blocked on along with the registered Sockets, and the + // handler called whenever it is discovered to be set. + // NB: SocketManager does NOT call ResetEvent on the event! + // NB: If processEvent returns false then the event is no longer registered, + // and the event object is assumed to have been closed by processEvent() + struct EventHandler { + virtual ~EventHandler() {} + virtual bool processEvent(HANDLE event) = 0; + }; + void addEvent(HANDLE event, EventHandler* ecb); + + // getMessage + // + // Either return a message from the thread's message queue or process a socket + // event. + // Returns whenever a message needs processing. Returns false if message is + // WM_QUIT, true for all other messages. + BOOL getMessage(MSG* msg, HWND hwnd, UINT minMsg, UINT maxMsg); + + protected: + void addListener(network::SocketListener* sock, HANDLE event, network::SocketServer* server); + void addSocket(network::Socket* sock, HANDLE event, network::SocketServer* server); + void resizeArrays(int numSockets); + void removeSocket(int index); + struct SocketInfo { + union { + network::Socket* conn; + network::SocketListener* listener; + } sock; + SOCKET fd; + bool is_conn; + bool is_event; + union { + network::SocketServer* server; + EventHandler* handler; + }; + }; + SocketInfo* sockets; + HANDLE* events; + int nSockets; + int nAvail; + }; + + } + +} + +#endif diff --git a/rfb_win32/TCharArray.cxx b/rfb_win32/TCharArray.cxx new file mode 100644 index 00000000..f8f03a69 --- /dev/null +++ b/rfb_win32/TCharArray.cxx @@ -0,0 +1,85 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#include <rfb_win32/TCharArray.h> + +namespace rfb { + + WCHAR* wstrDup(const WCHAR* s) { + if (!s) return 0; + WCHAR* t = new WCHAR[wcslen(s)+1]; + memcpy(t, s, sizeof(WCHAR)*(wcslen(s)+1)); + return t; + } + void wstrFree(WCHAR* s) {delete [] s;} + + char* strDup(const WCHAR* s) { + if (!s) return 0; + int len = wcslen(s); + char* t = new char[len+1]; + t[WideCharToMultiByte(CP_ACP, 0, s, len, t, len, 0, 0)] = 0; + return t; + } + + WCHAR* wstrDup(const char* s) { + if (!s) return 0; + int len = strlen(s); + WCHAR* t = new WCHAR[len+1]; + t[MultiByteToWideChar(CP_ACP, 0, s, len, t, len)] = 0; + return t; + } + + + bool wstrSplit(const WCHAR* src, const WCHAR limiter, WCHAR** out1, WCHAR** out2, bool fromEnd) { + WCharArray out1old, out2old; + if (out1) out1old.buf = *out1; + if (out2) out2old.buf = *out2; + int len = wcslen(src); + int i=0, increment=1, limit=len; + if (fromEnd) { + i=len-1; increment = -1; limit = -1; + } + while (i!=limit) { + if (src[i] == limiter) { + if (out1) { + *out1 = new WCHAR[i+1]; + if (i) memcpy(*out1, src, sizeof(WCHAR)*i); + (*out1)[i] = 0; + } + if (out2) { + *out2 = new WCHAR[len-i]; + if (len-i-1) memcpy(*out2, &src[i+1], sizeof(WCHAR)*(len-i-1)); + (*out2)[len-i-1] = 0; + } + return true; + } + i+=increment; + } + if (out1) *out1 = wstrDup(src); + if (out2) *out2 = 0; + return false; + } + + bool wstrContains(const WCHAR* src, WCHAR c) { + int l=wcslen(src); + for (int i=0; i<l; i++) + if (src[i] == c) return true; + return false; + } + +}; diff --git a/rfb_win32/TCharArray.h b/rfb_win32/TCharArray.h new file mode 100644 index 00000000..399e00a7 --- /dev/null +++ b/rfb_win32/TCharArray.h @@ -0,0 +1,119 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- TCharArray.h + +// This library contains the wide-character equivalent of CharArray, named +// WCharArray. In addition to providing wide-character equivalents of +// the char* string manipulation functions (strDup, strFree, etc), special +// versions of those functions are provided which attempt to convert from +// one format to the other. +// e.g. char* t = "hello world"; WCHAR* w = wstrDup(t); +// Results in w containing the wide-character text "hello world". +// For convenience, the WStr and CStr classes are also provided. These +// accept an existing (const) WCHAR* or char* null-terminated string and +// create a read-only copy of that in the desired format. The new copy +// will actually be the original copy if the format has not changed, otherwise +// it will be a new buffer owned by the WStr/CStr. + +// In addition to providing wide character functions, this header defines +// TCHAR* handling classes & functions. TCHAR is defined at compile time to +// either char or WCHAR. Programs can treat this as a third data type and +// call TStr() whenever a TCHAR* is required but a char* or WCHAR* is supplied, +// and TStr will do the right thing. + +#ifndef __RFB_WIN32_TCHARARRAY_H__ +#define __RFB_WIN32_TCHARARRAY_H__ + +#define WIN32_LEAN_AND_MEAN +#include <tchar.h> + +#include <rfb/util.h> + +namespace rfb { + + // -=- String duplication and cleanup functions. + // These routines also handle conversion between WCHAR* and char* + + char* strDup(const WCHAR* s); + WCHAR* wstrDup(const WCHAR* s); + WCHAR* wstrDup(const char* s); + void wstrFree(WCHAR* s); + + bool wstrSplit(const WCHAR* src, const WCHAR limiter, WCHAR** out1, WCHAR** out2, bool fromEnd=false); + bool wstrContains(const WCHAR* src, WCHAR c); + + // -=- Temporary format conversion classes + // CStr accepts WCHAR* or char* and behaves like a char* + // WStr accepts WCHAR* or char* and behaves like a WCHAR* + + struct WStr { + WStr(const char* s) : buf(wstrDup(s)), free_(true) {} + WStr(const WCHAR* s) : buf(s), free_(false) {} + ~WStr() {if (free_) wstrFree((WCHAR*)buf);} + operator const WCHAR*() {return buf;} + const WCHAR* buf; + bool free_; + }; + + struct CStr { + CStr(const char* s) : buf(s), free_(false) {} + CStr(const WCHAR* s) : buf(strDup(s)), free_(true) {} + ~CStr() {if (free_) strFree((char*)buf);} + operator const char*() {return buf;} + const char* buf; + bool free_; + }; + + // -=- Class to handle cleanup of arrays of native Win32 characters + class WCharArray { + public: + WCharArray() : buf(0) {} + WCharArray(char* str) : buf(wstrDup(str)) {strFree(str);} // note: assumes ownership + WCharArray(WCHAR* str) : buf(str) {} // note: assumes ownership + WCharArray(int len) { + buf = new WCHAR[len]; + } + ~WCharArray() { + delete [] buf; + } + // Get the buffer pointer & clear it (i.e. caller takes ownership) + WCHAR* takeBuf() {WCHAR* tmp = buf; buf = 0; return tmp;} + void replaceBuf(WCHAR* str) {delete [] buf; buf = str;} + WCHAR* buf; + }; + +#ifdef _UNICODE +#define tstrDup wstrDup +#define tstrFree wstrFree +#define tstrSplit wstrSplit +#define tstrContains wstrContains + typedef WCharArray TCharArray; + typedef WStr TStr; +#else +#define tstrDup strDup +#define tstrFree strFree +#define tstrSplit strSplit +#define tstrContains strContains + typedef CharArray TCharArray; + typedef CStr TStr; +#endif + +}; + +#endif
\ No newline at end of file diff --git a/rfb_win32/TrayIcon.h b/rfb_win32/TrayIcon.h new file mode 100644 index 00000000..85680f3f --- /dev/null +++ b/rfb_win32/TrayIcon.h @@ -0,0 +1,90 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- CView.h + +// An instance of the CView class is created for each VNC Viewer connection. + +#ifndef __RFB_WIN32_TRAY_ICON_H__ +#define __RFB_WIN32_TRAY_ICON_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <shellapi.h> +#include <rfb_win32/MsgWindow.h> +#include <rdr/Exception.h> + +namespace rfb { + + namespace win32 { + + class TrayIcon : public MsgWindow { + public: + TrayIcon() : MsgWindow(_T("VNCTray")) { +#ifdef NOTIFYICONDATA_V1_SIZE + nid.cbSize = NOTIFYICONDATA_V1_SIZE; +#else + nid.cbSize = sizeof(NOTIFYICONDATA); +#endif + + nid.hWnd = getHandle(); + nid.uID = 0; + nid.hIcon = 0; + nid.uFlags = NIF_ICON | NIF_MESSAGE; + nid.uCallbackMessage = WM_USER; + } + virtual ~TrayIcon() { + remove(); + } + bool setIcon(UINT icon) { + if (icon == 0) { + return remove(); + } else { + nid.hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(icon), + IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + return refresh(); + } + } + bool setToolTip(const TCHAR* text) { + if (text == 0) { + nid.uFlags &= ~NIF_TIP; + } else { + const int tipLen = sizeof(nid.szTip)/sizeof(TCHAR); + _tcsncpy(nid.szTip, text, tipLen); + nid.szTip[tipLen-1] = 0; + nid.uFlags |= NIF_TIP; + } + return refresh(); + } + bool remove() { + return Shell_NotifyIcon(NIM_DELETE, &nid) != 0; + } + bool refresh() { + return Shell_NotifyIcon(NIM_MODIFY, &nid) || Shell_NotifyIcon(NIM_ADD, &nid); + } + protected: + NOTIFYICONDATA nid; + }; + + }; + +}; + +#endif + + diff --git a/rfb_win32/WMCursor.cxx b/rfb_win32/WMCursor.cxx new file mode 100644 index 00000000..871d9376 --- /dev/null +++ b/rfb_win32/WMCursor.cxx @@ -0,0 +1,98 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMCursor.cxx + +// *** DOESN'T SEEM TO WORK WITH GetCursorInfo POS CODE BUILT-IN UNDER NT4SP6 +// *** INSTEAD, WE LOOK FOR Win2000/Win98 OR ABOVE +#define WINVER 0x0500 + +#include <rfb_win32/WMCursor.h> +#include <rfb_win32/OSVersion.h> +#include <rfb_win32/Win32Util.h> +#include <rfb/Exception.h> +#include <rfb/LogWriter.h> + +using namespace rdr; +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("WMCursor"); + + +typedef BOOL (WINAPI *_GetCursorInfo_proto)(PCURSORINFO pci); +DynamicFn<_GetCursorInfo_proto> _GetCursorInfo(_T("user32.dll"), "GetCursorInfo"); + + +WMCursor::WMCursor() : hooks(0), library(0), use_getCursorInfo(false), cursor(0) { +#if (WINVER >= 0x0500) + // Check the OS version + bool is_win98 = (osVersion.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) && + (osVersion.dwMajorVersion > 4) || ((osVersion.dwMajorVersion == 4) && (osVersion.dwMinorVersion > 0)); + bool is_win2K = (osVersion.dwPlatformId == VER_PLATFORM_WIN32_NT) && (osVersion.dwMajorVersion >= 5); + + // Use GetCursorInfo if OS version is sufficient + use_getCursorInfo = (is_win98 || is_win2K) && _GetCursorInfo.isValid(); +#else +#pragma message ("not building in GetCursorInfo support") +#endif + if (!use_getCursorInfo) { + hooks = new WMCursorHooks(); + if (hooks && hooks->start()) { + vlog.info("falling back to cursor hooking"); + } else { + delete hooks; + hooks = 0; + vlog.error("unable to monitor cursor shape"); + } + } else { + vlog.info("using GetCursorInfo"); + } + cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); +} + +WMCursor::~WMCursor() { + if (hooks) delete hooks; + if (library) FreeLibrary(library); +} + +WMCursor::Info +WMCursor::getCursorInfo() { + Info result; +#if (WINVER >= 0x0500) + if (use_getCursorInfo) { + CURSORINFO info; + info.cbSize = sizeof(CURSORINFO); + if ((*_GetCursorInfo)(&info)) { + result.cursor = info.hCursor; + result.position = Point(info.ptScreenPos.x, info.ptScreenPos.y); + result.visible = info.flags & CURSOR_SHOWING; + return result; + } + } +#endif + // Fall back to the old way of doing things + POINT pos; + if (hooks) cursor = hooks->getCursor(); + result.cursor = cursor; + result.visible = cursor != 0; + GetCursorPos(&pos); + result.position.x = pos.x; + result.position.y = pos.y; + return result; +} diff --git a/rfb_win32/WMCursor.h b/rfb_win32/WMCursor.h new file mode 100644 index 00000000..a96822a7 --- /dev/null +++ b/rfb_win32/WMCursor.h @@ -0,0 +1,65 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMCursor.h + +// WMCursor provides a single API through which the cursor state can be obtained +// The underlying implementation will use either GetCursorInfo, or use the +// wm_hooks library if GetCursorInfo is not available. + +#ifndef __RFB_WIN32_WM_CURSOR_H__ +#define __RFB_WIN32_WM_CURSOR_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <rfb_win32/WMHooks.h> + +namespace rfb { + + namespace win32 { + + class WMCursor { + public: + WMCursor(); + ~WMCursor(); + + struct Info { + HCURSOR cursor; + Point position; + bool visible; + Info() : cursor(0), visible(false) {} + bool operator!=(const Info& info) { + return ((cursor != info.cursor) || + (!position.equals(info.position)) || + (visible != info.visible)); + } + }; + + Info getCursorInfo(); + protected: + WMCursorHooks* hooks; + HMODULE library; + bool use_getCursorInfo; + HCURSOR cursor; + }; + + }; + +}; + +#endif // __RFB_WIN32_WM_CURSOR_H__ diff --git a/rfb_win32/WMHooks.cxx b/rfb_win32/WMHooks.cxx new file mode 100644 index 00000000..26a2363c --- /dev/null +++ b/rfb_win32/WMHooks.cxx @@ -0,0 +1,324 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMHooks.cxx + +#include <wm_hooks/wm_hooks.h> + +#include <rfb_win32/WMHooks.h> +#include <rfb_win32/Service.h> +#include <rfb/Threading.h> +#include <rfb/LogWriter.h> + +#include <list> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("WMHooks"); + +class WMHooksThread : public Thread { +public: + WMHooksThread() : Thread("WMHookThread"), active(true) {} + virtual void run(); + virtual Thread* join(); +protected: + bool active; +}; + +WMHooksThread* hook_mgr = 0; +std::list<WMHooks*> hooks; +std::list<WMCursorHooks*> cursor_hooks; +Mutex hook_mgr_lock; +HCURSOR hook_cursor = (HCURSOR)LoadImage(0, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED); + + +bool +StartHookThread() { + if (hook_mgr) return true; + vlog.debug("opening hook thread"); + hook_mgr = new WMHooksThread(); + if (!WM_Hooks_Install(hook_mgr->getThreadId(), 0)) { + vlog.error("failed to initialise hooks"); + delete hook_mgr->join(); + hook_mgr = 0; + return false; + } + hook_mgr->start(); + return true; +} + +void +StopHookThread() { + if (!hook_mgr) return; + if (!hooks.empty() || !cursor_hooks.empty()) return; + vlog.debug("closing hook thread"); + delete hook_mgr->join(); + hook_mgr = 0; +} + + +bool +AddHook(WMHooks* hook) { + vlog.debug("adding hook"); + Lock l(hook_mgr_lock); + if (!StartHookThread()) return false; + hooks.push_back(hook); + return true; +} + +bool +AddCursorHook(WMCursorHooks* hook) { + vlog.debug("adding cursor hook"); + Lock l(hook_mgr_lock); + if (cursor_hooks.empty()) WM_Hooks_EnableCursorShape(TRUE); + if (!StartHookThread()) return false; + cursor_hooks.push_back(hook); + return true; +} + +bool +RemHook(WMHooks* hook) { + { + vlog.debug("removing hook"); + Lock l(hook_mgr_lock); + hooks.remove(hook); + } + StopHookThread(); + return true; +} + +bool +RemCursorHook(WMCursorHooks* hook) { + { + vlog.debug("removing cursor hook"); + Lock l(hook_mgr_lock); + cursor_hooks.remove(hook); + } + StopHookThread(); + if (cursor_hooks.empty()) WM_Hooks_EnableCursorShape(FALSE); + return true; +} + +void +NotifyHooksRegion(const Region& r) { + Lock l(hook_mgr_lock); + std::list<WMHooks*>::iterator i; + for (i=hooks.begin(); i!=hooks.end(); i++) { + (*i)->new_changes.add_changed(r); + if (!(*i)->notified) { + (*i)->notified = true; + PostMessage((*i)->getHandle(), WM_USER, 0, 0); + } + } +} + +void +NotifyHooksCursor(HCURSOR c) { + Lock l(hook_mgr_lock); + hook_cursor = c; +} + +void +WMHooksThread::run() { + UINT windowMsg = WM_Hooks_WindowChanged(); + UINT clientAreaMsg = WM_Hooks_WindowClientAreaChanged(); + UINT borderMsg = WM_Hooks_WindowBorderChanged(); + UINT rectangleMsg = WM_Hooks_RectangleChanged(); + UINT cursorMsg = WM_Hooks_CursorChanged(); +#ifdef _DEBUG + UINT diagnosticMsg = WM_Hooks_Diagnostic(); +#endif + MSG msg; + RECT wrect; + HWND hwnd; + int count = 0; + + vlog.debug("starting hook thread"); + + while (active && GetMessage(&msg, NULL, 0, 0)) { + count++; + if (msg.message == windowMsg) { + hwnd = (HWND) msg.lParam; + if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) && + GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) + { + NotifyHooksRegion(Rect(wrect.left, wrect.top, + wrect.right, wrect.bottom)); + + } + } else if (msg.message == clientAreaMsg) { + hwnd = (HWND) msg.lParam; + if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) && + GetClientRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) + { + POINT pt = {0,0}; + if (ClientToScreen(hwnd, &pt)) { + NotifyHooksRegion(Rect(wrect.left+pt.x, wrect.top+pt.y, + wrect.right+pt.x, wrect.bottom+pt.y)); + } + } + } else if (msg.message == borderMsg) { + hwnd = (HWND) msg.lParam; + if (IsWindow(hwnd) && IsWindowVisible(hwnd) && !IsIconic(hwnd) && + GetWindowRect(hwnd, &wrect) && !IsRectEmpty(&wrect)) + { + Region changed(Rect(wrect.left, wrect.top, wrect.right, wrect.bottom)); + RECT crect; + POINT pt = {0,0}; + if (GetClientRect(hwnd, &crect) && ClientToScreen(hwnd, &pt) && + !IsRectEmpty(&crect)) + { + changed.assign_subtract(Rect(crect.left+pt.x, crect.top+pt.y, + crect.right+pt.x, crect.bottom+pt.y)); + } + NotifyHooksRegion(changed); + } + } else if (msg.message == rectangleMsg) { + Rect r = Rect(LOWORD(msg.wParam), HIWORD(msg.wParam), + LOWORD(msg.lParam), HIWORD(msg.lParam)); + if (!r.is_empty()) { + NotifyHooksRegion(r); + } + } else if (msg.message == cursorMsg) { + NotifyHooksCursor((HCURSOR)msg.lParam); +#ifdef _DEBUG + } else if (msg.message == diagnosticMsg) { + vlog.info("DIAG msg=%x(%d) wnd=%lx", msg.wParam, msg.wParam, msg.lParam); +#endif + } + } + + vlog.debug("stopping hook thread - processed %d events", count); + WM_Hooks_Remove(getThreadId()); +} + +Thread* +WMHooksThread::join() { + vlog.debug("stopping WMHooks thread"); + active = false; + PostThreadMessage(thread_id, WM_QUIT, 0, 0); + vlog.debug("joining WMHooks thread"); + return Thread::join(); +} + +// -=- WMHooks class + +rfb::win32::WMHooks::WMHooks() + : clipper(0), new_changes(true), fg_window(0), + notified(false), MsgWindow(_T("WMHooks")) { +} + +rfb::win32::WMHooks::~WMHooks() { + RemHook(this); + if (clipper) delete clipper; +} + +LRESULT +rfb::win32::WMHooks::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_USER: + { + // *** Yield, to allow the triggering update event to be processed + // BEFORE we try to grab the resulting changes. + // *** IMPROVES THINGS NOTICABLY ON WinXP + Sleep(0); + // *** + + Lock l(hook_mgr_lock); + notified = false; + new_changes.get_update(*clipper); + new_changes.clear(); + } + break; + } + return MsgWindow::processMessage(msg, wParam, lParam); +} + +bool +rfb::win32::WMHooks::setClipRect(const Rect& r) { + clip_region = r; + if (clipper) clipper->set_clip_region(clip_region); + return true; +} + +bool +rfb::win32::WMHooks::setUpdateTracker(UpdateTracker* ut) { + if (clipper) delete clipper; + clipper = new ClippedUpdateTracker(*ut); + clipper->set_clip_region(clip_region); + return AddHook(this); +} + +#ifdef _DEBUG +void +rfb::win32::WMHooks::setDiagnosticRange(UINT min, UINT max) { + WM_Hooks_SetDiagnosticRange(min, max); +} +#endif + + +// -=- WMBlockInput class + +Mutex blockMutex; +int blockCount = 0; + +rfb::win32::WMBlockInput::WMBlockInput() : active(false) { +} + +rfb::win32::WMBlockInput::~WMBlockInput() { + blockInputs(false); +} + +bool rfb::win32::WMBlockInput::blockInputs(bool on) { + if (on == active) return true; + vlog.debug("blockInput changed"); + Lock l(blockMutex); + int newCount = blockCount; + if (on) + newCount++; + else + newCount--; + if (WM_Hooks_EnableRealInputs(newCount==0, newCount==0)) { + vlog.debug("set blocking to %d", newCount); + blockCount = newCount; + active = on; + return true; + } + return false; +} + + +// -=- WMCursorHooks class + +rfb::win32::WMCursorHooks::WMCursorHooks() { +} + +rfb::win32::WMCursorHooks::~WMCursorHooks() { + RemCursorHook(this); +} + +bool +rfb::win32::WMCursorHooks::start() { + return AddCursorHook(this); +} + +HCURSOR +rfb::win32::WMCursorHooks::getCursor() const { + return hook_cursor; +} diff --git a/rfb_win32/WMHooks.h b/rfb_win32/WMHooks.h new file mode 100644 index 00000000..791df763 --- /dev/null +++ b/rfb_win32/WMHooks.h @@ -0,0 +1,85 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMHooks.h + +#ifndef __RFB_WIN32_WM_HOOKS_H__ +#define __RFB_WIN32_WM_HOOKS_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <rfb/UpdateTracker.h> +#include <rdr/Exception.h> +#include <rfb_win32/MsgWindow.h> + +namespace rfb { + + namespace win32 { + + class WMHooks : public MsgWindow { + public: + WMHooks(); + ~WMHooks(); + + bool setClipRect(const Rect& cr); + bool setUpdateTracker(UpdateTracker* ut); + + virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam); + +#ifdef _DEBUG + // Get notifications of any messages in the given range, to any hooked window + void setDiagnosticRange(UINT min, UINT max); +#endif + + protected: + ClippedUpdateTracker* clipper; + Region clip_region; + + void* fg_window; + Rect fg_window_rect; + + public: + SimpleUpdateTracker new_changes; + bool notified; + }; + + class WMBlockInput { + public: + WMBlockInput(); + ~WMBlockInput(); + bool blockInputs(bool block); + protected: + bool active; + }; + + // - Legacy cursor handling support + class WMCursorHooks { + public: + WMCursorHooks(); + ~WMCursorHooks(); + + bool start(); + + HCURSOR getCursor() const; + }; + + }; + +}; + +#endif // __RFB_WIN32_WM_HOOKS_H__ diff --git a/rfb_win32/WMNotifier.cxx b/rfb_win32/WMNotifier.cxx new file mode 100644 index 00000000..9773abf5 --- /dev/null +++ b/rfb_win32/WMNotifier.cxx @@ -0,0 +1,57 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMNotifier.cxx + +#include <rfb_win32/WMNotifier.h> +#include <rfb_win32/WMShatter.h> +#include <rfb_win32/MsgWindow.h> + +#include <rfb/LogWriter.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("WMMonitor"); + + +WMMonitor::WMMonitor() : MsgWindow(_T("WMMonitor")) { +} + +WMMonitor::~WMMonitor() { +} + + +LRESULT +WMMonitor::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { + switch (msg) { + case WM_DISPLAYCHANGE: + if (notifier) { + notifier->notifyDisplayEvent(Notifier::DisplaySizeChanged); + notifier->notifyDisplayEvent(Notifier::DisplayPixelFormatChanged); + } + break; + case WM_SYSCOLORCHANGE: + case WM_PALETTECHANGED: + if (notifier) { + notifier->notifyDisplayEvent(Notifier::DisplayColourMapChanged); + } + break; + }; + return MsgWindow::processMessage(msg, wParam, lParam); +} diff --git a/rfb_win32/WMNotifier.h b/rfb_win32/WMNotifier.h new file mode 100644 index 00000000..564d176f --- /dev/null +++ b/rfb_win32/WMNotifier.h @@ -0,0 +1,68 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMNotifier.h +// +// The WMNotifier is used to get callbacks indicating changes in the state +// of the system, for instance in the size/format/palette of the display. +// The WMNotifier contains a Win32 window, which receives notifications of +// system events and stores them. Whenever processEvent is called, any +// incoming events are processed and the appropriate notifier called. + +#ifndef __RFB_WIN32_NOTIFIER_H__ +#define __RFB_WIN32_NOTIFIER_H__ + +#include <rfb/SDesktop.h> +#include <rfb/Threading.h> +#include <rfb_win32/MsgWindow.h> +#include <rfb_win32/DeviceFrameBuffer.h> +#include <rfb_win32/SInput.h> + +namespace rfb { + + namespace win32 { + + // -=- Window Message Monitor implementation + + class WMMonitor : MsgWindow { + public: + + class Notifier { + public: + typedef enum {DisplaySizeChanged, DisplayColourMapChanged, + DisplayPixelFormatChanged} DisplayEventType; + virtual void notifyDisplayEvent(DisplayEventType evt) = 0; + }; + + WMMonitor(); + virtual ~WMMonitor(); + + void setNotifier(Notifier* wmn) {notifier=wmn;} + + protected: + // - Internal MsgWindow callback + virtual LRESULT processMessage(UINT msg, WPARAM wParam, LPARAM lParam); + + Notifier* notifier; + }; + + }; + +}; + +#endif // __RFB_WIN32_WMNOTIFIER_H__ diff --git a/rfb_win32/WMPoller.cxx b/rfb_win32/WMPoller.cxx new file mode 100644 index 00000000..f568b211 --- /dev/null +++ b/rfb_win32/WMPoller.cxx @@ -0,0 +1,101 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMPoller.cxx + +#include <rfb_win32/WMPoller.h> +#include <rfb/Exception.h> +#include <rfb/LogWriter.h> +#include <rfb/Configuration.h> + +#include <tchar.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("WMPoller"); + +BoolParameter rfb::win32::WMPoller::poll_console_windows("PollConsoleWindows", + "Server should poll console windows for updates", true); + +// -=- WMPoller class + +rfb::win32::WMPoller::WMPoller() : clipper(0) { +} + +rfb::win32::WMPoller::~WMPoller() { + if (clipper) delete clipper; +} + +bool +rfb::win32::WMPoller::processEvent() { + PollInfo info; + if (clipper && poll_console_windows) { + ::EnumWindows(WMPoller::enumWindowProc, (LPARAM) &info); + clipper->add_changed(info.poll_include); + } + return false; +} + +bool +rfb::win32::WMPoller::setClipRect(const Rect& r) { + clip_region = r; + if (clipper) clipper->set_clip_region(clip_region); + return true; +} + +bool +rfb::win32::WMPoller::setUpdateTracker(UpdateTracker* ut) { + if (clipper) delete clipper; + clipper = new ClippedUpdateTracker(*ut); + clipper->set_clip_region(clip_region); + return true; +} + +bool +rfb::win32::WMPoller::checkPollWindow(HWND w) { + TCHAR buffer[128]; + if (!GetClassName(w, buffer, 128)) + throw rdr::SystemException("unable to get window class:%u", GetLastError()); + if ((_tcscmp(buffer, _T("tty")) != 0) && + (_tcscmp(buffer, _T("ConsoleWindowClass")) != 0)) { + return false; + } + return true; +} + +void +rfb::win32::WMPoller::pollWindow(HWND w, PollInfo* i) { + RECT r; + if (IsWindowVisible(w) && GetWindowRect(w, &r)) { + if (IsRectEmpty(&r)) return; + Region wrgn(Rect(r.left, r.top, r.right, r.bottom)); + if (checkPollWindow(w)) { + wrgn.assign_subtract(i->poll_exclude); + i->poll_include.assign_union(wrgn); + } else { + i->poll_exclude.assign_union(wrgn); + } + } +} + +BOOL CALLBACK +rfb::win32::WMPoller::enumWindowProc(HWND w, LPARAM lp) { + pollWindow(w, (PollInfo*)lp); + return TRUE; +} diff --git a/rfb_win32/WMPoller.h b/rfb_win32/WMPoller.h new file mode 100644 index 00000000..3f3f402a --- /dev/null +++ b/rfb_win32/WMPoller.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMPoller.h +// +// Polls the foreground window. If the pollOnlyConsoles flag is set, +// then checks the window class of the foreground window first and +// only polls it if it's a console. +// If the pollAllWindows flag is set then iterates through visible +// windows, and polls the visible bits. If pollOnlyConsoles is also +// set then only visible parts of console windows will be polled. + +#ifndef __RFB_WIN32_WM_POLLER_H__ +#define __RFB_WIN32_WM_POLLER_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <rfb/UpdateTracker.h> +#include <rfb/Configuration.h> + +namespace rfb { + + namespace win32 { + + class WMPoller { + public: + WMPoller(); + ~WMPoller(); + + bool processEvent(); + bool setClipRect(const Rect& cr); + bool setUpdateTracker(UpdateTracker* ut); + + static BoolParameter poll_console_windows; + protected: + struct PollInfo { + Region poll_include; + Region poll_exclude; + }; + static bool checkPollWindow(HWND w); + static void pollWindow(HWND w, PollInfo* info); + static BOOL CALLBACK enumWindowProc(HWND w, LPARAM lp); + + ClippedUpdateTracker* clipper; + Region clip_region; + }; + + }; + +}; + +#endif // __RFB_WIN32_WM_POLLER_H__ diff --git a/rfb_win32/WMShatter.cxx b/rfb_win32/WMShatter.cxx new file mode 100644 index 00000000..f6a74848 --- /dev/null +++ b/rfb_win32/WMShatter.cxx @@ -0,0 +1,57 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMShatter.cxx + +#include <rfb_win32/WMShatter.h> + +#include <rfb/LogWriter.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("WMShatter"); + +bool +rfb::win32::IsSafeWM(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { + bool result = true; + switch (msg) { + // - UNSAFE MESSAGES + case WM_TIMER: + result = lParam == 0; + break; + }; + if (!result) { + vlog.info("IsSafeWM: 0x%x received 0x%x(%u, %lu) - not safe", window, msg, wParam, lParam); + } + return result; +} + +LRESULT +rfb::win32::SafeDefWindowProc(HWND window, UINT msg, WPARAM wParam, LPARAM lParam) { + if (IsSafeWM(window, msg, wParam, lParam)) + return DefWindowProc(window, msg, wParam, lParam); + return 0; +} + +LRESULT +rfb::win32::SafeDispatchMessage(const MSG* msg) { + if (IsSafeWM(msg->hwnd, msg->message, msg->wParam, msg->lParam)) + return DispatchMessage(msg); + return 0; +} diff --git a/rfb_win32/WMShatter.h b/rfb_win32/WMShatter.h new file mode 100644 index 00000000..7b81678f --- /dev/null +++ b/rfb_win32/WMShatter.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMShatter.h +// +// WMShatter provides the IsSafeWM routine, which returns true iff the +// supplied window message is safe to pass to DispatchMessage, or to +// process in the window procedure. +// +// This is only required, of course, to avoid so-called "shatter" attacks +// to be made against the VNC server, which take advantage of the noddy +// design of the Win32 window messaging system. +// +// The API here is designed to hopefully be future proof, so that if they +// ever come up with a proper way to determine whether a message is safe +// or not then it can just be reimplemented here... + +#ifndef __RFB_WIN32_SHATTER_H__ +#define __RFB_WIN32_SHATTER_H__ + +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +namespace rfb { + + namespace win32 { + + bool IsSafeWM(HWND window, UINT msg, WPARAM wParam, LPARAM lParam); + + LRESULT SafeDefWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam); + + LRESULT SafeDispatchMessage(const MSG* msg); + + }; + +}; + +#endif // __RFB_WIN32_SHATTER_H__ diff --git a/rfb_win32/WMWindowCopyRect.cxx b/rfb_win32/WMWindowCopyRect.cxx new file mode 100644 index 00000000..46d85eac --- /dev/null +++ b/rfb_win32/WMWindowCopyRect.cxx @@ -0,0 +1,83 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMCopyRect.cxx + +#include <rfb_win32/WMWindowCopyRect.h> +#include <rfb/LogWriter.h> +#include <windows.h> + +using namespace rfb; +using namespace rfb::win32; + +static LogWriter vlog("WMCopyRect"); + +// -=- WMHooks class + +rfb::win32::WMCopyRect::WMCopyRect() : clipper(0), fg_window(0) { +} + +rfb::win32::WMCopyRect::~WMCopyRect() { + if (clipper) delete clipper; +} + +bool +rfb::win32::WMCopyRect::processEvent() { + if (clipper) { + // See if the foreground window has moved + HWND window = GetForegroundWindow(); + if (window) { + RECT wrect; + if (IsWindow(window) && IsWindowVisible(window) && GetWindowRect(window, &wrect)) { + Rect winrect(wrect.left, wrect.top, wrect.right, wrect.bottom); + if (fg_window == window) { + + if (!fg_window_rect.tl.equals(winrect.tl)) { + // Window has moved - send a copyrect event to the client + Point delta = Point(winrect.tl.x-fg_window_rect.tl.x, winrect.tl.y-fg_window_rect.tl.y); + Region copy_dest = winrect; + clipper->add_copied(copy_dest, delta); + clipper->add_changed(Region(fg_window_rect).subtract(copy_dest)); + } + } + fg_window = window; + fg_window_rect = winrect; + } else { + fg_window = 0; + } + } else { + fg_window = 0; + } + } + return false; +} + +bool +rfb::win32::WMCopyRect::setClipRect(const Rect& r) { + clip_region = r; + if (clipper) clipper->set_clip_region(clip_region); + return true; +} + +bool +rfb::win32::WMCopyRect::setUpdateTracker(UpdateTracker* ut) { + if (clipper) delete clipper; + clipper = new ClippedUpdateTracker(*ut); + clipper->set_clip_region(clip_region); + return true; +} diff --git a/rfb_win32/WMWindowCopyRect.h b/rfb_win32/WMWindowCopyRect.h new file mode 100644 index 00000000..0750d86e --- /dev/null +++ b/rfb_win32/WMWindowCopyRect.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- WMWindowCopyRect.h +// +// Helper class which produces copyRect actions by monitoring the location +// of the current foreground window. +// Whenever processEvent is called, the foreground window's position is +// recalculated and a copy event flushed to the supplied UpdateTracker +// if appropriate. + +#ifndef __RFB_WIN32_WM_WINDOW_COPYRECT_H__ +#define __RFB_WIN32_WM_WINDOW_COPYRECT_H__ + +#include <rfb/UpdateTracker.h> + +namespace rfb { + + namespace win32 { + + class WMCopyRect { + public: + WMCopyRect(); + ~WMCopyRect(); + + bool processEvent(); + bool setClipRect(const Rect& cr); + bool setUpdateTracker(UpdateTracker* ut); + + protected: + ClippedUpdateTracker* clipper; + Region clip_region; + void* fg_window; + Rect fg_window_rect; + }; + + }; + +}; + +#endif // __RFB_WIN32_WM_WINDOW_COPYRECT_H__ diff --git a/rfb_win32/Win32Util.cxx b/rfb_win32/Win32Util.cxx new file mode 100644 index 00000000..e25f43ad --- /dev/null +++ b/rfb_win32/Win32Util.cxx @@ -0,0 +1,447 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// Win32Util.cxx + +#include <rfb_win32/Win32Util.h> +#include <rdr/Exception.h> +#include <rdr/HexOutStream.h> + + +namespace rfb { +namespace win32 { + +LogicalPalette::LogicalPalette() : palette(0), numEntries(0) { + BYTE buf[sizeof(LOGPALETTE)+256*sizeof(PALETTEENTRY)]; + LOGPALETTE* logpal = (LOGPALETTE*)buf; + logpal->palVersion = 0x300; + logpal->palNumEntries = 256; + for (int i=0; i<256;i++) { + logpal->palPalEntry[i].peRed = 0; + logpal->palPalEntry[i].peGreen = 0; + logpal->palPalEntry[i].peBlue = 0; + logpal->palPalEntry[i].peFlags = 0; + } + palette = CreatePalette(logpal); + if (!palette) + throw rdr::SystemException("failed to CreatePalette", GetLastError()); +} + +LogicalPalette::~LogicalPalette() { + if (palette) + if (!DeleteObject(palette)) + throw rdr::SystemException("del palette failed", GetLastError()); +} + +void LogicalPalette::setEntries(int start, int count, const Colour* cols) { + if (numEntries < count) { + ResizePalette(palette, start+count); + numEntries = start+count; + } + PALETTEENTRY* logpal = new PALETTEENTRY[count]; + for (int i=0; i<count; i++) { + logpal[i].peRed = cols[i].r >> 8; + logpal[i].peGreen = cols[i].g >> 8; + logpal[i].peBlue = cols[i].b >> 8; + logpal[i].peFlags = 0; + } + UnrealizeObject(palette); + SetPaletteEntries(palette, start, count, logpal); + delete [] logpal; +} + + +static LogWriter dcLog("DeviceContext"); + +PixelFormat DeviceContext::getPF() const { + return getPF(dc); +} + +PixelFormat DeviceContext::getPF(HDC dc) { + PixelFormat format; + CompatibleBitmap bitmap(dc, 1, 1); + + // -=- Get the bitmap format information + BitmapInfo bi; + memset(&bi, 0, sizeof(bi)); + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biBitCount = 0; + + if (!::GetDIBits(dc, bitmap, 0, 1, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS)) { + throw rdr::SystemException("unable to determine device pixel format", GetLastError()); + } + if (!::GetDIBits(dc, bitmap, 0, 1, NULL, (BITMAPINFO*)&bi, DIB_RGB_COLORS)) { + throw rdr::SystemException("unable to determine pixel shifts/palette", GetLastError()); + } + + // -=- Munge the bitmap info here + switch (bi.bmiHeader.biBitCount) { + case 1: + case 4: + bi.bmiHeader.biBitCount = 8; + break; + case 24: + bi.bmiHeader.biBitCount = 32; + break; + } + bi.bmiHeader.biPlanes = 1; + + format.trueColour = bi.bmiHeader.biBitCount > 8; + format.bigEndian = 0; + format.bpp = format.depth = bi.bmiHeader.biBitCount; + + if (format.trueColour) { + DWORD rMask=0, gMask=0, bMask=0; + + // Which true colour format is the DIB section using? + switch (bi.bmiHeader.biCompression) { + case BI_RGB: + // Default RGB layout + switch (bi.bmiHeader.biBitCount) { + case 16: + // RGB 555 - High Colour + dcLog.info("16-bit High Colour"); + rMask = 0x7c00; + bMask = 0x001f; + gMask = 0x03e0; + format.depth = 15; + break; + case 24: + case 32: + // RGB 888 - True Colour + dcLog.info("24/32-bit High Colour"); + rMask = 0xff0000; + gMask = 0x00ff00; + bMask = 0x0000ff; + format.depth = 24; + break; + default: + dcLog.error("bits per pixel %u not supported", bi.bmiHeader.biBitCount); + throw rdr::Exception("unknown bits per pixel specified"); + }; + break; + case BI_BITFIELDS: + // Custom RGB layout + rMask = bi.mask.red; + gMask = bi.mask.green; + bMask = bi.mask.blue; + dcLog.info("BitFields format: %lu, (%lx, %lx, %lx)", + bi.bmiHeader.biBitCount, rMask, gMask, bMask); + if (format.bpp == 32) + format.depth = 24; // ...probably + break; + }; + + // Convert the data we just retrieved + initMaxAndShift(rMask, &format.redMax, &format.redShift); + initMaxAndShift(gMask, &format.greenMax, &format.greenShift); + initMaxAndShift(bMask, &format.blueMax, &format.blueShift); + } + + return format; +} + + +WindowDC::WindowDC(HWND wnd) : hwnd(wnd) { + dc = GetDC(wnd); + if (!dc) + throw rdr::SystemException("GetDC failed", GetLastError()); +} +WindowDC::~WindowDC() { + if (dc) + ReleaseDC(hwnd, dc); +} + + +CompatibleDC::CompatibleDC(HDC existing) { + dc = CreateCompatibleDC(existing); + if (!dc) + throw rdr::SystemException("CreateCompatibleDC failed", GetLastError()); +} +CompatibleDC::~CompatibleDC() { + if (dc) + DeleteDC(dc); +} + + +BitmapDC::BitmapDC(HDC hdc, HBITMAP hbitmap) : CompatibleDC(hdc){ + oldBitmap = (HBITMAP)SelectObject(dc, hbitmap); + if (!oldBitmap) + throw rdr::SystemException("SelectObject to CompatibleDC failed", + GetLastError()); +} +BitmapDC::~BitmapDC() { + SelectObject(dc, oldBitmap); +} + + +CompatibleBitmap::CompatibleBitmap(HDC hdc, int width, int height) { + hbmp = CreateCompatibleBitmap(hdc, width, height); + if (!hbmp) + throw rdr::SystemException("CreateCompatibleBitmap() failed", + GetLastError()); +} +CompatibleBitmap::~CompatibleBitmap() { + if (hbmp) DeleteObject(hbmp); +} + + +PaletteSelector::PaletteSelector(HDC dc, HPALETTE pal) : device(dc), redrawRequired(false) { + oldPal = SelectPalette(dc, pal, FALSE); + redrawRequired = RealizePalette(dc) > 0; +} +PaletteSelector::~PaletteSelector() { + if (oldPal) SelectPalette(device, oldPal, TRUE); +} + + +IconInfo::IconInfo(HICON icon) { + if (!GetIconInfo(icon, this)) + throw rdr::SystemException("GetIconInfo() failed", GetLastError()); +} +IconInfo::~IconInfo() { + if (hbmColor) + DeleteObject(hbmColor); + if (hbmMask) + DeleteObject(hbmMask); +} + + +ModuleFileName::ModuleFileName(HMODULE module) : TCharArray(MAX_PATH) { + if (!module) module = GetModuleHandle(0); + if (!GetModuleFileName(module, buf, MAX_PATH)) + buf[0] = 0; +} + + +FileVersionInfo::FileVersionInfo(const TCHAR* filename) { + // Get executable name + ModuleFileName exeName; + if (!filename) filename = exeName.buf; + + // Get version info size + DWORD handle; + int size = GetFileVersionInfoSize((TCHAR*)filename, &handle); + if (!size) + throw rdr::SystemException("GetVersionInfoSize failed", GetLastError()); + + // Get version info + buf = new TCHAR[size]; + if (!GetFileVersionInfo((TCHAR*)filename, handle, size, buf)) + throw rdr::SystemException("GetVersionInfo failed", GetLastError()); +} + +const TCHAR* FileVersionInfo::getVerString(const TCHAR* name, DWORD langId) { + char langIdBuf[sizeof(langId)]; + for (int i=sizeof(langIdBuf)-1; i>=0; i--) { + langIdBuf[i] = langId & 0xff; + langId = langId >> 8; + } + + TCharArray langIdStr = rdr::HexOutStream::binToHexStr(langIdBuf, sizeof(langId)); + TCharArray infoName(_tcslen(_T("StringFileInfo")) + 4 + _tcslen(name) + _tcslen(langIdStr.buf)); + _stprintf(infoName.buf, _T("\\StringFileInfo\\%s\\%s"), langIdStr.buf, name); + + // Locate the required version string within the version info + TCHAR* buffer = 0; + UINT length = 0; + if (!VerQueryValue(buf, infoName.buf, (void**)&buffer, &length)) { + printf("unable to find %s version string", CStr(infoName.buf)); + throw rdr::Exception("VerQueryValue failed"); + } + return buffer; +} + + +bool splitPath(const TCHAR* path, TCHAR** dir, TCHAR** file) { + return tstrSplit(path, '\\', dir, file, true); +} + + +static LogWriter dfbLog("DynamicFn"); + +DynamicFnBase::DynamicFnBase(const TCHAR* dllName, const char* fnName) : dllHandle(0), fnPtr(0) { + dllHandle = LoadLibrary(dllName); + if (!dllHandle) { + dfbLog.info("DLL %s not found (%d)", (const char*)CStr(dllName), GetLastError()); + return; + } + fnPtr = GetProcAddress(dllHandle, fnName); + if (!fnPtr) + dfbLog.info("proc %s not found in %s (%d)", fnName, (const char*)CStr(dllName), GetLastError()); +} + +DynamicFnBase::~DynamicFnBase() { + if (dllHandle) + FreeLibrary(dllHandle); +} + + +static LogWriter miLog("MonitorInfo"); + +MonitorInfo::MonitorInfo(HWND window) { +#if (WINVER >= 0x0500) + typedef HMONITOR (WINAPI *_MonitorFromWindow_proto)(HWND,DWORD); + rfb::win32::DynamicFn<_MonitorFromWindow_proto> _MonitorFromWindow(_T("user32.dll"), "MonitorFromWindow"); + typedef BOOL (WINAPI *_GetMonitorInfo_proto)(HMONITOR,LPMONITORINFO); + rfb::win32::DynamicFn<_GetMonitorInfo_proto> _GetMonitorInfo(_T("user32.dll"), "GetMonitorInfoA"); + + // Can we dynamically link to the monitor functions? + if (_MonitorFromWindow.isValid()) { + if (_GetMonitorInfo.isValid()) { + HMONITOR monitor = (*_MonitorFromWindow)(window, MONITOR_DEFAULTTONEAREST); + miLog.debug("monitor=%lx", monitor); + if (monitor) { + memset(this, 0, sizeof(MONITORINFOEXA)); + cbSize = sizeof(MONITORINFOEXA); + if ((*_GetMonitorInfo)(monitor, this)) { + miLog.debug("monitor is %d,%d-%d,%d", rcMonitor.left, rcMonitor.top, rcMonitor.right, rcMonitor.bottom); + miLog.debug("work area is %d,%d-%d,%d", rcWork.left, rcWork.top, rcWork.right, rcWork.bottom); + miLog.debug("device is \"%s\"", szDevice); + return; + } + miLog.error("failed to get monitor info: %ld", GetLastError()); + } + } else { + miLog.debug("GetMonitorInfo not found"); + } + } else { + miLog.debug("MonitorFromWindow not found"); + } +#else +#pragma message ("not building in GetMonitorInfo") + cbSize = sizeof(MonitorInfo); + szDevice[0] = 0; +#endif + + // Legacy fallbacks - just return the desktop settings + miLog.debug("using legacy fall-backs"); + HWND desktop = GetDesktopWindow(); + GetWindowRect(desktop, &rcMonitor); + SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWork, 0); + dwFlags = 0; +} + + +#if (WINVER >= 0x0500) + +struct moveToMonitorData { + HWND window; + const char* monitorName; +}; + +typedef BOOL (WINAPI *_GetMonitorInfo_proto)(HMONITOR,LPMONITORINFO); +static rfb::win32::DynamicFn<_GetMonitorInfo_proto> _GetMonitorInfo(_T("user32.dll"), "GetMonitorInfoA"); + +static BOOL CALLBACK moveToMonitorEnumProc(HMONITOR monitor, + HDC dc, + LPRECT pos, + LPARAM d) { + moveToMonitorData* data = (moveToMonitorData*)d; + MONITORINFOEXA info; + memset(&info, 0, sizeof(info)); + info.cbSize = sizeof(info); + + if ((*_GetMonitorInfo)(monitor, &info)) { + if (stricmp(data->monitorName, info.szDevice) == 0) { + SetWindowPos(data->window, 0, + info.rcMonitor.left, info.rcMonitor.top, + info.rcMonitor.right, info.rcMonitor.bottom, + SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER); + return FALSE; + } + } + + return TRUE; +} + +#endif + +void moveToMonitor(HWND handle, const char* device) { + miLog.debug("moveToMonitor %s", device); + +#if (WINVER >= 0x500) + typedef BOOL (WINAPI *_EnumDisplayMonitors_proto)(HDC, LPCRECT, MONITORENUMPROC, LPARAM); + rfb::win32::DynamicFn<_EnumDisplayMonitors_proto> _EnumDisplayMonitors(_T("user32.dll"), "EnumDisplayMonitors"); + if (!_EnumDisplayMonitors.isValid()) { + miLog.debug("EnumDisplayMonitors not found"); + return; + } + + moveToMonitorData data; + data.window = handle; + data.monitorName = device; + + (*_EnumDisplayMonitors)(0, 0, &moveToMonitorEnumProc, (LPARAM)&data); +#endif +} + + +void centerWindow(HWND handle, HWND parent, bool clipToParent) { + RECT r; + if (parent && IsWindowVisible(parent)) { + if (!GetWindowRect(parent, &r)) return; + } else { + MonitorInfo mi(handle); + r=mi.rcWork; + } + centerWindow(handle, r, clipToParent); +} + +void centerWindow(HWND handle, const RECT& r, bool clipToRect) { + RECT wr; + if (!GetWindowRect(handle, &wr)) return; + int w = wr.right-wr.left; + int h = wr.bottom-wr.top; + if (clipToRect) { + w = min(r.right-r.left, w); + h = min(r.bottom-r.top, h); + } + int x = (r.left + r.right - w)/2; + int y = (r.top + r.bottom - h)/2; + UINT flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | (clipToRect ? 0 : SWP_NOSIZE); + SetWindowPos(handle, 0, x, y, w, h, flags); +} + + +int MsgBox(HWND parent, const TCHAR* msg, UINT flags) { + const TCHAR* msgType = 0; + UINT tflags = flags & 0x70; + if (tflags == MB_ICONHAND) + msgType = _T("Error"); + else if (tflags == MB_ICONQUESTION) + msgType = _T("Question"); + else if (tflags == MB_ICONEXCLAMATION) + msgType = _T("Warning"); + else if (tflags == MB_ICONASTERISK) + msgType = _T("Information"); + flags |= MB_TOPMOST | MB_SETFOREGROUND; + int len = _tcslen(AppName.buf) + 1; + if (msgType) len += _tcslen(msgType) + 3; + TCharArray title = new TCHAR[len]; + _tcscpy(title.buf, AppName.buf); + if (msgType) { + _tcscat(title.buf, _T(" : ")); + _tcscat(title.buf, msgType); + } + return MessageBox(parent, msg, title.buf, flags); +} + + +}; +}; diff --git a/rfb_win32/Win32Util.h b/rfb_win32/Win32Util.h new file mode 100644 index 00000000..5f0ab5a0 --- /dev/null +++ b/rfb_win32/Win32Util.h @@ -0,0 +1,217 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// -=- Win32Util.h + +// Miscellaneous but useful Win32 API utility functions & classes. +// In particular, a set of classes which wrap GDI objects, +// and some to handle palettes. + +#ifndef __RFB_WIN32_GDIUTIL_H__ +#define __RFB_WIN32_GDIUTIL_H__ + +#include <rfb/ColourMap.h> +#include <rfb/PixelFormat.h> +#include <rfb/Rect.h> +#include <rfb_win32/TCharArray.h> + +namespace rfb { + + namespace win32 { + + class LogicalPalette { + public: + LogicalPalette(); + ~LogicalPalette(); + void setEntries(int start, int count, const Colour* cols); + HPALETTE getHandle() {return palette;} + protected: + HPALETTE palette; + int numEntries; + }; + + class DeviceContext { + public: + DeviceContext() : dc(0) {} + virtual ~DeviceContext() {} + operator HDC() const {return dc;} + PixelFormat getPF() const; + static PixelFormat getPF(HDC dc); + protected: + HDC dc; + }; + + class WindowDC : public DeviceContext { + public: + WindowDC(HWND wnd); + virtual ~WindowDC(); + protected: + HWND hwnd; + }; + + class CompatibleDC : public DeviceContext { + public: + CompatibleDC(HDC existing); + virtual ~CompatibleDC(); + }; + + class BitmapDC : public CompatibleDC { + public: + BitmapDC(HDC hdc, HBITMAP hbitmap); + ~BitmapDC(); + protected: + HBITMAP oldBitmap; + }; + + class CompatibleBitmap { + public: + CompatibleBitmap(HDC hdc, int width, int height); + virtual ~CompatibleBitmap(); + operator HBITMAP() const {return hbmp;} + protected: + HBITMAP hbmp; + }; + + struct BitmapInfo { + BITMAPINFOHEADER bmiHeader; + union { + struct { + DWORD red; + DWORD green; + DWORD blue; + } mask; + RGBQUAD color[256]; + }; + }; + + inline void initMaxAndShift(DWORD mask, int* max, int* shift) { + for ((*shift) = 0; (mask & 1) == 0; (*shift)++) mask >>= 1; + (*max) = (rdr::U16)mask; + } + + class PaletteSelector { + public: + PaletteSelector(HDC dc, HPALETTE pal); + ~PaletteSelector(); + bool isRedrawRequired() {return redrawRequired;} + protected: + HPALETTE oldPal; + HDC device; + bool redrawRequired; + }; + + struct IconInfo : public ICONINFO { + IconInfo(HICON icon); + ~IconInfo(); + }; + + struct ModuleFileName : public TCharArray { + ModuleFileName(HMODULE module=0); + }; + + struct FileVersionInfo : public TCharArray { + FileVersionInfo(const TCHAR* filename=0); + const TCHAR* getVerString(const TCHAR* name, DWORD langId = 0x080904b0); + }; + + bool splitPath(const TCHAR* path, TCHAR** dir, TCHAR** file); + + class DynamicFnBase { + public: + DynamicFnBase(const TCHAR* dllName, const char* fnName); + ~DynamicFnBase(); + bool isValid() const {return fnPtr != 0;} + protected: + void* fnPtr; + HMODULE dllHandle; + }; + + template<class T> class DynamicFn : public DynamicFnBase { + public: + DynamicFn(const TCHAR* dllName, const char* fnName) : DynamicFnBase(dllName, fnName) {} + T operator *() const {return (T)fnPtr;}; + }; + + // Structure containing info on the monitor nearest the window. + // Copes with multi-monitor OSes and older ones. +#if (WINVER >= 0x0500) + struct MonitorInfo : MONITORINFOEXA { + MonitorInfo(HWND hwnd); + }; +#else + struct MonitorInfo { + MonitorInfo(HWND hwnd); + DWORD cbSize; + RECT rcMonitor; + RECT rcWork; + DWORD dwFlags; + char szDevice[1]; // Always null... + }; +#endif + void moveToMonitor(HWND handle, const char* device); + + class Handle { + public: + Handle(HANDLE h_=0) : h(h_) {} + ~Handle() { + if (h) CloseHandle(h); + } + operator HANDLE() {return h;} + HANDLE h; + }; + + // Center the window to a rectangle, or to a parent window. + // Optionally, resize the window to lay within the rect or parent window + // If the parent window is NULL then the working area if the window's + // current monitor is used instead. + void centerWindow(HWND handle, const RECT& r, bool clipToRect=false); + void centerWindow(HWND handle, HWND parent, bool clipToRect=false); + + // MsgBox helper function. Define rfb::win32::AppName somewhere in your + // code and MsgBox will use its value in informational messages. + extern TStr AppName; + int MsgBox(HWND parent, const TCHAR* message, UINT flags); + + // Get the computer name + struct ComputerName : TCharArray { + ComputerName() : TCharArray(MAX_COMPUTERNAME_LENGTH+1) { + ULONG namelength = MAX_COMPUTERNAME_LENGTH+1; + if (!GetComputerName(buf, &namelength)) + _tcscpy(buf, _T("")); + } + }; + + // Allocate and/or manage LocalAlloc memory. + struct LocalMem { + LocalMem(int size) : ptr(LocalAlloc(LMEM_FIXED, size)) { + if (!ptr) throw rdr::SystemException("LocalAlloc", GetLastError()); + } + LocalMem(void* p) : ptr(p) {} + ~LocalMem() {LocalFree(ptr);} + operator void*() {return ptr;} + void* takePtr() { + void* t = ptr; ptr = 0; return t; + } + void* ptr; + }; + + }; + +}; + +#endif diff --git a/rfb_win32/keymap.h b/rfb_win32/keymap.h new file mode 100644 index 00000000..69ce66f2 --- /dev/null +++ b/rfb_win32/keymap.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2002-2004 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +// keymap.h - this file is shared between SInput.cxx and CKeyboard.cxx +// +// Mapping of X keysyms to and from Windows VK codes. Ordering here must be +// such that when we look up a Windows VK code we get the preferred X keysym. +// Going the other way there is no problem because an X keysym always maps to +// exactly one Windows VK code. This map only contain keys which are not the +// normal keys for printable ASCII characters. For example it does not contain +// VK_SPACE (note that things like VK_ADD are for the plus key on the keypad, +// not on the main keyboard). + +struct keymap_t { + rdr::U32 keysym; + rdr::U8 vk; + bool extended; +}; + +static keymap_t keymap[] = { + + { XK_BackSpace, VK_BACK, 0 }, + { XK_Tab, VK_TAB, 0 }, + { XK_Clear, VK_CLEAR, 0 }, + { XK_Return, VK_RETURN, 0 }, + { XK_Pause, VK_PAUSE, 0 }, + { XK_Escape, VK_ESCAPE, 0 }, + { XK_Delete, VK_DELETE, 1 }, + + // Cursor control & motion + + { XK_Home, VK_HOME, 1 }, + { XK_Left, VK_LEFT, 1 }, + { XK_Up, VK_UP, 1 }, + { XK_Right, VK_RIGHT, 1 }, + { XK_Down, VK_DOWN, 1 }, + { XK_Page_Up, VK_PRIOR, 1 }, + { XK_Page_Down, VK_NEXT, 1 }, + { XK_End, VK_END, 1 }, + + // Misc functions + + { XK_Select, VK_SELECT, 0 }, + { XK_Print, VK_SNAPSHOT, 0 }, + { XK_Execute, VK_EXECUTE, 0 }, + { XK_Insert, VK_INSERT, 1 }, + { XK_Help, VK_HELP, 0 }, + { XK_Break, VK_CANCEL, 1 }, + + // Auxilliary Functions - must come before XK_KP_F1, etc + + { XK_F1, VK_F1, 0 }, + { XK_F2, VK_F2, 0 }, + { XK_F3, VK_F3, 0 }, + { XK_F4, VK_F4, 0 }, + { XK_F5, VK_F5, 0 }, + { XK_F6, VK_F6, 0 }, + { XK_F7, VK_F7, 0 }, + { XK_F8, VK_F8, 0 }, + { XK_F9, VK_F9, 0 }, + { XK_F10, VK_F10, 0 }, + { XK_F11, VK_F11, 0 }, + { XK_F12, VK_F12, 0 }, + { XK_F13, VK_F13, 0 }, + { XK_F14, VK_F14, 0 }, + { XK_F15, VK_F15, 0 }, + { XK_F16, VK_F16, 0 }, + { XK_F17, VK_F17, 0 }, + { XK_F18, VK_F18, 0 }, + { XK_F19, VK_F19, 0 }, + { XK_F20, VK_F20, 0 }, + { XK_F21, VK_F21, 0 }, + { XK_F22, VK_F22, 0 }, + { XK_F23, VK_F23, 0 }, + { XK_F24, VK_F24, 0 }, + + // Keypad Functions, keypad numbers + + { XK_KP_Tab, VK_TAB, 0 }, + { XK_KP_Enter, VK_RETURN, 1 }, + { XK_KP_F1, VK_F1, 0 }, + { XK_KP_F2, VK_F2, 0 }, + { XK_KP_F3, VK_F3, 0 }, + { XK_KP_F4, VK_F4, 0 }, + { XK_KP_Home, VK_HOME, 0 }, + { XK_KP_Left, VK_LEFT, 0 }, + { XK_KP_Up, VK_UP, 0 }, + { XK_KP_Right, VK_RIGHT, 0 }, + { XK_KP_Down, VK_DOWN, 0 }, + { XK_KP_End, VK_END, 0 }, + { XK_KP_Page_Up, VK_PRIOR, 0 }, + { XK_KP_Page_Down, VK_NEXT, 0 }, + { XK_KP_Begin, VK_CLEAR, 0 }, + { XK_KP_Insert, VK_INSERT, 0 }, + { XK_KP_Delete, VK_DELETE, 0 }, + { XK_KP_Multiply, VK_MULTIPLY, 0 }, + { XK_KP_Add, VK_ADD, 0 }, + { XK_KP_Separator, VK_SEPARATOR, 0 }, + { XK_KP_Subtract, VK_SUBTRACT, 0 }, + { XK_KP_Decimal, VK_DECIMAL, 0 }, + { XK_KP_Divide, VK_DIVIDE, 1 }, + + { XK_KP_0, VK_NUMPAD0, 0 }, + { XK_KP_1, VK_NUMPAD1, 0 }, + { XK_KP_2, VK_NUMPAD2, 0 }, + { XK_KP_3, VK_NUMPAD3, 0 }, + { XK_KP_4, VK_NUMPAD4, 0 }, + { XK_KP_5, VK_NUMPAD5, 0 }, + { XK_KP_6, VK_NUMPAD6, 0 }, + { XK_KP_7, VK_NUMPAD7, 0 }, + { XK_KP_8, VK_NUMPAD8, 0 }, + { XK_KP_9, VK_NUMPAD9, 0 }, + + // Modifiers + + { XK_Shift_L, VK_SHIFT, 0 }, + { XK_Shift_R, VK_SHIFT, 0 }, + { XK_Control_L, VK_CONTROL, 0 }, + { XK_Control_R, VK_CONTROL, 1 }, + { XK_Alt_L, VK_MENU, 0 }, + { XK_Alt_R, VK_MENU, 1 }, + + // Left & Right Windows keys & Windows Menu Key + + { XK_Super_L, VK_LWIN, 0 }, + { XK_Super_R, VK_RWIN, 0 }, + { XK_Menu, VK_APPS, 0 }, + + // Japanese stuff - almost certainly wrong... + + { XK_Kanji, VK_KANJI, 0 }, + { XK_Kana_Shift, VK_KANA, 0 }, + +}; diff --git a/rfb_win32/msvcwarning.h b/rfb_win32/msvcwarning.h new file mode 100644 index 00000000..e50d9c1a --- /dev/null +++ b/rfb_win32/msvcwarning.h @@ -0,0 +1,20 @@ +/* Copyright (C) 2002-2003 RealVNC Ltd. All Rights Reserved. + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#pragma warning( disable : 4244 ) // loss of data e.g. int to char +#pragma warning( disable : 4800 ) // forcing bool 'true' or 'false' +#pragma warning( disable : 4786 ) // debug info truncated diff --git a/rfb_win32/rfb_win32.dsp b/rfb_win32/rfb_win32.dsp new file mode 100644 index 00000000..2118bcb2 --- /dev/null +++ b/rfb_win32/rfb_win32.dsp @@ -0,0 +1,341 @@ +# Microsoft Developer Studio Project File - Name="rfb_win32" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=rfb_win32 - Win32 Debug Unicode +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "rfb_win32.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "rfb_win32.mak" CFG="rfb_win32 - Win32 Debug Unicode" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "rfb_win32 - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "rfb_win32 - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE "rfb_win32 - Win32 Debug Unicode" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "rfb_win32 - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I ".." /FI"msvcwarning.h" /D "NDEBUG" /D "_LIB" /D "WIN32" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x809 /d "NDEBUG" +# ADD RSC /l 0x809 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "rfb_win32 - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /I ".." /FI"msvcwarning.h" /D "_DEBUG" /D "_LIB" /D "WIN32" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "rfb_win32 - Win32 Debug Unicode" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "rfb_win32___Win32_Debug_Unicode" +# PROP BASE Intermediate_Dir "rfb_win32___Win32_Debug_Unicode" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug_Unicode" +# PROP Intermediate_Dir "Debug_Unicode" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /I ".." /FI"msvcwarning.h" /D "_DEBUG" /D "_LIB" /D "WIN32" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /I ".." /FI"msvcwarning.h" /D "_LIB" /D "_DEBUG" /D "WIN32" /D "_UNICODE" /D "UNICODE" /YX /FD /GZ /c +# ADD BASE RSC /l 0x809 /d "_DEBUG" +# ADD RSC /l 0x809 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "rfb_win32 - Win32 Release" +# Name "rfb_win32 - Win32 Debug" +# Name "rfb_win32 - Win32 Debug Unicode" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\AboutDialog.cxx +# End Source File +# Begin Source File + +SOURCE=.\CKeyboard.cxx +# End Source File +# Begin Source File + +SOURCE=.\CleanDesktop.cxx +# End Source File +# Begin Source File + +SOURCE=.\Clipboard.cxx +# End Source File +# Begin Source File + +SOURCE=.\CPointer.cxx +# End Source File +# Begin Source File + +SOURCE=.\CurrentUser.cxx +# End Source File +# Begin Source File + +SOURCE=.\DeviceFrameBuffer.cxx +# End Source File +# Begin Source File + +SOURCE=.\Dialog.cxx +# End Source File +# Begin Source File + +SOURCE=.\DIBSectionBuffer.cxx +# End Source File +# Begin Source File + +SOURCE=.\LaunchProcess.cxx +# End Source File +# Begin Source File + +SOURCE=.\MsgWindow.cxx +# End Source File +# Begin Source File + +SOURCE=.\OSVersion.cxx +# End Source File +# Begin Source File + +SOURCE=.\RegConfig.cxx +# End Source File +# Begin Source File + +SOURCE=.\Registry.cxx +# End Source File +# Begin Source File + +SOURCE=.\SDisplay.cxx +# End Source File +# Begin Source File + +SOURCE=.\Service.cxx +# End Source File +# Begin Source File + +SOURCE=.\SInput.cxx +# End Source File +# Begin Source File + +SOURCE=.\SocketManager.cxx +# End Source File +# Begin Source File + +SOURCE=.\TCharArray.cxx +# End Source File +# Begin Source File + +SOURCE=.\Win32Util.cxx +# End Source File +# Begin Source File + +SOURCE=.\WMCursor.cxx +# End Source File +# Begin Source File + +SOURCE=.\WMHooks.cxx +# End Source File +# Begin Source File + +SOURCE=.\WMNotifier.cxx +# End Source File +# Begin Source File + +SOURCE=.\WMPoller.cxx +# End Source File +# Begin Source File + +SOURCE=.\WMShatter.cxx +# End Source File +# Begin Source File + +SOURCE=.\WMWindowCopyRect.cxx +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\AboutDialog.h +# End Source File +# Begin Source File + +SOURCE=.\CKeyboard.h +# End Source File +# Begin Source File + +SOURCE=.\CleanDesktop.h +# End Source File +# Begin Source File + +SOURCE=.\Clipboard.h +# End Source File +# Begin Source File + +SOURCE=.\CPointer.h +# End Source File +# Begin Source File + +SOURCE=.\CurrentUser.h +# End Source File +# Begin Source File + +SOURCE=.\DeviceFrameBuffer.h +# End Source File +# Begin Source File + +SOURCE=.\Dialog.h +# End Source File +# Begin Source File + +SOURCE=.\DIBSectionBuffer.h +# End Source File +# Begin Source File + +SOURCE=.\IntervalTimer.h +# End Source File +# Begin Source File + +SOURCE=.\keymap.h +# End Source File +# Begin Source File + +SOURCE=.\LaunchProcess.h +# End Source File +# Begin Source File + +SOURCE=.\MsgWindow.h +# End Source File +# Begin Source File + +SOURCE=.\OSVersion.h +# End Source File +# Begin Source File + +SOURCE=.\RegConfig.h +# End Source File +# Begin Source File + +SOURCE=.\Registry.h +# End Source File +# Begin Source File + +SOURCE=.\SDisplay.h +# End Source File +# Begin Source File + +SOURCE=.\Security.h +# End Source File +# Begin Source File + +SOURCE=.\Service.h +# End Source File +# Begin Source File + +SOURCE=.\SInput.h +# End Source File +# Begin Source File + +SOURCE=.\SocketManager.h +# End Source File +# Begin Source File + +SOURCE=.\TCharArray.h +# End Source File +# Begin Source File + +SOURCE=.\TrayIcon.h +# End Source File +# Begin Source File + +SOURCE=.\Win32Util.h +# End Source File +# Begin Source File + +SOURCE=.\WMCursor.h +# End Source File +# Begin Source File + +SOURCE=.\WMHooks.h +# End Source File +# Begin Source File + +SOURCE=.\WMNotifier.h +# End Source File +# Begin Source File + +SOURCE=.\WMPoller.h +# End Source File +# Begin Source File + +SOURCE=.\WMShatter.h +# End Source File +# Begin Source File + +SOURCE=.\WMWindowCopyRect.h +# End Source File +# End Group +# End Target +# End Project |