/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace rfb; using namespace rfb::win32; // - Statics & consts static LogWriter vlog("DesktopWindow"); const int TIMER_BUMPSCROLL = 1; const int TIMER_POINTER_INTERVAL = 2; const int TIMER_POINTER_3BUTTON = 3; // // -=- DesktopWindowClass // // Window class used as the basis for all DesktopWindow instances // class DesktopWindowClass { public: DesktopWindowClass(); ~DesktopWindowClass(); ATOM classAtom; HINSTANCE instance; }; LRESULT CALLBACK DesktopWindowProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; if (msg == WM_CREATE) SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams); else if (msg == WM_DESTROY) SetWindowLong(wnd, GWL_USERDATA, 0); DesktopWindow* _this = (DesktopWindow*) GetWindowLong(wnd, GWL_USERDATA); if (!_this) { vlog.info("null _this in %x, message %u", wnd, msg); return rfb::win32::SafeDefWindowProc(wnd, msg, wParam, lParam); } try { result = _this->processMessage(msg, wParam, lParam); } catch (rfb::UnsupportedPixelFormatException &e) { MsgBox(0, e.str(), MB_OK | MB_ICONINFORMATION); _this->getCallback()->closeWindow(); } catch (rdr::Exception& e) { vlog.error("untrapped: %s", e.str()); } return result; }; static HCURSOR dotCursor = (HCURSOR)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDC_DOT_CURSOR), IMAGE_CURSOR, 0, 0, LR_SHARED); static HCURSOR arrowCursor = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED); DesktopWindowClass::DesktopWindowClass() : classAtom(0) { WNDCLASS wndClass; wndClass.style = 0; wndClass.lpfnWndProc = DesktopWindowProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = instance = GetModuleHandle(0); wndClass.hIcon = (HICON)LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 0, 0, LR_SHARED); if (!wndClass.hIcon) printf("unable to load icon:%ld", GetLastError()); wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = NULL; wndClass.lpszMenuName = 0; wndClass.lpszClassName = _T("rfb::win32::DesktopWindowClass"); classAtom = RegisterClass(&wndClass); if (!classAtom) { throw rdr::SystemException("unable to register DesktopWindow window class", GetLastError()); } } DesktopWindowClass::~DesktopWindowClass() { if (classAtom) { UnregisterClass((const TCHAR*)classAtom, instance); } } static DesktopWindowClass baseClass; // // -=- FrameClass // // Window class used for child windows that display pixel data // class FrameClass { public: FrameClass(); ~FrameClass(); ATOM classAtom; HINSTANCE instance; }; LRESULT CALLBACK FrameProc(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) { LRESULT result = 0; if (msg == WM_CREATE) SetWindowLong(wnd, GWL_USERDATA, (long)((CREATESTRUCT*)lParam)->lpCreateParams); else if (msg == WM_DESTROY) SetWindowLong(wnd, GWL_USERDATA, 0); DesktopWindow* _this = (DesktopWindow*) GetWindowLong(wnd, GWL_USERDATA); if (!_this) { vlog.info("null _this in %x, message %u", wnd, msg); return rfb::win32::SafeDefWindowProc(wnd, msg, wParam, lParam); } try { result = _this->processFrameMessage(msg, wParam, lParam); } catch (rdr::Exception& e) { vlog.error("untrapped: %s", e.str()); } return result; } FrameClass::FrameClass() : classAtom(0) { WNDCLASS wndClass; wndClass.style = 0; wndClass.lpfnWndProc = FrameProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = instance = GetModuleHandle(0); wndClass.hIcon = 0; wndClass.hCursor = NULL; wndClass.hbrBackground = NULL; wndClass.lpszMenuName = 0; wndClass.lpszClassName = _T("rfb::win32::FrameClass"); classAtom = RegisterClass(&wndClass); if (!classAtom) { throw rdr::SystemException("unable to register Frame window class", GetLastError()); } } FrameClass::~FrameClass() { if (classAtom) { UnregisterClass((const TCHAR*)classAtom, instance); } } FrameClass frameClass; // // -=- DesktopWindow instance implementation // DesktopWindow::DesktopWindow(Callback* cb) : bumpScroll(false), palette_changed(false), fullscreenActive(false), fullscreenRestore(false), systemCursorVisible(true), trackingMouseLeave(false), cursorInBuffer(false), cursorVisible(false), cursorAvailable(false), internalSetCursor(false), cursorImage(0), cursorMask(0), cursorWidth(0), cursorHeight(0), showToolbar(false), buffer(0), has_focus(false), autoScaling(false), window_size(0, 0, 32, 32), client_size(0, 0, 16, 16), handle(0), frameHandle(0), callback(cb) { // Create the window const char* name = "DesktopWindow"; handle = CreateWindow((const TCHAR*)baseClass.classAtom, TStr(name), WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, 0, 0, 10, 10, 0, 0, baseClass.instance, this); if (!handle) throw rdr::SystemException("unable to create WMNotifier window instance", GetLastError()); vlog.debug("created window \"%s\" (%x)", name, handle); // Create the toolbar tb.create(handle); vlog.debug("created toolbar window \"%s\" (%x)", "ViewerToolBar", tb.getHandle()); // Create the frame window frameHandle = CreateWindowEx(WS_EX_CLIENTEDGE, (const TCHAR*)frameClass.classAtom, 0, WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, handle, 0, frameClass.instance, this); if (!frameHandle) { throw rdr::SystemException("unable to create rfb frame window instance", GetLastError()); } vlog.debug("created window \"%s\" (%x)", "Frame Window", frameHandle); // Initialise the CPointer pointer handler ptr.setHWND(frameHandle); ptr.setIntervalTimerId(TIMER_POINTER_INTERVAL); ptr.set3ButtonTimerId(TIMER_POINTER_3BUTTON); // Initialise the bumpscroll timer bumpScrollTimer.setHWND(handle); bumpScrollTimer.setId(TIMER_BUMPSCROLL); // Hook the clipboard clipboard.setNotifier(this); // Create the backing buffer buffer = new win32::ScaledDIBSectionBuffer(frameHandle); // Show the window centerWindow(handle, 0); ShowWindow(handle, SW_SHOW); } DesktopWindow::~DesktopWindow() { vlog.debug("~DesktopWindow"); showSystemCursor(); if (handle) { disableLowLevelKeyEvents(handle); DestroyWindow(handle); handle = 0; } delete buffer; if (cursorImage) delete [] cursorImage; if (cursorMask) delete [] cursorMask; vlog.debug("~DesktopWindow done"); } void DesktopWindow::setFullscreen(bool fs) { if (fs && !fullscreenActive) { fullscreenActive = bumpScroll = true; // Un-minimize the window if required if (GetWindowLong(handle, GWL_STYLE) & WS_MINIMIZE) ShowWindow(handle, SW_RESTORE); // Save the current window position GetWindowRect(handle, &fullscreenOldRect); // Find the size of the display the window is on MonitorInfo mi(handle); // Hide the toolbar if (tb.isVisible()) tb.hide(); SetWindowLong(frameHandle, GWL_EXSTYLE, 0); // Set the window full-screen DWORD flags = GetWindowLong(handle, GWL_STYLE); fullscreenOldFlags = flags; flags = flags & ~(WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZE | WS_MINIMIZE); vlog.debug("flags=%x", flags); SetWindowLong(handle, GWL_STYLE, flags); SetWindowPos(handle, HWND_TOP, mi.rcMonitor.left, mi.rcMonitor.top, mi.rcMonitor.right-mi.rcMonitor.left, mi.rcMonitor.bottom-mi.rcMonitor.top, SWP_FRAMECHANGED); } else if (!fs && fullscreenActive) { fullscreenActive = bumpScroll = false; // Show the toolbar if (showToolbar) tb.show(); SetWindowLong(frameHandle, GWL_EXSTYLE, WS_EX_CLIENTEDGE); // Set the window non-fullscreen SetWindowLong(handle, GWL_STYLE, fullscreenOldFlags); // Set the window position SetWindowPos(handle, HWND_NOTOPMOST, fullscreenOldRect.left, fullscreenOldRect.top, fullscreenOldRect.right - fullscreenOldRect.left, fullscreenOldRect.bottom - fullscreenOldRect.top, SWP_FRAMECHANGED); } // Adjust the viewport offset to cope with change in size between FS // and previous window state. setViewportOffset(scrolloffset); } void DesktopWindow::setShowToolbar(bool st) { showToolbar = st; if (fullscreenActive) return; RECT r; GetWindowRect(handle, &r); bool maximized = GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE; if (showToolbar && !tb.isVisible()) { refreshToolbarButtons(); tb.show(); if (!maximized) r.bottom += tb.getHeight(); } else if (!showToolbar && tb.isVisible()) { tb.hide(); if (!maximized) r.bottom -= tb.getHeight(); } // Resize the chiled windows even if the parent window size // has not been changed (the main window is maximized) if (maximized) SendMessage(handle, WM_SIZE, 0, 0); else SetWindowPos(handle, NULL, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOMOVE | SWP_NOZORDER); } void DesktopWindow::refreshToolbarButtons() { int scale = getDesktopScale(); if (scale <= 10) { tb.enableButton(ID_ZOOM_IN, true); tb.enableButton(ID_ZOOM_OUT, false); } else if (scale >= 200) { tb.enableButton(ID_ZOOM_IN, false); tb.enableButton(ID_ZOOM_OUT, true); } else { tb.enableButton(ID_ZOOM_IN, true); tb.enableButton(ID_ZOOM_OUT, true); } if (buffer->isScaling() || isAutoScaling()) tb.enableButton(ID_ACTUAL_SIZE, true); else tb.enableButton(ID_ACTUAL_SIZE, false); if (isAutoScaling()) tb.pressButton(ID_AUTO_SIZE, true); else tb.pressButton(ID_AUTO_SIZE, false); } void DesktopWindow::setDisableWinKeys(bool dwk) { // Enable low-level event hooking, so we get special keys directly if (dwk) enableLowLevelKeyEvents(handle); else disableLowLevelKeyEvents(handle); } void DesktopWindow::setMonitor(const char* monitor) { MonitorInfo mi(monitor); mi.moveTo(handle); } char* DesktopWindow::getMonitor() const { MonitorInfo mi(handle); return strDup(mi.szDevice); } bool DesktopWindow::setViewportOffset(const Point& tl) { Point np = Point(__rfbmax(0, __rfbmin(tl.x, buffer->width()-client_size.width())), __rfbmax(0, __rfbmin(tl.y, buffer->height()-client_size.height()))); Point delta = np.translate(scrolloffset.negate()); if (!np.equals(scrolloffset)) { scrolloffset = np; ScrollWindowEx(frameHandle, -delta.x, -delta.y, 0, 0, 0, 0, SW_INVALIDATE); UpdateWindow(frameHandle); return true; } return false; } bool DesktopWindow::processBumpScroll(const Point& pos) { if (!bumpScroll) return false; int bumpScrollPixels = 20; bumpScrollDelta = Point(); if (pos.x == client_size.width()-1) bumpScrollDelta.x = bumpScrollPixels; else if (pos.x == 0) bumpScrollDelta.x = -bumpScrollPixels; if (pos.y == client_size.height()-1) bumpScrollDelta.y = bumpScrollPixels; else if (pos.y == 0) bumpScrollDelta.y = -bumpScrollPixels; if (bumpScrollDelta.x || bumpScrollDelta.y) { if (bumpScrollTimer.isActive()) return true; if (setViewportOffset(scrolloffset.translate(bumpScrollDelta))) { bumpScrollTimer.start(25); return true; } } bumpScrollTimer.stop(); return false; } LRESULT DesktopWindow::processMessage(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // -=- Process standard window messages case WM_NOTIFY: if (wParam == ID_TOOLBAR) tb.processWM_NOTIFY(wParam, lParam); break; case WM_DISPLAYCHANGE: // Display format has changed - notify callback callback->displayChanged(); break; // -=- Window position // Prevent the window from being resized to be too large if in normal mode. // If maximized or fullscreen the allow oversized windows. case WM_WINDOWPOSCHANGING: { WINDOWPOS* wpos = (WINDOWPOS*)lParam; if ((wpos->flags & SWP_NOSIZE) || isAutoScaling()) break; // Calculate the minimum size of main window RECT r; Rect min_size; int tbMinWidth = 0, tbMinHeight = 0; if (isToolbarEnabled()) { tbMinWidth = tb.getTotalWidth(); tbMinHeight = tb.getHeight(); SetRect(&r, 0, 0, tbMinWidth, tbMinHeight); AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE); min_size = Rect(r.left, r.top, r.right, r.bottom); } // Work out how big the window should ideally be DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE); DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL); DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE); SetRect(&r, 0, 0, buffer->width(), buffer->height()); AdjustWindowRectEx(&r, style, FALSE, style_ex); Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom); if (current_style & WS_VSCROLL) reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL); if (current_style & WS_HSCROLL) reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL); SetRect(&r, reqd_size.tl.x, reqd_size.tl.y, reqd_size.br.x, reqd_size.br.y); if (isToolbarEnabled()) r.bottom += tb.getHeight(); AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE); reqd_size = Rect(r.left, r.top, r.right, r.bottom); RECT current; GetWindowRect(handle, ¤t); if (min_size.width() > reqd_size.width()) { reqd_size.tl.x = min_size.tl.x; reqd_size.br.x = min_size.br.x; } if (min_size.height() > reqd_size.height()) { reqd_size.tl.y = min_size.tl.y; reqd_size.br.y = min_size.br.y; } if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) { // Ensure that the window isn't resized too large or too small if ((wpos->cx < min_size.width()) && isToolbarEnabled()) { wpos->cx = min_size.width(); wpos->x = current.left; } else if ((wpos->cx > reqd_size.width())) { wpos->cx = reqd_size.width(); wpos->x = current.left; } if ((wpos->cy < min_size.height()) && isToolbarEnabled()) { wpos->cy = min_size.height(); wpos->y = current.top; } else if (wpos->cy > reqd_size.height()) { wpos->cy = reqd_size.height(); wpos->y = current.top; } } } break; // Resize child windows and update window size info we have cached. case WM_SIZE: { Point old_offset = desktopToClient(Point(0, 0)); RECT r; // Resize child windows GetClientRect(handle, &r); if (tb.isVisible()) { MoveWindow(frameHandle, 0, tb.getHeight(), r.right, r.bottom - tb.getHeight(), TRUE); } else { MoveWindow(frameHandle, 0, 0, r.right, r.bottom, TRUE); } tb.autoSize(); // Update the cached sizing information GetWindowRect(frameHandle, &r); window_size = Rect(r.left, r.top, r.right, r.bottom); GetClientRect(frameHandle, &r); client_size = Rect(r.left, r.top, r.right, r.bottom); // Perform the AutoScaling operation if (isAutoScaling()) { fitBufferToWindow(false); } else { // Determine whether scrollbars are required calculateScrollBars(); } // Redraw if required if ((!old_offset.equals(desktopToClient(Point(0, 0)))) || isAutoScaling()) InvalidateRect(frameHandle, 0, TRUE); } break; // -=- Bump-scrolling case WM_TIMER: switch (wParam) { case TIMER_BUMPSCROLL: if (!setViewportOffset(scrolloffset.translate(bumpScrollDelta))) bumpScrollTimer.stop(); break; case TIMER_POINTER_INTERVAL: case TIMER_POINTER_3BUTTON: ptr.handleTimer(callback, wParam); break; } break; // -=- Track whether or not the window has focus case WM_SETFOCUS: has_focus = true; break; case WM_KILLFOCUS: has_focus = false; cursorOutsideBuffer(); // Restore the keyboard to a consistent state kbd.releaseAllKeys(callback); break; // -=- If the menu is about to be shown, make sure it's up to date case WM_INITMENU: callback->refreshMenu(true); break; // -=- Handle the extra window menu items // Pass system menu messages to the callback and only attempt // to process them ourselves if the callback returns false. case WM_SYSCOMMAND: // Call the supplied callback if (callback->sysCommand(wParam, lParam)) break; // - Not processed by the callback, so process it as a system message switch (wParam & 0xfff0) { // When restored, ensure that full-screen mode is re-enabled if required. case SC_RESTORE: { if (GetWindowLong(handle, GWL_STYLE) & WS_MINIMIZE) { rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam); setFullscreen(fullscreenRestore); } else if (fullscreenActive) setFullscreen(false); else rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam); return 0; } // If we are maximized or minimized then that cancels full-screen mode. case SC_MINIMIZE: case SC_MAXIMIZE: fullscreenRestore = fullscreenActive; setFullscreen(false); break; } break; // Treat all menu commands as system menu commands case WM_COMMAND: SendMessage(handle, WM_SYSCOMMAND, wParam, lParam); return 0; // -=- Handle keyboard input case WM_KEYUP: case WM_KEYDOWN: // Hook the MenuKey to pop-up the window menu if (menuKey && (wParam == menuKey)) { bool ctrlDown = (GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0; bool altDown = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; bool shiftDown = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0; if (!(ctrlDown || altDown || shiftDown)) { // If MenuKey is being released then pop-up the menu if ((msg == WM_KEYDOWN)) { // Make sure it's up to date // // NOTE: Here we call refreshMenu only to grey out Move and Size // menu items. Other things will be refreshed once again // while processing the WM_INITMENU message. // callback->refreshMenu(false); // Show it under the pointer POINT pt; GetCursorPos(&pt); cursorInBuffer = false; TrackPopupMenu(GetSystemMenu(handle, FALSE), TPM_CENTERALIGN | TPM_VCENTERALIGN, pt.x, pt.y, 0, handle, 0); } // Ignore the MenuKey keypress for both press & release events return 0; } } case WM_SYSKEYDOWN: case WM_SYSKEYUP: kbd.keyEvent(callback, wParam, lParam, (msg == WM_KEYDOWN) || (msg == WM_SYSKEYDOWN)); return 0; // -=- Handle mouse wheel scroll events #ifdef WM_MOUSEWHEEL case WM_MOUSEWHEEL: processMouseMessage(msg, wParam, lParam); break; #endif // -=- Handle the window closing case WM_CLOSE: vlog.debug("WM_CLOSE %x", handle); callback->closeWindow(); break; } return rfb::win32::SafeDefWindowProc(handle, msg, wParam, lParam); } LRESULT DesktopWindow::processFrameMessage(UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { // -=- Paint the remote frame buffer case WM_PAINT: { PAINTSTRUCT ps; HDC paintDC = BeginPaint(frameHandle, &ps); if (!paintDC) throw rdr::SystemException("unable to BeginPaint", GetLastError()); Rect pr = Rect(ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, ps.rcPaint.bottom); if (!pr.is_empty()) { // Draw using the correct palette PaletteSelector pSel(paintDC, windowPalette.getHandle()); if (buffer->bitmap) { // Update the bitmap's palette if (palette_changed) { palette_changed = false; buffer->refreshPalette(); } // Get device context BitmapDC bitmapDC(paintDC, buffer->bitmap); // Blit the border if required Rect bufpos = desktopToClient(buffer->getRect()); if (!pr.enclosed_by(bufpos)) { vlog.debug("draw border"); HBRUSH black = (HBRUSH) GetStockObject(BLACK_BRUSH); RECT r; SetRect(&r, 0, 0, bufpos.tl.x, client_size.height()); FillRect(paintDC, &r, black); SetRect(&r, bufpos.tl.x, 0, bufpos.br.x, bufpos.tl.y); FillRect(paintDC, &r, black); SetRect(&r, bufpos.br.x, 0, client_size.width(), client_size.height()); FillRect(paintDC, &r, black); SetRect(&r, bufpos.tl.x, bufpos.br.y, bufpos.br.x, client_size.height()); FillRect(paintDC, &r, black); } // Do the blit Point buf_pos = clientToDesktop(pr.tl); if (!BitBlt(paintDC, pr.tl.x, pr.tl.y, pr.width(), pr.height(), bitmapDC, buf_pos.x, buf_pos.y, SRCCOPY)) throw rdr::SystemException("unable to BitBlt to window", GetLastError()); } } EndPaint(frameHandle, &ps); // - Notify the callback that a paint message has finished processing callback->paintCompleted(); } return 0; // -=- Palette management case WM_PALETTECHANGED: vlog.debug("WM_PALETTECHANGED"); if ((HWND)wParam == frameHandle) { vlog.debug("ignoring"); break; } case WM_QUERYNEWPALETTE: vlog.debug("re-selecting palette"); { WindowDC wdc(frameHandle); PaletteSelector pSel(wdc, windowPalette.getHandle()); if (pSel.isRedrawRequired()) { InvalidateRect(frameHandle, 0, FALSE); UpdateWindow(frameHandle); } } return TRUE; case WM_VSCROLL: case WM_HSCROLL: { Point delta; int newpos = (msg == WM_VSCROLL) ? scrolloffset.y : scrolloffset.x; switch (LOWORD(wParam)) { case SB_PAGEUP: newpos -= 50; break; case SB_PAGEDOWN: newpos += 50; break; case SB_LINEUP: newpos -= 5; break; case SB_LINEDOWN: newpos += 5; break; case SB_THUMBTRACK: case SB_THUMBPOSITION: newpos = HIWORD(wParam); break; default: vlog.info("received unknown scroll message"); }; if (msg == WM_HSCROLL) setViewportOffset(Point(newpos, scrolloffset.y)); else setViewportOffset(Point(scrolloffset.x, newpos)); SCROLLINFO si; si.cbSize = sizeof(si); si.fMask = SIF_POS; si.nPos = newpos; SetScrollInfo(frameHandle, (msg == WM_VSCROLL) ? SB_VERT : SB_HORZ, &si, TRUE); } break; // -=- Cursor shape/visibility handling case WM_SETCURSOR: if (LOWORD(lParam) != HTCLIENT) break; SetCursor(cursorInBuffer ? dotCursor : arrowCursor); return TRUE; case WM_MOUSELEAVE: trackingMouseLeave = false; cursorOutsideBuffer(); return 0; // -=- Mouse input handling case WM_MOUSEMOVE: case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: case WM_LBUTTONDOWN: case WM_MBUTTONDOWN: case WM_RBUTTONDOWN: processMouseMessage(msg, wParam, lParam); break; } return rfb::win32::SafeDefWindowProc(frameHandle, msg, wParam, lParam); } void DesktopWindow::processMouseMessage(UINT msg, WPARAM wParam, LPARAM lParam) { if (!has_focus) { cursorOutsideBuffer(); return; } if (!trackingMouseLeave) { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_LEAVE; tme.hwndTrack = frameHandle; _TrackMouseEvent(&tme); trackingMouseLeave = true; } int mask = 0; if (LOWORD(wParam) & MK_LBUTTON) mask |= 1; if (LOWORD(wParam) & MK_MBUTTON) mask |= 2; if (LOWORD(wParam) & MK_RBUTTON) mask |= 4; #ifdef WM_MOUSEWHEEL if (msg == WM_MOUSEWHEEL) { int delta = (short)HIWORD(wParam); int repeats = (abs(delta)+119) / 120; int wheelMask = (delta > 0) ? 8 : 16; vlog.debug("repeats %d, mask %d\n",repeats,wheelMask); for (int i=0; igetRect().contains(p); if (!cursorInBuffer) { cursorOutsideBuffer(); return; } // If we're locally rendering the cursor then redraw it if (cursorAvailable) { // - Render the cursor! if (!p.equals(cursorPos)) { hideLocalCursor(); cursorPos = p; showLocalCursor(); if (cursorVisible) hideSystemCursor(); } } // If we are doing bump-scrolling then try that first... if (processBumpScroll(clientPos)) return; // Send a pointer event to the server oldpos = p; if (buffer->isScaling()) { p.x /= buffer->getScaleRatioX(); p.y /= buffer->getScaleRatioY(); } ptr.pointerEvent(callback, p, mask); #ifdef WM_MOUSEWHEEL } #endif } void DesktopWindow::hideLocalCursor() { // - Blit the cursor backing store over the cursor // *** ALWAYS call this BEFORE changing buffer PF!!! if (cursorVisible) { cursorVisible = false; buffer->DIBSectionBuffer::imageRect(cursorBackingRect, cursorBacking.data); invalidateDesktopRect(cursorBackingRect, false); } } void DesktopWindow::showLocalCursor() { if (cursorAvailable && !cursorVisible && cursorInBuffer) { if (!buffer->getScaledPixelFormat().equal(cursor.getPF()) || cursor.getRect().is_empty()) { vlog.info("attempting to render invalid local cursor"); cursorAvailable = false; showSystemCursor(); return; } cursorVisible = true; cursorBackingRect = cursor.getRect().translate(cursorPos).translate(cursor.hotspot.negate()); cursorBackingRect = cursorBackingRect.intersect(buffer->getRect()); buffer->getImage(cursorBacking.data, cursorBackingRect); renderLocalCursor(); invalidateDesktopRect(cursorBackingRect, false); } } void DesktopWindow::cursorOutsideBuffer() { cursorInBuffer = false; hideLocalCursor(); showSystemCursor(); } void DesktopWindow::renderLocalCursor() { Rect r = cursor.getRect(); r = r.translate(cursorPos).translate(cursor.hotspot.negate()); buffer->DIBSectionBuffer::maskRect(r, cursor.data, cursor.mask.buf); } void DesktopWindow::hideSystemCursor() { if (systemCursorVisible) { vlog.debug("hide system cursor"); systemCursorVisible = false; ShowCursor(FALSE); } } void DesktopWindow::showSystemCursor() { if (!systemCursorVisible) { vlog.debug("show system cursor"); systemCursorVisible = true; ShowCursor(TRUE); } } bool DesktopWindow::invalidateDesktopRect(const Rect& crect, bool scaling) { Rect rect; if (buffer->isScaling() && scaling) { rect = desktopToClient(buffer->calculateScaleBoundary(crect)); } else rect = desktopToClient(crect); if (rect.intersect(client_size).is_empty()) return false; RECT invalid = {rect.tl.x, rect.tl.y, rect.br.x, rect.br.y}; InvalidateRect(frameHandle, &invalid, FALSE); return true; } void DesktopWindow::notifyClipboardChanged(const char* text, int len) { callback->clientCutText(text, len); } void DesktopWindow::setPF(const PixelFormat& pf) { // If the cursor is the wrong format then clear it if (!pf.equal(buffer->getScaledPixelFormat())) setCursor(0, 0, Point(), 0, 0); // Update the desktop buffer buffer->setPF(pf); // Redraw the window InvalidateRect(frameHandle, 0, FALSE); } void DesktopWindow::setSize(int w, int h) { vlog.debug("setSize %dx%d", w, h); // If the locally-rendered cursor is visible then remove it hideLocalCursor(); // Resize the backing buffer buffer->setSize(w, h); // Calculate the pixel buffer aspect correlation. It's used // for the autoScaling operation. aspect_corr = (double)w / h; // If the window is not maximised or full-screen then resize it if (!(GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE) && !fullscreenActive) { // Resize the window to the required size RECT r = {0, 0, w, h}; AdjustWindowRectEx(&r, GetWindowLong(frameHandle, GWL_STYLE), FALSE, GetWindowLong(frameHandle, GWL_EXSTYLE)); if (isToolbarEnabled()) r.bottom += tb.getHeight(); AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE); // Resize about the center of the window, and clip to current monitor MonitorInfo mi(handle); resizeWindow(handle, r.right-r.left, r.bottom-r.top); mi.clipTo(handle); } else { // Ensure the screen contents are consistent InvalidateRect(frameHandle, 0, FALSE); } // Enable/disable scrollbars as appropriate calculateScrollBars(); } void DesktopWindow::setAutoScaling(bool as) { autoScaling = as; if (isToolbarEnabled()) refreshToolbarButtons(); if (as) fitBufferToWindow(); } void DesktopWindow::setDesktopScale(int scale_) { if (buffer->getScale() == scale_ || scale_ <= 0) return; bool state = buffer->isScaling(); buffer->setScale(scale_); state ^= buffer->isScaling(); if (state) convertCursorToBuffer(); if (isToolbarEnabled()) refreshToolbarButtons(); if (!(isAutoScaling() || isFullscreen() || (GetWindowLong(handle, GWL_STYLE) & WS_MAXIMIZE))) resizeDesktopWindowToBuffer(); printScale(); InvalidateRect(frameHandle, 0, FALSE); } void DesktopWindow::setDesktopScaleFilter(unsigned int scaleFilterID) { if (scaleFilterID == getDesktopScaleFilterID() || scaleFilterID > scaleFilterMaxNumber) return; buffer->setScaleFilter(scaleFilterID); InvalidateRect(frameHandle, 0, FALSE); } void DesktopWindow::convertCursorToBuffer() { if (memcmp(&(cursor.getPF()), &(buffer->getPF()), sizeof(PixelBuffer)) == 0) return; internalSetCursor = true; setCursor(cursorWidth, cursorHeight, cursorHotspot, cursorImage, cursorMask); internalSetCursor = false; } void DesktopWindow::fitBufferToWindow(bool repaint) { double scale_ratio; double resized_aspect_corr = double(client_size.width()) / client_size.height(); DWORD style = GetWindowLong(frameHandle, GWL_STYLE); if (style & (WS_VSCROLL | WS_HSCROLL)) { style &= ~(WS_VSCROLL | WS_HSCROLL); SetWindowLong(frameHandle, GWL_STYLE, style); SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); // Update the cached client size RECT r; GetClientRect(frameHandle, &r); client_size = Rect(r.left, r.top, r.right, r.bottom); } bool state = buffer->isScaling(); if (resized_aspect_corr > aspect_corr) { scale_ratio = (double)client_size.height() / buffer->getSrcHeight(); buffer->setScaleWindowSize(ceil(buffer->getSrcWidth()*scale_ratio), client_size.height()); } else { scale_ratio = (double)client_size.width() / buffer->getSrcWidth(); buffer->setScaleWindowSize(client_size.width(), ceil(buffer->getSrcHeight()*scale_ratio)); } state ^= buffer->isScaling(); if (state) convertCursorToBuffer(); printScale(); InvalidateRect(frameHandle, 0, FALSE); } void DesktopWindow::printScale() { setName(desktopName); } void DesktopWindow::setCursor(int w, int h, const Point& hotspot, void* data, void* mask) { hideLocalCursor(); cursor.hotspot = hotspot; cursor.setSize(w, h); cursor.setPF(buffer->getScaledPixelFormat()); // Convert the current cursor pixel format to bpp32 if scaling mode is on. // It need because ScaledDIBSection buffer always works with bpp32 pixel data // in scaling mode. if (buffer->isScaling()) { U8 *ptr = (U8*)cursor.data; U8 *dataPtr = (U8*)data; U32 pixel = 0; int bytesPerPixel = buffer->getPixelFormat().bpp / 8; int pixelCount = w * h; PixelFormat pf = buffer->getPixelFormat(); while (pixelCount--) { if (bytesPerPixel == 1) { pixel = *dataPtr++; } else if (bytesPerPixel == 2) { int b0 = *dataPtr++; int b1 = *dataPtr++; pixel = b1 << 8 | b0; } else if (bytesPerPixel == 4) { int b0 = *dataPtr++; int b1 = *dataPtr++; int b2 = *dataPtr++; int b3 = *dataPtr++; pixel = b3 << 24 | b2 << 16 | b1 << 8 | b0; } else { pixel = 0; } *ptr++ = (U8)((((pixel >> pf.blueShift ) & pf.blueMax ) * 255 + pf.blueMax /2) / pf.blueMax); *ptr++ = (U8)((((pixel >> pf.greenShift) & pf.greenMax) * 255 + pf.greenMax/2) / pf.greenMax); *ptr++ = (U8)((((pixel >> pf.redShift ) & pf.redMax ) * 255 + pf.redMax /2) / pf.redMax); *ptr++ = (U8)0; } } else { cursor.imageRect(cursor.getRect(), data); } memcpy(cursor.mask.buf, mask, cursor.maskLen()); cursor.crop(); cursorBacking.setSize(w, h); cursorBacking.setPF(buffer->getScaledPixelFormat()); cursorAvailable = true; showLocalCursor(); // Save the cursor parameters if (!internalSetCursor) { if (cursorImage) delete [] cursorImage; if (cursorMask) delete [] cursorMask; int cursorImageSize = (buffer->getPixelFormat().bpp/8) * w * h; cursorImage = new U8[cursorImageSize]; cursorMask = new U8[cursor.maskLen()]; memcpy(cursorImage, data, cursorImageSize); memcpy(cursorMask, mask, cursor.maskLen()); cursorWidth = w; cursorHeight = h; cursorHotspot = hotspot; } } PixelFormat DesktopWindow::getNativePF() const { vlog.debug("getNativePF()"); return WindowDC(handle).getPF(); } void DesktopWindow::refreshWindowPalette(int start, int count) { vlog.debug("refreshWindowPalette(%d, %d)", start, count); Colour colours[256]; if (count > 256) { vlog.debug("%d palette entries", count); throw rdr::Exception("too many palette entries"); } // Copy the palette from the DIBSectionBuffer ColourMap* cm = buffer->getColourMap(); if (!cm) return; for (int i=0; ilookup(i, &r, &g, &b); colours[i].r = r; colours[i].g = g; colours[i].b = b; } // Set the window palette windowPalette.setEntries(start, count, colours); // Cause the window to be redrawn palette_changed = true; InvalidateRect(handle, 0, FALSE); } void DesktopWindow::calculateScrollBars() { // Calculate the required size of window DWORD current_style = GetWindowLong(frameHandle, GWL_STYLE); DWORD style = current_style & ~(WS_VSCROLL | WS_HSCROLL); DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE); DWORD old_style; RECT r; SetRect(&r, 0, 0, buffer->width(), buffer->height()); AdjustWindowRectEx(&r, style, FALSE, style_ex); Rect reqd_size = Rect(r.left, r.top, r.right, r.bottom); if (!bumpScroll) { // We only enable scrollbars if bump-scrolling is not active. // Effectively, this means if full-screen is not active, // but I think it's better to make these things explicit. // Work out whether scroll bars are required do { old_style = style; if (!(style & WS_HSCROLL) && (reqd_size.width() > window_size.width())) { style |= WS_HSCROLL; reqd_size.br.y += GetSystemMetrics(SM_CXHSCROLL); } if (!(style & WS_VSCROLL) && (reqd_size.height() > window_size.height())) { style |= WS_VSCROLL; reqd_size.br.x += GetSystemMetrics(SM_CXVSCROLL); } } while (style != old_style); } // Tell Windows to update the window style & cached settings if (style != current_style) { SetWindowLong(frameHandle, GWL_STYLE, style); SetWindowPos(frameHandle, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED); } // Update the scroll settings SCROLLINFO si; if (style & WS_VSCROLL) { si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nMin = 0; si.nMax = buffer->height(); si.nPage = buffer->height() - (reqd_size.height() - window_size.height()); maxscrolloffset.y = __rfbmax(0, si.nMax-si.nPage); scrolloffset.y = __rfbmin(maxscrolloffset.y, scrolloffset.y); si.nPos = scrolloffset.y; SetScrollInfo(frameHandle, SB_VERT, &si, TRUE); } if (style & WS_HSCROLL) { si.cbSize = sizeof(si); si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS; si.nMin = 0; si.nMax = buffer->width(); si.nPage = buffer->width() - (reqd_size.width() - window_size.width()); maxscrolloffset.x = __rfbmax(0, si.nMax-si.nPage); scrolloffset.x = __rfbmin(maxscrolloffset.x, scrolloffset.x); si.nPos = scrolloffset.x; SetScrollInfo(frameHandle, SB_HORZ, &si, TRUE); } // Update the cached client size GetClientRect(frameHandle, &r); client_size = Rect(r.left, r.top, r.right, r.bottom); } void DesktopWindow::resizeDesktopWindowToBuffer() { RECT r; DWORD style = GetWindowLong(frameHandle, GWL_STYLE) & ~(WS_VSCROLL | WS_HSCROLL); DWORD style_ex = GetWindowLong(frameHandle, GWL_EXSTYLE); // Calculate the required size of the desktop window SetRect(&r, 0, 0, buffer->width(), buffer->height()); AdjustWindowRectEx(&r, style, FALSE, style_ex); if (isToolbarEnabled()) r.bottom += tb.getHeight(); AdjustWindowRect(&r, GetWindowLong(handle, GWL_STYLE), FALSE); // Set the required size, center the main window and clip to the current monitor SetWindowPos(handle, 0, 0, 0, r.right-r.left, r.bottom-r.top, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER | SWP_NOMOVE); centerWindow(handle, NULL); MonitorInfo mi(getMonitor()); mi.clipTo(handle); // Enable/disable scrollbars as appropriate calculateScrollBars(); } void DesktopWindow::setName(const char* name) { if (name != desktopName) { strCopy(desktopName, name, sizeof(desktopName)); } char *newTitle = new char[strlen(desktopName)+20]; sprintf(newTitle, "TightVNC: %.240s @ %i%%", desktopName, getDesktopScale()); SetWindowText(handle, TStr(newTitle)); delete [] newTitle; } void DesktopWindow::serverCutText(const char* str, int len) { CharArray t(len+1); memcpy(t.buf, str, len); t.buf[len] = 0; clipboard.setClipText(t.buf); } void DesktopWindow::fillRect(const Rect& r, Pixel pix) { Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r; if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor(); buffer->fillRect(r, pix); invalidateDesktopRect(r); } void DesktopWindow::imageRect(const Rect& r, void* pixels) { Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r; if (cursorBackingRect.overlaps(img_rect)) hideLocalCursor(); buffer->imageRect(r, pixels); invalidateDesktopRect(r); } void DesktopWindow::copyRect(const Rect& r, int srcX, int srcY) { Rect img_rect = buffer->isScaling() ? buffer->calculateScaleBoundary(r) : r; if (cursorBackingRect.overlaps(img_rect) || cursorBackingRect.overlaps(Rect(srcX, srcY, srcX+img_rect.width(), srcY+img_rect.height()))) hideLocalCursor(); buffer->copyRect(r, Point(r.tl.x-srcX, r.tl.y-srcY)); invalidateDesktopRect(r); } void DesktopWindow::invertRect(const Rect& r) { int stride; rdr::U8* p = buffer->isScaling() ? buffer->getPixelsRW(buffer->calculateScaleBoundary(r), &stride) : buffer->getPixelsRW(r, &stride); for (int y = 0; y < r.height(); y++) { for (int x = 0; x < r.width(); x++) { switch (buffer->getPF().bpp) { case 8: ((rdr::U8* )p)[x+y*stride] ^= 0xff; break; case 16: ((rdr::U16*)p)[x+y*stride] ^= 0xffff; break; case 32: ((rdr::U32*)p)[x+y*stride] ^= 0xffffffff; break; } } } invalidateDesktopRect(r); }