📄 vncdesktop.cpp
字号:
// Copyright (C) 2002-2003 RealVNC Ltd. 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.//// 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 <assert.h>#include "stdhdrs.h"// Custom headers#include <omnithread.h>#include "WinVNC.h"#include "VNCHooks\VNCHooks.h"#include "vncServer.h"#include "vncKeymap.h"#include "rfbRegion.h"#include "rfbRect.h"#include "vncDesktop.h"#include "vncService.h"// Constantsconst 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");const char szDesktopSink[] = "WinVNC desktop sink";// The desktop handler thread// This handles the messages posted by RFBLib to the vncDesktop windowclass 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); void PollWindow(rfb::Region2D &rgn, HWND hwnd);protected: vncServer *m_server; vncDesktop *m_desktop; omni_mutex m_returnLock; omni_condition *m_returnsig; BOOL m_return; BOOL m_returnset;};BOOLvncDesktopThread::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;}voidvncDesktopThread::ReturnVal(BOOL result){ omni_mutex_lock l(m_returnLock); m_returnset = TRUE; m_return = result; m_returnsig->signal();}voidvncDesktopThread::PollWindow(rfb::Region2D &rgn, 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_desktop->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 rect; // Get the rectangle if (GetWindowRect(hwnd, &rect)) { rfb::Rect wrect = rfb::Rect(rect).intersect(m_desktop->m_bmrect); if (!wrect.is_empty()) { rgn = rgn.union_(rfb::Region2D(wrect)); } }}void *vncDesktopThread::run_undetached(void *arg){ // Save the thread's "home" desktop, under NT (no effect under 9x) HDESK home_desktop = GetThreadDesktop(GetCurrentThreadId()); // Attempt to initialise and return success or failure if (!m_desktop->Startup()) { vncService::SelectHDESK(home_desktop); ReturnVal(FALSE); return NULL; } // Grab the initial display contents // *** m_desktop->m_buffer.GrabRegion(m_desktop->m_bmrect); // *** m_desktop->m_buffer.Clear(m_desktop->m_bmrect); // 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); // All changes in the state of the display are stored in a local // UpdateTracker object, and are flushed to the vncServer whenever // client updates are about to be triggered rfb::SimpleUpdateTracker clipped_updates; rfb::ClippedUpdateTracker updates(clipped_updates, m_desktop->m_bmrect); clipped_updates.enable_copyrect(true); // Incoming update messages are collated into a single region cache // The region cache areas are checked for changes before an update // is triggered, and the changed areas are passed to the UpdateTracker rfb::Region2D rgncache = m_desktop->m_bmrect; // The previous cursor position is stored, to allow us to erase the // old instance whenever it moves. rfb::Point oldcursorpos; // Set the hook thread to a high priority // *** set_priority(omni_thread::PRIORITY_HIGH); BOOL idle_skip = TRUE; ULONG idle_skip_count = 0; MSG msg; while (TRUE) { if (!PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE)) { // // - MESSAGE QUEUE EMPTY // Whenever the message queue becomes empty, we check to see whether // there are updates to be passed to clients. if (idle_skip) { idle_skip = FALSE; if (idle_skip_count++ < 4) { Sleep(5); continue; } } idle_skip_count = 0; // Clear the triggered flag m_desktop->m_update_triggered = FALSE; // // CHECK SCREEN FORMAT // First, we must check that the screen hasnt changed too much. if (m_desktop->m_displaychanged || !vncService::InputDesktopSelected()) { rfbServerInitMsg oldscrinfo = m_desktop->m_scrinfo; m_desktop->m_displaychanged = FALSE; // Attempt to close the old hooks if (!m_desktop->Shutdown()) { m_server->KillAuthClients(); break; } // Now attempt to re-install them! if (!m_desktop->Startup()) { m_server->KillAuthClients(); break; } // Check that the screen info hasn't changed vnclog.Print(LL_INTINFO, VNCLOG("SCR: old screen format %dx%dx%d\n"), oldscrinfo.framebufferWidth, oldscrinfo.framebufferHeight, oldscrinfo.format.bitsPerPixel); vnclog.Print(LL_INTINFO, VNCLOG("SCR: new screen format %dx%dx%d\n"), m_desktop->m_scrinfo.framebufferWidth, m_desktop->m_scrinfo.framebufferHeight, m_desktop->m_scrinfo.format.bitsPerPixel); if ((m_desktop->m_scrinfo.framebufferWidth != oldscrinfo.framebufferWidth) || (m_desktop->m_scrinfo.framebufferHeight != oldscrinfo.framebufferHeight)) { m_server->KillAuthClients(); break; } else if (memcmp(&m_desktop->m_scrinfo.format, &oldscrinfo.format, sizeof(rfbPixelFormat)) != 0) { m_server->UpdateLocalFormat(); } // Adjust the UpdateTracker clip region updates.set_clip_region(m_desktop->m_bmrect); // Add a full screen update to all the clients rgncache = rgncache.union_(rfb::Region2D(m_desktop->m_bmrect)); m_server->UpdatePalette(); } // // CALCULATE CHANGES // if (m_desktop->m_server->UpdateWanted()) { // POLL PROBLEM AREAS // We add specific areas of the screen to the region cache, // causing them to be fetched for processing. if (m_desktop->m_server->PollFullScreen()) { rfb::Rect rect = m_desktop->m_qtrscreen; rect = rect.translate(rfb::Point(0, m_desktop->m_pollingcycle * m_desktop->m_qtrscreen.br.y)); rgncache = rgncache.union_(rfb::Region2D(rect)); m_desktop->m_pollingcycle = (m_desktop->m_pollingcycle + 1) % 4; } if (m_desktop->m_server->PollForeground()) { // Get the window rectangle for the currently selected window HWND hwnd = GetForegroundWindow(); if (hwnd != NULL) PollWindow(rgncache, hwnd); } if (m_desktop->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(rgncache, hwnd); } } // PROCESS THE MOUSE POINTER // Some of the hard work is done in clients, some here // This code fetches the desktop under the old pointer position // but the client is responsible for actually encoding and sending // it when required. // This code also renders the pointer and saves the rendered position // Clients include this when rendering updates. // The code is complicated in this way because we wish to avoid // rendering parts of the screen the mouse moved through between // client updates, since in practice they will probably not have changed. // Re-render the mouse's old location if it's moved BOOL cursormoved = FALSE; POINT cursorpos; if (GetCursorPos(&cursorpos) && ((cursorpos.x != oldcursorpos.x) || (cursorpos.y != oldcursorpos.y))) { cursormoved = TRUE; oldcursorpos = rfb::Point(cursorpos); } if (cursormoved) { if (!m_desktop->m_cursorpos.is_empty()) rgncache = rgncache.union_(rfb::Region2D(m_desktop->m_cursorpos)); } { // Prevent any clients from accessing the Buffer omni_mutex_lock l(m_desktop->m_update_lock); // CHECK FOR COPYRECTS // This actually just checks where the Foreground window is m_desktop->CalcCopyRects(updates); // GRAB THE DISPLAY // Fetch data from the display to our display cache. m_desktop->m_buffer.GrabRegion(rgncache); // Render the mouse m_desktop->m_buffer.GrabMouse(); if (cursormoved) { // Inform clients that it has moved m_desktop->m_server->UpdateMouse(); // Get the buffer to fetch the pointer bitmap if (!m_desktop->m_cursorpos.is_empty()) rgncache = rgncache.union_(rfb::Region2D(m_desktop->m_cursorpos)); } // SCAN THE CHANGED REGION FOR ACTUAL CHANGES // The hooks return hints as to areas that may have changed. // We check the suggested areas, and just send the ones that // have actually changed. // Note that we deliberately don't check the copyrect destination // here, to reduce the overhead & the likelihood of corrupting the // backbuffer contents. rfb::Region2D checkrgn = rgncache.subtract(clipped_updates.get_copied_region()); rgncache = clipped_updates.get_copied_region(); rfb::Region2D changedrgn; m_desktop->m_buffer.CheckRegion(changedrgn, checkrgn); // FLUSH UPDATES TO CLIENTS // Add the bits that have really changed to their update regions // Note that the cursor is NOT included - they must do that // themselves, for the reasons above. // This call implicitly kicks clients to update themselves updates.add_changed(changedrgn); clipped_updates.get_update(m_server->GetUpdateTracker()); } // Clear the update tracker and region cache clipped_updates.clear(); } // Now wait for more messages to be queued if (!WaitMessage()) { vnclog.Print(LL_INTERR, VNCLOG("WaitMessage() failed\n")); break; } } else if (msg.message == RFB_SCREEN_UPDATE) { // Process an incoming update event // An area of the screen has changed rfb::Rect rect; rect.tl = rfb::Point((SHORT)LOWORD(msg.wParam), (SHORT)HIWORD(msg.wParam)); rect.br = rfb::Point((SHORT)LOWORD(msg.lParam), (SHORT)HIWORD(msg.lParam)); rect = rect.intersect(m_desktop->m_bmrect); if (!rect.is_empty()) { rgncache = rgncache.union_(rfb::Region2D(rect)); } idle_skip = TRUE; } else if (msg.message == RFB_MOUSE_UPDATE) { // Process an incoming mouse event // Save the cursor ID m_desktop->SetCursor((HCURSOR) msg.wParam); idle_skip = TRUE; } else if (msg.message == WM_QUIT) { break; } else { // Process any other messages normally DispatchMessage(&msg); idle_skip = TRUE; } } m_desktop->SetClipboardActive(FALSE); vnclog.Print(LL_INTINFO, VNCLOG("quitting desktop server thread\n")); // Clear all the hooks and close windows, etc. m_desktop->Shutdown(); // 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;}// ImplementationvncDesktop::vncDesktop(){ m_thread = NULL; m_hwnd = NULL; m_timerid = 0; m_hnextviewer = NULL; m_hcursor = NULL; m_displaychanged = FALSE; m_update_triggered = FALSE; m_hrootdc = NULL; m_hmemdc = NULL; m_membitmap = NULL; m_initialClipBoardSeen = FALSE; m_foreground_window = NULL; // Vars for Will Dean's DIBsection patch m_DIBbits = NULL; m_formatmunged = FALSE; m_clipboard_active = FALSE; m_pollingcycle = 0;}vncDesktop::~vncDesktop(){ vnclog.Print(LL_INTINFO, VNCLOG("killing screen server\n")); // 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();}// Tell the desktop hooks to grab & update a particular rectanglevoidvncDesktop::QueueRect(const rfb::Rect &rect){ ULONG vwParam = MAKELONG(rect.tl.x, rect.tl.y); ULONG vlParam = MAKELONG(rect.br.x, rect.br.y); PostMessage(Window(), RFB_SCREEN_UPDATE, vwParam, vlParam);} // Kick the desktop hooks to perform an updatevoidvncDesktop::TriggerUpdate(){ // Note that we should really lock the update lock here, // but there are periodic timer updates anyway, so // we don't actually need to. Something to think about. if (!m_update_triggered) { m_update_triggered = TRUE; PostMessage(Window(), WM_TIMER, 0, 0); }}// Routine to startup and install all the hooks and stuffBOOLvncDesktop::Startup(){ // Initialise the Desktop object // *** // vncService::SelectInputWinStation(); KillScreenSaver(); if (!InitDesktop()) return FALSE; if (!InitBitmap()) return FALSE; if (!ThunkBitmapInfo()) return FALSE; EnableOptimisedBlits(); if (!SetPixFormat()) return FALSE; if (!SetPixShifts()) return FALSE; if (!SetPalette()) return FALSE; if (!InitWindow()) return FALSE; // Add the system hook if (!SetHooks( GetCurrentThreadId(), RFB_SCREEN_UPDATE, RFB_COPYRECT_UPDATE, RFB_MOUSE_UPDATE )) { vnclog.Print(LL_INTERR, VNCLOG("failed to set system hooks\n")); // Switch on full screen polling, so they can see something, at least...
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -