metasploit-framework/external/source/vncdll/winvnc/vncDesktop.cpp

2965 lines
74 KiB
C++
Executable File

// Copyright (C) 2001-2004 HorizonLive.com, Inc. All Rights Reserved.
// Copyright (C) 2001-2004 TightVNC Team. All Rights Reserved.
// Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
//
// This file is part of the VNC system.
//
// The VNC system 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 program 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 program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// TightVNC distribution homepage on the Web: http://www.tightvnc.com/
//
// If the source code for the VNC system is not available from the place
// whence you received this file, check http://www.uk.research.att.com/vnc or contact
// the authors on vnc@uk.research.att.com for information on obtaining it.
// vncDesktop implementation
// System headers
#include "stdhdrs.h"
#include <omnithread.h>
// Custom headers
#include "vncServer.h"
#include "vncRegion.h"
#include "rectlist.h"
#include "vncDesktop.h"
#include "vncService.h"
#include "WallpaperUtils.h"
#include "TsSessions.h"
#if (_MSC_VER>= 1300)
#include <fstream>
#else
#include <fstream.h>
#endif
extern HINSTANCE hAppInstance;
// Constants
const UINT RFB_SCREEN_UPDATE = RegisterWindowMessage("WinVNC.Update.DrawRect");
const UINT RFB_COPYRECT_UPDATE = RegisterWindowMessage("WinVNC.Update.CopyRect");
const UINT RFB_MOUSE_UPDATE = RegisterWindowMessage("WinVNC.Update.Mouse");
// Messages for blocking remote input events
const UINT RFB_LOCAL_KEYBOARD = RegisterWindowMessage("WinVNC.Local.Keyboard");
const UINT RFB_LOCAL_MOUSE = RegisterWindowMessage("WinVNC.Local.Mouse");
const char szDesktopSink[] = "WinVNC desktop sink";
// Atoms
const char *VNC_WINDOWPOS_ATOMNAME = "VNCHooks.CopyRect.WindowPos";
ATOM VNC_WINDOWPOS_ATOM = NULL;
// Static members to use with new polling algorithm
const int vncDesktop::m_pollingOrder[32] = {
0, 16, 8, 24, 4, 20, 12, 28,
10, 26, 18, 2, 22, 6, 30, 14,
1, 17, 9, 25, 7, 23, 15, 31,
19, 3, 27, 11, 29, 13, 5, 21
};
int vncDesktop::m_pollingStep = 0;
BOOL IsWinNT()
{
return vncService::IsWinNT();
}
BOOL IsWinVerOrHigher(ULONG mj, ULONG mn)
{
return vncService::VersionMajor() > mj ||
vncService::VersionMajor() == mj && vncService::VersionMinor() >= mn;
}
BOOL IsNtVer(ULONG mj, ULONG mn)
{
if (!vncService::IsWinNT())
return FALSE;
return vncService::VersionMajor() == mj && vncService::VersionMinor() == mn;
}
BOOL vncDesktop::IsMultiMonDesktop()
{
if (!IsWinVerOrHigher(4, 10))
return FALSE;
return GetSystemMetrics(SM_CMONITORS) > 1;
}
// The desktop handler thread
// This handles the messages posted by RFBLib to the vncDesktop window
class vncDesktopThread : public omni_thread
{
public:
vncDesktopThread() { m_returnsig = NULL; }
protected:
~vncDesktopThread() { if (m_returnsig != NULL) delete m_returnsig; }
public:
virtual BOOL Init(vncDesktop *desktop, vncServer *server);
virtual void *run_undetached(void *arg);
virtual void ReturnVal(BOOL result);
protected:
vncServer *m_server;
vncDesktop *m_desktop;
omni_mutex m_returnLock;
omni_condition *m_returnsig;
BOOL m_return;
BOOL m_returnset;
};
BOOL
vncDesktopThread::Init(vncDesktop *desktop, vncServer *server)
{
// Save the server pointer
m_server = server;
m_desktop = desktop;
m_returnset = FALSE;
m_returnsig = new omni_condition(&m_returnLock);
// Start the thread
start_undetached();
// Wait for the thread to let us know if it failed to init
{ omni_mutex_lock l(m_returnLock);
while (!m_returnset)
{
m_returnsig->wait();
}
}
return m_return;
}
void
vncDesktopThread::ReturnVal(BOOL result)
{
omni_mutex_lock l(m_returnLock);
m_returnset = TRUE;
m_return = result;
m_returnsig->signal();
}
void *vncDesktopThread::run_undetached(void *arg)
{
// Save the thread's "home" desktop, under NT (no effect under 9x)
HDESK home_desktop = GetThreadDesktop(GetCurrentThreadId());
// Try to make session zero the console session
if (!inConsoleSession())
setConsoleSession();
// Attempt to initialise and return success or failure
if (!m_desktop->Startup())
{
// vncDesktop::Startup might mave changed video mode in SetupDisplayForConnection.
// it has to be reverted then.
// TODO: review strong guarantee conditions for vncDesktop::Startup
m_desktop->ResetDisplayToNormal();
vncService::SelectHDESK(home_desktop);
ReturnVal(FALSE);
return NULL;
}
RECT rect = m_desktop->GetSourceRect();
IntersectRect(&rect, &rect, &m_desktop->m_bmrect);
m_server->SetSharedRect(rect);
// Succeeded to initialise ok
ReturnVal(TRUE);
// START PROCESSING DESKTOP MESSAGES
// We set a flag inside the desktop handler here, to indicate it's now safe
// to handle clipboard messages
m_desktop->SetClipboardActive(TRUE);
SYSTEMTIME systime;
FILETIME ftime;
ULARGE_INTEGER now, droptime;
droptime.QuadPart = 0;
MSG msg;
while (TRUE)
{
if (!PeekMessage(&msg, m_desktop->Window(), NULL, NULL, PM_REMOVE))
{
// Whenever the message queue becomes empty, we check to see whether
// there are updates to be passed to clients (first we make sure
// that scheduled wallpaper removal is complete).
if (!m_server->WallpaperWait()) {
if (!m_desktop->CheckUpdates())
break;
}
// Now wait for more messages to be queued
if (!WaitMessage())
{
break;
}
}
else if (msg.message == RFB_SCREEN_UPDATE)
{
// TODO: suppress this message from hook when driver is active
// An area of the screen has changed (ignore if we have a driver)
if (m_desktop->m_videodriver == NULL)
{
RECT rect;
rect.left = (SHORT)LOWORD(msg.wParam);
rect.top = (SHORT)HIWORD(msg.wParam);
rect.right = (SHORT)LOWORD(msg.lParam);
rect.bottom = (SHORT)HIWORD(msg.lParam);
m_desktop->m_changed_rgn.AddRect(rect);
}
}
else if (msg.message == RFB_MOUSE_UPDATE)
{
// Save the cursor ID
m_desktop->SetCursor((HCURSOR) msg.wParam);
}
else if (msg.message == RFB_LOCAL_KEYBOARD)
{
// Block remote input events if necessary
if (vncService::IsWin95()) {
m_server->SetKeyboardCounter(-1);
if (m_server->KeyboardCounter() < 0) {
GetSystemTime(&systime);
SystemTimeToFileTime(&systime, &ftime);
droptime.LowPart = ftime.dwLowDateTime;
droptime.HighPart = ftime.dwHighDateTime;
droptime.QuadPart /= 10000000; // convert into seconds
m_server->BlockRemoteInput(true);
}
} else {
GetSystemTime(&systime);
SystemTimeToFileTime(&systime, &ftime);
droptime.LowPart = ftime.dwLowDateTime;
droptime.HighPart = ftime.dwHighDateTime;
droptime.QuadPart /= 10000000; // convert into seconds
m_server->BlockRemoteInput(true);
}
}
else if (msg.message == RFB_LOCAL_MOUSE)
{
// Block remote input events if necessary
if (vncService::IsWin95()) {
if (msg.wParam == WM_MOUSEMOVE) {
m_server->SetMouseCounter(-1, msg.pt, true);
} else {
m_server->SetMouseCounter(-1, msg.pt, false);
}
if (m_server->MouseCounter() < 0 && droptime.QuadPart == 0) {
GetSystemTime(&systime);
SystemTimeToFileTime(&systime, &ftime);
droptime.LowPart = ftime.dwLowDateTime;
droptime.HighPart = ftime.dwHighDateTime;
droptime.QuadPart /= 10000000; // convert into seconds
m_server->BlockRemoteInput(true);
}
} else {
GetSystemTime(&systime);
SystemTimeToFileTime(&systime, &ftime);
droptime.LowPart = ftime.dwLowDateTime;
droptime.HighPart = ftime.dwHighDateTime;
droptime.QuadPart /= 10000000; // convert into seconds
m_server->BlockRemoteInput(true);
}
}
else if (msg.message == WM_QUIT)
{
break;
}
#ifdef HORIZONLIVE
else if (msg.message == LS_QUIT)
{
// this is our custom quit message
break;
}
#endif
else
{
// Process any other messages normally
DispatchMessage(&msg);
}
// Check timer to unblock remote input events if necessary
// FIXME: rewrite this stuff to eliminate code duplication (ses above).
// FIXME: Use time() instead of GetSystemTime().
// FIXME: It's not necessary to do this on receiving _each_ message.
if (m_server->LocalInputPriority() && droptime.QuadPart != 0) {
GetSystemTime(&systime);
SystemTimeToFileTime(&systime, &ftime);
now.LowPart = ftime.dwLowDateTime;
now.HighPart = ftime.dwHighDateTime;
now.QuadPart /= 10000000; // convert into seconds
if (now.QuadPart - m_server->DisableTime() >= droptime.QuadPart) {
m_server->BlockRemoteInput(false);
droptime.QuadPart = 0;
m_server->SetKeyboardCounter(0);
m_server->SetMouseCounter(0, msg.pt, false);
}
}
}
m_desktop->SetClipboardActive(FALSE);
// Clear all the hooks and close windows, etc.
m_desktop->Shutdown();
// Return display settings to previous values.
m_desktop->ResetDisplayToNormal();
// Turn on the screen.
m_desktop->BlankScreen(FALSE);
// Clear the shift modifier keys, now that there are no remote clients
vncKeymap::ClearShiftKeys();
// Switch back into our home desktop, under NT (no effect under 9x)
vncService::SelectHDESK(home_desktop);
return NULL;
}
// Implementation of the vncDesktop class
vncDesktop::vncDesktop()
{
m_thread = NULL;
m_hwnd = NULL;
m_polling_flag = FALSE;
m_timer_polling = 0;
m_timer_blank_screen = 0;
m_hnextviewer = NULL;
m_hcursor = NULL;
m_displaychanged = FALSE;
m_hrootdc = NULL;
m_hmemdc = NULL;
m_membitmap = NULL;
m_initialClipBoardSeen = FALSE;
// Vars for Will Dean's DIBsection patch
m_DIBbits = NULL;
m_freemainbuff = FALSE;
m_formatmunged = FALSE;
m_mainbuff = NULL;
m_backbuff = NULL;
m_clipboard_active = FALSE;
m_hooks_active = FALSE;
m_hooks_may_change = FALSE;
m_lpAlternateDevMode = NULL;
m_copyrect_set = FALSE;
m_videodriver = NULL;
m_timer_blank_screen = 0;
}
vncDesktop::~vncDesktop()
{
// If we created a thread then here we delete it
// The thread itself does most of the cleanup
if(m_thread != NULL)
{
// Post a close message to quit our message handler thread
PostMessage(Window(), WM_QUIT, 0, 0);
// Join with the desktop handler thread
void *returnval;
m_thread->join(&returnval);
m_thread = NULL;
}
// Let's call Shutdown just in case something went wrong...
Shutdown();
_ASSERTE(!m_lpAlternateDevMode);
}
// Routine to startup and install all the hooks and stuff
BOOL
vncDesktop::Startup()
{
// Currently, we just check whether we're in the console session, and
// fail if not
if (!inConsoleSession()) {
return FALSE;
}
// Configure the display for optimal VNC performance.
SetupDisplayForConnection();
// Initialise the Desktop object
if (!InitDesktop())
return FALSE;
if (InitVideoDriver())
{
// this isn't really necessary
// InvalidateRect(NULL,NULL,TRUE);
}
if (!InitBitmap())
return FALSE;
if (!ThunkBitmapInfo())
return FALSE;
if (!SetPixFormat())
return FALSE;
if (!CreateBuffers())
return FALSE;
if (!SetPixShifts())
return FALSE;
if (!SetPalette())
return FALSE;
if (!InitWindow())
return FALSE;
// Add the system hook
//ActivateHooks();
m_server->PollFullScreen(TRUE);
m_hooks_may_change = true;
#ifndef HORIZONLIVE
// Start up the keyboard and mouse filters
//SetKeyboardFilterHook(m_server->LocalInputsDisabled());
//SetMouseFilterHook(m_server->LocalInputsDisabled());
#endif
// Start up the keyboard and mouse hooks for
// local event priority over remote impl.
if (m_server->LocalInputPriority())
SetLocalInputPriorityHook(true);
// Start a timer to handle Polling Mode. The timer will cause
// an "idle" event, which is necessary if Polling Mode is being used,
// to cause TriggerUpdate to be called.
SetPollingFlag(FALSE);
SetPollingTimer();
// If necessary, start a separate timer to preserve the diplay turned off.
UpdateBlankScreenTimer();
// Get hold of the WindowPos atom!
if ((VNC_WINDOWPOS_ATOM = GlobalAddAtom(VNC_WINDOWPOS_ATOMNAME)) == 0) {
return FALSE;
}
// this member must be initialized: we cant assume the absence
// of clients when desktop is created.
m_cursorpos.left = 0;
m_cursorpos.top = 0;
m_cursorpos.right = 0;
m_cursorpos.bottom = 0;
// Everything is ok, so return TRUE
return TRUE;
}
// Routine to shutdown all the hooks and stuff
BOOL vncDesktop::Shutdown()
{
// If we created timers then kill them
if (m_timer_polling)
{
KillTimer(Window(), TIMER_POLL);
m_timer_polling = 0;
}
if (m_timer_blank_screen)
{
KillTimer(Window(), TIMER_BLANK_SCREEN);
m_timer_blank_screen = 0;
}
// If we created a window then kill it and the hooks
if (m_hwnd != NULL)
{
//Remove the system hooks
//Unset keyboard and mouse hooks
SetLocalInputPriorityHook(false);
m_hooks_may_change = false;
ShutdownHooks();
#ifndef HORIZONLIVE
// Stop the keyboard and mouse filters
//SetKeyboardFilterHook(false);
//SetMouseFilterHook(false);
#endif
// The window is being closed - remove it from the viewer list
ChangeClipboardChain(m_hwnd, m_hnextviewer);
// Close the hook window
DestroyWindow(m_hwnd);
m_hwnd = NULL;
m_hnextviewer = NULL;
}
// Now free all the bitmap stuff
if (m_hrootdc != NULL)
{
// Release our device context
ReleaseDC(NULL, m_hrootdc);
m_hrootdc = NULL;
}
if (m_hmemdc != NULL)
{
// Release our device context
DeleteDC(m_hmemdc);
m_hmemdc = NULL;
}
if (m_membitmap != NULL)
{
// Release the custom bitmap, if any
DeleteObject(m_membitmap);
m_membitmap = NULL;
}
// Free back buffer
if (m_backbuff != NULL)
{
delete [] m_backbuff;
m_backbuff = NULL;
}
if (m_freemainbuff)
{
// Slow blits were enabled - free the slow blit buffer
if (m_mainbuff != NULL)
{
delete [] m_mainbuff;
m_mainbuff = NULL;
}
}
// Free the WindowPos atom!
if (VNC_WINDOWPOS_ATOM != NULL)
{
GlobalDeleteAtom(VNC_WINDOWPOS_ATOM);
}
ShutdownVideoDriver();
return TRUE;
}
// Routines to set/unset hooks via VNCHooks.dll
void
vncDesktop::ActivateHooks()
{
BOOL enable = !(m_server->DontSetHooks() && m_server->PollFullScreen());
if (enable && !m_hooks_active) {
//m_hooks_active = SetHook(m_hwnd,
// RFB_SCREEN_UPDATE,
// RFB_COPYRECT_UPDATE,
// RFB_MOUSE_UPDATE);
if (!m_hooks_active) {
// Switch on full screen polling, so they can see something, at least...
m_server->PollFullScreen(TRUE);
}
} else if (!enable) {
ShutdownHooks();
}
}
void
vncDesktop::ShutdownHooks()
{
//if (m_hooks_active)
// m_hooks_active = !UnSetHook(m_hwnd);
}
void
vncDesktop::TryActivateHooks()
{
if (m_hooks_may_change)
ActivateHooks();
}
// Routine to ensure we're on the correct NT desktop
BOOL
vncDesktop::InitDesktop()
{
if (vncService::InputDesktopSelected())
return TRUE;
// Ask for the current input desktop
return vncService::SelectDesktop(NULL);
}
// Routine used to close the screen saver, if it's active...
BOOL CALLBACK
KillScreenSaverFunc(HWND hwnd, LPARAM lParam)
{
char buffer[256];
// - ONLY try to close Screen-saver windows!!!
if ((GetClassName(hwnd, buffer, 256) != 0) &&
(strcmp(buffer, "WindowsScreenSaverClass") == 0))
PostMessage(hwnd, WM_CLOSE, 0, 0);
return TRUE;
}
void
vncDesktop::KillScreenSaver()
{
OSVERSIONINFO osversioninfo;
osversioninfo.dwOSVersionInfoSize = sizeof(osversioninfo);
// Get the current OS version
if (!GetVersionEx(&osversioninfo))
return;
// How to kill the screen saver depends on the OS
switch (osversioninfo.dwPlatformId)
{
case VER_PLATFORM_WIN32_WINDOWS:
{
// Windows 95
// Fidn the ScreenSaverClass window
HWND hsswnd = FindWindow ("WindowsScreenSaverClass", NULL);
if (hsswnd != NULL)
PostMessage(hsswnd, WM_CLOSE, 0, 0);
break;
}
case VER_PLATFORM_WIN32_NT:
{
// Windows NT
// Find the screensaver desktop
HDESK hDesk = OpenDesktop(
"Screen-saver",
0,
FALSE,
DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS
);
if (hDesk != NULL)
{
// Close all windows on the screen saver desktop
EnumDesktopWindows(hDesk, (WNDENUMPROC) &KillScreenSaverFunc, 0);
CloseDesktop(hDesk);
// Pause long enough for the screen-saver to close
//Sleep(2000);
// Reset the screen saver so it can run again
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_SENDWININICHANGE);
}
break;
}
}
}
void vncDesktop::ChangeResNow()
{
// IMPORTANT: Screen mode alteration may only take place on a single-mon system.
if (IsMultiMonDesktop())
{
return;
}
BOOL settingsUpdated = false;
int i = 0;
_ASSERTE(!m_lpAlternateDevMode);
m_lpAlternateDevMode = new DEVMODE; // *** create an instance of DEVMODE - Jeremy Peaks
if (!m_lpAlternateDevMode)
{
return;
}
// *** WBB - Obtain the current display settings.
// only on unimon
if (! EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, m_lpAlternateDevMode))
{
delete m_lpAlternateDevMode;
m_lpAlternateDevMode = NULL;
return;
}
origPelsWidth = m_lpAlternateDevMode->dmPelsWidth; // *** sets the original resolution for use later
origPelsHeight = m_lpAlternateDevMode->dmPelsHeight; // *** - Jeremy Peaks
// *** Open the registry key for resolution settings
/*HKEY checkdetails = 0;
RegOpenKeyEx(HKEY_LOCAL_MACHINE,
WINVNC_REGISTRY_KEY,
0,
KEY_READ,
&checkdetails);
if (checkdetails)
{
int slen=MAX_REG_ENTRY_LEN;
int valType;
char inouttext[MAX_REG_ENTRY_LEN];
memset(inouttext, 0, MAX_REG_ENTRY_LEN);
// *** Get the registry values for resolution change - Jeremy Peaks
RegQueryValueEx(checkdetails,
"ResWidth",
NULL,
(LPDWORD) &valType,
(LPBYTE) &inouttext,
(LPDWORD) &slen);
if ((valType == REG_SZ) &&
atol(inouttext)) { // *** if width is 0, then this isn't a valid resolution, so do nothing - Jeremy Peaks
m_lpAlternateDevMode->dmPelsWidth = atol(inouttext);
memset(inouttext, 0, MAX_REG_ENTRY_LEN);
RegQueryValueEx(checkdetails,
"ResHeight",
NULL,
(LPDWORD) &valType,
(LPBYTE) &inouttext,
(LPDWORD) &slen);
m_lpAlternateDevMode->dmPelsHeight = atol(inouttext);
if ((valType == REG_SZ ) &&
(m_lpAlternateDevMode->dmPelsHeight > 0)) {
// *** make res change - Jeremy Peaks
// testing: predefined Width/Height may become incompatible
// with new clrdepth/timings
long resultOfResChange = ChangeDisplaySettings(m_lpAlternateDevMode, CDS_TEST);
if (resultOfResChange == DISP_CHANGE_SUCCESSFUL) {
ChangeDisplaySettings(m_lpAlternateDevMode, CDS_UPDATEREGISTRY);
settingsUpdated = true;
}
}
}
RegCloseKey(checkdetails);
}*/
if (! settingsUpdated)
{
// Did not change the resolution.
delete m_lpAlternateDevMode;
m_lpAlternateDevMode = NULL;
}
}
void
vncDesktop::SetupDisplayForConnection()
{
KillScreenSaver();
ChangeResNow(); // *** - Jeremy Peaks
}
void
vncDesktop::ResetDisplayToNormal()
{
if (m_lpAlternateDevMode != NULL)
{
// *** In case the resolution was changed, revert to original settings now
m_lpAlternateDevMode->dmPelsWidth = origPelsWidth;
m_lpAlternateDevMode->dmPelsHeight = origPelsHeight;
long resultOfResChange = ChangeDisplaySettings(m_lpAlternateDevMode, CDS_TEST);
if (resultOfResChange == DISP_CHANGE_SUCCESSFUL)
ChangeDisplaySettings(m_lpAlternateDevMode, CDS_UPDATEREGISTRY);
delete m_lpAlternateDevMode;
m_lpAlternateDevMode = NULL;
}
}
RECT vncDesktop::GetSourceRect()
{
if (m_server->WindowShared())
{
RECT wrect;
GetWindowRect(m_server->GetWindowShared(), &wrect);
return wrect;
}
else if (m_server->ScreenAreaShared())
{
return m_server->GetScreenAreaRect();
}
else if (m_server->PrimaryDisplayOnlyShared())
{
RECT pdr = { 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) };
return pdr;
}
else
{
#ifdef _DEBUG
RECT rd;
_ASSERTE(GetSourceDisplayRect(rd));
_ASSERTE(EqualRect(&rd, &m_bmrect));
#endif
return m_bmrect;
}
}
RECT GetScreenRect()
{
RECT screenrect;
if (IsWinVerOrHigher(4, 10))
{
screenrect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
screenrect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
screenrect.right = screenrect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
screenrect.bottom = screenrect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);
}
else
{
screenrect.left = 0;
screenrect.top = 0;
screenrect.right = GetSystemMetrics(SM_CXSCREEN);
screenrect.bottom = GetSystemMetrics(SM_CYSCREEN);
}
return screenrect;
}
BOOL vncDesktop::GetSourceDisplayRect(RECT &rdisp_rect)
{
if (!m_hrootdc)
m_hrootdc = ::GetDC(NULL);
if (!m_hrootdc)
{
return FALSE;
}
// TODO: refactor it
rdisp_rect = GetScreenRect();
return TRUE;
}
BOOL vncDesktop::InitBitmap()
{
// IMPORTANT: here an optimization may be implemented
// when only a fixed rect is shared.
// then m_bmrect should be set to that rect.
if (!GetSourceDisplayRect(m_bmrect))
{
return FALSE;
}
// Create a compatible memory DC
m_hmemdc = CreateCompatibleDC(m_hrootdc);
if (m_hmemdc == NULL) {
return FALSE;
}
// Check that the device capabilities are ok
if ((GetDeviceCaps(m_hrootdc, RASTERCAPS) & RC_BITBLT) == 0)
{
return FALSE;
}
if ((GetDeviceCaps(m_hmemdc, RASTERCAPS) & RC_DI_BITMAP) == 0)
{
return FALSE;
}
// Create the bitmap to be compatible with the ROOT DC!!!
m_membitmap = CreateCompatibleBitmap(
m_hrootdc,
m_bmrect.right - m_bmrect.left,
m_bmrect.bottom - m_bmrect.top);
if (m_membitmap == NULL)
{
return FALSE;
}
// Get the bitmap's format and colour details
int result;
m_bminfo.bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_bminfo.bmi.bmiHeader.biBitCount = 0;
result = ::GetDIBits(m_hmemdc, m_membitmap, 0, 1, NULL, &m_bminfo.bmi, DIB_RGB_COLORS);
if (result == 0) {
return FALSE;
}
result = ::GetDIBits(m_hmemdc, m_membitmap, 0, 1, NULL, &m_bminfo.bmi, DIB_RGB_COLORS);
if (result == 0) {
return FALSE;
}
if (GetDeviceCaps(m_hmemdc, PLANES) != 1)
{
return FALSE;
}
// Henceforth we want to use a top-down scanning representation
m_bminfo.bmi.bmiHeader.biHeight = - abs(m_bminfo.bmi.bmiHeader.biHeight);
// Is the bitmap palette-based or truecolour?
m_bminfo.truecolour = (GetDeviceCaps(m_hmemdc, RASTERCAPS) & RC_PALETTE) == 0;
return TRUE;
}
BOOL
vncDesktop::ThunkBitmapInfo()
{
// If we leave the pixel format intact, the blits can be optimised (Will Dean's patch)
m_formatmunged = FALSE;
// HACK ***. Optimised blits don't work with palette-based displays, yet
if (!m_bminfo.truecolour) {
m_formatmunged = TRUE;
}
// Attempt to force the actual format into one we can handle
// We can handle 8-bit-palette and 16/32-bit-truecolour modes
switch (m_bminfo.bmi.bmiHeader.biBitCount)
{
case 1:
case 4:
// Correct the BITMAPINFO header to the format we actually want
m_bminfo.bmi.bmiHeader.biClrUsed = 0;
m_bminfo.bmi.bmiHeader.biPlanes = 1;
m_bminfo.bmi.bmiHeader.biCompression = BI_RGB;
m_bminfo.bmi.bmiHeader.biBitCount = 8;
m_bminfo.bmi.bmiHeader.biSizeImage =
abs((m_bminfo.bmi.bmiHeader.biWidth *
m_bminfo.bmi.bmiHeader.biHeight *
m_bminfo.bmi.bmiHeader.biBitCount)/ 8);
m_bminfo.bmi.bmiHeader.biClrImportant = 0;
m_bminfo.truecolour = FALSE;
// Display format is non-VNC compatible - use the slow blit method
m_formatmunged = TRUE;
break;
case 24:
// Update the bitmapinfo header
m_bminfo.bmi.bmiHeader.biBitCount = 32;
m_bminfo.bmi.bmiHeader.biPlanes = 1;
m_bminfo.bmi.bmiHeader.biCompression = BI_RGB;
m_bminfo.bmi.bmiHeader.biSizeImage =
abs((m_bminfo.bmi.bmiHeader.biWidth *
m_bminfo.bmi.bmiHeader.biHeight *
m_bminfo.bmi.bmiHeader.biBitCount)/ 8);
// Display format is non-VNC compatible - use the slow blit method
m_formatmunged = TRUE;
break;
}
return TRUE;
}
BOOL
vncDesktop::SetPixFormat()
{
// Examine the bitmapinfo structure to obtain the current pixel format
m_scrinfo.format.trueColour = m_bminfo.truecolour;
m_scrinfo.format.bigEndian = 0;
// Set up the native buffer width, height and format
m_scrinfo.framebufferWidth = (CARD16) (m_bmrect.right - m_bmrect.left); // Swap endian before actually sending
m_scrinfo.framebufferHeight = (CARD16) (m_bmrect.bottom - m_bmrect.top); // Swap endian before actually sending
m_scrinfo.format.bitsPerPixel = (CARD8) m_bminfo.bmi.bmiHeader.biBitCount;
m_scrinfo.format.depth = (CARD8) m_bminfo.bmi.bmiHeader.biBitCount;
// Calculate the number of bytes per row
m_bytesPerRow = m_scrinfo.framebufferWidth * m_scrinfo.format.bitsPerPixel / 8;
return TRUE;
}
BOOL
vncDesktop::SetPixShifts()
{
// Sort out the colour shifts, etc.
DWORD redMask=0, blueMask=0, greenMask = 0;
switch (m_bminfo.bmi.bmiHeader.biBitCount)
{
case 16:
if (m_videodriver&& m_videodriver->IsDirectAccessInEffect())
{
// IMPORTANT: Mirage colormask is always 565
redMask = 0xf800;
greenMask = 0x07e0;
blueMask = 0x001f;
}
else if (m_bminfo.bmi.bmiHeader.biCompression == BI_RGB)
{
// Standard 16-bit display
// each word single pixel 5-5-5
redMask = 0x7c00; greenMask = 0x03e0; blueMask = 0x001f;
}
else
{
if (m_bminfo.bmi.bmiHeader.biCompression == BI_BITFIELDS)
{
redMask = *(DWORD *) &m_bminfo.bmi.bmiColors[0];
greenMask = *(DWORD *) &m_bminfo.bmi.bmiColors[1];
blueMask = *(DWORD *) &m_bminfo.bmi.bmiColors[2];
}
}
break;
case 32:
// Standard 24/32 bit displays
if (m_bminfo.bmi.bmiHeader.biCompression == BI_RGB ||
m_videodriver && m_videodriver->IsDirectAccessInEffect())
{
redMask = 0xff0000;
greenMask = 0xff00;
blueMask = 0x00ff;
// The real color depth is 24 bits in this case. If the depth
// is set to 32, the Tight encoder shows worse performance.
m_scrinfo.format.depth = 24;
}
else
{
if (m_bminfo.bmi.bmiHeader.biCompression == BI_BITFIELDS)
{
redMask = *(DWORD *) &m_bminfo.bmi.bmiColors[0];
greenMask = *(DWORD *) &m_bminfo.bmi.bmiColors[1];
blueMask = *(DWORD *) &m_bminfo.bmi.bmiColors[2];
}
}
break;
default:
// Other pixel formats are only valid if they're palette-based
if (m_bminfo.truecolour)
{
return FALSE;
}
return TRUE;
}
// Convert the data we just retrieved
MaskToMaxAndShift(redMask, m_scrinfo.format.redMax, m_scrinfo.format.redShift);
MaskToMaxAndShift(greenMask, m_scrinfo.format.greenMax, m_scrinfo.format.greenShift);
MaskToMaxAndShift(blueMask, m_scrinfo.format.blueMax, m_scrinfo.format.blueShift);
return TRUE;
}
BOOL
vncDesktop::SetPalette()
{
// Lock the current display palette into the memory DC we're holding
// *** CHECK THIS FOR LEAKS!
if (!m_bminfo.truecolour)
{
LOGPALETTE *palette;
UINT size = sizeof(LOGPALETTE) + (sizeof(PALETTEENTRY) * 256);
palette = (LOGPALETTE *) new char[size];
if (palette == NULL) {
return FALSE;
}
// Initialise the structure
palette->palVersion = 0x300;
palette->palNumEntries = 256;
// Get the system colours
if (GetSystemPaletteEntries(m_hrootdc, 0, 256, palette->palPalEntry) == 0)
{
delete [] palette;
return FALSE;
}
// Create a palette from those
HPALETTE pal = CreatePalette(palette);
if (pal == NULL)
{
delete [] palette;
return FALSE;
}
// Select the palette into our memory DC
HPALETTE oldpalette = SelectPalette(m_hmemdc, pal, FALSE);
if (oldpalette == NULL)
{
delete [] palette;
DeleteObject(pal);
return FALSE;
}
// Worked, so realise the palette
RealizePalette(m_hmemdc);
// It worked!
delete [] palette;
DeleteObject(oldpalette);
return TRUE;
}
// Not a palette based local screen - forget it!
return TRUE;
}
LRESULT CALLBACK DesktopWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
ATOM m_wndClass = 0;
BOOL vncDesktop::InitWindow()
{
if (m_wndClass == 0) {
// Create the window class
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = 0;
wndclass.lpfnWndProc = &DesktopWndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hAppInstance;
wndclass.hIcon = NULL;
wndclass.hCursor = NULL;
wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = (const char *) NULL;
wndclass.lpszClassName = szDesktopSink;
wndclass.hIconSm = NULL;
// Register it
m_wndClass = RegisterClassEx(&wndclass);
}
// And create a window
m_hwnd = CreateWindow(szDesktopSink,
"WinVNC",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
400, 200,
NULL,
NULL,
hAppInstance,
NULL);
if (m_hwnd == NULL) {
return FALSE;
}
// Set the "this" pointer for the window
SetWindowLong(m_hwnd, -21, (long)this); // #define GWL_USERDATA (-21)
// Enable clipboard hooking
m_hnextviewer = SetClipboardViewer(m_hwnd);
return TRUE;
}
BOOL
vncDesktop::CreateBuffers()
{
// Create a new DIB section ***
HBITMAP tempbitmap = NULL;
if (!m_formatmunged)
{
tempbitmap = CreateDIBSection(
m_hmemdc,
&m_bminfo.bmi,
DIB_RGB_COLORS,
&m_DIBbits,
NULL,
0);
}
m_freemainbuff = false;
// NOTE m_mainbuff and m_backbuff allocation can not be supressed
// even with direct access mirror surface view
if (tempbitmap == NULL)
{
m_DIBbits = NULL;
// create our own buffer to copy blits through
if ((m_mainbuff = new BYTE [ScreenBuffSize()]) == NULL) {
return FALSE;
}
m_freemainbuff = true;
if ((m_backbuff = new BYTE [ScreenBuffSize()]) == NULL) {
return FALSE;
}
return TRUE;
}
// Create our own buffer to copy blits through
if ((m_backbuff = new BYTE [ScreenBuffSize()]) == NULL) {
if (tempbitmap!= NULL)
DeleteObject(tempbitmap);
return FALSE;
}
// Delete the old memory bitmap
if (m_membitmap != NULL) {
DeleteObject(m_membitmap);
m_membitmap = NULL;
}
// Replace old membitmap with DIB section
m_membitmap = tempbitmap;
m_mainbuff = (BYTE *)m_DIBbits;
return TRUE;
}
BOOL
vncDesktop::Init(vncServer *server)
{
// Save the server pointer
m_server = server;
// Load in the arrow cursor
m_hdefcursor = LoadCursor(NULL, IDC_ARROW);
m_hcursor = m_hdefcursor;
// Spawn a thread to handle that window's message queue
vncDesktopThread *thread = new vncDesktopThread;
if (thread == NULL)
return FALSE;
m_thread = thread;
return thread->Init(this, m_server);
}
void
vncDesktop::RequestUpdate()
{
PostMessage(m_hwnd, WM_TIMER, TIMER_POLL, 0);
}
int
vncDesktop::ScreenBuffSize()
{
return m_scrinfo.format.bitsPerPixel/8 *
m_scrinfo.framebufferWidth *
m_scrinfo.framebufferHeight;
}
void
vncDesktop::FillDisplayInfo(rfbServerInitMsg *scrinfo)
{
memcpy(scrinfo, &m_scrinfo, sz_rfbServerInitMsg);
}
// Function to capture an area of the screen immediately prior to sending
// an update.
void vncDesktop::CaptureScreen(RECT &UpdateArea, BYTE *scrBuff)
{
// ASSUME rect related to virtual desktop
if (m_videodriver && m_videodriver->IsDirectAccessInEffect())
CaptureScreenFromMirage(UpdateArea, scrBuff);
else CaptureScreenFromAdapterGeneral(UpdateArea, scrBuff);
}
void vncDesktop::CaptureScreenFromAdapterGeneral(RECT rect, BYTE *scrBuff)
{
// ASSUME rect related to virtual desktop
// Protect the memory bitmap
omni_mutex_lock l(m_bitbltlock);
// Finish drawing anything in this thread
// Wish we could do this for the whole system - maybe we should
// do something with LockWindowUpdate here.
GdiFlush();
// Select the memory bitmap into the memory DC
HBITMAP oldbitmap;
if ((oldbitmap = (HBITMAP) SelectObject(m_hmemdc, m_membitmap)) == NULL)
return;
// Capture screen into bitmap
BOOL blitok = BitBlt(
m_hmemdc,
// source in m_hrootdc is relative to a virtual desktop,
// whereas dst coordinates of m_hmemdc are relative to its top-left corner (0, 0)
rect.left - m_bmrect.left,
rect.top - m_bmrect.top,
rect.right - rect.left,
rect.bottom - rect.top,
m_hrootdc,
rect.left, rect.top,
SRCCOPY);
// Select the old bitmap back into the memory DC
SelectObject(m_hmemdc, oldbitmap);
if (blitok)
{
// Copy the new data to the screen buffer (CopyToBuffer optimises this if possible)
CopyToBuffer(rect, scrBuff);
}
}
void vncDesktop::CaptureScreenFromMirage(RECT UpdateArea, BYTE *scrBuff)
{
// ASSUME rect related to virtual desktop
_ASSERTE(m_videodriver);
omni_mutex_lock l(m_bitbltlock);
CopyToBuffer(UpdateArea, scrBuff, m_videodriver->GetScreenView());
}
void vncDesktop::CaptureMouseRect()
{
POINT CursorPos;
ICONINFO IconInfo;
// If the mouse cursor handle is invalid then forget it
if (m_hcursor == NULL)
return;
// Get the cursor position
if (!GetCursorPos(&CursorPos))
return;
// Translate position for hotspot
if (GetIconInfo(m_hcursor, &IconInfo))
{
CursorPos.x -= ((int) IconInfo.xHotspot);
CursorPos.y -= ((int) IconInfo.yHotspot);
if (IconInfo.hbmMask != NULL)
DeleteObject(IconInfo.hbmMask);
if (IconInfo.hbmColor != NULL)
DeleteObject(IconInfo.hbmColor);
}
// Save the bounding rectangle
m_cursorpos.left = CursorPos.x;
m_cursorpos.top = CursorPos.y;
m_cursorpos.right = CursorPos.x + GetSystemMetrics(SM_CXCURSOR);
m_cursorpos.bottom = CursorPos.y + GetSystemMetrics(SM_CYCURSOR);
}
// Add the mouse pointer to the buffer
void vncDesktop::CaptureMouse(BYTE *scrBuff, UINT scrBuffSize)
{
// Protect the memory bitmap
omni_mutex_lock l(m_bitbltlock);
CaptureMouseRect();
// Select the memory bitmap into the memory DC
HBITMAP oldbitmap;
if ((oldbitmap = (HBITMAP) SelectObject(m_hmemdc, m_membitmap)) == NULL)
return;
// Draw the cursor
DrawIconEx(
m_hmemdc, // handle to device context
m_cursorpos.left - m_bmrect.left,
m_cursorpos.top - m_bmrect.top,
m_hcursor, // handle to icon to draw
0,0, // width of the icon
0, // index of frame in animated cursor
NULL, // handle to background brush
DI_NORMAL // icon-drawing flags
);
// Select the old bitmap back into the memory DC
SelectObject(m_hmemdc, oldbitmap);
// Clip the bounding rect to the screen
RECT screen = m_server->GetSharedRect();
// Copy the mouse cursor into the screen buffer, if any of it is visible
if (IntersectRect(&m_cursorpos, &m_cursorpos, &screen))
CopyToBuffer(m_cursorpos, scrBuff);
}
// Obtain cursor image data in server's local format.
// The length of databuf[] should be at least (width * height * 4).
BOOL
vncDesktop::GetRichCursorData(BYTE *databuf, HCURSOR hcursor, int width, int height)
{
// Protect the memory bitmap (is it really necessary here?)
omni_mutex_lock l(m_bitbltlock);
// Create bitmap, select it into memory DC
HBITMAP membitmap = CreateCompatibleBitmap(m_hrootdc, width, height);
if (membitmap == NULL) {
return FALSE;
}
HBITMAP oldbitmap = (HBITMAP) SelectObject(m_hmemdc, membitmap);
if (oldbitmap == NULL) {
DeleteObject(membitmap);
return FALSE;
}
// Draw the cursor
DrawIconEx(m_hmemdc, 0, 0, hcursor, 0, 0, 0, NULL, DI_IMAGE);
SelectObject(m_hmemdc, oldbitmap);
// Prepare BITMAPINFO structure (copy most m_bminfo fields)
BITMAPINFO *bmi = (BITMAPINFO *)calloc(1, sizeof(BITMAPINFO) + 256 * sizeof(RGBQUAD));
memcpy(bmi, &m_bminfo.bmi, sizeof(BITMAPINFO) + 256 * sizeof(RGBQUAD));
bmi->bmiHeader.biWidth = width;
bmi->bmiHeader.biHeight = -height;
// Clear data buffer and extract RGB data
memset(databuf, 0x00, width * height * 4);
int lines = GetDIBits(m_hmemdc, membitmap, 0, height, databuf, bmi, DIB_RGB_COLORS);
// Cleanup
free(bmi);
DeleteObject(membitmap);
return (lines != 0);
}
// Return the current mouse pointer position
RECT
vncDesktop::MouseRect()
{
return m_cursorpos;
}
void vncDesktop::SetCursor(HCURSOR cursor)
{
if (cursor == NULL)
m_hcursor = m_hdefcursor;
else
m_hcursor = cursor;
}
//
// Convert text from Unix (LF only) format to CR+LF.
// NOTE: The size of dst[] buffer must be at least (strlen(src) * 2 + 1).
//
void
vncDesktop::ConvertClipText(char *dst, const char *src)
{
const char *ptr0 = src;
const char *ptr1;
#ifdef __x64__
__int64 dst_pos = 0;
#else
int dst_pos = 0;
#endif
while ((ptr1 = strchr(ptr0, '\n')) != NULL) {
// Copy the string before the LF
if (ptr1 != ptr0) {
memcpy(&dst[dst_pos], ptr0, ptr1 - ptr0);
dst_pos += ptr1 - ptr0;
}
// Don't insert CR if there is one already
if (ptr1 == ptr0 || *(ptr1 - 1) != '\r') {
dst[dst_pos++] = '\r';
}
// Append LF
dst[dst_pos++] = '\n';
// Next position in the source text
ptr0 = ptr1 + 1;
}
// Copy the last string with no LF, but with '\0'
memcpy(&dst[dst_pos], ptr0, &src[strlen(src)] - ptr0 + 1);
}
//
// Manipulation of the clipboard
//
void
vncDesktop::SetClipText(LPSTR text)
{
// Open the system clipboard
if (OpenClipboard(m_hwnd))
{
// Empty it
if (EmptyClipboard())
{
HANDLE hMem = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
strlen(text) * 2 + 1);
if (hMem != NULL)
{
LPSTR pMem = (char*)GlobalLock(hMem);
// Get the data (with line endings converted to CR+LF)
ConvertClipText(pMem, text);
// Tell the clipboard
GlobalUnlock(hMem);
SetClipboardData(CF_TEXT, hMem);
}
}
}
// Now close it
CloseClipboard();
}
// INTERNAL METHODS
inline void
vncDesktop::MaskToMaxAndShift(DWORD mask, CARD16 &max, CARD8 &shift)
{
for (shift = 0; (mask & 1) == 0; shift++)
mask >>= 1;
max = (CARD16) mask;
}
// Copy data from the memory bitmap into a buffer
void vncDesktop::CopyToBuffer(RECT rect, BYTE *destbuff)
{
// Are we being asked to blit from the DIBsection to itself?
if (destbuff == m_DIBbits)
{
// Yes. Ignore the request!
return;
}
// Protect the memory bitmap
omni_mutex_lock l(m_bitbltlock);
const int crect_re_vd_left = rect.left - m_bmrect.left;
const int crect_re_vd_top = rect.top - m_bmrect.top;
_ASSERTE(crect_re_vd_left >= 0);
_ASSERTE(crect_re_vd_top >= 0);
// Calculate the scanline-ordered y position to copy from
// NB: m_membitmap is bottom2top
const int y_inv_re_vd = m_bmrect.bottom - m_bmrect.top - rect.bottom;
_ASSERTE(y_inv_re_vd >= 0);
// Calculate where in the output buffer to put the data
BYTE * destbuffpos = destbuff + (m_bytesPerRow * crect_re_vd_top);
// Set the number of bytes for GetDIBits to actually write
// NOTE : GetDIBits pads the destination buffer if biSizeImage < no. of bytes required
m_bminfo.bmi.bmiHeader.biSizeImage = (rect.bottom-rect.top) * m_bytesPerRow;
// Get the actual bits from the bitmap into the bit buffer
// If fast (DIBsection) blits are disabled then use the old GetDIBits technique
if (m_DIBbits == NULL)
{
if (GetDIBits(
m_hmemdc,
m_membitmap,
y_inv_re_vd,
rect.bottom - rect.top,
destbuffpos,
&m_bminfo.bmi,
DIB_RGB_COLORS) == 0)
{
#ifdef _MSC_VER
_RPT1(_CRT_WARN, "vncDesktop : [1] GetDIBits failed! %d\n", GetLastError());
_RPT3(_CRT_WARN, "vncDesktop : thread = %d, DC = %d, bitmap = %d\n", omni_thread::self(), m_hmemdc, m_membitmap);
_RPT2(_CRT_WARN, "vncDesktop : y = %d, height = %d\n", y_inv_re_vd, (rect.bottom-rect.top));
#endif
}
}
else
{
// Fast blits are enabled. [I have a sneaking suspicion this will never get used, unless
// something weird goes wrong in the code. It's here to keep the function general, though!]
const int bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
BYTE *srcbuffpos = (BYTE*)m_DIBbits;
srcbuffpos += (m_bytesPerRow * crect_re_vd_top) + (bytesPerPixel * crect_re_vd_left);
destbuffpos += bytesPerPixel * crect_re_vd_left;
const int widthBytes = (rect.right - rect.left) * bytesPerPixel;
for (int y = rect.top; y < rect.bottom; y++)
{
memcpy(destbuffpos, srcbuffpos, widthBytes);
srcbuffpos += m_bytesPerRow;
destbuffpos += m_bytesPerRow;
}
}
}
void vncDesktop::CopyToBuffer(RECT rect, BYTE *destbuff, const BYTE *srcbuffpos)
{
const int crect_re_vd_left = rect.left - m_bmrect.left;
const int crect_re_vd_top = rect.top - m_bmrect.top;
_ASSERTE(crect_re_vd_left >= 0);
_ASSERTE(crect_re_vd_top >= 0);
const int bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
const int bmoffset = (m_bytesPerRow * crect_re_vd_top) + (bytesPerPixel * crect_re_vd_left);
BYTE *destbuffpos = destbuff + bmoffset;
srcbuffpos += bmoffset;
const int widthBytes = (rect.right - rect.left) * bytesPerPixel;
for (int y = rect.top; y < rect.bottom; y++)
{
memcpy(destbuffpos, srcbuffpos, widthBytes);
srcbuffpos += m_bytesPerRow;
destbuffpos += m_bytesPerRow;
}
}
// Callback routine used internally to catch window movement...
BOOL CALLBACK
EnumWindowsFnCopyRect(HWND hwnd, LPARAM arg)
{
//For excluding the popup windows
if ((GetWindowLong( hwnd, GWL_STYLE) & WS_POPUP) ==0)
{
HANDLE prop = GetProp(hwnd, (LPCTSTR) MAKELONG(VNC_WINDOWPOS_ATOM, 0));
if (prop != NULL) {
if (IsWindowVisible(hwnd)) {
RECT dest;
POINT source;
// Get the window rectangle
if (GetWindowRect(hwnd, &dest)) {
// Old position
source.x = (SHORT) LOWORD(prop);
source.y = (SHORT) HIWORD(prop);
// Got the destination position. Now send to clients!
if ((source.x != dest.left) || (source.y != dest.top)) {
// Update the property entry
SHORT x = (SHORT) dest.left;
SHORT y = (SHORT) dest.top;
SetProp(hwnd,
(LPCTSTR) MAKELONG(VNC_WINDOWPOS_ATOM, 0),
(HANDLE) MAKELONG(x, y));
// Store of the copyrect
((vncDesktop*)arg)->CopyRect(dest, source);
}
} else {
RemoveProp(hwnd, (LPCTSTR) MAKELONG(VNC_WINDOWPOS_ATOM, 0));
}
} else {
RemoveProp(hwnd, (LPCTSTR) MAKELONG(VNC_WINDOWPOS_ATOM, 0));
}
} else {
// If the window has become visible then save its position!
if (IsWindowVisible(hwnd)) {
RECT dest;
if (GetWindowRect(hwnd, &dest)) {
SHORT x = (SHORT) dest.left;
SHORT y = (SHORT) dest.top;
SetProp(hwnd,
(LPCTSTR) MAKELONG(VNC_WINDOWPOS_ATOM, 0),
(HANDLE) MAKELONG(x, y));
}
}
}
}
return TRUE;
}
void
vncDesktop::SetLocalInputDisableHook(BOOL enable)
{
//SetKeyboardFilterHook(enable);
//SetMouseFilterHook(enable);
}
void
vncDesktop::SetLocalInputPriorityHook(BOOL enable)
{
if (vncService::IsWin95()) {
//SetKeyboardPriorityHook(m_hwnd,enable,RFB_LOCAL_KEYBOARD);
//SetMousePriorityHook(m_hwnd,enable,RFB_LOCAL_MOUSE);
} else {
//SetKeyboardPriorityLLHook(m_hwnd,enable,RFB_LOCAL_KEYBOARD);
//SetMousePriorityLLHook(m_hwnd,enable,RFB_LOCAL_MOUSE);
}
if (!enable)
// FIXME: incremental semantics broken here;
// that's why we're compelled to consume extra unlocks
m_server->BlockRemoteInput(false);
}
// Routine to find out which windows have moved
void
vncDesktop::CalcCopyRects()
{
// Enumerate all the desktop windows for movement
EnumWindows((WNDENUMPROC)EnumWindowsFnCopyRect, (LPARAM) this);
}
// Window procedure for the Desktop window
LRESULT CALLBACK
DesktopWndProc(HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
vncDesktop *_this = (vncDesktop*)GetWindowLong(hwnd, -21);// #define GWL_USERDATA (-2
switch (iMsg)
{
// GENERAL
case WM_DISPLAYCHANGE:
// The display resolution is changing
// We must kick off any clients since their screen size will be wrong
_this->m_displaychanged = TRUE;
return 0;
case WM_SYSCOLORCHANGE:
case WM_PALETTECHANGED:
// The palette colours have changed, so tell the server
// Get the system palette
if (!_this->SetPalette())
PostQuitMessage(0);
// Update any palette-based clients, too
_this->m_server->UpdatePalette();
return 0;
case WM_TIMER:
switch (wParam) {
case vncDesktop::TIMER_POLL:
_this->SetPollingFlag(true);
break;
case vncDesktop::TIMER_BLANK_SCREEN:
if (_this->m_server->GetBlankScreen())
_this->BlankScreen(TRUE);
break;
case vncDesktop::TIMER_RESTORE_SCREEN:
_this->BlankScreen(FALSE);
break;
}
return 0;
// CLIPBOARD MESSAGES
case WM_CHANGECBCHAIN:
// The clipboard chain has changed - check our nextviewer handle
if ((HWND)wParam == _this->m_hnextviewer)
_this->m_hnextviewer = (HWND)lParam;
else
if (_this->m_hnextviewer != NULL)
SendMessage(_this->m_hnextviewer,
WM_CHANGECBCHAIN,
wParam, lParam);
return 0;
case WM_DRAWCLIPBOARD:
// The clipboard contents have changed
if((GetClipboardOwner() != _this->Window()) &&
_this->m_initialClipBoardSeen &&
_this->m_clipboard_active)
{
LPSTR cliptext = NULL;
// Open the clipboard
if (OpenClipboard(_this->Window()))
{
// Get the clipboard data
HGLOBAL cliphandle = GetClipboardData(CF_TEXT);
if (cliphandle != NULL)
{
LPSTR clipdata = (LPSTR) GlobalLock(cliphandle);
// Copy it into a new buffer
if (clipdata == NULL)
cliptext = NULL;
else
cliptext = strdup(clipdata);
// Release the buffer and close the clipboard
GlobalUnlock(cliphandle);
}
CloseClipboard();
}
if (cliptext != NULL)
{
size_t cliplen = strlen(cliptext);
LPSTR unixtext = (char *)malloc(cliplen+1);
// Replace CR-LF with LF - never send CR-LF on the wire,
// since Unix won't like it
int unixpos=0;
for (size_t x=0; x<cliplen; x++)
{
if (cliptext[x] != '\x0d')
{
unixtext[unixpos] = cliptext[x];
unixpos++;
}
}
unixtext[unixpos] = 0;
// Free the clip text
free(cliptext);
cliptext = NULL;
// Now send the unix text to the server
_this->m_server->UpdateClipText(unixtext);
free(unixtext);
}
}
_this->m_initialClipBoardSeen = TRUE;
if (_this->m_hnextviewer != NULL)
{
// Pass the message to the next window in clipboard viewer chain.
return SendMessage(_this->m_hnextviewer, WM_DRAWCLIPBOARD, 0,0);
}
return 0;
default:
return DefWindowProc(hwnd, iMsg, wParam, lParam);
}
}
BOOL vncDesktop::CheckUpdates()
{
#ifndef _DEBUG
try
{
#endif
// Re-install polling timer if necessary
if (m_server->PollingCycleChanged())
{
SetPollingTimer();
m_server->PollingCycleChanged(false);
}
// Update the state of blank screen timer
UpdateBlankScreenTimer();
// Has the display resolution or desktop changed?
if (m_displaychanged || !vncService::InputDesktopSelected() || !inConsoleSession())
{
rfbServerInitMsg oldscrinfo = m_scrinfo;
m_displaychanged = FALSE;
// Attempt to close the old hooks
if (!Shutdown())
{
m_server->KillAuthClients();
return FALSE;
}
// Now attempt to re-install them!
ChangeResNow();
if (!Startup())
{
m_server->KillAuthClients();
return FALSE;
}
// Check if the screen info has changed
// Call this regardless of screen format change
m_server->UpdateLocalFormat();
// Add a full screen update to all the clients
m_changed_rgn.AddRect(m_bmrect);
m_server->UpdatePalette();
}
// TRIGGER THE UPDATE
RECT rect = m_server->GetSharedRect();
RECT new_rect = GetSourceRect();
IntersectRect(&new_rect, &new_rect, &m_bmrect);
// Update screen size if required
if (!EqualRect(&new_rect, &rect))
{
m_server->SetSharedRect(new_rect);
bool sendnewfb = false;
if (rect.right - rect.left != new_rect.right - new_rect.left ||
rect.bottom - rect.top != new_rect.bottom - new_rect.top)
sendnewfb = true;
// FIXME: We should not send NewFBSize if a client
// did not send framebuffer update request.
m_server->SetNewFBSize(sendnewfb);
m_changed_rgn.Clear();
if (sendnewfb && m_server->WindowShared())
{
if (new_rect.right - new_rect.left == 0 &&
new_rect.bottom - new_rect.top == 0)
{
// window is minimized
return TRUE;
}
else
{
// window is restored
// window is resized
m_changed_rgn.AddRect(new_rect);
}
}
else
{
return TRUE;
}
}
// If we have clients full region requests
if (m_server->FullRgnRequested())
{
// Capture screen to main buffer
CaptureScreen(rect, m_mainbuff);
// If we have a video driver - reset counter
if ( m_videodriver != NULL && m_videodriver->IsActive())
{
m_videodriver->ResetCounter();
}
}
// If we have incremental update requests
if (m_server->IncrRgnRequested())
{
vncRegion rgn;
// Use either a mirror video driver, or perform polling
if (m_videodriver != NULL && m_videodriver->IsActive())
{
// FIXME: If there were no incremental update requests
// for some time, we will loose updates.
// IMPORTANT: Mirage outputs the regions re (0, 0)
// so we have to offset them re virtual display
// TODOTODO
BOOL bCursorShape = FALSE;
m_videodriver->HandleDriverChanges(
this,
m_changed_rgn,
m_bmrect.left,
m_bmrect.top,
bCursorShape);
}
else
{
if (GetPollingFlag())
{
SetPollingFlag(false);
PerformPolling();
}
}
// Check for moved windows
// PrimaryDisplayOnlyShared: check if any problems when
// dragging from another display
if ((m_server->FullScreen() || m_server->PrimaryDisplayOnlyShared()) &&
!(m_videodriver && m_videodriver->IsHandlingScreen2ScreenBlt()))
{
CalcCopyRects();
}
if (m_copyrect_set)
{
// Send copyrect to all clients
m_server->CopyRect(m_copyrect_rect, m_copyrect_src);
m_copyrect_set = false;
// DEBUG: Continue auditing the code from this point.
// IMPORTANT: this order: CopyRectToBuffer, CaptureScreen, GetChangedRegion
// Copy old window rect to back buffer
CopyRectToBuffer(m_copyrect_rect, m_copyrect_src);
// Copy new window rect to main buffer
CaptureScreen(m_copyrect_rect, m_mainbuff);
// Get changed pixels to rgn
GetChangedRegion(rgn, m_copyrect_rect);
RECT rect;
rect.left= m_copyrect_src.x;
rect.top = m_copyrect_src.y;
rect.right = rect.left + (m_copyrect_rect.right - m_copyrect_rect.left);
rect.bottom = rect.top + (m_copyrect_rect.bottom - m_copyrect_rect.top);
// Refresh old window rect
m_changed_rgn.AddRect(rect);
// Don't refresh new window rect
m_changed_rgn.SubtractRect(m_copyrect_rect);
}
// Get only desktop area
vncRegion temprgn;
temprgn.Clear();
temprgn.AddRect(rect);
m_changed_rgn.Intersect(temprgn);
// Get list of rectangles for checking
rectlist rectsToScan;
m_changed_rgn.Rectangles(rectsToScan);
// Capture and check them
CheckRects(rgn, rectsToScan);
// Update the mouse
m_server->UpdateMouse();
// Send changed region data to all clients
m_server->UpdateRegion(rgn);
// Clear changed region
m_changed_rgn.Clear();
}
// Trigger an update to be sent
if (m_server->FullRgnRequested() || m_server->IncrRgnRequested())
{
m_server->TriggerUpdate();
}
#ifndef _DEBUG
}
catch (...)
{
m_server->KillAuthClients();
return FALSE;
}
#endif
return TRUE;
}
void
vncDesktop::SetPollingTimer()
{
const UINT driverCycle = 30;
const UINT minPollingCycle = 5;
UINT msec;
if (m_videodriver != NULL) {
msec = driverCycle;
} else {
msec = m_server->GetPollingCycle() / 16;
if (msec < minPollingCycle) {
msec = minPollingCycle;
}
}
m_timer_polling = (UINT)SetTimer(Window(), TIMER_POLL, msec, NULL);
}
inline void vncDesktop::CheckRects(vncRegion &rgn, rectlist &rects)
{
#ifndef _DEBUG
try
{
#endif
rectlist::iterator i;
for (i = rects.begin(); i != rects.end(); i++)
{
// Copy data to the main buffer
// FIXME: Maybe call CaptureScreen() just once?
// Check what would be more efficient.
CaptureScreen(*i, m_mainbuff);
// Check for changes in the rectangle
GetChangedRegion(rgn, *i);
}
#ifndef _DEBUG
}
catch (...)
{
throw;
}
#endif
}
// This notably improves performance when using Visual C++ 6.0 compiler
#pragma function(memcpy, memcmp)
static const int BLOCK_SIZE = 32;
/*
// A dummy version of GetChangedRegion() created for troubleshoot purposes
// when GetChangedRegion() et al are suspected for bugs/need changes.
// The code below is as simple and clear as possible.
void vncDesktop::GetChangedRegion(vncRegion &rgn, const RECT &rect)
{
rgn.AddRect(rect);
// Copy the changes to the back buffer
const int c2rect_re_vd_top = rect.top - m_bmrect.top;
const int c3rect_re_vd_left = rect.left - m_bmrect.left;
_ASSERTE(c2rect_re_vd_top >= 0);
_ASSERTE(c3rect_re_vd_left >= 0);
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
const int offset = c2rect_re_vd_top * m_bytesPerRow + c3rect_re_vd_left * bytesPerPixel;
unsigned char *o_ptr = m_backbuff + offset;
unsigned char *n_ptr = m_mainbuff + offset;
const int bytes_in_row = (rect.right - rect.left) * bytesPerPixel;
for (int y = rect.top; y < rect.bottom; y++)
{
memcpy(o_ptr, n_ptr, bytes_in_row);
n_ptr += m_bytesPerRow;
o_ptr += m_bytesPerRow;
}
}
*/
/*
// DEBUG: Another dumb and slow version of GetChangedRegion().
void vncDesktop::GetChangedRegion(vncRegion &rgn, const RECT &rect)
{
RECT newRect;
// Copy the changes to the back buffer
const int top = rect.top - m_bmrect.top;
const int left = rect.left - m_bmrect.left;
_ASSERTE(top >= 0);
_ASSERTE(left >= 0);
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
const int offset = top * m_bytesPerRow + left * bytesPerPixel;
unsigned char *o_ptr = m_backbuff + offset;
unsigned char *n_ptr = m_mainbuff + offset;
const int bytes_in_row = (rect.right - rect.left) * bytesPerPixel;
for (int y = rect.top; y < rect.bottom; y++) {
int x0 = rect.left;
while (x0 < rect.right) {
unsigned char *pNew = m_mainbuff + y * m_bytesPerRow + x0 * bytesPerPixel;
unsigned char *pOld = m_backbuff + y * m_bytesPerRow + x0 * bytesPerPixel;
if (memcmp(pOld, pNew, bytesPerPixel) != 0) {
break; // x0 points to the first difference at the left
}
x0++;
}
SetRect(&newRect, x0, y, rect.right, y + 1);
if (newRect.right - newRect.left > 0 && newRect.bottom - newRect.top > 0) {
rgn.AddRect(newRect);
}
memcpy(o_ptr, n_ptr, bytes_in_row);
n_ptr += m_bytesPerRow;
o_ptr += m_bytesPerRow;
}
}
*/
void vncDesktop::GetChangedRegion(vncRegion &rgn, const RECT &rect)
{
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
const int bytes_per_scanline = (rect.right - rect.left) * bytesPerPixel;
const int crect_re_vd_left = rect.left - m_bmrect.left;
const int crect_re_vd_top = rect.top - m_bmrect.top;
_ASSERTE(crect_re_vd_left >= 0);
_ASSERTE(crect_re_vd_top >= 0);
const int offset = crect_re_vd_top * m_bytesPerRow + crect_re_vd_left * bytesPerPixel;
unsigned char *o_ptr = m_backbuff + offset;
unsigned char *n_ptr = m_mainbuff + offset;
RECT new_rect = rect;
// Fast processing for small rectangles
if (rect.right - rect.left <= BLOCK_SIZE &&
rect.bottom - rect.top <= BLOCK_SIZE)
{
for (int y = rect.top; y < rect.bottom; y++)
{
if (memcmp(o_ptr, n_ptr, bytes_per_scanline) != 0)
{
new_rect.top = y;
UpdateChangedSubRect(rgn, new_rect);
break;
}
o_ptr += m_bytesPerRow;
n_ptr += m_bytesPerRow;
}
return;
}
// Process bigger rectangles
BOOL bTop4Move = TRUE;
for (int y = rect.top; y < rect.bottom; y++)
{
if (memcmp(o_ptr, n_ptr, bytes_per_scanline) != 0)
{
if (bTop4Move)
{
new_rect.top = y;
bTop4Move = FALSE;
}
// Skip a number of lines after a non-matched one
int n = BLOCK_SIZE / 2 - 1;
y += n;
o_ptr += n * m_bytesPerRow;
n_ptr += n * m_bytesPerRow;
}
else
{
if (!bTop4Move)
{
new_rect.bottom = y;
UpdateChangedRect(rgn, new_rect);
bTop4Move = TRUE;
}
}
o_ptr += m_bytesPerRow;
n_ptr += m_bytesPerRow;
}
if (!bTop4Move)
{
new_rect.bottom = rect.bottom;
UpdateChangedRect(rgn, new_rect);
}
}
void vncDesktop::UpdateChangedRect(vncRegion &rgn, const RECT &rect)
{
// Pass small rectangles directly to UpdateChangedSubRect
if (rect.right - rect.left <= BLOCK_SIZE &&
rect.bottom - rect.top <= BLOCK_SIZE)
{
UpdateChangedSubRect(rgn, rect);
return;
}
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
RECT new_rect;
int x, y, ay;
const int crect_re_vd_left = rect.left - m_bmrect.left;
const int crect_re_vd_top = rect.top - m_bmrect.top;
_ASSERTE(crect_re_vd_left >= 0);
_ASSERTE(crect_re_vd_top >= 0);
// Scan down the rectangle
const int offset = crect_re_vd_top * m_bytesPerRow + crect_re_vd_left * bytesPerPixel;
unsigned char *o_topleft_ptr = m_backbuff + offset;
unsigned char *n_topleft_ptr = m_mainbuff + offset;
for (y = rect.top; y < rect.bottom; y += BLOCK_SIZE)
{
// Work out way down the bitmap
unsigned char *o_row_ptr = o_topleft_ptr;
unsigned char *n_row_ptr = n_topleft_ptr;
const int blockbottom = Min(y + BLOCK_SIZE, rect.bottom);
new_rect.bottom = blockbottom;
BOOL bLeft4Move = TRUE;
for (x = rect.left; x < rect.right; x += BLOCK_SIZE)
{
// Work our way across the row
unsigned char *n_block_ptr = n_row_ptr;
unsigned char *o_block_ptr = o_row_ptr;
const UINT blockright = Min(x + BLOCK_SIZE, rect.right);
const UINT bytesPerBlockRow = (blockright-x) * bytesPerPixel;
// Scan this block
for (ay = y; ay < blockbottom; ay++)
{
if (memcmp(n_block_ptr, o_block_ptr, bytesPerBlockRow) != 0)
break;
n_block_ptr += m_bytesPerRow;
o_block_ptr += m_bytesPerRow;
}
if (ay < blockbottom)
{
// There were changes, so this block will need to be updated
if (bLeft4Move)
{
new_rect.left = x;
bLeft4Move = FALSE;
new_rect.top = ay;
}
else if (ay < new_rect.top)
{
new_rect.top = ay;
}
}
else
{
// No changes in this block, process previous changed blocks if any
if (!bLeft4Move)
{
new_rect.right = x;
UpdateChangedSubRect(rgn, new_rect);
bLeft4Move = TRUE;
}
}
o_row_ptr += bytesPerBlockRow;
n_row_ptr += bytesPerBlockRow;
}
if (!bLeft4Move)
{
new_rect.right = rect.right;
UpdateChangedSubRect(rgn, new_rect);
}
o_topleft_ptr += m_bytesPerRow * BLOCK_SIZE;
n_topleft_ptr += m_bytesPerRow * BLOCK_SIZE;
}
}
void vncDesktop::UpdateChangedSubRect(vncRegion &rgn, const RECT &rect)
{
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
int bytes_in_row = (rect.right - rect.left) * bytesPerPixel;
int y, i;
const int crect_re_vd_left = rect.left - m_bmrect.left;
const int crect_re_vd_bottom = rect.bottom - m_bmrect.top;
_ASSERTE(crect_re_vd_left >= 0);
_ASSERTE(crect_re_vd_bottom >= 0);
// Exclude unchanged scan lines at the bottom
int offset = (crect_re_vd_bottom - 1) * m_bytesPerRow + crect_re_vd_left * bytesPerPixel;
unsigned char *o_ptr = m_backbuff + offset;
unsigned char *n_ptr = m_mainbuff + offset;
RECT final_rect = rect;
final_rect.bottom = rect.top + 1;
for (y = rect.bottom - 1; y > rect.top; y--)
{
if (memcmp(o_ptr, n_ptr, bytes_in_row) != 0)
{
final_rect.bottom = y + 1;
break;
}
n_ptr -= m_bytesPerRow;
o_ptr -= m_bytesPerRow;
}
// Exclude unchanged pixels at left and right sides
const int c2rect_re_vd_left = final_rect.left - m_bmrect.left;
const int c2rect_re_vd_top = final_rect.top - m_bmrect.top;
_ASSERTE(c2rect_re_vd_left >= 0);
_ASSERTE(c2rect_re_vd_top >= 0);
offset = c2rect_re_vd_top * m_bytesPerRow + c2rect_re_vd_left * bytesPerPixel;
o_ptr = m_backbuff + offset;
n_ptr = m_mainbuff + offset;
int left_delta = bytes_in_row - 1;
int right_delta = 0;
for (y = final_rect.top; y < final_rect.bottom; y++)
{
for (i = 0; i < bytes_in_row - 1; i++)
{
if (n_ptr[i] != o_ptr[i])
{
if (i < left_delta)
left_delta = i;
break;
}
}
for (i = bytes_in_row - 1; i > 0; i--)
{
if (n_ptr[i] != o_ptr[i])
{
if (i > right_delta)
right_delta = i;
break;
}
}
n_ptr += m_bytesPerRow;
o_ptr += m_bytesPerRow;
}
final_rect.right = final_rect.left + right_delta / bytesPerPixel + 1;
final_rect.left += left_delta / bytesPerPixel;
// Update the rectangle
rgn.AddRect(final_rect);
// Copy the changes to the back buffer
const int c3rect_re_vd_left = final_rect.left - m_bmrect.left;
_ASSERTE(c3rect_re_vd_left >= 0);
offset = c2rect_re_vd_top * m_bytesPerRow + c3rect_re_vd_left * bytesPerPixel;
o_ptr = m_backbuff + offset;
n_ptr = m_mainbuff + offset;
bytes_in_row = (final_rect.right - final_rect.left) * bytesPerPixel;
for (y = final_rect.top; y < final_rect.bottom; y++)
{
memcpy(o_ptr, n_ptr, bytes_in_row);
n_ptr += m_bytesPerRow;
o_ptr += m_bytesPerRow;
}
}
void vncDesktop::PerformPolling()
{
if (m_server->PollFullScreen())
{
// Poll full screen
RECT full_rect = m_server->GetSharedRect();
PollArea(full_rect);
}
else
{
// Poll a window
if (m_server->PollForeground())
{
// Get the window rectangle for the currently selected window
HWND hwnd = GetForegroundWindow();
if (hwnd != NULL)
PollWindow(hwnd);
}
if (m_server->PollUnderCursor())
{
// Find the mouse position
POINT mousepos;
if (GetCursorPos(&mousepos))
{
// Find the window under the mouse
HWND hwnd = WindowFromPoint(mousepos);
if (hwnd != NULL)
PollWindow(hwnd);
}
}
}
}
void
vncDesktop::PollWindow(HWND hwnd)
{
// Are we set to low-load polling?
if (m_server->PollOnEventOnly())
{
// Yes, so only poll if the remote user has done something
if (!m_server->RemoteEventReceived()) {
return;
}
}
// Does the client want us to poll only console windows?
if (m_server->PollConsoleOnly())
{
char classname[20];
// Yes, so check that this is a console window...
if (GetClassName(hwnd, classname, sizeof(classname))) {
if ((strcmp(classname, "tty") != 0) &&
(strcmp(classname, "ConsoleWindowClass") != 0)) {
return;
}
}
}
RECT full_rect = m_server->GetSharedRect();
RECT rect;
// Get the rectangle
if (GetWindowRect(hwnd, &rect)) {
if (IntersectRect(&rect, &rect, &full_rect)) {
PollArea(rect);
}
}
}
//
// Implementation of the polling algorithm.
//
void vncDesktop::PollArea(const RECT &rect)
{
const int scanLine = m_pollingOrder[m_pollingStep++ % 32];
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
// Align 32x32 tiles to the left top corner of the shared area
const RECT shared = m_server->GetSharedRect();
const int leftAligned = ((rect.left - shared.left) & 0xFFFFFFE0) + shared.left;
const int topAligned = ((rect.top - shared.top) & 0xFFFFFFE0) + shared.top;
RECT rowRect = rect; // we'll need left and right borders
for (int y = topAligned; y < rect.bottom; y += 32)
{
const int tile_h = min(rect.bottom - y, 32);
// TODO: refactor it
int sl = scanLine;
// window captions suffer an arbitrary scanline...
if (y == topAligned)
sl = 31;
sl = min(sl, tile_h-1);
const int scan_y = y + sl;
_ASSERTE(scan_y >= rect.top);
_ASSERTE(scan_y < rect.bottom);
rowRect.top = scan_y;
rowRect.bottom = scan_y + 1;
CaptureScreen(rowRect, m_mainbuff);
const int offset = (scan_y-m_bmrect.top) * m_bytesPerRow + (leftAligned-m_bmrect.left) * bytesPerPixel;
const unsigned char *o_ptr = m_backbuff + offset;
const unsigned char *n_ptr = m_mainbuff + offset;
for (int x = leftAligned; x < rect.right; x += 32)
{
const int tile_w = min(rect.right - x, 32);
const int nBytes = tile_w * bytesPerPixel;
if (memcmp(o_ptr, n_ptr, nBytes) != 0)
{
RECT tileRect;
tileRect.left = x;
tileRect.top = y;
tileRect.right = x + tile_w;
tileRect.bottom = y + tile_h;
m_changed_rgn.AddRect(tileRect);
}
o_ptr += nBytes;
n_ptr += nBytes;
}
}
}
void vncDesktop::CopyRect(const RECT &rcDest, const POINT &ptSrc)
{
const int offset_x = rcDest.left - ptSrc.x;
const int offset_y = rcDest.top - ptSrc.y;
// Clip the destination to the screen
RECT destrect;
if (!IntersectRect(&destrect, &rcDest, &m_server->GetSharedRect()))
return;
// NOTE: This is important. Each pixel in destrect is either salvaged
// by copyrect or became dirty.
m_changed_rgn.AddRect(destrect);
// Work out the source rectangle
RECT srcrect;
srcrect.left = destrect.left - offset_x;
srcrect.top = destrect.top - offset_y;
srcrect.right = srcrect.left + destrect.right - destrect.left;
srcrect.bottom = srcrect.top + destrect.bottom - destrect.top;
// Clip the source to the screen
RECT srcrect2;
if (!IntersectRect(&srcrect2, &srcrect, &m_server->GetSharedRect()))
return;
destrect.left += (srcrect2.left - srcrect.left);
destrect.top += (srcrect2.top - srcrect.top);
destrect.right = srcrect2.right - srcrect2.left + destrect.left;
destrect.bottom = srcrect2.bottom - srcrect2.top + destrect.top;
if ( destrect.right - destrect.left >= 16 &&
destrect.bottom - destrect.top >= 16 ) {
m_changed_rgn.SubtractRect(destrect);
m_copyrect_rect = destrect;
m_copyrect_src.x = srcrect2.left;
m_copyrect_src.y = srcrect2.top;
m_copyrect_set = TRUE;
//DPF(("CopyRect: (%d, %d) (%d, %d, %d, %d)\n",
// m_copyrect_src.x,
// m_copyrect_src.y,
// m_copyrect_rect.left,
// m_copyrect_rect.top,
// m_copyrect_rect.right,
// m_copyrect_rect.bottom));
}
}
//
// Copy the data from one rectangle of the back buffer to another.
//
void vncDesktop::CopyRectToBuffer(const RECT &dest, const POINT &source)
{
const int src_x = source.x - m_bmrect.left;
const int src_y = source.y - m_bmrect.top;
_ASSERTE(src_x >= 0);
_ASSERTE(src_y >= 0);
const int dst_x = dest.left - m_bmrect.left;
const int dst_y = dest.top - m_bmrect.top;
_ASSERTE(dst_x >= 0);
_ASSERTE(dst_y >= 0);
const unsigned int bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
const unsigned int bytesPerLine = (dest.right - dest.left) * bytesPerPixel;
BYTE *srcptr = m_backbuff + src_y * m_bytesPerRow + src_x * bytesPerPixel;
BYTE *destptr = m_backbuff + dst_y * m_bytesPerRow + dst_x * bytesPerPixel;
if (dst_y < src_y) {
for (int y = dest.top; y < dest.bottom; y++) {
memmove(destptr, srcptr, bytesPerLine);
srcptr += m_bytesPerRow;
destptr += m_bytesPerRow;
}
} else {
srcptr += m_bytesPerRow * (dest.bottom - dest.top - 1);
destptr += m_bytesPerRow * (dest.bottom - dest.top - 1);
for (int y = dest.bottom; y > dest.top; y--) {
memmove(destptr, srcptr, bytesPerLine);
srcptr -= m_bytesPerRow;
destptr -= m_bytesPerRow;
}
}
}
BOOL IsBadDirectAccessConfig()
{
if (IsWinVerOrHigher(5, 1))
{
if (GetSystemMetrics(SM_XVIRTUALSCREEN) < 0)
return TRUE;
if (GetSystemMetrics(SM_YVIRTUALSCREEN) < 0)
return TRUE;
}
return FALSE;
}
BOOL vncDesktop::InitVideoDriver()
{
// Mirror video drivers supported under Win2K, WinXP, WinVista
// and Windows NT 4.0 SP3 (we assume SP6).
if (!vncService::IsWinNT())
return FALSE;
// FIXME: Windows NT 4.0 support is broken and thus we disable it here.
if (!IsWinVerOrHigher(5, 0))
return FALSE;
if (m_server->DontUseDriver())
{
return FALSE;
}
BOOL bIsBadDASDConfig = IsBadDirectAccessConfig();
BOOL bSolicitDASD = m_server->DriverDirectAccess() & !bIsBadDASDConfig;
_ASSERTE(!m_videodriver);
m_videodriver = new vncVideoDriver;
if (!m_videodriver)
{
return FALSE;
}
if (IsWinVerOrHigher(5, 0))
{
// restart the driver if left running.
// NOTE that on NT4 it must be running beforehand
if (m_videodriver->TestMapped())
{
m_videodriver->Deactivate();
}
_ASSERTE(!m_videodriver->TestMapped());
}
{
RECT vdesk_rect;
GetSourceDisplayRect(vdesk_rect);
BOOL b = m_videodriver->Activate(bSolicitDASD, &vdesk_rect);
}
if (!m_videodriver->CheckVersion())
{
// IMPORTANT: fail on NT46
if (IsNtVer(4, 0))
return FALSE;
}
if (m_videodriver->MapSharedbuffers(bSolicitDASD))
{
}
else
{
delete m_videodriver;
m_videodriver = NULL;
return FALSE;
}
_ASSERTE(bSolicitDASD == m_videodriver->IsDirectAccessInEffect());
return TRUE;
}
void vncDesktop::ShutdownVideoDriver()
{
if (m_videodriver == NULL)
return;
delete m_videodriver;
m_videodriver = NULL;
}
void
vncDesktop::UpdateBlankScreenTimer()
{
BOOL active = m_server->GetBlankScreen();
if (active && !m_timer_blank_screen) {
m_timer_blank_screen = (UINT)SetTimer(Window(), TIMER_BLANK_SCREEN, 50, NULL);
} else if (!active && m_timer_blank_screen) {
KillTimer(Window(), TIMER_BLANK_SCREEN);
m_timer_blank_screen = 0;
PostMessage(m_hwnd, WM_TIMER, TIMER_RESTORE_SCREEN, 0);
}
}
void
vncDesktop::BlankScreen(BOOL set)
{
if (set) {
SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 1, NULL, 0);
SendMessage(GetDesktopWindow(), WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)2);
} else {
SystemParametersInfo(SPI_SETPOWEROFFACTIVE, 0, NULL, 0);
SendMessage(GetDesktopWindow(), WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)-1);
}
}
// created for debug purposes
bool SaveBitmapToBMPFile(
HANDLE hFile,
void *ptrBm,
void *ptrPal,
int bmwidth,
int bmheight,
int bmstride,
int bmclrdepth)
{
BITMAPINFOHEADER bih = {0};
bih.biSize = sizeof(bih);
bih.biWidth = bmwidth;
bih.biHeight = bmheight;
bih.biPlanes = 1;
bih.biCompression = BI_RGB;
DWORD bitFields[3] = {0, 0, 0};
if (bmclrdepth == 1)
{
bih.biBitCount = 1;
bih.biClrUsed = 2;
}
else if (bmclrdepth == 2)
{
bih.biBitCount = 2;
bih.biClrUsed = 4;
}
else if (bmclrdepth == 4)
{
bih.biBitCount = 4;
bih.biClrUsed = 0x10;
}
else if (bmclrdepth == 8)
{
bih.biBitCount = 8;
bih.biClrUsed = 0x100;
}
else if (bmclrdepth == 16)
{
bih.biBitCount = 16;
bih.biCompression = BI_BITFIELDS;
// TODO: use actual masks
bitFields[0] = 0xF800;
bitFields[1] = 0x07E0;
bitFields[2] = 0x001F;
}
else if (bmclrdepth == 24)
{
bih.biBitCount = 24;
}
else if (bmclrdepth == 32)
{
bih.biBitCount = 32;
}
else
_ASSERTE(false);
BITMAPFILEHEADER bfh = {0};
bfh.bfType = 0x4d42; // 0x42 = "B" 0x4d = "M"
bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + bih.biSize;
if (bih.biClrUsed)
{
bfh.bfOffBits += bih.biClrUsed * sizeof(RGBQUAD);
}
else if (bitFields[0] || bitFields[1] || bitFields[2])
{
bfh.bfOffBits += sizeof(bitFields);
}
unsigned lineSize = (((bih.biWidth * bih.biBitCount) + 15) / 8) & ~1;
bfh.bfSize = bfh.bfOffBits + lineSize * bih.biHeight;
ULONG ulnWr = 0;
if (!WriteFile(hFile, &bfh, sizeof(bfh), &ulnWr, NULL) || ulnWr!=sizeof(bfh))
return false;
if (!WriteFile(hFile, &bih, sizeof(bih), &ulnWr, NULL) || ulnWr!=sizeof(bih))
return false;
if (ptrPal)
{
if (!WriteFile(hFile, ptrPal, bih.biClrUsed * sizeof(RGBQUAD), &ulnWr, NULL) || ulnWr!=bih.biClrUsed * sizeof(RGBQUAD))
return false;
}
else if (bih.biCompression == BI_BITFIELDS)
{
if (!WriteFile(hFile, bitFields, sizeof(bitFields), &ulnWr, NULL) || ulnWr!=sizeof(bitFields))
return false;
}
for (int i = 0; i < bih.biHeight; i++)
{
char *pDWr = (char*)ptrBm + (bih.biHeight - i - 1) * bmstride;
if (!WriteFile(hFile, pDWr, lineSize, &ulnWr, NULL) || ulnWr!=lineSize)
return false;
}
return true;
}
// created for debug purposes
bool bDbgBmDump(
void *ptr,
int bmwidth,
int bmheight,
int bmstride,
int bmclrdepth)
{
if (bmclrdepth!=16 && bmclrdepth!=32)
{
// TODO: add 8 bpp
return false;
}
SYSTEMTIME stm;
GetSystemTime(&stm);
TCHAR szFileName[MAX_PATH];
sprintf(
szFileName,
"%04u.%02u.%02u-%02u-%02u-%02u-0x%08p.bmp",
stm.wYear, stm.wMonth, stm.wDay,
stm.wHour, stm.wMinute, stm.wSecond,
ptr);
HANDLE hFile = CreateFile(
szFileName,
FILE_WRITE_DATA,
0,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile==INVALID_HANDLE_VALUE)
{
return false;
}
bool b= SaveBitmapToBMPFile(
hFile,
ptr,
NULL,
bmwidth,
bmheight,
bmstride,
bmclrdepth);
CloseHandle(hFile);
return b;
}
// created for debug purposes
bool vncDesktop::bDbgDumpSurfBuffers(const RECT &rcl)
{
const int c2rect_re_vd_top = rcl.top - m_bmrect.top;
const int c3rect_re_vd_left = rcl.left - m_bmrect.left;
_ASSERTE(c2rect_re_vd_top >= 0);
_ASSERTE(c3rect_re_vd_left >= 0);
const UINT bytesPerPixel = m_scrinfo.format.bitsPerPixel / 8;
const int offset = c2rect_re_vd_top * m_bytesPerRow + c3rect_re_vd_left * bytesPerPixel;
bool b1 = bDbgBmDump(
m_mainbuff+offset,
rcl.right - rcl.left,
rcl.bottom - rcl.top,
m_bytesPerRow,
m_scrinfo.format.bitsPerPixel);
bool b2 = bDbgBmDump(
m_backbuff+offset,
rcl.right - rcl.left,
rcl.bottom - rcl.top,
m_bytesPerRow,
m_scrinfo.format.bitsPerPixel);
return b1 && b2;
}