diff options
| author | 2018-04-25 18:07:30 -0400 | |
|---|---|---|
| committer | 2018-04-25 18:07:30 -0400 | |
| commit | 9b1b081cfdb1c0fb6457278775e0823f8bc10f62 (patch) | |
| tree | ce8840148d8445055ba9e4f12263b2208f234c16 /src/lib/arch/win32 | |
Import Upstream version 2.0.0+dfsgupstream/2.0.0+dfsg
Diffstat (limited to 'src/lib/arch/win32')
28 files changed, 5487 insertions, 0 deletions
diff --git a/src/lib/arch/win32/ArchConsoleWindows.cpp b/src/lib/arch/win32/ArchConsoleWindows.cpp new file mode 100644 index 0000000..4514555 --- /dev/null +++ b/src/lib/arch/win32/ArchConsoleWindows.cpp @@ -0,0 +1,23 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchConsoleWindows.h" + +ArchConsoleWindows::ArchConsoleWindows() { } + +ArchConsoleWindows::~ArchConsoleWindows() { } diff --git a/src/lib/arch/win32/ArchConsoleWindows.h b/src/lib/arch/win32/ArchConsoleWindows.h new file mode 100644 index 0000000..f1f0cc9 --- /dev/null +++ b/src/lib/arch/win32/ArchConsoleWindows.h @@ -0,0 +1,29 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/ArchConsoleStd.h" + +#define ARCH_CONSOLE ArchConsoleWindows + +class ArchConsoleWindows : public ArchConsoleStd { +public: + ArchConsoleWindows(); + virtual ~ArchConsoleWindows(); +}; diff --git a/src/lib/arch/win32/ArchDaemonWindows.cpp b/src/lib/arch/win32/ArchDaemonWindows.cpp new file mode 100644 index 0000000..efcf235 --- /dev/null +++ b/src/lib/arch/win32/ArchDaemonWindows.cpp @@ -0,0 +1,704 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchDaemonWindows.h" +#include "arch/win32/ArchMiscWindows.h" +#include "arch/win32/XArchWindows.h" +#include "arch/Arch.h" +#include "common/stdvector.h" + +#include <sstream> + +// +// ArchDaemonWindows +// + +ArchDaemonWindows* ArchDaemonWindows::s_daemon = NULL; + +ArchDaemonWindows::ArchDaemonWindows() : +m_daemonThreadID(0) +{ + m_quitMessage = RegisterWindowMessage("BarrierDaemonExit"); +} + +ArchDaemonWindows::~ArchDaemonWindows() +{ + // do nothing +} + +int +ArchDaemonWindows::runDaemon(RunFunc runFunc) +{ + assert(s_daemon != NULL); + return s_daemon->doRunDaemon(runFunc); +} + +void +ArchDaemonWindows::daemonRunning(bool running) +{ + if (s_daemon != NULL) { + s_daemon->doDaemonRunning(running); + } +} + +UINT +ArchDaemonWindows::getDaemonQuitMessage() +{ + if (s_daemon != NULL) { + return s_daemon->doGetDaemonQuitMessage(); + } + else { + return 0; + } +} + +void +ArchDaemonWindows::daemonFailed(int result) +{ + assert(s_daemon != NULL); + throw XArchDaemonRunFailed(result); +} + +void +ArchDaemonWindows::installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonInstallFailed(new XArchEvalWindows); + } + + // create the service + SC_HANDLE service = CreateService( + mgr, + name, + name, + 0, + SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + pathname, + NULL, + NULL, + dependencies, + NULL, + NULL); + + if (service == NULL) { + // can't create service + DWORD err = GetLastError(); + if (err != ERROR_SERVICE_EXISTS) { + CloseServiceHandle(mgr); + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + } + else { + // done with service (but only try to close if not null) + CloseServiceHandle(service); + } + + // done with manager + CloseServiceHandle(mgr); + + // open the registry key for this service + HKEY key = openNTServicesKey(); + key = ArchMiscWindows::addKey(key, name); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + try { + uninstallDaemon(name); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + + // set the description + ArchMiscWindows::setValue(key, _T("Description"), description); + + // set command line + key = ArchMiscWindows::addKey(key, _T("Parameters")); + if (key == NULL) { + // can't open key + DWORD err = GetLastError(); + ArchMiscWindows::closeKey(key); + try { + uninstallDaemon(name); + } + catch (...) { + // ignore + } + throw XArchDaemonInstallFailed(new XArchEvalWindows(err)); + } + ArchMiscWindows::setValue(key, _T("CommandLine"), commandLine); + + // done with registry + ArchMiscWindows::closeKey(key); +} + +void +ArchDaemonWindows::uninstallDaemon(const char* name) +{ + // remove parameters for this service. ignore failures. + HKEY key = openNTServicesKey(); + key = ArchMiscWindows::openKey(key, name); + if (key != NULL) { + ArchMiscWindows::deleteKey(key, _T("Parameters")); + ArchMiscWindows::closeKey(key); + } + + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + // can't open service manager + throw XArchDaemonUninstallFailed(new XArchEvalWindows); + } + + // open the service. oddly, you must open a service to delete it. + SC_HANDLE service = OpenService(mgr, name, DELETE | SERVICE_STOP); + if (service == NULL) { + DWORD err = GetLastError(); + CloseServiceHandle(mgr); + if (err != ERROR_SERVICE_DOES_NOT_EXIST) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } + + // stop the service. we don't care if we fail. + SERVICE_STATUS status; + ControlService(service, SERVICE_CONTROL_STOP, &status); + + // delete the service + const bool okay = (DeleteService(service) == 0); + const DWORD err = GetLastError(); + + // clean up + CloseServiceHandle(service); + CloseServiceHandle(mgr); + + // give windows a chance to remove the service before + // we check if it still exists. + ARCH->sleep(1); + + // handle failure. ignore error if service isn't installed anymore. + if (!okay && isDaemonInstalled(name)) { + if (err == ERROR_SUCCESS) { + // this seems to occur even though the uninstall was successful. + // it could be a timing issue, i.e., isDaemonInstalled is + // called too soon. i've added a sleep to try and stop this. + return; + } + if (err == ERROR_IO_PENDING) { + // this seems to be a spurious error + return; + } + if (err != ERROR_SERVICE_MARKED_FOR_DELETE) { + throw XArchDaemonUninstallFailed(new XArchEvalWindows(err)); + } + throw XArchDaemonUninstallNotInstalled(new XArchEvalWindows(err)); + } +} + +int +ArchDaemonWindows::daemonize(const char* name, DaemonFunc func) +{ + assert(name != NULL); + assert(func != NULL); + + // save daemon function + m_daemonFunc = func; + + // construct the service entry + SERVICE_TABLE_ENTRY entry[2]; + entry[0].lpServiceName = const_cast<char*>(name); + entry[0].lpServiceProc = &ArchDaemonWindows::serviceMainEntry; + entry[1].lpServiceName = NULL; + entry[1].lpServiceProc = NULL; + + // hook us up to the service control manager. this won't return + // (if successful) until the processes have terminated. + s_daemon = this; + if (StartServiceCtrlDispatcher(entry) == 0) { + // StartServiceCtrlDispatcher failed + s_daemon = NULL; + throw XArchDaemonFailed(new XArchEvalWindows); + } + + s_daemon = NULL; + return m_daemonResult; +} + +bool +ArchDaemonWindows::canInstallDaemon(const char* /*name*/) +{ + // check if we can open service manager for write + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE); + if (mgr == NULL) { + return false; + } + CloseServiceHandle(mgr); + + // check if we can open the registry key + HKEY key = openNTServicesKey(); + ArchMiscWindows::closeKey(key); + + return (key != NULL); +} + +bool +ArchDaemonWindows::isDaemonInstalled(const char* name) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + return false; + } + + // open the service + SC_HANDLE service = OpenService(mgr, name, GENERIC_READ); + + // clean up + if (service != NULL) { + CloseServiceHandle(service); + } + CloseServiceHandle(mgr); + + return (service != NULL); +} + +HKEY +ArchDaemonWindows::openNTServicesKey() +{ + static const char* s_keyNames[] = { + _T("SYSTEM"), + _T("CurrentControlSet"), + _T("Services"), + NULL + }; + + return ArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_keyNames); +} + +bool +ArchDaemonWindows::isRunState(DWORD state) +{ + switch (state) { + case SERVICE_START_PENDING: + case SERVICE_CONTINUE_PENDING: + case SERVICE_RUNNING: + return true; + + default: + return false; + } +} + +int +ArchDaemonWindows::doRunDaemon(RunFunc run) +{ + // should only be called from DaemonFunc + assert(m_serviceMutex != NULL); + assert(run != NULL); + + // create message queue for this thread + MSG dummy; + PeekMessage(&dummy, NULL, 0, 0, PM_NOREMOVE); + + int result = 0; + ARCH->lockMutex(m_serviceMutex); + m_daemonThreadID = GetCurrentThreadId(); + while (m_serviceState != SERVICE_STOPPED) { + // wait until we're told to start + while (!isRunState(m_serviceState) && + m_serviceState != SERVICE_STOP_PENDING) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + + // run unless told to stop + if (m_serviceState != SERVICE_STOP_PENDING) { + ARCH->unlockMutex(m_serviceMutex); + try { + result = run(); + } + catch (...) { + ARCH->lockMutex(m_serviceMutex); + setStatusError(0); + m_serviceState = SERVICE_STOPPED; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + ARCH->unlockMutex(m_serviceMutex); + throw; + } + ARCH->lockMutex(m_serviceMutex); + } + + // notify of new state + if (m_serviceState == SERVICE_PAUSE_PENDING) { + m_serviceState = SERVICE_PAUSED; + } + else { + m_serviceState = SERVICE_STOPPED; + } + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); + return result; +} + +void +ArchDaemonWindows::doDaemonRunning(bool running) +{ + ARCH->lockMutex(m_serviceMutex); + if (running) { + m_serviceState = SERVICE_RUNNING; + setStatus(m_serviceState); + ARCH->broadcastCondVar(m_serviceCondVar); + } + ARCH->unlockMutex(m_serviceMutex); +} + +UINT +ArchDaemonWindows::doGetDaemonQuitMessage() +{ + return m_quitMessage; +} + +void +ArchDaemonWindows::setStatus(DWORD state) +{ + setStatus(state, 0, 0); +} + +void +ArchDaemonWindows::setStatus(DWORD state, DWORD step, DWORD waitHint) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = state; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = step; + status.dwWaitHint = waitHint; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +ArchDaemonWindows::setStatusError(DWORD error) +{ + assert(s_daemon != NULL); + + SERVICE_STATUS status; + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | + SERVICE_INTERACTIVE_PROCESS; + status.dwCurrentState = SERVICE_STOPPED; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_PAUSE_CONTINUE | + SERVICE_ACCEPT_SHUTDOWN; + status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + status.dwServiceSpecificExitCode = error; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + SetServiceStatus(s_daemon->m_statusHandle, &status); +} + +void +ArchDaemonWindows::serviceMain(DWORD argc, LPTSTR* argvIn) +{ + typedef std::vector<LPCTSTR> ArgList; + typedef std::vector<std::string> Arguments; + const char** argv = const_cast<const char**>(argvIn); + + // create synchronization objects + m_serviceMutex = ARCH->newMutex(); + m_serviceCondVar = ARCH->newCondVar(); + + // register our service handler function + m_statusHandle = RegisterServiceCtrlHandler(argv[0], + &ArchDaemonWindows::serviceHandlerEntry); + if (m_statusHandle == 0) { + // cannot start as service + m_daemonResult = -1; + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); + return; + } + + // tell service control manager that we're starting + m_serviceState = SERVICE_START_PENDING; + setStatus(m_serviceState, 0, 10000); + + std::string commandLine; + + // if no arguments supplied then try getting them from the registry. + // the first argument doesn't count because it's the service name. + Arguments args; + ArgList myArgv; + if (argc <= 1) { + // read command line + HKEY key = openNTServicesKey(); + key = ArchMiscWindows::openKey(key, argvIn[0]); + key = ArchMiscWindows::openKey(key, _T("Parameters")); + if (key != NULL) { + commandLine = ArchMiscWindows::readValueString(key, + _T("CommandLine")); + } + + // if the command line isn't empty then parse and use it + if (!commandLine.empty()) { + // parse, honoring double quoted substrings + std::string::size_type i = commandLine.find_first_not_of(" \t"); + while (i != std::string::npos && i != commandLine.size()) { + // find end of string + std::string::size_type e; + if (commandLine[i] == '\"') { + // quoted. find closing quote. + ++i; + e = commandLine.find("\"", i); + + // whitespace must follow closing quote + if (e == std::string::npos || + (e + 1 != commandLine.size() && + commandLine[e + 1] != ' ' && + commandLine[e + 1] != '\t')) { + args.clear(); + break; + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + else { + // unquoted. find next whitespace. + e = commandLine.find_first_of(" \t", i); + if (e == std::string::npos) { + e = commandLine.size(); + } + + // extract + args.push_back(commandLine.substr(i, e - i)); + i = e + 1; + } + + // next argument + i = commandLine.find_first_not_of(" \t", i); + } + + // service name goes first + myArgv.push_back(argv[0]); + + // get pointers + for (size_t j = 0; j < args.size(); ++j) { + myArgv.push_back(args[j].c_str()); + } + + // adjust argc/argv + argc = (DWORD)myArgv.size(); + argv = &myArgv[0]; + } + } + + m_commandLine = commandLine; + + try { + // invoke daemon function + m_daemonResult = m_daemonFunc(static_cast<int>(argc), argv); + } + catch (XArchDaemonRunFailed& e) { + setStatusError(e.m_result); + m_daemonResult = -1; + } + catch (...) { + setStatusError(1); + m_daemonResult = -1; + } + + // clean up + ARCH->closeCondVar(m_serviceCondVar); + ARCH->closeMutex(m_serviceMutex); + + // we're going to exit now, so set status to stopped + m_serviceState = SERVICE_STOPPED; + setStatus(m_serviceState, 0, 10000); +} + +void WINAPI +ArchDaemonWindows::serviceMainEntry(DWORD argc, LPTSTR* argv) +{ + s_daemon->serviceMain(argc, argv); +} + +void +ArchDaemonWindows::serviceHandler(DWORD ctrl) +{ + assert(m_serviceMutex != NULL); + assert(m_serviceCondVar != NULL); + + ARCH->lockMutex(m_serviceMutex); + + // ignore request if service is already stopped + if (s_daemon == NULL || m_serviceState == SERVICE_STOPPED) { + if (s_daemon != NULL) { + setStatus(m_serviceState); + } + ARCH->unlockMutex(m_serviceMutex); + return; + } + + switch (ctrl) { + case SERVICE_CONTROL_PAUSE: + m_serviceState = SERVICE_PAUSE_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + case SERVICE_CONTROL_CONTINUE: + // FIXME -- maybe should flush quit messages from queue + m_serviceState = SERVICE_CONTINUE_PENDING; + setStatus(m_serviceState, 0, 5000); + ARCH->broadcastCondVar(m_serviceCondVar); + break; + + case SERVICE_CONTROL_STOP: + case SERVICE_CONTROL_SHUTDOWN: + m_serviceState = SERVICE_STOP_PENDING; + setStatus(m_serviceState, 0, 5000); + PostThreadMessage(m_daemonThreadID, m_quitMessage, 0, 0); + ARCH->broadcastCondVar(m_serviceCondVar); + while (isRunState(m_serviceState)) { + ARCH->waitCondVar(m_serviceCondVar, m_serviceMutex, -1.0); + } + break; + + default: + // unknown service command + // fall through + + case SERVICE_CONTROL_INTERROGATE: + setStatus(m_serviceState); + break; + } + + ARCH->unlockMutex(m_serviceMutex); +} + +void WINAPI +ArchDaemonWindows::serviceHandlerEntry(DWORD ctrl) +{ + s_daemon->serviceHandler(ctrl); +} + +void +ArchDaemonWindows::start(const char* name) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // open the service + SC_HANDLE service = OpenService( + mgr, name, SERVICE_START); + + if (service == NULL) { + CloseServiceHandle(mgr); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // start the service + if (!StartService(service, 0, NULL)) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } +} + +void +ArchDaemonWindows::stop(const char* name) +{ + // open service manager + SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_READ); + if (mgr == NULL) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // open the service + SC_HANDLE service = OpenService( + mgr, name, + SERVICE_STOP | SERVICE_QUERY_STATUS); + + if (service == NULL) { + CloseServiceHandle(mgr); + throw XArchDaemonFailed(new XArchEvalWindows()); + } + + // ask the service to stop, asynchronously + SERVICE_STATUS ss; + if (!ControlService(service, SERVICE_CONTROL_STOP, &ss)) { + DWORD dwErrCode = GetLastError(); + if (dwErrCode != ERROR_SERVICE_NOT_ACTIVE) { + throw XArchDaemonFailed(new XArchEvalWindows()); + } + } +} + +void +ArchDaemonWindows::installDaemon() +{ + // install default daemon if not already installed. + if (!isDaemonInstalled(DEFAULT_DAEMON_NAME)) { + char path[MAX_PATH]; + GetModuleFileName(ArchMiscWindows::instanceWin32(), path, MAX_PATH); + + // wrap in quotes so a malicious user can't start \Program.exe as admin. + std::stringstream ss; + ss << '"'; + ss << path; + ss << '"'; + + installDaemon(DEFAULT_DAEMON_NAME, DEFAULT_DAEMON_INFO, ss.str().c_str(), "", ""); + } + + start(DEFAULT_DAEMON_NAME); +} + +void +ArchDaemonWindows::uninstallDaemon() +{ + // remove service if installed. + if (isDaemonInstalled(DEFAULT_DAEMON_NAME)) { + uninstallDaemon(DEFAULT_DAEMON_NAME); + } +} diff --git a/src/lib/arch/win32/ArchDaemonWindows.h b/src/lib/arch/win32/ArchDaemonWindows.h new file mode 100644 index 0000000..2db9792 --- /dev/null +++ b/src/lib/arch/win32/ArchDaemonWindows.h @@ -0,0 +1,151 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchDaemon.h" +#include "arch/IArchMultithread.h" +#include "common/stdstring.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <tchar.h> + +#define ARCH_DAEMON ArchDaemonWindows + +//! Win32 implementation of IArchDaemon +class ArchDaemonWindows : public IArchDaemon { +public: + typedef int (*RunFunc)(void); + + ArchDaemonWindows(); + virtual ~ArchDaemonWindows(); + + //! Run the daemon + /*! + When the client calls \c daemonize(), the \c DaemonFunc should call this + function after initialization and argument parsing to perform the + daemon processing. The \c runFunc should perform the daemon's + main loop, calling \c daemonRunning(true) when it enters the main loop + (i.e. after initialization) and \c daemonRunning(false) when it leaves + the main loop. The \c runFunc is called in a new thread and when the + daemon must exit the main loop due to some external control the + getDaemonQuitMessage() is posted to the thread. This function returns + what \c runFunc returns. \c runFunc should call \c daemonFailed() if + the daemon fails. + */ + static int runDaemon(RunFunc runFunc); + + //! Indicate daemon is in main loop + /*! + The \c runFunc passed to \c runDaemon() should call this function + to indicate when it has entered (\c running is \c true) or exited + (\c running is \c false) the main loop. + */ + static void daemonRunning(bool running); + + //! Indicate failure of running daemon + /*! + The \c runFunc passed to \c runDaemon() should call this function + to indicate failure. \c result is returned by \c daemonize(). + */ + static void daemonFailed(int result); + + //! Get daemon quit message + /*! + The windows NT daemon tells daemon thread to exit by posting this + message to it. The thread must, of course, have a message queue + for this to work. + */ + static UINT getDaemonQuitMessage(); + + // IArchDaemon overrides + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies); + virtual void uninstallDaemon(const char* name); + virtual void installDaemon(); + virtual void uninstallDaemon(); + virtual int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name); + virtual bool isDaemonInstalled(const char* name); + std::string commandLine() const { return m_commandLine; } + +private: + static HKEY openNTServicesKey(); + + int doRunDaemon(RunFunc runFunc); + void doDaemonRunning(bool running); + UINT doGetDaemonQuitMessage(); + + static void setStatus(DWORD state); + static void setStatus(DWORD state, DWORD step, DWORD waitHint); + static void setStatusError(DWORD error); + + static bool isRunState(DWORD state); + + void serviceMain(DWORD, LPTSTR*); + static void WINAPI serviceMainEntry(DWORD, LPTSTR*); + + void serviceHandler(DWORD ctrl); + static void WINAPI serviceHandlerEntry(DWORD ctrl); + + void start(const char* name); + void stop(const char* name); + +private: + class XArchDaemonRunFailed { + public: + XArchDaemonRunFailed(int result) : m_result(result) { } + + public: + int m_result; + }; + +private: + static ArchDaemonWindows* s_daemon; + + ArchMutex m_serviceMutex; + ArchCond m_serviceCondVar; + DWORD m_serviceState; + bool m_serviceHandlerWaiting; + bool m_serviceRunning; + + DWORD m_daemonThreadID; + DaemonFunc m_daemonFunc; + int m_daemonResult; + + SERVICE_STATUS_HANDLE m_statusHandle; + + UINT m_quitMessage; + + std::string m_commandLine; +}; + +#define DEFAULT_DAEMON_NAME _T("Barrier") +#define DEFAULT_DAEMON_INFO _T("Manages the Barrier foreground processes.") + +static const TCHAR* const g_daemonKeyPath[] = { + _T("SOFTWARE"), + _T("The Barrier Project"), + _T("Barrier"), + _T("Service"), + NULL +}; diff --git a/src/lib/arch/win32/ArchFileWindows.cpp b/src/lib/arch/win32/ArchFileWindows.cpp new file mode 100644 index 0000000..53b4b59 --- /dev/null +++ b/src/lib/arch/win32/ArchFileWindows.cpp @@ -0,0 +1,203 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchFileWindows.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <shlobj.h> +#include <tchar.h> +#include <string.h> + +// +// ArchFileWindows +// + +ArchFileWindows::ArchFileWindows() +{ + // do nothing +} + +ArchFileWindows::~ArchFileWindows() +{ + // do nothing +} + +const char* +ArchFileWindows::getBasename(const char* pathname) +{ + if (pathname == NULL) { + return NULL; + } + + // check for last slash + const char* basename = strrchr(pathname, '/'); + if (basename != NULL) { + ++basename; + } + else { + basename = pathname; + } + + // check for last backslash + const char* basename2 = strrchr(pathname, '\\'); + if (basename2 != NULL && basename2 > basename) { + basename = basename2 + 1; + } + + return basename; +} + +std::string +ArchFileWindows::getUserDirectory() +{ + // try %HOMEPATH% + TCHAR dir[MAX_PATH]; + DWORD size = sizeof(dir) / sizeof(TCHAR); + DWORD result = GetEnvironmentVariable(_T("HOMEPATH"), dir, size); + if (result != 0 && result <= size) { + // sanity check -- if dir doesn't appear to start with a + // drive letter and isn't a UNC name then don't use it + // FIXME -- allow UNC names + if (dir[0] != '\0' && (dir[1] == ':' || + ((dir[0] == '\\' || dir[0] == '/') && + (dir[1] == '\\' || dir[1] == '/')))) { + return dir; + } + } + + // get the location of the personal files. that's as close to + // a home directory as we're likely to find. + ITEMIDLIST* idl; + if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &idl))) { + TCHAR* path = NULL; + if (SHGetPathFromIDList(idl, dir)) { + DWORD attr = GetFileAttributes(dir); + if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + path = dir; + } + + IMalloc* shalloc; + if (SUCCEEDED(SHGetMalloc(&shalloc))) { + shalloc->Free(idl); + shalloc->Release(); + } + + if (path != NULL) { + return path; + } + } + + // use root of C drive as a default + return "C:"; +} + +std::string +ArchFileWindows::getSystemDirectory() +{ + // get windows directory + char dir[MAX_PATH]; + if (GetWindowsDirectory(dir, sizeof(dir)) != 0) { + return dir; + } + else { + // can't get it. use C:\ as a default. + return "C:"; + } +} + +std::string +ArchFileWindows::getInstalledDirectory() +{ + char fileNameBuffer[MAX_PATH]; + GetModuleFileName(NULL, fileNameBuffer, MAX_PATH); + std::string fileName(fileNameBuffer); + size_t lastSlash = fileName.find_last_of("\\"); + fileName = fileName.substr(0, lastSlash); + + return fileName; +} + +std::string +ArchFileWindows::getLogDirectory() +{ + return getInstalledDirectory(); +} + +std::string +ArchFileWindows::getPluginDirectory() +{ + if (!m_pluginDirectory.empty()) { + return m_pluginDirectory; + } + + std::string dir = getProfileDirectory(); + dir.append("\\Plugins"); + return dir; +} + +std::string +ArchFileWindows::getProfileDirectory() +{ + String dir; + if (!m_profileDirectory.empty()) { + dir = m_profileDirectory; + } + else { + TCHAR result[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, result))) { + dir = result; + } + else { + dir = getUserDirectory(); + } + } + + // HACK: append program name, this seems wrong. + dir.append("\\Barrier"); + + return dir; +} + +std::string +ArchFileWindows::concatPath(const std::string& prefix, + const std::string& suffix) +{ + std::string path; + path.reserve(prefix.size() + 1 + suffix.size()); + path += prefix; + if (path.size() == 0 || + (path[path.size() - 1] != '\\' && + path[path.size() - 1] != '/')) { + path += '\\'; + } + path += suffix; + return path; +} + +void +ArchFileWindows::setProfileDirectory(const String& s) +{ + m_profileDirectory = s; +} + +void +ArchFileWindows::setPluginDirectory(const String& s) +{ + m_pluginDirectory = s; +} diff --git a/src/lib/arch/win32/ArchFileWindows.h b/src/lib/arch/win32/ArchFileWindows.h new file mode 100644 index 0000000..4747b9c --- /dev/null +++ b/src/lib/arch/win32/ArchFileWindows.h @@ -0,0 +1,47 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchFile.h" + +#define ARCH_FILE ArchFileWindows + +//! Win32 implementation of IArchFile +class ArchFileWindows : public IArchFile { +public: + ArchFileWindows(); + virtual ~ArchFileWindows(); + + // IArchFile overrides + virtual const char* getBasename(const char* pathname); + virtual std::string getUserDirectory(); + virtual std::string getSystemDirectory(); + virtual std::string getInstalledDirectory(); + virtual std::string getLogDirectory(); + virtual std::string getPluginDirectory(); + virtual std::string getProfileDirectory(); + virtual std::string concatPath(const std::string& prefix, + const std::string& suffix); + virtual void setProfileDirectory(const String& s); + virtual void setPluginDirectory(const String& s); + +private: + String m_profileDirectory; + String m_pluginDirectory; +}; diff --git a/src/lib/arch/win32/ArchInternetWindows.cpp b/src/lib/arch/win32/ArchInternetWindows.cpp new file mode 100644 index 0000000..7f69c7f --- /dev/null +++ b/src/lib/arch/win32/ArchInternetWindows.cpp @@ -0,0 +1,224 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchInternetWindows.h" +#include "arch/win32/XArchWindows.h" +#include "arch/Arch.h" +#include "common/Version.h" + +#include <sstream> +#include <Wininet.h> +#include <Shlwapi.h> + +struct WinINetUrl { + String m_scheme; + String m_host; + String m_path; + INTERNET_PORT m_port; + DWORD m_flags; +}; + +class WinINetRequest { +public: + WinINetRequest(const String& url); + ~WinINetRequest(); + + String send(); + void openSession(); + void connect(); + void openRequest(); + +private: + HINTERNET m_session; + HINTERNET m_connect; + HINTERNET m_request; + WinINetUrl m_url; + bool m_used; +}; + +// +// ArchInternetWindows +// + +String +ArchInternetWindows::get(const String& url) +{ + WinINetRequest request(url); + return request.send(); +} + +String +ArchInternetWindows::urlEncode(const String& url) +{ + TCHAR buffer[1024]; + DWORD bufferSize = sizeof(buffer); + + if (UrlEscape(url.c_str(), buffer, &bufferSize, URL_ESCAPE_UNSAFE) != S_OK) { + throw XArch(new XArchEvalWindows()); + } + + String result(buffer); + + // the win32 url encoding funcitons are pretty useless (to us) and only + // escape "unsafe" chars, but not + or =, so we need to replace these + // manually (and probably many other chars). + barrier::string::findReplaceAll(result, "+", "%2B"); + barrier::string::findReplaceAll(result, "=", "%3D"); + + return result; +} + +// +// WinINetRequest +// + +static WinINetUrl parseUrl(const String& url); + +WinINetRequest::WinINetRequest(const String& url) : + m_session(NULL), + m_connect(NULL), + m_request(NULL), + m_used(false), + m_url(parseUrl(url)) +{ +} + +WinINetRequest::~WinINetRequest() +{ + if (m_request != NULL) { + InternetCloseHandle(m_request); + } + + if (m_connect != NULL) { + InternetCloseHandle(m_connect); + } + + if (m_session != NULL) { + InternetCloseHandle(m_session); + } +} + +String +WinINetRequest::send() +{ + if (m_used) { + throw XArch("class is one time use."); + } + m_used = true; + + openSession(); + connect(); + openRequest(); + + String headers("Content-Type: text/html"); + if (!HttpSendRequest(m_request, headers.c_str(), (DWORD)headers.length(), NULL, NULL)) { + throw XArch(new XArchEvalWindows()); + } + + std::stringstream result; + CHAR buffer[1025]; + DWORD read = 0; + + while (InternetReadFile(m_request, buffer, sizeof(buffer) - 1, &read) && (read != 0)) { + buffer[read] = 0; + result << buffer; + read = 0; + } + + return result.str(); +} + +void +WinINetRequest::openSession() +{ + std::stringstream userAgent; + userAgent << "Barrier "; + userAgent << kVersion; + + m_session = InternetOpen( + userAgent.str().c_str(), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + NULL); + + if (m_session == NULL) { + throw XArch(new XArchEvalWindows()); + } +} + +void +WinINetRequest::connect() +{ + m_connect = InternetConnect( + m_session, + m_url.m_host.c_str(), + m_url.m_port, + NULL, + NULL, + INTERNET_SERVICE_HTTP, + NULL, + NULL); + + if (m_connect == NULL) { + throw XArch(new XArchEvalWindows()); + } +} + +void +WinINetRequest::openRequest() +{ + m_request = HttpOpenRequest( + m_connect, + "GET", + m_url.m_path.c_str(), + HTTP_VERSION, + NULL, + NULL, + m_url.m_flags, + NULL); + + if (m_request == NULL) { + throw XArch(new XArchEvalWindows()); + } +} + +// nb: i tried to use InternetCrackUrl here, but couldn't quite get that to +// work. here's some (less robust) code to split the url into components. +// this works fine with simple urls, but doesn't consider the full url spec. +static WinINetUrl +parseUrl(const String& url) +{ + WinINetUrl parsed; + + size_t schemeEnd = url.find("://"); + size_t hostEnd = url.find('/', schemeEnd + 3); + + parsed.m_scheme = url.substr(0, schemeEnd); + parsed.m_host = url.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3)); + parsed.m_path = url.substr(hostEnd); + + parsed.m_port = INTERNET_DEFAULT_HTTP_PORT; + parsed.m_flags = 0; + + if (parsed.m_scheme.find("https") != String::npos) { + parsed.m_port = INTERNET_DEFAULT_HTTPS_PORT; + parsed.m_flags = INTERNET_FLAG_SECURE; + } + + return parsed; +} diff --git a/src/lib/arch/win32/ArchInternetWindows.h b/src/lib/arch/win32/ArchInternetWindows.h new file mode 100644 index 0000000..bab8d3c --- /dev/null +++ b/src/lib/arch/win32/ArchInternetWindows.h @@ -0,0 +1,28 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#define ARCH_INTERNET ArchInternetWindows + +#include "base/String.h" + +class ArchInternetWindows { +public: + String get(const String& url); + String urlEncode(const String& url); +}; diff --git a/src/lib/arch/win32/ArchLogWindows.cpp b/src/lib/arch/win32/ArchLogWindows.cpp new file mode 100644 index 0000000..bc17abf --- /dev/null +++ b/src/lib/arch/win32/ArchLogWindows.cpp @@ -0,0 +1,95 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchLogWindows.h" +#include "arch/win32/ArchMiscWindows.h" + +#include <string.h> + +// +// ArchLogWindows +// + +ArchLogWindows::ArchLogWindows() : m_eventLog(NULL) +{ + // do nothing +} + +ArchLogWindows::~ArchLogWindows() +{ + // do nothing +} + +void +ArchLogWindows::openLog(const char* name) +{ + if (m_eventLog == NULL) { + m_eventLog = RegisterEventSource(NULL, name); + } +} + +void +ArchLogWindows::closeLog() +{ + if (m_eventLog != NULL) { + DeregisterEventSource(m_eventLog); + m_eventLog = NULL; + } +} + +void +ArchLogWindows::showLog(bool) +{ + // do nothing +} + +void +ArchLogWindows::writeLog(ELevel level, const char* msg) +{ + if (m_eventLog != NULL) { + // convert priority + WORD type; + switch (level) { + case kERROR: + type = EVENTLOG_ERROR_TYPE; + break; + + case kWARNING: + type = EVENTLOG_WARNING_TYPE; + break; + + default: + type = EVENTLOG_INFORMATION_TYPE; + break; + } + + // log it + // FIXME -- win32 wants to use a message table to look up event + // strings. log messages aren't organized that way so we'll + // just dump our string into the raw data section of the event + // so users can at least see the message. note that we use our + // level as the event category. + ReportEvent(m_eventLog, type, static_cast<WORD>(level), + 0, // event ID + NULL, + 0, + (DWORD)strlen(msg) + 1, // raw data size + NULL, + const_cast<char*>(msg));// raw data + } +} diff --git a/src/lib/arch/win32/ArchLogWindows.h b/src/lib/arch/win32/ArchLogWindows.h new file mode 100644 index 0000000..3a997f1 --- /dev/null +++ b/src/lib/arch/win32/ArchLogWindows.h @@ -0,0 +1,42 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchLog.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#define ARCH_LOG ArchLogWindows + +//! Win32 implementation of IArchLog +class ArchLogWindows : public IArchLog { +public: + ArchLogWindows(); + virtual ~ArchLogWindows(); + + // IArchLog overrides + virtual void openLog(const char* name); + virtual void closeLog(); + virtual void showLog(bool showIfEmpty); + virtual void writeLog(ELevel, const char*); + +private: + HANDLE m_eventLog; +}; diff --git a/src/lib/arch/win32/ArchMiscWindows.cpp b/src/lib/arch/win32/ArchMiscWindows.cpp new file mode 100644 index 0000000..924afa2 --- /dev/null +++ b/src/lib/arch/win32/ArchMiscWindows.cpp @@ -0,0 +1,524 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchMiscWindows.h" +#include "arch/win32/ArchDaemonWindows.h" +#include "base/Log.h" +#include "common/Version.h" + +#include <Wtsapi32.h> +#pragma warning(disable: 4099) +#include <Userenv.h> +#pragma warning(default: 4099) + +// parent process name for services in Vista +#define SERVICE_LAUNCHER "services.exe" + +#ifndef ES_SYSTEM_REQUIRED +#define ES_SYSTEM_REQUIRED ((DWORD)0x00000001) +#endif +#ifndef ES_DISPLAY_REQUIRED +#define ES_DISPLAY_REQUIRED ((DWORD)0x00000002) +#endif +#ifndef ES_CONTINUOUS +#define ES_CONTINUOUS ((DWORD)0x80000000) +#endif +typedef DWORD EXECUTION_STATE; + +// +// ArchMiscWindows +// + +ArchMiscWindows::Dialogs* ArchMiscWindows::s_dialogs = NULL; +DWORD ArchMiscWindows::s_busyState = 0; +ArchMiscWindows::STES_t ArchMiscWindows::s_stes = NULL; +HICON ArchMiscWindows::s_largeIcon = NULL; +HICON ArchMiscWindows::s_smallIcon = NULL; +HINSTANCE ArchMiscWindows::s_instanceWin32 = NULL; + +void +ArchMiscWindows::cleanup() +{ + delete s_dialogs; +} + +void +ArchMiscWindows::init() +{ + // stop windows system error dialogs from showing. + SetErrorMode(SEM_FAILCRITICALERRORS); + + s_dialogs = new Dialogs; +} + +void +ArchMiscWindows::setIcons(HICON largeIcon, HICON smallIcon) +{ + s_largeIcon = largeIcon; + s_smallIcon = smallIcon; +} + +void +ArchMiscWindows::getIcons(HICON& largeIcon, HICON& smallIcon) +{ + largeIcon = s_largeIcon; + smallIcon = s_smallIcon; +} + +int +ArchMiscWindows::runDaemon(RunFunc runFunc) +{ + return ArchDaemonWindows::runDaemon(runFunc); +} + +void +ArchMiscWindows::daemonRunning(bool running) +{ + ArchDaemonWindows::daemonRunning(running); +} + +void +ArchMiscWindows::daemonFailed(int result) +{ + ArchDaemonWindows::daemonFailed(result); +} + +UINT +ArchMiscWindows::getDaemonQuitMessage() +{ + return ArchDaemonWindows::getDaemonQuitMessage(); +} + +HKEY +ArchMiscWindows::openKey(HKEY key, const TCHAR* keyName) +{ + return openKey(key, keyName, false); +} + +HKEY +ArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames) +{ + return openKey(key, keyNames, false); +} + +HKEY +ArchMiscWindows::addKey(HKEY key, const TCHAR* keyName) +{ + return openKey(key, keyName, true); +} + +HKEY +ArchMiscWindows::addKey(HKEY key, const TCHAR* const* keyNames) +{ + return openKey(key, keyNames, true); +} + +HKEY +ArchMiscWindows::openKey(HKEY key, const TCHAR* keyName, bool create) +{ + // ignore if parent is NULL + if (key == NULL) { + return NULL; + } + + // open next key + HKEY newKey; + LONG result = RegOpenKeyEx(key, keyName, 0, + KEY_WRITE | KEY_QUERY_VALUE, &newKey); + if (result != ERROR_SUCCESS && create) { + DWORD disp; + result = RegCreateKeyEx(key, keyName, 0, TEXT(""), + 0, KEY_WRITE | KEY_QUERY_VALUE, + NULL, &newKey, &disp); + } + if (result != ERROR_SUCCESS) { + RegCloseKey(key); + return NULL; + } + + // switch to new key + RegCloseKey(key); + return newKey; +} + +HKEY +ArchMiscWindows::openKey(HKEY key, const TCHAR* const* keyNames, bool create) +{ + for (size_t i = 0; key != NULL && keyNames[i] != NULL; ++i) { + // open next key + key = openKey(key, keyNames[i], create); + } + return key; +} + +void +ArchMiscWindows::closeKey(HKEY key) +{ + assert(key != NULL); + if (key==NULL) return; + RegCloseKey(key); +} + +void +ArchMiscWindows::deleteKey(HKEY key, const TCHAR* name) +{ + assert(key != NULL); + assert(name != NULL); + if (key==NULL || name==NULL) return; + RegDeleteKey(key, name); +} + +void +ArchMiscWindows::deleteValue(HKEY key, const TCHAR* name) +{ + assert(key != NULL); + assert(name != NULL); + if (key==NULL || name==NULL) return; + RegDeleteValue(key, name); +} + +bool +ArchMiscWindows::hasValue(HKEY key, const TCHAR* name) +{ + DWORD type; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); + return (result == ERROR_SUCCESS && + (type == REG_DWORD || type == REG_SZ)); +} + +ArchMiscWindows::EValueType +ArchMiscWindows::typeOfValue(HKEY key, const TCHAR* name) +{ + DWORD type; + LONG result = RegQueryValueEx(key, name, 0, &type, NULL, NULL); + if (result != ERROR_SUCCESS) { + return kNO_VALUE; + } + switch (type) { + case REG_DWORD: + return kUINT; + + case REG_SZ: + return kSTRING; + + case REG_BINARY: + return kBINARY; + + default: + return kUNKNOWN; + } +} + +void +ArchMiscWindows::setValue(HKEY key, + const TCHAR* name, const std::string& value) +{ + assert(key != NULL); + if (key == NULL) { + // TODO: throw exception + return; + } + RegSetValueEx(key, name, 0, REG_SZ, + reinterpret_cast<const BYTE*>(value.c_str()), + (DWORD)value.size() + 1); +} + +void +ArchMiscWindows::setValue(HKEY key, const TCHAR* name, DWORD value) +{ + assert(key != NULL); + if (key == NULL) { + // TODO: throw exception + return; + } + RegSetValueEx(key, name, 0, REG_DWORD, + reinterpret_cast<CONST BYTE*>(&value), + sizeof(DWORD)); +} + +void +ArchMiscWindows::setValueBinary(HKEY key, + const TCHAR* name, const std::string& value) +{ + assert(key != NULL); + assert(name != NULL); + if (key == NULL || name == NULL) { + // TODO: throw exception + return; + } + RegSetValueEx(key, name, 0, REG_BINARY, + reinterpret_cast<const BYTE*>(value.data()), + (DWORD)value.size()); +} + +std::string +ArchMiscWindows::readBinaryOrString(HKEY key, const TCHAR* name, DWORD type) +{ + // get the size of the string + DWORD actualType; + DWORD size = 0; + LONG result = RegQueryValueEx(key, name, 0, &actualType, NULL, &size); + if (result != ERROR_SUCCESS || actualType != type) { + return std::string(); + } + + // if zero size then return empty string + if (size == 0) { + return std::string(); + } + + // allocate space + char* buffer = new char[size]; + + // read it + result = RegQueryValueEx(key, name, 0, &actualType, + reinterpret_cast<BYTE*>(buffer), &size); + if (result != ERROR_SUCCESS || actualType != type) { + delete[] buffer; + return std::string(); + } + + // clean up and return value + if (type == REG_SZ && buffer[size - 1] == '\0') { + // don't include terminating nul; std::string will add one. + --size; + } + std::string value(buffer, size); + delete[] buffer; + return value; +} + +std::string +ArchMiscWindows::readValueString(HKEY key, const TCHAR* name) +{ + return readBinaryOrString(key, name, REG_SZ); +} + +std::string +ArchMiscWindows::readValueBinary(HKEY key, const TCHAR* name) +{ + return readBinaryOrString(key, name, REG_BINARY); +} + +DWORD +ArchMiscWindows::readValueInt(HKEY key, const TCHAR* name) +{ + DWORD type; + DWORD value; + DWORD size = sizeof(value); + LONG result = RegQueryValueEx(key, name, 0, &type, + reinterpret_cast<BYTE*>(&value), &size); + if (result != ERROR_SUCCESS || type != REG_DWORD) { + return 0; + } + return value; +} + +void +ArchMiscWindows::addDialog(HWND hwnd) +{ + s_dialogs->insert(hwnd); +} + +void +ArchMiscWindows::removeDialog(HWND hwnd) +{ + s_dialogs->erase(hwnd); +} + +bool +ArchMiscWindows::processDialog(MSG* msg) +{ + for (Dialogs::const_iterator index = s_dialogs->begin(); + index != s_dialogs->end(); ++index) { + if (IsDialogMessage(*index, msg)) { + return true; + } + } + return false; +} + +void +ArchMiscWindows::addBusyState(DWORD busyModes) +{ + s_busyState |= busyModes; + setThreadExecutionState(s_busyState); +} + +void +ArchMiscWindows::removeBusyState(DWORD busyModes) +{ + s_busyState &= ~busyModes; + setThreadExecutionState(s_busyState); +} + +void +ArchMiscWindows::setThreadExecutionState(DWORD busyModes) +{ + // look up function dynamically so we work on older systems + if (s_stes == NULL) { + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel != NULL) { + s_stes = reinterpret_cast<STES_t>(GetProcAddress(kernel, + "SetThreadExecutionState")); + } + if (s_stes == NULL) { + s_stes = &ArchMiscWindows::dummySetThreadExecutionState; + } + } + + // convert to STES form + EXECUTION_STATE state = 0; + if ((busyModes & kSYSTEM) != 0) { + state |= ES_SYSTEM_REQUIRED; + } + if ((busyModes & kDISPLAY) != 0) { + state |= ES_DISPLAY_REQUIRED; + } + if (state != 0) { + state |= ES_CONTINUOUS; + } + + // do it + s_stes(state); +} + +DWORD +ArchMiscWindows::dummySetThreadExecutionState(DWORD) +{ + // do nothing + return 0; +} + +void +ArchMiscWindows::wakeupDisplay() +{ + // We can't use ::setThreadExecutionState here because it sets + // ES_CONTINUOUS, which we don't want. + + if (s_stes == NULL) { + HINSTANCE kernel = LoadLibrary("kernel32.dll"); + if (kernel != NULL) { + s_stes = reinterpret_cast<STES_t>(GetProcAddress(kernel, + "SetThreadExecutionState")); + } + if (s_stes == NULL) { + s_stes = &ArchMiscWindows::dummySetThreadExecutionState; + } + } + + s_stes(ES_DISPLAY_REQUIRED); + + // restore the original execution states + setThreadExecutionState(s_busyState); +} + +bool +ArchMiscWindows::wasLaunchedAsService() +{ + String name; + if (!getParentProcessName(name)) { + LOG((CLOG_ERR "cannot determine if process was launched as service")); + return false; + } + + return (name == SERVICE_LAUNCHER); +} + +bool +ArchMiscWindows::getParentProcessName(String &name) +{ + PROCESSENTRY32 parentEntry; + if (!getParentProcessEntry(parentEntry)){ + LOG((CLOG_ERR "could not get entry for parent process")); + return false; + } + + name = parentEntry.szExeFile; + return true; +} + +BOOL WINAPI +ArchMiscWindows::getSelfProcessEntry(PROCESSENTRY32& entry) +{ + // get entry from current PID + return getProcessEntry(entry, GetCurrentProcessId()); +} + +BOOL WINAPI +ArchMiscWindows::getParentProcessEntry(PROCESSENTRY32& entry) +{ + // get the current process, so we can get parent PID + PROCESSENTRY32 selfEntry; + if (!getSelfProcessEntry(selfEntry)) { + return FALSE; + } + + // get entry from parent PID + return getProcessEntry(entry, selfEntry.th32ParentProcessID); +} + +BOOL WINAPI +ArchMiscWindows::getProcessEntry(PROCESSENTRY32& entry, DWORD processID) +{ + // first we need to take a snapshot of the running processes + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + LOG((CLOG_ERR "could not get process snapshot (error: %i)", + GetLastError())); + return FALSE; + } + + entry.dwSize = sizeof(PROCESSENTRY32); + + // get the first process, and if we can't do that then it's + // unlikely we can go any further + BOOL gotEntry = Process32First(snapshot, &entry); + if (!gotEntry) { + LOG((CLOG_ERR "could not get first process entry (error: %i)", + GetLastError())); + return FALSE; + } + + while(gotEntry) { + + if (entry.th32ProcessID == processID) { + // found current process + return TRUE; + } + + // now move on to the next entry (when we reach end, loop will stop) + gotEntry = Process32Next(snapshot, &entry); + } + + return FALSE; +} + +HINSTANCE +ArchMiscWindows::instanceWin32() +{ + assert(s_instanceWin32 != NULL); + return s_instanceWin32; +} + +void +ArchMiscWindows::setInstanceWin32(HINSTANCE instance) +{ + assert(instance != NULL); + s_instanceWin32 = instance; +}
\ No newline at end of file diff --git a/src/lib/arch/win32/ArchMiscWindows.h b/src/lib/arch/win32/ArchMiscWindows.h new file mode 100644 index 0000000..0ecd79d --- /dev/null +++ b/src/lib/arch/win32/ArchMiscWindows.h @@ -0,0 +1,202 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "common/common.h" +#include "common/stdstring.h" +#include "common/stdset.h" +#include "base/String.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <Tlhelp32.h> + +//! Miscellaneous win32 functions. +class ArchMiscWindows { +public: + enum EValueType { + kUNKNOWN, + kNO_VALUE, + kUINT, + kSTRING, + kBINARY + }; + enum EBusyModes { + kIDLE = 0x0000, + kSYSTEM = 0x0001, + kDISPLAY = 0x0002 + }; + + typedef int (*RunFunc)(void); + + //! Initialize + static void init(); + + //! Delete memory + static void cleanup(); + + //! Set the application icons + /*! + Set the application icons. + */ + static void setIcons(HICON largeIcon, HICON smallIcon); + + //! Get the application icons + /*! + Get the application icons. + */ + static void getIcons(HICON& largeIcon, HICON& smallIcon); + + //! Run the daemon + /*! + Delegates to ArchDaemonWindows. + */ + static int runDaemon(RunFunc runFunc); + + //! Indicate daemon is in main loop + /*! + Delegates to ArchDaemonWindows. + */ + static void daemonRunning(bool running); + + //! Indicate failure of running daemon + /*! + Delegates to ArchDaemonWindows. + */ + static void daemonFailed(int result); + + //! Get daemon quit message + /*! + Delegates to ArchDaemonWindows. + */ + static UINT getDaemonQuitMessage(); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* child); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* const* keyPath); + + //! Open/create and return a registry key, closing the parent key + static HKEY addKey(HKEY parent, const TCHAR* child); + + //! Open/create and return a registry key, closing the parent key + static HKEY addKey(HKEY parent, const TCHAR* const* keyPath); + + //! Close a key + static void closeKey(HKEY); + + //! Delete a key (which should have no subkeys) + static void deleteKey(HKEY parent, const TCHAR* name); + + //! Delete a value + static void deleteValue(HKEY parent, const TCHAR* name); + + //! Test if a value exists + static bool hasValue(HKEY key, const TCHAR* name); + + //! Get type of value + static EValueType typeOfValue(HKEY key, const TCHAR* name); + + //! Set a string value in the registry + static void setValue(HKEY key, const TCHAR* name, + const std::string& value); + + //! Set a DWORD value in the registry + static void setValue(HKEY key, const TCHAR* name, DWORD value); + + //! Set a BINARY value in the registry + /*! + Sets the \p name value of \p key to \p value.data(). + */ + static void setValueBinary(HKEY key, const TCHAR* name, + const std::string& value); + + //! Read a string value from the registry + static std::string readValueString(HKEY, const TCHAR* name); + + //! Read a DWORD value from the registry + static DWORD readValueInt(HKEY, const TCHAR* name); + + //! Read a BINARY value from the registry + static std::string readValueBinary(HKEY, const TCHAR* name); + + //! Add a dialog + static void addDialog(HWND); + + //! Remove a dialog + static void removeDialog(HWND); + + //! Process dialog message + /*! + Checks if the message is destined for a dialog. If so the message + is passed to the dialog and returns true, otherwise returns false. + */ + static bool processDialog(MSG*); + + //! Disable power saving + static void addBusyState(DWORD busyModes); + + //! Enable power saving + static void removeBusyState(DWORD busyModes); + + //! Briefly interrupt power saving + static void wakeupDisplay(); + + //! Returns true if this process was launched via NT service host. + static bool wasLaunchedAsService(); + + //! Returns true if we got the parent process name. + static bool getParentProcessName(String &name); + + static HINSTANCE instanceWin32(); + + static void setInstanceWin32(HINSTANCE instance); + + static BOOL WINAPI getProcessEntry(PROCESSENTRY32& entry, DWORD processID); + static BOOL WINAPI getSelfProcessEntry(PROCESSENTRY32& entry); + static BOOL WINAPI getParentProcessEntry(PROCESSENTRY32& entry); + +private: + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* child, bool create); + + //! Open and return a registry key, closing the parent key + static HKEY openKey(HKEY parent, const TCHAR* const* keyPath, + bool create); + + //! Read a string value from the registry + static std::string readBinaryOrString(HKEY, const TCHAR* name, DWORD type); + + //! Set thread busy state + static void setThreadExecutionState(DWORD); + + static DWORD WINAPI dummySetThreadExecutionState(DWORD); + +private: + typedef std::set<HWND> Dialogs; + typedef DWORD (WINAPI *STES_t)(DWORD); + + static Dialogs* s_dialogs; + static DWORD s_busyState; + static STES_t s_stes; + static HICON s_largeIcon; + static HICON s_smallIcon; + static HINSTANCE s_instanceWin32; +}; diff --git a/src/lib/arch/win32/ArchMultithreadWindows.cpp b/src/lib/arch/win32/ArchMultithreadWindows.cpp new file mode 100644 index 0000000..d3fd059 --- /dev/null +++ b/src/lib/arch/win32/ArchMultithreadWindows.cpp @@ -0,0 +1,705 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#if defined(_MSC_VER) && !defined(_MT) +# error multithreading compile option is required +#endif + +#include "arch/win32/ArchMultithreadWindows.h" +#include "arch/Arch.h" +#include "arch/XArch.h" + +#include <process.h> + +// +// note -- implementation of condition variable taken from: +// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html +// titled "Strategies for Implementing POSIX Condition Variables +// on Win32." it also provides an implementation that doesn't +// suffer from the incorrectness problem described in our +// corresponding header but it is slower, still unfair, and +// can cause busy waiting. +// + +// +// ArchThreadImpl +// + +class ArchThreadImpl { +public: + ArchThreadImpl(); + ~ArchThreadImpl(); + +public: + int m_refCount; + HANDLE m_thread; + DWORD m_id; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + HANDLE m_cancel; + bool m_cancelling; + HANDLE m_exit; + void* m_result; + void* m_networkData; +}; + +ArchThreadImpl::ArchThreadImpl() : + m_refCount(1), + m_thread(NULL), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancelling(false), + m_result(NULL), + m_networkData(NULL) +{ + m_exit = CreateEvent(NULL, TRUE, FALSE, NULL); + m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL); +} + +ArchThreadImpl::~ArchThreadImpl() +{ + CloseHandle(m_exit); + CloseHandle(m_cancel); +} + + +// +// ArchMultithreadWindows +// + +ArchMultithreadWindows* ArchMultithreadWindows::s_instance = NULL; + +ArchMultithreadWindows::ArchMultithreadWindows() +{ + assert(s_instance == NULL); + s_instance = this; + + // no signal handlers + for (size_t i = 0; i < kNUM_SIGNALS; ++i) { + m_signalFunc[i] = NULL; + m_signalUserData[i] = NULL; + } + + // create mutex for thread list + m_threadMutex = newMutex(); + + // create thread for calling (main) thread and add it to our + // list. no need to lock the mutex since we're the only thread. + m_mainThread = new ArchThreadImpl; + m_mainThread->m_thread = NULL; + m_mainThread->m_id = GetCurrentThreadId(); + insert(m_mainThread); +} + +ArchMultithreadWindows::~ArchMultithreadWindows() +{ + s_instance = NULL; + + // clean up thread list + for (ThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + delete *index; + } + + // done with mutex + delete m_threadMutex; +} + +void +ArchMultithreadWindows::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +ArchMultithreadWindows::getNetworkDataForThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +HANDLE +ArchMultithreadWindows::getCancelEventForCurrentThread() +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + return thread->m_cancel; +} + +ArchMultithreadWindows* +ArchMultithreadWindows::getInstance() +{ + return s_instance; +} + +ArchCond +ArchMultithreadWindows::newCondVar() +{ + ArchCondImpl* cond = new ArchCondImpl; + cond->m_events[ArchCondImpl::kSignal] = CreateEvent(NULL, + FALSE, FALSE, NULL); + cond->m_events[ArchCondImpl::kBroadcast] = CreateEvent(NULL, + TRUE, FALSE, NULL); + cond->m_waitCountMutex = newMutex(); + cond->m_waitCount = 0; + return cond; +} + +void +ArchMultithreadWindows::closeCondVar(ArchCond cond) +{ + CloseHandle(cond->m_events[ArchCondImpl::kSignal]); + CloseHandle(cond->m_events[ArchCondImpl::kBroadcast]); + closeMutex(cond->m_waitCountMutex); + delete cond; +} + +void +ArchMultithreadWindows::signalCondVar(ArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake one thread if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[ArchCondImpl::kSignal]); + } +} + +void +ArchMultithreadWindows::broadcastCondVar(ArchCond cond) +{ + // is anybody waiting? + lockMutex(cond->m_waitCountMutex); + const bool hasWaiter = (cond->m_waitCount > 0); + unlockMutex(cond->m_waitCountMutex); + + // wake all threads if anybody is waiting + if (hasWaiter) { + SetEvent(cond->m_events[ArchCondImpl::kBroadcast]); + } +} + +bool +ArchMultithreadWindows::waitCondVar(ArchCond cond, + ArchMutex mutex, double timeout) +{ + // prepare to wait + const DWORD winTimeout = (timeout < 0.0) ? INFINITE : + static_cast<DWORD>(1000.0 * timeout); + + // make a list of the condition variable events and the cancel event + // for the current thread. + HANDLE handles[4]; + handles[0] = cond->m_events[ArchCondImpl::kSignal]; + handles[1] = cond->m_events[ArchCondImpl::kBroadcast]; + handles[2] = getCancelEventForCurrentThread(); + + // update waiter count + lockMutex(cond->m_waitCountMutex); + ++cond->m_waitCount; + unlockMutex(cond->m_waitCountMutex); + + // release mutex. this should be atomic with the wait so that it's + // impossible for another thread to signal us between the unlock and + // the wait, which would lead to a lost signal on broadcasts. + // however, we're using a manual reset event for broadcasts which + // stays set until we reset it, so we don't lose the broadcast. + unlockMutex(mutex); + + // wait for a signal or broadcast + // TODO: this doesn't always signal when kill signals are sent + DWORD result = WaitForMultipleObjects(3, handles, FALSE, winTimeout); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 2 && + WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 2; + } + + // update the waiter count and check if we're the last waiter + lockMutex(cond->m_waitCountMutex); + --cond->m_waitCount; + const bool last = (result == WAIT_OBJECT_0 + 1 && cond->m_waitCount == 0); + unlockMutex(cond->m_waitCountMutex); + + // reset the broadcast event if we're the last waiter + if (last) { + ResetEvent(cond->m_events[ArchCondImpl::kBroadcast]); + } + + // reacquire the mutex + lockMutex(mutex); + + // cancel thread if necessary + if (result == WAIT_OBJECT_0 + 2) { + ARCH->testCancelThread(); + } + + // return success or failure + return (result == WAIT_OBJECT_0 + 0 || + result == WAIT_OBJECT_0 + 1); +} + +ArchMutex +ArchMultithreadWindows::newMutex() +{ + ArchMutexImpl* mutex = new ArchMutexImpl; + InitializeCriticalSection(&mutex->m_mutex); + return mutex; +} + +void +ArchMultithreadWindows::closeMutex(ArchMutex mutex) +{ + DeleteCriticalSection(&mutex->m_mutex); + delete mutex; +} + +void +ArchMultithreadWindows::lockMutex(ArchMutex mutex) +{ + EnterCriticalSection(&mutex->m_mutex); +} + +void +ArchMultithreadWindows::unlockMutex(ArchMutex mutex) +{ + LeaveCriticalSection(&mutex->m_mutex); +} + +ArchThread +ArchMultithreadWindows::newThread(ThreadFunc func, void* data) +{ + lockMutex(m_threadMutex); + + // create thread impl for new thread + ArchThreadImpl* thread = new ArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create thread + unsigned int id = 0; + thread->m_thread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0, + threadFunc, (void*)thread, 0, &id)); + thread->m_id = static_cast<DWORD>(id); + + // check if thread was started + if (thread->m_thread == 0) { + // failed to start thread so clean up + delete thread; + thread = NULL; + } + else { + // add thread to list + insert(thread); + + // increment ref count to account for the thread itself + refThread(thread); + } + + // note that the child thread will wait until we release this mutex + unlockMutex(m_threadMutex); + + return thread; +} + +ArchThread +ArchMultithreadWindows::newCurrentThread() +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = find(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +ArchMultithreadWindows::closeThread(ArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // close the handle (main thread has a NULL handle) + if (thread->m_thread != NULL) { + CloseHandle(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRefOrCreate(thread->m_id) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +ArchThread +ArchMultithreadWindows::copyThread(ArchThread thread) +{ + refThread(thread); + return thread; +} + +void +ArchMultithreadWindows::cancelThread(ArchThread thread) +{ + assert(thread != NULL); + + // set cancel flag + SetEvent(thread->m_cancel); +} + +void +ArchMultithreadWindows::setPriorityOfThread(ArchThread thread, int n) +{ + struct PriorityInfo { + public: + DWORD m_class; + int m_level; + }; + static const PriorityInfo s_pClass[] = { + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { IDLE_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { NORMAL_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { HIGH_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_IDLE }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_LOWEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_BELOW_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_ABOVE_NORMAL }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_HIGHEST }, + { REALTIME_PRIORITY_CLASS, THREAD_PRIORITY_TIME_CRITICAL} + }; +#if defined(_DEBUG) + // don't use really high priorities when debugging + static const size_t s_pMax = 13; +#else + static const size_t s_pMax = sizeof(s_pClass) / sizeof(s_pClass[0]) - 1; +#endif + static const size_t s_pBase = 8; // index of normal priority + + assert(thread != NULL); + + size_t index; + if (n > 0 && s_pBase < (size_t)n) { + // lowest priority + index = 0; + } + else { + index = (size_t)((int)s_pBase - n); + if (index > s_pMax) { + // highest priority + index = s_pMax; + } + } + SetPriorityClass(GetCurrentProcess(), s_pClass[index].m_class); + SetThreadPriority(thread->m_thread, s_pClass[index].m_level); +} + +void +ArchMultithreadWindows::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(GetCurrentThreadId()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +ArchMultithreadWindows::wait(ArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + ArchThreadImpl* self = findNoRef(GetCurrentThreadId()); + + // ignore wait if trying to wait on ourself + if (target == self) { + unlockMutex(m_threadMutex); + return false; + } + + // ref the target so it can't go away while we're watching it + refThread(target); + + unlockMutex(m_threadMutex); + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for this thread to be cancelled or woken up or for the + // target thread to terminate. + HANDLE handles[2]; + handles[0] = target->m_exit; + handles[1] = self->m_cancel; + DWORD result = WaitForMultipleObjects(2, handles, FALSE, t); + + // cancel takes priority + if (result != WAIT_OBJECT_0 + 1 && + WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) { + result = WAIT_OBJECT_0 + 1; + } + + // release target + closeThread(target); + + // handle result + switch (result) { + case WAIT_OBJECT_0 + 0: + // target thread terminated + return true; + + case WAIT_OBJECT_0 + 1: + // this thread was cancelled. does not return. + testCancelThreadImpl(self); + + default: + // timeout or error + return false; + } +} + +bool +ArchMultithreadWindows::isSameThread(ArchThread thread1, ArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +ArchMultithreadWindows::isExitedThread(ArchThread thread) +{ + // poll exit event + return (WaitForSingleObject(thread->m_exit, 0) == WAIT_OBJECT_0); +} + +void* +ArchMultithreadWindows::getResultOfThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +ArchMultithreadWindows::getIDOfThread(ArchThread thread) +{ + return static_cast<ThreadID>(thread->m_id); +} + +void +ArchMultithreadWindows::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +ArchMultithreadWindows::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + ARCH->unblockPollSocket(m_mainThread); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +ArchThreadImpl* +ArchMultithreadWindows::find(DWORD id) +{ + ArchThreadImpl* impl = findNoRef(id); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +ArchThreadImpl* +ArchMultithreadWindows::findNoRef(DWORD id) +{ + ArchThreadImpl* impl = findNoRefOrCreate(id); + if (impl == NULL) { + // create thread for calling thread which isn't in our list and + // add it to the list. this won't normally happen but it can if + // the system calls us under a new thread, like it does when we + // run as a service. + impl = new ArchThreadImpl; + impl->m_thread = NULL; + impl->m_id = GetCurrentThreadId(); + insert(impl); + } + return impl; +} + +ArchThreadImpl* +ArchMultithreadWindows::findNoRefOrCreate(DWORD id) +{ + // linear search + for (ThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_id == id) { + return *index; + } + } + return NULL; +} + +void +ArchMultithreadWindows::insert(ArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRefOrCreate(thread->m_id) == NULL); + + // append to list + m_threadList.push_back(thread); +} + +void +ArchMultithreadWindows::erase(ArchThreadImpl* thread) +{ + for (ThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +ArchMultithreadWindows::refThread(ArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRefOrCreate(thread->m_id) != NULL); + ++thread->m_refCount; +} + +void +ArchMultithreadWindows::testCancelThreadImpl(ArchThreadImpl* thread) +{ + assert(thread != NULL); + + // poll cancel event. return if not set. + const DWORD result = WaitForSingleObject(thread->m_cancel, 0); + if (result != WAIT_OBJECT_0) { + return; + } + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = !thread->m_cancelling; + thread->m_cancelling = true; + ResetEvent(thread->m_cancel); + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +unsigned int __stdcall +ArchMultithreadWindows::threadFunc(void* vrep) +{ + // get the thread + ArchThreadImpl* thread = static_cast<ArchThreadImpl*>(vrep); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return 0; +} + +void +ArchMultithreadWindows::doThreadFunc(ArchThread thread) +{ + // wait for parent to initialize this object + lockMutex(m_threadMutex); + unlockMutex(m_threadMutex); + + void* result = NULL; + try { + // go + result = (*thread->m_func)(thread->m_userData); + } + + catch (XThreadCancel&) { + // client called cancel() + } + catch (...) { + // note -- don't catch (...) to avoid masking bugs + SetEvent(thread->m_exit); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + unlockMutex(m_threadMutex); + SetEvent(thread->m_exit); + + // done with thread + closeThread(thread); +} diff --git a/src/lib/arch/win32/ArchMultithreadWindows.h b/src/lib/arch/win32/ArchMultithreadWindows.h new file mode 100644 index 0000000..99aa640 --- /dev/null +++ b/src/lib/arch/win32/ArchMultithreadWindows.h @@ -0,0 +1,116 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchMultithread.h" +#include "common/stdlist.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#define ARCH_MULTITHREAD ArchMultithreadWindows + +class ArchCondImpl { +public: + enum { kSignal = 0, kBroadcast }; + + HANDLE m_events[2]; + mutable int m_waitCount; + ArchMutex m_waitCountMutex; +}; + +class ArchMutexImpl { +public: + CRITICAL_SECTION m_mutex; +}; + +//! Win32 implementation of IArchMultithread +class ArchMultithreadWindows : public IArchMultithread { +public: + ArchMultithreadWindows(); + virtual ~ArchMultithreadWindows(); + + //! @name manipulators + //@{ + + void setNetworkDataForCurrentThread(void*); + + //@} + //! @name accessors + //@{ + + HANDLE getCancelEventForCurrentThread(); + + void* getNetworkDataForThread(ArchThread); + + static ArchMultithreadWindows* getInstance(); + + //@} + + // IArchMultithread overrides + virtual ArchCond newCondVar(); + virtual void closeCondVar(ArchCond); + virtual void signalCondVar(ArchCond); + virtual void broadcastCondVar(ArchCond); + virtual bool waitCondVar(ArchCond, ArchMutex, double timeout); + virtual ArchMutex newMutex(); + virtual void closeMutex(ArchMutex); + virtual void lockMutex(ArchMutex); + virtual void unlockMutex(ArchMutex); + virtual ArchThread newThread(ThreadFunc, void*); + virtual ArchThread newCurrentThread(); + virtual ArchThread copyThread(ArchThread); + virtual void closeThread(ArchThread); + virtual void cancelThread(ArchThread); + virtual void setPriorityOfThread(ArchThread, int n); + virtual void testCancelThread(); + virtual bool wait(ArchThread, double timeout); + virtual bool isSameThread(ArchThread, ArchThread); + virtual bool isExitedThread(ArchThread); + virtual void* getResultOfThread(ArchThread); + virtual ThreadID getIDOfThread(ArchThread); + virtual void setSignalHandler(ESignal, SignalFunc, void*); + virtual void raiseSignal(ESignal); + +private: + ArchThreadImpl* find(DWORD id); + ArchThreadImpl* findNoRef(DWORD id); + ArchThreadImpl* findNoRefOrCreate(DWORD id); + void insert(ArchThreadImpl* thread); + void erase(ArchThreadImpl* thread); + + void refThread(ArchThreadImpl* rep); + void testCancelThreadImpl(ArchThreadImpl* rep); + + void doThreadFunc(ArchThread thread); + static unsigned int __stdcall threadFunc(void* vrep); + +private: + typedef std::list<ArchThread> ThreadList; + + static ArchMultithreadWindows* s_instance; + + ArchMutex m_threadMutex; + + ThreadList m_threadList; + ArchThread m_mainThread; + + SignalFunc m_signalFunc[kNUM_SIGNALS]; + void* m_signalUserData[kNUM_SIGNALS]; +}; diff --git a/src/lib/arch/win32/ArchNetworkWinsock.cpp b/src/lib/arch/win32/ArchNetworkWinsock.cpp new file mode 100644 index 0000000..722c4c5 --- /dev/null +++ b/src/lib/arch/win32/ArchNetworkWinsock.cpp @@ -0,0 +1,985 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchNetworkWinsock.h" +#include "arch/win32/ArchMultithreadWindows.h" +#include "arch/win32/XArchWindows.h" +#include "arch/IArchMultithread.h" +#include "arch/Arch.h" + +#include <malloc.h> + +static const int s_family[] = { + PF_UNSPEC, + PF_INET, + PF_INET6, +}; +static const int s_type[] = { + SOCK_DGRAM, + SOCK_STREAM +}; + +static SOCKET (PASCAL FAR *accept_winsock)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen); +static int (PASCAL FAR *bind_winsock)(SOCKET s, const struct sockaddr FAR *addr, int namelen); +static int (PASCAL FAR *close_winsock)(SOCKET s); +static int (PASCAL FAR *connect_winsock)(SOCKET s, const struct sockaddr FAR *name, int namelen); +static int (PASCAL FAR *gethostname_winsock)(char FAR * name, int namelen); +static int (PASCAL FAR *getsockerror_winsock)(void); +static int (PASCAL FAR *getsockopt_winsock)(SOCKET s, int level, int optname, void FAR * optval, int FAR *optlen); +static u_short (PASCAL FAR *htons_winsock)(u_short v); +static char FAR * (PASCAL FAR *inet_ntoa_winsock)(struct in_addr in); +static unsigned long (PASCAL FAR *inet_addr_winsock)(const char FAR * cp); +static int (PASCAL FAR *ioctl_winsock)(SOCKET s, int cmd, void FAR * data); +static int (PASCAL FAR *listen_winsock)(SOCKET s, int backlog); +static u_short (PASCAL FAR *ntohs_winsock)(u_short v); +static int (PASCAL FAR *recv_winsock)(SOCKET s, void FAR * buf, int len, int flags); +static int (PASCAL FAR *select_winsock)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout); +static int (PASCAL FAR *send_winsock)(SOCKET s, const void FAR * buf, int len, int flags); +static int (PASCAL FAR *setsockopt_winsock)(SOCKET s, int level, int optname, const void FAR * optval, int optlen); +static int (PASCAL FAR *shutdown_winsock)(SOCKET s, int how); +static SOCKET (PASCAL FAR *socket_winsock)(int af, int type, int protocol); +static struct hostent FAR * (PASCAL FAR *gethostbyaddr_winsock)(const char FAR * addr, int len, int type); +static struct hostent FAR * (PASCAL FAR *gethostbyname_winsock)(const char FAR * name); +static int (PASCAL FAR *WSACleanup_winsock)(void); +static int (PASCAL FAR *WSAFDIsSet_winsock)(SOCKET, fd_set FAR * fdset); +static WSAEVENT (PASCAL FAR *WSACreateEvent_winsock)(void); +static BOOL (PASCAL FAR *WSACloseEvent_winsock)(WSAEVENT); +static BOOL (PASCAL FAR *WSASetEvent_winsock)(WSAEVENT); +static BOOL (PASCAL FAR *WSAResetEvent_winsock)(WSAEVENT); +static int (PASCAL FAR *WSAEventSelect_winsock)(SOCKET, WSAEVENT, long); +static DWORD (PASCAL FAR *WSAWaitForMultipleEvents_winsock)(DWORD, const WSAEVENT FAR*, BOOL, DWORD, BOOL); +static int (PASCAL FAR *WSAEnumNetworkEvents_winsock)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS); + +#undef FD_ISSET +#define FD_ISSET(fd, set) WSAFDIsSet_winsock((SOCKET)(fd), (fd_set FAR *)(set)) + +#define setfunc(var, name, type) var = (type)netGetProcAddress(module, #name) + +static HMODULE s_networkModule = NULL; + +static +FARPROC +netGetProcAddress(HMODULE module, LPCSTR name) +{ + FARPROC func = ::GetProcAddress(module, name); + if (!func) { + throw XArchNetworkSupport(""); + } + return func; +} + +ArchNetAddressImpl* +ArchNetAddressImpl::alloc(size_t size) +{ + size_t totalSize = size + ADDR_HDR_SIZE; + ArchNetAddressImpl* addr = (ArchNetAddressImpl*)malloc(totalSize); + addr->m_len = (int)size; + return addr; +} + + +// +// ArchNetworkWinsock +// + +ArchNetworkWinsock::ArchNetworkWinsock() : + m_mutex(NULL) +{ +} + +ArchNetworkWinsock::~ArchNetworkWinsock() +{ + if (s_networkModule != NULL) { + WSACleanup_winsock(); + ::FreeLibrary(s_networkModule); + + WSACleanup_winsock = NULL; + s_networkModule = NULL; + } + if (m_mutex != NULL) { + ARCH->closeMutex(m_mutex); + } + + EventList::iterator it; + for (it = m_unblockEvents.begin(); it != m_unblockEvents.end(); it++) { + delete *it; + } +} + +void +ArchNetworkWinsock::init() +{ + static const char* s_library[] = { "ws2_32.dll" }; + + assert(WSACleanup_winsock == NULL); + assert(s_networkModule == NULL); + + // try each winsock library + for (size_t i = 0; i < sizeof(s_library) / sizeof(s_library[0]); ++i) { + try { + initModule((HMODULE)::LoadLibrary(s_library[i])); + m_mutex = ARCH->newMutex(); + return; + } + catch (XArchNetwork&) { + // ignore + } + } + + // can't initialize any library + throw XArchNetworkSupport("Cannot load winsock library"); +} + +void +ArchNetworkWinsock::initModule(HMODULE module) +{ + if (module == NULL) { + throw XArchNetworkSupport(""); + } + + // get startup function address + int (PASCAL FAR *startup)(WORD, LPWSADATA); + setfunc(startup, WSAStartup, int(PASCAL FAR*)(WORD, LPWSADATA)); + + // startup network library + WORD version = MAKEWORD(2 /*major*/, 2 /*minor*/); + WSADATA data; + int err = startup(version, &data); + if (data.wVersion != version) { + throw XArchNetworkSupport(new XArchEvalWinsock(err)); + } + if (err != 0) { + // some other initialization error + throwError(err); + } + + // get function addresses + setfunc(accept_winsock, accept, SOCKET (PASCAL FAR *)(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen)); + setfunc(bind_winsock, bind, int (PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *addr, int namelen)); + setfunc(close_winsock, closesocket, int (PASCAL FAR *)(SOCKET s)); + setfunc(connect_winsock, connect, int (PASCAL FAR *)(SOCKET s, const struct sockaddr FAR *name, int namelen)); + setfunc(gethostname_winsock, gethostname, int (PASCAL FAR *)(char FAR * name, int namelen)); + setfunc(getsockerror_winsock, WSAGetLastError, int (PASCAL FAR *)(void)); + setfunc(getsockopt_winsock, getsockopt, int (PASCAL FAR *)(SOCKET s, int level, int optname, void FAR * optval, int FAR *optlen)); + setfunc(htons_winsock, htons, u_short (PASCAL FAR *)(u_short v)); + setfunc(inet_ntoa_winsock, inet_ntoa, char FAR * (PASCAL FAR *)(struct in_addr in)); + setfunc(inet_addr_winsock, inet_addr, unsigned long (PASCAL FAR *)(const char FAR * cp)); + setfunc(ioctl_winsock, ioctlsocket, int (PASCAL FAR *)(SOCKET s, int cmd, void FAR *)); + setfunc(listen_winsock, listen, int (PASCAL FAR *)(SOCKET s, int backlog)); + setfunc(ntohs_winsock, ntohs, u_short (PASCAL FAR *)(u_short v)); + setfunc(recv_winsock, recv, int (PASCAL FAR *)(SOCKET s, void FAR * buf, int len, int flags)); + setfunc(select_winsock, select, int (PASCAL FAR *)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout)); + setfunc(send_winsock, send, int (PASCAL FAR *)(SOCKET s, const void FAR * buf, int len, int flags)); + setfunc(setsockopt_winsock, setsockopt, int (PASCAL FAR *)(SOCKET s, int level, int optname, const void FAR * optval, int optlen)); + setfunc(shutdown_winsock, shutdown, int (PASCAL FAR *)(SOCKET s, int how)); + setfunc(socket_winsock, socket, SOCKET (PASCAL FAR *)(int af, int type, int protocol)); + setfunc(gethostbyaddr_winsock, gethostbyaddr, struct hostent FAR * (PASCAL FAR *)(const char FAR * addr, int len, int type)); + setfunc(gethostbyname_winsock, gethostbyname, struct hostent FAR * (PASCAL FAR *)(const char FAR * name)); + setfunc(WSACleanup_winsock, WSACleanup, int (PASCAL FAR *)(void)); + setfunc(WSAFDIsSet_winsock, __WSAFDIsSet, int (PASCAL FAR *)(SOCKET, fd_set FAR *)); + setfunc(WSACreateEvent_winsock, WSACreateEvent, WSAEVENT (PASCAL FAR *)(void)); + setfunc(WSACloseEvent_winsock, WSACloseEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSASetEvent_winsock, WSASetEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSAResetEvent_winsock, WSAResetEvent, BOOL (PASCAL FAR *)(WSAEVENT)); + setfunc(WSAEventSelect_winsock, WSAEventSelect, int (PASCAL FAR *)(SOCKET, WSAEVENT, long)); + setfunc(WSAWaitForMultipleEvents_winsock, WSAWaitForMultipleEvents, DWORD (PASCAL FAR *)(DWORD, const WSAEVENT FAR*, BOOL, DWORD, BOOL)); + setfunc(WSAEnumNetworkEvents_winsock, WSAEnumNetworkEvents, int (PASCAL FAR *)(SOCKET, WSAEVENT, LPWSANETWORKEVENTS)); + + s_networkModule = module; +} + +ArchSocket +ArchNetworkWinsock::newSocket(EAddressFamily family, ESocketType type) +{ + // create socket + SOCKET fd = socket_winsock(s_family[family], s_type[type], 0); + if (fd == INVALID_SOCKET) { + throwError(getsockerror_winsock()); + } + try { + setBlockingOnSocket(fd, false); + BOOL flag = 0; + int size = sizeof(flag); + if (setsockopt_winsock(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + } + catch (...) { + close_winsock(fd); + throw; + } + + // allocate socket object + ArchSocketImpl* socket = new ArchSocketImpl; + socket->m_socket = fd; + socket->m_refCount = 1; + socket->m_event = WSACreateEvent_winsock(); + socket->m_pollWrite = true; + return socket; +} + +ArchSocket +ArchNetworkWinsock::copySocket(ArchSocket s) +{ + assert(s != NULL); + + // ref the socket and return it + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + return s; +} + +void +ArchNetworkWinsock::closeSocket(ArchSocket s) +{ + assert(s != NULL); + + // unref the socket and note if it should be released + ARCH->lockMutex(m_mutex); + const bool doClose = (--s->m_refCount == 0); + ARCH->unlockMutex(m_mutex); + + // close the socket if necessary + if (doClose) { + if (close_winsock(s->m_socket) == SOCKET_ERROR) { + // close failed. restore the last ref and throw. + int err = getsockerror_winsock(); + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + throwError(err); + } + WSACloseEvent_winsock(s->m_event); + delete s; + } +} + +void +ArchNetworkWinsock::closeSocketForRead(ArchSocket s) +{ + assert(s != NULL); + + if (shutdown_winsock(s->m_socket, SD_RECEIVE) == SOCKET_ERROR) { + if (getsockerror_winsock() != WSAENOTCONN) { + throwError(getsockerror_winsock()); + } + } +} + +void +ArchNetworkWinsock::closeSocketForWrite(ArchSocket s) +{ + assert(s != NULL); + + if (shutdown_winsock(s->m_socket, SD_SEND) == SOCKET_ERROR) { + if (getsockerror_winsock() != WSAENOTCONN) { + throwError(getsockerror_winsock()); + } + } +} + +void +ArchNetworkWinsock::bindSocket(ArchSocket s, ArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (bind_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +void +ArchNetworkWinsock::listenOnSocket(ArchSocket s) +{ + assert(s != NULL); + + // hardcoding backlog + if (listen_winsock(s->m_socket, 3) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +ArchSocket +ArchNetworkWinsock::acceptSocket(ArchSocket s, ArchNetAddress* const addr) +{ + assert(s != NULL); + + // create new socket and temporary address + ArchSocketImpl* socket = new ArchSocketImpl; + ArchNetAddress tmp = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in6)); + + // accept on socket + SOCKET fd = accept_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, tmp), &tmp->m_len); + if (fd == INVALID_SOCKET) { + int err = getsockerror_winsock(); + delete socket; + free(tmp); + if (addr) { + *addr = NULL; + } + if (err == WSAEWOULDBLOCK) { + return NULL; + } + throwError(err); + } + + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close_winsock(fd); + delete socket; + free(tmp); + if (addr) { + *addr = NULL; + } + throw; + } + + // initialize socket + socket->m_socket = fd; + socket->m_refCount = 1; + socket->m_event = WSACreateEvent_winsock(); + socket->m_pollWrite = true; + + // copy address if requested + if (addr != NULL) { + *addr = ARCH->copyAddr(tmp); + } + + free(tmp); + return socket; +} + +bool +ArchNetworkWinsock::connectSocket(ArchSocket s, ArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (connect_winsock(s->m_socket, TYPED_ADDR(struct sockaddr, addr), + addr->m_len) == SOCKET_ERROR) { + if (getsockerror_winsock() == WSAEISCONN) { + return true; + } + if (getsockerror_winsock() == WSAEWOULDBLOCK) { + return false; + } + throwError(getsockerror_winsock()); + } + return true; +} + +int +ArchNetworkWinsock::pollSocket(PollEntry pe[], int num, double timeout) +{ + int i; + DWORD n; + + // prepare sockets and wait list + bool canWrite = false; + WSAEVENT* events = (WSAEVENT*)alloca((num + 1) * sizeof(WSAEVENT)); + for (i = 0, n = 0; i < num; ++i) { + // reset return flags + pe[i].m_revents = 0; + + // set invalid flag if socket is bogus then go to next socket + if (pe[i].m_socket == NULL) { + pe[i].m_revents |= kPOLLNVAL; + continue; + } + + // select desired events + long socketEvents = 0; + if ((pe[i].m_events & kPOLLIN) != 0) { + socketEvents |= FD_READ | FD_ACCEPT | FD_CLOSE; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + socketEvents |= FD_WRITE | FD_CONNECT | FD_CLOSE; + + // if m_pollWrite is false then we assume the socket is + // writable. winsock doesn't signal writability except + // when the state changes from unwritable. + if (!pe[i].m_socket->m_pollWrite) { + canWrite = true; + pe[i].m_revents |= kPOLLOUT; + } + } + + // if no events then ignore socket + if (socketEvents == 0) { + continue; + } + + // select socket for desired events + WSAEventSelect_winsock(pe[i].m_socket->m_socket, + pe[i].m_socket->m_event, socketEvents); + + // add socket event to wait list + events[n++] = pe[i].m_socket->m_event; + } + + // if no sockets then return immediately + if (n == 0) { + return 0; + } + + // add the unblock event + ArchMultithreadWindows* mt = ArchMultithreadWindows::getInstance(); + ArchThread thread = mt->newCurrentThread(); + WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread); + ARCH->closeThread(thread); + if (unblockEvent == NULL) { + unblockEvent = new WSAEVENT; + m_unblockEvents.push_back(unblockEvent); + *unblockEvent = WSACreateEvent_winsock(); + mt->setNetworkDataForCurrentThread(unblockEvent); + } + events[n++] = *unblockEvent; + + // prepare timeout + DWORD t = (timeout < 0.0) ? INFINITE : (DWORD)(1000.0 * timeout); + if (canWrite) { + // if we know we can write then don't block + t = 0; + } + + // wait + DWORD result = WSAWaitForMultipleEvents_winsock(n, events, FALSE, t, FALSE); + + // reset the unblock event + WSAResetEvent_winsock(*unblockEvent); + + // handle results + if (result == WSA_WAIT_FAILED) { + if (getsockerror_winsock() == WSAEINTR) { + // interrupted system call + ARCH->testCancelThread(); + return 0; + } + throwError(getsockerror_winsock()); + } + if (result == WSA_WAIT_TIMEOUT && !canWrite) { + return 0; + } + if (result == WSA_WAIT_EVENT_0 + n - 1) { + // the unblock event was signalled + return 0; + } + for (i = 0, n = 0; i < num; ++i) { + // skip events we didn't check + if (pe[i].m_socket == NULL || + (pe[i].m_events & (kPOLLIN | kPOLLOUT)) == 0) { + continue; + } + + // get events + WSANETWORKEVENTS info; + if (WSAEnumNetworkEvents_winsock(pe[i].m_socket->m_socket, + pe[i].m_socket->m_event, &info) == SOCKET_ERROR) { + continue; + } + if ((info.lNetworkEvents & FD_READ) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((info.lNetworkEvents & FD_ACCEPT) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((info.lNetworkEvents & FD_WRITE) != 0) { + pe[i].m_revents |= kPOLLOUT; + + // socket is now writable so don't bothing polling for + // writable until it becomes unwritable. + pe[i].m_socket->m_pollWrite = false; + } + if ((info.lNetworkEvents & FD_CONNECT) != 0) { + if (info.iErrorCode[FD_CONNECT_BIT] != 0) { + pe[i].m_revents |= kPOLLERR; + } + else { + pe[i].m_revents |= kPOLLOUT; + pe[i].m_socket->m_pollWrite = false; + } + } + if ((info.lNetworkEvents & FD_CLOSE) != 0) { + if (info.iErrorCode[FD_CLOSE_BIT] != 0) { + pe[i].m_revents |= kPOLLERR; + } + else { + if ((pe[i].m_events & kPOLLIN) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + pe[i].m_revents |= kPOLLOUT; + } + } + } + if (pe[i].m_revents != 0) { + ++n; + } + } + + return (int)n; +} + +void +ArchNetworkWinsock::unblockPollSocket(ArchThread thread) +{ + // set the unblock event + ArchMultithreadWindows* mt = ArchMultithreadWindows::getInstance(); + WSAEVENT* unblockEvent = (WSAEVENT*)mt->getNetworkDataForThread(thread); + if (unblockEvent != NULL) { + WSASetEvent_winsock(*unblockEvent); + } +} + +size_t +ArchNetworkWinsock::readSocket(ArchSocket s, void* buf, size_t len) +{ + assert(s != NULL); + + int n = recv_winsock(s->m_socket, buf, (int)len, 0); + if (n == SOCKET_ERROR) { + int err = getsockerror_winsock(); + if (err == WSAEINTR || err == WSAEWOULDBLOCK) { + return 0; + } + throwError(err); + } + return static_cast<size_t>(n); +} + +size_t +ArchNetworkWinsock::writeSocket(ArchSocket s, const void* buf, size_t len) +{ + assert(s != NULL); + + int n = send_winsock(s->m_socket, buf, (int)len, 0); + if (n == SOCKET_ERROR) { + int err = getsockerror_winsock(); + if (err == WSAEINTR) { + return 0; + } + if (err == WSAEWOULDBLOCK) { + s->m_pollWrite = true; + return 0; + } + throwError(err); + } + return static_cast<size_t>(n); +} + +void +ArchNetworkWinsock::throwErrorOnSocket(ArchSocket s) +{ + assert(s != NULL); + + // get the error from the socket layer + int err = 0; + int size = sizeof(err); + if (getsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_ERROR, &err, &size) == SOCKET_ERROR) { + err = getsockerror_winsock(); + } + + // throw if there's an error + if (err != 0) { + throwError(err); + } +} + +void +ArchNetworkWinsock::setBlockingOnSocket(SOCKET s, bool blocking) +{ + assert(s != 0); + + int flag = blocking ? 0 : 1; + if (ioctl_winsock(s, FIONBIO, &flag) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } +} + +bool +ArchNetworkWinsock::setNoDelayOnSocket(ArchSocket s, bool noDelay) +{ + assert(s != NULL); + + // get old state + BOOL oflag; + int size = sizeof(oflag); + if (getsockopt_winsock(s->m_socket, IPPROTO_TCP, + TCP_NODELAY, &oflag, &size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + // set new state + BOOL flag = noDelay ? 1 : 0; + size = sizeof(flag); + if (setsockopt_winsock(s->m_socket, IPPROTO_TCP, + TCP_NODELAY, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + return (oflag != 0); +} + +bool +ArchNetworkWinsock::setReuseAddrOnSocket(ArchSocket s, bool reuse) +{ + assert(s != NULL); + + // get old state + BOOL oflag; + int size = sizeof(oflag); + if (getsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_REUSEADDR, &oflag, &size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + // set new state + BOOL flag = reuse ? 1 : 0; + size = sizeof(flag); + if (setsockopt_winsock(s->m_socket, SOL_SOCKET, + SO_REUSEADDR, &flag, size) == SOCKET_ERROR) { + throwError(getsockerror_winsock()); + } + + return (oflag != 0); +} + +std::string +ArchNetworkWinsock::getHostName() +{ + char name[256]; + if (gethostname_winsock(name, sizeof(name)) == -1) { + name[0] = '\0'; + } + else { + name[sizeof(name) - 1] = '\0'; + } + return name; +} + +ArchNetAddress +ArchNetworkWinsock::newAnyAddr(EAddressFamily family) +{ + ArchNetAddressImpl* addr = NULL; + switch (family) { + case kINET: { + addr = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in)); + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + ipAddr->sin_family = AF_INET; + ipAddr->sin_port = 0; + ipAddr->sin_addr.s_addr = INADDR_ANY; + break; + } + + case kINET6: { + addr = ArchNetAddressImpl::alloc(sizeof(struct sockaddr_in6)); + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + ipAddr->sin6_family = AF_INET6; + ipAddr->sin6_port = 0; + memcpy(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any)); + break; + } + + default: + assert(0 && "invalid family"); + } + return addr; +} + +ArchNetAddress +ArchNetworkWinsock::copyAddr(ArchNetAddress addr) +{ + assert(addr != NULL); + + ArchNetAddressImpl* copy = ArchNetAddressImpl::alloc(addr->m_len); + memcpy(TYPED_ADDR(void, copy), TYPED_ADDR(void, addr), addr->m_len); + return copy; +} + +ArchNetAddress +ArchNetworkWinsock::nameToAddr(const std::string& name) +{ + // allocate address + + ArchNetAddressImpl* addr = new ArchNetAddressImpl; + + struct addrinfo hints; + struct addrinfo *p; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + int ret = -1; + + ARCH->lockMutex(m_mutex); + if ((ret = getaddrinfo(name.c_str(), NULL, &hints, &p)) != 0) { + ARCH->unlockMutex(m_mutex); + delete addr; + throwNameError(ret); + } + + if (p->ai_family == AF_INET) { + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + } else { + addr->m_len = (socklen_t)sizeof(struct sockaddr_in6); + } + + memcpy(&addr->m_addr, p->ai_addr, addr->m_len); + freeaddrinfo(p); + ARCH->unlockMutex(m_mutex); + return addr; +} + +void +ArchNetworkWinsock::closeAddr(ArchNetAddress addr) +{ + assert(addr != NULL); + + free(addr); +} + +std::string +ArchNetworkWinsock::addrToName(ArchNetAddress addr) +{ + assert(addr != NULL); + + char host[1024]; + char service[20]; + int ret = getnameinfo(TYPED_ADDR(struct sockaddr, addr), addr->m_len, host, sizeof(host), service, sizeof(service), 0); + + if (ret != NULL) { + throwNameError(ret); + } + + // return (primary) name + std::string name = host; + return name; +} + +std::string +ArchNetworkWinsock::addrToString(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + return inet_ntoa_winsock(ipAddr->sin_addr); + } + + case kINET6: { + char strAddr[INET6_ADDRSTRLEN]; + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + inet_ntop(AF_INET6, &ipAddr->sin6_addr, strAddr, INET6_ADDRSTRLEN); + return strAddr; + } + + default: + assert(0 && "unknown address family"); + return ""; + } +} + +IArchNetwork::EAddressFamily +ArchNetworkWinsock::getAddrFamily(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (addr->m_addr.ss_family) { + case AF_INET: + return kINET; + + case AF_INET6: + return kINET6; + + default: + return kUNKNOWN; + } +} + +void +ArchNetworkWinsock::setAddrPort(ArchNetAddress addr, int port) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + ipAddr->sin_port = htons_winsock(static_cast<u_short>(port)); + break; + } + + case kINET6: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + ipAddr->sin6_port = htons_winsock(static_cast<u_short>(port)); + break; + } + + default: + assert(0 && "unknown address family"); + break; + } +} + +int +ArchNetworkWinsock::getAddrPort(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + return ntohs_winsock(ipAddr->sin_port); + } + + case kINET6: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + return ntohs_winsock(ipAddr->sin6_port); + } + + default: + assert(0 && "unknown address family"); + return 0; + } +} + +bool +ArchNetworkWinsock::isAnyAddr(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + return (addr->m_len == sizeof(struct sockaddr_in) && + ipAddr->sin_addr.s_addr == INADDR_ANY); + } + + case kINET6: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + return (addr->m_len == sizeof(struct sockaddr_in) && + memcmp(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any))== 0); + } + + default: + assert(0 && "unknown address family"); + return true; + } +} + +bool +ArchNetworkWinsock::isEqualAddr(ArchNetAddress a, ArchNetAddress b) +{ + return (a == b || (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0)); +} + +void +ArchNetworkWinsock::throwError(int err) +{ + switch (err) { + case WSAEACCES: + throw XArchNetworkAccess(new XArchEvalWinsock(err)); + + case WSAEMFILE: + case WSAENOBUFS: + case WSAENETDOWN: + throw XArchNetworkResource(new XArchEvalWinsock(err)); + + case WSAEPROTOTYPE: + case WSAEPROTONOSUPPORT: + case WSAEAFNOSUPPORT: + case WSAEPFNOSUPPORT: + case WSAESOCKTNOSUPPORT: + case WSAEINVAL: + case WSAENOPROTOOPT: + case WSAEOPNOTSUPP: + case WSAESHUTDOWN: + case WSANOTINITIALISED: + case WSAVERNOTSUPPORTED: + case WSASYSNOTREADY: + throw XArchNetworkSupport(new XArchEvalWinsock(err)); + + case WSAEADDRNOTAVAIL: + throw XArchNetworkNoAddress(new XArchEvalWinsock(err)); + + case WSAEADDRINUSE: + throw XArchNetworkAddressInUse(new XArchEvalWinsock(err)); + + case WSAEHOSTUNREACH: + case WSAENETUNREACH: + throw XArchNetworkNoRoute(new XArchEvalWinsock(err)); + + case WSAENOTCONN: + throw XArchNetworkNotConnected(new XArchEvalWinsock(err)); + + case WSAEDISCON: + throw XArchNetworkShutdown(new XArchEvalWinsock(err)); + + case WSAENETRESET: + case WSAECONNABORTED: + case WSAECONNRESET: + throw XArchNetworkDisconnected(new XArchEvalWinsock(err)); + + case WSAECONNREFUSED: + throw XArchNetworkConnectionRefused(new XArchEvalWinsock(err)); + + case WSAEHOSTDOWN: + case WSAETIMEDOUT: + throw XArchNetworkTimedOut(new XArchEvalWinsock(err)); + + case WSAHOST_NOT_FOUND: + throw XArchNetworkNameUnknown(new XArchEvalWinsock(err)); + + case WSANO_DATA: + throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err)); + + case WSANO_RECOVERY: + throw XArchNetworkNameFailure(new XArchEvalWinsock(err)); + + case WSATRY_AGAIN: + throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err)); + + default: + throw XArchNetwork(new XArchEvalWinsock(err)); + } +} + +void +ArchNetworkWinsock::throwNameError(int err) +{ + switch (err) { + case WSAHOST_NOT_FOUND: + throw XArchNetworkNameUnknown(new XArchEvalWinsock(err)); + + case WSANO_DATA: + throw XArchNetworkNameNoAddress(new XArchEvalWinsock(err)); + + case WSANO_RECOVERY: + throw XArchNetworkNameFailure(new XArchEvalWinsock(err)); + + case WSATRY_AGAIN: + throw XArchNetworkNameUnavailable(new XArchEvalWinsock(err)); + + default: + throw XArchNetworkName(new XArchEvalWinsock(err)); + } +} diff --git a/src/lib/arch/win32/ArchNetworkWinsock.h b/src/lib/arch/win32/ArchNetworkWinsock.h new file mode 100644 index 0000000..0b01671 --- /dev/null +++ b/src/lib/arch/win32/ArchNetworkWinsock.h @@ -0,0 +1,111 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <ws2tcpip.h> +// declare no functions in winsock2 +#ifndef INCL_WINSOCK_API_PROTOTYPES +#define INCL_WINSOCK_API_PROTOTYPES 0 +#endif +#define INCL_WINSOCK_API_TYPEDEFS 0 + +#include "arch/IArchNetwork.h" +#include "arch/IArchMultithread.h" + +#include <WinSock2.h> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <list> + +#pragma comment(lib, "ws2_32.lib") + +#define ARCH_NETWORK ArchNetworkWinsock + +class ArchSocketImpl { +public: + SOCKET m_socket; + int m_refCount; + WSAEVENT m_event; + bool m_pollWrite; +}; + +class ArchNetAddressImpl { +public: + static ArchNetAddressImpl* alloc(size_t); + +public: + int m_len; + struct sockaddr_storage m_addr; +}; +#define ADDR_HDR_SIZE offsetof(ArchNetAddressImpl, m_addr) +#define TYPED_ADDR(type_, addr_) (reinterpret_cast<type_*>(&addr_->m_addr)) + +//! Win32 implementation of IArchNetwork +class ArchNetworkWinsock : public IArchNetwork { +public: + ArchNetworkWinsock(); + virtual ~ArchNetworkWinsock(); + + virtual void init(); + + // IArchNetwork overrides + virtual ArchSocket newSocket(EAddressFamily, ESocketType); + virtual ArchSocket copySocket(ArchSocket s); + virtual void closeSocket(ArchSocket s); + virtual void closeSocketForRead(ArchSocket s); + virtual void closeSocketForWrite(ArchSocket s); + virtual void bindSocket(ArchSocket s, ArchNetAddress addr); + virtual void listenOnSocket(ArchSocket s); + virtual ArchSocket acceptSocket(ArchSocket s, ArchNetAddress* addr); + virtual bool connectSocket(ArchSocket s, ArchNetAddress name); + virtual int pollSocket(PollEntry[], int num, double timeout); + virtual void unblockPollSocket(ArchThread thread); + virtual size_t readSocket(ArchSocket s, void* buf, size_t len); + virtual size_t writeSocket(ArchSocket s, + const void* buf, size_t len); + virtual void throwErrorOnSocket(ArchSocket); + virtual bool setNoDelayOnSocket(ArchSocket, bool noDelay); + virtual bool setReuseAddrOnSocket(ArchSocket, bool reuse); + virtual std::string getHostName(); + virtual ArchNetAddress newAnyAddr(EAddressFamily); + virtual ArchNetAddress copyAddr(ArchNetAddress); + virtual ArchNetAddress nameToAddr(const std::string&); + virtual void closeAddr(ArchNetAddress); + virtual std::string addrToName(ArchNetAddress); + virtual std::string addrToString(ArchNetAddress); + virtual EAddressFamily getAddrFamily(ArchNetAddress); + virtual void setAddrPort(ArchNetAddress, int port); + virtual int getAddrPort(ArchNetAddress); + virtual bool isAnyAddr(ArchNetAddress); + virtual bool isEqualAddr(ArchNetAddress, ArchNetAddress); + +private: + void initModule(HMODULE); + + void setBlockingOnSocket(SOCKET, bool blocking); + + void throwError(int); + void throwNameError(int); + +private: + typedef std::list<WSAEVENT> EventList; + + ArchMutex m_mutex; + EventList m_unblockEvents; +}; diff --git a/src/lib/arch/win32/ArchSleepWindows.cpp b/src/lib/arch/win32/ArchSleepWindows.cpp new file mode 100644 index 0000000..69648a7 --- /dev/null +++ b/src/lib/arch/win32/ArchSleepWindows.cpp @@ -0,0 +1,61 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchSleepWindows.h" +#include "arch/Arch.h" +#include "arch/win32/ArchMultithreadWindows.h" + +// +// ArchSleepWindows +// + +ArchSleepWindows::ArchSleepWindows() +{ + // do nothing +} + +ArchSleepWindows::~ArchSleepWindows() +{ + // do nothing +} + +void +ArchSleepWindows::sleep(double timeout) +{ + ARCH->testCancelThread(); + if (timeout < 0.0) { + return; + } + + // get the cancel event from the current thread. this only + // works if we're using the windows multithread object but + // this is windows so that's pretty certain; we'll get a + // link error if we're not, though. + ArchMultithreadWindows* mt = ArchMultithreadWindows::getInstance(); + if (mt != NULL) { + HANDLE cancelEvent = mt->getCancelEventForCurrentThread(); + WaitForSingleObject(cancelEvent, (DWORD)(1000.0 * timeout)); + if (timeout == 0.0) { + Sleep(0); + } + } + else { + Sleep((DWORD)(1000.0 * timeout)); + } + ARCH->testCancelThread(); +} diff --git a/src/lib/arch/win32/ArchSleepWindows.h b/src/lib/arch/win32/ArchSleepWindows.h new file mode 100644 index 0000000..d673caf --- /dev/null +++ b/src/lib/arch/win32/ArchSleepWindows.h @@ -0,0 +1,33 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchSleep.h" + +#define ARCH_SLEEP ArchSleepWindows + +//! Win32 implementation of IArchSleep +class ArchSleepWindows : public IArchSleep { +public: + ArchSleepWindows(); + virtual ~ArchSleepWindows(); + + // IArchSleep overrides + virtual void sleep(double timeout); +}; diff --git a/src/lib/arch/win32/ArchStringWindows.cpp b/src/lib/arch/win32/ArchStringWindows.cpp new file mode 100644 index 0000000..deaf536 --- /dev/null +++ b/src/lib/arch/win32/ArchStringWindows.cpp @@ -0,0 +1,46 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchStringWindows.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <stdio.h> + +// +// ArchStringWindows +// + +#include "arch/multibyte.h" +#define HAVE_VSNPRINTF 1 +#define ARCH_VSNPRINTF _vsnprintf +#include "arch/vsnprintf.h" + +ArchStringWindows::ArchStringWindows() +{ +} + +ArchStringWindows::~ArchStringWindows() +{ +} + +IArchString::EWideCharEncoding +ArchStringWindows::getWideCharEncoding() +{ + return kUTF16; +} diff --git a/src/lib/arch/win32/ArchStringWindows.h b/src/lib/arch/win32/ArchStringWindows.h new file mode 100644 index 0000000..23812dc --- /dev/null +++ b/src/lib/arch/win32/ArchStringWindows.h @@ -0,0 +1,34 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchString.h" + +#define ARCH_STRING ArchStringWindows + +//! Win32 implementation of IArchString +class ArchStringWindows : public IArchString { +public: + ArchStringWindows(); + virtual ~ArchStringWindows(); + + // IArchString overrides + virtual EWideCharEncoding + getWideCharEncoding(); +}; diff --git a/src/lib/arch/win32/ArchSystemWindows.cpp b/src/lib/arch/win32/ArchSystemWindows.cpp new file mode 100644 index 0000000..cf3b066 --- /dev/null +++ b/src/lib/arch/win32/ArchSystemWindows.cpp @@ -0,0 +1,166 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchSystemWindows.h" +#include "arch/win32/ArchMiscWindows.h" +#include "arch/win32/XArchWindows.h" + +#include "tchar.h" +#include <string> + +#include <windows.h> +#include <psapi.h> + +static const char* s_settingsKeyNames[] = { + _T("SOFTWARE"), + _T("Barrier"), + NULL +}; + +// +// ArchSystemWindows +// + +ArchSystemWindows::ArchSystemWindows() +{ + // do nothing +} + +ArchSystemWindows::~ArchSystemWindows() +{ + // do nothing +} + +std::string +ArchSystemWindows::getOSName() const +{ + std::string osName ("Microsoft Windows <unknown>"); + static const TCHAR* const windowsVersionKeyNames[] = { + _T("SOFTWARE"), + _T("Microsoft"), + _T("Windows NT"), + _T("CurrentVersion"), + NULL + }; + + HKEY key = ArchMiscWindows::openKey(HKEY_LOCAL_MACHINE, windowsVersionKeyNames); + if (key == NULL) { + return osName; + } + + std::string productName = ArchMiscWindows::readValueString(key, "ProductName"); + if (osName.empty()) { + return osName; + } + + return "Microsoft " + productName; +} + +std::string +ArchSystemWindows::getPlatformName() const +{ +#ifdef _X86_ + if (isWOW64()) + return "x86 (WOW64)"; + else + return "x86"; +#else +#ifdef _AMD64_ + return "x64"; +#else + return "Unknown"; +#endif +#endif +} + +std::string +ArchSystemWindows::setting(const std::string& valueName) const +{ + HKEY key = ArchMiscWindows::openKey(HKEY_LOCAL_MACHINE, s_settingsKeyNames); + if (key == NULL) + return ""; + + return ArchMiscWindows::readValueString(key, valueName.c_str()); +} + +void +ArchSystemWindows::setting(const std::string& valueName, const std::string& valueString) const +{ + HKEY key = ArchMiscWindows::addKey(HKEY_LOCAL_MACHINE, s_settingsKeyNames); + if (key == NULL) + throw XArch(std::string("could not access registry key: ") + valueName); + ArchMiscWindows::setValue(key, valueName.c_str(), valueString.c_str()); +} + +bool +ArchSystemWindows::isWOW64() const +{ +#if WINVER >= _WIN32_WINNT_WINXP + typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); + HMODULE hModule = GetModuleHandle(TEXT("kernel32")); + if (!hModule) return FALSE; + + LPFN_ISWOW64PROCESS fnIsWow64Process = + (LPFN_ISWOW64PROCESS) GetProcAddress(hModule, "IsWow64Process"); + + BOOL bIsWow64 = FALSE; + if (NULL != fnIsWow64Process && + fnIsWow64Process(GetCurrentProcess(), &bIsWow64) && + bIsWow64) + { + return true; + } +#endif + return false; +} +#pragma comment(lib, "psapi") + +std::string +ArchSystemWindows::getLibsUsed(void) const +{ + HMODULE hMods[1024]; + HANDLE hProcess; + DWORD cbNeeded; + unsigned int i; + char hex[16]; + + DWORD pid = GetCurrentProcessId(); + + std::string msg = "pid:" + std::to_string((unsigned long long)pid) + "\n"; + + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid); + + if (NULL == hProcess) { + return msg; + } + + if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { + for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { + TCHAR szModName[MAX_PATH]; + if (GetModuleFileNameEx(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR))) { + sprintf(hex, "(0x%08llX)", reinterpret_cast<long long>(hMods[i])); + msg += szModName; + msg.append(hex); + msg.append("\n"); + } + } + } + + CloseHandle(hProcess); + return msg; +} diff --git a/src/lib/arch/win32/ArchSystemWindows.h b/src/lib/arch/win32/ArchSystemWindows.h new file mode 100644 index 0000000..3d45ee6 --- /dev/null +++ b/src/lib/arch/win32/ArchSystemWindows.h @@ -0,0 +1,39 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2004 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchSystem.h" + +#define ARCH_SYSTEM ArchSystemWindows + +//! Win32 implementation of IArchString +class ArchSystemWindows : public IArchSystem { +public: + ArchSystemWindows(); + virtual ~ArchSystemWindows(); + + // IArchSystem overrides + virtual std::string getOSName() const; + virtual std::string getPlatformName() const; + virtual std::string setting(const std::string& valueName) const; + virtual void setting(const std::string& valueName, const std::string& valueString) const; + virtual std::string getLibsUsed(void) const; + + bool isWOW64() const; +}; diff --git a/src/lib/arch/win32/ArchTaskBarWindows.cpp b/src/lib/arch/win32/ArchTaskBarWindows.cpp new file mode 100644 index 0000000..731dc59 --- /dev/null +++ b/src/lib/arch/win32/ArchTaskBarWindows.cpp @@ -0,0 +1,514 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchTaskBarWindows.h" +#include "arch/win32/ArchMiscWindows.h" +#include "arch/IArchTaskBarReceiver.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "barrier/win32/AppUtilWindows.h" + +#include <string.h> +#include <shellapi.h> + +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; + +// +// ArchTaskBarWindows +// + +ArchTaskBarWindows* ArchTaskBarWindows::s_instance = NULL; + +ArchTaskBarWindows::ArchTaskBarWindows() : + m_mutex(NULL), + m_condVar(NULL), + m_ready(false), + m_result(0), + m_thread(NULL), + m_hwnd(NULL), + m_taskBarRestart(0), + m_nextID(kFirstReceiverID) +{ + // save the singleton instance + s_instance = this; +} + +ArchTaskBarWindows::~ArchTaskBarWindows() +{ + if (m_thread != NULL) { + PostMessage(m_hwnd, WM_QUIT, 0, 0); + ARCH->wait(m_thread, -1.0); + ARCH->closeThread(m_thread); + } + if (m_condVar != NULL) { + ARCH->closeCondVar(m_condVar); + } + if (m_mutex != NULL) { + ARCH->closeMutex(m_mutex); + } + s_instance = NULL; +} + +void +ArchTaskBarWindows::init() +{ + // 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(&ArchTaskBarWindows::threadEntry, this); + + // wait for child thread + while (!m_ready) { + ARCH->waitCondVar(m_condVar, m_mutex, -1.0); + } + + // ready + ARCH->unlockMutex(m_mutex); +} + +void +ArchTaskBarWindows::addDialog(HWND hwnd) +{ + ArchMiscWindows::addDialog(hwnd); +} + +void +ArchTaskBarWindows::removeDialog(HWND hwnd) +{ + ArchMiscWindows::removeDialog(hwnd); +} + +void +ArchTaskBarWindows::addReceiver(IArchTaskBarReceiver* receiver) +{ + // ignore bogus receiver + if (receiver == NULL) { + return; + } + + // add receiver if necessary + ReceiverToInfoMap::iterator index = m_receivers.find(receiver); + if (index == m_receivers.end()) { + // add it, creating a new message ID for it + ReceiverInfo 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 +ArchTaskBarWindows::removeReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + ReceiverToInfoMap::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 +ArchTaskBarWindows::updateReceiver(IArchTaskBarReceiver* receiver) +{ + // find receiver + ReceiverToInfoMap::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 +ArchTaskBarWindows::getNextID() +{ + if (m_oldIDs.empty()) { + return m_nextID++; + } + UINT id = m_oldIDs.back(); + m_oldIDs.pop_back(); + return id; +} + +void +ArchTaskBarWindows::recycleID(UINT id) +{ + m_oldIDs.push_back(id); +} + +void +ArchTaskBarWindows::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 +ArchTaskBarWindows::removeIcon(UINT id) +{ + ARCH->lockMutex(m_mutex); + removeIconNoLock(id); + ARCH->unlockMutex(m_mutex); +} + +void +ArchTaskBarWindows::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 +ArchTaskBarWindows::addAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (ReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + modifyIconNoLock(index, NIM_ADD); + } + ARCH->unlockMutex(m_mutex); +} + +void +ArchTaskBarWindows::removeAllIcons() +{ + ARCH->lockMutex(m_mutex); + for (ReceiverToInfoMap::const_iterator index = m_receivers.begin(); + index != m_receivers.end(); ++index) { + removeIconNoLock(index->second.m_id); + } + ARCH->unlockMutex(m_mutex); +} + +void +ArchTaskBarWindows::modifyIconNoLock( + ReceiverToInfoMap::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 = static_cast<HICON>( + const_cast<IArchTaskBarReceiver::Icon>(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 +ArchTaskBarWindows::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 +ArchTaskBarWindows::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 +ArchTaskBarWindows::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 (Dialogs::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 (Dialogs::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 +ArchTaskBarWindows::wndProc(HWND hwnd, + UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case kNotifyReceiver: { + // lookup receiver + CIDToReceiverMap::const_iterator index = m_idTable.find((UINT)wParam); + if (index != m_idTable.end()) { + IArchTaskBarReceiver* receiver = index->second->first; + handleIconMessage(receiver, lParam); + return 0; + } + break; + } + + case kAddReceiver: + addIcon((UINT)wParam); + break; + + case kRemoveReceiver: + removeIcon((UINT)wParam); + break; + + case kUpdateReceiver: + updateIcon((UINT)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 +ArchTaskBarWindows::staticWndProc(HWND hwnd, UINT msg, + WPARAM wParam, LPARAM lParam) +{ + // if msg is WM_NCCREATE, extract the ArchTaskBarWindows* and put + // it in the extra window data then forward the call. + ArchTaskBarWindows* self = NULL; + if (msg == WM_NCCREATE) { + CREATESTRUCT* createInfo; + createInfo = reinterpret_cast<CREATESTRUCT*>(lParam); + self = static_cast<ArchTaskBarWindows*>( + createInfo->lpCreateParams); + SetWindowLongPtr(hwnd, 0, reinterpret_cast<LONG_PTR>(createInfo->lpCreateParams)); + } + else { + // get the extra window data and forward the call + LONG_PTR data = GetWindowLongPtr(hwnd, 0); + if (data != 0) { + self = static_cast<ArchTaskBarWindows*>(reinterpret_cast<void*>(data)); + } + } + + // forward the message + if (self != NULL) { + return self->wndProc(hwnd, msg, wParam, lParam); + } + else { + return DefWindowProc(hwnd, msg, wParam, lParam); + } +} + +void +ArchTaskBarWindows::threadMainLoop() +{ + // register the task bar restart message + m_taskBarRestart = RegisterWindowMessage(TEXT("TaskbarCreated")); + + // register a window class + LPCTSTR className = TEXT("BarrierTaskBar"); + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_NOCLOSE; + classInfo.lpfnWndProc = &ArchTaskBarWindows::staticWndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = sizeof(ArchTaskBarWindows*); + classInfo.hInstance = instanceWin32(); + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = className; + classInfo.hIconSm = NULL; + ATOM windowClass = RegisterClassEx(&classInfo); + + // create window + m_hwnd = CreateWindowEx(WS_EX_TOOLWINDOW, + className, + TEXT("Barrier Task Bar"), + WS_POPUP, + 0, 0, 1, 1, + NULL, + NULL, + instanceWin32(), + static_cast<void*>(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(className, instanceWin32()); + return; + } + + // main loop + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) { + if (!processDialogs(&msg)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + // clean up + removeAllIcons(); + DestroyWindow(m_hwnd); + UnregisterClass(className, instanceWin32()); +} + +void* +ArchTaskBarWindows::threadEntry(void* self) +{ + static_cast<ArchTaskBarWindows*>(self)->threadMainLoop(); + return NULL; +} + +HINSTANCE ArchTaskBarWindows::instanceWin32() +{ + return ArchMiscWindows::instanceWin32(); +}
\ No newline at end of file diff --git a/src/lib/arch/win32/ArchTaskBarWindows.h b/src/lib/arch/win32/ArchTaskBarWindows.h new file mode 100644 index 0000000..0edddf8 --- /dev/null +++ b/src/lib/arch/win32/ArchTaskBarWindows.h @@ -0,0 +1,114 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchTaskBar.h" +#include "arch/IArchMultithread.h" +#include "common/stdmap.h" +#include "common/stdvector.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#define ARCH_TASKBAR ArchTaskBarWindows + +//! Win32 implementation of IArchTaskBar +class ArchTaskBarWindows : public IArchTaskBar { +public: + ArchTaskBarWindows(); + virtual ~ArchTaskBarWindows(); + + virtual void init(); + + //! Add a dialog window + /*! + Tell the task bar event loop about a dialog. Win32 annoyingly + requires messages destined for modeless dialog boxes to be + dispatched differently than other messages. + */ + static void addDialog(HWND); + + //! Remove a dialog window + /*! + Remove a dialog window added via \c addDialog(). + */ + static void removeDialog(HWND); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); + +private: + class ReceiverInfo { + public: + UINT m_id; + }; + + typedef std::map<IArchTaskBarReceiver*, ReceiverInfo> ReceiverToInfoMap; + typedef std::map<UINT, ReceiverToInfoMap::iterator> CIDToReceiverMap; + typedef std::vector<UINT> CIDStack; + typedef std::map<HWND, bool> Dialogs; + + UINT getNextID(); + void recycleID(UINT); + + void addIcon(UINT); + void removeIcon(UINT); + void updateIcon(UINT); + void addAllIcons(); + void removeAllIcons(); + void modifyIconNoLock(ReceiverToInfoMap::const_iterator, + DWORD taskBarMessage); + void removeIconNoLock(UINT id); + void handleIconMessage(IArchTaskBarReceiver*, LPARAM); + + bool processDialogs(MSG*); + LRESULT wndProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK + staticWndProc(HWND, UINT, WPARAM, LPARAM); + void threadMainLoop(); + static void* threadEntry(void*); + + HINSTANCE instanceWin32(); + +private: + static ArchTaskBarWindows* s_instance; + + // multithread data + ArchMutex m_mutex; + ArchCond m_condVar; + bool m_ready; + int m_result; + ArchThread m_thread; + + // child thread data + HWND m_hwnd; + UINT m_taskBarRestart; + + // shared data + ReceiverToInfoMap m_receivers; + CIDToReceiverMap m_idTable; + CIDStack m_oldIDs; + UINT m_nextID; + + // dialogs + Dialogs m_dialogs; + Dialogs m_addedDialogs; +}; diff --git a/src/lib/arch/win32/ArchTimeWindows.cpp b/src/lib/arch/win32/ArchTimeWindows.cpp new file mode 100644 index 0000000..568a483 --- /dev/null +++ b/src/lib/arch/win32/ArchTimeWindows.cpp @@ -0,0 +1,89 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/ArchTimeWindows.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#define MMNODRV // Disable: Installable driver support +#define MMNOSOUND // Disable: Sound support +#define MMNOWAVE // Disable: Waveform support +#define MMNOMIDI // Disable: MIDI support +#define MMNOAUX // Disable: Auxiliary audio support +#define MMNOMIXER // Disable: Mixer support +#define MMNOJOY // Disable: Joystick support +#define MMNOMCI // Disable: MCI support +#define MMNOMMIO // Disable: Multimedia file I/O support +#define MMNOMMSYSTEM // Disable: General MMSYSTEM functions +#include <MMSystem.h> + +typedef WINMMAPI DWORD (WINAPI *PTimeGetTime)(void); + +static double s_freq = 0.0; +static HINSTANCE s_mmInstance = NULL; +static PTimeGetTime s_tgt = NULL; + + +// +// ArchTimeWindows +// + +ArchTimeWindows::ArchTimeWindows() +{ + assert(s_freq == 0.0 || s_mmInstance == NULL); + + LARGE_INTEGER freq; + if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0) { + s_freq = 1.0 / static_cast<double>(freq.QuadPart); + } + else { + // load winmm.dll and get timeGetTime + s_mmInstance = LoadLibrary("winmm"); + if (s_mmInstance != NULL) { + s_tgt = (PTimeGetTime)GetProcAddress(s_mmInstance, "timeGetTime"); + } + } +} + +ArchTimeWindows::~ArchTimeWindows() +{ + s_freq = 0.0; + if (s_mmInstance == NULL) { + FreeLibrary(static_cast<HMODULE>(s_mmInstance)); + s_tgt = NULL; + s_mmInstance = NULL; + } +} + +double +ArchTimeWindows::time() +{ + // get time. we try three ways, in order of descending precision + if (s_freq != 0.0) { + LARGE_INTEGER c; + QueryPerformanceCounter(&c); + return s_freq * static_cast<double>(c.QuadPart); + } + else if (s_tgt != NULL) { + return 0.001 * static_cast<double>(s_tgt()); + } + else { + return 0.001 * static_cast<double>(GetTickCount()); + } +} diff --git a/src/lib/arch/win32/ArchTimeWindows.h b/src/lib/arch/win32/ArchTimeWindows.h new file mode 100644 index 0000000..42351a1 --- /dev/null +++ b/src/lib/arch/win32/ArchTimeWindows.h @@ -0,0 +1,33 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/IArchTime.h" + +#define ARCH_TIME ArchTimeWindows + +//! Win32 implementation of IArchTime +class ArchTimeWindows : public IArchTime { +public: + ArchTimeWindows(); + virtual ~ArchTimeWindows(); + + // IArchTime overrides + virtual double time(); +}; diff --git a/src/lib/arch/win32/XArchWindows.cpp b/src/lib/arch/win32/XArchWindows.cpp new file mode 100644 index 0000000..eeec0e1 --- /dev/null +++ b/src/lib/arch/win32/XArchWindows.cpp @@ -0,0 +1,120 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "arch/win32/XArchWindows.h" +#include "arch/win32/ArchNetworkWinsock.h" +#include "base/String.h" + +// +// XArchEvalWindows +// + +std::string +XArchEvalWindows::eval() const throw() +{ + char* cmsg; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, + m_error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&cmsg, + 0, + NULL) == 0) { + cmsg = NULL; + return barrier::string::sprintf("Unknown error, code %d", m_error); + } + std::string smsg(cmsg); + LocalFree(cmsg); + return smsg; +} + + +// +// XArchEvalWinsock +// + +std::string +XArchEvalWinsock::eval() const throw() +{ + // built-in windows function for looking up error message strings + // may not look up network error messages correctly. we'll have + // to do it ourself. + static const struct { int m_code; const char* m_msg; } s_netErrorCodes[] = { + /* 10004 */{WSAEINTR, "The (blocking) call was canceled via WSACancelBlockingCall"}, + /* 10009 */{WSAEBADF, "Bad file handle"}, + /* 10013 */{WSAEACCES, "The requested address is a broadcast address, but the appropriate flag was not set"}, + /* 10014 */{WSAEFAULT, "WSAEFAULT"}, + /* 10022 */{WSAEINVAL, "WSAEINVAL"}, + /* 10024 */{WSAEMFILE, "No more file descriptors available"}, + /* 10035 */{WSAEWOULDBLOCK, "Socket is marked as non-blocking and no connections are present or the receive operation would block"}, + /* 10036 */{WSAEINPROGRESS, "A blocking Windows Sockets operation is in progress"}, + /* 10037 */{WSAEALREADY, "The asynchronous routine being canceled has already completed"}, + /* 10038 */{WSAENOTSOCK, "At least on descriptor is not a socket"}, + /* 10039 */{WSAEDESTADDRREQ, "A destination address is required"}, + /* 10040 */{WSAEMSGSIZE, "The datagram was too large to fit into the specified buffer and was truncated"}, + /* 10041 */{WSAEPROTOTYPE, "The specified protocol is the wrong type for this socket"}, + /* 10042 */{WSAENOPROTOOPT, "The option is unknown or unsupported"}, + /* 10043 */{WSAEPROTONOSUPPORT,"The specified protocol is not supported"}, + /* 10044 */{WSAESOCKTNOSUPPORT,"The specified socket type is not supported by this address family"}, + /* 10045 */{WSAEOPNOTSUPP, "The referenced socket is not a type that supports that operation"}, + /* 10046 */{WSAEPFNOSUPPORT, "BSD: Protocol family not supported"}, + /* 10047 */{WSAEAFNOSUPPORT, "The specified address family is not supported"}, + /* 10048 */{WSAEADDRINUSE, "The specified address is already in use"}, + /* 10049 */{WSAEADDRNOTAVAIL, "The specified address is not available from the local machine"}, + /* 10050 */{WSAENETDOWN, "The Windows Sockets implementation has detected that the network subsystem has failed"}, + /* 10051 */{WSAENETUNREACH, "The network can't be reached from this host at this time"}, + /* 10052 */{WSAENETRESET, "The connection must be reset because the Windows Sockets implementation dropped it"}, + /* 10053 */{WSAECONNABORTED, "The virtual circuit was aborted due to timeout or other failure"}, + /* 10054 */{WSAECONNRESET, "The virtual circuit was reset by the remote side"}, + /* 10055 */{WSAENOBUFS, "No buffer space is available or a buffer deadlock has occured. The socket cannot be created"}, + /* 10056 */{WSAEISCONN, "The socket is already connected"}, + /* 10057 */{WSAENOTCONN, "The socket is not connected"}, + /* 10058 */{WSAESHUTDOWN, "The socket has been shutdown"}, + /* 10059 */{WSAETOOMANYREFS, "BSD: Too many references"}, + /* 10060 */{WSAETIMEDOUT, "Attempt to connect timed out without establishing a connection"}, + /* 10061 */{WSAECONNREFUSED, "Connection was refused"}, + /* 10062 */{WSAELOOP, "Undocumented WinSock error code used in BSD"}, + /* 10063 */{WSAENAMETOOLONG, "Undocumented WinSock error code used in BSD"}, + /* 10064 */{WSAEHOSTDOWN, "Undocumented WinSock error code used in BSD"}, + /* 10065 */{WSAEHOSTUNREACH, "No route to host"}, + /* 10066 */{WSAENOTEMPTY, "Undocumented WinSock error code"}, + /* 10067 */{WSAEPROCLIM, "Undocumented WinSock error code"}, + /* 10068 */{WSAEUSERS, "Undocumented WinSock error code"}, + /* 10069 */{WSAEDQUOT, "Undocumented WinSock error code"}, + /* 10070 */{WSAESTALE, "Undocumented WinSock error code"}, + /* 10071 */{WSAEREMOTE, "Undocumented WinSock error code"}, + /* 10091 */{WSASYSNOTREADY, "Underlying network subsytem is not ready for network communication"}, + /* 10092 */{WSAVERNOTSUPPORTED, "The version of WinSock API support requested is not provided in this implementation"}, + /* 10093 */{WSANOTINITIALISED, "WinSock subsystem not properly initialized"}, + /* 10101 */{WSAEDISCON, "Virtual circuit has gracefully terminated connection"}, + /* 11001 */{WSAHOST_NOT_FOUND, "The specified host is unknown"}, + /* 11002 */{WSATRY_AGAIN, "A temporary error occurred on an authoritative name server"}, + /* 11003 */{WSANO_RECOVERY, "A non-recoverable name server error occurred"}, + /* 11004 */{WSANO_DATA, "The requested name is valid but does not have an IP address"}, + /* end */{0, NULL} + }; + + for (unsigned int i = 0; s_netErrorCodes[i].m_code != 0; ++i) { + if (s_netErrorCodes[i].m_code == m_error) { + return s_netErrorCodes[i].m_msg; + } + } + return "Unknown error"; +} diff --git a/src/lib/arch/win32/XArchWindows.h b/src/lib/arch/win32/XArchWindows.h new file mode 100644 index 0000000..10106b1 --- /dev/null +++ b/src/lib/arch/win32/XArchWindows.h @@ -0,0 +1,49 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * 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 LICENSE 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "arch/XArch.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +//! Lazy error message string evaluation for windows +class XArchEvalWindows : public XArchEval { +public: + XArchEvalWindows() : m_error(GetLastError()) { } + XArchEvalWindows(DWORD error) : m_error(error) { } + virtual ~XArchEvalWindows() { } + + virtual std::string eval() const; + +private: + DWORD m_error; +}; + +//! Lazy error message string evaluation for winsock +class XArchEvalWinsock : public XArchEval { +public: + XArchEvalWinsock(int error) : m_error(error) { } + virtual ~XArchEvalWinsock() { } + + virtual std::string eval() const; + +private: + int m_error; +}; |
