// 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 // 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 #else #include #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; xm_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; }