/* * synergy -- mouse and keyboard sharing utility * Copyright (C) 2003 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 "CArchTaskBarWindows.h" #include "IArchTaskBarReceiver.h" #include "CArch.h" #include "XArch.h" #include #include static const UINT kAddReceiver = WM_USER + 10; static const UINT kRemoveReceiver = WM_USER + 11; static const UINT kUpdateReceiver = WM_USER + 12; static const UINT kNotifyReceiver = WM_USER + 13; static const UINT kFirstReceiverID = WM_USER + 14; // // CArchTaskBarWindows // CArchTaskBarWindows* CArchTaskBarWindows::s_instance = NULL; HINSTANCE CArchTaskBarWindows::s_appInstance = NULL; CArchTaskBarWindows::CArchTaskBarWindows(void* appInstance) : m_nextID(kFirstReceiverID) { // save the singleton instance s_instance = this; // save app instance s_appInstance = reinterpret_cast(appInstance); // we need a mutex m_mutex = ARCH->newMutex(); // and a condition variable which uses the above mutex m_ready = false; m_condVar = ARCH->newCondVar(); // we're going to want to get a result from the thread we're // about to create to know if it initialized successfully. // so we lock the condition variable. ARCH->lockMutex(m_mutex); // open a window and run an event loop in a separate thread. // this has to happen in a separate thread because if we // create a window on the current desktop with the current // thread then the current thread won't be able to switch // desktops if it needs to. m_thread = ARCH->newThread(&CArchTaskBarWindows::threadEntry, this); // wait for child thread while (!m_ready) { ARCH->waitCondVar(m_condVar, m_mutex, -1.0); } // ready ARCH->unlockMutex(m_mutex); } CArchTaskBarWindows::~CArchTaskBarWindows() { if (m_thread != NULL) { ARCH->cancelThread(m_thread); ARCH->wait(m_thread, -1.0); ARCH->closeThread(m_thread); } ARCH->closeCondVar(m_condVar); ARCH->closeMutex(m_mutex); s_instance = NULL; } void CArchTaskBarWindows::addDialog(HWND hwnd) { // add dialog to added dialogs list ARCH->lockMutex(s_instance->m_mutex); s_instance->m_addedDialogs.insert(std::make_pair(hwnd, true)); ARCH->unlockMutex(s_instance->m_mutex); } void CArchTaskBarWindows::removeDialog(HWND hwnd) { // mark dialog as removed ARCH->lockMutex(s_instance->m_mutex); CDialogs::iterator index = s_instance->m_dialogs.find(hwnd); if (index != s_instance->m_dialogs.end()) { index->second = false; } s_instance->m_addedDialogs.erase(hwnd); ARCH->unlockMutex(s_instance->m_mutex); } void CArchTaskBarWindows::addReceiver(IArchTaskBarReceiver* receiver) { // ignore bogus receiver if (receiver == NULL) { return; } // add receiver if necessary CReceiverToInfoMap::iterator index = m_receivers.find(receiver); if (index == m_receivers.end()) { // add it, creating a new message ID for it CReceiverInfo info; info.m_id = getNextID(); index = m_receivers.insert(std::make_pair(receiver, info)).first; // add ID to receiver mapping m_idTable.insert(std::make_pair(info.m_id, index)); } // add receiver PostMessage(m_hwnd, kAddReceiver, index->second.m_id, 0); } void CArchTaskBarWindows::removeReceiver(IArchTaskBarReceiver* receiver) { // find receiver CReceiverToInfoMap::iterator index = m_receivers.find(receiver); if (index == m_receivers.end()) { return; } // remove icon. wait for this to finish before returning. SendMessage(m_hwnd, kRemoveReceiver, index->second.m_id, 0); // recycle the ID recycleID(index->second.m_id); // discard m_idTable.erase(index->second.m_id); m_receivers.erase(index); } void CArchTaskBarWindows::updateReceiver(IArchTaskBarReceiver* receiver) { // find receiver CReceiverToInfoMap::const_iterator index = m_receivers.find(receiver); if (index == m_receivers.end()) { return; } // update icon and tool tip PostMessage(m_hwnd, kUpdateReceiver, index->second.m_id, 0); } UINT CArchTaskBarWindows::getNextID() { if (m_oldIDs.empty()) { return m_nextID++; } UINT id = m_oldIDs.back(); m_oldIDs.pop_back(); return id; } void CArchTaskBarWindows::recycleID(UINT id) { m_oldIDs.push_back(id); } void CArchTaskBarWindows::addIcon(UINT id) { ARCH->lockMutex(m_mutex); CIDToReceiverMap::const_iterator index = m_idTable.find(id); if (index != m_idTable.end()) { modifyIconNoLock(index->second, NIM_ADD); } ARCH->unlockMutex(m_mutex); } void CArchTaskBarWindows::removeIcon(UINT id) { ARCH->lockMutex(m_mutex); removeIconNoLock(id); ARCH->unlockMutex(m_mutex); } void CArchTaskBarWindows::updateIcon(UINT id) { ARCH->lockMutex(m_mutex); CIDToReceiverMap::const_iterator index = m_idTable.find(id); if (index != m_idTable.end()) { modifyIconNoLock(index->second, NIM_MODIFY); } ARCH->unlockMutex(m_mutex); } void CArchTaskBarWindows::addAllIcons() { ARCH->lockMutex(m_mutex); for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); index != m_receivers.end(); ++index) { modifyIconNoLock(index, NIM_ADD); } ARCH->unlockMutex(m_mutex); } void CArchTaskBarWindows::removeAllIcons() { ARCH->lockMutex(m_mutex); for (CReceiverToInfoMap::const_iterator index = m_receivers.begin(); index != m_receivers.end(); ++index) { removeIconNoLock(index->second.m_id); } ARCH->unlockMutex(m_mutex); } void CArchTaskBarWindows::modifyIconNoLock( CReceiverToInfoMap::const_iterator index, DWORD taskBarMessage) { // get receiver UINT id = index->second.m_id; IArchTaskBarReceiver* receiver = index->first; // lock receiver so icon and tool tip are guaranteed to be consistent receiver->lock(); // get icon data HICON icon = reinterpret_cast( const_cast(receiver->getIcon())); // get tool tip std::string toolTip = receiver->getToolTip(); // done querying receiver->unlock(); // prepare to add icon NOTIFYICONDATA data; data.cbSize = sizeof(NOTIFYICONDATA); data.hWnd = m_hwnd; data.uID = id; data.uFlags = NIF_MESSAGE; data.uCallbackMessage = kNotifyReceiver; data.hIcon = icon; if (icon != NULL) { data.uFlags |= NIF_ICON; } if (!toolTip.empty()) { strncpy(data.szTip, toolTip.c_str(), sizeof(data.szTip)); data.szTip[sizeof(data.szTip) - 1] = '\0'; data.uFlags |= NIF_TIP; } else { data.szTip[0] = '\0'; } // add icon if (Shell_NotifyIcon(taskBarMessage, &data) == 0) { // failed } } void CArchTaskBarWindows::removeIconNoLock(UINT id) { NOTIFYICONDATA data; data.cbSize = sizeof(NOTIFYICONDATA); data.hWnd = m_hwnd; data.uID = id; if (Shell_NotifyIcon(NIM_DELETE, &data) == 0) { // failed } } void CArchTaskBarWindows::handleIconMessage( IArchTaskBarReceiver* receiver, LPARAM lParam) { // process message switch (lParam) { case WM_LBUTTONDOWN: receiver->showStatus(); break; case WM_LBUTTONDBLCLK: receiver->primaryAction(); break; case WM_RBUTTONUP: { POINT p; GetCursorPos(&p); receiver->runMenu(p.x, p.y); break; } case WM_MOUSEMOVE: // currently unused break; default: // unused break; } } bool CArchTaskBarWindows::processDialogs(MSG* msg) { // only one thread can be in this method on any particular object // at any given time. that's not a problem since only our event // loop calls this method and there's just one of those. ARCH->lockMutex(m_mutex); // remove removed dialogs m_dialogs.erase(false); // merge added dialogs into the dialog list for (CDialogs::const_iterator index = m_addedDialogs.begin(); index != m_addedDialogs.end(); ++index) { m_dialogs.insert(std::make_pair(index->first, index->second)); } m_addedDialogs.clear(); ARCH->unlockMutex(m_mutex); // check message against all dialogs until one handles it. // note that we don't hold a lock while checking because // the message is processed and may make calls to this // object. that's okay because addDialog() and // removeDialog() don't change the map itself (just the // values of some elements). ARCH->lockMutex(m_mutex); for (CDialogs::const_iterator index = m_dialogs.begin(); index != m_dialogs.end(); ++index) { if (index->second) { ARCH->unlockMutex(m_mutex); if (IsDialogMessage(index->first, msg)) { return true; } ARCH->lockMutex(m_mutex); } } ARCH->unlockMutex(m_mutex); return false; } LRESULT CArchTaskBarWindows::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case kNotifyReceiver: { // lookup receiver CIDToReceiverMap::const_iterator index = m_idTable.find(wParam); if (index != m_idTable.end()) { IArchTaskBarReceiver* receiver = index->second->first; handleIconMessage(receiver, lParam); return 0; } break; } case kAddReceiver: addIcon(wParam); break; case kRemoveReceiver: removeIcon(wParam); break; case kUpdateReceiver: updateIcon(wParam); break; default: if (msg == m_taskBarRestart) { // task bar was recreated so re-add our icons addAllIcons(); } break; } return DefWindowProc(hwnd, msg, wParam, lParam); } LRESULT CALLBACK CArchTaskBarWindows::staticWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // if msg is WM_NCCREATE, extract the CArchTaskBarWindows* and put // it in the extra window data then forward the call. CArchTaskBarWindows* self = NULL; if (msg == WM_NCCREATE) { CREATESTRUCT* createInfo; createInfo = reinterpret_cast(lParam); self = reinterpret_cast( createInfo->lpCreateParams); SetWindowLong(hwnd, 0, reinterpret_cast(self)); } else { // get the extra window data and forward the call LONG data = GetWindowLong(hwnd, 0); if (data != 0) { self = reinterpret_cast( reinterpret_cast(data)); } } // forward the message if (self != NULL) { return self->wndProc(hwnd, msg, wParam, lParam); } else { return DefWindowProc(hwnd, msg, wParam, lParam); } } void CArchTaskBarWindows::threadMainLoop() { // register the task bar restart message m_taskBarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); // register a window class WNDCLASSEX classInfo; classInfo.cbSize = sizeof(classInfo); classInfo.style = CS_NOCLOSE; classInfo.lpfnWndProc = &CArchTaskBarWindows::staticWndProc; classInfo.cbClsExtra = 0; classInfo.cbWndExtra = sizeof(CArchTaskBarWindows*); classInfo.hInstance = s_appInstance; classInfo.hIcon = NULL; classInfo.hCursor = NULL; classInfo.hbrBackground = NULL; classInfo.lpszMenuName = NULL; classInfo.lpszClassName = TEXT("SynergyTaskBar"); classInfo.hIconSm = NULL; ATOM windowClass = RegisterClassEx(&classInfo); // create window m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, reinterpret_cast(windowClass), TEXT("Synergy Task Bar"), WS_POPUP, 0, 0, 1, 1, NULL, NULL, s_appInstance, reinterpret_cast(this)); // signal ready ARCH->lockMutex(m_mutex); m_ready = true; ARCH->broadcastCondVar(m_condVar); ARCH->unlockMutex(m_mutex); // handle failure if (m_hwnd == NULL) { UnregisterClass((LPCTSTR)windowClass, s_appInstance); return; } try { // main loop MSG msg; for (;;) { // wait for message if (ARCH->waitForEvent(NULL, -1.0) != IArchMultithread::kEvent) { continue; } // peek for message and remove it. we don't GetMessage() // because we should never block here, only in waitForEvent(). if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { continue; } // check message against dialogs if (!processDialogs(&msg)) { // process message TranslateMessage(&msg); DispatchMessage(&msg); } } } catch (XThread&) { // clean up removeAllIcons(); DestroyWindow(m_hwnd); UnregisterClass((LPCTSTR)windowClass, s_appInstance); throw; } } void* CArchTaskBarWindows::threadEntry(void* self) { reinterpret_cast(self)->threadMainLoop(); return NULL; }