diff options
Diffstat (limited to 'src/lib/arch')
77 files changed, 11014 insertions, 0 deletions
diff --git a/src/lib/arch/Arch.cpp b/src/lib/arch/Arch.cpp new file mode 100644 index 0000000..0a3b3e5 --- /dev/null +++ b/src/lib/arch/Arch.cpp @@ -0,0 +1,60 @@ +/* + * 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/Arch.h" + +// +// Arch +// + +Arch* Arch::s_instance = NULL; + +Arch::Arch() +{ + assert(s_instance == NULL); + s_instance = this; +} + +Arch::Arch(Arch* arch) +{ + s_instance = arch; +} + +Arch::~Arch() +{ +#if SYSAPI_WIN32 + ArchMiscWindows::cleanup(); +#endif +} + +void +Arch::init() +{ + ARCH_NETWORK::init(); +#if SYSAPI_WIN32 + ARCH_TASKBAR::init(); + ArchMiscWindows::init(); +#endif +} + +Arch* +Arch::getInstance() +{ + assert(s_instance != NULL); + return s_instance; +} diff --git a/src/lib/arch/Arch.h b/src/lib/arch/Arch.h new file mode 100644 index 0000000..42a73c2 --- /dev/null +++ b/src/lib/arch/Arch.h @@ -0,0 +1,144 @@ +/* + * 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/>. + */ + +// TODO: consider whether or not to use either encapsulation (as below) +// or inheritance (as it is now) for the ARCH stuff. +// +// case for encapsulation: +// pros: +// - compiler errors for missing pv implementations are not absolutely bonkers. +// - function names don't have to be so verbose. +// - easier to understand and debug. +// - ctors in IArch implementations can call other implementations. +// cons: +// - slightly more code for calls to ARCH. +// - you'll have to modify each ARCH call. +// +// also, we may want to consider making each encapsulated +// class lazy-loaded so that apps like the daemon don't load +// stuff when they don't need it. + +#pragma once + +#include "common/common.h" + +#if SYSAPI_WIN32 +# include "arch/win32/ArchConsoleWindows.h" +# include "arch/win32/ArchDaemonWindows.h" +# include "arch/win32/ArchFileWindows.h" +# include "arch/win32/ArchLogWindows.h" +# include "arch/win32/ArchMiscWindows.h" +# include "arch/win32/ArchMultithreadWindows.h" +# include "arch/win32/ArchNetworkWinsock.h" +# include "arch/win32/ArchSleepWindows.h" +# include "arch/win32/ArchStringWindows.h" +# include "arch/win32/ArchSystemWindows.h" +# include "arch/win32/ArchTaskBarWindows.h" +# include "arch/win32/ArchTimeWindows.h" +# include "arch/win32/ArchInternetWindows.h" +#elif SYSAPI_UNIX +# include "arch/unix/ArchConsoleUnix.h" +# include "arch/unix/ArchDaemonUnix.h" +# include "arch/unix/ArchFileUnix.h" +# include "arch/unix/ArchLogUnix.h" +# if HAVE_PTHREAD +# include "arch/unix/ArchMultithreadPosix.h" +# endif +# include "arch/unix/ArchNetworkBSD.h" +# include "arch/unix/ArchSleepUnix.h" +# include "arch/unix/ArchStringUnix.h" +# include "arch/unix/ArchSystemUnix.h" +# include "arch/unix/ArchTaskBarXWindows.h" +# include "arch/unix/ArchTimeUnix.h" +# include "arch/unix/ArchInternetUnix.h" +#endif + +/*! +\def ARCH +This macro evaluates to the singleton Arch object. +*/ +#define ARCH (Arch::getInstance()) + +//! Delegating implementation of architecture dependent interfaces +/*! +This class is a centralized interface to all architecture dependent +interface implementations (except miscellaneous functions). It +instantiates an implementation of each interface and delegates calls +to each method to those implementations. Clients should use the +\c ARCH macro to access this object. Clients must also instantiate +exactly one of these objects before attempting to call any method, +typically at the beginning of \c main(). +*/ +class Arch : public ARCH_CONSOLE, + public ARCH_DAEMON, + public ARCH_FILE, + public ARCH_LOG, + public ARCH_MULTITHREAD, + public ARCH_NETWORK, + public ARCH_SLEEP, + public ARCH_STRING, + public ARCH_SYSTEM, + public ARCH_TASKBAR, + public ARCH_TIME { +public: + Arch(); + Arch(Arch* arch); + virtual ~Arch(); + + //! Call init on other arch classes. + /*! + Some arch classes depend on others to exist first. When init is called + these clases will have ARCH available for use. + */ + virtual void init(); + + // + // accessors + // + + //! Return the singleton instance + /*! + The client must have instantiated exactly once Arch object before + calling this function. + */ + static Arch* getInstance(); + + static void setInstance(Arch* s) { s_instance = s; } + + ARCH_INTERNET& internet() const { return (ARCH_INTERNET&)m_internet; } + +private: + static Arch* s_instance; + ARCH_INTERNET m_internet; +}; + +//! Convenience object to lock/unlock an arch mutex +class ArchMutexLock { +public: + ArchMutexLock(ArchMutex mutex) : m_mutex(mutex) + { + ARCH->lockMutex(m_mutex); + } + ~ArchMutexLock() + { + ARCH->unlockMutex(m_mutex); + } + +private: + ArchMutex m_mutex; +}; diff --git a/src/lib/arch/ArchConsoleStd.cpp b/src/lib/arch/ArchConsoleStd.cpp new file mode 100644 index 0000000..f7f7691 --- /dev/null +++ b/src/lib/arch/ArchConsoleStd.cpp @@ -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/>. + */ + +#include "arch/ArchConsoleStd.h" +#include "base/Log.h" + +#include <iostream> + +void +ArchConsoleStd::writeConsole(ELevel level, const char* str) +{ + if ((level >= kFATAL) && (level <= kWARNING)) + std::cerr << str << std::endl; + else + std::cout << str << std::endl; + + std::cout.flush(); +}
\ No newline at end of file diff --git a/src/lib/arch/ArchConsoleStd.h b/src/lib/arch/ArchConsoleStd.h new file mode 100644 index 0000000..8560fad --- /dev/null +++ b/src/lib/arch/ArchConsoleStd.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/IArchConsole.h" + +//! Cross platform implementation of IArchConsole +class ArchConsoleStd : public IArchConsole { +public: + ArchConsoleStd() { } + virtual ~ArchConsoleStd() { } + + // IArchConsole overrides + virtual void openConsole(const char* title) { } + virtual void closeConsole() { } + virtual void showConsole(bool) { } + virtual void writeConsole(ELevel level, const char*); +}; diff --git a/src/lib/arch/ArchDaemonNone.cpp b/src/lib/arch/ArchDaemonNone.cpp new file mode 100644 index 0000000..1222549 --- /dev/null +++ b/src/lib/arch/ArchDaemonNone.cpp @@ -0,0 +1,85 @@ +/* + * 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/ArchDaemonNone.h" + +// +// ArchDaemonNone +// + +ArchDaemonNone::ArchDaemonNone() +{ + // do nothing +} + +ArchDaemonNone::~ArchDaemonNone() +{ + // do nothing +} + +void +ArchDaemonNone::installDaemon(const char*, + const char*, + const char*, + const char*, + const char*) +{ + // do nothing +} + +void +ArchDaemonNone::uninstallDaemon(const char*) +{ + // do nothing +} + +int +ArchDaemonNone::daemonize(const char* name, DaemonFunc func) +{ + // simply forward the call to func. obviously, this doesn't + // do any daemonizing. + return func(1, &name); +} + +bool +ArchDaemonNone::canInstallDaemon(const char*) +{ + return false; +} + +bool +ArchDaemonNone::isDaemonInstalled(const char*) +{ + return false; +} + +void +ArchDaemonNone::installDaemon() +{ +} + +void +ArchDaemonNone::uninstallDaemon() +{ +} + +std::string +ArchDaemonNone::commandLine() const +{ + return ""; +} diff --git a/src/lib/arch/ArchDaemonNone.h b/src/lib/arch/ArchDaemonNone.h new file mode 100644 index 0000000..fd59758 --- /dev/null +++ b/src/lib/arch/ArchDaemonNone.h @@ -0,0 +1,50 @@ +/* + * 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" + +#define ARCH_DAEMON ArchDaemonNone + +//! Dummy implementation of IArchDaemon +/*! +This class implements IArchDaemon for a platform that does not have +daemons. The install and uninstall functions do nothing, the query +functions return false, and \c daemonize() simply calls the passed +function and returns its result. +*/ +class ArchDaemonNone : public IArchDaemon { +public: + ArchDaemonNone(); + virtual ~ArchDaemonNone(); + + // 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 int daemonize(const char* name, DaemonFunc func); + virtual bool canInstallDaemon(const char* name); + virtual bool isDaemonInstalled(const char* name); + virtual void installDaemon(); + virtual void uninstallDaemon(); + virtual std::string commandLine() const; +}; diff --git a/src/lib/arch/CMakeLists.txt b/src/lib/arch/CMakeLists.txt new file mode 100644 index 0000000..113cdd9 --- /dev/null +++ b/src/lib/arch/CMakeLists.txt @@ -0,0 +1,47 @@ +# barrier -- mouse and keyboard sharing utility +# Copyright (C) 2012-2016 Symless Ltd. +# Copyright (C) 2009 Nick Bolton +# +# 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/>. + +file(GLOB headers "*.h") +file(GLOB sources "*.cpp") + +if (BARRIER_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +# arch +if (WIN32) + file(GLOB arch_headers "win32/*.h") + file(GLOB arch_sources "win32/*.cpp") +elseif (UNIX) + file(GLOB arch_headers "unix/*.h") + file(GLOB arch_sources "unix/*.cpp") +endif() + +list(APPEND sources ${arch_sources}) +list(APPEND headers ${arch_headers}) + +if (BARRIER_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +add_library(arch STATIC ${sources}) + +if (UNIX) + target_link_libraries(arch ${libs}) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") + target_link_libraries(arch dl) + endif() +endif() diff --git a/src/lib/arch/IArchConsole.h b/src/lib/arch/IArchConsole.h new file mode 100644 index 0000000..d115c50 --- /dev/null +++ b/src/lib/arch/IArchConsole.h @@ -0,0 +1,66 @@ +/* + * 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/IInterface.h" +#include "base/ELevel.h" + +//! Interface for architecture dependent console output +/*! +This interface defines the console operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchConsole : public IInterface { +public: + //! @name manipulators + //@{ + + //! Open the console + /*! + Opens the console for writing. The console is opened automatically + on the first write so calling this method is optional. Uses \c title + for the console's title if appropriate for the architecture. Calling + this method on an already open console must have no effect. + */ + virtual void openConsole(const char* title) = 0; + + //! Close the console + /*! + Close the console. Calling this method on an already closed console + must have no effect. + */ + virtual void closeConsole() = 0; + + //! Show the console + /*! + Causes the console to become visible. This generally only makes sense + for a console in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the console if it's not empty. + */ + virtual void showConsole(bool showIfEmpty) = 0; + + //! Write to the console + /*! + Writes the given string to the console, opening it if necessary. + */ + virtual void writeConsole(ELevel, const char*) = 0; + + //@} +}; diff --git a/src/lib/arch/IArchDaemon.h b/src/lib/arch/IArchDaemon.h new file mode 100644 index 0000000..a4983d3 --- /dev/null +++ b/src/lib/arch/IArchDaemon.h @@ -0,0 +1,128 @@ +/* + * 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/IInterface.h" +#include "base/String.h" + +//! Interface for architecture dependent daemonizing +/*! +This interface defines the operations required by barrier for installing +uninstalling daeamons and daemonizing a process. Each architecture must +implement this interface. +*/ +class IArchDaemon : public IInterface { +public: + typedef int (*DaemonFunc)(int argc, const char** argv); + + //! @name manipulators + //@{ + + //! Install daemon + /*! + Install a daemon. \c name is the name of the daemon passed to the + system and \c description is a short human readable description of + the daemon. \c pathname is the path to the daemon executable. + \c commandLine should \b not include the name of program as the + first argument. If \c allUsers is true then the daemon will be + installed to start at boot time, otherwise it will be installed to + start when the current user logs in. If \p dependencies is not NULL + then it's a concatenation of NUL terminated other daemon names + followed by a NUL; the daemon will be configured to startup after + the listed daemons. Throws an \c XArchDaemon exception on failure. + */ + virtual void installDaemon(const char* name, + const char* description, + const char* pathname, + const char* commandLine, + const char* dependencies) = 0; + + //! Uninstall daemon + /*! + Uninstall a daemon. Throws an \c XArchDaemon on failure. + */ + virtual void uninstallDaemon(const char* name) = 0; + + //! Install daemon + /*! + Installs the default daemon. + */ + virtual void installDaemon() = 0; + + //! Uninstall daemon + /*! + Uninstalls the default daemon. + */ + virtual void uninstallDaemon() = 0; + + //! Daemonize the process + /*! + Daemonize. Throw XArchDaemonFailed on error. \c name is the name + of the daemon. Once daemonized, \c func is invoked and daemonize + returns when and what it does. + + Exactly what happens when daemonizing depends on the platform. + <ul> + <li>unix: + Detaches from terminal. \c func gets passed one argument, the + name passed to daemonize(). + <li>win32: + Becomes a service. Argument 0 is the name of the service + and the rest are the arguments passed to StartService(). + \c func is only called when the service is actually started. + \c func must call \c ArchMiscWindows::runDaemon() to finally + becoming a service. The \c runFunc function passed to \c runDaemon() + must call \c ArchMiscWindows::daemonRunning(true) when it + enters the main loop (i.e. after initialization) and + \c ArchMiscWindows::daemonRunning(false) when it leaves + the main loop. The \c stopFunc function passed to \c runDaemon() + is called when the daemon must exit the main loop and it must cause + \c runFunc to return. \c func should return what \c runDaemon() + returns. \c func or \c runFunc can call + \c ArchMiscWindows::daemonFailed() to indicate startup failure. + </ul> + */ + virtual int daemonize(const char* name, DaemonFunc func) = 0; + + //! Check if user has permission to install the daemon + /*! + Returns true iff the caller has permission to install or + uninstall the daemon. Note that even if this method returns + true it's possible that installing/uninstalling the service + may still fail. This method ignores whether or not the + service is already installed. + */ + virtual bool canInstallDaemon(const char* name) = 0; + + //! Check if the daemon is installed + /*! + Returns true iff the daemon is installed. + */ + virtual bool isDaemonInstalled(const char* name) = 0; + + //@} + + //! Get the command line + /*! + Gets the command line with which the application was started. + */ + virtual std::string commandLine() const = 0; + + //@} +}; diff --git a/src/lib/arch/IArchFile.h b/src/lib/arch/IArchFile.h new file mode 100644 index 0000000..5fdd288 --- /dev/null +++ b/src/lib/arch/IArchFile.h @@ -0,0 +1,105 @@ +/* + * 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/IInterface.h" +#include "common/stdstring.h" +#include "base/String.h" + +//! Interface for architecture dependent file system operations +/*! +This interface defines the file system operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchFile : public IInterface { +public: + //! @name manipulators + //@{ + + //! Extract base name + /*! + Find the base name in the given \c pathname. + */ + virtual const char* getBasename(const char* pathname) = 0; + + //! Get user's home directory + /*! + Returns the user's home directory. Returns the empty string if + this cannot be determined. + */ + virtual std::string getUserDirectory() = 0; + + //! Get system directory + /*! + Returns the ussystem configuration file directory. + */ + virtual std::string getSystemDirectory() = 0; + + //! Get installed directory + /*! + Returns the directory in which Barrier is installed. + */ + virtual std::string getInstalledDirectory() = 0; + + //! Get log directory + /*! + Returns the log file directory. + */ + virtual std::string getLogDirectory() = 0; + + //! Get plugins directory + /*! + Returns the plugin files directory. If no plugin directory is set, + this will return the plugin folder within the user's profile. + */ + virtual std::string getPluginDirectory() = 0; + + //! Get user's profile directory + /*! + Returns the user's profile directory. If no profile directory is set, + this will return the user's profile according to the operating system, + which will depend on which user launched the program. + */ + virtual std::string getProfileDirectory() = 0; + + //! Concatenate path components + /*! + Concatenate pathname components with a directory separator + between them. This should not check if the resulting path + is longer than allowed by the system; we'll rely on the + system calls to tell us that. + */ + virtual std::string concatPath( + const std::string& prefix, + const std::string& suffix) = 0; + + //@} + //! Set the user's profile directory + /* + Returns the user's profile directory. + */ + virtual void setProfileDirectory(const String& s) = 0; + + //@} + //! Set the user's plugin directory + /* + Returns the user's plugin directory. + */ + virtual void setPluginDirectory(const String& s) = 0; +}; diff --git a/src/lib/arch/IArchLog.h b/src/lib/arch/IArchLog.h new file mode 100644 index 0000000..165b1df --- /dev/null +++ b/src/lib/arch/IArchLog.h @@ -0,0 +1,63 @@ +/* + * 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/IInterface.h" +#include "base/ELevel.h" + +//! Interface for architecture dependent logging +/*! +This interface defines the logging operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchLog : public IInterface { +public: + //! @name manipulators + //@{ + + //! Open the log + /*! + Opens the log for writing. The log must be opened before being + written to. + */ + virtual void openLog(const char* name) = 0; + + //! Close the log + /*! + Close the log. + */ + virtual void closeLog() = 0; + + //! Show the log + /*! + Causes the log to become visible. This generally only makes sense + for a log in a graphical user interface. Other implementations + will do nothing. Iff \p showIfEmpty is \c false then the implementation + may optionally only show the log if it's not empty. + */ + virtual void showLog(bool showIfEmpty) = 0; + + //! Write to the log + /*! + Writes the given string to the log with the given level. + */ + virtual void writeLog(ELevel, const char*) = 0; + + //@} +}; diff --git a/src/lib/arch/IArchMultithread.h b/src/lib/arch/IArchMultithread.h new file mode 100644 index 0000000..e8d358b --- /dev/null +++ b/src/lib/arch/IArchMultithread.h @@ -0,0 +1,273 @@ +/* + * 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/IInterface.h" + +/*! +\class ArchCondImpl +\brief Internal condition variable data. +An architecture dependent type holding the necessary data for a +condition variable. +*/ +class ArchCondImpl; + +/*! +\var ArchCond +\brief Opaque condition variable type. +An opaque type representing a condition variable. +*/ +typedef ArchCondImpl* ArchCond; + +/*! +\class ArchMutexImpl +\brief Internal mutex data. +An architecture dependent type holding the necessary data for a mutex. +*/ +class ArchMutexImpl; + +/*! +\var ArchMutex +\brief Opaque mutex type. +An opaque type representing a mutex. +*/ +typedef ArchMutexImpl* ArchMutex; + +/*! +\class ArchThreadImpl +\brief Internal thread data. +An architecture dependent type holding the necessary data for a thread. +*/ +class ArchThreadImpl; + +/*! +\var ArchThread +\brief Opaque thread type. +An opaque type representing a thread. +*/ +typedef ArchThreadImpl* ArchThread; + +//! Interface for architecture dependent multithreading +/*! +This interface defines the multithreading operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchMultithread : public IInterface { +public: + //! Type of thread entry point + typedef void* (*ThreadFunc)(void*); + //! Type of thread identifier + typedef unsigned int ThreadID; + //! Types of signals + /*! + Not all platforms support all signals. Unsupported signals are + ignored. + */ + enum ESignal { + kINTERRUPT, //!< Interrupt (e.g. Ctrl+C) + kTERMINATE, //!< Terminate (e.g. Ctrl+Break) + kHANGUP, //!< Hangup (SIGHUP) + kUSER, //!< User (SIGUSR2) + kNUM_SIGNALS + }; + //! Type of signal handler function + typedef void (*SignalFunc)(ESignal, void* userData); + + //! @name manipulators + //@{ + + // + // condition variable methods + // + + //! Create a condition variable + /*! + The condition variable is an opaque data type. + */ + virtual ArchCond newCondVar() = 0; + + //! Destroy a condition variable + virtual void closeCondVar(ArchCond) = 0; + + //! Signal a condition variable + /*! + Signalling a condition variable releases one waiting thread. + */ + virtual void signalCondVar(ArchCond) = 0; + + //! Broadcast a condition variable + /*! + Broadcasting a condition variable releases all waiting threads. + */ + virtual void broadcastCondVar(ArchCond) = 0; + + //! Wait on a condition variable + /*! + Wait on a conditation variable for up to \c timeout seconds. + If \c timeout is < 0 then there is no timeout. The mutex must + be locked when this method is called. The mutex is unlocked + during the wait and locked again before returning. Returns + true if the condition variable was signalled and false on + timeout. + + (Cancellation point) + */ + virtual bool waitCondVar(ArchCond, ArchMutex, double timeout) = 0; + + // + // mutex methods + // + + //! Create a recursive mutex + /*! + Creates a recursive mutex. A thread may lock a recursive mutex + when it already holds a lock on that mutex. The mutex is an + opaque data type. + */ + virtual ArchMutex newMutex() = 0; + + //! Destroy a mutex + virtual void closeMutex(ArchMutex) = 0; + + //! Lock a mutex + virtual void lockMutex(ArchMutex) = 0; + + //! Unlock a mutex + virtual void unlockMutex(ArchMutex) = 0; + + // + // thread methods + // + + //! Start a new thread + /*! + Creates and starts a new thread, using \c func as the entry point + and passing it \c userData. The thread is an opaque data type. + */ + virtual ArchThread newThread(ThreadFunc func, void* userData) = 0; + + //! Get a reference to the calling thread + /*! + Returns a thread representing the current (i.e. calling) thread. + */ + virtual ArchThread newCurrentThread() = 0; + + //! Copy a thread object + /*! + Returns a reference to to thread referred to by \c thread. + */ + virtual ArchThread copyThread(ArchThread thread) = 0; + + //! Release a thread reference + /*! + Deletes the given thread object. This does not destroy the thread + the object referred to, even if there are no remaining references. + Use cancelThread() and waitThread() to stop a thread and wait for + it to exit. + */ + virtual void closeThread(ArchThread) = 0; + + //! Force a thread to exit + /*! + Causes \c thread to exit when it next calls a cancellation point. + A thread avoids cancellation as long as it nevers calls a + cancellation point. Once it begins the cancellation process it + must always let cancellation go to completion but may take as + long as necessary to clean up. + */ + virtual void cancelThread(ArchThread thread) = 0; + + //! Change thread priority + /*! + Changes the priority of \c thread by \c n. If \c n is positive + the thread has a lower priority and if negative a higher priority. + Some architectures may not support either or both directions. + */ + virtual void setPriorityOfThread(ArchThread, int n) = 0; + + //! Cancellation point + /*! + This method does nothing but is a cancellation point. Clients + can make their own functions cancellation points by calling this + method at appropriate times. + + (Cancellation point) + */ + virtual void testCancelThread() = 0; + + //! Wait for a thread to exit + /*! + Waits for up to \c timeout seconds for \c thread to exit (normally + or by cancellation). Waits forever if \c timeout < 0. Returns + true if the thread exited, false otherwise. Waiting on the current + thread returns immediately with false. + + (Cancellation point) + */ + virtual bool wait(ArchThread thread, double timeout) = 0; + + //! Compare threads + /*! + Returns true iff two thread objects refer to the same thread. + Note that comparing thread objects directly is meaningless. + */ + virtual bool isSameThread(ArchThread, ArchThread) = 0; + + //! Test if thread exited + /*! + Returns true iff \c thread has exited. + */ + virtual bool isExitedThread(ArchThread thread) = 0; + + //! Returns the exit code of a thread + /*! + Waits indefinitely for \c thread to exit (if it hasn't yet) then + returns the thread's exit code. + + (Cancellation point) + */ + virtual void* getResultOfThread(ArchThread thread) = 0; + + //! Returns an ID for a thread + /*! + Returns some ID number for \c thread. This is for logging purposes. + All thread objects referring to the same thread return the same ID. + However, clients should us isSameThread() to compare thread objects + instead of comparing IDs. + */ + virtual ThreadID getIDOfThread(ArchThread thread) = 0; + + //! Set the interrupt handler + /*! + Sets the function to call on receipt of an external interrupt. + By default and when \p func is NULL, the main thread is cancelled. + */ + virtual void setSignalHandler(ESignal, SignalFunc func, + void* userData) = 0; + + //! Invoke the signal handler + /*! + Invokes the signal handler for \p signal, if any. If no handler + cancels the main thread for \c kINTERRUPT and \c kTERMINATE and + ignores the call otherwise. + */ + virtual void raiseSignal(ESignal signal) = 0; + + //@} +}; diff --git a/src/lib/arch/IArchNetwork.h b/src/lib/arch/IArchNetwork.h new file mode 100644 index 0000000..b859506 --- /dev/null +++ b/src/lib/arch/IArchNetwork.h @@ -0,0 +1,283 @@ +/* + * 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/IInterface.h" +#include "common/stdstring.h" + +class ArchThreadImpl; +typedef ArchThreadImpl* ArchThread; + +/*! +\class ArchSocketImpl +\brief Internal socket data. +An architecture dependent type holding the necessary data for a socket. +*/ +class ArchSocketImpl; + +/*! +\var ArchSocket +\brief Opaque socket type. +An opaque type representing a socket. +*/ +typedef ArchSocketImpl* ArchSocket; + +/*! +\class ArchNetAddressImpl +\brief Internal network address data. +An architecture dependent type holding the necessary data for a network +address. +*/ +class ArchNetAddressImpl; + +/*! +\var ArchNetAddress +\brief Opaque network address type. +An opaque type representing a network address. +*/ +typedef ArchNetAddressImpl* ArchNetAddress; + +//! Interface for architecture dependent networking +/*! +This interface defines the networking operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchNetwork : public IInterface { +public: + //! Supported address families + enum EAddressFamily { + kUNKNOWN, + kINET, + kINET6, + }; + + //! Supported socket types + enum ESocketType { + kDGRAM, + kSTREAM + }; + + //! Events for \c poll() + /*! + Events for \c poll() are bitmasks and can be combined using the + bitwise operators. + */ + enum { + kPOLLIN = 1, //!< Socket is readable + kPOLLOUT = 2, //!< Socket is writable + kPOLLERR = 4, //!< The socket is in an error state + kPOLLNVAL = 8 //!< The socket is invalid + }; + + //! A socket query for \c poll() + class PollEntry { + public: + //! The socket to query + ArchSocket m_socket; + + //! The events to query for + /*! + The events to query for can be any combination of kPOLLIN and + kPOLLOUT. + */ + unsigned short m_events; + + //! The result events + unsigned short m_revents; + }; + + //! @name manipulators + //@{ + + //! Create a new socket + /*! + The socket is an opaque data type. + */ + virtual ArchSocket newSocket(EAddressFamily, ESocketType) = 0; + + //! Copy a socket object + /*! + Returns a reference to to socket referred to by \c s. + */ + virtual ArchSocket copySocket(ArchSocket s) = 0; + + //! Release a socket reference + /*! + Deletes the given socket object. This does not destroy the socket + the object referred to until there are no remaining references for + the socket. + */ + virtual void closeSocket(ArchSocket s) = 0; + + //! Close socket for further reads + /*! + Calling this disallows future reads on socket \c s. + */ + virtual void closeSocketForRead(ArchSocket s) = 0; + + //! Close socket for further writes + /*! + Calling this disallows future writes on socket \c s. + */ + virtual void closeSocketForWrite(ArchSocket s) = 0; + + //! Bind socket to address + /*! + Binds socket \c s to the address \c addr. + */ + virtual void bindSocket(ArchSocket s, ArchNetAddress addr) = 0; + + //! Listen for connections on socket + /*! + Causes the socket \c s to begin listening for incoming connections. + */ + virtual void listenOnSocket(ArchSocket s) = 0; + + //! Accept connection on socket + /*! + Accepts a connection on socket \c s, returning a new socket for the + connection and filling in \c addr with the address of the remote + end. \c addr may be NULL if the remote address isn't required. + The original socket \c s is unaffected and remains in the listening + state. The new socket shares most of the properties of \c s except + it's not in the listening state and it's connected. Returns NULL + if there are no pending connection requests. + */ + virtual ArchSocket acceptSocket(ArchSocket s, ArchNetAddress* addr) = 0; + + //! Connect socket + /*! + Connects the socket \c s to the remote address \c addr. Returns + true if the connection succeed immediately, false if the connection + is in progress, and throws if the connection failed immediately. + If it returns false, \c pollSocket() can be used to wait on the + socket for writing to detect when the connection finally succeeds + or fails. + */ + virtual bool connectSocket(ArchSocket s, ArchNetAddress addr) = 0; + + //! Check socket state + /*! + Tests the state of \c num sockets for readability and/or writability. + Waits up to \c timeout seconds for some socket to become readable + and/or writable (or indefinitely if \c timeout < 0). Returns the + number of sockets that were readable (if readability was being + queried) or writable (if writablility was being queried) and sets + the \c m_revents members of the entries. \c kPOLLERR and \c kPOLLNVAL + are set in \c m_revents as appropriate. If a socket indicates + \c kPOLLERR then \c throwErrorOnSocket() can be used to determine + the type of error. Returns 0 immediately regardless of the \c timeout + if no valid sockets are selected for testing. + + (Cancellation point) + */ + virtual int pollSocket(PollEntry[], int num, double timeout) = 0; + + //! Unblock thread in pollSocket() + /*! + Cause a thread that's in a pollSocket() call to return. This + call may return before the thread is unblocked. If the thread is + not in a pollSocket() call this call has no effect. + */ + virtual void unblockPollSocket(ArchThread thread) = 0; + + //! Read data from socket + /*! + Read up to \c len bytes from socket \c s in \c buf and return the + number of bytes read. The number of bytes can be less than \c len + if not enough data is available. Returns 0 if the remote end has + disconnected and/or there is no more queued received data. + */ + virtual size_t readSocket(ArchSocket s, void* buf, size_t len) = 0; + + //! Write data from socket + /*! + Write up to \c len bytes to socket \c s from \c buf and return the + number of bytes written. The number of bytes can be less than + \c len if the remote end disconnected or the internal buffers fill + up. + */ + virtual size_t writeSocket(ArchSocket s, + const void* buf, size_t len) = 0; + + //! Check error on socket + /*! + If the socket \c s is in an error state then throws an appropriate + XArchNetwork exception. + */ + virtual void throwErrorOnSocket(ArchSocket s) = 0; + + //! Turn Nagle algorithm on or off on socket + /*! + Set socket to send messages immediately (true) or to collect small + messages into one packet (false). Returns the previous state. + */ + virtual bool setNoDelayOnSocket(ArchSocket, bool noDelay) = 0; + + //! Turn address reuse on or off on socket + /*! + Allows the address this socket is bound to to be reused while in the + TIME_WAIT state. Returns the previous state. + */ + virtual bool setReuseAddrOnSocket(ArchSocket, bool reuse) = 0; + + //! Return local host's name + virtual std::string getHostName() = 0; + + //! Create an "any" network address + virtual ArchNetAddress newAnyAddr(EAddressFamily) = 0; + + //! Copy a network address + virtual ArchNetAddress copyAddr(ArchNetAddress) = 0; + + //! Convert a name to a network address + virtual ArchNetAddress nameToAddr(const std::string&) = 0; + + //! Destroy a network address + virtual void closeAddr(ArchNetAddress) = 0; + + //! Convert an address to a host name + virtual std::string addrToName(ArchNetAddress) = 0; + + //! Convert an address to a string + virtual std::string addrToString(ArchNetAddress) = 0; + + //! Get an address's family + virtual EAddressFamily getAddrFamily(ArchNetAddress) = 0; + + //! Set the port of an address + virtual void setAddrPort(ArchNetAddress, int port) = 0; + + //! Get the port of an address + virtual int getAddrPort(ArchNetAddress) = 0; + + //! Test addresses for equality + virtual bool isEqualAddr(ArchNetAddress, ArchNetAddress) = 0; + + //! Test for the "any" address + /*! + Returns true if \c addr is the "any" address. \c newAnyAddr() + returns an "any" address. + */ + virtual bool isAnyAddr(ArchNetAddress addr) = 0; + + //@} + + virtual void init() = 0; +}; diff --git a/src/lib/arch/IArchSleep.h b/src/lib/arch/IArchSleep.h new file mode 100644 index 0000000..9999d0e --- /dev/null +++ b/src/lib/arch/IArchSleep.h @@ -0,0 +1,44 @@ +/* + * 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/IInterface.h" + +//! Interface for architecture dependent sleeping +/*! +This interface defines the sleep operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchSleep : public IInterface { +public: + //! @name manipulators + //@{ + + //! Sleep + /*! + Blocks the calling thread for \c timeout seconds. If + \c timeout < 0.0 then the call returns immediately. If \c timeout + == 0.0 then the calling thread yields the CPU. + + (cancellation point) + */ + virtual void sleep(double timeout) = 0; + + //@} +}; diff --git a/src/lib/arch/IArchString.cpp b/src/lib/arch/IArchString.cpp new file mode 100644 index 0000000..f618c12 --- /dev/null +++ b/src/lib/arch/IArchString.cpp @@ -0,0 +1,190 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2011 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/IArchString.h" +#include "arch/Arch.h" +#include "common/common.h" + +#include <climits> +#include <cstring> +#include <cstdlib> + +static ArchMutex s_mutex = NULL; + +// +// use C library non-reentrant multibyte conversion with mutex +// + +IArchString::~IArchString() +{ + if (s_mutex != NULL) { + ARCH->closeMutex(s_mutex); + s_mutex = NULL; + } +} + +int +IArchString::convStringWCToMB(char* dst, + const wchar_t* src, UInt32 n, bool* errors) +{ + ptrdiff_t len = 0; + + bool dummyErrors; + if (errors == NULL) { + errors = &dummyErrors; + } + + if (s_mutex == NULL) { + s_mutex = ARCH->newMutex(); + } + + ARCH->lockMutex(s_mutex); + + if (dst == NULL) { + char dummy[MB_LEN_MAX]; + for (const wchar_t* scan = src; n > 0; ++scan, --n) { + ptrdiff_t mblen = wctomb(dummy, *scan); + if (mblen == -1) { + *errors = true; + mblen = 1; + } + len += mblen; + } + ptrdiff_t mblen = wctomb(dummy, L'\0'); + if (mblen != -1) { + len += mblen - 1; + } + } + else { + char* dst0 = dst; + for (const wchar_t* scan = src; n > 0; ++scan, --n) { + ptrdiff_t mblen = wctomb(dst, *scan); + if (mblen == -1) { + *errors = true; + *dst++ = '?'; + } + else { + dst += mblen; + } + } + ptrdiff_t mblen = wctomb(dst, L'\0'); + if (mblen != -1) { + // don't include nul terminator + dst += mblen - 1; + } + len = dst - dst0; + } + ARCH->unlockMutex(s_mutex); + + return (int)len; +} + +int +IArchString::convStringMBToWC(wchar_t* dst, + const char* src, UInt32 n_param, bool* errors) +{ + ptrdiff_t n = (ptrdiff_t)n_param; // fix compiler warning + ptrdiff_t len = 0; + wchar_t dummy; + + bool dummyErrors; + if (errors == NULL) { + errors = &dummyErrors; + } + + if (s_mutex == NULL) { + s_mutex = ARCH->newMutex(); + } + + ARCH->lockMutex(s_mutex); + + if (dst == NULL) { + for (const char* scan = src; n > 0; ) { + ptrdiff_t mblen = mbtowc(&dummy, scan, n); + switch (mblen) { + case -2: + // incomplete last character. convert to unknown character. + *errors = true; + len += 1; + n = 0; + break; + + case -1: + // invalid character. count one unknown character and + // start at the next byte. + *errors = true; + len += 1; + scan += 1; + n -= 1; + break; + + case 0: + len += 1; + scan += 1; + n -= 1; + break; + + default: + // normal character + len += 1; + scan += mblen; + n -= mblen; + break; + } + } + } + else { + wchar_t* dst0 = dst; + for (const char* scan = src; n > 0; ++dst) { + ptrdiff_t mblen = mbtowc(dst, scan, n); + switch (mblen) { + case -2: + // incomplete character. convert to unknown character. + *errors = true; + *dst = (wchar_t)0xfffd; + n = 0; + break; + + case -1: + // invalid character. count one unknown character and + // start at the next byte. + *errors = true; + *dst = (wchar_t)0xfffd; + scan += 1; + n -= 1; + break; + + case 0: + *dst = (wchar_t)0x0000; + scan += 1; + n -= 1; + break; + + default: + // normal character + scan += mblen; + n -= mblen; + break; + } + } + len = dst - dst0; + } + ARCH->unlockMutex(s_mutex); + + return (int)len; +} diff --git a/src/lib/arch/IArchString.h b/src/lib/arch/IArchString.h new file mode 100644 index 0000000..ea10b65 --- /dev/null +++ b/src/lib/arch/IArchString.h @@ -0,0 +1,72 @@ +/* + * 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/IInterface.h" +#include "common/basic_types.h" + +#include <stdarg.h> + +//! Interface for architecture dependent string operations +/*! +This interface defines the string operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchString : public IInterface { +public: + virtual ~IArchString(); + + //! Wide character encodings + /*! + The known wide character encodings + */ + enum EWideCharEncoding { + kUCS2, //!< The UCS-2 encoding + kUCS4, //!< The UCS-4 encoding + kUTF16, //!< The UTF-16 encoding + kUTF32 //!< The UTF-32 encoding + }; + + //! @name manipulators + //@{ + + //! printf() to limited size buffer with va_list + /*! + This method is equivalent to vsprintf() except it will not write + more than \c n bytes to the buffer, returning -1 if the output + was truncated and the number of bytes written not including the + trailing NUL otherwise. + */ + virtual int vsnprintf(char* str, + int size, const char* fmt, va_list ap); + + //! Convert multibyte string to wide character string + virtual int convStringMBToWC(wchar_t*, + const char*, UInt32 n, bool* errors); + + //! Convert wide character string to multibyte string + virtual int convStringWCToMB(char*, + const wchar_t*, UInt32 n, bool* errors); + + //! Return the architecture's native wide character encoding + virtual EWideCharEncoding + getWideCharEncoding() = 0; + + //@} +}; diff --git a/src/lib/arch/IArchSystem.h b/src/lib/arch/IArchSystem.h new file mode 100644 index 0000000..9446505 --- /dev/null +++ b/src/lib/arch/IArchSystem.h @@ -0,0 +1,66 @@ +/* + * 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 "common/IInterface.h" +#include "common/stdstring.h" + +//! Interface for architecture dependent system queries +/*! +This interface defines operations for querying system info. +*/ +class IArchSystem : public IInterface { +public: + //! @name accessors + //@{ + + //! Identify the OS + /*! + Returns a string identifying the operating system. + */ + virtual std::string getOSName() const = 0; + + //! Identify the platform + /*! + Returns a string identifying the platform this OS is running on. + */ + virtual std::string getPlatformName() const = 0; + //@} + + //! Get a Barrier setting + /*! + Reads a Barrier setting from the system. + */ + virtual std::string setting(const std::string& valueName) const = 0; + //@} + + //! Set a Barrier setting + /*! + Writes a Barrier setting from the system. + */ + virtual void setting(const std::string& valueName, const std::string& valueString) const = 0; + //@} + + //! Get the pathnames of the libraries used by Barrier + /* + Returns a string containing the full path names of all loaded libraries at the point it is called. + */ + virtual std::string getLibsUsed(void) const = 0; + //@} +}; diff --git a/src/lib/arch/IArchTaskBar.h b/src/lib/arch/IArchTaskBar.h new file mode 100644 index 0000000..85a32d8 --- /dev/null +++ b/src/lib/arch/IArchTaskBar.h @@ -0,0 +1,63 @@ +/* + * 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 "common/IInterface.h" + +class IArchTaskBarReceiver; + +//! Interface for architecture dependent task bar control +/*! +This interface defines the task bar icon operations required +by barrier. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBar : public IInterface { +public: + //! @name manipulators + //@{ + + //! Add a receiver + /*! + Add a receiver object to be notified of user and application + events. This should be called before other methods. When + the receiver is added to the task bar, its icon appears on + the task bar. + */ + virtual void addReceiver(IArchTaskBarReceiver*) = 0; + + //! Remove a receiver + /*! + Remove a receiver object from the task bar. This removes the + icon from the task bar. + */ + virtual void removeReceiver(IArchTaskBarReceiver*) = 0; + + //! Update a receiver + /*! + Updates the display of the receiver on the task bar. This + should be called when the receiver appearance may have changed + (e.g. it's icon or tool tip has changed). + */ + virtual void updateReceiver(IArchTaskBarReceiver*) = 0; + + //@} + + virtual void init() = 0; +}; diff --git a/src/lib/arch/IArchTaskBarReceiver.h b/src/lib/arch/IArchTaskBarReceiver.h new file mode 100644 index 0000000..8a925b4 --- /dev/null +++ b/src/lib/arch/IArchTaskBarReceiver.h @@ -0,0 +1,98 @@ +/* + * 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 "base/String.h" +#include "common/IInterface.h" + +class IScreen; +class INode; + +//! Interface for architecture dependent task bar event handling +/*! +This interface defines the task bar icon event handlers required +by barrier. Each architecture must implement this interface +though each operation can be a no-op. +*/ +class IArchTaskBarReceiver : public IInterface { +public: + // Icon data is architecture dependent + typedef void* Icon; + + //! @name manipulators + //@{ + + //! Show status window + /*! + Open a window displaying current status. This should return + immediately without waiting for the window to be closed. + */ + virtual void showStatus() = 0; + + //! Popup menu + /*! + Popup a menu of operations at or around \c x,y and perform the + chosen operation. + */ + virtual void runMenu(int x, int y) = 0; + + //! Perform primary action + /*! + Perform the primary (default) action. + */ + virtual void primaryAction() = 0; + + //@} + //! @name accessors + //@{ + + //! Lock receiver + /*! + Locks the receiver from changing state. The receiver should be + locked when querying it's state to ensure consistent results. + Each call to \c lock() must have a matching \c unlock() and + locks cannot be nested. + */ + virtual void lock() const = 0; + + //! Unlock receiver + virtual void unlock() const = 0; + + //! Get icon + /*! + Returns the icon to display in the task bar. The interface + to set the icon is left to subclasses. Getting and setting + the icon must be thread safe. + */ + virtual const Icon getIcon() const = 0; + + //! Get tooltip + /*! + Returns the tool tip to display in the task bar. The interface + to set the tooltip is left to sublclasses. Getting and setting + the icon must be thread safe. + */ + virtual std::string getToolTip() const = 0; + + virtual void updateStatus(INode*, const String& errorMsg) = 0; + + virtual void cleanup() {} + + //@} +}; diff --git a/src/lib/arch/IArchTime.h b/src/lib/arch/IArchTime.h new file mode 100644 index 0000000..abb3cdd --- /dev/null +++ b/src/lib/arch/IArchTime.h @@ -0,0 +1,41 @@ +/* + * 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/IInterface.h" + +//! Interface for architecture dependent time operations +/*! +This interface defines the time operations required by +barrier. Each architecture must implement this interface. +*/ +class IArchTime : public IInterface { +public: + //! @name manipulators + //@{ + + //! Get the current time + /*! + Returns the number of seconds since some arbitrary starting time. + This should return as high a precision as reasonable. + */ + virtual double time() = 0; + + //@} +}; diff --git a/src/lib/arch/XArch.h b/src/lib/arch/XArch.h new file mode 100644 index 0000000..457c620 --- /dev/null +++ b/src/lib/arch/XArch.h @@ -0,0 +1,161 @@ +/* + * 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/stdexcept.h" + +//! Generic thread exception +/*! +Exceptions derived from this class are used by the multithreading +library to perform stack unwinding when a thread terminates. These +exceptions must always be rethrown by clients when caught. +*/ +class XThread { }; + +//! Thread exception to cancel +/*! +Thrown to cancel a thread. Clients must not throw this type, but +must rethrow it if caught (by XThreadCancel, XThread, or ...). +*/ +class XThreadCancel : public XThread { }; + +/*! +\def RETHROW_XTHREAD +Convenience macro to rethrow an XThread exception but ignore other +exceptions. Put this in your catch (...) handler after necessary +cleanup but before leaving or returning from the handler. +*/ +#define RETHROW_XTHREAD \ + try { throw; } catch (XThread&) { throw; } catch (...) { } + +//! Lazy error message string evaluation +/*! +This class encapsulates platform dependent error string lookup. +Platforms subclass this type, taking an appropriate error code +type in the c'tor and overriding eval() to return the error +string for that error code. +*/ +class XArchEval { +public: + XArchEval() { } + virtual ~XArchEval() _NOEXCEPT { } + + virtual std::string eval() const = 0; +}; + +//! Generic exception architecture dependent library +class XArch : public std::runtime_error { +public: + XArch(XArchEval* adopted) : std::runtime_error(adopted->eval()) { delete adopted; } + XArch(const std::string& msg) : std::runtime_error(msg) { } + virtual ~XArch() _NOEXCEPT { } +}; + +// Macro to declare XArch derived types +#define XARCH_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_(XArchEval* adoptedEvaluator) : super_(adoptedEvaluator) { } \ + name_(const std::string& msg) : super_(msg) { } \ +} + +//! Generic network exception +/*! +Exceptions derived from this class are used by the networking +library to indicate various errors. +*/ +XARCH_SUBCLASS(XArchNetwork, XArch); + +//! Operation was interrupted +XARCH_SUBCLASS(XArchNetworkInterrupted, XArchNetwork); + +//! Network insufficient permission +XARCH_SUBCLASS(XArchNetworkAccess, XArchNetwork); + +//! Network insufficient resources +XARCH_SUBCLASS(XArchNetworkResource, XArchNetwork); + +//! No support for requested network resource/service +XARCH_SUBCLASS(XArchNetworkSupport, XArchNetwork); + +//! Network I/O error +XARCH_SUBCLASS(XArchNetworkIO, XArchNetwork); + +//! Network address is unavailable or not local +XARCH_SUBCLASS(XArchNetworkNoAddress, XArchNetwork); + +//! Network address in use +XARCH_SUBCLASS(XArchNetworkAddressInUse, XArchNetwork); + +//! No route to address +XARCH_SUBCLASS(XArchNetworkNoRoute, XArchNetwork); + +//! Socket not connected +XARCH_SUBCLASS(XArchNetworkNotConnected, XArchNetwork); + +//! Remote read end of socket has closed +XARCH_SUBCLASS(XArchNetworkShutdown, XArchNetwork); + +//! Remote end of socket has disconnected +XARCH_SUBCLASS(XArchNetworkDisconnected, XArchNetwork); + +//! Remote end of socket refused connection +XARCH_SUBCLASS(XArchNetworkConnectionRefused, XArchNetwork); + +//! Remote end of socket is not responding +XARCH_SUBCLASS(XArchNetworkTimedOut, XArchNetwork); + +//! Generic network name lookup erros +XARCH_SUBCLASS(XArchNetworkName, XArchNetwork); + +//! The named host is unknown +XARCH_SUBCLASS(XArchNetworkNameUnknown, XArchNetworkName); + +//! The named host is known but has no address +XARCH_SUBCLASS(XArchNetworkNameNoAddress, XArchNetworkName); + +//! Non-recoverable name server error +XARCH_SUBCLASS(XArchNetworkNameFailure, XArchNetworkName); + +//! Temporary name server error +XARCH_SUBCLASS(XArchNetworkNameUnavailable, XArchNetworkName); + +//! The named host is known but no supported address +XARCH_SUBCLASS(XArchNetworkNameUnsupported, XArchNetworkName); + +//! Generic daemon exception +/*! +Exceptions derived from this class are used by the daemon +library to indicate various errors. +*/ +XARCH_SUBCLASS(XArchDaemon, XArch); + +//! Could not daemonize +XARCH_SUBCLASS(XArchDaemonFailed, XArchDaemon); + +//! Could not install daemon +XARCH_SUBCLASS(XArchDaemonInstallFailed, XArchDaemon); + +//! Could not uninstall daemon +XARCH_SUBCLASS(XArchDaemonUninstallFailed, XArchDaemon); + +//! Attempted to uninstall a daemon that was not installed +XARCH_SUBCLASS(XArchDaemonUninstallNotInstalled, XArchDaemonUninstallFailed); diff --git a/src/lib/arch/multibyte.h b/src/lib/arch/multibyte.h new file mode 100644 index 0000000..4a4e0ec --- /dev/null +++ b/src/lib/arch/multibyte.h @@ -0,0 +1,56 @@ +/* + * 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 "arch/Arch.h" + +#include <climits> +#include <cstring> +#include <cstdlib> +#if HAVE_LOCALE_H +# include <locale.h> +#endif +#if HAVE_WCHAR_H || defined(_MSC_VER) +# include <wchar.h> +#elif __APPLE__ + // wtf? Darwin puts mbtowc() et al. in stdlib +# include <cstdlib> +#else + // platform apparently has no wchar_t support. provide dummy + // implementations. hopefully at least the C++ compiler has + // a built-in wchar_t type. + +static inline +int +mbtowc(wchar_t* dst, const char* src, int n) +{ + *dst = static_cast<wchar_t>(*src); + return 1; +} + +static inline +int +wctomb(char* dst, wchar_t src) +{ + *dst = static_cast<char>(src); + return 1; +} + +#endif diff --git a/src/lib/arch/unix/ArchConsoleUnix.cpp b/src/lib/arch/unix/ArchConsoleUnix.cpp new file mode 100644 index 0000000..79a4634 --- /dev/null +++ b/src/lib/arch/unix/ArchConsoleUnix.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/unix/ArchConsoleUnix.h" + +ArchConsoleUnix::ArchConsoleUnix() { } + +ArchConsoleUnix::~ArchConsoleUnix() { } diff --git a/src/lib/arch/unix/ArchConsoleUnix.h b/src/lib/arch/unix/ArchConsoleUnix.h new file mode 100644 index 0000000..8326ab5 --- /dev/null +++ b/src/lib/arch/unix/ArchConsoleUnix.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 ArchConsoleUnix + +class ArchConsoleUnix : public ArchConsoleStd { +public: + ArchConsoleUnix(); + virtual ~ArchConsoleUnix(); +}; diff --git a/src/lib/arch/unix/ArchDaemonUnix.cpp b/src/lib/arch/unix/ArchDaemonUnix.cpp new file mode 100644 index 0000000..a03bf7a --- /dev/null +++ b/src/lib/arch/unix/ArchDaemonUnix.cpp @@ -0,0 +1,132 @@ +/* + * 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/unix/ArchDaemonUnix.h" + +#include "arch/unix/XArchUnix.h" +#include "base/Log.h" + +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <cstdlib> + +// +// ArchDaemonUnix +// + +ArchDaemonUnix::ArchDaemonUnix() +{ + // do nothing +} + +ArchDaemonUnix::~ArchDaemonUnix() +{ + // do nothing +} + + +#ifdef __APPLE__ + +// In Mac OS X, fork()'d child processes can't use most APIs (the frameworks +// that Barrier uses in fact prevent it and make the process just up and die), +// so need to exec a copy of the program that doesn't fork so isn't limited. +int +execSelfNonDaemonized() +{ + extern char** NXArgv; + char** selfArgv = NXArgv; + + setenv("_BARRIER_DAEMONIZED", "", 1); + + execvp(selfArgv[0], selfArgv); + return 0; +} + +bool alreadyDaemonized() { + return getenv("_BARRIER_DAEMONIZED") != NULL; +} + +#endif + +int +ArchDaemonUnix::daemonize(const char* name, DaemonFunc func) +{ +#ifdef __APPLE__ + if (alreadyDaemonized()) + return func(1, &name); +#endif + + // fork so shell thinks we're done and so we're not a process + // group leader + switch (fork()) { + case -1: + // failed + throw XArchDaemonFailed(new XArchEvalUnix(errno)); + + case 0: + // child + break; + + default: + // parent exits + exit(0); + } + + // become leader of a new session + setsid(); + +#ifndef __APPLE__ + // NB: don't run chdir on apple; causes strange behaviour. + // chdir to root so we don't keep mounted filesystems points busy + // TODO: this is a bit of a hack - can we find a better solution? + int chdirErr = chdir("/"); + if (chdirErr) + // NB: file logging actually isn't working at this point! + LOG((CLOG_ERR "chdir error: %i", chdirErr)); +#endif + + // mask off permissions for any but owner + umask(077); + + // close open files. we only expect stdin, stdout, stderr to be open. + close(0); + close(1); + close(2); + + // attach file descriptors 0, 1, 2 to /dev/null so inadvertent use + // of standard I/O safely goes in the bit bucket. + open("/dev/null", O_RDONLY); + open("/dev/null", O_RDWR); + + int dupErr = dup(1); + + if (dupErr < 0) { + // NB: file logging actually isn't working at this point! + LOG((CLOG_ERR "dup error: %i", dupErr)); + } + +#ifdef __APPLE__ + return execSelfNonDaemonized(); +#endif + + // invoke function + return func(1, &name); +} diff --git a/src/lib/arch/unix/ArchDaemonUnix.h b/src/lib/arch/unix/ArchDaemonUnix.h new file mode 100644 index 0000000..530159a --- /dev/null +++ b/src/lib/arch/unix/ArchDaemonUnix.h @@ -0,0 +1,36 @@ +/* + * 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/ArchDaemonNone.h" + +#undef ARCH_DAEMON +#define ARCH_DAEMON ArchDaemonUnix + +//! Unix implementation of IArchDaemon +class ArchDaemonUnix : public ArchDaemonNone { +public: + ArchDaemonUnix(); + virtual ~ArchDaemonUnix(); + + // IArchDaemon overrides + virtual int daemonize(const char* name, DaemonFunc func); +}; + +#define CONFIG_FILE "/etc/barrier/barrierd.conf" diff --git a/src/lib/arch/unix/ArchFileUnix.cpp b/src/lib/arch/unix/ArchFileUnix.cpp new file mode 100644 index 0000000..d9ae8b2 --- /dev/null +++ b/src/lib/arch/unix/ArchFileUnix.cpp @@ -0,0 +1,163 @@ +/* + * 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/unix/ArchFileUnix.h" + +#include <stdio.h> +#include <unistd.h> +#include <pwd.h> +#include <sys/types.h> +#include <cstring> + +// +// ArchFileUnix +// + +ArchFileUnix::ArchFileUnix() +{ + // do nothing +} + +ArchFileUnix::~ArchFileUnix() +{ + // do nothing +} + +const char* +ArchFileUnix::getBasename(const char* pathname) +{ + if (pathname == NULL) { + return NULL; + } + + const char* basename = strrchr(pathname, '/'); + if (basename != NULL) { + return basename + 1; + } + else { + return pathname; + } +} + +std::string +ArchFileUnix::getUserDirectory() +{ + char* buffer = NULL; + std::string dir; +#if HAVE_GETPWUID_R + struct passwd pwent; + struct passwd* pwentp; +#if defined(_SC_GETPW_R_SIZE_MAX) + long size = sysconf(_SC_GETPW_R_SIZE_MAX); + if (size == -1) { + size = BUFSIZ; + } +#else + long size = BUFSIZ; +#endif + buffer = new char[size]; + getpwuid_r(getuid(), &pwent, buffer, size, &pwentp); +#else + struct passwd* pwentp = getpwuid(getuid()); +#endif + if (pwentp != NULL && pwentp->pw_dir != NULL) { + dir = pwentp->pw_dir; + } + delete[] buffer; + return dir; +} + +std::string +ArchFileUnix::getSystemDirectory() +{ + return "/etc"; +} + +std::string +ArchFileUnix::getInstalledDirectory() +{ +#if WINAPI_XWINDOWS + return "/usr/bin"; +#else + return "/Applications/Barrier.app/Contents/MacOS"; +#endif +} + +std::string +ArchFileUnix::getLogDirectory() +{ + return "/var/log"; +} + +std::string +ArchFileUnix::getPluginDirectory() +{ + if (!m_pluginDirectory.empty()) { + return m_pluginDirectory; + } + +#if WINAPI_XWINDOWS + return getProfileDirectory().append("/plugins"); +#else + return getProfileDirectory().append("/Plugins"); +#endif +} + +std::string +ArchFileUnix::getProfileDirectory() +{ + String dir; + if (!m_profileDirectory.empty()) { + dir = m_profileDirectory; + } + else { +#if WINAPI_XWINDOWS + dir = getUserDirectory().append("/.barrier"); +#else + dir = getUserDirectory().append("/Library/Application Support/Barrier"); +#endif + } + return dir; + +} + +std::string +ArchFileUnix::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 += suffix; + return path; +} + +void +ArchFileUnix::setProfileDirectory(const String& s) +{ + m_profileDirectory = s; +} + +void +ArchFileUnix::setPluginDirectory(const String& s) +{ + m_pluginDirectory = s; +} diff --git a/src/lib/arch/unix/ArchFileUnix.h b/src/lib/arch/unix/ArchFileUnix.h new file mode 100644 index 0000000..86dea0c --- /dev/null +++ b/src/lib/arch/unix/ArchFileUnix.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 ArchFileUnix + +//! Unix implementation of IArchFile +class ArchFileUnix : public IArchFile { +public: + ArchFileUnix(); + virtual ~ArchFileUnix(); + + // 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/unix/ArchInternetUnix.cpp b/src/lib/arch/unix/ArchInternetUnix.cpp new file mode 100644 index 0000000..fd1e135 --- /dev/null +++ b/src/lib/arch/unix/ArchInternetUnix.cpp @@ -0,0 +1,126 @@ +/* + * 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/unix/ArchInternetUnix.h" + +#include "arch/XArch.h" +#include "common/Version.h" +#include "base/Log.h" + +#include <sstream> +#include <curl/curl.h> + +class CurlFacade { +public: + CurlFacade(); + ~CurlFacade(); + String get(const String& url); + String urlEncode(const String& url); + +private: + CURL* m_curl; +}; + +// +// ArchInternetUnix +// + +String +ArchInternetUnix::get(const String& url) +{ + CurlFacade curl; + return curl.get(url); +} + +String +ArchInternetUnix::urlEncode(const String& url) +{ + CurlFacade curl; + return curl.urlEncode(url); +} + +// +// CurlFacade +// + +static size_t +curlWriteCallback(void *contents, size_t size, size_t nmemb, void *userp) +{ + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +CurlFacade::CurlFacade() : + m_curl(NULL) +{ + CURLcode init = curl_global_init(CURL_GLOBAL_ALL); + if (init != CURLE_OK) { + throw XArch("CURL global init failed."); + } + + m_curl = curl_easy_init(); + if (m_curl == NULL) { + throw XArch("CURL easy init failed."); + } +} + +CurlFacade::~CurlFacade() +{ + if (m_curl != NULL) { + curl_easy_cleanup(m_curl); + } + + curl_global_cleanup(); +} + +String +CurlFacade::get(const String& url) +{ + curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, curlWriteCallback); + + std::stringstream userAgent; + userAgent << "Barrier "; + userAgent << kVersion; + curl_easy_setopt(m_curl, CURLOPT_USERAGENT, userAgent.str().c_str()); + + std::string result; + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &result); + + CURLcode code = curl_easy_perform(m_curl); + if (code != CURLE_OK) { + LOG((CLOG_ERR "curl perform error: %s", curl_easy_strerror(code))); + throw XArch("CURL perform failed."); + } + + return result; +} + +String +CurlFacade::urlEncode(const String& url) +{ + char* resultCStr = curl_easy_escape(m_curl, url.c_str(), 0); + + if (resultCStr == NULL) { + throw XArch("CURL escape failed."); + } + + std::string result(resultCStr); + curl_free(resultCStr); + + return result; +} diff --git a/src/lib/arch/unix/ArchInternetUnix.h b/src/lib/arch/unix/ArchInternetUnix.h new file mode 100644 index 0000000..2413d8f --- /dev/null +++ b/src/lib/arch/unix/ArchInternetUnix.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 ArchInternetUnix + +#include "base/String.h" + +class ArchInternetUnix { +public: + String get(const String& url); + String urlEncode(const String& url); +}; diff --git a/src/lib/arch/unix/ArchLogUnix.cpp b/src/lib/arch/unix/ArchLogUnix.cpp new file mode 100644 index 0000000..b1f9089 --- /dev/null +++ b/src/lib/arch/unix/ArchLogUnix.cpp @@ -0,0 +1,84 @@ +/* + * 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/unix/ArchLogUnix.h" + +#include <syslog.h> + +// +// ArchLogUnix +// + +ArchLogUnix::ArchLogUnix() +{ + // do nothing +} + +ArchLogUnix::~ArchLogUnix() +{ + // do nothing +} + +void +ArchLogUnix::openLog(const char* name) +{ + openlog(name, 0, LOG_DAEMON); +} + +void +ArchLogUnix::closeLog() +{ + closelog(); +} + +void +ArchLogUnix::showLog(bool) +{ + // do nothing +} + +void +ArchLogUnix::writeLog(ELevel level, const char* msg) +{ + // convert level + int priority; + switch (level) { + case kERROR: + priority = LOG_ERR; + break; + + case kWARNING: + priority = LOG_WARNING; + break; + + case kNOTE: + priority = LOG_NOTICE; + break; + + case kINFO: + priority = LOG_INFO; + break; + + default: + priority = LOG_DEBUG; + break; + } + + // log it + syslog(priority, "%s", msg); +} diff --git a/src/lib/arch/unix/ArchLogUnix.h b/src/lib/arch/unix/ArchLogUnix.h new file mode 100644 index 0000000..cdd733f --- /dev/null +++ b/src/lib/arch/unix/ArchLogUnix.h @@ -0,0 +1,36 @@ +/* + * 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 ARCH_LOG ArchLogUnix + +//! Unix implementation of IArchLog +class ArchLogUnix : public IArchLog { +public: + ArchLogUnix(); + virtual ~ArchLogUnix(); + + // IArchLog overrides + virtual void openLog(const char* name); + virtual void closeLog(); + virtual void showLog(bool); + virtual void writeLog(ELevel, const char*); +}; diff --git a/src/lib/arch/unix/ArchMultithreadPosix.cpp b/src/lib/arch/unix/ArchMultithreadPosix.cpp new file mode 100644 index 0000000..ade6c51 --- /dev/null +++ b/src/lib/arch/unix/ArchMultithreadPosix.cpp @@ -0,0 +1,812 @@ +/* + * 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/unix/ArchMultithreadPosix.h" + +#include "arch/Arch.h" +#include "arch/XArch.h" + +#include <signal.h> +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif +#include <cerrno> + +#define SIGWAKEUP SIGUSR1 + +#if !HAVE_PTHREAD_SIGNAL + // boy, is this platform broken. forget about pthread signal + // handling and let signals through to every process. barrier + // will not terminate cleanly when it gets SIGTERM or SIGINT. +# define pthread_sigmask sigprocmask +# define pthread_kill(tid_, sig_) kill(0, (sig_)) +# define sigwait(set_, sig_) +# undef HAVE_POSIX_SIGWAIT +# define HAVE_POSIX_SIGWAIT 1 +#endif + +static +void +setSignalSet(sigset_t* sigset) +{ + sigemptyset(sigset); + sigaddset(sigset, SIGHUP); + sigaddset(sigset, SIGINT); + sigaddset(sigset, SIGTERM); + sigaddset(sigset, SIGUSR2); +} + +// +// ArchThreadImpl +// + +class ArchThreadImpl { +public: + ArchThreadImpl(); + +public: + int m_refCount; + IArchMultithread::ThreadID m_id; + pthread_t m_thread; + IArchMultithread::ThreadFunc m_func; + void* m_userData; + bool m_cancel; + bool m_cancelling; + bool m_exited; + void* m_result; + void* m_networkData; +}; + +ArchThreadImpl::ArchThreadImpl() : + m_refCount(1), + m_id(0), + m_func(NULL), + m_userData(NULL), + m_cancel(false), + m_cancelling(false), + m_exited(false), + m_result(NULL), + m_networkData(NULL) +{ + // do nothing +} + + +// +// ArchMultithreadPosix +// + +ArchMultithreadPosix* ArchMultithreadPosix::s_instance = NULL; + +ArchMultithreadPosix::ArchMultithreadPosix() : + m_newThreadCalled(false), + m_nextID(0) +{ + 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 = pthread_self(); + insert(m_mainThread); + + // install SIGWAKEUP handler. this causes SIGWAKEUP to interrupt + // system calls. we use that when cancelling a thread to force it + // to wake up immediately if it's blocked in a system call. we + // won't need this until another thread is created but it's fine + // to install it now. + struct sigaction act; + sigemptyset(&act.sa_mask); +# if defined(SA_INTERRUPT) + act.sa_flags = SA_INTERRUPT; +# else + act.sa_flags = 0; +# endif + act.sa_handler = &threadCancel; + sigaction(SIGWAKEUP, &act, NULL); + + // set desired signal dispositions. let SIGWAKEUP through but + // ignore SIGPIPE (we'll handle EPIPE). + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGWAKEUP); + pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); + sigemptyset(&sigset); + sigaddset(&sigset, SIGPIPE); + pthread_sigmask(SIG_BLOCK, &sigset, NULL); +} + +ArchMultithreadPosix::~ArchMultithreadPosix() +{ + assert(s_instance != NULL); + + closeMutex(m_threadMutex); + s_instance = NULL; +} + +void +ArchMultithreadPosix::setNetworkDataForCurrentThread(void* data) +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = find(pthread_self()); + thread->m_networkData = data; + unlockMutex(m_threadMutex); +} + +void* +ArchMultithreadPosix::getNetworkDataForThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + void* data = thread->m_networkData; + unlockMutex(m_threadMutex); + return data; +} + +ArchMultithreadPosix* +ArchMultithreadPosix::getInstance() +{ + return s_instance; +} + +ArchCond +ArchMultithreadPosix::newCondVar() +{ + ArchCondImpl* cond = new ArchCondImpl; + int status = pthread_cond_init(&cond->m_cond, NULL); + (void)status; + assert(status == 0); + return cond; +} + +void +ArchMultithreadPosix::closeCondVar(ArchCond cond) +{ + int status = pthread_cond_destroy(&cond->m_cond); + (void)status; + assert(status == 0); + delete cond; +} + +void +ArchMultithreadPosix::signalCondVar(ArchCond cond) +{ + int status = pthread_cond_signal(&cond->m_cond); + (void)status; + assert(status == 0); +} + +void +ArchMultithreadPosix::broadcastCondVar(ArchCond cond) +{ + int status = pthread_cond_broadcast(&cond->m_cond); + (void)status; + assert(status == 0); +} + +bool +ArchMultithreadPosix::waitCondVar(ArchCond cond, + ArchMutex mutex, double timeout) +{ + // we can't wait on a condition variable and also wake it up for + // cancellation since we don't use posix cancellation. so we + // must wake up periodically to check for cancellation. we + // can't simply go back to waiting after the check since the + // condition may have changed and we'll have lost the signal. + // so we have to return to the caller. since the caller will + // always check for spurious wakeups the only drawback here is + // performance: we're waking up a lot more than desired. + static const double maxCancellationLatency = 0.1; + if (timeout < 0.0 || timeout > maxCancellationLatency) { + timeout = maxCancellationLatency; + } + + // see if we should cancel this thread + testCancelThread(); + + // get final time + struct timeval now; + gettimeofday(&now, NULL); + struct timespec finalTime; + finalTime.tv_sec = now.tv_sec; + finalTime.tv_nsec = now.tv_usec * 1000; + long timeout_sec = (long)timeout; + long timeout_nsec = (long)(1.0e+9 * (timeout - timeout_sec)); + finalTime.tv_sec += timeout_sec; + finalTime.tv_nsec += timeout_nsec; + if (finalTime.tv_nsec >= 1000000000) { + finalTime.tv_nsec -= 1000000000; + finalTime.tv_sec += 1; + } + + // wait + int status = pthread_cond_timedwait(&cond->m_cond, + &mutex->m_mutex, &finalTime); + + // check for cancel again + testCancelThread(); + + switch (status) { + case 0: + // success + return true; + + case ETIMEDOUT: + return false; + + default: + assert(0 && "condition variable wait error"); + return false; + } +} + +ArchMutex +ArchMultithreadPosix::newMutex() +{ + pthread_mutexattr_t attr; + int status = pthread_mutexattr_init(&attr); + assert(status == 0); + ArchMutexImpl* mutex = new ArchMutexImpl; + status = pthread_mutex_init(&mutex->m_mutex, &attr); + assert(status == 0); + return mutex; +} + +void +ArchMultithreadPosix::closeMutex(ArchMutex mutex) +{ + int status = pthread_mutex_destroy(&mutex->m_mutex); + (void)status; + assert(status == 0); + delete mutex; +} + +void +ArchMultithreadPosix::lockMutex(ArchMutex mutex) +{ + int status = pthread_mutex_lock(&mutex->m_mutex); + + switch (status) { + case 0: + // success + return; + + case EDEADLK: + assert(0 && "lock already owned"); + break; + + case EAGAIN: + assert(0 && "too many recursive locks"); + break; + + default: + assert(0 && "unexpected error"); + break; + } +} + +void +ArchMultithreadPosix::unlockMutex(ArchMutex mutex) +{ + int status = pthread_mutex_unlock(&mutex->m_mutex); + + switch (status) { + case 0: + // success + return; + + case EPERM: + assert(0 && "thread doesn't own a lock"); + break; + + default: + assert(0 && "unexpected error"); + break; + } +} + +ArchThread +ArchMultithreadPosix::newThread(ThreadFunc func, void* data) +{ + assert(func != NULL); + + // initialize signal handler. we do this here instead of the + // constructor so we can avoid daemonizing (using fork()) + // when there are multiple threads. clients can safely + // use condition variables and mutexes before creating a + // new thread and they can safely use the only thread + // they have access to, the main thread, so they really + // can't tell the difference. + if (!m_newThreadCalled) { + m_newThreadCalled = true; +#if HAVE_PTHREAD_SIGNAL + startSignalHandler(); +#endif + } + + lockMutex(m_threadMutex); + + // create thread impl for new thread + ArchThreadImpl* thread = new ArchThreadImpl; + thread->m_func = func; + thread->m_userData = data; + + // create the thread. pthread_create() on RedHat 7.2 smp fails + // if passed a NULL attr so use a default attr. + pthread_attr_t attr; + int status = pthread_attr_init(&attr); + if (status == 0) { + status = pthread_create(&thread->m_thread, &attr, + &ArchMultithreadPosix::threadFunc, thread); + pthread_attr_destroy(&attr); + } + + // check if thread was started + if (status != 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 +ArchMultithreadPosix::newCurrentThread() +{ + lockMutex(m_threadMutex); + ArchThreadImpl* thread = find(pthread_self()); + unlockMutex(m_threadMutex); + assert(thread != NULL); + return thread; +} + +void +ArchMultithreadPosix::closeThread(ArchThread thread) +{ + assert(thread != NULL); + + // decrement ref count and clean up thread if no more references + if (--thread->m_refCount == 0) { + // detach from thread (unless it's the main thread) + if (thread->m_func != NULL) { + pthread_detach(thread->m_thread); + } + + // remove thread from list + lockMutex(m_threadMutex); + assert(findNoRef(thread->m_thread) == thread); + erase(thread); + unlockMutex(m_threadMutex); + + // done with thread + delete thread; + } +} + +ArchThread +ArchMultithreadPosix::copyThread(ArchThread thread) +{ + refThread(thread); + return thread; +} + +void +ArchMultithreadPosix::cancelThread(ArchThread thread) +{ + assert(thread != NULL); + + // set cancel and wakeup flags if thread can be cancelled + bool wakeup = false; + lockMutex(m_threadMutex); + if (!thread->m_exited && !thread->m_cancelling) { + thread->m_cancel = true; + wakeup = true; + } + unlockMutex(m_threadMutex); + + // force thread to exit system calls if wakeup is true + if (wakeup) { + pthread_kill(thread->m_thread, SIGWAKEUP); + } +} + +void +ArchMultithreadPosix::setPriorityOfThread(ArchThread thread, int /*n*/) +{ + assert(thread != NULL); + + // FIXME +} + +void +ArchMultithreadPosix::testCancelThread() +{ + // find current thread + lockMutex(m_threadMutex); + ArchThreadImpl* thread = findNoRef(pthread_self()); + unlockMutex(m_threadMutex); + + // test cancel on thread + testCancelThreadImpl(thread); +} + +bool +ArchMultithreadPosix::wait(ArchThread target, double timeout) +{ + assert(target != NULL); + + lockMutex(m_threadMutex); + + // find current thread + ArchThreadImpl* self = findNoRef(pthread_self()); + + // 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); + + try { + // do first test regardless of timeout + testCancelThreadImpl(self); + if (isExitedThread(target)) { + closeThread(target); + return true; + } + + // wait and repeat test if there's a timeout + if (timeout != 0.0) { + const double start = ARCH->time(); + do { + // wait a little + ARCH->sleep(0.05); + + // repeat test + testCancelThreadImpl(self); + if (isExitedThread(target)) { + closeThread(target); + return true; + } + + // repeat wait and test until timed out + } while (timeout < 0.0 || (ARCH->time() - start) <= timeout); + } + + closeThread(target); + return false; + } + catch (...) { + closeThread(target); + throw; + } +} + +bool +ArchMultithreadPosix::isSameThread(ArchThread thread1, ArchThread thread2) +{ + return (thread1 == thread2); +} + +bool +ArchMultithreadPosix::isExitedThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + bool exited = thread->m_exited; + unlockMutex(m_threadMutex); + return exited; +} + +void* +ArchMultithreadPosix::getResultOfThread(ArchThread thread) +{ + lockMutex(m_threadMutex); + void* result = thread->m_result; + unlockMutex(m_threadMutex); + return result; +} + +IArchMultithread::ThreadID +ArchMultithreadPosix::getIDOfThread(ArchThread thread) +{ + return thread->m_id; +} + +void +ArchMultithreadPosix::setSignalHandler( + ESignal signal, SignalFunc func, void* userData) +{ + lockMutex(m_threadMutex); + m_signalFunc[signal] = func; + m_signalUserData[signal] = userData; + unlockMutex(m_threadMutex); +} + +void +ArchMultithreadPosix::raiseSignal(ESignal signal) +{ + lockMutex(m_threadMutex); + if (m_signalFunc[signal] != NULL) { + m_signalFunc[signal](signal, m_signalUserData[signal]); + pthread_kill(m_mainThread->m_thread, SIGWAKEUP); + } + else if (signal == kINTERRUPT || signal == kTERMINATE) { + ARCH->cancelThread(m_mainThread); + } + unlockMutex(m_threadMutex); +} + +void +ArchMultithreadPosix::startSignalHandler() +{ + // set signal mask. the main thread blocks these signals and + // the signal handler thread will listen for them. + sigset_t sigset, oldsigset; + setSignalSet(&sigset); + pthread_sigmask(SIG_BLOCK, &sigset, &oldsigset); + + // fire up the INT and TERM signal handler thread. we could + // instead arrange to catch and handle these signals but + // we'd be unable to cancel the main thread since no pthread + // calls are allowed in a signal handler. + pthread_attr_t attr; + int status = pthread_attr_init(&attr); + if (status == 0) { + status = pthread_create(&m_signalThread, &attr, + &ArchMultithreadPosix::threadSignalHandler, + NULL); + pthread_attr_destroy(&attr); + } + if (status != 0) { + // can't create thread to wait for signal so don't block + // the signals. + pthread_sigmask(SIG_UNBLOCK, &oldsigset, NULL); + } +} + +ArchThreadImpl* +ArchMultithreadPosix::find(pthread_t thread) +{ + ArchThreadImpl* impl = findNoRef(thread); + if (impl != NULL) { + refThread(impl); + } + return impl; +} + +ArchThreadImpl* +ArchMultithreadPosix::findNoRef(pthread_t thread) +{ + // linear search + for (ThreadList::const_iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if ((*index)->m_thread == thread) { + return *index; + } + } + return NULL; +} + +void +ArchMultithreadPosix::insert(ArchThreadImpl* thread) +{ + assert(thread != NULL); + + // thread shouldn't already be on the list + assert(findNoRef(thread->m_thread) == NULL); + + // set thread id. note that we don't worry about m_nextID + // wrapping back to 0 and duplicating thread ID's since the + // likelihood of barrier running that long is vanishingly + // small. + thread->m_id = ++m_nextID; + + // append to list + m_threadList.push_back(thread); +} + +void +ArchMultithreadPosix::erase(ArchThreadImpl* thread) +{ + for (ThreadList::iterator index = m_threadList.begin(); + index != m_threadList.end(); ++index) { + if (*index == thread) { + m_threadList.erase(index); + break; + } + } +} + +void +ArchMultithreadPosix::refThread(ArchThreadImpl* thread) +{ + assert(thread != NULL); + assert(findNoRef(thread->m_thread) != NULL); + ++thread->m_refCount; +} + +void +ArchMultithreadPosix::testCancelThreadImpl(ArchThreadImpl* thread) +{ + assert(thread != NULL); + + // update cancel state + lockMutex(m_threadMutex); + bool cancel = false; + if (thread->m_cancel && !thread->m_cancelling) { + thread->m_cancelling = true; + thread->m_cancel = false; + cancel = true; + } + unlockMutex(m_threadMutex); + + // unwind thread's stack if cancelling + if (cancel) { + throw XThreadCancel(); + } +} + +void* +ArchMultithreadPosix::threadFunc(void* vrep) +{ + // get the thread + ArchThreadImpl* thread = static_cast<ArchThreadImpl*>(vrep); + + // setup pthreads + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); + pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL); + + // run thread + s_instance->doThreadFunc(thread); + + // terminate the thread + return NULL; +} + +void +ArchMultithreadPosix::doThreadFunc(ArchThread thread) +{ + // default priority is slightly below normal + setPriorityOfThread(thread, 1); + + // 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 + lockMutex(m_threadMutex); + thread->m_exited = true; + unlockMutex(m_threadMutex); + closeThread(thread); + throw; + } + + // thread has exited + lockMutex(m_threadMutex); + thread->m_result = result; + thread->m_exited = true; + unlockMutex(m_threadMutex); + + // done with thread + closeThread(thread); +} + +void +ArchMultithreadPosix::threadCancel(int) +{ + // do nothing +} + +void* +ArchMultithreadPosix::threadSignalHandler(void*) +{ + // detach + pthread_detach(pthread_self()); + + // add signal to mask + sigset_t sigset; + setSignalSet(&sigset); + + // also wait on SIGABRT. on linux (others?) this thread (process) + // will persist after all the other threads evaporate due to an + // assert unless we wait on SIGABRT. that means our resources (like + // the socket we're listening on) are not released and never will be + // until the lingering thread is killed. i don't know why sigwait() + // should protect the thread from being killed. note that sigwait() + // doesn't actually return if we receive SIGABRT and, for some + // reason, we don't have to block SIGABRT. + sigaddset(&sigset, SIGABRT); + + // we exit the loop via thread cancellation in sigwait() + for (;;) { + // wait +#if HAVE_POSIX_SIGWAIT + int signal = 0; + sigwait(&sigset, &signal); +#else + sigwait(&sigset); +#endif + + // if we get here then the signal was raised + switch (signal) { + case SIGINT: + ARCH->raiseSignal(kINTERRUPT); + break; + + case SIGTERM: + ARCH->raiseSignal(kTERMINATE); + break; + + case SIGHUP: + ARCH->raiseSignal(kHANGUP); + break; + + case SIGUSR2: + ARCH->raiseSignal(kUSER); + break; + + default: + // ignore + break; + } + } + + return NULL; +} diff --git a/src/lib/arch/unix/ArchMultithreadPosix.h b/src/lib/arch/unix/ArchMultithreadPosix.h new file mode 100644 index 0000000..98b5eda --- /dev/null +++ b/src/lib/arch/unix/ArchMultithreadPosix.h @@ -0,0 +1,115 @@ +/* + * 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" + +#include <pthread.h> + +#define ARCH_MULTITHREAD ArchMultithreadPosix + +class ArchCondImpl { +public: + pthread_cond_t m_cond; +}; + +class ArchMutexImpl { +public: + pthread_mutex_t m_mutex; +}; + +//! Posix implementation of IArchMultithread +class ArchMultithreadPosix : public IArchMultithread { +public: + ArchMultithreadPosix(); + virtual ~ArchMultithreadPosix(); + + //! @name manipulators + //@{ + + void setNetworkDataForCurrentThread(void*); + + //@} + //! @name accessors + //@{ + + void* getNetworkDataForThread(ArchThread); + + static ArchMultithreadPosix* 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: + void startSignalHandler(); + + ArchThreadImpl* find(pthread_t thread); + ArchThreadImpl* findNoRef(pthread_t thread); + void insert(ArchThreadImpl* thread); + void erase(ArchThreadImpl* thread); + + void refThread(ArchThreadImpl* rep); + void testCancelThreadImpl(ArchThreadImpl* rep); + + void doThreadFunc(ArchThread thread); + static void* threadFunc(void* vrep); + static void threadCancel(int); + static void* threadSignalHandler(void* vrep); + +private: + typedef std::list<ArchThread> ThreadList; + + static ArchMultithreadPosix* s_instance; + + bool m_newThreadCalled; + + ArchMutex m_threadMutex; + ArchThread m_mainThread; + ThreadList m_threadList; + ThreadID m_nextID; + + pthread_t m_signalThread; + SignalFunc m_signalFunc[kNUM_SIGNALS]; + void* m_signalUserData[kNUM_SIGNALS]; +}; diff --git a/src/lib/arch/unix/ArchNetworkBSD.cpp b/src/lib/arch/unix/ArchNetworkBSD.cpp new file mode 100644 index 0000000..149218c --- /dev/null +++ b/src/lib/arch/unix/ArchNetworkBSD.cpp @@ -0,0 +1,1005 @@ +/* + * 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/unix/ArchNetworkBSD.h" + +#include "arch/unix/ArchMultithreadPosix.h" +#include "arch/unix/XArchUnix.h" +#include "arch/Arch.h" + +#if HAVE_UNISTD_H +# include <unistd.h> +#endif +#include <netinet/in.h> +#include <netdb.h> +#if !defined(TCP_NODELAY) +# include <netinet/tcp.h> +#endif +#include <arpa/inet.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +#if HAVE_POLL +# include <poll.h> +#else +# if HAVE_SYS_SELECT_H +# include <sys/select.h> +# endif +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# endif +#endif + +#if !HAVE_INET_ATON +# include <stdio.h> +#endif + +static const int s_family[] = { + PF_UNSPEC, + PF_INET, + PF_INET6, +}; +static const int s_type[] = { + SOCK_DGRAM, + SOCK_STREAM +}; + +#if !HAVE_INET_ATON +// parse dotted quad addresses. we don't bother with the weird BSD'ism +// of handling octal and hex and partial forms. +static +in_addr_t +inet_aton(const char* cp, struct in_addr* inp) +{ + unsigned int a, b, c, d; + if (sscanf(cp, "%u.%u.%u.%u", &a, &b, &c, &d) != 4) { + return 0; + } + if (a >= 256 || b >= 256 || c >= 256 || d >= 256) { + return 0; + } + unsigned char* incp = (unsigned char*)inp; + incp[0] = (unsigned char)(a & 0xffu); + incp[1] = (unsigned char)(b & 0xffu); + incp[2] = (unsigned char)(c & 0xffu); + incp[3] = (unsigned char)(d & 0xffu); + return inp->s_addr; +} +#endif + +// +// ArchNetworkBSD +// + +ArchNetworkBSD::ArchNetworkBSD() +{ +} + +ArchNetworkBSD::~ArchNetworkBSD() +{ + ARCH->closeMutex(m_mutex); +} + +void +ArchNetworkBSD::init() +{ + // create mutex to make some calls thread safe + m_mutex = ARCH->newMutex(); +} + +ArchSocket +ArchNetworkBSD::newSocket(EAddressFamily family, ESocketType type) +{ + // create socket + int fd = socket(s_family[family], s_type[type], 0); + if (fd == -1) { + throwError(errno); + } + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close(fd); + throw; + } + + // allocate socket object + ArchSocketImpl* newSocket = new ArchSocketImpl; + newSocket->m_fd = fd; + newSocket->m_refCount = 1; + return newSocket; +} + +ArchSocket +ArchNetworkBSD::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 +ArchNetworkBSD::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(s->m_fd) == -1) { + // close failed. restore the last ref and throw. + int err = errno; + ARCH->lockMutex(m_mutex); + ++s->m_refCount; + ARCH->unlockMutex(m_mutex); + throwError(err); + } + delete s; + } +} + +void +ArchNetworkBSD::closeSocketForRead(ArchSocket s) +{ + assert(s != NULL); + + if (shutdown(s->m_fd, 0) == -1) { + if (errno != ENOTCONN) { + throwError(errno); + } + } +} + +void +ArchNetworkBSD::closeSocketForWrite(ArchSocket s) +{ + assert(s != NULL); + + if (shutdown(s->m_fd, 1) == -1) { + if (errno != ENOTCONN) { + throwError(errno); + } + } +} + +void +ArchNetworkBSD::bindSocket(ArchSocket s, ArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (bind(s->m_fd, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == -1) { + throwError(errno); + } +} + +void +ArchNetworkBSD::listenOnSocket(ArchSocket s) +{ + assert(s != NULL); + + // hardcoding backlog + if (listen(s->m_fd, 3) == -1) { + throwError(errno); + } +} + +ArchSocket +ArchNetworkBSD::acceptSocket(ArchSocket s, ArchNetAddress* addr) +{ + assert(s != NULL); + + // if user passed NULL in addr then use scratch space + ArchNetAddress dummy; + if (addr == NULL) { + addr = &dummy; + } + + // create new socket and address + ArchSocketImpl* newSocket = new ArchSocketImpl; + *addr = new ArchNetAddressImpl; + + // accept on socket + ACCEPT_TYPE_ARG3 len = (ACCEPT_TYPE_ARG3)((*addr)->m_len); + int fd = accept(s->m_fd, TYPED_ADDR(struct sockaddr, (*addr)), &len); + (*addr)->m_len = (socklen_t)len; + if (fd == -1) { + int err = errno; + delete newSocket; + delete *addr; + *addr = NULL; + if (err == EAGAIN) { + return NULL; + } + throwError(err); + } + + try { + setBlockingOnSocket(fd, false); + } + catch (...) { + close(fd); + delete newSocket; + delete *addr; + *addr = NULL; + throw; + } + + // initialize socket + newSocket->m_fd = fd; + newSocket->m_refCount = 1; + + // discard address if not requested + if (addr == &dummy) { + ARCH->closeAddr(dummy); + } + + return newSocket; +} + +bool +ArchNetworkBSD::connectSocket(ArchSocket s, ArchNetAddress addr) +{ + assert(s != NULL); + assert(addr != NULL); + + if (connect(s->m_fd, TYPED_ADDR(struct sockaddr, addr), addr->m_len) == -1) { + if (errno == EISCONN) { + return true; + } + if (errno == EINPROGRESS) { + return false; + } + throwError(errno); + } + return true; +} + +#if HAVE_POLL + +int +ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) +{ + assert(pe != NULL || num == 0); + + // return if nothing to do + if (num == 0) { + if (timeout > 0.0) { + ARCH->sleep(timeout); + } + return 0; + } + + // allocate space for translated query + struct pollfd* pfd = new struct pollfd[1 + num]; + + // translate query + for (int i = 0; i < num; ++i) { + pfd[i].fd = (pe[i].m_socket == NULL) ? -1 : pe[i].m_socket->m_fd; + pfd[i].events = 0; + if ((pe[i].m_events & kPOLLIN) != 0) { + pfd[i].events |= POLLIN; + } + if ((pe[i].m_events & kPOLLOUT) != 0) { + pfd[i].events |= POLLOUT; + } + } + int n = num; + + // add the unblock pipe + const int* unblockPipe = getUnblockPipe(); + if (unblockPipe != NULL) { + pfd[n].fd = unblockPipe[0]; + pfd[n].events = POLLIN; + ++n; + } + + // prepare timeout + int t = (timeout < 0.0) ? -1 : static_cast<int>(1000.0 * timeout); + + // do the poll + n = poll(pfd, n, t); + + // reset the unblock pipe + if (n > 0 && unblockPipe != NULL && (pfd[num].revents & POLLIN) != 0) { + // the unblock event was signalled. flush the pipe. + char dummy[100]; + int ignore; + + do { + ignore = read(unblockPipe[0], dummy, sizeof(dummy)); + } while (errno != EAGAIN); + + // don't count this unblock pipe in return value + --n; + } + + // handle results + if (n == -1) { + if (errno == EINTR) { + // interrupted system call + ARCH->testCancelThread(); + delete[] pfd; + return 0; + } + delete[] pfd; + throwError(errno); + } + + // translate back + for (int i = 0; i < num; ++i) { + pe[i].m_revents = 0; + if ((pfd[i].revents & POLLIN) != 0) { + pe[i].m_revents |= kPOLLIN; + } + if ((pfd[i].revents & POLLOUT) != 0) { + pe[i].m_revents |= kPOLLOUT; + } + if ((pfd[i].revents & POLLERR) != 0) { + pe[i].m_revents |= kPOLLERR; + } + if ((pfd[i].revents & POLLNVAL) != 0) { + pe[i].m_revents |= kPOLLNVAL; + } + } + + delete[] pfd; + return n; +} + +#else + +int +ArchNetworkBSD::pollSocket(PollEntry pe[], int num, double timeout) +{ + int i, n; + + // prepare sets for select + n = 0; + fd_set readSet, writeSet, errSet; + fd_set* readSetP = NULL; + fd_set* writeSetP = NULL; + fd_set* errSetP = NULL; + FD_ZERO(&readSet); + FD_ZERO(&writeSet); + FD_ZERO(&errSet); + for (i = 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; + } + + int fdi = pe[i].m_socket->m_fd; + if (pe[i].m_events & kPOLLIN) { + FD_SET(pe[i].m_socket->m_fd, &readSet); + readSetP = &readSet; + if (fdi > n) { + n = fdi; + } + } + if (pe[i].m_events & kPOLLOUT) { + FD_SET(pe[i].m_socket->m_fd, &writeSet); + writeSetP = &writeSet; + if (fdi > n) { + n = fdi; + } + } + if (true) { + FD_SET(pe[i].m_socket->m_fd, &errSet); + errSetP = &errSet; + if (fdi > n) { + n = fdi; + } + } + } + + // add the unblock pipe + const int* unblockPipe = getUnblockPipe(); + if (unblockPipe != NULL) { + FD_SET(unblockPipe[0], &readSet); + readSetP = &readSet; + if (unblockPipe[0] > n) { + n = unblockPipe[0]; + } + } + + // if there are no sockets then don't block forever + if (n == 0 && timeout < 0.0) { + timeout = 0.0; + } + + // prepare timeout for select + struct timeval timeout2; + struct timeval* timeout2P; + if (timeout < 0.0) { + timeout2P = NULL; + } + else { + timeout2P = &timeout2; + timeout2.tv_sec = static_cast<int>(timeout); + timeout2.tv_usec = static_cast<int>(1.0e+6 * + (timeout - timeout2.tv_sec)); + } + + // do the select + n = select((SELECT_TYPE_ARG1) n + 1, + SELECT_TYPE_ARG234 readSetP, + SELECT_TYPE_ARG234 writeSetP, + SELECT_TYPE_ARG234 errSetP, + SELECT_TYPE_ARG5 timeout2P); + + // reset the unblock pipe + if (n > 0 && unblockPipe != NULL && FD_ISSET(unblockPipe[0], &readSet)) { + // the unblock event was signalled. flush the pipe. + char dummy[100]; + do { + read(unblockPipe[0], dummy, sizeof(dummy)); + } while (errno != EAGAIN); + } + + // handle results + if (n == -1) { + if (errno == EINTR) { + // interrupted system call + ARCH->testCancelThread(); + return 0; + } + throwError(errno); + } + n = 0; + for (i = 0; i < num; ++i) { + if (pe[i].m_socket != NULL) { + if (FD_ISSET(pe[i].m_socket->m_fd, &readSet)) { + pe[i].m_revents |= kPOLLIN; + } + if (FD_ISSET(pe[i].m_socket->m_fd, &writeSet)) { + pe[i].m_revents |= kPOLLOUT; + } + if (FD_ISSET(pe[i].m_socket->m_fd, &errSet)) { + pe[i].m_revents |= kPOLLERR; + } + } + if (pe[i].m_revents != 0) { + ++n; + } + } + + return n; +} + +#endif + +void +ArchNetworkBSD::unblockPollSocket(ArchThread thread) +{ + const int* unblockPipe = getUnblockPipeForThread(thread); + if (unblockPipe != NULL) { + char dummy = 0; + int ignore; + + ignore = write(unblockPipe[1], &dummy, 1); + } +} + +size_t +ArchNetworkBSD::readSocket(ArchSocket s, void* buf, size_t len) +{ + assert(s != NULL); + + ssize_t n = read(s->m_fd, buf, len); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + return 0; + } + throwError(errno); + } + return n; +} + +size_t +ArchNetworkBSD::writeSocket(ArchSocket s, const void* buf, size_t len) +{ + assert(s != NULL); + + ssize_t n = write(s->m_fd, buf, len); + if (n == -1) { + if (errno == EINTR || errno == EAGAIN) { + return 0; + } + throwError(errno); + } + return n; +} + +void +ArchNetworkBSD::throwErrorOnSocket(ArchSocket s) +{ + assert(s != NULL); + + // get the error from the socket layer + int err = 0; + socklen_t size = (socklen_t)sizeof(err); + if (getsockopt(s->m_fd, SOL_SOCKET, SO_ERROR, + (optval_t*)&err, &size) == -1) { + err = errno; + } + + // throw if there's an error + if (err != 0) { + throwError(err); + } +} + +void +ArchNetworkBSD::setBlockingOnSocket(int fd, bool blocking) +{ + assert(fd != -1); + + int mode = fcntl(fd, F_GETFL, 0); + if (mode == -1) { + throwError(errno); + } + if (blocking) { + mode &= ~O_NONBLOCK; + } + else { + mode |= O_NONBLOCK; + } + if (fcntl(fd, F_SETFL, mode) == -1) { + throwError(errno); + } +} + +bool +ArchNetworkBSD::setNoDelayOnSocket(ArchSocket s, bool noDelay) +{ + assert(s != NULL); + + // get old state + int oflag; + socklen_t size = (socklen_t)sizeof(oflag); + if (getsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, + (optval_t*)&oflag, &size) == -1) { + throwError(errno); + } + + int flag = noDelay ? 1 : 0; + size = (socklen_t)sizeof(flag); + if (setsockopt(s->m_fd, IPPROTO_TCP, TCP_NODELAY, + (optval_t*)&flag, size) == -1) { + throwError(errno); + } + + return (oflag != 0); +} + +bool +ArchNetworkBSD::setReuseAddrOnSocket(ArchSocket s, bool reuse) +{ + assert(s != NULL); + + // get old state + int oflag; + socklen_t size = (socklen_t)sizeof(oflag); + if (getsockopt(s->m_fd, SOL_SOCKET, SO_REUSEADDR, + (optval_t*)&oflag, &size) == -1) { + throwError(errno); + } + + int flag = reuse ? 1 : 0; + size = (socklen_t)sizeof(flag); + if (setsockopt(s->m_fd, SOL_SOCKET, SO_REUSEADDR, + (optval_t*)&flag, size) == -1) { + throwError(errno); + } + + return (oflag != 0); +} + +std::string +ArchNetworkBSD::getHostName() +{ + char name[256]; + if (gethostname(name, sizeof(name)) == -1) { + name[0] = '\0'; + } + else { + name[sizeof(name) - 1] = '\0'; + } + return name; +} + +ArchNetAddress +ArchNetworkBSD::newAnyAddr(EAddressFamily family) +{ + // allocate address + ArchNetAddressImpl* addr = new ArchNetAddressImpl; + + // fill it in + switch (family) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + ipAddr->sin_family = AF_INET; + ipAddr->sin_port = 0; + ipAddr->sin_addr.s_addr = INADDR_ANY; + addr->m_len = (socklen_t)sizeof(struct sockaddr_in); + break; + } + + case kINET6: { + 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)); + addr->m_len = (socklen_t)sizeof(struct sockaddr_in6); + break; + } + default: + delete addr; + assert(0 && "invalid family"); + } + + return addr; +} + +ArchNetAddress +ArchNetworkBSD::copyAddr(ArchNetAddress addr) +{ + assert(addr != NULL); + + // allocate and copy address + return new ArchNetAddressImpl(*addr); +} + +ArchNetAddress +ArchNetworkBSD::nameToAddr(const std::string& name) +{ + // allocate address + ArchNetAddressImpl* addr = new ArchNetAddressImpl; + + char ipstr[INET6_ADDRSTRLEN]; + struct addrinfo hints; + struct addrinfo *p; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + + 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 +ArchNetworkBSD::closeAddr(ArchNetAddress addr) +{ + assert(addr != NULL); + + delete addr; +} + +std::string +ArchNetworkBSD::addrToName(ArchNetAddress addr) +{ + assert(addr != NULL); + + // mutexed name lookup (ugh) + ARCH->lockMutex(m_mutex); + 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 != 0) { + ARCH->unlockMutex(m_mutex); + throwNameError(ret); + } + + // save (primary) name + std::string name = host; + + // done with static buffer + ARCH->unlockMutex(m_mutex); + + return name; +} + +std::string +ArchNetworkBSD::addrToString(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + ARCH->lockMutex(m_mutex); + std::string s = inet_ntoa(ipAddr->sin_addr); + ARCH->unlockMutex(m_mutex); + return s; + } + + case kINET6: { + char strAddr[INET6_ADDRSTRLEN]; + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + ARCH->lockMutex(m_mutex); + inet_ntop(AF_INET6, &ipAddr->sin6_addr, strAddr, INET6_ADDRSTRLEN); + ARCH->unlockMutex(m_mutex); + return strAddr; + } + + default: + assert(0 && "unknown address family"); + return ""; + } +} + +IArchNetwork::EAddressFamily +ArchNetworkBSD::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 +ArchNetworkBSD::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(port); + break; + } + + case kINET6: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + ipAddr->sin6_port = htons(port); + break; + } + + default: + assert(0 && "unknown address family"); + break; + } +} + +int +ArchNetworkBSD::getAddrPort(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + return ntohs(ipAddr->sin_port); + } + + case kINET6: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + return ntohs(ipAddr->sin6_port); + } + + default: + assert(0 && "unknown address family"); + return 0; + } +} + +bool +ArchNetworkBSD::isAnyAddr(ArchNetAddress addr) +{ + assert(addr != NULL); + + switch (getAddrFamily(addr)) { + case kINET: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in, addr); + return (ipAddr->sin_addr.s_addr == INADDR_ANY && + addr->m_len == (socklen_t)sizeof(struct sockaddr_in)); + } + + case kINET6: { + auto* ipAddr = TYPED_ADDR(struct sockaddr_in6, addr); + return (memcmp(&ipAddr->sin6_addr, &in6addr_any, sizeof(in6addr_any)) == 0 && + addr->m_len == (socklen_t)sizeof(struct sockaddr_in6)); + } + + default: + assert(0 && "unknown address family"); + return true; + } +} + +bool +ArchNetworkBSD::isEqualAddr(ArchNetAddress a, ArchNetAddress b) +{ + return (a->m_len == b->m_len && + memcmp(&a->m_addr, &b->m_addr, a->m_len) == 0); +} + +const int* +ArchNetworkBSD::getUnblockPipe() +{ + ArchMultithreadPosix* mt = ArchMultithreadPosix::getInstance(); + ArchThread thread = mt->newCurrentThread(); + const int* p = getUnblockPipeForThread(thread); + ARCH->closeThread(thread); + return p; +} + +const int* +ArchNetworkBSD::getUnblockPipeForThread(ArchThread thread) +{ + ArchMultithreadPosix* mt = ArchMultithreadPosix::getInstance(); + int* unblockPipe = (int*)mt->getNetworkDataForThread(thread); + if (unblockPipe == NULL) { + unblockPipe = new int[2]; + if (pipe(unblockPipe) != -1) { + try { + setBlockingOnSocket(unblockPipe[0], false); + mt->setNetworkDataForCurrentThread(unblockPipe); + } + catch (...) { + delete[] unblockPipe; + unblockPipe = NULL; + } + } + else { + delete[] unblockPipe; + unblockPipe = NULL; + } + } + return unblockPipe; +} + +void +ArchNetworkBSD::throwError(int err) +{ + switch (err) { + case EINTR: + ARCH->testCancelThread(); + throw XArchNetworkInterrupted(new XArchEvalUnix(err)); + + case EACCES: + case EPERM: + throw XArchNetworkAccess(new XArchEvalUnix(err)); + + case ENFILE: + case EMFILE: + case ENODEV: + case ENOBUFS: + case ENOMEM: + case ENETDOWN: +#if defined(ENOSR) + case ENOSR: +#endif + throw XArchNetworkResource(new XArchEvalUnix(err)); + + case EPROTOTYPE: + case EPROTONOSUPPORT: + case EAFNOSUPPORT: + case EPFNOSUPPORT: + case ESOCKTNOSUPPORT: + case EINVAL: + case ENOPROTOOPT: + case EOPNOTSUPP: + case ESHUTDOWN: +#if defined(ENOPKG) + case ENOPKG: +#endif + throw XArchNetworkSupport(new XArchEvalUnix(err)); + + case EIO: + throw XArchNetworkIO(new XArchEvalUnix(err)); + + case EADDRNOTAVAIL: + throw XArchNetworkNoAddress(new XArchEvalUnix(err)); + + case EADDRINUSE: + throw XArchNetworkAddressInUse(new XArchEvalUnix(err)); + + case EHOSTUNREACH: + case ENETUNREACH: + throw XArchNetworkNoRoute(new XArchEvalUnix(err)); + + case ENOTCONN: + throw XArchNetworkNotConnected(new XArchEvalUnix(err)); + + case EPIPE: + throw XArchNetworkShutdown(new XArchEvalUnix(err)); + + case ECONNABORTED: + case ECONNRESET: + throw XArchNetworkDisconnected(new XArchEvalUnix(err)); + + case ECONNREFUSED: + throw XArchNetworkConnectionRefused(new XArchEvalUnix(err)); + + case EHOSTDOWN: + case ETIMEDOUT: + throw XArchNetworkTimedOut(new XArchEvalUnix(err)); + + default: + throw XArchNetwork(new XArchEvalUnix(err)); + } +} + +void +ArchNetworkBSD::throwNameError(int err) +{ + static const char* s_msg[] = { + "The specified host is unknown", + "The requested name is valid but does not have an IP address", + "A non-recoverable name server error occurred", + "A temporary error occurred on an authoritative name server", + "An unknown name server error occurred" + }; + + switch (err) { + case HOST_NOT_FOUND: + throw XArchNetworkNameUnknown(s_msg[0]); + + case NO_DATA: + throw XArchNetworkNameNoAddress(s_msg[1]); + + case NO_RECOVERY: + throw XArchNetworkNameFailure(s_msg[2]); + + case TRY_AGAIN: + throw XArchNetworkNameUnavailable(s_msg[3]); + + default: + throw XArchNetworkName(s_msg[4]); + } +} diff --git a/src/lib/arch/unix/ArchNetworkBSD.h b/src/lib/arch/unix/ArchNetworkBSD.h new file mode 100644 index 0000000..3f5679a --- /dev/null +++ b/src/lib/arch/unix/ArchNetworkBSD.h @@ -0,0 +1,105 @@ +/* + * 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/IArchNetwork.h" +#include "arch/IArchMultithread.h" + +#if HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif +#if HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif + +#if !HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +// old systems may use char* for [gs]etsockopt()'s optval argument. +// this should be void on modern systems but char is forwards +// compatible so we always use it. +typedef char optval_t; + +#define ARCH_NETWORK ArchNetworkBSD +#define TYPED_ADDR(type_, addr_) (reinterpret_cast<type_*>(&addr_->m_addr)) + +class ArchSocketImpl { +public: + int m_fd; + int m_refCount; +}; + +class ArchNetAddressImpl { +public: + ArchNetAddressImpl() : m_len(sizeof(m_addr)) { } + +public: + struct sockaddr_storage m_addr; + socklen_t m_len; +}; + +//! Berkeley (BSD) sockets implementation of IArchNetwork +class ArchNetworkBSD : public IArchNetwork { +public: + ArchNetworkBSD(); + virtual ~ArchNetworkBSD(); + + 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: + const int* getUnblockPipe(); + const int* getUnblockPipeForThread(ArchThread); + void setBlockingOnSocket(int fd, bool blocking); + void throwError(int); + void throwNameError(int); + +private: + ArchMutex m_mutex; +}; diff --git a/src/lib/arch/unix/ArchSleepUnix.cpp b/src/lib/arch/unix/ArchSleepUnix.cpp new file mode 100644 index 0000000..48e2600 --- /dev/null +++ b/src/lib/arch/unix/ArchSleepUnix.cpp @@ -0,0 +1,94 @@ +/* + * 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/unix/ArchSleepUnix.h" + +#include "arch/Arch.h" + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif +#if !HAVE_NANOSLEEP +# if HAVE_SYS_SELECT_H +# include <sys/select.h> +# endif +# if HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +# if HAVE_UNISTD_H +# include <unistd.h> +# endif +#endif + +// +// ArchSleepUnix +// + +ArchSleepUnix::ArchSleepUnix() +{ + // do nothing +} + +ArchSleepUnix::~ArchSleepUnix() +{ + // do nothing +} + +void +ArchSleepUnix::sleep(double timeout) +{ + ARCH->testCancelThread(); + if (timeout < 0.0) { + return; + } + +#if HAVE_NANOSLEEP + // prep timeout + struct timespec t; + t.tv_sec = (long)timeout; + t.tv_nsec = (long)(1.0e+9 * (timeout - (double)t.tv_sec)); + + // wait + while (nanosleep(&t, &t) < 0) + ARCH->testCancelThread(); +#else + /* emulate nanosleep() with select() */ + double startTime = ARCH->time(); + double timeLeft = timeout; + while (timeLeft > 0.0) { + struct timeval timeout2; + timeout2.tv_sec = static_cast<int>(timeLeft); + timeout2.tv_usec = static_cast<int>(1.0e+6 * (timeLeft - + timeout2.tv_sec)); + select((SELECT_TYPE_ARG1) 0, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 &timeout2); + ARCH->testCancelThread(); + timeLeft = timeout - (ARCH->time() - startTime); + } +#endif +} diff --git a/src/lib/arch/unix/ArchSleepUnix.h b/src/lib/arch/unix/ArchSleepUnix.h new file mode 100644 index 0000000..3e307a5 --- /dev/null +++ b/src/lib/arch/unix/ArchSleepUnix.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 ArchSleepUnix + +//! Unix implementation of IArchSleep +class ArchSleepUnix : public IArchSleep { +public: + ArchSleepUnix(); + virtual ~ArchSleepUnix(); + + // IArchSleep overrides + virtual void sleep(double timeout); +}; diff --git a/src/lib/arch/unix/ArchStringUnix.cpp b/src/lib/arch/unix/ArchStringUnix.cpp new file mode 100644 index 0000000..591c826 --- /dev/null +++ b/src/lib/arch/unix/ArchStringUnix.cpp @@ -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/>. + */ + +#include "arch/unix/ArchStringUnix.h" + +#include <stdio.h> + +// +// ArchStringUnix +// + +#include "arch/multibyte.h" +#include "arch/vsnprintf.h" + +ArchStringUnix::ArchStringUnix() +{ +} + +ArchStringUnix::~ArchStringUnix() +{ +} + +IArchString::EWideCharEncoding +ArchStringUnix::getWideCharEncoding() +{ + return kUCS4; +} diff --git a/src/lib/arch/unix/ArchStringUnix.h b/src/lib/arch/unix/ArchStringUnix.h new file mode 100644 index 0000000..f7d0035 --- /dev/null +++ b/src/lib/arch/unix/ArchStringUnix.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 ArchStringUnix + +//! Unix implementation of IArchString +class ArchStringUnix : public IArchString { +public: + ArchStringUnix(); + virtual ~ArchStringUnix(); + + // IArchString overrides + virtual EWideCharEncoding + getWideCharEncoding(); +}; diff --git a/src/lib/arch/unix/ArchSystemUnix.cpp b/src/lib/arch/unix/ArchSystemUnix.cpp new file mode 100644 index 0000000..f51e47f --- /dev/null +++ b/src/lib/arch/unix/ArchSystemUnix.cpp @@ -0,0 +1,80 @@ +/* + * 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/unix/ArchSystemUnix.h" + +#include <sys/utsname.h> + +// +// ArchSystemUnix +// + +ArchSystemUnix::ArchSystemUnix() +{ + // do nothing +} + +ArchSystemUnix::~ArchSystemUnix() +{ + // do nothing +} + +std::string +ArchSystemUnix::getOSName() const +{ +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname info; + if (uname(&info) == 0) { + std::string msg; + msg += info.sysname; + msg += " "; + msg += info.release; + return msg; + } +#endif + return "Unix"; +} + +std::string +ArchSystemUnix::getPlatformName() const +{ +#if defined(HAVE_SYS_UTSNAME_H) + struct utsname info; + if (uname(&info) == 0) { + return std::string(info.machine); + } +#endif + return "unknown"; +} + +std::string +ArchSystemUnix::setting(const std::string&) const +{ + return ""; +} + +void +ArchSystemUnix::setting(const std::string&, const std::string&) const +{ +} + +std::string +ArchSystemUnix::getLibsUsed(void) const +{ + return "not implemented.\nuse lsof on shell"; +} diff --git a/src/lib/arch/unix/ArchSystemUnix.h b/src/lib/arch/unix/ArchSystemUnix.h new file mode 100644 index 0000000..aa9c564 --- /dev/null +++ b/src/lib/arch/unix/ArchSystemUnix.h @@ -0,0 +1,38 @@ +/* + * 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 ArchSystemUnix + +//! Unix implementation of IArchString +class ArchSystemUnix : public IArchSystem { +public: + ArchSystemUnix(); + virtual ~ArchSystemUnix(); + + // IArchSystem overrides + virtual std::string getOSName() const; + virtual std::string getPlatformName() const; + virtual std::string setting(const std::string&) const; + virtual void setting(const std::string&, const std::string&) const; + virtual std::string getLibsUsed(void) const; + +}; diff --git a/src/lib/arch/unix/ArchTaskBarXWindows.cpp b/src/lib/arch/unix/ArchTaskBarXWindows.cpp new file mode 100644 index 0000000..c3577ad --- /dev/null +++ b/src/lib/arch/unix/ArchTaskBarXWindows.cpp @@ -0,0 +1,51 @@ +/* + * 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/unix/ArchTaskBarXWindows.h" + +// +// ArchTaskBarXWindows +// + +ArchTaskBarXWindows::ArchTaskBarXWindows() +{ + // do nothing +} + +ArchTaskBarXWindows::~ArchTaskBarXWindows() +{ + // do nothing +} + +void +ArchTaskBarXWindows::addReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +ArchTaskBarXWindows::removeReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} + +void +ArchTaskBarXWindows::updateReceiver(IArchTaskBarReceiver* /*receiver*/) +{ + // do nothing +} diff --git a/src/lib/arch/unix/ArchTaskBarXWindows.h b/src/lib/arch/unix/ArchTaskBarXWindows.h new file mode 100644 index 0000000..f2c8977 --- /dev/null +++ b/src/lib/arch/unix/ArchTaskBarXWindows.h @@ -0,0 +1,35 @@ +/* + * 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" + +#define ARCH_TASKBAR ArchTaskBarXWindows + +//! X11 implementation of IArchTaskBar +class ArchTaskBarXWindows : public IArchTaskBar { +public: + ArchTaskBarXWindows(); + virtual ~ArchTaskBarXWindows(); + + // IArchTaskBar overrides + virtual void addReceiver(IArchTaskBarReceiver*); + virtual void removeReceiver(IArchTaskBarReceiver*); + virtual void updateReceiver(IArchTaskBarReceiver*); +}; diff --git a/src/lib/arch/unix/ArchTimeUnix.cpp b/src/lib/arch/unix/ArchTimeUnix.cpp new file mode 100644 index 0000000..24685aa --- /dev/null +++ b/src/lib/arch/unix/ArchTimeUnix.cpp @@ -0,0 +1,52 @@ +/* + * 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/unix/ArchTimeUnix.h" + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +// +// ArchTimeUnix +// + +ArchTimeUnix::ArchTimeUnix() +{ + // do nothing +} + +ArchTimeUnix::~ArchTimeUnix() +{ + // do nothing +} + +double +ArchTimeUnix::time() +{ + struct timeval t; + gettimeofday(&t, NULL); + return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec; +} diff --git a/src/lib/arch/unix/ArchTimeUnix.h b/src/lib/arch/unix/ArchTimeUnix.h new file mode 100644 index 0000000..3c5c0f8 --- /dev/null +++ b/src/lib/arch/unix/ArchTimeUnix.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 ArchTimeUnix + +//! Generic Unix implementation of IArchTime +class ArchTimeUnix : public IArchTime { +public: + ArchTimeUnix(); + virtual ~ArchTimeUnix(); + + // IArchTime overrides + virtual double time(); +}; diff --git a/src/lib/arch/unix/XArchUnix.cpp b/src/lib/arch/unix/XArchUnix.cpp new file mode 100644 index 0000000..fc7ff65 --- /dev/null +++ b/src/lib/arch/unix/XArchUnix.cpp @@ -0,0 +1,32 @@ +/* + * 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/unix/XArchUnix.h" + +#include <cstring> + +// +// XArchEvalUnix +// + +std::string +XArchEvalUnix::eval() const +{ + // FIXME -- not thread safe + return strerror(m_error); +} diff --git a/src/lib/arch/unix/XArchUnix.h b/src/lib/arch/unix/XArchUnix.h new file mode 100644 index 0000000..ae62f4c --- /dev/null +++ b/src/lib/arch/unix/XArchUnix.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/XArch.h" + +//! Lazy error message string evaluation for unix +class XArchEvalUnix : public XArchEval { +public: + XArchEvalUnix(int error) : m_error(error) { } + virtual ~XArchEvalUnix() _NOEXCEPT { } + + virtual std::string eval() const; + +private: + int m_error; +}; diff --git a/src/lib/arch/vsnprintf.h b/src/lib/arch/vsnprintf.h new file mode 100644 index 0000000..5a4e3dc --- /dev/null +++ b/src/lib/arch/vsnprintf.h @@ -0,0 +1,67 @@ +/* + * 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/IArchString.h" + +#if HAVE_VSNPRINTF + +#if !defined(ARCH_VSNPRINTF) +# define ARCH_VSNPRINTF vsnprintf +#endif + +int +IArchString::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + int n = ::ARCH_VSNPRINTF(str, size, fmt, ap); + if (n > size) { + n = -1; + } + return n; +} + +#elif SYSAPI_UNIX // !HAVE_VSNPRINTF + +#include <stdio.h> + +int +IArchString::vsnprintf(char* str, int size, const char* fmt, va_list ap) +{ + static FILE* bitbucket = fopen("/dev/null", "w"); + if (bitbucket == NULL) { + // uh oh + if (size > 0) { + str[0] = '\0'; + } + return 0; + } + else { + // count the characters using the bitbucket + int n = vfprintf(bitbucket, fmt, ap); + if (n + 1 <= size) { + // it'll fit so print it into str + vsprintf(str, fmt, ap); + } + return n; + } +} + +#else // !HAVE_VSNPRINTF && !SYSAPI_UNIX + +#error vsnprintf not implemented + +#endif // !HAVE_VSNPRINTF 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; +}; |
