/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2002 Chris Schoeneman * * This package is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * found in the file COPYING that should have accompanied this file. * * This package 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. */ #include "CMSWindowsScreen.h" #include "CMSWindowsClipboard.h" #include "CMSWindowsScreenSaver.h" #include "CPlatform.h" #include "CClipboard.h" #include "IMSWindowsScreenEventHandler.h" #include "IScreenReceiver.h" #include "CThread.h" #include "CLock.h" #include "TMethodJob.h" #include "CLog.h" #include "CString.h" #include // // add backwards compatible multihead support (and suppress bogus warning) // #pragma warning(push) #pragma warning(disable: 4706) // assignment within conditional #define COMPILE_MULTIMON_STUBS #include #pragma warning(pop) // // CMSWindowsScreen // HINSTANCE CMSWindowsScreen::s_instance = NULL; CMSWindowsScreen* CMSWindowsScreen::s_screen = NULL; CMSWindowsScreen::CMSWindowsScreen(IScreenReceiver* receiver, IMSWindowsScreenEventHandler* eventHandler) : m_receiver(receiver), m_eventHandler(eventHandler), m_class(NULL), m_icon(NULL), m_cursor(NULL), m_is95Family(CPlatform::isWindows95Family()), m_window(NULL), m_x(0), m_y(0), m_w(0), m_h(0), m_multimon(false), m_threadID(0), m_lastThreadID(0), m_nextClipboardWindow(NULL), m_clipboardOwner(NULL), m_timer(0), m_desk(NULL), m_deskName(), m_hookLibrary(NULL), m_installScreensaver(NULL), m_uninstallScreensaver(NULL), m_screensaver(NULL), m_screensaverNotify(false) { assert(s_screen == NULL); assert(m_receiver != NULL); assert(m_eventHandler != NULL); s_screen = this; // make sure this thread has a message queue MSG dummy; PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); } CMSWindowsScreen::~CMSWindowsScreen() { assert(s_screen != NULL); assert(m_class == 0); s_screen = NULL; } void CMSWindowsScreen::init(HINSTANCE instance) { s_instance = instance; } HWND CMSWindowsScreen::openDesktop() { // save thread id m_threadID = GetCurrentThreadId(); // get the input desktop and switch to it if (!switchDesktop(openInputDesktop())) { return NULL; } // poll input desktop to see if it changes (onPreDispatch() // handles WM_TIMER) m_timer = 0; if (!m_is95Family) { m_timer = SetTimer(NULL, 0, 200, NULL); } return m_window; } void CMSWindowsScreen::closeDesktop() { // remove timer if (m_timer != 0) { KillTimer(NULL, m_timer); } // disconnect from desktop switchDesktop(NULL); // clear thread id m_threadID = 0; assert(m_window == NULL); assert(m_desk == NULL); } bool CMSWindowsScreen::isMultimon() const { return m_multimon; } HINSTANCE CMSWindowsScreen::getInstance() { return s_instance; } void CMSWindowsScreen::open() { assert(s_instance != NULL); assert(m_class == 0); log((CLOG_DEBUG "opening display")); // create the transparent cursor createBlankCursor(); // register a window class WNDCLASSEX classInfo; classInfo.cbSize = sizeof(classInfo); classInfo.style = CS_DBLCLKS | CS_NOCLOSE; classInfo.lpfnWndProc = &CMSWindowsScreen::wndProc; classInfo.cbClsExtra = 0; classInfo.cbWndExtra = 0; classInfo.hInstance = s_instance; classInfo.hIcon = NULL; classInfo.hCursor = m_cursor; classInfo.hbrBackground = NULL; classInfo.lpszMenuName = NULL; classInfo.lpszClassName = "Synergy"; classInfo.hIconSm = NULL; m_class = RegisterClassEx(&classInfo); // get screen shape updateScreenShape(); // initialize the screen saver m_screensaver = new CMSWindowsScreenSaver(); // load the hook library and get the screen saver functions m_hookLibrary = LoadLibrary("synrgyhk"); if (m_hookLibrary != NULL) { m_installScreensaver = (InstallScreenSaverFunc)GetProcAddress( m_hookLibrary, "installScreenSaver"); m_uninstallScreensaver = (UninstallScreenSaverFunc)GetProcAddress( m_hookLibrary, "uninstallScreenSaver"); if (m_installScreensaver == NULL || m_uninstallScreensaver == NULL) { // disable if either install or uninstall is unavailable m_installScreensaver = NULL; m_uninstallScreensaver = NULL; } } } void CMSWindowsScreen::mainLoop() { // must call mainLoop() from same thread as openDesktop() assert(m_threadID == GetCurrentThreadId()); // event loop CEvent event; event.m_result = 0; for (;;) { // wait for an event in a cancellable way CThread::waitForEvent(); GetMessage(&event.m_msg, NULL, 0, 0); // handle quit message if (event.m_msg.message == WM_QUIT) { CThread::getCurrentThread().cancel(); } // dispatch message if (!onPreDispatch(&event)) { TranslateMessage(&event.m_msg); DispatchMessage(&event.m_msg); } } } void CMSWindowsScreen::exitMainLoop() { PostThreadMessage(m_threadID, WM_QUIT, 0, 0); } void CMSWindowsScreen::close() { assert(s_instance != NULL); // done with hook library if (m_hookLibrary != NULL) { FreeLibrary(m_hookLibrary); m_installScreensaver = NULL; m_uninstallScreensaver = NULL; m_hookLibrary = NULL; } // done with screen saver delete m_screensaver; m_screensaver = NULL; // unregister the window class if (m_class != 0) { UnregisterClass((LPCTSTR)m_class, s_instance); m_class = 0; } // delete resources if (m_cursor != NULL) { DestroyCursor(m_cursor); m_cursor = NULL; } log((CLOG_DEBUG "closed display")); } bool CMSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src) { CMSWindowsClipboard dst(m_window); if (src != NULL) { // save clipboard data return CClipboard::copy(&dst, src); } else { // assert clipboard ownership if (!dst.open(0)) { return false; } dst.empty(); dst.close(); return true; } } void CMSWindowsScreen::checkClipboards() { // if we think we own the clipboard but we don't then somebody // grabbed the clipboard on this screen without us knowing. // tell the server that this screen grabbed the clipboard. // // this works around bugs in the clipboard viewer chain. // sometimes NT will simply never send WM_DRAWCLIPBOARD // messages for no apparent reason and rebooting fixes the // problem. since we don't want a broken clipboard until the // next reboot we do this double check. clipboard ownership // won't be reflected on other screens until we leave but at // least the clipboard itself will work. HWND clipboardOwner = GetClipboardOwner(); if (m_clipboardOwner != clipboardOwner) { m_clipboardOwner = clipboardOwner; if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { m_receiver->onGrabClipboard(kClipboardClipboard); m_receiver->onGrabClipboard(kClipboardSelection); } } } void CMSWindowsScreen::openScreensaver(bool notify) { assert(m_screensaver != NULL); m_screensaverNotify = notify; if (m_screensaverNotify) { if (m_installScreensaver != NULL) { m_installScreensaver(); } } else { m_screensaver->disable(); } } void CMSWindowsScreen::closeScreensaver() { if (m_screensaver != NULL) { if (m_screensaverNotify) { if (m_uninstallScreensaver != NULL) { m_uninstallScreensaver(); } } else { m_screensaver->enable(); } } m_screensaverNotify = false; } void CMSWindowsScreen::screensaver(bool activate) { assert(m_screensaver != NULL); if (activate) { m_screensaver->activate(); } else { m_screensaver->deactivate(); } } void CMSWindowsScreen::syncDesktop() { // change calling thread's desktop if (!m_is95Family) { if (SetThreadDesktop(m_desk) == 0) { // log((CLOG_WARN "failed to set desktop: %d", GetLastError())); } } // attach input queues if not already attached. this has a habit // of sucking up more and more CPU each time it's called (even if // the threads are already attached). since we only expect one // thread to call this more than once we can save just the last // the attached thread. DWORD threadID = GetCurrentThreadId(); if (threadID != m_lastThreadID && threadID != m_threadID) { m_lastThreadID = threadID; AttachThreadInput(threadID, m_threadID, TRUE); } } bool CMSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const { CMSWindowsClipboard src(m_window); CClipboard::copy(dst, &src); return true; } void CMSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const { assert(m_class != 0); x = m_x; y = m_y; w = m_w; h = m_h; } void CMSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const { POINT pos; if (GetCursorPos(&pos)) { x = pos.x; y = pos.y; } else { getCursorCenter(x, y); } } void CMSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const { x = GetSystemMetrics(SM_CXSCREEN) >> 1; y = GetSystemMetrics(SM_CYSCREEN) >> 1; } void CMSWindowsScreen::updateScreenShape() { // get shape m_x = GetSystemMetrics(SM_XVIRTUALSCREEN); m_y = GetSystemMetrics(SM_YVIRTUALSCREEN); m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN); m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); log((CLOG_INFO "screen shape: %d,%d %dx%d", m_x, m_y, m_w, m_h)); // check for multiple monitors m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) || m_h != GetSystemMetrics(SM_CYSCREEN)); } bool CMSWindowsScreen::onPreDispatch(const CEvent* event) { // handle event const MSG* msg = &event->m_msg; switch (msg->message) { case SYNERGY_MSG_SCREEN_SAVER: { // activating or deactivating? bool activate = (msg->wParam != 0); // ignore this message if there are any other screen saver // messages already in the queue. this is important because // our checkStarted() function has a deliberate delay, so it // can't respond to events at full CPU speed and will fall // behind if a lot of screen saver events are generated. // that can easily happen because windows will continually // send SC_SCREENSAVE until the screen saver starts, even if // the screen saver is disabled! MSG msg; if (!PeekMessage(&msg, NULL, SYNERGY_MSG_SCREEN_SAVER, SYNERGY_MSG_SCREEN_SAVER, PM_NOREMOVE)) { if (activate) { if (m_screensaver->checkStarted( SYNERGY_MSG_SCREEN_SAVER, FALSE, 0)) { m_eventHandler->onScreensaver(true); } } else { m_eventHandler->onScreensaver(false); } } return true; } case WM_TIMER: // if current desktop is not the input desktop then switch to it. // windows 95 doesn't support multiple desktops so don't bother // to check under it. if (!m_is95Family) { HDESK desk = openInputDesktop(); if (desk != NULL) { if (isCurrentDesktop(desk)) { CloseDesktop(desk); } else { switchDesktop(desk); } } } return true; } return m_eventHandler->onPreDispatch(event); } bool CMSWindowsScreen::onEvent(CEvent* event) { assert(event != NULL); const MSG& msg = event->m_msg; switch (msg.message) { case WM_QUERYENDSESSION: if (m_is95Family) { event->m_result = TRUE; return true; } break; case WM_ENDSESSION: if (m_is95Family) { if (msg.wParam == TRUE && msg.lParam == 0) { exitMainLoop(); } return true; } break; case WM_PAINT: ValidateRect(msg.hwnd, NULL); return true; case WM_DRAWCLIPBOARD: log((CLOG_DEBUG "clipboard was taken")); // first pass it on if (m_nextClipboardWindow != NULL) { SendMessage(m_nextClipboardWindow, msg.message, msg.wParam, msg.lParam); } // now notify client that somebody changed the clipboard (unless // we're now the owner, in which case it's because we took // ownership, or now it's owned by nobody, which will happen if // we owned it and switched desktops because we destroy our // window to do that). m_clipboardOwner = GetClipboardOwner(); if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) { m_receiver->onGrabClipboard(kClipboardClipboard); m_receiver->onGrabClipboard(kClipboardSelection); } return true; case WM_CHANGECBCHAIN: if (m_nextClipboardWindow == (HWND)msg.wParam) { m_nextClipboardWindow = (HWND)msg.lParam; } else if (m_nextClipboardWindow != NULL) { SendMessage(m_nextClipboardWindow, msg.message, msg.wParam, msg.lParam); } return true; case WM_DISPLAYCHANGE: { // screen resolution may have changed. get old shape. SInt32 xOld, yOld, wOld, hOld; getShape(xOld, yOld, wOld, hOld); // update shape updateScreenShape(); // collect new screen info CClientInfo info; getShape(info.m_x, info.m_y, info.m_w, info.m_h); getCursorPos(info.m_mx, info.m_my); info.m_zoneSize = m_eventHandler->getJumpZoneSize(); // do nothing if resolution hasn't changed if (info.m_x != xOld || info.m_y != yOld || info.m_w != wOld || info.m_h != hOld) { // forward event m_eventHandler->onEvent(event); // send new screen info m_receiver->onInfoChanged(info); } return true; } } return m_eventHandler->onEvent(event); } void CMSWindowsScreen::createBlankCursor() { // create a transparent cursor int cw = GetSystemMetrics(SM_CXCURSOR); int ch = GetSystemMetrics(SM_CYCURSOR); UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); m_cursor = CreateCursor(s_instance, 0, 0, cw, ch, cursorAND, cursorXOR); delete[] cursorXOR; delete[] cursorAND; } bool CMSWindowsScreen::switchDesktop(HDESK desk) { // did we own the clipboard? bool ownClipboard = (m_clipboardOwner == m_window && m_window != NULL); // destroy old window if (m_window != NULL) { // first remove clipboard snooper ChangeClipboardChain(m_window, m_nextClipboardWindow); m_nextClipboardWindow = NULL; // we no longer own the clipboard if (ownClipboard) { m_clipboardOwner = NULL; } // let client clean up before we destroy the window m_eventHandler->preDestroyWindow(m_window); // now destroy window DestroyWindow(m_window); m_window = NULL; // done with desk if (!m_is95Family) { CloseDesktop(m_desk); } m_desk = NULL; m_deskName = ""; } // if no new desktop then we're done if (desk == NULL) { log((CLOG_INFO "disconnecting desktop")); return true; } // uninstall screen saver hooks if (m_screensaverNotify) { if (m_uninstallScreensaver != NULL) { m_uninstallScreensaver(); } } // set the desktop. can only do this when there are no windows // and hooks on the current desktop owned by this thread. if (SetThreadDesktop(desk) == 0) { log((CLOG_ERR "failed to set desktop: %d", GetLastError())); if (!m_is95Family) { CloseDesktop(desk); } return false; } // create the window m_window = CreateWindowEx(WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW, (LPCTSTR)m_class, "Synergy", WS_POPUP, 0, 0, 1, 1, NULL, NULL, getInstance(), NULL); if (m_window == NULL) { log((CLOG_ERR "failed to create window: %d", GetLastError())); if (!m_is95Family) { CloseDesktop(desk); } return false; } // reinstall screen saver hooks if (m_screensaverNotify) { if (m_installScreensaver != NULL) { m_installScreensaver(); } } // install our clipboard snooper m_nextClipboardWindow = SetClipboardViewer(m_window); // reassert clipboard ownership if (ownClipboard) { // FIXME -- take clipboard ownership, but we should also set // the clipboard data. } m_clipboardOwner = GetClipboardOwner(); // save new desktop m_desk = desk; m_deskName = getDesktopName(m_desk); log((CLOG_INFO "switched to desktop \"%s\"", m_deskName.c_str())); // let client prepare the window m_eventHandler->postCreateWindow(m_window); return true; } HDESK CMSWindowsScreen::openInputDesktop() const { if (m_is95Family) { // there's only one desktop on windows 95 et al. return GetThreadDesktop(GetCurrentThreadId()); } else { return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE, DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | GENERIC_WRITE); } } CString CMSWindowsScreen::getDesktopName(HDESK desk) const { if (desk == NULL) { return CString(); } else if (m_is95Family) { return "desktop"; } else { DWORD size; GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); TCHAR* name = new TCHAR[size / sizeof(TCHAR) + 1]; GetUserObjectInformation(desk, UOI_NAME, name, size, &size); CString result(name); delete[] name; return result; } } bool CMSWindowsScreen::isCurrentDesktop(HDESK desk) const { return CStringUtil::CaselessCmp::equal(getDesktopName(desk), m_deskName); } LRESULT CALLBACK CMSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { assert(s_screen != NULL); CEvent event; event.m_msg.hwnd = hwnd; event.m_msg.message = msg; event.m_msg.wParam = wParam; event.m_msg.lParam = lParam; event.m_result = 0; if (s_screen->onEvent(&event)) { return event.m_result; } else { return DefWindowProc(hwnd, msg, wParam, lParam); } }