diff options
Diffstat (limited to 'src/lib')
421 files changed, 76649 insertions, 0 deletions
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt new file mode 100644 index 0000000..70a0629 --- /dev/null +++ b/src/lib/CMakeLists.txt @@ -0,0 +1,27 @@ +# 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/>. + +add_subdirectory(arch) +add_subdirectory(base) +add_subdirectory(client) +add_subdirectory(common) +add_subdirectory(io) +add_subdirectory(ipc) +add_subdirectory(mt) +add_subdirectory(net) +add_subdirectory(platform) +add_subdirectory(server) +add_subdirectory(barrier) 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; +}; diff --git a/src/lib/barrier/App.cpp b/src/lib/barrier/App.cpp new file mode 100644 index 0000000..1f4eda3 --- /dev/null +++ b/src/lib/barrier/App.cpp @@ -0,0 +1,329 @@ +/* + * 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 "barrier/App.h" + +#include "base/Log.h" +#include "common/Version.h" +#include "barrier/protocol_types.h" +#include "arch/Arch.h" +#include "base/XBase.h" +#include "arch/XArch.h" +#include "base/log_outputters.h" +#include "barrier/XBarrier.h" +#include "barrier/ArgsBase.h" +#include "ipc/IpcServerProxy.h" +#include "base/TMethodEventJob.h" +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" +#include "base/EventQueue.h" + +#if SYSAPI_WIN32 +#include "arch/win32/ArchMiscWindows.h" +#include "base/IEventQueue.h" +#include "base/TMethodJob.h" +#endif + +#include <iostream> +#include <stdio.h> + +#if WINAPI_CARBON +#include <ApplicationServices/ApplicationServices.h> +#endif + +#if defined(__APPLE__) +#include "platform/OSXDragSimulator.h" +#endif + +App* App::s_instance = nullptr; + +// +// App +// + +App::App(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver, ArgsBase* args) : + m_bye(&exit), + m_taskBarReceiver(NULL), + m_suspended(false), + m_events(events), + m_args(args), + m_fileLog(nullptr), + m_createTaskBarReceiver(createTaskBarReceiver), + m_appUtil(events), + m_ipcClient(nullptr), + m_socketMultiplexer(nullptr) +{ + assert(s_instance == nullptr); + s_instance = this; +} + +App::~App() +{ + s_instance = nullptr; + delete m_args; +} + +void +App::version() +{ + char buffer[500]; + sprintf( + buffer, + "%s %s, protocol version %d.%d\n%s", + argsBase().m_pname, + kVersion, + kProtocolMajorVersion, + kProtocolMinorVersion, + kCopyright + ); + + std::cout << buffer << std::endl; +} + +int +App::run(int argc, char** argv) +{ +#if MAC_OS_X_VERSION_10_7 + // dock hide only supported on lion :( + ProcessSerialNumber psn = { 0, kCurrentProcess }; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + GetCurrentProcess(&psn); +#pragma GCC diagnostic pop + + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); +#endif + + // install application in to arch + appUtil().adoptApp(this); + + // HACK: fail by default (saves us setting result in each catch) + int result = kExitFailed; + + try { + result = appUtil().run(argc, argv); + } + catch (XExitApp& e) { + // instead of showing a nasty error, just exit with the error code. + // not sure if i like this behaviour, but it's probably better than + // using the exit(int) function! + result = e.getCode(); + } + catch (std::exception& e) { + LOG((CLOG_CRIT "An error occurred: %s\n", e.what())); + } + catch (...) { + LOG((CLOG_CRIT "An unknown error occurred.\n")); + } + + appUtil().beforeAppExit(); + + return result; +} + +int +App::daemonMainLoop(int, const char**) +{ +#if SYSAPI_WIN32 + SystemLogger sysLogger(daemonName(), false); +#else + SystemLogger sysLogger(daemonName(), true); +#endif + return mainLoop(); +} + +void +App::setupFileLogging() +{ + if (argsBase().m_logFile != NULL) { + m_fileLog = new FileLogOutputter(argsBase().m_logFile); + CLOG->insert(m_fileLog); + LOG((CLOG_DEBUG1 "logging to file (%s) enabled", argsBase().m_logFile)); + } +} + +void +App::loggingFilterWarning() +{ + if (CLOG->getFilter() > CLOG->getConsoleMaxLevel()) { + if (argsBase().m_logFile == NULL) { + LOG((CLOG_WARN "log messages above %s are NOT sent to console (use file logging)", + CLOG->getFilterName(CLOG->getConsoleMaxLevel()))); + } + } +} + +void +App::initApp(int argc, const char** argv) +{ + // parse command line + parseArgs(argc, argv); + + ARCH->setProfileDirectory(argsBase().m_profileDirectory); + ARCH->setPluginDirectory(argsBase().m_pluginDirectory); + + // set log filter + if (!CLOG->setFilter(argsBase().m_logFilter)) { + LOG((CLOG_PRINT "%s: unrecognized log level `%s'" BYE, + argsBase().m_pname, argsBase().m_logFilter, argsBase().m_pname)); + m_bye(kExitArgs); + } + loggingFilterWarning(); + + if (argsBase().m_enableDragDrop) { + LOG((CLOG_INFO "drag and drop enabled")); + } + + // setup file logging after parsing args + setupFileLogging(); + + // load configuration + loadConfig(); + + if (!argsBase().m_disableTray) { + + // create a log buffer so we can show the latest message + // as a tray icon tooltip + BufferedLogOutputter* logBuffer = new BufferedLogOutputter(1000); + CLOG->insert(logBuffer, true); + + // make the task bar receiver. the user can control this app + // through the task bar. + m_taskBarReceiver = m_createTaskBarReceiver(logBuffer, m_events); + } +} + +void +App::initIpcClient() +{ + m_ipcClient = new IpcClient(m_events, m_socketMultiplexer); + m_ipcClient->connect(); + + m_events->adoptHandler( + m_events->forIpcClient().messageReceived(), m_ipcClient, + new TMethodEventJob<App>(this, &App::handleIpcMessage)); +} + +void +App::cleanupIpcClient() +{ + m_ipcClient->disconnect(); + m_events->removeHandler(m_events->forIpcClient().messageReceived(), m_ipcClient); + delete m_ipcClient; +} + +void +App::handleIpcMessage(const Event& e, void*) +{ + IpcMessage* m = static_cast<IpcMessage*>(e.getDataObject()); + if (m->type() == kIpcShutdown) { + LOG((CLOG_INFO "got ipc shutdown message")); + m_events->addEvent(Event(Event::kQuit)); + } +} + +void +App::runEventsLoop(void*) +{ + m_events->loop(); + +#if defined(MAC_OS_X_VERSION_10_7) + + stopCocoaLoop(); + +#endif +} + +// +// MinimalApp +// + +MinimalApp::MinimalApp() : + App(NULL, NULL, new ArgsBase()) +{ + m_arch.init(); + setEvents(m_events); +} + +MinimalApp::~MinimalApp() +{ +} + +int +MinimalApp::standardStartup(int argc, char** argv) +{ + return 0; +} + +int +MinimalApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) +{ + return 0; +} + +void +MinimalApp::startNode() +{ +} + +int +MinimalApp::mainLoop() +{ + return 0; +} + +int +MinimalApp::foregroundStartup(int argc, char** argv) +{ + return 0; +} + +barrier::Screen* +MinimalApp::createScreen() +{ + return NULL; +} + +void +MinimalApp::loadConfig() +{ +} + +bool +MinimalApp::loadConfig(const String& pathname) +{ + return false; +} + +const char* +MinimalApp::daemonInfo() const +{ + return ""; +} + +const char* +MinimalApp::daemonName() const +{ + return ""; +} + +void +MinimalApp::parseArgs(int argc, const char* const* argv) +{ +} diff --git a/src/lib/barrier/App.h b/src/lib/barrier/App.h new file mode 100644 index 0000000..b7c77a0 --- /dev/null +++ b/src/lib/barrier/App.h @@ -0,0 +1,200 @@ +/* + * 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 "ipc/IpcClient.h" +#include "barrier/IApp.h" +#include "base/String.h" +#include "base/Log.h" +#include "base/EventQueue.h" +#include "common/common.h" + +#if SYSAPI_WIN32 +#include "barrier/win32/AppUtilWindows.h" +#elif SYSAPI_UNIX +#include "barrier/unix/AppUtilUnix.h" +#endif + +class IArchTaskBarReceiver; +class BufferedLogOutputter; +class ILogOutputter; +class FileLogOutputter; +namespace barrier { class Screen; } +class IEventQueue; +class SocketMultiplexer; + +typedef IArchTaskBarReceiver* (*CreateTaskBarReceiverFunc)(const BufferedLogOutputter*, IEventQueue* events); + +class App : public IApp { +public: + App(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver, ArgsBase* args); + virtual ~App(); + + // Returns args that are common between server and client. + ArgsBase& argsBase() const { return *m_args; } + + // Prints the current compiled version. + virtual void version(); + + // Prints help specific to client or server. + virtual void help() = 0; + + // Parse command line arguments. + virtual void parseArgs(int argc, const char* const* argv) = 0; + + int run(int argc, char** argv); + + int daemonMainLoop(int, const char**); + + virtual void loadConfig() = 0; + virtual bool loadConfig(const String& pathname) = 0; + + // A description of the daemon (used only on Windows). + virtual const char* daemonInfo() const = 0; + + // Function pointer for function to exit immediately. + // TODO: this is old C code - use inheritance to normalize + void (*m_bye)(int); + + static App& instance() { assert(s_instance != nullptr); return *s_instance; } + + // If --log was specified in args, then add a file logger. + void setupFileLogging(); + + // If messages will be hidden (to improve performance), warn user. + void loggingFilterWarning(); + + // Parses args, sets up file logging, and loads the config. + void initApp(int argc, const char** argv); + + // HACK: accept non-const, but make it const anyway + void initApp(int argc, char** argv) { initApp(argc, (const char**)argv); } + + ARCH_APP_UTIL& appUtil() { return m_appUtil; } + + virtual IArchTaskBarReceiver* taskBarReceiver() const { return m_taskBarReceiver; } + + virtual void setByeFunc(void(*bye)(int)) { m_bye = bye; } + virtual void bye(int error) { m_bye(error); } + + virtual IEventQueue* getEvents() const { return m_events; } + + void setSocketMultiplexer(SocketMultiplexer* sm) { m_socketMultiplexer = sm; } + SocketMultiplexer* getSocketMultiplexer() const { return m_socketMultiplexer; } + + void setEvents(EventQueue& events) { m_events = &events; } + +private: + void handleIpcMessage(const Event&, void*); + +protected: + void initIpcClient(); + void cleanupIpcClient(); + void runEventsLoop(void*); + + IArchTaskBarReceiver* m_taskBarReceiver; + bool m_suspended; + IEventQueue* m_events; + +private: + ArgsBase* m_args; + static App* s_instance; + FileLogOutputter* m_fileLog; + CreateTaskBarReceiverFunc m_createTaskBarReceiver; + ARCH_APP_UTIL m_appUtil; + IpcClient* m_ipcClient; + SocketMultiplexer* m_socketMultiplexer; +}; + +class MinimalApp : public App { +public: + MinimalApp(); + virtual ~MinimalApp(); + + // IApp overrides + virtual int standardStartup(int argc, char** argv); + virtual int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup); + virtual void startNode(); + virtual int mainLoop(); + virtual int foregroundStartup(int argc, char** argv); + virtual barrier::Screen* + createScreen(); + virtual void loadConfig(); + virtual bool loadConfig(const String& pathname); + virtual const char* daemonInfo() const; + virtual const char* daemonName() const; + virtual void parseArgs(int argc, const char* const* argv); + +private: + Arch m_arch; + Log m_log; + EventQueue m_events; +}; + +#if WINAPI_MSWINDOWS +#define DAEMON_RUNNING(running_) ArchMiscWindows::daemonRunning(running_) +#else +#define DAEMON_RUNNING(running_) +#endif + +#define HELP_COMMON_INFO_1 \ + " -d, --debug <level> filter out log messages with priority below level.\n" \ + " level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n" \ + " DEBUG, DEBUG1, DEBUG2.\n" \ + " -n, --name <screen-name> use screen-name instead the hostname to identify\n" \ + " this screen in the configuration.\n" \ + " -1, --no-restart do not try to restart on failure.\n" \ + " --restart restart the server automatically if it fails. (*)\n" \ + " -l --log <file> write log messages to file.\n" \ + " --no-tray disable the system tray icon.\n" \ + " --enable-drag-drop enable file drag & drop.\n" \ + " --enable-crypto enable the crypto (ssl) plugin.\n" + +#define HELP_COMMON_INFO_2 \ + " -h, --help display this help and exit.\n" \ + " --version display version information and exit.\n" + +#define HELP_COMMON_ARGS \ + " [--name <screen-name>]" \ + " [--restart|--no-restart]" \ + " [--debug <level>]" + +// system args (windows/unix) +#if SYSAPI_UNIX + +// unix daemon mode args +# define HELP_SYS_ARGS \ + " [--daemon|--no-daemon]" +# define HELP_SYS_INFO \ + " -f, --no-daemon run in the foreground.\n" \ + " --daemon run as a daemon. (*)\n" + +#elif SYSAPI_WIN32 + +// windows args +# define HELP_SYS_ARGS \ + " [--service <action>] [--relaunch] [--exit-pause]" +# define HELP_SYS_INFO \ + " --service <action> manage the windows service, valid options are:\n" \ + " install/uninstall/start/stop\n" \ + " --relaunch persistently relaunches process in current user \n" \ + " session (useful for vista and upward).\n" \ + " --exit-pause wait for key press on exit, can be useful for\n" \ + " reading error messages that occur on exit.\n" +#endif diff --git a/src/lib/barrier/AppUtil.cpp b/src/lib/barrier/AppUtil.cpp new file mode 100644 index 0000000..3298d7b --- /dev/null +++ b/src/lib/barrier/AppUtil.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 "barrier/AppUtil.h" + +AppUtil* AppUtil::s_instance = nullptr; + +AppUtil::AppUtil() : +m_app(nullptr) +{ + s_instance = this; +} + +AppUtil::~AppUtil() +{ +} + +void +AppUtil::adoptApp(IApp* app) +{ + app->setByeFunc(&exitAppStatic); + m_app = app; +} + +IApp& +AppUtil::app() const +{ + assert(m_app != nullptr); + return *m_app; +} + +AppUtil& +AppUtil::instance() +{ + assert(s_instance != nullptr); + return *s_instance; +} diff --git a/src/lib/barrier/AppUtil.h b/src/lib/barrier/AppUtil.h new file mode 100644 index 0000000..6f5f073 --- /dev/null +++ b/src/lib/barrier/AppUtil.h @@ -0,0 +1,40 @@ +/* + * 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 "barrier/IAppUtil.h" +#include "barrier/XBarrier.h" + +class AppUtil : public IAppUtil { +public: + AppUtil(); + virtual ~AppUtil(); + + virtual void adoptApp(IApp* app); + IApp& app() const; + virtual void exitApp(int code) { throw XExitApp(code); } + + static AppUtil& instance(); + static void exitAppStatic(int code) { instance().exitApp(code); } + virtual void beforeAppExit() {} + +private: + IApp* m_app; + static AppUtil* s_instance; +}; diff --git a/src/lib/barrier/ArgParser.cpp b/src/lib/barrier/ArgParser.cpp new file mode 100644 index 0000000..20e849c --- /dev/null +++ b/src/lib/barrier/ArgParser.cpp @@ -0,0 +1,519 @@ +/* + * 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 "barrier/ArgParser.h" + +#include "barrier/StreamChunker.h" +#include "barrier/App.h" +#include "barrier/ServerArgs.h" +#include "barrier/ClientArgs.h" +#include "barrier/ToolArgs.h" +#include "barrier/ArgsBase.h" +#include "base/Log.h" +#include "base/String.h" + +#ifdef WINAPI_MSWINDOWS +#include <VersionHelpers.h> +#endif + +ArgsBase* ArgParser::m_argsBase = NULL; + +ArgParser::ArgParser(App* app) : + m_app(app) +{ +} + +bool +ArgParser::parseServerArgs(ServerArgs& args, int argc, const char* const* argv) +{ + setArgsBase(args); + updateCommonArgs(argv); + + for (int i = 1; i < argc; ++i) { + if (parsePlatformArg(args, argc, argv, i)) { + continue; + } + else if (parseGenericArgs(argc, argv, i)) { + continue; + } + else if (parseDeprecatedArgs(argc, argv, i)) { + continue; + } + else if (isArg(i, argc, argv, "-a", "--address", 1)) { + // save listen address + args.m_barrierAddress = argv[++i]; + } + else if (isArg(i, argc, argv, "-c", "--config", 1)) { + // save configuration file path + args.m_configFile = argv[++i]; + } + else { + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_pname, argv[i], args.m_pname)); + return false; + } + } + + if (checkUnexpectedArgs()) { + return false; + } + + return true; +} + +bool +ArgParser::parseClientArgs(ClientArgs& args, int argc, const char* const* argv) +{ + setArgsBase(args); + updateCommonArgs(argv); + + int i; + for (i = 1; i < argc; ++i) { + if (parsePlatformArg(args, argc, argv, i)) { + continue; + } + else if (parseGenericArgs(argc, argv, i)) { + continue; + } + else if (parseDeprecatedArgs(argc, argv, i)) { + continue; + } + else if (isArg(i, argc, argv, NULL, "--camp")) { + // ignore -- included for backwards compatibility + } + else if (isArg(i, argc, argv, NULL, "--no-camp")) { + // ignore -- included for backwards compatibility + } + else if (isArg(i, argc, argv, NULL, "--yscroll", 1)) { + // define scroll + args.m_yscroll = atoi(argv[++i]); + } + else { + if (i + 1 == argc) { + args.m_barrierAddress = argv[i]; + return true; + } + + LOG((CLOG_PRINT "%s: unrecognized option `%s'" BYE, args.m_pname, argv[i], args.m_pname)); + return false; + } + } + + if (args.m_shouldExit) + return true; + + // exactly one non-option argument (server-address) + if (i == argc) { + LOG((CLOG_PRINT "%s: a server address or name is required" BYE, + args.m_pname, args.m_pname)); + return false; + } + + if (checkUnexpectedArgs()) { + return false; + } + + return true; +} + +bool +ArgParser::parsePlatformArg(ArgsBase& argsBase, const int& argc, const char* const* argv, int& i) +{ +#if WINAPI_MSWINDOWS + if (isArg(i, argc, argv, NULL, "--service")) { + LOG((CLOG_WARN "obsolete argument --service, use barrierd instead.")); + argsBase.m_shouldExit = true; + } + else if (isArg(i, argc, argv, NULL, "--exit-pause")) { + argsBase.m_pauseOnExit = true; + } + else if (isArg(i, argc, argv, NULL, "--stop-on-desk-switch")) { + argsBase.m_stopOnDeskSwitch = true; + } + else { + // option not supported here + return false; + } + + return true; +#elif WINAPI_XWINDOWS + if (isArg(i, argc, argv, "-display", "--display", 1)) { + // use alternative display + argsBase.m_display = argv[++i]; + } + + else if (isArg(i, argc, argv, NULL, "--no-xinitthreads")) { + argsBase.m_disableXInitThreads = true; + } + + else { + // option not supported here + return false; + } + + return true; +#elif WINAPI_CARBON + // no options for carbon + return false; +#endif +} + +bool +ArgParser::parseToolArgs(ToolArgs& args, int argc, const char* const* argv) +{ + for (int i = 1; i < argc; ++i) { + if (isArg(i, argc, argv, NULL, "--get-active-desktop", 0)) { + args.m_printActiveDesktopName = true; + return true; + } + else if (isArg(i, argc, argv, NULL, "--login-auth", 0)) { + args.m_loginAuthenticate = true; + return true; + } + else if (isArg(i, argc, argv, NULL, "--get-installed-dir", 0)) { + args.m_getInstalledDir = true; + return true; + } + else if (isArg(i, argc, argv, NULL, "--get-profile-dir", 0)) { + args.m_getProfileDir = true; + return true; + } + else if (isArg(i, argc, argv, NULL, "--get-arch", 0)) { + args.m_getArch = true; + return true; + } + else if (isArg(i, argc, argv, NULL, "--notify-activation", 0)) { + args.m_notifyActivation = true; + return true; + } + else if (isArg(i, argc, argv, NULL, "--notify-update", 0)) { + args.m_notifyUpdate = true; + return true; + } + else { + return false; + } + } + + return false; +} + +bool +ArgParser::parseGenericArgs(int argc, const char* const* argv, int& i) +{ + if (isArg(i, argc, argv, "-d", "--debug", 1)) { + // change logging level + argsBase().m_logFilter = argv[++i]; + } + else if (isArg(i, argc, argv, "-l", "--log", 1)) { + argsBase().m_logFile = argv[++i]; + } + else if (isArg(i, argc, argv, "-f", "--no-daemon")) { + // not a daemon + argsBase().m_daemon = false; + } + else if (isArg(i, argc, argv, NULL, "--daemon")) { + // daemonize + argsBase().m_daemon = true; + } + else if (isArg(i, argc, argv, "-n", "--name", 1)) { + // save screen name + argsBase().m_name = argv[++i]; + } + else if (isArg(i, argc, argv, "-1", "--no-restart")) { + // don't try to restart + argsBase().m_restartable = false; + } + else if (isArg(i, argc, argv, NULL, "--restart")) { + // try to restart + argsBase().m_restartable = true; + } + else if (isArg(i, argc, argv, "-z", NULL)) { + argsBase().m_backend = true; + } + else if (isArg(i, argc, argv, NULL, "--no-hooks")) { + argsBase().m_noHooks = true; + } + else if (isArg(i, argc, argv, "-h", "--help")) { + if (m_app) { + m_app->help(); + } + argsBase().m_shouldExit = true; + } + else if (isArg(i, argc, argv, NULL, "--version")) { + if (m_app) { + m_app->version(); + } + argsBase().m_shouldExit = true; + } + else if (isArg(i, argc, argv, NULL, "--no-tray")) { + argsBase().m_disableTray = true; + } + else if (isArg(i, argc, argv, NULL, "--ipc")) { + argsBase().m_enableIpc = true; + } + else if (isArg(i, argc, argv, NULL, "--server")) { + // HACK: stop error happening when using portable (barrierp) + } + else if (isArg(i, argc, argv, NULL, "--client")) { + // HACK: stop error happening when using portable (barrierp) + } + else if (isArg(i, argc, argv, NULL, "--enable-drag-drop")) { + bool useDragDrop = true; + +#ifdef WINAPI_XWINDOWS + + useDragDrop = false; + LOG((CLOG_INFO "ignoring --enable-drag-drop, not supported on linux.")); + +#endif + +#ifdef WINAPI_MSWINDOWS + + if (!IsWindowsVistaOrGreater()) { + useDragDrop = false; + LOG((CLOG_INFO "ignoring --enable-drag-drop, not supported below vista.")); + } +#endif + + if (useDragDrop) { + argsBase().m_enableDragDrop = true; + } + } + else if (isArg(i, argc, argv, NULL, "--enable-crypto")) { + argsBase().m_enableCrypto = true; + } + else if (isArg(i, argc, argv, NULL, "--profile-dir", 1)) { + argsBase().m_profileDirectory = argv[++i]; + } + else if (isArg(i, argc, argv, NULL, "--plugin-dir", 1)) { + argsBase().m_pluginDirectory = argv[++i]; + } + else { + // option not supported here + return false; + } + + return true; +} + +bool +ArgParser::parseDeprecatedArgs(int argc, const char* const* argv, int& i) +{ + if (isArg(i, argc, argv, NULL, "--crypto-pass")) { + LOG((CLOG_NOTE "--crypto-pass is deprecated")); + i++; + return true; + } + else if (isArg(i, argc, argv, NULL, "--res-w")) { + LOG((CLOG_NOTE "--res-w is deprecated")); + i++; + return true; + } + else if (isArg(i, argc, argv, NULL, "--res-h")) { + LOG((CLOG_NOTE "--res-h is deprecated")); + i++; + return true; + } + else if (isArg(i, argc, argv, NULL, "--prm-wc")) { + LOG((CLOG_NOTE "--prm-wc is deprecated")); + i++; + return true; + } + else if (isArg(i, argc, argv, NULL, "--prm-hc")) { + LOG((CLOG_NOTE "--prm-hc is deprecated")); + i++; + return true; + } + + return false; +} + +bool +ArgParser::isArg( + int argi, int argc, const char* const* argv, + const char* name1, const char* name2, + int minRequiredParameters) +{ + if ((name1 != NULL && strcmp(argv[argi], name1) == 0) || + (name2 != NULL && strcmp(argv[argi], name2) == 0)) { + // match. check args left. + if (argi + minRequiredParameters >= argc) { + LOG((CLOG_PRINT "%s: missing arguments for `%s'" BYE, + argsBase().m_pname, argv[argi], argsBase().m_pname)); + argsBase().m_shouldExit = true; + return false; + } + return true; + } + + // no match + return false; +} + +void +ArgParser::splitCommandString(String& command, std::vector<String>& argv) +{ + if (command.empty()) { + return ; + } + + size_t leftDoubleQuote = 0; + size_t rightDoubleQuote = 0; + searchDoubleQuotes(command, leftDoubleQuote, rightDoubleQuote); + + size_t startPos = 0; + size_t space = command.find(" ", startPos); + + while (space != String::npos) { + bool ignoreThisSpace = false; + + // check if the space is between two double quotes + if (space > leftDoubleQuote && space < rightDoubleQuote) { + ignoreThisSpace = true; + } + else if (space > rightDoubleQuote){ + searchDoubleQuotes(command, leftDoubleQuote, rightDoubleQuote, rightDoubleQuote + 1); + } + + if (!ignoreThisSpace) { + String subString = command.substr(startPos, space - startPos); + + removeDoubleQuotes(subString); + argv.push_back(subString); + } + + // find next space + if (ignoreThisSpace) { + space = command.find(" ", rightDoubleQuote + 1); + } + else { + startPos = space + 1; + space = command.find(" ", startPos); + } + } + + String subString = command.substr(startPos, command.size()); + removeDoubleQuotes(subString); + argv.push_back(subString); +} + +bool +ArgParser::searchDoubleQuotes(String& command, size_t& left, size_t& right, size_t startPos) +{ + bool result = false; + left = String::npos; + right = String::npos; + + left = command.find("\"", startPos); + if (left != String::npos) { + right = command.find("\"", left + 1); + if (right != String::npos) { + result = true; + } + } + + if (!result) { + left = 0; + right = 0; + } + + return result; +} + +void +ArgParser::removeDoubleQuotes(String& arg) +{ + // if string is surrounded by double quotes, remove them + if (arg[0] == '\"' && + arg[arg.size() - 1] == '\"') { + arg = arg.substr(1, arg.size() - 2); + } +} + +const char** +ArgParser::getArgv(std::vector<String>& argsArray) +{ + size_t argc = argsArray.size(); + + // caller is responsible for deleting the outer array only + // we use the c string pointers from argsArray and assign + // them to the inner array. So caller only need to use + // delete[] to delete the outer array + const char** argv = new const char*[argc]; + + for (size_t i = 0; i < argc; i++) { + argv[i] = argsArray[i].c_str(); + } + + return argv; +} + +String +ArgParser::assembleCommand(std::vector<String>& argsArray, String ignoreArg, int parametersRequired) +{ + String result; + + for (std::vector<String>::iterator it = argsArray.begin(); it != argsArray.end(); ++it) { + if (it->compare(ignoreArg) == 0) { + it = it + parametersRequired; + continue; + } + + // if there is a space in this arg, use double quotes surround it + if ((*it).find(" ") != String::npos) { + (*it).insert(0, "\""); + (*it).push_back('\"'); + } + + result.append(*it); + // add space to saperate args + result.append(" "); + } + + if (!result.empty()) { + // remove the tail space + result = result.substr(0, result.size() - 1); + } + + return result; +} + +void +ArgParser::updateCommonArgs(const char* const* argv) +{ + argsBase().m_name = ARCH->getHostName(); + argsBase().m_pname = ARCH->getBasename(argv[0]); +} + +bool +ArgParser::checkUnexpectedArgs() +{ +#if SYSAPI_WIN32 + // suggest that user installs as a windows service. when launched as + // service, process should automatically detect that it should run in + // daemon mode. + if (argsBase().m_daemon) { + LOG((CLOG_ERR + "the --daemon argument is not supported on windows. " + "instead, install %s as a service (--service install)", + argsBase().m_pname)); + return true; + } +#endif + + return false; +} diff --git a/src/lib/barrier/ArgParser.h b/src/lib/barrier/ArgParser.h new file mode 100644 index 0000000..5fc2649 --- /dev/null +++ b/src/lib/barrier/ArgParser.h @@ -0,0 +1,63 @@ +/* + * 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 + +#include "base/String.h" +#include "common/stdvector.h" + +class ServerArgs; +class ClientArgs; +class ToolArgs; +class ArgsBase; +class App; + +class ArgParser { + +public: + ArgParser(App* app); + + bool parseServerArgs(ServerArgs& args, int argc, const char* const* argv); + bool parseClientArgs(ClientArgs& args, int argc, const char* const* argv); + bool parsePlatformArg(ArgsBase& argsBase, const int& argc, const char* const* argv, int& i); + bool parseToolArgs(ToolArgs& args, int argc, const char* const* argv); + bool parseGenericArgs(int argc, const char* const* argv, int& i); + bool parseDeprecatedArgs(int argc, const char* const* argv, int& i); + void setArgsBase(ArgsBase& argsBase) { m_argsBase = &argsBase; } + + static bool isArg(int argi, int argc, const char* const* argv, + const char* name1, const char* name2, + int minRequiredParameters = 0); + static void splitCommandString(String& command, std::vector<String>& argv); + static bool searchDoubleQuotes(String& command, size_t& left, + size_t& right, size_t startPos = 0); + static void removeDoubleQuotes(String& arg); + static const char** getArgv(std::vector<String>& argsArray); + static String assembleCommand(std::vector<String>& argsArray, + String ignoreArg = "", int parametersRequired = 0); + +private: + void updateCommonArgs(const char* const* argv); + bool checkUnexpectedArgs(); + + static ArgsBase& argsBase() { return *m_argsBase; } + +private: + App* m_app; + + static ArgsBase* m_argsBase; +}; diff --git a/src/lib/barrier/ArgsBase.cpp b/src/lib/barrier/ArgsBase.cpp new file mode 100644 index 0000000..eedb312 --- /dev/null +++ b/src/lib/barrier/ArgsBase.cpp @@ -0,0 +1,53 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "barrier/ArgsBase.h" + +ArgsBase::ArgsBase() : +#if SYSAPI_WIN32 +m_daemon(false), // daemon mode not supported on windows (use --service) +m_debugServiceWait(false), +m_pauseOnExit(false), +m_stopOnDeskSwitch(false), +#else +m_daemon(true), // backward compatibility for unix (daemon by default) +#endif +#if WINAPI_XWINDOWS +m_disableXInitThreads(false), +#endif +m_backend(false), +m_restartable(true), +m_noHooks(false), +m_pname(NULL), +m_logFilter(NULL), +m_logFile(NULL), +m_display(NULL), +m_disableTray(false), +m_enableIpc(false), +m_enableDragDrop(false), +m_shouldExit(false), +m_barrierAddress(), +m_enableCrypto(false), +m_profileDirectory(""), +m_pluginDirectory("") +{ +} + +ArgsBase::~ArgsBase() +{ +} diff --git a/src/lib/barrier/ArgsBase.h b/src/lib/barrier/ArgsBase.h new file mode 100644 index 0000000..1f49984 --- /dev/null +++ b/src/lib/barrier/ArgsBase.h @@ -0,0 +1,54 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "base/String.h" + +class ArgsBase { +public: + ArgsBase(); + virtual ~ArgsBase(); + +public: + bool m_daemon; + bool m_backend; + bool m_restartable; + bool m_noHooks; + const char* m_pname; + const char* m_logFilter; + const char* m_logFile; + const char* m_display; + String m_name; + bool m_disableTray; + bool m_enableIpc; + bool m_enableDragDrop; +#if SYSAPI_WIN32 + bool m_debugServiceWait; + bool m_pauseOnExit; + bool m_stopOnDeskSwitch; +#endif +#if WINAPI_XWINDOWS + bool m_disableXInitThreads; +#endif + bool m_shouldExit; + String m_barrierAddress; + bool m_enableCrypto; + String m_profileDirectory; + String m_pluginDirectory; +}; diff --git a/src/lib/barrier/CMakeLists.txt b/src/lib/barrier/CMakeLists.txt new file mode 100644 index 0000000..6978aef --- /dev/null +++ b/src/lib/barrier/CMakeLists.txt @@ -0,0 +1,40 @@ +# 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") + +# 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(synlib STATIC ${sources}) + +if (UNIX) + target_link_libraries(synlib arch client ipc net base platform mt server) +endif() diff --git a/src/lib/barrier/Chunk.cpp b/src/lib/barrier/Chunk.cpp new file mode 100644 index 0000000..f11bff5 --- /dev/null +++ b/src/lib/barrier/Chunk.cpp @@ -0,0 +1,30 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 "barrier/Chunk.h" +#include "base/String.h" + +Chunk::Chunk(size_t size): m_dataSize(0) +{ + m_chunk = new char[size]; + memset(m_chunk, 0, size); +} + +Chunk::~Chunk() +{ + delete[] m_chunk; +} diff --git a/src/lib/barrier/Chunk.h b/src/lib/barrier/Chunk.h new file mode 100644 index 0000000..42b85bf --- /dev/null +++ b/src/lib/barrier/Chunk.h @@ -0,0 +1,30 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 + +#include "common/basic_types.h" + +class Chunk { +public: + Chunk(size_t size); + ~Chunk(); + +public: + size_t m_dataSize; + char* m_chunk; +}; diff --git a/src/lib/barrier/ClientApp.cpp b/src/lib/barrier/ClientApp.cpp new file mode 100644 index 0000000..87686f2 --- /dev/null +++ b/src/lib/barrier/ClientApp.cpp @@ -0,0 +1,560 @@ +/* + * 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 "barrier/ClientApp.h" + +#include "client/Client.h" +#include "barrier/ArgParser.h" +#include "barrier/protocol_types.h" +#include "barrier/Screen.h" +#include "barrier/XScreen.h" +#include "barrier/ClientArgs.h" +#include "net/NetworkAddress.h" +#include "net/TCPSocketFactory.h" +#include "net/SocketMultiplexer.h" +#include "net/XSocket.h" +#include "mt/Thread.h" +#include "arch/IArchTaskBarReceiver.h" +#include "arch/Arch.h" +#include "base/String.h" +#include "base/Event.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/log_outputters.h" +#include "base/EventQueue.h" +#include "base/TMethodJob.h" +#include "base/Log.h" +#include "common/Version.h" + +#if SYSAPI_WIN32 +#include "arch/win32/ArchMiscWindows.h" +#endif + +#if WINAPI_MSWINDOWS +#include "platform/MSWindowsScreen.h" +#elif WINAPI_XWINDOWS +#include "platform/XWindowsScreen.h" +#elif WINAPI_CARBON +#include "platform/OSXScreen.h" +#endif + +#if defined(__APPLE__) +#include "platform/OSXDragSimulator.h" +#endif + +#include <iostream> +#include <stdio.h> +#include <sstream> + +#define RETRY_TIME 1.0 + +ClientApp::ClientApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) : + App(events, createTaskBarReceiver, new ClientArgs()), + m_client(NULL), + m_clientScreen(NULL), + m_serverAddress(NULL) +{ +} + +ClientApp::~ClientApp() +{ +} + +void +ClientApp::parseArgs(int argc, const char* const* argv) +{ + ArgParser argParser(this); + bool result = argParser.parseClientArgs(args(), argc, argv); + + if (!result || args().m_shouldExit) { + m_bye(kExitArgs); + } + else { + // save server address + if (!args().m_barrierAddress.empty()) { + try { + *m_serverAddress = NetworkAddress(args().m_barrierAddress, kDefaultPort); + m_serverAddress->resolve(); + } + catch (XSocketAddress& e) { + // allow an address that we can't look up if we're restartable. + // we'll try to resolve the address each time we connect to the + // server. a bad port will never get better. patch by Brent + // Priddy. + if (!args().m_restartable || e.getError() == XSocketAddress::kBadPort) { + LOG((CLOG_PRINT "%s: %s" BYE, + args().m_pname, e.what(), args().m_pname)); + m_bye(kExitFailed); + } + } + } + } +} + +void +ClientApp::help() +{ +#if WINAPI_XWINDOWS +# define WINAPI_ARG \ + " [--display <display>] [--no-xinitthreads]" +# define WINAPI_INFO \ + " --display <display> connect to the X server at <display>\n" \ + " --no-xinitthreads do not call XInitThreads()\n" +#else +# define WINAPI_ARG "" +# define WINAPI_INFO "" +#endif + + std::ostringstream buffer; + buffer << "Start the barrier client and connect to a remote server component." << std::endl + << std::endl + << "Usage: " << args().m_pname << " [--yscroll <delta>]" << WINAPI_ARG << HELP_SYS_ARGS + << HELP_COMMON_ARGS << " <server-address>" << std::endl + << std::endl + << "Options:" << std::endl + << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO + << " --yscroll <delta> defines the vertical scrolling delta, which is" << std::endl + << " 120 by default." << std::endl + << HELP_COMMON_INFO_2 + << std::endl + << "Default options are marked with a *" << std::endl + << std::endl + << "The server address is of the form: [<hostname>][:<port>]. The hostname" << std::endl + << "must be the address or hostname of the server. The port overrides the" << std::endl + << "default port, " << kDefaultPort << "." << std::endl; + + LOG((CLOG_PRINT "%s", buffer.str().c_str())); +} + +const char* +ClientApp::daemonName() const +{ +#if SYSAPI_WIN32 + return "Barrier Client"; +#elif SYSAPI_UNIX + return "barrierc"; +#endif +} + +const char* +ClientApp::daemonInfo() const +{ +#if SYSAPI_WIN32 + return "Allows another computer to share it's keyboard and mouse with this computer."; +#elif SYSAPI_UNIX + return ""; +#endif +} + +barrier::Screen* +ClientApp::createScreen() +{ +#if WINAPI_MSWINDOWS + return new barrier::Screen(new MSWindowsScreen( + false, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events); +#elif WINAPI_XWINDOWS + return new barrier::Screen(new XWindowsScreen( + args().m_display, false, args().m_disableXInitThreads, + args().m_yscroll, m_events), m_events); +#elif WINAPI_CARBON + return new barrier::Screen(new OSXScreen(m_events, false), m_events); +#endif +} + +void +ClientApp::updateStatus() +{ + updateStatus(""); +} + + +void +ClientApp::updateStatus(const String& msg) +{ + if (m_taskBarReceiver) + { + m_taskBarReceiver->updateStatus(m_client, msg); + } +} + + +void +ClientApp::resetRestartTimeout() +{ + // retry time can nolonger be changed + //s_retryTime = 0.0; +} + + +double +ClientApp::nextRestartTimeout() +{ + // retry at a constant rate (Issue 52) + return RETRY_TIME; + + /* + // choose next restart timeout. we start with rapid retries + // then slow down. + if (s_retryTime < 1.0) { + s_retryTime = 1.0; + } + else if (s_retryTime < 3.0) { + s_retryTime = 3.0; + } + else { + s_retryTime = 5.0; + } + return s_retryTime; + */ +} + + +void +ClientApp::handleScreenError(const Event&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + m_events->addEvent(Event(Event::kQuit)); +} + + +barrier::Screen* +ClientApp::openClientScreen() +{ + barrier::Screen* screen = createScreen(); + screen->setEnableDragDrop(argsBase().m_enableDragDrop); + m_events->adoptHandler(m_events->forIScreen().error(), + screen->getEventTarget(), + new TMethodEventJob<ClientApp>( + this, &ClientApp::handleScreenError)); + return screen; +} + + +void +ClientApp::closeClientScreen(barrier::Screen* screen) +{ + if (screen != NULL) { + m_events->removeHandler(m_events->forIScreen().error(), + screen->getEventTarget()); + delete screen; + } +} + + +void +ClientApp::handleClientRestart(const Event&, void* vtimer) +{ + // discard old timer + EventQueueTimer* timer = static_cast<EventQueueTimer*>(vtimer); + m_events->deleteTimer(timer); + m_events->removeHandler(Event::kTimer, timer); + + // reconnect + startClient(); +} + + +void +ClientApp::scheduleClientRestart(double retryTime) +{ + // install a timer and handler to retry later + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + EventQueueTimer* timer = m_events->newOneShotTimer(retryTime, NULL); + m_events->adoptHandler(Event::kTimer, timer, + new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientRestart, timer)); +} + + +void +ClientApp::handleClientConnected(const Event&, void*) +{ + LOG((CLOG_NOTE "connected to server")); + resetRestartTimeout(); + updateStatus(); +} + + +void +ClientApp::handleClientFailed(const Event& e, void*) +{ + Client::FailInfo* info = + static_cast<Client::FailInfo*>(e.getData()); + + updateStatus(String("Failed to connect to server: ") + info->m_what); + if (!args().m_restartable || !info->m_retry) { + LOG((CLOG_ERR "failed to connect to server: %s", info->m_what.c_str())); + m_events->addEvent(Event(Event::kQuit)); + } + else { + LOG((CLOG_WARN "failed to connect to server: %s", info->m_what.c_str())); + if (!m_suspended) { + scheduleClientRestart(nextRestartTimeout()); + } + } + delete info; +} + + +void +ClientApp::handleClientDisconnected(const Event&, void*) +{ + LOG((CLOG_NOTE "disconnected from server")); + if (!args().m_restartable) { + m_events->addEvent(Event(Event::kQuit)); + } + else if (!m_suspended) { + scheduleClientRestart(nextRestartTimeout()); + } + updateStatus(); +} + +Client* +ClientApp::openClient(const String& name, const NetworkAddress& address, + barrier::Screen* screen) +{ + Client* client = new Client( + m_events, + name, + address, + new TCPSocketFactory(m_events, getSocketMultiplexer()), + screen, + args()); + + try { + m_events->adoptHandler( + m_events->forClient().connected(), + client->getEventTarget(), + new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientConnected)); + + m_events->adoptHandler( + m_events->forClient().connectionFailed(), + client->getEventTarget(), + new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientFailed)); + + m_events->adoptHandler( + m_events->forClient().disconnected(), + client->getEventTarget(), + new TMethodEventJob<ClientApp>(this, &ClientApp::handleClientDisconnected)); + + } catch (std::bad_alloc &ba) { + delete client; + throw ba; + } + + return client; +} + + +void +ClientApp::closeClient(Client* client) +{ + if (client == NULL) { + return; + } + + m_events->removeHandler(m_events->forClient().connected(), client); + m_events->removeHandler(m_events->forClient().connectionFailed(), client); + m_events->removeHandler(m_events->forClient().disconnected(), client); + delete client; +} + +int +ClientApp::foregroundStartup(int argc, char** argv) +{ + initApp(argc, argv); + + // never daemonize + return mainLoop(); +} + +bool +ClientApp::startClient() +{ + double retryTime; + barrier::Screen* clientScreen = NULL; + try { + if (m_clientScreen == NULL) { + clientScreen = openClientScreen(); + m_client = openClient(args().m_name, + *m_serverAddress, clientScreen); + m_clientScreen = clientScreen; + LOG((CLOG_NOTE "started client")); + } + + m_client->connect(); + + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "secondary screen unavailable: %s", e.what())); + closeClientScreen(clientScreen); + updateStatus(String("secondary screen unavailable: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "failed to start client: %s", e.what())); + closeClientScreen(clientScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start client: %s", e.what())); + closeClientScreen(clientScreen); + return false; + } + + if (args().m_restartable) { + scheduleClientRestart(retryTime); + return true; + } + else { + // don't try again + return false; + } +} + + +void +ClientApp::stopClient() +{ + closeClient(m_client); + closeClientScreen(m_clientScreen); + m_client = NULL; + m_clientScreen = NULL; +} + + +int +ClientApp::mainLoop() +{ + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + SocketMultiplexer multiplexer; + setSocketMultiplexer(&multiplexer); + + // start client, etc + appUtil().startNode(); + + // init ipc client after node start, since create a new screen wipes out + // the event queue (the screen ctors call adoptBuffer). + if (argsBase().m_enableIpc) { + initIpcClient(); + } + + // run event loop. if startClient() failed we're supposed to retry + // later. the timer installed by startClient() will take care of + // that. + DAEMON_RUNNING(true); + +#if defined(MAC_OS_X_VERSION_10_7) + + Thread thread( + new TMethodJob<ClientApp>( + this, &ClientApp::runEventsLoop, + NULL)); + + // wait until carbon loop is ready + OSXScreen* screen = dynamic_cast<OSXScreen*>( + m_clientScreen->getPlatformScreen()); + screen->waitForCarbonLoop(); + + runCocoaApp(); +#else + m_events->loop(); +#endif + + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping client")); + stopClient(); + updateStatus(); + LOG((CLOG_NOTE "stopped client")); + + if (argsBase().m_enableIpc) { + cleanupIpcClient(); + } + + return kExitSuccess; +} + +static +int +daemonMainLoopStatic(int argc, const char** argv) +{ + return ClientApp::instance().daemonMainLoop(argc, argv); +} + +int +ClientApp::standardStartup(int argc, char** argv) +{ + initApp(argc, argv); + + // daemonize if requested + if (args().m_daemon) { + return ARCH->daemonize(daemonName(), &daemonMainLoopStatic); + } + else { + return mainLoop(); + } +} + +int +ClientApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) +{ + // general initialization + m_serverAddress = new NetworkAddress; + args().m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + int result; + try + { + // run + result = startup(argc, argv); + } + catch (...) + { + if (m_taskBarReceiver) + { + // done with task bar receiver + delete m_taskBarReceiver; + } + + delete m_serverAddress; + + throw; + } + + return result; +} + +void +ClientApp::startNode() +{ + // start the client. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting client")); + if (!startClient()) { + m_bye(kExitFailed); + } +} diff --git a/src/lib/barrier/ClientApp.h b/src/lib/barrier/ClientApp.h new file mode 100644 index 0000000..777f3d3 --- /dev/null +++ b/src/lib/barrier/ClientApp.h @@ -0,0 +1,83 @@ +/* + * 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 "barrier/App.h" + +namespace barrier { class Screen; } +class Event; +class Client; +class NetworkAddress; +class Thread; +class ClientArgs; + +class ClientApp : public App { +public: + ClientApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver); + virtual ~ClientApp(); + + // Parse client specific command line arguments. + void parseArgs(int argc, const char* const* argv); + + // Prints help specific to client. + void help(); + + // Returns arguments that are common and for client. + ClientArgs& args() const { return (ClientArgs&)argsBase(); } + + const char* daemonName() const; + const char* daemonInfo() const; + + // TODO: move to server only (not supported on client) + void loadConfig() { } + bool loadConfig(const String& pathname) { return false; } + + int foregroundStartup(int argc, char** argv); + int standardStartup(int argc, char** argv); + int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup); + barrier::Screen* createScreen(); + void updateStatus(); + void updateStatus(const String& msg); + void resetRestartTimeout(); + double nextRestartTimeout(); + void handleScreenError(const Event&, void*); + barrier::Screen* openClientScreen(); + void closeClientScreen(barrier::Screen* screen); + void handleClientRestart(const Event&, void* vtimer); + void scheduleClientRestart(double retryTime); + void handleClientConnected(const Event&, void*); + void handleClientFailed(const Event& e, void*); + void handleClientDisconnected(const Event&, void*); + Client* openClient(const String& name, const NetworkAddress& address, + barrier::Screen* screen); + void closeClient(Client* client); + bool startClient(); + void stopClient(); + int mainLoop(); + void startNode(); + + static ClientApp& instance() { return (ClientApp&)App::instance(); } + + Client* getClientPtr() { return m_client; } + +private: + Client* m_client; + barrier::Screen*m_clientScreen; + NetworkAddress* m_serverAddress; +}; diff --git a/src/lib/barrier/ClientArgs.cpp b/src/lib/barrier/ClientArgs.cpp new file mode 100644 index 0000000..5c9ed88 --- /dev/null +++ b/src/lib/barrier/ClientArgs.cpp @@ -0,0 +1,23 @@ +/* + * 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 "barrier/ClientArgs.h" + +ClientArgs::ClientArgs() : + m_yscroll(0) +{ +} diff --git a/src/lib/barrier/ClientArgs.h b/src/lib/barrier/ClientArgs.h new file mode 100644 index 0000000..70285fa --- /dev/null +++ b/src/lib/barrier/ClientArgs.h @@ -0,0 +1,30 @@ +/* + * 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 + +#include "barrier/ArgsBase.h" + +class NetworkAddress; + +class ClientArgs : public ArgsBase { +public: + ClientArgs(); + +public: + int m_yscroll; +}; diff --git a/src/lib/barrier/ClientTaskBarReceiver.cpp b/src/lib/barrier/ClientTaskBarReceiver.cpp new file mode 100644 index 0000000..2ea6566 --- /dev/null +++ b/src/lib/barrier/ClientTaskBarReceiver.cpp @@ -0,0 +1,141 @@ +/* + * 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 "barrier/ClientTaskBarReceiver.h" +#include "client/Client.h" +#include "mt/Lock.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "arch/Arch.h" +#include "common/Version.h" + +// +// ClientTaskBarReceiver +// + +ClientTaskBarReceiver::ClientTaskBarReceiver(IEventQueue* events) : + m_state(kNotRunning), + m_events(events) +{ + // do nothing +} + +ClientTaskBarReceiver::~ClientTaskBarReceiver() +{ + // do nothing +} + +void +ClientTaskBarReceiver::updateStatus(Client* client, const String& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (client == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_server = client->getServerAddress().getHostname(); + + if (client->isConnected()) { + m_state = kConnected; + } + else if (client->isConnecting()) { + m_state = kConnecting; + } + else { + m_state = kNotConnected; + } + } + + // let subclasses have a go + onStatusChanged(client); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +ClientTaskBarReceiver::EState +ClientTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const String& +ClientTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +void +ClientTaskBarReceiver::quit() +{ + m_events->addEvent(Event(Event::kQuit)); +} + +void +ClientTaskBarReceiver::onStatusChanged(Client*) +{ + // do nothing +} + +void +ClientTaskBarReceiver::lock() const +{ + // do nothing +} + +void +ClientTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +ClientTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return barrier::string::sprintf("%s: Not running", kAppVersion); + + case kNotWorking: + return barrier::string::sprintf("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return barrier::string::sprintf("%s: Not connected: %s", + kAppVersion, m_errorMessage.c_str()); + + case kConnecting: + return barrier::string::sprintf("%s: Connecting to %s...", + kAppVersion, m_server.c_str()); + + case kConnected: + return barrier::string::sprintf("%s: Connected to %s", + kAppVersion, m_server.c_str()); + + default: + return ""; + } +} diff --git a/src/lib/barrier/ClientTaskBarReceiver.h b/src/lib/barrier/ClientTaskBarReceiver.h new file mode 100644 index 0000000..da15154 --- /dev/null +++ b/src/lib/barrier/ClientTaskBarReceiver.h @@ -0,0 +1,95 @@ +/* + * 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/>. + */ + +#ifndef CCLIENTTASKBARRECEIVER_H +#define CCLIENTTASKBARRECEIVER_H + +#include "base/String.h" +#include "arch/IArchTaskBarReceiver.h" +#include "base/log_outputters.h" +#include "client/Client.h" + +class IEventQueue; + +//! Implementation of IArchTaskBarReceiver for the barrier server +class ClientTaskBarReceiver : public IArchTaskBarReceiver { +public: + ClientTaskBarReceiver(IEventQueue* events); + virtual ~ClientTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the client. + */ + void updateStatus(Client*, const String& errorMsg); + + void updateStatus(INode* n, const String& errorMsg) { updateStatus((Client*)n, errorMsg); } + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + virtual void cleanup() {} + +protected: + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnecting, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const String& getErrorMessage() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does nothing. + */ + virtual void onStatusChanged(Client* client); + +private: + EState m_state; + String m_errorMessage; + String m_server; + IEventQueue* m_events; +}; + +IArchTaskBarReceiver* createTaskBarReceiver(const BufferedLogOutputter* logBuffer, IEventQueue* events); + +#endif diff --git a/src/lib/barrier/Clipboard.cpp b/src/lib/barrier/Clipboard.cpp new file mode 100644 index 0000000..a6a166d --- /dev/null +++ b/src/lib/barrier/Clipboard.cpp @@ -0,0 +1,118 @@ +/* + * 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 "barrier/Clipboard.h" + +// +// Clipboard +// + +Clipboard::Clipboard() : + m_open(false), + m_owner(false) +{ + open(0); + empty(); + close(); +} + +Clipboard::~Clipboard() +{ + // do nothing +} + +bool +Clipboard::empty() +{ + assert(m_open); + + // clear all data + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } + + // save time + m_timeOwned = m_time; + + // we're the owner now + m_owner = true; + + return true; +} + +void +Clipboard::add(EFormat format, const String& data) +{ + assert(m_open); + assert(m_owner); + + m_data[format] = data; + m_added[format] = true; +} + +bool +Clipboard::open(Time time) const +{ + assert(!m_open); + + m_open = true; + m_time = time; + + return true; +} + +void +Clipboard::close() const +{ + assert(m_open); + + m_open = false; +} + +Clipboard::Time +Clipboard::getTime() const +{ + return m_timeOwned; +} + +bool +Clipboard::has(EFormat format) const +{ + assert(m_open); + return m_added[format]; +} + +String +Clipboard::get(EFormat format) const +{ + assert(m_open); + return m_data[format]; +} + +void +Clipboard::unmarshall(const String& data, Time time) +{ + IClipboard::unmarshall(this, data, time); +} + +String +Clipboard::marshall() const +{ + return IClipboard::marshall(this); +} diff --git a/src/lib/barrier/Clipboard.h b/src/lib/barrier/Clipboard.h new file mode 100644 index 0000000..23bea75 --- /dev/null +++ b/src/lib/barrier/Clipboard.h @@ -0,0 +1,71 @@ +/* + * 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 "barrier/IClipboard.h" + +//! Memory buffer clipboard +/*! +This class implements a clipboard that stores data in memory. +*/ +class Clipboard : public IClipboard { +public: + Clipboard(); + virtual ~Clipboard(); + + //! @name manipulators + //@{ + + //! Unmarshall clipboard data + /*! + Extract marshalled clipboard data and store it in this clipboard. + Sets the clipboard time to \c time. + */ + void unmarshall(const String& data, Time time); + + //@} + //! @name accessors + //@{ + + //! Marshall clipboard data + /*! + Merge this clipboard's data into a single buffer that can be later + unmarshalled to restore the clipboard and return the buffer. + */ + String marshall() const; + + //@} + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const String& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual String get(EFormat) const; + +private: + mutable bool m_open; + mutable Time m_time; + bool m_owner; + Time m_timeOwned; + bool m_added[kNumFormats]; + String m_data[kNumFormats]; +}; diff --git a/src/lib/barrier/ClipboardChunk.cpp b/src/lib/barrier/ClipboardChunk.cpp new file mode 100644 index 0000000..bc71471 --- /dev/null +++ b/src/lib/barrier/ClipboardChunk.cpp @@ -0,0 +1,154 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 "barrier/ClipboardChunk.h" + +#include "barrier/ProtocolUtil.h" +#include "barrier/protocol_types.h" +#include "io/IStream.h" +#include "base/Log.h" +#include <cstring> + +size_t ClipboardChunk::s_expectedSize = 0; + +ClipboardChunk::ClipboardChunk(size_t size) : + Chunk(size) +{ + m_dataSize = size - CLIPBOARD_CHUNK_META_SIZE; +} + +ClipboardChunk* +ClipboardChunk::start( + ClipboardID id, + UInt32 sequence, + const String& size) +{ + size_t sizeLength = size.size(); + ClipboardChunk* start = new ClipboardChunk(sizeLength + CLIPBOARD_CHUNK_META_SIZE); + char* chunk = start->m_chunk; + + chunk[0] = id; + std::memcpy (&chunk[1], &sequence, 4); + chunk[5] = kDataStart; + memcpy(&chunk[6], size.c_str(), sizeLength); + chunk[sizeLength + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0'; + + return start; +} + +ClipboardChunk* +ClipboardChunk::data( + ClipboardID id, + UInt32 sequence, + const String& data) +{ + size_t dataSize = data.size(); + ClipboardChunk* chunk = new ClipboardChunk(dataSize + CLIPBOARD_CHUNK_META_SIZE); + char* chunkData = chunk->m_chunk; + + chunkData[0] = id; + std::memcpy (&chunkData[1], &sequence, 4); + chunkData[5] = kDataChunk; + memcpy(&chunkData[6], data.c_str(), dataSize); + chunkData[dataSize + CLIPBOARD_CHUNK_META_SIZE - 1] = '\0'; + + return chunk; +} + +ClipboardChunk* +ClipboardChunk::end(ClipboardID id, UInt32 sequence) +{ + ClipboardChunk* end = new ClipboardChunk(CLIPBOARD_CHUNK_META_SIZE); + char* chunk = end->m_chunk; + + chunk[0] = id; + std::memcpy (&chunk[1], &sequence, 4); + chunk[5] = kDataEnd; + chunk[CLIPBOARD_CHUNK_META_SIZE - 1] = '\0'; + + return end; +} + +int +ClipboardChunk::assemble(barrier::IStream* stream, + String& dataCached, + ClipboardID& id, + UInt32& sequence) +{ + UInt8 mark; + String data; + + if (!ProtocolUtil::readf(stream, kMsgDClipboard + 4, &id, &sequence, &mark, &data)) { + return kError; + } + + if (mark == kDataStart) { + s_expectedSize = barrier::string::stringToSizeType(data); + LOG((CLOG_DEBUG "start receiving clipboard data")); + dataCached.clear(); + return kStart; + } + else if (mark == kDataChunk) { + dataCached.append(data); + return kNotFinish; + } + else if (mark == kDataEnd) { + // validate + if (id >= kClipboardEnd) { + return kError; + } + else if (s_expectedSize != dataCached.size()) { + LOG((CLOG_ERR "corrupted clipboard data, expected size=%d actual size=%d", s_expectedSize, dataCached.size())); + return kError; + } + return kFinish; + } + + LOG((CLOG_ERR "clipboard transmission failed: unknown error")); + return kError; +} + +void +ClipboardChunk::send(barrier::IStream* stream, void* data) +{ + ClipboardChunk* clipboardData = static_cast<ClipboardChunk*>(data); + + LOG((CLOG_DEBUG1 "sending clipboard chunk")); + + char* chunk = clipboardData->m_chunk; + ClipboardID id = chunk[0]; + UInt32 sequence; + std::memcpy (&sequence, &chunk[1], 4); + UInt8 mark = chunk[5]; + String dataChunk(&chunk[6], clipboardData->m_dataSize); + + switch (mark) { + case kDataStart: + LOG((CLOG_DEBUG2 "sending clipboard chunk start: size=%s", dataChunk.c_str())); + break; + + case kDataChunk: + LOG((CLOG_DEBUG2 "sending clipboard chunk data: size=%i", dataChunk.size())); + break; + + case kDataEnd: + LOG((CLOG_DEBUG2 "sending clipboard finished")); + break; + } + + ProtocolUtil::writef(stream, kMsgDClipboard, id, sequence, mark, &dataChunk); +} diff --git a/src/lib/barrier/ClipboardChunk.h b/src/lib/barrier/ClipboardChunk.h new file mode 100644 index 0000000..6402aca --- /dev/null +++ b/src/lib/barrier/ClipboardChunk.h @@ -0,0 +1,60 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 + +#include "barrier/Chunk.h" +#include "barrier/clipboard_types.h" +#include "base/String.h" +#include "common/basic_types.h" + +#define CLIPBOARD_CHUNK_META_SIZE 7 + +namespace barrier { +class IStream; +}; + +class ClipboardChunk : public Chunk { +public: + ClipboardChunk(size_t size); + + static ClipboardChunk* + start( + ClipboardID id, + UInt32 sequence, + const String& size); + static ClipboardChunk* + data( + ClipboardID id, + UInt32 sequence, + const String& data); + static ClipboardChunk* + end(ClipboardID id, UInt32 sequence); + + static int assemble( + barrier::IStream* stream, + String& dataCached, + ClipboardID& id, + UInt32& sequence); + + static void send(barrier::IStream* stream, void* data); + + static size_t getExpectedSize() { return s_expectedSize; } + +private: + static size_t s_expectedSize; +}; diff --git a/src/lib/barrier/DragInformation.cpp b/src/lib/barrier/DragInformation.cpp new file mode 100644 index 0000000..db28f3d --- /dev/null +++ b/src/lib/barrier/DragInformation.cpp @@ -0,0 +1,158 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 "barrier/DragInformation.h" +#include "base/Log.h" + +#include <fstream> +#include <sstream> +#include <stdexcept> + +using namespace std; + +DragInformation::DragInformation() : + m_filename(), + m_filesize(0) +{ +} + +void +DragInformation::parseDragInfo(DragFileList& dragFileList, UInt32 fileNum, String data) +{ + size_t startPos = 0; + size_t findResult1 = 0; + size_t findResult2 = 0; + dragFileList.clear(); + String slash("\\"); + if (data.find("/", startPos) != string::npos) { + slash = "/"; + } + + UInt32 index = 0; + while (index < fileNum) { + findResult1 = data.find(',', startPos); + findResult2 = data.find_last_of(slash, findResult1); + + if (findResult1 == startPos) { + //TODO: file number does not match, something goes wrong + break; + } + + // set filename + if (findResult1 - findResult2 > 1) { + String filename = data.substr(findResult2 + 1, + findResult1 - findResult2 - 1); + DragInformation di; + di.setFilename(filename); + dragFileList.push_back(di); + } + startPos = findResult1 + 1; + + //set filesize + findResult2 = data.find(',', startPos); + if (findResult2 - findResult1 > 1) { + String filesize = data.substr(findResult1 + 1, + findResult2 - findResult1 - 1); + size_t size = stringToNum(filesize); + dragFileList.at(index).setFilesize(size); + } + startPos = findResult1 + 1; + + ++index; + } + + LOG((CLOG_DEBUG "drag info received, total drag file number: %i", + dragFileList.size())); + + for (size_t i = 0; i < dragFileList.size(); ++i) { + LOG((CLOG_DEBUG "dragging file %i name: %s", + i + 1, + dragFileList.at(i).getFilename().c_str())); + } +} + +String +DragInformation::getDragFileExtension(String filename) +{ + size_t findResult = string::npos; + findResult = filename.find_last_of(".", filename.size()); + if (findResult != string::npos) { + return filename.substr(findResult + 1, filename.size() - findResult - 1); + } + else { + return ""; + } +} + +int +DragInformation::setupDragInfo(DragFileList& fileList, String& output) +{ + int size = static_cast<int>(fileList.size()); + for (int i = 0; i < size; ++i) { + output.append(fileList.at(i).getFilename()); + output.append(","); + String filesize = getFileSize(fileList.at(i).getFilename()); + output.append(filesize); + output.append(","); + } + return size; +} + +bool +DragInformation::isFileValid(String filename) +{ + bool result = false; + std::fstream file(filename.c_str(), ios::in|ios::binary); + + if (file.is_open()) { + result = true; + } + + file. close(); + + return result; +} + +size_t +DragInformation::stringToNum(String& str) +{ + istringstream iss(str.c_str()); + size_t size; + iss >> size; + return size; +} + +String +DragInformation::getFileSize(String& filename) +{ + std::fstream file(filename.c_str(), ios::in|ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("failed to get file size"); + } + + // check file size + file.seekg (0, std::ios::end); + size_t size = (size_t)file.tellg(); + + stringstream ss; + ss << size; + + file. close(); + + return ss.str(); +} diff --git a/src/lib/barrier/DragInformation.h b/src/lib/barrier/DragInformation.h new file mode 100644 index 0000000..b985bd1 --- /dev/null +++ b/src/lib/barrier/DragInformation.h @@ -0,0 +1,53 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "common/stdvector.h" +#include "base/String.h" +#include "base/EventTypes.h" + +class DragInformation; +typedef std::vector<DragInformation> DragFileList; + +class DragInformation { +public: + DragInformation(); + ~DragInformation() { } + + String& getFilename() { return m_filename; } + void setFilename(String& name) { m_filename = name; } + size_t getFilesize() { return m_filesize; } + void setFilesize(size_t size) { m_filesize = size; } + + static void parseDragInfo(DragFileList& dragFileList, UInt32 fileNum, String data); + static String getDragFileExtension(String filename); + // helper function to setup drag info + // example: filename1,filesize1,filename2,filesize2, + // return file count + static int setupDragInfo(DragFileList& fileList, String& output); + + static bool isFileValid(String filename); + +private: + static size_t stringToNum(String& str); + static String getFileSize(String& filename); + +private: + String m_filename; + size_t m_filesize; +}; diff --git a/src/lib/barrier/DropHelper.cpp b/src/lib/barrier/DropHelper.cpp new file mode 100644 index 0000000..ee5e5ee --- /dev/null +++ b/src/lib/barrier/DropHelper.cpp @@ -0,0 +1,53 @@ +/* + * 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 "barrier/DropHelper.h" + +#include "base/Log.h" + +#include <fstream> + +void +DropHelper::writeToDir(const String& destination, DragFileList& fileList, String& data) +{ + LOG((CLOG_DEBUG "dropping file, files=%i target=%s", fileList.size(), destination.c_str())); + + if (!destination.empty() && fileList.size() > 0) { + std::fstream file; + String dropTarget = destination; +#ifdef SYSAPI_WIN32 + dropTarget.append("\\"); +#else + dropTarget.append("/"); +#endif + dropTarget.append(fileList.at(0).getFilename()); + file.open(dropTarget.c_str(), std::ios::out | std::ios::binary); + if (!file.is_open()) { + LOG((CLOG_ERR "drop file failed: can not open %s", dropTarget.c_str())); + } + + file.write(data.c_str(), data.size()); + file.close(); + + LOG((CLOG_DEBUG "%s is saved to %s", fileList.at(0).getFilename().c_str(), destination.c_str())); + + fileList.clear(); + } + else { + LOG((CLOG_ERR "drop file failed: drop target is empty")); + } +} diff --git a/src/lib/barrier/DropHelper.h b/src/lib/barrier/DropHelper.h new file mode 100644 index 0000000..67facbb --- /dev/null +++ b/src/lib/barrier/DropHelper.h @@ -0,0 +1,27 @@ +/* + * 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 + +#include "barrier/DragInformation.h" +#include "base/String.h" + +class DropHelper { +public: + static void writeToDir(const String& destination, + DragFileList& fileList, String& data); +}; diff --git a/src/lib/barrier/FileChunk.cpp b/src/lib/barrier/FileChunk.cpp new file mode 100644 index 0000000..3a98568 --- /dev/null +++ b/src/lib/barrier/FileChunk.cpp @@ -0,0 +1,156 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 "barrier/FileChunk.h" + +#include "barrier/ProtocolUtil.h" +#include "barrier/protocol_types.h" +#include "io/IStream.h" +#include "base/Stopwatch.h" +#include "base/Log.h" + +static const UInt16 kIntervalThreshold = 1; + +FileChunk::FileChunk(size_t size) : + Chunk(size) +{ + m_dataSize = size - FILE_CHUNK_META_SIZE; +} + +FileChunk* +FileChunk::start(const String& size) +{ + size_t sizeLength = size.size(); + FileChunk* start = new FileChunk(sizeLength + FILE_CHUNK_META_SIZE); + char* chunk = start->m_chunk; + chunk[0] = kDataStart; + memcpy(&chunk[1], size.c_str(), sizeLength); + chunk[sizeLength + 1] = '\0'; + + return start; +} + +FileChunk* +FileChunk::data(UInt8* data, size_t dataSize) +{ + FileChunk* chunk = new FileChunk(dataSize + FILE_CHUNK_META_SIZE); + char* chunkData = chunk->m_chunk; + chunkData[0] = kDataChunk; + memcpy(&chunkData[1], data, dataSize); + chunkData[dataSize + 1] = '\0'; + + return chunk; +} + +FileChunk* +FileChunk::end() +{ + FileChunk* end = new FileChunk(FILE_CHUNK_META_SIZE); + char* chunk = end->m_chunk; + chunk[0] = kDataEnd; + chunk[1] = '\0'; + + return end; +} + +int +FileChunk::assemble(barrier::IStream* stream, String& dataReceived, size_t& expectedSize) +{ + // parse + UInt8 mark = 0; + String content; + static size_t receivedDataSize; + static double elapsedTime; + static Stopwatch stopwatch; + + if (!ProtocolUtil::readf(stream, kMsgDFileTransfer + 4, &mark, &content)) { + return kError; + } + + switch (mark) { + case kDataStart: + dataReceived.clear(); + expectedSize = barrier::string::stringToSizeType(content); + receivedDataSize = 0; + elapsedTime = 0; + stopwatch.reset(); + + if (CLOG->getFilter() >= kDEBUG2) { + LOG((CLOG_DEBUG2 "recv file size=%s", content.c_str())); + stopwatch.start(); + } + return kStart; + + case kDataChunk: + dataReceived.append(content); + if (CLOG->getFilter() >= kDEBUG2) { + LOG((CLOG_DEBUG2 "recv file chunck size=%i", content.size())); + double interval = stopwatch.getTime(); + receivedDataSize += content.size(); + LOG((CLOG_DEBUG2 "recv file interval=%f s", interval)); + if (interval >= kIntervalThreshold) { + double averageSpeed = receivedDataSize / interval / 1000; + LOG((CLOG_DEBUG2 "recv file average speed=%f kb/s", averageSpeed)); + + receivedDataSize = 0; + elapsedTime += interval; + stopwatch.reset(); + } + } + return kNotFinish; + + case kDataEnd: + if (expectedSize != dataReceived.size()) { + LOG((CLOG_ERR "corrupted clipboard data, expected size=%d actual size=%d", expectedSize, dataReceived.size())); + return kError; + } + + if (CLOG->getFilter() >= kDEBUG2) { + LOG((CLOG_DEBUG2 "file transfer finished")); + elapsedTime += stopwatch.getTime(); + double averageSpeed = expectedSize / elapsedTime / 1000; + LOG((CLOG_DEBUG2 "file transfer finished: total time consumed=%f s", elapsedTime)); + LOG((CLOG_DEBUG2 "file transfer finished: total data received=%i kb", expectedSize / 1000)); + LOG((CLOG_DEBUG2 "file transfer finished: total average speed=%f kb/s", averageSpeed)); + } + return kFinish; + } + + return kError; +} + +void +FileChunk::send(barrier::IStream* stream, UInt8 mark, char* data, size_t dataSize) +{ + String chunk(data, dataSize); + + switch (mark) { + case kDataStart: + LOG((CLOG_DEBUG2 "sending file chunk start: size=%s", data)); + break; + + case kDataChunk: + LOG((CLOG_DEBUG2 "sending file chunk: size=%i", chunk.size())); + break; + + case kDataEnd: + LOG((CLOG_DEBUG2 "sending file finished")); + break; + } + + ProtocolUtil::writef(stream, kMsgDFileTransfer, mark, &chunk); +} diff --git a/src/lib/barrier/FileChunk.h b/src/lib/barrier/FileChunk.h new file mode 100644 index 0000000..bdc2f64 --- /dev/null +++ b/src/lib/barrier/FileChunk.h @@ -0,0 +1,46 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 + +#include "barrier/Chunk.h" +#include "base/String.h" +#include "common/basic_types.h" + +#define FILE_CHUNK_META_SIZE 2 + +namespace barrier { +class IStream; +}; + +class FileChunk : public Chunk { +public: + FileChunk(size_t size); + + static FileChunk* start(const String& size); + static FileChunk* data(UInt8* data, size_t dataSize); + static FileChunk* end(); + static int assemble( + barrier::IStream* stream, + String& dataCached, + size_t& expectedSize); + static void send( + barrier::IStream* stream, + UInt8 mark, + char* data, + size_t dataSize); +}; diff --git a/src/lib/barrier/IApp.h b/src/lib/barrier/IApp.h new file mode 100644 index 0000000..3a8cd56 --- /dev/null +++ b/src/lib/barrier/IApp.h @@ -0,0 +1,47 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "common/IInterface.h" + +typedef int (*StartupFunc)(int, char**); + +class ILogOutputter; +class ArgsBase; +class IArchTaskBarReceiver; +namespace barrier { class Screen; } +class IEventQueue; + +class IApp : public IInterface +{ +public: + virtual void setByeFunc(void(*bye)(int)) = 0; + virtual ArgsBase& argsBase() const = 0; + virtual int standardStartup(int argc, char** argv) = 0; + virtual int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) = 0; + virtual void startNode() = 0; + virtual IArchTaskBarReceiver* taskBarReceiver() const = 0; + virtual void bye(int error) = 0; + virtual int mainLoop() = 0; + virtual void initApp(int argc, const char** argv) = 0; + virtual const char* daemonName() const = 0; + virtual int foregroundStartup(int argc, char** argv) = 0; + virtual barrier::Screen* createScreen() = 0; + virtual IEventQueue* getEvents() const = 0; +}; diff --git a/src/lib/barrier/IAppUtil.h b/src/lib/barrier/IAppUtil.h new file mode 100644 index 0000000..39df65d --- /dev/null +++ b/src/lib/barrier/IAppUtil.h @@ -0,0 +1,31 @@ +/* + * 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 "barrier/IApp.h" + +class IAppUtil : public IInterface { +public: + virtual void adoptApp(IApp* app) = 0; + virtual IApp& app() const = 0; + virtual int run(int argc, char** argv) = 0; + virtual void beforeAppExit() = 0; + virtual void startNode() = 0; +}; diff --git a/src/lib/barrier/IClient.h b/src/lib/barrier/IClient.h new file mode 100644 index 0000000..d9b2194 --- /dev/null +++ b/src/lib/barrier/IClient.h @@ -0,0 +1,176 @@ +/* + * 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 "barrier/clipboard_types.h" +#include "barrier/IScreen.h" +#include "barrier/key_types.h" +#include "barrier/mouse_types.h" +#include "barrier/option_types.h" +#include "base/String.h" + +//! Client interface +/*! +This interface defines the methods necessary for the server to +communicate with a client. +*/ +class IClient : public IScreen { +public: + //! @name manipulators + //@{ + + //! Enter screen + /*! + Enter the screen. The cursor should be warped to \p xAbs,yAbs. + \p mask is the expected toggle button state and the client should + update its state to match. \p forScreensaver is true iff the + screen is being entered because the screen saver is starting. + Subsequent clipboard events should report \p seqNum. + */ + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + + //! Leave screen + /*! + Leave the screen. Return false iff the user may not leave the + client's screen (because, for example, a button is down). + */ + virtual bool leave() = 0; + + //! Set clipboard + /*! + Update the client's clipboard. This implies that the client's + clipboard is now up to date. If the client's clipboard was + already known to be up to date then this may do nothing. \c data + has marshalled clipboard data. + */ + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + + //! Grab clipboard + /*! + Grab (i.e. take ownership of) the client's clipboard. Since this + is called when another client takes ownership of the clipboard it + implies that the client's clipboard is out of date. + */ + virtual void grabClipboard(ClipboardID) = 0; + + //! Mark clipboard dirty + /*! + Mark the client's clipboard as dirty (out of date) or clean (up to + date). + */ + virtual void setClipboardDirty(ClipboardID, bool dirty) = 0; + + //! Notify of key press + /*! + Synthesize key events to generate a press of key \c id. If possible + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). + */ + virtual void keyDown(KeyID id, KeyModifierMask, KeyButton) = 0; + + //! Notify of key repeat + /*! + Synthesize key events to generate a press and release of key \c id + \c count times. If possible match the given modifier mask. + */ + virtual void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton) = 0; + + //! Notify of key release + /*! + Synthesize key events to generate a release of key \c id. If possible + match the given modifier mask. + */ + virtual void keyUp(KeyID id, KeyModifierMask, KeyButton) = 0; + + //! Notify of mouse press + /*! + Synthesize mouse events to generate a press of mouse button \c id. + */ + virtual void mouseDown(ButtonID id) = 0; + + //! Notify of mouse release + /*! + Synthesize mouse events to generate a release of mouse button \c id. + */ + virtual void mouseUp(ButtonID id) = 0; + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion to the absolute + screen position \c xAbs,yAbs. + */ + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion by the relative + amount \c xRel,yRel. + */ + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + + //! Notify of mouse wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c xDelta + and \c yDelta. Deltas are positive for motion away from the user or + to the right and negative for motion towards the user or to the left. + Each wheel click should generate a delta of +/-120. + */ + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + + //! Notify of screen saver change + virtual void screensaver(bool activate) = 0; + + //! Notify of options changes + /*! + Reset all options to their default values. + */ + virtual void resetOptions() = 0; + + //! Notify of options changes + /*! + Set options to given values. Ignore unknown options and don't + modify our options that aren't given in \c options. + */ + virtual void setOptions(const OptionsList& options) = 0; + + //@} + //! @name accessors + //@{ + + //! Get client name + /*! + Return the client's name. + */ + virtual String getName() const = 0; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; +}; diff --git a/src/lib/barrier/IClipboard.cpp b/src/lib/barrier/IClipboard.cpp new file mode 100644 index 0000000..19b4b56 --- /dev/null +++ b/src/lib/barrier/IClipboard.cpp @@ -0,0 +1,168 @@ +/* + * 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 "barrier/IClipboard.h" +#include "common/stdvector.h" + +// +// IClipboard +// + +void +IClipboard::unmarshall(IClipboard* clipboard, const String& data, Time time) +{ + assert(clipboard != NULL); + + const char* index = data.data(); + + if (clipboard->open(time)) { + // clear existing data + clipboard->empty(); + + // read the number of formats + const UInt32 numFormats = readUInt32(index); + index += 4; + + // read each format + for (UInt32 i = 0; i < numFormats; ++i) { + // get the format id + IClipboard::EFormat format = + static_cast<IClipboard::EFormat>(readUInt32(index)); + index += 4; + + // get the size of the format data + UInt32 size = readUInt32(index); + index += 4; + + // save the data if it's a known format. if either the client + // or server supports more clipboard formats than the other + // then one of them will get a format >= kNumFormats here. + if (format <IClipboard::kNumFormats) { + clipboard->add(format, String(index, size)); + } + index += size; + } + + // done + clipboard->close(); + } +} + +String +IClipboard::marshall(const IClipboard* clipboard) +{ + // return data format: + // 4 bytes => number of formats included + // 4 bytes => format enum + // 4 bytes => clipboard data size n + // n bytes => clipboard data + // back to the second 4 bytes if there is another format + + assert(clipboard != NULL); + + String data; + + std::vector<String> formatData; + formatData.resize(IClipboard::kNumFormats); + // FIXME -- use current time + if (clipboard->open(0)) { + + // compute size of marshalled data + UInt32 size = 4; + UInt32 numFormats = 0; + for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) { + if (clipboard->has(static_cast<IClipboard::EFormat>(format))) { + ++numFormats; + formatData[format] = + clipboard->get(static_cast<IClipboard::EFormat>(format)); + size += 4 + 4 + (UInt32)formatData[format].size(); + } + } + + // allocate space + data.reserve(size); + + // marshall the data + writeUInt32(&data, numFormats); + for (UInt32 format = 0; format != IClipboard::kNumFormats; ++format) { + if (clipboard->has(static_cast<IClipboard::EFormat>(format))) { + writeUInt32(&data, format); + writeUInt32(&data, (UInt32)formatData[format].size()); + data += formatData[format]; + } + } + clipboard->close(); + } + + return data; +} + +bool +IClipboard::copy(IClipboard* dst, const IClipboard* src) +{ + assert(dst != NULL); + assert(src != NULL); + + return copy(dst, src, src->getTime()); +} + +bool +IClipboard::copy(IClipboard* dst, const IClipboard* src, Time time) +{ + assert(dst != NULL); + assert(src != NULL); + + bool success = false; + if (src->open(time)) { + if (dst->open(time)) { + if (dst->empty()) { + for (SInt32 format = 0; + format != IClipboard::kNumFormats; ++format) { + IClipboard::EFormat eFormat = (IClipboard::EFormat)format; + if (src->has(eFormat)) { + dst->add(eFormat, src->get(eFormat)); + } + } + success = true; + } + dst->close(); + } + src->close(); + } + + return success; +} + +UInt32 +IClipboard::readUInt32(const char* buf) +{ + const unsigned char* ubuf = reinterpret_cast<const unsigned char*>(buf); + return (static_cast<UInt32>(ubuf[0]) << 24) | + (static_cast<UInt32>(ubuf[1]) << 16) | + (static_cast<UInt32>(ubuf[2]) << 8) | + static_cast<UInt32>(ubuf[3]); +} + +void +IClipboard::writeUInt32(String* buf, UInt32 v) +{ + *buf += static_cast<UInt8>((v >> 24) & 0xff); + *buf += static_cast<UInt8>((v >> 16) & 0xff); + *buf += static_cast<UInt8>((v >> 8) & 0xff); + *buf += static_cast<UInt8>( v & 0xff); +} diff --git a/src/lib/barrier/IClipboard.h b/src/lib/barrier/IClipboard.h new file mode 100644 index 0000000..e11b264 --- /dev/null +++ b/src/lib/barrier/IClipboard.h @@ -0,0 +1,169 @@ +/* + * 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 "base/String.h" +#include "base/EventTypes.h" +#include "common/IInterface.h" + +//! Clipboard interface +/*! +This interface defines the methods common to all clipboards. +*/ +class IClipboard : public IInterface { +public: + //! Timestamp type + /*! + Timestamp type. Timestamps are in milliseconds from some + arbitrary starting time. Timestamps will wrap around to 0 + after about 49 3/4 days. + */ + typedef UInt32 Time; + + //! Clipboard formats + /*! + The list of known clipboard formats. kNumFormats must be last and + formats must be sequential starting from zero. Clipboard data set + via add() and retrieved via get() must be in one of these formats. + Platform dependent clipboard subclasses can and should present any + suitable formats derivable from these formats. + + \c kText is a text format encoded in UTF-8. Newlines are LF (not + CR or LF/CR). + + \c kBitmap is an image format. The data is a BMP file without the + 14 byte header (i.e. starting at the INFOHEADER) and with the image + data immediately following the 40 byte INFOHEADER. + + \c kHTML is a text format encoded in UTF-8 and containing a valid + HTML fragment (but not necessarily a complete HTML document). + Newlines are LF. + */ + enum EFormat { + kText, //!< Text format, UTF-8, newline is LF + kHTML, //!< HTML format, HTML fragment, UTF-8, newline is LF + kBitmap, //!< Bitmap format, BMP 24/32bpp, BI_RGB + kNumFormats //!< The number of clipboard formats + }; + + //! @name manipulators + //@{ + + //! Empty clipboard + /*! + Take ownership of the clipboard and clear all data from it. + This must be called between a successful open() and close(). + Return false if the clipboard ownership could not be taken; + the clipboard should not be emptied in this case. + */ + virtual bool empty() = 0; + + //! Add data + /*! + Add data in the given format to the clipboard. May only be + called after a successful empty(). + */ + virtual void add(EFormat, const String& data) = 0; + + //@} + //! @name accessors + //@{ + + //! Open clipboard + /*! + Open the clipboard. Return true iff the clipboard could be + opened. If open() returns true then the client must call + close() at some later time; if it returns false then close() + must not be called. \c time should be the current time or + a time in the past when the open should effectively have taken + place. + */ + virtual bool open(Time time) const = 0; + + //! Close clipboard + /*! + Close the clipboard. close() must match a preceding successful + open(). This signals that the clipboard has been filled with + all the necessary data or all data has been read. It does not + mean the clipboard ownership should be released (if it was + taken). + */ + virtual void close() const = 0; + + //! Get time + /*! + Return the timestamp passed to the last successful open(). + */ + virtual Time getTime() const = 0; + + //! Check for data + /*! + Return true iff the clipboard contains data in the given + format. Must be called between a successful open() and close(). + */ + virtual bool has(EFormat) const = 0; + + //! Get data + /*! + Return the data in the given format. Returns the empty string + if there is no data in that format. Must be called between + a successful open() and close(). + */ + virtual String get(EFormat) const = 0; + + //! Marshall clipboard data + /*! + Merge \p clipboard's data into a single buffer that can be later + unmarshalled to restore the clipboard and return the buffer. + */ + static String marshall(const IClipboard* clipboard); + + //! Unmarshall clipboard data + /*! + Extract marshalled clipboard data and store it in \p clipboard. + Sets the clipboard time to \c time. + */ + static void unmarshall(IClipboard* clipboard, + const String& data, Time time); + + //! Copy clipboard + /*! + Transfers all the data in one clipboard to another. The + clipboards can be of any concrete clipboard type (and + they don't have to be the same type). This also sets + the destination clipboard's timestamp to source clipboard's + timestamp. Returns true iff the copy succeeded. + */ + static bool copy(IClipboard* dst, const IClipboard* src); + + //! Copy clipboard + /*! + Transfers all the data in one clipboard to another. The + clipboards can be of any concrete clipboard type (and they + don't have to be the same type). This also sets the + timestamp to \c time. Returns true iff the copy succeeded. + */ + static bool copy(IClipboard* dst, const IClipboard* src, Time); + + //@} + +private: + static UInt32 readUInt32(const char*); + static void writeUInt32(String*, UInt32); +}; diff --git a/src/lib/barrier/IKeyState.cpp b/src/lib/barrier/IKeyState.cpp new file mode 100644 index 0000000..5d1114c --- /dev/null +++ b/src/lib/barrier/IKeyState.cpp @@ -0,0 +1,161 @@ +/* + * 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 "barrier/IKeyState.h" +#include "base/EventQueue.h" + +#include <cstring> +#include <cstdlib> + +// +// IKeyState +// + +IKeyState::IKeyState(IEventQueue* events) +{ +} + +// +// IKeyState::KeyInfo +// + +IKeyState::KeyInfo* +IKeyState::KeyInfo::alloc(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count) +{ + KeyInfo* info = (KeyInfo*)malloc(sizeof(KeyInfo)); + info->m_key = id; + info->m_mask = mask; + info->m_button = button; + info->m_count = count; + info->m_screens = NULL; + info->m_screensBuffer[0] = '\0'; + return info; +} + +IKeyState::KeyInfo* +IKeyState::KeyInfo::alloc(KeyID id, + KeyModifierMask mask, KeyButton button, SInt32 count, + const std::set<String>& destinations) +{ + String screens = join(destinations); + + // build structure + KeyInfo* info = (KeyInfo*)malloc(sizeof(KeyInfo) + screens.size()); + info->m_key = id; + info->m_mask = mask; + info->m_button = button; + info->m_count = count; + info->m_screens = info->m_screensBuffer; + strcpy(info->m_screensBuffer, screens.c_str()); + return info; +} + +IKeyState::KeyInfo* +IKeyState::KeyInfo::alloc(const KeyInfo& x) +{ + KeyInfo* info = (KeyInfo*)malloc(sizeof(KeyInfo) + + strlen(x.m_screensBuffer)); + info->m_key = x.m_key; + info->m_mask = x.m_mask; + info->m_button = x.m_button; + info->m_count = x.m_count; + info->m_screens = x.m_screens ? info->m_screensBuffer : NULL; + strcpy(info->m_screensBuffer, x.m_screensBuffer); + return info; +} + +bool +IKeyState::KeyInfo::isDefault(const char* screens) +{ + return (screens == NULL || screens[0] == '\0'); +} + +bool +IKeyState::KeyInfo::contains(const char* screens, const String& name) +{ + // special cases + if (isDefault(screens)) { + return false; + } + if (screens[0] == '*') { + return true; + } + + // search + String match; + match.reserve(name.size() + 2); + match += ":"; + match += name; + match += ":"; + return (strstr(screens, match.c_str()) != NULL); +} + +bool +IKeyState::KeyInfo::equal(const KeyInfo* a, const KeyInfo* b) +{ + return (a->m_key == b->m_key && + a->m_mask == b->m_mask && + a->m_button == b->m_button && + a->m_count == b->m_count && + strcmp(a->m_screensBuffer, b->m_screensBuffer) == 0); +} + +String +IKeyState::KeyInfo::join(const std::set<String>& destinations) +{ + // collect destinations into a string. names are surrounded by ':' + // which makes searching easy. the string is empty if there are no + // destinations and "*" means all destinations. + String screens; + for (std::set<String>::const_iterator i = destinations.begin(); + i != destinations.end(); ++i) { + if (*i == "*") { + screens = "*"; + break; + } + else { + if (screens.empty()) { + screens = ":"; + } + screens += *i; + screens += ":"; + } + } + return screens; +} + +void +IKeyState::KeyInfo::split(const char* screens, std::set<String>& dst) +{ + dst.clear(); + if (isDefault(screens)) { + return; + } + if (screens[0] == '*') { + dst.insert("*"); + return; + } + + const char* i = screens + 1; + while (*i != '\0') { + const char* j = strchr(i, ':'); + dst.insert(String(i, j - i)); + i = j + 1; + } +} diff --git a/src/lib/barrier/IKeyState.h b/src/lib/barrier/IKeyState.h new file mode 100644 index 0000000..b9d4706 --- /dev/null +++ b/src/lib/barrier/IKeyState.h @@ -0,0 +1,174 @@ +/* + * 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 "barrier/key_types.h" +#include "base/Event.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "base/EventTypes.h" +#include "common/stdset.h" +#include "common/IInterface.h" + +//! Key state interface +/*! +This interface provides access to set and query the keyboard state and +to synthesize key events. +*/ +class IKeyState : public IInterface { +public: + IKeyState(IEventQueue* events); + + enum { + kNumButtons = 0x200 + }; + + //! Key event data + class KeyInfo { + public: + static KeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count); + static KeyInfo* alloc(KeyID, KeyModifierMask, KeyButton, SInt32 count, + const std::set<String>& destinations); + static KeyInfo* alloc(const KeyInfo&); + + static bool isDefault(const char* screens); + static bool contains(const char* screens, const String& name); + static bool equal(const KeyInfo*, const KeyInfo*); + static String join(const std::set<String>& destinations); + static void split(const char* screens, std::set<String>&); + + public: + KeyID m_key; + KeyModifierMask m_mask; + KeyButton m_button; + SInt32 m_count; + char* m_screens; + char m_screensBuffer[1]; + }; + + typedef std::set<KeyButton> KeyButtonSet; + + //! @name manipulators + //@{ + + //! Update the keyboard map + /*! + Causes the key state to get updated to reflect the current keyboard + mapping. + */ + virtual void updateKeyMap() = 0; + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state. + */ + virtual void updateKeyState() = 0; + + //! Set half-duplex mask + /*! + Sets which modifier toggle keys are half-duplex. A half-duplex + toggle key doesn't report a key release when toggled on and + doesn't report a key press when toggled off. + */ + virtual void setHalfDuplexMask(KeyModifierMask) = 0; + + //! Fake a key press + /*! + Synthesizes a key press event and updates the key state. + */ + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) = 0; + + //! Fake a key repeat + /*! + Synthesizes a key repeat event and updates the key state. + */ + virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) = 0; + + //! Fake a key release + /*! + Synthesizes a key release event and updates the key state. + */ + virtual bool fakeKeyUp(KeyButton button) = 0; + + //! Fake key releases for all fake pressed keys + /*! + Synthesizes a key release event for every key that is synthetically + pressed and updates the key state. + */ + virtual void fakeAllKeysUp() = 0; + + //! Fake ctrl+alt+del + /*! + Synthesize a press of ctrl+alt+del. Return true if processing is + complete and false if normal key processing should continue. + */ + virtual bool fakeCtrlAltDel() = 0; + + //! Fake a media key + /*! + Synthesizes a media key down and up. Only Mac would implement this by + use cocoa appkit framework. + */ + virtual bool fakeMediaKey(KeyID id) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if key is pressed + /*! + Returns true iff the given key is down. Half-duplex toggles + always return false. + */ + virtual bool isKeyDown(KeyButton) const = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. + */ + virtual KeyModifierMask + getActiveModifiers() const = 0; + + //! Get the active modifiers from OS + /*! + Returns the modifiers that are currently active according to the + operating system. + */ + virtual KeyModifierMask + pollActiveModifiers() const = 0; + + //! Get the active keyboard layout from OS + /*! + Returns the active keyboard layout according to the operating system. + */ + virtual SInt32 pollActiveGroup() const = 0; + + //! Get the keys currently pressed from OS + /*! + Adds any keys that are currently pressed according to the operating + system to \p pressedKeys. + */ + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + + //@} +}; diff --git a/src/lib/barrier/INode.h b/src/lib/barrier/INode.h new file mode 100644 index 0000000..2e78f7c --- /dev/null +++ b/src/lib/barrier/INode.h @@ -0,0 +1,25 @@ +/* + * 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 INode : IInterface { + +}; diff --git a/src/lib/barrier/IPlatformScreen.cpp b/src/lib/barrier/IPlatformScreen.cpp new file mode 100644 index 0000000..d1d9f78 --- /dev/null +++ b/src/lib/barrier/IPlatformScreen.cpp @@ -0,0 +1,24 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 "barrier/IPlatformScreen.h" + +bool +IPlatformScreen::fakeMediaKey(KeyID id) +{ + return false; +} diff --git a/src/lib/barrier/IPlatformScreen.h b/src/lib/barrier/IPlatformScreen.h new file mode 100644 index 0000000..440e218 --- /dev/null +++ b/src/lib/barrier/IPlatformScreen.h @@ -0,0 +1,227 @@ +/* + * 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 "barrier/DragInformation.h" +#include "barrier/clipboard_types.h" +#include "barrier/IScreen.h" +#include "barrier/IPrimaryScreen.h" +#include "barrier/ISecondaryScreen.h" +#include "barrier/IKeyState.h" +#include "barrier/option_types.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all platform dependent +screen implementations that are used by both primary and secondary +screens. +*/ +class IPlatformScreen : public IScreen, + public IPrimaryScreen, public ISecondaryScreen, + public IKeyState { +public: + //! @name manipulators + //@{ + + IPlatformScreen(IEventQueue* events) : IKeyState(events) { } + + //! Enable screen + /*! + Enable the screen, preparing it to report system and user events. + For a secondary screen it also means preparing to synthesize events + and hiding the cursor. + */ + virtual void enable() = 0; + + //! Disable screen + /*! + Undoes the operations in enable() and events should no longer + be reported. + */ + virtual void disable() = 0; + + //! Enter screen + /*! + Called when the user navigates to this screen. + */ + virtual void enter() = 0; + + //! Leave screen + /*! + Called when the user navigates off the screen. Returns true on + success, false on failure. A typical reason for failure is being + unable to install the keyboard and mouse snoopers on a primary + screen. Secondary screens should not fail. + */ + virtual bool leave() = 0; + + //! Set clipboard + /*! + Set the contents of the system clipboard indicated by \c id. + */ + virtual bool setClipboard(ClipboardID id, const IClipboard*) = 0; + + //! Check clipboard owner + /*! + Check ownership of all clipboards and post grab events for any that + have changed. This is used as a backup in case the system doesn't + reliably report clipboard ownership changes. + */ + virtual void checkClipboards() = 0; + + //! Open screen saver + /*! + Open the screen saver. If \c notify is true then this object must + send events when the screen saver activates or deactivates until + \c closeScreensaver() is called. If \c notify is false then the + screen saver is disabled and restored on \c closeScreensaver(). + */ + virtual void openScreensaver(bool notify) = 0; + + //! Close screen saver + /*! + // Close the screen saver. Stop reporting screen saver activation + and deactivation and, if the screen saver was disabled by + openScreensaver(), enable the screen saver. + */ + virtual void closeScreensaver() = 0; + + //! Activate/deactivate screen saver + /*! + Forcibly activate the screen saver if \c activate is true otherwise + forcibly deactivate it. + */ + virtual void screensaver(bool activate) = 0; + + //! Notify of options changes + /*! + Reset all options to their default values. + */ + virtual void resetOptions() = 0; + + //! Notify of options changes + /*! + Set options to given values. Ignore unknown options and don't + modify options that aren't given in \c options. + */ + virtual void setOptions(const OptionsList& options) = 0; + + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + virtual void setSequenceNumber(UInt32) = 0; + + //! Change dragging status + virtual void setDraggingStarted(bool started) = 0; + + //@} + //! @name accessors + //@{ + + //! Test if is primary screen + /*! + Return true iff this screen is a primary screen. + */ + virtual bool isPrimary() const = 0; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides) = 0; + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask) = 0; + virtual void unregisterHotKey(UInt32 id) = 0; + virtual void fakeInputBegin() = 0; + virtual void fakeInputEnd() = 0; + virtual SInt32 getJumpZoneSize() const = 0; + virtual bool isAnyMouseButtonDown(UInt32& buttonID) const = 0; + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press) = 0; + virtual void fakeMouseMove(SInt32 x, SInt32 y) = 0; + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + // IKeyState overrides + virtual void updateKeyMap() = 0; + virtual void updateKeyState() = 0; + virtual void setHalfDuplexMask(KeyModifierMask) = 0; + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) = 0; + virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) = 0; + virtual bool fakeKeyUp(KeyButton button) = 0; + virtual void fakeAllKeysUp() = 0; + virtual bool fakeCtrlAltDel() = 0; + virtual bool fakeMediaKey(KeyID id); + virtual bool isKeyDown(KeyButton) const = 0; + virtual KeyModifierMask + getActiveModifiers() const = 0; + virtual KeyModifierMask + pollActiveModifiers() const = 0; + virtual SInt32 pollActiveGroup() const = 0; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + + virtual String& getDraggingFilename() = 0; + virtual void clearDraggingFilename() = 0; + virtual bool isDraggingStarted() = 0; + virtual bool isFakeDraggingStarted() = 0; + + virtual void fakeDraggingFiles(DragFileList fileList) = 0; + virtual const String& + getDropTarget() const = 0; + +protected: + //! Handle system event + /*! + A platform screen is expected to install a handler for system + events in its c'tor like so: + \code + m_events->adoptHandler(Event::kSystem, + m_events->getSystemTarget(), + new TMethodEventJob<CXXXPlatformScreen>(this, + &CXXXPlatformScreen::handleSystemEvent)); + \endcode + It should remove the handler in its d'tor. Override the + \c handleSystemEvent() method to process system events. + It should post the events \c IScreen as appropriate. + + A primary screen has further responsibilities. It should post + the events in \c IPrimaryScreen as appropriate. It should also + call \c onKey() on its \c KeyState whenever a key is pressed + or released (but not for key repeats). And it should call + \c updateKeyMap() on its \c KeyState if necessary when the keyboard + mapping changes. + + The target of all events should be the value returned by + \c getEventTarget(). + */ + virtual void handleSystemEvent(const Event& event, void*) = 0; +}; diff --git a/src/lib/barrier/IPrimaryScreen.cpp b/src/lib/barrier/IPrimaryScreen.cpp new file mode 100644 index 0000000..4954e4f --- /dev/null +++ b/src/lib/barrier/IPrimaryScreen.cpp @@ -0,0 +1,91 @@ +/* + * 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 "barrier/IPrimaryScreen.h" +#include "base/EventQueue.h" + +#include <cstdlib> + +// +// IPrimaryScreen::ButtonInfo +// + +IPrimaryScreen::ButtonInfo* +IPrimaryScreen::ButtonInfo::alloc(ButtonID id, KeyModifierMask mask) +{ + ButtonInfo* info = (ButtonInfo*)malloc(sizeof(ButtonInfo)); + info->m_button = id; + info->m_mask = mask; + return info; +} + +IPrimaryScreen::ButtonInfo* +IPrimaryScreen::ButtonInfo::alloc(const ButtonInfo& x) +{ + ButtonInfo* info = (ButtonInfo*)malloc(sizeof(ButtonInfo)); + info->m_button = x.m_button; + info->m_mask = x.m_mask; + return info; +} + +bool +IPrimaryScreen::ButtonInfo::equal(const ButtonInfo* a, const ButtonInfo* b) +{ + return (a->m_button == b->m_button && a->m_mask == b->m_mask); +} + + +// +// IPrimaryScreen::MotionInfo +// + +IPrimaryScreen::MotionInfo* +IPrimaryScreen::MotionInfo::alloc(SInt32 x, SInt32 y) +{ + MotionInfo* info = (MotionInfo*)malloc(sizeof(MotionInfo)); + info->m_x = x; + info->m_y = y; + return info; +} + + +// +// IPrimaryScreen::WheelInfo +// + +IPrimaryScreen::WheelInfo* +IPrimaryScreen::WheelInfo::alloc(SInt32 xDelta, SInt32 yDelta) +{ + WheelInfo* info = (WheelInfo*)malloc(sizeof(WheelInfo)); + info->m_xDelta = xDelta; + info->m_yDelta = yDelta; + return info; +} + + +// +// IPrimaryScreen::HotKeyInfo +// + +IPrimaryScreen::HotKeyInfo* +IPrimaryScreen::HotKeyInfo::alloc(UInt32 id) +{ + HotKeyInfo* info = (HotKeyInfo*)malloc(sizeof(HotKeyInfo)); + info->m_id = id; + return info; +} diff --git a/src/lib/barrier/IPrimaryScreen.h b/src/lib/barrier/IPrimaryScreen.h new file mode 100644 index 0000000..7f3fa9c --- /dev/null +++ b/src/lib/barrier/IPrimaryScreen.h @@ -0,0 +1,165 @@ +/* + * 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 "barrier/key_types.h" +#include "barrier/mouse_types.h" +#include "base/Event.h" +#include "base/EventTypes.h" +#include "common/IInterface.h" + +//! Primary screen interface +/*! +This interface defines the methods common to all platform dependent +primary screen implementations. +*/ +class IPrimaryScreen : public IInterface { +public: + //! Button event data + class ButtonInfo { + public: + static ButtonInfo* alloc(ButtonID, KeyModifierMask); + static ButtonInfo* alloc(const ButtonInfo&); + + static bool equal(const ButtonInfo*, const ButtonInfo*); + + public: + ButtonID m_button; + KeyModifierMask m_mask; + }; + //! Motion event data + class MotionInfo { + public: + static MotionInfo* alloc(SInt32 x, SInt32 y); + + public: + SInt32 m_x; + SInt32 m_y; + }; + //! Wheel motion event data + class WheelInfo { + public: + static WheelInfo* alloc(SInt32 xDelta, SInt32 yDelta); + + public: + SInt32 m_xDelta; + SInt32 m_yDelta; + }; + //! Hot key event data + class HotKeyInfo { + public: + static HotKeyInfo* alloc(UInt32 id); + + public: + UInt32 m_id; + }; + + //! @name manipulators + //@{ + + //! Update configuration + /*! + This is called when the configuration has changed. \c activeSides + is a bitmask of EDirectionMask indicating which sides of the + primary screen are linked to clients. Override to handle the + possible change in jump zones. + */ + virtual void reconfigure(UInt32 activeSides) = 0; + + //! Warp cursor + /*! + Warp the cursor to the absolute coordinates \c x,y. Also + discard input events up to and including the warp before + returning. + */ + virtual void warpCursor(SInt32 x, SInt32 y) = 0; + + //! Register a system hotkey + /*! + Registers a system-wide hotkey. The screen should arrange for an event + to be delivered to itself when the hot key is pressed or released. When + that happens the screen should post a \c getHotKeyDownEvent() or + \c getHotKeyUpEvent(), respectively. The hot key is key \p key with + exactly the modifiers \p mask. Returns 0 on failure otherwise an id + that can be used to unregister the hotkey. + + A hot key is a set of modifiers and a key, which may itself be a modifier. + The hot key is pressed when the hot key's modifiers and only those + modifiers are logically down (active) and the key is pressed. The hot + key is released when the key is released, regardless of the modifiers. + + The hot key event should be generated no matter what window or application + has the focus. No other window or application should receive the key + press or release events (they can and should see the modifier key events). + When the key is a modifier, it's acceptable to allow the user to press + the modifiers in any order or to require the user to press the given key + last. + */ + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask) = 0; + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + virtual void unregisterHotKey(UInt32 id) = 0; + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() may not be + nested. + */ + virtual void fakeInputBegin() = 0; + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + virtual void fakeInputEnd() = 0; + + //@} + //! @name accessors + //@{ + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + virtual SInt32 getJumpZoneSize() const = 0; + + //! Test if mouse is pressed + /*! + Return true if any mouse button is currently pressed. Ideally, + "current" means up to the last processed event but it can mean + the current physical mouse button state. + */ + virtual bool isAnyMouseButtonDown(UInt32& buttonID) const = 0; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0; + + //@} +}; diff --git a/src/lib/barrier/IScreen.h b/src/lib/barrier/IScreen.h new file mode 100644 index 0000000..47d6578 --- /dev/null +++ b/src/lib/barrier/IScreen.h @@ -0,0 +1,71 @@ +/* + * 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 "barrier/clipboard_types.h" +#include "base/Event.h" +#include "base/EventTypes.h" +#include "common/IInterface.h" + +class IClipboard; + +//! Screen interface +/*! +This interface defines the methods common to all screens. +*/ +class IScreen : public IInterface { +public: + struct ClipboardInfo { + public: + ClipboardID m_id; + UInt32 m_sequenceNumber; + }; + + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the target used for events created by this object. + */ + virtual void* getEventTarget() const = 0; + + //! Get clipboard + /*! + Save the contents of the clipboard indicated by \c id and return + true iff successful. + */ + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + + //! Get screen shape + /*! + Return the position of the upper-left corner of the screen in \c x and + \c y and the size of the screen in \c width and \c height. + */ + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + //@} +}; diff --git a/src/lib/barrier/IScreenSaver.h b/src/lib/barrier/IScreenSaver.h new file mode 100644 index 0000000..fc21ac5 --- /dev/null +++ b/src/lib/barrier/IScreenSaver.h @@ -0,0 +1,75 @@ +/* + * 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 "base/Event.h" +#include "common/IInterface.h" + +//! Screen saver interface +/*! +This interface defines the methods common to all screen savers. +*/ +class IScreenSaver : public IInterface { +public: + // note -- the c'tor/d'tor must *not* enable/disable the screen saver + + //! @name manipulators + //@{ + + //! Enable screen saver + /*! + Enable the screen saver, restoring the screen saver settings to + what they were when disable() was previously called. If disable() + wasn't previously called then it should keep the current settings + or use reasonable defaults. + */ + virtual void enable() = 0; + + //! Disable screen saver + /*! + Disable the screen saver, saving the old settings for the next + call to enable(). + */ + virtual void disable() = 0; + + //! Activate screen saver + /*! + Activate (i.e. show) the screen saver. + */ + virtual void activate() = 0; + + //! Deactivate screen saver + /*! + Deactivate (i.e. hide) the screen saver, reseting the screen saver + timer. + */ + virtual void deactivate() = 0; + + //@} + //! @name accessors + //@{ + + //! Test if screen saver on + /*! + Returns true iff the screen saver is currently active (showing). + */ + virtual bool isActive() const = 0; + + //@} +}; diff --git a/src/lib/barrier/ISecondaryScreen.h b/src/lib/barrier/ISecondaryScreen.h new file mode 100644 index 0000000..527ca2e --- /dev/null +++ b/src/lib/barrier/ISecondaryScreen.h @@ -0,0 +1,61 @@ +/* + * 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 "barrier/mouse_types.h" +#include "base/Event.h" +#include "base/EventTypes.h" +#include "common/IInterface.h" + +//! Secondary screen interface +/*! +This interface defines the methods common to all platform dependent +secondary screen implementations. +*/ +class ISecondaryScreen : public IInterface { +public: + //! @name accessors + //@{ + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + virtual void fakeMouseButton(ButtonID id, bool press) = 0; + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + virtual void fakeMouseMove(SInt32 x, SInt32 y) = 0; + + //! Fake mouse move + /*! + Synthesize a mouse move to the relative coordinates \c dx,dy. + */ + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c xDelta and \c yDelta. + */ + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0; + + //@} +}; diff --git a/src/lib/barrier/KeyMap.cpp b/src/lib/barrier/KeyMap.cpp new file mode 100644 index 0000000..fd68204 --- /dev/null +++ b/src/lib/barrier/KeyMap.cpp @@ -0,0 +1,1344 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2005 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 "barrier/KeyMap.h" +#include "barrier/key_types.h" +#include "base/Log.h" + +#include <assert.h> +#include <cctype> +#include <cstdlib> + +namespace barrier { + +KeyMap::NameToKeyMap* KeyMap::s_nameToKeyMap = NULL; +KeyMap::NameToModifierMap* KeyMap::s_nameToModifierMap = NULL; +KeyMap::KeyToNameMap* KeyMap::s_keyToNameMap = NULL; +KeyMap::ModifierToNameMap* KeyMap::s_modifierToNameMap = NULL; + +KeyMap::KeyMap() : + m_numGroups(0), + m_composeAcrossGroups(false) +{ + m_modifierKeyItem.m_id = kKeyNone; + m_modifierKeyItem.m_group = 0; + m_modifierKeyItem.m_button = 0; + m_modifierKeyItem.m_required = 0; + m_modifierKeyItem.m_sensitive = 0; + m_modifierKeyItem.m_generates = 0; + m_modifierKeyItem.m_dead = false; + m_modifierKeyItem.m_lock = false; + m_modifierKeyItem.m_client = 0; +} + +KeyMap::~KeyMap() +{ + // do nothing +} + +void +KeyMap::swap(KeyMap& x) +{ + m_keyIDMap.swap(x.m_keyIDMap); + m_modifierKeys.swap(x.m_modifierKeys); + m_halfDuplex.swap(x.m_halfDuplex); + m_halfDuplexMods.swap(x.m_halfDuplexMods); + SInt32 tmp1 = m_numGroups; + m_numGroups = x.m_numGroups; + x.m_numGroups = tmp1; + bool tmp2 = m_composeAcrossGroups; + m_composeAcrossGroups = x.m_composeAcrossGroups; + x.m_composeAcrossGroups = tmp2; +} + +void +KeyMap::addKeyEntry(const KeyItem& item) +{ + // ignore kKeyNone + if (item.m_id == kKeyNone) { + return; + } + + // resize number of groups for key + SInt32 numGroups = item.m_group + 1; + if (getNumGroups() > numGroups) { + numGroups = getNumGroups(); + } + KeyGroupTable& groupTable = m_keyIDMap[item.m_id]; + if (groupTable.size() < static_cast<size_t>(numGroups)) { + groupTable.resize(numGroups); + } + + // make a list from the item + KeyItemList items; + items.push_back(item); + + // set group and dead key flag on the item + KeyItem& newItem = items.back(); + newItem.m_dead = isDeadKey(item.m_id); + + // mask the required bits with the sensitive bits + newItem.m_required &= newItem.m_sensitive; + + // see if we already have this item; just return if so + KeyEntryList& entries = groupTable[item.m_group]; + for (size_t i = 0, n = entries.size(); i < n; ++i) { + if (entries[i].size() == 1 && newItem == entries[i][0]) { + return; + } + } + + // add item list + entries.push_back(items); + LOG((CLOG_DEBUG5 "add key: %04x %d %03x %04x (%04x %04x %04x)%s", newItem.m_id, newItem.m_group, newItem.m_button, newItem.m_client, newItem.m_required, newItem.m_sensitive, newItem.m_generates, newItem.m_dead ? " dead" : "")); +} + +void +KeyMap::addKeyAliasEntry(KeyID targetID, SInt32 group, + KeyModifierMask targetRequired, + KeyModifierMask targetSensitive, + KeyID sourceID, + KeyModifierMask sourceRequired, + KeyModifierMask sourceSensitive) +{ + // if we can already generate the target as desired then we're done. + if (findCompatibleKey(targetID, group, targetRequired, + targetSensitive) != NULL) { + return; + } + + // find a compatible source, preferably in the same group + for (SInt32 gd = 0, n = getNumGroups(); gd < n; ++gd) { + SInt32 eg = getEffectiveGroup(group, gd); + const KeyItemList* sourceEntry = + findCompatibleKey(sourceID, eg, + sourceRequired, sourceSensitive); + if (sourceEntry != NULL && sourceEntry->size() == 1) { + KeyMap::KeyItem targetItem = sourceEntry->back(); + targetItem.m_id = targetID; + targetItem.m_group = eg; + addKeyEntry(targetItem); + break; + } + } +} + +bool +KeyMap::addKeyCombinationEntry(KeyID id, SInt32 group, + const KeyID* keys, UInt32 numKeys) +{ + // disallow kKeyNone + if (id == kKeyNone) { + return false; + } + + SInt32 numGroups = group + 1; + if (getNumGroups() > numGroups) { + numGroups = getNumGroups(); + } + KeyGroupTable& groupTable = m_keyIDMap[id]; + if (groupTable.size() < static_cast<size_t>(numGroups)) { + groupTable.resize(numGroups); + } + if (!groupTable[group].empty()) { + // key is already in the table + return false; + } + + // convert to buttons + KeyItemList items; + for (UInt32 i = 0; i < numKeys; ++i) { + KeyIDMap::const_iterator gtIndex = m_keyIDMap.find(keys[i]); + if (gtIndex == m_keyIDMap.end()) { + return false; + } + const KeyGroupTable& groupTable = gtIndex->second; + + // if we allow group switching during composition then search all + // groups for keys, otherwise search just the given group. + SInt32 n = 1; + if (m_composeAcrossGroups) { + n = (SInt32)groupTable.size(); + } + + bool found = false; + for (SInt32 gd = 0; gd < n && !found; ++gd) { + SInt32 eg = (group + gd) % getNumGroups(); + const KeyEntryList& entries = groupTable[eg]; + for (size_t j = 0; j < entries.size(); ++j) { + if (entries[j].size() == 1) { + found = true; + items.push_back(entries[j][0]); + break; + } + } + } + if (!found) { + // required key is not in keyboard group + return false; + } + } + + // add key + groupTable[group].push_back(items); + return true; +} + +void +KeyMap::allowGroupSwitchDuringCompose() +{ + m_composeAcrossGroups = true; +} + +void +KeyMap::addHalfDuplexButton(KeyButton button) +{ + m_halfDuplex.insert(button); +} + +void +KeyMap::clearHalfDuplexModifiers() +{ + m_halfDuplexMods.clear(); +} + +void +KeyMap::addHalfDuplexModifier(KeyID key) +{ + m_halfDuplexMods.insert(key); +} + +void +KeyMap::finish() +{ + m_numGroups = findNumGroups(); + + // make sure every key has the same number of groups + for (KeyIDMap::iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + i->second.resize(m_numGroups); + } + + // compute keys that generate each modifier + setModifierKeys(); +} + +void +KeyMap::foreachKey(ForeachKeyCallback cb, void* userData) +{ + for (KeyIDMap::iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + KeyGroupTable& groupTable = i->second; + for (size_t group = 0; group < groupTable.size(); ++group) { + KeyEntryList& entryList = groupTable[group]; + for (size_t j = 0; j < entryList.size(); ++j) { + KeyItemList& itemList = entryList[j]; + for (size_t k = 0; k < itemList.size(); ++k) { + (*cb)(i->first, static_cast<SInt32>(group), + itemList[k], userData); + } + } + } + } +} + +const KeyMap::KeyItem* +KeyMap::mapKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + LOG((CLOG_DEBUG1 "mapKey %04x (%d) with mask %04x, start state: %04x", id, id, desiredMask, currentState)); + + // handle group change + if (id == kKeyNextGroup) { + keys.push_back(Keystroke(1, false, false)); + return NULL; + } + else if (id == kKeyPrevGroup) { + keys.push_back(Keystroke(-1, false, false)); + return NULL; + } + + const KeyItem* item; + switch (id) { + case kKeyShift_L: + case kKeyShift_R: + case kKeyControl_L: + case kKeyControl_R: + case kKeyAlt_L: + case kKeyAlt_R: + case kKeyMeta_L: + case kKeyMeta_R: + case kKeySuper_L: + case kKeySuper_R: + case kKeyAltGr: + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + item = mapModifierKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + break; + + case kKeySetModifiers: + if (!keysForModifierState(0, group, activeModifiers, currentState, + desiredMask, desiredMask, 0, keys)) { + LOG((CLOG_DEBUG1 "unable to set modifiers %04x", desiredMask)); + return NULL; + } + return &m_modifierKeyItem; + + case kKeyClearModifiers: + if (!keysForModifierState(0, group, activeModifiers, currentState, + currentState & ~desiredMask, + desiredMask, 0, keys)) { + LOG((CLOG_DEBUG1 "unable to clear modifiers %04x", desiredMask)); + return NULL; + } + return &m_modifierKeyItem; + + default: + if (isCommand(desiredMask)) { + item = mapCommandKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + } + else { + item = mapCharacterKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); + } + break; + } + + if (item != NULL) { + LOG((CLOG_DEBUG1 "mapped to %03x, new state %04x", item->m_button, currentState)); + } + return item; +} + +SInt32 +KeyMap::getNumGroups() const +{ + return m_numGroups; +} + +SInt32 +KeyMap::getEffectiveGroup(SInt32 group, SInt32 offset) const +{ + return (group + offset + getNumGroups()) % getNumGroups(); +} + +const KeyMap::KeyItemList* +KeyMap::findCompatibleKey(KeyID id, SInt32 group, + KeyModifierMask required, KeyModifierMask sensitive) const +{ + assert(group >= 0 && group < getNumGroups()); + + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + return NULL; + } + + const KeyEntryList& entries = i->second[group]; + for (size_t j = 0; j < entries.size(); ++j) { + if ((entries[j].back().m_sensitive & sensitive) == 0 || + (entries[j].back().m_required & sensitive) == + (required & sensitive)) { + return &entries[j]; + } + } + + return NULL; +} + +bool +KeyMap::isHalfDuplex(KeyID key, KeyButton button) const +{ + return (m_halfDuplex.count(button) + m_halfDuplexMods.count(key) > 0); +} + +bool +KeyMap::isCommand(KeyModifierMask mask) const +{ + return ((mask & getCommandModifiers()) != 0); +} + +KeyModifierMask +KeyMap::getCommandModifiers() const +{ + // we currently treat ctrl, alt, meta and super as command modifiers. + // some platforms may have a more limited set (OS X only needs Alt) + // but this works anyway. + return KeyModifierControl | + KeyModifierAlt | + KeyModifierAltGr | + KeyModifierMeta | + KeyModifierSuper; +} + +void +KeyMap::collectButtons(const ModifierToKeys& mods, ButtonToKeyMap& keys) +{ + keys.clear(); + for (ModifierToKeys::const_iterator i = mods.begin(); + i != mods.end(); ++i) { + keys.insert(std::make_pair(i->second.m_button, &i->second)); + } +} + +void +KeyMap::initModifierKey(KeyItem& item) +{ + item.m_generates = 0; + item.m_lock = false; + switch (item.m_id) { + case kKeyShift_L: + case kKeyShift_R: + item.m_generates = KeyModifierShift; + break; + + case kKeyControl_L: + case kKeyControl_R: + item.m_generates = KeyModifierControl; + break; + + case kKeyAlt_L: + case kKeyAlt_R: + item.m_generates = KeyModifierAlt; + break; + + case kKeyMeta_L: + case kKeyMeta_R: + item.m_generates = KeyModifierMeta; + break; + + case kKeySuper_L: + case kKeySuper_R: + item.m_generates = KeyModifierSuper; + break; + + case kKeyAltGr: + item.m_generates = KeyModifierAltGr; + break; + + case kKeyCapsLock: + item.m_generates = KeyModifierCapsLock; + item.m_lock = true; + break; + + case kKeyNumLock: + item.m_generates = KeyModifierNumLock; + item.m_lock = true; + break; + + case kKeyScrollLock: + item.m_generates = KeyModifierScrollLock; + item.m_lock = true; + break; + + default: + // not a modifier + break; + } +} + +SInt32 +KeyMap::findNumGroups() const +{ + size_t max = 0; + for (KeyIDMap::const_iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + if (i->second.size() > max) { + max = i->second.size(); + } + } + return static_cast<SInt32>(max); +} + +void +KeyMap::setModifierKeys() +{ + m_modifierKeys.clear(); + m_modifierKeys.resize(kKeyModifierNumBits * getNumGroups()); + for (KeyIDMap::const_iterator i = m_keyIDMap.begin(); + i != m_keyIDMap.end(); ++i) { + const KeyGroupTable& groupTable = i->second; + for (size_t g = 0; g < groupTable.size(); ++g) { + const KeyEntryList& entries = groupTable[g]; + for (size_t j = 0; j < entries.size(); ++j) { + // skip multi-key sequences + if (entries[j].size() != 1) { + continue; + } + + // skip keys that don't generate a modifier + const KeyItem& item = entries[j].back(); + if (item.m_generates == 0) { + continue; + } + + // add key to each indicated modifier in this group + for (SInt32 b = 0; b < kKeyModifierNumBits; ++b) { + // skip if item doesn't generate bit b + if (((1u << b) & item.m_generates) != 0) { + SInt32 mIndex = (SInt32)g * kKeyModifierNumBits + b; + m_modifierKeys[mIndex].push_back(&item); + } + } + } + } + } +} + +const KeyMap::KeyItem* +KeyMap::mapCommandKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + static const KeyModifierMask s_overrideModifiers = 0xffffu; + + // find KeySym in table + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + // unknown key + LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); + return NULL; + } + const KeyGroupTable& keyGroupTable = i->second; + + // find the first key that generates this KeyID + const KeyItem* keyItem = NULL; + SInt32 numGroups = getNumGroups(); + for (SInt32 groupOffset = 0; groupOffset < numGroups; ++groupOffset) { + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + const KeyEntryList& entryList = keyGroupTable[effectiveGroup]; + for (size_t i = 0; i < entryList.size(); ++i) { + if (entryList[i].size() != 1) { + // ignore multikey entries + continue; + } + + // match based on shift and make sure all required modifiers, + // except shift, are already in the desired mask; we're + // after the right button not the right character. + // we'll use desiredMask as-is, overriding the key's required + // modifiers, when synthesizing this button. + const KeyItem& item = entryList[i].back(); + KeyModifierMask desiredShiftMask = KeyModifierShift & desiredMask; + KeyModifierMask requiredIgnoreShiftMask = item.m_required & ~KeyModifierShift; + if ((item.m_required & desiredShiftMask) == (item.m_sensitive & desiredShiftMask) && + ((requiredIgnoreShiftMask & desiredMask) == requiredIgnoreShiftMask)) { + LOG((CLOG_INFO "found key in group %d", effectiveGroup)); + keyItem = &item; + break; + } + } + if (keyItem != NULL) { + break; + } + } + if (keyItem == NULL) { + // no mapping for this keysym + LOG((CLOG_DEBUG1 "no mapping for key %04x", id)); + return NULL; + } + + // make working copy of modifiers + ModifierToKeys newModifiers = activeModifiers; + KeyModifierMask newState = currentState; + SInt32 newGroup = group; + + // don't try to change CapsLock + desiredMask = (desiredMask & ~KeyModifierCapsLock) | + (currentState & KeyModifierCapsLock); + + // add the key + if (!keysForKeyItem(*keyItem, newGroup, newModifiers, + newState, desiredMask, + s_overrideModifiers, isAutoRepeat, keys)) { + LOG((CLOG_DEBUG1 "can't map key")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore modifier keys + if (!keysToRestoreModifiers(*keyItem, group, newModifiers, newState, + activeModifiers, keys)) { + LOG((CLOG_DEBUG1 "failed to restore modifiers")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore group + if (newGroup != group) { + keys.push_back(Keystroke(group, true, true)); + } + + // save new modifiers + activeModifiers = newModifiers; + currentState = newState; + + return keyItem; +} + +const KeyMap::KeyItem* +KeyMap::mapCharacterKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + // find KeySym in table + KeyIDMap::const_iterator i = m_keyIDMap.find(id); + if (i == m_keyIDMap.end()) { + // unknown key + LOG((CLOG_DEBUG1 "key %04x is not on keyboard", id)); + return NULL; + } + const KeyGroupTable& keyGroupTable = i->second; + + // find best key in any group, starting with the active group + SInt32 keyIndex = -1; + SInt32 numGroups = getNumGroups(); + SInt32 groupOffset; + LOG((CLOG_DEBUG1 "find best: %04x %04x", currentState, desiredMask)); + for (groupOffset = 0; groupOffset < numGroups; ++groupOffset) { + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + keyIndex = findBestKey(keyGroupTable[effectiveGroup], + currentState, desiredMask); + if (keyIndex != -1) { + LOG((CLOG_DEBUG1 "found key in group %d", effectiveGroup)); + break; + } + } + if (keyIndex == -1) { + // no mapping for this keysym + LOG((CLOG_DEBUG1 "no mapping for key %04x", id)); + return NULL; + } + + // get keys to press for key + SInt32 effectiveGroup = getEffectiveGroup(group, groupOffset); + const KeyItemList& itemList = keyGroupTable[effectiveGroup][keyIndex]; + if (itemList.empty()) { + return NULL; + } + const KeyItem& keyItem = itemList.back(); + + // make working copy of modifiers + ModifierToKeys newModifiers = activeModifiers; + KeyModifierMask newState = currentState; + SInt32 newGroup = group; + + // add each key + for (size_t j = 0; j < itemList.size(); ++j) { + if (!keysForKeyItem(itemList[j], newGroup, newModifiers, + newState, desiredMask, + 0, isAutoRepeat, keys)) { + LOG((CLOG_DEBUG1 "can't map key")); + keys.clear(); + return NULL; + } + } + + // add keystrokes to restore modifier keys + if (!keysToRestoreModifiers(keyItem, group, newModifiers, newState, + activeModifiers, keys)) { + LOG((CLOG_DEBUG1 "failed to restore modifiers")); + keys.clear(); + return NULL; + } + + // add keystrokes to restore group + if (newGroup != group) { + keys.push_back(Keystroke(group, true, true)); + } + + // save new modifiers + activeModifiers = newModifiers; + currentState = newState; + + return &keyItem; +} + +const KeyMap::KeyItem* +KeyMap::mapModifierKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const +{ + return mapCharacterKey(keys, id, group, activeModifiers, + currentState, desiredMask, isAutoRepeat); +} + +SInt32 +KeyMap::findBestKey(const KeyEntryList& entryList, + KeyModifierMask /*currentState*/, + KeyModifierMask desiredState) const +{ + // check for an item that can accommodate the desiredState exactly + for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) { + const KeyItem& item = entryList[i].back(); + if ((item.m_required & desiredState) == item.m_required && + (item.m_required & desiredState) == (item.m_sensitive & desiredState)) { + LOG((CLOG_DEBUG1 "best key index %d of %d (exact)", i + 1, entryList.size())); + return i; + } + } + + // choose the item that requires the fewest modifier changes + SInt32 bestCount = 32; + SInt32 bestIndex = -1; + for (SInt32 i = 0; i < (SInt32)entryList.size(); ++i) { + const KeyItem& item = entryList[i].back(); + KeyModifierMask change = + ((item.m_required ^ desiredState) & item.m_sensitive); + SInt32 n = getNumModifiers(change); + if (n < bestCount) { + bestCount = n; + bestIndex = i; + } + } + if (bestIndex != -1) { + LOG((CLOG_DEBUG1 "best key index %d of %d (%d modifiers)", + bestIndex + 1, entryList.size(), bestCount)); + } + + return bestIndex; +} + + +const KeyMap::KeyItem* +KeyMap::keyForModifier(KeyButton button, SInt32 group, + SInt32 modifierBit) const +{ + assert(modifierBit >= 0 && modifierBit < kKeyModifierNumBits); + assert(group >= 0 && group < getNumGroups()); + + // find a key that generates the given modifier in the given group + // but doesn't use the given button, presumably because we're trying + // to generate a KeyID that's only bound the the given button. + // this is important when a shift button is modified by shift; we + // must use the other shift button to do the shifting. + const ModifierKeyItemList& items = + m_modifierKeys[group * kKeyModifierNumBits + modifierBit]; + for (ModifierKeyItemList::const_iterator i = items.begin(); + i != items.end(); ++i) { + if ((*i)->m_button != button) { + return (*i); + } + } + return NULL; +} + +bool +KeyMap::keysForKeyItem(const KeyItem& keyItem, SInt32& group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, KeyModifierMask desiredState, + KeyModifierMask overrideModifiers, + bool isAutoRepeat, + Keystrokes& keystrokes) const +{ + static const KeyModifierMask s_notRequiredMask = + KeyModifierAltGr | KeyModifierNumLock | KeyModifierScrollLock; + + // add keystrokes to adjust the group + if (group != keyItem.m_group) { + group = keyItem.m_group; + keystrokes.push_back(Keystroke(group, true, false)); + } + + EKeystroke type; + if (keyItem.m_dead) { + // adjust modifiers for dead key + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + keyItem.m_required, keyItem.m_sensitive, + 0, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match modifier state for dead key %d", keyItem.m_button)); + return false; + } + + // press and release the dead key + type = kKeystrokeClick; + } + else { + // if this a command key then we don't have to match some of the + // key's required modifiers. + KeyModifierMask sensitive = keyItem.m_sensitive & ~overrideModifiers; + + // XXX -- must handle pressing a modifier. in particular, if we want + // to synthesize a KeyID on level 1 of a KeyButton that has Shift_L + // mapped to level 0 then we must release that button if it's down + // (in order to satisfy a shift modifier) then press a different + // button (any other button) mapped to the shift modifier and then + // the Shift_L button. + // match key's required state + LOG((CLOG_DEBUG1 "state: %04x,%04x,%04x", currentState, keyItem.m_required, sensitive)); + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + keyItem.m_required, sensitive, + 0, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match modifier state (%04x,%04x) for key %d", keyItem.m_required, keyItem.m_sensitive, keyItem.m_button)); + return false; + } + + // match desiredState as closely as possible. we must not + // change any modifiers in keyItem.m_sensitive. and if the key + // is a modifier, we don't want to change that modifier. + LOG((CLOG_DEBUG1 "desired state: %04x %04x,%04x,%04x", desiredState, currentState, keyItem.m_required, keyItem.m_sensitive)); + if (!keysForModifierState(keyItem.m_button, group, + activeModifiers, currentState, + desiredState, + ~(sensitive | keyItem.m_generates), + s_notRequiredMask, keystrokes)) { + LOG((CLOG_DEBUG1 "unable to match desired modifier state (%04x,%04x) for key %d", desiredState, ~keyItem.m_sensitive & 0xffffu, keyItem.m_button)); + return false; + } + + // repeat or press of key + type = isAutoRepeat ? kKeystrokeRepeat : kKeystrokePress; + } + addKeystrokes(type, keyItem, activeModifiers, currentState, keystrokes); + + return true; +} + +bool +KeyMap::keysToRestoreModifiers(const KeyItem& keyItem, SInt32, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + const ModifierToKeys& desiredModifiers, + Keystrokes& keystrokes) const +{ + // XXX -- we're not considering modified modifiers here + + ModifierToKeys oldModifiers = activeModifiers; + + // get the pressed modifier buttons before and after + ButtonToKeyMap oldKeys, newKeys; + collectButtons(oldModifiers, oldKeys); + collectButtons(desiredModifiers, newKeys); + + // release unwanted keys + for (ModifierToKeys::const_iterator i = oldModifiers.begin(); + i != oldModifiers.end(); ++i) { + KeyButton button = i->second.m_button; + if (button != keyItem.m_button && newKeys.count(button) == 0) { + EKeystroke type = kKeystrokeRelease; + if (i->second.m_lock) { + type = kKeystrokeUnmodify; + } + addKeystrokes(type, i->second, + activeModifiers, currentState, keystrokes); + } + } + + // press wanted keys + for (ModifierToKeys::const_iterator i = desiredModifiers.begin(); + i != desiredModifiers.end(); ++i) { + KeyButton button = i->second.m_button; + if (button != keyItem.m_button && oldKeys.count(button) == 0) { + EKeystroke type = kKeystrokePress; + if (i->second.m_lock) { + type = kKeystrokeModify; + } + addKeystrokes(type, i->second, + activeModifiers, currentState, keystrokes); + } + } + + return true; +} + +bool +KeyMap::keysForModifierState(KeyButton button, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask requiredState, KeyModifierMask sensitiveMask, + KeyModifierMask notRequiredMask, + Keystrokes& keystrokes) const +{ + // compute which modifiers need changing + KeyModifierMask flipMask = ((currentState ^ requiredState) & sensitiveMask); + // if a modifier is not required then don't even try to match it. if + // we don't mask out notRequiredMask then we'll try to match those + // modifiers but succeed if we can't. however, this is known not + // to work if the key itself is a modifier (the numlock toggle can + // interfere) so we don't try to match at all. + flipMask &= ~notRequiredMask; + LOG((CLOG_DEBUG1 "flip: %04x (%04x vs %04x in %04x - %04x)", flipMask, currentState, requiredState, sensitiveMask & 0xffffu, notRequiredMask & 0xffffu)); + if (flipMask == 0) { + return true; + } + + // fix modifiers. this is complicated by the fact that a modifier may + // be sensitive to other modifiers! (who thought that up?) + // + // we'll assume that modifiers with higher bits are affected by modifiers + // with lower bits. there's not much basis for that assumption except + // that we're pretty sure shift isn't changed by other modifiers. + for (SInt32 bit = kKeyModifierNumBits; bit-- > 0; ) { + KeyModifierMask mask = (1u << bit); + if ((flipMask & mask) == 0) { + // modifier is already correct + continue; + } + + // do we want the modifier active or inactive? + bool active = ((requiredState & mask) != 0); + + // get the KeyItem for the modifier in the group + const KeyItem* keyItem = keyForModifier(button, group, bit); + if (keyItem == NULL) { + if ((mask & notRequiredMask) == 0) { + LOG((CLOG_DEBUG1 "no key for modifier %04x", mask)); + return false; + } + else { + continue; + } + } + + // if this modifier is sensitive to modifiers then adjust those + // modifiers. also check if our assumption was correct. note + // that we only need to adjust the modifiers on key down. + KeyModifierMask sensitive = keyItem->m_sensitive; + if ((sensitive & mask) != 0) { + // modifier is sensitive to itself. that makes no sense + // so ignore it. + LOG((CLOG_DEBUG1 "modifier %04x modified by itself", mask)); + sensitive &= ~mask; + } + if (sensitive != 0) { + if (sensitive > mask) { + // our assumption is incorrect + LOG((CLOG_DEBUG1 "modifier %04x modified by %04x", mask, sensitive)); + return false; + } + if (active && !keysForModifierState(button, group, + activeModifiers, currentState, + keyItem->m_required, sensitive, + notRequiredMask, keystrokes)) { + return false; + } + else if (!active) { + // release the modifier + // XXX -- this doesn't work! if Alt and Meta are mapped + // to one key and we want to release Meta we can't do + // that without also releasing Alt. + // need to think about support for modified modifiers. + } + } + + // current state should match required state + if ((currentState & sensitive) != (keyItem->m_required & sensitive)) { + LOG((CLOG_DEBUG1 "unable to match modifier state for modifier %04x (%04x vs %04x in %04x)", mask, currentState, keyItem->m_required, sensitive)); + return false; + } + + // add keystrokes + EKeystroke type = active ? kKeystrokeModify : kKeystrokeUnmodify; + addKeystrokes(type, *keyItem, activeModifiers, currentState, + keystrokes); + } + + return true; +} + +void +KeyMap::addKeystrokes(EKeystroke type, const KeyItem& keyItem, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + Keystrokes& keystrokes) const +{ + KeyButton button = keyItem.m_button; + UInt32 data = keyItem.m_client; + switch (type) { + case kKeystrokePress: + keystrokes.push_back(Keystroke(button, true, false, data)); + if (keyItem.m_generates != 0) { + if (!keyItem.m_lock || (currentState & keyItem.m_generates) == 0) { + // add modifier key and activate modifier + activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + currentState |= keyItem.m_generates; + } + else { + // deactivate locking modifier + activeModifiers.erase(keyItem.m_generates); + currentState &= ~keyItem.m_generates; + } + } + break; + + case kKeystrokeRelease: + keystrokes.push_back(Keystroke(button, false, false, data)); + if (keyItem.m_generates != 0 && !keyItem.m_lock) { + // remove key from active modifiers + std::pair<ModifierToKeys::iterator, + ModifierToKeys::iterator> range = + activeModifiers.equal_range(keyItem.m_generates); + for (ModifierToKeys::iterator i = range.first; + i != range.second; ++i) { + if (i->second.m_button == button) { + activeModifiers.erase(i); + break; + } + } + + // if no more keys for this modifier then deactivate modifier + if (activeModifiers.count(keyItem.m_generates) == 0) { + currentState &= ~keyItem.m_generates; + } + } + break; + + case kKeystrokeRepeat: + keystrokes.push_back(Keystroke(button, false, true, data)); + keystrokes.push_back(Keystroke(button, true, true, data)); + // no modifier changes on key repeat + break; + + case kKeystrokeClick: + keystrokes.push_back(Keystroke(button, true, false, data)); + keystrokes.push_back(Keystroke(button, false, false, data)); + // no modifier changes on key click + break; + + case kKeystrokeModify: + case kKeystrokeUnmodify: + if (keyItem.m_lock) { + // we assume there's just one button for this modifier + if (m_halfDuplex.count(button) > 0) { + if (type == kKeystrokeModify) { + // turn half-duplex toggle on (press) + keystrokes.push_back(Keystroke(button, true, false, data)); + } + else { + // turn half-duplex toggle off (release) + keystrokes.push_back(Keystroke(button, false, false, data)); + } + } + else { + // toggle (click) + keystrokes.push_back(Keystroke(button, true, false, data)); + keystrokes.push_back(Keystroke(button, false, false, data)); + } + } + else if (type == kKeystrokeModify) { + // press modifier + keystrokes.push_back(Keystroke(button, true, false, data)); + } + else { + // release all the keys that generate the modifier that are + // currently down + std::pair<ModifierToKeys::const_iterator, + ModifierToKeys::const_iterator> range = + activeModifiers.equal_range(keyItem.m_generates); + for (ModifierToKeys::const_iterator i = range.first; + i != range.second; ++i) { + keystrokes.push_back(Keystroke(i->second.m_button, + false, false, i->second.m_client)); + } + } + + if (type == kKeystrokeModify) { + activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + currentState |= keyItem.m_generates; + } + else { + activeModifiers.erase(keyItem.m_generates); + currentState &= ~keyItem.m_generates; + } + break; + } +} + +SInt32 +KeyMap::getNumModifiers(KeyModifierMask state) +{ + SInt32 n = 0; + for (; state != 0; state >>= 1) { + if ((state & 1) != 0) { + ++n; + } + } + return n; +} + +bool +KeyMap::isDeadKey(KeyID key) +{ + return (key == kKeyCompose || (key >= 0x0300 && key <= 0x036f)); +} + +KeyID +KeyMap::getDeadKey(KeyID key) +{ + if (isDeadKey(key)) { + // already dead + return key; + } + + switch (key) { + case '`': + return kKeyDeadGrave; + + case 0xb4u: + return kKeyDeadAcute; + + case '^': + case 0x2c6: + return kKeyDeadCircumflex; + + case '~': + case 0x2dcu: + return kKeyDeadTilde; + + case 0xafu: + return kKeyDeadMacron; + + case 0x2d8u: + return kKeyDeadBreve; + + case 0x2d9u: + return kKeyDeadAbovedot; + + case 0xa8u: + return kKeyDeadDiaeresis; + + case 0xb0u: + case 0x2dau: + return kKeyDeadAbovering; + + case '\"': + case 0x2ddu: + return kKeyDeadDoubleacute; + + case 0x2c7u: + return kKeyDeadCaron; + + case 0xb8u: + return kKeyDeadCedilla; + + case 0x2dbu: + return kKeyDeadOgonek; + + default: + // unknown + return kKeyNone; + } +} + +String +KeyMap::formatKey(KeyID key, KeyModifierMask mask) +{ + // initialize tables + initKeyNameMaps(); + + String x; + for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) { + KeyModifierMask mod = (1u << i); + if ((mask & mod) != 0 && s_modifierToNameMap->count(mod) > 0) { + x += s_modifierToNameMap->find(mod)->second; + x += "+"; + } + } + if (key != kKeyNone) { + if (s_keyToNameMap->count(key) > 0) { + x += s_keyToNameMap->find(key)->second; + } + // XXX -- we're assuming ASCII here + else if (key >= 33 && key < 127) { + x += (char)key; + } + else { + x += barrier::string::sprintf("\\u%04x", key); + } + } + else if (!x.empty()) { + // remove trailing '+' + x.erase(x.size() - 1); + } + return x; +} + +bool +KeyMap::parseKey(const String& x, KeyID& key) +{ + // initialize tables + initKeyNameMaps(); + + // parse the key + key = kKeyNone; + if (s_nameToKeyMap->count(x) > 0) { + key = s_nameToKeyMap->find(x)->second; + } + // XXX -- we're assuming ASCII encoding here + else if (x.size() == 1) { + if (!isgraph(x[0])) { + // unknown key + return false; + } + key = (KeyID)x[0]; + } + else if (x.size() == 6 && x[0] == '\\' && x[1] == 'u') { + // escaped unicode (\uXXXX where XXXX is a hex number) + char* end; + key = (KeyID)strtol(x.c_str() + 2, &end, 16); + if (*end != '\0') { + return false; + } + } + else if (!x.empty()) { + // unknown key + return false; + } + + return true; +} + +bool +KeyMap::parseModifiers(String& x, KeyModifierMask& mask) +{ + // initialize tables + initKeyNameMaps(); + + mask = 0; + String::size_type tb = x.find_first_not_of(" \t", 0); + while (tb != String::npos) { + // get next component + String::size_type te = x.find_first_of(" \t+)", tb); + if (te == String::npos) { + te = x.size(); + } + String c = x.substr(tb, te - tb); + if (c.empty()) { + // missing component + return false; + } + + if (s_nameToModifierMap->count(c) > 0) { + KeyModifierMask mod = s_nameToModifierMap->find(c)->second; + if ((mask & mod) != 0) { + // modifier appears twice + return false; + } + mask |= mod; + } + else { + // unknown string + x.erase(0, tb); + String::size_type tb = x.find_first_not_of(" \t"); + String::size_type te = x.find_last_not_of(" \t"); + if (tb == String::npos) { + x = ""; + } + else { + x = x.substr(tb, te - tb + 1); + } + return true; + } + + // check for '+' or end of string + tb = x.find_first_not_of(" \t", te); + if (tb != String::npos) { + if (x[tb] != '+') { + // expected '+' + return false; + } + tb = x.find_first_not_of(" \t", tb + 1); + } + } + + // parsed the whole thing + x = ""; + return true; +} + +void +KeyMap::initKeyNameMaps() +{ + // initialize tables + if (s_nameToKeyMap == NULL) { + s_nameToKeyMap = new NameToKeyMap; + s_keyToNameMap = new KeyToNameMap; + for (const KeyNameMapEntry* i = kKeyNameMap; i->m_name != NULL; ++i) { + (*s_nameToKeyMap)[i->m_name] = i->m_id; + (*s_keyToNameMap)[i->m_id] = i->m_name; + } + } + if (s_nameToModifierMap == NULL) { + s_nameToModifierMap = new NameToModifierMap; + s_modifierToNameMap = new ModifierToNameMap; + for (const KeyModifierNameMapEntry* i = kModifierNameMap; + i->m_name != NULL; ++i) { + (*s_nameToModifierMap)[i->m_name] = i->m_mask; + (*s_modifierToNameMap)[i->m_mask] = i->m_name; + } + } +} + + +// +// KeyMap::KeyItem +// + +bool +KeyMap::KeyItem::operator==(const KeyItem& x) const +{ + return (m_id == x.m_id && + m_group == x.m_group && + m_button == x.m_button && + m_required == x.m_required && + m_sensitive == x.m_sensitive && + m_generates == x.m_generates && + m_dead == x.m_dead && + m_lock == x.m_lock && + m_client == x.m_client); +} + + +// +// KeyMap::Keystroke +// + +KeyMap::Keystroke::Keystroke(KeyButton button, + bool press, bool repeat, UInt32 data) : + m_type(kButton) +{ + m_data.m_button.m_button = button; + m_data.m_button.m_press = press; + m_data.m_button.m_repeat = repeat; + m_data.m_button.m_client = data; +} + +KeyMap::Keystroke::Keystroke(SInt32 group, bool absolute, bool restore) : + m_type(kGroup) +{ + m_data.m_group.m_group = group; + m_data.m_group.m_absolute = absolute; + m_data.m_group.m_restore = restore; +} + +} diff --git a/src/lib/barrier/KeyMap.h b/src/lib/barrier/KeyMap.h new file mode 100644 index 0000000..b6eb865 --- /dev/null +++ b/src/lib/barrier/KeyMap.h @@ -0,0 +1,512 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2005 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 "barrier/key_types.h" +#include "base/String.h" +#include "common/stdmap.h" +#include "common/stdset.h" +#include "common/stdvector.h" + +#include <gtest/gtest_prod.h> + +namespace barrier { + +//! Key map +/*! +This class provides a keyboard mapping. +*/ +class KeyMap { +public: + KeyMap(); + virtual ~KeyMap(); + + //! KeyID synthesis info + /*! + This structure contains the information necessary to synthesize a + keystroke that generates a KeyID (stored elsewhere). \c m_sensitive + lists the modifiers that the key is affected by and must therefore + be in the correct state, which is listed in \c m_required. If the + key is mapped to a modifier, that modifier is in \c m_generates and + is not in \c m_sensitive. + */ + struct KeyItem { + public: + KeyID m_id; //!< KeyID + SInt32 m_group; //!< Group for key + KeyButton m_button; //!< Button to generate KeyID + KeyModifierMask m_required; //!< Modifiers required for KeyID + KeyModifierMask m_sensitive; //!< Modifiers key is sensitive to + KeyModifierMask m_generates; //!< Modifiers key is mapped to + bool m_dead; //!< \c true if this is a dead KeyID + bool m_lock; //!< \c true if this locks a modifier + UInt32 m_client; //!< Client data + + public: + bool operator==(const KeyItem&) const; + }; + + //! The KeyButtons needed to synthesize a KeyID + /*! + An ordered list of \c KeyItems produces a particular KeyID. If + the KeyID can be synthesized directly then there is one entry in + the list. If dead keys are required then they're listed first. + A list is the minimal set of keystrokes necessary to synthesize + the KeyID, so it doesn't include no-ops. A list does not include + any modifier keys unless the KeyID is a modifier, in which case + it has exactly one KeyItem for the modifier itself. + */ + typedef std::vector<KeyItem> KeyItemList; + + //! A keystroke + class Keystroke { + public: + enum EType { + kButton, //!< Synthesize button + kGroup //!< Set new group + }; + + Keystroke(KeyButton, bool press, bool repeat, UInt32 clientData); + Keystroke(SInt32 group, bool absolute, bool restore); + + public: + struct Button { + public: + KeyButton m_button; //!< Button to synthesize + bool m_press; //!< \c true iff press + bool m_repeat; //!< \c true iff for an autorepeat + UInt32 m_client; //!< Client data + }; + struct Group { + public: + SInt32 m_group; //!< Group/offset to change to/by + bool m_absolute; //!< \c true iff change to, else by + bool m_restore; //!< \c true iff for restoring state + }; + union Data { + public: + Button m_button; + Group m_group; + }; + + EType m_type; + Data m_data; + }; + + //! A sequence of keystrokes + typedef std::vector<Keystroke> Keystrokes; + + //! A mapping of a modifier to keys for that modifier + typedef std::multimap<KeyModifierMask, KeyItem> ModifierToKeys; + + //! A set of buttons + typedef std::map<KeyButton, const KeyItem*> ButtonToKeyMap; + + //! Callback type for \c foreachKey + typedef void (*ForeachKeyCallback)(KeyID, SInt32 group, + KeyItem&, void* userData); + + //! @name manipulators + //@{ + + //! Swap with another \c KeyMap + virtual void swap(KeyMap&); + + //! Add a key entry + /*! + Adds \p item to the entries for the item's id and group. The + \c m_dead member is set automatically. + */ + void addKeyEntry(const KeyItem& item); + + //! Add an alias key entry + /*! + If \p targetID with the modifiers given by \p targetRequired and + \p targetSensitive is not available in group \p group then find an + entry for \p sourceID with modifiers given by \p sourceRequired and + \p sourceSensitive in any group with exactly one item and, if found, + add a new item just like it except using id \p targetID. This + effectively makes the \p sourceID an alias for \p targetID (i.e. we + can generate \p targetID using \p sourceID). + */ + void addKeyAliasEntry(KeyID targetID, SInt32 group, + KeyModifierMask targetRequired, + KeyModifierMask targetSensitive, + KeyID sourceID, + KeyModifierMask sourceRequired, + KeyModifierMask sourceSensitive); + + //! Add a key sequence entry + /*! + Adds the sequence of keys \p keys (\p numKeys elements long) to + synthesize key \p id in group \p group. This looks up in the + map each key in \p keys. If all are found then each key is + converted to the button for that key and the buttons are added + as the entry for \p id. If \p id is already in the map or at + least one key in \p keys is not in the map then nothing is added + and this returns \c false, otherwise it returns \c true. + */ + bool addKeyCombinationEntry(KeyID id, SInt32 group, + const KeyID* keys, UInt32 numKeys); + + //! Enable composition across groups + /*! + If called then the keyboard map will allow switching between groups + during key composition. Not all systems allow that. + */ + void allowGroupSwitchDuringCompose(); + + //! Add a half-duplex button + /*! + Records that button \p button is a half-duplex key. This is called + when translating the system's keyboard map. It's independent of the + half-duplex modifier calls. + */ + void addHalfDuplexButton(KeyButton button); + + //! Remove all half-duplex modifiers + /*! + Removes all half-duplex modifiers. This is called to set user + configurable half-duplex settings. + */ + void clearHalfDuplexModifiers(); + + //! Add a half-duplex modifier + /*! + Records that modifier key \p key is half-duplex. This is called to + set user configurable half-duplex settings. + */ + virtual void addHalfDuplexModifier(KeyID key); + + //! Finish adding entries + /*! + Called after adding entries, this does some internal housekeeping. + */ + virtual void finish(); + + //! Iterate over all added keys items + /*! + Calls \p cb for every key item. + */ + virtual void foreachKey(ForeachKeyCallback cb, void* userData); + + //@} + //! @name accessors + //@{ + + //! Map key press/repeat to keystrokes. + /*! + Converts press/repeat of key \p id in group \p group with current + modifiers as given in \p currentState and the desired modifiers in + \p desiredMask into the keystrokes necessary to synthesize that key + event in \p keys. It returns the \c KeyItem of the key being + pressed/repeated, or NULL if the key cannot be mapped. + */ + virtual const KeyItem* mapKey(Keystrokes& keys, KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + //! Get number of groups + /*! + Returns the number of keyboard groups (independent layouts) in the map. + */ + SInt32 getNumGroups() const; + + //! Compute a group number + /*! + Returns the number of the group \p offset groups after group \p group. + */ + SInt32 getEffectiveGroup(SInt32 group, SInt32 offset) const; + + //! Find key entry compatible with modifiers + /*! + Returns the \c KeyItemList for the first entry for \p id in group + \p group that is compatible with the given modifiers, or NULL + if there isn't one. A button list is compatible with a modifiers + if it is either insensitive to all modifiers in \p sensitive or + it requires the modifiers to be in the state indicated by \p required + for every modifier indicated by \p sensitive. + */ + const KeyItemList* findCompatibleKey(KeyID id, SInt32 group, + KeyModifierMask required, + KeyModifierMask sensitive) const; + + //! Test if modifier is half-duplex + /*! + Returns \c true iff modifier key \p key or button \p button is + half-duplex. + */ + virtual bool isHalfDuplex(KeyID key, KeyButton button) const; + + //! Test if modifiers indicate a command + /*! + Returns \c true iff the modifiers in \p mask contain any command + modifiers. A command modifier is used for keyboard shortcuts and + hotkeys, Rather than trying to synthesize a character, a command + is trying to synthesize a particular set of buttons. So it's not + important to match the shift or AltGr state to achieve a character + but it is important to match the modifier state exactly. + */ + bool isCommand(KeyModifierMask mask) const; + + // Get the modifiers that indicate a command + /*! + Returns the modifiers that when combined with other keys indicate + a command (e.g. shortcut or hotkey). + */ + KeyModifierMask getCommandModifiers() const; + + //! Get buttons from modifier map + /*! + Put all the keys in \p modifiers into \p keys. + */ + static void collectButtons(const ModifierToKeys& modifiers, + ButtonToKeyMap& keys); + + //! Set modifier key state + /*! + Sets the modifier key state (\c m_generates and \c m_lock) in \p item + based on the \c m_id in \p item. + */ + static void initModifierKey(KeyItem& item); + + //! Test for a dead key + /*! + Returns \c true if \p key is a dead key. + */ + static bool isDeadKey(KeyID key); + + //! Get corresponding dead key + /*! + Returns the dead key corresponding to \p key if one exists, otherwise + return \c kKeyNone. This returns \p key if it's already a dead key. + */ + static KeyID getDeadKey(KeyID key); + + //! Get string for a key and modifier mask + /*! + Converts a key and modifier mask into a string representing the + combination. + */ + static String formatKey(KeyID key, KeyModifierMask); + + //! Parse a string into a key + /*! + Converts a string into a key. Returns \c true on success and \c false + if the string cannot be parsed. + */ + static bool parseKey(const String&, KeyID&); + + //! Parse a string into a modifier mask + /*! + Converts a string into a modifier mask. Returns \c true on success + and \c false if the string cannot be parsed. The modifiers plus any + remaining leading and trailing whitespace is stripped from the input + string. + */ + static bool parseModifiers(String&, KeyModifierMask&); + + //@} + +private: + FRIEND_TEST(KeyMapTests, + findBestKey_requiredDown_matchExactFirstItem); + FRIEND_TEST(KeyMapTests, + findBestKey_requiredAndExtraSensitiveDown_matchExactFirstItem); + FRIEND_TEST(KeyMapTests, + findBestKey_requiredAndExtraSensitiveDown_matchExactSecondItem); + FRIEND_TEST(KeyMapTests, + findBestKey_extraSensitiveDown_matchExactSecondItem); + FRIEND_TEST(KeyMapTests, + findBestKey_noRequiredDown_matchOneRequiredChangeItem); + FRIEND_TEST(KeyMapTests, + findBestKey_onlyOneRequiredDown_matchTwoRequiredChangesItem); + FRIEND_TEST(KeyMapTests, findBestKey_noRequiredDown_cannotMatch); + +private: + //! Ways to synthesize a key + enum EKeystroke { + kKeystrokePress, //!< Synthesize a press + kKeystrokeRelease, //!< Synthesize a release + kKeystrokeRepeat, //!< Synthesize an autorepeat + kKeystrokeClick, //!< Synthesize a press and release + kKeystrokeModify, //!< Synthesize pressing a modifier + kKeystrokeUnmodify //!< Synthesize releasing a modifier + }; + + // A list of ways to synthesize a KeyID + typedef std::vector<KeyItemList> KeyEntryList; + + // computes the number of groups + SInt32 findNumGroups() const; + + // computes the map of modifiers to the keys that generate the modifiers + void setModifierKeys(); + + // maps a command key. a command key is a keyboard shortcut and we're + // trying to synthesize a button press with an exact sets of modifiers, + // not trying to synthesize a character. so we just need to find the + // right button and synthesize the requested modifiers without regard + // to what character they would synthesize. we disallow multikey + // entries since they don't make sense as hotkeys. + const KeyItem* mapCommandKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // maps a character key. a character key is trying to synthesize a + // particular KeyID and isn't entirely concerned with the modifiers + // used to do it. + const KeyItem* mapCharacterKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // maps a modifier key + const KeyItem* mapModifierKey(Keystrokes& keys, + KeyID id, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredMask, + bool isAutoRepeat) const; + + // returns the index into \p entryList of the KeyItemList requiring + // the fewest modifier changes between \p currentState and + // \p desiredState. + SInt32 findBestKey(const KeyEntryList& entryList, + KeyModifierMask currentState, + KeyModifierMask desiredState) const; + + // gets the \c KeyItem used to synthesize the modifier who's bit is + // given by \p modifierBit in group \p group and does not synthesize + // the key \p button. + const KeyItem* keyForModifier(KeyButton button, SInt32 group, + SInt32 modifierBit) const; + + // fills \p keystrokes with the keys to synthesize the key in + // \p keyItem taking the modifiers into account. returns \c true + // iff successful and sets \p currentState to the + // resulting modifier state. + bool keysForKeyItem(const KeyItem& keyItem, + SInt32& group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask desiredState, + KeyModifierMask overrideModifiers, + bool isAutoRepeat, + Keystrokes& keystrokes) const; + + // fills \p keystrokes with the keys to synthesize the modifiers + // in \p desiredModifiers from the active modifiers listed in + // \p activeModifiers not including the key in \p keyItem. + // returns \c true iff successful. + bool keysToRestoreModifiers(const KeyItem& keyItem, + SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + const ModifierToKeys& desiredModifiers, + Keystrokes& keystrokes) const; + + // fills \p keystrokes and \p undo with the keys to change the + // current modifier state in \p currentState to match the state in + // \p requiredState for each modifier indicated in \p sensitiveMask. + // returns \c true iff successful and sets \p currentState to the + // resulting modifier state. + bool keysForModifierState(KeyButton button, SInt32 group, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + KeyModifierMask requiredState, + KeyModifierMask sensitiveMask, + KeyModifierMask notRequiredMask, + Keystrokes& keystrokes) const; + + // Adds keystrokes to synthesize key \p keyItem in mode \p type to + // \p keystrokes and to undo the synthesis to \p undo. + void addKeystrokes(EKeystroke type, + const KeyItem& keyItem, + ModifierToKeys& activeModifiers, + KeyModifierMask& currentState, + Keystrokes& keystrokes) const; + + // Returns the number of modifiers indicated in \p state. + static SInt32 getNumModifiers(KeyModifierMask state); + + // Initialize key name/id maps + static void initKeyNameMaps(); + + // not implemented + KeyMap(const KeyMap&); + KeyMap& operator=(const KeyMap&); + +private: + // Ways to synthesize a KeyID over multiple keyboard groups + typedef std::vector<KeyEntryList> KeyGroupTable; + + // Table of KeyID to ways to synthesize that KeyID + typedef std::map<KeyID, KeyGroupTable> KeyIDMap; + + // List of KeyItems that generate a particular modifier + typedef std::vector<const KeyItem*> ModifierKeyItemList; + + // Map a modifier to the KeyItems that synthesize that modifier + typedef std::vector<ModifierKeyItemList> ModifierToKeyTable; + + // A set of keys + typedef std::set<KeyID> KeySet; + + // A set of buttons + typedef std::set<KeyButton> KeyButtonSet; + + // Key maps for parsing/formatting + typedef std::map<String, KeyID, + barrier::string::CaselessCmp> NameToKeyMap; + typedef std::map<String, KeyModifierMask, + barrier::string::CaselessCmp> NameToModifierMap; + typedef std::map<KeyID, String> KeyToNameMap; + typedef std::map<KeyModifierMask, String> ModifierToNameMap; + + // KeyID info + KeyIDMap m_keyIDMap; + SInt32 m_numGroups; + ModifierToKeyTable m_modifierKeys; + + // composition info + bool m_composeAcrossGroups; + + // half-duplex info + KeyButtonSet m_halfDuplex; // half-duplex set by barrier + KeySet m_halfDuplexMods; // half-duplex set by user + + // dummy KeyItem for changing modifiers + KeyItem m_modifierKeyItem; + + // parsing/formatting tables + static NameToKeyMap* s_nameToKeyMap; + static NameToModifierMap* s_nameToModifierMap; + static KeyToNameMap* s_keyToNameMap; + static ModifierToNameMap* s_modifierToNameMap; +}; + +} diff --git a/src/lib/barrier/KeyState.cpp b/src/lib/barrier/KeyState.cpp new file mode 100644 index 0000000..fc5579d --- /dev/null +++ b/src/lib/barrier/KeyState.cpp @@ -0,0 +1,936 @@ +/* + * 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 "barrier/KeyState.h" +#include "base/Log.h" + +#include <cstring> +#include <algorithm> +#include <iterator> +#include <list> + +static const KeyButton kButtonMask = (KeyButton)(IKeyState::kNumButtons - 1); + +static const KeyID s_decomposeTable[] = { + // spacing version of dead keys + 0x0060, 0x0300, 0x0020, 0, // grave, dead_grave, space + 0x00b4, 0x0301, 0x0020, 0, // acute, dead_acute, space + 0x005e, 0x0302, 0x0020, 0, // asciicircum, dead_circumflex, space + 0x007e, 0x0303, 0x0020, 0, // asciitilde, dead_tilde, space + 0x00a8, 0x0308, 0x0020, 0, // diaeresis, dead_diaeresis, space + 0x00b0, 0x030a, 0x0020, 0, // degree, dead_abovering, space + 0x00b8, 0x0327, 0x0020, 0, // cedilla, dead_cedilla, space + 0x02db, 0x0328, 0x0020, 0, // ogonek, dead_ogonek, space + 0x02c7, 0x030c, 0x0020, 0, // caron, dead_caron, space + 0x02d9, 0x0307, 0x0020, 0, // abovedot, dead_abovedot, space + 0x02dd, 0x030b, 0x0020, 0, // doubleacute, dead_doubleacute, space + 0x02d8, 0x0306, 0x0020, 0, // breve, dead_breve, space + 0x00af, 0x0304, 0x0020, 0, // macron, dead_macron, space + + // Latin-1 (ISO 8859-1) + 0x00c0, 0x0300, 0x0041, 0, // Agrave, dead_grave, A + 0x00c1, 0x0301, 0x0041, 0, // Aacute, dead_acute, A + 0x00c2, 0x0302, 0x0041, 0, // Acircumflex, dead_circumflex, A + 0x00c3, 0x0303, 0x0041, 0, // Atilde, dead_tilde, A + 0x00c4, 0x0308, 0x0041, 0, // Adiaeresis, dead_diaeresis, A + 0x00c5, 0x030a, 0x0041, 0, // Aring, dead_abovering, A + 0x00c7, 0x0327, 0x0043, 0, // Ccedilla, dead_cedilla, C + 0x00c8, 0x0300, 0x0045, 0, // Egrave, dead_grave, E + 0x00c9, 0x0301, 0x0045, 0, // Eacute, dead_acute, E + 0x00ca, 0x0302, 0x0045, 0, // Ecircumflex, dead_circumflex, E + 0x00cb, 0x0308, 0x0045, 0, // Ediaeresis, dead_diaeresis, E + 0x00cc, 0x0300, 0x0049, 0, // Igrave, dead_grave, I + 0x00cd, 0x0301, 0x0049, 0, // Iacute, dead_acute, I + 0x00ce, 0x0302, 0x0049, 0, // Icircumflex, dead_circumflex, I + 0x00cf, 0x0308, 0x0049, 0, // Idiaeresis, dead_diaeresis, I + 0x00d1, 0x0303, 0x004e, 0, // Ntilde, dead_tilde, N + 0x00d2, 0x0300, 0x004f, 0, // Ograve, dead_grave, O + 0x00d3, 0x0301, 0x004f, 0, // Oacute, dead_acute, O + 0x00d4, 0x0302, 0x004f, 0, // Ocircumflex, dead_circumflex, O + 0x00d5, 0x0303, 0x004f, 0, // Otilde, dead_tilde, O + 0x00d6, 0x0308, 0x004f, 0, // Odiaeresis, dead_diaeresis, O + 0x00d9, 0x0300, 0x0055, 0, // Ugrave, dead_grave, U + 0x00da, 0x0301, 0x0055, 0, // Uacute, dead_acute, U + 0x00db, 0x0302, 0x0055, 0, // Ucircumflex, dead_circumflex, U + 0x00dc, 0x0308, 0x0055, 0, // Udiaeresis, dead_diaeresis, U + 0x00dd, 0x0301, 0x0059, 0, // Yacute, dead_acute, Y + 0x00e0, 0x0300, 0x0061, 0, // agrave, dead_grave, a + 0x00e1, 0x0301, 0x0061, 0, // aacute, dead_acute, a + 0x00e2, 0x0302, 0x0061, 0, // acircumflex, dead_circumflex, a + 0x00e3, 0x0303, 0x0061, 0, // atilde, dead_tilde, a + 0x00e4, 0x0308, 0x0061, 0, // adiaeresis, dead_diaeresis, a + 0x00e5, 0x030a, 0x0061, 0, // aring, dead_abovering, a + 0x00e7, 0x0327, 0x0063, 0, // ccedilla, dead_cedilla, c + 0x00e8, 0x0300, 0x0065, 0, // egrave, dead_grave, e + 0x00e9, 0x0301, 0x0065, 0, // eacute, dead_acute, e + 0x00ea, 0x0302, 0x0065, 0, // ecircumflex, dead_circumflex, e + 0x00eb, 0x0308, 0x0065, 0, // ediaeresis, dead_diaeresis, e + 0x00ec, 0x0300, 0x0069, 0, // igrave, dead_grave, i + 0x00ed, 0x0301, 0x0069, 0, // iacute, dead_acute, i + 0x00ee, 0x0302, 0x0069, 0, // icircumflex, dead_circumflex, i + 0x00ef, 0x0308, 0x0069, 0, // idiaeresis, dead_diaeresis, i + 0x00f1, 0x0303, 0x006e, 0, // ntilde, dead_tilde, n + 0x00f2, 0x0300, 0x006f, 0, // ograve, dead_grave, o + 0x00f3, 0x0301, 0x006f, 0, // oacute, dead_acute, o + 0x00f4, 0x0302, 0x006f, 0, // ocircumflex, dead_circumflex, o + 0x00f5, 0x0303, 0x006f, 0, // otilde, dead_tilde, o + 0x00f6, 0x0308, 0x006f, 0, // odiaeresis, dead_diaeresis, o + 0x00f9, 0x0300, 0x0075, 0, // ugrave, dead_grave, u + 0x00fa, 0x0301, 0x0075, 0, // uacute, dead_acute, u + 0x00fb, 0x0302, 0x0075, 0, // ucircumflex, dead_circumflex, u + 0x00fc, 0x0308, 0x0075, 0, // udiaeresis, dead_diaeresis, u + 0x00fd, 0x0301, 0x0079, 0, // yacute, dead_acute, y + 0x00ff, 0x0308, 0x0079, 0, // ydiaeresis, dead_diaeresis, y + + // Latin-2 (ISO 8859-2) + 0x0104, 0x0328, 0x0041, 0, // Aogonek, dead_ogonek, A + 0x013d, 0x030c, 0x004c, 0, // Lcaron, dead_caron, L + 0x015a, 0x0301, 0x0053, 0, // Sacute, dead_acute, S + 0x0160, 0x030c, 0x0053, 0, // Scaron, dead_caron, S + 0x015e, 0x0327, 0x0053, 0, // Scedilla, dead_cedilla, S + 0x0164, 0x030c, 0x0054, 0, // Tcaron, dead_caron, T + 0x0179, 0x0301, 0x005a, 0, // Zacute, dead_acute, Z + 0x017d, 0x030c, 0x005a, 0, // Zcaron, dead_caron, Z + 0x017b, 0x0307, 0x005a, 0, // Zabovedot, dead_abovedot, Z + 0x0105, 0x0328, 0x0061, 0, // aogonek, dead_ogonek, a + 0x013e, 0x030c, 0x006c, 0, // lcaron, dead_caron, l + 0x015b, 0x0301, 0x0073, 0, // sacute, dead_acute, s + 0x0161, 0x030c, 0x0073, 0, // scaron, dead_caron, s + 0x015f, 0x0327, 0x0073, 0, // scedilla, dead_cedilla, s + 0x0165, 0x030c, 0x0074, 0, // tcaron, dead_caron, t + 0x017a, 0x0301, 0x007a, 0, // zacute, dead_acute, z + 0x017e, 0x030c, 0x007a, 0, // zcaron, dead_caron, z + 0x017c, 0x0307, 0x007a, 0, // zabovedot, dead_abovedot, z + 0x0154, 0x0301, 0x0052, 0, // Racute, dead_acute, R + 0x0102, 0x0306, 0x0041, 0, // Abreve, dead_breve, A + 0x0139, 0x0301, 0x004c, 0, // Lacute, dead_acute, L + 0x0106, 0x0301, 0x0043, 0, // Cacute, dead_acute, C + 0x010c, 0x030c, 0x0043, 0, // Ccaron, dead_caron, C + 0x0118, 0x0328, 0x0045, 0, // Eogonek, dead_ogonek, E + 0x011a, 0x030c, 0x0045, 0, // Ecaron, dead_caron, E + 0x010e, 0x030c, 0x0044, 0, // Dcaron, dead_caron, D + 0x0143, 0x0301, 0x004e, 0, // Nacute, dead_acute, N + 0x0147, 0x030c, 0x004e, 0, // Ncaron, dead_caron, N + 0x0150, 0x030b, 0x004f, 0, // Odoubleacute, dead_doubleacute, O + 0x0158, 0x030c, 0x0052, 0, // Rcaron, dead_caron, R + 0x016e, 0x030a, 0x0055, 0, // Uring, dead_abovering, U + 0x0170, 0x030b, 0x0055, 0, // Udoubleacute, dead_doubleacute, U + 0x0162, 0x0327, 0x0054, 0, // Tcedilla, dead_cedilla, T + 0x0155, 0x0301, 0x0072, 0, // racute, dead_acute, r + 0x0103, 0x0306, 0x0061, 0, // abreve, dead_breve, a + 0x013a, 0x0301, 0x006c, 0, // lacute, dead_acute, l + 0x0107, 0x0301, 0x0063, 0, // cacute, dead_acute, c + 0x010d, 0x030c, 0x0063, 0, // ccaron, dead_caron, c + 0x0119, 0x0328, 0x0065, 0, // eogonek, dead_ogonek, e + 0x011b, 0x030c, 0x0065, 0, // ecaron, dead_caron, e + 0x010f, 0x030c, 0x0064, 0, // dcaron, dead_caron, d + 0x0144, 0x0301, 0x006e, 0, // nacute, dead_acute, n + 0x0148, 0x030c, 0x006e, 0, // ncaron, dead_caron, n + 0x0151, 0x030b, 0x006f, 0, // odoubleacute, dead_doubleacute, o + 0x0159, 0x030c, 0x0072, 0, // rcaron, dead_caron, r + 0x016f, 0x030a, 0x0075, 0, // uring, dead_abovering, u + 0x0171, 0x030b, 0x0075, 0, // udoubleacute, dead_doubleacute, u + 0x0163, 0x0327, 0x0074, 0, // tcedilla, dead_cedilla, t + + // Latin-3 (ISO 8859-3) + 0x0124, 0x0302, 0x0048, 0, // Hcircumflex, dead_circumflex, H + 0x0130, 0x0307, 0x0049, 0, // Iabovedot, dead_abovedot, I + 0x011e, 0x0306, 0x0047, 0, // Gbreve, dead_breve, G + 0x0134, 0x0302, 0x004a, 0, // Jcircumflex, dead_circumflex, J + 0x0125, 0x0302, 0x0068, 0, // hcircumflex, dead_circumflex, h + 0x011f, 0x0306, 0x0067, 0, // gbreve, dead_breve, g + 0x0135, 0x0302, 0x006a, 0, // jcircumflex, dead_circumflex, j + 0x010a, 0x0307, 0x0043, 0, // Cabovedot, dead_abovedot, C + 0x0108, 0x0302, 0x0043, 0, // Ccircumflex, dead_circumflex, C + 0x0120, 0x0307, 0x0047, 0, // Gabovedot, dead_abovedot, G + 0x011c, 0x0302, 0x0047, 0, // Gcircumflex, dead_circumflex, G + 0x016c, 0x0306, 0x0055, 0, // Ubreve, dead_breve, U + 0x015c, 0x0302, 0x0053, 0, // Scircumflex, dead_circumflex, S + 0x010b, 0x0307, 0x0063, 0, // cabovedot, dead_abovedot, c + 0x0109, 0x0302, 0x0063, 0, // ccircumflex, dead_circumflex, c + 0x0121, 0x0307, 0x0067, 0, // gabovedot, dead_abovedot, g + 0x011d, 0x0302, 0x0067, 0, // gcircumflex, dead_circumflex, g + 0x016d, 0x0306, 0x0075, 0, // ubreve, dead_breve, u + 0x015d, 0x0302, 0x0073, 0, // scircumflex, dead_circumflex, s + + // Latin-4 (ISO 8859-4) + 0x0156, 0x0327, 0x0052, 0, // Rcedilla, dead_cedilla, R + 0x0128, 0x0303, 0x0049, 0, // Itilde, dead_tilde, I + 0x013b, 0x0327, 0x004c, 0, // Lcedilla, dead_cedilla, L + 0x0112, 0x0304, 0x0045, 0, // Emacron, dead_macron, E + 0x0122, 0x0327, 0x0047, 0, // Gcedilla, dead_cedilla, G + 0x0157, 0x0327, 0x0072, 0, // rcedilla, dead_cedilla, r + 0x0129, 0x0303, 0x0069, 0, // itilde, dead_tilde, i + 0x013c, 0x0327, 0x006c, 0, // lcedilla, dead_cedilla, l + 0x0113, 0x0304, 0x0065, 0, // emacron, dead_macron, e + 0x0123, 0x0327, 0x0067, 0, // gcedilla, dead_cedilla, g + 0x0100, 0x0304, 0x0041, 0, // Amacron, dead_macron, A + 0x012e, 0x0328, 0x0049, 0, // Iogonek, dead_ogonek, I + 0x0116, 0x0307, 0x0045, 0, // Eabovedot, dead_abovedot, E + 0x012a, 0x0304, 0x0049, 0, // Imacron, dead_macron, I + 0x0145, 0x0327, 0x004e, 0, // Ncedilla, dead_cedilla, N + 0x014c, 0x0304, 0x004f, 0, // Omacron, dead_macron, O + 0x0136, 0x0327, 0x004b, 0, // Kcedilla, dead_cedilla, K + 0x0172, 0x0328, 0x0055, 0, // Uogonek, dead_ogonek, U + 0x0168, 0x0303, 0x0055, 0, // Utilde, dead_tilde, U + 0x016a, 0x0304, 0x0055, 0, // Umacron, dead_macron, U + 0x0101, 0x0304, 0x0061, 0, // amacron, dead_macron, a + 0x012f, 0x0328, 0x0069, 0, // iogonek, dead_ogonek, i + 0x0117, 0x0307, 0x0065, 0, // eabovedot, dead_abovedot, e + 0x012b, 0x0304, 0x0069, 0, // imacron, dead_macron, i + 0x0146, 0x0327, 0x006e, 0, // ncedilla, dead_cedilla, n + 0x014d, 0x0304, 0x006f, 0, // omacron, dead_macron, o + 0x0137, 0x0327, 0x006b, 0, // kcedilla, dead_cedilla, k + 0x0173, 0x0328, 0x0075, 0, // uogonek, dead_ogonek, u + 0x0169, 0x0303, 0x0075, 0, // utilde, dead_tilde, u + 0x016b, 0x0304, 0x0075, 0, // umacron, dead_macron, u + + // Latin-8 (ISO 8859-14) + 0x1e02, 0x0307, 0x0042, 0, // Babovedot, dead_abovedot, B + 0x1e03, 0x0307, 0x0062, 0, // babovedot, dead_abovedot, b + 0x1e0a, 0x0307, 0x0044, 0, // Dabovedot, dead_abovedot, D + 0x1e80, 0x0300, 0x0057, 0, // Wgrave, dead_grave, W + 0x1e82, 0x0301, 0x0057, 0, // Wacute, dead_acute, W + 0x1e0b, 0x0307, 0x0064, 0, // dabovedot, dead_abovedot, d + 0x1ef2, 0x0300, 0x0059, 0, // Ygrave, dead_grave, Y + 0x1e1e, 0x0307, 0x0046, 0, // Fabovedot, dead_abovedot, F + 0x1e1f, 0x0307, 0x0066, 0, // fabovedot, dead_abovedot, f + 0x1e40, 0x0307, 0x004d, 0, // Mabovedot, dead_abovedot, M + 0x1e41, 0x0307, 0x006d, 0, // mabovedot, dead_abovedot, m + 0x1e56, 0x0307, 0x0050, 0, // Pabovedot, dead_abovedot, P + 0x1e81, 0x0300, 0x0077, 0, // wgrave, dead_grave, w + 0x1e57, 0x0307, 0x0070, 0, // pabovedot, dead_abovedot, p + 0x1e83, 0x0301, 0x0077, 0, // wacute, dead_acute, w + 0x1e60, 0x0307, 0x0053, 0, // Sabovedot, dead_abovedot, S + 0x1ef3, 0x0300, 0x0079, 0, // ygrave, dead_grave, y + 0x1e84, 0x0308, 0x0057, 0, // Wdiaeresis, dead_diaeresis, W + 0x1e85, 0x0308, 0x0077, 0, // wdiaeresis, dead_diaeresis, w + 0x1e61, 0x0307, 0x0073, 0, // sabovedot, dead_abovedot, s + 0x0174, 0x0302, 0x0057, 0, // Wcircumflex, dead_circumflex, W + 0x1e6a, 0x0307, 0x0054, 0, // Tabovedot, dead_abovedot, T + 0x0176, 0x0302, 0x0059, 0, // Ycircumflex, dead_circumflex, Y + 0x0175, 0x0302, 0x0077, 0, // wcircumflex, dead_circumflex, w + 0x1e6b, 0x0307, 0x0074, 0, // tabovedot, dead_abovedot, t + 0x0177, 0x0302, 0x0079, 0, // ycircumflex, dead_circumflex, y + + // Latin-9 (ISO 8859-15) + 0x0178, 0x0308, 0x0059, 0, // Ydiaeresis, dead_diaeresis, Y + + // Compose key sequences + 0x00c6, kKeyCompose, 0x0041, 0x0045, 0, // AE, A, E + 0x00c1, kKeyCompose, 0x0041, 0x0027, 0, // Aacute, A, apostrophe + 0x00c2, kKeyCompose, 0x0041, 0x0053, 0, // Acircumflex, A, asciicircum + 0x00c3, kKeyCompose, 0x0041, 0x0022, 0, // Adiaeresis, A, quotedbl + 0x00c0, kKeyCompose, 0x0041, 0x0060, 0, // Agrave, A, grave + 0x00c5, kKeyCompose, 0x0041, 0x002a, 0, // Aring, A, asterisk + 0x00c3, kKeyCompose, 0x0041, 0x007e, 0, // Atilde, A, asciitilde + 0x00c7, kKeyCompose, 0x0043, 0x002c, 0, // Ccedilla, C, comma + 0x00d0, kKeyCompose, 0x0044, 0x002d, 0, // ETH, D, minus + 0x00c9, kKeyCompose, 0x0045, 0x0027, 0, // Eacute, E, apostrophe + 0x00ca, kKeyCompose, 0x0045, 0x0053, 0, // Ecircumflex, E, asciicircum + 0x00cb, kKeyCompose, 0x0045, 0x0022, 0, // Ediaeresis, E, quotedbl + 0x00c8, kKeyCompose, 0x0045, 0x0060, 0, // Egrave, E, grave + 0x00cd, kKeyCompose, 0x0049, 0x0027, 0, // Iacute, I, apostrophe + 0x00ce, kKeyCompose, 0x0049, 0x0053, 0, // Icircumflex, I, asciicircum + 0x00cf, kKeyCompose, 0x0049, 0x0022, 0, // Idiaeresis, I, quotedbl + 0x00cc, kKeyCompose, 0x0049, 0x0060, 0, // Igrave, I, grave + 0x00d1, kKeyCompose, 0x004e, 0x007e, 0, // Ntilde, N, asciitilde + 0x00d3, kKeyCompose, 0x004f, 0x0027, 0, // Oacute, O, apostrophe + 0x00d4, kKeyCompose, 0x004f, 0x0053, 0, // Ocircumflex, O, asciicircum + 0x00d6, kKeyCompose, 0x004f, 0x0022, 0, // Odiaeresis, O, quotedbl + 0x00d2, kKeyCompose, 0x004f, 0x0060, 0, // Ograve, O, grave + 0x00d8, kKeyCompose, 0x004f, 0x002f, 0, // Ooblique, O, slash + 0x00d5, kKeyCompose, 0x004f, 0x007e, 0, // Otilde, O, asciitilde + 0x00de, kKeyCompose, 0x0054, 0x0048, 0, // THORN, T, H + 0x00da, kKeyCompose, 0x0055, 0x0027, 0, // Uacute, U, apostrophe + 0x00db, kKeyCompose, 0x0055, 0x0053, 0, // Ucircumflex, U, asciicircum + 0x00dc, kKeyCompose, 0x0055, 0x0022, 0, // Udiaeresis, U, quotedbl + 0x00d9, kKeyCompose, 0x0055, 0x0060, 0, // Ugrave, U, grave + 0x00dd, kKeyCompose, 0x0059, 0x0027, 0, // Yacute, Y, apostrophe + 0x00e1, kKeyCompose, 0x0061, 0x0027, 0, // aacute, a, apostrophe + 0x00e2, kKeyCompose, 0x0061, 0x0053, 0, // acircumflex, a, asciicircum + 0x00b4, kKeyCompose, 0x0027, 0x0027, 0, // acute, apostrophe, apostrophe + 0x00e4, kKeyCompose, 0x0061, 0x0022, 0, // adiaeresis, a, quotedbl + 0x00e6, kKeyCompose, 0x0061, 0x0065, 0, // ae, a, e + 0x00e0, kKeyCompose, 0x0061, 0x0060, 0, // agrave, a, grave + 0x00e5, kKeyCompose, 0x0061, 0x002a, 0, // aring, a, asterisk + 0x0040, kKeyCompose, 0x0041, 0x0054, 0, // at, A, T + 0x00e3, kKeyCompose, 0x0061, 0x007e, 0, // atilde, a, asciitilde + 0x005c, kKeyCompose, 0x002f, 0x002f, 0, // backslash, slash, slash + 0x007c, kKeyCompose, 0x004c, 0x0056, 0, // bar, L, V + 0x007b, kKeyCompose, 0x0028, 0x002d, 0, // braceleft, parenleft, minus + 0x007d, kKeyCompose, 0x0029, 0x002d, 0, // braceright, parenright, minus + 0x005b, kKeyCompose, 0x0028, 0x0028, 0, // bracketleft, parenleft, parenleft + 0x005d, kKeyCompose, 0x0029, 0x0029, 0, // bracketright, parenright, parenright + 0x00a6, kKeyCompose, 0x0042, 0x0056, 0, // brokenbar, B, V + 0x00e7, kKeyCompose, 0x0063, 0x002c, 0, // ccedilla, c, comma + 0x00b8, kKeyCompose, 0x002c, 0x002c, 0, // cedilla, comma, comma + 0x00a2, kKeyCompose, 0x0063, 0x002f, 0, // cent, c, slash + 0x00a9, kKeyCompose, 0x0028, 0x0063, 0, // copyright, parenleft, c + 0x00a4, kKeyCompose, 0x006f, 0x0078, 0, // currency, o, x + 0x00b0, kKeyCompose, 0x0030, 0x0053, 0, // degree, 0, asciicircum + 0x00a8, kKeyCompose, 0x0022, 0x0022, 0, // diaeresis, quotedbl, quotedbl + 0x00f7, kKeyCompose, 0x003a, 0x002d, 0, // division, colon, minus + 0x00e9, kKeyCompose, 0x0065, 0x0027, 0, // eacute, e, apostrophe + 0x00ea, kKeyCompose, 0x0065, 0x0053, 0, // ecircumflex, e, asciicircum + 0x00eb, kKeyCompose, 0x0065, 0x0022, 0, // ediaeresis, e, quotedbl + 0x00e8, kKeyCompose, 0x0065, 0x0060, 0, // egrave, e, grave + 0x00f0, kKeyCompose, 0x0064, 0x002d, 0, // eth, d, minus + 0x00a1, kKeyCompose, 0x0021, 0x0021, 0, // exclamdown, exclam, exclam + 0x00ab, kKeyCompose, 0x003c, 0x003c, 0, // guillemotleft, less, less + 0x00bb, kKeyCompose, 0x003e, 0x003e, 0, // guillemotright, greater, greater + 0x0023, kKeyCompose, 0x002b, 0x002b, 0, // numbersign, plus, plus + 0x00ad, kKeyCompose, 0x002d, 0x002d, 0, // hyphen, minus, minus + 0x00ed, kKeyCompose, 0x0069, 0x0027, 0, // iacute, i, apostrophe + 0x00ee, kKeyCompose, 0x0069, 0x0053, 0, // icircumflex, i, asciicircum + 0x00ef, kKeyCompose, 0x0069, 0x0022, 0, // idiaeresis, i, quotedbl + 0x00ec, kKeyCompose, 0x0069, 0x0060, 0, // igrave, i, grave + 0x00af, kKeyCompose, 0x002d, 0x0053, 0, // macron, minus, asciicircum + 0x00ba, kKeyCompose, 0x006f, 0x005f, 0, // masculine, o, underscore + 0x00b5, kKeyCompose, 0x0075, 0x002f, 0, // mu, u, slash + 0x00d7, kKeyCompose, 0x0078, 0x0078, 0, // multiply, x, x + 0x00a0, kKeyCompose, 0x0020, 0x0020, 0, // nobreakspace, space, space + 0x00ac, kKeyCompose, 0x002c, 0x002d, 0, // notsign, comma, minus + 0x00f1, kKeyCompose, 0x006e, 0x007e, 0, // ntilde, n, asciitilde + 0x00f3, kKeyCompose, 0x006f, 0x0027, 0, // oacute, o, apostrophe + 0x00f4, kKeyCompose, 0x006f, 0x0053, 0, // ocircumflex, o, asciicircum + 0x00f6, kKeyCompose, 0x006f, 0x0022, 0, // odiaeresis, o, quotedbl + 0x00f2, kKeyCompose, 0x006f, 0x0060, 0, // ograve, o, grave + 0x00bd, kKeyCompose, 0x0031, 0x0032, 0, // onehalf, 1, 2 + 0x00bc, kKeyCompose, 0x0031, 0x0034, 0, // onequarter, 1, 4 + 0x00b9, kKeyCompose, 0x0031, 0x0053, 0, // onesuperior, 1, asciicircum + 0x00aa, kKeyCompose, 0x0061, 0x005f, 0, // ordfeminine, a, underscore + 0x00f8, kKeyCompose, 0x006f, 0x002f, 0, // oslash, o, slash + 0x00f5, kKeyCompose, 0x006f, 0x007e, 0, // otilde, o, asciitilde + 0x00b6, kKeyCompose, 0x0070, 0x0021, 0, // paragraph, p, exclam + 0x00b7, kKeyCompose, 0x002e, 0x002e, 0, // periodcentered, period, period + 0x00b1, kKeyCompose, 0x002b, 0x002d, 0, // plusminus, plus, minus + 0x00bf, kKeyCompose, 0x003f, 0x003f, 0, // questiondown, question, question + 0x00ae, kKeyCompose, 0x0028, 0x0072, 0, // registered, parenleft, r + 0x00a7, kKeyCompose, 0x0073, 0x006f, 0, // section, s, o + 0x00df, kKeyCompose, 0x0073, 0x0073, 0, // ssharp, s, s + 0x00a3, kKeyCompose, 0x004c, 0x002d, 0, // sterling, L, minus + 0x00fe, kKeyCompose, 0x0074, 0x0068, 0, // thorn, t, h + 0x00be, kKeyCompose, 0x0033, 0x0034, 0, // threequarters, 3, 4 + 0x00b3, kKeyCompose, 0x0033, 0x0053, 0, // threesuperior, 3, asciicircum + 0x00b2, kKeyCompose, 0x0032, 0x0053, 0, // twosuperior, 2, asciicircum + 0x00fa, kKeyCompose, 0x0075, 0x0027, 0, // uacute, u, apostrophe + 0x00fb, kKeyCompose, 0x0075, 0x0053, 0, // ucircumflex, u, asciicircum + 0x00fc, kKeyCompose, 0x0075, 0x0022, 0, // udiaeresis, u, quotedbl + 0x00f9, kKeyCompose, 0x0075, 0x0060, 0, // ugrave, u, grave + 0x00fd, kKeyCompose, 0x0079, 0x0027, 0, // yacute, y, apostrophe + 0x00ff, kKeyCompose, 0x0079, 0x0022, 0, // ydiaeresis, y, quotedbl + 0x00a5, kKeyCompose, 0x0079, 0x003d, 0, // yen, y, equal + + // end of table + 0 +}; + +static const KeyID s_numpadTable[] = { + kKeyKP_Space, 0x0020, + kKeyKP_Tab, kKeyTab, + kKeyKP_Enter, kKeyReturn, + kKeyKP_F1, kKeyF1, + kKeyKP_F2, kKeyF2, + kKeyKP_F3, kKeyF3, + kKeyKP_F4, kKeyF4, + kKeyKP_Home, kKeyHome, + kKeyKP_Left, kKeyLeft, + kKeyKP_Up, kKeyUp, + kKeyKP_Right, kKeyRight, + kKeyKP_Down, kKeyDown, + kKeyKP_PageUp, kKeyPageUp, + kKeyKP_PageDown, kKeyPageDown, + kKeyKP_End, kKeyEnd, + kKeyKP_Begin, kKeyBegin, + kKeyKP_Insert, kKeyInsert, + kKeyKP_Delete, kKeyDelete, + kKeyKP_Equal, 0x003d, + kKeyKP_Multiply, 0x002a, + kKeyKP_Add, 0x002b, + kKeyKP_Separator, 0x002c, + kKeyKP_Subtract, 0x002d, + kKeyKP_Decimal, 0x002e, + kKeyKP_Divide, 0x002f, + kKeyKP_0, 0x0030, + kKeyKP_1, 0x0031, + kKeyKP_2, 0x0032, + kKeyKP_3, 0x0033, + kKeyKP_4, 0x0034, + kKeyKP_5, 0x0035, + kKeyKP_6, 0x0036, + kKeyKP_7, 0x0037, + kKeyKP_8, 0x0038, + kKeyKP_9, 0x0039 +}; + +// +// KeyState +// + +KeyState::KeyState(IEventQueue* events) : + IKeyState(events), + m_keyMapPtr(new barrier::KeyMap()), + m_keyMap(*m_keyMapPtr), + m_mask(0), + m_events(events) +{ + init(); +} + +KeyState::KeyState(IEventQueue* events, barrier::KeyMap& keyMap) : + IKeyState(events), + m_keyMapPtr(0), + m_keyMap(keyMap), + m_mask(0), + m_events(events) +{ + init(); +} + +KeyState::~KeyState() +{ + if (m_keyMapPtr) + delete m_keyMapPtr; +} + +void +KeyState::init() +{ + memset(&m_keys, 0, sizeof(m_keys)); + memset(&m_syntheticKeys, 0, sizeof(m_syntheticKeys)); + memset(&m_keyClientData, 0, sizeof(m_keyClientData)); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); +} + +void +KeyState::onKey(KeyButton button, bool down, KeyModifierMask newState) +{ + // update modifier state + m_mask = newState; + LOG((CLOG_DEBUG1 "new mask: 0x%04x", m_mask)); + + // ignore bogus buttons + button &= kButtonMask; + if (button == 0) { + return; + } + + // update key state + if (down) { + m_keys[button] = 1; + m_syntheticKeys[button] = 1; + } + else { + m_keys[button] = 0; + m_syntheticKeys[button] = 0; + } +} + +void +KeyState::sendKeyEvent( + void* target, bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (m_keyMap.isHalfDuplex(key, button)) { + if (isAutoRepeat) { + // ignore auto-repeat on half-duplex keys + } + else { + m_events->addEvent(Event(m_events->forIKeyState().keyDown(), target, + KeyInfo::alloc(key, mask, button, 1))); + m_events->addEvent(Event(m_events->forIKeyState().keyUp(), target, + KeyInfo::alloc(key, mask, button, 1))); + } + } + else { + if (isAutoRepeat) { + m_events->addEvent(Event(m_events->forIKeyState().keyRepeat(), target, + KeyInfo::alloc(key, mask, button, count))); + } + else if (press) { + m_events->addEvent(Event(m_events->forIKeyState().keyDown(), target, + KeyInfo::alloc(key, mask, button, 1))); + } + else { + m_events->addEvent(Event(m_events->forIKeyState().keyUp(), target, + KeyInfo::alloc(key, mask, button, 1))); + } + } +} + +void +KeyState::updateKeyMap() +{ + // get the current keyboard map + barrier::KeyMap keyMap; + getKeyMap(keyMap); + m_keyMap.swap(keyMap); + m_keyMap.finish(); + + // add special keys + addCombinationEntries(); + addKeypadEntries(); + addAliasEntries(); +} + +void +KeyState::updateKeyState() +{ + // reset our state + memset(&m_keys, 0, sizeof(m_keys)); + memset(&m_syntheticKeys, 0, sizeof(m_syntheticKeys)); + memset(&m_keyClientData, 0, sizeof(m_keyClientData)); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); + m_activeModifiers.clear(); + + // get the current keyboard state + KeyButtonSet keysDown; + pollPressedKeys(keysDown); + for (KeyButtonSet::const_iterator i = keysDown.begin(); + i != keysDown.end(); ++i) { + m_keys[*i] = 1; + } + + // get the current modifier state + m_mask = pollActiveModifiers(); + + // set active modifiers + AddActiveModifierContext addModifierContext(pollActiveGroup(), m_mask, + m_activeModifiers); + m_keyMap.foreachKey(&KeyState::addActiveModifierCB, &addModifierContext); + + LOG((CLOG_DEBUG1 "modifiers on update: 0x%04x", m_mask)); +} + +void +KeyState::addActiveModifierCB(KeyID, SInt32 group, + barrier::KeyMap::KeyItem& keyItem, void* vcontext) +{ + AddActiveModifierContext* context = + static_cast<AddActiveModifierContext*>(vcontext); + if (group == context->m_activeGroup && + (keyItem.m_generates & context->m_mask) != 0) { + context->m_activeModifiers.insert(std::make_pair( + keyItem.m_generates, keyItem)); + } +} + +void +KeyState::setHalfDuplexMask(KeyModifierMask mask) +{ + m_keyMap.clearHalfDuplexModifiers(); + if ((mask & KeyModifierCapsLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyCapsLock); + } + if ((mask & KeyModifierNumLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyNumLock); + } + if ((mask & KeyModifierScrollLock) != 0) { + m_keyMap.addHalfDuplexModifier(kKeyScrollLock); + } +} + +void +KeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, KeyButton serverID) +{ + // if this server key is already down then this is probably a + // mis-reported autorepeat. + serverID &= kButtonMask; + if (m_serverKeys[serverID] != 0) { + fakeKeyRepeat(id, mask, 1, serverID); + return; + } + + // ignore certain keys + if (isIgnoredKey(id, mask)) { + LOG((CLOG_DEBUG1 "ignored key %04x %04x", id, mask)); + return; + } + + // get keys for key press + Keystrokes keys; + ModifierToKeys oldActiveModifiers = m_activeModifiers; + const barrier::KeyMap::KeyItem* keyItem = + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, false); + if (keyItem == NULL) { + // a media key won't be mapped on mac, so we need to fake it in a + // special way + if (id == kKeyAudioDown || id == kKeyAudioUp || + id == kKeyAudioMute || id == kKeyAudioPlay || + id == kKeyAudioPrev || id == kKeyAudioNext || + id == kKeyBrightnessDown || id == kKeyBrightnessUp + ) { + LOG((CLOG_DEBUG1 "emulating media key")); + fakeMediaKey(id); + } + + return; + } + + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); + updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); + if (localID != 0) { + // note keys down + ++m_keys[localID]; + ++m_syntheticKeys[localID]; + m_keyClientData[localID] = keyItem->m_client; + m_serverKeys[serverID] = localID; + } + + // generate key events + fakeKeys(keys, 1); +} + +bool +KeyState::fakeKeyRepeat( + KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton serverID) +{ + serverID &= kButtonMask; + + // if we haven't seen this button go down then ignore it + KeyButton oldLocalID = m_serverKeys[serverID]; + if (oldLocalID == 0) { + return false; + } + + // get keys for key repeat + Keystrokes keys; + ModifierToKeys oldActiveModifiers = m_activeModifiers; + const barrier::KeyMap::KeyItem* keyItem = + m_keyMap.mapKey(keys, id, pollActiveGroup(), m_activeModifiers, + getActiveModifiersRValue(), mask, true); + if (keyItem == NULL) { + return false; + } + KeyButton localID = (KeyButton)(keyItem->m_button & kButtonMask); + if (localID == 0) { + return false; + } + + // if the KeyButton for the auto-repeat is not the same as for the + // initial press then mark the initial key as released and the new + // key as pressed. this can happen when we auto-repeat after a + // dead key. for example, a dead accent followed by 'a' will + // generate an 'a with accent' followed by a repeating 'a'. the + // KeyButtons for the two KeyIDs might be different. + if (localID != oldLocalID) { + // replace key up with previous KeyButton but leave key down + // alone so it uses the new KeyButton. + for (Keystrokes::iterator index = keys.begin(); + index != keys.end(); ++index) { + if (index->m_type == Keystroke::kButton && + index->m_data.m_button.m_button == localID) { + index->m_data.m_button.m_button = oldLocalID; + break; + } + } + + // note that old key is now up + --m_keys[oldLocalID]; + --m_syntheticKeys[oldLocalID]; + + // note keys down + updateModifierKeyState(localID, oldActiveModifiers, m_activeModifiers); + ++m_keys[localID]; + ++m_syntheticKeys[localID]; + m_keyClientData[localID] = keyItem->m_client; + m_serverKeys[serverID] = localID; + } + + // generate key events + fakeKeys(keys, count); + return true; +} + +bool +KeyState::fakeKeyUp(KeyButton serverID) +{ + // if we haven't seen this button go down then ignore it + KeyButton localID = m_serverKeys[serverID & kButtonMask]; + if (localID == 0) { + return false; + } + + // get the sequence of keys to simulate key release + Keystrokes keys; + keys.push_back(Keystroke(localID, false, false, m_keyClientData[localID])); + + // note keys down + --m_keys[localID]; + --m_syntheticKeys[localID]; + m_serverKeys[serverID] = 0; + + // check if this is a modifier + ModifierToKeys::iterator i = m_activeModifiers.begin(); + while (i != m_activeModifiers.end()) { + if (i->second.m_button == localID && !i->second.m_lock) { + // modifier is no longer down + KeyModifierMask mask = i->first; + + ModifierToKeys::iterator tmp = i; + ++i; + m_activeModifiers.erase(tmp); + + if (m_activeModifiers.count(mask) == 0) { + // no key for modifier is down so deactivate modifier + m_mask &= ~mask; + LOG((CLOG_DEBUG1 "new state %04x", m_mask)); + } + } + else { + ++i; + } + } + + // generate key events + fakeKeys(keys, 1); + return true; +} + +void +KeyState::fakeAllKeysUp() +{ + Keystrokes keys; + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (m_syntheticKeys[i] > 0) { + keys.push_back(Keystroke(i, false, false, m_keyClientData[i])); + m_keys[i] = 0; + m_syntheticKeys[i] = 0; + } + } + fakeKeys(keys, 1); + memset(&m_serverKeys, 0, sizeof(m_serverKeys)); + m_activeModifiers.clear(); + m_mask = pollActiveModifiers(); +} + +bool +KeyState::fakeMediaKey(KeyID id) +{ + return false; +} + +bool +KeyState::isKeyDown(KeyButton button) const +{ + return (m_keys[button & kButtonMask] > 0); +} + +KeyModifierMask +KeyState::getActiveModifiers() const +{ + return m_mask; +} + +KeyModifierMask& +KeyState::getActiveModifiersRValue() +{ + return m_mask; +} + +SInt32 +KeyState::getEffectiveGroup(SInt32 group, SInt32 offset) const +{ + return m_keyMap.getEffectiveGroup(group, offset); +} + +bool +KeyState::isIgnoredKey(KeyID key, KeyModifierMask) const +{ + switch (key) { + case kKeyCapsLock: + case kKeyNumLock: + case kKeyScrollLock: + return true; + + default: + return false; + } +} + +KeyButton +KeyState::getButton(KeyID id, SInt32 group) const +{ + const barrier::KeyMap::KeyItemList* items = + m_keyMap.findCompatibleKey(id, group, 0, 0); + if (items == NULL) { + return 0; + } + else { + return items->back().m_button; + } +} + +void +KeyState::addAliasEntries() +{ + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + // if we can't shift any kKeyTab key in a particular group but we can + // shift kKeyLeftTab then add a shifted kKeyTab entry that matches a + // shifted kKeyLeftTab entry. + m_keyMap.addKeyAliasEntry(kKeyTab, g, + KeyModifierShift, KeyModifierShift, + kKeyLeftTab, + KeyModifierShift, KeyModifierShift); + + // if we have no kKeyLeftTab but we do have a kKeyTab that can be + // shifted then add kKeyLeftTab that matches a kKeyTab. + m_keyMap.addKeyAliasEntry(kKeyLeftTab, g, + KeyModifierShift, KeyModifierShift, + kKeyTab, + 0, KeyModifierShift); + + // map non-breaking space to space + m_keyMap.addKeyAliasEntry(0x20, g, 0, 0, 0xa0, 0, 0); + } +} + +void +KeyState::addKeypadEntries() +{ + // map every numpad key to its equivalent non-numpad key if it's not + // on the keyboard. + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + for (size_t i = 0; i < sizeof(s_numpadTable) / + sizeof(s_numpadTable[0]); i += 2) { + m_keyMap.addKeyCombinationEntry(s_numpadTable[i], g, + s_numpadTable + i + 1, 1); + } + } +} + +void +KeyState::addCombinationEntries() +{ + for (SInt32 g = 0, n = m_keyMap.getNumGroups(); g < n; ++g) { + // add dead and compose key composition sequences + for (const KeyID* i = s_decomposeTable; *i != 0; ++i) { + // count the decomposed keys for this key + UInt32 numKeys = 0; + for (const KeyID* j = i; *++j != 0; ) { + ++numKeys; + } + + // add an entry for this key + m_keyMap.addKeyCombinationEntry(*i, g, i + 1, numKeys); + + // next key + i += numKeys + 1; + } + } +} + +void +KeyState::fakeKeys(const Keystrokes& keys, UInt32 count) +{ + // do nothing if no keys or no repeats + if (count == 0 || keys.empty()) { + return; + } + + // generate key events + LOG((CLOG_DEBUG1 "keystrokes:")); + for (Keystrokes::const_iterator k = keys.begin(); k != keys.end(); ) { + if (k->m_type == Keystroke::kButton && k->m_data.m_button.m_repeat) { + // repeat from here up to but not including the next key + // with m_repeat == false count times. + Keystrokes::const_iterator start = k; + while (count-- > 0) { + // send repeating events + for (k = start; k != keys.end() && + k->m_type == Keystroke::kButton && + k->m_data.m_button.m_repeat; ++k) { + fakeKey(*k); + } + } + + // note -- k is now on the first non-repeat key after the + // repeat keys, exactly where we'd like to continue from. + } + else { + // send event + fakeKey(*k); + + // next key + ++k; + } + } +} + +void +KeyState::updateModifierKeyState(KeyButton button, + const ModifierToKeys& oldModifiers, + const ModifierToKeys& newModifiers) +{ + // get the pressed modifier buttons before and after + barrier::KeyMap::ButtonToKeyMap oldKeys, newKeys; + for (ModifierToKeys::const_iterator i = oldModifiers.begin(); + i != oldModifiers.end(); ++i) { + oldKeys.insert(std::make_pair(i->second.m_button, &i->second)); + } + for (ModifierToKeys::const_iterator i = newModifiers.begin(); + i != newModifiers.end(); ++i) { + newKeys.insert(std::make_pair(i->second.m_button, &i->second)); + } + + // get the modifier buttons that were pressed or released + barrier::KeyMap::ButtonToKeyMap pressed, released; + std::set_difference(oldKeys.begin(), oldKeys.end(), + newKeys.begin(), newKeys.end(), + std::inserter(released, released.end()), + ButtonToKeyLess()); + std::set_difference(newKeys.begin(), newKeys.end(), + oldKeys.begin(), oldKeys.end(), + std::inserter(pressed, pressed.end()), + ButtonToKeyLess()); + + // update state + for (barrier::KeyMap::ButtonToKeyMap::const_iterator i = released.begin(); + i != released.end(); ++i) { + if (i->first != button) { + m_keys[i->first] = 0; + m_syntheticKeys[i->first] = 0; + } + } + for (barrier::KeyMap::ButtonToKeyMap::const_iterator i = pressed.begin(); + i != pressed.end(); ++i) { + if (i->first != button) { + m_keys[i->first] = 1; + m_syntheticKeys[i->first] = 1; + m_keyClientData[i->first] = i->second->m_client; + } + } +} + +// +// KeyState::AddActiveModifierContext +// + +KeyState::AddActiveModifierContext::AddActiveModifierContext( + SInt32 group, KeyModifierMask mask, + ModifierToKeys& activeModifiers) : + m_activeGroup(group), + m_mask(mask), + m_activeModifiers(activeModifiers) +{ + // do nothing +} diff --git a/src/lib/barrier/KeyState.h b/src/lib/barrier/KeyState.h new file mode 100644 index 0000000..737d515 --- /dev/null +++ b/src/lib/barrier/KeyState.h @@ -0,0 +1,232 @@ +/* + * 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 "barrier/IKeyState.h" +#include "barrier/KeyMap.h" + +//! Core key state +/*! +This class provides key state services. Subclasses must implement a few +platform specific methods. +*/ +class KeyState : public IKeyState { +public: + KeyState(IEventQueue* events); + KeyState(IEventQueue* events, barrier::KeyMap& keyMap); + virtual ~KeyState(); + + //! @name manipulators + //@{ + + //! Handle key event + /*! + Sets the state of \p button to down or up and updates the current + modifier state to \p newState. This method should be called by + primary screens only in response to local events. For auto-repeat + set \p down to \c true. Overrides must forward to the superclass. + */ + virtual void onKey(KeyButton button, bool down, + KeyModifierMask newState); + + //! Post a key event + /*! + Posts a key event. This may adjust the event or post additional + events in some circumstances. If this is overridden it must forward + to the superclass. + */ + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + + //@} + //! @name accessors + //@{ + + //@} + + // IKeyState overrides + virtual void updateKeyMap(); + virtual void updateKeyState(); + virtual void setHalfDuplexMask(KeyModifierMask); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual bool fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + virtual bool fakeCtrlAltDel() = 0; + virtual bool fakeMediaKey(KeyID id); + + virtual bool isKeyDown(KeyButton) const; + virtual KeyModifierMask + getActiveModifiers() const; + virtual KeyModifierMask + pollActiveModifiers() const = 0; + virtual SInt32 pollActiveGroup() const = 0; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const = 0; + + SInt32 getKeyState(KeyButton keyButton) { return m_keys[keyButton]; } + +protected: + typedef barrier::KeyMap::Keystroke Keystroke; + + //! @name protected manipulators + //@{ + + //! Get the keyboard map + /*! + Fills \p keyMap with the current keyboard map. + */ + virtual void getKeyMap(barrier::KeyMap& keyMap) = 0; + + //! Fake a key event + /*! + Synthesize an event for \p keystroke. + */ + virtual void fakeKey(const Keystroke& keystroke) = 0; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. The state may be modified. + */ + virtual KeyModifierMask& + getActiveModifiersRValue(); + + //@} + //! @name protected accessors + //@{ + + //! Compute a group number + /*! + Returns the number of the group \p offset groups after group \p group. + */ + SInt32 getEffectiveGroup(SInt32 group, SInt32 offset) const; + + //! Check if key is ignored + /*! + Returns \c true if and only if the key should always be ignored. + The default returns \c true only for the toggle keys. + */ + virtual bool isIgnoredKey(KeyID key, KeyModifierMask mask) const; + + //! Get button for a KeyID + /*! + Return the button mapped to key \p id in group \p group if any, + otherwise returns 0. + */ + KeyButton getButton(KeyID id, SInt32 group) const; + + //@} + +private: + typedef barrier::KeyMap::Keystrokes Keystrokes; + typedef barrier::KeyMap::ModifierToKeys ModifierToKeys; +public: + struct AddActiveModifierContext { + public: + AddActiveModifierContext(SInt32 group, KeyModifierMask mask, + ModifierToKeys& activeModifiers); + + public: + SInt32 m_activeGroup; + KeyModifierMask m_mask; + ModifierToKeys& m_activeModifiers; + + private: + // not implemented + AddActiveModifierContext(const AddActiveModifierContext&); + AddActiveModifierContext& operator=(const AddActiveModifierContext&); + }; +private: + + class ButtonToKeyLess { + public: + bool operator()(const barrier::KeyMap::ButtonToKeyMap::value_type& a, + const barrier::KeyMap::ButtonToKeyMap::value_type b) const + { + return (a.first < b.first); + } + }; + + // not implemented + KeyState(const KeyState&); + KeyState& operator=(const KeyState&); + + // called by all ctors. + void init(); + + // adds alias key sequences. these are sequences that are equivalent + // to other sequences. + void addAliasEntries(); + + // adds non-keypad key sequences for keypad KeyIDs + void addKeypadEntries(); + + // adds key sequences for combination KeyIDs (those built using + // dead keys) + void addCombinationEntries(); + + // synthesize key events. synthesize auto-repeat events count times. + void fakeKeys(const Keystrokes&, UInt32 count); + + // update key state to match changes to modifiers + void updateModifierKeyState(KeyButton button, + const ModifierToKeys& oldModifiers, + const ModifierToKeys& newModifiers); + + // active modifiers collection callback + static void addActiveModifierCB(KeyID id, SInt32 group, + barrier::KeyMap::KeyItem& keyItem, void* vcontext); + +private: + // must be declared before m_keyMap. used when this class owns the key map. + barrier::KeyMap* m_keyMapPtr; + + // the keyboard map + barrier::KeyMap& m_keyMap; + + // current modifier state + KeyModifierMask m_mask; + + // the active modifiers and the buttons activating them + ModifierToKeys m_activeModifiers; + + // current keyboard state (> 0 if pressed, 0 otherwise). this is + // initialized to the keyboard state according to the system then + // it tracks synthesized events. + SInt32 m_keys[kNumButtons]; + + // synthetic keyboard state (> 0 if pressed, 0 otherwise). this + // tracks the synthesized keyboard state. if m_keys[n] > 0 but + // m_syntheticKeys[n] == 0 then the key was pressed locally and + // not synthesized yet. + SInt32 m_syntheticKeys[kNumButtons]; + + // client data for each pressed key + UInt32 m_keyClientData[kNumButtons]; + + // server keyboard state. an entry is 0 if not the key isn't pressed + // otherwise it's the local KeyButton synthesized for the server key. + KeyButton m_serverKeys[kNumButtons]; + + IEventQueue* m_events; +}; diff --git a/src/lib/barrier/PacketStreamFilter.cpp b/src/lib/barrier/PacketStreamFilter.cpp new file mode 100644 index 0000000..16f0fe7 --- /dev/null +++ b/src/lib/barrier/PacketStreamFilter.cpp @@ -0,0 +1,198 @@ +/* + * 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 "barrier/PacketStreamFilter.h" +#include "base/IEventQueue.h" +#include "mt/Lock.h" +#include "base/TMethodEventJob.h" + +#include <cstring> +#include <memory> + +// +// PacketStreamFilter +// + +PacketStreamFilter::PacketStreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream) : + StreamFilter(events, stream, adoptStream), + m_size(0), + m_inputShutdown(false), + m_events(events) +{ + // do nothing +} + +PacketStreamFilter::~PacketStreamFilter() +{ + // do nothing +} + +void +PacketStreamFilter::close() +{ + Lock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + StreamFilter::close(); +} + +UInt32 +PacketStreamFilter::read(void* buffer, UInt32 n) +{ + if (n == 0) { + return 0; + } + + Lock lock(&m_mutex); + + // if not enough data yet then give up + if (!isReadyNoLock()) { + return 0; + } + + // read no more than what's left in the buffered packet + if (n > m_size) { + n = m_size; + } + + // read it + if (buffer != NULL) { + memcpy(buffer, m_buffer.peek(n), n); + } + m_buffer.pop(n); + m_size -= n; + + // get next packet's size if we've finished with this packet and + // there's enough data to do so. + readPacketSize(); + + if (m_inputShutdown && m_size == 0) { + m_events->addEvent(Event(m_events->forIStream().inputShutdown(), + getEventTarget(), NULL)); + } + + return n; +} + +void +PacketStreamFilter::write(const void* buffer, UInt32 count) +{ + // write the length of the payload + UInt8 length[4]; + length[0] = (UInt8)((count >> 24) & 0xff); + length[1] = (UInt8)((count >> 16) & 0xff); + length[2] = (UInt8)((count >> 8) & 0xff); + length[3] = (UInt8)( count & 0xff); + getStream()->write(length, sizeof(length)); + + // write the payload + getStream()->write(buffer, count); +} + +void +PacketStreamFilter::shutdownInput() +{ + Lock lock(&m_mutex); + m_size = 0; + m_buffer.pop(m_buffer.getSize()); + StreamFilter::shutdownInput(); +} + +bool +PacketStreamFilter::isReady() const +{ + Lock lock(&m_mutex); + return isReadyNoLock(); +} + +UInt32 +PacketStreamFilter::getSize() const +{ + Lock lock(&m_mutex); + return isReadyNoLock() ? m_size : 0; +} + +bool +PacketStreamFilter::isReadyNoLock() const +{ + return (m_size != 0 && m_buffer.getSize() >= m_size); +} + +void +PacketStreamFilter::readPacketSize() +{ + // note -- m_mutex must be locked on entry + + if (m_size == 0 && m_buffer.getSize() >= 4) { + UInt8 buffer[4]; + memcpy(buffer, m_buffer.peek(sizeof(buffer)), sizeof(buffer)); + m_buffer.pop(sizeof(buffer)); + m_size = ((UInt32)buffer[0] << 24) | + ((UInt32)buffer[1] << 16) | + ((UInt32)buffer[2] << 8) | + (UInt32)buffer[3]; + } +} + +bool +PacketStreamFilter::readMore() +{ + // note if we have whole packet + bool wasReady = isReadyNoLock(); + + // read more data + char buffer[4096]; + UInt32 n = getStream()->read(buffer, sizeof(buffer)); + while (n > 0) { + m_buffer.write(buffer, n); + n = getStream()->read(buffer, sizeof(buffer)); + } + + // if we don't yet have the next packet size then get it, + // if possible. + readPacketSize(); + + // note if we now have a whole packet + bool isReady = isReadyNoLock(); + + // if we weren't ready before but now we are then send a + // input ready event apparently from the filtered stream. + return (wasReady != isReady); +} + +void +PacketStreamFilter::filterEvent(const Event& event) +{ + if (event.getType() == m_events->forIStream().inputReady()) { + Lock lock(&m_mutex); + if (!readMore()) { + return; + } + } + else if (event.getType() == m_events->forIStream().inputShutdown()) { + // discard this if we have buffered data + Lock lock(&m_mutex); + m_inputShutdown = true; + if (m_size != 0) { + return; + } + } + + // pass event + StreamFilter::filterEvent(event); +} diff --git a/src/lib/barrier/PacketStreamFilter.h b/src/lib/barrier/PacketStreamFilter.h new file mode 100644 index 0000000..bcbd604 --- /dev/null +++ b/src/lib/barrier/PacketStreamFilter.h @@ -0,0 +1,59 @@ +/* + * 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 "io/StreamFilter.h" +#include "io/StreamBuffer.h" +#include "mt/Mutex.h" + +class IEventQueue; + +//! Packetizing stream filter +/*! +Filters a stream to read and write packets. +*/ +class PacketStreamFilter : public StreamFilter { +public: + PacketStreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream = true); + ~PacketStreamFilter(); + + // IStream overrides + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void shutdownInput(); + virtual bool isReady() const; + virtual UInt32 getSize() const; + +protected: + // StreamFilter overrides + virtual void filterEvent(const Event&); + +private: + bool isReadyNoLock() const; + void readPacketSize(); + bool readMore(); + +private: + Mutex m_mutex; + UInt32 m_size; + StreamBuffer m_buffer; + bool m_inputShutdown; + IEventQueue* m_events; +}; diff --git a/src/lib/barrier/PlatformScreen.cpp b/src/lib/barrier/PlatformScreen.cpp new file mode 100644 index 0000000..b0fdc75 --- /dev/null +++ b/src/lib/barrier/PlatformScreen.cpp @@ -0,0 +1,123 @@ +/* + * 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 "barrier/PlatformScreen.h" +#include "barrier/App.h" +#include "barrier/ArgsBase.h" + +PlatformScreen::PlatformScreen(IEventQueue* events) : + IPlatformScreen(events), + m_draggingStarted(false), + m_fakeDraggingStarted(false) +{ +} + +PlatformScreen::~PlatformScreen() +{ + // do nothing +} + +void +PlatformScreen::updateKeyMap() +{ + getKeyState()->updateKeyMap(); +} + +void +PlatformScreen::updateKeyState() +{ + getKeyState()->updateKeyState(); + updateButtons(); +} + +void +PlatformScreen::setHalfDuplexMask(KeyModifierMask mask) +{ + getKeyState()->setHalfDuplexMask(mask); +} + +void +PlatformScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + getKeyState()->fakeKeyDown(id, mask, button); +} + +bool +PlatformScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + return getKeyState()->fakeKeyRepeat(id, mask, count, button); +} + +bool +PlatformScreen::fakeKeyUp(KeyButton button) +{ + return getKeyState()->fakeKeyUp(button); +} + +void +PlatformScreen::fakeAllKeysUp() +{ + getKeyState()->fakeAllKeysUp(); +} + +bool +PlatformScreen::fakeCtrlAltDel() +{ + return getKeyState()->fakeCtrlAltDel(); +} + +bool +PlatformScreen::isKeyDown(KeyButton button) const +{ + return getKeyState()->isKeyDown(button); +} + +KeyModifierMask +PlatformScreen::getActiveModifiers() const +{ + return getKeyState()->getActiveModifiers(); +} + +KeyModifierMask +PlatformScreen::pollActiveModifiers() const +{ + return getKeyState()->pollActiveModifiers(); +} + +SInt32 +PlatformScreen::pollActiveGroup() const +{ + return getKeyState()->pollActiveGroup(); +} + +void +PlatformScreen::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + getKeyState()->pollPressedKeys(pressedKeys); +} + +bool +PlatformScreen::isDraggingStarted() +{ + if (App::instance().argsBase().m_enableDragDrop) { + return m_draggingStarted; + } + return false; +} diff --git a/src/lib/barrier/PlatformScreen.h b/src/lib/barrier/PlatformScreen.h new file mode 100644 index 0000000..82cbfaa --- /dev/null +++ b/src/lib/barrier/PlatformScreen.h @@ -0,0 +1,127 @@ +/*
+ * 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 "barrier/IPlatformScreen.h"
+#include "barrier/DragInformation.h"
+#include "common/stdexcept.h"
+
+//! Base screen implementation
+/*!
+This screen implementation is the superclass of all other screen
+implementations. It implements a handful of methods and requires
+subclasses to implement the rest.
+*/
+class PlatformScreen : public IPlatformScreen {
+public:
+ PlatformScreen(IEventQueue* events);
+ virtual ~PlatformScreen();
+
+ // IScreen overrides
+ virtual void* getEventTarget() const = 0;
+ virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0;
+ virtual void getShape(SInt32& x, SInt32& y,
+ SInt32& width, SInt32& height) const = 0;
+ virtual void getCursorPos(SInt32& x, SInt32& y) const = 0;
+
+ // IPrimaryScreen overrides
+ virtual void reconfigure(UInt32 activeSides) = 0;
+ virtual void warpCursor(SInt32 x, SInt32 y) = 0;
+ virtual UInt32 registerHotKey(KeyID key,
+ KeyModifierMask mask) = 0;
+ virtual void unregisterHotKey(UInt32 id) = 0;
+ virtual void fakeInputBegin() = 0;
+ virtual void fakeInputEnd() = 0;
+ virtual SInt32 getJumpZoneSize() const = 0;
+ virtual bool isAnyMouseButtonDown(UInt32& buttonID) const = 0;
+ virtual void getCursorCenter(SInt32& x, SInt32& y) const = 0;
+
+ // ISecondaryScreen overrides
+ virtual void fakeMouseButton(ButtonID id, bool press) = 0;
+ virtual void fakeMouseMove(SInt32 x, SInt32 y) = 0;
+ virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const = 0;
+ virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const = 0;
+
+ // IKeyState overrides
+ virtual void updateKeyMap();
+ virtual void updateKeyState();
+ virtual void setHalfDuplexMask(KeyModifierMask);
+ virtual void fakeKeyDown(KeyID id, KeyModifierMask mask,
+ KeyButton button);
+ virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask,
+ SInt32 count, KeyButton button);
+ virtual bool fakeKeyUp(KeyButton button);
+ virtual void fakeAllKeysUp();
+ virtual bool fakeCtrlAltDel();
+ virtual bool isKeyDown(KeyButton) const;
+ virtual KeyModifierMask
+ getActiveModifiers() const;
+ virtual KeyModifierMask
+ pollActiveModifiers() const;
+ virtual SInt32 pollActiveGroup() const;
+ virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const;
+
+ virtual void setDraggingStarted(bool started) { m_draggingStarted = started; }
+ virtual bool isDraggingStarted();
+ virtual bool isFakeDraggingStarted() { return m_fakeDraggingStarted; }
+ virtual String& getDraggingFilename() { return m_draggingFilename; }
+ virtual void clearDraggingFilename() { }
+
+ // IPlatformScreen overrides
+ virtual void enable() = 0;
+ virtual void disable() = 0;
+ virtual void enter() = 0;
+ virtual bool leave() = 0;
+ virtual bool setClipboard(ClipboardID, const IClipboard*) = 0;
+ virtual void checkClipboards() = 0;
+ virtual void openScreensaver(bool notify) = 0;
+ virtual void closeScreensaver() = 0;
+ virtual void screensaver(bool activate) = 0;
+ virtual void resetOptions() = 0;
+ virtual void setOptions(const OptionsList& options) = 0;
+ virtual void setSequenceNumber(UInt32) = 0;
+ virtual bool isPrimary() const = 0;
+
+ virtual void fakeDraggingFiles(DragFileList fileList) { throw std::runtime_error("fakeDraggingFiles not implemented"); }
+ virtual const String&
+ getDropTarget() const { throw std::runtime_error("getDropTarget not implemented"); }
+
+protected:
+ //! Update mouse buttons
+ /*!
+ Subclasses must implement this method to update their internal mouse
+ button mapping and, if desired, state tracking.
+ */
+ virtual void updateButtons() = 0;
+
+ //! Get the key state
+ /*!
+ Subclasses must implement this method to return the platform specific
+ key state object that each subclass must have.
+ */
+ virtual IKeyState* getKeyState() const = 0;
+
+ // IPlatformScreen overrides
+ virtual void handleSystemEvent(const Event& event, void*) = 0;
+
+protected:
+ String m_draggingFilename;
+ bool m_draggingStarted;
+ bool m_fakeDraggingStarted;
+};
diff --git a/src/lib/barrier/PortableTaskBarReceiver.cpp b/src/lib/barrier/PortableTaskBarReceiver.cpp new file mode 100644 index 0000000..384cacd --- /dev/null +++ b/src/lib/barrier/PortableTaskBarReceiver.cpp @@ -0,0 +1,121 @@ +/* + * 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 "barrier/PortableTaskBarReceiver.h" +#include "mt/Lock.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "arch/Arch.h" +#include "common/Version.h" + +// +// PortableTaskBarReceiver +// + +PortableTaskBarReceiver::PortableTaskBarReceiver(IEventQueue* events) : + m_state(kNotRunning), + m_events(events) +{ + // do nothing +} + +PortableTaskBarReceiver::~PortableTaskBarReceiver() +{ + // do nothing +} + +void +PortableTaskBarReceiver::updateStatus(INode* node, const String& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (node == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_state = kNotConnected; + } + + // let subclasses have a go + onStatusChanged(node); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +PortableTaskBarReceiver::EState +PortableTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const String& +PortableTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +void +PortableTaskBarReceiver::quit() +{ + m_events->addEvent(Event(Event::kQuit)); +} + +void +PortableTaskBarReceiver::onStatusChanged(INode*) +{ + // do nothing +} + +void +PortableTaskBarReceiver::lock() const +{ + // do nothing +} + +void +PortableTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +PortableTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return barrier::string::sprintf("%s: Not running", kAppVersion); + + case kNotWorking: + return barrier::string::sprintf("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return barrier::string::sprintf("%s: Unknown", kAppVersion); + + default: + return ""; + } +} diff --git a/src/lib/barrier/PortableTaskBarReceiver.h b/src/lib/barrier/PortableTaskBarReceiver.h new file mode 100644 index 0000000..d335e44 --- /dev/null +++ b/src/lib/barrier/PortableTaskBarReceiver.h @@ -0,0 +1,96 @@ +/* + * 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 "barrier/INode.h" +#include "arch/IArchTaskBarReceiver.h" +#include "base/log_outputters.h" +#include "base/EventTypes.h" +#include "base/Event.h" +#include "base/String.h" +#include "common/stdvector.h" + +class IEventQueue; + +//! Implementation of IArchTaskBarReceiver for the barrier server +class PortableTaskBarReceiver : public IArchTaskBarReceiver { +public: + PortableTaskBarReceiver(IEventQueue* events); + virtual ~PortableTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the server. + */ + void updateStatus(INode*, const String& errorMsg); + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + typedef std::vector<String> Clients; + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const String& getErrorMessage() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does + nothing. + */ + virtual void onStatusChanged(INode* node); + +private: + EState m_state; + String m_errorMessage; + + String m_server; + Clients m_clients; + + IEventQueue* m_events; +}; + +IArchTaskBarReceiver* createTaskBarReceiver(const BufferedLogOutputter* logBuffer, IEventQueue* events); diff --git a/src/lib/barrier/ProtocolUtil.cpp b/src/lib/barrier/ProtocolUtil.cpp new file mode 100644 index 0000000..42fe69c --- /dev/null +++ b/src/lib/barrier/ProtocolUtil.cpp @@ -0,0 +1,544 @@ +/* + * 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 "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "base/Log.h" +#include "common/stdvector.h" + +#include <cctype> +#include <cstring> + +// +// ProtocolUtil +// + +void +ProtocolUtil::writef(barrier::IStream* stream, const char* fmt, ...) +{ + assert(stream != NULL); + assert(fmt != NULL); + LOG((CLOG_DEBUG2 "writef(%s)", fmt)); + + va_list args; + va_start(args, fmt); + UInt32 size = getLength(fmt, args); + va_end(args); + va_start(args, fmt); + vwritef(stream, fmt, size, args); + va_end(args); +} + +bool +ProtocolUtil::readf(barrier::IStream* stream, const char* fmt, ...) +{ + assert(stream != NULL); + assert(fmt != NULL); + LOG((CLOG_DEBUG2 "readf(%s)", fmt)); + + bool result; + va_list args; + va_start(args, fmt); + try { + vreadf(stream, fmt, args); + result = true; + } + catch (XIO&) { + result = false; + } + va_end(args); + return result; +} + +void +ProtocolUtil::vwritef(barrier::IStream* stream, + const char* fmt, UInt32 size, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // done if nothing to write + if (size == 0) { + return; + } + + // fill buffer + UInt8* buffer = new UInt8[size]; + writef(buffer, fmt, args); + + try { + // write buffer + stream->write(buffer, size); + LOG((CLOG_DEBUG2 "wrote %d bytes", size)); + + delete[] buffer; + } + catch (XBase&) { + delete[] buffer; + throw; + } +} + +void +ProtocolUtil::vreadf(barrier::IStream* stream, const char* fmt, va_list args) +{ + assert(stream != NULL); + assert(fmt != NULL); + + // begin scanning + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the data + UInt8 buffer[4]; + read(stream, buffer, len); + + // convert it + void* v = va_arg(args, void*); + switch (len) { + case 1: + // 1 byte integer + *static_cast<UInt8*>(v) = buffer[0]; + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *static_cast<UInt8*>(v), *static_cast<UInt8*>(v))); + break; + + case 2: + // 2 byte integer + *static_cast<UInt16*>(v) = + static_cast<UInt16>( + (static_cast<UInt16>(buffer[0]) << 8) | + static_cast<UInt16>(buffer[1])); + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *static_cast<UInt16*>(v), *static_cast<UInt16*>(v))); + break; + + case 4: + // 4 byte integer + *static_cast<UInt32*>(v) = + (static_cast<UInt32>(buffer[0]) << 24) | + (static_cast<UInt32>(buffer[1]) << 16) | + (static_cast<UInt32>(buffer[2]) << 8) | + static_cast<UInt32>(buffer[3]); + LOG((CLOG_DEBUG2 "readf: read %d byte integer: %d (0x%x)", len, *static_cast<UInt32*>(v), *static_cast<UInt32*>(v))); + break; + } + break; + } + + case 'I': { + // check for valid length + assert(len == 1 || len == 2 || len == 4); + + // read the vector length + UInt8 buffer[4]; + read(stream, buffer, 4); + UInt32 n = (static_cast<UInt32>(buffer[0]) << 24) | + (static_cast<UInt32>(buffer[1]) << 16) | + (static_cast<UInt32>(buffer[2]) << 8) | + static_cast<UInt32>(buffer[3]); + + // convert it + void* v = va_arg(args, void*); + switch (len) { + case 1: + // 1 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 1); + static_cast<std::vector<UInt8>*>(v)->push_back( + buffer[0]); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, static_cast<std::vector<UInt8>*>(v)->back(), static_cast<std::vector<UInt8>*>(v)->back())); + } + break; + + case 2: + // 2 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 2); + static_cast<std::vector<UInt16>*>(v)->push_back( + static_cast<UInt16>( + (static_cast<UInt16>(buffer[0]) << 8) | + static_cast<UInt16>(buffer[1]))); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, static_cast<std::vector<UInt16>*>(v)->back(), static_cast<std::vector<UInt16>*>(v)->back())); + } + break; + + case 4: + // 4 byte integer + for (UInt32 i = 0; i < n; ++i) { + read(stream, buffer, 4); + static_cast<std::vector<UInt32>*>(v)->push_back( + (static_cast<UInt32>(buffer[0]) << 24) | + (static_cast<UInt32>(buffer[1]) << 16) | + (static_cast<UInt32>(buffer[2]) << 8) | + static_cast<UInt32>(buffer[3])); + LOG((CLOG_DEBUG2 "readf: read %d byte integer[%d]: %d (0x%x)", len, i, static_cast<std::vector<UInt32>*>(v)->back(), static_cast<std::vector<UInt32>*>(v)->back())); + } + break; + } + break; + } + + case 's': { + assert(len == 0); + + // read the string length + UInt8 buffer[128]; + read(stream, buffer, 4); + UInt32 len = (static_cast<UInt32>(buffer[0]) << 24) | + (static_cast<UInt32>(buffer[1]) << 16) | + (static_cast<UInt32>(buffer[2]) << 8) | + static_cast<UInt32>(buffer[3]); + + // use a fixed size buffer if its big enough + const bool useFixed = (len <= sizeof(buffer)); + + // allocate a buffer to read the data + UInt8* sBuffer = buffer; + if (!useFixed) { + sBuffer = new UInt8[len]; + } + + // read the data + try { + read(stream, sBuffer, len); + } + catch (...) { + if (!useFixed) { + delete[] sBuffer; + } + throw; + } + + LOG((CLOG_DEBUG2 "readf: read %d byte string", len)); + + // save the data + String* dst = va_arg(args, String*); + dst->assign((const char*)sBuffer, len); + + // release the buffer + if (!useFixed) { + delete[] sBuffer; + } + break; + } + + case '%': + assert(len == 0); + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // read next character + char buffer[1]; + read(stream, buffer, 1); + + // verify match + if (buffer[0] != *fmt) { + LOG((CLOG_DEBUG2 "readf: format mismatch: %c vs %c", *fmt, buffer[0])); + throw XIOReadMismatch(); + } + + // next format character + ++fmt; + } + } +} + +UInt32 +ProtocolUtil::getLength(const char* fmt, va_list args) +{ + UInt32 n = 0; + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': + assert(len == 1 || len == 2 || len == 4); + (void)va_arg(args, UInt32); + break; + + case 'I': + assert(len == 1 || len == 2 || len == 4); + switch (len) { + case 1: + len = (UInt32)(va_arg(args, std::vector<UInt8>*))->size() + 4; + break; + + case 2: + len = 2 * (UInt32)(va_arg(args, std::vector<UInt16>*))->size() + 4; + break; + + case 4: + len = 4 * (UInt32)(va_arg(args, std::vector<UInt32>*))->size() + 4; + break; + } + break; + + case 's': + assert(len == 0); + len = (UInt32)(va_arg(args, String*))->size() + 4; + (void)va_arg(args, UInt8*); + break; + + case 'S': + assert(len == 0); + len = va_arg(args, UInt32) + 4; + (void)va_arg(args, UInt8*); + break; + + case '%': + assert(len == 0); + len = 1; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // accumulate size + n += len; + ++fmt; + } + else { + // regular character + ++n; + ++fmt; + } + } + return n; +} + +void +ProtocolUtil::writef(void* buffer, const char* fmt, va_list args) +{ + UInt8* dst = static_cast<UInt8*>(buffer); + + while (*fmt) { + if (*fmt == '%') { + // format specifier. determine argument size. + ++fmt; + UInt32 len = eatLength(&fmt); + switch (*fmt) { + case 'i': { + const UInt32 v = va_arg(args, UInt32); + switch (len) { + case 1: + // 1 byte integer + *dst++ = static_cast<UInt8>(v & 0xff); + break; + + case 2: + // 2 byte integer + *dst++ = static_cast<UInt8>((v >> 8) & 0xff); + *dst++ = static_cast<UInt8>( v & 0xff); + break; + + case 4: + // 4 byte integer + *dst++ = static_cast<UInt8>((v >> 24) & 0xff); + *dst++ = static_cast<UInt8>((v >> 16) & 0xff); + *dst++ = static_cast<UInt8>((v >> 8) & 0xff); + *dst++ = static_cast<UInt8>( v & 0xff); + break; + + default: + assert(0 && "invalid integer format length"); + return; + } + break; + } + + case 'I': { + switch (len) { + case 1: { + // 1 byte integers + const std::vector<UInt8>* list = + va_arg(args, const std::vector<UInt8>*); + const UInt32 n = (UInt32)list->size(); + *dst++ = static_cast<UInt8>((n >> 24) & 0xff); + *dst++ = static_cast<UInt8>((n >> 16) & 0xff); + *dst++ = static_cast<UInt8>((n >> 8) & 0xff); + *dst++ = static_cast<UInt8>( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + *dst++ = (*list)[i]; + } + break; + } + + case 2: { + // 2 byte integers + const std::vector<UInt16>* list = + va_arg(args, const std::vector<UInt16>*); + const UInt32 n = (UInt32)list->size(); + *dst++ = static_cast<UInt8>((n >> 24) & 0xff); + *dst++ = static_cast<UInt8>((n >> 16) & 0xff); + *dst++ = static_cast<UInt8>((n >> 8) & 0xff); + *dst++ = static_cast<UInt8>( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + const UInt16 v = (*list)[i]; + *dst++ = static_cast<UInt8>((v >> 8) & 0xff); + *dst++ = static_cast<UInt8>( v & 0xff); + } + break; + } + + case 4: { + // 4 byte integers + const std::vector<UInt32>* list = + va_arg(args, const std::vector<UInt32>*); + const UInt32 n = (UInt32)list->size(); + *dst++ = static_cast<UInt8>((n >> 24) & 0xff); + *dst++ = static_cast<UInt8>((n >> 16) & 0xff); + *dst++ = static_cast<UInt8>((n >> 8) & 0xff); + *dst++ = static_cast<UInt8>( n & 0xff); + for (UInt32 i = 0; i < n; ++i) { + const UInt32 v = (*list)[i]; + *dst++ = static_cast<UInt8>((v >> 24) & 0xff); + *dst++ = static_cast<UInt8>((v >> 16) & 0xff); + *dst++ = static_cast<UInt8>((v >> 8) & 0xff); + *dst++ = static_cast<UInt8>( v & 0xff); + } + break; + } + + default: + assert(0 && "invalid integer vector format length"); + return; + } + break; + } + + case 's': { + assert(len == 0); + const String* src = va_arg(args, String*); + const UInt32 len = (src != NULL) ? (UInt32)src->size() : 0; + *dst++ = static_cast<UInt8>((len >> 24) & 0xff); + *dst++ = static_cast<UInt8>((len >> 16) & 0xff); + *dst++ = static_cast<UInt8>((len >> 8) & 0xff); + *dst++ = static_cast<UInt8>( len & 0xff); + if (len != 0) { + memcpy(dst, src->data(), len); + dst += len; + } + break; + } + + case 'S': { + assert(len == 0); + const UInt32 len = va_arg(args, UInt32); + const UInt8* src = va_arg(args, UInt8*); + *dst++ = static_cast<UInt8>((len >> 24) & 0xff); + *dst++ = static_cast<UInt8>((len >> 16) & 0xff); + *dst++ = static_cast<UInt8>((len >> 8) & 0xff); + *dst++ = static_cast<UInt8>( len & 0xff); + memcpy(dst, src, len); + dst += len; + break; + } + + case '%': + assert(len == 0); + *dst++ = '%'; + break; + + default: + assert(0 && "invalid format specifier"); + } + + // next format character + ++fmt; + } + else { + // copy regular character + *dst++ = *fmt++; + } + } +} + +UInt32 +ProtocolUtil::eatLength(const char** pfmt) +{ + const char* fmt = *pfmt; + UInt32 n = 0; + for (;;) { + UInt32 d; + switch (*fmt) { + case '0': d = 0; break; + case '1': d = 1; break; + case '2': d = 2; break; + case '3': d = 3; break; + case '4': d = 4; break; + case '5': d = 5; break; + case '6': d = 6; break; + case '7': d = 7; break; + case '8': d = 8; break; + case '9': d = 9; break; + default: *pfmt = fmt; return n; + } + n = 10 * n + d; + ++fmt; + } +} + +void +ProtocolUtil::read(barrier::IStream* stream, void* vbuffer, UInt32 count) +{ + assert(stream != NULL); + assert(vbuffer != NULL); + + UInt8* buffer = static_cast<UInt8*>(vbuffer); + while (count > 0) { + // read more + UInt32 n = stream->read(buffer, count); + + // bail if stream has hungup + if (n == 0) { + LOG((CLOG_DEBUG2 "unexpected disconnect in readf(), %d bytes left", count)); + throw XIOEndOfStream(); + } + + // prepare for next read + buffer += n; + count -= n; + } +} + + +// +// XIOReadMismatch +// + +String +XIOReadMismatch::getWhat() const throw() +{ + return format("XIOReadMismatch", "ProtocolUtil::readf() mismatch"); +} diff --git a/src/lib/barrier/ProtocolUtil.h b/src/lib/barrier/ProtocolUtil.h new file mode 100644 index 0000000..78bb5ca --- /dev/null +++ b/src/lib/barrier/ProtocolUtil.h @@ -0,0 +1,96 @@ +/* + * 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 "io/XIO.h" +#include "base/EventTypes.h" + +#include <stdarg.h> + +namespace barrier { class IStream; } + +//! Barrier protocol utilities +/*! +This class provides various functions for implementing the barrier +protocol. +*/ +class ProtocolUtil { +public: + //! Write formatted data + /*! + Write formatted binary data to a stream. \c fmt consists of + regular characters and format specifiers. Format specifiers + begin with \%. All characters not part of a format specifier + are regular and are transmitted unchanged. + + Format specifiers are: + - \%\% -- literal `\%' + - \%1i -- converts integer argument to 1 byte integer + - \%2i -- converts integer argument to 2 byte integer in NBO + - \%4i -- converts integer argument to 4 byte integer in NBO + - \%1I -- converts std::vector<UInt8>* to 1 byte integers + - \%2I -- converts std::vector<UInt16>* to 2 byte integers in NBO + - \%4I -- converts std::vector<UInt32>* to 4 byte integers in NBO + - \%s -- converts String* to stream of bytes + - \%S -- converts integer N and const UInt8* to stream of N bytes + */ + static void writef(barrier::IStream*, + const char* fmt, ...); + + //! Read formatted data + /*! + Read formatted binary data from a buffer. This performs the + reverse operation of writef(). Returns true if the entire + format was successfully parsed, false otherwise. + + Format specifiers are: + - \%\% -- read (and discard) a literal `\%' + - \%1i -- reads a 1 byte integer; argument is a SInt32* or UInt32* + - \%2i -- reads an NBO 2 byte integer; arg is SInt32* or UInt32* + - \%4i -- reads an NBO 4 byte integer; arg is SInt32* or UInt32* + - \%1I -- reads 1 byte integers; arg is std::vector<UInt8>* + - \%2I -- reads NBO 2 byte integers; arg is std::vector<UInt16>* + - \%4I -- reads NBO 4 byte integers; arg is std::vector<UInt32>* + - \%s -- reads bytes; argument must be a String*, \b not a char* + */ + static bool readf(barrier::IStream*, + const char* fmt, ...); + +private: + static void vwritef(barrier::IStream*, + const char* fmt, UInt32 size, va_list); + static void vreadf(barrier::IStream*, + const char* fmt, va_list); + + static UInt32 getLength(const char* fmt, va_list); + static void writef(void*, const char* fmt, va_list); + static UInt32 eatLength(const char** fmt); + static void read(barrier::IStream*, void*, UInt32); +}; + +//! Mismatched read exception +/*! +Thrown by ProtocolUtil::readf() when the data being read does not +match the format. +*/ +class XIOReadMismatch : public XIO { +public: + // XBase overrides + virtual String getWhat() const throw(); +}; diff --git a/src/lib/barrier/Screen.cpp b/src/lib/barrier/Screen.cpp new file mode 100644 index 0000000..32442f6 --- /dev/null +++ b/src/lib/barrier/Screen.cpp @@ -0,0 +1,559 @@ +/* + * 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 "barrier/Screen.h" +#include "barrier/IPlatformScreen.h" +#include "barrier/protocol_types.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "server/ClientProxy.h" +#include "base/TMethodEventJob.h" + +namespace barrier { + +// +// Screen +// + +Screen::Screen(IPlatformScreen* platformScreen, IEventQueue* events) : + m_screen(platformScreen), + m_isPrimary(platformScreen->isPrimary()), + m_enabled(false), + m_entered(m_isPrimary), + m_screenSaverSync(true), + m_fakeInput(false), + m_events(events), + m_mock(false), + m_enableDragDrop(false) +{ + assert(m_screen != NULL); + + // reset options + resetOptions(); + + LOG((CLOG_DEBUG "opened display")); +} + +Screen::~Screen() +{ + if (m_mock) { + return; + } + + if (m_enabled) { + disable(); + } + assert(!m_enabled); + assert(m_entered == m_isPrimary); + delete m_screen; + LOG((CLOG_DEBUG "closed display")); +} + +void +Screen::enable() +{ + assert(!m_enabled); + + m_screen->updateKeyMap(); + m_screen->updateKeyState(); + m_screen->enable(); + if (m_isPrimary) { + enablePrimary(); + } + else { + enableSecondary(); + } + + // note activation + m_enabled = true; +} + +void +Screen::disable() +{ + assert(m_enabled); + + if (!m_isPrimary && m_entered) { + leave(); + } + else if (m_isPrimary && !m_entered) { + enter(0); + } + m_screen->disable(); + if (m_isPrimary) { + disablePrimary(); + } + else { + disableSecondary(); + } + + // note deactivation + m_enabled = false; +} + +void +Screen::enter(KeyModifierMask toggleMask) +{ + assert(m_entered == false); + LOG((CLOG_INFO "entering screen")); + + // now on screen + m_entered = true; + + m_screen->enter(); + if (m_isPrimary) { + enterPrimary(); + } + else { + enterSecondary(toggleMask); + } +} + +bool +Screen::leave() +{ + assert(m_entered == true); + LOG((CLOG_INFO "leaving screen")); + + if (!m_screen->leave()) { + return false; + } + if (m_isPrimary) { + leavePrimary(); + } + else { + leaveSecondary(); + } + + // make sure our idea of clipboard ownership is correct + m_screen->checkClipboards(); + + // now not on screen + m_entered = false; + + return true; +} + +void +Screen::reconfigure(UInt32 activeSides) +{ + assert(m_isPrimary); + m_screen->reconfigure(activeSides); +} + +void +Screen::warpCursor(SInt32 x, SInt32 y) +{ + assert(m_isPrimary); + m_screen->warpCursor(x, y); +} + +void +Screen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); +} + +void +Screen::grabClipboard(ClipboardID id) +{ + m_screen->setClipboard(id, NULL); +} + +void +Screen::screensaver(bool activate) +{ + if (!m_isPrimary) { + // activate/deactivation screen saver iff synchronization enabled + if (m_screenSaverSync) { + m_screen->screensaver(activate); + } + } +} + +void +Screen::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + // check for ctrl+alt+del emulation + if (id == kKeyDelete && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + LOG((CLOG_DEBUG "emulating ctrl+alt+del press")); + if (m_screen->fakeCtrlAltDel()) { + return; + } + } + m_screen->fakeKeyDown(id, mask, button); +} + +void +Screen::keyRepeat(KeyID id, + KeyModifierMask mask, SInt32 count, KeyButton button) +{ + assert(!m_isPrimary); + m_screen->fakeKeyRepeat(id, mask, count, button); +} + +void +Screen::keyUp(KeyID, KeyModifierMask, KeyButton button) +{ + m_screen->fakeKeyUp(button); +} + +void +Screen::mouseDown(ButtonID button) +{ + m_screen->fakeMouseButton(button, true); +} + +void +Screen::mouseUp(ButtonID button) +{ + m_screen->fakeMouseButton(button, false); +} + +void +Screen::mouseMove(SInt32 x, SInt32 y) +{ + assert(!m_isPrimary); + m_screen->fakeMouseMove(x, y); +} + +void +Screen::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + assert(!m_isPrimary); + m_screen->fakeMouseRelativeMove(dx, dy); +} + +void +Screen::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + assert(!m_isPrimary); + m_screen->fakeMouseWheel(xDelta, yDelta); +} + +void +Screen::resetOptions() +{ + // reset options + m_halfDuplex = 0; + + // if screen saver synchronization was off then turn it on since + // that's the default option state. + if (!m_screenSaverSync) { + m_screenSaverSync = true; + if (!m_isPrimary) { + m_screen->openScreensaver(false); + } + } + + // let screen handle its own options + m_screen->resetOptions(); +} + +void +Screen::setOptions(const OptionsList& options) +{ + // update options + bool oldScreenSaverSync = m_screenSaverSync; + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + if (options[i] == kOptionScreenSaverSync) { + m_screenSaverSync = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "screen saver synchronization %s", m_screenSaverSync ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexCapsLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierCapsLock; + } + else { + m_halfDuplex &= ~KeyModifierCapsLock; + } + LOG((CLOG_DEBUG1 "half-duplex caps-lock %s", ((m_halfDuplex & KeyModifierCapsLock) != 0) ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexNumLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierNumLock; + } + else { + m_halfDuplex &= ~KeyModifierNumLock; + } + LOG((CLOG_DEBUG1 "half-duplex num-lock %s", ((m_halfDuplex & KeyModifierNumLock) != 0) ? "on" : "off")); + } + else if (options[i] == kOptionHalfDuplexScrollLock) { + if (options[i + 1] != 0) { + m_halfDuplex |= KeyModifierScrollLock; + } + else { + m_halfDuplex &= ~KeyModifierScrollLock; + } + LOG((CLOG_DEBUG1 "half-duplex scroll-lock %s", ((m_halfDuplex & KeyModifierScrollLock) != 0) ? "on" : "off")); + } + } + + // update half-duplex options + m_screen->setHalfDuplexMask(m_halfDuplex); + + // update screen saver synchronization + if (!m_isPrimary && oldScreenSaverSync != m_screenSaverSync) { + if (m_screenSaverSync) { + m_screen->openScreensaver(false); + } + else { + m_screen->closeScreensaver(); + } + } + + // let screen handle its own options + m_screen->setOptions(options); +} + +void +Screen::setSequenceNumber(UInt32 seqNum) +{ + m_screen->setSequenceNumber(seqNum); +} + +UInt32 +Screen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + return m_screen->registerHotKey(key, mask); +} + +void +Screen::unregisterHotKey(UInt32 id) +{ + m_screen->unregisterHotKey(id); +} + +void +Screen::fakeInputBegin() +{ + assert(!m_fakeInput); + + m_fakeInput = true; + m_screen->fakeInputBegin(); +} + +void +Screen::fakeInputEnd() +{ + assert(m_fakeInput); + + m_fakeInput = false; + m_screen->fakeInputEnd(); +} + +bool +Screen::isOnScreen() const +{ + return m_entered; +} + +bool +Screen::isLockedToScreen() const +{ + // check for pressed mouse buttons + // HACK: commented out as it breaks new drag drop feature + UInt32 buttonID = 0; + + if (m_screen->isAnyMouseButtonDown(buttonID)) { + if (buttonID != kButtonLeft) { + LOG((CLOG_DEBUG "locked by mouse buttonID: %d", buttonID)); + } + + if (m_enableDragDrop) { + return (buttonID == kButtonLeft) ? false : true; + } + else { + return true; + } + } + + // not locked + return false; +} + +SInt32 +Screen::getJumpZoneSize() const +{ + if (!m_isPrimary) { + return 0; + } + else { + return m_screen->getJumpZoneSize(); + } +} + +void +Screen::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +KeyModifierMask +Screen::getActiveModifiers() const +{ + return m_screen->getActiveModifiers(); +} + +KeyModifierMask +Screen::pollActiveModifiers() const +{ + return m_screen->pollActiveModifiers(); +} + +bool +Screen::isDraggingStarted() const +{ + return m_screen->isDraggingStarted(); +} + +bool +Screen::isFakeDraggingStarted() const +{ + return m_screen->isFakeDraggingStarted(); +} + +void +Screen::setDraggingStarted(bool started) +{ + m_screen->setDraggingStarted(started); +} + +void +Screen::startDraggingFiles(DragFileList& fileList) +{ + m_screen->fakeDraggingFiles(fileList); +} + +void +Screen::setEnableDragDrop(bool enabled) +{ + m_enableDragDrop = enabled; +} + +String& +Screen::getDraggingFilename() const +{ + return m_screen->getDraggingFilename(); +} + +void +Screen::clearDraggingFilename() +{ + m_screen->clearDraggingFilename(); +} + +const String& +Screen::getDropTarget() const +{ + return m_screen->getDropTarget(); +} + +void* +Screen::getEventTarget() const +{ + return m_screen; +} + +bool +Screen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +Screen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +Screen::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +Screen::enablePrimary() +{ + // get notified of screen saver activation/deactivation + m_screen->openScreensaver(true); + + // claim screen changed size + m_events->addEvent(Event(m_events->forIScreen().shapeChanged(), getEventTarget())); +} + +void +Screen::enableSecondary() +{ + // assume primary has all clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + grabClipboard(id); + } + + // disable the screen saver if synchronization is enabled + if (m_screenSaverSync) { + m_screen->openScreensaver(false); + } +} + +void +Screen::disablePrimary() +{ + // done with screen saver + m_screen->closeScreensaver(); +} + +void +Screen::disableSecondary() +{ + // done with screen saver + m_screen->closeScreensaver(); +} + +void +Screen::enterPrimary() +{ + // do nothing +} + +void +Screen::enterSecondary(KeyModifierMask) +{ + // do nothing +} + +void +Screen::leavePrimary() +{ + // we don't track keys while on the primary screen so update our + // idea of them now. this is particularly to update the state of + // the toggle modifiers. + m_screen->updateKeyState(); +} + +void +Screen::leaveSecondary() +{ + // release any keys we think are still down + m_screen->fakeAllKeysUp(); +} + +} diff --git a/src/lib/barrier/Screen.h b/src/lib/barrier/Screen.h new file mode 100644 index 0000000..b16feff --- /dev/null +++ b/src/lib/barrier/Screen.h @@ -0,0 +1,345 @@ +/* + * 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 "barrier/DragInformation.h" +#include "barrier/clipboard_types.h" +#include "barrier/IScreen.h" +#include "barrier/key_types.h" +#include "barrier/mouse_types.h" +#include "barrier/option_types.h" +#include "base/String.h" + +class IClipboard; +class IPlatformScreen; +class IEventQueue; + +namespace barrier { + +//! Platform independent screen +/*! +This is a platform independent screen. It can work as either a +primary or secondary screen. +*/ +class Screen : public IScreen { +public: + Screen(IPlatformScreen* platformScreen, IEventQueue* events); + virtual ~Screen(); + +#ifdef TEST_ENV + Screen() : m_mock(true) { } +#endif + + //! @name manipulators + //@{ + + //! Activate screen + /*! + Activate the screen, preparing it to report system and user events. + For a secondary screen it also means disabling the screen saver if + synchronizing it and preparing to synthesize events. + */ + virtual void enable(); + + //! Deactivate screen + /*! + Undoes the operations in activate() and events are no longer + reported. It also releases keys that are logically pressed. + */ + virtual void disable(); + + //! Enter screen + /*! + Called when the user navigates to this screen. \p toggleMask has the + toggle keys that should be turned on on the secondary screen. + */ + void enter(KeyModifierMask toggleMask); + + //! Leave screen + /*! + Called when the user navigates off this screen. + */ + bool leave(); + + //! Update configuration + /*! + This is called when the configuration has changed. \c activeSides + is a bitmask of EDirectionMask indicating which sides of the + primary screen are linked to clients. + */ + void reconfigure(UInt32 activeSides); + + //! Warp cursor + /*! + Warps the cursor to the absolute coordinates \c x,y. Also + discards input events up to and including the warp before + returning. + */ + void warpCursor(SInt32 x, SInt32 y); + + //! Set clipboard + /*! + Sets the system's clipboard contents. This is usually called + soon after an enter(). + */ + void setClipboard(ClipboardID, const IClipboard*); + + //! Grab clipboard + /*! + Grabs (i.e. take ownership of) the system clipboard. + */ + void grabClipboard(ClipboardID); + + //! Activate/deactivate screen saver + /*! + Forcibly activates the screen saver if \c activate is true otherwise + forcibly deactivates it. + */ + void screensaver(bool activate); + + //! Notify of key press + /*! + Synthesize key events to generate a press of key \c id. If possible + match the given modifier mask. The KeyButton identifies the physical + key on the server that generated this key down. The client must + ensure that a key up or key repeat that uses the same KeyButton will + synthesize an up or repeat for the same client key synthesized by + keyDown(). + */ + void keyDown(KeyID id, KeyModifierMask, KeyButton); + + //! Notify of key repeat + /*! + Synthesize key events to generate a press and release of key \c id + \c count times. If possible match the given modifier mask. + */ + void keyRepeat(KeyID id, KeyModifierMask, + SInt32 count, KeyButton); + + //! Notify of key release + /*! + Synthesize key events to generate a release of key \c id. If possible + match the given modifier mask. + */ + void keyUp(KeyID id, KeyModifierMask, KeyButton); + + //! Notify of mouse press + /*! + Synthesize mouse events to generate a press of mouse button \c id. + */ + void mouseDown(ButtonID id); + + //! Notify of mouse release + /*! + Synthesize mouse events to generate a release of mouse button \c id. + */ + void mouseUp(ButtonID id); + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion to the absolute + screen position \c xAbs,yAbs. + */ + void mouseMove(SInt32 xAbs, SInt32 yAbs); + + //! Notify of mouse motion + /*! + Synthesize mouse events to generate mouse motion by the relative + amount \c xRel,yRel. + */ + void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + + //! Notify of mouse wheel motion + /*! + Synthesize mouse events to generate mouse wheel motion of \c xDelta + and \c yDelta. Deltas are positive for motion away from the user or + to the right and negative for motion towards the user or to the left. + Each wheel click should generate a delta of +/-120. + */ + void mouseWheel(SInt32 xDelta, SInt32 yDelta); + + //! Notify of options changes + /*! + Resets all options to their default values. + */ + virtual void resetOptions(); + + //! Notify of options changes + /*! + Set options to given values. Ignores unknown options and doesn't + modify options that aren't given in \c options. + */ + virtual void setOptions(const OptionsList& options); + + //! Set clipboard sequence number + /*! + Sets the sequence number to use in subsequent clipboard events. + */ + void setSequenceNumber(UInt32); + + //! Register a system hotkey + /*! + Registers a system-wide hotkey for key \p key with modifiers \p mask. + Returns an id used to unregister the hotkey. + */ + UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + void unregisterHotKey(UInt32 id); + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() may not be + nested. + */ + void fakeInputBegin(); + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //! Change dragging status + void setDraggingStarted(bool started); + + //! Fake a files dragging operation + void startDraggingFiles(DragFileList& fileList); + + void setEnableDragDrop(bool enabled); + //@} + //! @name accessors + //@{ + + //! Test if cursor on screen + /*! + Returns true iff the cursor is on the screen. + */ + bool isOnScreen() const; + + //! Get screen lock state + /*! + Returns true if there's any reason that the user should not be + allowed to leave the screen (usually because a button or key is + pressed). If this method returns true it logs a message as to + why at the CLOG_DEBUG level. + */ + bool isLockedToScreen() const; + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + SInt32 getJumpZoneSize() const; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + void getCursorCenter(SInt32& x, SInt32& y) const; + + //! Get the active modifiers + /*! + Returns the modifiers that are currently active according to our + shadowed state. + */ + KeyModifierMask getActiveModifiers() const; + + //! Get the active modifiers from OS + /*! + Returns the modifiers that are currently active according to the + operating system. + */ + KeyModifierMask pollActiveModifiers() const; + + //! Test if file is dragged on primary screen + bool isDraggingStarted() const; + + //! Test if file is dragged on secondary screen + bool isFakeDraggingStarted() const; + + //! Get the filename of the file being dragged + String& getDraggingFilename() const; + + //! Clear the filename of the file that was dragged + void clearDraggingFilename(); + + //! Get the drop target directory + const String& getDropTarget() const; + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + IPlatformScreen* getPlatformScreen() { return m_screen; } + +protected: + void enablePrimary(); + void enableSecondary(); + void disablePrimary(); + void disableSecondary(); + + void enterPrimary(); + void enterSecondary(KeyModifierMask toggleMask); + void leavePrimary(); + void leaveSecondary(); + +private: + // our platform dependent screen + IPlatformScreen* m_screen; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if screen is enabled + bool m_enabled; + + // true if the cursor is on this screen + bool m_entered; + + // true if screen saver should be synchronized to server + bool m_screenSaverSync; + + // note toggle keys that toggles on up/down (false) or on + // transition (true) + KeyModifierMask m_halfDuplex; + + // true if we're faking input on a primary screen + bool m_fakeInput; + + IEventQueue* m_events; + + bool m_mock; + bool m_enableDragDrop; +}; + +} diff --git a/src/lib/barrier/ServerApp.cpp b/src/lib/barrier/ServerApp.cpp new file mode 100644 index 0000000..112f290 --- /dev/null +++ b/src/lib/barrier/ServerApp.cpp @@ -0,0 +1,859 @@ +/* + * 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 "barrier/ServerApp.h" + +#include "server/Server.h" +#include "server/ClientListener.h" +#include "server/ClientProxy.h" +#include "server/PrimaryClient.h" +#include "barrier/ArgParser.h" +#include "barrier/Screen.h" +#include "barrier/XScreen.h" +#include "barrier/ServerTaskBarReceiver.h" +#include "barrier/ServerArgs.h" +#include "net/SocketMultiplexer.h" +#include "net/TCPSocketFactory.h" +#include "net/XSocket.h" +#include "arch/Arch.h" +#include "base/EventQueue.h" +#include "base/log_outputters.h" +#include "base/FunctionEventJob.h" +#include "base/TMethodJob.h" +#include "base/IEventQueue.h" +#include "base/Log.h" +#include "base/TMethodEventJob.h" +#include "common/Version.h" + +#if SYSAPI_WIN32 +#include "arch/win32/ArchMiscWindows.h" +#endif + +#if WINAPI_MSWINDOWS +#include "platform/MSWindowsScreen.h" +#elif WINAPI_XWINDOWS +#include "platform/XWindowsScreen.h" +#elif WINAPI_CARBON +#include "platform/OSXScreen.h" +#endif + +#if defined(__APPLE__) +#include "platform/OSXDragSimulator.h" +#endif + +#include <iostream> +#include <stdio.h> +#include <fstream> +#include <sstream> + +// +// ServerApp +// + +ServerApp::ServerApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver) : + App(events, createTaskBarReceiver, new ServerArgs()), + m_server(NULL), + m_serverState(kUninitialized), + m_serverScreen(NULL), + m_primaryClient(NULL), + m_listener(NULL), + m_timer(NULL), + m_barrierAddress(NULL) +{ +} + +ServerApp::~ServerApp() +{ +} + +void +ServerApp::parseArgs(int argc, const char* const* argv) +{ + ArgParser argParser(this); + bool result = argParser.parseServerArgs(args(), argc, argv); + + if (!result || args().m_shouldExit) { + m_bye(kExitArgs); + } + else { + if (!args().m_barrierAddress.empty()) { + try { + *m_barrierAddress = NetworkAddress(args().m_barrierAddress, + kDefaultPort); + m_barrierAddress->resolve(); + } + catch (XSocketAddress& e) { + LOG((CLOG_PRINT "%s: %s" BYE, + args().m_pname, e.what(), args().m_pname)); + m_bye(kExitArgs); + } + } + } +} + +void +ServerApp::help() +{ + // window api args (windows/x-windows/carbon) +#if WINAPI_XWINDOWS +# define WINAPI_ARGS \ + " [--display <display>] [--no-xinitthreads]" +# define WINAPI_INFO \ + " --display <display> connect to the X server at <display>\n" \ + " --no-xinitthreads do not call XInitThreads()\n" +#else +# define WINAPI_ARGS "" +# define WINAPI_INFO "" +#endif + + std::ostringstream buffer; + buffer << "Start the barrier server component." << std::endl + << std::endl + << "Usage: " << args().m_pname + << " [--address <address>]" + << " [--config <pathname>]" + << WINAPI_ARGS << HELP_SYS_ARGS << HELP_COMMON_ARGS << std::endl + << std::endl + << "Options:" << std::endl + << " -a, --address <address> listen for clients on the given address." << std::endl + << " -c, --config <pathname> use the named configuration file instead." << std::endl + << HELP_COMMON_INFO_1 << WINAPI_INFO << HELP_SYS_INFO << HELP_COMMON_INFO_2 << std::endl + << "Default options are marked with a *" << std::endl + << std::endl + << "The argument for --address is of the form: [<hostname>][:<port>]. The" << std::endl + << "hostname must be the address or hostname of an interface on the system." << std::endl + << "The default is to listen on all interfaces. The port overrides the" << std::endl + << "default port, " << kDefaultPort << "." << std::endl + << std::endl + << "If no configuration file pathname is provided then the first of the" << std::endl + << "following to load successfully sets the configuration:" << std::endl + << " $HOME/" << USR_CONFIG_NAME << std::endl + << " " << ARCH->concatPath(ARCH->getSystemDirectory(), SYS_CONFIG_NAME) << std::endl; + + LOG((CLOG_PRINT "%s", buffer.str().c_str())); +} + +void +ServerApp::reloadSignalHandler(Arch::ESignal, void*) +{ + IEventQueue* events = App::instance().getEvents(); + events->addEvent(Event(events->forServerApp().reloadConfig(), + events->getSystemTarget())); +} + +void +ServerApp::reloadConfig(const Event&, void*) +{ + LOG((CLOG_DEBUG "reload configuration")); + if (loadConfig(args().m_configFile)) { + if (m_server != NULL) { + m_server->setConfig(*args().m_config); + } + LOG((CLOG_NOTE "reloaded configuration")); + } +} + +void +ServerApp::loadConfig() +{ + bool loaded = false; + + // load the config file, if specified + if (!args().m_configFile.empty()) { + loaded = loadConfig(args().m_configFile); + } + + // load the default configuration if no explicit file given + else { + // get the user's home directory + String path = ARCH->getUserDirectory(); + if (!path.empty()) { + // complete path + path = ARCH->concatPath(path, USR_CONFIG_NAME); + + // now try loading the user's configuration + if (loadConfig(path)) { + loaded = true; + args().m_configFile = path; + } + } + if (!loaded) { + // try the system-wide config file + path = ARCH->getSystemDirectory(); + if (!path.empty()) { + path = ARCH->concatPath(path, SYS_CONFIG_NAME); + if (loadConfig(path)) { + loaded = true; + args().m_configFile = path; + } + } + } + } + + if (!loaded) { + LOG((CLOG_PRINT "%s: no configuration available", args().m_pname)); + m_bye(kExitConfig); + } +} + +bool +ServerApp::loadConfig(const String& pathname) +{ + try { + // load configuration + LOG((CLOG_DEBUG "opening configuration \"%s\"", pathname.c_str())); + std::ifstream configStream(pathname.c_str()); + if (!configStream.is_open()) { + // report failure to open configuration as a debug message + // since we try several paths and we expect some to be + // missing. + LOG((CLOG_DEBUG "cannot open configuration \"%s\"", + pathname.c_str())); + return false; + } + configStream >> *args().m_config; + LOG((CLOG_DEBUG "configuration read successfully")); + return true; + } + catch (XConfigRead& e) { + // report error in configuration file + LOG((CLOG_ERR "cannot read configuration \"%s\": %s", + pathname.c_str(), e.what())); + } + return false; +} + +void +ServerApp::forceReconnect(const Event&, void*) +{ + if (m_server != NULL) { + m_server->disconnect(); + } +} + +void +ServerApp::handleClientConnected(const Event&, void* vlistener) +{ + ClientListener* listener = static_cast<ClientListener*>(vlistener); + ClientProxy* client = listener->getNextClient(); + if (client != NULL) { + m_server->adoptClient(client); + updateStatus(); + } +} + +void +ServerApp::handleClientsDisconnected(const Event&, void*) +{ + m_events->addEvent(Event(Event::kQuit)); +} + +void +ServerApp::closeServer(Server* server) +{ + if (server == NULL) { + return; + } + + // tell all clients to disconnect + server->disconnect(); + + // wait for clients to disconnect for up to timeout seconds + double timeout = 3.0; + EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL); + m_events->adoptHandler(Event::kTimer, timer, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleClientsDisconnected)); + m_events->adoptHandler(m_events->forServer().disconnected(), server, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleClientsDisconnected)); + + m_events->loop(); + + m_events->removeHandler(Event::kTimer, timer); + m_events->deleteTimer(timer); + m_events->removeHandler(m_events->forServer().disconnected(), server); + + // done with server + delete server; +} + +void +ServerApp::stopRetryTimer() +{ + if (m_timer != NULL) { + m_events->deleteTimer(m_timer); + m_events->removeHandler(Event::kTimer, NULL); + m_timer = NULL; + } +} + +void +ServerApp::updateStatus() +{ + updateStatus(""); +} + +void ServerApp::updateStatus(const String& msg) +{ + if (m_taskBarReceiver) + { + m_taskBarReceiver->updateStatus(m_server, msg); + } +} + +void +ServerApp::closeClientListener(ClientListener* listen) +{ + if (listen != NULL) { + m_events->removeHandler(m_events->forClientListener().connected(), listen); + delete listen; + } +} + +void +ServerApp::stopServer() +{ + if (m_serverState == kStarted) { + closeServer(m_server); + closeClientListener(m_listener); + m_server = NULL; + m_listener = NULL; + m_serverState = kInitialized; + } + else if (m_serverState == kStarting) { + stopRetryTimer(); + m_serverState = kInitialized; + } + assert(m_server == NULL); + assert(m_listener == NULL); +} + +void +ServerApp::closePrimaryClient(PrimaryClient* primaryClient) +{ + delete primaryClient; +} + +void +ServerApp::closeServerScreen(barrier::Screen* screen) +{ + if (screen != NULL) { + m_events->removeHandler(m_events->forIScreen().error(), + screen->getEventTarget()); + m_events->removeHandler(m_events->forIScreen().suspend(), + screen->getEventTarget()); + m_events->removeHandler(m_events->forIScreen().resume(), + screen->getEventTarget()); + delete screen; + } +} + +void ServerApp::cleanupServer() +{ + stopServer(); + if (m_serverState == kInitialized) { + closePrimaryClient(m_primaryClient); + closeServerScreen(m_serverScreen); + m_primaryClient = NULL; + m_serverScreen = NULL; + m_serverState = kUninitialized; + } + else if (m_serverState == kInitializing || + m_serverState == kInitializingToStart) { + stopRetryTimer(); + m_serverState = kUninitialized; + } + assert(m_primaryClient == NULL); + assert(m_serverScreen == NULL); + assert(m_serverState == kUninitialized); +} + +void +ServerApp::retryHandler(const Event&, void*) +{ + // discard old timer + assert(m_timer != NULL); + stopRetryTimer(); + + // try initializing/starting the server again + switch (m_serverState) { + case kUninitialized: + case kInitialized: + case kStarted: + assert(0 && "bad internal server state"); + break; + + case kInitializing: + LOG((CLOG_DEBUG1 "retry server initialization")); + m_serverState = kUninitialized; + if (!initServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + break; + + case kInitializingToStart: + LOG((CLOG_DEBUG1 "retry server initialization")); + m_serverState = kUninitialized; + if (!initServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + else if (m_serverState == kInitialized) { + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + } + break; + + case kStarting: + LOG((CLOG_DEBUG1 "retry starting server")); + m_serverState = kInitialized; + if (!startServer()) { + m_events->addEvent(Event(Event::kQuit)); + } + break; + } +} + +bool ServerApp::initServer() +{ + // skip if already initialized or initializing + if (m_serverState != kUninitialized) { + return true; + } + + double retryTime; + barrier::Screen* serverScreen = NULL; + PrimaryClient* primaryClient = NULL; + try { + String name = args().m_config->getCanonicalName(args().m_name); + serverScreen = openServerScreen(); + primaryClient = openPrimaryClient(name, serverScreen); + m_serverScreen = serverScreen; + m_primaryClient = primaryClient; + m_serverState = kInitialized; + updateStatus(); + return true; + } + catch (XScreenUnavailable& e) { + LOG((CLOG_WARN "primary screen unavailable: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + updateStatus(String("primary screen unavailable: ") + e.what()); + retryTime = e.getRetryTime(); + } + catch (XScreenOpenFailure& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closePrimaryClient(primaryClient); + closeServerScreen(serverScreen); + return false; + } + + if (args().m_restartable) { + // install a timer and handler to retry later + assert(m_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + m_timer = m_events->newOneShotTimer(retryTime, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<ServerApp>(this, &ServerApp::retryHandler)); + m_serverState = kInitializing; + return true; + } + else { + // don't try again + return false; + } +} + +barrier::Screen* +ServerApp::openServerScreen() +{ + barrier::Screen* screen = createScreen(); + screen->setEnableDragDrop(argsBase().m_enableDragDrop); + m_events->adoptHandler(m_events->forIScreen().error(), + screen->getEventTarget(), + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleScreenError)); + m_events->adoptHandler(m_events->forIScreen().suspend(), + screen->getEventTarget(), + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleSuspend)); + m_events->adoptHandler(m_events->forIScreen().resume(), + screen->getEventTarget(), + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleResume)); + return screen; +} + +bool +ServerApp::startServer() +{ + // skip if already started or starting + if (m_serverState == kStarting || m_serverState == kStarted) { + return true; + } + + // initialize if necessary + if (m_serverState != kInitialized) { + if (!initServer()) { + // hard initialization failure + return false; + } + if (m_serverState == kInitializing) { + // not ready to start + m_serverState = kInitializingToStart; + return true; + } + assert(m_serverState == kInitialized); + } + + double retryTime; + ClientListener* listener = NULL; + try { + listener = openClientListener(args().m_config->getBarrierAddress()); + m_server = openServer(*args().m_config, m_primaryClient); + listener->setServer(m_server); + m_server->setListener(listener); + m_listener = listener; + updateStatus(); + LOG((CLOG_NOTE "started server, waiting for clients")); + m_serverState = kStarted; + return true; + } + catch (XSocketAddressInUse& e) { + LOG((CLOG_WARN "cannot listen for clients: %s", e.what())); + closeClientListener(listener); + updateStatus(String("cannot listen for clients: ") + e.what()); + retryTime = 10.0; + } + catch (XBase& e) { + LOG((CLOG_CRIT "failed to start server: %s", e.what())); + closeClientListener(listener); + return false; + } + + if (args().m_restartable) { + // install a timer and handler to retry later + assert(m_timer == NULL); + LOG((CLOG_DEBUG "retry in %.0f seconds", retryTime)); + m_timer = m_events->newOneShotTimer(retryTime, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<ServerApp>(this, &ServerApp::retryHandler)); + m_serverState = kStarting; + return true; + } + else { + // don't try again + return false; + } +} + +barrier::Screen* +ServerApp::createScreen() +{ +#if WINAPI_MSWINDOWS + return new barrier::Screen(new MSWindowsScreen( + true, args().m_noHooks, args().m_stopOnDeskSwitch, m_events), m_events); +#elif WINAPI_XWINDOWS + return new barrier::Screen(new XWindowsScreen( + args().m_display, true, args().m_disableXInitThreads, 0, m_events), m_events); +#elif WINAPI_CARBON + return new barrier::Screen(new OSXScreen(m_events, true), m_events); +#endif +} + +PrimaryClient* +ServerApp::openPrimaryClient(const String& name, barrier::Screen* screen) +{ + LOG((CLOG_DEBUG1 "creating primary screen")); + return new PrimaryClient(name, screen); + +} + +void +ServerApp::handleScreenError(const Event&, void*) +{ + LOG((CLOG_CRIT "error on screen")); + m_events->addEvent(Event(Event::kQuit)); +} + +void +ServerApp::handleSuspend(const Event&, void*) +{ + if (!m_suspended) { + LOG((CLOG_INFO "suspend")); + stopServer(); + m_suspended = true; + } +} + +void +ServerApp::handleResume(const Event&, void*) +{ + if (m_suspended) { + LOG((CLOG_INFO "resume")); + startServer(); + m_suspended = false; + } +} + +ClientListener* +ServerApp::openClientListener(const NetworkAddress& address) +{ + ClientListener* listen = new ClientListener( + address, + new TCPSocketFactory(m_events, getSocketMultiplexer()), + m_events, + args().m_enableCrypto); + + m_events->adoptHandler( + m_events->forClientListener().connected(), listen, + new TMethodEventJob<ServerApp>( + this, &ServerApp::handleClientConnected, listen)); + + return listen; +} + +Server* +ServerApp::openServer(Config& config, PrimaryClient* primaryClient) +{ + Server* server = new Server(config, primaryClient, m_serverScreen, m_events, args()); + try { + m_events->adoptHandler( + m_events->forServer().disconnected(), server, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleNoClients)); + + m_events->adoptHandler( + m_events->forServer().screenSwitched(), server, + new TMethodEventJob<ServerApp>(this, &ServerApp::handleScreenSwitched)); + + } catch (std::bad_alloc &ba) { + delete server; + throw ba; + } + + return server; +} + +void +ServerApp::handleNoClients(const Event&, void*) +{ + updateStatus(); +} + +void +ServerApp::handleScreenSwitched(const Event& e, void*) +{ +} + +int +ServerApp::mainLoop() +{ + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + SocketMultiplexer multiplexer; + setSocketMultiplexer(&multiplexer); + + // if configuration has no screens then add this system + // as the default + if (args().m_config->begin() == args().m_config->end()) { + args().m_config->addScreen(args().m_name); + } + + // set the contact address, if provided, in the config. + // otherwise, if the config doesn't have an address, use + // the default. + if (m_barrierAddress->isValid()) { + args().m_config->setBarrierAddress(*m_barrierAddress); + } + else if (!args().m_config->getBarrierAddress().isValid()) { + args().m_config->setBarrierAddress(NetworkAddress(kDefaultPort)); + } + + // canonicalize the primary screen name + String primaryName = args().m_config->getCanonicalName(args().m_name); + if (primaryName.empty()) { + LOG((CLOG_CRIT "unknown screen name `%s'", args().m_name.c_str())); + return kExitFailed; + } + + // start server, etc + appUtil().startNode(); + + // init ipc client after node start, since create a new screen wipes out + // the event queue (the screen ctors call adoptBuffer). + if (argsBase().m_enableIpc) { + initIpcClient(); + } + + // handle hangup signal by reloading the server's configuration + ARCH->setSignalHandler(Arch::kHANGUP, &reloadSignalHandler, NULL); + m_events->adoptHandler(m_events->forServerApp().reloadConfig(), + m_events->getSystemTarget(), + new TMethodEventJob<ServerApp>(this, &ServerApp::reloadConfig)); + + // handle force reconnect event by disconnecting clients. they'll + // reconnect automatically. + m_events->adoptHandler(m_events->forServerApp().forceReconnect(), + m_events->getSystemTarget(), + new TMethodEventJob<ServerApp>(this, &ServerApp::forceReconnect)); + + // to work around the sticky meta keys problem, we'll give users + // the option to reset the state of barriers + m_events->adoptHandler(m_events->forServerApp().resetServer(), + m_events->getSystemTarget(), + new TMethodEventJob<ServerApp>(this, &ServerApp::resetServer)); + + // run event loop. if startServer() failed we're supposed to retry + // later. the timer installed by startServer() will take care of + // that. + DAEMON_RUNNING(true); + +#if defined(MAC_OS_X_VERSION_10_7) + + Thread thread( + new TMethodJob<ServerApp>( + this, &ServerApp::runEventsLoop, + NULL)); + + // wait until carbon loop is ready + OSXScreen* screen = dynamic_cast<OSXScreen*>( + m_serverScreen->getPlatformScreen()); + screen->waitForCarbonLoop(); + + runCocoaApp(); +#else + m_events->loop(); +#endif + + DAEMON_RUNNING(false); + + // close down + LOG((CLOG_DEBUG1 "stopping server")); + m_events->removeHandler(m_events->forServerApp().forceReconnect(), + m_events->getSystemTarget()); + m_events->removeHandler(m_events->forServerApp().reloadConfig(), + m_events->getSystemTarget()); + cleanupServer(); + updateStatus(); + LOG((CLOG_NOTE "stopped server")); + + if (argsBase().m_enableIpc) { + cleanupIpcClient(); + } + + return kExitSuccess; +} + +void ServerApp::resetServer(const Event&, void*) +{ + LOG((CLOG_DEBUG1 "resetting server")); + stopServer(); + cleanupServer(); + startServer(); +} + +int +ServerApp::runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup) +{ + // general initialization + m_barrierAddress = new NetworkAddress; + args().m_config = new Config(m_events); + args().m_pname = ARCH->getBasename(argv[0]); + + // install caller's output filter + if (outputter != NULL) { + CLOG->insert(outputter); + } + + // run + int result = startup(argc, argv); + + if (m_taskBarReceiver) + { + // done with task bar receiver + delete m_taskBarReceiver; + } + + delete args().m_config; + delete m_barrierAddress; + return result; +} + +int daemonMainLoopStatic(int argc, const char** argv) { + return ServerApp::instance().daemonMainLoop(argc, argv); +} + +int +ServerApp::standardStartup(int argc, char** argv) +{ + initApp(argc, argv); + + // daemonize if requested + if (args().m_daemon) { + return ARCH->daemonize(daemonName(), daemonMainLoopStatic); + } + else { + return mainLoop(); + } +} + +int +ServerApp::foregroundStartup(int argc, char** argv) +{ + initApp(argc, argv); + + // never daemonize + return mainLoop(); +} + +const char* +ServerApp::daemonName() const +{ +#if SYSAPI_WIN32 + return "Barrier Server"; +#elif SYSAPI_UNIX + return "barriers"; +#endif +} + +const char* +ServerApp::daemonInfo() const +{ +#if SYSAPI_WIN32 + return "Shares this computers mouse and keyboard with other computers."; +#elif SYSAPI_UNIX + return ""; +#endif +} + +void +ServerApp::startNode() +{ + // start the server. if this return false then we've failed and + // we shouldn't retry. + LOG((CLOG_DEBUG1 "starting server")); + if (!startServer()) { + m_bye(kExitFailed); + } +} diff --git a/src/lib/barrier/ServerApp.h b/src/lib/barrier/ServerApp.h new file mode 100644 index 0000000..528aa24 --- /dev/null +++ b/src/lib/barrier/ServerApp.h @@ -0,0 +1,127 @@ +/* + * 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 "barrier/ArgsBase.h" +#include "barrier/App.h" +#include "base/String.h" +#include "server/Config.h" +#include "net/NetworkAddress.h" +#include "arch/Arch.h" +#include "arch/IArchMultithread.h" +#include "barrier/ArgsBase.h" +#include "base/EventTypes.h" + +#include <map> + +enum EServerState { + kUninitialized, + kInitializing, + kInitializingToStart, + kInitialized, + kStarting, + kStarted +}; + +class Server; +namespace barrier { class Screen; } +class ClientListener; +class EventQueueTimer; +class ILogOutputter; +class IEventQueue; +class ServerArgs; + +class ServerApp : public App { +public: + ServerApp(IEventQueue* events, CreateTaskBarReceiverFunc createTaskBarReceiver); + virtual ~ServerApp(); + + // Parse server specific command line arguments. + void parseArgs(int argc, const char* const* argv); + + // Prints help specific to server. + void help(); + + // Returns arguments that are common and for server. + ServerArgs& args() const { return (ServerArgs&)argsBase(); } + + const char* daemonName() const; + const char* daemonInfo() const; + + // TODO: Document these functions. + static void reloadSignalHandler(Arch::ESignal, void*); + + void reloadConfig(const Event&, void*); + void loadConfig(); + bool loadConfig(const String& pathname); + void forceReconnect(const Event&, void*); + void resetServer(const Event&, void*); + void handleClientConnected(const Event&, void* vlistener); + void handleClientsDisconnected(const Event&, void*); + void closeServer(Server* server); + void stopRetryTimer(); + void updateStatus(); + void updateStatus(const String& msg); + void closeClientListener(ClientListener* listen); + void stopServer(); + void closePrimaryClient(PrimaryClient* primaryClient); + void closeServerScreen(barrier::Screen* screen); + void cleanupServer(); + bool initServer(); + void retryHandler(const Event&, void*); + barrier::Screen* openServerScreen(); + barrier::Screen* createScreen(); + PrimaryClient* openPrimaryClient(const String& name, barrier::Screen* screen); + void handleScreenError(const Event&, void*); + void handleSuspend(const Event&, void*); + void handleResume(const Event&, void*); + ClientListener* openClientListener(const NetworkAddress& address); + Server* openServer(Config& config, PrimaryClient* primaryClient); + void handleNoClients(const Event&, void*); + bool startServer(); + int mainLoop(); + int runInner(int argc, char** argv, ILogOutputter* outputter, StartupFunc startup); + int standardStartup(int argc, char** argv); + int foregroundStartup(int argc, char** argv); + void startNode(); + + static ServerApp& instance() { return (ServerApp&)App::instance(); } + + Server* getServerPtr() { return m_server; } + + Server* m_server; + EServerState m_serverState; + barrier::Screen* m_serverScreen; + PrimaryClient* m_primaryClient; + ClientListener* m_listener; + EventQueueTimer* m_timer; + NetworkAddress* m_barrierAddress; + +private: + void handleScreenSwitched(const Event&, void* data); +}; + +// configuration file name +#if SYSAPI_WIN32 +#define USR_CONFIG_NAME "barrier.sgc" +#define SYS_CONFIG_NAME "barrier.sgc" +#elif SYSAPI_UNIX +#define USR_CONFIG_NAME ".barrier.conf" +#define SYS_CONFIG_NAME "barrier.conf" +#endif diff --git a/src/lib/barrier/ServerArgs.cpp b/src/lib/barrier/ServerArgs.cpp new file mode 100644 index 0000000..49832f2 --- /dev/null +++ b/src/lib/barrier/ServerArgs.cpp @@ -0,0 +1,25 @@ +/* + * 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 "barrier/ServerArgs.h" + +ServerArgs::ServerArgs() : + m_configFile(), + m_config(NULL) +{ +} + diff --git a/src/lib/barrier/ServerArgs.h b/src/lib/barrier/ServerArgs.h new file mode 100644 index 0000000..9c6e568 --- /dev/null +++ b/src/lib/barrier/ServerArgs.h @@ -0,0 +1,32 @@ +/* + * 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 + +#include "barrier/ArgsBase.h" + +class NetworkAddress; +class Config; + +class ServerArgs : public ArgsBase { +public: + ServerArgs(); + +public: + String m_configFile; + Config* m_config; +}; diff --git a/src/lib/barrier/ServerTaskBarReceiver.cpp b/src/lib/barrier/ServerTaskBarReceiver.cpp new file mode 100644 index 0000000..b427cd1 --- /dev/null +++ b/src/lib/barrier/ServerTaskBarReceiver.cpp @@ -0,0 +1,138 @@ +/* + * 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 "barrier/ServerTaskBarReceiver.h" +#include "server/Server.h" +#include "mt/Lock.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "arch/Arch.h" +#include "common/Version.h" + +// +// ServerTaskBarReceiver +// + +ServerTaskBarReceiver::ServerTaskBarReceiver(IEventQueue* events) : + m_state(kNotRunning), + m_events(events) +{ + // do nothing +} + +ServerTaskBarReceiver::~ServerTaskBarReceiver() +{ + // do nothing +} + +void +ServerTaskBarReceiver::updateStatus(Server* server, const String& errorMsg) +{ + { + // update our status + m_errorMessage = errorMsg; + if (server == NULL) { + if (m_errorMessage.empty()) { + m_state = kNotRunning; + } + else { + m_state = kNotWorking; + } + } + else { + m_clients.clear(); + server->getClients(m_clients); + if (m_clients.size() <= 1) { + m_state = kNotConnected; + } + else { + m_state = kConnected; + } + } + + // let subclasses have a go + onStatusChanged(server); + } + + // tell task bar + ARCH->updateReceiver(this); +} + +ServerTaskBarReceiver::EState +ServerTaskBarReceiver::getStatus() const +{ + return m_state; +} + +const String& +ServerTaskBarReceiver::getErrorMessage() const +{ + return m_errorMessage; +} + +const ServerTaskBarReceiver::Clients& +ServerTaskBarReceiver::getClients() const +{ + return m_clients; +} + +void +ServerTaskBarReceiver::quit() +{ + m_events->addEvent(Event(Event::kQuit)); +} + +void +ServerTaskBarReceiver::onStatusChanged(Server*) +{ + // do nothing +} + +void +ServerTaskBarReceiver::lock() const +{ + // do nothing +} + +void +ServerTaskBarReceiver::unlock() const +{ + // do nothing +} + +std::string +ServerTaskBarReceiver::getToolTip() const +{ + switch (m_state) { + case kNotRunning: + return barrier::string::sprintf("%s: Not running", kAppVersion); + + case kNotWorking: + return barrier::string::sprintf("%s: %s", + kAppVersion, m_errorMessage.c_str()); + + case kNotConnected: + return barrier::string::sprintf("%s: Waiting for clients", kAppVersion); + + case kConnected: + return barrier::string::sprintf("%s: Connected", kAppVersion); + + default: + return ""; + } +} diff --git a/src/lib/barrier/ServerTaskBarReceiver.h b/src/lib/barrier/ServerTaskBarReceiver.h new file mode 100644 index 0000000..3cef9c0 --- /dev/null +++ b/src/lib/barrier/ServerTaskBarReceiver.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 "server/Server.h" +#include "barrier/ServerApp.h" +#include "arch/IArchTaskBarReceiver.h" +#include "base/EventTypes.h" +#include "base/String.h" +#include "base/Event.h" +#include "common/stdvector.h" + +class IEventQueue; + +//! Implementation of IArchTaskBarReceiver for the barrier server +class ServerTaskBarReceiver : public IArchTaskBarReceiver { +public: + ServerTaskBarReceiver(IEventQueue* events); + virtual ~ServerTaskBarReceiver(); + + //! @name manipulators + //@{ + + //! Update status + /*! + Determine the status and query required information from the server. + */ + void updateStatus(Server*, const String& errorMsg); + + void updateStatus(INode* n, const String& errorMsg) { updateStatus((Server*)n, errorMsg); } + + //@} + + // IArchTaskBarReceiver overrides + virtual void showStatus() = 0; + virtual void runMenu(int x, int y) = 0; + virtual void primaryAction() = 0; + virtual void lock() const; + virtual void unlock() const; + virtual const Icon getIcon() const = 0; + virtual std::string getToolTip() const; + +protected: + typedef std::vector<String> Clients; + enum EState { + kNotRunning, + kNotWorking, + kNotConnected, + kConnected, + kMaxState + }; + + //! Get status + EState getStatus() const; + + //! Get error message + const String& getErrorMessage() const; + + //! Get connected clients + const Clients& getClients() const; + + //! Quit app + /*! + Causes the application to quit gracefully + */ + void quit(); + + //! Status change notification + /*! + Called when status changes. The default implementation does + nothing. + */ + virtual void onStatusChanged(Server* server); + +private: + EState m_state; + String m_errorMessage; + Clients m_clients; + IEventQueue* m_events; +}; + +IArchTaskBarReceiver* createTaskBarReceiver(const BufferedLogOutputter* logBuffer, IEventQueue* events); diff --git a/src/lib/barrier/StreamChunker.cpp b/src/lib/barrier/StreamChunker.cpp new file mode 100644 index 0000000..8b8971c --- /dev/null +++ b/src/lib/barrier/StreamChunker.cpp @@ -0,0 +1,166 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 "barrier/StreamChunker.h" + +#include "mt/Lock.h" +#include "mt/Mutex.h" +#include "barrier/FileChunk.h" +#include "barrier/ClipboardChunk.h" +#include "barrier/protocol_types.h" +#include "base/EventTypes.h" +#include "base/Event.h" +#include "base/IEventQueue.h" +#include "base/EventTypes.h" +#include "base/Log.h" +#include "base/Stopwatch.h" +#include "base/String.h" +#include "common/stdexcept.h" + +#include <fstream> + +using namespace std; + +static const size_t g_chunkSize = 32 * 1024; //32kb + +bool StreamChunker::s_isChunkingFile = false; +bool StreamChunker::s_interruptFile = false; +Mutex* StreamChunker::s_interruptMutex = NULL; + +void +StreamChunker::sendFile( + char* filename, + IEventQueue* events, + void* eventTarget) +{ + s_isChunkingFile = true; + + std::fstream file(static_cast<char*>(filename), std::ios::in | std::ios::binary); + + if (!file.is_open()) { + throw runtime_error("failed to open file"); + } + + // check file size + file.seekg (0, std::ios::end); + size_t size = (size_t)file.tellg(); + + // send first message (file size) + String fileSize = barrier::string::sizeTypeToString(size); + FileChunk* sizeMessage = FileChunk::start(fileSize); + + events->addEvent(Event(events->forFile().fileChunkSending(), eventTarget, sizeMessage)); + + // send chunk messages with a fixed chunk size + size_t sentLength = 0; + size_t chunkSize = g_chunkSize; + file.seekg (0, std::ios::beg); + + while (true) { + if (s_interruptFile) { + s_interruptFile = false; + LOG((CLOG_DEBUG "file transmission interrupted")); + break; + } + + events->addEvent(Event(events->forFile().keepAlive(), eventTarget)); + + // make sure we don't read too much from the mock data. + if (sentLength + chunkSize > size) { + chunkSize = size - sentLength; + } + + char* chunkData = new char[chunkSize]; + file.read(chunkData, chunkSize); + UInt8* data = reinterpret_cast<UInt8*>(chunkData); + FileChunk* fileChunk = FileChunk::data(data, chunkSize); + delete[] chunkData; + + events->addEvent(Event(events->forFile().fileChunkSending(), eventTarget, fileChunk)); + + sentLength += chunkSize; + file.seekg (sentLength, std::ios::beg); + + if (sentLength == size) { + break; + } + } + + // send last message + FileChunk* end = FileChunk::end(); + + events->addEvent(Event(events->forFile().fileChunkSending(), eventTarget, end)); + + file.close(); + + s_isChunkingFile = false; +} + +void +StreamChunker::sendClipboard( + String& data, + size_t size, + ClipboardID id, + UInt32 sequence, + IEventQueue* events, + void* eventTarget) +{ + // send first message (data size) + String dataSize = barrier::string::sizeTypeToString(size); + ClipboardChunk* sizeMessage = ClipboardChunk::start(id, sequence, dataSize); + + events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, sizeMessage)); + + // send clipboard chunk with a fixed size + size_t sentLength = 0; + size_t chunkSize = g_chunkSize; + + while (true) { + events->addEvent(Event(events->forFile().keepAlive(), eventTarget)); + + // make sure we don't read too much from the mock data. + if (sentLength + chunkSize > size) { + chunkSize = size - sentLength; + } + + String chunk(data.substr(sentLength, chunkSize).c_str(), chunkSize); + ClipboardChunk* dataChunk = ClipboardChunk::data(id, sequence, chunk); + + events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, dataChunk)); + + sentLength += chunkSize; + if (sentLength == size) { + break; + } + } + + // send last message + ClipboardChunk* end = ClipboardChunk::end(id, sequence); + + events->addEvent(Event(events->forClipboard().clipboardSending(), eventTarget, end)); + + LOG((CLOG_DEBUG "sent clipboard size=%d", sentLength)); +} + +void +StreamChunker::interruptFile() +{ + if (s_isChunkingFile) { + s_interruptFile = true; + LOG((CLOG_INFO "previous dragged file has become invalid")); + } +} diff --git a/src/lib/barrier/StreamChunker.h b/src/lib/barrier/StreamChunker.h new file mode 100644 index 0000000..ab57c7e --- /dev/null +++ b/src/lib/barrier/StreamChunker.h @@ -0,0 +1,45 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "barrier/clipboard_types.h" +#include "base/String.h" + +class IEventQueue; +class Mutex; + +class StreamChunker { +public: + static void sendFile( + char* filename, + IEventQueue* events, + void* eventTarget); + static void sendClipboard( + String& data, + size_t size, + ClipboardID id, + UInt32 sequence, + IEventQueue* events, + void* eventTarget); + static void interruptFile(); + +private: + static bool s_isChunkingFile; + static bool s_interruptFile; + static Mutex* s_interruptMutex; +}; diff --git a/src/lib/barrier/ToolApp.cpp b/src/lib/barrier/ToolApp.cpp new file mode 100644 index 0000000..ae85e6d --- /dev/null +++ b/src/lib/barrier/ToolApp.cpp @@ -0,0 +1,205 @@ +/* + * 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 "barrier/ToolApp.h" + +#include "barrier/ArgParser.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/String.h" + +#include <iostream> +#include <sstream> + +#if SYSAPI_WIN32 +#include "platform/MSWindowsSession.h" +#endif + +#define JSON_URL "https://symless.com/account/json/" + +enum { + kErrorOk, + kErrorArgs, + kErrorException, + kErrorUnknown +}; + +UInt32 +ToolApp::run(int argc, char** argv) +{ + if (argc <= 1) { + std::cerr << "no args" << std::endl; + return kErrorArgs; + } + + try { + ArgParser argParser(this); + bool result = argParser.parseToolArgs(m_args, argc, argv); + + if (!result) { + m_bye(kExitArgs); + } + + if (m_args.m_printActiveDesktopName) { +#if SYSAPI_WIN32 + MSWindowsSession session; + String name = session.getActiveDesktopName(); + if (name.empty()) { + LOG((CLOG_CRIT "failed to get active desktop name")); + return kExitFailed; + } + else { + String output = barrier::string::sprintf("activeDesktop:%s", name.c_str()); + LOG((CLOG_INFO "%s", output.c_str())); + } +#endif + } + else if (m_args.m_loginAuthenticate) { + loginAuth(); + } + else if (m_args.m_getInstalledDir) { + std::cout << ARCH->getInstalledDirectory() << std::endl; + } + else if (m_args.m_getProfileDir) { + std::cout << ARCH->getProfileDirectory() << std::endl; + } + else if (m_args.m_getArch) { + std::cout << ARCH->getPlatformName() << std::endl; + } + else if (m_args.m_notifyUpdate) { + notifyUpdate(); + } + else if (m_args.m_notifyActivation) { + notifyActivation(); + } + else { + throw XBarrier("Nothing to do"); + } + } + catch (std::exception& e) { + LOG((CLOG_CRIT "An error occurred: %s\n", e.what())); + return kExitFailed; + } + catch (...) { + LOG((CLOG_CRIT "An unknown error occurred.\n")); + return kExitFailed; + } + +#if WINAPI_XWINDOWS + // HACK: avoid sigsegv on linux + m_bye(kErrorOk); +#endif + + return kErrorOk; +} + +void +ToolApp::help() +{ +} + +void +ToolApp::loginAuth() +{ + String credentials; + std::cin >> credentials; + + std::vector<String> parts = barrier::string::splitString(credentials, ':'); + size_t count = parts.size(); + + if (count == 2 ) { + String email = parts[0]; + String password = parts[1]; + + std::stringstream ss; + ss << JSON_URL << "auth/"; + ss << "?email=" << ARCH->internet().urlEncode(email); + ss << "&password=" << password; + + std::cout << ARCH->internet().get(ss.str()) << std::endl; + } + else { + throw XBarrier("Invalid credentials."); + } +} + +void +ToolApp::notifyUpdate() +{ + String data; + std::cin >> data; + + std::vector<String> parts = barrier::string::splitString(data, ':'); + size_t count = parts.size(); + + if (count == 3) { + std::stringstream ss; + ss << JSON_URL << "notify/update"; + ss << "?from=" << parts[0]; + ss << "&to=" << parts[1]; + + std::cout << ARCH->internet().get(ss.str()) << std::endl; + } + else { + throw XBarrier("Invalid update data."); + } +} + +void +ToolApp::notifyActivation() +{ + String info; + std::cin >> info; + + std::vector<String> parts = barrier::string::splitString(info, ':'); + size_t count = parts.size(); + + if (count == 3 || count == 4) { + String action = parts[0]; + String identity = parts[1]; + String macHash = parts[2]; + String os; + + if (count == 4) { + os = parts[3]; + } + else { + os = ARCH->getOSName(); + } + + std::stringstream ss; + ss << JSON_URL << "notify/"; + ss << "?action=" << action; + ss << "&identity=" << ARCH->internet().urlEncode(identity); + ss << "&mac=" << ARCH->internet().urlEncode(macHash); + ss << "&os=" << ARCH->internet().urlEncode(ARCH->getOSName()); + ss << "&arch=" << ARCH->internet().urlEncode(ARCH->getPlatformName()); + + try { + std::cout << ARCH->internet().get(ss.str()) << std::endl; + } + catch (std::exception& e) { + LOG((CLOG_NOTE "An error occurred during notification: %s\n", e.what())); + } + catch (...) { + LOG((CLOG_NOTE "An unknown error occurred during notification.\n")); + } + } + else { + LOG((CLOG_NOTE "notification failed")); + } +} diff --git a/src/lib/barrier/ToolApp.h b/src/lib/barrier/ToolApp.h new file mode 100644 index 0000000..5cb9a7c --- /dev/null +++ b/src/lib/barrier/ToolApp.h @@ -0,0 +1,37 @@ +/* + * 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 + +#include "barrier/App.h" +#include "barrier/ToolArgs.h" +#include "common/basic_types.h" + +class ToolApp : public MinimalApp +{ +public: + UInt32 run(int argc, char** argv); + void help(); + +private: + void loginAuth(); + void notifyActivation(); + void notifyUpdate(); + +private: + ToolArgs m_args; +}; diff --git a/src/lib/barrier/ToolArgs.cpp b/src/lib/barrier/ToolArgs.cpp new file mode 100644 index 0000000..634a784 --- /dev/null +++ b/src/lib/barrier/ToolArgs.cpp @@ -0,0 +1,29 @@ +/* + * 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 "barrier/ToolArgs.h" + +ToolArgs::ToolArgs() : + m_printActiveDesktopName(false), + m_loginAuthenticate(false), + m_getInstalledDir(false), + m_getProfileDir(false), + m_getArch(false), + m_notifyActivation(false), + m_notifyUpdate(false) +{ +} diff --git a/src/lib/barrier/ToolArgs.h b/src/lib/barrier/ToolArgs.h new file mode 100644 index 0000000..36b4be3 --- /dev/null +++ b/src/lib/barrier/ToolArgs.h @@ -0,0 +1,34 @@ +/* + * 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 + +#include "base/String.h" + +class ToolArgs { +public: + ToolArgs(); + +public: + bool m_printActiveDesktopName; + bool m_loginAuthenticate; + bool m_getInstalledDir; + bool m_getProfileDir; + bool m_getArch; + bool m_notifyActivation; + bool m_notifyUpdate; +}; diff --git a/src/lib/barrier/XBarrier.cpp b/src/lib/barrier/XBarrier.cpp new file mode 100644 index 0000000..49a015e --- /dev/null +++ b/src/lib/barrier/XBarrier.cpp @@ -0,0 +1,133 @@ +/* + * 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 "barrier/XBarrier.h" +#include "base/String.h" + +// +// XBadClient +// + +String +XBadClient::getWhat() const throw() +{ + return "XBadClient"; +} + + +// +// XIncompatibleClient +// + +XIncompatibleClient::XIncompatibleClient(int major, int minor) : + m_major(major), + m_minor(minor) +{ + // do nothing +} + +int +XIncompatibleClient::getMajor() const throw() +{ + return m_major; +} + +int +XIncompatibleClient::getMinor() const throw() +{ + return m_minor; +} + +String +XIncompatibleClient::getWhat() const throw() +{ + return format("XIncompatibleClient", "incompatible client %{1}.%{2}", + barrier::string::sprintf("%d", m_major).c_str(), + barrier::string::sprintf("%d", m_minor).c_str()); +} + + +// +// XDuplicateClient +// + +XDuplicateClient::XDuplicateClient(const String& name) : + m_name(name) +{ + // do nothing +} + +const String& +XDuplicateClient::getName() const throw() +{ + return m_name; +} + +String +XDuplicateClient::getWhat() const throw() +{ + return format("XDuplicateClient", "duplicate client %{1}", m_name.c_str()); +} + + +// +// XUnknownClient +// + +XUnknownClient::XUnknownClient(const String& name) : + m_name(name) +{ + // do nothing +} + +const String& +XUnknownClient::getName() const throw() +{ + return m_name; +} + +String +XUnknownClient::getWhat() const throw() +{ + return format("XUnknownClient", "unknown client %{1}", m_name.c_str()); +} + + +// +// XExitApp +// + +XExitApp::XExitApp(int code) : + m_code(code) +{ + // do nothing +} + +int +XExitApp::getCode() const throw() +{ + return m_code; +} + +String +XExitApp::getWhat() const throw() +{ + return format( + "XExitApp", "exiting with code %{1}", + barrier::string::sprintf("%d", m_code).c_str()); +} diff --git a/src/lib/barrier/XBarrier.h b/src/lib/barrier/XBarrier.h new file mode 100644 index 0000000..fdf5213 --- /dev/null +++ b/src/lib/barrier/XBarrier.h @@ -0,0 +1,135 @@ +/* + * 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 "base/XBase.h" + +//! Generic barrier exception +XBASE_SUBCLASS(XBarrier, XBase); + +//! Subscription error +/*! +Thrown when there is a problem with the subscription. +*/ +XBASE_SUBCLASS(XSubscription, XBarrier); + +//! Client error exception +/*! +Thrown when the client fails to follow the protocol. +*/ +XBASE_SUBCLASS_WHAT(XBadClient, XBarrier); + +//! Incompatible client exception +/*! +Thrown when a client attempting to connect has an incompatible version. +*/ +class XIncompatibleClient : public XBarrier { +public: + XIncompatibleClient(int major, int minor); + + //! @name accessors + //@{ + + //! Get client's major version number + int getMajor() const throw(); + //! Get client's minor version number + int getMinor() const throw(); + + //@} + +protected: + virtual String getWhat() const throw(); + +private: + int m_major; + int m_minor; +}; + +//! Client already connected exception +/*! +Thrown when a client attempting to connect is using the same name as +a client that is already connected. +*/ +class XDuplicateClient : public XBarrier { +public: + XDuplicateClient(const String& name); + virtual ~XDuplicateClient() _NOEXCEPT { } + + //! @name accessors + //@{ + + //! Get client's name + virtual const String& + getName() const throw(); + + //@} + +protected: + virtual String getWhat() const throw(); + +private: + String m_name; +}; + +//! Client not in map exception +/*! +Thrown when a client attempting to connect is using a name that is +unknown to the server. +*/ +class XUnknownClient : public XBarrier { +public: + XUnknownClient(const String& name); + virtual ~XUnknownClient() _NOEXCEPT { } + + //! @name accessors + //@{ + + //! Get the client's name + virtual const String& + getName() const throw(); + + //@} + +protected: + virtual String getWhat() const throw(); + +private: + String m_name; +}; + +//! Generic exit eception +/*! +Thrown when we want to abort, with the opportunity to clean up. This is a +little bit of a hack, but it's a better way of exiting, than just calling +exit(int). +*/ +class XExitApp : public XBarrier { +public: + XExitApp(int code); + virtual ~XExitApp() _NOEXCEPT { } + + //! Get the exit code + int getCode() const throw(); + +protected: + virtual String getWhat() const throw(); + +private: + int m_code; +}; diff --git a/src/lib/barrier/XScreen.cpp b/src/lib/barrier/XScreen.cpp new file mode 100644 index 0000000..a202240 --- /dev/null +++ b/src/lib/barrier/XScreen.cpp @@ -0,0 +1,68 @@ +/* + * 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 "barrier/XScreen.h" + +// +// XScreenOpenFailure +// + +String +XScreenOpenFailure::getWhat() const throw() +{ + return format("XScreenOpenFailure", "unable to open screen"); +} + + +// +// XScreenXInputFailure +// + +String +XScreenXInputFailure::getWhat() const throw() +{ + return ""; +} + + +// +// XScreenUnavailable +// + +XScreenUnavailable::XScreenUnavailable(double timeUntilRetry) : + m_timeUntilRetry(timeUntilRetry) +{ + // do nothing +} + +XScreenUnavailable::~XScreenUnavailable() _NOEXCEPT +{ + // do nothing +} + +double +XScreenUnavailable::getRetryTime() const +{ + return m_timeUntilRetry; +} + +String +XScreenUnavailable::getWhat() const throw() +{ + return format("XScreenUnavailable", "unable to open screen"); +} diff --git a/src/lib/barrier/XScreen.h b/src/lib/barrier/XScreen.h new file mode 100644 index 0000000..0cb511e --- /dev/null +++ b/src/lib/barrier/XScreen.h @@ -0,0 +1,68 @@ +/* + * 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 "base/XBase.h" + +//! Generic screen exception +XBASE_SUBCLASS(XScreen, XBase); + +//! Cannot open screen exception +/*! +Thrown when a screen cannot be opened or initialized. +*/ +XBASE_SUBCLASS_WHAT(XScreenOpenFailure, XScreen); + +//! XInput exception +/*! +Thrown when an XInput error occurs +*/ +XBASE_SUBCLASS_WHAT(XScreenXInputFailure, XScreen); + +//! Screen unavailable exception +/*! +Thrown when a screen cannot be opened or initialized but retrying later +may be successful. +*/ +class XScreenUnavailable : public XScreenOpenFailure { +public: + /*! + \c timeUntilRetry is the suggested time the caller should wait until + trying to open the screen again. + */ + XScreenUnavailable(double timeUntilRetry); + virtual ~XScreenUnavailable() _NOEXCEPT; + + //! @name manipulators + //@{ + + //! Get retry time + /*! + Returns the suggested time to wait until retrying to open the screen. + */ + double getRetryTime() const; + + //@} + +protected: + virtual String getWhat() const throw(); + +private: + double m_timeUntilRetry; +}; diff --git a/src/lib/barrier/clipboard_types.h b/src/lib/barrier/clipboard_types.h new file mode 100644 index 0000000..54f2732 --- /dev/null +++ b/src/lib/barrier/clipboard_types.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 "common/basic_types.h" + +//! Clipboard ID +/*! +Type to hold a clipboard identifier. +*/ +typedef UInt8 ClipboardID; + +//! @name Clipboard identifiers +//@{ +// clipboard identifiers. kClipboardClipboard is what is normally +// considered the clipboard (e.g. the cut/copy/paste menu items +// affect it). kClipboardSelection is the selection on those +// platforms that can treat the selection as a clipboard (e.g. X +// windows). clipboard identifiers must be sequential starting +// at zero. +static const ClipboardID kClipboardClipboard = 0; +static const ClipboardID kClipboardSelection = 1; + +// the number of clipboards (i.e. one greater than the last clipboard id) +static const ClipboardID kClipboardEnd = 2; +//@} diff --git a/src/lib/barrier/key_types.cpp b/src/lib/barrier/key_types.cpp new file mode 100644 index 0000000..902670d --- /dev/null +++ b/src/lib/barrier/key_types.cpp @@ -0,0 +1,208 @@ +/* + * 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 "barrier/key_types.h" + +const KeyNameMapEntry kKeyNameMap[] = { + { "AltGr", kKeyAltGr }, + { "Alt_L", kKeyAlt_L }, + { "Alt_R", kKeyAlt_R }, + { "AppMail", kKeyAppMail }, + { "AppMedia", kKeyAppMedia }, + { "AppUser1", kKeyAppUser1 }, + { "AppUser2", kKeyAppUser2 }, + { "AudioDown", kKeyAudioDown }, + { "AudioMute", kKeyAudioMute }, + { "AudioNext", kKeyAudioNext }, + { "AudioPlay", kKeyAudioPlay }, + { "AudioPrev", kKeyAudioPrev }, + { "AudioStop", kKeyAudioStop }, + { "AudioUp", kKeyAudioUp }, + { "BackSpace", kKeyBackSpace }, + { "Begin", kKeyBegin }, + { "Break", kKeyBreak }, + { "Cancel", kKeyCancel }, + { "CapsLock", kKeyCapsLock }, + { "Clear", kKeyClear }, + { "Control_L", kKeyControl_L }, + { "Control_R", kKeyControl_R }, + { "Delete", kKeyDelete }, + { "Down", kKeyDown }, + { "Eject", kKeyEject }, + { "End", kKeyEnd }, + { "Escape", kKeyEscape }, + { "Execute", kKeyExecute }, + { "F1", kKeyF1 }, + { "F2", kKeyF2 }, + { "F3", kKeyF3 }, + { "F4", kKeyF4 }, + { "F5", kKeyF5 }, + { "F6", kKeyF6 }, + { "F7", kKeyF7 }, + { "F8", kKeyF8 }, + { "F9", kKeyF9 }, + { "F10", kKeyF10 }, + { "F11", kKeyF11 }, + { "F12", kKeyF12 }, + { "F13", kKeyF13 }, + { "F14", kKeyF14 }, + { "F15", kKeyF15 }, + { "F16", kKeyF16 }, + { "F17", kKeyF17 }, + { "F18", kKeyF18 }, + { "F19", kKeyF19 }, + { "F20", kKeyF20 }, + { "F21", kKeyF21 }, + { "F22", kKeyF22 }, + { "F23", kKeyF23 }, + { "F24", kKeyF24 }, + { "F25", kKeyF25 }, + { "F26", kKeyF26 }, + { "F27", kKeyF27 }, + { "F28", kKeyF28 }, + { "F29", kKeyF29 }, + { "F30", kKeyF30 }, + { "F31", kKeyF31 }, + { "F32", kKeyF32 }, + { "F33", kKeyF33 }, + { "F34", kKeyF34 }, + { "F35", kKeyF35 }, + { "Find", kKeyFind }, + { "Help", kKeyHelp }, + { "Henkan", kKeyHenkan }, + { "Home", kKeyHome }, + { "Hyper_L", kKeyHyper_L }, + { "Hyper_R", kKeyHyper_R }, + { "Insert", kKeyInsert }, + { "KP_0", kKeyKP_0 }, + { "KP_1", kKeyKP_1 }, + { "KP_2", kKeyKP_2 }, + { "KP_3", kKeyKP_3 }, + { "KP_4", kKeyKP_4 }, + { "KP_5", kKeyKP_5 }, + { "KP_6", kKeyKP_6 }, + { "KP_7", kKeyKP_7 }, + { "KP_8", kKeyKP_8 }, + { "KP_9", kKeyKP_9 }, + { "KP_Add", kKeyKP_Add }, + { "KP_Begin", kKeyKP_Begin }, + { "KP_Decimal", kKeyKP_Decimal }, + { "KP_Delete", kKeyKP_Delete }, + { "KP_Divide", kKeyKP_Divide }, + { "KP_Down", kKeyKP_Down }, + { "KP_End", kKeyKP_End }, + { "KP_Enter", kKeyKP_Enter }, + { "KP_Equal", kKeyKP_Equal }, + { "KP_F1", kKeyKP_F1 }, + { "KP_F2", kKeyKP_F2 }, + { "KP_F3", kKeyKP_F3 }, + { "KP_F4", kKeyKP_F4 }, + { "KP_Home", kKeyKP_Home }, + { "KP_Insert", kKeyKP_Insert }, + { "KP_Left", kKeyKP_Left }, + { "KP_Multiply", kKeyKP_Multiply }, + { "KP_PageDown", kKeyKP_PageDown }, + { "KP_PageUp", kKeyKP_PageUp }, + { "KP_Right", kKeyKP_Right }, + { "KP_Separator", kKeyKP_Separator }, + { "KP_Space", kKeyKP_Space }, + { "KP_Subtract", kKeyKP_Subtract }, + { "KP_Tab", kKeyKP_Tab }, + { "KP_Up", kKeyKP_Up }, + { "Left", kKeyLeft }, + { "LeftTab", kKeyLeftTab }, + { "Linefeed", kKeyLinefeed }, + { "Menu", kKeyMenu }, + { "Meta_L", kKeyMeta_L }, + { "Meta_R", kKeyMeta_R }, + { "NumLock", kKeyNumLock }, + { "PageDown", kKeyPageDown }, + { "PageUp", kKeyPageUp }, + { "Pause", kKeyPause }, + { "Print", kKeyPrint }, + { "Redo", kKeyRedo }, + { "Return", kKeyReturn }, + { "Right", kKeyRight }, + { "ScrollLock", kKeyScrollLock }, + { "Select", kKeySelect }, + { "ShiftLock", kKeyShiftLock }, + { "Shift_L", kKeyShift_L }, + { "Shift_R", kKeyShift_R }, + { "Sleep", kKeySleep }, + { "Super_L", kKeySuper_L }, + { "Super_R", kKeySuper_R }, + { "SysReq", kKeySysReq }, + { "Tab", kKeyTab }, + { "Undo", kKeyUndo }, + { "Up", kKeyUp }, + { "WWWBack", kKeyWWWBack }, + { "WWWFavorites", kKeyWWWFavorites }, + { "WWWForward", kKeyWWWForward }, + { "WWWHome", kKeyWWWHome }, + { "WWWRefresh", kKeyWWWRefresh }, + { "WWWSearch", kKeyWWWSearch }, + { "WWWStop", kKeyWWWStop }, + { "Zenkaku", kKeyZenkaku }, + { "Space", 0x0020 }, + { "Exclaim", 0x0021 }, + { "DoubleQuote", 0x0022 }, + { "Number", 0x0023 }, + { "Dollar", 0x0024 }, + { "Percent", 0x0025 }, + { "Ampersand", 0x0026 }, + { "Apostrophe", 0x0027 }, + { "ParenthesisL", 0x0028 }, + { "ParenthesisR", 0x0029 }, + { "Asterisk", 0x002a }, + { "Plus", 0x002b }, + { "Comma", 0x002c }, + { "Minus", 0x002d }, + { "Period", 0x002e }, + { "Slash", 0x002f }, + { "Colon", 0x003a }, + { "Semicolon", 0x003b }, + { "Less", 0x003c }, + { "Equal", 0x003d }, + { "Greater", 0x003e }, + { "Question", 0x003f }, + { "At", 0x0040 }, + { "BracketL", 0x005b }, + { "Backslash", 0x005c }, + { "BracketR", 0x005d }, + { "Circumflex", 0x005e }, + { "Underscore", 0x005f }, + { "Grave", 0x0060 }, + { "BraceL", 0x007b }, + { "Bar", 0x007c }, + { "BraceR", 0x007d }, + { "Tilde", 0x007e }, + { NULL, 0 }, +}; + +const KeyModifierNameMapEntry kModifierNameMap[] = { + { "Alt", KeyModifierAlt }, + { "AltGr", KeyModifierAltGr }, +// { "CapsLock", KeyModifierCapsLock }, + { "Control", KeyModifierControl }, + { "Meta", KeyModifierMeta }, +// { "NumLock", KeyModifierNumLock }, +// { "ScrollLock", KeyModifierScrollLock }, + { "Shift", KeyModifierShift }, + { "Super", KeyModifierSuper }, + { NULL, 0 }, +}; diff --git a/src/lib/barrier/key_types.h b/src/lib/barrier/key_types.h new file mode 100644 index 0000000..7a8ea53 --- /dev/null +++ b/src/lib/barrier/key_types.h @@ -0,0 +1,314 @@ +/* + * 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/basic_types.h" + +//! Key ID +/*! +Type to hold a key symbol identifier. The encoding is UTF-32, using +U+E000 through U+EFFF for the various control keys (e.g. arrow +keys, function keys, modifier keys, etc). +*/ +typedef UInt32 KeyID; + +//! Key Code +/*! +Type to hold a physical key identifier. That is, it identifies a +physical key on the keyboard. KeyButton 0 is reserved to be an +invalid key; platforms that use 0 as a physical key identifier +will have to remap that value to some arbitrary unused id. +*/ +typedef UInt16 KeyButton; + +//! Modifier key mask +/*! +Type to hold a bitmask of key modifiers (e.g. shift keys). +*/ +typedef UInt32 KeyModifierMask; + +//! Modifier key ID +/*! +Type to hold the id of a key modifier (e.g. a shift key). +*/ +typedef UInt32 KeyModifierID; + +//! @name Modifier key masks +//@{ +static const KeyModifierMask KeyModifierShift = 0x0001; +static const KeyModifierMask KeyModifierControl = 0x0002; +static const KeyModifierMask KeyModifierAlt = 0x0004; +static const KeyModifierMask KeyModifierMeta = 0x0008; +static const KeyModifierMask KeyModifierSuper = 0x0010; +static const KeyModifierMask KeyModifierAltGr = 0x0020; +static const KeyModifierMask KeyModifierLevel5Lock = 0x0040; +static const KeyModifierMask KeyModifierCapsLock = 0x1000; +static const KeyModifierMask KeyModifierNumLock = 0x2000; +static const KeyModifierMask KeyModifierScrollLock = 0x4000; +//@} + +//! @name Modifier key bits +//@{ +static const UInt32 kKeyModifierBitNone = 16; +static const UInt32 kKeyModifierBitShift = 0; +static const UInt32 kKeyModifierBitControl = 1; +static const UInt32 kKeyModifierBitAlt = 2; +static const UInt32 kKeyModifierBitMeta = 3; +static const UInt32 kKeyModifierBitSuper = 4; +static const UInt32 kKeyModifierBitAltGr = 5; +static const UInt32 kKeyModifierBitLevel5Lock = 6; +static const UInt32 kKeyModifierBitCapsLock = 12; +static const UInt32 kKeyModifierBitNumLock = 13; +static const UInt32 kKeyModifierBitScrollLock = 14; +static const SInt32 kKeyModifierNumBits = 16; +//@} + +//! @name Modifier key identifiers +//@{ +static const KeyModifierID kKeyModifierIDNull = 0; +static const KeyModifierID kKeyModifierIDShift = 1; +static const KeyModifierID kKeyModifierIDControl = 2; +static const KeyModifierID kKeyModifierIDAlt = 3; +static const KeyModifierID kKeyModifierIDMeta = 4; +static const KeyModifierID kKeyModifierIDSuper = 5; +static const KeyModifierID kKeyModifierIDAltGr = 6; +static const KeyModifierID kKeyModifierIDLast = 7; +//@} + +//! @name Key identifiers +//@{ +// all identifiers except kKeyNone and those in 0xE000 to 0xE0FF +// inclusive are equal to the corresponding X11 keysym - 0x1000. + +// no key +static const KeyID kKeyNone = 0x0000; + +// TTY functions +static const KeyID kKeyBackSpace = 0xEF08; /* back space, back char */ +static const KeyID kKeyTab = 0xEF09; +static const KeyID kKeyLinefeed = 0xEF0A; /* Linefeed, LF */ +static const KeyID kKeyClear = 0xEF0B; +static const KeyID kKeyReturn = 0xEF0D; /* Return, enter */ +static const KeyID kKeyPause = 0xEF13; /* Pause, hold */ +static const KeyID kKeyScrollLock = 0xEF14; +static const KeyID kKeySysReq = 0xEF15; +static const KeyID kKeyEscape = 0xEF1B; +static const KeyID kKeyHenkan = 0xEF23; /* Start/Stop Conversion */ +static const KeyID kKeyKana = 0xEF26; /* Kana */ +static const KeyID kKeyHiraganaKatakana = 0xEF27; /* Hiragana/Katakana toggle */ +static const KeyID kKeyZenkaku = 0xEF2A; /* Zenkaku/Hankaku */ +static const KeyID kKeyKanzi = 0xEF2A; /* Kanzi */ +static const KeyID kKeyHangul = 0xEF31; /* Hangul */ +static const KeyID kKeyHanja = 0xEF34; /* Hanja */ +static const KeyID kKeyDelete = 0xEFFF; /* Delete, rubout */ + +// cursor control +static const KeyID kKeyHome = 0xEF50; +static const KeyID kKeyLeft = 0xEF51; /* Move left, left arrow */ +static const KeyID kKeyUp = 0xEF52; /* Move up, up arrow */ +static const KeyID kKeyRight = 0xEF53; /* Move right, right arrow */ +static const KeyID kKeyDown = 0xEF54; /* Move down, down arrow */ +static const KeyID kKeyPageUp = 0xEF55; +static const KeyID kKeyPageDown = 0xEF56; +static const KeyID kKeyEnd = 0xEF57; /* EOL */ +static const KeyID kKeyBegin = 0xEF58; /* BOL */ + +// misc functions +static const KeyID kKeySelect = 0xEF60; /* Select, mark */ +static const KeyID kKeyPrint = 0xEF61; +static const KeyID kKeyExecute = 0xEF62; /* Execute, run, do */ +static const KeyID kKeyInsert = 0xEF63; /* Insert, insert here */ +static const KeyID kKeyUndo = 0xEF65; /* Undo, oops */ +static const KeyID kKeyRedo = 0xEF66; /* redo, again */ +static const KeyID kKeyMenu = 0xEF67; +static const KeyID kKeyFind = 0xEF68; /* Find, search */ +static const KeyID kKeyCancel = 0xEF69; /* Cancel, stop, abort, exit */ +static const KeyID kKeyHelp = 0xEF6A; /* Help */ +static const KeyID kKeyBreak = 0xEF6B; +static const KeyID kKeyAltGr = 0xEF7E; /* Character set switch */ +static const KeyID kKeyNumLock = 0xEF7F; + +// keypad +static const KeyID kKeyKP_Space = 0xEF80; /* space */ +static const KeyID kKeyKP_Tab = 0xEF89; +static const KeyID kKeyKP_Enter = 0xEF8D; /* enter */ +static const KeyID kKeyKP_F1 = 0xEF91; /* PF1, KP_A, ... */ +static const KeyID kKeyKP_F2 = 0xEF92; +static const KeyID kKeyKP_F3 = 0xEF93; +static const KeyID kKeyKP_F4 = 0xEF94; +static const KeyID kKeyKP_Home = 0xEF95; +static const KeyID kKeyKP_Left = 0xEF96; +static const KeyID kKeyKP_Up = 0xEF97; +static const KeyID kKeyKP_Right = 0xEF98; +static const KeyID kKeyKP_Down = 0xEF99; +static const KeyID kKeyKP_PageUp = 0xEF9A; +static const KeyID kKeyKP_PageDown = 0xEF9B; +static const KeyID kKeyKP_End = 0xEF9C; +static const KeyID kKeyKP_Begin = 0xEF9D; +static const KeyID kKeyKP_Insert = 0xEF9E; +static const KeyID kKeyKP_Delete = 0xEF9F; +static const KeyID kKeyKP_Equal = 0xEFBD; /* equals */ +static const KeyID kKeyKP_Multiply = 0xEFAA; +static const KeyID kKeyKP_Add = 0xEFAB; +static const KeyID kKeyKP_Separator= 0xEFAC; /* separator, often comma */ +static const KeyID kKeyKP_Subtract = 0xEFAD; +static const KeyID kKeyKP_Decimal = 0xEFAE; +static const KeyID kKeyKP_Divide = 0xEFAF; +static const KeyID kKeyKP_0 = 0xEFB0; +static const KeyID kKeyKP_1 = 0xEFB1; +static const KeyID kKeyKP_2 = 0xEFB2; +static const KeyID kKeyKP_3 = 0xEFB3; +static const KeyID kKeyKP_4 = 0xEFB4; +static const KeyID kKeyKP_5 = 0xEFB5; +static const KeyID kKeyKP_6 = 0xEFB6; +static const KeyID kKeyKP_7 = 0xEFB7; +static const KeyID kKeyKP_8 = 0xEFB8; +static const KeyID kKeyKP_9 = 0xEFB9; + +// function keys +static const KeyID kKeyF1 = 0xEFBE; +static const KeyID kKeyF2 = 0xEFBF; +static const KeyID kKeyF3 = 0xEFC0; +static const KeyID kKeyF4 = 0xEFC1; +static const KeyID kKeyF5 = 0xEFC2; +static const KeyID kKeyF6 = 0xEFC3; +static const KeyID kKeyF7 = 0xEFC4; +static const KeyID kKeyF8 = 0xEFC5; +static const KeyID kKeyF9 = 0xEFC6; +static const KeyID kKeyF10 = 0xEFC7; +static const KeyID kKeyF11 = 0xEFC8; +static const KeyID kKeyF12 = 0xEFC9; +static const KeyID kKeyF13 = 0xEFCA; +static const KeyID kKeyF14 = 0xEFCB; +static const KeyID kKeyF15 = 0xEFCC; +static const KeyID kKeyF16 = 0xEFCD; +static const KeyID kKeyF17 = 0xEFCE; +static const KeyID kKeyF18 = 0xEFCF; +static const KeyID kKeyF19 = 0xEFD0; +static const KeyID kKeyF20 = 0xEFD1; +static const KeyID kKeyF21 = 0xEFD2; +static const KeyID kKeyF22 = 0xEFD3; +static const KeyID kKeyF23 = 0xEFD4; +static const KeyID kKeyF24 = 0xEFD5; +static const KeyID kKeyF25 = 0xEFD6; +static const KeyID kKeyF26 = 0xEFD7; +static const KeyID kKeyF27 = 0xEFD8; +static const KeyID kKeyF28 = 0xEFD9; +static const KeyID kKeyF29 = 0xEFDA; +static const KeyID kKeyF30 = 0xEFDB; +static const KeyID kKeyF31 = 0xEFDC; +static const KeyID kKeyF32 = 0xEFDD; +static const KeyID kKeyF33 = 0xEFDE; +static const KeyID kKeyF34 = 0xEFDF; +static const KeyID kKeyF35 = 0xEFE0; + +// modifiers +static const KeyID kKeyShift_L = 0xEFE1; /* Left shift */ +static const KeyID kKeyShift_R = 0xEFE2; /* Right shift */ +static const KeyID kKeyControl_L = 0xEFE3; /* Left control */ +static const KeyID kKeyControl_R = 0xEFE4; /* Right control */ +static const KeyID kKeyCapsLock = 0xEFE5; /* Caps lock */ +static const KeyID kKeyShiftLock = 0xEFE6; /* Shift lock */ +static const KeyID kKeyMeta_L = 0xEFE7; /* Left meta */ +static const KeyID kKeyMeta_R = 0xEFE8; /* Right meta */ +static const KeyID kKeyAlt_L = 0xEFE9; /* Left alt */ +static const KeyID kKeyAlt_R = 0xEFEA; /* Right alt */ +static const KeyID kKeySuper_L = 0xEFEB; /* Left super */ +static const KeyID kKeySuper_R = 0xEFEC; /* Right super */ +static const KeyID kKeyHyper_L = 0xEFED; /* Left hyper */ +static const KeyID kKeyHyper_R = 0xEFEE; /* Right hyper */ + +// multi-key character composition +static const KeyID kKeyCompose = 0xEF20; +static const KeyID kKeyDeadGrave = 0x0300; +static const KeyID kKeyDeadAcute = 0x0301; +static const KeyID kKeyDeadCircumflex = 0x0302; +static const KeyID kKeyDeadTilde = 0x0303; +static const KeyID kKeyDeadMacron = 0x0304; +static const KeyID kKeyDeadBreve = 0x0306; +static const KeyID kKeyDeadAbovedot = 0x0307; +static const KeyID kKeyDeadDiaeresis = 0x0308; +static const KeyID kKeyDeadAbovering = 0x030a; +static const KeyID kKeyDeadDoubleacute = 0x030b; +static const KeyID kKeyDeadCaron = 0x030c; +static const KeyID kKeyDeadCedilla = 0x0327; +static const KeyID kKeyDeadOgonek = 0x0328; + +// more function and modifier keys +static const KeyID kKeyLeftTab = 0xEE20; + +// update modifiers +static const KeyID kKeySetModifiers = 0xEE06; +static const KeyID kKeyClearModifiers = 0xEE07; + +// group change +static const KeyID kKeyNextGroup = 0xEE08; +static const KeyID kKeyPrevGroup = 0xEE0A; + +// extended keys +static const KeyID kKeyEject = 0xE001; +static const KeyID kKeySleep = 0xE05F; +static const KeyID kKeyWWWBack = 0xE0A6; +static const KeyID kKeyWWWForward = 0xE0A7; +static const KeyID kKeyWWWRefresh = 0xE0A8; +static const KeyID kKeyWWWStop = 0xE0A9; +static const KeyID kKeyWWWSearch = 0xE0AA; +static const KeyID kKeyWWWFavorites = 0xE0AB; +static const KeyID kKeyWWWHome = 0xE0AC; +static const KeyID kKeyAudioMute = 0xE0AD; +static const KeyID kKeyAudioDown = 0xE0AE; +static const KeyID kKeyAudioUp = 0xE0AF; +static const KeyID kKeyAudioNext = 0xE0B0; +static const KeyID kKeyAudioPrev = 0xE0B1; +static const KeyID kKeyAudioStop = 0xE0B2; +static const KeyID kKeyAudioPlay = 0xE0B3; +static const KeyID kKeyAppMail = 0xE0B4; +static const KeyID kKeyAppMedia = 0xE0B5; +static const KeyID kKeyAppUser1 = 0xE0B6; +static const KeyID kKeyAppUser2 = 0xE0B7; +static const KeyID kKeyBrightnessDown = 0xE0B8; +static const KeyID kKeyBrightnessUp = 0xE0B9; +static const KeyID kKeyMissionControl = 0xE0C0; +static const KeyID kKeyLaunchpad = 0xE0C1; + +//@} + +struct KeyNameMapEntry { + const char* m_name; + KeyID m_id; +}; +struct KeyModifierNameMapEntry { + const char* m_name; + KeyModifierMask m_mask; +}; + +//! Key name to KeyID table +/*! +A table of key names to the corresponding KeyID. Only the keys listed +above plus non-alphanumeric ASCII characters are in the table. The end +of the table is the first pair with a NULL m_name. +*/ +extern const struct KeyNameMapEntry kKeyNameMap[]; + +//! Modifier key name to KeyModifierMask table +/*! +A table of modifier key names to the corresponding KeyModifierMask. +The end of the table is the first pair with a NULL m_name. +*/ +extern const struct KeyModifierNameMapEntry kModifierNameMap[]; diff --git a/src/lib/barrier/mouse_types.h b/src/lib/barrier/mouse_types.h new file mode 100644 index 0000000..cf860c0 --- /dev/null +++ b/src/lib/barrier/mouse_types.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 "base/EventTypes.h" + +//! Mouse button ID +/*! +Type to hold a mouse button identifier. +*/ +typedef UInt8 ButtonID; + +//! @name Mouse button identifiers +//@{ +static const ButtonID kButtonNone = 0; +static const ButtonID kButtonLeft = 1; +static const ButtonID kButtonMiddle = 2; +static const ButtonID kButtonRight = 3; +static const ButtonID kButtonExtra0 = 4; + +static const ButtonID kMacButtonRight = 2; +static const ButtonID kMacButtonMiddle = 3; +//@} + +static const UInt8 NumButtonIDs = 5; diff --git a/src/lib/barrier/option_types.h b/src/lib/barrier/option_types.h new file mode 100644 index 0000000..6323e37 --- /dev/null +++ b/src/lib/barrier/option_types.h @@ -0,0 +1,99 @@ +/* + * 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 "base/EventTypes.h" +#include "common/stdvector.h" + +//! Option ID +/*! +Type to hold an option identifier. +*/ +typedef UInt32 OptionID; + +//! Option Value +/*! +Type to hold an option value. +*/ +typedef SInt32 OptionValue; + +// for now, options are just pairs of integers +typedef std::vector<UInt32> OptionsList; + +// macro for packing 4 character strings into 4 byte integers +#define OPTION_CODE(_s) \ + (static_cast<UInt32>(static_cast<unsigned char>(_s[0]) << 24) | \ + static_cast<UInt32>(static_cast<unsigned char>(_s[1]) << 16) | \ + static_cast<UInt32>(static_cast<unsigned char>(_s[2]) << 8) | \ + static_cast<UInt32>(static_cast<unsigned char>(_s[3]) )) + +//! @name Option identifiers +//@{ +static const OptionID kOptionHalfDuplexCapsLock = OPTION_CODE("HDCL"); +static const OptionID kOptionHalfDuplexNumLock = OPTION_CODE("HDNL"); +static const OptionID kOptionHalfDuplexScrollLock = OPTION_CODE("HDSL"); +static const OptionID kOptionModifierMapForShift = OPTION_CODE("MMFS"); +static const OptionID kOptionModifierMapForControl = OPTION_CODE("MMFC"); +static const OptionID kOptionModifierMapForAlt = OPTION_CODE("MMFA"); +static const OptionID kOptionModifierMapForAltGr = OPTION_CODE("MMFG"); +static const OptionID kOptionModifierMapForMeta = OPTION_CODE("MMFM"); +static const OptionID kOptionModifierMapForSuper = OPTION_CODE("MMFR"); +static const OptionID kOptionHeartbeat = OPTION_CODE("HART"); +static const OptionID kOptionScreenSwitchCorners = OPTION_CODE("SSCM"); +static const OptionID kOptionScreenSwitchCornerSize = OPTION_CODE("SSCS"); +static const OptionID kOptionScreenSwitchDelay = OPTION_CODE("SSWT"); +static const OptionID kOptionScreenSwitchTwoTap = OPTION_CODE("SSTT"); +static const OptionID kOptionScreenSwitchNeedsShift = OPTION_CODE("SSNS"); +static const OptionID kOptionScreenSwitchNeedsControl = OPTION_CODE("SSNC"); +static const OptionID kOptionScreenSwitchNeedsAlt = OPTION_CODE("SSNA"); +static const OptionID kOptionScreenSaverSync = OPTION_CODE("SSVR"); +static const OptionID kOptionXTestXineramaUnaware = OPTION_CODE("XTXU"); +static const OptionID kOptionScreenPreserveFocus = OPTION_CODE("SFOC"); +static const OptionID kOptionRelativeMouseMoves = OPTION_CODE("MDLT"); +static const OptionID kOptionWin32KeepForeground = OPTION_CODE("_KFW"); +static const OptionID kOptionClipboardSharing = OPTION_CODE("CLPS"); +//@} + +//! @name Screen switch corner enumeration +//@{ +enum EScreenSwitchCorners { + kNoCorner, + kTopLeft, + kTopRight, + kBottomLeft, + kBottomRight, + kFirstCorner = kTopLeft, + kLastCorner = kBottomRight +}; +//@} + +//! @name Screen switch corner masks +//@{ +enum EScreenSwitchCornerMasks { + kNoCornerMask = 0, + kTopLeftMask = 1 << (kTopLeft - kFirstCorner), + kTopRightMask = 1 << (kTopRight - kFirstCorner), + kBottomLeftMask = 1 << (kBottomLeft - kFirstCorner), + kBottomRightMask = 1 << (kBottomRight - kFirstCorner), + kAllCornersMask = kTopLeftMask | kTopRightMask | + kBottomLeftMask | kBottomRightMask +}; +//@} + +#undef OPTION_CODE diff --git a/src/lib/barrier/protocol_types.cpp b/src/lib/barrier/protocol_types.cpp new file mode 100644 index 0000000..07acf61 --- /dev/null +++ b/src/lib/barrier/protocol_types.cpp @@ -0,0 +1,53 @@ +/* + * 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 "barrier/protocol_types.h" + +const char* kMsgHello = "Barrier%2i%2i"; +const char* kMsgHelloBack = "Barrier%2i%2i%s"; +const char* kMsgCNoop = "CNOP"; +const char* kMsgCClose = "CBYE"; +const char* kMsgCEnter = "CINN%2i%2i%4i%2i"; +const char* kMsgCLeave = "COUT"; +const char* kMsgCClipboard = "CCLP%1i%4i"; +const char* kMsgCScreenSaver = "CSEC%1i"; +const char* kMsgCResetOptions = "CROP"; +const char* kMsgCInfoAck = "CIAK"; +const char* kMsgCKeepAlive = "CALV"; +const char* kMsgDKeyDown = "DKDN%2i%2i%2i"; +const char* kMsgDKeyDown1_0 = "DKDN%2i%2i"; +const char* kMsgDKeyRepeat = "DKRP%2i%2i%2i%2i"; +const char* kMsgDKeyRepeat1_0 = "DKRP%2i%2i%2i"; +const char* kMsgDKeyUp = "DKUP%2i%2i%2i"; +const char* kMsgDKeyUp1_0 = "DKUP%2i%2i"; +const char* kMsgDMouseDown = "DMDN%1i"; +const char* kMsgDMouseUp = "DMUP%1i"; +const char* kMsgDMouseMove = "DMMV%2i%2i"; +const char* kMsgDMouseRelMove = "DMRM%2i%2i"; +const char* kMsgDMouseWheel = "DMWM%2i%2i"; +const char* kMsgDMouseWheel1_0 = "DMWM%2i"; +const char* kMsgDClipboard = "DCLP%1i%4i%1i%s"; +const char* kMsgDInfo = "DINF%2i%2i%2i%2i%2i%2i%2i"; +const char* kMsgDSetOptions = "DSOP%4I"; +const char* kMsgDFileTransfer = "DFTR%1i%s"; +const char* kMsgDDragInfo = "DDRG%2i%s"; +const char* kMsgQInfo = "QINF"; +const char* kMsgEIncompatible = "EICV%2i%2i"; +const char* kMsgEBusy = "EBSY"; +const char* kMsgEUnknown = "EUNK"; +const char* kMsgEBad = "EBAD"; diff --git a/src/lib/barrier/protocol_types.h b/src/lib/barrier/protocol_types.h new file mode 100644 index 0000000..bc5e037 --- /dev/null +++ b/src/lib/barrier/protocol_types.h @@ -0,0 +1,337 @@ +/* + * 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 "base/EventTypes.h" + +// protocol version number +// 1.0: initial protocol +// 1.1: adds KeyCode to key press, release, and repeat +// 1.2: adds mouse relative motion +// 1.3: adds keep alive and deprecates heartbeats, +// adds horizontal mouse scrolling +// 1.4: adds crypto support +// 1.5: adds file transfer and removes home brew crypto +// 1.6: adds clipboard streaming +// NOTE: with new version, barrier minor version should increment +static const SInt16 kProtocolMajorVersion = 1; +static const SInt16 kProtocolMinorVersion = 6; + +// default contact port number +static const UInt16 kDefaultPort = 24800; + +// maximum total length for greeting returned by client +static const UInt32 kMaxHelloLength = 1024; + +// time between kMsgCKeepAlive (in seconds). a non-positive value disables +// keep alives. this is the default rate that can be overridden using an +// option. +static const double kKeepAliveRate = 3.0; + +// number of skipped kMsgCKeepAlive messages that indicates a problem +static const double kKeepAlivesUntilDeath = 3.0; + +// obsolete heartbeat stuff +static const double kHeartRate = -1.0; +static const double kHeartBeatsUntilDeath = 3.0; + +// direction constants +enum EDirection { + kNoDirection, + kLeft, + kRight, + kTop, + kBottom, + kFirstDirection = kLeft, + kLastDirection = kBottom, + kNumDirections = kLastDirection - kFirstDirection + 1 +}; +enum EDirectionMask { + kNoDirMask = 0, + kLeftMask = 1 << kLeft, + kRightMask = 1 << kRight, + kTopMask = 1 << kTop, + kBottomMask = 1 << kBottom +}; + +// Data transfer constants +enum EDataTransfer { + kDataStart = 1, + kDataChunk = 2, + kDataEnd = 3 +}; + +// Data received constants +enum EDataReceived { + kStart, + kNotFinish, + kFinish, + kError +}; + +// +// message codes (trailing NUL is not part of code). in comments, $n +// refers to the n'th argument (counting from one). message codes are +// always 4 bytes optionally followed by message specific parameters +// except those for the greeting handshake. +// + +// +// positions and sizes are signed 16 bit integers. +// + +// +// greeting handshake messages +// + +// say hello to client; primary -> secondary +// $1 = protocol major version number supported by server. $2 = +// protocol minor version number supported by server. +extern const char* kMsgHello; + +// respond to hello from server; secondary -> primary +// $1 = protocol major version number supported by client. $2 = +// protocol minor version number supported by client. $3 = client +// name. +extern const char* kMsgHelloBack; + + +// +// command codes +// + +// no operation; secondary -> primary +extern const char* kMsgCNoop; + +// close connection; primary -> secondary +extern const char* kMsgCClose; + +// enter screen: primary -> secondary +// entering screen at screen position $1 = x, $2 = y. x,y are +// absolute screen coordinates. $3 = sequence number, which is +// used to order messages between screens. the secondary screen +// must return this number with some messages. $4 = modifier key +// mask. this will have bits set for each toggle modifier key +// that is activated on entry to the screen. the secondary screen +// should adjust its toggle modifiers to reflect that state. +extern const char* kMsgCEnter; + +// leave screen: primary -> secondary +// leaving screen. the secondary screen should send clipboard +// data in response to this message for those clipboards that +// it has grabbed (i.e. has sent a kMsgCClipboard for and has +// not received a kMsgCClipboard for with a greater sequence +// number) and that were grabbed or have changed since the +// last leave. +extern const char* kMsgCLeave; + +// grab clipboard: primary <-> secondary +// sent by screen when some other app on that screen grabs a +// clipboard. $1 = the clipboard identifier, $2 = sequence number. +// secondary screens must use the sequence number passed in the +// most recent kMsgCEnter. the primary always sends 0. +extern const char* kMsgCClipboard; + +// screensaver change: primary -> secondary +// screensaver on primary has started ($1 == 1) or closed ($1 == 0) +extern const char* kMsgCScreenSaver; + +// reset options: primary -> secondary +// client should reset all of its options to their defaults. +extern const char* kMsgCResetOptions; + +// resolution change acknowledgment: primary -> secondary +// sent by primary in response to a secondary screen's kMsgDInfo. +// this is sent for every kMsgDInfo, whether or not the primary +// had sent a kMsgQInfo. +extern const char* kMsgCInfoAck; + +// keep connection alive: primary <-> secondary +// sent by the server periodically to verify that connections are still +// up and running. clients must reply in kind on receipt. if the server +// gets an error sending the message or does not receive a reply within +// a reasonable time then the server disconnects the client. if the +// client doesn't receive these (or any message) periodically then it +// should disconnect from the server. the appropriate interval is +// defined by an option. +extern const char* kMsgCKeepAlive; + +// +// data codes +// + +// key pressed: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +// the KeyButton identifies the physical key on the primary used to +// generate this key. the secondary should note the KeyButton along +// with the physical key it uses to generate the key press. on +// release, the secondary can then use the primary's KeyButton to +// find its corresponding physical key and release it. this is +// necessary because the KeyID on release may not be the KeyID of +// the press. this can happen with combining (dead) keys or if +// the keyboard layouts are not identical and the user releases +// a modifier key before releasing the modified key. +extern const char* kMsgDKeyDown; + +// key pressed 1.0: same as above but without KeyButton +extern const char* kMsgDKeyDown1_0; + +// key auto-repeat: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = number of repeats, $4 = KeyButton +extern const char* kMsgDKeyRepeat; + +// key auto-repeat 1.0: same as above but without KeyButton +extern const char* kMsgDKeyRepeat1_0; + +// key released: primary -> secondary +// $1 = KeyID, $2 = KeyModifierMask, $3 = KeyButton +extern const char* kMsgDKeyUp; + +// key released 1.0: same as above but without KeyButton +extern const char* kMsgDKeyUp1_0; + +// mouse button pressed: primary -> secondary +// $1 = ButtonID +extern const char* kMsgDMouseDown; + +// mouse button released: primary -> secondary +// $1 = ButtonID +extern const char* kMsgDMouseUp; + +// mouse moved: primary -> secondary +// $1 = x, $2 = y. x,y are absolute screen coordinates. +extern const char* kMsgDMouseMove; + +// relative mouse move: primary -> secondary +// $1 = dx, $2 = dy. dx,dy are motion deltas. +extern const char* kMsgDMouseRelMove; + +// mouse scroll: primary -> secondary +// $1 = xDelta, $2 = yDelta. the delta should be +120 for one tick forward +// (away from the user) or right and -120 for one tick backward (toward +// the user) or left. +extern const char* kMsgDMouseWheel; + +// mouse vertical scroll: primary -> secondary +// like as kMsgDMouseWheel except only sends $1 = yDelta. +extern const char* kMsgDMouseWheel1_0; + +// clipboard data: primary <-> secondary +// $2 = sequence number, $3 = mark $4 = clipboard data. the sequence number +// is 0 when sent by the primary. secondary screens should use the +// sequence number from the most recent kMsgCEnter. $1 = clipboard +// identifier. +extern const char* kMsgDClipboard; + +// client data: secondary -> primary +// $1 = coordinate of leftmost pixel on secondary screen, +// $2 = coordinate of topmost pixel on secondary screen, +// $3 = width of secondary screen in pixels, +// $4 = height of secondary screen in pixels, +// $5 = size of warp zone, (obsolete) +// $6, $7 = the x,y position of the mouse on the secondary screen. +// +// the secondary screen must send this message in response to the +// kMsgQInfo message. it must also send this message when the +// screen's resolution changes. in this case, the secondary screen +// should ignore any kMsgDMouseMove messages until it receives a +// kMsgCInfoAck in order to prevent attempts to move the mouse off +// the new screen area. +extern const char* kMsgDInfo; + +// set options: primary -> secondary +// client should set the given option/value pairs. $1 = option/value +// pairs. +extern const char* kMsgDSetOptions; + +// file data: primary <-> secondary +// transfer file data. A mark is used in the first byte. +// 0 means the content followed is the file size. +// 1 means the content followed is the chunk data. +// 2 means the file transfer is finished. +extern const char* kMsgDFileTransfer; + +// drag infomation: primary <-> secondary +// transfer drag infomation. The first 2 bytes are used for storing +// the number of dragging objects. Then the following string consists +// of each object's directory. +extern const char* kMsgDDragInfo; + +// +// query codes +// + +// query screen info: primary -> secondary +// client should reply with a kMsgDInfo. +extern const char* kMsgQInfo; + + +// +// error codes +// + +// incompatible versions: primary -> secondary +// $1 = major version of primary, $2 = minor version of primary. +extern const char* kMsgEIncompatible; + +// name provided when connecting is already in use: primary -> secondary +extern const char* kMsgEBusy; + +// unknown client: primary -> secondary +// name provided when connecting is not in primary's screen +// configuration map. +extern const char* kMsgEUnknown; + +// protocol violation: primary -> secondary +// primary should disconnect after sending this message. +extern const char* kMsgEBad; + + +// +// structures +// + +//! Screen information +/*! +This class contains information about a screen. +*/ +class ClientInfo { +public: + //! Screen position + /*! + The position of the upper-left corner of the screen. This is + typically 0,0. + */ + SInt32 m_x, m_y; + + //! Screen size + /*! + The size of the screen in pixels. + */ + SInt32 m_w, m_h; + + //! Obsolete (jump zone size) + SInt32 obsolete1; + + //! Mouse position + /*! + The current location of the mouse cursor. + */ + SInt32 m_mx, m_my; +}; diff --git a/src/lib/barrier/unix/AppUtilUnix.cpp b/src/lib/barrier/unix/AppUtilUnix.cpp new file mode 100644 index 0000000..a1548d8 --- /dev/null +++ b/src/lib/barrier/unix/AppUtilUnix.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 "barrier/unix/AppUtilUnix.h" +#include "barrier/ArgsBase.h" + +AppUtilUnix::AppUtilUnix(IEventQueue* events) +{ +} + +AppUtilUnix::~AppUtilUnix() +{ +} + +int +standardStartupStatic(int argc, char** argv) +{ + return AppUtil::instance().app().standardStartup(argc, argv); +} + +int +AppUtilUnix::run(int argc, char** argv) +{ + return app().runInner(argc, argv, NULL, &standardStartupStatic); +} + +void +AppUtilUnix::startNode() +{ + app().startNode(); +} diff --git a/src/lib/barrier/unix/AppUtilUnix.h b/src/lib/barrier/unix/AppUtilUnix.h new file mode 100644 index 0000000..fefcfea --- /dev/null +++ b/src/lib/barrier/unix/AppUtilUnix.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 "barrier/AppUtil.h" + +#define ARCH_APP_UTIL AppUtilUnix + +class IEventQueue; + +class AppUtilUnix : public AppUtil { +public: + AppUtilUnix(IEventQueue* events); + virtual ~AppUtilUnix(); + + int run(int argc, char** argv); + void startNode(); +}; diff --git a/src/lib/barrier/win32/AppUtilWindows.cpp b/src/lib/barrier/win32/AppUtilWindows.cpp new file mode 100644 index 0000000..560b029 --- /dev/null +++ b/src/lib/barrier/win32/AppUtilWindows.cpp @@ -0,0 +1,185 @@ +/* + * 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 "barrier/win32/AppUtilWindows.h" +#include "barrier/Screen.h" +#include "barrier/ArgsBase.h" +#include "barrier/App.h" +#include "barrier/XBarrier.h" +#include "platform/MSWindowsScreen.h" +#include "arch/win32/XArchWindows.h" +#include "arch/win32/ArchMiscWindows.h" +#include "arch/IArchTaskBarReceiver.h" +#include "base/Log.h" +#include "base/log_outputters.h" +#include "base/IEventQueue.h" +#include "base/Event.h" +#include "base/EventQueue.h" +#include "common/Version.h" + +#include <sstream> +#include <iostream> +#include <conio.h> +#include <VersionHelpers.h> + +AppUtilWindows::AppUtilWindows(IEventQueue* events) : + m_events(events), + m_exitMode(kExitModeNormal) +{ + if (SetConsoleCtrlHandler((PHANDLER_ROUTINE)consoleHandler, TRUE) == FALSE) + { + throw XArch(new XArchEvalWindows()); + } +} + +AppUtilWindows::~AppUtilWindows() +{ +} + +BOOL WINAPI AppUtilWindows::consoleHandler(DWORD) +{ + LOG((CLOG_INFO "got shutdown signal")); + IEventQueue* events = AppUtil::instance().app().getEvents(); + events->addEvent(Event(Event::kQuit)); + return TRUE; +} + +static +int +mainLoopStatic() +{ + return AppUtil::instance().app().mainLoop(); +} + +int +AppUtilWindows::daemonNTMainLoop(int argc, const char** argv) +{ + app().initApp(argc, argv); + debugServiceWait(); + + // NB: what the hell does this do?! + app().argsBase().m_backend = false; + + return ArchMiscWindows::runDaemon(mainLoopStatic); +} + +void +AppUtilWindows::exitApp(int code) +{ + switch (m_exitMode) { + + case kExitModeDaemon: + ArchMiscWindows::daemonFailed(code); + break; + + default: + throw XExitApp(code); + } +} + +int daemonNTMainLoopStatic(int argc, const char** argv) +{ + return AppUtilWindows::instance().daemonNTMainLoop(argc, argv); +} + +int +AppUtilWindows::daemonNTStartup(int, char**) +{ + SystemLogger sysLogger(app().daemonName(), false); + m_exitMode = kExitModeDaemon; + return ARCH->daemonize(app().daemonName(), daemonNTMainLoopStatic); +} + +static +int +daemonNTStartupStatic(int argc, char** argv) +{ + return AppUtilWindows::instance().daemonNTStartup(argc, argv); +} + +static +int +foregroundStartupStatic(int argc, char** argv) +{ + return AppUtil::instance().app().foregroundStartup(argc, argv); +} + +void +AppUtilWindows::beforeAppExit() +{ + // this can be handy for debugging, since the application is launched in + // a new console window, and will normally close on exit (making it so + // that we can't see error messages). + if (app().argsBase().m_pauseOnExit) { + std::cout << std::endl << "press any key to exit..." << std::endl; + int c = _getch(); + } +} + +int +AppUtilWindows::run(int argc, char** argv) +{ + if (!IsWindowsXPSP3OrGreater()) { + throw std::runtime_error("Barrier only supports Windows XP SP3 and above."); + } + + // record window instance for tray icon, etc + ArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL)); + + MSWindowsScreen::init(ArchMiscWindows::instanceWin32()); + Thread::getCurrentThread().setPriority(-14); + + StartupFunc startup; + if (ArchMiscWindows::wasLaunchedAsService()) { + startup = &daemonNTStartupStatic; + } else { + startup = &foregroundStartupStatic; + app().argsBase().m_daemon = false; + } + + return app().runInner(argc, argv, NULL, startup); +} + +AppUtilWindows& +AppUtilWindows::instance() +{ + return (AppUtilWindows&)AppUtil::instance(); +} + +void +AppUtilWindows::debugServiceWait() +{ + if (app().argsBase().m_debugServiceWait) + { + while(true) + { + // this code is only executed when the process is launched via the + // windows service controller (and --debug-service-wait arg is + // used). to debug, set a breakpoint on this line so that + // execution is delayed until the debugger is attached. + ARCH->sleep(1); + LOG((CLOG_INFO "waiting for debugger to attach")); + } + } +} + +void +AppUtilWindows::startNode() +{ + app().startNode(); +} diff --git a/src/lib/barrier/win32/AppUtilWindows.h b/src/lib/barrier/win32/AppUtilWindows.h new file mode 100644 index 0000000..c5da228 --- /dev/null +++ b/src/lib/barrier/win32/AppUtilWindows.h @@ -0,0 +1,62 @@ +/* + * 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 "barrier/AppUtil.h" +#include "base/String.h" + +#define WIN32_LEAN_AND_MEAN +#include "Windows.h" + +#define ARCH_APP_UTIL AppUtilWindows + +class IEventQueue; + +enum AppExitMode { + kExitModeNormal, + kExitModeDaemon +}; + +class AppUtilWindows : public AppUtil { +public: + AppUtilWindows(IEventQueue* events); + virtual ~AppUtilWindows(); + + int daemonNTStartup(int, char**); + + int daemonNTMainLoop(int argc, const char** argv); + + void debugServiceWait(); + + int run(int argc, char** argv); + + void exitApp(int code); + + void beforeAppExit(); + + static AppUtilWindows& instance(); + + void startNode(); + +private: + AppExitMode m_exitMode; + IEventQueue* m_events; + + static BOOL WINAPI consoleHandler(DWORD Event); +}; diff --git a/src/lib/barrier/win32/DaemonApp.cpp b/src/lib/barrier/win32/DaemonApp.cpp new file mode 100644 index 0000000..62fecf8 --- /dev/null +++ b/src/lib/barrier/win32/DaemonApp.cpp @@ -0,0 +1,361 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "barrier/win32/DaemonApp.h" + +#include "barrier/App.h" +#include "barrier/ArgParser.h" +#include "barrier/ServerArgs.h" +#include "barrier/ClientArgs.h" +#include "ipc/IpcClientProxy.h" +#include "ipc/IpcMessage.h" +#include "ipc/IpcLogOutputter.h" +#include "net/SocketMultiplexer.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/TMethodJob.h" +#include "base/TMethodEventJob.h" +#include "base/EventQueue.h" +#include "base/log_outputters.h" +#include "base/Log.h" + +#include "arch/win32/ArchMiscWindows.h" +#include "arch/win32/XArchWindows.h" +#include "barrier/Screen.h" +#include "platform/MSWindowsScreen.h" +#include "platform/MSWindowsDebugOutputter.h" +#include "platform/MSWindowsWatchdog.h" +#include "platform/MSWindowsEventQueueBuffer.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#include <string> +#include <iostream> +#include <sstream> + +using namespace std; + +DaemonApp* DaemonApp::s_instance = NULL; + +int +mainLoopStatic() +{ + DaemonApp::s_instance->mainLoop(true); + return kExitSuccess; +} + +int +mainLoopStatic(int, const char**) +{ + return ArchMiscWindows::runDaemon(mainLoopStatic); +} + +DaemonApp::DaemonApp() : + m_ipcServer(nullptr), + m_ipcLogOutputter(nullptr), + m_watchdog(nullptr), + m_events(nullptr), + m_fileLogOutputter(nullptr) +{ + s_instance = this; +} + +DaemonApp::~DaemonApp() +{ +} + +int +DaemonApp::run(int argc, char** argv) +{ + // win32 instance needed for threading, etc. + ArchMiscWindows::setInstanceWin32(GetModuleHandle(NULL)); + + Arch arch; + arch.init(); + + Log log; + EventQueue events; + m_events = &events; + + bool uninstall = false; + try + { + // sends debug messages to visual studio console window. + log.insert(new MSWindowsDebugOutputter()); + + // default log level to system setting. + string logLevel = arch.setting("LogLevel"); + if (logLevel != "") + log.setFilter(logLevel.c_str()); + + bool foreground = false; + + for (int i = 1; i < argc; ++i) { + string arg(argv[i]); + + if (arg == "/f" || arg == "-f") { + foreground = true; + } + else if (arg == "/install") { + uninstall = true; + arch.installDaemon(); + return kExitSuccess; + } + else if (arg == "/uninstall") { + arch.uninstallDaemon(); + return kExitSuccess; + } + else { + stringstream ss; + ss << "Unrecognized argument: " << arg; + foregroundError(ss.str().c_str()); + return kExitArgs; + } + } + + if (foreground) { + // run process in foreground instead of daemonizing. + // useful for debugging. + mainLoop(false); + } + else { + arch.daemonize("Barrier", mainLoopStatic); + } + + return kExitSuccess; + } + catch (XArch& e) { + String message = e.what(); + if (uninstall && (message.find("The service has not been started") != String::npos)) { + // TODO: if we're keeping this use error code instead (what is it?!). + // HACK: this message happens intermittently, not sure where from but + // it's quite misleading for the user. they thing something has gone + // horribly wrong, but it's just the service manager reporting a false + // positive (the service has actually shut down in most cases). + } + else { + foregroundError(message.c_str()); + } + return kExitFailed; + } + catch (std::exception& e) { + foregroundError(e.what()); + return kExitFailed; + } + catch (...) { + foregroundError("Unrecognized error."); + return kExitFailed; + } +} + +void +DaemonApp::mainLoop(bool daemonized) +{ + try + { + DAEMON_RUNNING(true); + + if (daemonized) { + m_fileLogOutputter = new FileLogOutputter(logFilename().c_str()); + CLOG->insert(m_fileLogOutputter); + } + + // create socket multiplexer. this must happen after daemonization + // on unix because threads evaporate across a fork(). + SocketMultiplexer multiplexer; + + // uses event queue, must be created here. + m_ipcServer = new IpcServer(m_events, &multiplexer); + + // send logging to gui via ipc, log system adopts outputter. + m_ipcLogOutputter = new IpcLogOutputter(*m_ipcServer, kIpcClientGui, true); + CLOG->insert(m_ipcLogOutputter); + + m_watchdog = new MSWindowsWatchdog(daemonized, false, *m_ipcServer, *m_ipcLogOutputter); + m_watchdog->setFileLogOutputter(m_fileLogOutputter); + + m_events->adoptHandler( + m_events->forIpcServer().messageReceived(), m_ipcServer, + new TMethodEventJob<DaemonApp>(this, &DaemonApp::handleIpcMessage)); + + m_ipcServer->listen(); + + // install the platform event queue to handle service stop events. + m_events->adoptBuffer(new MSWindowsEventQueueBuffer(m_events)); + + String command = ARCH->setting("Command"); + bool elevate = ARCH->setting("Elevate") == "1"; + if (command != "") { + LOG((CLOG_INFO "using last known command: %s", command.c_str())); + m_watchdog->setCommand(command, elevate); + } + + m_watchdog->startAsync(); + + m_events->loop(); + + m_watchdog->stop(); + delete m_watchdog; + + m_events->removeHandler( + m_events->forIpcServer().messageReceived(), m_ipcServer); + + CLOG->remove(m_ipcLogOutputter); + delete m_ipcLogOutputter; + delete m_ipcServer; + + DAEMON_RUNNING(false); + } + catch (std::exception& e) { + LOG((CLOG_CRIT "An error occurred: %s", e.what())); + } + catch (...) { + LOG((CLOG_CRIT "An unknown error occurred.\n")); + } +} + +void +DaemonApp::foregroundError(const char* message) +{ + MessageBox(NULL, message, "Barrier Service", MB_OK | MB_ICONERROR); +} + +std::string +DaemonApp::logFilename() +{ + string logFilename; + logFilename = ARCH->setting("LogFilename"); + if (logFilename.empty()) { + logFilename = ARCH->getLogDirectory(); + logFilename.append("/"); + logFilename.append(LOG_FILENAME); + } + + return logFilename; +} + +void +DaemonApp::handleIpcMessage(const Event& e, void*) +{ + IpcMessage* m = static_cast<IpcMessage*>(e.getDataObject()); + switch (m->type()) { + case kIpcCommand: { + IpcCommandMessage* cm = static_cast<IpcCommandMessage*>(m); + String command = cm->command(); + + // if empty quotes, clear. + if (command == "\"\"") { + command.clear(); + } + + if (!command.empty()) { + LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str())); + + std::vector<String> argsArray; + ArgParser::splitCommandString(command, argsArray); + ArgParser argParser(NULL); + const char** argv = argParser.getArgv(argsArray); + ServerArgs serverArgs; + ClientArgs clientArgs; + int argc = static_cast<int>(argsArray.size()); + bool server = argsArray[0].find("barriers") != String::npos ? true : false; + ArgsBase* argBase = NULL; + + if (server) { + argParser.parseServerArgs(serverArgs, argc, argv); + argBase = &serverArgs; + } + else { + argParser.parseClientArgs(clientArgs, argc, argv); + argBase = &clientArgs; + } + + delete[] argv; + + String logLevel(argBase->m_logFilter); + if (!logLevel.empty()) { + try { + // change log level based on that in the command string + // and change to that log level now. + ARCH->setting("LogLevel", logLevel); + CLOG->setFilter(logLevel.c_str()); + } + catch (XArch& e) { + LOG((CLOG_ERR "failed to save LogLevel setting, %s", e.what())); + } + } + + // eg. no log-to-file while running in foreground + if (m_fileLogOutputter != nullptr) { + String logFilename; + if (argBase->m_logFile != NULL) { + logFilename = String(argBase->m_logFile); + ARCH->setting("LogFilename", logFilename); + m_watchdog->setFileLogOutputter(m_fileLogOutputter); + command = ArgParser::assembleCommand(argsArray, "--log", 1); + LOG((CLOG_DEBUG "removed log file argument and filename %s from command ", logFilename.c_str())); + LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str())); + } else { + m_watchdog->setFileLogOutputter(NULL); + } + m_fileLogOutputter->setLogFilename(logFilename.c_str()); + } + } + else { + LOG((CLOG_DEBUG "empty command, elevate=%d", cm->elevate())); + } + + try { + // store command in system settings. this is used when the daemon + // next starts. + ARCH->setting("Command", command); + + // TODO: it would be nice to store bools/ints... + ARCH->setting("Elevate", String(cm->elevate() ? "1" : "0")); + } + catch (XArch& e) { + LOG((CLOG_ERR "failed to save settings, %s", e.what())); + } + + // tell the relauncher about the new command. this causes the + // relauncher to stop the existing command and start the new + // command. + m_watchdog->setCommand(command, cm->elevate()); + + break; + } + + case kIpcHello: + IpcHelloMessage* hm = static_cast<IpcHelloMessage*>(m); + String type; + switch (hm->clientType()) { + case kIpcClientGui: type = "gui"; break; + case kIpcClientNode: type = "node"; break; + default: type = "unknown"; break; + } + + LOG((CLOG_DEBUG "ipc hello, type=%s", type.c_str())); + + const char * serverstatus = m_watchdog->isProcessActive() ? "active" : "not active"; + LOG((CLOG_INFO "server status: %s", serverstatus)); + + m_ipcLogOutputter->notifyBuffer(); + break; + } +} diff --git a/src/lib/barrier/win32/DaemonApp.h b/src/lib/barrier/win32/DaemonApp.h new file mode 100644 index 0000000..2a8484b --- /dev/null +++ b/src/lib/barrier/win32/DaemonApp.h @@ -0,0 +1,58 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "arch/Arch.h" +#include "ipc/IpcServer.h" + +#include <string> + +class Event; +class IpcLogOutputter; +class FileLogOutputter; + +class MSWindowsWatchdog; + +class DaemonApp { + +public: + DaemonApp(); + virtual ~DaemonApp(); + int run(int argc, char** argv); + void mainLoop(bool daemonized); + +private: + void daemonize(); + void foregroundError(const char* message); + std::string logFilename(); + void handleIpcMessage(const Event&, void*); + +public: + static DaemonApp* s_instance; + + MSWindowsWatchdog* m_watchdog; + +private: + IpcServer* m_ipcServer; + IpcLogOutputter* m_ipcLogOutputter; + IEventQueue* m_events; + FileLogOutputter* m_fileLogOutputter; +}; + +#define LOG_FILENAME "barrierd.log" diff --git a/src/lib/base/CMakeLists.txt b/src/lib/base/CMakeLists.txt new file mode 100644 index 0000000..66ba5a6 --- /dev/null +++ b/src/lib/base/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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() + +add_library(base STATIC ${sources}) + +if (UNIX) + target_link_libraries(base common) +endif() diff --git a/src/lib/base/ELevel.h b/src/lib/base/ELevel.h new file mode 100644 index 0000000..ec0f94f --- /dev/null +++ b/src/lib/base/ELevel.h @@ -0,0 +1,38 @@ +/* + * 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/>. + */ + +#pragma once + +//! Log levels +/*! +The logging priority levels in order of highest to lowest priority. +*/ +enum ELevel { + kPRINT = -1, //!< For print only (no file or time) + kFATAL, //!< For fatal errors + kERROR, //!< For serious errors + kWARNING, //!< For minor errors and warnings + kNOTE, //!< For messages about notable events + kINFO, //!< For informational messages + kDEBUG, //!< For important debugging messages + kDEBUG1, //!< For verbosity +1 debugging messages + kDEBUG2, //!< For verbosity +2 debugging messages + kDEBUG3, //!< For verbosity +3 debugging messages + kDEBUG4, //!< For verbosity +4 debugging messages + kDEBUG5 //!< For verbosity +5 debugging messages +}; diff --git a/src/lib/base/Event.cpp b/src/lib/base/Event.cpp new file mode 100644 index 0000000..f2c1a12 --- /dev/null +++ b/src/lib/base/Event.cpp @@ -0,0 +1,100 @@ +/* + * 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 "base/Event.h" +#include "base/EventQueue.h" + +// +// Event +// + +Event::Event() : + m_type(kUnknown), + m_target(NULL), + m_data(NULL), + m_flags(0), + m_dataObject(nullptr) +{ + // do nothing +} + +Event::Event(Type type, void* target, void* data, Flags flags) : + m_type(type), + m_target(target), + m_data(data), + m_flags(flags), + m_dataObject(nullptr) +{ + // do nothing +} + +Event::Type +Event::getType() const +{ + return m_type; +} + +void* +Event::getTarget() const +{ + return m_target; +} + +void* +Event::getData() const +{ + return m_data; +} + +EventData* +Event::getDataObject() const +{ + return m_dataObject; +} + +Event::Flags +Event::getFlags() const +{ + return m_flags; +} + +void +Event::deleteData(const Event& event) +{ + switch (event.getType()) { + case kUnknown: + case kQuit: + case kSystem: + case kTimer: + break; + + default: + if ((event.getFlags() & kDontFreeData) == 0) { + free(event.getData()); + delete event.getDataObject(); + } + break; + } +} + +void +Event::setDataObject(EventData* dataObject) +{ + assert(m_dataObject == nullptr); + m_dataObject = dataObject; +} diff --git a/src/lib/base/Event.h b/src/lib/base/Event.h new file mode 100644 index 0000000..2741813 --- /dev/null +++ b/src/lib/base/Event.h @@ -0,0 +1,126 @@ +/* + * 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/basic_types.h" +#include "common/stdmap.h" + +class EventData { +public: + EventData() { } + virtual ~EventData() { } +}; + +//! Event +/*! +A \c Event holds an event type and a pointer to event data. +*/ +class Event { +public: + typedef UInt32 Type; + enum { + kUnknown, //!< The event type is unknown + kQuit, //!< The quit event + kSystem, //!< The data points to a system event type + kTimer, //!< The data points to timer info + kLast //!< Must be last + }; + + typedef UInt32 Flags; + enum { + kNone = 0x00, //!< No flags + kDeliverImmediately = 0x01, //!< Dispatch and free event immediately + kDontFreeData = 0x02 //!< Don't free data in deleteData + }; + + Event(); + + //! Create \c Event with data (POD) + /*! + The \p data must be POD (plain old data) allocated by malloc(), + which means it cannot have a constructor, destructor or be + composed of any types that do. For non-POD (normal C++ objects + use \c setDataObject(). + \p target is the intended recipient of the event. + \p flags is any combination of \c Flags. + */ + Event(Type type, void* target = NULL, void* data = NULL, + Flags flags = kNone); + + //! @name manipulators + //@{ + + //! Release event data + /*! + Deletes event data for the given event (using free()). + */ + static void deleteData(const Event&); + + //! Set data (non-POD) + /*! + Set non-POD (non plain old data), where delete is called when the event + is deleted, and the destructor is called. + */ + void setDataObject(EventData* dataObject); + + //@} + //! @name accessors + //@{ + + //! Get event type + /*! + Returns the event type. + */ + Type getType() const; + + //! Get the event target + /*! + Returns the event target. + */ + void* getTarget() const; + + //! Get the event data (POD). + /*! + Returns the event data (POD). + */ + void* getData() const; + + //! Get the event data (non-POD) + /*! + Returns the event data (non-POD). The difference between this and + \c getData() is that when delete is called on this data, so non-POD + (non plain old data) dtor is called. + */ + EventData* getDataObject() const; + + //! Get event flags + /*! + Returns the event flags. + */ + Flags getFlags() const; + + //@} + +private: + Type m_type; + void* m_target; + void* m_data; + Flags m_flags; + EventData* m_dataObject; +}; diff --git a/src/lib/base/EventQueue.cpp b/src/lib/base/EventQueue.cpp new file mode 100644 index 0000000..b17e35b --- /dev/null +++ b/src/lib/base/EventQueue.cpp @@ -0,0 +1,658 @@ +/* + * 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 "base/EventQueue.h" + +#include "mt/Mutex.h" +#include "mt/Lock.h" +#include "arch/Arch.h" +#include "base/SimpleEventQueueBuffer.h" +#include "base/Stopwatch.h" +#include "base/IEventJob.h" +#include "base/EventTypes.h" +#include "base/Log.h" +#include "base/XBase.h" +#include "../gui/src/ShutdownCh.h" + +EVENT_TYPE_ACCESSOR(Client) +EVENT_TYPE_ACCESSOR(IStream) +EVENT_TYPE_ACCESSOR(IpcClient) +EVENT_TYPE_ACCESSOR(IpcClientProxy) +EVENT_TYPE_ACCESSOR(IpcServer) +EVENT_TYPE_ACCESSOR(IpcServerProxy) +EVENT_TYPE_ACCESSOR(IDataSocket) +EVENT_TYPE_ACCESSOR(IListenSocket) +EVENT_TYPE_ACCESSOR(ISocket) +EVENT_TYPE_ACCESSOR(OSXScreen) +EVENT_TYPE_ACCESSOR(ClientListener) +EVENT_TYPE_ACCESSOR(ClientProxy) +EVENT_TYPE_ACCESSOR(ClientProxyUnknown) +EVENT_TYPE_ACCESSOR(Server) +EVENT_TYPE_ACCESSOR(ServerApp) +EVENT_TYPE_ACCESSOR(IKeyState) +EVENT_TYPE_ACCESSOR(IPrimaryScreen) +EVENT_TYPE_ACCESSOR(IScreen) +EVENT_TYPE_ACCESSOR(Clipboard) +EVENT_TYPE_ACCESSOR(File) + +// interrupt handler. this just adds a quit event to the queue. +static +void +interrupt(Arch::ESignal, void* data) +{ + EventQueue* events = static_cast<EventQueue*>(data); + events->addEvent(Event(Event::kQuit)); +} + + +// +// EventQueue +// + +EventQueue::EventQueue() : + m_systemTarget(0), + m_nextType(Event::kLast), + m_typesForClient(NULL), + m_typesForIStream(NULL), + m_typesForIpcClient(NULL), + m_typesForIpcClientProxy(NULL), + m_typesForIpcServer(NULL), + m_typesForIpcServerProxy(NULL), + m_typesForIDataSocket(NULL), + m_typesForIListenSocket(NULL), + m_typesForISocket(NULL), + m_typesForOSXScreen(NULL), + m_typesForClientListener(NULL), + m_typesForClientProxy(NULL), + m_typesForClientProxyUnknown(NULL), + m_typesForServer(NULL), + m_typesForServerApp(NULL), + m_typesForIKeyState(NULL), + m_typesForIPrimaryScreen(NULL), + m_typesForIScreen(NULL), + m_typesForClipboard(NULL), + m_typesForFile(NULL), + m_readyMutex(new Mutex), + m_readyCondVar(new CondVar<bool>(m_readyMutex, false)) +{ + m_mutex = ARCH->newMutex(); + ARCH->setSignalHandler(Arch::kINTERRUPT, &interrupt, this); + ARCH->setSignalHandler(Arch::kTERMINATE, &interrupt, this); + m_buffer = new SimpleEventQueueBuffer; +} + +EventQueue::~EventQueue() +{ + delete m_buffer; + delete m_readyCondVar; + delete m_readyMutex; + + ARCH->setSignalHandler(Arch::kINTERRUPT, NULL, NULL); + ARCH->setSignalHandler(Arch::kTERMINATE, NULL, NULL); + ARCH->closeMutex(m_mutex); +} + +void +EventQueue::loop() +{ + m_buffer->init(); + { + Lock lock(m_readyMutex); + *m_readyCondVar = true; + m_readyCondVar->signal(); + } + LOG((CLOG_DEBUG "event queue is ready")); + while (!m_pending.empty()) { + LOG((CLOG_DEBUG "add pending events to buffer")); + Event& event = m_pending.front(); + addEventToBuffer(event); + m_pending.pop(); + } + + Event event; + getEvent(event); + while (event.getType() != Event::kQuit) { + dispatchEvent(event); + Event::deleteData(event); + getEvent(event); + } +} + +Event::Type +EventQueue::registerTypeOnce(Event::Type& type, const char* name) +{ + ArchMutexLock lock(m_mutex); + if (type == Event::kUnknown) { + m_typeMap.insert(std::make_pair(m_nextType, name)); + m_nameMap.insert(std::make_pair(name, m_nextType)); + LOG((CLOG_DEBUG1 "registered event type %s as %d", name, m_nextType)); + type = m_nextType++; + } + return type; +} + +const char* +EventQueue::getTypeName(Event::Type type) +{ + switch (type) { + case Event::kUnknown: + return "nil"; + + case Event::kQuit: + return "quit"; + + case Event::kSystem: + return "system"; + + case Event::kTimer: + return "timer"; + + default: + TypeMap::const_iterator i = m_typeMap.find(type); + if (i == m_typeMap.end()) { + return "<unknown>"; + } + else { + return i->second; + } + } +} + +void +EventQueue::adoptBuffer(IEventQueueBuffer* buffer) +{ + ArchMutexLock lock(m_mutex); + + LOG((CLOG_DEBUG "adopting new buffer")); + + if (m_events.size() != 0) { + // this can come as a nasty surprise to programmers expecting + // their events to be raised, only to have them deleted. + LOG((CLOG_DEBUG "discarding %d event(s)", m_events.size())); + } + + // discard old buffer and old events + delete m_buffer; + for (EventTable::iterator i = m_events.begin(); i != m_events.end(); ++i) { + Event::deleteData(i->second); + } + m_events.clear(); + m_oldEventIDs.clear(); + + // use new buffer + m_buffer = buffer; + if (m_buffer == NULL) { + m_buffer = new SimpleEventQueueBuffer; + } +} + +bool +EventQueue::parent_requests_shutdown() const +{ + char ch; + return m_parentStream.try_read_char(ch) && ch == ShutdownCh; +} + +bool +EventQueue::getEvent(Event& event, double timeout) +{ + Stopwatch timer(true); +retry: + // before handling any events make sure we don't need to shutdown + if (parent_requests_shutdown()) { + event = Event(Event::kQuit); + return false; + } + // if no events are waiting then handle timers and then wait + while (m_buffer->isEmpty()) { + // handle timers first + if (hasTimerExpired(event)) { + return true; + } + + // get time remaining in timeout + double timeLeft = timeout - timer.getTime(); + if (timeout >= 0.0 && timeLeft <= 0.0) { + return false; + } + + // get time until next timer expires. if there is a timer + // and it'll expire before the client's timeout then use + // that duration for our timeout instead. + double timerTimeout = getNextTimerTimeout(); + if (timeout < 0.0 || (timerTimeout >= 0.0 && timerTimeout < timeLeft)) { + timeLeft = timerTimeout; + } + + // wait for an event + m_buffer->waitForEvent(timeLeft); + } + + // get the event + UInt32 dataID; + IEventQueueBuffer::Type type = m_buffer->getEvent(event, dataID); + switch (type) { + case IEventQueueBuffer::kNone: + if (timeout < 0.0 || timeout <= timer.getTime()) { + // don't want to fail if client isn't expecting that + // so if getEvent() fails with an infinite timeout + // then just try getting another event. + goto retry; + } + return false; + + case IEventQueueBuffer::kSystem: + return true; + + case IEventQueueBuffer::kUser: + { + ArchMutexLock lock(m_mutex); + event = removeEvent(dataID); + return true; + } + + default: + assert(0 && "invalid event type"); + return false; + } +} + +bool +EventQueue::dispatchEvent(const Event& event) +{ + void* target = event.getTarget(); + IEventJob* job = getHandler(event.getType(), target); + if (job == NULL) { + job = getHandler(Event::kUnknown, target); + } + if (job != NULL) { + job->run(event); + return true; + } + return false; +} + +void +EventQueue::addEvent(const Event& event) +{ + // discard bogus event types + switch (event.getType()) { + case Event::kUnknown: + case Event::kSystem: + case Event::kTimer: + return; + + default: + break; + } + + if ((event.getFlags() & Event::kDeliverImmediately) != 0) { + dispatchEvent(event); + Event::deleteData(event); + } + else if (!(*m_readyCondVar)) { + m_pending.push(event); + } + else { + addEventToBuffer(event); + } +} + +void +EventQueue::addEventToBuffer(const Event& event) +{ + ArchMutexLock lock(m_mutex); + + // store the event's data locally + UInt32 eventID = saveEvent(event); + + // add it + if (!m_buffer->addEvent(eventID)) { + // failed to send event + removeEvent(eventID); + Event::deleteData(event); + } +} + +EventQueueTimer* +EventQueue::newTimer(double duration, void* target) +{ + assert(duration > 0.0); + + EventQueueTimer* timer = m_buffer->newTimer(duration, false); + if (target == NULL) { + target = timer; + } + ArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(Timer(timer, duration, + duration + m_time.getTime(), target, false)); + return timer; +} + +EventQueueTimer* +EventQueue::newOneShotTimer(double duration, void* target) +{ + assert(duration > 0.0); + + EventQueueTimer* timer = m_buffer->newTimer(duration, true); + if (target == NULL) { + target = timer; + } + ArchMutexLock lock(m_mutex); + m_timers.insert(timer); + // initial duration is requested duration plus whatever's on + // the clock currently because the latter will be subtracted + // the next time we check for timers. + m_timerQueue.push(Timer(timer, duration, + duration + m_time.getTime(), target, true)); + return timer; +} + +void +EventQueue::deleteTimer(EventQueueTimer* timer) +{ + ArchMutexLock lock(m_mutex); + for (TimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + if (index->getTimer() == timer) { + m_timerQueue.erase(index); + break; + } + } + Timers::iterator index = m_timers.find(timer); + if (index != m_timers.end()) { + m_timers.erase(index); + } + m_buffer->deleteTimer(timer); +} + +void +EventQueue::adoptHandler(Event::Type type, void* target, IEventJob* handler) +{ + ArchMutexLock lock(m_mutex); + IEventJob*& job = m_handlers[target][type]; + delete job; + job = handler; +} + +void +EventQueue::removeHandler(Event::Type type, void* target) +{ + IEventJob* handler = NULL; + { + ArchMutexLock lock(m_mutex); + HandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + TypeHandlerTable& typeHandlers = index->second; + TypeHandlerTable::iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + handler = index2->second; + typeHandlers.erase(index2); + } + } + } + delete handler; +} + +void +EventQueue::removeHandlers(void* target) +{ + std::vector<IEventJob*> handlers; + { + ArchMutexLock lock(m_mutex); + HandlerTable::iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + // copy to handlers array and clear table for target + TypeHandlerTable& typeHandlers = index->second; + for (TypeHandlerTable::iterator index2 = typeHandlers.begin(); + index2 != typeHandlers.end(); ++index2) { + handlers.push_back(index2->second); + } + typeHandlers.clear(); + } + } + + // delete handlers + for (std::vector<IEventJob*>::iterator index = handlers.begin(); + index != handlers.end(); ++index) { + delete *index; + } +} + +bool +EventQueue::isEmpty() const +{ + return (m_buffer->isEmpty() && getNextTimerTimeout() != 0.0); +} + +IEventJob* +EventQueue::getHandler(Event::Type type, void* target) const +{ + ArchMutexLock lock(m_mutex); + HandlerTable::const_iterator index = m_handlers.find(target); + if (index != m_handlers.end()) { + const TypeHandlerTable& typeHandlers = index->second; + TypeHandlerTable::const_iterator index2 = typeHandlers.find(type); + if (index2 != typeHandlers.end()) { + return index2->second; + } + } + return NULL; +} + +UInt32 +EventQueue::saveEvent(const Event& event) +{ + // choose id + UInt32 id; + if (!m_oldEventIDs.empty()) { + // reuse an id + id = m_oldEventIDs.back(); + m_oldEventIDs.pop_back(); + } + else { + // make a new id + id = static_cast<UInt32>(m_events.size()); + } + + // save data + m_events[id] = event; + return id; +} + +Event +EventQueue::removeEvent(UInt32 eventID) +{ + // look up id + EventTable::iterator index = m_events.find(eventID); + if (index == m_events.end()) { + return Event(); + } + + // get data + Event event = index->second; + m_events.erase(index); + + // save old id for reuse + m_oldEventIDs.push_back(eventID); + + return event; +} + +bool +EventQueue::hasTimerExpired(Event& event) +{ + // return true if there's a timer in the timer priority queue that + // has expired. if returning true then fill in event appropriately + // and reset and reinsert the timer. + if (m_timerQueue.empty()) { + return false; + } + + // get time elapsed since last check + const double time = m_time.getTime(); + m_time.reset(); + + // countdown elapsed time + for (TimerQueue::iterator index = m_timerQueue.begin(); + index != m_timerQueue.end(); ++index) { + (*index) -= time; + } + + // done if no timers are expired + if (m_timerQueue.top() > 0.0) { + return false; + } + + // remove timer from queue + Timer timer = m_timerQueue.top(); + m_timerQueue.pop(); + + // prepare event and reset the timer's clock + timer.fillEvent(m_timerEvent); + event = Event(Event::kTimer, timer.getTarget(), &m_timerEvent); + timer.reset(); + + // reinsert timer into queue if it's not a one-shot + if (!timer.isOneShot()) { + m_timerQueue.push(timer); + } + + return true; +} + +double +EventQueue::getNextTimerTimeout() const +{ + // return -1 if no timers, 0 if the top timer has expired, otherwise + // the time until the top timer in the timer priority queue will + // expire. + if (m_timerQueue.empty()) { + return -1.0; + } + if (m_timerQueue.top() <= 0.0) { + return 0.0; + } + return m_timerQueue.top(); +} + +Event::Type +EventQueue::getRegisteredType(const String& name) const +{ + NameMap::const_iterator found = m_nameMap.find(name); + if (found != m_nameMap.end()) + return found->second; + + return Event::kUnknown; +} + +void* +EventQueue::getSystemTarget() +{ + // any unique arbitrary pointer will do + return &m_systemTarget; +} + +void +EventQueue::waitForReady() const +{ + double timeout = ARCH->time() + 10; + Lock lock(m_readyMutex); + + while (!m_readyCondVar->wait()) { + if (ARCH->time() > timeout) { + throw std::runtime_error("event queue is not ready within 5 sec"); + } + } +} + +// +// EventQueue::Timer +// + +EventQueue::Timer::Timer(EventQueueTimer* timer, double timeout, + double initialTime, void* target, bool oneShot) : + m_timer(timer), + m_timeout(timeout), + m_target(target), + m_oneShot(oneShot), + m_time(initialTime) +{ + assert(m_timeout > 0.0); +} + +EventQueue::Timer::~Timer() +{ + // do nothing +} + +void +EventQueue::Timer::reset() +{ + m_time = m_timeout; +} + +EventQueue::Timer& +EventQueue::Timer::operator-=(double dt) +{ + m_time -= dt; + return *this; +} + +EventQueue::Timer::operator double() const +{ + return m_time; +} + +bool +EventQueue::Timer::isOneShot() const +{ + return m_oneShot; +} + +EventQueueTimer* +EventQueue::Timer::getTimer() const +{ + return m_timer; +} + +void* +EventQueue::Timer::getTarget() const +{ + return m_target; +} + +void +EventQueue::Timer::fillEvent(TimerEvent& event) const +{ + event.m_timer = m_timer; + event.m_count = 0; + if (m_time <= 0.0) { + event.m_count = static_cast<UInt32>((m_timeout - m_time) / m_timeout); + } +} + +bool +EventQueue::Timer::operator<(const Timer& t) const +{ + return m_time < t.m_time; +} diff --git a/src/lib/base/EventQueue.h b/src/lib/base/EventQueue.h new file mode 100644 index 0000000..97e7fba --- /dev/null +++ b/src/lib/base/EventQueue.h @@ -0,0 +1,200 @@ +/* + * 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 "mt/CondVar.h" +#include "arch/IArchMultithread.h" +#include "base/IEventQueue.h" +#include "base/Event.h" +#include "base/PriorityQueue.h" +#include "base/Stopwatch.h" +#include "common/stdmap.h" +#include "common/stdset.h" +#include "base/NonBlockingStream.h" + +#include <queue> + +class Mutex; + +//! Event queue +/*! +An event queue that implements the platform independent parts and +delegates the platform dependent parts to a subclass. +*/ +class EventQueue : public IEventQueue { +public: + EventQueue(); + virtual ~EventQueue(); + + // IEventQueue overrides + virtual void loop(); + virtual void adoptBuffer(IEventQueueBuffer*); + virtual bool getEvent(Event& event, double timeout = -1.0); + virtual bool dispatchEvent(const Event& event); + virtual void addEvent(const Event& event); + virtual EventQueueTimer* + newTimer(double duration, void* target); + virtual EventQueueTimer* + newOneShotTimer(double duration, void* target); + virtual void deleteTimer(EventQueueTimer*); + virtual void adoptHandler(Event::Type type, + void* target, IEventJob* handler); + virtual void removeHandler(Event::Type type, void* target); + virtual void removeHandlers(void* target); + virtual Event::Type + registerTypeOnce(Event::Type& type, const char* name); + virtual bool isEmpty() const; + virtual IEventJob* getHandler(Event::Type type, void* target) const; + virtual const char* getTypeName(Event::Type type); + virtual Event::Type + getRegisteredType(const String& name) const; + void* getSystemTarget(); + virtual void waitForReady() const; + +private: + UInt32 saveEvent(const Event& event); + Event removeEvent(UInt32 eventID); + bool hasTimerExpired(Event& event); + double getNextTimerTimeout() const; + void addEventToBuffer(const Event& event); + bool parent_requests_shutdown() const; + +private: + class Timer { + public: + Timer(EventQueueTimer*, double timeout, double initialTime, + void* target, bool oneShot); + ~Timer(); + + void reset(); + + Timer& operator-=(double); + + operator double() const; + + bool isOneShot() const; + EventQueueTimer* + getTimer() const; + void* getTarget() const; + void fillEvent(TimerEvent&) const; + + bool operator<(const Timer&) const; + + private: + EventQueueTimer* m_timer; + double m_timeout; + void* m_target; + bool m_oneShot; + double m_time; + }; + + typedef std::set<EventQueueTimer*> Timers; + typedef PriorityQueue<Timer> TimerQueue; + typedef std::map<UInt32, Event> EventTable; + typedef std::vector<UInt32> EventIDList; + typedef std::map<Event::Type, const char*> TypeMap; + typedef std::map<String, Event::Type> NameMap; + typedef std::map<Event::Type, IEventJob*> TypeHandlerTable; + typedef std::map<void*, TypeHandlerTable> HandlerTable; + + int m_systemTarget; + ArchMutex m_mutex; + + // registered events + Event::Type m_nextType; + TypeMap m_typeMap; + NameMap m_nameMap; + + // buffer of events + IEventQueueBuffer* m_buffer; + + // saved events + EventTable m_events; + EventIDList m_oldEventIDs; + + // timers + Stopwatch m_time; + Timers m_timers; + TimerQueue m_timerQueue; + TimerEvent m_timerEvent; + + // event handlers + HandlerTable m_handlers; + +public: + // + // Event type providers. + // + ClientEvents& forClient(); + IStreamEvents& forIStream(); + IpcClientEvents& forIpcClient(); + IpcClientProxyEvents& forIpcClientProxy(); + IpcServerEvents& forIpcServer(); + IpcServerProxyEvents& forIpcServerProxy(); + IDataSocketEvents& forIDataSocket(); + IListenSocketEvents& forIListenSocket(); + ISocketEvents& forISocket(); + OSXScreenEvents& forOSXScreen(); + ClientListenerEvents& forClientListener(); + ClientProxyEvents& forClientProxy(); + ClientProxyUnknownEvents& forClientProxyUnknown(); + ServerEvents& forServer(); + ServerAppEvents& forServerApp(); + IKeyStateEvents& forIKeyState(); + IPrimaryScreenEvents& forIPrimaryScreen(); + IScreenEvents& forIScreen(); + ClipboardEvents& forClipboard(); + FileEvents& forFile(); + +private: + ClientEvents* m_typesForClient; + IStreamEvents* m_typesForIStream; + IpcClientEvents* m_typesForIpcClient; + IpcClientProxyEvents* m_typesForIpcClientProxy; + IpcServerEvents* m_typesForIpcServer; + IpcServerProxyEvents* m_typesForIpcServerProxy; + IDataSocketEvents* m_typesForIDataSocket; + IListenSocketEvents* m_typesForIListenSocket; + ISocketEvents* m_typesForISocket; + OSXScreenEvents* m_typesForOSXScreen; + ClientListenerEvents* m_typesForClientListener; + ClientProxyEvents* m_typesForClientProxy; + ClientProxyUnknownEvents* m_typesForClientProxyUnknown; + ServerEvents* m_typesForServer; + ServerAppEvents* m_typesForServerApp; + IKeyStateEvents* m_typesForIKeyState; + IPrimaryScreenEvents* m_typesForIPrimaryScreen; + IScreenEvents* m_typesForIScreen; + ClipboardEvents* m_typesForClipboard; + FileEvents* m_typesForFile; + Mutex* m_readyMutex; + CondVar<bool>* m_readyCondVar; + std::queue<Event> m_pending; + NonBlockingStream m_parentStream; +}; + +#define EVENT_TYPE_ACCESSOR(type_) \ +type_##Events& \ +EventQueue::for##type_() { \ + if (m_typesFor##type_ == NULL) { \ + m_typesFor##type_ = new type_##Events(); \ + m_typesFor##type_->setEvents(dynamic_cast<IEventQueue*>(this)); \ + } \ + return *m_typesFor##type_; \ +} diff --git a/src/lib/base/EventTypes.cpp b/src/lib/base/EventTypes.cpp new file mode 100644 index 0000000..9a3e46c --- /dev/null +++ b/src/lib/base/EventTypes.cpp @@ -0,0 +1,203 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 "base/EventTypes.h" +#include "base/IEventQueue.h" + +#include <assert.h> +#include <stddef.h> + +EventTypes::EventTypes() : + m_events(NULL) +{ +} + +IEventQueue* +EventTypes::getEvents() const +{ + assert(m_events != NULL); + return m_events; +} + +void +EventTypes::setEvents(IEventQueue* events) +{ + m_events = events; +} + +// +// Client +// + +REGISTER_EVENT(Client, connected) +REGISTER_EVENT(Client, connectionFailed) +REGISTER_EVENT(Client, disconnected) + +// +// IStream +// + +REGISTER_EVENT(IStream, inputReady) +REGISTER_EVENT(IStream, outputFlushed) +REGISTER_EVENT(IStream, outputError) +REGISTER_EVENT(IStream, inputShutdown) +REGISTER_EVENT(IStream, outputShutdown) + +// +// IpcClient +// + +REGISTER_EVENT(IpcClient, connected) +REGISTER_EVENT(IpcClient, messageReceived) + +// +// IpcClientProxy +// + +REGISTER_EVENT(IpcClientProxy, messageReceived) +REGISTER_EVENT(IpcClientProxy, disconnected) + +// +// IpcServerProxy +// + +REGISTER_EVENT(IpcServerProxy, messageReceived) + +// +// IDataSocket +// + +REGISTER_EVENT(IDataSocket, connected) +REGISTER_EVENT(IDataSocket, secureConnected) +REGISTER_EVENT(IDataSocket, connectionFailed) + +// +// IListenSocket +// + +REGISTER_EVENT(IListenSocket, connecting) + +// +// ISocket +// + +REGISTER_EVENT(ISocket, disconnected) +REGISTER_EVENT(ISocket, stopRetry) + +// +// OSXScreen +// + +REGISTER_EVENT(OSXScreen, confirmSleep) + +// +// ClientListener +// + +REGISTER_EVENT(ClientListener, accepted) +REGISTER_EVENT(ClientListener, connected) + +// +// ClientProxy +// + +REGISTER_EVENT(ClientProxy, ready) +REGISTER_EVENT(ClientProxy, disconnected) + +// +// ClientProxyUnknown +// + +REGISTER_EVENT(ClientProxyUnknown, success) +REGISTER_EVENT(ClientProxyUnknown, failure) + +// +// Server +// + +REGISTER_EVENT(Server, error) +REGISTER_EVENT(Server, connected) +REGISTER_EVENT(Server, disconnected) +REGISTER_EVENT(Server, switchToScreen) +REGISTER_EVENT(Server, switchInDirection) +REGISTER_EVENT(Server, keyboardBroadcast) +REGISTER_EVENT(Server, lockCursorToScreen) +REGISTER_EVENT(Server, screenSwitched) + +// +// ServerApp +// + +REGISTER_EVENT(ServerApp, reloadConfig) +REGISTER_EVENT(ServerApp, forceReconnect) +REGISTER_EVENT(ServerApp, resetServer) + +// +// IKeyState +// + +REGISTER_EVENT(IKeyState, keyDown) +REGISTER_EVENT(IKeyState, keyUp) +REGISTER_EVENT(IKeyState, keyRepeat) + +// +// IPrimaryScreen +// + +REGISTER_EVENT(IPrimaryScreen, buttonDown) +REGISTER_EVENT(IPrimaryScreen, buttonUp) +REGISTER_EVENT(IPrimaryScreen, motionOnPrimary) +REGISTER_EVENT(IPrimaryScreen, motionOnSecondary) +REGISTER_EVENT(IPrimaryScreen, wheel) +REGISTER_EVENT(IPrimaryScreen, screensaverActivated) +REGISTER_EVENT(IPrimaryScreen, screensaverDeactivated) +REGISTER_EVENT(IPrimaryScreen, hotKeyDown) +REGISTER_EVENT(IPrimaryScreen, hotKeyUp) +REGISTER_EVENT(IPrimaryScreen, fakeInputBegin) +REGISTER_EVENT(IPrimaryScreen, fakeInputEnd) + +// +// IScreen +// + +REGISTER_EVENT(IScreen, error) +REGISTER_EVENT(IScreen, shapeChanged) +REGISTER_EVENT(IScreen, suspend) +REGISTER_EVENT(IScreen, resume) + +// +// IpcServer +// + +REGISTER_EVENT(IpcServer, clientConnected) +REGISTER_EVENT(IpcServer, messageReceived) + +// +// Clipboard +// + +REGISTER_EVENT(Clipboard, clipboardGrabbed) +REGISTER_EVENT(Clipboard, clipboardChanged) +REGISTER_EVENT(Clipboard, clipboardSending) + +// +// File +// + +REGISTER_EVENT(File, fileChunkSending) +REGISTER_EVENT(File, fileRecieveCompleted) +REGISTER_EVENT(File, keepAlive) diff --git a/src/lib/base/EventTypes.h b/src/lib/base/EventTypes.h new file mode 100644 index 0000000..d044c86 --- /dev/null +++ b/src/lib/base/EventTypes.h @@ -0,0 +1,754 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "base/Event.h" + +class IEventQueue; + +class EventTypes { +public: + EventTypes(); + void setEvents(IEventQueue* events); + +protected: + IEventQueue* getEvents() const; + +private: + IEventQueue* m_events; +}; + +#define REGISTER_EVENT(type_, name_) \ +Event::Type \ +type_##Events::name_() \ +{ \ + return getEvents()->registerTypeOnce(m_##name_, __FUNCTION__); \ +} + +class ClientEvents : public EventTypes { +public: + ClientEvents() : + m_connected(Event::kUnknown), + m_connectionFailed(Event::kUnknown), + m_disconnected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the connected event type. This is sent when the client has + successfully connected to the server. + */ + Event::Type connected(); + + //! Get connection failed event type + /*! + Returns the connection failed event type. This is sent when the + server fails for some reason. The event data is a FailInfo*. + */ + Event::Type connectionFailed(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when the client + has disconnected from the server (and only after having successfully + connected). + */ + Event::Type disconnected(); + + //@} + +private: + Event::Type m_connected; + Event::Type m_connectionFailed; + Event::Type m_disconnected; +}; + +class IStreamEvents : public EventTypes { +public: + IStreamEvents() : + m_inputReady(Event::kUnknown), + m_outputFlushed(Event::kUnknown), + m_outputError(Event::kUnknown), + m_inputShutdown(Event::kUnknown), + m_outputShutdown(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get input ready event type + /*! + Returns the input ready event type. A stream sends this event + when \c read() will return with data. + */ + Event::Type inputReady(); + + //! Get output flushed event type + /*! + Returns the output flushed event type. A stream sends this event + when the output buffer has been flushed. If there have been no + writes since the event was posted, calling \c shutdownOutput() or + \c close() will not discard any data and \c flush() will return + immediately. + */ + Event::Type outputFlushed(); + + //! Get output error event type + /*! + Returns the output error event type. A stream sends this event + when a write has failed. + */ + Event::Type outputError(); + + //! Get input shutdown event type + /*! + Returns the input shutdown event type. This is sent when the + input side of the stream has shutdown. When the input has + shutdown, no more data will ever be available to read. + */ + Event::Type inputShutdown(); + + //! Get output shutdown event type + /*! + Returns the output shutdown event type. This is sent when the + output side of the stream has shutdown. When the output has + shutdown, no more data can ever be written to the stream. Any + attempt to do so will generate a output error event. + */ + Event::Type outputShutdown(); + + //@} + +private: + Event::Type m_inputReady; + Event::Type m_outputFlushed; + Event::Type m_outputError; + Event::Type m_inputShutdown; + Event::Type m_outputShutdown; +}; + +class IpcClientEvents : public EventTypes { +public: + IpcClientEvents() : + m_connected(Event::kUnknown), + m_messageReceived(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when the socket is connected. + Event::Type connected(); + + //! Raised when a message is received. + Event::Type messageReceived(); + + //@} + +private: + Event::Type m_connected; + Event::Type m_messageReceived; +}; + +class IpcClientProxyEvents : public EventTypes { +public: + IpcClientProxyEvents() : + m_messageReceived(Event::kUnknown), + m_disconnected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when the server receives a message from a client. + Event::Type messageReceived(); + + //! Raised when the client disconnects from the server. + Event::Type disconnected(); + + //@} + +private: + Event::Type m_messageReceived; + Event::Type m_disconnected; +}; + +class IpcServerEvents : public EventTypes { +public: + IpcServerEvents() : + m_clientConnected(Event::kUnknown), + m_messageReceived(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when we have created the client proxy. + Event::Type clientConnected(); + + //! Raised when a message is received through a client proxy. + Event::Type messageReceived(); + + //@} + +private: + Event::Type m_clientConnected; + Event::Type m_messageReceived; +}; + +class IpcServerProxyEvents : public EventTypes { +public: + IpcServerProxyEvents() : + m_messageReceived(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Raised when the client receives a message from the server. + Event::Type messageReceived(); + + //@} + +private: + Event::Type m_messageReceived; +}; + +class IDataSocketEvents : public EventTypes { +public: + IDataSocketEvents() : + m_connected(Event::kUnknown), + m_secureConnected(Event::kUnknown), + m_connectionFailed(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get connected event type + /*! + Returns the socket connected event type. A socket sends this + event when a remote connection has been established. + */ + Event::Type connected(); + + //! Get secure connected event type + /*! + Returns the secure socket connected event type. A secure socket sends + this event when a remote connection has been established. + */ + Event::Type secureConnected(); + + //! Get connection failed event type + /*! + Returns the socket connection failed event type. A socket sends + this event when an attempt to connect to a remote port has failed. + The data is a pointer to a ConnectionFailedInfo. + */ + Event::Type connectionFailed(); + + //@} + +private: + Event::Type m_connected; + Event::Type m_secureConnected; + Event::Type m_connectionFailed; +}; + +class IListenSocketEvents : public EventTypes { +public: + IListenSocketEvents() : + m_connecting(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get connecting event type + /*! + Returns the socket connecting event type. A socket sends this + event when a remote connection is waiting to be accepted. + */ + Event::Type connecting(); + + //@} + +private: + Event::Type m_connecting; +}; + +class ISocketEvents : public EventTypes { +public: + ISocketEvents() : + m_disconnected(Event::kUnknown), + m_stopRetry(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get disconnected event type + /*! + Returns the socket disconnected event type. A socket sends this + event when the remote side of the socket has disconnected or + shutdown both input and output. + */ + Event::Type disconnected(); + + //! Get stop retry event type + /*! + Returns the stop retry event type. This is sent when the client + doesn't want to reconnect after it disconnects from the server. + */ + Event::Type stopRetry(); + + //@} + +private: + Event::Type m_disconnected; + Event::Type m_stopRetry; +}; + +class OSXScreenEvents : public EventTypes { +public: + OSXScreenEvents() : + m_confirmSleep(Event::kUnknown) { } + + //! @name accessors + //@{ + + Event::Type confirmSleep(); + + //@} + +private: + Event::Type m_confirmSleep; +}; + +class ClientListenerEvents : public EventTypes { +public: + ClientListenerEvents() : + m_accepted(Event::kUnknown), + m_connected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get accepted event type + /*! + Returns the accepted event type. This is sent whenever a server + accepts a client. + */ + Event::Type accepted(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent whenever a + a client connects. + */ + Event::Type connected(); + + //@} + +private: + Event::Type m_accepted; + Event::Type m_connected; +}; + +class ClientProxyEvents : public EventTypes { +public: + ClientProxyEvents() : + m_ready(Event::kUnknown), + m_disconnected(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get ready event type + /*! + Returns the ready event type. This is sent when the client has + completed the initial handshake. Until it is sent, the client is + not fully connected. + */ + Event::Type ready(); + + //! Get disconnect event type + /*! + Returns the disconnect event type. This is sent when the client + disconnects or is disconnected. The target is getEventTarget(). + */ + Event::Type disconnected(); + + //@} + +private: + Event::Type m_ready; + Event::Type m_disconnected; +}; + +class ClientProxyUnknownEvents : public EventTypes { +public: + ClientProxyUnknownEvents() : + m_success(Event::kUnknown), + m_failure(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get success event type + /*! + Returns the success event type. This is sent when the client has + correctly responded to the hello message. The target is this. + */ + Event::Type success(); + + //! Get failure event type + /*! + Returns the failure event type. This is sent when a client fails + to correctly respond to the hello message. The target is this. + */ + Event::Type failure(); + + //@} + +private: + Event::Type m_success; + Event::Type m_failure; +}; + +class ServerEvents : public EventTypes { +public: + ServerEvents() : + m_error(Event::kUnknown), + m_connected(Event::kUnknown), + m_disconnected(Event::kUnknown), + m_switchToScreen(Event::kUnknown), + m_switchInDirection(Event::kUnknown), + m_keyboardBroadcast(Event::kUnknown), + m_lockCursorToScreen(Event::kUnknown), + m_screenSwitched(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get error event type + /*! + Returns the error event type. This is sent when the server fails + for some reason. + */ + Event::Type error(); + + //! Get connected event type + /*! + Returns the connected event type. This is sent when a client screen + has connected. The event data is a \c ScreenConnectedInfo* that + indicates the connected screen. + */ + Event::Type connected(); + + //! Get disconnected event type + /*! + Returns the disconnected event type. This is sent when all the + clients have disconnected. + */ + Event::Type disconnected(); + + //! Get switch to screen event type + /*! + Returns the switch to screen event type. The server responds to this + by switching screens. The event data is a \c SwitchToScreenInfo* + that indicates the target screen. + */ + Event::Type switchToScreen(); + + //! Get switch in direction event type + /*! + Returns the switch in direction event type. The server responds to this + by switching screens. The event data is a \c SwitchInDirectionInfo* + that indicates the target direction. + */ + Event::Type switchInDirection(); + + //! Get keyboard broadcast event type + /*! + Returns the keyboard broadcast event type. The server responds + to this by turning on keyboard broadcasting or turning it off. The + event data is a \c KeyboardBroadcastInfo*. + */ + Event::Type keyboardBroadcast(); + + //! Get lock cursor event type + /*! + Returns the lock cursor event type. The server responds to this + by locking the cursor to the active screen or unlocking it. The + event data is a \c LockCursorToScreenInfo*. + */ + Event::Type lockCursorToScreen(); + + //! Get screen switched event type + /*! + Returns the screen switched event type. This is raised when the + screen has been switched to a client. + */ + Event::Type screenSwitched(); + + //@} + +private: + Event::Type m_error; + Event::Type m_connected; + Event::Type m_disconnected; + Event::Type m_switchToScreen; + Event::Type m_switchInDirection; + Event::Type m_keyboardBroadcast; + Event::Type m_lockCursorToScreen; + Event::Type m_screenSwitched; +}; + +class ServerAppEvents : public EventTypes { +public: + ServerAppEvents() : + m_reloadConfig(Event::kUnknown), + m_forceReconnect(Event::kUnknown), + m_resetServer(Event::kUnknown) { } + + //! @name accessors + //@{ + + Event::Type reloadConfig(); + Event::Type forceReconnect(); + Event::Type resetServer(); + + //@} + +private: + Event::Type m_reloadConfig; + Event::Type m_forceReconnect; + Event::Type m_resetServer; +}; + +class IKeyStateEvents : public EventTypes { +public: + IKeyStateEvents() : + m_keyDown(Event::kUnknown), + m_keyUp(Event::kUnknown), + m_keyRepeat(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get key down event type. Event data is KeyInfo*, count == 1. + Event::Type keyDown(); + + //! Get key up event type. Event data is KeyInfo*, count == 1. + Event::Type keyUp(); + + //! Get key repeat event type. Event data is KeyInfo*. + Event::Type keyRepeat(); + + //@} + +private: + Event::Type m_keyDown; + Event::Type m_keyUp; + Event::Type m_keyRepeat; +}; + +class IPrimaryScreenEvents : public EventTypes { +public: + IPrimaryScreenEvents() : + m_buttonDown(Event::kUnknown), + m_buttonUp(Event::kUnknown), + m_motionOnPrimary(Event::kUnknown), + m_motionOnSecondary(Event::kUnknown), + m_wheel(Event::kUnknown), + m_screensaverActivated(Event::kUnknown), + m_screensaverDeactivated(Event::kUnknown), + m_hotKeyDown(Event::kUnknown), + m_hotKeyUp(Event::kUnknown), + m_fakeInputBegin(Event::kUnknown), + m_fakeInputEnd(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! button down event type. Event data is ButtonInfo*. + Event::Type buttonDown(); + + //! button up event type. Event data is ButtonInfo*. + Event::Type buttonUp(); + + //! mouse motion on the primary screen event type + /*! + Event data is MotionInfo* and the values are an absolute position. + */ + Event::Type motionOnPrimary(); + + //! mouse motion on a secondary screen event type + /*! + Event data is MotionInfo* and the values are motion deltas not + absolute coordinates. + */ + Event::Type motionOnSecondary(); + + //! mouse wheel event type. Event data is WheelInfo*. + Event::Type wheel(); + + //! screensaver activated event type + Event::Type screensaverActivated(); + + //! screensaver deactivated event type + Event::Type screensaverDeactivated(); + + //! hot key down event type. Event data is HotKeyInfo*. + Event::Type hotKeyDown(); + + //! hot key up event type. Event data is HotKeyInfo*. + Event::Type hotKeyUp(); + + //! start of fake input event type + Event::Type fakeInputBegin(); + + //! end of fake input event type + Event::Type fakeInputEnd(); + + //@} + +private: + Event::Type m_buttonDown; + Event::Type m_buttonUp; + Event::Type m_motionOnPrimary; + Event::Type m_motionOnSecondary; + Event::Type m_wheel; + Event::Type m_screensaverActivated; + Event::Type m_screensaverDeactivated; + Event::Type m_hotKeyDown; + Event::Type m_hotKeyUp; + Event::Type m_fakeInputBegin; + Event::Type m_fakeInputEnd; +}; + +class IScreenEvents : public EventTypes { +public: + IScreenEvents() : + m_error(Event::kUnknown), + m_shapeChanged(Event::kUnknown), + m_suspend(Event::kUnknown), + m_resume(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get error event type + /*! + Returns the error event type. This is sent whenever the screen has + failed for some reason (e.g. the X Windows server died). + */ + Event::Type error(); + + //! Get shape changed event type + /*! + Returns the shape changed event type. This is sent whenever the + screen's shape changes. + */ + Event::Type shapeChanged(); + + //! Get suspend event type + /*! + Returns the suspend event type. This is sent whenever the system goes + to sleep or a user session is deactivated (fast user switching). + */ + Event::Type suspend(); + + //! Get resume event type + /*! + Returns the resume event type. This is sent whenever the system wakes + up or a user session is activated (fast user switching). + */ + Event::Type resume(); + + //@} + +private: + Event::Type m_error; + Event::Type m_shapeChanged; + Event::Type m_suspend; + Event::Type m_resume; +}; + +class ClipboardEvents : public EventTypes { +public: + ClipboardEvents() : + m_clipboardGrabbed(Event::kUnknown), + m_clipboardChanged(Event::kUnknown), + m_clipboardSending(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Get clipboard grabbed event type + /*! + Returns the clipboard grabbed event type. This is sent whenever the + clipboard is grabbed by some other application so we don't own it + anymore. The data is a pointer to a ClipboardInfo. + */ + Event::Type clipboardGrabbed(); + + //! Get clipboard changed event type + /*! + Returns the clipboard changed event type. This is sent whenever the + contents of the clipboard has changed. The data is a pointer to a + IScreen::ClipboardInfo. + */ + Event::Type clipboardChanged(); + + //! Clipboard sending event type + /*! + Returns the clipboard sending event type. This is used to send + clipboard chunks. + */ + Event::Type clipboardSending(); + + //@} + +private: + Event::Type m_clipboardGrabbed; + Event::Type m_clipboardChanged; + Event::Type m_clipboardSending; +}; + +class FileEvents : public EventTypes { +public: + FileEvents() : + m_fileChunkSending(Event::kUnknown), + m_fileRecieveCompleted(Event::kUnknown), + m_keepAlive(Event::kUnknown) { } + + //! @name accessors + //@{ + + //! Sending a file chunk + Event::Type fileChunkSending(); + + //! Completed receiving a file + Event::Type fileRecieveCompleted(); + + //! Send a keep alive + Event::Type keepAlive(); + + //@} + +private: + Event::Type m_fileChunkSending; + Event::Type m_fileRecieveCompleted; + Event::Type m_keepAlive; +}; diff --git a/src/lib/base/FunctionEventJob.cpp b/src/lib/base/FunctionEventJob.cpp new file mode 100644 index 0000000..705e058 --- /dev/null +++ b/src/lib/base/FunctionEventJob.cpp @@ -0,0 +1,44 @@ +/* + * 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 "base/FunctionEventJob.h" + +// +// FunctionEventJob +// + +FunctionEventJob::FunctionEventJob( + void (*func)(const Event&, void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +FunctionEventJob::~FunctionEventJob() +{ + // do nothing +} + +void +FunctionEventJob::run(const Event& event) +{ + if (m_func != NULL) { + m_func(event, m_arg); + } +} diff --git a/src/lib/base/FunctionEventJob.h b/src/lib/base/FunctionEventJob.h new file mode 100644 index 0000000..4b2c2fc --- /dev/null +++ b/src/lib/base/FunctionEventJob.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 "base/IEventJob.h" + +//! Use a function as an event job +/*! +An event job class that invokes a function. +*/ +class FunctionEventJob : public IEventJob { +public: + //! run() invokes \c func(arg) + FunctionEventJob(void (*func)(const Event&, void*), void* arg = NULL); + virtual ~FunctionEventJob(); + + // IEventJob overrides + virtual void run(const Event&); + +private: + void (*m_func)(const Event&, void*); + void* m_arg; +}; diff --git a/src/lib/base/FunctionJob.cpp b/src/lib/base/FunctionJob.cpp new file mode 100644 index 0000000..859010e --- /dev/null +++ b/src/lib/base/FunctionJob.cpp @@ -0,0 +1,43 @@ +/* + * 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 "base/FunctionJob.h" + +// +// FunctionJob +// + +FunctionJob::FunctionJob(void (*func)(void*), void* arg) : + m_func(func), + m_arg(arg) +{ + // do nothing +} + +FunctionJob::~FunctionJob() +{ + // do nothing +} + +void +FunctionJob::run() +{ + if (m_func != NULL) { + m_func(m_arg); + } +} diff --git a/src/lib/base/FunctionJob.h b/src/lib/base/FunctionJob.h new file mode 100644 index 0000000..9cdfa9d --- /dev/null +++ b/src/lib/base/FunctionJob.h @@ -0,0 +1,39 @@ +/* + * 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 "base/IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a function. +*/ +class FunctionJob : public IJob { +public: + //! run() invokes \c func(arg) + FunctionJob(void (*func)(void*), void* arg = NULL); + virtual ~FunctionJob(); + + // IJob overrides + virtual void run(); + +private: + void (*m_func)(void*); + void* m_arg; +}; diff --git a/src/lib/base/IEventJob.h b/src/lib/base/IEventJob.h new file mode 100644 index 0000000..3e4a420 --- /dev/null +++ b/src/lib/base/IEventJob.h @@ -0,0 +1,33 @@ +/* + * 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" + +class Event; + +//! Event handler interface +/*! +An event job is an interface for executing a event handler. +*/ +class IEventJob : public IInterface { +public: + //! Run the job + virtual void run(const Event&) = 0; +}; diff --git a/src/lib/base/IEventQueue.h b/src/lib/base/IEventQueue.h new file mode 100644 index 0000000..cd4f0b3 --- /dev/null +++ b/src/lib/base/IEventQueue.h @@ -0,0 +1,251 @@ +/* + * 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 "base/Event.h" +#include "base/String.h" + +class IEventJob; +class IEventQueueBuffer; + +// Opaque type for timer info. This is defined by subclasses of +// IEventQueueBuffer. +class EventQueueTimer; + +// Event type registration classes. +class ClientEvents; +class IStreamEvents; +class IpcClientEvents; +class IpcClientProxyEvents; +class IpcServerEvents; +class IpcServerProxyEvents; +class IDataSocketEvents; +class IListenSocketEvents; +class ISocketEvents; +class OSXScreenEvents; +class ClientListenerEvents; +class ClientProxyEvents; +class ClientProxyUnknownEvents; +class ServerEvents; +class ServerAppEvents; +class IKeyStateEvents; +class IPrimaryScreenEvents; +class IScreenEvents; +class ClipboardEvents; +class FileEvents; + +//! Event queue interface +/*! +An event queue provides a queue of Events. Clients can block waiting +on any event becoming available at the head of the queue and can place +new events at the end of the queue. Clients can also add and remove +timers which generate events periodically. +*/ +class IEventQueue : public IInterface { +public: + class TimerEvent { + public: + EventQueueTimer* m_timer; //!< The timer + UInt32 m_count; //!< Number of repeats + }; + + //! @name manipulators + //@{ + + //! Loop the event queue until quit + /*! + Dequeues and dispatches events until the kQuit event is found. + */ + virtual void loop() = 0; + + //! Set the buffer + /*! + Replace the current event queue buffer. Any queued events are + discarded. The queue takes ownership of the buffer. + */ + virtual void adoptBuffer(IEventQueueBuffer*) = 0; + + //! Remove event from queue + /*! + Returns the next event on the queue into \p event. If no event is + available then blocks for up to \p timeout seconds, or forever if + \p timeout is negative. Returns true iff an event was available. + */ + virtual bool getEvent(Event& event, double timeout = -1.0) = 0; + + //! Dispatch an event + /*! + Looks up the dispatcher for the event's target and invokes it. + Returns true iff a dispatcher exists for the target. + */ + virtual bool dispatchEvent(const Event& event) = 0; + + //! Add event to queue + /*! + Adds \p event to the end of the queue. + */ + virtual void addEvent(const Event& event) = 0; + + //! Create a recurring timer + /*! + Creates and returns a timer. An event is returned after \p duration + seconds and the timer is reset to countdown again. When a timer event + is returned the data points to a \c TimerEvent. The client must pass + the returned timer to \c deleteTimer() (whether or not the timer has + expired) to release the timer. The returned timer event uses the + given \p target. If \p target is NULL it uses the returned timer as + the target. + + Events for a single timer don't accumulate in the queue, even if the + client reading events can't keep up. Instead, the \c m_count member + of the \c TimerEvent indicates how many events for the timer would + have been put on the queue since the last event for the timer was + removed (or since the timer was added). + */ + virtual EventQueueTimer* + newTimer(double duration, void* target) = 0; + + //! Create a one-shot timer + /*! + Creates and returns a one-shot timer. An event is returned when + the timer expires and the timer is removed from further handling. + When a timer event is returned the data points to a \c TimerEvent. + The c_count member of the \c TimerEvent is always 1. The client + must pass the returned timer to \c deleteTimer() (whether or not the + timer has expired) to release the timer. The returned timer event + uses the given \p target. If \p target is NULL it uses the returned + timer as the target. + */ + virtual EventQueueTimer* + newOneShotTimer(double duration, + void* target) = 0; + + //! Destroy a timer + /*! + Destroys a previously created timer. The timer is removed from the + queue and will not generate event, even if the timer has expired. + */ + virtual void deleteTimer(EventQueueTimer*) = 0; + + //! Register an event handler for an event type + /*! + Registers an event handler for \p type and \p target. The \p handler + is adopted. Any existing handler for the type,target pair is deleted. + \c dispatchEvent() will invoke \p handler for any event for \p target + of type \p type. If no such handler exists it will use the handler + for \p target and type \p kUnknown if it exists. + */ + virtual void adoptHandler(Event::Type type, + void* target, IEventJob* handler) = 0; + + //! Unregister an event handler for an event type + /*! + Unregisters an event handler for the \p type, \p target pair and + deletes it. + */ + virtual void removeHandler(Event::Type type, void* target) = 0; + + //! Unregister all event handlers for an event target + /*! + Unregisters all event handlers for the \p target and deletes them. + */ + virtual void removeHandlers(void* target) = 0; + + //! Creates a new event type + /*! + If \p type contains \c kUnknown then it is set to a unique event + type id otherwise it is left alone. The final value of \p type + is returned. + */ + virtual Event::Type + registerTypeOnce(Event::Type& type, + const char* name) = 0; + + //! Wait for event queue to become ready + /*! + Blocks on the current thread until the event queue is ready for events to + be added. + */ + virtual void waitForReady() const = 0; + + //@} + //! @name accessors + //@{ + + //! Test if queue is empty + /*! + Returns true iff the queue has no events in it, including timer + events. + */ + virtual bool isEmpty() const = 0; + + //! Get an event handler + /*! + Finds and returns the event handler for the \p type, \p target pair + if it exists, otherwise it returns NULL. + */ + virtual IEventJob* getHandler(Event::Type type, void* target) const = 0; + + //! Get name for event + /*! + Returns the name for the event \p type. This is primarily for + debugging. + */ + virtual const char* getTypeName(Event::Type type) = 0; + + //! Get an event type by name + /*! + Returns the registered type for an event for a given name. + */ + virtual Event::Type getRegisteredType(const String& name) const = 0; + + //! Get the system event type target + /*! + Returns the target to use for dispatching \c Event::kSystem events. + */ + virtual void* getSystemTarget() = 0; + + //@} + + // + // Event type providers. + // + + virtual ClientEvents& forClient() = 0; + virtual IStreamEvents& forIStream() = 0; + virtual IpcClientEvents& forIpcClient() = 0; + virtual IpcClientProxyEvents& forIpcClientProxy() = 0; + virtual IpcServerEvents& forIpcServer() = 0; + virtual IpcServerProxyEvents& forIpcServerProxy() = 0; + virtual IDataSocketEvents& forIDataSocket() = 0; + virtual IListenSocketEvents& forIListenSocket() = 0; + virtual ISocketEvents& forISocket() = 0; + virtual OSXScreenEvents& forOSXScreen() = 0; + virtual ClientListenerEvents& forClientListener() = 0; + virtual ClientProxyEvents& forClientProxy() = 0; + virtual ClientProxyUnknownEvents& forClientProxyUnknown() = 0; + virtual ServerEvents& forServer() = 0; + virtual ServerAppEvents& forServerApp() = 0; + virtual IKeyStateEvents& forIKeyState() = 0; + virtual IPrimaryScreenEvents& forIPrimaryScreen() = 0; + virtual IScreenEvents& forIScreen() = 0; + virtual ClipboardEvents& forClipboard() = 0; + virtual FileEvents& forFile() = 0; +}; diff --git a/src/lib/base/IEventQueueBuffer.h b/src/lib/base/IEventQueueBuffer.h new file mode 100644 index 0000000..b594436 --- /dev/null +++ b/src/lib/base/IEventQueueBuffer.h @@ -0,0 +1,101 @@ +/* + * 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/basic_types.h" + +class Event; +class EventQueueTimer; + +//! Event queue buffer interface +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class IEventQueueBuffer : public IInterface { +public: + enum Type { + kNone, //!< No event is available + kSystem, //!< Event is a system event + kUser //!< Event is a user event + }; + + //! @name manipulators + //@{ + + //! Initialize + /*! + Useful for platform-specific initialisation from a specific thread. + */ + virtual void init() = 0; + + //! Block waiting for an event + /*! + Wait for an event in the event queue buffer for up to \p timeout + seconds. + */ + virtual void waitForEvent(double timeout) = 0; + + //! Get the next event + /*! + Get the next event from the buffer. Return kNone if no event is + available. If a system event is next, return kSystem and fill in + event. The event data in a system event can point to a static + buffer (because Event::deleteData() will not attempt to delete + data in a kSystem event). Otherwise, return kUser and fill in + \p dataID with the value passed to \c addEvent(). + */ + virtual Type getEvent(Event& event, UInt32& dataID) = 0; + + //! Post an event + /*! + Add the given event to the end of the queue buffer. This is a user + event and \c getEvent() must be able to identify it as such and + return \p dataID. This method must cause \c waitForEvent() to + return at some future time if it's blocked waiting on an event. + */ + virtual bool addEvent(UInt32 dataID) = 0; + + //@} + //! @name accessors + //@{ + + //! Check if event queue buffer is empty + /*! + Return true iff the event queue buffer is empty. + */ + virtual bool isEmpty() const = 0; + + //! Create a timer object + /*! + Create and return a timer object. The object is opaque and is + used only by the buffer but it must be a valid object (i.e. + not NULL). + */ + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const = 0; + + //! Destroy a timer object + /*! + Destroy a timer object previously returned by \c newTimer(). + */ + virtual void deleteTimer(EventQueueTimer*) const = 0; + + //@} +}; diff --git a/src/lib/base/IJob.h b/src/lib/base/IJob.h new file mode 100644 index 0000000..f966ec0 --- /dev/null +++ b/src/lib/base/IJob.h @@ -0,0 +1,31 @@ +/* + * 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" + +//! Job interface +/*! +A job is an interface for executing some function. +*/ +class IJob : public IInterface { +public: + //! Run the job + virtual void run() = 0; +}; diff --git a/src/lib/base/ILogOutputter.h b/src/lib/base/ILogOutputter.h new file mode 100644 index 0000000..ab218fc --- /dev/null +++ b/src/lib/base/ILogOutputter.h @@ -0,0 +1,69 @@ +/* + * 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 "base/Log.h" +#include "base/ELevel.h" +#include "common/IInterface.h" + +//! Outputter interface +/*! +Type of outputter interface. The logger performs all output through +outputters. ILogOutputter overrides must not call any log functions +directly or indirectly. +*/ +class ILogOutputter : public IInterface { +public: + //! @name manipulators + //@{ + + //! Open the outputter + /*! + Opens the outputter for writing. Calling this method on an + already open outputter must have no effect. + */ + virtual void open(const char* title) = 0; + + //! Close the outputter + /*! + Close the outputter. Calling this method on an already closed + outputter must have no effect. + */ + virtual void close() = 0; + + //! Show the outputter + /*! + Causes the output to become visible. This generally only makes sense + for a logger 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 show(bool showIfEmpty) = 0; + + //! Write a message with level + /*! + Writes \c message, which has the given \c level, to a log. + If this method returns true then Log will stop passing the + message to all outputters in the outputter chain, otherwise + it continues. Most implementations should return true. + */ + virtual bool write(ELevel level, const char* message) = 0; + + //@} +}; diff --git a/src/lib/base/Log.cpp b/src/lib/base/Log.cpp new file mode 100644 index 0000000..823bf6d --- /dev/null +++ b/src/lib/base/Log.cpp @@ -0,0 +1,309 @@ +/* + * 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" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/String.h" +#include "base/log_outputters.h" +#include "common/Version.h" + +#include <cstdio> +#include <cstring> +#include <iostream> +#include <ctime> + +// names of priorities +static const char* g_priority[] = { + "FATAL", + "ERROR", + "WARNING", + "NOTE", + "INFO", + "DEBUG", + "DEBUG1", + "DEBUG2", + "DEBUG3", + "DEBUG4", + "DEBUG5" +}; + +// number of priorities +static const int g_numPriority = (int)(sizeof(g_priority) / sizeof(g_priority[0])); + +// the default priority +#ifndef NDEBUG +static const int g_defaultMaxPriority = kDEBUG; +#else +static const int g_defaultMaxPriority = kINFO; +#endif + +// +// Log +// + +Log* Log::s_log = NULL; + +Log::Log() +{ + assert(s_log == NULL); + + // create mutex for multithread safe operation + m_mutex = ARCH->newMutex(); + + // other initalization + m_maxPriority = g_defaultMaxPriority; + m_maxNewlineLength = 0; + insert(new ConsoleLogOutputter); + + s_log = this; +} + +Log::Log(Log* src) +{ + s_log = src; +} + +Log::~Log() +{ + // clean up + for (OutputterList::iterator index = m_outputters.begin(); + index != m_outputters.end(); ++index) { + delete *index; + } + for (OutputterList::iterator index = m_alwaysOutputters.begin(); + index != m_alwaysOutputters.end(); ++index) { + delete *index; + } + ARCH->closeMutex(m_mutex); +} + +Log* +Log::getInstance() +{ + assert(s_log != NULL); + return s_log; +} + +const char* +Log::getFilterName() const +{ + return getFilterName(getFilter()); +} + +const char* +Log::getFilterName(int level) const +{ + if (level < 0) { + return "Message"; + } + return g_priority[level]; +} + +void +Log::print(const char* file, int line, const char* fmt, ...) +{ + // check if fmt begins with a priority argument + ELevel priority = kINFO; + if ((strlen(fmt) > 2) && (fmt[0] == '%' && fmt[1] == 'z')) { + + // 060 in octal is 0 (48 in decimal), so subtracting this converts ascii + // number it a true number. we could use atoi instead, but this is how + // it was done originally. + priority = (ELevel)(fmt[2] - '\060'); + + // move the pointer on past the debug priority char + fmt += 3; + } + + // done if below priority threshold + if (priority > getFilter()) { + return; + } + + // compute prefix padding length + char stack[1024]; + + // compute suffix padding length + int sPad = m_maxNewlineLength; + + // print to buffer, leaving space for a newline at the end and prefix + // at the beginning. + char* buffer = stack; + int len = (int)(sizeof(stack) / sizeof(stack[0])); + while (true) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len - sPad, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > (int)len) { + if (buffer != stack) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if the buffer was big enough then continue + else { + break; + } + } + + // print the prefix to the buffer. leave space for priority label. + // do not prefix time and file for kPRINT (CLOG_PRINT) + if (priority != kPRINT) { + + struct tm *tm; + char timestamp[50]; + time_t t; + time(&t); + tm = localtime(&t); + sprintf(timestamp, "%04i-%02i-%02iT%02i:%02i:%02i", tm->tm_year + 1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + + // square brackets, spaces, comma and null terminator take about 10 + size_t size = 10; + size += strlen(timestamp); + size += strlen(g_priority[priority]); + size += strlen(buffer); +#ifndef NDEBUG + size += strlen(file); + // assume there is no file contains over 100k lines of code + size += 6; +#endif + char* message = new char[size]; + +#ifndef NDEBUG + sprintf(message, "[%s] %s: %s\n\t%s,%d", timestamp, g_priority[priority], buffer, file, line); +#else + sprintf(message, "[%s] %s: %s", timestamp, g_priority[priority], buffer); +#endif + + output(priority, message); + delete[] message; + } else { + output(priority, buffer); + } + + // clean up + if (buffer != stack) { + delete[] buffer; + } +} + +void +Log::insert(ILogOutputter* outputter, bool alwaysAtHead) +{ + assert(outputter != NULL); + + ArchMutexLock lock(m_mutex); + if (alwaysAtHead) { + m_alwaysOutputters.push_front(outputter); + } + else { + m_outputters.push_front(outputter); + } + + outputter->open(kAppVersion); + + // Issue 41 + // don't show log unless user requests it, as some users find this + // feature irritating (i.e. when they lose network connectivity). + // in windows the log window can be displayed by selecting "show log" + // from the barrier system tray icon. + // if this causes problems for other architectures, then a different + // work around should be attempted. + //outputter->show(false); +} + +void +Log::remove(ILogOutputter* outputter) +{ + ArchMutexLock lock(m_mutex); + m_outputters.remove(outputter); + m_alwaysOutputters.remove(outputter); +} + +void +Log::pop_front(bool alwaysAtHead) +{ + ArchMutexLock lock(m_mutex); + OutputterList* list = alwaysAtHead ? &m_alwaysOutputters : &m_outputters; + if (!list->empty()) { + delete list->front(); + list->pop_front(); + } +} + +bool +Log::setFilter(const char* maxPriority) +{ + if (maxPriority != NULL) { + for (int i = 0; i < g_numPriority; ++i) { + if (strcmp(maxPriority, g_priority[i]) == 0) { + setFilter(i); + return true; + } + } + return false; + } + return true; +} + +void +Log::setFilter(int maxPriority) +{ + ArchMutexLock lock(m_mutex); + m_maxPriority = maxPriority; +} + +int +Log::getFilter() const +{ + ArchMutexLock lock(m_mutex); + return m_maxPriority; +} + +void +Log::output(ELevel priority, char* msg) +{ + assert(priority >= -1 && priority < g_numPriority); + assert(msg != NULL); + if (!msg) return; + + ArchMutexLock lock(m_mutex); + + OutputterList::const_iterator i; + + for (i = m_alwaysOutputters.begin(); i != m_alwaysOutputters.end(); ++i) { + + // write to outputter + (*i)->write(priority, msg); + } + + for (i = m_outputters.begin(); i != m_outputters.end(); ++i) { + + // write to outputter and break out of loop if it returns false + if (!(*i)->write(priority, msg)) { + break; + } + } +} diff --git a/src/lib/base/Log.h b/src/lib/base/Log.h new file mode 100644 index 0000000..1d09be2 --- /dev/null +++ b/src/lib/base/Log.h @@ -0,0 +1,211 @@ +/* + * 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 "arch/Arch.h" +#include "common/common.h" +#include "common/stdlist.h" + +#include <stdarg.h> + +#define CLOG (Log::getInstance()) +#define BYE "\nTry `%s --help' for more information." + +class ILogOutputter; +class Thread; + +//! Logging facility +/*! +The logging class; all console output should go through this class. +It supports multithread safe operation, several message priority levels, +filtering by priority, and output redirection. The macros LOG() and +LOGC() provide convenient access. +*/ +class Log { +public: + Log(); + Log(Log* src); + ~Log(); + + //! @name manipulators + //@{ + + //! Add an outputter to the head of the list + /*! + Inserts an outputter to the head of the outputter list. When the + logger writes a message, it goes to the outputter at the head of + the outputter list. If that outputter's \c write() method returns + true then it also goes to the next outputter, as so on until an + outputter returns false or there are no more outputters. Outputters + still in the outputter list when the log is destroyed will be + deleted. If \c alwaysAtHead is true then the outputter is always + called before all outputters with \c alwaysAtHead false and the + return value of the outputter is ignored. + + By default, the logger has one outputter installed which writes to + the console. + */ + void insert(ILogOutputter* adopted, + bool alwaysAtHead = false); + + //! Remove an outputter from the list + /*! + Removes the first occurrence of the given outputter from the + outputter list. It does nothing if the outputter is not in the + list. The outputter is not deleted. + */ + void remove(ILogOutputter* orphaned); + + //! Remove the outputter from the head of the list + /*! + Removes and deletes the outputter at the head of the outputter list. + This does nothing if the outputter list is empty. Only removes + outputters that were inserted with the matching \c alwaysAtHead. + */ + void pop_front(bool alwaysAtHead = false); + + //! Set the minimum priority filter. + /*! + Set the filter. Messages below this priority are discarded. + The default priority is 4 (INFO) (unless built without NDEBUG + in which case it's 5 (DEBUG)). setFilter(const char*) returns + true if the priority \c name was recognized; if \c name is NULL + then it simply returns true. + */ + bool setFilter(const char* name); + + //! Set the minimum priority filter (by ordinal). + void setFilter(int); + + //@} + //! @name accessors + //@{ + + //! Print a log message + /*! + Print a log message using the printf-like \c format and arguments + preceded by the filename and line number. If \c file is NULL then + neither the file nor the line are printed. + */ + void print(const char* file, int line, + const char* format, ...); + + //! Get the minimum priority level. + int getFilter() const; + + //! Get the filter name of the current filter level. + const char* getFilterName() const; + + //! Get the filter name of a specified filter level. + const char* getFilterName(int level) const; + + //! Get the singleton instance of the log + static Log* getInstance(); + + //! Get the console filter level (messages above this are not sent to console). + int getConsoleMaxLevel() const { return kDEBUG2; } + + //@} + +private: + void output(ELevel priority, char* msg); + +private: + typedef std::list<ILogOutputter*> OutputterList; + + static Log* s_log; + + ArchMutex m_mutex; + OutputterList m_outputters; + OutputterList m_alwaysOutputters; + int m_maxNewlineLength; + int m_maxPriority; +}; + +/*! +\def LOG(arg) +Write to the log. Because macros cannot accept variable arguments, this +should be invoked like so: +\code +LOG((CLOG_XXX "%d and %d are %s", x, y, x == y ? "equal" : "not equal")); +\endcode +In particular, notice the double open and close parentheses. Also note +that there is no comma after the \c CLOG_XXX. The \c XXX should be +replaced by one of enumerants in \c Log::ELevel without the leading +\c k. For example, \c CLOG_INFO. The special \c CLOG_PRINT level will +not be filtered and is never prefixed by the filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is defined during the build then it expands to a +call to Log::print. Otherwise it expands to a call to Log::printt, +which includes the filename and line number. +*/ + +/*! +\def LOGC(expr, arg) +Write to the log if and only if expr is true. Because macros cannot accept +variable arguments, this should be invoked like so: +\code +LOGC(x == y, (CLOG_XXX "%d and %d are equal", x, y)); +\endcode +In particular, notice the parentheses around everything after the boolean +expression. Also note that there is no comma after the \c CLOG_XXX. +The \c XXX should be replaced by one of enumerants in \c Log::ELevel +without the leading \c k. For example, \c CLOG_INFO. The special +\c CLOG_PRINT level will not be filtered and is never prefixed by the +filename and line number. + +If \c NOLOGGING is defined during the build then this macro expands to +nothing. If \c NDEBUG is not defined during the build then it expands +to a call to Log::print that prints the filename and line number, +otherwise it expands to a call that doesn't. +*/ + +#if defined(NOLOGGING) +#define LOG(_a1) +#define LOGC(_a1, _a2) +#define CLOG_TRACE +#elif defined(NDEBUG) +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE NULL, 0, +#else +#define LOG(_a1) CLOG->print _a1 +#define LOGC(_a1, _a2) if (_a1) CLOG->print _a2 +#define CLOG_TRACE __FILE__, __LINE__, +#endif + +// the CLOG_* defines are line and file plus %z and an octal number (060=0, +// 071=9), but the limitation is that once we run out of numbers at either +// end, then we resort to using non-numerical chars. this still works (since +// to deduce the number we subtract octal \060, so '/' is -1, and ':' is 10 + +#define CLOG_PRINT CLOG_TRACE "%z\057" // char is '/' +#define CLOG_CRIT CLOG_TRACE "%z\060" // char is '0' +#define CLOG_ERR CLOG_TRACE "%z\061" +#define CLOG_WARN CLOG_TRACE "%z\062" +#define CLOG_NOTE CLOG_TRACE "%z\063" +#define CLOG_INFO CLOG_TRACE "%z\064" +#define CLOG_DEBUG CLOG_TRACE "%z\065" +#define CLOG_DEBUG1 CLOG_TRACE "%z\066" +#define CLOG_DEBUG2 CLOG_TRACE "%z\067" +#define CLOG_DEBUG3 CLOG_TRACE "%z\070" +#define CLOG_DEBUG4 CLOG_TRACE "%z\071" // char is '9' +#define CLOG_DEBUG5 CLOG_TRACE "%z\072" // char is ':' diff --git a/src/lib/base/NonBlockingStream.cpp b/src/lib/base/NonBlockingStream.cpp new file mode 100644 index 0000000..d44add1 --- /dev/null +++ b/src/lib/base/NonBlockingStream.cpp @@ -0,0 +1,60 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2008 Debauchee Open Source Group + * + * 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(_WIN32) + +#include "base/NonBlockingStream.h" + +#include <unistd.h> // tcgetattr/tcsetattr, read +#include <termios.h> // tcgetattr/tcsetattr +#include <fcntl.h> +#include <errno.h> +#include <assert.h> + +NonBlockingStream::NonBlockingStream(int fd) : + _fd(fd) +{ + // disable ICANON & ECHO so we don't have to wait for a newline + // before we get data (and to keep it from being echoed back out) + termios ta; + tcgetattr(fd, &ta); + _p_ta_previous = new termios(ta); + ta.c_lflag &= ~(ICANON | ECHO); + tcsetattr(fd, TCSANOW, &ta); + + // prevent IO from blocking so we can poll (read()) + int _cntl_previous = fcntl(fd, F_GETFL); + fcntl(fd, F_SETFL, _cntl_previous | O_NONBLOCK); +} + +NonBlockingStream::~NonBlockingStream() +{ + tcsetattr(_fd, TCSANOW, _p_ta_previous); + fcntl(_fd, F_SETFL, _cntl_previous); + delete _p_ta_previous; +} + +bool NonBlockingStream::try_read_char(char &ch) const +{ + int result = read(_fd, &ch, 1); + if (result == 1) + return true; + assert(result == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)); + return false; +} + +#endif // !defined(_WIN32) diff --git a/src/lib/base/NonBlockingStream.h b/src/lib/base/NonBlockingStream.h new file mode 100644 index 0000000..4c27762 --- /dev/null +++ b/src/lib/base/NonBlockingStream.h @@ -0,0 +1,49 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2008 Debauchee Open Source Group + * + * 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 + +// windows doesn't have a unistd.h so this class won't work as-written. +// at the moment barrier doesn't need this functionality on windows so +// it's left as a stub to be optimized out +#if defined(_WIN32) + +class NonBlockingStream +{ +public: + bool try_read_char(char &ch) const { return false; }; +}; + +#else // non-windows platforms + +struct termios; + +class NonBlockingStream +{ +public: + explicit NonBlockingStream(int fd = 0); + ~NonBlockingStream(); + + bool try_read_char(char &ch) const; + +private: + int _fd; + termios * _p_ta_previous; + int _cntl_previous; +}; + +#endif diff --git a/src/lib/base/PriorityQueue.h b/src/lib/base/PriorityQueue.h new file mode 100644 index 0000000..d2ca70e --- /dev/null +++ b/src/lib/base/PriorityQueue.h @@ -0,0 +1,138 @@ +/* + * 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/stdvector.h" + +#include <algorithm> +#include <functional> + +//! A priority queue with an iterator +/*! +This priority queue is the same as a standard priority queue except: +it sorts by std::greater, it has a forward iterator through the elements +(which can appear in any order), and its contents can be swapped. +*/ +template <class T, class Container = std::vector<T>, +#if defined(_MSC_VER) + class Compare = std::greater<Container::value_type> > +#else + class Compare = std::greater<typename Container::value_type> > +#endif +class PriorityQueue { +public: + typedef typename Container::value_type value_type; + typedef typename Container::size_type size_type; + typedef typename Container::iterator iterator; + typedef typename Container::const_iterator const_iterator; + typedef Container container_type; + + PriorityQueue() { } + PriorityQueue(Container& swappedIn) { swap(swappedIn); } + ~PriorityQueue() { } + + //! @name manipulators + //@{ + + //! Add element + void push(const value_type& v) + { + c.push_back(v); + std::push_heap(c.begin(), c.end(), comp); + } + + //! Remove head element + void pop() + { + std::pop_heap(c.begin(), c.end(), comp); + c.pop_back(); + } + + //! Erase element + void erase(iterator i) + { + c.erase(i); + std::make_heap(c.begin(), c.end(), comp); + } + + //! Get start iterator + iterator begin() + { + return c.begin(); + } + + //! Get end iterator + iterator end() + { + return c.end(); + } + + //! Swap contents with another priority queue + void swap(PriorityQueue<T, Container, Compare>& q) + { + c.swap(q.c); + } + + //! Swap contents with another container + void swap(Container& c2) + { + c.swap(c2); + std::make_heap(c.begin(), c.end(), comp); + } + + //@} + //! @name accessors + //@{ + + //! Returns true if there are no elements + bool empty() const + { + return c.empty(); + } + + //! Returns the number of elements + size_type size() const + { + return c.size(); + } + + //! Returns the head element + const value_type& top() const + { + return c.front(); + } + + //! Get start iterator + const_iterator begin() const + { + return c.begin(); + } + + //! Get end iterator + const_iterator end() const + { + return c.end(); + } + + //@} + +private: + Container c; + Compare comp; +}; diff --git a/src/lib/base/SimpleEventQueueBuffer.cpp b/src/lib/base/SimpleEventQueueBuffer.cpp new file mode 100644 index 0000000..b55fe55 --- /dev/null +++ b/src/lib/base/SimpleEventQueueBuffer.cpp @@ -0,0 +1,101 @@ +/* + * 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 "base/SimpleEventQueueBuffer.h" +#include "base/Stopwatch.h" +#include "arch/Arch.h" + +class EventQueueTimer { }; + +// +// SimpleEventQueueBuffer +// + +SimpleEventQueueBuffer::SimpleEventQueueBuffer() +{ + m_queueMutex = ARCH->newMutex(); + m_queueReadyCond = ARCH->newCondVar(); + m_queueReady = false; +} + +SimpleEventQueueBuffer::~SimpleEventQueueBuffer() +{ + ARCH->closeCondVar(m_queueReadyCond); + ARCH->closeMutex(m_queueMutex); +} + +void +SimpleEventQueueBuffer::waitForEvent(double timeout) +{ + ArchMutexLock lock(m_queueMutex); + Stopwatch timer(true); + while (!m_queueReady) { + double timeLeft = timeout; + if (timeLeft >= 0.0) { + timeLeft -= timer.getTime(); + if (timeLeft < 0.0) { + return; + } + } + ARCH->waitCondVar(m_queueReadyCond, m_queueMutex, timeLeft); + } +} + +IEventQueueBuffer::Type +SimpleEventQueueBuffer::getEvent(Event&, UInt32& dataID) +{ + ArchMutexLock lock(m_queueMutex); + if (!m_queueReady) { + return kNone; + } + dataID = m_queue.back(); + m_queue.pop_back(); + m_queueReady = !m_queue.empty(); + return kUser; +} + +bool +SimpleEventQueueBuffer::addEvent(UInt32 dataID) +{ + ArchMutexLock lock(m_queueMutex); + m_queue.push_front(dataID); + if (!m_queueReady) { + m_queueReady = true; + ARCH->broadcastCondVar(m_queueReadyCond); + } + return true; +} + +bool +SimpleEventQueueBuffer::isEmpty() const +{ + ArchMutexLock lock(m_queueMutex); + return !m_queueReady; +} + +EventQueueTimer* +SimpleEventQueueBuffer::newTimer(double, bool) const +{ + return new EventQueueTimer; +} + +void +SimpleEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const +{ + delete timer; +} diff --git a/src/lib/base/SimpleEventQueueBuffer.h b/src/lib/base/SimpleEventQueueBuffer.h new file mode 100644 index 0000000..4aa76d3 --- /dev/null +++ b/src/lib/base/SimpleEventQueueBuffer.h @@ -0,0 +1,52 @@ +/* + * 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 "base/IEventQueueBuffer.h" +#include "arch/IArchMultithread.h" +#include "common/stddeque.h" + +//! In-memory event queue buffer +/*! +An event queue buffer provides a queue of events for an IEventQueue. +*/ +class SimpleEventQueueBuffer : public IEventQueueBuffer { +public: + SimpleEventQueueBuffer(); + ~SimpleEventQueueBuffer(); + + // IEventQueueBuffer overrides + void init() { } + virtual void waitForEvent(double timeout); + virtual Type getEvent(Event& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(EventQueueTimer*) const; + +private: + typedef std::deque<UInt32> EventDeque; + + ArchMutex m_queueMutex; + ArchCond m_queueReadyCond; + bool m_queueReady; + EventDeque m_queue; +}; + diff --git a/src/lib/base/Stopwatch.cpp b/src/lib/base/Stopwatch.cpp new file mode 100644 index 0000000..b9ceb85 --- /dev/null +++ b/src/lib/base/Stopwatch.cpp @@ -0,0 +1,130 @@ +/* + * 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 "base/Stopwatch.h" +#include "arch/Arch.h" + +// +// Stopwatch +// + +Stopwatch::Stopwatch(bool triggered) : + m_mark(0.0), + m_triggered(triggered), + m_stopped(triggered) +{ + if (!triggered) { + m_mark = ARCH->time(); + } +} + +Stopwatch::~Stopwatch() +{ + // do nothing +} + +double +Stopwatch::reset() +{ + if (m_stopped) { + const double dt = m_mark; + m_mark = 0.0; + return dt; + } + else { + const double t = ARCH->time(); + const double dt = t - m_mark; + m_mark = t; + return dt; + } +} + +void +Stopwatch::stop() +{ + if (m_stopped) { + return; + } + + // save the elapsed time + m_mark = ARCH->time() - m_mark; + m_stopped = true; +} + +void +Stopwatch::start() +{ + m_triggered = false; + if (!m_stopped) { + return; + } + + // set the mark such that it reports the time elapsed at stop() + m_mark = ARCH->time() - m_mark; + m_stopped = false; +} + +void +Stopwatch::setTrigger() +{ + stop(); + m_triggered = true; +} + +double +Stopwatch::getTime() +{ + if (m_triggered) { + const double dt = m_mark; + start(); + return dt; + } + else if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +Stopwatch::operator double() +{ + return getTime(); +} + +bool +Stopwatch::isStopped() const +{ + return m_stopped; +} + +double +Stopwatch::getTime() const +{ + if (m_stopped) { + return m_mark; + } + else { + return ARCH->time() - m_mark; + } +} + +Stopwatch::operator double() const +{ + return getTime(); +} diff --git a/src/lib/base/Stopwatch.h b/src/lib/base/Stopwatch.h new file mode 100644 index 0000000..dda74ea --- /dev/null +++ b/src/lib/base/Stopwatch.h @@ -0,0 +1,109 @@ +/* + * 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" + +//! A timer class +/*! +This class measures time intervals. All time interval measurement +should use this class. +*/ +class Stopwatch { +public: + /*! + The default constructor does an implicit reset() or setTrigger(). + If triggered == false then the clock starts ticking. + */ + Stopwatch(bool triggered = false); + ~Stopwatch(); + + //! @name manipulators + //@{ + + //! Reset the timer to zero + /*! + Set the start time to the current time, returning the time since + the last reset. This does not remove the trigger if it's set nor + does it start a stopped clock. If the clock is stopped then + subsequent reset()'s will return 0. + */ + double reset(); + + //! Stop the timer + /*! + Stop the stopwatch. The time interval while stopped is not + counted by the stopwatch. stop() does not remove the trigger. + Has no effect if already stopped. + */ + void stop(); + + //! Start the timer + /*! + Start the stopwatch. start() removes the trigger, even if the + stopwatch was already started. + */ + void start(); + + //! Stop the timer and set the trigger + /*! + setTrigger() stops the clock like stop() except there's an + implicit start() the next time (non-const) getTime() is called. + This is useful when you want the clock to start the first time + you check it. + */ + void setTrigger(); + + //! Get elapsed time + /*! + Returns the time since the last reset() (or calls reset() and + returns zero if the trigger is set). + */ + double getTime(); + //! Same as getTime() + operator double(); + //@} + //! @name accessors + //@{ + + //! Check if timer is stopped + /*! + Returns true if the stopwatch is stopped. + */ + bool isStopped() const; + + // return the time since the last reset(). + //! Get elapsed time + /*! + Returns the time since the last reset(). This cannot trigger the + stopwatch to start and will not clear the trigger. + */ + double getTime() const; + //! Same as getTime() const + operator double() const; + //@} + +private: + double getClock() const; + +private: + double m_mark; + bool m_triggered; + bool m_stopped; +}; diff --git a/src/lib/base/String.cpp b/src/lib/base/String.cpp new file mode 100644 index 0000000..97b8997 --- /dev/null +++ b/src/lib/base/String.cpp @@ -0,0 +1,295 @@ +/* + * 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/Arch.h" +#include "base/String.h" +#include "common/common.h" +#include "common/stdvector.h" + +#include <cctype> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <algorithm> +#include <stdio.h> +#include <cstdarg> +#include <sstream> +#include <iomanip> +#include <algorithm> +#include <cerrno> + +namespace barrier { +namespace string { + +String +format(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + String result = vformat(fmt, args); + va_end(args); + return result; +} + +String +vformat(const char* fmt, va_list args) +{ + // find highest indexed substitution and the locations of substitutions + std::vector<size_t> pos; + std::vector<size_t> width; + std::vector<size_t> index; + size_t maxIndex = 0; + for (const char* scan = fmt; *scan != '\0'; ++scan) { + if (*scan == '%') { + ++scan; + if (*scan == '\0') { + break; + } + else if (*scan == '%') { + // literal + index.push_back(0); + pos.push_back(static_cast<size_t>((scan - 1) - fmt)); + width.push_back(2); + } + else if (*scan == '{') { + // get argument index + char* end; + errno = 0; + long i = strtol(scan + 1, &end, 10); + if (errno || (i < 0) || (*end != '}')) { + // invalid index -- ignore + scan = end - 1; // BUG if there are digits? + } + else { + index.push_back(i); + pos.push_back(static_cast<size_t>((scan - 1) - fmt)); + width.push_back(static_cast<size_t>((end - scan) + 2)); + if (i > maxIndex) { + maxIndex = i; + } + scan = end; + } + } + else { + // improper escape -- ignore + } + } + } + + // get args + std::vector<const char*> value; + std::vector<size_t> length; + value.push_back("%"); + length.push_back(1); + for (int i = 0; i < maxIndex; ++i) { + const char* arg = va_arg(args, const char*); + size_t len = strlen(arg); + value.push_back(arg); + length.push_back(len); + } + + // compute final length + size_t resultLength = strlen(fmt); + const int n = static_cast<int>(pos.size()); + for (int i = 0; i < n; ++i) { + resultLength -= width[i]; + resultLength += length[index[i]]; + } + + // substitute + String result; + result.reserve(resultLength); + size_t src = 0; + for (int i = 0; i < n; ++i) { + result.append(fmt + src, pos[i] - src); + result.append(value[index[i]]); + src = pos[i] + width[i]; + } + result.append(fmt + src); + + return result; +} + +String +sprintf(const char* fmt, ...) +{ + char tmp[1024]; + char* buffer = tmp; + int len = (int)(sizeof(tmp) / sizeof(tmp[0])); + String result; + while (buffer != NULL) { + // try printing into the buffer + va_list args; + va_start(args, fmt); + int n = ARCH->vsnprintf(buffer, len, fmt, args); + va_end(args); + + // if the buffer wasn't big enough then make it bigger and try again + if (n < 0 || n > len) { + if (buffer != tmp) { + delete[] buffer; + } + len *= 2; + buffer = new char[len]; + } + + // if it was big enough then save the string and don't try again + else { + result = buffer; + if (buffer != tmp) { + delete[] buffer; + } + buffer = NULL; + } + } + + return result; +} + +void +findReplaceAll( + String& subject, + const String& find, + const String& replace) +{ + size_t pos = 0; + while ((pos = subject.find(find, pos)) != String::npos) { + subject.replace(pos, find.length(), replace); + pos += replace.length(); + } +} + +String +removeFileExt(String filename) +{ + size_t dot = filename.find_last_of('.'); + + if (dot == String::npos) { + return filename; + } + + return filename.substr(0, dot); +} + +void +toHex(String& subject, int width, const char fill) +{ + std::stringstream ss; + ss << std::hex; + for (unsigned int i = 0; i < subject.length(); i++) { + ss << std::setw(width) << std::setfill(fill) << (int)(unsigned char)subject[i]; + } + + subject = ss.str(); +} + +void +uppercase(String& subject) +{ + std::transform(subject.begin(), subject.end(), subject.begin(), ::toupper); +} + +void +removeChar(String& subject, const char c) +{ + subject.erase(std::remove(subject.begin(), subject.end(), c), subject.end()); +} + +String +sizeTypeToString(size_t n) +{ + std::stringstream ss; + ss << n; + return ss.str(); +} + +size_t +stringToSizeType(String string) +{ + std::istringstream iss(string); + size_t value; + iss >> value; + return value; +} + +std::vector<String> +splitString(String string, const char c) +{ + std::vector<String> results; + + size_t head = 0; + size_t separator = string.find(c); + while (separator != String::npos) { + if (head!=separator) { + results.push_back(string.substr(head, separator - head)); + } + head = separator + 1; + separator = string.find(c, head); + } + + if (head < string.size()) { + results.push_back(string.substr(head, string.size() - head)); + } + + return results; +} + +// +// CaselessCmp +// + +bool +CaselessCmp::cmpEqual( + const String::value_type& a, + const String::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) == tolower(b); +} + +bool +CaselessCmp::cmpLess( + const String::value_type& a, + const String::value_type& b) +{ + // should use std::tolower but not in all versions of libstdc++ have it + return tolower(a) < tolower(b); +} + +bool +CaselessCmp::less(const String& a, const String& b) +{ + return std::lexicographical_compare( + a.begin(), a.end(), + b.begin(), b.end(), + &barrier::string::CaselessCmp::cmpLess); +} + +bool +CaselessCmp::equal(const String& a, const String& b) +{ + return !(less(a, b) || less(b, a)); +} + +bool +CaselessCmp::operator()(const String& a, const String& b) const +{ + return less(a, b); +} + +} +} diff --git a/src/lib/base/String.h b/src/lib/base/String.h new file mode 100644 index 0000000..3661461 --- /dev/null +++ b/src/lib/base/String.h @@ -0,0 +1,135 @@ +/* + * 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 <stdarg.h> +#include <vector> + +// use standard C++ string class for our string class +typedef std::string String; + +namespace barrier { + +//! String utilities +/*! +Provides functions for string manipulation. +*/ +namespace string { + +//! Format positional arguments +/*! +Format a string using positional arguments. fmt has literal +characters and conversion specifications introduced by `\%': +- \%\% -- literal `\%' +- \%{n} -- positional element n, n a positive integer, {} are literal + +All arguments in the variable list are const char*. Positional +elements are indexed from 1. +*/ +String format(const char* fmt, ...); + +//! Format positional arguments +/*! +Same as format() except takes va_list. +*/ +String vformat(const char* fmt, va_list); + +//! Print a string using sprintf-style formatting +/*! +Equivalent to sprintf() except the result is returned as a String. +*/ +String sprintf(const char* fmt, ...); + +//! Find and replace all +/*! +Finds \c find inside \c subject and replaces it with \c replace +*/ +void findReplaceAll(String& subject, const String& find, const String& replace); + +//! Remove file extension +/*! +Finds the last dot and remove all characters from the dot to the end +*/ +String removeFileExt(String filename); + +//! Convert into hexdecimal +/*! +Convert each character in \c subject into hexdecimal form with \c width +*/ +void toHex(String& subject, int width, const char fill = '0'); + +//! Convert to all uppercase +/*! +Convert each character in \c subject to uppercase +*/ +void uppercase(String& subject); + +//! Remove all specific char in suject +/*! +Remove all specific \c c in \c suject +*/ +void removeChar(String& subject, const char c); + +//! Convert a size type to a string +/*! +Convert an size type to a string +*/ +String sizeTypeToString(size_t n); + +//! Convert a string to a size type +/*! +Convert an a \c string to an size type +*/ +size_t stringToSizeType(String string); + +//! Split a string into substrings +/*! +Split a \c string that separated by a \c c into substrings +*/ +std::vector<String> splitString(String string, const char c); + +//! Case-insensitive comparisons +/*! +This class provides case-insensitve comparison functions. +*/ +class CaselessCmp { + public: + //! Same as less() + bool operator()(const String& a, const String& b) const; + + //! Returns true iff \c a is lexicographically less than \c b + static bool less(const String& a, const String& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool equal(const String& a, const String& b); + + //! Returns true iff \c a is lexicographically less than \c b + static bool cmpLess(const String::value_type& a, + const String::value_type& b); + + //! Returns true iff \c a is lexicographically equal to \c b + static bool cmpEqual(const String::value_type& a, + const String::value_type& b); +}; + +} +} diff --git a/src/lib/base/TMethodEventJob.h b/src/lib/base/TMethodEventJob.h new file mode 100644 index 0000000..a65f8c9 --- /dev/null +++ b/src/lib/base/TMethodEventJob.h @@ -0,0 +1,71 @@ +/* + * 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 "IEventJob.h" + +//! Use a member function as an event job +/*! +An event job class that invokes a member function. +*/ +template <class T> +class TMethodEventJob : public IEventJob { +public: + //! run(event) invokes \c object->method(event, arg) + TMethodEventJob(T* object, + void (T::*method)(const Event&, void*), + void* arg = NULL); + virtual ~TMethodEventJob(); + + // IJob overrides + virtual void run(const Event&); + +private: + T* m_object; + void (T::*m_method)(const Event&, void*); + void* m_arg; +}; + +template <class T> +inline +TMethodEventJob<T>::TMethodEventJob(T* object, + void (T::*method)(const Event&, void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template <class T> +inline +TMethodEventJob<T>::~TMethodEventJob() +{ + // do nothing +} + +template <class T> +inline +void +TMethodEventJob<T>::run(const Event& event) +{ + if (m_object != NULL) { + (m_object->*m_method)(event, m_arg); + } +} diff --git a/src/lib/base/TMethodJob.h b/src/lib/base/TMethodJob.h new file mode 100644 index 0000000..ec88f05 --- /dev/null +++ b/src/lib/base/TMethodJob.h @@ -0,0 +1,68 @@ +/* + * 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 "IJob.h" + +//! Use a function as a job +/*! +A job class that invokes a member function. +*/ +template <class T> +class TMethodJob : public IJob { +public: + //! run() invokes \c object->method(arg) + TMethodJob(T* object, void (T::*method)(void*), void* arg = NULL); + virtual ~TMethodJob(); + + // IJob overrides + virtual void run(); + +private: + T* m_object; + void (T::*m_method)(void*); + void* m_arg; +}; + +template <class T> +inline +TMethodJob<T>::TMethodJob(T* object, void (T::*method)(void*), void* arg) : + m_object(object), + m_method(method), + m_arg(arg) +{ + // do nothing +} + +template <class T> +inline +TMethodJob<T>::~TMethodJob() +{ + // do nothing +} + +template <class T> +inline +void +TMethodJob<T>::run() +{ + if (m_object != NULL) { + (m_object->*m_method)(m_arg); + } +} diff --git a/src/lib/base/Unicode.cpp b/src/lib/base/Unicode.cpp new file mode 100644 index 0000000..6a077e7 --- /dev/null +++ b/src/lib/base/Unicode.cpp @@ -0,0 +1,784 @@ +/* + * 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" +#include "base/Unicode.h" + +#include <cstring> + +// +// local utility functions +// + +inline +static +UInt16 +decode16(const UInt8* n, bool byteSwapped) +{ + union x16 { + UInt8 n8[2]; + UInt16 n16; + } c; + if (byteSwapped) { + c.n8[0] = n[1]; + c.n8[1] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + } + return c.n16; +} + +inline +static +UInt32 +decode32(const UInt8* n, bool byteSwapped) +{ + union x32 { + UInt8 n8[4]; + UInt32 n32; + } c; + if (byteSwapped) { + c.n8[0] = n[3]; + c.n8[1] = n[2]; + c.n8[2] = n[1]; + c.n8[3] = n[0]; + } + else { + c.n8[0] = n[0]; + c.n8[1] = n[1]; + c.n8[2] = n[2]; + c.n8[3] = n[3]; + } + return c.n32; +} + +inline +static +void +resetError(bool* errors) +{ + if (errors != NULL) { + *errors = false; + } +} + +inline +static +void +setError(bool* errors) +{ + if (errors != NULL) { + *errors = true; + } +} + + +// +// Unicode +// + +UInt32 Unicode::s_invalid = 0x0000ffff; +UInt32 Unicode::s_replacement = 0x0000fffd; + +bool +Unicode::isUTF8(const String& src) +{ + // convert and test each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + for (UInt32 n = (UInt32)src.size(); n > 0; ) { + if (fromUTF8(data, n) == s_invalid) { + return false; + } + } + return true; +} + +String +Unicode::UTF8ToUCS2(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00010000) { + setError(errors); + c = s_replacement; + } + UInt16 ucs2 = static_cast<UInt16>(c); + dst.append(reinterpret_cast<const char*>(&ucs2), 2); + } + + return dst; +} + +String +Unicode::UTF8ToUCS4(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + dst.append(reinterpret_cast<const char*>(&c), 4); + } + + return dst; +} + +String +Unicode::UTF8ToUTF16(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(2 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + if (c < 0x00010000) { + UInt16 ucs2 = static_cast<UInt16>(c); + dst.append(reinterpret_cast<const char*>(&ucs2), 2); + } + else { + c -= 0x00010000; + UInt16 utf16h = static_cast<UInt16>((c >> 10) + 0xd800); + UInt16 utf16l = static_cast<UInt16>((c & 0x03ff) + 0xdc00); + dst.append(reinterpret_cast<const char*>(&utf16h), 2); + dst.append(reinterpret_cast<const char*>(&utf16l), 2); + } + } + + return dst; +} + +String +Unicode::UTF8ToUTF32(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // get size of input string and reserve some space in output + UInt32 n = (UInt32)src.size(); + String dst; + dst.reserve(4 * n); + + // convert each character + const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str()); + while (n > 0) { + UInt32 c = fromUTF8(data, n); + if (c == s_invalid) { + c = s_replacement; + } + else if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + dst.append(reinterpret_cast<const char*>(&c), 4); + } + + return dst; +} + +String +Unicode::UTF8ToText(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert to wide char + UInt32 size; + wchar_t* tmp = UTF8ToWideChar(src, size, errors); + + // convert string to multibyte + int len = ARCH->convStringWCToMB(NULL, tmp, size, errors); + char* mbs = new char[len + 1]; + ARCH->convStringWCToMB(mbs, tmp, size, errors); + String text(mbs, len); + + // clean up + delete[] mbs; + delete[] tmp; + + return text; +} + +String +Unicode::UCS2ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 1; + return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::UCS4ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 2; + return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::UTF16ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 1; + return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::UTF32ToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert + UInt32 n = (UInt32)src.size() >> 2; + return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors); +} + +String +Unicode::textToUTF8(const String& src, bool* errors) +{ + // default to success + resetError(errors); + + // convert string to wide characters + UInt32 n = (UInt32)src.size(); + int len = ARCH->convStringMBToWC(NULL, src.c_str(), n, errors); + wchar_t* wcs = new wchar_t[len + 1]; + ARCH->convStringMBToWC(wcs, src.c_str(), n, errors); + + // convert to UTF8 + String utf8 = wideCharToUTF8(wcs, len, errors); + + // clean up + delete[] wcs; + + return utf8; +} + +wchar_t* +Unicode::UTF8ToWideChar(const String& src, UInt32& size, bool* errors) +{ + // convert to platform's wide character encoding + String tmp; + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + tmp = UTF8ToUCS2(src, errors); + size = (UInt32)tmp.size() >> 1; + break; + + case IArchString::kUCS4: + tmp = UTF8ToUCS4(src, errors); + size = (UInt32)tmp.size() >> 2; + break; + + case IArchString::kUTF16: + tmp = UTF8ToUTF16(src, errors); + size = (UInt32)tmp.size() >> 1; + break; + + case IArchString::kUTF32: + tmp = UTF8ToUTF32(src, errors); + size = (UInt32)tmp.size() >> 2; + break; + + default: + assert(0 && "unknown wide character encoding"); + } + + // copy to a wchar_t array + wchar_t* dst = new wchar_t[size]; + ::memcpy(dst, tmp.data(), sizeof(wchar_t) * size); + return dst; +} + +String +Unicode::wideCharToUTF8(const wchar_t* src, UInt32 size, bool* errors) +{ + // convert from platform's wide character encoding. + // note -- this must include a wide nul character (independent of + // the String's nul character). + switch (ARCH->getWideCharEncoding()) { + case IArchString::kUCS2: + return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + case IArchString::kUCS4: + return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + case IArchString::kUTF16: + return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + case IArchString::kUTF32: + return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors); + + default: + assert(0 && "unknown wide character encoding"); + return String(); + } +} + +String +Unicode::doUCS2ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +String +Unicode::doUCS4ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + toUTF8(dst, c, errors); + } + + return dst; +} + +String +Unicode::doUTF16ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode16(data, false)) { + case 0x0000feff: + data += 2; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 2; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 2, --n) { + UInt32 c = decode16(data, byteSwapped); + if (c < 0x0000d800 || c > 0x0000dfff) { + toUTF8(dst, c, errors); + } + else if (n == 1) { + // error -- missing second word + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else if (c >= 0x0000d800 && c <= 0x0000dbff) { + UInt32 c2 = decode16(data, byteSwapped); + data += 2; + --n; + if (c2 < 0x0000dc00 || c2 > 0x0000dfff) { + // error -- [d800,dbff] not followed by [dc00,dfff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + else { + c = (((c - 0x0000d800) << 10) | (c2 - 0x0000dc00)) + 0x00010000; + toUTF8(dst, c, errors); + } + } + else { + // error -- [dc00,dfff] without leading [d800,dbff] + setError(errors); + toUTF8(dst, s_replacement, NULL); + } + } + + return dst; +} + +String +Unicode::doUTF32ToUTF8(const UInt8* data, UInt32 n, bool* errors) +{ + // make some space + String dst; + dst.reserve(n); + + // check if first character is 0xfffe or 0xfeff + bool byteSwapped = false; + if (n >= 1) { + switch (decode32(data, false)) { + case 0x0000feff: + data += 4; + --n; + break; + + case 0x0000fffe: + byteSwapped = true; + data += 4; + --n; + break; + + default: + break; + } + } + + // convert each character + for (; n > 0; data += 4, --n) { + UInt32 c = decode32(data, byteSwapped); + if (c >= 0x00110000) { + setError(errors); + c = s_replacement; + } + toUTF8(dst, c, errors); + } + + return dst; +} + +UInt32 +Unicode::fromUTF8(const UInt8*& data, UInt32& n) +{ + assert(data != NULL); + assert(n != 0); + + // compute character encoding length, checking for overlong + // sequences (i.e. characters that don't use the shortest + // possible encoding). + UInt32 size; + if (data[0] < 0x80) { + // 0xxxxxxx + size = 1; + } + else if (data[0] < 0xc0) { + // 10xxxxxx -- in the middle of a multibyte character. counts + // as one invalid character. + --n; + ++data; + return s_invalid; + } + else if (data[0] < 0xe0) { + // 110xxxxx + size = 2; + } + else if (data[0] < 0xf0) { + // 1110xxxx + size = 3; + } + else if (data[0] < 0xf8) { + // 11110xxx + size = 4; + } + else if (data[0] < 0xfc) { + // 111110xx + size = 5; + } + else if (data[0] < 0xfe) { + // 1111110x + size = 6; + } + else { + // invalid sequence. dunno how many bytes to skip so skip one. + --n; + ++data; + return s_invalid; + } + + // make sure we have enough data + if (size > n) { + data += n; + n = 0; + return s_invalid; + } + + // extract character + UInt32 c; + switch (size) { + case 1: + c = static_cast<UInt32>(data[0]); + break; + + case 2: + c = ((static_cast<UInt32>(data[0]) & 0x1f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + case 3: + c = ((static_cast<UInt32>(data[0]) & 0x0f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[2]) & 0x3f) ); + break; + + case 4: + c = ((static_cast<UInt32>(data[0]) & 0x07) << 18) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + case 5: + c = ((static_cast<UInt32>(data[0]) & 0x03) << 24) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 18) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + case 6: + c = ((static_cast<UInt32>(data[0]) & 0x01) << 30) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 24) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 18) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 12) | + ((static_cast<UInt32>(data[1]) & 0x3f) << 6) | + ((static_cast<UInt32>(data[1]) & 0x3f) ); + break; + + default: + assert(0 && "invalid size"); + return s_invalid; + } + + // check that all bytes after the first have the pattern 10xxxxxx. + // truncated sequences are treated as a single malformed character. + bool truncated = false; + switch (size) { + case 6: + if ((data[5] & 0xc0) != 0x80) { + truncated = true; + size = 5; + } + // fall through + + case 5: + if ((data[4] & 0xc0) != 0x80) { + truncated = true; + size = 4; + } + // fall through + + case 4: + if ((data[3] & 0xc0) != 0x80) { + truncated = true; + size = 3; + } + // fall through + + case 3: + if ((data[2] & 0xc0) != 0x80) { + truncated = true; + size = 2; + } + // fall through + + case 2: + if ((data[1] & 0xc0) != 0x80) { + truncated = true; + size = 1; + } + } + + // update parameters + data += size; + n -= size; + + // invalid if sequence was truncated + if (truncated) { + return s_invalid; + } + + // check for characters that didn't use the smallest possible encoding + static UInt32 s_minChar[] = { + 0, + 0x00000000, + 0x00000080, + 0x00000800, + 0x00010000, + 0x00200000, + 0x04000000 + }; + if (c < s_minChar[size]) { + return s_invalid; + } + + // check for characters not in ISO-10646 + if (c >= 0x0000d800 && c <= 0x0000dfff) { + return s_invalid; + } + if (c >= 0x0000fffe && c <= 0x0000ffff) { + return s_invalid; + } + + return c; +} + +void +Unicode::toUTF8(String& dst, UInt32 c, bool* errors) +{ + UInt8 data[6]; + + // handle characters outside the valid range + if ((c >= 0x0000d800 && c <= 0x0000dfff) || c >= 0x80000000) { + setError(errors); + c = s_replacement; + } + + // convert to UTF-8 + if (c < 0x00000080) { + data[0] = static_cast<UInt8>(c); + dst.append(reinterpret_cast<char*>(data), 1); + } + else if (c < 0x00000800) { + data[0] = static_cast<UInt8>(((c >> 6) & 0x0000001f) + 0xc0); + data[1] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 2); + } + else if (c < 0x00010000) { + data[0] = static_cast<UInt8>(((c >> 12) & 0x0000000f) + 0xe0); + data[1] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 3); + } + else if (c < 0x00200000) { + data[0] = static_cast<UInt8>(((c >> 18) & 0x00000007) + 0xf0); + data[1] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[3] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 4); + } + else if (c < 0x04000000) { + data[0] = static_cast<UInt8>(((c >> 24) & 0x00000003) + 0xf8); + data[1] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80); + data[3] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[4] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 5); + } + else if (c < 0x80000000) { + data[0] = static_cast<UInt8>(((c >> 30) & 0x00000001) + 0xfc); + data[1] = static_cast<UInt8>(((c >> 24) & 0x0000003f) + 0x80); + data[2] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80); + data[3] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80); + data[4] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80); + data[5] = static_cast<UInt8>((c & 0x0000003f) + 0x80); + dst.append(reinterpret_cast<char*>(data), 6); + } + else { + assert(0 && "character out of range"); + } +} diff --git a/src/lib/base/Unicode.h b/src/lib/base/Unicode.h new file mode 100644 index 0000000..1391c1e --- /dev/null +++ b/src/lib/base/Unicode.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/>. + */ + +#pragma once + +#include "base/String.h" +#include "common/basic_types.h" + +//! Unicode utility functions +/*! +This class provides functions for converting between various Unicode +encodings and the current locale encoding. +*/ +class Unicode { +public: + //! @name accessors + //@{ + + //! Test UTF-8 string for validity + /*! + Returns true iff the string contains a valid sequence of UTF-8 + encoded characters. + */ + static bool isUTF8(const String&); + + //! Convert from UTF-8 to UCS-2 encoding + /*! + Convert from UTF-8 to UCS-2. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-2. + Decoding errors do not set *errors. + */ + static String UTF8ToUCS2(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to UCS-4 encoding + /*! + Convert from UTF-8 to UCS-4. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UCS-4. + Decoding errors do not set *errors. + */ + static String UTF8ToUCS4(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-16 encoding + /*! + Convert from UTF-8 to UTF-16. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-16. + Decoding errors do not set *errors. + */ + static String UTF8ToUTF16(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to UTF-32 encoding + /*! + Convert from UTF-8 to UTF-32. If errors is not NULL then *errors + is set to true iff any character could not be encoded in UTF-32. + Decoding errors do not set *errors. + */ + static String UTF8ToUTF32(const String&, bool* errors = NULL); + + //! Convert from UTF-8 to the current locale encoding + /*! + Convert from UTF-8 to the current locale encoding. If errors is not + NULL then *errors is set to true iff any character could not be encoded. + Decoding errors do not set *errors. + */ + static String UTF8ToText(const String&, bool* errors = NULL); + + //! Convert from UCS-2 to UTF-8 + /*! + Convert from UCS-2 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UCS2ToUTF8(const String&, bool* errors = NULL); + + //! Convert from UCS-4 to UTF-8 + /*! + Convert from UCS-4 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UCS4ToUTF8(const String&, bool* errors = NULL); + + //! Convert from UTF-16 to UTF-8 + /*! + Convert from UTF-16 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UTF16ToUTF8(const String&, bool* errors = NULL); + + //! Convert from UTF-32 to UTF-8 + /*! + Convert from UTF-32 to UTF-8. If errors is not NULL then *errors is + set to true iff any character could not be decoded. + */ + static String UTF32ToUTF8(const String&, bool* errors = NULL); + + //! Convert from the current locale encoding to UTF-8 + /*! + Convert from the current locale encoding to UTF-8. If errors is not + NULL then *errors is set to true iff any character could not be decoded. + */ + static String textToUTF8(const String&, bool* errors = NULL); + + //@} + +private: + // convert UTF8 to wchar_t string (using whatever encoding is native + // to the platform). caller must delete[] the returned string. the + // string is *not* nul terminated; the length (in characters) is + // returned in size. + static wchar_t* UTF8ToWideChar(const String&, + UInt32& size, bool* errors); + + // convert nul terminated wchar_t string (in platform's native + // encoding) to UTF8. + static String wideCharToUTF8(const wchar_t*, + UInt32 size, bool* errors); + + // internal conversion to UTF8 + static String doUCS2ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static String doUCS4ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static String doUTF16ToUTF8(const UInt8* src, UInt32 n, bool* errors); + static String doUTF32ToUTF8(const UInt8* src, UInt32 n, bool* errors); + + // convert characters to/from UTF8 + static UInt32 fromUTF8(const UInt8*& src, UInt32& size); + static void toUTF8(String& dst, UInt32 c, bool* errors); + +private: + static UInt32 s_invalid; + static UInt32 s_replacement; +}; diff --git a/src/lib/base/XBase.cpp b/src/lib/base/XBase.cpp new file mode 100644 index 0000000..29ae927 --- /dev/null +++ b/src/lib/base/XBase.cpp @@ -0,0 +1,76 @@ +/* + * 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 "base/XBase.h" +#include "base/String.h" + +#include <cerrno> +#include <cstdarg> + +// +// XBase +// + +XBase::XBase() : + std::runtime_error("") +{ + // do nothing +} + +XBase::XBase(const String& msg) : + std::runtime_error(msg) +{ + // do nothing +} + +XBase::~XBase() _NOEXCEPT +{ + // do nothing +} + +const char* +XBase::what() const _NOEXCEPT +{ + const char* what = std::runtime_error::what(); + if (strlen(what) == 0) { + m_what = getWhat(); + return m_what.c_str(); + } + return what; +} + +String +XBase::format(const char* /*id*/, const char* fmt, ...) const throw() +{ + // FIXME -- lookup message string using id as an index. set + // fmt to that string if it exists. + + // format + String result; + va_list args; + va_start(args, fmt); + try { + result = barrier::string::vformat(fmt, args); + } + catch (...) { + // ignore + } + va_end(args); + + return result; +} diff --git a/src/lib/base/XBase.h b/src/lib/base/XBase.h new file mode 100644 index 0000000..3064b6c --- /dev/null +++ b/src/lib/base/XBase.h @@ -0,0 +1,125 @@ +/* + * 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 "base/String.h" +#include "common/stdexcept.h" + +//! Exception base class +/*! +This is the base class of most exception types. +*/ +class XBase : public std::runtime_error { +public: + //! Use getWhat() as the result of what() + XBase(); + //! Use \c msg as the result of what() + XBase(const String& msg); + virtual ~XBase() _NOEXCEPT; + + //! Reason for exception + virtual const char* what() const _NOEXCEPT; + +protected: + //! Get a human readable string describing the exception + virtual String getWhat() const throw() { return ""; } + + //! Format a string + /*! + Looks up a message format using \c id, using \c defaultFormat if + no format can be found, then replaces positional parameters in + the format string and returns the result. + */ + virtual String format(const char* id, + const char* defaultFormat, ...) const throw(); +private: + mutable String m_what; +}; + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const String&. getWhat() is not +declared. +*/ +#define XBASE_SUBCLASS(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const String& msg) : super_(msg) { } \ + virtual ~name_() _NOEXCEPT { } \ +} + +/*! +\def XBASE_SUBCLASS +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const String&. getWhat() must be +implemented. +*/ +#define XBASE_SUBCLASS_WHAT(name_, super_) \ +class name_ : public super_ { \ +public: \ + name_() : super_() { } \ + name_(const String& msg) : super_(msg) { } \ + virtual ~name_() _NOEXCEPT { } \ + \ +protected: \ + virtual String getWhat() const throw(); \ +} + +/*! +\def XBASE_SUBCLASS_FORMAT +Convenience macro to subclass from XBase (or a subclass of it), +providing the c'tor taking a const String&. what() is overridden +to call getWhat() when first called; getWhat() can format the +error message and can call what() to get the message passed to the +c'tor. +*/ +#define XBASE_SUBCLASS_FORMAT(name_, super_) \ +class name_ : public super_ { \ +private: \ + enum EState { kFirst, kFormat, kDone }; \ + \ +public: \ + name_() : super_(), m_state(kDone) { } \ + name_(const String& msg) : super_(msg), m_state(kFirst) { } \ + virtual ~name_() _NOEXCEPT { } \ + \ + virtual const char* what() const _NOEXCEPT \ + { \ + if (m_state == kFirst) { \ + m_state = kFormat; \ + m_formatted = getWhat(); \ + m_state = kDone; \ + } \ + if (m_state == kDone) { \ + return m_formatted.c_str(); \ + } \ + else { \ + return super_::what(); \ + } \ + } \ + \ +protected: \ + virtual String getWhat() const throw(); \ + \ +private: \ + mutable EState m_state; \ + mutable std::string m_formatted; \ +} diff --git a/src/lib/base/log_outputters.cpp b/src/lib/base/log_outputters.cpp new file mode 100644 index 0000000..8e56c26 --- /dev/null +++ b/src/lib/base/log_outputters.cpp @@ -0,0 +1,337 @@ +/* + * 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 "base/log_outputters.h" +#include "base/TMethodJob.h" +#include "arch/Arch.h" + +#include <fstream> + +enum EFileLogOutputter { + kFileSizeLimit = 1024 // kb +}; + +// +// StopLogOutputter +// + +StopLogOutputter::StopLogOutputter() +{ + // do nothing +} + +StopLogOutputter::~StopLogOutputter() +{ + // do nothing +} + +void +StopLogOutputter::open(const char*) +{ + // do nothing +} + +void +StopLogOutputter::close() +{ + // do nothing +} + +void +StopLogOutputter::show(bool) +{ + // do nothing +} + +bool +StopLogOutputter::write(ELevel, const char*) +{ + return false; +} + + +// +// ConsoleLogOutputter +// + +ConsoleLogOutputter::ConsoleLogOutputter() +{ +} + +ConsoleLogOutputter::~ConsoleLogOutputter() +{ +} + +void +ConsoleLogOutputter::open(const char* title) +{ + ARCH->openConsole(title); +} + +void +ConsoleLogOutputter::close() +{ + ARCH->closeConsole(); +} + +void +ConsoleLogOutputter::show(bool showIfEmpty) +{ + ARCH->showConsole(showIfEmpty); +} + +bool +ConsoleLogOutputter::write(ELevel level, const char* msg) +{ + ARCH->writeConsole(level, msg); + return true; +} + +void +ConsoleLogOutputter::flush() +{ + +} + + +// +// SystemLogOutputter +// + +SystemLogOutputter::SystemLogOutputter() +{ + // do nothing +} + +SystemLogOutputter::~SystemLogOutputter() +{ + // do nothing +} + +void +SystemLogOutputter::open(const char* title) +{ + ARCH->openLog(title); +} + +void +SystemLogOutputter::close() +{ + ARCH->closeLog(); +} + +void +SystemLogOutputter::show(bool showIfEmpty) +{ + ARCH->showLog(showIfEmpty); +} + +bool +SystemLogOutputter::write(ELevel level, const char* msg) +{ + ARCH->writeLog(level, msg); + return true; +} + +// +// SystemLogger +// + +SystemLogger::SystemLogger(const char* title, bool blockConsole) : + m_stop(NULL) +{ + // redirect log messages + if (blockConsole) { + m_stop = new StopLogOutputter; + CLOG->insert(m_stop); + } + m_syslog = new SystemLogOutputter; + m_syslog->open(title); + CLOG->insert(m_syslog); +} + +SystemLogger::~SystemLogger() +{ + CLOG->remove(m_syslog); + delete m_syslog; + if (m_stop != NULL) { + CLOG->remove(m_stop); + delete m_stop; + } +} + + +// +// BufferedLogOutputter +// + +BufferedLogOutputter::BufferedLogOutputter(UInt32 maxBufferSize) : + m_maxBufferSize(maxBufferSize) +{ + // do nothing +} + +BufferedLogOutputter::~BufferedLogOutputter() +{ + // do nothing +} + +BufferedLogOutputter::const_iterator +BufferedLogOutputter::begin() const +{ + return m_buffer.begin(); +} + +BufferedLogOutputter::const_iterator +BufferedLogOutputter::end() const +{ + return m_buffer.end(); +} + +void +BufferedLogOutputter::open(const char*) +{ + // do nothing +} + +void +BufferedLogOutputter::close() +{ + // remove all elements from the buffer + m_buffer.clear(); +} + +void +BufferedLogOutputter::show(bool) +{ + // do nothing +} + +bool +BufferedLogOutputter::write(ELevel, const char* message) +{ + while (m_buffer.size() >= m_maxBufferSize) { + m_buffer.pop_front(); + } + m_buffer.push_back(String(message)); + return true; +} + + +// +// FileLogOutputter +// + +FileLogOutputter::FileLogOutputter(const char* logFile) +{ + setLogFilename(logFile); +} + +FileLogOutputter::~FileLogOutputter() +{ +} + +void +FileLogOutputter::setLogFilename(const char* logFile) +{ + assert(logFile != NULL); + m_fileName = logFile; +} + +bool +FileLogOutputter::write(ELevel level, const char *message) +{ + bool moveFile = false; + + std::ofstream m_handle; + m_handle.open(m_fileName.c_str(), std::fstream::app); + if (m_handle.is_open() && m_handle.fail() != true) { + m_handle << message << std::endl; + + // when file size exceeds limits, move to 'old log' filename. + size_t p = m_handle.tellp(); + if (p > (kFileSizeLimit * 1024)) { + moveFile = true; + } + } + m_handle.close(); + + if (moveFile) { + String oldLogFilename = barrier::string::sprintf("%s.1", m_fileName.c_str()); + remove(oldLogFilename.c_str()); + rename(m_fileName.c_str(), oldLogFilename.c_str()); + } + + return true; +} + +void +FileLogOutputter::open(const char *title) {} + +void +FileLogOutputter::close() {} + +void +FileLogOutputter::show(bool showIfEmpty) {} + +// +// MesssageBoxLogOutputter +// + +MesssageBoxLogOutputter::MesssageBoxLogOutputter() +{ + // do nothing +} + +MesssageBoxLogOutputter::~MesssageBoxLogOutputter() +{ + // do nothing +} + +void +MesssageBoxLogOutputter::open(const char* title) +{ + // do nothing +} + +void +MesssageBoxLogOutputter::close() +{ + // do nothing +} + +void +MesssageBoxLogOutputter::show(bool showIfEmpty) +{ + // do nothing +} + +bool +MesssageBoxLogOutputter::write(ELevel level, const char* msg) +{ + // don't spam user with messages. + if (level > kERROR) { + return true; + } + +#if SYSAPI_WIN32 + MessageBox(NULL, msg, CLOG->getFilterName(level), MB_OK); +#endif + + return true; +} diff --git a/src/lib/base/log_outputters.h b/src/lib/base/log_outputters.h new file mode 100644 index 0000000..c4940aa --- /dev/null +++ b/src/lib/base/log_outputters.h @@ -0,0 +1,172 @@ +/* + * 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 "mt/Thread.h" +#include "base/ILogOutputter.h" +#include "base/String.h" +#include "common/basic_types.h" +#include "common/stddeque.h" + +#include <list> +#include <fstream> + +//! Stop traversing log chain outputter +/*! +This outputter performs no output and returns false from \c write(), +causing the logger to stop traversing the outputter chain. Insert +this to prevent already inserted outputters from writing. +*/ +class StopLogOutputter : public ILogOutputter { +public: + StopLogOutputter(); + virtual ~StopLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +}; + +//! Write log to console +/*! +This outputter writes output to the console. The level for each +message is ignored. +*/ +class ConsoleLogOutputter : public ILogOutputter { +public: + ConsoleLogOutputter(); + virtual ~ConsoleLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual void flush(); +}; + +//! Write log to file +/*! +This outputter writes output to the file. The level for each +message is ignored. +*/ + +class FileLogOutputter : public ILogOutputter { +public: + FileLogOutputter(const char* logFile); + virtual ~FileLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + + void setLogFilename(const char* title); + +private: + std::string m_fileName; +}; + +//! Write log to system log +/*! +This outputter writes output to the system log. +*/ +class SystemLogOutputter : public ILogOutputter { +public: + SystemLogOutputter(); + virtual ~SystemLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +}; + +//! Write log to system log only +/*! +Creating an object of this type inserts a StopLogOutputter followed +by a SystemLogOutputter into Log. The destructor removes those +outputters. Add one of these to any scope that needs to write to +the system log (only) and restore the old outputters when exiting +the scope. +*/ +class SystemLogger { +public: + SystemLogger(const char* title, bool blockConsole); + ~SystemLogger(); + +private: + ILogOutputter* m_syslog; + ILogOutputter* m_stop; +}; + +//! Save log history +/*! +This outputter records the last N log messages. +*/ +class BufferedLogOutputter : public ILogOutputter { +private: + typedef std::deque<String> Buffer; + +public: + typedef Buffer::const_iterator const_iterator; + + BufferedLogOutputter(UInt32 maxBufferSize); + virtual ~BufferedLogOutputter(); + + //! @name accessors + //@{ + + //! Get start of buffer + const_iterator begin() const; + + //! Get end of buffer + const_iterator end() const; + + //@} + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +private: + UInt32 m_maxBufferSize; + Buffer m_buffer; +}; + +//! Write log to message box +/*! +The level for each message is ignored. +*/ +class MesssageBoxLogOutputter : public ILogOutputter { +public: + MesssageBoxLogOutputter(); + virtual ~MesssageBoxLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); +}; diff --git a/src/lib/client/CMakeLists.txt b/src/lib/client/CMakeLists.txt new file mode 100644 index 0000000..97dc9db --- /dev/null +++ b/src/lib/client/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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() + +add_library(client STATIC ${sources}) + +if (UNIX) + target_link_libraries(client synlib io) +endif() diff --git a/src/lib/client/Client.cpp b/src/lib/client/Client.cpp new file mode 100644 index 0000000..2158ee2 --- /dev/null +++ b/src/lib/client/Client.cpp @@ -0,0 +1,836 @@ +/* + * 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 "client/Client.h" + +#include "client/ServerProxy.h" +#include "barrier/Screen.h" +#include "barrier/FileChunk.h" +#include "barrier/DropHelper.h" +#include "barrier/PacketStreamFilter.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/protocol_types.h" +#include "barrier/XBarrier.h" +#include "barrier/StreamChunker.h" +#include "barrier/IPlatformScreen.h" +#include "mt/Thread.h" +#include "net/TCPSocket.h" +#include "net/IDataSocket.h" +#include "net/ISocketFactory.h" +#include "net/SecureSocket.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" +#include "common/stdexcept.h" + +#include <cstring> +#include <cstdlib> +#include <sstream> +#include <fstream> + +// +// Client +// + +Client::Client( + IEventQueue* events, + const String& name, const NetworkAddress& address, + ISocketFactory* socketFactory, + barrier::Screen* screen, + ClientArgs const& args) : + m_mock(false), + m_name(name), + m_serverAddress(address), + m_socketFactory(socketFactory), + m_screen(screen), + m_stream(NULL), + m_timer(NULL), + m_server(NULL), + m_ready(false), + m_active(false), + m_suspended(false), + m_connectOnResume(false), + m_events(events), + m_sendFileThread(NULL), + m_writeToDropDirThread(NULL), + m_socket(NULL), + m_useSecureNetwork(args.m_enableCrypto), + m_args(args), + m_enableClipboard(true) +{ + assert(m_socketFactory != NULL); + assert(m_screen != NULL); + + // register suspend/resume event handlers + m_events->adoptHandler(m_events->forIScreen().suspend(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleSuspend)); + m_events->adoptHandler(m_events->forIScreen().resume(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleResume)); + + if (m_args.m_enableDragDrop) { + m_events->adoptHandler(m_events->forFile().fileChunkSending(), + this, + new TMethodEventJob<Client>(this, + &Client::handleFileChunkSending)); + m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(), + this, + new TMethodEventJob<Client>(this, + &Client::handleFileRecieveCompleted)); + } +} + +Client::~Client() +{ + if (m_mock) { + return; + } + + m_events->removeHandler(m_events->forIScreen().suspend(), + getEventTarget()); + m_events->removeHandler(m_events->forIScreen().resume(), + getEventTarget()); + + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + delete m_socketFactory; +} + +void +Client::connect() +{ + if (m_stream != NULL) { + return; + } + if (m_suspended) { + m_connectOnResume = true; + return; + } + + try { + // resolve the server hostname. do this every time we connect + // in case we couldn't resolve the address earlier or the address + // has changed (which can happen frequently if this is a laptop + // being shuttled between various networks). patch by Brent + // Priddy. + m_serverAddress.resolve(); + + // m_serverAddress will be null if the hostname address is not reolved + if (m_serverAddress.getAddress() != NULL) { + // to help users troubleshoot, show server host name (issue: 60) + LOG((CLOG_NOTE "connecting to '%s': %s:%i", + m_serverAddress.getHostname().c_str(), + ARCH->addrToString(m_serverAddress.getAddress()).c_str(), + m_serverAddress.getPort())); + } + + // create the socket + IDataSocket* socket = m_socketFactory->create( + ARCH->getAddrFamily(m_serverAddress.getAddress()), + m_useSecureNetwork); + m_socket = dynamic_cast<TCPSocket*>(socket); + + // filter socket messages, including a packetizing filter + m_stream = socket; + m_stream = new PacketStreamFilter(m_events, m_stream, true); + + // connect + LOG((CLOG_DEBUG1 "connecting to server")); + setupConnecting(); + setupTimer(); + socket->connect(m_serverAddress); + } + catch (XBase& e) { + cleanupTimer(); + cleanupConnecting(); + cleanupStream(); + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(e.what()); + return; + } +} + +void +Client::disconnect(const char* msg) +{ + m_connectOnResume = false; + cleanupTimer(); + cleanupScreen(); + cleanupConnecting(); + cleanupConnection(); + if (msg != NULL) { + sendConnectionFailedEvent(msg); + } + else { + sendEvent(m_events->forClient().disconnected(), NULL); + } +} + +void +Client::handshakeComplete() +{ + m_ready = true; + m_screen->enable(); + sendEvent(m_events->forClient().connected(), NULL); +} + +bool +Client::isConnected() const +{ + return (m_server != NULL); +} + +bool +Client::isConnecting() const +{ + return (m_timer != NULL); +} + +NetworkAddress +Client::getServerAddress() const +{ + return m_serverAddress; +} + +void* +Client::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +Client::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +Client::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + m_screen->getShape(x, y, w, h); +} + +void +Client::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +Client::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool) +{ + m_active = true; + m_screen->mouseMove(xAbs, yAbs); + m_screen->enter(mask); + + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + m_sendFileThread = NULL; + } +} + +bool +Client::leave() +{ + m_active = false; + + m_screen->leave(); + + if (m_enableClipboard) { + // send clipboards that we own and that have changed + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_ownClipboard[id]) { + sendClipboard(id); + } + } + } + + return true; +} + +void +Client::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + m_screen->setClipboard(id, clipboard); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +Client::grabClipboard(ClipboardID id) +{ + m_screen->grabClipboard(id); + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; +} + +void +Client::setClipboardDirty(ClipboardID, bool) +{ + assert(0 && "shouldn't be called"); +} + +void +Client::keyDown(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyDown(id, mask, button); +} + +void +Client::keyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + m_screen->keyRepeat(id, mask, count, button); +} + +void +Client::keyUp(KeyID id, KeyModifierMask mask, KeyButton button) +{ + m_screen->keyUp(id, mask, button); +} + +void +Client::mouseDown(ButtonID id) +{ + m_screen->mouseDown(id); +} + +void +Client::mouseUp(ButtonID id) +{ + m_screen->mouseUp(id); +} + +void +Client::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->mouseMove(x, y); +} + +void +Client::mouseRelativeMove(SInt32 dx, SInt32 dy) +{ + m_screen->mouseRelativeMove(dx, dy); +} + +void +Client::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + m_screen->mouseWheel(xDelta, yDelta); +} + +void +Client::screensaver(bool activate) +{ + m_screen->screensaver(activate); +} + +void +Client::resetOptions() +{ + m_screen->resetOptions(); +} + +void +Client::setOptions(const OptionsList& options) +{ + for (OptionsList::const_iterator index = options.begin(); + index != options.end(); ++index) { + const OptionID id = *index; + if (id == kOptionClipboardSharing) { + index++; + if (*index == static_cast<OptionValue>(false)) { + LOG((CLOG_NOTE "clipboard sharing is disabled")); + } + m_enableClipboard = *index; + + break; + } + } + + m_screen->setOptions(options); +} + +String +Client::getName() const +{ + return m_name; +} + +void +Client::sendClipboard(ClipboardID id) +{ + // note -- m_mutex must be locked on entry + assert(m_screen != NULL); + assert(m_server != NULL); + + // get clipboard data. set the clipboard time to the last + // clipboard time before getting the data from the screen + // as the screen may detect an unchanged clipboard and + // avoid copying the data. + Clipboard clipboard; + if (clipboard.open(m_timeClipboard[id])) { + clipboard.close(); + } + m_screen->getClipboard(id, &clipboard); + + // check time + if (m_timeClipboard[id] == 0 || + clipboard.getTime() != m_timeClipboard[id]) { + // save new time + m_timeClipboard[id] = clipboard.getTime(); + + // marshall the data + String data = clipboard.marshall(); + + // save and send data if different or not yet sent + if (!m_sentClipboard[id] || data != m_dataClipboard[id]) { + m_sentClipboard[id] = true; + m_dataClipboard[id] = data; + m_server->onClipboardChanged(id, &clipboard); + } + } +} + +void +Client::sendEvent(Event::Type type, void* data) +{ + m_events->addEvent(Event(type, getEventTarget(), data)); +} + +void +Client::sendConnectionFailedEvent(const char* msg) +{ + FailInfo* info = new FailInfo(msg); + info->m_retry = true; + Event event(m_events->forClient().connectionFailed(), getEventTarget(), info, Event::kDontFreeData); + m_events->addEvent(event); +} + +void +Client::sendFileChunk(const void* data) +{ + FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data)); + LOG((CLOG_DEBUG1 "send file chunk")); + assert(m_server != NULL); + + // relay + m_server->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); +} + +void +Client::setupConnecting() +{ + assert(m_stream != NULL); + + if (m_args.m_enableCrypto) { + m_events->adoptHandler(m_events->forIDataSocket().secureConnected(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleConnected)); + } + else { + m_events->adoptHandler(m_events->forIDataSocket().connected(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleConnected)); + } + + m_events->adoptHandler(m_events->forIDataSocket().connectionFailed(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleConnectionFailed)); +} + +void +Client::setupConnection() +{ + assert(m_stream != NULL); + + m_events->adoptHandler(m_events->forISocket().disconnected(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleDisconnected)); + m_events->adoptHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleHello)); + m_events->adoptHandler(m_events->forIStream().outputError(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleOutputError)); + m_events->adoptHandler(m_events->forIStream().inputShutdown(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleDisconnected)); + m_events->adoptHandler(m_events->forIStream().outputShutdown(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleDisconnected)); + + m_events->adoptHandler(m_events->forISocket().stopRetry(), + m_stream->getEventTarget(), + new TMethodEventJob<Client>(this, &Client::handleStopRetry)); +} + +void +Client::setupScreen() +{ + assert(m_server == NULL); + + m_ready = false; + m_server = new ServerProxy(this, m_stream, m_events); + m_events->adoptHandler(m_events->forIScreen().shapeChanged(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleShapeChanged)); + m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), + getEventTarget(), + new TMethodEventJob<Client>(this, + &Client::handleClipboardGrabbed)); +} + +void +Client::setupTimer() +{ + assert(m_timer == NULL); + + m_timer = m_events->newOneShotTimer(15.0, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<Client>(this, + &Client::handleConnectTimeout)); +} + +void +Client::cleanupConnecting() +{ + if (m_stream != NULL) { + m_events->removeHandler(m_events->forIDataSocket().connected(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIDataSocket().connectionFailed(), + m_stream->getEventTarget()); + } +} + +void +Client::cleanupConnection() +{ + if (m_stream != NULL) { + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputError(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().inputShutdown(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputShutdown(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forISocket().disconnected(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forISocket().stopRetry(), + m_stream->getEventTarget()); + cleanupStream(); + } +} + +void +Client::cleanupScreen() +{ + if (m_server != NULL) { + if (m_ready) { + m_screen->disable(); + m_ready = false; + } + m_events->removeHandler(m_events->forIScreen().shapeChanged(), + getEventTarget()); + m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), + getEventTarget()); + delete m_server; + m_server = NULL; + } +} + +void +Client::cleanupTimer() +{ + if (m_timer != NULL) { + m_events->removeHandler(Event::kTimer, m_timer); + m_events->deleteTimer(m_timer); + m_timer = NULL; + } +} + +void +Client::cleanupStream() +{ + delete m_stream; + m_stream = NULL; +} + +void +Client::handleConnected(const Event&, void*) +{ + LOG((CLOG_DEBUG1 "connected; wait for hello")); + cleanupConnecting(); + setupConnection(); + + // reset clipboard state + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_ownClipboard[id] = false; + m_sentClipboard[id] = false; + m_timeClipboard[id] = 0; + } +} + +void +Client::handleConnectionFailed(const Event& event, void*) +{ + IDataSocket::ConnectionFailedInfo* info = + static_cast<IDataSocket::ConnectionFailedInfo*>(event.getData()); + + cleanupTimer(); + cleanupConnecting(); + cleanupStream(); + LOG((CLOG_DEBUG1 "connection failed")); + sendConnectionFailedEvent(info->m_what.c_str()); + delete info; +} + +void +Client::handleConnectTimeout(const Event&, void*) +{ + cleanupTimer(); + cleanupConnecting(); + cleanupConnection(); + cleanupStream(); + LOG((CLOG_DEBUG1 "connection timed out")); + sendConnectionFailedEvent("Timed out"); +} + +void +Client::handleOutputError(const Event&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_WARN "error sending to server")); + sendEvent(m_events->forClient().disconnected(), NULL); +} + +void +Client::handleDisconnected(const Event&, void*) +{ + cleanupTimer(); + cleanupScreen(); + cleanupConnection(); + LOG((CLOG_DEBUG1 "disconnected")); + sendEvent(m_events->forClient().disconnected(), NULL); +} + +void +Client::handleShapeChanged(const Event&, void*) +{ + LOG((CLOG_DEBUG "resolution changed")); + m_server->onInfoChanged(); +} + +void +Client::handleClipboardGrabbed(const Event& event, void*) +{ + if (!m_enableClipboard) { + return; + } + + const IScreen::ClipboardInfo* info = + static_cast<const IScreen::ClipboardInfo*>(event.getData()); + + // grab ownership + m_server->onGrabClipboard(info->m_id); + + // we now own the clipboard and it has not been sent to the server + m_ownClipboard[info->m_id] = true; + m_sentClipboard[info->m_id] = false; + m_timeClipboard[info->m_id] = 0; + + // if we're not the active screen then send the clipboard now, + // otherwise we'll wait until we leave. + if (!m_active) { + sendClipboard(info->m_id); + } +} + +void +Client::handleHello(const Event&, void*) +{ + SInt16 major, minor; + if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) { + sendConnectionFailedEvent("Protocol error from server, check encryption settings"); + cleanupTimer(); + cleanupConnection(); + return; + } + + // check versions + LOG((CLOG_DEBUG1 "got hello version %d.%d", major, minor)); + if (major < kProtocolMajorVersion || + (major == kProtocolMajorVersion && minor < kProtocolMinorVersion)) { + sendConnectionFailedEvent(XIncompatibleClient(major, minor).what()); + cleanupTimer(); + cleanupConnection(); + return; + } + + // say hello back + LOG((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion)); + ProtocolUtil::writef(m_stream, kMsgHelloBack, + kProtocolMajorVersion, + kProtocolMinorVersion, &m_name); + + // now connected but waiting to complete handshake + setupScreen(); + cleanupTimer(); + + // make sure we process any remaining messages later. we won't + // receive another event for already pending messages so we fake + // one. + if (m_stream->isReady()) { + m_events->addEvent(Event(m_events->forIStream().inputReady(), + m_stream->getEventTarget())); + } +} + +void +Client::handleSuspend(const Event&, void*) +{ + LOG((CLOG_INFO "suspend")); + m_suspended = true; + bool wasConnected = isConnected(); + disconnect(NULL); + m_connectOnResume = wasConnected; +} + +void +Client::handleResume(const Event&, void*) +{ + LOG((CLOG_INFO "resume")); + m_suspended = false; + if (m_connectOnResume) { + m_connectOnResume = false; + connect(); + } +} + +void +Client::handleFileChunkSending(const Event& event, void*) +{ + sendFileChunk(event.getData()); +} + +void +Client::handleFileRecieveCompleted(const Event& event, void*) +{ + onFileRecieveCompleted(); +} + +void +Client::onFileRecieveCompleted() +{ + if (isReceivedFileSizeValid()) { + m_writeToDropDirThread = new Thread( + new TMethodJob<Client>( + this, &Client::writeToDropDirThread)); + } +} + +void +Client::handleStopRetry(const Event&, void*) +{ + m_args.m_restartable = false; +} + +void +Client::writeToDropDirThread(void*) +{ + LOG((CLOG_DEBUG "starting write to drop dir thread")); + + while (m_screen->isFakeDraggingStarted()) { + ARCH->sleep(.1f); + } + + DropHelper::writeToDir(m_screen->getDropTarget(), m_dragFileList, + m_receivedFileData); +} + +void +Client::dragInfoReceived(UInt32 fileNum, String data) +{ + // TODO: fix duplicate function from CServer + if (!m_args.m_enableDragDrop) { + LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info.")); + return; + } + + DragInformation::parseDragInfo(m_dragFileList, fileNum, data); + + m_screen->startDraggingFiles(m_dragFileList); +} + +bool +Client::isReceivedFileSizeValid() +{ + return m_expectedFileSize == m_receivedFileData.size(); +} + +void +Client::sendFileToServer(const char* filename) +{ + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + } + + m_sendFileThread = new Thread( + new TMethodJob<Client>( + this, &Client::sendFileThread, + static_cast<void*>(const_cast<char*>(filename)))); +} + +void +Client::sendFileThread(void* filename) +{ + try { + char* name = static_cast<char*>(filename); + StreamChunker::sendFile(name, m_events, this); + } + catch (std::runtime_error& error) { + LOG((CLOG_ERR "failed sending file chunks: %s", error.what())); + } + + m_sendFileThread = NULL; +} + +void +Client::sendDragInfo(UInt32 fileCount, String& info, size_t size) +{ + m_server->sendDragInfo(fileCount, info.c_str(), size); +} diff --git a/src/lib/client/Client.h b/src/lib/client/Client.h new file mode 100644 index 0000000..7ac515d --- /dev/null +++ b/src/lib/client/Client.h @@ -0,0 +1,227 @@ +/* + * 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 "barrier/IClient.h" + +#include "barrier/Clipboard.h" +#include "barrier/DragInformation.h" +#include "barrier/INode.h" +#include "barrier/ClientArgs.h" +#include "net/NetworkAddress.h" +#include "base/EventTypes.h" +#include "mt/CondVar.h" + +class EventQueueTimer; +namespace barrier { class Screen; } +class ServerProxy; +class IDataSocket; +class ISocketFactory; +namespace barrier { class IStream; } +class IEventQueue; +class Thread; +class TCPSocket; + +//! Barrier client +/*! +This class implements the top-level client algorithms for barrier. +*/ +class Client : public IClient, public INode { +public: + class FailInfo { + public: + FailInfo(const char* what) : m_retry(false), m_what(what) { } + bool m_retry; + String m_what; + }; + +public: + /*! + This client will attempt to connect to the server using \p name + as its name and \p address as the server's address and \p factory + to create the socket. \p screen is the local screen. + */ + Client(IEventQueue* events, const String& name, + const NetworkAddress& address, ISocketFactory* socketFactory, + barrier::Screen* screen, ClientArgs const& args); + + ~Client(); + + //! @name manipulators + //@{ + + //! Connect to server + /*! + Starts an attempt to connect to the server. This is ignored if + the client is trying to connect or is already connected. + */ + void connect(); + + //! Disconnect + /*! + Disconnects from the server with an optional error message. + */ + void disconnect(const char* msg); + + //! Notify of handshake complete + /*! + Notifies the client that the connection handshake has completed. + */ + virtual void handshakeComplete(); + + //! Received drag information + void dragInfoReceived(UInt32 fileNum, String data); + + //! Create a new thread and use it to send file to Server + void sendFileToServer(const char* filename); + + //! Send dragging file information back to server + void sendDragInfo(UInt32 fileCount, String& info, size_t size); + + + //@} + //! @name accessors + //@{ + + //! Test if connected + /*! + Returns true iff the client is successfully connected to the server. + */ + bool isConnected() const; + + //! Test if connecting + /*! + Returns true iff the client is currently attempting to connect to + the server. + */ + bool isConnecting() const; + + //! Get address of server + /*! + Returns the address of the server the client is connected (or wants + to connect) to. + */ + NetworkAddress getServerAddress() const; + + //! Return true if recieved file size is valid + bool isReceivedFileSizeValid(); + + //! Return expected file size + size_t& getExpectedFileSize() { return m_expectedFileSize; } + + //! Return received file data + String& getReceivedFileData() { return m_receivedFileData; } + + //! Return drag file list + DragFileList getDragFileList() { return m_dragFileList; } + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual String getName() const; + +private: + void sendClipboard(ClipboardID); + void sendEvent(Event::Type, void*); + void sendConnectionFailedEvent(const char* msg); + void sendFileChunk(const void* data); + void sendFileThread(void*); + void writeToDropDirThread(void*); + void setupConnecting(); + void setupConnection(); + void setupScreen(); + void setupTimer(); + void cleanupConnecting(); + void cleanupConnection(); + void cleanupScreen(); + void cleanupTimer(); + void cleanupStream(); + void handleConnected(const Event&, void*); + void handleConnectionFailed(const Event&, void*); + void handleConnectTimeout(const Event&, void*); + void handleOutputError(const Event&, void*); + void handleDisconnected(const Event&, void*); + void handleShapeChanged(const Event&, void*); + void handleClipboardGrabbed(const Event&, void*); + void handleHello(const Event&, void*); + void handleSuspend(const Event& event, void*); + void handleResume(const Event& event, void*); + void handleFileChunkSending(const Event&, void*); + void handleFileRecieveCompleted(const Event&, void*); + void handleStopRetry(const Event&, void*); + void onFileRecieveCompleted(); + void sendClipboardThread(void*); + +public: + bool m_mock; + +private: + String m_name; + NetworkAddress m_serverAddress; + ISocketFactory* m_socketFactory; + barrier::Screen* m_screen; + barrier::IStream* m_stream; + EventQueueTimer* m_timer; + ServerProxy* m_server; + bool m_ready; + bool m_active; + bool m_suspended; + bool m_connectOnResume; + bool m_ownClipboard[kClipboardEnd]; + bool m_sentClipboard[kClipboardEnd]; + IClipboard::Time m_timeClipboard[kClipboardEnd]; + String m_dataClipboard[kClipboardEnd]; + IEventQueue* m_events; + std::size_t m_expectedFileSize; + String m_receivedFileData; + DragFileList m_dragFileList; + String m_dragFileExt; + Thread* m_sendFileThread; + Thread* m_writeToDropDirThread; + TCPSocket* m_socket; + bool m_useSecureNetwork; + ClientArgs m_args; + bool m_enableClipboard; +}; diff --git a/src/lib/client/ServerProxy.cpp b/src/lib/client/ServerProxy.cpp new file mode 100644 index 0000000..9224db6 --- /dev/null +++ b/src/lib/client/ServerProxy.cpp @@ -0,0 +1,908 @@ +/* + * 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 "client/ServerProxy.h" + +#include "client/Client.h" +#include "barrier/FileChunk.h" +#include "barrier/ClipboardChunk.h" +#include "barrier/StreamChunker.h" +#include "barrier/Clipboard.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/option_types.h" +#include "barrier/protocol_types.h" +#include "io/IStream.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/XBase.h" + +#include <memory> + +// +// ServerProxy +// + +ServerProxy::ServerProxy(Client* client, barrier::IStream* stream, IEventQueue* events) : + m_client(client), + m_stream(stream), + m_seqNum(0), + m_compressMouse(false), + m_compressMouseRelative(false), + m_xMouse(0), + m_yMouse(0), + m_dxMouse(0), + m_dyMouse(0), + m_ignoreMouse(false), + m_keepAliveAlarm(0.0), + m_keepAliveAlarmTimer(NULL), + m_parser(&ServerProxy::parseHandshakeMessage), + m_events(events) +{ + assert(m_client != NULL); + assert(m_stream != NULL); + + // initialize modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) + m_modifierTranslationTable[id] = id; + + // handle data on stream + m_events->adoptHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget(), + new TMethodEventJob<ServerProxy>(this, + &ServerProxy::handleData)); + + m_events->adoptHandler(m_events->forClipboard().clipboardSending(), + this, + new TMethodEventJob<ServerProxy>(this, + &ServerProxy::handleClipboardSendingEvent)); + + // send heartbeat + setKeepAliveRate(kKeepAliveRate); +} + +ServerProxy::~ServerProxy() +{ + setKeepAliveRate(-1.0); + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget()); +} + +void +ServerProxy::resetKeepAliveAlarm() +{ + if (m_keepAliveAlarmTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_keepAliveAlarmTimer); + m_events->deleteTimer(m_keepAliveAlarmTimer); + m_keepAliveAlarmTimer = NULL; + } + if (m_keepAliveAlarm > 0.0) { + m_keepAliveAlarmTimer = + m_events->newOneShotTimer(m_keepAliveAlarm, NULL); + m_events->adoptHandler(Event::kTimer, m_keepAliveAlarmTimer, + new TMethodEventJob<ServerProxy>(this, + &ServerProxy::handleKeepAliveAlarm)); + } +} + +void +ServerProxy::setKeepAliveRate(double rate) +{ + m_keepAliveAlarm = rate * kKeepAlivesUntilDeath; + resetKeepAliveAlarm(); +} + +void +ServerProxy::handleData(const Event&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = m_stream->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from server: %d bytes", n)); + m_client->disconnect("incomplete message from server"); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + switch ((this->*m_parser)(code)) { + case kOkay: + break; + + case kUnknown: + LOG((CLOG_ERR "invalid message from server: %c%c%c%c", code[0], code[1], code[2], code[3])); + m_client->disconnect("invalid message from server"); + return; + + case kDisconnect: + return; + } + + // next message + n = m_stream->read(code, 4); + } + + flushCompressedMouse(); +} + +ServerProxy::EResult +ServerProxy::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + + // handshake is complete + m_parser = &ServerProxy::parseMessage; + m_client->handshakeComplete(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + ProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + + else if (memcmp(code, kMsgEIncompatible, 4) == 0) { + SInt32 major, minor; + ProtocolUtil::readf(m_stream, + kMsgEIncompatible + 4, &major, &minor); + LOG((CLOG_ERR "server has incompatible version %d.%d", major, minor)); + m_client->disconnect("server has incompatible version"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBusy, 4) == 0) { + LOG((CLOG_ERR "server already has a connected client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server already has a connected client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEUnknown, 4) == 0) { + LOG((CLOG_ERR "server refused client with name \"%s\"", m_client->getName().c_str())); + m_client->disconnect("server refused client with our name"); + return kDisconnect; + } + + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + return kOkay; +} + +ServerProxy::EResult +ServerProxy::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDMouseMove, 4) == 0) { + mouseMove(); + } + + else if (memcmp(code, kMsgDMouseRelMove, 4) == 0) { + mouseRelativeMove(); + } + + else if (memcmp(code, kMsgDMouseWheel, 4) == 0) { + mouseWheel(); + } + + else if (memcmp(code, kMsgDKeyDown, 4) == 0) { + keyDown(); + } + + else if (memcmp(code, kMsgDKeyUp, 4) == 0) { + keyUp(); + } + + else if (memcmp(code, kMsgDMouseDown, 4) == 0) { + mouseDown(); + } + + else if (memcmp(code, kMsgDMouseUp, 4) == 0) { + mouseUp(); + } + + else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) { + keyRepeat(); + } + + else if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // echo keep alives and reset alarm + ProtocolUtil::writef(m_stream, kMsgCKeepAlive); + resetKeepAliveAlarm(); + } + + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // accept and discard no-op + } + + else if (memcmp(code, kMsgCEnter, 4) == 0) { + enter(); + } + + else if (memcmp(code, kMsgCLeave, 4) == 0) { + leave(); + } + + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + grabClipboard(); + } + + else if (memcmp(code, kMsgCScreenSaver, 4) == 0) { + screensaver(); + } + + else if (memcmp(code, kMsgQInfo, 4) == 0) { + queryInfo(); + } + + else if (memcmp(code, kMsgCInfoAck, 4) == 0) { + infoAcknowledgment(); + } + + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + setClipboard(); + } + + else if (memcmp(code, kMsgCResetOptions, 4) == 0) { + resetOptions(); + } + + else if (memcmp(code, kMsgDSetOptions, 4) == 0) { + setOptions(); + } + + else if (memcmp(code, kMsgDFileTransfer, 4) == 0) { + fileChunkReceived(); + } + else if (memcmp(code, kMsgDDragInfo, 4) == 0) { + dragInfoReceived(); + } + + else if (memcmp(code, kMsgCClose, 4) == 0) { + // server wants us to hangup + LOG((CLOG_DEBUG1 "recv close")); + m_client->disconnect(NULL); + return kDisconnect; + } + else if (memcmp(code, kMsgEBad, 4) == 0) { + LOG((CLOG_ERR "server disconnected due to a protocol error")); + m_client->disconnect("server reported a protocol error"); + return kDisconnect; + } + else { + return kUnknown; + } + + // send a reply. this is intended to work around a delay when + // running a linux server and an OS X (any BSD?) client. the + // client waits to send an ACK (if the system control flag + // net.inet.tcp.delayed_ack is 1) in hopes of piggybacking it + // on a data packet. we provide that packet here. i don't + // know why a delayed ACK should cause the server to wait since + // TCP_NODELAY is enabled. + ProtocolUtil::writef(m_stream, kMsgCNoop); + + return kOkay; +} + +void +ServerProxy::handleKeepAliveAlarm(const Event&, void*) +{ + LOG((CLOG_NOTE "server is dead")); + m_client->disconnect("server is not responding"); +} + +void +ServerProxy::onInfoChanged() +{ + // ignore mouse motion until we receive acknowledgment of our info + // change message. + m_ignoreMouse = true; + + // send info update + queryInfo(); +} + +bool +ServerProxy::onGrabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG1 "sending clipboard %d changed", id)); + ProtocolUtil::writef(m_stream, kMsgCClipboard, id, m_seqNum); + return true; +} + +void +ServerProxy::onClipboardChanged(ClipboardID id, const IClipboard* clipboard) +{ + String data = IClipboard::marshall(clipboard); + LOG((CLOG_DEBUG "sending clipboard %d seqnum=%d", id, m_seqNum)); + + StreamChunker::sendClipboard(data, data.size(), id, m_seqNum, m_events, this); +} + +void +ServerProxy::flushCompressedMouse() +{ + if (m_compressMouse) { + m_compressMouse = false; + m_client->mouseMove(m_xMouse, m_yMouse); + } + if (m_compressMouseRelative) { + m_compressMouseRelative = false; + m_client->mouseRelativeMove(m_dxMouse, m_dyMouse); + m_dxMouse = 0; + m_dyMouse = 0; + } +} + +void +ServerProxy::sendInfo(const ClientInfo& info) +{ + LOG((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d", info.m_x, info.m_y, info.m_w, info.m_h)); + ProtocolUtil::writef(m_stream, kMsgDInfo, + info.m_x, info.m_y, + info.m_w, info.m_h, 0, + info.m_mx, info.m_my); +} + +KeyID +ServerProxy::translateKey(KeyID id) const +{ + static const KeyID s_translationTable[kKeyModifierIDLast][2] = { + { kKeyNone, kKeyNone }, + { kKeyShift_L, kKeyShift_R }, + { kKeyControl_L, kKeyControl_R }, + { kKeyAlt_L, kKeyAlt_R }, + { kKeyMeta_L, kKeyMeta_R }, + { kKeySuper_L, kKeySuper_R }, + { kKeyAltGr, kKeyAltGr} + }; + + KeyModifierID id2 = kKeyModifierIDNull; + UInt32 side = 0; + switch (id) { + case kKeyShift_L: + id2 = kKeyModifierIDShift; + side = 0; + break; + + case kKeyShift_R: + id2 = kKeyModifierIDShift; + side = 1; + break; + + case kKeyControl_L: + id2 = kKeyModifierIDControl; + side = 0; + break; + + case kKeyControl_R: + id2 = kKeyModifierIDControl; + side = 1; + break; + + case kKeyAlt_L: + id2 = kKeyModifierIDAlt; + side = 0; + break; + + case kKeyAlt_R: + id2 = kKeyModifierIDAlt; + side = 1; + break; + + case kKeyAltGr: + id2 = kKeyModifierIDAltGr; + side = 1; // there is only one alt gr key on the right side + break; + + case kKeyMeta_L: + id2 = kKeyModifierIDMeta; + side = 0; + break; + + case kKeyMeta_R: + id2 = kKeyModifierIDMeta; + side = 1; + break; + + case kKeySuper_L: + id2 = kKeyModifierIDSuper; + side = 0; + break; + + case kKeySuper_R: + id2 = kKeyModifierIDSuper; + side = 1; + break; + } + + if (id2 != kKeyModifierIDNull) { + return s_translationTable[m_modifierTranslationTable[id2]][side]; + } + else { + return id; + } +} + +KeyModifierMask +ServerProxy::translateModifierMask(KeyModifierMask mask) const +{ + static const KeyModifierMask s_masks[kKeyModifierIDLast] = { + 0x0000, + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper, + KeyModifierAltGr + }; + + KeyModifierMask newMask = mask & ~(KeyModifierShift | + KeyModifierControl | + KeyModifierAlt | + KeyModifierMeta | + KeyModifierSuper | + KeyModifierAltGr ); + if ((mask & KeyModifierShift) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDShift]]; + } + if ((mask & KeyModifierControl) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDControl]]; + } + if ((mask & KeyModifierAlt) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAlt]]; + } + if ((mask & KeyModifierAltGr) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDAltGr]]; + } + if ((mask & KeyModifierMeta) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDMeta]]; + } + if ((mask & KeyModifierSuper) != 0) { + newMask |= s_masks[m_modifierTranslationTable[kKeyModifierIDSuper]]; + } + return newMask; +} + +void +ServerProxy::enter() +{ + // parse + SInt16 x, y; + UInt16 mask; + UInt32 seqNum; + ProtocolUtil::readf(m_stream, kMsgCEnter + 4, &x, &y, &seqNum, &mask); + LOG((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask)); + + // discard old compressed mouse motion, if any + m_compressMouse = false; + m_compressMouseRelative = false; + m_dxMouse = 0; + m_dyMouse = 0; + m_seqNum = seqNum; + + // forward + m_client->enter(x, y, seqNum, static_cast<KeyModifierMask>(mask), false); +} + +void +ServerProxy::leave() +{ + // parse + LOG((CLOG_DEBUG1 "recv leave")); + + // send last mouse motion + flushCompressedMouse(); + + // forward + m_client->leave(); +} + +void +ServerProxy::setClipboard() +{ + // parse + static String dataCached; + ClipboardID id; + UInt32 seq; + + int r = ClipboardChunk::assemble(m_stream, dataCached, id, seq); + + if (r == kStart) { + size_t size = ClipboardChunk::getExpectedSize(); + LOG((CLOG_DEBUG "receiving clipboard %d size=%d", id, size)); + } + else if (r == kFinish) { + LOG((CLOG_DEBUG "received clipboard %d size=%d", id, dataCached.size())); + + // forward + Clipboard clipboard; + clipboard.unmarshall(dataCached, 0); + m_client->setClipboard(id, &clipboard); + + LOG((CLOG_INFO "clipboard was updated")); + } +} + +void +ServerProxy::grabClipboard() +{ + // parse + ClipboardID id; + UInt32 seqNum; + ProtocolUtil::readf(m_stream, kMsgCClipboard + 4, &id, &seqNum); + LOG((CLOG_DEBUG "recv grab clipboard %d", id)); + + // validate + if (id >= kClipboardEnd) { + return; + } + + // forward + m_client->grabClipboard(id); +} + +void +ServerProxy::keyDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + ProtocolUtil::readf(m_stream, kMsgDKeyDown + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key down id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast<KeyID>(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast<KeyModifierMask>(mask)); + if (id2 != static_cast<KeyID>(id) || + mask2 != static_cast<KeyModifierMask>(mask)) + LOG((CLOG_DEBUG1 "key down translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyDown(id2, mask2, button); +} + +void +ServerProxy::keyRepeat() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, count, button; + ProtocolUtil::readf(m_stream, kMsgDKeyRepeat + 4, + &id, &mask, &count, &button); + LOG((CLOG_DEBUG1 "recv key repeat id=0x%08x, mask=0x%04x, count=%d, button=0x%04x", id, mask, count, button)); + + // translate + KeyID id2 = translateKey(static_cast<KeyID>(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast<KeyModifierMask>(mask)); + if (id2 != static_cast<KeyID>(id) || + mask2 != static_cast<KeyModifierMask>(mask)) + LOG((CLOG_DEBUG1 "key repeat translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyRepeat(id2, mask2, count, button); +} + +void +ServerProxy::keyUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + UInt16 id, mask, button; + ProtocolUtil::readf(m_stream, kMsgDKeyUp + 4, &id, &mask, &button); + LOG((CLOG_DEBUG1 "recv key up id=0x%08x, mask=0x%04x, button=0x%04x", id, mask, button)); + + // translate + KeyID id2 = translateKey(static_cast<KeyID>(id)); + KeyModifierMask mask2 = translateModifierMask( + static_cast<KeyModifierMask>(mask)); + if (id2 != static_cast<KeyID>(id) || + mask2 != static_cast<KeyModifierMask>(mask)) + LOG((CLOG_DEBUG1 "key up translated to id=0x%08x, mask=0x%04x", id2, mask2)); + + // forward + m_client->keyUp(id2, mask2, button); +} + +void +ServerProxy::mouseDown() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + ProtocolUtil::readf(m_stream, kMsgDMouseDown + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse down id=%d", id)); + + // forward + m_client->mouseDown(static_cast<ButtonID>(id)); +} + +void +ServerProxy::mouseUp() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt8 id; + ProtocolUtil::readf(m_stream, kMsgDMouseUp + 4, &id); + LOG((CLOG_DEBUG1 "recv mouse up id=%d", id)); + + // forward + m_client->mouseUp(static_cast<ButtonID>(id)); +} + +void +ServerProxy::mouseMove() +{ + // parse + bool ignore; + SInt16 x, y; + ProtocolUtil::readf(m_stream, kMsgDMouseMove + 4, &x, &y); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouse && m_stream->isReady()) { + m_compressMouse = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouse) { + m_compressMouseRelative = false; + ignore = true; + m_xMouse = x; + m_yMouse = y; + m_dxMouse = 0; + m_dyMouse = 0; + } + LOG((CLOG_DEBUG2 "recv mouse move %d,%d", x, y)); + + // forward + if (!ignore) { + m_client->mouseMove(x, y); + } +} + +void +ServerProxy::mouseRelativeMove() +{ + // parse + bool ignore; + SInt16 dx, dy; + ProtocolUtil::readf(m_stream, kMsgDMouseRelMove + 4, &dx, &dy); + + // note if we should ignore the move + ignore = m_ignoreMouse; + + // compress mouse motion events if more input follows + if (!ignore && !m_compressMouseRelative && m_stream->isReady()) { + m_compressMouseRelative = true; + } + + // if compressing then ignore the motion but record it + if (m_compressMouseRelative) { + ignore = true; + m_dxMouse += dx; + m_dyMouse += dy; + } + LOG((CLOG_DEBUG2 "recv mouse relative move %d,%d", dx, dy)); + + // forward + if (!ignore) { + m_client->mouseRelativeMove(dx, dy); + } +} + +void +ServerProxy::mouseWheel() +{ + // get mouse up to date + flushCompressedMouse(); + + // parse + SInt16 xDelta, yDelta; + ProtocolUtil::readf(m_stream, kMsgDMouseWheel + 4, &xDelta, &yDelta); + LOG((CLOG_DEBUG2 "recv mouse wheel %+d,%+d", xDelta, yDelta)); + + // forward + m_client->mouseWheel(xDelta, yDelta); +} + +void +ServerProxy::screensaver() +{ + // parse + SInt8 on; + ProtocolUtil::readf(m_stream, kMsgCScreenSaver + 4, &on); + LOG((CLOG_DEBUG1 "recv screen saver on=%d", on)); + + // forward + m_client->screensaver(on != 0); +} + +void +ServerProxy::resetOptions() +{ + // parse + LOG((CLOG_DEBUG1 "recv reset options")); + + // forward + m_client->resetOptions(); + + // reset keep alive + setKeepAliveRate(kKeepAliveRate); + + // reset modifier translation table + for (KeyModifierID id = 0; id < kKeyModifierIDLast; ++id) { + m_modifierTranslationTable[id] = id; + } +} + +void +ServerProxy::setOptions() +{ + // parse + OptionsList options; + ProtocolUtil::readf(m_stream, kMsgDSetOptions + 4, &options); + LOG((CLOG_DEBUG1 "recv set options size=%d", options.size())); + + // forward + m_client->setOptions(options); + + // update modifier table + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + KeyModifierID id = kKeyModifierIDNull; + if (options[i] == kOptionModifierMapForShift) { + id = kKeyModifierIDShift; + } + else if (options[i] == kOptionModifierMapForControl) { + id = kKeyModifierIDControl; + } + else if (options[i] == kOptionModifierMapForAlt) { + id = kKeyModifierIDAlt; + } + else if (options[i] == kOptionModifierMapForAltGr) { + id = kKeyModifierIDAltGr; + } + else if (options[i] == kOptionModifierMapForMeta) { + id = kKeyModifierIDMeta; + } + else if (options[i] == kOptionModifierMapForSuper) { + id = kKeyModifierIDSuper; + } + else if (options[i] == kOptionHeartbeat) { + // update keep alive + setKeepAliveRate(1.0e-3 * static_cast<double>(options[i + 1])); + } + + if (id != kKeyModifierIDNull) { + m_modifierTranslationTable[id] = + static_cast<KeyModifierID>(options[i + 1]); + LOG((CLOG_DEBUG1 "modifier %d mapped to %d", id, m_modifierTranslationTable[id])); + } + } +} + +void +ServerProxy::queryInfo() +{ + ClientInfo info; + m_client->getShape(info.m_x, info.m_y, info.m_w, info.m_h); + m_client->getCursorPos(info.m_mx, info.m_my); + sendInfo(info); +} + +void +ServerProxy::infoAcknowledgment() +{ + LOG((CLOG_DEBUG1 "recv info acknowledgment")); + m_ignoreMouse = false; +} + +void +ServerProxy::fileChunkReceived() +{ + int result = FileChunk::assemble( + m_stream, + m_client->getReceivedFileData(), + m_client->getExpectedFileSize()); + + if (result == kFinish) { + m_events->addEvent(Event(m_events->forFile().fileRecieveCompleted(), m_client)); + } + else if (result == kStart) { + if (m_client->getDragFileList().size() > 0) { + String filename = m_client->getDragFileList().at(0).getFilename(); + LOG((CLOG_DEBUG "start receiving %s", filename.c_str())); + } + } +} + +void +ServerProxy::dragInfoReceived() +{ + // parse + UInt32 fileNum = 0; + String content; + ProtocolUtil::readf(m_stream, kMsgDDragInfo + 4, &fileNum, &content); + + m_client->dragInfoReceived(fileNum, content); +} + +void +ServerProxy::handleClipboardSendingEvent(const Event& event, void*) +{ + ClipboardChunk::send(m_stream, event.getData()); +} + +void +ServerProxy::fileChunkSending(UInt8 mark, char* data, size_t dataSize) +{ + FileChunk::send(m_stream, mark, data, dataSize); +} + +void +ServerProxy::sendDragInfo(UInt32 fileCount, const char* info, size_t size) +{ + String data(info, size); + ProtocolUtil::writef(m_stream, kMsgDDragInfo, fileCount, &data); +} diff --git a/src/lib/client/ServerProxy.h b/src/lib/client/ServerProxy.h new file mode 100644 index 0000000..2ad711a --- /dev/null +++ b/src/lib/client/ServerProxy.h @@ -0,0 +1,133 @@ +/* + * 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 "barrier/clipboard_types.h" +#include "barrier/key_types.h" +#include "base/Event.h" +#include "base/Stopwatch.h" +#include "base/String.h" + +class Client; +class ClientInfo; +class EventQueueTimer; +class IClipboard; +namespace barrier { class IStream; } +class IEventQueue; + +//! Proxy for server +/*! +This class acts a proxy for the server, converting calls into messages +to the server and messages from the server to calls on the client. +*/ +class ServerProxy { +public: + /*! + Process messages from the server on \p stream and forward to + \p client. + */ + ServerProxy(Client* client, barrier::IStream* stream, IEventQueue* events); + ~ServerProxy(); + + //! @name manipulators + //@{ + + void onInfoChanged(); + bool onGrabClipboard(ClipboardID); + void onClipboardChanged(ClipboardID, const IClipboard*); + + //@} + + // sending file chunk to server + void fileChunkSending(UInt8 mark, char* data, size_t dataSize); + + // sending dragging information to server + void sendDragInfo(UInt32 fileCount, const char* info, size_t size); + +#ifdef TEST_ENV + void handleDataForTest() { handleData(Event(), NULL); } +#endif + +protected: + enum EResult { kOkay, kUnknown, kDisconnect }; + EResult parseHandshakeMessage(const UInt8* code); + EResult parseMessage(const UInt8* code); + +private: + // if compressing mouse motion then send the last motion now + void flushCompressedMouse(); + + void sendInfo(const ClientInfo&); + + void resetKeepAliveAlarm(); + void setKeepAliveRate(double); + + // modifier key translation + KeyID translateKey(KeyID) const; + KeyModifierMask translateModifierMask(KeyModifierMask) const; + + // event handlers + void handleData(const Event&, void*); + void handleKeepAliveAlarm(const Event&, void*); + + // message handlers + void enter(); + void leave(); + void setClipboard(); + void grabClipboard(); + void keyDown(); + void keyRepeat(); + void keyUp(); + void mouseDown(); + void mouseUp(); + void mouseMove(); + void mouseRelativeMove(); + void mouseWheel(); + void screensaver(); + void resetOptions(); + void setOptions(); + void queryInfo(); + void infoAcknowledgment(); + void fileChunkReceived(); + void dragInfoReceived(); + void handleClipboardSendingEvent(const Event&, void*); + +private: + typedef EResult (ServerProxy::*MessageParser)(const UInt8*); + + Client* m_client; + barrier::IStream* m_stream; + + UInt32 m_seqNum; + + bool m_compressMouse; + bool m_compressMouseRelative; + SInt32 m_xMouse, m_yMouse; + SInt32 m_dxMouse, m_dyMouse; + + bool m_ignoreMouse; + + KeyModifierID m_modifierTranslationTable[kKeyModifierIDLast]; + + double m_keepAliveAlarm; + EventQueueTimer* m_keepAliveAlarmTimer; + + MessageParser m_parser; + IEventQueue* m_events; +}; diff --git a/src/lib/common/CMakeLists.txt b/src/lib/common/CMakeLists.txt new file mode 100644 index 0000000..56d9fc9 --- /dev/null +++ b/src/lib/common/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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() + +add_library(common STATIC ${sources}) diff --git a/src/lib/common/IInterface.h b/src/lib/common/IInterface.h new file mode 100644 index 0000000..84a76a9 --- /dev/null +++ b/src/lib/common/IInterface.h @@ -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/>. + */ + +#pragma once + +#include "common/common.h" + +//! Base class of interfaces +/*! +This is the base class of all interface classes. An interface class has +only pure virtual methods. +*/ +class IInterface { +public: + //! Interface destructor does nothing + virtual ~IInterface() { } +}; diff --git a/src/lib/common/MacOSXPrecomp.h b/src/lib/common/MacOSXPrecomp.h new file mode 100644 index 0000000..7dbc8d0 --- /dev/null +++ b/src/lib/common/MacOSXPrecomp.h @@ -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/>. + */ + + // +// Prefix header for all source files of the 'deleteme' target in the 'deleteme' project. +// + +#include <Carbon/Carbon.h> diff --git a/src/lib/common/Version.cpp b/src/lib/common/Version.cpp new file mode 100644 index 0000000..94d6c5d --- /dev/null +++ b/src/lib/common/Version.cpp @@ -0,0 +1,29 @@ +/* + * 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 "common/Version.h" + +const char* kApplication = "Barrier"; +const char* kCopyright = "Copyright (C) 2018 Debauchee Open Source Group\n" + "Copyright (C) 2012-2016 Symless Ltd.\n" + "Copyright (C) 2008-2014 Nick Bolton\n" + "Copyright (C) 2002-2014 Chris Schoeneman"; +const char* kContact = "Email: todo@mail.com"; +const char* kWebsite = "https://github.com/debauchee/barrier/"; +const char* kVersion = BARRIER_VERSION; +const char* kAppVersion = "Barrier " BARRIER_VERSION; diff --git a/src/lib/common/Version.h b/src/lib/common/Version.h new file mode 100644 index 0000000..66bb2e2 --- /dev/null +++ b/src/lib/common/Version.h @@ -0,0 +1,39 @@ +/* + * 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" + +// set version macro if not set yet +#if !defined(BARRIER_VERSION) +#error Version was not set (should be passed to compiler). +#endif + +// important strings +extern const char* kApplication; +extern const char* kCopyright; +extern const char* kContact; +extern const char* kWebsite; + +// build version. follows linux kernel style: an even minor number implies +// a release version, odd implies development version. +extern const char* kVersion; + +// application version +extern const char* kAppVersion; diff --git a/src/lib/common/basic_types.h b/src/lib/common/basic_types.h new file mode 100644 index 0000000..f84550c --- /dev/null +++ b/src/lib/common/basic_types.h @@ -0,0 +1,92 @@ +/* + * 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" + +// +// pick types of particular sizes +// + +#if !defined(TYPE_OF_SIZE_1) +# if SIZEOF_CHAR == 1 +# define TYPE_OF_SIZE_1 char +# endif +#endif + +#if !defined(TYPE_OF_SIZE_2) +# if SIZEOF_INT == 2 +# define TYPE_OF_SIZE_2 int +# else +# define TYPE_OF_SIZE_2 short +# endif +#endif + +#if !defined(TYPE_OF_SIZE_4) + // Carbon defines SInt32 and UInt32 in terms of long +# if SIZEOF_INT == 4 && !defined(__APPLE__) +# define TYPE_OF_SIZE_4 int +# else +# define TYPE_OF_SIZE_4 long +# endif +#endif + + // +// verify existence of required types +// + +#if !defined(TYPE_OF_SIZE_1) +# error No 1 byte integer type +#endif +#if !defined(TYPE_OF_SIZE_2) +# error No 2 byte integer type +#endif +#if !defined(TYPE_OF_SIZE_4) +# error No 4 byte integer type +#endif + + +// +// make typedefs +// +// except for SInt8 and UInt8 these types are only guaranteed to be +// at least as big as indicated (in bits). that is, they may be +// larger than indicated. +// + +// Added this because it doesn't compile on OS X 10.6 because they are already defined in Carbon +#if !defined(__MACTYPES__) +#if defined(__APPLE__) +#include <CoreServices/CoreServices.h> +#else +typedef signed TYPE_OF_SIZE_1 SInt8; +typedef signed TYPE_OF_SIZE_2 SInt16; +typedef signed TYPE_OF_SIZE_4 SInt32; +typedef unsigned TYPE_OF_SIZE_1 UInt8; +typedef unsigned TYPE_OF_SIZE_2 UInt16; +typedef unsigned TYPE_OF_SIZE_4 UInt32; +#endif +#endif +// +// clean up +// + +#undef TYPE_OF_SIZE_1 +#undef TYPE_OF_SIZE_2 +#undef TYPE_OF_SIZE_4 diff --git a/src/lib/common/common.h b/src/lib/common/common.h new file mode 100644 index 0000000..5ea215f --- /dev/null +++ b/src/lib/common/common.h @@ -0,0 +1,58 @@ +/* + * 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 + +#if defined(_WIN32) +# define SYSAPI_WIN32 1 +# define WINAPI_MSWINDOWS 1 +#elif HAVE_CONFIG_H +# include "config.h" +#else +# error "config.h missing" +#endif + +// VC++ has built-in sized types +#if defined(_MSC_VER) +# include <wchar.h> +# define TYPE_OF_SIZE_1 __int8 +# define TYPE_OF_SIZE_2 __int16 +# define TYPE_OF_SIZE_4 __int32 +#else +# define SIZE_OF_CHAR 1 +# define SIZE_OF_SHORT 2 +# define SIZE_OF_INT 4 +# define SIZE_OF_LONG 4 +#endif + +// define NULL +#include <stddef.h> + +// make assert available since we use it a lot +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +enum { + kExitSuccess = 0, // successful completion + kExitFailed = 1, // general failure + kExitTerminated = 2, // killed by signal + kExitArgs = 3, // bad arguments + kExitConfig = 4, // cannot read configuration + kExitSubscription = 5 // subscription error +}; diff --git a/src/lib/common/stdbitset.h b/src/lib/common/stdbitset.h new file mode 100644 index 0000000..1096249 --- /dev/null +++ b/src/lib/common/stdbitset.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <bitset> +#include "common/stdpost.h" diff --git a/src/lib/common/stddeque.h b/src/lib/common/stddeque.h new file mode 100644 index 0000000..ffaed24 --- /dev/null +++ b/src/lib/common/stddeque.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <deque> +#include "common/stdpost.h" diff --git a/src/lib/common/stdexcept.h b/src/lib/common/stdexcept.h new file mode 100644 index 0000000..2bd96b3 --- /dev/null +++ b/src/lib/common/stdexcept.h @@ -0,0 +1,23 @@ +/* + * 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 <stdexcept> + +// apple declares _NOEXCEPT +#ifndef _NOEXCEPT +# define _NOEXCEPT throw() +#endif diff --git a/src/lib/common/stdfstream.h b/src/lib/common/stdfstream.h new file mode 100644 index 0000000..e9aa263 --- /dev/null +++ b/src/lib/common/stdfstream.h @@ -0,0 +1,22 @@ +/* + * 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 "common/stdpre.h" +#include <fstream> +#include "common/stdpost.h" +#include "common/stdistream.h" diff --git a/src/lib/common/stdistream.h b/src/lib/common/stdistream.h new file mode 100644 index 0000000..b19e2ab --- /dev/null +++ b/src/lib/common/stdistream.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/>. + */ + +#include "common/stdpre.h" +#if HAVE_ISTREAM +#include <istream> +#else +#include <iostream> +#endif +#include "common/stdpost.h" + +#if defined(_MSC_VER) && _MSC_VER <= 1200 +// VC++6 istream has no overloads for __int* types, .NET does +inline +std::istream& operator>>(std::istream& s, SInt8& i) +{ return s >> (signed char&)i; } +inline +std::istream& operator>>(std::istream& s, SInt16& i) +{ return s >> (short&)i; } +inline +std::istream& operator>>(std::istream& s, SInt32& i) +{ return s >> (int&)i; } +inline +std::istream& operator>>(std::istream& s, UInt8& i) +{ return s >> (unsigned char&)i; } +inline +std::istream& operator>>(std::istream& s, UInt16& i) +{ return s >> (unsigned short&)i; } +inline +std::istream& operator>>(std::istream& s, UInt32& i) +{ return s >> (unsigned int&)i; } +#endif diff --git a/src/lib/common/stdlist.h b/src/lib/common/stdlist.h new file mode 100644 index 0000000..d530e57 --- /dev/null +++ b/src/lib/common/stdlist.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <list> +#include "common/stdpost.h" diff --git a/src/lib/common/stdmap.h b/src/lib/common/stdmap.h new file mode 100644 index 0000000..2351074 --- /dev/null +++ b/src/lib/common/stdmap.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <map> +#include "common/stdpost.h" diff --git a/src/lib/common/stdostream.h b/src/lib/common/stdostream.h new file mode 100644 index 0000000..bb82285 --- /dev/null +++ b/src/lib/common/stdostream.h @@ -0,0 +1,25 @@ +/* + * 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 "common/stdpre.h" +#if HAVE_OSTREAM +#include <ostream> +#else +#include <iostream> +#endif +#include "common/stdpost.h" diff --git a/src/lib/common/stdpost.h b/src/lib/common/stdpost.h new file mode 100644 index 0000000..8046da0 --- /dev/null +++ b/src/lib/common/stdpost.h @@ -0,0 +1,21 @@ +/* + * 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) +#pragma warning(pop) +#endif diff --git a/src/lib/common/stdpre.h b/src/lib/common/stdpre.h new file mode 100644 index 0000000..8ccd308 --- /dev/null +++ b/src/lib/common/stdpre.h @@ -0,0 +1,31 @@ +/* + * 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) +#pragma warning(disable: 4786) // identifier truncated +#pragma warning(disable: 4514) // unreferenced inline +#pragma warning(disable: 4710) // not inlined +#pragma warning(disable: 4663) // C++ change, template specialization +#pragma warning(disable: 4503) // decorated name length too long +#pragma warning(push, 3) +#pragma warning(disable: 4018) // signed/unsigned mismatch +#pragma warning(disable: 4284) +#pragma warning(disable: 4146) // unary minus on unsigned value +#pragma warning(disable: 4127) // conditional expression is constant +#pragma warning(disable: 4701) // variable possibly used uninitialized +#endif diff --git a/src/lib/common/stdset.h b/src/lib/common/stdset.h new file mode 100644 index 0000000..1c98971 --- /dev/null +++ b/src/lib/common/stdset.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <set> +#include "common/stdpost.h" diff --git a/src/lib/common/stdsstream.h b/src/lib/common/stdsstream.h new file mode 100644 index 0000000..43671ff --- /dev/null +++ b/src/lib/common/stdsstream.h @@ -0,0 +1,376 @@ +/* + * 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 "common/stdpre.h" + +#if HAVE_SSTREAM || !defined(__GNUC__) || (__GNUC__ >= 3) + +#include <sstream> + +#elif defined(__GNUC_MINOR__) && (__GNUC_MINOR__ >= 95) +// g++ 2.95 didn't ship with sstream. the following is a backport +// by Magnus Fromreide of the sstream in g++ 3.0. + +/* This is part of libio/iostream, providing -*- C++ -*- input/output. +Copyright (C) 2012-2016 Symless Ltd. +Copyright (C) 2000 Free Software Foundation + +This file is part of the GNU IO Library. This library is free +software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) +any later version. + +This library 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 library; see the file LICENSE. If not, write to the Free +Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +As a special exception, if you link this library with files +compiled with a GNU compiler to produce an executable, this does not cause +the resulting executable to be covered by the GNU General Public License. +This exception does not however invalidate any other reasons why +the executable file might be covered by the GNU General Public License. */ + +/* Written by Magnus Fromreide (magfr@lysator.liu.se). */ +/* seekoff and ideas for overflow is largely borrowed from libstdc++-v3 */ + +#include <iostream.h> +#include <streambuf.h> +#include <string> + +namespace std +{ + class stringbuf : public streambuf + { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + stringbuf(int which=ios::in|ios::out) + : streambuf(), mode(static_cast<ios::open_mode>(which)), + stream(NULL), stream_len(0) + { + stringbuf_init(); + } + + explicit + stringbuf(const string &str, int which=ios::in|ios::out) + : streambuf(), mode(static_cast<ios::open_mode>(which)), + stream(NULL), stream_len(0) + { + if (mode & (ios::in|ios::out)) + { + stream_len = str.size(); + stream = new char_type[stream_len]; + str.copy(stream, stream_len); + } + stringbuf_init(); + } + + virtual + ~stringbuf() + { + delete[] stream; + } + + string + str() const + { + if (pbase() != 0) + return string(stream, pptr()-pbase()); + else + return string(); + } + + void + str(const string& str) + { + delete[] stream; + stream_len = str.size(); + stream = new char_type[stream_len]; + str.copy(stream, stream_len); + stringbuf_init(); + } + + protected: + // The buffer is already in gptr, so if it ends then it is out of data. + virtual int + underflow() + { + return EOF; + } + + virtual int + overflow(int c = EOF) + { + int res; + if (mode & ios::out) + { + if (c != EOF) + { + streamsize old_stream_len = stream_len; + stream_len += 1; + char_type* new_stream = new char_type[stream_len]; + memcpy(new_stream, stream, old_stream_len); + delete[] stream; + stream = new_stream; + stringbuf_sync(gptr()-eback(), pptr()-pbase()); + sputc(c); + res = c; + } + else + res = EOF; + } + else + res = 0; + return res; + } + + virtual streambuf* + setbuf(char_type* s, streamsize n) + { + if (n != 0) + { + delete[] stream; + stream = new char_type[n]; + memcpy(stream, s, n); + stream_len = n; + stringbuf_sync(0, 0); + } + return this; + } + + virtual pos_type + seekoff(off_type off, ios::seek_dir way, int which = ios::in | ios::out) + { + pos_type ret = pos_type(off_type(-1)); + bool testin = which & ios::in && mode & ios::in; + bool testout = which & ios::out && mode & ios::out; + bool testboth = testin && testout && way != ios::cur; + + if (stream_len && ((testin != testout) || testboth)) + { + char_type* beg = stream; + char_type* curi = NULL; + char_type* curo = NULL; + char_type* endi = NULL; + char_type* endo = NULL; + + if (testin) + { + curi = gptr(); + endi = egptr(); + } + if (testout) + { + curo = pptr(); + endo = epptr(); + } + + off_type newoffi = 0; + off_type newoffo = 0; + if (way == ios::beg) + { + newoffi = beg - curi; + newoffo = beg - curo; + } + else if (way == ios::end) + { + newoffi = endi - curi; + newoffo = endo - curo; + } + + if (testin && newoffi + off + curi - beg >= 0 && + endi - beg >= newoffi + off + curi - beg) + { + gbump(newoffi + off); + ret = pos_type(newoffi + off + curi); + } + if (testout && newoffo + off + curo - beg >= 0 && + endo - beg >= newoffo + off + curo - beg) + { + pbump(newoffo + off); + ret = pos_type(newoffo + off + curo); + } + } + return ret; + } + + virtual pos_type + seekpos(pos_type sp, int which = ios::in | ios::out) + { + pos_type ret = seekoff(sp, ios::beg, which); + return ret; + } + + private: + void + stringbuf_sync(streamsize i, streamsize o) + { + if (mode & ios::in) + setg(stream, stream + i, stream + stream_len); + if (mode & ios::out) + { + setp(stream, stream + stream_len); + pbump(o); + } + } + void + stringbuf_init() + { + if (mode & ios::ate) + stringbuf_sync(0, stream_len); + else + stringbuf_sync(0, 0); + } + + private: + ios::open_mode mode; + char_type* stream; + streamsize stream_len; + }; + + class istringstream : public istream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + istringstream(int which=ios::in) + : istream(&sb), sb(which | ios::in) + { } + + explicit + istringstream(const string& str, int which=ios::in) + : istream(&sb), sb(str, which | ios::in) + { } + + stringbuf* + rdbuf() const + { + return const_cast<stringbuf*>(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + void + str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; + + class ostringstream : public ostream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + ostringstream(int which=ios::out) + : ostream(&sb), sb(which | ios::out) + { } + + explicit + ostringstream(const string& str, int which=ios::out) + : ostream(&sb), sb(str, which | ios::out) + { } + + stringbuf* + rdbuf() const + { + return const_cast<stringbuf*>(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + + void str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; + + class stringstream : public iostream { + public: + typedef char char_type; + typedef int int_type; + typedef streampos pos_type; + typedef streamoff off_type; + + explicit + stringstream(int which=ios::out|ios::in) + : iostream(&sb), sb(which) + { } + + explicit + stringstream(const string& str, int which=ios::out|ios::in) + : iostream(&sb), sb(str, which) + { } + + stringbuf* + rdbuf() const + { + return const_cast<stringbuf*>(&sb); + } + + string + str() const + { + return rdbuf()->str(); + } + + void + str(const string& s) + { + rdbuf()->str(s); + } + private: + stringbuf sb; + }; +}; + +#else /* not g++ 2.95 and no <sstream> */ + +#error "Standard C++ library is missing required sstream header." + +#endif /* not g++ 2.95 and no <sstream> */ + +#include "common/stdpost.h" +#include "common/stdistream.h" diff --git a/src/lib/common/stdstring.h b/src/lib/common/stdstring.h new file mode 100644 index 0000000..f320ca8 --- /dev/null +++ b/src/lib/common/stdstring.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <string> +#include "common/stdpost.h" diff --git a/src/lib/common/stdvector.h b/src/lib/common/stdvector.h new file mode 100644 index 0000000..ab4b853 --- /dev/null +++ b/src/lib/common/stdvector.h @@ -0,0 +1,21 @@ +/* + * 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 "common/stdpre.h" +#include <vector> +#include "common/stdpost.h" diff --git a/src/lib/io/CMakeLists.txt b/src/lib/io/CMakeLists.txt new file mode 100644 index 0000000..32ae7ec --- /dev/null +++ b/src/lib/io/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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() + +add_library(io STATIC ${sources}) diff --git a/src/lib/io/IStream.h b/src/lib/io/IStream.h new file mode 100644 index 0000000..cf93ac4 --- /dev/null +++ b/src/lib/io/IStream.h @@ -0,0 +1,120 @@ +/* + * 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 "base/Event.h" +#include "base/IEventQueue.h" +#include "base/EventTypes.h" + +class IEventQueue; + +namespace barrier { + +//! Bidirectional stream interface +/*! +Defines the interface for all streams. +*/ +class IStream : public IInterface { +public: + IStream() { } + + //! @name manipulators + //@{ + + //! Close the stream + /*! + Closes the stream. Pending input data and buffered output data + are discarded. Use \c flush() before \c close() to send buffered + output data. Attempts to \c read() after a close return 0, + attempts to \c write() generate output error events, and attempts + to \c flush() return immediately. + */ + virtual void close() = 0; + + //! Read from stream + /*! + Read up to \p n bytes into \p buffer, returning the number read + (zero if no data is available or input is shutdown). \p buffer + may be NULL in which case the data is discarded. + */ + virtual UInt32 read(void* buffer, UInt32 n) = 0; + + //! Write to stream + /*! + Write \c n bytes from \c buffer to the stream. If this can't + complete immediately it will block. Data may be buffered in + order to return more quickly. A output error event is generated + when writing fails. + */ + virtual void write(const void* buffer, UInt32 n) = 0; + + //! Flush the stream + /*! + Waits until all buffered data has been written to the stream. + */ + virtual void flush() = 0; + + //! Shutdown input + /*! + Shutdown the input side of the stream. Any pending input data is + discarded and further reads immediately return 0. + */ + virtual void shutdownInput() = 0; + + //! Shutdown output + /*! + Shutdown the output side of the stream. Any buffered output data + is discarded and further writes generate output error events. Use + \c flush() before \c shutdownOutput() to send buffered output data. + */ + virtual void shutdownOutput() = 0; + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this stream. It + should be the source stream in a chain of stream filters. + */ + virtual void* getEventTarget() const = 0; + + //! Test if \c read() will succeed + /*! + Returns true iff an immediate \c read() will return data. This + may or may not be the same as \c getSize() > 0, depending on the + stream type. + */ + virtual bool isReady() const = 0; + + //! Get bytes available to read + /*! + Returns a conservative estimate of the available bytes to read + (i.e. a number not greater than the actual number of bytes). + Some streams may not be able to determine this and will always + return zero. + */ + virtual UInt32 getSize() const = 0; + + //@} +}; + +} diff --git a/src/lib/io/StreamBuffer.cpp b/src/lib/io/StreamBuffer.cpp new file mode 100644 index 0000000..61f05ba --- /dev/null +++ b/src/lib/io/StreamBuffer.cpp @@ -0,0 +1,146 @@ +/* + * 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 "io/StreamBuffer.h" + +// +// StreamBuffer +// + +const UInt32 StreamBuffer::kChunkSize = 4096; + +StreamBuffer::StreamBuffer() : + m_size(0), + m_headUsed(0) +{ + // do nothing +} + +StreamBuffer::~StreamBuffer() +{ + // do nothing +} + +const void* +StreamBuffer::peek(UInt32 n) +{ + assert(n <= m_size); + + // if requesting no data then return NULL so we don't try to access + // an empty list. + if (n == 0) { + return NULL; + } + + // reserve space in first chunk + ChunkList::iterator head = m_chunks.begin(); + head->reserve(n + m_headUsed); + + // consolidate chunks into the first chunk until it has n bytes + ChunkList::iterator scan = head; + ++scan; + while (head->size() - m_headUsed < n && scan != m_chunks.end()) { + head->insert(head->end(), scan->begin(), scan->end()); + scan = m_chunks.erase(scan); + } + + return static_cast<const void*>(&(head->begin()[m_headUsed])); +} + +void +StreamBuffer::pop(UInt32 n) +{ + // discard all chunks if n is greater than or equal to m_size + if (n >= m_size) { + m_size = 0; + m_headUsed = 0; + m_chunks.clear(); + return; + } + + // update size + m_size -= n; + + // discard chunks until more than n bytes would've been discarded + ChunkList::iterator scan = m_chunks.begin(); + assert(scan != m_chunks.end()); + while (scan->size() - m_headUsed <= n) { + n -= (UInt32)scan->size() - m_headUsed; + m_headUsed = 0; + scan = m_chunks.erase(scan); + assert(scan != m_chunks.end()); + } + + // remove left over bytes from the head chunk + if (n > 0) { + m_headUsed += n; + } +} + +void +StreamBuffer::write(const void* vdata, UInt32 n) +{ + assert(vdata != NULL); + + // ignore if no data, otherwise update size + if (n == 0) { + return; + } + m_size += n; + + // cast data to bytes + const UInt8* data = static_cast<const UInt8*>(vdata); + + // point to last chunk if it has space, otherwise append an empty chunk + ChunkList::iterator scan = m_chunks.end(); + if (scan != m_chunks.begin()) { + --scan; + if (scan->size() >= kChunkSize) { + ++scan; + } + } + if (scan == m_chunks.end()) { + scan = m_chunks.insert(scan, Chunk()); + } + + // append data in chunks + while (n > 0) { + // choose number of bytes for next chunk + assert(scan->size() <= kChunkSize); + UInt32 count = kChunkSize - (UInt32)scan->size(); + if (count > n) + count = n; + + // transfer data + scan->insert(scan->end(), data, data + count); + n -= count; + data += count; + + // append another empty chunk if we're not done yet + if (n > 0) { + ++scan; + scan = m_chunks.insert(scan, Chunk()); + } + } +} + +UInt32 +StreamBuffer::getSize() const +{ + return m_size; +} diff --git a/src/lib/io/StreamBuffer.h b/src/lib/io/StreamBuffer.h new file mode 100644 index 0000000..49b666b --- /dev/null +++ b/src/lib/io/StreamBuffer.h @@ -0,0 +1,79 @@ +/* + * 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 "base/EventTypes.h" +#include "common/stdlist.h" +#include "common/stdvector.h" + +//! FIFO of bytes +/*! +This class maintains a FIFO (first-in, last-out) buffer of bytes. +*/ +class StreamBuffer { +public: + StreamBuffer(); + ~StreamBuffer(); + + //! @name manipulators + //@{ + + //! Read data without removing from buffer + /*! + Return a pointer to memory with the next \c n bytes in the buffer + (which must be <= getSize()). The caller must not modify the returned + memory nor delete it. + */ + const void* peek(UInt32 n); + + //! Discard data + /*! + Discards the next \c n bytes. If \c n >= getSize() then the buffer + is cleared. + */ + void pop(UInt32 n); + + //! Write data to buffer + /*! + Appends \c n bytes from \c data to the buffer. + */ + void write(const void* data, UInt32 n); + + //@} + //! @name accessors + //@{ + + //! Get size of buffer + /*! + Returns the number of bytes in the buffer. + */ + UInt32 getSize() const; + + //@} + +private: + static const UInt32 kChunkSize; + + typedef std::vector<UInt8> Chunk; + typedef std::list<Chunk> ChunkList; + + ChunkList m_chunks; + UInt32 m_size; + UInt32 m_headUsed; +}; diff --git a/src/lib/io/StreamFilter.cpp b/src/lib/io/StreamFilter.cpp new file mode 100644 index 0000000..170e237 --- /dev/null +++ b/src/lib/io/StreamFilter.cpp @@ -0,0 +1,118 @@ +/* + * 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 "io/StreamFilter.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +// +// StreamFilter +// + +StreamFilter::StreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream) : + m_stream(stream), + m_adopted(adoptStream), + m_events(events) +{ + // replace handlers for m_stream + m_events->removeHandlers(m_stream->getEventTarget()); + m_events->adoptHandler(Event::kUnknown, m_stream->getEventTarget(), + new TMethodEventJob<StreamFilter>(this, + &StreamFilter::handleUpstreamEvent)); +} + +StreamFilter::~StreamFilter() +{ + m_events->removeHandler(Event::kUnknown, m_stream->getEventTarget()); + if (m_adopted) { + delete m_stream; + } +} + +void +StreamFilter::close() +{ + getStream()->close(); +} + +UInt32 +StreamFilter::read(void* buffer, UInt32 n) +{ + return getStream()->read(buffer, n); +} + +void +StreamFilter::write(const void* buffer, UInt32 n) +{ + getStream()->write(buffer, n); +} + +void +StreamFilter::flush() +{ + getStream()->flush(); +} + +void +StreamFilter::shutdownInput() +{ + getStream()->shutdownInput(); +} + +void +StreamFilter::shutdownOutput() +{ + getStream()->shutdownOutput(); +} + +void* +StreamFilter::getEventTarget() const +{ + return const_cast<void*>(static_cast<const void*>(this)); +} + +bool +StreamFilter::isReady() const +{ + return getStream()->isReady(); +} + +UInt32 +StreamFilter::getSize() const +{ + return getStream()->getSize(); +} + +barrier::IStream* +StreamFilter::getStream() const +{ + return m_stream; +} + +void +StreamFilter::filterEvent(const Event& event) +{ + m_events->dispatchEvent(Event(event.getType(), + getEventTarget(), event.getData())); +} + +void +StreamFilter::handleUpstreamEvent(const Event& event, void*) +{ + filterEvent(event); +} diff --git a/src/lib/io/StreamFilter.h b/src/lib/io/StreamFilter.h new file mode 100644 index 0000000..e578e0c --- /dev/null +++ b/src/lib/io/StreamFilter.h @@ -0,0 +1,73 @@ +/* + * 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 "io/IStream.h" +#include "base/IEventQueue.h" + +//! A stream filter +/*! +This class wraps a stream. Subclasses provide indirect access +to the wrapped stream, typically performing some filtering. +*/ +class StreamFilter : public barrier::IStream { +public: + /*! + Create a wrapper around \c stream. Iff \c adoptStream is true then + this object takes ownership of the stream and will delete it in the + d'tor. + */ + StreamFilter(IEventQueue* events, barrier::IStream* stream, bool adoptStream = true); + virtual ~StreamFilter(); + + // IStream overrides + // These all just forward to the underlying stream except getEventTarget. + // Override as necessary. getEventTarget returns a pointer to this. + virtual void close(); + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual void* getEventTarget() const; + virtual bool isReady() const; + virtual UInt32 getSize() const; + + //! Get the stream + /*! + Returns the stream passed to the c'tor. + */ + barrier::IStream* getStream() const; + +protected: + //! Handle events from source stream + /*! + Does the event filtering. The default simply dispatches an event + identical except using this object as the event target. + */ + virtual void filterEvent(const Event&); + +private: + void handleUpstreamEvent(const Event&, void*); + +private: + barrier::IStream* m_stream; + bool m_adopted; + IEventQueue* m_events; +}; diff --git a/src/lib/io/XIO.cpp b/src/lib/io/XIO.cpp new file mode 100644 index 0000000..1299d23 --- /dev/null +++ b/src/lib/io/XIO.cpp @@ -0,0 +1,51 @@ +/* + * 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 "io/XIO.h" + +// +// XIOClosed +// + +String +XIOClosed::getWhat() const throw() +{ + return format("XIOClosed", "already closed"); +} + + +// +// XIOEndOfStream +// + +String +XIOEndOfStream::getWhat() const throw() +{ + return format("XIOEndOfStream", "reached end of stream"); +} + + +// +// XIOWouldBlock +// + +String +XIOWouldBlock::getWhat() const throw() +{ + return format("XIOWouldBlock", "stream operation would block"); +} diff --git a/src/lib/io/XIO.h b/src/lib/io/XIO.h new file mode 100644 index 0000000..4964441 --- /dev/null +++ b/src/lib/io/XIO.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 "base/XBase.h" + +//! Generic I/O exception +XBASE_SUBCLASS(XIO, XBase); + +//! I/O closing exception +/*! +Thrown if a stream cannot be closed. +*/ +XBASE_SUBCLASS(XIOClose, XIO); + +//! I/O already closed exception +/*! +Thrown when attempting to close or perform I/O on an already closed. +stream. +*/ +XBASE_SUBCLASS_WHAT(XIOClosed, XIO); + +//! I/O end of stream exception +/*! +Thrown when attempting to read beyond the end of a stream. +*/ +XBASE_SUBCLASS_WHAT(XIOEndOfStream, XIO); + +//! I/O would block exception +/*! +Thrown if an operation on a stream would block. +*/ +XBASE_SUBCLASS_WHAT(XIOWouldBlock, XIO); diff --git a/src/lib/ipc/CMakeLists.txt b/src/lib/ipc/CMakeLists.txt new file mode 100644 index 0000000..3c7302a --- /dev/null +++ b/src/lib/ipc/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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() + +add_library(ipc STATIC ${sources}) + +if (UNIX) + target_link_libraries(ipc arch base common mt io net synlib) +endif() diff --git a/src/lib/ipc/Ipc.cpp b/src/lib/ipc/Ipc.cpp new file mode 100644 index 0000000..78b8407 --- /dev/null +++ b/src/lib/ipc/Ipc.cpp @@ -0,0 +1,24 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/Ipc.h" + +const char* kIpcMsgHello = "IHEL%1i"; +const char* kIpcMsgLogLine = "ILOG%s"; +const char* kIpcMsgCommand = "ICMD%s%1i"; +const char* kIpcMsgShutdown = "ISDN"; diff --git a/src/lib/ipc/Ipc.h b/src/lib/ipc/Ipc.h new file mode 100644 index 0000000..bc69c08 --- /dev/null +++ b/src/lib/ipc/Ipc.h @@ -0,0 +1,52 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#define IPC_HOST "127.0.0.1" +#define IPC_PORT 24801 + +enum EIpcMessage { + kIpcHello, + kIpcLogLine, + kIpcCommand, + kIpcShutdown, +}; + +enum EIpcClientType { + kIpcClientUnknown, + kIpcClientGui, + kIpcClientNode, +}; + +// handshake: node/gui -> daemon +// $1 = type, the client identifies it's self as gui or node (barrierc/s). +extern const char* kIpcMsgHello; + +// log line: daemon -> gui +// $1 = aggregate log lines collected from barriers/c or the daemon itself. +extern const char* kIpcMsgLogLine; + +// command: gui -> daemon +// $1 = command; the command for the daemon to launch, typically the full +// path to barriers/c. $2 = true when process must be elevated on ms windows. +extern const char* kIpcMsgCommand; + +// shutdown: daemon -> node +// the daemon tells barriers/c to shut down gracefully. +extern const char* kIpcMsgShutdown; diff --git a/src/lib/ipc/IpcClient.cpp b/src/lib/ipc/IpcClient.cpp new file mode 100644 index 0000000..4eeae5b --- /dev/null +++ b/src/lib/ipc/IpcClient.cpp @@ -0,0 +1,108 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcClient.h" +#include "ipc/Ipc.h" +#include "ipc/IpcServerProxy.h" +#include "ipc/IpcMessage.h" +#include "base/TMethodEventJob.h" + +// +// IpcClient +// + +IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : + m_serverAddress(NetworkAddress(IPC_HOST, IPC_PORT)), + m_socket(events, socketMultiplexer, IArchNetwork::kINET), + m_server(nullptr), + m_events(events) +{ + init(); +} + +IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) : + m_serverAddress(NetworkAddress(IPC_HOST, port)), + m_socket(events, socketMultiplexer, IArchNetwork::kINET), + m_server(nullptr), + m_events(events) +{ + init(); +} + +void +IpcClient::init() +{ + m_serverAddress.resolve(); +} + +IpcClient::~IpcClient() +{ +} + +void +IpcClient::connect() +{ + m_events->adoptHandler( + m_events->forIDataSocket().connected(), m_socket.getEventTarget(), + new TMethodEventJob<IpcClient>( + this, &IpcClient::handleConnected)); + + m_socket.connect(m_serverAddress); + m_server = new IpcServerProxy(m_socket, m_events); + + m_events->adoptHandler( + m_events->forIpcServerProxy().messageReceived(), m_server, + new TMethodEventJob<IpcClient>( + this, &IpcClient::handleMessageReceived)); +} + +void +IpcClient::disconnect() +{ + m_events->removeHandler(m_events->forIDataSocket().connected(), m_socket.getEventTarget()); + m_events->removeHandler(m_events->forIpcServerProxy().messageReceived(), m_server); + + m_server->disconnect(); + delete m_server; + m_server = nullptr; +} + +void +IpcClient::send(const IpcMessage& message) +{ + assert(m_server != nullptr); + m_server->send(message); +} + +void +IpcClient::handleConnected(const Event&, void*) +{ + m_events->addEvent(Event( + m_events->forIpcClient().connected(), this, m_server, Event::kDontFreeData)); + + IpcHelloMessage message(kIpcClientNode); + send(message); +} + +void +IpcClient::handleMessageReceived(const Event& e, void*) +{ + Event event(m_events->forIpcClient().messageReceived(), this); + event.setDataObject(e.getDataObject()); + m_events->addEvent(event); +} diff --git a/src/lib/ipc/IpcClient.h b/src/lib/ipc/IpcClient.h new file mode 100644 index 0000000..1e9bca6 --- /dev/null +++ b/src/lib/ipc/IpcClient.h @@ -0,0 +1,64 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "net/NetworkAddress.h" +#include "net/TCPSocket.h" +#include "base/EventTypes.h" + +class IpcServerProxy; +class IpcMessage; +class IEventQueue; +class SocketMultiplexer; + +//! IPC client for communication between daemon and GUI. +/*! + * See \ref IpcServer description. + */ +class IpcClient { +public: + IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer); + IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port); + virtual ~IpcClient(); + + //! @name manipulators + //@{ + + //! Connects to the IPC server at localhost. + void connect(); + + //! Disconnects from the IPC server. + void disconnect(); + + //! Sends a message to the server. + void send(const IpcMessage& message); + + //@} + +private: + void init(); + void handleConnected(const Event&, void*); + void handleMessageReceived(const Event&, void*); + +private: + NetworkAddress m_serverAddress; + TCPSocket m_socket; + IpcServerProxy* m_server; + IEventQueue* m_events; +}; diff --git a/src/lib/ipc/IpcClientProxy.cpp b/src/lib/ipc/IpcClientProxy.cpp new file mode 100644 index 0000000..af85eca --- /dev/null +++ b/src/lib/ipc/IpcClientProxy.cpp @@ -0,0 +1,194 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcClientProxy.h" + +#include "ipc/Ipc.h" +#include "ipc/IpcMessage.h" +#include "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "arch/Arch.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +// +// IpcClientProxy +// + +IpcClientProxy::IpcClientProxy(barrier::IStream& stream, IEventQueue* events) : + m_stream(stream), + m_clientType(kIpcClientUnknown), + m_disconnecting(false), + m_readMutex(ARCH->newMutex()), + m_writeMutex(ARCH->newMutex()), + m_events(events) +{ + m_events->adoptHandler( + m_events->forIStream().inputReady(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleData)); + + m_events->adoptHandler( + m_events->forIStream().outputError(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleWriteError)); + + m_events->adoptHandler( + m_events->forIStream().inputShutdown(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleDisconnect)); + + m_events->adoptHandler( + m_events->forIStream().outputShutdown(), stream.getEventTarget(), + new TMethodEventJob<IpcClientProxy>( + this, &IpcClientProxy::handleWriteError)); +} + +IpcClientProxy::~IpcClientProxy() +{ + m_events->removeHandler( + m_events->forIStream().inputReady(), m_stream.getEventTarget()); + m_events->removeHandler( + m_events->forIStream().outputError(), m_stream.getEventTarget()); + m_events->removeHandler( + m_events->forIStream().inputShutdown(), m_stream.getEventTarget()); + m_events->removeHandler( + m_events->forIStream().outputShutdown(), m_stream.getEventTarget()); + + // don't delete the stream while it's being used. + ARCH->lockMutex(m_readMutex); + ARCH->lockMutex(m_writeMutex); + delete &m_stream; + ARCH->unlockMutex(m_readMutex); + ARCH->unlockMutex(m_writeMutex); + + ARCH->closeMutex(m_readMutex); + ARCH->closeMutex(m_writeMutex); +} + +void +IpcClientProxy::handleDisconnect(const Event&, void*) +{ + disconnect(); + LOG((CLOG_DEBUG "ipc client disconnected")); +} + +void +IpcClientProxy::handleWriteError(const Event&, void*) +{ + disconnect(); + LOG((CLOG_DEBUG "ipc client write error")); +} + +void +IpcClientProxy::handleData(const Event&, void*) +{ + // don't allow the dtor to destroy the stream while we're using it. + ArchMutexLock lock(m_readMutex); + + LOG((CLOG_DEBUG "start ipc handle data")); + + UInt8 code[4]; + UInt32 n = m_stream.read(code, 4); + while (n != 0) { + + LOG((CLOG_DEBUG "ipc read: %c%c%c%c", + code[0], code[1], code[2], code[3])); + + IpcMessage* m = nullptr; + if (memcmp(code, kIpcMsgHello, 4) == 0) { + m = parseHello(); + } + else if (memcmp(code, kIpcMsgCommand, 4) == 0) { + m = parseCommand(); + } + else { + LOG((CLOG_ERR "invalid ipc message")); + disconnect(); + } + + // don't delete with this event; the data is passed to a new event. + Event e(m_events->forIpcClientProxy().messageReceived(), this, NULL, Event::kDontFreeData); + e.setDataObject(m); + m_events->addEvent(e); + + n = m_stream.read(code, 4); + } + + LOG((CLOG_DEBUG "finished ipc handle data")); +} + +void +IpcClientProxy::send(const IpcMessage& message) +{ + // don't allow other threads to write until we've finished the entire + // message. stream write is locked, but only for that single write. + // also, don't allow the dtor to destroy the stream while we're using it. + ArchMutexLock lock(m_writeMutex); + + LOG((CLOG_DEBUG4 "ipc write: %d", message.type())); + + switch (message.type()) { + case kIpcLogLine: { + const IpcLogLineMessage& llm = static_cast<const IpcLogLineMessage&>(message); + const String logLine = llm.logLine(); + ProtocolUtil::writef(&m_stream, kIpcMsgLogLine, &logLine); + break; + } + + case kIpcShutdown: + ProtocolUtil::writef(&m_stream, kIpcMsgShutdown); + break; + + default: + LOG((CLOG_ERR "ipc message not supported: %d", message.type())); + break; + } +} + +IpcHelloMessage* +IpcClientProxy::parseHello() +{ + UInt8 type; + ProtocolUtil::readf(&m_stream, kIpcMsgHello + 4, &type); + + m_clientType = static_cast<EIpcClientType>(type); + + // must be deleted by event handler. + return new IpcHelloMessage(m_clientType); +} + +IpcCommandMessage* +IpcClientProxy::parseCommand() +{ + String command; + UInt8 elevate; + ProtocolUtil::readf(&m_stream, kIpcMsgCommand + 4, &command, &elevate); + + // must be deleted by event handler. + return new IpcCommandMessage(command, elevate != 0); +} + +void +IpcClientProxy::disconnect() +{ + LOG((CLOG_DEBUG "ipc disconnect, closing stream")); + m_disconnecting = true; + m_stream.close(); + m_events->addEvent(Event(m_events->forIpcClientProxy().disconnected(), this)); +} diff --git a/src/lib/ipc/IpcClientProxy.h b/src/lib/ipc/IpcClientProxy.h new file mode 100644 index 0000000..eaa12c7 --- /dev/null +++ b/src/lib/ipc/IpcClientProxy.h @@ -0,0 +1,55 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "ipc/Ipc.h" +#include "arch/IArchMultithread.h" +#include "base/EventTypes.h" +#include "base/Event.h" + +namespace barrier { class IStream; } +class IpcMessage; +class IpcCommandMessage; +class IpcHelloMessage; +class IEventQueue; + +class IpcClientProxy { + friend class IpcServer; + +public: + IpcClientProxy(barrier::IStream& stream, IEventQueue* events); + virtual ~IpcClientProxy(); + +private: + void send(const IpcMessage& message); + void handleData(const Event&, void*); + void handleDisconnect(const Event&, void*); + void handleWriteError(const Event&, void*); + IpcHelloMessage* parseHello(); + IpcCommandMessage* parseCommand(); + void disconnect(); + +private: + barrier::IStream& m_stream; + EIpcClientType m_clientType; + bool m_disconnecting; + ArchMutex m_readMutex; + ArchMutex m_writeMutex; + IEventQueue* m_events; +}; diff --git a/src/lib/ipc/IpcLogOutputter.cpp b/src/lib/ipc/IpcLogOutputter.cpp new file mode 100644 index 0000000..984793e --- /dev/null +++ b/src/lib/ipc/IpcLogOutputter.cpp @@ -0,0 +1,228 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcLogOutputter.h" + +#include "ipc/IpcServer.h" +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" +#include "ipc/IpcClientProxy.h" +#include "mt/Thread.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Event.h" +#include "base/EventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" + +enum EIpcLogOutputter { + kBufferMaxSize = 1000, + kMaxSendLines = 100, + kBufferRateWriteLimit = 1000, // writes per kBufferRateTime + kBufferRateTimeLimit = 1 // seconds +}; + +IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread) : + m_ipcServer(ipcServer), + m_bufferMutex(ARCH->newMutex()), + m_sending(false), + m_bufferThread(nullptr), + m_running(false), + m_notifyCond(ARCH->newCondVar()), + m_notifyMutex(ARCH->newMutex()), + m_bufferThreadId(0), + m_bufferWaiting(false), + m_bufferMaxSize(kBufferMaxSize), + m_bufferRateWriteLimit(kBufferRateWriteLimit), + m_bufferRateTimeLimit(kBufferRateTimeLimit), + m_bufferWriteCount(0), + m_bufferRateStart(ARCH->time()), + m_clientType(clientType), + m_runningMutex(ARCH->newMutex()) +{ + if (useThread) { + m_bufferThread = new Thread(new TMethodJob<IpcLogOutputter>( + this, &IpcLogOutputter::bufferThread)); + } +} + +IpcLogOutputter::~IpcLogOutputter() +{ + close(); + + ARCH->closeMutex(m_bufferMutex); + + if (m_bufferThread != nullptr) { + m_bufferThread->cancel(); + m_bufferThread->wait(); + delete m_bufferThread; + } + + ARCH->closeCondVar(m_notifyCond); + ARCH->closeMutex(m_notifyMutex); +} + +void +IpcLogOutputter::open(const char* title) +{ +} + +void +IpcLogOutputter::close() +{ + if (m_bufferThread != nullptr) { + ArchMutexLock lock(m_runningMutex); + m_running = false; + notifyBuffer(); + m_bufferThread->wait(5); + } +} + +void +IpcLogOutputter::show(bool showIfEmpty) +{ +} + +bool +IpcLogOutputter::write(ELevel, const char* text) +{ + // ignore events from the buffer thread (would cause recursion). + if (m_bufferThread != nullptr && + Thread::getCurrentThread().getID() == m_bufferThreadId) { + return true; + } + + appendBuffer(text); + notifyBuffer(); + + return true; +} + +void +IpcLogOutputter::appendBuffer(const String& text) +{ + ArchMutexLock lock(m_bufferMutex); + + double elapsed = ARCH->time() - m_bufferRateStart; + if (elapsed < m_bufferRateTimeLimit) { + if (m_bufferWriteCount >= m_bufferRateWriteLimit) { + // discard the log line if we've logged too much. + return; + } + } + else { + m_bufferWriteCount = 0; + m_bufferRateStart = ARCH->time(); + } + + if (m_buffer.size() >= m_bufferMaxSize) { + // if the queue is exceeds size limit, + // throw away the oldest item + m_buffer.pop_front(); + } + + m_buffer.push_back(text); + m_bufferWriteCount++; +} + +bool +IpcLogOutputter::isRunning() +{ + ArchMutexLock lock(m_runningMutex); + return m_running; +} + +void +IpcLogOutputter::bufferThread(void*) +{ + m_bufferThreadId = m_bufferThread->getID(); + m_running = true; + + try { + while (isRunning()) { + if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) { + ArchMutexLock lock(m_notifyMutex); + ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1); + } + + sendBuffer(); + } + } + catch (XArch& e) { + LOG((CLOG_ERR "ipc log buffer thread error, %s", e.what())); + } + + LOG((CLOG_DEBUG "ipc log buffer thread finished")); +} + +void +IpcLogOutputter::notifyBuffer() +{ + ArchMutexLock lock(m_notifyMutex); + ARCH->broadcastCondVar(m_notifyCond); +} + +String +IpcLogOutputter::getChunk(size_t count) +{ + ArchMutexLock lock(m_bufferMutex); + + if (m_buffer.size() < count) { + count = m_buffer.size(); + } + + String chunk; + for (size_t i = 0; i < count; i++) { + chunk.append(m_buffer.front()); + chunk.append("\n"); + m_buffer.pop_front(); + } + return chunk; +} + +void +IpcLogOutputter::sendBuffer() +{ + if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) { + return; + } + + IpcLogLineMessage message(getChunk(kMaxSendLines)); + m_sending = true; + m_ipcServer.send(message, kIpcClientGui); + m_sending = false; +} + +void +IpcLogOutputter::bufferMaxSize(UInt16 bufferMaxSize) +{ + m_bufferMaxSize = bufferMaxSize; +} + +UInt16 +IpcLogOutputter::bufferMaxSize() const +{ + return m_bufferMaxSize; +} + +void +IpcLogOutputter::bufferRateLimit(UInt16 writeLimit, double timeLimit) +{ + m_bufferRateWriteLimit = writeLimit; + m_bufferRateTimeLimit = timeLimit; +} diff --git a/src/lib/ipc/IpcLogOutputter.h b/src/lib/ipc/IpcLogOutputter.h new file mode 100644 index 0000000..461f022 --- /dev/null +++ b/src/lib/ipc/IpcLogOutputter.h @@ -0,0 +1,119 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "arch/Arch.h" +#include "arch/IArchMultithread.h" +#include "base/ILogOutputter.h" +#include "ipc/Ipc.h" + +#include <deque> + +class IpcServer; +class Event; +class IpcClientProxy; + +//! Write log to GUI over IPC +/*! +This outputter writes output to the GUI via IPC. +*/ +class IpcLogOutputter : public ILogOutputter { +public: + /*! + If \p useThread is \c true, the buffer will be sent using a thread. + If \p useThread is \c false, then the buffer needs to be sent manually + using the \c sendBuffer() function. + */ + IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread); + virtual ~IpcLogOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + + //! @name manipulators + //@{ + + //! Notify that the buffer should be sent. + void notifyBuffer(); + + //! Set the buffer size + /*! + Set the maximum size of the buffer to protect memory + from runaway logging. + */ + void bufferMaxSize(UInt16 bufferMaxSize); + + //! Set the rate limit + /*! + Set the maximum number of \p writeRate for every \p timeRate in seconds. + */ + void bufferRateLimit(UInt16 writeLimit, double timeLimit); + + //! Send the buffer + /*! + Sends a chunk of the buffer to the IPC server, normally called + when threaded mode is on. + */ + void sendBuffer(); + + //@} + + //! @name accessors + //@{ + + //! Get the buffer size + /*! + Returns the maximum size of the buffer. + */ + UInt16 bufferMaxSize() const; + + //@} + +private: + void init(); + void bufferThread(void*); + String getChunk(size_t count); + void appendBuffer(const String& text); + bool isRunning(); + +private: + typedef std::deque<String> Buffer; + + IpcServer& m_ipcServer; + Buffer m_buffer; + ArchMutex m_bufferMutex; + bool m_sending; + Thread* m_bufferThread; + bool m_running; + ArchCond m_notifyCond; + ArchMutex m_notifyMutex; + bool m_bufferWaiting; + IArchMultithread::ThreadID + m_bufferThreadId; + UInt16 m_bufferMaxSize; + UInt16 m_bufferRateWriteLimit; + double m_bufferRateTimeLimit; + UInt16 m_bufferWriteCount; + double m_bufferRateStart; + EIpcClientType m_clientType; + ArchMutex m_runningMutex; +}; diff --git a/src/lib/ipc/IpcMessage.cpp b/src/lib/ipc/IpcMessage.cpp new file mode 100644 index 0000000..deef22d --- /dev/null +++ b/src/lib/ipc/IpcMessage.cpp @@ -0,0 +1,69 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" + +IpcMessage::IpcMessage(UInt8 type) : + m_type(type) +{ +} + +IpcMessage::~IpcMessage() +{ +} + +IpcHelloMessage::IpcHelloMessage(EIpcClientType clientType) : + IpcMessage(kIpcHello), + m_clientType(clientType) +{ +} + +IpcHelloMessage::~IpcHelloMessage() +{ +} + +IpcShutdownMessage::IpcShutdownMessage() : +IpcMessage(kIpcShutdown) +{ +} + +IpcShutdownMessage::~IpcShutdownMessage() +{ +} + +IpcLogLineMessage::IpcLogLineMessage(const String& logLine) : +IpcMessage(kIpcLogLine), +m_logLine(logLine) +{ +} + +IpcLogLineMessage::~IpcLogLineMessage() +{ +} + +IpcCommandMessage::IpcCommandMessage(const String& command, bool elevate) : +IpcMessage(kIpcCommand), +m_command(command), +m_elevate(elevate) +{ +} + +IpcCommandMessage::~IpcCommandMessage() +{ +} diff --git a/src/lib/ipc/IpcMessage.h b/src/lib/ipc/IpcMessage.h new file mode 100644 index 0000000..5cc3d79 --- /dev/null +++ b/src/lib/ipc/IpcMessage.h @@ -0,0 +1,85 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "ipc/Ipc.h" +#include "base/EventTypes.h" +#include "base/String.h" +#include "base/Event.h" + +class IpcMessage : public EventData { +public: + virtual ~IpcMessage(); + + //! Gets the message type ID. + UInt8 type() const { return m_type; } + +protected: + IpcMessage(UInt8 type); + +private: + UInt8 m_type; +}; + +class IpcHelloMessage : public IpcMessage { +public: + IpcHelloMessage(EIpcClientType clientType); + virtual ~IpcHelloMessage(); + + //! Gets the message type ID. + EIpcClientType clientType() const { return m_clientType; } + +private: + EIpcClientType m_clientType; +}; + +class IpcShutdownMessage : public IpcMessage { +public: + IpcShutdownMessage(); + virtual ~IpcShutdownMessage(); +}; + + +class IpcLogLineMessage : public IpcMessage { +public: + IpcLogLineMessage(const String& logLine); + virtual ~IpcLogLineMessage(); + + //! Gets the log line. + String logLine() const { return m_logLine; } + +private: + String m_logLine; +}; + +class IpcCommandMessage : public IpcMessage { +public: + IpcCommandMessage(const String& command, bool elevate); + virtual ~IpcCommandMessage(); + + //! Gets the command. + String command() const { return m_command; } + + //! Gets whether or not the process should be elevated on MS Windows. + bool elevate() const { return m_elevate; } + +private: + String m_command; + bool m_elevate; +}; diff --git a/src/lib/ipc/IpcServer.cpp b/src/lib/ipc/IpcServer.cpp new file mode 100644 index 0000000..e05a913 --- /dev/null +++ b/src/lib/ipc/IpcServer.cpp @@ -0,0 +1,187 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcServer.h" + +#include "ipc/Ipc.h" +#include "ipc/IpcClientProxy.h" +#include "ipc/IpcMessage.h" +#include "net/IDataSocket.h" +#include "io/IStream.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/Event.h" +#include "base/Log.h" + +// +// IpcServer +// + +IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : + m_mock(false), + m_events(events), + m_socketMultiplexer(socketMultiplexer), + m_socket(nullptr), + m_address(NetworkAddress(IPC_HOST, IPC_PORT)) +{ + init(); +} + +IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) : + m_mock(false), + m_events(events), + m_socketMultiplexer(socketMultiplexer), + m_address(NetworkAddress(IPC_HOST, port)) +{ + init(); +} + +void +IpcServer::init() +{ + m_socket = new TCPListenSocket(m_events, m_socketMultiplexer, IArchNetwork::kINET); + + m_clientsMutex = ARCH->newMutex(); + m_address.resolve(); + + m_events->adoptHandler( + m_events->forIListenSocket().connecting(), m_socket, + new TMethodEventJob<IpcServer>( + this, &IpcServer::handleClientConnecting)); +} + +IpcServer::~IpcServer() +{ + if (m_mock) { + return; + } + + if (m_socket != nullptr) { + delete m_socket; + } + + ARCH->lockMutex(m_clientsMutex); + ClientList::iterator it; + for (it = m_clients.begin(); it != m_clients.end(); it++) { + deleteClient(*it); + } + m_clients.clear(); + ARCH->unlockMutex(m_clientsMutex); + ARCH->closeMutex(m_clientsMutex); + + m_events->removeHandler(m_events->forIListenSocket().connecting(), m_socket); +} + +void +IpcServer::listen() +{ + m_socket->bind(m_address); +} + +void +IpcServer::handleClientConnecting(const Event&, void*) +{ + barrier::IStream* stream = m_socket->accept(); + if (stream == NULL) { + return; + } + + LOG((CLOG_DEBUG "accepted ipc client connection")); + + ARCH->lockMutex(m_clientsMutex); + IpcClientProxy* proxy = new IpcClientProxy(*stream, m_events); + m_clients.push_back(proxy); + ARCH->unlockMutex(m_clientsMutex); + + m_events->adoptHandler( + m_events->forIpcClientProxy().disconnected(), proxy, + new TMethodEventJob<IpcServer>( + this, &IpcServer::handleClientDisconnected)); + + m_events->adoptHandler( + m_events->forIpcClientProxy().messageReceived(), proxy, + new TMethodEventJob<IpcServer>( + this, &IpcServer::handleMessageReceived)); + + m_events->addEvent(Event( + m_events->forIpcServer().clientConnected(), this, proxy, Event::kDontFreeData)); +} + +void +IpcServer::handleClientDisconnected(const Event& e, void*) +{ + IpcClientProxy* proxy = static_cast<IpcClientProxy*>(e.getTarget()); + + ArchMutexLock lock(m_clientsMutex); + m_clients.remove(proxy); + deleteClient(proxy); + + LOG((CLOG_DEBUG "ipc client proxy removed, connected=%d", m_clients.size())); +} + +void +IpcServer::handleMessageReceived(const Event& e, void*) +{ + Event event(m_events->forIpcServer().messageReceived(), this); + event.setDataObject(e.getDataObject()); + m_events->addEvent(event); +} + +void +IpcServer::deleteClient(IpcClientProxy* proxy) +{ + m_events->removeHandler(m_events->forIpcClientProxy().messageReceived(), proxy); + m_events->removeHandler(m_events->forIpcClientProxy().disconnected(), proxy); + delete proxy; +} + +bool +IpcServer::hasClients(EIpcClientType clientType) const +{ + ArchMutexLock lock(m_clientsMutex); + + if (m_clients.empty()) { + return false; + } + + ClientList::const_iterator it; + for (it = m_clients.begin(); it != m_clients.end(); it++) { + // at least one client is alive and type matches, there are clients. + IpcClientProxy* p = *it; + if (!p->m_disconnecting && p->m_clientType == clientType) { + return true; + } + } + + // all clients must be disconnecting, no active clients. + return false; +} + +void +IpcServer::send(const IpcMessage& message, EIpcClientType filterType) +{ + ArchMutexLock lock(m_clientsMutex); + + ClientList::iterator it; + for (it = m_clients.begin(); it != m_clients.end(); it++) { + IpcClientProxy* proxy = *it; + if (proxy->m_clientType == filterType) { + proxy->send(message); + } + } +} diff --git a/src/lib/ipc/IpcServer.h b/src/lib/ipc/IpcServer.h new file mode 100644 index 0000000..d9bbe3e --- /dev/null +++ b/src/lib/ipc/IpcServer.h @@ -0,0 +1,92 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "ipc/Ipc.h" +#include "net/TCPListenSocket.h" +#include "net/NetworkAddress.h" +#include "arch/Arch.h" +#include "base/EventTypes.h" + +#include <list> + +class Event; +class IpcClientProxy; +class IpcMessage; +class IEventQueue; +class SocketMultiplexer; + +//! IPC server for communication between daemon and GUI. +/*! +The IPC server listens on localhost. The IPC client runs on both the +client/server process or the GUI. The IPC server runs on the daemon process. +This allows the GUI to send config changes to the daemon and client/server, +and allows the daemon and client/server to send log data to the GUI. +*/ +class IpcServer { +public: + IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer); + IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port); + virtual ~IpcServer(); + + //! @name manipulators + //@{ + + //! Opens a TCP socket only allowing local connections. + virtual void listen(); + + //! Send a message to all clients matching the filter type. + virtual void send(const IpcMessage& message, EIpcClientType filterType); + + //@} + //! @name accessors + //@{ + + //! Returns true when there are clients of the specified type connected. + virtual bool hasClients(EIpcClientType clientType) const; + + //@} + +private: + void init(); + void handleClientConnecting(const Event&, void*); + void handleClientDisconnected(const Event&, void*); + void handleMessageReceived(const Event&, void*); + void deleteClient(IpcClientProxy* proxy); + +private: + typedef std::list<IpcClientProxy*> ClientList; + + bool m_mock; + IEventQueue* m_events; + SocketMultiplexer* m_socketMultiplexer; + TCPListenSocket* m_socket; + NetworkAddress m_address; + ClientList m_clients; + ArchMutex m_clientsMutex; + +#ifdef TEST_ENV +public: + IpcServer() : + m_mock(true), + m_events(nullptr), + m_socketMultiplexer(nullptr), + m_socket(nullptr) { } +#endif +}; diff --git a/src/lib/ipc/IpcServerProxy.cpp b/src/lib/ipc/IpcServerProxy.cpp new file mode 100644 index 0000000..820e1ab --- /dev/null +++ b/src/lib/ipc/IpcServerProxy.cpp @@ -0,0 +1,123 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "ipc/IpcServerProxy.h" + +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" +#include "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +// +// IpcServerProxy +// + +IpcServerProxy::IpcServerProxy(barrier::IStream& stream, IEventQueue* events) : + m_stream(stream), + m_events(events) +{ + m_events->adoptHandler(m_events->forIStream().inputReady(), + stream.getEventTarget(), + new TMethodEventJob<IpcServerProxy>( + this, &IpcServerProxy::handleData)); +} + +IpcServerProxy::~IpcServerProxy() +{ + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream.getEventTarget()); +} + +void +IpcServerProxy::handleData(const Event&, void*) +{ + LOG((CLOG_DEBUG "start ipc handle data")); + + UInt8 code[4]; + UInt32 n = m_stream.read(code, 4); + while (n != 0) { + + LOG((CLOG_DEBUG "ipc read: %c%c%c%c", + code[0], code[1], code[2], code[3])); + + IpcMessage* m = nullptr; + if (memcmp(code, kIpcMsgLogLine, 4) == 0) { + m = parseLogLine(); + } + else if (memcmp(code, kIpcMsgShutdown, 4) == 0) { + m = new IpcShutdownMessage(); + } + else { + LOG((CLOG_ERR "invalid ipc message")); + disconnect(); + } + + // don't delete with this event; the data is passed to a new event. + Event e(m_events->forIpcServerProxy().messageReceived(), this, NULL, Event::kDontFreeData); + e.setDataObject(m); + m_events->addEvent(e); + + n = m_stream.read(code, 4); + } + + LOG((CLOG_DEBUG "finished ipc handle data")); +} + +void +IpcServerProxy::send(const IpcMessage& message) +{ + LOG((CLOG_DEBUG4 "ipc write: %d", message.type())); + + switch (message.type()) { + case kIpcHello: { + const IpcHelloMessage& hm = static_cast<const IpcHelloMessage&>(message); + ProtocolUtil::writef(&m_stream, kIpcMsgHello, hm.clientType()); + break; + } + + case kIpcCommand: { + const IpcCommandMessage& cm = static_cast<const IpcCommandMessage&>(message); + const String command = cm.command(); + ProtocolUtil::writef(&m_stream, kIpcMsgCommand, &command); + break; + } + + default: + LOG((CLOG_ERR "ipc message not supported: %d", message.type())); + break; + } +} + +IpcLogLineMessage* +IpcServerProxy::parseLogLine() +{ + String logLine; + ProtocolUtil::readf(&m_stream, kIpcMsgLogLine + 4, &logLine); + + // must be deleted by event handler. + return new IpcLogLineMessage(logLine); +} + +void +IpcServerProxy::disconnect() +{ + LOG((CLOG_DEBUG "ipc disconnect, closing stream")); + m_stream.close(); +} diff --git a/src/lib/ipc/IpcServerProxy.h b/src/lib/ipc/IpcServerProxy.h new file mode 100644 index 0000000..f2218a4 --- /dev/null +++ b/src/lib/ipc/IpcServerProxy.h @@ -0,0 +1,46 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "base/Event.h" +#include "base/EventTypes.h" + +namespace barrier { class IStream; } +class IpcMessage; +class IpcLogLineMessage; +class IEventQueue; + +class IpcServerProxy { + friend class IpcClient; + +public: + IpcServerProxy(barrier::IStream& stream, IEventQueue* events); + virtual ~IpcServerProxy(); + +private: + void send(const IpcMessage& message); + + void handleData(const Event&, void*); + IpcLogLineMessage* parseLogLine(); + void disconnect(); + +private: + barrier::IStream& m_stream; + IEventQueue* m_events; +}; diff --git a/src/lib/mt/CMakeLists.txt b/src/lib/mt/CMakeLists.txt new file mode 100644 index 0000000..9ee5ea4 --- /dev/null +++ b/src/lib/mt/CMakeLists.txt @@ -0,0 +1,24 @@ +# 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() + +add_library(mt STATIC ${sources}) diff --git a/src/lib/mt/CondVar.cpp b/src/lib/mt/CondVar.cpp new file mode 100644 index 0000000..11318f9 --- /dev/null +++ b/src/lib/mt/CondVar.cpp @@ -0,0 +1,91 @@ +/* + * 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 "mt/CondVar.h" +#include "arch/Arch.h" +#include "base/Stopwatch.h" + +// +// CondVarBase +// + +CondVarBase::CondVarBase(Mutex* mutex) : + m_mutex(mutex) +{ + assert(m_mutex != NULL); + m_cond = ARCH->newCondVar(); +} + +CondVarBase::~CondVarBase() +{ + ARCH->closeCondVar(m_cond); +} + +void +CondVarBase::lock() const +{ + m_mutex->lock(); +} + +void +CondVarBase::unlock() const +{ + m_mutex->unlock(); +} + +void +CondVarBase::signal() +{ + ARCH->signalCondVar(m_cond); +} + +void +CondVarBase::broadcast() +{ + ARCH->broadcastCondVar(m_cond); +} + +bool +CondVarBase::wait(Stopwatch& timer, double timeout) const +{ + double remain = timeout-timer.getTime(); + // Some ARCH wait()s return prematurely, retry until really timed out + // In particular, ArchMultithreadPosix::waitCondVar() returns every 100ms + do { + // Always call wait at least once, even if remain is 0, to give + // other thread a chance to grab the mutex to avoid deadlocks on + // busy waiting. + if (remain<0.0) remain=0.0; + if (wait(remain)) + return true; + remain = timeout - timer.getTime(); + } while (remain >= 0.0); + return false; +} + +bool +CondVarBase::wait(double timeout) const +{ + return ARCH->waitCondVar(m_cond, m_mutex->m_mutex, timeout); +} + +Mutex* +CondVarBase::getMutex() const +{ + return m_mutex; +} diff --git a/src/lib/mt/CondVar.h b/src/lib/mt/CondVar.h new file mode 100644 index 0000000..0ab956b --- /dev/null +++ b/src/lib/mt/CondVar.h @@ -0,0 +1,225 @@ +/* + * 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 "mt/Mutex.h" +#include "common/basic_types.h" + +class Stopwatch; + +//! Generic condition variable +/*! +This class provides functionality common to all condition variables +but doesn't provide the actual variable storage. A condition variable +is a multiprocessing primitive that can be waited on. Every condition +variable has an associated mutex. +*/ +class CondVarBase { +public: + /*! + \c mutex must not be NULL. All condition variables have an + associated mutex. The mutex needn't be unique to one condition + variable. + */ + CondVarBase(Mutex* mutex); + ~CondVarBase(); + + //! @name manipulators + //@{ + + //! Lock the condition variable's mutex + /*! + Lock the condition variable's mutex. The condition variable should + be locked before reading or writing it. It must be locked for a + call to wait(). Locks are not recursive; locking a locked mutex + will deadlock the thread. + */ + void lock() const; + + //! Unlock the condition variable's mutex + void unlock() const; + + //! Signal the condition variable + /*! + Wake up one waiting thread, if there are any. Which thread gets + woken is undefined. + */ + void signal(); + + //! Signal the condition variable + /*! + Wake up all waiting threads, if any. + */ + void broadcast(); + + //@} + //! @name accessors + //@{ + + //! Wait on the condition variable + /*! + Wait on the condition variable. If \c timeout < 0 then wait until + signalled, otherwise up to \c timeout seconds or until signalled, + whichever comes first. Returns true if the object was signalled + during the wait, false otherwise. + + The proper way to wait for a condition is: + \code + cv.lock(); + while (cv-expr) { + cv.wait(); + } + cv.unlock(); + \endcode + where \c cv-expr involves the value of \c cv and is false when the + condition is satisfied. + + (cancellation point) + */ + bool wait(double timeout = -1.0) const; + + //! Wait on the condition variable + /*! + Same as \c wait(double) but use \c timer to compare against \c timeout. + Since clients normally wait on condition variables in a loop, clients + can use this to avoid recalculating \c timeout on each iteration. + Passing a stopwatch with a negative \c timeout is pointless (it will + never time out) but permitted. + + (cancellation point) + */ + bool wait(Stopwatch& timer, double timeout) const; + + //! Get the mutex + /*! + Get the mutex passed to the c'tor. + */ + Mutex* getMutex() const; + + //@} + +private: + // not implemented + CondVarBase(const CondVarBase&); + CondVarBase& operator=(const CondVarBase&); + +private: + Mutex* m_mutex; + ArchCond m_cond; +}; + +//! Condition variable +/*! +A condition variable with storage for type \c T. +*/ +template <class T> +class CondVar : public CondVarBase { +public: + //! Initialize using \c value + CondVar(Mutex* mutex, const T& value); + //! Initialize using another condition variable's value + CondVar(const CondVar&); + ~CondVar(); + + //! @name manipulators + //@{ + + //! Assigns the value of \c cv to this + /*! + Set the variable's value. The condition variable should be locked + before calling this method. + */ + CondVar& operator=(const CondVar& cv); + + //! Assigns \c value to this + /*! + Set the variable's value. The condition variable should be locked + before calling this method. + */ + CondVar& operator=(const T& v); + + //@} + //! @name accessors + //@{ + + //! Get the variable's value + /*! + Get the variable's value. The condition variable should be locked + before calling this method. + */ + operator const volatile T&() const; + + //@} + +private: + volatile T m_data; +}; + +template <class T> +inline +CondVar<T>::CondVar( + Mutex* mutex, + const T& data) : + CondVarBase(mutex), + m_data(data) +{ + // do nothing +} + +template <class T> +inline +CondVar<T>::CondVar( + const CondVar& cv) : + CondVarBase(cv.getMutex()), + m_data(cv.m_data) +{ + // do nothing +} + +template <class T> +inline +CondVar<T>::~CondVar() +{ + // do nothing +} + +template <class T> +inline +CondVar<T>& +CondVar<T>::operator=(const CondVar<T>& cv) +{ + m_data = cv.m_data; + return *this; +} + +template <class T> +inline +CondVar<T>& +CondVar<T>::operator=(const T& data) +{ + m_data = data; + return *this; +} + +template <class T> +inline +CondVar<T>::operator const volatile T&() const +{ + return m_data; +} diff --git a/src/lib/mt/Lock.cpp b/src/lib/mt/Lock.cpp new file mode 100644 index 0000000..80721b9 --- /dev/null +++ b/src/lib/mt/Lock.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 "mt/Lock.h" +#include "mt/CondVar.h" +#include "mt/Mutex.h" + +// +// Lock +// + +Lock::Lock(const Mutex* mutex) : + m_mutex(mutex) +{ + m_mutex->lock(); +} + +Lock::Lock(const CondVarBase* cv) : + m_mutex(cv->getMutex()) +{ + m_mutex->lock(); +} + +Lock::~Lock() +{ + m_mutex->unlock(); +} diff --git a/src/lib/mt/Lock.h b/src/lib/mt/Lock.h new file mode 100644 index 0000000..4a3f311 --- /dev/null +++ b/src/lib/mt/Lock.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 "common/common.h" + +class Mutex; +class CondVarBase; + +//! Mutual exclusion lock utility +/*! +This class locks a mutex or condition variable in the c'tor and unlocks +it in the d'tor. It's easier and safer than manually locking and +unlocking since unlocking must usually be done no matter how a function +exits (including by unwinding due to an exception). +*/ +class Lock { +public: + //! Lock the mutex \c mutex + Lock(const Mutex* mutex); + //! Lock the condition variable \c cv + Lock(const CondVarBase* cv); + //! Unlock the mutex or condition variable + ~Lock(); + +private: + // not implemented + Lock(const Lock&); + Lock& operator=(const Lock&); + +private: + const Mutex* m_mutex; +}; diff --git a/src/lib/mt/Mutex.cpp b/src/lib/mt/Mutex.cpp new file mode 100644 index 0000000..e9a62e8 --- /dev/null +++ b/src/lib/mt/Mutex.cpp @@ -0,0 +1,58 @@ +/* + * 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 "mt/Mutex.h" + +#include "arch/Arch.h" + +// +// Mutex +// + +Mutex::Mutex() +{ + m_mutex = ARCH->newMutex(); +} + +Mutex::Mutex(const Mutex&) +{ + m_mutex = ARCH->newMutex(); +} + +Mutex::~Mutex() +{ + ARCH->closeMutex(m_mutex); +} + +Mutex& +Mutex::operator=(const Mutex&) +{ + return *this; +} + +void +Mutex::lock() const +{ + ARCH->lockMutex(m_mutex); +} + +void +Mutex::unlock() const +{ + ARCH->unlockMutex(m_mutex); +} diff --git a/src/lib/mt/Mutex.h b/src/lib/mt/Mutex.h new file mode 100644 index 0000000..51a9649 --- /dev/null +++ b/src/lib/mt/Mutex.h @@ -0,0 +1,79 @@ +/* + * 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" + +//! Mutual exclusion +/*! +A non-recursive mutual exclusion object. Only one thread at a time can +hold a lock on a mutex. Any thread that attempts to lock a locked mutex +will block until the mutex is unlocked. At that time, if any threads are +blocked, exactly one waiting thread will acquire the lock and continue +running. A thread may not lock a mutex it already owns the lock on; if +it tries it will deadlock itself. +*/ +class Mutex { +public: + Mutex(); + //! Equivalent to default c'tor + /*! + Copy c'tor doesn't copy anything. It just makes it possible to + copy objects that contain a mutex. + */ + Mutex(const Mutex&); + ~Mutex(); + + //! @name manipulators + //@{ + + //! Does nothing + /*! + This does nothing. It just makes it possible to assign objects + that contain a mutex. + */ + Mutex& operator=(const Mutex&); + + //@} + //! @name accessors + //@{ + + //! Lock the mutex + /*! + Locks the mutex, which must not have been previously locked by the + calling thread. This blocks if the mutex is already locked by another + thread. + + (cancellation point) + */ + void lock() const; + + //! Unlock the mutex + /*! + Unlocks the mutex, which must have been previously locked by the + calling thread. + */ + void unlock() const; + + //@} + +private: + friend class CondVarBase; + ArchMutex m_mutex; +}; diff --git a/src/lib/mt/Thread.cpp b/src/lib/mt/Thread.cpp new file mode 100644 index 0000000..7474c16 --- /dev/null +++ b/src/lib/mt/Thread.cpp @@ -0,0 +1,187 @@ +/* + * 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 "mt/Thread.h" + +#include "mt/XMT.h" +#include "mt/XThread.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/IJob.h" + +// +// Thread +// + +Thread::Thread(IJob* job) +{ + m_thread = ARCH->newThread(&Thread::threadFunc, job); + if (m_thread == NULL) { + // couldn't create thread + delete job; + throw XMTThreadUnavailable(); + } +} + +Thread::Thread(const Thread& thread) +{ + m_thread = ARCH->copyThread(thread.m_thread); +} + +Thread::Thread(ArchThread adoptedThread) +{ + m_thread = adoptedThread; +} + +Thread::~Thread() +{ + ARCH->closeThread(m_thread); +} + +Thread& +Thread::operator=(const Thread& thread) +{ + // copy given thread and release ours + ArchThread copy = ARCH->copyThread(thread.m_thread); + ARCH->closeThread(m_thread); + + // cut over + m_thread = copy; + + return *this; +} + +void +Thread::exit(void* result) +{ + throw XThreadExit(result); +} + +void +Thread::cancel() +{ + ARCH->cancelThread(m_thread); +} + +void +Thread::setPriority(int n) +{ + ARCH->setPriorityOfThread(m_thread, n); +} + +void +Thread::unblockPollSocket() +{ + ARCH->unblockPollSocket(m_thread); +} + +Thread +Thread::getCurrentThread() +{ + return Thread(ARCH->newCurrentThread()); +} + +void +Thread::testCancel() +{ + ARCH->testCancelThread(); +} + +bool +Thread::wait(double timeout) const +{ + return ARCH->wait(m_thread, timeout); +} + +void* +Thread::getResult() const +{ + if (wait()) + return ARCH->getResultOfThread(m_thread); + else + return NULL; +} + +IArchMultithread::ThreadID +Thread::getID() const +{ + return ARCH->getIDOfThread(m_thread); +} + +bool +Thread::operator==(const Thread& thread) const +{ + return ARCH->isSameThread(m_thread, thread.m_thread); +} + +bool +Thread::operator!=(const Thread& thread) const +{ + return !ARCH->isSameThread(m_thread, thread.m_thread); +} + +void* +Thread::threadFunc(void* vjob) +{ + // get this thread's id for logging + IArchMultithread::ThreadID id; + { + ArchThread thread = ARCH->newCurrentThread(); + id = ARCH->getIDOfThread(thread); + ARCH->closeThread(thread); + } + + // get job + IJob* job = static_cast<IJob*>(vjob); + + // run job + void* result = NULL; + try { + // go + LOG((CLOG_DEBUG1 "thread 0x%08x entry", id)); + job->run(); + LOG((CLOG_DEBUG1 "thread 0x%08x exit", id)); + } + catch (XThreadCancel&) { + // client called cancel() + LOG((CLOG_DEBUG1 "caught cancel on thread 0x%08x", id)); + delete job; + throw; + } + catch (XThreadExit& e) { + // client called exit() + result = e.m_result; + LOG((CLOG_DEBUG1 "caught exit on thread 0x%08x, result %p", id, result)); + } + catch (XBase& e) { + LOG((CLOG_ERR "exception on thread 0x%08x: %s", id, e.what())); + delete job; + throw; + } + catch (...) { + LOG((CLOG_ERR "exception on thread 0x%08x: <unknown>", id)); + delete job; + throw; + } + + // done with job + delete job; + + // return exit result + return result; +} diff --git a/src/lib/mt/Thread.h b/src/lib/mt/Thread.h new file mode 100644 index 0000000..a7434fd --- /dev/null +++ b/src/lib/mt/Thread.h @@ -0,0 +1,210 @@ +/* + * 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" + +class IJob; + +//! Thread handle +/*! +Creating a Thread creates a new context of execution (i.e. thread) that +runs simulatenously with the calling thread. A Thread is only a handle +to a thread; deleting a Thread does not cancel or destroy the thread it +refers to and multiple Thread objects can refer to the same thread. + +Threads can terminate themselves but cannot be forced to terminate by +other threads. However, other threads can signal a thread to terminate +itself by cancelling it. And a thread can wait (block) on another thread +to terminate. + +Most functions that can block for an arbitrary time are cancellation +points. A cancellation point is a function that can be interrupted by +a request to cancel the thread. Cancellation points are noted in the +documentation. +*/ +// note -- do not derive from this class +class Thread { +public: + //! Run \c adoptedJob in a new thread + /*! + Create and start a new thread executing the \c adoptedJob. The + new thread takes ownership of \c adoptedJob and will delete it. + */ + Thread(IJob* adoptedJob); + + //! Duplicate a thread handle + /*! + Make a new thread object that refers to an existing thread. + This does \b not start a new thread. + */ + Thread(const Thread&); + + //! Release a thread handle + /*! + Release a thread handle. This does not terminate the thread. A thread + will keep running until the job completes or calls exit() or allows + itself to be cancelled. + */ + ~Thread(); + + //! @name manipulators + //@{ + + //! Assign thread handle + /*! + Assign a thread handle. This has no effect on the threads, it simply + makes this thread object refer to another thread. It does \b not + start a new thread. + */ + Thread& operator=(const Thread&); + + //! Terminate the calling thread + /*! + Terminate the calling thread. This function does not return but + the stack is unwound and automatic objects are destroyed, as if + exit() threw an exception (which is, in fact, what it does). The + argument is saved as the result returned by getResult(). If you + have \c catch(...) blocks then you should add the following before + each to avoid catching the exit: + \code + catch(ThreadExit&) { throw; } + \endcode + or add the \c RETHROW_XTHREAD macro to the \c catch(...) block. + */ + static void exit(void*); + + //! Cancel thread + /*! + Cancel the thread. cancel() never waits for the thread to + terminate; it just posts the cancel and returns. A thread will + terminate when it enters a cancellation point with cancellation + enabled. If cancellation is disabled then the cancel is + remembered but not acted on until the first call to a + cancellation point after cancellation is enabled. + + A cancellation point is a function that can act on cancellation. + A cancellation point does not return if there's a cancel pending. + Instead, it unwinds the stack and destroys automatic objects, as + if cancel() threw an exception (which is, in fact, what it does). + Threads must take care to unlock and clean up any resources they + may have, especially mutexes. They can \c catch(XThreadCancel) to + do that then rethrow the exception or they can let it happen + automatically by doing clean up in the d'tors of automatic + objects (like Lock). Clients are strongly encouraged to do the latter. + During cancellation, further cancel() calls are ignored (i.e. + a thread cannot be interrupted by a cancel during cancellation). + + Clients that \c catch(XThreadCancel) must always rethrow the + exception. Clients that \c catch(...) must either rethrow the + exception or include a \c catch(XThreadCancel) handler that + rethrows. The \c RETHROW_XTHREAD macro may be useful for that. + */ + void cancel(); + + //! Change thread priority + /*! + Change the priority of the thread. Normal priority is 0, 1 is + the next lower, etc. -1 is the next higher, etc. but boosting + the priority may not be permitted and will be silenty ignored. + */ + void setPriority(int n); + + //! Force pollSocket() to return + /*! + Forces a currently blocked pollSocket() in the thread to return + immediately. + */ + void unblockPollSocket(); + + //@} + //! @name accessors + //@{ + + //! Get current thread's handle + /*! + Return a Thread object representing the calling thread. + */ + static Thread getCurrentThread(); + + //! Test for cancellation + /*! + testCancel() does nothing but is a cancellation point. Call + this to make a function itself a cancellation point. If the + thread was cancelled and cancellation is enabled this will + cause the thread to unwind the stack and terminate. + + (cancellation point) + */ + static void testCancel(); + + //! Wait for thread to terminate + /*! + Waits for the thread to terminate (by exit() or cancel() or + by returning from the thread job) for up to \c timeout seconds, + returning true if the thread terminated and false otherwise. + This returns immediately with false if called by a thread on + itself and immediately with true if the thread has already + terminated. This will wait forever if \c timeout < 0.0. + + (cancellation point) + */ + bool wait(double timeout = -1.0) const; + + //! Get the exit result + /*! + Returns the exit result. This does an implicit wait(). It returns + NULL immediately if called by a thread on itself or on a thread that + was cancelled. + + (cancellation point) + */ + void* getResult() const; + + //! Get the thread id + /*! + Returns an integer id for this thread. This id must not be used to + check if two Thread objects refer to the same thread. Use + operator==() for that. + */ + IArchMultithread::ThreadID + getID() const; + + //! Compare thread handles + /*! + Returns true if two Thread objects refer to the same thread. + */ + bool operator==(const Thread&) const; + + //! Compare thread handles + /*! + Returns true if two Thread objects do not refer to the same thread. + */ + bool operator!=(const Thread&) const; + + //@} + +private: + Thread(ArchThread); + + static void* threadFunc(void*); + +private: + ArchThread m_thread; +}; diff --git a/src/lib/mt/XMT.cpp b/src/lib/mt/XMT.cpp new file mode 100644 index 0000000..9aa5852 --- /dev/null +++ b/src/lib/mt/XMT.cpp @@ -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/>. + */ + +#include "XMT.h" + +// +// XMTThreadUnavailable +// + +String +XMTThreadUnavailable::getWhat() const throw() +{ + return format("XMTThreadUnavailable", "cannot create thread"); +} diff --git a/src/lib/mt/XMT.h b/src/lib/mt/XMT.h new file mode 100644 index 0000000..9e48fd9 --- /dev/null +++ b/src/lib/mt/XMT.h @@ -0,0 +1,30 @@ +/* + * 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 "base/XBase.h" + +//! Generic multithreading exception +XBASE_SUBCLASS(XMT, XBase); + +//! Thread creation exception +/*! +Thrown when a thread cannot be created. +*/ +XBASE_SUBCLASS_WHAT(XMTThreadUnavailable, XMT); diff --git a/src/lib/mt/XThread.h b/src/lib/mt/XThread.h new file mode 100644 index 0000000..acc32e3 --- /dev/null +++ b/src/lib/mt/XThread.h @@ -0,0 +1,37 @@ +/* + * 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" + +//! Thread exception to exit +/*! +Thrown by Thread::exit() to exit a thread. Clients of Thread +must not throw this type but must rethrow it if caught (by +XThreadExit, XThread, or ...). +*/ +class XThreadExit : public XThread { +public: + //! \c result is the result of the thread + XThreadExit(void* result) : m_result(result) { } + ~XThreadExit() { } + +public: + void* m_result; +}; diff --git a/src/lib/net/CMakeLists.txt b/src/lib/net/CMakeLists.txt new file mode 100644 index 0000000..5439450 --- /dev/null +++ b/src/lib/net/CMakeLists.txt @@ -0,0 +1,28 @@ +# 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() + +add_library(net STATIC ${sources}) + +if (UNIX) + target_link_libraries(net mt io ${OPENSSL_LIBS}) +endif() diff --git a/src/lib/net/IDataSocket.cpp b/src/lib/net/IDataSocket.cpp new file mode 100644 index 0000000..cc679c3 --- /dev/null +++ b/src/lib/net/IDataSocket.cpp @@ -0,0 +1,39 @@ +/* + * 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 "net/IDataSocket.h" +#include "base/EventQueue.h" + +// +// IDataSocket +// + +void +IDataSocket::close() +{ + // this is here to work around a VC++6 bug. see the header file. + assert(0 && "bad call"); +} + +void* +IDataSocket::getEventTarget() const +{ + // this is here to work around a VC++6 bug. see the header file. + assert(0 && "bad call"); + return NULL; +} diff --git a/src/lib/net/IDataSocket.h b/src/lib/net/IDataSocket.h new file mode 100644 index 0000000..dc07df5 --- /dev/null +++ b/src/lib/net/IDataSocket.h @@ -0,0 +1,73 @@ +/* + * 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 "net/ISocket.h" +#include "io/IStream.h" +#include "base/String.h" +#include "base/EventTypes.h" + +//! Data stream socket interface +/*! +This interface defines the methods common to all network sockets that +represent a full-duplex data stream. +*/ +class IDataSocket : public ISocket, public barrier::IStream { +public: + class ConnectionFailedInfo { + public: + ConnectionFailedInfo(const char* what) : m_what(what) { } + String m_what; + }; + + IDataSocket(IEventQueue* events) { } + + //! @name manipulators + //@{ + + //! Connect socket + /*! + Attempt to connect to a remote endpoint. This returns immediately + and sends a connected event when successful or a connection failed + event when it fails. The stream acts as if shutdown for input and + output until the stream connects. + */ + virtual void connect(const NetworkAddress&) = 0; + + //@} + + // ISocket overrides + // close() and getEventTarget() aren't pure to work around a bug + // in VC++6. it claims the methods are unused locals and warns + // that it's removing them. it's presumably tickled by inheriting + // methods with identical signatures from both superclasses. + virtual void bind(const NetworkAddress&) = 0; + virtual void close(); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n) = 0; + virtual void write(const void* buffer, UInt32 n) = 0; + virtual void flush() = 0; + virtual void shutdownInput() = 0; + virtual void shutdownOutput() = 0; + virtual bool isReady() const = 0; + virtual bool isFatal() const = 0; + virtual UInt32 getSize() const = 0; +}; diff --git a/src/lib/net/IListenSocket.h b/src/lib/net/IListenSocket.h new file mode 100644 index 0000000..73dcc6e --- /dev/null +++ b/src/lib/net/IListenSocket.h @@ -0,0 +1,51 @@ +/* + * 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 "net/ISocket.h" +#include "base/EventTypes.h" + +class IDataSocket; + +//! Listen socket interface +/*! +This interface defines the methods common to all network sockets that +listen for incoming connections. +*/ +class IListenSocket : public ISocket { +public: + //! @name manipulators + //@{ + + //! Accept connection + /*! + Accept a connection, returning a socket representing the full-duplex + data stream. Returns NULL if no socket is waiting to be accepted. + This is only valid after a call to \c bind(). + */ + virtual IDataSocket* + accept() = 0; + + //@} + + // ISocket overrides + virtual void bind(const NetworkAddress&) = 0; + virtual void close() = 0; + virtual void* getEventTarget() const = 0; +}; diff --git a/src/lib/net/ISocket.h b/src/lib/net/ISocket.h new file mode 100644 index 0000000..0e9688b --- /dev/null +++ b/src/lib/net/ISocket.h @@ -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/>. + */ + +#pragma once + +#include "common/IInterface.h" +#include "base/Event.h" +#include "base/EventTypes.h" + +class NetworkAddress; + +//! Generic socket interface +/*! +This interface defines the methods common to all network sockets. +Generated events use \c this as the target. +*/ +class ISocket : public IInterface { +public: + //! @name manipulators + //@{ + + //! Bind socket to address + /*! + Binds the socket to a particular address. + */ + virtual void bind(const NetworkAddress&) = 0; + + //! Close socket + /*! + Closes the socket. This should flush the output stream. + */ + virtual void close() = 0; + + //@} + //! @name accessors + //@{ + + //! Get event target + /*! + Returns the event target for events generated by this socket. + */ + virtual void* getEventTarget() const = 0; + + //@} +}; diff --git a/src/lib/net/ISocketFactory.h b/src/lib/net/ISocketFactory.h new file mode 100644 index 0000000..e440953 --- /dev/null +++ b/src/lib/net/ISocketFactory.h @@ -0,0 +1,48 @@ +/* + * 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 "arch/IArchNetwork.h" + +class IDataSocket; +class IListenSocket; + +//! Socket factory +/*! +This interface defines the methods common to all factories used to +create sockets. +*/ +class ISocketFactory : public IInterface { +public: + //! @name accessors + //@{ + + //! Create data socket + virtual IDataSocket* create( + IArchNetwork::EAddressFamily family, + bool secure) const = 0; + + //! Create listen socket + virtual IListenSocket* createListen( + IArchNetwork::EAddressFamily family, + bool secure) const = 0; + + //@} +}; diff --git a/src/lib/net/ISocketMultiplexerJob.h b/src/lib/net/ISocketMultiplexerJob.h new file mode 100644 index 0000000..ddd3ba5 --- /dev/null +++ b/src/lib/net/ISocketMultiplexerJob.h @@ -0,0 +1,76 @@ +/* + * 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/IArchNetwork.h" +#include "common/IInterface.h" + +//! Socket multiplexer job +/*! +A socket multiplexer job handles events on a socket. +*/ +class ISocketMultiplexerJob : public IInterface { +public: + //! @name manipulators + //@{ + + //! Handle socket event + /*! + Called by a socket multiplexer when the socket becomes readable, + writable, or has an error. It should return itself if the same + job can continue to service events, a new job if the socket must + be serviced differently, or NULL if the socket should no longer + be serviced. The socket is readable if \p readable is true, + writable if \p writable is true, and in error if \p error is + true. + + This call must not attempt to directly change the job for this + socket by calling \c addSocket() or \c removeSocket() on the + multiplexer. It must instead return the new job. It can, + however, add or remove jobs for other sockets. + */ + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error) = 0; + + //@} + //! @name accessors + //@{ + + //! Get the socket + /*! + Return the socket to multiplex + */ + virtual ArchSocket getSocket() const = 0; + + //! Check for interest in readability + /*! + Return true if the job is interested in being run if the socket + becomes readable. + */ + virtual bool isReadable() const = 0; + + //! Check for interest in writability + /*! + Return true if the job is interested in being run if the socket + becomes writable. + */ + virtual bool isWritable() const = 0; + + //@} +}; diff --git a/src/lib/net/NetworkAddress.cpp b/src/lib/net/NetworkAddress.cpp new file mode 100644 index 0000000..c395ab0 --- /dev/null +++ b/src/lib/net/NetworkAddress.cpp @@ -0,0 +1,214 @@ +/* + * 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 "net/NetworkAddress.h" + +#include "net/XSocket.h" +#include "arch/Arch.h" +#include "arch/XArch.h" + +#include <cstdlib> + +// +// NetworkAddress +// + +// name re-resolution adapted from a patch by Brent Priddy. + +NetworkAddress::NetworkAddress() : + m_address(NULL), + m_hostname(), + m_port(0) +{ + // note -- make no calls to Network socket interface here; + // we're often called prior to Network::init(). +} + +NetworkAddress::NetworkAddress(int port) : + m_address(NULL), + m_hostname(), + m_port(port) +{ + checkPort(); + m_address = ARCH->newAnyAddr(IArchNetwork::kINET); + ARCH->setAddrPort(m_address, m_port); +} + +NetworkAddress::NetworkAddress(const NetworkAddress& addr) : + m_address(addr.m_address != NULL ? ARCH->copyAddr(addr.m_address) : NULL), + m_hostname(addr.m_hostname), + m_port(addr.m_port) +{ + // do nothing +} + +NetworkAddress::NetworkAddress(const String& hostname, int port) : + m_address(NULL), + m_hostname(hostname), + m_port(port) +{ + // check for port suffix + String::size_type i = m_hostname.rfind(':'); + if (i != String::npos && i + 1 < m_hostname.size()) { + // found a colon. see if it looks like an IPv6 address. + bool colonNotation = false; + bool dotNotation = false; + bool doubleColon = false; + for (String::size_type j = 0; j < i; ++j) { + if (m_hostname[j] == ':') { + colonNotation = true; + dotNotation = false; + if (m_hostname[j + 1] == ':') { + doubleColon = true; + } + } + else if (m_hostname[j] == '.' && colonNotation) { + dotNotation = true; + } + } + + // port suffix is ambiguous with IPv6 notation if there's + // a double colon and the end of the address is not in dot + // notation. in that case we assume it's not a port suffix. + // the user can replace the double colon with zeros to + // disambiguate. + if ((!doubleColon || dotNotation) && !colonNotation) { + // parse port from hostname + char* end; + const char* chostname = m_hostname.c_str(); + long suffixPort = strtol(chostname + i + 1, &end, 10); + if (end == chostname + i + 1 || *end != '\0') { + throw XSocketAddress(XSocketAddress::kBadPort, + m_hostname, m_port); + } + + // trim port from hostname + m_hostname.erase(i); + + // save port + m_port = static_cast<int>(suffixPort); + } + } + + // check port number + checkPort(); +} + +NetworkAddress::~NetworkAddress() +{ + if (m_address != NULL) { + ARCH->closeAddr(m_address); + } +} + +NetworkAddress& +NetworkAddress::operator=(const NetworkAddress& addr) +{ + ArchNetAddress newAddr = NULL; + if (addr.m_address != NULL) { + newAddr = ARCH->copyAddr(addr.m_address); + } + if (m_address != NULL) { + ARCH->closeAddr(m_address); + } + m_address = newAddr; + m_hostname = addr.m_hostname; + m_port = addr.m_port; + return *this; +} + +void +NetworkAddress::resolve() +{ + // discard previous address + if (m_address != NULL) { + ARCH->closeAddr(m_address); + m_address = NULL; + } + + try { + // if hostname is empty then use wildcard address otherwise look + // up the name. + if (m_hostname.empty()) { + m_address = ARCH->newAnyAddr(IArchNetwork::kINET); + } + else { + m_address = ARCH->nameToAddr(m_hostname); + } + } + catch (XArchNetworkNameUnknown&) { + throw XSocketAddress(XSocketAddress::kNotFound, m_hostname, m_port); + } + catch (XArchNetworkNameNoAddress&) { + throw XSocketAddress(XSocketAddress::kNoAddress, m_hostname, m_port); + } + catch (XArchNetworkNameUnsupported&) { + throw XSocketAddress(XSocketAddress::kUnsupported, m_hostname, m_port); + } + catch (XArchNetworkName&) { + throw XSocketAddress(XSocketAddress::kUnknown, m_hostname, m_port); + } + + // set port in address + ARCH->setAddrPort(m_address, m_port); +} + +bool +NetworkAddress::operator==(const NetworkAddress& addr) const +{ + return ARCH->isEqualAddr(m_address, addr.m_address); +} + +bool +NetworkAddress::operator!=(const NetworkAddress& addr) const +{ + return !operator==(addr); +} + +bool +NetworkAddress::isValid() const +{ + return (m_address != NULL); +} + +const ArchNetAddress& +NetworkAddress::getAddress() const +{ + return m_address; +} + +int +NetworkAddress::getPort() const +{ + return m_port; +} + +String +NetworkAddress::getHostname() const +{ + return m_hostname; +} + +void +NetworkAddress::checkPort() +{ + // check port number + if (m_port <= 0 || m_port > 65535) { + throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port); + } +} diff --git a/src/lib/net/NetworkAddress.h b/src/lib/net/NetworkAddress.h new file mode 100644 index 0000000..cbd15f5 --- /dev/null +++ b/src/lib/net/NetworkAddress.h @@ -0,0 +1,123 @@ +/* + * 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 "base/String.h" +#include "base/EventTypes.h" +#include "arch/IArchNetwork.h" + +//! Network address type +/*! +This class represents a network address. +*/ +class NetworkAddress { +public: + /*! + Constructs the invalid address + */ + NetworkAddress(); + + /*! + Construct the wildcard address with the given port. \c port must + not be zero. + */ + NetworkAddress(int port); + + /*! + Construct the network address for the given \c hostname and \c port. + If \c hostname can be parsed as a numerical address then that's how + it's used, otherwise it's used as a host name. If \c hostname ends + in ":[0-9]+" then that suffix is extracted and used as the port, + overridding the port parameter. The resulting port must be a valid + port number (zero is not a valid port number) otherwise \c XSocketAddress + is thrown with an error of \c XSocketAddress::kBadPort. The hostname + is not resolved by the c'tor; use \c resolve to do that. + */ + NetworkAddress(const String& hostname, int port); + + NetworkAddress(const NetworkAddress&); + + ~NetworkAddress(); + + NetworkAddress& operator=(const NetworkAddress&); + + //! @name manipulators + //@{ + + //! Resolve address + /*! + Resolves the hostname to an address. This can be done any number of + times and is done automatically by the c'tor taking a hostname. + Throws XSocketAddress if resolution is unsuccessful, after which + \c isValid returns false until the next call to this method. + */ + void resolve(); + + //@} + //! @name accessors + //@{ + + //! Check address equality + /*! + Returns true if this address is equal to \p address. + */ + bool operator==(const NetworkAddress& address) const; + + //! Check address inequality + /*! + Returns true if this address is not equal to \p address. + */ + bool operator!=(const NetworkAddress& address) const; + + //! Check address validity + /*! + Returns true if this is not the invalid address. + */ + bool isValid() const; + + //! Get address + /*! + Returns the address in the platform's native network address + structure. + */ + const ArchNetAddress& getAddress() const; + + //! Get port + /*! + Returns the port passed to the c'tor as a suffix to the hostname, + if that existed, otherwise as passed directly to the c'tor. + */ + int getPort() const; + + //! Get hostname + /*! + Returns the hostname passed to the c'tor sans any port suffix. + */ + String getHostname() const; + + //@} + +private: + void checkPort(); + +private: + ArchNetAddress m_address; + String m_hostname; + int m_port; +}; diff --git a/src/lib/net/SecureListenSocket.cpp b/src/lib/net/SecureListenSocket.cpp new file mode 100644 index 0000000..58ffe09 --- /dev/null +++ b/src/lib/net/SecureListenSocket.cpp @@ -0,0 +1,85 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 "SecureListenSocket.h" + +#include "SecureSocket.h" +#include "net/NetworkAddress.h" +#include "net/SocketMultiplexer.h" +#include "net/TSocketMultiplexerMethodJob.h" +#include "arch/XArch.h" + +static const char s_certificateDir[] = { "SSL" }; +static const char s_certificateFilename[] = { "Barrier.pem" }; + +// +// SecureListenSocket +// + +SecureListenSocket::SecureListenSocket( + IEventQueue* events, + SocketMultiplexer* socketMultiplexer, + IArchNetwork::EAddressFamily family) : + TCPListenSocket(events, socketMultiplexer, family) +{ +} + +IDataSocket* +SecureListenSocket::accept() +{ + SecureSocket* socket = NULL; + try { + socket = new SecureSocket( + m_events, + m_socketMultiplexer, + ARCH->acceptSocket(m_socket, NULL)); + socket->initSsl(true); + + if (socket != NULL) { + setListeningJob(); + } + + String certificateFilename = barrier::string::sprintf("%s/%s/%s", + ARCH->getProfileDirectory().c_str(), + s_certificateDir, + s_certificateFilename); + + bool loaded = socket->loadCertificates(certificateFilename); + if (!loaded) { + delete socket; + return NULL; + } + + socket->secureAccept(); + + return dynamic_cast<IDataSocket*>(socket); + } + catch (XArchNetwork&) { + if (socket != NULL) { + delete socket; + setListeningJob(); + } + return NULL; + } + catch (std::exception &ex) { + if (socket != NULL) { + delete socket; + setListeningJob(); + } + throw ex; + } +} diff --git a/src/lib/net/SecureListenSocket.h b/src/lib/net/SecureListenSocket.h new file mode 100644 index 0000000..d0c6e23 --- /dev/null +++ b/src/lib/net/SecureListenSocket.h @@ -0,0 +1,36 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 + +#include "net/TCPListenSocket.h" +#include "common/stdset.h" + +class IEventQueue; +class SocketMultiplexer; +class IDataSocket; + +class SecureListenSocket : public TCPListenSocket{ +public: + SecureListenSocket(IEventQueue* events, + SocketMultiplexer* socketMultiplexer, + IArchNetwork::EAddressFamily family); + + // IListenSocket overrides + virtual IDataSocket* + accept(); +}; diff --git a/src/lib/net/SecureSocket.cpp b/src/lib/net/SecureSocket.cpp new file mode 100644 index 0000000..1fefae0 --- /dev/null +++ b/src/lib/net/SecureSocket.cpp @@ -0,0 +1,867 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 "SecureSocket.h" + +#include "net/TSocketMultiplexerMethodJob.h" +#include "base/TMethodEventJob.h" +#include "net/TCPSocket.h" +#include "mt/Lock.h" +#include "arch/XArch.h" +#include "base/Log.h" + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <cstring> +#include <cstdlib> +#include <memory> +#include <fstream> +#include <memory> + +// +// SecureSocket +// + +#define MAX_ERROR_SIZE 65535 + +static const float s_retryDelay = 0.01f; + +enum { + kMsgSize = 128 +}; + +static const char kFingerprintDirName[] = "SSL/Fingerprints"; +//static const char kFingerprintLocalFilename[] = "Local.txt"; +static const char kFingerprintTrustedServersFilename[] = "TrustedServers.txt"; +//static const char kFingerprintTrustedClientsFilename[] = "TrustedClients.txt"; + +struct Ssl { + SSL_CTX* m_context; + SSL* m_ssl; +}; + +SecureSocket::SecureSocket( + IEventQueue* events, + SocketMultiplexer* socketMultiplexer, + IArchNetwork::EAddressFamily family) : + TCPSocket(events, socketMultiplexer, family), + m_ssl(nullptr), + m_secureReady(false), + m_fatal(false) +{ +} + +SecureSocket::SecureSocket( + IEventQueue* events, + SocketMultiplexer* socketMultiplexer, + ArchSocket socket) : + TCPSocket(events, socketMultiplexer, socket), + m_ssl(nullptr), + m_secureReady(false), + m_fatal(false) +{ +} + +SecureSocket::~SecureSocket() +{ + isFatal(true); + // take socket from multiplexer ASAP otherwise the race condition + // could cause events to get called on a dead object. TCPSocket + // will do this, too, but the double-call is harmless + setJob(NULL); + if (m_ssl->m_ssl != NULL) { + SSL_shutdown(m_ssl->m_ssl); + + SSL_free(m_ssl->m_ssl); + m_ssl->m_ssl = NULL; + } + if (m_ssl->m_context != NULL) { + SSL_CTX_free(m_ssl->m_context); + m_ssl->m_context = NULL; + } + // removing sleep() because I have no idea why you would want to do it + // ... smells of trying to cover up a bug you don't understand + //ARCH->sleep(1); + delete m_ssl; +} + +void +SecureSocket::close() +{ + isFatal(true); + + SSL_shutdown(m_ssl->m_ssl); + + TCPSocket::close(); +} + +void +SecureSocket::connect(const NetworkAddress& addr) +{ + m_events->adoptHandler(m_events->forIDataSocket().connected(), + getEventTarget(), + new TMethodEventJob<SecureSocket>(this, + &SecureSocket::handleTCPConnected)); + + TCPSocket::connect(addr); +} + +ISocketMultiplexerJob* +SecureSocket::newJob() +{ + // after TCP connection is established, SecureSocket will pick up + // connected event and do secureConnect + if (m_connected && !m_secureReady) { + return NULL; + } + + return TCPSocket::newJob(); +} + +void +SecureSocket::secureConnect() +{ + setJob(new TSocketMultiplexerMethodJob<SecureSocket>( + this, &SecureSocket::serviceConnect, + getSocket(), isReadable(), isWritable())); +} + +void +SecureSocket::secureAccept() +{ + setJob(new TSocketMultiplexerMethodJob<SecureSocket>( + this, &SecureSocket::serviceAccept, + getSocket(), isReadable(), isWritable())); +} + +TCPSocket::EJobResult +SecureSocket::doRead() +{ + static UInt8 buffer[4096]; + memset(buffer, 0, sizeof(buffer)); + int bytesRead = 0; + int status = 0; + + if (isSecureReady()) { + status = secureRead(buffer, sizeof(buffer), bytesRead); + if (status < 0) { + return kBreak; + } + else if (status == 0) { + return kNew; + } + } + else { + return kRetry; + } + + if (bytesRead > 0) { + bool wasEmpty = (m_inputBuffer.getSize() == 0); + + // slurp up as much as possible + do { + m_inputBuffer.write(buffer, bytesRead); + + status = secureRead(buffer, sizeof(buffer), bytesRead); + if (status < 0) { + return kBreak; + } + } while (bytesRead > 0 || status > 0); + + // send input ready if input buffer was empty + if (wasEmpty) { + sendEvent(m_events->forIStream().inputReady()); + } + } + else { + // remote write end of stream hungup. our input side + // has therefore shutdown but don't flush our buffer + // since there's still data to be read. + sendEvent(m_events->forIStream().inputShutdown()); + if (!m_writable && m_inputBuffer.getSize() == 0) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + m_readable = false; + return kNew; + } + + return kRetry; +} + +TCPSocket::EJobResult +SecureSocket::doWrite() +{ + static bool s_retry = false; + static int s_retrySize = 0; + static std::unique_ptr<char[]> s_staticBuffer; + static std::size_t s_staticBufferSize = 0; + + // write data + int bufferSize = 0; + int bytesWrote = 0; + int status = 0; + + if (!isSecureReady()) + return kRetry; + + if (s_retry) { + bufferSize = s_retrySize; + } else { + bufferSize = m_outputBuffer.getSize(); + if (bufferSize > s_staticBufferSize) { + s_staticBuffer.reset(new char[bufferSize]); + s_staticBufferSize = bufferSize; + } + if (bufferSize > 0) { + memcpy(s_staticBuffer.get(), m_outputBuffer.peek(bufferSize), bufferSize); + } + } + + if (bufferSize == 0) { + return kRetry; + } + + status = secureWrite(s_staticBuffer.get(), bufferSize, bytesWrote); + if (status > 0) { + s_retry = false; + } else if (status < 0) { + return kBreak; + } else if (status == 0) { + s_retry = true; + s_retrySize = bufferSize; + return kNew; + } + + if (bytesWrote > 0) { + discardWrittenData(bytesWrote); + return kNew; + } + + return kRetry; +} + +int +SecureSocket::secureRead(void* buffer, int size, int& read) +{ + if (m_ssl->m_ssl != NULL) { + LOG((CLOG_DEBUG2 "reading secure socket")); + read = SSL_read(m_ssl->m_ssl, buffer, size); + + static int retry; + + // Check result will cleanup the connection in the case of a fatal + checkResult(read, retry); + + if (retry) { + return 0; + } + + if (isFatal()) { + return -1; + } + } + // According to SSL spec, the number of bytes read must not be negative and + // not have an error code from SSL_get_error(). If this happens, it is + // itself an error. Let the parent handle the case + return read; +} + +int +SecureSocket::secureWrite(const void* buffer, int size, int& wrote) +{ + if (m_ssl->m_ssl != NULL) { + LOG((CLOG_DEBUG2 "writing secure socket:%p", this)); + + wrote = SSL_write(m_ssl->m_ssl, buffer, size); + + static int retry; + + // Check result will cleanup the connection in the case of a fatal + checkResult(wrote, retry); + + if (retry) { + return 0; + } + + if (isFatal()) { + return -1; + } + } + // According to SSL spec, r must not be negative and not have an error code + // from SSL_get_error(). If this happens, it is itself an error. Let the + // parent handle the case + return wrote; +} + +bool +SecureSocket::isSecureReady() +{ + return m_secureReady; +} + +void +SecureSocket::initSsl(bool server) +{ + m_ssl = new Ssl(); + m_ssl->m_context = NULL; + m_ssl->m_ssl = NULL; + + initContext(server); +} + +bool +SecureSocket::loadCertificates(String& filename) +{ + if (filename.empty()) { + showError("ssl certificate is not specified"); + return false; + } + else { + std::ifstream file(filename.c_str()); + bool exist = file.good(); + file.close(); + + if (!exist) { + String errorMsg("ssl certificate doesn't exist: "); + errorMsg.append(filename); + showError(errorMsg.c_str()); + return false; + } + } + + int r = 0; + r = SSL_CTX_use_certificate_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM); + if (r <= 0) { + showError("could not use ssl certificate"); + return false; + } + + r = SSL_CTX_use_PrivateKey_file(m_ssl->m_context, filename.c_str(), SSL_FILETYPE_PEM); + if (r <= 0) { + showError("could not use ssl private key"); + return false; + } + + r = SSL_CTX_check_private_key(m_ssl->m_context); + if (!r) { + showError("could not verify ssl private key"); + return false; + } + + return true; +} + +void +SecureSocket::initContext(bool server) +{ + SSL_library_init(); + + const SSL_METHOD* method; + + // load & register all cryptos, etc. + OpenSSL_add_all_algorithms(); + + // load all error messages + SSL_load_error_strings(); + + if (CLOG->getFilter() >= kINFO) { + showSecureLibInfo(); + } + + // SSLv23_method uses TLSv1, with the ability to fall back to SSLv3 + if (server) { + method = SSLv23_server_method(); + } + else { + method = SSLv23_client_method(); + } + + // create new context from method + SSL_METHOD* m = const_cast<SSL_METHOD*>(method); + m_ssl->m_context = SSL_CTX_new(m); + + // drop SSLv3 support + SSL_CTX_set_options(m_ssl->m_context, SSL_OP_NO_SSLv3); + + if (m_ssl->m_context == NULL) { + showError(); + } +} + +void +SecureSocket::createSSL() +{ + // I assume just one instance is needed + // get new SSL state with context + if (m_ssl->m_ssl == NULL) { + assert(m_ssl->m_context != NULL); + m_ssl->m_ssl = SSL_new(m_ssl->m_context); + } +} + +int +SecureSocket::secureAccept(int socket) +{ + createSSL(); + + // set connection socket to SSL state + SSL_set_fd(m_ssl->m_ssl, socket); + + LOG((CLOG_DEBUG2 "accepting secure socket")); + int r = SSL_accept(m_ssl->m_ssl); + + static int retry; + + checkResult(r, retry); + + if (isFatal()) { + // tell user and sleep so the socket isn't hammered. + LOG((CLOG_ERR "failed to accept secure socket")); + LOG((CLOG_INFO "client connection may not be secure")); + m_secureReady = false; + ARCH->sleep(1); + retry = 0; + return -1; // Failed, error out + } + + // If not fatal and no retry, state is good + if (retry == 0) { + m_secureReady = true; + LOG((CLOG_INFO "accepted secure socket")); + if (CLOG->getFilter() >= kDEBUG1) { + showSecureCipherInfo(); + } + showSecureConnectInfo(); + return 1; + } + + // If not fatal and retry is set, not ready, and return retry + if (retry > 0) { + LOG((CLOG_DEBUG2 "retry accepting secure socket")); + m_secureReady = false; + ARCH->sleep(s_retryDelay); + return 0; + } + + // no good state exists here + LOG((CLOG_ERR "unexpected state attempting to accept connection")); + return -1; +} + +int +SecureSocket::secureConnect(int socket) +{ + createSSL(); + + // attach the socket descriptor + SSL_set_fd(m_ssl->m_ssl, socket); + + LOG((CLOG_DEBUG2 "connecting secure socket")); + int r = SSL_connect(m_ssl->m_ssl); + + static int retry; + + checkResult(r, retry); + + if (isFatal()) { + LOG((CLOG_ERR "failed to connect secure socket")); + retry = 0; + return -1; + } + + // If we should retry, not ready and return 0 + if (retry > 0) { + LOG((CLOG_DEBUG2 "retry connect secure socket")); + m_secureReady = false; + ARCH->sleep(s_retryDelay); + return 0; + } + + retry = 0; + // No error, set ready, process and return ok + m_secureReady = true; + if (verifyCertFingerprint()) { + LOG((CLOG_INFO "connected to secure socket")); + if (!showCertificate()) { + disconnect(); + return -1;// Cert fail, error + } + } + else { + LOG((CLOG_ERR "failed to verify server certificate fingerprint")); + disconnect(); + return -1; // Fingerprint failed, error + } + LOG((CLOG_DEBUG2 "connected secure socket")); + if (CLOG->getFilter() >= kDEBUG1) { + showSecureCipherInfo(); + } + showSecureConnectInfo(); + return 1; +} + +bool +SecureSocket::showCertificate() +{ + X509* cert; + char* line; + + // get the server's certificate + cert = SSL_get_peer_certificate(m_ssl->m_ssl); + if (cert != NULL) { + line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); + LOG((CLOG_INFO "server ssl certificate info: %s", line)); + OPENSSL_free(line); + X509_free(cert); + } + else { + showError("server has no ssl certificate"); + return false; + } + + return true; +} + +void +SecureSocket::checkResult(int status, int& retry) +{ + // ssl errors are a little quirky. the "want" errors are normal and + // should result in a retry. + + int errorCode = SSL_get_error(m_ssl->m_ssl, status); + + switch (errorCode) { + case SSL_ERROR_NONE: + retry = 0; + // operation completed + break; + + case SSL_ERROR_ZERO_RETURN: + // connection closed + isFatal(true); + LOG((CLOG_DEBUG "ssl connection closed")); + break; + + case SSL_ERROR_WANT_READ: + retry++; + LOG((CLOG_DEBUG2 "want to read, error=%d, attempt=%d", errorCode, retry)); + break; + + case SSL_ERROR_WANT_WRITE: + // Need to make sure the socket is known to be writable so the impending + // select action actually triggers on a write. This isn't necessary for + // m_readable because the socket logic is always readable + m_writable = true; + retry++; + LOG((CLOG_DEBUG2 "want to write, error=%d, attempt=%d", errorCode, retry)); + break; + + case SSL_ERROR_WANT_CONNECT: + retry++; + LOG((CLOG_DEBUG2 "want to connect, error=%d, attempt=%d", errorCode, retry)); + break; + + case SSL_ERROR_WANT_ACCEPT: + retry++; + LOG((CLOG_DEBUG2 "want to accept, error=%d, attempt=%d", errorCode, retry)); + break; + + case SSL_ERROR_SYSCALL: + LOG((CLOG_ERR "ssl error occurred (system call failure)")); + if (ERR_peek_error() == 0) { + if (status == 0) { + LOG((CLOG_ERR "eof violates ssl protocol")); + } + else if (status == -1) { + // underlying socket I/O reproted an error + try { + ARCH->throwErrorOnSocket(getSocket()); + } + catch (XArchNetwork& e) { + LOG((CLOG_ERR "%s", e.what())); + } + } + } + + isFatal(true); + break; + + case SSL_ERROR_SSL: + LOG((CLOG_ERR "ssl error occurred (generic failure)")); + isFatal(true); + break; + + default: + LOG((CLOG_ERR "ssl error occurred (unknown failure)")); + isFatal(true); + break; + } + + if (isFatal()) { + retry = 0; + showError(); + disconnect(); + } +} + +void +SecureSocket::showError(const char* reason) +{ + if (reason != NULL) { + LOG((CLOG_ERR "%s", reason)); + } + + String error = getError(); + if (!error.empty()) { + LOG((CLOG_ERR "%s", error.c_str())); + } +} + +String +SecureSocket::getError() +{ + unsigned long e = ERR_get_error(); + + if (e != 0) { + char error[MAX_ERROR_SIZE]; + ERR_error_string_n(e, error, MAX_ERROR_SIZE); + return error; + } + else { + return ""; + } +} + +void +SecureSocket::disconnect() +{ + sendEvent(getEvents()->forISocket().stopRetry()); + sendEvent(getEvents()->forISocket().disconnected()); + sendEvent(getEvents()->forIStream().inputShutdown()); +} + +void +SecureSocket::formatFingerprint(String& fingerprint, bool hex, bool separator) +{ + if (hex) { + // to hexidecimal + barrier::string::toHex(fingerprint, 2); + } + + // all uppercase + barrier::string::uppercase(fingerprint); + + if (separator) { + // add colon to separate each 2 charactors + size_t separators = fingerprint.size() / 2; + for (size_t i = 1; i < separators; i++) { + fingerprint.insert(i * 3 - 1, ":"); + } + } +} + +bool +SecureSocket::verifyCertFingerprint() +{ + // calculate received certificate fingerprint + X509 *cert = cert = SSL_get_peer_certificate(m_ssl->m_ssl); + EVP_MD* tempDigest; + unsigned char tempFingerprint[EVP_MAX_MD_SIZE]; + unsigned int tempFingerprintLen; + tempDigest = (EVP_MD*)EVP_sha1(); + int digestResult = X509_digest(cert, tempDigest, tempFingerprint, &tempFingerprintLen); + + if (digestResult <= 0) { + LOG((CLOG_ERR "failed to calculate fingerprint, digest result: %d", digestResult)); + return false; + } + + // format fingerprint into hexdecimal format with colon separator + String fingerprint(reinterpret_cast<char*>(tempFingerprint), tempFingerprintLen); + formatFingerprint(fingerprint); + LOG((CLOG_NOTE "server fingerprint: %s", fingerprint.c_str())); + + String trustedServersFilename; + trustedServersFilename = barrier::string::sprintf( + "%s/%s/%s", + ARCH->getProfileDirectory().c_str(), + kFingerprintDirName, + kFingerprintTrustedServersFilename); + + // check if this fingerprint exist + String fileLine; + std::ifstream file; + file.open(trustedServersFilename.c_str()); + + bool isValid = false; + while (!file.eof() && file.is_open()) { + getline(file,fileLine); + if (!fileLine.empty()) { + if (fileLine.compare(fingerprint) == 0) { + isValid = true; + break; + } + } + } + + file.close(); + return isValid; +} + +ISocketMultiplexerJob* +SecureSocket::serviceConnect(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + Lock lock(&getMutex()); + + int status = 0; +#ifdef SYSAPI_WIN32 + status = secureConnect(static_cast<int>(getSocket()->m_socket)); +#elif SYSAPI_UNIX + status = secureConnect(getSocket()->m_fd); +#endif + + // If status < 0, error happened + if (status < 0) { + return NULL; + } + + // If status > 0, success + if (status > 0) { + sendEvent(m_events->forIDataSocket().secureConnected()); + return newJob(); + } + + // Retry case + return new TSocketMultiplexerMethodJob<SecureSocket>( + this, &SecureSocket::serviceConnect, + getSocket(), isReadable(), isWritable()); +} + +ISocketMultiplexerJob* +SecureSocket::serviceAccept(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + Lock lock(&getMutex()); + + int status = 0; +#ifdef SYSAPI_WIN32 + status = secureAccept(static_cast<int>(getSocket()->m_socket)); +#elif SYSAPI_UNIX + status = secureAccept(getSocket()->m_fd); +#endif + // If status < 0, error happened + if (status < 0) { + return NULL; + } + + // If status > 0, success + if (status > 0) { + sendEvent(m_events->forClientListener().accepted()); + return newJob(); + } + + // Retry case + return new TSocketMultiplexerMethodJob<SecureSocket>( + this, &SecureSocket::serviceAccept, + getSocket(), isReadable(), isWritable()); +} + +void +showCipherStackDesc(STACK_OF(SSL_CIPHER) * stack) { + char msg[kMsgSize]; + int i = 0; + for ( ; i < sk_SSL_CIPHER_num(stack) ; i++) { + const SSL_CIPHER * cipher = sk_SSL_CIPHER_value(stack,i); + + SSL_CIPHER_description(cipher, msg, kMsgSize); + + // Why does SSL put a newline in the description? + int pos = (int)strlen(msg) - 1; + if (msg[pos] == '\n') { + msg[pos] = '\0'; + } + + LOG((CLOG_DEBUG1 "%s",msg)); + } +} + +void +SecureSocket::showSecureCipherInfo() +{ + STACK_OF(SSL_CIPHER) * sStack = SSL_get_ciphers(m_ssl->m_ssl); + + if (sStack == NULL) { + LOG((CLOG_DEBUG1 "local cipher list not available")); + } + else { + LOG((CLOG_DEBUG1 "available local ciphers:")); + showCipherStackDesc(sStack); + } + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + // m_ssl->m_ssl->session->ciphers is not forward compatable, + // In future release of OpenSSL, it's not visible, + STACK_OF(SSL_CIPHER) * cStack = m_ssl->m_ssl->session->ciphers; +#else + // Use SSL_get_client_ciphers() for newer versions + STACK_OF(SSL_CIPHER) * cStack = SSL_get_client_ciphers(m_ssl->m_ssl); +#endif + if (cStack == NULL) { + LOG((CLOG_DEBUG1 "remote cipher list not available")); + } + else { + LOG((CLOG_DEBUG1 "available remote ciphers:")); + showCipherStackDesc(cStack); + } + return; +} + +void +SecureSocket::showSecureLibInfo() +{ + LOG((CLOG_INFO "%s",SSLeay_version(SSLEAY_VERSION))); + LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_CFLAGS))); + LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_BUILT_ON))); + LOG((CLOG_DEBUG1 "openSSL : %s",SSLeay_version(SSLEAY_PLATFORM))); + LOG((CLOG_DEBUG1 "%s",SSLeay_version(SSLEAY_DIR))); + return; +} + +void +SecureSocket::showSecureConnectInfo() +{ + const SSL_CIPHER* cipher = SSL_get_current_cipher(m_ssl->m_ssl); + + if (cipher != NULL) { + char msg[kMsgSize]; + SSL_CIPHER_description(cipher, msg, kMsgSize); + LOG((CLOG_INFO "%s", msg)); + } + return; +} + +void +SecureSocket::handleTCPConnected(const Event& event, void*) +{ + if (getSocket() == nullptr) { + LOG((CLOG_DEBUG "disregarding stale connect event")); + return; + } + secureConnect(); +} diff --git a/src/lib/net/SecureSocket.h b/src/lib/net/SecureSocket.h new file mode 100644 index 0000000..01d3c3f --- /dev/null +++ b/src/lib/net/SecureSocket.h @@ -0,0 +1,95 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 + +#include "net/TCPSocket.h" +#include "net/XSocket.h" + +class IEventQueue; +class SocketMultiplexer; +class ISocketMultiplexerJob; + +struct Ssl; + +//! Secure socket +/*! +A secure socket using SSL. +*/ +class SecureSocket : public TCPSocket { +public: + SecureSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family); + SecureSocket(IEventQueue* events, + SocketMultiplexer* socketMultiplexer, + ArchSocket socket); + ~SecureSocket(); + + // ISocket overrides + void close(); + + // IDataSocket overrides + virtual void connect(const NetworkAddress&); + + ISocketMultiplexerJob* + newJob(); + bool isFatal() const { return m_fatal; } + void isFatal(bool b) { m_fatal = b; } + bool isSecureReady(); + void secureConnect(); + void secureAccept(); + int secureRead(void* buffer, int size, int& read); + int secureWrite(const void* buffer, int size, int& wrote); + EJobResult doRead(); + EJobResult doWrite(); + void initSsl(bool server); + bool loadCertificates(String& CertFile); + +private: + // SSL + void initContext(bool server); + void createSSL(); + int secureAccept(int s); + int secureConnect(int s); + bool showCertificate(); + void checkResult(int n, int& retry); + void showError(const char* reason = NULL); + String getError(); + void disconnect(); + void formatFingerprint(String& fingerprint, + bool hex = true, + bool separator = true); + bool verifyCertFingerprint(); + + ISocketMultiplexerJob* + serviceConnect(ISocketMultiplexerJob*, + bool, bool, bool); + + ISocketMultiplexerJob* + serviceAccept(ISocketMultiplexerJob*, + bool, bool, bool); + + void showSecureConnectInfo(); + void showSecureLibInfo(); + void showSecureCipherInfo(); + + void handleTCPConnected(const Event& event, void*); + +private: + Ssl* m_ssl; + bool m_secureReady; + bool m_fatal; +}; diff --git a/src/lib/net/SocketMultiplexer.cpp b/src/lib/net/SocketMultiplexer.cpp new file mode 100644 index 0000000..c4bc64a --- /dev/null +++ b/src/lib/net/SocketMultiplexer.cpp @@ -0,0 +1,352 @@ +/* + * 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 "net/SocketMultiplexer.h" + +#include "net/ISocketMultiplexerJob.h" +#include "mt/CondVar.h" +#include "mt/Lock.h" +#include "mt/Mutex.h" +#include "mt/Thread.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/TMethodJob.h" +#include "common/stdvector.h" + +// +// SocketMultiplexer +// + +SocketMultiplexer::SocketMultiplexer() : + m_mutex(new Mutex), + m_thread(NULL), + m_update(false), + m_jobsReady(new CondVar<bool>(m_mutex, false)), + m_jobListLock(new CondVar<bool>(m_mutex, false)), + m_jobListLockLocked(new CondVar<bool>(m_mutex, false)), + m_jobListLocker(NULL), + m_jobListLockLocker(NULL) +{ + // this pointer just has to be unique and not NULL. it will + // never be dereferenced. it's used to identify cursor nodes + // in the jobs list. + // TODO: Remove this evilness + m_cursorMark = reinterpret_cast<ISocketMultiplexerJob*>(this); + + // start thread + m_thread = new Thread(new TMethodJob<SocketMultiplexer>( + this, &SocketMultiplexer::serviceThread)); +} + +SocketMultiplexer::~SocketMultiplexer() +{ + m_thread->cancel(); + m_thread->unblockPollSocket(); + m_thread->wait(); + delete m_thread; + delete m_jobsReady; + delete m_jobListLock; + delete m_jobListLockLocked; + delete m_jobListLocker; + delete m_jobListLockLocker; + delete m_mutex; + + // clean up jobs + for (SocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end(); ++i) { + delete *(i->second); + } +} + +void +SocketMultiplexer::addSocket(ISocket* socket, ISocketMultiplexerJob* job) +{ + assert(socket != NULL); + assert(job != NULL); + + // prevent other threads from locking the job list + lockJobListLock(); + + // break thread out of poll + m_thread->unblockPollSocket(); + + // lock the job list + lockJobList(); + + // insert/replace job + SocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i == m_socketJobMap.end()) { + // we *must* put the job at the end so the order of jobs in + // the list continue to match the order of jobs in pfds in + // serviceThread(). + JobCursor j = m_socketJobs.insert(m_socketJobs.end(), job); + m_update = true; + m_socketJobMap.insert(std::make_pair(socket, j)); + } + else { + JobCursor j = i->second; + if (*j != job) { + delete *j; + *j = job; + } + m_update = true; + } + + // unlock the job list + unlockJobList(); +} + +void +SocketMultiplexer::removeSocket(ISocket* socket) +{ + assert(socket != NULL); + + // prevent other threads from locking the job list + lockJobListLock(); + + // break thread out of poll + m_thread->unblockPollSocket(); + + // lock the job list + lockJobList(); + + // remove job. rather than removing it from the map we put NULL + // in the list instead so the order of jobs in the list continues + // to match the order of jobs in pfds in serviceThread(). + SocketJobMap::iterator i = m_socketJobMap.find(socket); + if (i != m_socketJobMap.end()) { + if (*(i->second) != NULL) { + delete *(i->second); + *(i->second) = NULL; + m_update = true; + } + } + + // unlock the job list + unlockJobList(); +} + +void +SocketMultiplexer::serviceThread(void*) +{ + std::vector<IArchNetwork::PollEntry> pfds; + IArchNetwork::PollEntry pfd; + + // service the connections + for (;;) { + Thread::testCancel(); + + // wait until there are jobs to handle + { + Lock lock(m_mutex); + while (!(bool)*m_jobsReady) { + m_jobsReady->wait(); + } + } + + // lock the job list + lockJobListLock(); + lockJobList(); + + // collect poll entries + if (m_update) { + m_update = false; + pfds.clear(); + pfds.reserve(m_socketJobMap.size()); + + JobCursor cursor = newCursor(); + JobCursor jobCursor = nextCursor(cursor); + while (jobCursor != m_socketJobs.end()) { + ISocketMultiplexerJob* job = *jobCursor; + if (job != NULL) { + pfd.m_socket = job->getSocket(); + pfd.m_events = 0; + if (job->isReadable()) { + pfd.m_events |= IArchNetwork::kPOLLIN; + } + if (job->isWritable()) { + pfd.m_events |= IArchNetwork::kPOLLOUT; + } + pfds.push_back(pfd); + } + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + int status; + try { + // check for status + if (!pfds.empty()) { + status = ARCH->pollSocket(&pfds[0], (int)pfds.size(), -1); + } + else { + status = 0; + } + } + catch (XArchNetwork& e) { + LOG((CLOG_WARN "error in socket multiplexer: %s", e.what())); + status = 0; + } + + if (status != 0) { + // iterate over socket jobs, invoking each and saving the + // new job. + UInt32 i = 0; + JobCursor cursor = newCursor(); + JobCursor jobCursor = nextCursor(cursor); + while (i < pfds.size() && jobCursor != m_socketJobs.end()) { + if (*jobCursor != NULL) { + // get poll state + unsigned short revents = pfds[i].m_revents; + bool read = ((revents & IArchNetwork::kPOLLIN) != 0); + bool write = ((revents & IArchNetwork::kPOLLOUT) != 0); + bool error = ((revents & (IArchNetwork::kPOLLERR | + IArchNetwork::kPOLLNVAL)) != 0); + + // run job + ISocketMultiplexerJob* job = *jobCursor; + ISocketMultiplexerJob* newJob = job->run(read, write, error); + + // save job, if different + if (newJob != job) { + Lock lock(m_mutex); + delete job; + *jobCursor = newJob; + m_update = true; + } + ++i; + } + + // next job + jobCursor = nextCursor(cursor); + } + deleteCursor(cursor); + } + + // delete any removed socket jobs + for (SocketJobMap::iterator i = m_socketJobMap.begin(); + i != m_socketJobMap.end();) { + if (*(i->second) == NULL) { + m_socketJobs.erase(i->second); + m_socketJobMap.erase(i++); + m_update = true; + } + else { + ++i; + } + } + + // unlock the job list + unlockJobList(); + } +} + +SocketMultiplexer::JobCursor +SocketMultiplexer::newCursor() +{ + Lock lock(m_mutex); + return m_socketJobs.insert(m_socketJobs.begin(), m_cursorMark); +} + +SocketMultiplexer::JobCursor +SocketMultiplexer::nextCursor(JobCursor cursor) +{ + Lock lock(m_mutex); + JobCursor j = m_socketJobs.end(); + JobCursor i = cursor; + while (++i != m_socketJobs.end()) { + if (*i != m_cursorMark) { + // found a real job (as opposed to a cursor) + j = i; + + // move our cursor just past the job + m_socketJobs.splice(++i, m_socketJobs, cursor); + break; + } + } + return j; +} + +void +SocketMultiplexer::deleteCursor(JobCursor cursor) +{ + Lock lock(m_mutex); + m_socketJobs.erase(cursor); +} + +void +SocketMultiplexer::lockJobListLock() +{ + Lock lock(m_mutex); + + // wait for the lock on the lock + while (*m_jobListLockLocked) { + m_jobListLockLocked->wait(); + } + + // take ownership of the lock on the lock + *m_jobListLockLocked = true; + m_jobListLockLocker = new Thread(Thread::getCurrentThread()); +} + +void +SocketMultiplexer::lockJobList() +{ + Lock lock(m_mutex); + + // make sure we're the one that called lockJobListLock() + assert(*m_jobListLockLocker == Thread::getCurrentThread()); + + // wait for the job list lock + while (*m_jobListLock) { + m_jobListLock->wait(); + } + + // take ownership of the lock + *m_jobListLock = true; + m_jobListLocker = m_jobListLockLocker; + m_jobListLockLocker = NULL; + + // release the lock on the lock + *m_jobListLockLocked = false; + m_jobListLockLocked->broadcast(); +} + +void +SocketMultiplexer::unlockJobList() +{ + Lock lock(m_mutex); + + // make sure we're the one that called lockJobList() + assert(*m_jobListLocker == Thread::getCurrentThread()); + + // release the lock + delete m_jobListLocker; + m_jobListLocker = NULL; + *m_jobListLock = false; + m_jobListLock->signal(); + + // set new jobs ready state + bool isReady = !m_socketJobMap.empty(); + if (*m_jobsReady != isReady) { + *m_jobsReady = isReady; + m_jobsReady->signal(); + } +} diff --git a/src/lib/net/SocketMultiplexer.h b/src/lib/net/SocketMultiplexer.h new file mode 100644 index 0000000..4aa39fc --- /dev/null +++ b/src/lib/net/SocketMultiplexer.h @@ -0,0 +1,111 @@ +/* + * 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/IArchNetwork.h" +#include "common/stdlist.h" +#include "common/stdmap.h" + +template <class T> +class CondVar; +class Mutex; +class Thread; +class ISocket; +class ISocketMultiplexerJob; + +//! Socket multiplexer +/*! +A socket multiplexer services multiple sockets simultaneously. +*/ +class SocketMultiplexer { +public: + SocketMultiplexer(); + ~SocketMultiplexer(); + + //! @name manipulators + //@{ + + void addSocket(ISocket*, ISocketMultiplexerJob*); + + void removeSocket(ISocket*); + + //@} + //! @name accessors + //@{ + + // maybe belongs on ISocketMultiplexer + static SocketMultiplexer* + getInstance(); + + //@} + +private: + // list of jobs. we use a list so we can safely iterate over it + // while other threads modify it. + typedef std::list<ISocketMultiplexerJob*> SocketJobs; + typedef SocketJobs::iterator JobCursor; + typedef std::map<ISocket*, JobCursor> SocketJobMap; + + // service sockets. the service thread will only access m_sockets + // and m_update while m_pollable and m_polling are true. all other + // threads must only modify these when m_pollable and m_polling are + // false. only the service thread sets m_polling. + void serviceThread(void*); + + // create, iterate, and destroy a cursor. a cursor is used to + // safely iterate through the job list while other threads modify + // the list. it works by inserting a dummy item in the list and + // moving that item through the list. the dummy item will never + // be removed by other edits so an iterator pointing at the item + // remains valid until we remove the dummy item in deleteCursor(). + // nextCursor() finds the next non-dummy item, moves our dummy + // item just past it, and returns an iterator for the non-dummy + // item. all cursor calls lock the mutex for their duration. + JobCursor newCursor(); + JobCursor nextCursor(JobCursor); + void deleteCursor(JobCursor); + + // lock out locking the job list. this blocks if another thread + // has already locked out locking. once it returns, only the + // calling thread will be able to lock the job list after any + // current lock is released. + void lockJobListLock(); + + // lock the job list. this blocks if the job list is already + // locked. the calling thread must have called requestJobLock. + void lockJobList(); + + // unlock the job list and the lock out on locking. + void unlockJobList(); + +private: + Mutex* m_mutex; + Thread* m_thread; + bool m_update; + CondVar<bool>* m_jobsReady; + CondVar<bool>* m_jobListLock; + CondVar<bool>* m_jobListLockLocked; + Thread* m_jobListLocker; + Thread* m_jobListLockLocker; + + SocketJobs m_socketJobs; + SocketJobMap m_socketJobMap; + ISocketMultiplexerJob* + m_cursorMark; +}; diff --git a/src/lib/net/TCPListenSocket.cpp b/src/lib/net/TCPListenSocket.cpp new file mode 100644 index 0000000..8e1540e --- /dev/null +++ b/src/lib/net/TCPListenSocket.cpp @@ -0,0 +1,158 @@ +/* + * 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 "net/TCPListenSocket.h" + +#include "net/NetworkAddress.h" +#include "net/SocketMultiplexer.h" +#include "net/TCPSocket.h" +#include "net/TSocketMultiplexerMethodJob.h" +#include "net/XSocket.h" +#include "io/XIO.h" +#include "mt/Lock.h" +#include "mt/Mutex.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/IEventQueue.h" + +// +// TCPListenSocket +// + +TCPListenSocket::TCPListenSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) : + m_events(events), + m_socketMultiplexer(socketMultiplexer) +{ + m_mutex = new Mutex; + try { + m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM); + } + catch (XArchNetwork& e) { + throw XSocketCreate(e.what()); + } +} + +TCPListenSocket::~TCPListenSocket() +{ + try { + if (m_socket != NULL) { + m_socketMultiplexer->removeSocket(this); + ARCH->closeSocket(m_socket); + } + } + catch (...) { + // ignore + } + delete m_mutex; +} + +void +TCPListenSocket::bind(const NetworkAddress& addr) +{ + try { + Lock lock(m_mutex); + ARCH->setReuseAddrOnSocket(m_socket, true); + ARCH->bindSocket(m_socket, addr.getAddress()); + ARCH->listenOnSocket(m_socket); + m_socketMultiplexer->addSocket(this, + new TSocketMultiplexerMethodJob<TCPListenSocket>( + this, &TCPListenSocket::serviceListening, + m_socket, true, false)); + } + catch (XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void +TCPListenSocket::close() +{ + Lock lock(m_mutex); + if (m_socket == NULL) { + throw XIOClosed(); + } + try { + m_socketMultiplexer->removeSocket(this); + ARCH->closeSocket(m_socket); + m_socket = NULL; + } + catch (XArchNetwork& e) { + throw XSocketIOClose(e.what()); + } +} + +void* +TCPListenSocket::getEventTarget() const +{ + return const_cast<void*>(static_cast<const void*>(this)); +} + +IDataSocket* +TCPListenSocket::accept() +{ + IDataSocket* socket = NULL; + try { + socket = new TCPSocket(m_events, m_socketMultiplexer, ARCH->acceptSocket(m_socket, NULL)); + if (socket != NULL) { + setListeningJob(); + } + return socket; + } + catch (XArchNetwork&) { + if (socket != NULL) { + delete socket; + setListeningJob(); + } + return NULL; + } + catch (std::exception &ex) { + if (socket != NULL) { + delete socket; + setListeningJob(); + } + throw ex; + } +} + +void +TCPListenSocket::setListeningJob() +{ + m_socketMultiplexer->addSocket(this, + new TSocketMultiplexerMethodJob<TCPListenSocket>( + this, &TCPListenSocket::serviceListening, + m_socket, true, false)); +} + +ISocketMultiplexerJob* +TCPListenSocket::serviceListening(ISocketMultiplexerJob* job, + bool read, bool, bool error) +{ + if (error) { + close(); + return NULL; + } + if (read) { + m_events->addEvent(Event(m_events->forIListenSocket().connecting(), this, NULL)); + // stop polling on this socket until the client accepts + return NULL; + } + return job; +} diff --git a/src/lib/net/TCPListenSocket.h b/src/lib/net/TCPListenSocket.h new file mode 100644 index 0000000..1060356 --- /dev/null +++ b/src/lib/net/TCPListenSocket.h @@ -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/>. + */ + +#pragma once + +#include "net/IListenSocket.h" +#include "arch/IArchNetwork.h" + +class Mutex; +class ISocketMultiplexerJob; +class IEventQueue; +class SocketMultiplexer; + +//! TCP listen socket +/*! +A listen socket using TCP. +*/ +class TCPListenSocket : public IListenSocket { +public: + TCPListenSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family); + virtual ~TCPListenSocket(); + + // ISocket overrides + virtual void bind(const NetworkAddress&); + virtual void close(); + virtual void* getEventTarget() const; + + // IListenSocket overrides + virtual IDataSocket* + accept(); + +protected: + void setListeningJob(); + +public: + ISocketMultiplexerJob* + serviceListening(ISocketMultiplexerJob*, + bool, bool, bool); + +protected: + ArchSocket m_socket; + Mutex* m_mutex; + IEventQueue* m_events; + SocketMultiplexer* m_socketMultiplexer; +}; diff --git a/src/lib/net/TCPSocket.cpp b/src/lib/net/TCPSocket.cpp new file mode 100644 index 0000000..dce81ee --- /dev/null +++ b/src/lib/net/TCPSocket.cpp @@ -0,0 +1,596 @@ +/* + * 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 "net/TCPSocket.h" + +#include "net/NetworkAddress.h" +#include "net/SocketMultiplexer.h" +#include "net/TSocketMultiplexerMethodJob.h" +#include "net/XSocket.h" +#include "mt/Lock.h" +#include "arch/Arch.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/IEventJob.h" + +#include <cstring> +#include <cstdlib> +#include <memory> + +// +// TCPSocket +// + +TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family) : + IDataSocket(events), + m_events(events), + m_mutex(), + m_flushed(&m_mutex, true), + m_socketMultiplexer(socketMultiplexer) +{ + try { + m_socket = ARCH->newSocket(family, IArchNetwork::kSTREAM); + } + catch (XArchNetwork& e) { + throw XSocketCreate(e.what()); + } + + LOG((CLOG_DEBUG "Opening new socket: %08X", m_socket)); + + init(); +} + +TCPSocket::TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, ArchSocket socket) : + IDataSocket(events), + m_events(events), + m_mutex(), + m_socket(socket), + m_flushed(&m_mutex, true), + m_socketMultiplexer(socketMultiplexer) +{ + assert(m_socket != NULL); + + LOG((CLOG_DEBUG "Opening new socket: %08X", m_socket)); + + // socket starts in connected state + init(); + onConnected(); + setJob(newJob()); +} + +TCPSocket::~TCPSocket() +{ + try { + close(); + } + catch (...) { + // ignore + } +} + +void +TCPSocket::bind(const NetworkAddress& addr) +{ + try { + ARCH->bindSocket(m_socket, addr.getAddress()); + } + catch (XArchNetworkAddressInUse& e) { + throw XSocketAddressInUse(e.what()); + } + catch (XArchNetwork& e) { + throw XSocketBind(e.what()); + } +} + +void +TCPSocket::close() +{ + LOG((CLOG_DEBUG "Closing socket: %08X", m_socket)); + + // remove ourself from the multiplexer + setJob(NULL); + + Lock lock(&m_mutex); + + // clear buffers and enter disconnected state + if (m_connected) { + sendEvent(m_events->forISocket().disconnected()); + } + onDisconnected(); + + // close the socket + if (m_socket != NULL) { + ArchSocket socket = m_socket; + m_socket = NULL; + try { + ARCH->closeSocket(socket); + } + catch (XArchNetwork& e) { + // ignore, there's not much we can do + LOG((CLOG_WARN "error closing socket: %s", e.what())); + } + } +} + +void* +TCPSocket::getEventTarget() const +{ + return const_cast<void*>(static_cast<const void*>(this)); +} + +UInt32 +TCPSocket::read(void* buffer, UInt32 n) +{ + // copy data directly from our input buffer + Lock lock(&m_mutex); + UInt32 size = m_inputBuffer.getSize(); + if (n > size) { + n = size; + } + if (buffer != NULL && n != 0) { + memcpy(buffer, m_inputBuffer.peek(n), n); + } + m_inputBuffer.pop(n); + + // if no more data and we cannot read or write then send disconnected + if (n > 0 && m_inputBuffer.getSize() == 0 && !m_readable && !m_writable) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + + return n; +} + +void +TCPSocket::write(const void* buffer, UInt32 n) +{ + bool wasEmpty; + { + Lock lock(&m_mutex); + + // must not have shutdown output + if (!m_writable) { + sendEvent(m_events->forIStream().outputError()); + return; + } + + // ignore empty writes + if (n == 0) { + return; + } + + // copy data to the output buffer + wasEmpty = (m_outputBuffer.getSize() == 0); + m_outputBuffer.write(buffer, n); + + // there's data to write + m_flushed = false; + } + + // make sure we're waiting to write + if (wasEmpty) { + setJob(newJob()); + } +} + +void +TCPSocket::flush() +{ + Lock lock(&m_mutex); + while (m_flushed == false) { + m_flushed.wait(); + } +} + +void +TCPSocket::shutdownInput() +{ + bool useNewJob = false; + { + Lock lock(&m_mutex); + + // shutdown socket for reading + try { + ARCH->closeSocketForRead(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for reading + if (m_readable) { + sendEvent(m_events->forIStream().inputShutdown()); + onInputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob()); + } +} + +void +TCPSocket::shutdownOutput() +{ + bool useNewJob = false; + { + Lock lock(&m_mutex); + + // shutdown socket for writing + try { + ARCH->closeSocketForWrite(m_socket); + } + catch (XArchNetwork&) { + // ignore + } + + // shutdown buffer for writing + if (m_writable) { + sendEvent(m_events->forIStream().outputShutdown()); + onOutputShutdown(); + useNewJob = true; + } + } + if (useNewJob) { + setJob(newJob()); + } +} + +bool +TCPSocket::isReady() const +{ + Lock lock(&m_mutex); + return (m_inputBuffer.getSize() > 0); +} + +bool +TCPSocket::isFatal() const +{ + // TCP sockets aren't ever left in a fatal state. + LOG((CLOG_ERR "isFatal() not valid for non-secure connections")); + return false; +} + +UInt32 +TCPSocket::getSize() const +{ + Lock lock(&m_mutex); + return m_inputBuffer.getSize(); +} + +void +TCPSocket::connect(const NetworkAddress& addr) +{ + { + Lock lock(&m_mutex); + + // fail on attempts to reconnect + if (m_socket == NULL || m_connected) { + sendConnectionFailedEvent("busy"); + return; + } + + try { + if (ARCH->connectSocket(m_socket, addr.getAddress())) { + sendEvent(m_events->forIDataSocket().connected()); + onConnected(); + } + else { + // connection is in progress + m_writable = true; + } + } + catch (XArchNetwork& e) { + throw XSocketConnect(e.what()); + } + } + setJob(newJob()); +} + +void +TCPSocket::init() +{ + // default state + m_connected = false; + m_readable = false; + m_writable = false; + + try { + // turn off Nagle algorithm. we send lots of very short messages + // that should be sent without (much) delay. for example, the + // mouse motion messages are much less useful if they're delayed. + ARCH->setNoDelayOnSocket(m_socket, true); + } + catch (XArchNetwork& e) { + try { + ARCH->closeSocket(m_socket); + m_socket = NULL; + } + catch (XArchNetwork&) { + // ignore + } + throw XSocketCreate(e.what()); + } +} + +TCPSocket::EJobResult +TCPSocket::doRead() +{ + UInt8 buffer[4096]; + memset(buffer, 0, sizeof(buffer)); + size_t bytesRead = 0; + + bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + + if (bytesRead > 0) { + bool wasEmpty = (m_inputBuffer.getSize() == 0); + + // slurp up as much as possible + do { + m_inputBuffer.write(buffer, (UInt32)bytesRead); + + bytesRead = ARCH->readSocket(m_socket, buffer, sizeof(buffer)); + } while (bytesRead > 0); + + // send input ready if input buffer was empty + if (wasEmpty) { + sendEvent(m_events->forIStream().inputReady()); + } + } + else { + // remote write end of stream hungup. our input side + // has therefore shutdown but don't flush our buffer + // since there's still data to be read. + sendEvent(m_events->forIStream().inputShutdown()); + if (!m_writable && m_inputBuffer.getSize() == 0) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + m_readable = false; + return kNew; + } + + return kRetry; +} + +TCPSocket::EJobResult +TCPSocket::doWrite() +{ + // write data + UInt32 bufferSize = 0; + int bytesWrote = 0; + + bufferSize = m_outputBuffer.getSize(); + const void* buffer = m_outputBuffer.peek(bufferSize); + bytesWrote = (UInt32)ARCH->writeSocket(m_socket, buffer, bufferSize); + + if (bytesWrote > 0) { + discardWrittenData(bytesWrote); + return kNew; + } + + return kRetry; +} + +void +TCPSocket::setJob(ISocketMultiplexerJob* job) +{ + // multiplexer will delete the old job + if (job == NULL) { + m_socketMultiplexer->removeSocket(this); + } + else { + m_socketMultiplexer->addSocket(this, job); + } +} + +ISocketMultiplexerJob* +TCPSocket::newJob() +{ + // note -- must have m_mutex locked on entry + + if (m_socket == NULL) { + return NULL; + } + else if (!m_connected) { + assert(!m_readable); + if (!(m_readable || m_writable)) { + return NULL; + } + return new TSocketMultiplexerMethodJob<TCPSocket>( + this, &TCPSocket::serviceConnecting, + m_socket, m_readable, m_writable); + } + else { + if (!(m_readable || (m_writable && (m_outputBuffer.getSize() > 0)))) { + return NULL; + } + return new TSocketMultiplexerMethodJob<TCPSocket>( + this, &TCPSocket::serviceConnected, + m_socket, m_readable, + m_writable && (m_outputBuffer.getSize() > 0)); + } +} + +void +TCPSocket::sendConnectionFailedEvent(const char* msg) +{ + ConnectionFailedInfo* info = new ConnectionFailedInfo(msg); + m_events->addEvent(Event(m_events->forIDataSocket().connectionFailed(), + getEventTarget(), info, Event::kDontFreeData)); +} + +void +TCPSocket::sendEvent(Event::Type type) +{ + m_events->addEvent(Event(type, getEventTarget(), NULL)); +} + +void +TCPSocket::discardWrittenData(int bytesWrote) +{ + m_outputBuffer.pop(bytesWrote); + if (m_outputBuffer.getSize() == 0) { + sendEvent(m_events->forIStream().outputFlushed()); + m_flushed = true; + m_flushed.broadcast(); + } +} + +void +TCPSocket::onConnected() +{ + m_connected = true; + m_readable = true; + m_writable = true; +} + +void +TCPSocket::onInputShutdown() +{ + m_inputBuffer.pop(m_inputBuffer.getSize()); + m_readable = false; +} + +void +TCPSocket::onOutputShutdown() +{ + m_outputBuffer.pop(m_outputBuffer.getSize()); + m_writable = false; + + // we're now flushed + m_flushed = true; + m_flushed.broadcast(); +} + +void +TCPSocket::onDisconnected() +{ + // disconnected + onInputShutdown(); + onOutputShutdown(); + m_connected = false; +} + +ISocketMultiplexerJob* +TCPSocket::serviceConnecting(ISocketMultiplexerJob* job, + bool, bool write, bool error) +{ + Lock lock(&m_mutex); + + // should only check for errors if error is true but checking a new + // socket (and a socket that's connecting should be new) for errors + // should be safe and Mac OS X appears to have a bug where a + // non-blocking stream socket that fails to connect immediately is + // reported by select as being writable (i.e. connected) even when + // the connection has failed. this is easily demonstrated on OS X + // 10.3.4 by starting a barrier client and telling to connect to + // another system that's not running a barrier server. it will + // claim to have connected then quickly disconnect (i guess because + // read returns 0 bytes). unfortunately, barrier attempts to + // reconnect immediately, the process repeats and we end up + // spinning the CPU. luckily, OS X does set SO_ERROR on the + // socket correctly when the connection has failed so checking for + // errors works. (curiously, sometimes OS X doesn't report + // connection refused. when that happens it at least doesn't + // report the socket as being writable so barrier is able to time + // out the attempt.) + if (error || true) { + try { + // connection may have failed or succeeded + ARCH->throwErrorOnSocket(m_socket); + } + catch (XArchNetwork& e) { + sendConnectionFailedEvent(e.what()); + onDisconnected(); + return newJob(); + } + } + + if (write) { + sendEvent(m_events->forIDataSocket().connected()); + onConnected(); + return newJob(); + } + + return job; +} + +ISocketMultiplexerJob* +TCPSocket::serviceConnected(ISocketMultiplexerJob* job, + bool read, bool write, bool error) +{ + Lock lock(&m_mutex); + + if (error) { + sendEvent(m_events->forISocket().disconnected()); + onDisconnected(); + return newJob(); + } + + EJobResult result = kRetry; + if (write) { + try { + result = doWrite(); + } + catch (XArchNetworkShutdown&) { + // remote read end of stream hungup. our output side + // has therefore shutdown. + onOutputShutdown(); + sendEvent(m_events->forIStream().outputShutdown()); + if (!m_readable && m_inputBuffer.getSize() == 0) { + sendEvent(m_events->forISocket().disconnected()); + m_connected = false; + } + result = kNew; + } + catch (XArchNetworkDisconnected&) { + // stream hungup + onDisconnected(); + sendEvent(m_events->forISocket().disconnected()); + result = kNew; + } + catch (XArchNetwork& e) { + // other write error + LOG((CLOG_WARN "error writing socket: %s", e.what())); + onDisconnected(); + sendEvent(m_events->forIStream().outputError()); + sendEvent(m_events->forISocket().disconnected()); + result = kNew; + } + } + + if (read && m_readable) { + try { + result = doRead(); + } + catch (XArchNetworkDisconnected&) { + // stream hungup + sendEvent(m_events->forISocket().disconnected()); + onDisconnected(); + result = kNew; + } + catch (XArchNetwork& e) { + // ignore other read error + LOG((CLOG_WARN "error reading socket: %s", e.what())); + } + } + + return result == kBreak ? NULL : result == kNew ? newJob() : job; +} diff --git a/src/lib/net/TCPSocket.h b/src/lib/net/TCPSocket.h new file mode 100644 index 0000000..1006f88 --- /dev/null +++ b/src/lib/net/TCPSocket.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 "net/IDataSocket.h" +#include "io/StreamBuffer.h" +#include "mt/CondVar.h" +#include "mt/Mutex.h" +#include "arch/IArchNetwork.h" + +class Mutex; +class Thread; +class ISocketMultiplexerJob; +class IEventQueue; +class SocketMultiplexer; + +//! TCP data socket +/*! +A data socket using TCP. +*/ +class TCPSocket : public IDataSocket { +public: + TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, IArchNetwork::EAddressFamily family); + TCPSocket(IEventQueue* events, SocketMultiplexer* socketMultiplexer, ArchSocket socket); + virtual ~TCPSocket(); + + // ISocket overrides + virtual void bind(const NetworkAddress&); + virtual void close(); + virtual void* getEventTarget() const; + + // IStream overrides + virtual UInt32 read(void* buffer, UInt32 n); + virtual void write(const void* buffer, UInt32 n); + virtual void flush(); + virtual void shutdownInput(); + virtual void shutdownOutput(); + virtual bool isReady() const; + virtual bool isFatal() const; + virtual UInt32 getSize() const; + + // IDataSocket overrides + virtual void connect(const NetworkAddress&); + + + virtual ISocketMultiplexerJob* + newJob(); + +protected: + enum EJobResult { + kBreak = -1, //!< Break the Job chain + kRetry, //!< Retry the same job + kNew //!< Require a new job + }; + + ArchSocket getSocket() { return m_socket; } + IEventQueue* getEvents() { return m_events; } + virtual EJobResult doRead(); + virtual EJobResult doWrite(); + + void setJob(ISocketMultiplexerJob*); + + bool isReadable() { return m_readable; } + bool isWritable() { return m_writable; } + + Mutex& getMutex() { return m_mutex; } + + void sendEvent(Event::Type); + void discardWrittenData(int bytesWrote); + +private: + void init(); + + void sendConnectionFailedEvent(const char*); + void onConnected(); + void onInputShutdown(); + void onOutputShutdown(); + void onDisconnected(); + + ISocketMultiplexerJob* + serviceConnecting(ISocketMultiplexerJob*, + bool, bool, bool); + ISocketMultiplexerJob* + serviceConnected(ISocketMultiplexerJob*, + bool, bool, bool); + +protected: + bool m_readable; + bool m_writable; + bool m_connected; + IEventQueue* m_events; + StreamBuffer m_inputBuffer; + StreamBuffer m_outputBuffer; + +private: + Mutex m_mutex; + ArchSocket m_socket; + CondVar<bool> m_flushed; + SocketMultiplexer* m_socketMultiplexer; +}; diff --git a/src/lib/net/TCPSocketFactory.cpp b/src/lib/net/TCPSocketFactory.cpp new file mode 100644 index 0000000..6ff4ef8 --- /dev/null +++ b/src/lib/net/TCPSocketFactory.cpp @@ -0,0 +1,68 @@ +/* + * 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 "net/TCPSocketFactory.h" +#include "net/TCPSocket.h" +#include "net/TCPListenSocket.h" +#include "net/SecureSocket.h" +#include "net/SecureListenSocket.h" +#include "arch/Arch.h" +#include "base/Log.h" + +// +// TCPSocketFactory +// + +TCPSocketFactory::TCPSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer) : + m_events(events), + m_socketMultiplexer(socketMultiplexer) +{ + // do nothing +} + +TCPSocketFactory::~TCPSocketFactory() +{ + // do nothing +} + +IDataSocket* +TCPSocketFactory::create(IArchNetwork::EAddressFamily family, bool secure) const +{ + if (secure) { + SecureSocket* secureSocket = new SecureSocket(m_events, m_socketMultiplexer, family); + secureSocket->initSsl (false); + return secureSocket; + } + else { + return new TCPSocket(m_events, m_socketMultiplexer, family); + } +} + +IListenSocket* +TCPSocketFactory::createListen(IArchNetwork::EAddressFamily family, bool secure) const +{ + IListenSocket* socket = NULL; + if (secure) { + socket = new SecureListenSocket(m_events, m_socketMultiplexer, family); + } + else { + socket = new TCPListenSocket(m_events, m_socketMultiplexer, family); + } + + return socket; +} diff --git a/src/lib/net/TCPSocketFactory.h b/src/lib/net/TCPSocketFactory.h new file mode 100644 index 0000000..0195ec4 --- /dev/null +++ b/src/lib/net/TCPSocketFactory.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 "net/ISocketFactory.h" +#include "arch/IArchNetwork.h" + +class IEventQueue; +class SocketMultiplexer; + +//! Socket factory for TCP sockets +class TCPSocketFactory : public ISocketFactory { +public: + TCPSocketFactory(IEventQueue* events, SocketMultiplexer* socketMultiplexer); + virtual ~TCPSocketFactory(); + + // ISocketFactory overrides + virtual IDataSocket* create( + IArchNetwork::EAddressFamily family, + bool secure) const; + virtual IListenSocket* createListen( + IArchNetwork::EAddressFamily family, + bool secure) const; + +private: + IEventQueue* m_events; + SocketMultiplexer* m_socketMultiplexer; +}; diff --git a/src/lib/net/TSocketMultiplexerMethodJob.h b/src/lib/net/TSocketMultiplexerMethodJob.h new file mode 100644 index 0000000..90efbe7 --- /dev/null +++ b/src/lib/net/TSocketMultiplexerMethodJob.h @@ -0,0 +1,109 @@ +/* + * 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 "net/ISocketMultiplexerJob.h" +#include "arch/Arch.h" + +//! Use a method as a socket multiplexer job +/*! +A socket multiplexer job class that invokes a member function. +*/ +template <class T> +class TSocketMultiplexerMethodJob : public ISocketMultiplexerJob { +public: + typedef ISocketMultiplexerJob* + (T::*Method)(ISocketMultiplexerJob*, bool, bool, bool); + + //! run() invokes \c object->method(arg) + TSocketMultiplexerMethodJob(T* object, Method method, + ArchSocket socket, bool readable, bool writeable); + virtual ~TSocketMultiplexerMethodJob(); + + // IJob overrides + virtual ISocketMultiplexerJob* + run(bool readable, bool writable, bool error); + virtual ArchSocket getSocket() const; + virtual bool isReadable() const; + virtual bool isWritable() const; + +private: + T* m_object; + Method m_method; + ArchSocket m_socket; + bool m_readable; + bool m_writable; + void* m_arg; +}; + +template <class T> +inline +TSocketMultiplexerMethodJob<T>::TSocketMultiplexerMethodJob(T* object, + Method method, ArchSocket socket, + bool readable, bool writable) : + m_object(object), + m_method(method), + m_socket(ARCH->copySocket(socket)), + m_readable(readable), + m_writable(writable) +{ + // do nothing +} + +template <class T> +inline +TSocketMultiplexerMethodJob<T>::~TSocketMultiplexerMethodJob() +{ + ARCH->closeSocket(m_socket); +} + +template <class T> +inline +ISocketMultiplexerJob* +TSocketMultiplexerMethodJob<T>::run(bool read, bool write, bool error) +{ + if (m_object != NULL) { + return (m_object->*m_method)(this, read, write, error); + } + return NULL; +} + +template <class T> +inline +ArchSocket +TSocketMultiplexerMethodJob<T>::getSocket() const +{ + return m_socket; +} + +template <class T> +inline +bool +TSocketMultiplexerMethodJob<T>::isReadable() const +{ + return m_readable; +} + +template <class T> +inline +bool +TSocketMultiplexerMethodJob<T>::isWritable() const +{ + return m_writable; +} diff --git a/src/lib/net/XSocket.cpp b/src/lib/net/XSocket.cpp new file mode 100644 index 0000000..13e0fc3 --- /dev/null +++ b/src/lib/net/XSocket.cpp @@ -0,0 +1,117 @@ +/* + * 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 "net/XSocket.h" +#include "base/String.h" + +// +// XSocketAddress +// + +XSocketAddress::XSocketAddress(EError error, + const String& hostname, int port) _NOEXCEPT : + m_error(error), + m_hostname(hostname), + m_port(port) +{ + // do nothing +} + +XSocketAddress::EError +XSocketAddress::getError() const throw() +{ + return m_error; +} + +String +XSocketAddress::getHostname() const throw() +{ + return m_hostname; +} + +int +XSocketAddress::getPort() const throw() +{ + return m_port; +} + +String +XSocketAddress::getWhat() const throw() +{ + static const char* s_errorID[] = { + "XSocketAddressUnknown", + "XSocketAddressNotFound", + "XSocketAddressNoAddress", + "XSocketAddressUnsupported", + "XSocketAddressBadPort" + }; + static const char* s_errorMsg[] = { + "unknown error for: %{1}:%{2}", + "address not found for: %{1}", + "no address for: %{1}", + "unsupported address for: %{1}", + "invalid port" // m_port may not be set to the bad port + }; + return format(s_errorID[m_error], s_errorMsg[m_error], + m_hostname.c_str(), + barrier::string::sprintf("%d", m_port).c_str()); +} + + +// +// XSocketIOClose +// + +String +XSocketIOClose::getWhat() const throw() +{ + return format("XSocketIOClose", "close: %{1}", what()); +} + + +// +// XSocketBind +// + +String +XSocketBind::getWhat() const throw() +{ + return format("XSocketBind", "cannot bind address: %{1}", what()); +} + + +// +// XSocketConnect +// + +String +XSocketConnect::getWhat() const throw() +{ + return format("XSocketConnect", "cannot connect socket: %{1}", what()); +} + + +// +// XSocketCreate +// + +String +XSocketCreate::getWhat() const throw() +{ + return format("XSocketCreate", "cannot create socket: %{1}", what()); +} diff --git a/src/lib/net/XSocket.h b/src/lib/net/XSocket.h new file mode 100644 index 0000000..be02b5b --- /dev/null +++ b/src/lib/net/XSocket.h @@ -0,0 +1,98 @@ +/* + * 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 "io/XIO.h" +#include "base/XBase.h" +#include "base/String.h" +#include "common/basic_types.h" + +//! Generic socket exception +XBASE_SUBCLASS(XSocket, XBase); + +//! Socket bad address exception +/*! +Thrown when attempting to create an invalid network address. +*/ +class XSocketAddress : public XSocket { +public: + //! Failure codes + enum EError { + kUnknown, //!< Unknown error + kNotFound, //!< The hostname is unknown + kNoAddress, //!< The hostname is valid but has no IP address + kUnsupported, //!< The hostname is valid but has no supported address + kBadPort //!< The port is invalid + }; + + XSocketAddress(EError, const String& hostname, int port) _NOEXCEPT; + virtual ~XSocketAddress() _NOEXCEPT { } + + //! @name accessors + //@{ + + //! Get the error code + EError getError() const throw(); + //! Get the hostname + String getHostname() const throw(); + //! Get the port + int getPort() const throw(); + + //@} + +protected: + // XBase overrides + virtual String getWhat() const throw(); + +private: + EError m_error; + String m_hostname; + int m_port; +}; + +//! I/O closing exception +/*! +Thrown if a stream cannot be closed. +*/ +XBASE_SUBCLASS_FORMAT(XSocketIOClose, XIOClose); + +//! Socket cannot bind address exception +/*! +Thrown when a socket cannot be bound to an address. +*/ +XBASE_SUBCLASS_FORMAT(XSocketBind, XSocket); + +//! Socket address in use exception +/*! +Thrown when a socket cannot be bound to an address because the address +is already in use. +*/ +XBASE_SUBCLASS(XSocketAddressInUse, XSocketBind); + +//! Cannot connect socket exception +/*! +Thrown when a socket cannot connect to a remote endpoint. +*/ +XBASE_SUBCLASS_FORMAT(XSocketConnect, XSocket); + +//! Cannot create socket exception +/*! +Thrown when a socket cannot be created (by the operating system). +*/ +XBASE_SUBCLASS_FORMAT(XSocketCreate, XSocket); diff --git a/src/lib/platform/CMakeLists.txt b/src/lib/platform/CMakeLists.txt new file mode 100644 index 0000000..a1718a1 --- /dev/null +++ b/src/lib/platform/CMakeLists.txt @@ -0,0 +1,49 @@ +# 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/>. + +if (WIN32) + file(GLOB headers "MSWindows*.h" "ImmuneKeysReader.h" "synwinhk.h") + file(GLOB sources "MSWindows*.cpp" "ImmuneKeysReader.cpp") +elseif (APPLE) + file(GLOB headers "OSX*.h" "IOSX*.h") + file(GLOB sources "OSX*.cpp" "IOSX*.cpp" "OSX*.m" "OSX*.mm") +elseif (UNIX) + file(GLOB headers "XWindows*.h") + file(GLOB sources "XWindows*.cpp") +endif() + +if (BARRIER_ADD_HEADERS) + list(APPEND sources ${headers}) +endif() + +if (APPLE) + list(APPEND inc + /System/Library/Frameworks + ) +endif() + +include_directories(${inc}) +add_library(platform STATIC ${sources}) +target_link_libraries(platform client ${libs}) + +if (UNIX) + target_link_libraries(platform io net ipc synlib client ${libs}) +endif() + +if (APPLE) + find_library(COCOA_LIBRARY Cocoa) + target_link_libraries(platform ${COCOA_LIBRARY}) +endif() diff --git a/src/lib/platform/IMSWindowsClipboardFacade.h b/src/lib/platform/IMSWindowsClipboardFacade.h new file mode 100644 index 0000000..03c6248 --- /dev/null +++ b/src/lib/platform/IMSWindowsClipboardFacade.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/>. + */ + +#ifndef IMWINDOWSCLIPBOARDFACADE +#define IMWINDOWSCLIPBOARDFACADE + +#include "common/IInterface.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class IMSWindowsClipboardConverter; + +class IMSWindowsClipboardFacade : public IInterface +{ +public: + virtual void write(HANDLE win32Data, UINT win32Format) = 0; + virtual ~IMSWindowsClipboardFacade() { } +}; + +#endif
\ No newline at end of file diff --git a/src/lib/platform/IOSXKeyResource.cpp b/src/lib/platform/IOSXKeyResource.cpp new file mode 100644 index 0000000..0c5abe7 --- /dev/null +++ b/src/lib/platform/IOSXKeyResource.cpp @@ -0,0 +1,189 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 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 "platform/IOSXKeyResource.h" + +#include <Carbon/Carbon.h> + +KeyID +IOSXKeyResource::getKeyID(UInt8 c) +{ + if (c == 0) { + return kKeyNone; + } + else if (c >= 32 && c < 127) { + // ASCII + return static_cast<KeyID>(c); + } + else { + // handle special keys + switch (c) { + case 0x01: + return kKeyHome; + + case 0x02: + return kKeyKP_Enter; + + case 0x03: + return kKeyKP_Enter; + + case 0x04: + return kKeyEnd; + + case 0x05: + return kKeyHelp; + + case 0x08: + return kKeyBackSpace; + + case 0x09: + return kKeyTab; + + case 0x0b: + return kKeyPageUp; + + case 0x0c: + return kKeyPageDown; + + case 0x0d: + return kKeyReturn; + + case 0x10: + // OS X maps all the function keys (F1, etc) to this one key. + // we can't determine the right key here so we have to do it + // some other way. + return kKeyNone; + + case 0x1b: + return kKeyEscape; + + case 0x1c: + return kKeyLeft; + + case 0x1d: + return kKeyRight; + + case 0x1e: + return kKeyUp; + + case 0x1f: + return kKeyDown; + + case 0x7f: + return kKeyDelete; + + case 0x06: + case 0x07: + case 0x0a: + case 0x0e: + case 0x0f: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + // discard other control characters + return kKeyNone; + + default: + // not special or unknown + break; + } + + // create string with character + char str[2]; + str[0] = static_cast<char>(c); + str[1] = 0; + + // get current keyboard script + TISInputSourceRef isref = TISCopyCurrentKeyboardInputSource(); + CFArrayRef langs = (CFArrayRef) TISGetInputSourceProperty(isref, kTISPropertyInputSourceLanguages); + CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding( + (CFStringRef)CFArrayGetValueAtIndex(langs, 0)); + // convert to unicode + CFStringRef cfString = + CFStringCreateWithCStringNoCopy( + kCFAllocatorDefault, str, encoding, kCFAllocatorNull); + + // sometimes CFStringCreate...() returns NULL (e.g. Apple Korean + // encoding with char value 214). if it did then make no key, + // otherwise CFStringCreateMutableCopy() will crash. + if (cfString == NULL) { + return kKeyNone; + } + + // convert to precomposed + CFMutableStringRef mcfString = + CFStringCreateMutableCopy(kCFAllocatorDefault, 0, cfString); + CFRelease(cfString); + CFStringNormalize(mcfString, kCFStringNormalizationFormC); + + // check result + int unicodeLength = CFStringGetLength(mcfString); + if (unicodeLength == 0) { + CFRelease(mcfString); + return kKeyNone; + } + if (unicodeLength > 1) { + // FIXME -- more than one character, we should handle this + CFRelease(mcfString); + return kKeyNone; + } + + // get unicode character + UniChar uc = CFStringGetCharacterAtIndex(mcfString, 0); + CFRelease(mcfString); + + // convert to KeyID + return static_cast<KeyID>(uc); + } +} + +KeyID +IOSXKeyResource::unicharToKeyID(UniChar c) +{ + switch (c) { + case 3: + return kKeyKP_Enter; + + case 8: + return kKeyBackSpace; + + case 9: + return kKeyTab; + + case 13: + return kKeyReturn; + + case 27: + return kKeyEscape; + + case 127: + return kKeyDelete; + + default: + if (c < 32) { + return kKeyNone; + } + return static_cast<KeyID>(c); + } +} diff --git a/src/lib/platform/IOSXKeyResource.h b/src/lib/platform/IOSXKeyResource.h new file mode 100644 index 0000000..fc190ef --- /dev/null +++ b/src/lib/platform/IOSXKeyResource.h @@ -0,0 +1,36 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 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 + +#include "barrier/KeyState.h" + +class IOSXKeyResource : public IInterface { +public: + virtual bool isValid() const = 0; + virtual UInt32 getNumModifierCombinations() const = 0; + virtual UInt32 getNumTables() const = 0; + virtual UInt32 getNumButtons() const = 0; + virtual UInt32 getTableForModifier(UInt32 mask) const = 0; + virtual KeyID getKey(UInt32 table, UInt32 button) const = 0; + + // Convert a character in the current script to the equivalent KeyID + static KeyID getKeyID(UInt8); + + // Convert a unicode character to the equivalent KeyID. + static KeyID unicharToKeyID(UniChar); +}; diff --git a/src/lib/platform/ImmuneKeysReader.cpp b/src/lib/platform/ImmuneKeysReader.cpp new file mode 100644 index 0000000..72baed3 --- /dev/null +++ b/src/lib/platform/ImmuneKeysReader.cpp @@ -0,0 +1,53 @@ +/* +* barrier -- mouse and keyboard sharing utility +* Copyright (C) 2018 Deuauche Open Source Group +* +* 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 "ImmuneKeysReader.h" + +#include <fstream> + +const std::size_t AllocatedLineSize = 1024; +const char CommentChar = '#'; + +static void add_key(const char * const buffer, std::vector<DWORD> &keys) +{ + const char *first; + // skip spaces. ignore blank lines and comment lines + for (first = buffer; *first == ' '; ++first); + if (*first != 0 && *first != CommentChar) + keys.emplace_back(std::stoul(first, 0, 0)); +} + +/*static*/ bool ImmuneKeysReader::get_list(const char * const path, std::vector<DWORD> &keys, std::string &badline) +{ + // default values for return params + keys.clear(); + badline.clear(); + std::ifstream stream(path, std::ios::in); + if (stream.is_open()) { + // size includes the null-terminator + char buffer[AllocatedLineSize]; + while (stream.getline(&buffer[0], AllocatedLineSize)) { + try { + add_key(buffer, keys); + } catch (...) { + badline = buffer; + return false; + } + } + } + return true; +}
\ No newline at end of file diff --git a/src/lib/platform/ImmuneKeysReader.h b/src/lib/platform/ImmuneKeysReader.h new file mode 100644 index 0000000..b46cbbe --- /dev/null +++ b/src/lib/platform/ImmuneKeysReader.h @@ -0,0 +1,34 @@ +/* +* barrier -- mouse and keyboard sharing utility +* Copyright (C) 2018 Deuauche Open Source Group +* +* 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 <vector> +#include <string> + +// let's not import all of Windows just to get this typedef +typedef unsigned long DWORD; + +class ImmuneKeysReader +{ +public: + static bool get_list(const char * const path, std::vector<DWORD> &keys, std::string &badLine); + +private: + // static class + explicit ImmuneKeysReader() {} +}; diff --git a/src/lib/platform/MSWindowsClipboard.cpp b/src/lib/platform/MSWindowsClipboard.cpp new file mode 100644 index 0000000..8ab50df --- /dev/null +++ b/src/lib/platform/MSWindowsClipboard.cpp @@ -0,0 +1,232 @@ +/* + * 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 "platform/MSWindowsClipboard.h" + +#include "platform/MSWindowsClipboardTextConverter.h" +#include "platform/MSWindowsClipboardUTF16Converter.h" +#include "platform/MSWindowsClipboardBitmapConverter.h" +#include "platform/MSWindowsClipboardHTMLConverter.h" +#include "platform/MSWindowsClipboardFacade.h" +#include "arch/win32/ArchMiscWindows.h" +#include "base/Log.h" + +// +// MSWindowsClipboard +// + +UINT MSWindowsClipboard::s_ownershipFormat = 0; + +MSWindowsClipboard::MSWindowsClipboard(HWND window) : + m_window(window), + m_time(0), + m_facade(new MSWindowsClipboardFacade()), + m_deleteFacade(true) +{ + // add converters, most desired first + m_converters.push_back(new MSWindowsClipboardUTF16Converter); + m_converters.push_back(new MSWindowsClipboardBitmapConverter); + m_converters.push_back(new MSWindowsClipboardHTMLConverter); +} + +MSWindowsClipboard::~MSWindowsClipboard() +{ + clearConverters(); + + // dependency injection causes confusion over ownership, so we need + // logic to decide whether or not we delete the facade. there must + // be a more elegant way of doing this. + if (m_deleteFacade) + delete m_facade; +} + +void +MSWindowsClipboard::setFacade(IMSWindowsClipboardFacade& facade) +{ + delete m_facade; + m_facade = &facade; + m_deleteFacade = false; +} + +bool +MSWindowsClipboard::emptyUnowned() +{ + LOG((CLOG_DEBUG "empty clipboard")); + + // empty the clipboard (and take ownership) + if (!EmptyClipboard()) { + // unable to cause this in integ tests, but this error has never + // actually been reported by users. + LOG((CLOG_DEBUG "failed to grab clipboard")); + return false; + } + + return true; +} + +bool +MSWindowsClipboard::empty() +{ + if (!emptyUnowned()) { + return false; + } + + // mark clipboard as being owned by barrier + HGLOBAL data = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, 1); + if (NULL == SetClipboardData(getOwnershipFormat(), data)) { + LOG((CLOG_DEBUG "failed to set clipboard data")); + GlobalFree(data); + return false; + } + + return true; +} + +void +MSWindowsClipboard::add(EFormat format, const String& data) +{ + LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format)); + + // convert data to win32 form + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IMSWindowsClipboardConverter* converter = *index; + + // skip converters for other formats + if (converter->getFormat() == format) { + HANDLE win32Data = converter->fromIClipboard(data); + if (win32Data != NULL) { + UINT win32Format = converter->getWin32Format(); + m_facade->write(win32Data, win32Format); + } + } + } +} + +bool +MSWindowsClipboard::open(Time time) const +{ + LOG((CLOG_DEBUG "open clipboard")); + + if (!OpenClipboard(m_window)) { + // unable to cause this in integ tests; but this can happen! + // * http://symless.com/pm/issues/86 + // * http://symless.com/pm/issues/1256 + // logging improved to see if we can catch more info next time. + LOG((CLOG_WARN "failed to open clipboard: %d", GetLastError())); + return false; + } + + m_time = time; + + return true; +} + +void +MSWindowsClipboard::close() const +{ + LOG((CLOG_DEBUG "close clipboard")); + CloseClipboard(); +} + +IClipboard::Time +MSWindowsClipboard::getTime() const +{ + return m_time; +} + +bool +MSWindowsClipboard::has(EFormat format) const +{ + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IMSWindowsClipboardConverter* converter = *index; + if (converter->getFormat() == format) { + if (IsClipboardFormatAvailable(converter->getWin32Format())) { + return true; + } + } + } + return false; +} + +String +MSWindowsClipboard::get(EFormat format) const +{ + // find the converter for the first clipboard format we can handle + IMSWindowsClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + + converter = *index; + if (converter->getFormat() == format) { + break; + } + converter = NULL; + } + + // if no converter then we don't recognize any formats + if (converter == NULL) { + LOG((CLOG_WARN "no converter for format %d", format)); + return String(); + } + + // get a handle to the clipboard data + HANDLE win32Data = GetClipboardData(converter->getWin32Format()); + if (win32Data == NULL) { + // nb: can't cause this using integ tests; this is only caused when + // the selected converter returns an invalid format -- which you + // cannot cause using public functions. + return String(); + } + + // convert + return converter->toIClipboard(win32Data); +} + +void +MSWindowsClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +bool +MSWindowsClipboard::isOwnedByBarrier() +{ + // create ownership format if we haven't yet + if (s_ownershipFormat == 0) { + s_ownershipFormat = RegisterClipboardFormat(TEXT("BarrierOwnership")); + } + return (IsClipboardFormatAvailable(getOwnershipFormat()) != 0); +} + +UINT +MSWindowsClipboard::getOwnershipFormat() +{ + // create ownership format if we haven't yet + if (s_ownershipFormat == 0) { + s_ownershipFormat = RegisterClipboardFormat(TEXT("BarrierOwnership")); + } + + // return the format + return s_ownershipFormat; +} diff --git a/src/lib/platform/MSWindowsClipboard.h b/src/lib/platform/MSWindowsClipboard.h new file mode 100644 index 0000000..3e92a39 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboard.h @@ -0,0 +1,113 @@ +/* + * 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 "platform/MSWindowsClipboardFacade.h" +#include "barrier/IClipboard.h" +#include "common/stdvector.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class IMSWindowsClipboardConverter; +class IMSWindowsClipboardFacade; + +//! Microsoft windows clipboard implementation +class MSWindowsClipboard : public IClipboard { +public: + MSWindowsClipboard(HWND window); + MSWindowsClipboard(HWND window, IMSWindowsClipboardFacade &facade); + virtual ~MSWindowsClipboard(); + + //! Empty clipboard without ownership + /*! + Take ownership of the clipboard and clear all data from it. + This must be called between a successful open() and close(). + Return false if the clipboard ownership could not be taken; + the clipboard should not be emptied in this case. Unlike + empty(), isOwnedByBarrier() will return false when emptied + this way. This is useful when barrier wants to put data on + clipboard but pretend (to itself) that some other app did it. + When using empty(), barrier assumes the data came from the + server and doesn't need to be sent back. emptyUnowned() + makes barrier send the data to the server. + */ + bool emptyUnowned(); + + //! Test if clipboard is owned by barrier + static bool isOwnedByBarrier(); + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const String& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual String get(EFormat) const; + + void setFacade(IMSWindowsClipboardFacade& facade); + +private: + void clearConverters(); + + UINT convertFormatToWin32(EFormat) const; + HANDLE convertTextToWin32(const String& data) const; + String convertTextFromWin32(HANDLE) const; + + static UINT getOwnershipFormat(); + +private: + typedef std::vector<IMSWindowsClipboardConverter*> ConverterList; + + HWND m_window; + mutable Time m_time; + ConverterList m_converters; + static UINT s_ownershipFormat; + IMSWindowsClipboardFacade* m_facade; + bool m_deleteFacade; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all win32 clipboard format +converters. +*/ +class IMSWindowsClipboardConverter : public IInterface { +public: + // accessors + + // return the clipboard format this object converts from/to + virtual IClipboard::EFormat + getFormat() const = 0; + + // return the atom representing the win32 clipboard format that + // this object converts from/to + virtual UINT getWin32Format() const = 0; + + // convert from the IClipboard format to the win32 clipboard format. + // the input data must be in the IClipboard format returned by + // getFormat(). the return data will be in the win32 clipboard + // format returned by getWin32Format(), allocated by GlobalAlloc(). + virtual HANDLE fromIClipboard(const String&) const = 0; + + // convert from the win32 clipboard format to the IClipboard format + // (i.e., the reverse of fromIClipboard()). + virtual String toIClipboard(HANDLE data) const = 0; +}; diff --git a/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp b/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp new file mode 100644 index 0000000..decbad6 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardAnyTextConverter.cpp @@ -0,0 +1,149 @@ +/* + * 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 "platform/MSWindowsClipboardAnyTextConverter.h" + +// +// MSWindowsClipboardAnyTextConverter +// + +MSWindowsClipboardAnyTextConverter::MSWindowsClipboardAnyTextConverter() +{ + // do nothing +} + +MSWindowsClipboardAnyTextConverter::~MSWindowsClipboardAnyTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +MSWindowsClipboardAnyTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +HANDLE +MSWindowsClipboardAnyTextConverter::fromIClipboard(const String& data) const +{ + // convert linefeeds and then convert to desired encoding + String text = doFromIClipboard(convertLinefeedToWin32(data)); + UInt32 size = (UInt32)text.size(); + + // copy to memory handle + HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size); + if (gData != NULL) { + // get a pointer to the allocated memory + char* dst = (char*)GlobalLock(gData); + if (dst != NULL) { + memcpy(dst, text.data(), size); + GlobalUnlock(gData); + } + else { + GlobalFree(gData); + gData = NULL; + } + } + + return gData; +} + +String +MSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const +{ + // get datator + const char* src = (const char*)GlobalLock(data); + UInt32 srcSize = (UInt32)GlobalSize(data); + if (src == NULL || srcSize <= 1) { + return String(); + } + + // convert text + String text = doToIClipboard(String(src, srcSize)); + + // release handle + GlobalUnlock(data); + + // convert newlines + return convertLinefeedToUnix(text); +} + +String +MSWindowsClipboardAnyTextConverter::convertLinefeedToWin32( + const String& src) const +{ + // note -- we assume src is a valid UTF-8 string + + // count newlines in string + UInt32 numNewlines = 0; + UInt32 n = (UInt32)src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (*scan == '\n') { + ++numNewlines; + } + } + if (numNewlines == 0) { + return src; + } + + // allocate new string + String dst; + dst.reserve(src.size() + numNewlines); + + // copy string, converting newlines + n = (UInt32)src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] == '\n') { + dst += '\r'; + } + dst += scan[0]; + } + + return dst; +} + +String +MSWindowsClipboardAnyTextConverter::convertLinefeedToUnix( + const String& src) const +{ + // count newlines in string + UInt32 numNewlines = 0; + UInt32 n = (UInt32)src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] == '\r' && scan[1] == '\n') { + ++numNewlines; + } + } + if (numNewlines == 0) { + return src; + } + + // allocate new string + String dst; + dst.reserve(src.size()); + + // copy string, converting newlines + n = (UInt32)src.size(); + for (const char* scan = src.c_str(); n > 0; ++scan, --n) { + if (scan[0] != '\r' || scan[1] != '\n') { + dst += scan[0]; + } + } + + return dst; +} diff --git a/src/lib/platform/MSWindowsClipboardAnyTextConverter.h b/src/lib/platform/MSWindowsClipboardAnyTextConverter.h new file mode 100644 index 0000000..cabdb0b --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardAnyTextConverter.h @@ -0,0 +1,57 @@ +/* + * 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 "platform/MSWindowsClipboard.h" + +//! Convert to/from some text encoding +class MSWindowsClipboardAnyTextConverter : + public IMSWindowsClipboardConverter { +public: + MSWindowsClipboardAnyTextConverter(); + virtual ~MSWindowsClipboardAnyTextConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const = 0; + virtual HANDLE fromIClipboard(const String&) const; + virtual String toIClipboard(HANDLE) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion only. Memory handle allocation and + linefeed conversion is done by this class. doFromIClipboard() + must include the nul terminator in the returned string (not + including the String's nul terminator). + */ + virtual String doFromIClipboard(const String&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion only. Memory handle allocation and + linefeed conversion is done by this class. + */ + virtual String doToIClipboard(const String&) const = 0; + +private: + String convertLinefeedToWin32(const String&) const; + String convertLinefeedToUnix(const String&) const; +}; diff --git a/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp new file mode 100644 index 0000000..16bd4bf --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardBitmapConverter.cpp @@ -0,0 +1,152 @@ +/* + * 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 "platform/MSWindowsClipboardBitmapConverter.h" + +#include "base/Log.h" + +// +// MSWindowsClipboardBitmapConverter +// + +MSWindowsClipboardBitmapConverter::MSWindowsClipboardBitmapConverter() +{ + // do nothing +} + +MSWindowsClipboardBitmapConverter::~MSWindowsClipboardBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +MSWindowsClipboardBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +UINT +MSWindowsClipboardBitmapConverter::getWin32Format() const +{ + return CF_DIB; +} + +HANDLE +MSWindowsClipboardBitmapConverter::fromIClipboard(const String& data) const +{ + // copy to memory handle + HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, data.size()); + if (gData != NULL) { + // get a pointer to the allocated memory + char* dst = (char*)GlobalLock(gData); + if (dst != NULL) { + memcpy(dst, data.data(), data.size()); + GlobalUnlock(gData); + } + else { + GlobalFree(gData); + gData = NULL; + } + } + + return gData; +} + +String +MSWindowsClipboardBitmapConverter::toIClipboard(HANDLE data) const +{ + // get datator + LPVOID src = GlobalLock(data); + if (src == NULL) { + return String(); + } + UInt32 srcSize = (UInt32)GlobalSize(data); + + // check image type + const BITMAPINFO* bitmap = static_cast<const BITMAPINFO*>(src); + LOG((CLOG_INFO "bitmap: %dx%d %d", bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, (int)bitmap->bmiHeader.biBitCount)); + if (bitmap->bmiHeader.biPlanes == 1 && + (bitmap->bmiHeader.biBitCount == 24 || + bitmap->bmiHeader.biBitCount == 32) && + bitmap->bmiHeader.biCompression == BI_RGB) { + // already in canonical form + String image(static_cast<char const*>(src), srcSize); + GlobalUnlock(data); + return image; + } + + // create a destination DIB section + LOG((CLOG_INFO "convert image from: depth=%d comp=%d", bitmap->bmiHeader.biBitCount, bitmap->bmiHeader.biCompression)); + void* raw; + BITMAPINFOHEADER info; + LONG w = bitmap->bmiHeader.biWidth; + LONG h = bitmap->bmiHeader.biHeight; + info.biSize = sizeof(BITMAPINFOHEADER); + info.biWidth = w; + info.biHeight = h; + info.biPlanes = 1; + info.biBitCount = 32; + info.biCompression = BI_RGB; + info.biSizeImage = 0; + info.biXPelsPerMeter = 1000; + info.biYPelsPerMeter = 1000; + info.biClrUsed = 0; + info.biClrImportant = 0; + HDC dc = GetDC(NULL); + HBITMAP dst = CreateDIBSection(dc, (BITMAPINFO*)&info, + DIB_RGB_COLORS, &raw, NULL, 0); + + // find the start of the pixel data + const char* srcBits = (const char*)bitmap + bitmap->bmiHeader.biSize; + if (bitmap->bmiHeader.biBitCount >= 16) { + if (bitmap->bmiHeader.biCompression == BI_BITFIELDS && + (bitmap->bmiHeader.biBitCount == 16 || + bitmap->bmiHeader.biBitCount == 32)) { + srcBits += 3 * sizeof(DWORD); + } + } + else if (bitmap->bmiHeader.biClrUsed != 0) { + srcBits += bitmap->bmiHeader.biClrUsed * sizeof(RGBQUAD); + } + else { + //http://msdn.microsoft.com/en-us/library/ke55d167(VS.80).aspx + srcBits += (1i64 << bitmap->bmiHeader.biBitCount) * sizeof(RGBQUAD); + } + + // copy source image to destination image + HDC dstDC = CreateCompatibleDC(dc); + HGDIOBJ oldBitmap = SelectObject(dstDC, dst); + SetDIBitsToDevice(dstDC, 0, 0, w, h, 0, 0, 0, h, + srcBits, bitmap, DIB_RGB_COLORS); + SelectObject(dstDC, oldBitmap); + DeleteDC(dstDC); + GdiFlush(); + + // extract data + String image((const char*)&info, info.biSize); + image.append((const char*)raw, 4 * w * h); + + // clean up GDI + DeleteObject(dst); + ReleaseDC(NULL, dc); + + // release handle + GlobalUnlock(data); + + return image; +} diff --git a/src/lib/platform/MSWindowsClipboardBitmapConverter.h b/src/lib/platform/MSWindowsClipboardBitmapConverter.h new file mode 100644 index 0000000..52b5547 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardBitmapConverter.h @@ -0,0 +1,36 @@ +/* + * 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 "platform/MSWindowsClipboard.h" + +//! Convert to/from some text encoding +class MSWindowsClipboardBitmapConverter : + public IMSWindowsClipboardConverter { +public: + MSWindowsClipboardBitmapConverter(); + virtual ~MSWindowsClipboardBitmapConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const; + virtual HANDLE fromIClipboard(const String&) const; + virtual String toIClipboard(HANDLE) const; +}; diff --git a/src/lib/platform/MSWindowsClipboardFacade.cpp b/src/lib/platform/MSWindowsClipboardFacade.cpp new file mode 100644 index 0000000..3b6478f --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardFacade.cpp @@ -0,0 +1,31 @@ +/* + * 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 "platform/MSWindowsClipboardFacade.h" + +#include "platform/MSWindowsClipboard.h" + +void MSWindowsClipboardFacade::write(HANDLE win32Data, UINT win32Format) +{ + if (SetClipboardData(win32Format, win32Data) == NULL) { + // free converted data if we couldn't put it on + // the clipboard. + // nb: couldn't cause this in integ tests. + GlobalFree(win32Data); + } +} diff --git a/src/lib/platform/MSWindowsClipboardFacade.h b/src/lib/platform/MSWindowsClipboardFacade.h new file mode 100644 index 0000000..a95e835 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardFacade.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 "platform/IMSWindowsClipboardFacade.h" + +#include "barrier/IClipboard.h" + +class MSWindowsClipboardFacade : public IMSWindowsClipboardFacade +{ +public: + virtual void write(HANDLE win32Data, UINT win32Format); +}; diff --git a/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp b/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp new file mode 100644 index 0000000..347a224 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardHTMLConverter.cpp @@ -0,0 +1,120 @@ +/* + * 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 "platform/MSWindowsClipboardHTMLConverter.h" + +#include "base/String.h" + +// +// MSWindowsClipboardHTMLConverter +// + +MSWindowsClipboardHTMLConverter::MSWindowsClipboardHTMLConverter() +{ + m_format = RegisterClipboardFormat("HTML Format"); +} + +MSWindowsClipboardHTMLConverter::~MSWindowsClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +MSWindowsClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +UINT +MSWindowsClipboardHTMLConverter::getWin32Format() const +{ + return m_format; +} + +String +MSWindowsClipboardHTMLConverter::doFromIClipboard(const String& data) const +{ + // prepare to CF_HTML format prefix and suffix + String prefix("Version:0.9\r\nStartHTML:0000000105\r\n" + "EndHTML:ZZZZZZZZZZ\r\n" + "StartFragment:XXXXXXXXXX\r\nEndFragment:YYYYYYYYYY\r\n" + "<!DOCTYPE><HTML><BODY><!--StartFragment-->"); + String suffix("<!--EndFragment--></BODY></HTML>\r\n"); + + // Get byte offsets for header + UInt32 StartFragment = (UInt32)prefix.size(); + UInt32 EndFragment = StartFragment + (UInt32)data.size(); + // StartHTML is constant by the design of the prefix + UInt32 EndHTML = EndFragment + (UInt32)suffix.size(); + + prefix.replace(prefix.find("XXXXXXXXXX"), 10, + barrier::string::sprintf("%010u", StartFragment)); + prefix.replace(prefix.find("YYYYYYYYYY"), 10, + barrier::string::sprintf("%010u", EndFragment)); + prefix.replace(prefix.find("ZZZZZZZZZZ"), 10, + barrier::string::sprintf("%010u", EndHTML)); + + // concatenate + prefix += data; + prefix += suffix; + return prefix; +} + +String +MSWindowsClipboardHTMLConverter::doToIClipboard(const String& data) const +{ + // get fragment start/end args + String startArg = findArg(data, "StartFragment"); + String endArg = findArg(data, "EndFragment"); + if (startArg.empty() || endArg.empty()) { + return String(); + } + + // convert args to integers + SInt32 start = (SInt32)atoi(startArg.c_str()); + SInt32 end = (SInt32)atoi(endArg.c_str()); + if (start <= 0 || end <= 0 || start >= end) { + return String(); + } + + // extract the fragment + return data.substr(start, end - start); +} + +String +MSWindowsClipboardHTMLConverter::findArg( + const String& data, const String& name) const +{ + String::size_type i = data.find(name); + if (i == String::npos) { + return String(); + } + i = data.find_first_of(":\r\n", i); + if (i == String::npos || data[i] != ':') { + return String(); + } + i = data.find_first_of("0123456789\r\n", i + 1); + if (i == String::npos || data[i] == '\r' || data[i] == '\n') { + return String(); + } + String::size_type j = data.find_first_not_of("0123456789", i); + if (j == String::npos) { + j = data.size(); + } + return data.substr(i, j - i); +} diff --git a/src/lib/platform/MSWindowsClipboardHTMLConverter.h b/src/lib/platform/MSWindowsClipboardHTMLConverter.h new file mode 100644 index 0000000..66c8045 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardHTMLConverter.h @@ -0,0 +1,45 @@ +/* + * 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 "platform/MSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from HTML encoding +class MSWindowsClipboardHTMLConverter : + public MSWindowsClipboardAnyTextConverter { +public: + MSWindowsClipboardHTMLConverter(); + virtual ~MSWindowsClipboardHTMLConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual UINT getWin32Format() const; + +protected: + // MSWindowsClipboardAnyTextConverter overrides + virtual String doFromIClipboard(const String&) const; + virtual String doToIClipboard(const String&) const; + +private: + String findArg(const String& data, const String& name) const; + +private: + UINT m_format; +}; diff --git a/src/lib/platform/MSWindowsClipboardTextConverter.cpp b/src/lib/platform/MSWindowsClipboardTextConverter.cpp new file mode 100644 index 0000000..360c72c --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardTextConverter.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 "platform/MSWindowsClipboardTextConverter.h" + +#include "base/Unicode.h" + +// +// MSWindowsClipboardTextConverter +// + +MSWindowsClipboardTextConverter::MSWindowsClipboardTextConverter() +{ + // do nothing +} + +MSWindowsClipboardTextConverter::~MSWindowsClipboardTextConverter() +{ + // do nothing +} + +UINT +MSWindowsClipboardTextConverter::getWin32Format() const +{ + return CF_TEXT; +} + +String +MSWindowsClipboardTextConverter::doFromIClipboard(const String& data) const +{ + // convert and add nul terminator + return Unicode::UTF8ToText(data) += '\0'; +} + +String +MSWindowsClipboardTextConverter::doToIClipboard(const String& data) const +{ + // convert and truncate at first nul terminator + String dst = Unicode::textToUTF8(data); + String::size_type n = dst.find('\0'); + if (n != String::npos) { + dst.erase(n); + } + return dst; +} diff --git a/src/lib/platform/MSWindowsClipboardTextConverter.h b/src/lib/platform/MSWindowsClipboardTextConverter.h new file mode 100644 index 0000000..fb081c3 --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardTextConverter.h @@ -0,0 +1,37 @@ +/* + * 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 "platform/MSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from locale text encoding +class MSWindowsClipboardTextConverter : + public MSWindowsClipboardAnyTextConverter { +public: + MSWindowsClipboardTextConverter(); + virtual ~MSWindowsClipboardTextConverter(); + + // IMSWindowsClipboardConverter overrides + virtual UINT getWin32Format() const; + +protected: + // MSWindowsClipboardAnyTextConverter overrides + virtual String doFromIClipboard(const String&) const; + virtual String doToIClipboard(const String&) const; +}; diff --git a/src/lib/platform/MSWindowsClipboardUTF16Converter.cpp b/src/lib/platform/MSWindowsClipboardUTF16Converter.cpp new file mode 100644 index 0000000..0f8642a --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardUTF16Converter.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 "platform/MSWindowsClipboardUTF16Converter.h" + +#include "base/Unicode.h" + +// +// MSWindowsClipboardUTF16Converter +// + +MSWindowsClipboardUTF16Converter::MSWindowsClipboardUTF16Converter() +{ + // do nothing +} + +MSWindowsClipboardUTF16Converter::~MSWindowsClipboardUTF16Converter() +{ + // do nothing +} + +UINT +MSWindowsClipboardUTF16Converter::getWin32Format() const +{ + return CF_UNICODETEXT; +} + +String +MSWindowsClipboardUTF16Converter::doFromIClipboard(const String& data) const +{ + // convert and add nul terminator + return Unicode::UTF8ToUTF16(data).append(sizeof(wchar_t), 0); +} + +String +MSWindowsClipboardUTF16Converter::doToIClipboard(const String& data) const +{ + // convert and strip nul terminator + String dst = Unicode::UTF16ToUTF8(data); + String::size_type n = dst.find('\0'); + if (n != String::npos) { + dst.erase(n); + } + return dst; +} diff --git a/src/lib/platform/MSWindowsClipboardUTF16Converter.h b/src/lib/platform/MSWindowsClipboardUTF16Converter.h new file mode 100644 index 0000000..e7222bc --- /dev/null +++ b/src/lib/platform/MSWindowsClipboardUTF16Converter.h @@ -0,0 +1,37 @@ +/* + * 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 "platform/MSWindowsClipboardAnyTextConverter.h" + +//! Convert to/from UTF-16 encoding +class MSWindowsClipboardUTF16Converter : + public MSWindowsClipboardAnyTextConverter { +public: + MSWindowsClipboardUTF16Converter(); + virtual ~MSWindowsClipboardUTF16Converter(); + + // IMSWindowsClipboardConverter overrides + virtual UINT getWin32Format() const; + +protected: + // MSWindowsClipboardAnyTextConverter overrides + virtual String doFromIClipboard(const String&) const; + virtual String doToIClipboard(const String&) const; +}; diff --git a/src/lib/platform/MSWindowsDebugOutputter.cpp b/src/lib/platform/MSWindowsDebugOutputter.cpp new file mode 100644 index 0000000..43c38ad --- /dev/null +++ b/src/lib/platform/MSWindowsDebugOutputter.cpp @@ -0,0 +1,58 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#include "platform/MSWindowsDebugOutputter.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <string> + +MSWindowsDebugOutputter::MSWindowsDebugOutputter() +{ +} + +MSWindowsDebugOutputter::~MSWindowsDebugOutputter() +{ +} + +void +MSWindowsDebugOutputter::open(const char* title) +{ +} + +void +MSWindowsDebugOutputter::close() +{ +} + +void +MSWindowsDebugOutputter::show(bool showIfEmpty) +{ +} + +bool +MSWindowsDebugOutputter::write(ELevel level, const char* msg) +{ + OutputDebugString((std::string(msg) + "\n").c_str()); + return true; +} + +void +MSWindowsDebugOutputter::flush() +{ +} diff --git a/src/lib/platform/MSWindowsDebugOutputter.h b/src/lib/platform/MSWindowsDebugOutputter.h new file mode 100644 index 0000000..01fd97e --- /dev/null +++ b/src/lib/platform/MSWindowsDebugOutputter.h @@ -0,0 +1,39 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2012 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/>. + */ + +#pragma once + +#include "base/ILogOutputter.h" + +//! Write log to debugger +/*! +This outputter writes output to the debugger. In Visual Studio, this +can be seen in the Output window. +*/ +class MSWindowsDebugOutputter : public ILogOutputter { +public: + MSWindowsDebugOutputter(); + virtual ~MSWindowsDebugOutputter(); + + // ILogOutputter overrides + virtual void open(const char* title); + virtual void close(); + virtual void show(bool showIfEmpty); + virtual bool write(ELevel level, const char* message); + virtual void flush(); +}; diff --git a/src/lib/platform/MSWindowsDesks.cpp b/src/lib/platform/MSWindowsDesks.cpp new file mode 100644 index 0000000..b43a218 --- /dev/null +++ b/src/lib/platform/MSWindowsDesks.cpp @@ -0,0 +1,923 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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 "platform/MSWindowsDesks.h" + +#include "platform/MSWindowsScreen.h" +#include "barrier/IScreenSaver.h" +#include "barrier/XScreen.h" +#include "mt/Lock.h" +#include "mt/Thread.h" +#include "arch/win32/ArchMiscWindows.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/IJob.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" +#include "base/IEventQueue.h" + +#include <malloc.h> + +// these are only defined when WINVER >= 0x0500 +#if !defined(SPI_GETMOUSESPEED) +#define SPI_GETMOUSESPEED 112 +#endif +#if !defined(SPI_SETMOUSESPEED) +#define SPI_SETMOUSESPEED 113 +#endif +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// <unused>; <unused> +#define BARRIER_MSG_SWITCH BARRIER_HOOK_LAST_MSG + 1 +// <unused>; <unused> +#define BARRIER_MSG_ENTER BARRIER_HOOK_LAST_MSG + 2 +// <unused>; <unused> +#define BARRIER_MSG_LEAVE BARRIER_HOOK_LAST_MSG + 3 +// wParam = flags, HIBYTE(lParam) = virtual key, LOBYTE(lParam) = scan code +#define BARRIER_MSG_FAKE_KEY BARRIER_HOOK_LAST_MSG + 4 + // flags, XBUTTON id +#define BARRIER_MSG_FAKE_BUTTON BARRIER_HOOK_LAST_MSG + 5 +// x; y +#define BARRIER_MSG_FAKE_MOVE BARRIER_HOOK_LAST_MSG + 6 +// xDelta; yDelta +#define BARRIER_MSG_FAKE_WHEEL BARRIER_HOOK_LAST_MSG + 7 +// POINT*; <unused> +#define BARRIER_MSG_CURSOR_POS BARRIER_HOOK_LAST_MSG + 8 +// IKeyState*; <unused> +#define BARRIER_MSG_SYNC_KEYS BARRIER_HOOK_LAST_MSG + 9 +// install; <unused> +#define BARRIER_MSG_SCREENSAVER BARRIER_HOOK_LAST_MSG + 10 +// dx; dy +#define BARRIER_MSG_FAKE_REL_MOVE BARRIER_HOOK_LAST_MSG + 11 +// enable; <unused> +#define BARRIER_MSG_FAKE_INPUT BARRIER_HOOK_LAST_MSG + 12 + +// +// MSWindowsDesks +// + +MSWindowsDesks::MSWindowsDesks( + bool isPrimary, bool noHooks, + const IScreenSaver* screensaver, IEventQueue* events, + IJob* updateKeys, bool stopOnDeskSwitch) : + m_isPrimary(isPrimary), + m_noHooks(noHooks), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_timer(NULL), + m_screensaver(screensaver), + m_screensaverNotify(false), + m_activeDesk(NULL), + m_activeDeskName(), + m_mutex(), + m_deskReady(&m_mutex, false), + m_updateKeys(updateKeys), + m_events(events), + m_stopOnDeskSwitch(stopOnDeskSwitch) +{ + m_cursor = createBlankCursor(); + m_deskClass = createDeskWindowClass(m_isPrimary); + m_keyLayout = GetKeyboardLayout(GetCurrentThreadId()); + resetOptions(); +} + +MSWindowsDesks::~MSWindowsDesks() +{ + disable(); + destroyClass(m_deskClass); + destroyCursor(m_cursor); + delete m_updateKeys; +} + +void +MSWindowsDesks::enable() +{ + m_threadID = GetCurrentThreadId(); + + // set the active desk and (re)install the hooks + checkDesk(); + + // install the desk timer. this timer periodically checks + // which desk is active and reinstalls the hooks as necessary. + // we wouldn't need this if windows notified us of a desktop + // change but as far as i can tell it doesn't. + m_timer = m_events->newTimer(0.2, NULL); + m_events->adoptHandler(Event::kTimer, m_timer, + new TMethodEventJob<MSWindowsDesks>( + this, &MSWindowsDesks::handleCheckDesk)); + + updateKeys(); +} + +void +MSWindowsDesks::disable() +{ + // remove timer + if (m_timer != NULL) { + m_events->removeHandler(Event::kTimer, m_timer); + m_events->deleteTimer(m_timer); + m_timer = NULL; + } + + // destroy desks + removeDesks(); + + m_isOnScreen = m_isPrimary; +} + +void +MSWindowsDesks::enter() +{ + sendMessage(BARRIER_MSG_ENTER, 0, 0); +} + +void +MSWindowsDesks::leave(HKL keyLayout) +{ + sendMessage(BARRIER_MSG_LEAVE, (WPARAM)keyLayout, 0); +} + +void +MSWindowsDesks::resetOptions() +{ + m_leaveForegroundOption = false; +} + +void +MSWindowsDesks::setOptions(const OptionsList& options) +{ + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + if (options[i] == kOptionWin32KeepForeground) { + m_leaveForegroundOption = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "%s the foreground window", m_leaveForegroundOption ? "don\'t grab" : "grab")); + } + } +} + +void +MSWindowsDesks::updateKeys() +{ + sendMessage(BARRIER_MSG_SYNC_KEYS, 0, 0); +} + +void +MSWindowsDesks::setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon) +{ + m_x = x; + m_y = y; + m_w = width; + m_h = height; + m_xCenter = xCenter; + m_yCenter = yCenter; + m_multimon = isMultimon; +} + +void +MSWindowsDesks::installScreensaverHooks(bool install) +{ + if (m_isPrimary && m_screensaverNotify != install) { + m_screensaverNotify = install; + sendMessage(BARRIER_MSG_SCREENSAVER, install, 0); + } +} + +void +MSWindowsDesks::fakeInputBegin() +{ + sendMessage(BARRIER_MSG_FAKE_INPUT, 1, 0); +} + +void +MSWindowsDesks::fakeInputEnd() +{ + sendMessage(BARRIER_MSG_FAKE_INPUT, 0, 0); +} + +void +MSWindowsDesks::getCursorPos(SInt32& x, SInt32& y) const +{ + POINT pos; + sendMessage(BARRIER_MSG_CURSOR_POS, reinterpret_cast<WPARAM>(&pos), 0); + x = pos.x; + y = pos.y; +} + +void +MSWindowsDesks::fakeKeyEvent( + KeyButton button, UINT virtualKey, + bool press, bool /*isAutoRepeat*/) const +{ + // synthesize event + DWORD flags = 0; + if (((button & 0x100u) != 0)) { + flags |= KEYEVENTF_EXTENDEDKEY; + } + if (!press) { + flags |= KEYEVENTF_KEYUP; + } + sendMessage(BARRIER_MSG_FAKE_KEY, flags, + MAKEWORD(static_cast<BYTE>(button & 0xffu), + static_cast<BYTE>(virtualKey & 0xffu))); +} + +void +MSWindowsDesks::fakeMouseButton(ButtonID button, bool press) +{ + // the system will swap the meaning of left/right for us if + // the user has configured a left-handed mouse but we don't + // want it to swap since we want the handedness of the + // server's mouse. so pre-swap for a left-handed mouse. + if (GetSystemMetrics(SM_SWAPBUTTON)) { + switch (button) { + case kButtonLeft: + button = kButtonRight; + break; + + case kButtonRight: + button = kButtonLeft; + break; + } + } + + // map button id to button flag and button data + DWORD data = 0; + DWORD flags; + switch (button) { + case kButtonLeft: + flags = press ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; + break; + + case kButtonMiddle: + flags = press ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; + break; + + case kButtonRight: + flags = press ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; + break; + + case kButtonExtra0 + 0: + data = XBUTTON1; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + case kButtonExtra0 + 1: + data = XBUTTON2; + flags = press ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP; + break; + + default: + return; + } + + // do it + sendMessage(BARRIER_MSG_FAKE_BUTTON, flags, data); +} + +void +MSWindowsDesks::fakeMouseMove(SInt32 x, SInt32 y) const +{ + sendMessage(BARRIER_MSG_FAKE_MOVE, + static_cast<WPARAM>(x), + static_cast<LPARAM>(y)); +} + +void +MSWindowsDesks::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + sendMessage(BARRIER_MSG_FAKE_REL_MOVE, + static_cast<WPARAM>(dx), + static_cast<LPARAM>(dy)); +} + +void +MSWindowsDesks::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + sendMessage(BARRIER_MSG_FAKE_WHEEL, xDelta, yDelta); +} + +void +MSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const +{ + if (m_activeDesk != NULL && m_activeDesk->m_window != NULL) { + PostThreadMessage(m_activeDesk->m_threadID, msg, wParam, lParam); + waitForDesk(); + } +} + +HCURSOR +MSWindowsDesks::createBlankCursor() const +{ + // create a transparent cursor + int cw = GetSystemMetrics(SM_CXCURSOR); + int ch = GetSystemMetrics(SM_CYCURSOR); + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; + UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; + memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(MSWindowsScreen::getWindowInstance(), + 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +MSWindowsDesks::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +MSWindowsDesks::createDeskWindowClass(bool isPrimary) const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = isPrimary ? + &MSWindowsDesks::primaryDeskProc : + &MSWindowsDesks::secondaryDeskProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = MSWindowsScreen::getWindowInstance(); + classInfo.hIcon = NULL; + classInfo.hCursor = m_cursor; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "BarrierDesk"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +MSWindowsDesks::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(MAKEINTATOM(windowClass), + MSWindowsScreen::getWindowInstance()); + } +} + +HWND +MSWindowsDesks::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + MAKEINTATOM(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + MSWindowsScreen::getWindowInstance(), + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +void +MSWindowsDesks::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +LRESULT CALLBACK +MSWindowsDesks::primaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +LRESULT CALLBACK +MSWindowsDesks::secondaryDeskProc( + HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + // would like to detect any local user input and hide the hider + // window but for now we just detect mouse motion. + bool hide = false; + switch (msg) { + case WM_MOUSEMOVE: + if (LOWORD(lParam) != 0 || HIWORD(lParam) != 0) { + hide = true; + } + break; + } + + if (hide && IsWindowVisible(hwnd)) { + ReleaseCapture(); + SetWindowPos(hwnd, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + } + + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +void +MSWindowsDesks::deskMouseMove(SInt32 x, SInt32 y) const +{ + // when using absolute positioning with mouse_event(), + // the normalized device coordinates range over only + // the primary screen. + SInt32 w = GetSystemMetrics(SM_CXSCREEN); + SInt32 h = GetSystemMetrics(SM_CYSCREEN); + mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, + (DWORD)((65535.0f * x) / (w - 1) + 0.5f), + (DWORD)((65535.0f * y) / (h - 1) + 0.5f), + 0, 0); +} + +void +MSWindowsDesks::deskMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // relative moves are subject to cursor acceleration which we don't + // want.so we disable acceleration, do the relative move, then + // restore acceleration. there's a slight chance we'll end up in + // the wrong place if the user moves the cursor using this system's + // mouse while simultaneously moving the mouse on the server + // system. that defeats the purpose of barrier so we'll assume + // that won't happen. even if it does, the next mouse move will + // correct the position. + + // save mouse speed & acceleration + int oldSpeed[4]; + bool accelChanged = + SystemParametersInfo(SPI_GETMOUSE,0, oldSpeed, 0) && + SystemParametersInfo(SPI_GETMOUSESPEED, 0, oldSpeed + 3, 0); + + // use 1:1 motion + if (accelChanged) { + int newSpeed[4] = { 0, 0, 0, 1 }; + accelChanged = + SystemParametersInfo(SPI_SETMOUSE, 0, newSpeed, 0) || + SystemParametersInfo(SPI_SETMOUSESPEED, 0, newSpeed + 3, 0); + } + + // move relative to mouse position + mouse_event(MOUSEEVENTF_MOVE, dx, dy, 0, 0); + + // restore mouse speed & acceleration + if (accelChanged) { + SystemParametersInfo(SPI_SETMOUSE, 0, oldSpeed, 0); + SystemParametersInfo(SPI_SETMOUSESPEED, 0, oldSpeed + 3, 0); + } +} + +void +MSWindowsDesks::deskEnter(Desk* desk) +{ + if (!m_isPrimary) { + ReleaseCapture(); + } + ShowCursor(TRUE); + SetWindowPos(desk->m_window, HWND_BOTTOM, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOACTIVATE | SWP_HIDEWINDOW); + + // restore the foreground window + // XXX -- this raises the window to the top of the Z-order. we + // want it to stay wherever it was to properly support X-mouse + // (mouse over activation) but i've no idea how to do that. + // the obvious workaround of using SetWindowPos() to move it back + // after being raised doesn't work. + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_foregroundWindow); + AttachThreadInput(thatThread, thisThread, FALSE); + EnableWindow(desk->m_window, FALSE); + desk->m_foregroundWindow = NULL; +} + +void +MSWindowsDesks::deskLeave(Desk* desk, HKL keyLayout) +{ + ShowCursor(FALSE); + if (m_isPrimary) { + // map a window to hide the cursor and to use whatever keyboard + // layout we choose rather than the keyboard layout of the last + // active window. + int x, y, w, h; + // with a low level hook the cursor will never budge so + // just a 1x1 window is sufficient. + x = m_xCenter; + y = m_yCenter; + w = 1; + h = 1; + SetWindowPos(desk->m_window, HWND_TOP, x, y, w, h, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // since we're using low-level hooks, disable the foreground window + // so it can't mess up any of our keyboard events. the console + // program, for example, will cause characters to be reported as + // unshifted, regardless of the shift key state. interestingly + // we do see the shift key go down and up. + // + // note that we must enable the window to activate it and we + // need to disable the window on deskEnter. + desk->m_foregroundWindow = getForegroundWindow(); + if (desk->m_foregroundWindow != NULL) { + EnableWindow(desk->m_window, TRUE); + SetActiveWindow(desk->m_window); + DWORD thisThread = + GetWindowThreadProcessId(desk->m_window, NULL); + DWORD thatThread = + GetWindowThreadProcessId(desk->m_foregroundWindow, NULL); + AttachThreadInput(thatThread, thisThread, TRUE); + SetForegroundWindow(desk->m_window); + AttachThreadInput(thatThread, thisThread, FALSE); + } + + // switch to requested keyboard layout + ActivateKeyboardLayout(keyLayout, 0); + } + else { + // move hider window under the cursor center, raise, and show it + SetWindowPos(desk->m_window, HWND_TOP, + m_xCenter, m_yCenter, 1, 1, + SWP_NOACTIVATE | SWP_SHOWWINDOW); + + // watch for mouse motion. if we see any then we hide the + // hider window so the user can use the physically attached + // mouse if desired. we'd rather not capture the mouse but + // we aren't notified when the mouse leaves our window. + SetCapture(desk->m_window); + + // warp the mouse to the cursor center + LOG((CLOG_DEBUG2 "warping cursor to center: %+d,%+d", m_xCenter, m_yCenter)); + deskMouseMove(m_xCenter, m_yCenter); + } +} + +void +MSWindowsDesks::deskThread(void* vdesk) +{ + MSG msg; + + // use given desktop for this thread + Desk* desk = static_cast<Desk*>(vdesk); + desk->m_threadID = GetCurrentThreadId(); + desk->m_window = NULL; + desk->m_foregroundWindow = NULL; + if (desk->m_desk != NULL && SetThreadDesktop(desk->m_desk) != 0) { + // create a message queue + PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE); + + // create a window. we use this window to hide the cursor. + try { + desk->m_window = createWindow(m_deskClass, "BarrierDesk"); + LOG((CLOG_DEBUG "desk %s window is 0x%08x", desk->m_name.c_str(), desk->m_window)); + } + catch (...) { + // ignore + LOG((CLOG_DEBUG "can't create desk window for %s", desk->m_name.c_str())); + } + } + + // tell main thread that we're ready + { + Lock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + while (GetMessage(&msg, NULL, 0, 0)) { + switch (msg.message) { + default: + TranslateMessage(&msg); + DispatchMessage(&msg); + continue; + + case BARRIER_MSG_SWITCH: + if (m_isPrimary && !m_noHooks) { + MSWindowsHook::uninstall(); + if (m_screensaverNotify) { + MSWindowsHook::uninstallScreenSaver(); + MSWindowsHook::installScreenSaver(); + } + if (!MSWindowsHook::install()) { + // we won't work on this desk + LOG((CLOG_DEBUG "Cannot hook on this desk")); + } + // a window on the primary screen with low-level hooks + // should never activate. + if (desk->m_window) + EnableWindow(desk->m_window, FALSE); + } + break; + + case BARRIER_MSG_ENTER: + m_isOnScreen = true; + deskEnter(desk); + break; + + case BARRIER_MSG_LEAVE: + m_isOnScreen = false; + m_keyLayout = (HKL)msg.wParam; + deskLeave(desk, m_keyLayout); + break; + + case BARRIER_MSG_FAKE_KEY: + keybd_event(HIBYTE(msg.lParam), LOBYTE(msg.lParam), (DWORD)msg.wParam, 0); + break; + + case BARRIER_MSG_FAKE_BUTTON: + if (msg.wParam != 0) { + mouse_event((DWORD)msg.wParam, 0, 0, (DWORD)msg.lParam, 0); + } + break; + + case BARRIER_MSG_FAKE_MOVE: + deskMouseMove(static_cast<SInt32>(msg.wParam), + static_cast<SInt32>(msg.lParam)); + break; + + case BARRIER_MSG_FAKE_REL_MOVE: + deskMouseRelativeMove(static_cast<SInt32>(msg.wParam), + static_cast<SInt32>(msg.lParam)); + break; + + case BARRIER_MSG_FAKE_WHEEL: + // XXX -- add support for x-axis scrolling + if (msg.lParam != 0) { + mouse_event(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam, 0); + } + break; + + case BARRIER_MSG_CURSOR_POS: { + POINT* pos = reinterpret_cast<POINT*>(msg.wParam); + if (!GetCursorPos(pos)) { + pos->x = m_xCenter; + pos->y = m_yCenter; + } + break; + } + + case BARRIER_MSG_SYNC_KEYS: + m_updateKeys->run(); + break; + + case BARRIER_MSG_SCREENSAVER: + if (!m_noHooks) { + if (msg.wParam != 0) { + MSWindowsHook::installScreenSaver(); + } + else { + MSWindowsHook::uninstallScreenSaver(); + } + } + break; + + case BARRIER_MSG_FAKE_INPUT: + keybd_event(BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY, + BARRIER_HOOK_FAKE_INPUT_SCANCODE, + msg.wParam ? 0 : KEYEVENTF_KEYUP, 0); + break; + } + + // notify that message was processed + Lock lock(&m_mutex); + m_deskReady = true; + m_deskReady.broadcast(); + } + + // clean up + deskEnter(desk); + if (desk->m_window != NULL) { + DestroyWindow(desk->m_window); + } + if (desk->m_desk != NULL) { + closeDesktop(desk->m_desk); + } +} + +MSWindowsDesks::Desk* +MSWindowsDesks::addDesk(const String& name, HDESK hdesk) +{ + Desk* desk = new Desk; + desk->m_name = name; + desk->m_desk = hdesk; + desk->m_targetID = GetCurrentThreadId(); + desk->m_thread = new Thread(new TMethodJob<MSWindowsDesks>( + this, &MSWindowsDesks::deskThread, desk)); + waitForDesk(); + m_desks.insert(std::make_pair(name, desk)); + return desk; +} + +void +MSWindowsDesks::removeDesks() +{ + for (Desks::iterator index = m_desks.begin(); + index != m_desks.end(); ++index) { + Desk* desk = index->second; + PostThreadMessage(desk->m_threadID, WM_QUIT, 0, 0); + desk->m_thread->wait(); + delete desk->m_thread; + delete desk; + } + m_desks.clear(); + m_activeDesk = NULL; + m_activeDeskName = ""; +} + +void +MSWindowsDesks::checkDesk() +{ + // get current desktop. if we already know about it then return. + Desk* desk; + HDESK hdesk = openInputDesktop(); + String name = getDesktopName(hdesk); + Desks::const_iterator index = m_desks.find(name); + if (index == m_desks.end()) { + desk = addDesk(name, hdesk); + // hold on to hdesk until thread exits so the desk can't + // be removed by the system + } + else { + closeDesktop(hdesk); + desk = index->second; + } + + // if we are told to shut down on desk switch, and this is not the + // first switch, then shut down. + if (m_stopOnDeskSwitch && m_activeDesk != NULL && name != m_activeDeskName) { + LOG((CLOG_DEBUG "shutting down because of desk switch to \"%s\"", name.c_str())); + m_events->addEvent(Event(Event::kQuit)); + return; + } + + // if active desktop changed then tell the old and new desk threads + // about the change. don't switch desktops when the screensaver is + // active becaue we'd most likely switch to the screensaver desktop + // which would have the side effect of forcing the screensaver to + // stop. + if (name != m_activeDeskName && !m_screensaver->isActive()) { + // show cursor on previous desk + bool wasOnScreen = m_isOnScreen; + if (!wasOnScreen) { + sendMessage(BARRIER_MSG_ENTER, 0, 0); + } + + // check for desk accessibility change. we don't get events + // from an inaccessible desktop so when we switch from an + // inaccessible desktop to an accessible one we have to + // update the keyboard state. + LOG((CLOG_DEBUG "switched to desk \"%s\"", name.c_str())); + bool syncKeys = false; + bool isAccessible = isDeskAccessible(desk); + if (isDeskAccessible(m_activeDesk) != isAccessible) { + if (isAccessible) { + LOG((CLOG_DEBUG "desktop is now accessible")); + syncKeys = true; + } + else { + LOG((CLOG_DEBUG "desktop is now inaccessible")); + } + } + + // switch desk + m_activeDesk = desk; + m_activeDeskName = name; + sendMessage(BARRIER_MSG_SWITCH, 0, 0); + + // hide cursor on new desk + if (!wasOnScreen) { + sendMessage(BARRIER_MSG_LEAVE, (WPARAM)m_keyLayout, 0); + } + + // update keys if necessary + if (syncKeys) { + updateKeys(); + } + } + else if (name != m_activeDeskName) { + // screen saver might have started + PostThreadMessage(m_threadID, BARRIER_MSG_SCREEN_SAVER, TRUE, 0); + } +} + +bool +MSWindowsDesks::isDeskAccessible(const Desk* desk) const +{ + return (desk != NULL && desk->m_desk != NULL); +} + +void +MSWindowsDesks::waitForDesk() const +{ + MSWindowsDesks* self = const_cast<MSWindowsDesks*>(this); + + Lock lock(&m_mutex); + while (!(bool)m_deskReady) { + m_deskReady.wait(); + } + self->m_deskReady = false; +} + +void +MSWindowsDesks::handleCheckDesk(const Event&, void*) +{ + checkDesk(); + + // also check if screen saver is running if on a modern OS and + // this is the primary screen. + if (m_isPrimary) { + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, FALSE); + PostThreadMessage(m_threadID, BARRIER_MSG_SCREEN_SAVER, running, 0); + } +} + +HDESK +MSWindowsDesks::openInputDesktop() +{ + return OpenInputDesktop( + DF_ALLOWOTHERACCOUNTHOOK, TRUE, + DESKTOP_CREATEWINDOW | DESKTOP_HOOKCONTROL | GENERIC_WRITE); +} + +void +MSWindowsDesks::closeDesktop(HDESK desk) +{ + if (desk != NULL) { + CloseDesktop(desk); + } +} + +String +MSWindowsDesks::getDesktopName(HDESK desk) +{ + if (desk == NULL) { + return String(); + } + else { + DWORD size; + GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size); + TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); + GetUserObjectInformation(desk, UOI_NAME, name, size, &size); + String result(name); + return result; + } +} + +HWND +MSWindowsDesks::getForegroundWindow() const +{ + // Ideally we'd return NULL as much as possible, only returning + // the actual foreground window when we know it's going to mess + // up our keyboard input. For now we'll just let the user + // decide. + if (m_leaveForegroundOption) { + return NULL; + } + return GetForegroundWindow(); +} diff --git a/src/lib/platform/MSWindowsDesks.h b/src/lib/platform/MSWindowsDesks.h new file mode 100644 index 0000000..da93c34 --- /dev/null +++ b/src/lib/platform/MSWindowsDesks.h @@ -0,0 +1,297 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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 "platform/synwinhk.h" +#include "barrier/key_types.h" +#include "barrier/mouse_types.h" +#include "barrier/option_types.h" +#include "mt/CondVar.h" +#include "mt/Mutex.h" +#include "base/String.h" +#include "common/stdmap.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class Event; +class EventQueueTimer; +class Thread; +class IJob; +class IScreenSaver; +class IEventQueue; + +//! Microsoft Windows desk handling +/*! +Desks in Microsoft Windows are only remotely like desktops on X11 +systems. A desk is another virtual surface for windows but desks +impose serious restrictions: a thread can interact with only one +desk at a time, you can't switch desks if the thread has any hooks +installed or owns any windows, windows cannot exist on multiple +desks at once, etc. Basically, they're useless except for running +the login window or the screensaver, which is what they're used +for. Barrier must deal with them mainly because of the login +window and screensaver but users can create their own desks and +barrier should work on those too. + +This class encapsulates all the desk nastiness. Clients of this +object don't have to know anything about desks. +*/ +class MSWindowsDesks { +public: + //! Constructor + /*! + \p isPrimary is true iff the desk is for a primary screen. + \p screensaver points to a screensaver object and it's used + only to check if the screensaver is active. The \p updateKeys + job is adopted and is called when the key state should be + updated in a thread attached to the current desk. + \p hookLibrary must be a handle to the hook library. + */ + MSWindowsDesks( + bool isPrimary, bool noHooks, + const IScreenSaver* screensaver, IEventQueue* events, + IJob* updateKeys, bool stopOnDeskSwitch); + ~MSWindowsDesks(); + + //! @name manipulators + //@{ + + //! Enable desk tracking + /*! + Enables desk tracking. While enabled, this object checks to see + if the desk has changed and ensures that the hooks are installed + on the new desk. \c setShape should be called at least once + before calling \c enable. + */ + void enable(); + + //! Disable desk tracking + /*! + Disables desk tracking. \sa enable. + */ + void disable(); + + //! Notify of entering a desk + /*! + Prepares a desk for when the cursor enters it. + */ + void enter(); + + //! Notify of leaving a desk + /*! + Prepares a desk for when the cursor leaves it. + */ + void leave(HKL keyLayout); + + //! Notify of options changes + /*! + Resets all options to their default values. + */ + void resetOptions(); + + //! Notify of options changes + /*! + Set options to given values. Ignores unknown options and doesn't + modify options that aren't given in \c options. + */ + void setOptions(const OptionsList& options); + + //! Update the key state + /*! + Causes the key state to get updated to reflect the physical keyboard + state and current keyboard mapping. + */ + void updateKeys(); + + //! Tell desk about new size + /*! + This tells the desks that the display size has changed. + */ + void setShape(SInt32 x, SInt32 y, + SInt32 width, SInt32 height, + SInt32 xCenter, SInt32 yCenter, bool isMultimon); + + //! Install/uninstall screensaver hooks + /*! + If \p install is true then the screensaver hooks are installed and, + if desk tracking is enabled, updated whenever the desk changes. If + \p install is false then the screensaver hooks are uninstalled. + */ + void installScreensaverHooks(bool install); + + //! Start ignoring user input + /*! + Starts ignoring user input so we don't pick up our own synthesized events. + */ + void fakeInputBegin(); + + //! Stop ignoring user input + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Return the current position of the cursor in \c x and \c y. + */ + void getCursorPos(SInt32& x, SInt32& y) const; + + //! Fake key press/release + /*! + Synthesize a press or release of key \c button. + */ + void fakeKeyEvent(KeyButton button, UINT virtualKey, + bool press, bool isAutoRepeat) const; + + //! Fake mouse press/release + /*! + Synthesize a press or release of mouse button \c id. + */ + void fakeMouseButton(ButtonID id, bool press); + + //! Fake mouse move + /*! + Synthesize a mouse move to the absolute coordinates \c x,y. + */ + void fakeMouseMove(SInt32 x, SInt32 y) const; + + //! Fake mouse move + /*! + Synthesize a mouse move to the relative coordinates \c dx,dy. + */ + void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + + //! Fake mouse wheel + /*! + Synthesize a mouse wheel event of amount \c delta in direction \c axis. + */ + void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + //@} + +private: + class Desk { + public: + String m_name; + Thread* m_thread; + DWORD m_threadID; + DWORD m_targetID; + HDESK m_desk; + HWND m_window; + HWND m_foregroundWindow; + bool m_lowLevel; + }; + typedef std::map<String, Desk*> Desks; + + // initialization and shutdown operations + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // message handlers + void deskMouseMove(SInt32 x, SInt32 y) const; + void deskMouseRelativeMove(SInt32 dx, SInt32 dy) const; + void deskEnter(Desk* desk); + void deskLeave(Desk* desk, HKL keyLayout); + void deskThread(void* vdesk); + + // desk switch checking and handling + Desk* addDesk(const String& name, HDESK hdesk); + void removeDesks(); + void checkDesk(); + bool isDeskAccessible(const Desk* desk) const; + void handleCheckDesk(const Event& event, void*); + + // communication with desk threads + void waitForDesk() const; + void sendMessage(UINT, WPARAM, LPARAM) const; + + // work around for messed up keyboard events from low-level hooks + HWND getForegroundWindow() const; + + // desk API wrappers + HDESK openInputDesktop(); + void closeDesktop(HDESK); + String getDesktopName(HDESK); + + // our desk window procs + static LRESULT CALLBACK primaryDeskProc(HWND, UINT, WPARAM, LPARAM); + static LRESULT CALLBACK secondaryDeskProc(HWND, UINT, WPARAM, LPARAM); + +private: + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if hooks are not to be installed (useful for debugging) + bool m_noHooks; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_deskClass; + HCURSOR m_cursor; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // the timer used to check for desktop switching + EventQueueTimer* m_timer; + + // screen saver stuff + DWORD m_threadID; + const IScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // the current desk and it's name + Desk* m_activeDesk; + String m_activeDeskName; + + // one desk per desktop and a cond var to communicate with it + Mutex m_mutex; + CondVar<bool> m_deskReady; + Desks m_desks; + + // keyboard stuff + IJob* m_updateKeys; + HKL m_keyLayout; + + // options + bool m_leaveForegroundOption; + + IEventQueue* m_events; + + // true if program should stop on desk switch. + bool m_stopOnDeskSwitch; +}; diff --git a/src/lib/platform/MSWindowsDropTarget.cpp b/src/lib/platform/MSWindowsDropTarget.cpp new file mode 100644 index 0000000..d647808 --- /dev/null +++ b/src/lib/platform/MSWindowsDropTarget.cpp @@ -0,0 +1,178 @@ +/* + * 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 "platform/MSWindowsDropTarget.h" + +#include "base/Log.h" +#include "common/common.h" + +#include <stdio.h> +#include <Shlobj.h> + +void getDropData(IDataObject *pDataObject); + +MSWindowsDropTarget* MSWindowsDropTarget::s_instance = NULL; + +MSWindowsDropTarget::MSWindowsDropTarget() : + m_refCount(1), + m_allowDrop(false) +{ + s_instance = this; +} + +MSWindowsDropTarget::~MSWindowsDropTarget() +{ +} + +MSWindowsDropTarget& +MSWindowsDropTarget::instance() +{ + assert(s_instance != NULL); + return *s_instance; +} + +HRESULT +MSWindowsDropTarget::DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect) +{ + // check if data object contain drop + m_allowDrop = queryDataObject(dataObject); + if (m_allowDrop) { + getDropData(dataObject); + } + + *effect = DROPEFFECT_NONE; + + return S_OK; +} + +HRESULT +MSWindowsDropTarget::DragOver(DWORD keyState, POINTL point, DWORD* effect) +{ + *effect = DROPEFFECT_NONE; + + return S_OK; +} + +HRESULT +MSWindowsDropTarget::DragLeave(void) +{ + return S_OK; +} + +HRESULT +MSWindowsDropTarget::Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect) +{ + *effect = DROPEFFECT_NONE; + + return S_OK; +} + +bool +MSWindowsDropTarget::queryDataObject(IDataObject* dataObject) +{ + // check if it supports CF_HDROP using a HGLOBAL + FORMATETC fmtetc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + + return dataObject->QueryGetData(&fmtetc) == S_OK ? true : false; +} + +void +MSWindowsDropTarget::setDraggingFilename(char* const filename) +{ + m_dragFilename = filename; +} + +std::string +MSWindowsDropTarget::getDraggingFilename() +{ + return m_dragFilename; +} + +void +MSWindowsDropTarget::clearDraggingFilename() +{ + m_dragFilename.clear(); +} + +void +getDropData(IDataObject* dataObject) +{ + // construct a FORMATETC object + FORMATETC fmtEtc = { CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM stgMed; + + // See if the dataobject contains any DROP stored as a HGLOBAL + if (dataObject->QueryGetData(&fmtEtc) == S_OK) { + if (dataObject->GetData(&fmtEtc, &stgMed) == S_OK) { + // get data here + PVOID data = GlobalLock(stgMed.hGlobal); + + // data object global handler contains: + // DROPFILESfilename1 filename2 two spaces as the end + // TODO: get multiple filenames + wchar_t* wcData = (wchar_t*)((LPBYTE)data + sizeof(DROPFILES)); + + // convert wchar to char + char* filename = new char[wcslen(wcData) + 1]; + filename[wcslen(wcData)] = '\0'; + wcstombs(filename, wcData, wcslen(wcData)); + + MSWindowsDropTarget::instance().setDraggingFilename(filename); + + GlobalUnlock(stgMed.hGlobal); + + // release the data using the COM API + ReleaseStgMedium(&stgMed); + + delete[] filename; + } + } +} + +HRESULT __stdcall +MSWindowsDropTarget::QueryInterface (REFIID iid, void ** object) +{ + if (iid == IID_IDropTarget || iid == IID_IUnknown) { + AddRef(); + *object = this; + return S_OK; + } + else { + *object = 0; + return E_NOINTERFACE; + } +} + +ULONG __stdcall +MSWindowsDropTarget::AddRef(void) +{ + return InterlockedIncrement(&m_refCount); +} + +ULONG __stdcall +MSWindowsDropTarget::Release(void) +{ + LONG count = InterlockedDecrement(&m_refCount); + + if (count == 0) { + delete this; + return 0; + } + else { + return count; + } +} diff --git a/src/lib/platform/MSWindowsDropTarget.h b/src/lib/platform/MSWindowsDropTarget.h new file mode 100644 index 0000000..6d60845 --- /dev/null +++ b/src/lib/platform/MSWindowsDropTarget.h @@ -0,0 +1,59 @@ +/* + * 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 + +#include <string> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <oleidl.h> + +class MSWindowsScreen; + +class MSWindowsDropTarget : public IDropTarget { +public: + MSWindowsDropTarget(); + ~MSWindowsDropTarget(); + + // IUnknown implementation + HRESULT __stdcall QueryInterface(REFIID iid, void** object); + ULONG __stdcall AddRef(void); + ULONG __stdcall Release(void); + + // IDropTarget implementation + HRESULT __stdcall DragEnter(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect); + HRESULT __stdcall DragOver(DWORD keyState, POINTL point, DWORD* effect); + HRESULT __stdcall DragLeave(void); + HRESULT __stdcall Drop(IDataObject* dataObject, DWORD keyState, POINTL point, DWORD* effect); + + void setDraggingFilename(char* const); + std::string getDraggingFilename(); + void clearDraggingFilename(); + + static MSWindowsDropTarget& + instance(); + +private: + bool queryDataObject(IDataObject* dataObject); + + long m_refCount; + bool m_allowDrop; + std::string m_dragFilename; + + static MSWindowsDropTarget* + s_instance; +}; diff --git a/src/lib/platform/MSWindowsEventQueueBuffer.cpp b/src/lib/platform/MSWindowsEventQueueBuffer.cpp new file mode 100644 index 0000000..f6de157 --- /dev/null +++ b/src/lib/platform/MSWindowsEventQueueBuffer.cpp @@ -0,0 +1,146 @@ +/* + * 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 "platform/MSWindowsEventQueueBuffer.h" + +#include "arch/win32/ArchMiscWindows.h" +#include "mt/Thread.h" +#include "base/IEventQueue.h" + +// +// EventQueueTimer +// + +class EventQueueTimer { }; + + +// +// MSWindowsEventQueueBuffer +// + +MSWindowsEventQueueBuffer::MSWindowsEventQueueBuffer(IEventQueue* events) : + m_events(events) +{ + // remember thread. we'll be posting messages to it. + m_thread = GetCurrentThreadId(); + + // create a message type for custom events + m_userEvent = RegisterWindowMessage("BARRIER_USER_EVENT"); + + // get message type for daemon quit + m_daemonQuit = ArchMiscWindows::getDaemonQuitMessage(); + + // make sure this thread has a message queue + MSG dummy; + PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE); +} + +MSWindowsEventQueueBuffer::~MSWindowsEventQueueBuffer() +{ + // do nothing +} + +void +MSWindowsEventQueueBuffer::waitForEvent(double timeout) +{ + // check if messages are available first. if we don't do this then + // MsgWaitForMultipleObjects() will block even if the queue isn't + // empty if the messages in the queue were there before the last + // call to GetMessage()/PeekMessage(). + if (!isEmpty()) { + return; + } + + // convert timeout + DWORD t; + if (timeout < 0.0) { + t = INFINITE; + } + else { + t = (DWORD)(1000.0 * timeout); + } + + // wait for a message. we cannot be interrupted by thread + // cancellation but that's okay because we're run in the main + // thread and we never cancel that thread. + HANDLE dummy[1]; + MsgWaitForMultipleObjects(0, dummy, FALSE, t, QS_ALLINPUT); +} + +IEventQueueBuffer::Type +MSWindowsEventQueueBuffer::getEvent(Event& event, UInt32& dataID) +{ + // peek at messages first. waiting for QS_ALLINPUT will return + // if a message has been sent to our window but GetMessage will + // dispatch that message behind our backs and block. PeekMessage + // will also dispatch behind our backs but won't block. + if (!PeekMessage(&m_event, NULL, 0, 0, PM_NOREMOVE) && + !PeekMessage(&m_event, (HWND)-1, 0, 0, PM_NOREMOVE)) { + return kNone; + } + + // BOOL. yeah, right. + BOOL result = GetMessage(&m_event, NULL, 0, 0); + if (result == -1) { + return kNone; + } + else if (result == 0) { + event = Event(Event::kQuit); + return kSystem; + } + else if (m_daemonQuit != 0 && m_event.message == m_daemonQuit) { + event = Event(Event::kQuit); + return kSystem; + } + else if (m_event.message == m_userEvent) { + dataID = static_cast<UInt32>(m_event.wParam); + return kUser; + } + else { + event = Event(Event::kSystem, + m_events->getSystemTarget(), &m_event); + return kSystem; + } +} + +bool +MSWindowsEventQueueBuffer::addEvent(UInt32 dataID) +{ + return (PostThreadMessage(m_thread, m_userEvent, + static_cast<WPARAM>(dataID), 0) != 0); +} + +bool +MSWindowsEventQueueBuffer::isEmpty() const +{ + // don't use QS_POINTER, QS_TOUCH, or any meta-flags that include them (like QS_ALLINPUT) + // because they can cause GetQueueStatus() to always return 0 and we miss events + return (HIWORD(GetQueueStatus(QS_POSTMESSAGE)) == 0); +} + +EventQueueTimer* +MSWindowsEventQueueBuffer::newTimer(double, bool) const +{ + return new EventQueueTimer; +} + +void +MSWindowsEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const +{ + delete timer; +} diff --git a/src/lib/platform/MSWindowsEventQueueBuffer.h b/src/lib/platform/MSWindowsEventQueueBuffer.h new file mode 100644 index 0000000..6a0f9f9 --- /dev/null +++ b/src/lib/platform/MSWindowsEventQueueBuffer.h @@ -0,0 +1,50 @@ +/* + * 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 "base/IEventQueueBuffer.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class IEventQueue; + +//! Event queue buffer for Win32 +class MSWindowsEventQueueBuffer : public IEventQueueBuffer { +public: + MSWindowsEventQueueBuffer(IEventQueue* events); + virtual ~MSWindowsEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void init() { } + virtual void waitForEvent(double timeout); + virtual Type getEvent(Event& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(EventQueueTimer*) const; + +private: + DWORD m_thread; + UINT m_userEvent; + MSG m_event; + UINT m_daemonQuit; + IEventQueue* m_events; +}; diff --git a/src/lib/platform/MSWindowsHook.cpp b/src/lib/platform/MSWindowsHook.cpp new file mode 100644 index 0000000..929888e --- /dev/null +++ b/src/lib/platform/MSWindowsHook.cpp @@ -0,0 +1,629 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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 "platform/MSWindowsHook.h" +#include "platform/MSWindowsHookResource.h" +#include "platform/ImmuneKeysReader.h" +#include "barrier/protocol_types.h" +#include "barrier/XScreen.h" +#include "base/Log.h" + + // + // debugging compile flag. when not zero the server doesn't grab + // the keyboard when the mouse leaves the server screen. this + // makes it possible to use the debugger (via the keyboard) when + // all user input would normally be caught by the hook procedures. + // +#define NO_GRAB_KEYBOARD 0 + +static const DWORD g_threadID = GetCurrentThreadId(); + +static WindowsHookResource g_hkMessage; +static WindowsHookResource g_hkKeyboard; +static WindowsHookResource g_hkMouse; +static EHookMode g_mode = kHOOK_DISABLE; +static UInt32 g_zoneSides = 0; +static SInt32 g_zoneSize = 0; +static SInt32 g_xScreen = 0; +static SInt32 g_yScreen = 0; +static SInt32 g_wScreen = 0; +static SInt32 g_hScreen = 0; +static WPARAM g_deadVirtKey = 0; +static WPARAM g_deadRelease = 0; +static LPARAM g_deadLParam = 0; +static BYTE g_deadKeyState[256] = { 0 }; +static BYTE g_keyState[256] = { 0 }; +static bool g_fakeServerInput = false; +static std::vector<DWORD> g_immuneKeys; + +static const std::string ImmuneKeysPath = ArchFileWindows().getProfileDirectory() + "\\ImmuneKeys.txt"; + +static std::vector<DWORD> immune_keys_list() +{ + std::vector<DWORD> keys; + std::string badLine; + if (!ImmuneKeysReader::get_list(ImmuneKeysPath.c_str(), keys, badLine)) + LOG((CLOG_ERR "Reading immune keys stopped at: %s", badLine.c_str())); + return keys; +} + +inline static +bool is_immune_key(DWORD target) +{ + for (auto key : g_immuneKeys) { + if (key == target) + return true; + } + return false; +} + +void +MSWindowsHook::setSides(UInt32 sides) +{ + g_zoneSides = sides; +} + +void +MSWindowsHook::setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize) +{ + g_zoneSize = jumpZoneSize; + g_xScreen = x; + g_yScreen = y; + g_wScreen = w; + g_hScreen = h; +} + +void +MSWindowsHook::setMode(EHookMode mode) +{ + g_mode = mode; +} + +#if !NO_GRAB_KEYBOARD +static +void +keyboardGetState(BYTE keys[256], DWORD vkCode, bool kf_up) +{ + // we have to use GetAsyncKeyState() rather than GetKeyState() because + // we don't pass through most keys so the event synchronous state + // doesn't get updated. we do that because certain modifier keys have + // side effects, like alt and the windows key. + if (vkCode < 0 || vkCode >= 256) { + return; + } + + // Keep track of key state on our own in case GetAsyncKeyState() fails + g_keyState[vkCode] = kf_up ? 0 : 0x80; + g_keyState[VK_SHIFT] = g_keyState[VK_LSHIFT] | g_keyState[VK_RSHIFT]; + + SHORT key; + // Test whether GetAsyncKeyState() is being honest with us + key = GetAsyncKeyState(vkCode); + + if (key & 0x80) { + // The only time we know for sure that GetAsyncKeyState() is working + // is when it tells us that the current key is down. + // In this case, update g_keyState to reflect what GetAsyncKeyState() + // is telling us, just in case we have gotten out of sync + + for (int i = 0; i < 256; ++i) { + key = GetAsyncKeyState(i); + g_keyState[i] = (BYTE)((key < 0) ? 0x80u : 0); + } + } + + // copy g_keyState to keys + for (int i = 0; i < 256; ++i) { + keys[i] = g_keyState[i]; + } + + key = GetKeyState(VK_CAPITAL); + keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1)); +} + +static +WPARAM +makeKeyMsg(UINT virtKey, char c, bool noAltGr) +{ + return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0); +} + +static +bool +keyboardHookHandler(WPARAM wParam, LPARAM lParam) +{ + DWORD vkCode = static_cast<DWORD>(wParam); + bool kf_up = (lParam & (KF_UP << 16)) != 0; + + // check for special events indicating if we should start or stop + // passing events through and not report them to the server. this + // is used to allow the server to synthesize events locally but + // not pick them up as user events. + if (wParam == BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY && + ((lParam >> 16) & 0xffu) == BARRIER_HOOK_FAKE_INPUT_SCANCODE) { + // update flag + g_fakeServerInput = ((lParam & 0x80000000u) == 0); + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, + 0xff000000u | wParam, lParam); + + // discard event + return true; + } + + // if we're expecting fake input then just pass the event through + // and do not forward to the server + if (g_fakeServerInput) { + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, + 0xfe000000u | wParam, lParam); + return false; + } + + // VK_RSHIFT may be sent with an extended scan code but right shift + // is not an extended key so we reset that bit. + if (wParam == VK_RSHIFT) { + lParam &= ~0x01000000u; + } + + // tell server about event + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, wParam, lParam); + + // ignore dead key release + if ((g_deadVirtKey == wParam || g_deadRelease == wParam) && + (lParam & 0x80000000u) != 0) { + g_deadRelease = 0; + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, + wParam | 0x04000000, lParam); + return false; + } + + // we need the keyboard state for ToAscii() + BYTE keys[256]; + keyboardGetState(keys, vkCode, kf_up); + + // ToAscii() maps ctrl+letter to the corresponding control code + // and ctrl+backspace to delete. we don't want those translations + // so clear the control modifier state. however, if we want to + // simulate AltGr (which is ctrl+alt) then we must not clear it. + UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL]; + UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU]; + if ((control & 0x80) == 0 || (menu & 0x80) == 0) { + keys[VK_LCONTROL] = 0; + keys[VK_RCONTROL] = 0; + keys[VK_CONTROL] = 0; + } else { + keys[VK_LCONTROL] = 0x80; + keys[VK_RCONTROL] = 0x80; + keys[VK_CONTROL] = 0x80; + keys[VK_LMENU] = 0x80; + keys[VK_RMENU] = 0x80; + keys[VK_MENU] = 0x80; + } + + // ToAscii() needs to know if a menu is active for some reason. + // we don't know and there doesn't appear to be any way to find + // out. so we'll just assume a menu is active if the menu key + // is down. + // FIXME -- figure out some way to check if a menu is active + UINT flags = 0; + if ((menu & 0x80) != 0) + flags |= 1; + + // if we're on the server screen then just pass numpad keys with alt + // key down as-is. we won't pick up the resulting character but the + // local app will. if on a client screen then grab keys as usual; + // if the client is a windows system it'll synthesize the expected + // character. if not then it'll probably just do nothing. + if (g_mode != kHOOK_RELAY_EVENTS) { + // we don't use virtual keys because we don't know what the + // state of the numlock key is. we'll hard code the scan codes + // instead. hopefully this works across all keyboards. + UINT sc = (lParam & 0x01ff0000u) >> 16; + if (menu && + (sc >= 0x47u && sc <= 0x52u && sc != 0x4au && sc != 0x4eu)) { + return false; + } + } + + WORD c = 0; + + // map the key event to a character. we have to put the dead + // key back first and this has the side effect of removing it. + if (g_deadVirtKey != 0) { + if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags) == 2) { + // If ToAscii returned 2, it means that we accidentally removed + // a double dead key instead of restoring it. Thus, we call + // ToAscii again with the same parameters to restore the + // internal dead key state. + ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + + // We need to keep track of this because g_deadVirtKey will be + // cleared later on; this would cause the dead key release to + // incorrectly restore the dead key state. + g_deadRelease = g_deadVirtKey; + } + } + + UINT scanCode = ((lParam & 0x10ff0000u) >> 16); + int n = ToAscii((UINT)wParam, scanCode, keys, &c, flags); + + // if mapping failed and ctrl and alt are pressed then try again + // with both not pressed. this handles the case where ctrl and + // alt are being used as individual modifiers rather than AltGr. + // we note that's the case in the message sent back to barrier + // because there's no simple way to deduce it after the fact. + // we have to put the dead key back first, if there was one. + bool noAltGr = false; + if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) { + noAltGr = true; + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, + wParam | 0x05000000, lParam); + if (g_deadVirtKey != 0) { + if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags) == 2) { + ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + g_deadRelease = g_deadVirtKey; + } + } + BYTE keys2[256]; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + keys2[i] = keys[i]; + } + keys2[VK_LCONTROL] = 0; + keys2[VK_RCONTROL] = 0; + keys2[VK_CONTROL] = 0; + keys2[VK_LMENU] = 0; + keys2[VK_RMENU] = 0; + keys2[VK_MENU] = 0; + n = ToAscii((UINT)wParam, scanCode, keys2, &c, flags); + } + + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, + wParam | ((c & 0xff) << 8) | + ((n & 0xff) << 16) | 0x06000000, + lParam); + WPARAM charAndVirtKey = 0; + bool clearDeadKey = false; + switch (n) { + default: + // key is a dead key + + if (lParam & 0x80000000u) + // This handles the obscure situation where a key has been + // pressed which is both a dead key and a normal character + // depending on which modifiers have been pressed. We + // break here to prevent it from being considered a dead + // key. + break; + + g_deadVirtKey = wParam; + g_deadLParam = lParam; + for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) { + g_deadKeyState[i] = keys[i]; + } + break; + + case 0: + // key doesn't map to a character. this can happen if + // non-character keys are pressed after a dead key. + charAndVirtKey = makeKeyMsg((UINT)wParam, (char)0, noAltGr); + break; + + case 1: + // key maps to a character composed with dead key + charAndVirtKey = makeKeyMsg((UINT)wParam, (char)LOBYTE(c), noAltGr); + clearDeadKey = true; + break; + + case 2: { + // previous dead key not composed. send a fake key press + // and release for the dead key to our window. + WPARAM deadCharAndVirtKey = + makeKeyMsg((UINT)g_deadVirtKey, (char)LOBYTE(c), noAltGr); + PostThreadMessage(g_threadID, BARRIER_MSG_KEY, + deadCharAndVirtKey, g_deadLParam & 0x7fffffffu); + PostThreadMessage(g_threadID, BARRIER_MSG_KEY, + deadCharAndVirtKey, g_deadLParam | 0x80000000u); + + // use uncomposed character + charAndVirtKey = makeKeyMsg((UINT)wParam, (char)HIBYTE(c), noAltGr); + clearDeadKey = true; + break; + } + } + + // put back the dead key, if any, for the application to use + if (g_deadVirtKey != 0) { + ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16, + g_deadKeyState, &c, flags); + } + + // clear out old dead key state + if (clearDeadKey) { + g_deadVirtKey = 0; + g_deadLParam = 0; + } + + // forward message to our window. do this whether or not we're + // forwarding events to clients because this'll keep our thread's + // key state table up to date. that's important for querying + // the scroll lock toggle state. + // XXX -- with hot keys for actions we may only need to do this when + // forwarding. + if (charAndVirtKey != 0) { + PostThreadMessage(g_threadID, BARRIER_MSG_DEBUG, + charAndVirtKey | 0x07000000, lParam); + PostThreadMessage(g_threadID, BARRIER_MSG_KEY, charAndVirtKey, lParam); + } + + if (g_mode == kHOOK_RELAY_EVENTS) { + // let certain keys pass through + switch (wParam) { + case VK_CAPITAL: + case VK_NUMLOCK: + case VK_SCROLL: + // pass event on. we want to let these through to + // the window proc because otherwise the keyboard + // lights may not stay synchronized. + case VK_HANGUL: + // pass event on because we're using a low level hook + + break; + + default: + // discard + return true; + } + } + + return false; +} + +static +LRESULT CALLBACK +keyboardLLHook(int code, WPARAM wParam, LPARAM lParam) +{ + // decode the message + KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam); + + // do not filter non-action events nor immune keys + if (code == HC_ACTION && !is_immune_key(info->vkCode)) { + WPARAM wParam = info->vkCode; + LPARAM lParam = 1; // repeat code + lParam |= (info->scanCode << 16); // scan code + if (info->flags & LLKHF_EXTENDED) { + lParam |= (1lu << 24); // extended key + } + if (info->flags & LLKHF_ALTDOWN) { + lParam |= (1lu << 29); // context code + } + if (info->flags & LLKHF_UP) { + lParam |= (1lu << 31); // transition + } + // FIXME -- bit 30 should be set if key was already down but + // we don't know that info. as a result we'll never generate + // key repeat events. + + // handle the message + if (keyboardHookHandler(wParam, lParam)) { + return 1; + } + } + + return CallNextHookEx(g_hkKeyboard, code, wParam, lParam); +} +#endif // !NO_GRAB_KEYBOARD + +static +bool +mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data) +{ + switch (wParam) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + // always relay the event. eat it if relaying. + PostThreadMessage(g_threadID, BARRIER_MSG_MOUSE_BUTTON, wParam, data); + return (g_mode == kHOOK_RELAY_EVENTS); + + case WM_MOUSEWHEEL: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay event + PostThreadMessage(g_threadID, BARRIER_MSG_MOUSE_WHEEL, data, 0); + } + return (g_mode == kHOOK_RELAY_EVENTS); + + case WM_NCMOUSEMOVE: + case WM_MOUSEMOVE: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay and eat event + PostThreadMessage(g_threadID, BARRIER_MSG_MOUSE_MOVE, x, y); + return true; + } else if (g_mode == kHOOK_WATCH_JUMP_ZONE) { + // low level hooks can report bogus mouse positions that are + // outside of the screen. jeez. naturally we end up getting + // fake motion in the other direction to get the position back + // on the screen, which plays havoc with switch on double tap. + // Server deals with that. we'll clamp positions onto the + // screen. also, if we discard events for positions outside + // of the screen then the mouse appears to get a bit jerky + // near the edge. we can either accept that or pass the bogus + // events. we'll try passing the events. + bool bogus = false; + if (x < g_xScreen) { + x = g_xScreen; + bogus = true; + } else if (x >= g_xScreen + g_wScreen) { + x = g_xScreen + g_wScreen - 1; + bogus = true; + } + if (y < g_yScreen) { + y = g_yScreen; + bogus = true; + } else if (y >= g_yScreen + g_hScreen) { + y = g_yScreen + g_hScreen - 1; + bogus = true; + } + + // check for mouse inside jump zone + bool inside = false; + if (!inside && (g_zoneSides & kLeftMask) != 0) { + inside = (x < g_xScreen + g_zoneSize); + } + if (!inside && (g_zoneSides & kRightMask) != 0) { + inside = (x >= g_xScreen + g_wScreen - g_zoneSize); + } + if (!inside && (g_zoneSides & kTopMask) != 0) { + inside = (y < g_yScreen + g_zoneSize); + } + if (!inside && (g_zoneSides & kBottomMask) != 0) { + inside = (y >= g_yScreen + g_hScreen - g_zoneSize); + } + + // relay the event + PostThreadMessage(g_threadID, BARRIER_MSG_MOUSE_MOVE, x, y); + + // if inside and not bogus then eat the event + return inside && !bogus; + } + } + + // pass the event + return false; +} + +static +LRESULT CALLBACK +mouseLLHook(int code, WPARAM wParam, LPARAM lParam) +{ + // do not filter non-action events + if (code == HC_ACTION) { + // decode the message + MSLLHOOKSTRUCT* info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam); + SInt32 x = static_cast<SInt32>(info->pt.x); + SInt32 y = static_cast<SInt32>(info->pt.y); + SInt32 w = static_cast<SInt16>(HIWORD(info->mouseData)); + + // handle the message + if (mouseHookHandler(wParam, x, y, w)) { + return 1; + } + } + + return CallNextHookEx(g_hkMouse, code, wParam, lParam); +} + +bool +MSWindowsHook::install() +{ + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + // reset fake input flag + g_fakeServerInput = false; + + // setup immune keys + g_immuneKeys = immune_keys_list(); + LOG((CLOG_DEBUG "Found %u immune keys in %s", g_immuneKeys.size(), ImmuneKeysPath.c_str())); + +#if NO_GRAB_KEYBOARD + // we only need the mouse hook + if (!g_hkMouse.set(WH_MOUSE_LL, &mouseLLHook, NULL, 0)) + return false; +#else + // we need both hooks. if either fails, discard the other + if (!g_hkMouse.set(WH_MOUSE_LL, &mouseLLHook, NULL, 0) || + !g_hkKeyboard.set(WH_KEYBOARD_LL, &keyboardLLHook, NULL, 0)) { + g_hkMouse.unset(); + g_hkKeyboard.unset(); + return false; + } +#endif + + return true; +} + +void +MSWindowsHook::uninstall() +{ + // discard old dead keys + g_deadVirtKey = 0; + g_deadLParam = 0; + + g_hkMouse.unset(); + g_hkKeyboard.unset(); + + uninstallScreenSaver(); +} + +static +LRESULT CALLBACK +getMessageHook(int code, WPARAM wParam, LPARAM lParam) +{ + if (code >= 0) { + MSG* msg = reinterpret_cast<MSG*>(lParam); + if (msg->message == WM_SYSCOMMAND && + msg->wParam == SC_SCREENSAVE) { + // broadcast screen saver started message + PostThreadMessage(g_threadID, + BARRIER_MSG_SCREEN_SAVER, TRUE, 0); + } + } + + return CallNextHookEx(g_hkMessage, code, wParam, lParam); +} + +bool +MSWindowsHook::installScreenSaver() +{ + // install hook unless it's already installed + if (g_hkMessage.is_set()) + return true; + return g_hkMessage.set(WH_GETMESSAGE, &getMessageHook, NULL, 0); +} + +void +MSWindowsHook::uninstallScreenSaver() +{ + g_hkMessage.unset(); +}
\ No newline at end of file diff --git a/src/lib/platform/MSWindowsHook.h b/src/lib/platform/MSWindowsHook.h new file mode 100644 index 0000000..7b2121c --- /dev/null +++ b/src/lib/platform/MSWindowsHook.h @@ -0,0 +1,39 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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/>. + */ + +#pragma once + +#include "platform/synwinhk.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +//! Loads and provides functions for the Windows hook +class MSWindowsHook +{ +public: + void setSides(UInt32 sides); + void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize); + void setMode(EHookMode mode); + + static bool install(); + static void uninstall(); + static bool installScreenSaver(); + static void uninstallScreenSaver(); +}; diff --git a/src/lib/platform/MSWindowsHookResource.cpp b/src/lib/platform/MSWindowsHookResource.cpp new file mode 100644 index 0000000..ced5ff1 --- /dev/null +++ b/src/lib/platform/MSWindowsHookResource.cpp @@ -0,0 +1,33 @@ +#include "MSWindowsHookResource.h" + +WindowsHookResource::WindowsHookResource() : + _hook(NULL) +{ +} + +WindowsHookResource::~WindowsHookResource() +{ + unset(); +} + +bool WindowsHookResource::set(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId) +{ + if (is_set()) + return false; + _hook = SetWindowsHookEx(idHook, lpfn, hmod, dwThreadId); + return is_set(); +} + +bool WindowsHookResource::unset() +{ + if (is_set()) { + if (UnhookWindowsHookEx(_hook) == 0) { + return false; + } + _hook = NULL; + } + return true; +} + +bool WindowsHookResource::is_set() const { return _hook != NULL; } +WindowsHookResource::operator HHOOK() const { return _hook; }
\ No newline at end of file diff --git a/src/lib/platform/MSWindowsHookResource.h b/src/lib/platform/MSWindowsHookResource.h new file mode 100644 index 0000000..b66c4b8 --- /dev/null +++ b/src/lib/platform/MSWindowsHookResource.h @@ -0,0 +1,20 @@ +#pragma once + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class WindowsHookResource +{ +public: + explicit WindowsHookResource(); + ~WindowsHookResource(); + + bool set(int idHook, HOOKPROC lpfn, HINSTANCE hmod, DWORD dwThreadId); + bool unset(); + + bool is_set() const; + operator HHOOK() const; + +private: + HHOOK _hook; +};
\ No newline at end of file diff --git a/src/lib/platform/MSWindowsKeyState.cpp b/src/lib/platform/MSWindowsKeyState.cpp new file mode 100644 index 0000000..2f29f72 --- /dev/null +++ b/src/lib/platform/MSWindowsKeyState.cpp @@ -0,0 +1,1406 @@ +/* + * 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 "platform/MSWindowsKeyState.h" + +#include "platform/MSWindowsDesks.h" +#include "mt/Thread.h" +#include "arch/win32/ArchMiscWindows.h" +#include "base/FunctionJob.h" +#include "base/Log.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +// extended mouse buttons +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// +// MSWindowsKeyState +// + +// map virtual keys to barrier key enumeration +const KeyID MSWindowsKeyState::s_virtualKey[] = +{ + /* 0x000 */ { kKeyNone }, // reserved + /* 0x001 */ { kKeyNone }, // VK_LBUTTON + /* 0x002 */ { kKeyNone }, // VK_RBUTTON + /* 0x003 */ { kKeyNone }, // VK_CANCEL + /* 0x004 */ { kKeyNone }, // VK_MBUTTON + /* 0x005 */ { kKeyNone }, // VK_XBUTTON1 + /* 0x006 */ { kKeyNone }, // VK_XBUTTON2 + /* 0x007 */ { kKeyNone }, // undefined + /* 0x008 */ { kKeyBackSpace }, // VK_BACK + /* 0x009 */ { kKeyTab }, // VK_TAB + /* 0x00a */ { kKeyNone }, // undefined + /* 0x00b */ { kKeyNone }, // undefined + /* 0x00c */ { kKeyClear }, // VK_CLEAR + /* 0x00d */ { kKeyReturn }, // VK_RETURN + /* 0x00e */ { kKeyNone }, // undefined + /* 0x00f */ { kKeyNone }, // undefined + /* 0x010 */ { kKeyShift_L }, // VK_SHIFT + /* 0x011 */ { kKeyControl_L }, // VK_CONTROL + /* 0x012 */ { kKeyAlt_L }, // VK_MENU + /* 0x013 */ { kKeyPause }, // VK_PAUSE + /* 0x014 */ { kKeyCapsLock }, // VK_CAPITAL + /* 0x015 */ { kKeyKana }, // VK_HANGUL, VK_KANA + /* 0x016 */ { kKeyNone }, // undefined + /* 0x017 */ { kKeyNone }, // VK_JUNJA + /* 0x018 */ { kKeyNone }, // VK_FINAL + /* 0x019 */ { kKeyKanzi }, // VK_HANJA, VK_KANJI + /* 0x01a */ { kKeyNone }, // undefined + /* 0x01b */ { kKeyEscape }, // VK_ESCAPE + /* 0x01c */ { kKeyHenkan }, // VK_CONVERT + /* 0x01d */ { kKeyNone }, // VK_NONCONVERT + /* 0x01e */ { kKeyNone }, // VK_ACCEPT + /* 0x01f */ { kKeyNone }, // VK_MODECHANGE + /* 0x020 */ { kKeyNone }, // VK_SPACE + /* 0x021 */ { kKeyKP_PageUp }, // VK_PRIOR + /* 0x022 */ { kKeyKP_PageDown },// VK_NEXT + /* 0x023 */ { kKeyKP_End }, // VK_END + /* 0x024 */ { kKeyKP_Home }, // VK_HOME + /* 0x025 */ { kKeyKP_Left }, // VK_LEFT + /* 0x026 */ { kKeyKP_Up }, // VK_UP + /* 0x027 */ { kKeyKP_Right }, // VK_RIGHT + /* 0x028 */ { kKeyKP_Down }, // VK_DOWN + /* 0x029 */ { kKeySelect }, // VK_SELECT + /* 0x02a */ { kKeyNone }, // VK_PRINT + /* 0x02b */ { kKeyExecute }, // VK_EXECUTE + /* 0x02c */ { kKeyPrint }, // VK_SNAPSHOT + /* 0x02d */ { kKeyKP_Insert }, // VK_INSERT + /* 0x02e */ { kKeyKP_Delete }, // VK_DELETE + /* 0x02f */ { kKeyHelp }, // VK_HELP + /* 0x030 */ { kKeyNone }, // VK_0 + /* 0x031 */ { kKeyNone }, // VK_1 + /* 0x032 */ { kKeyNone }, // VK_2 + /* 0x033 */ { kKeyNone }, // VK_3 + /* 0x034 */ { kKeyNone }, // VK_4 + /* 0x035 */ { kKeyNone }, // VK_5 + /* 0x036 */ { kKeyNone }, // VK_6 + /* 0x037 */ { kKeyNone }, // VK_7 + /* 0x038 */ { kKeyNone }, // VK_8 + /* 0x039 */ { kKeyNone }, // VK_9 + /* 0x03a */ { kKeyNone }, // undefined + /* 0x03b */ { kKeyNone }, // undefined + /* 0x03c */ { kKeyNone }, // undefined + /* 0x03d */ { kKeyNone }, // undefined + /* 0x03e */ { kKeyNone }, // undefined + /* 0x03f */ { kKeyNone }, // undefined + /* 0x040 */ { kKeyNone }, // undefined + /* 0x041 */ { kKeyNone }, // VK_A + /* 0x042 */ { kKeyNone }, // VK_B + /* 0x043 */ { kKeyNone }, // VK_C + /* 0x044 */ { kKeyNone }, // VK_D + /* 0x045 */ { kKeyNone }, // VK_E + /* 0x046 */ { kKeyNone }, // VK_F + /* 0x047 */ { kKeyNone }, // VK_G + /* 0x048 */ { kKeyNone }, // VK_H + /* 0x049 */ { kKeyNone }, // VK_I + /* 0x04a */ { kKeyNone }, // VK_J + /* 0x04b */ { kKeyNone }, // VK_K + /* 0x04c */ { kKeyNone }, // VK_L + /* 0x04d */ { kKeyNone }, // VK_M + /* 0x04e */ { kKeyNone }, // VK_N + /* 0x04f */ { kKeyNone }, // VK_O + /* 0x050 */ { kKeyNone }, // VK_P + /* 0x051 */ { kKeyNone }, // VK_Q + /* 0x052 */ { kKeyNone }, // VK_R + /* 0x053 */ { kKeyNone }, // VK_S + /* 0x054 */ { kKeyNone }, // VK_T + /* 0x055 */ { kKeyNone }, // VK_U + /* 0x056 */ { kKeyNone }, // VK_V + /* 0x057 */ { kKeyNone }, // VK_W + /* 0x058 */ { kKeyNone }, // VK_X + /* 0x059 */ { kKeyNone }, // VK_Y + /* 0x05a */ { kKeyNone }, // VK_Z + /* 0x05b */ { kKeySuper_L }, // VK_LWIN + /* 0x05c */ { kKeySuper_R }, // VK_RWIN + /* 0x05d */ { kKeyMenu }, // VK_APPS + /* 0x05e */ { kKeyNone }, // undefined + /* 0x05f */ { kKeySleep }, // VK_SLEEP + /* 0x060 */ { kKeyKP_0 }, // VK_NUMPAD0 + /* 0x061 */ { kKeyKP_1 }, // VK_NUMPAD1 + /* 0x062 */ { kKeyKP_2 }, // VK_NUMPAD2 + /* 0x063 */ { kKeyKP_3 }, // VK_NUMPAD3 + /* 0x064 */ { kKeyKP_4 }, // VK_NUMPAD4 + /* 0x065 */ { kKeyKP_5 }, // VK_NUMPAD5 + /* 0x066 */ { kKeyKP_6 }, // VK_NUMPAD6 + /* 0x067 */ { kKeyKP_7 }, // VK_NUMPAD7 + /* 0x068 */ { kKeyKP_8 }, // VK_NUMPAD8 + /* 0x069 */ { kKeyKP_9 }, // VK_NUMPAD9 + /* 0x06a */ { kKeyKP_Multiply },// VK_MULTIPLY + /* 0x06b */ { kKeyKP_Add }, // VK_ADD + /* 0x06c */ { kKeyKP_Separator },// VK_SEPARATOR + /* 0x06d */ { kKeyKP_Subtract },// VK_SUBTRACT + /* 0x06e */ { kKeyKP_Decimal }, // VK_DECIMAL + /* 0x06f */ { kKeyNone }, // VK_DIVIDE + /* 0x070 */ { kKeyF1 }, // VK_F1 + /* 0x071 */ { kKeyF2 }, // VK_F2 + /* 0x072 */ { kKeyF3 }, // VK_F3 + /* 0x073 */ { kKeyF4 }, // VK_F4 + /* 0x074 */ { kKeyF5 }, // VK_F5 + /* 0x075 */ { kKeyF6 }, // VK_F6 + /* 0x076 */ { kKeyF7 }, // VK_F7 + /* 0x077 */ { kKeyF8 }, // VK_F8 + /* 0x078 */ { kKeyF9 }, // VK_F9 + /* 0x079 */ { kKeyF10 }, // VK_F10 + /* 0x07a */ { kKeyF11 }, // VK_F11 + /* 0x07b */ { kKeyF12 }, // VK_F12 + /* 0x07c */ { kKeyF13 }, // VK_F13 + /* 0x07d */ { kKeyF14 }, // VK_F14 + /* 0x07e */ { kKeyF15 }, // VK_F15 + /* 0x07f */ { kKeyF16 }, // VK_F16 + /* 0x080 */ { kKeyF17 }, // VK_F17 + /* 0x081 */ { kKeyF18 }, // VK_F18 + /* 0x082 */ { kKeyF19 }, // VK_F19 + /* 0x083 */ { kKeyF20 }, // VK_F20 + /* 0x084 */ { kKeyF21 }, // VK_F21 + /* 0x085 */ { kKeyF22 }, // VK_F22 + /* 0x086 */ { kKeyF23 }, // VK_F23 + /* 0x087 */ { kKeyF24 }, // VK_F24 + /* 0x088 */ { kKeyNone }, // unassigned + /* 0x089 */ { kKeyNone }, // unassigned + /* 0x08a */ { kKeyNone }, // unassigned + /* 0x08b */ { kKeyNone }, // unassigned + /* 0x08c */ { kKeyNone }, // unassigned + /* 0x08d */ { kKeyNone }, // unassigned + /* 0x08e */ { kKeyNone }, // unassigned + /* 0x08f */ { kKeyNone }, // unassigned + /* 0x090 */ { kKeyNumLock }, // VK_NUMLOCK + /* 0x091 */ { kKeyScrollLock }, // VK_SCROLL + /* 0x092 */ { kKeyNone }, // unassigned + /* 0x093 */ { kKeyNone }, // unassigned + /* 0x094 */ { kKeyNone }, // unassigned + /* 0x095 */ { kKeyNone }, // unassigned + /* 0x096 */ { kKeyNone }, // unassigned + /* 0x097 */ { kKeyNone }, // unassigned + /* 0x098 */ { kKeyNone }, // unassigned + /* 0x099 */ { kKeyNone }, // unassigned + /* 0x09a */ { kKeyNone }, // unassigned + /* 0x09b */ { kKeyNone }, // unassigned + /* 0x09c */ { kKeyNone }, // unassigned + /* 0x09d */ { kKeyNone }, // unassigned + /* 0x09e */ { kKeyNone }, // unassigned + /* 0x09f */ { kKeyNone }, // unassigned + /* 0x0a0 */ { kKeyShift_L }, // VK_LSHIFT + /* 0x0a1 */ { kKeyShift_R }, // VK_RSHIFT + /* 0x0a2 */ { kKeyControl_L }, // VK_LCONTROL + /* 0x0a3 */ { kKeyControl_R }, // VK_RCONTROL + /* 0x0a4 */ { kKeyAlt_L }, // VK_LMENU + /* 0x0a5 */ { kKeyAlt_R }, // VK_RMENU + /* 0x0a6 */ { kKeyNone }, // VK_BROWSER_BACK + /* 0x0a7 */ { kKeyNone }, // VK_BROWSER_FORWARD + /* 0x0a8 */ { kKeyNone }, // VK_BROWSER_REFRESH + /* 0x0a9 */ { kKeyNone }, // VK_BROWSER_STOP + /* 0x0aa */ { kKeyNone }, // VK_BROWSER_SEARCH + /* 0x0ab */ { kKeyNone }, // VK_BROWSER_FAVORITES + /* 0x0ac */ { kKeyNone }, // VK_BROWSER_HOME + /* 0x0ad */ { kKeyNone }, // VK_VOLUME_MUTE + /* 0x0ae */ { kKeyNone }, // VK_VOLUME_DOWN + /* 0x0af */ { kKeyNone }, // VK_VOLUME_UP + /* 0x0b0 */ { kKeyNone }, // VK_MEDIA_NEXT_TRACK + /* 0x0b1 */ { kKeyNone }, // VK_MEDIA_PREV_TRACK + /* 0x0b2 */ { kKeyNone }, // VK_MEDIA_STOP + /* 0x0b3 */ { kKeyNone }, // VK_MEDIA_PLAY_PAUSE + /* 0x0b4 */ { kKeyNone }, // VK_LAUNCH_MAIL + /* 0x0b5 */ { kKeyNone }, // VK_LAUNCH_MEDIA_SELECT + /* 0x0b6 */ { kKeyNone }, // VK_LAUNCH_APP1 + /* 0x0b7 */ { kKeyNone }, // VK_LAUNCH_APP2 + /* 0x0b8 */ { kKeyNone }, // unassigned + /* 0x0b9 */ { kKeyNone }, // unassigned + /* 0x0ba */ { kKeyNone }, // OEM specific + /* 0x0bb */ { kKeyNone }, // OEM specific + /* 0x0bc */ { kKeyNone }, // OEM specific + /* 0x0bd */ { kKeyNone }, // OEM specific + /* 0x0be */ { kKeyNone }, // OEM specific + /* 0x0bf */ { kKeyNone }, // OEM specific + /* 0x0c0 */ { kKeyNone }, // OEM specific + /* 0x0c1 */ { kKeyNone }, // unassigned + /* 0x0c2 */ { kKeyNone }, // unassigned + /* 0x0c3 */ { kKeyNone }, // unassigned + /* 0x0c4 */ { kKeyNone }, // unassigned + /* 0x0c5 */ { kKeyNone }, // unassigned + /* 0x0c6 */ { kKeyNone }, // unassigned + /* 0x0c7 */ { kKeyNone }, // unassigned + /* 0x0c8 */ { kKeyNone }, // unassigned + /* 0x0c9 */ { kKeyNone }, // unassigned + /* 0x0ca */ { kKeyNone }, // unassigned + /* 0x0cb */ { kKeyNone }, // unassigned + /* 0x0cc */ { kKeyNone }, // unassigned + /* 0x0cd */ { kKeyNone }, // unassigned + /* 0x0ce */ { kKeyNone }, // unassigned + /* 0x0cf */ { kKeyNone }, // unassigned + /* 0x0d0 */ { kKeyNone }, // unassigned + /* 0x0d1 */ { kKeyNone }, // unassigned + /* 0x0d2 */ { kKeyNone }, // unassigned + /* 0x0d3 */ { kKeyNone }, // unassigned + /* 0x0d4 */ { kKeyNone }, // unassigned + /* 0x0d5 */ { kKeyNone }, // unassigned + /* 0x0d6 */ { kKeyNone }, // unassigned + /* 0x0d7 */ { kKeyNone }, // unassigned + /* 0x0d8 */ { kKeyNone }, // unassigned + /* 0x0d9 */ { kKeyNone }, // unassigned + /* 0x0da */ { kKeyNone }, // unassigned + /* 0x0db */ { kKeyNone }, // OEM specific + /* 0x0dc */ { kKeyNone }, // OEM specific + /* 0x0dd */ { kKeyNone }, // OEM specific + /* 0x0de */ { kKeyNone }, // OEM specific + /* 0x0df */ { kKeyNone }, // OEM specific + /* 0x0e0 */ { kKeyNone }, // OEM specific + /* 0x0e1 */ { kKeyNone }, // OEM specific + /* 0x0e2 */ { kKeyNone }, // OEM specific + /* 0x0e3 */ { kKeyNone }, // OEM specific + /* 0x0e4 */ { kKeyNone }, // OEM specific + /* 0x0e5 */ { kKeyNone }, // unassigned + /* 0x0e6 */ { kKeyNone }, // OEM specific + /* 0x0e7 */ { kKeyNone }, // unassigned + /* 0x0e8 */ { kKeyNone }, // unassigned + /* 0x0e9 */ { kKeyNone }, // OEM specific + /* 0x0ea */ { kKeyNone }, // OEM specific + /* 0x0eb */ { kKeyNone }, // OEM specific + /* 0x0ec */ { kKeyNone }, // OEM specific + /* 0x0ed */ { kKeyNone }, // OEM specific + /* 0x0ee */ { kKeyNone }, // OEM specific + /* 0x0ef */ { kKeyNone }, // OEM specific + /* 0x0f0 */ { kKeyNone }, // OEM specific + /* 0x0f1 */ { kKeyNone }, // OEM specific + /* 0x0f2 */ { kKeyHiraganaKatakana }, // VK_OEM_COPY + /* 0x0f3 */ { kKeyZenkaku }, // VK_OEM_AUTO + /* 0x0f4 */ { kKeyZenkaku }, // VK_OEM_ENLW + /* 0x0f5 */ { kKeyNone }, // OEM specific + /* 0x0f6 */ { kKeyNone }, // VK_ATTN + /* 0x0f7 */ { kKeyNone }, // VK_CRSEL + /* 0x0f8 */ { kKeyNone }, // VK_EXSEL + /* 0x0f9 */ { kKeyNone }, // VK_EREOF + /* 0x0fa */ { kKeyNone }, // VK_PLAY + /* 0x0fb */ { kKeyNone }, // VK_ZOOM + /* 0x0fc */ { kKeyNone }, // reserved + /* 0x0fd */ { kKeyNone }, // VK_PA1 + /* 0x0fe */ { kKeyNone }, // VK_OEM_CLEAR + /* 0x0ff */ { kKeyNone }, // reserved + + /* 0x100 */ { kKeyNone }, // reserved + /* 0x101 */ { kKeyNone }, // VK_LBUTTON + /* 0x102 */ { kKeyNone }, // VK_RBUTTON + /* 0x103 */ { kKeyBreak }, // VK_CANCEL + /* 0x104 */ { kKeyNone }, // VK_MBUTTON + /* 0x105 */ { kKeyNone }, // VK_XBUTTON1 + /* 0x106 */ { kKeyNone }, // VK_XBUTTON2 + /* 0x107 */ { kKeyNone }, // undefined + /* 0x108 */ { kKeyNone }, // VK_BACK + /* 0x109 */ { kKeyNone }, // VK_TAB + /* 0x10a */ { kKeyNone }, // undefined + /* 0x10b */ { kKeyNone }, // undefined + /* 0x10c */ { kKeyClear }, // VK_CLEAR + /* 0x10d */ { kKeyKP_Enter }, // VK_RETURN + /* 0x10e */ { kKeyNone }, // undefined + /* 0x10f */ { kKeyNone }, // undefined + /* 0x110 */ { kKeyShift_R }, // VK_SHIFT + /* 0x111 */ { kKeyControl_R }, // VK_CONTROL + /* 0x112 */ { kKeyAlt_R }, // VK_MENU + /* 0x113 */ { kKeyNone }, // VK_PAUSE + /* 0x114 */ { kKeyNone }, // VK_CAPITAL + /* 0x115 */ { kKeyHangul }, // VK_HANGUL + /* 0x116 */ { kKeyNone }, // undefined + /* 0x117 */ { kKeyNone }, // VK_JUNJA + /* 0x118 */ { kKeyNone }, // VK_FINAL + /* 0x119 */ { kKeyHanja }, // VK_HANJA + /* 0x11a */ { kKeyNone }, // undefined + /* 0x11b */ { kKeyNone }, // VK_ESCAPE + /* 0x11c */ { kKeyNone }, // VK_CONVERT + /* 0x11d */ { kKeyNone }, // VK_NONCONVERT + /* 0x11e */ { kKeyNone }, // VK_ACCEPT + /* 0x11f */ { kKeyNone }, // VK_MODECHANGE + /* 0x120 */ { kKeyNone }, // VK_SPACE + /* 0x121 */ { kKeyPageUp }, // VK_PRIOR + /* 0x122 */ { kKeyPageDown }, // VK_NEXT + /* 0x123 */ { kKeyEnd }, // VK_END + /* 0x124 */ { kKeyHome }, // VK_HOME + /* 0x125 */ { kKeyLeft }, // VK_LEFT + /* 0x126 */ { kKeyUp }, // VK_UP + /* 0x127 */ { kKeyRight }, // VK_RIGHT + /* 0x128 */ { kKeyDown }, // VK_DOWN + /* 0x129 */ { kKeySelect }, // VK_SELECT + /* 0x12a */ { kKeyNone }, // VK_PRINT + /* 0x12b */ { kKeyExecute }, // VK_EXECUTE + /* 0x12c */ { kKeyPrint }, // VK_SNAPSHOT + /* 0x12d */ { kKeyInsert }, // VK_INSERT + /* 0x12e */ { kKeyDelete }, // VK_DELETE + /* 0x12f */ { kKeyHelp }, // VK_HELP + /* 0x130 */ { kKeyNone }, // VK_0 + /* 0x131 */ { kKeyNone }, // VK_1 + /* 0x132 */ { kKeyNone }, // VK_2 + /* 0x133 */ { kKeyNone }, // VK_3 + /* 0x134 */ { kKeyNone }, // VK_4 + /* 0x135 */ { kKeyNone }, // VK_5 + /* 0x136 */ { kKeyNone }, // VK_6 + /* 0x137 */ { kKeyNone }, // VK_7 + /* 0x138 */ { kKeyNone }, // VK_8 + /* 0x139 */ { kKeyNone }, // VK_9 + /* 0x13a */ { kKeyNone }, // undefined + /* 0x13b */ { kKeyNone }, // undefined + /* 0x13c */ { kKeyNone }, // undefined + /* 0x13d */ { kKeyNone }, // undefined + /* 0x13e */ { kKeyNone }, // undefined + /* 0x13f */ { kKeyNone }, // undefined + /* 0x140 */ { kKeyNone }, // undefined + /* 0x141 */ { kKeyNone }, // VK_A + /* 0x142 */ { kKeyNone }, // VK_B + /* 0x143 */ { kKeyNone }, // VK_C + /* 0x144 */ { kKeyNone }, // VK_D + /* 0x145 */ { kKeyNone }, // VK_E + /* 0x146 */ { kKeyNone }, // VK_F + /* 0x147 */ { kKeyNone }, // VK_G + /* 0x148 */ { kKeyNone }, // VK_H + /* 0x149 */ { kKeyNone }, // VK_I + /* 0x14a */ { kKeyNone }, // VK_J + /* 0x14b */ { kKeyNone }, // VK_K + /* 0x14c */ { kKeyNone }, // VK_L + /* 0x14d */ { kKeyNone }, // VK_M + /* 0x14e */ { kKeyNone }, // VK_N + /* 0x14f */ { kKeyNone }, // VK_O + /* 0x150 */ { kKeyNone }, // VK_P + /* 0x151 */ { kKeyNone }, // VK_Q + /* 0x152 */ { kKeyNone }, // VK_R + /* 0x153 */ { kKeyNone }, // VK_S + /* 0x154 */ { kKeyNone }, // VK_T + /* 0x155 */ { kKeyNone }, // VK_U + /* 0x156 */ { kKeyNone }, // VK_V + /* 0x157 */ { kKeyNone }, // VK_W + /* 0x158 */ { kKeyNone }, // VK_X + /* 0x159 */ { kKeyNone }, // VK_Y + /* 0x15a */ { kKeyNone }, // VK_Z + /* 0x15b */ { kKeySuper_L }, // VK_LWIN + /* 0x15c */ { kKeySuper_R }, // VK_RWIN + /* 0x15d */ { kKeyMenu }, // VK_APPS + /* 0x15e */ { kKeyNone }, // undefined + /* 0x15f */ { kKeyNone }, // VK_SLEEP + /* 0x160 */ { kKeyNone }, // VK_NUMPAD0 + /* 0x161 */ { kKeyNone }, // VK_NUMPAD1 + /* 0x162 */ { kKeyNone }, // VK_NUMPAD2 + /* 0x163 */ { kKeyNone }, // VK_NUMPAD3 + /* 0x164 */ { kKeyNone }, // VK_NUMPAD4 + /* 0x165 */ { kKeyNone }, // VK_NUMPAD5 + /* 0x166 */ { kKeyNone }, // VK_NUMPAD6 + /* 0x167 */ { kKeyNone }, // VK_NUMPAD7 + /* 0x168 */ { kKeyNone }, // VK_NUMPAD8 + /* 0x169 */ { kKeyNone }, // VK_NUMPAD9 + /* 0x16a */ { kKeyNone }, // VK_MULTIPLY + /* 0x16b */ { kKeyNone }, // VK_ADD + /* 0x16c */ { kKeyKP_Separator },// VK_SEPARATOR + /* 0x16d */ { kKeyNone }, // VK_SUBTRACT + /* 0x16e */ { kKeyNone }, // VK_DECIMAL + /* 0x16f */ { kKeyKP_Divide }, // VK_DIVIDE + /* 0x170 */ { kKeyNone }, // VK_F1 + /* 0x171 */ { kKeyNone }, // VK_F2 + /* 0x172 */ { kKeyNone }, // VK_F3 + /* 0x173 */ { kKeyNone }, // VK_F4 + /* 0x174 */ { kKeyNone }, // VK_F5 + /* 0x175 */ { kKeyNone }, // VK_F6 + /* 0x176 */ { kKeyNone }, // VK_F7 + /* 0x177 */ { kKeyNone }, // VK_F8 + /* 0x178 */ { kKeyNone }, // VK_F9 + /* 0x179 */ { kKeyNone }, // VK_F10 + /* 0x17a */ { kKeyNone }, // VK_F11 + /* 0x17b */ { kKeyNone }, // VK_F12 + /* 0x17c */ { kKeyF13 }, // VK_F13 + /* 0x17d */ { kKeyF14 }, // VK_F14 + /* 0x17e */ { kKeyF15 }, // VK_F15 + /* 0x17f */ { kKeyF16 }, // VK_F16 + /* 0x180 */ { kKeyF17 }, // VK_F17 + /* 0x181 */ { kKeyF18 }, // VK_F18 + /* 0x182 */ { kKeyF19 }, // VK_F19 + /* 0x183 */ { kKeyF20 }, // VK_F20 + /* 0x184 */ { kKeyF21 }, // VK_F21 + /* 0x185 */ { kKeyF22 }, // VK_F22 + /* 0x186 */ { kKeyF23 }, // VK_F23 + /* 0x187 */ { kKeyF24 }, // VK_F24 + /* 0x188 */ { kKeyNone }, // unassigned + /* 0x189 */ { kKeyNone }, // unassigned + /* 0x18a */ { kKeyNone }, // unassigned + /* 0x18b */ { kKeyNone }, // unassigned + /* 0x18c */ { kKeyNone }, // unassigned + /* 0x18d */ { kKeyNone }, // unassigned + /* 0x18e */ { kKeyNone }, // unassigned + /* 0x18f */ { kKeyNone }, // unassigned + /* 0x190 */ { kKeyNumLock }, // VK_NUMLOCK + /* 0x191 */ { kKeyNone }, // VK_SCROLL + /* 0x192 */ { kKeyNone }, // unassigned + /* 0x193 */ { kKeyNone }, // unassigned + /* 0x194 */ { kKeyNone }, // unassigned + /* 0x195 */ { kKeyNone }, // unassigned + /* 0x196 */ { kKeyNone }, // unassigned + /* 0x197 */ { kKeyNone }, // unassigned + /* 0x198 */ { kKeyNone }, // unassigned + /* 0x199 */ { kKeyNone }, // unassigned + /* 0x19a */ { kKeyNone }, // unassigned + /* 0x19b */ { kKeyNone }, // unassigned + /* 0x19c */ { kKeyNone }, // unassigned + /* 0x19d */ { kKeyNone }, // unassigned + /* 0x19e */ { kKeyNone }, // unassigned + /* 0x19f */ { kKeyNone }, // unassigned + /* 0x1a0 */ { kKeyShift_L }, // VK_LSHIFT + /* 0x1a1 */ { kKeyShift_R }, // VK_RSHIFT + /* 0x1a2 */ { kKeyControl_L }, // VK_LCONTROL + /* 0x1a3 */ { kKeyControl_R }, // VK_RCONTROL + /* 0x1a4 */ { kKeyAlt_L }, // VK_LMENU + /* 0x1a5 */ { kKeyAlt_R }, // VK_RMENU + /* 0x1a6 */ { kKeyWWWBack }, // VK_BROWSER_BACK + /* 0x1a7 */ { kKeyWWWForward }, // VK_BROWSER_FORWARD + /* 0x1a8 */ { kKeyWWWRefresh }, // VK_BROWSER_REFRESH + /* 0x1a9 */ { kKeyWWWStop }, // VK_BROWSER_STOP + /* 0x1aa */ { kKeyWWWSearch }, // VK_BROWSER_SEARCH + /* 0x1ab */ { kKeyWWWFavorites },// VK_BROWSER_FAVORITES + /* 0x1ac */ { kKeyWWWHome }, // VK_BROWSER_HOME + /* 0x1ad */ { kKeyAudioMute }, // VK_VOLUME_MUTE + /* 0x1ae */ { kKeyAudioDown }, // VK_VOLUME_DOWN + /* 0x1af */ { kKeyAudioUp }, // VK_VOLUME_UP + /* 0x1b0 */ { kKeyAudioNext }, // VK_MEDIA_NEXT_TRACK + /* 0x1b1 */ { kKeyAudioPrev }, // VK_MEDIA_PREV_TRACK + /* 0x1b2 */ { kKeyAudioStop }, // VK_MEDIA_STOP + /* 0x1b3 */ { kKeyAudioPlay }, // VK_MEDIA_PLAY_PAUSE + /* 0x1b4 */ { kKeyAppMail }, // VK_LAUNCH_MAIL + /* 0x1b5 */ { kKeyAppMedia }, // VK_LAUNCH_MEDIA_SELECT + /* 0x1b6 */ { kKeyAppUser1 }, // VK_LAUNCH_APP1 + /* 0x1b7 */ { kKeyAppUser2 }, // VK_LAUNCH_APP2 + /* 0x1b8 */ { kKeyNone }, // unassigned + /* 0x1b9 */ { kKeyNone }, // unassigned + /* 0x1ba */ { kKeyNone }, // OEM specific + /* 0x1bb */ { kKeyNone }, // OEM specific + /* 0x1bc */ { kKeyNone }, // OEM specific + /* 0x1bd */ { kKeyNone }, // OEM specific + /* 0x1be */ { kKeyNone }, // OEM specific + /* 0x1bf */ { kKeyNone }, // OEM specific + /* 0x1c0 */ { kKeyNone }, // OEM specific + /* 0x1c1 */ { kKeyNone }, // unassigned + /* 0x1c2 */ { kKeyNone }, // unassigned + /* 0x1c3 */ { kKeyNone }, // unassigned + /* 0x1c4 */ { kKeyNone }, // unassigned + /* 0x1c5 */ { kKeyNone }, // unassigned + /* 0x1c6 */ { kKeyNone }, // unassigned + /* 0x1c7 */ { kKeyNone }, // unassigned + /* 0x1c8 */ { kKeyNone }, // unassigned + /* 0x1c9 */ { kKeyNone }, // unassigned + /* 0x1ca */ { kKeyNone }, // unassigned + /* 0x1cb */ { kKeyNone }, // unassigned + /* 0x1cc */ { kKeyNone }, // unassigned + /* 0x1cd */ { kKeyNone }, // unassigned + /* 0x1ce */ { kKeyNone }, // unassigned + /* 0x1cf */ { kKeyNone }, // unassigned + /* 0x1d0 */ { kKeyNone }, // unassigned + /* 0x1d1 */ { kKeyNone }, // unassigned + /* 0x1d2 */ { kKeyNone }, // unassigned + /* 0x1d3 */ { kKeyNone }, // unassigned + /* 0x1d4 */ { kKeyNone }, // unassigned + /* 0x1d5 */ { kKeyNone }, // unassigned + /* 0x1d6 */ { kKeyNone }, // unassigned + /* 0x1d7 */ { kKeyNone }, // unassigned + /* 0x1d8 */ { kKeyNone }, // unassigned + /* 0x1d9 */ { kKeyNone }, // unassigned + /* 0x1da */ { kKeyNone }, // unassigned + /* 0x1db */ { kKeyNone }, // OEM specific + /* 0x1dc */ { kKeyNone }, // OEM specific + /* 0x1dd */ { kKeyNone }, // OEM specific + /* 0x1de */ { kKeyNone }, // OEM specific + /* 0x1df */ { kKeyNone }, // OEM specific + /* 0x1e0 */ { kKeyNone }, // OEM specific + /* 0x1e1 */ { kKeyNone }, // OEM specific + /* 0x1e2 */ { kKeyNone }, // OEM specific + /* 0x1e3 */ { kKeyNone }, // OEM specific + /* 0x1e4 */ { kKeyNone }, // OEM specific + /* 0x1e5 */ { kKeyNone }, // unassigned + /* 0x1e6 */ { kKeyNone }, // OEM specific + /* 0x1e7 */ { kKeyNone }, // unassigned + /* 0x1e8 */ { kKeyNone }, // unassigned + /* 0x1e9 */ { kKeyNone }, // OEM specific + /* 0x1ea */ { kKeyNone }, // OEM specific + /* 0x1eb */ { kKeyNone }, // OEM specific + /* 0x1ec */ { kKeyNone }, // OEM specific + /* 0x1ed */ { kKeyNone }, // OEM specific + /* 0x1ee */ { kKeyNone }, // OEM specific + /* 0x1ef */ { kKeyNone }, // OEM specific + /* 0x1f0 */ { kKeyNone }, // OEM specific + /* 0x1f1 */ { kKeyNone }, // OEM specific + /* 0x1f2 */ { kKeyNone }, // VK_OEM_COPY + /* 0x1f3 */ { kKeyNone }, // VK_OEM_AUTO + /* 0x1f4 */ { kKeyNone }, // VK_OEM_ENLW + /* 0x1f5 */ { kKeyNone }, // OEM specific + /* 0x1f6 */ { kKeyNone }, // VK_ATTN + /* 0x1f7 */ { kKeyNone }, // VK_CRSEL + /* 0x1f8 */ { kKeyNone }, // VK_EXSEL + /* 0x1f9 */ { kKeyNone }, // VK_EREOF + /* 0x1fa */ { kKeyNone }, // VK_PLAY + /* 0x1fb */ { kKeyNone }, // VK_ZOOM + /* 0x1fc */ { kKeyNone }, // reserved + /* 0x1fd */ { kKeyNone }, // VK_PA1 + /* 0x1fe */ { kKeyNone }, // VK_OEM_CLEAR + /* 0x1ff */ { kKeyNone } // reserved +}; + +struct Win32Modifiers { +public: + UINT m_vk; + KeyModifierMask m_mask; +}; + +static const Win32Modifiers s_modifiers[] = +{ + { VK_SHIFT, KeyModifierShift }, + { VK_LSHIFT, KeyModifierShift }, + { VK_RSHIFT, KeyModifierShift }, + { VK_CONTROL, KeyModifierControl }, + { VK_LCONTROL, KeyModifierControl }, + { VK_RCONTROL, KeyModifierControl }, + { VK_MENU, KeyModifierAlt }, + { VK_LMENU, KeyModifierAlt }, + { VK_RMENU, KeyModifierAlt }, + { VK_LWIN, KeyModifierSuper }, + { VK_RWIN, KeyModifierSuper } +}; + +MSWindowsKeyState::MSWindowsKeyState( + MSWindowsDesks* desks, void* eventTarget, IEventQueue* events) : + KeyState(events), + m_eventTarget(eventTarget), + m_desks(desks), + m_keyLayout(GetKeyboardLayout(0)), + m_fixTimer(NULL), + m_lastDown(0), + m_useSavedModifiers(false), + m_savedModifiers(0), + m_originalSavedModifiers(0), + m_events(events) +{ + init(); +} + +MSWindowsKeyState::MSWindowsKeyState( + MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, barrier::KeyMap& keyMap) : + KeyState(events, keyMap), + m_eventTarget(eventTarget), + m_desks(desks), + m_keyLayout(GetKeyboardLayout(0)), + m_fixTimer(NULL), + m_lastDown(0), + m_useSavedModifiers(false), + m_savedModifiers(0), + m_originalSavedModifiers(0), + m_events(events) +{ + init(); +} + +MSWindowsKeyState::~MSWindowsKeyState() +{ + disable(); +} + +void +MSWindowsKeyState::init() +{ + // look up symbol that's available on winNT family but not win95 + HMODULE userModule = GetModuleHandle("user32.dll"); + m_ToUnicodeEx = (ToUnicodeEx_t)GetProcAddress(userModule, "ToUnicodeEx"); +} + +void +MSWindowsKeyState::disable() +{ + if (m_fixTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_fixTimer); + m_events->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + m_lastDown = 0; +} + +KeyButton +MSWindowsKeyState::virtualKeyToButton(UINT virtualKey) const +{ + return m_virtualKeyToButton[virtualKey & 0xffu]; +} + +void +MSWindowsKeyState::setKeyLayout(HKL keyLayout) +{ + m_keyLayout = keyLayout; +} + +bool +MSWindowsKeyState::testAutoRepeat(bool press, bool isRepeat, KeyButton button) +{ + if (!isRepeat) { + isRepeat = (press && m_lastDown != 0 && button == m_lastDown); + } + if (press) { + m_lastDown = button; + } + else { + m_lastDown = 0; + } + return isRepeat; +} + +void +MSWindowsKeyState::saveModifiers() +{ + m_savedModifiers = getActiveModifiers(); + m_originalSavedModifiers = m_savedModifiers; +} + +void +MSWindowsKeyState::useSavedModifiers(bool enable) +{ + if (enable != m_useSavedModifiers) { + m_useSavedModifiers = enable; + if (!m_useSavedModifiers) { + // transfer any modifier state changes to KeyState's state + KeyModifierMask mask = m_originalSavedModifiers ^ m_savedModifiers; + getActiveModifiersRValue() = + (getActiveModifiers() & ~mask) | (m_savedModifiers & mask); + } + } +} + +KeyID +MSWindowsKeyState::mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const +{ + static const KeyModifierMask s_controlAlt = + KeyModifierControl | KeyModifierAlt; + + // extract character, virtual key, and if we didn't use AltGr + char c = (char)((charAndVirtKey & 0xff00u) >> 8); + UINT vkCode = (charAndVirtKey & 0xffu); + bool noAltGr = ((charAndVirtKey & 0xff0000u) != 0); + + // handle some keys via table lookup + KeyID id = getKeyID(vkCode, (KeyButton)((info >> 16) & 0x1ffu)); + + // check if not in table; map character to key id + if (id == kKeyNone && c != 0) { + if ((c & 0x80u) == 0) { + // ASCII + id = static_cast<KeyID>(c) & 0xffu; + } + else { + // character is not really ASCII. instead it's some + // character in the current ANSI code page. try to + // convert that to a Unicode character. if we fail + // then use the single byte character as is. + char src = c; + wchar_t unicode; + if (MultiByteToWideChar(CP_THREAD_ACP, MB_PRECOMPOSED, + &src, 1, &unicode, 1) > 0) { + id = static_cast<KeyID>(unicode); + } + else { + id = static_cast<KeyID>(c) & 0xffu; + } + } + } + + // set modifier mask + if (maskOut != NULL) { + KeyModifierMask active = getActiveModifiers(); + if (!noAltGr && (active & s_controlAlt) == s_controlAlt) { + // if !noAltGr then we're only interested in matching the + // key, not the AltGr. AltGr is down (i.e. control and alt + // are down) but we don't want the client to have to match + // that so we clear it. + active &= ~s_controlAlt; + } + if (id == kKeyHangul) { + // If shift-space is used to change input mode, clear shift modifier. + active &= ~KeyModifierShift; + } + *maskOut = active; + } + + return id; +} + +bool +MSWindowsKeyState::didGroupsChange() const +{ + GroupList groups; + return (getGroups(groups) && groups != m_groups); +} + +UINT +MSWindowsKeyState::mapKeyToVirtualKey(KeyID key) const +{ + if (key == kKeyNone) { + return 0; + } + KeyToVKMap::const_iterator i = m_keyToVKMap.find(key); + if (i == m_keyToVKMap.end()) { + return 0; + } + else { + return i->second; + } +} + +void +MSWindowsKeyState::onKey(KeyButton button, bool down, KeyModifierMask newState) +{ + KeyState::onKey(button, down, newState); +} + +void +MSWindowsKeyState::sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + if (press || isAutoRepeat) { + // send key + if (press && !isAutoRepeat) { + KeyState::sendKeyEvent(target, true, false, + key, mask, 1, button); + if (count > 0) { + --count; + } + } + if (count >= 1) { + KeyState::sendKeyEvent(target, true, true, + key, mask, count, button); + } + } + else { + // do key up + KeyState::sendKeyEvent(target, false, false, key, mask, 1, button); + } +} + +void +MSWindowsKeyState::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + KeyState::fakeKeyDown(id, mask, button); +} + +bool +MSWindowsKeyState::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + return KeyState::fakeKeyRepeat(id, mask, count, button); +} + +bool +MSWindowsKeyState::fakeCtrlAltDel() +{ + // to fake ctrl+alt+del on the NT family we broadcast a suitable + // hotkey to all windows on the winlogon desktop. however, the + // current thread must be on that desktop to do the broadcast + // and we can't switch just any thread because some own windows + // or hooks. so start a new thread to do the real work. + HANDLE hEvtSendSas = OpenEvent(EVENT_MODIFY_STATE, FALSE, "Global\\SendSAS"); + if (hEvtSendSas) { + LOG((CLOG_DEBUG "found the SendSAS event - signaling my launcher to simulate ctrl+alt+del")); + SetEvent(hEvtSendSas); + CloseHandle(hEvtSendSas); + } + else { + Thread cad(new FunctionJob(&MSWindowsKeyState::ctrlAltDelThread)); + cad.wait(); + } + + return true; +} + +void +MSWindowsKeyState::ctrlAltDelThread(void*) +{ + // get the Winlogon desktop at whatever privilege we can + HDESK desk = OpenDesktop("Winlogon", 0, FALSE, MAXIMUM_ALLOWED); + if (desk != NULL) { + if (SetThreadDesktop(desk)) { + PostMessage(HWND_BROADCAST, WM_HOTKEY, 0, + MAKELPARAM(MOD_CONTROL | MOD_ALT, VK_DELETE)); + } + else { + LOG((CLOG_DEBUG "can't switch to Winlogon desk: %d", GetLastError())); + } + CloseDesktop(desk); + } + else { + LOG((CLOG_DEBUG "can't open Winlogon desk: %d", GetLastError())); + } +} + +KeyModifierMask +MSWindowsKeyState::pollActiveModifiers() const +{ + KeyModifierMask state = 0; + + // get non-toggle modifiers from our own shadow key state + for (size_t i = 0; i < sizeof(s_modifiers) / sizeof(s_modifiers[0]); ++i) { + KeyButton button = virtualKeyToButton(s_modifiers[i].m_vk); + if (button != 0 && isKeyDown(button)) { + state |= s_modifiers[i].m_mask; + } + } + + // we can get toggle modifiers from the system + if ((GetKeyState(VK_CAPITAL) & 0x01) != 0) { + state |= KeyModifierCapsLock; + } + if ((GetKeyState(VK_NUMLOCK) & 0x01) != 0) { + state |= KeyModifierNumLock; + } + if ((GetKeyState(VK_SCROLL) & 0x01) != 0) { + state |= KeyModifierScrollLock; + } + + return state; +} + +SInt32 +MSWindowsKeyState::pollActiveGroup() const +{ + // determine the thread that'll receive this event + HWND targetWindow = GetForegroundWindow(); + DWORD targetThread = GetWindowThreadProcessId(targetWindow, NULL); + + // get keyboard layout for the thread + HKL hkl = GetKeyboardLayout(targetThread); + + if (!hkl) { + // GetKeyboardLayout failed. Maybe targetWindow is a console window. + // We're getting the keyboard layout of the desktop instead. + targetWindow = GetDesktopWindow(); + targetThread = GetWindowThreadProcessId(targetWindow, NULL); + hkl = GetKeyboardLayout(targetThread); + } + + // get group + GroupMap::const_iterator i = m_groupMap.find(hkl); + if (i == m_groupMap.end()) { + LOG((CLOG_DEBUG1 "can't find keyboard layout %08x", hkl)); + return 0; + } + + return i->second; +} + +void +MSWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + BYTE keyState[256]; + if (!GetKeyboardState(keyState)) { + LOG((CLOG_ERR "GetKeyboardState returned false on pollPressedKeys")); + return; + } + for (KeyButton i = 1; i < 256; ++i) { + if ((keyState[i] & 0x80) != 0) { + KeyButton keyButton = virtualKeyToButton(i); + if (keyButton != 0) { + pressedKeys.insert(keyButton); + } + } + } +} + +void +MSWindowsKeyState::getKeyMap(barrier::KeyMap& keyMap) +{ + // update keyboard groups + if (getGroups(m_groups)) { + m_groupMap.clear(); + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + m_groupMap[m_groups[g]] = g; + } + } + HKL activeLayout = GetKeyboardLayout(0); + + // clear table + memset(m_virtualKeyToButton, 0, sizeof(m_virtualKeyToButton)); + m_keyToVKMap.clear(); + + barrier::KeyMap::KeyItem item; + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + item.m_group = g; + ActivateKeyboardLayout(m_groups[g], 0); + + // clear tables + memset(m_buttonToVK, 0, sizeof(m_buttonToVK)); + memset(m_buttonToNumpadVK, 0, sizeof(m_buttonToNumpadVK)); + + // map buttons (scancodes) to virtual keys + for (KeyButton i = 1; i < 256; ++i) { + UINT vk = MapVirtualKey(i, 1); + if (vk == 0) { + // unmapped + continue; + } + + // deal with certain virtual keys specially + switch (vk) { + case VK_SHIFT: + // this is important for sending the correct modifier to the + // client, a patch from bug #242 (right shift broken for ms + // remote desktop) removed this to just use left shift, which + // caused bug #2799 (right shift broken for osx). + // we must not repeat this same mistake and must fix platform + // specific bugs in code that only affects that platform. + if (MapVirtualKey(VK_RSHIFT, 0) == i) { + vk = VK_RSHIFT; + } + else { + vk = VK_LSHIFT; + } + break; + + case VK_CONTROL: + vk = VK_LCONTROL; + break; + + case VK_MENU: + vk = VK_LMENU; + break; + + case VK_NUMLOCK: + vk = VK_PAUSE; + break; + + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + // numpad keys are saved in their own table + m_buttonToNumpadVK[i] = vk; + continue; + + case VK_LWIN: + case VK_RWIN: + break; + + case VK_RETURN: + case VK_PRIOR: + case VK_NEXT: + case VK_END: + case VK_HOME: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_INSERT: + case VK_DELETE: + // also add extended key for these + m_buttonToVK[i | 0x100u] = vk; + break; + } + + if (m_buttonToVK[i] == 0) { + m_buttonToVK[i] = vk; + } + } + + // now map virtual keys to buttons. multiple virtual keys may map + // to a single button. if the virtual key matches the one in + // m_buttonToVK then we use the button as is. if not then it's + // either a numpad key and we use the button as is or it's an + // extended button. + for (UINT i = 1; i < 255; ++i) { + // skip virtual keys we don't want + switch (i) { + case VK_LBUTTON: + case VK_RBUTTON: + case VK_MBUTTON: + case VK_XBUTTON1: + case VK_XBUTTON2: + case VK_SHIFT: + case VK_CONTROL: + case VK_MENU: + continue; + } + + // get the button + KeyButton button = static_cast<KeyButton>(MapVirtualKey(i, 0)); + if (button == 0) { + continue; + } + + // deal with certain virtual keys specially + switch (i) { + case VK_NUMPAD0: + case VK_NUMPAD1: + case VK_NUMPAD2: + case VK_NUMPAD3: + case VK_NUMPAD4: + case VK_NUMPAD5: + case VK_NUMPAD6: + case VK_NUMPAD7: + case VK_NUMPAD8: + case VK_NUMPAD9: + case VK_DECIMAL: + m_buttonToNumpadVK[button] = i; + break; + + default: + // add extended key if virtual keys don't match + if (m_buttonToVK[button] != i) { + m_buttonToVK[button | 0x100u] = i; + } + break; + } + } + + // set virtual key to button table + if (activeLayout == m_groups[g]) { + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToVK[i] != 0) { + if (m_virtualKeyToButton[m_buttonToVK[i]] == 0) { + m_virtualKeyToButton[m_buttonToVK[i]] = i; + } + } + if (m_buttonToNumpadVK[i] != 0) { + if (m_virtualKeyToButton[m_buttonToNumpadVK[i]] == 0) { + m_virtualKeyToButton[m_buttonToNumpadVK[i]] = i; + } + } + } + } + + // add numpad keys + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToNumpadVK[i] != 0) { + item.m_id = getKeyID(m_buttonToNumpadVK[i], i); + item.m_button = i; + item.m_required = KeyModifierNumLock; + item.m_sensitive = KeyModifierNumLock | KeyModifierShift; + item.m_generates = 0; + item.m_client = m_buttonToNumpadVK[i]; + addKeyEntry(keyMap, item); + } + } + + // add other keys + BYTE keys[256]; + memset(keys, 0, sizeof(keys)); + for (KeyButton i = 0; i < 512; ++i) { + if (m_buttonToVK[i] != 0) { + // initialize item + item.m_id = getKeyID(m_buttonToVK[i], i); + item.m_button = i; + item.m_required = 0; + item.m_sensitive = 0; + item.m_client = m_buttonToVK[i]; + + // get flags for modifier keys + barrier::KeyMap::initModifierKey(item); + + if (item.m_id == 0) { + // translate virtual key to a character with and without + // shift, caps lock, and AltGr. + struct Modifier { + UINT m_vk1; + UINT m_vk2; + BYTE m_state; + KeyModifierMask m_mask; + }; + static const Modifier modifiers[] = { + { VK_SHIFT, VK_SHIFT, 0x80u, KeyModifierShift }, + { VK_CAPITAL, VK_CAPITAL, 0x01u, KeyModifierCapsLock }, + { VK_CONTROL, VK_MENU, 0x80u, KeyModifierControl | + KeyModifierAlt } + }; + static const size_t s_numModifiers = + sizeof(modifiers) / sizeof(modifiers[0]); + static const size_t s_numCombinations = 1 << s_numModifiers; + KeyID id[s_numCombinations]; + + bool anyFound = false; + KeyButton button = static_cast<KeyButton>(i & 0xffu); + for (size_t j = 0; j < s_numCombinations; ++j) { + for (size_t k = 0; k < s_numModifiers; ++k) { + //if ((j & (1 << k)) != 0) { + // http://msdn.microsoft.com/en-us/library/ke55d167.aspx + if ((j & (1i64 << k)) != 0) { + keys[modifiers[k].m_vk1] = modifiers[k].m_state; + keys[modifiers[k].m_vk2] = modifiers[k].m_state; + } + else { + keys[modifiers[k].m_vk1] = 0; + keys[modifiers[k].m_vk2] = 0; + } + } + id[j] = getIDForKey(item, button, + m_buttonToVK[i], keys, m_groups[g]); + if (id[j] != 0) { + anyFound = true; + } + } + + if (anyFound) { + // determine what modifiers we're sensitive to. + // we're sensitive if the KeyID changes when the + // modifier does. + item.m_sensitive = 0; + for (size_t k = 0; k < s_numModifiers; ++k) { + for (size_t j = 0; j < s_numCombinations; ++j) { + //if (id[j] != id[j ^ (1u << k)]) { + // http://msdn.microsoft.com/en-us/library/ke55d167.aspx + if (id[j] != id[j ^ (1ui64 << k)]) { + item.m_sensitive |= modifiers[k].m_mask; + break; + } + } + } + + // save each key. the map will automatically discard + // duplicates, like an unshift and shifted version of + // a key that's insensitive to shift. + for (size_t j = 0; j < s_numCombinations; ++j) { + item.m_id = id[j]; + item.m_required = 0; + for (size_t k = 0; k < s_numModifiers; ++k) { + if ((j & (1i64 << k)) != 0) { + item.m_required |= modifiers[k].m_mask; + } + } + addKeyEntry(keyMap, item); + } + } + } + else { + // found in table + switch (m_buttonToVK[i]) { + case VK_TAB: + // add kKeyLeftTab, too + item.m_id = kKeyLeftTab; + item.m_required |= KeyModifierShift; + item.m_sensitive |= KeyModifierShift; + addKeyEntry(keyMap, item); + item.m_id = kKeyTab; + item.m_required &= ~KeyModifierShift; + break; + + case VK_CANCEL: + item.m_required |= KeyModifierControl; + item.m_sensitive |= KeyModifierControl; + break; + + case VK_SNAPSHOT: + item.m_sensitive |= KeyModifierAlt; + if ((i & 0x100u) == 0) { + // non-extended snapshot key requires alt + item.m_required |= KeyModifierAlt; + } + break; + } + addKeyEntry(keyMap, item); + } + } + } + } + + // restore keyboard layout + ActivateKeyboardLayout(activeLayout, 0); +} + +void +MSWindowsKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: { + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + KeyButton button = keystroke.m_data.m_button.m_button; + + // windows doesn't send key ups for key repeats + if (keystroke.m_data.m_button.m_repeat && + !keystroke.m_data.m_button.m_press) { + LOG((CLOG_DEBUG1 " discard key repeat release")); + break; + } + + // get the virtual key for the button + UINT vk = keystroke.m_data.m_button.m_client; + + // special handling of VK_SNAPSHOT + if (vk == VK_SNAPSHOT) { + if ((getActiveModifiers() & KeyModifierAlt) != 0) { + // snapshot active window + button = 1; + } + else { + // snapshot full screen + button = 0; + } + } + + // synthesize event + m_desks->fakeKeyEvent(button, vk, + keystroke.m_data.m_button.m_press, + keystroke.m_data.m_button.m_repeat); + break; + } + + case Keystroke::kGroup: + // we don't restore the group. we'd like to but we can't be + // sure the restoring group change will be processed after the + // key events. + if (!keystroke.m_data.m_group.m_restore) { + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); + setWindowGroup(keystroke.m_data.m_group.m_group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); + setWindowGroup(getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)); + } + } + break; + } +} + +KeyModifierMask& +MSWindowsKeyState::getActiveModifiersRValue() +{ + if (m_useSavedModifiers) { + return m_savedModifiers; + } + else { + return KeyState::getActiveModifiersRValue(); + } +} + +bool +MSWindowsKeyState::getGroups(GroupList& groups) const +{ + // get keyboard layouts + UInt32 newNumLayouts = GetKeyboardLayoutList(0, NULL); + if (newNumLayouts == 0) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + return false; + } + HKL* newLayouts = new HKL[newNumLayouts]; + newNumLayouts = GetKeyboardLayoutList(newNumLayouts, newLayouts); + if (newNumLayouts == 0) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + delete[] newLayouts; + return false; + } + + groups.clear(); + groups.insert(groups.end(), newLayouts, newLayouts + newNumLayouts); + delete[] newLayouts; + return true; +} + +void +MSWindowsKeyState::setWindowGroup(SInt32 group) +{ + HWND targetWindow = GetForegroundWindow(); + + bool sysCharSet = true; + // XXX -- determine if m_groups[group] can be used with the system + // character set. + + PostMessage(targetWindow, WM_INPUTLANGCHANGEREQUEST, + sysCharSet ? 1 : 0, (LPARAM)m_groups[group]); + + // XXX -- use a short delay to let the target window process the message + // before it sees the keyboard events. i'm not sure why this is + // necessary since the messages should arrive in order. if we don't + // delay, though, some of our keyboard events may disappear. + Sleep(100); +} + +KeyID +MSWindowsKeyState::getKeyID(UINT virtualKey, KeyButton button) const +{ + // Some virtual keycodes have same values. + // VK_HANGUL == VK_KANA, VK_HANJA == NK_KANJI + // which are used to change the input mode of IME. + // But they have different X11 keysym. So we should distinguish them. + if ((LOWORD(m_keyLayout) & 0xffffu) == 0x0412u) { // 0x0412 : Korean Locale ID + if (virtualKey == VK_HANGUL || virtualKey == VK_HANJA) { + // If shift-space is used to change the input mode, + // the extented bit is not set. So add it to get right key id. + button |= 0x100u; + } + } + + if ((button & 0x100u) != 0) { + virtualKey += 0x100u; + } + return s_virtualKey[virtualKey]; +} + +UINT +MSWindowsKeyState::mapButtonToVirtualKey(KeyButton button) const +{ + return m_buttonToVK[button]; +} + +KeyID +MSWindowsKeyState::getIDForKey(barrier::KeyMap::KeyItem& item, + KeyButton button, UINT virtualKey, + PBYTE keyState, HKL hkl) const +{ + WCHAR unicode[2]; + int n = m_ToUnicodeEx( + virtualKey, button, keyState, unicode, + sizeof(unicode) / sizeof(unicode[0]), 0, hkl); + KeyID id = static_cast<KeyID>(unicode[0]); + + switch (n) { + case -1: + return barrier::KeyMap::getDeadKey(id); + + default: + case 0: + // unmapped + return kKeyNone; + + case 1: + return id; + + case 2: + // left over dead key in buffer. this used to recurse, + // but apparently this causes a stack overflow, so just + // return no key instead. + return kKeyNone; + } +} + +void +MSWindowsKeyState::addKeyEntry(barrier::KeyMap& keyMap, barrier::KeyMap::KeyItem& item) +{ + keyMap.addKeyEntry(item); + if (item.m_group == 0) { + m_keyToVKMap[item.m_id] = static_cast<UINT>(item.m_client); + } +} + diff --git a/src/lib/platform/MSWindowsKeyState.h b/src/lib/platform/MSWindowsKeyState.h new file mode 100644 index 0000000..b226d8b --- /dev/null +++ b/src/lib/platform/MSWindowsKeyState.h @@ -0,0 +1,233 @@ +/* + * 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 "barrier/KeyState.h" +#include "base/String.h" +#include "common/stdvector.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class Event; +class EventQueueTimer; +class MSWindowsDesks; +class IEventQueue; + +//! Microsoft Windows key mapper +/*! +This class maps KeyIDs to keystrokes. +*/ +class MSWindowsKeyState : public KeyState { +public: + MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events); + MSWindowsKeyState(MSWindowsDesks* desks, void* eventTarget, IEventQueue* events, barrier::KeyMap& keyMap); + virtual ~MSWindowsKeyState(); + + //! @name manipulators + //@{ + + //! Handle screen disabling + /*! + Called when screen is disabled. This is needed to deal with platform + brokenness. + */ + void disable(); + + //! Set the active keyboard layout + /*! + Uses \p keyLayout when querying the keyboard. + */ + void setKeyLayout(HKL keyLayout); + + //! Test and set autorepeat state + /*! + Returns true if the given button is autorepeating and updates internal + state. + */ + bool testAutoRepeat(bool press, bool isRepeat, KeyButton); + + //! Remember modifier state + /*! + Records the current non-toggle modifier state. + */ + void saveModifiers(); + + //! Set effective modifier state + /*! + Temporarily sets the non-toggle modifier state to those saved by the + last call to \c saveModifiers if \p enable is \c true. Restores the + modifier state to the current modifier state if \p enable is \c false. + This is for synthesizing keystrokes on the primary screen when the + cursor is on a secondary screen. When on a secondary screen we capture + all non-toggle modifier state, track the state internally and do not + pass it on. So if Alt+F1 synthesizes Alt+X we need to synthesize + not just X but also Alt, despite the fact that our internal modifier + state indicates Alt is down, because local apps never saw the Alt down + event. + */ + void useSavedModifiers(bool enable); + + //@} + //! @name accessors + //@{ + + //! Map a virtual key to a button + /*! + Returns the button for the \p virtualKey. + */ + KeyButton virtualKeyToButton(UINT virtualKey) const; + + //! Map key event to a key + /*! + Converts a key event into a KeyID and the shadow modifier state + to a modifier mask. + */ + KeyID mapKeyFromEvent(WPARAM charAndVirtKey, + LPARAM info, KeyModifierMask* maskOut) const; + + //! Check if keyboard groups have changed + /*! + Returns true iff the number or order of the keyboard groups have + changed since the last call to updateKeys(). + */ + bool didGroupsChange() const; + + //! Map key to virtual key + /*! + Returns the virtual key for key \p key or 0 if there's no such virtual + key. + */ + UINT mapKeyToVirtualKey(KeyID key) const; + + //! Map virtual key and button to KeyID + /*! + Returns the KeyID for virtual key \p virtualKey and button \p button + (button should include the extended key bit), or kKeyNone if there is + no such key. + */ + KeyID getKeyID(UINT virtualKey, KeyButton button) const; + + //! Map button to virtual key + /*! + Returns the virtual key for button \p button + (button should include the extended key bit), or kKeyNone if there is + no such key. + */ + UINT mapButtonToVirtualKey(KeyButton button) const; + + //@} + + // IKeyState overrides + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + + // KeyState overrides + virtual void onKey(KeyButton button, bool down, + KeyModifierMask newState); + virtual void sendKeyEvent(void* target, + bool press, bool isAutoRepeat, + KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button); + + // Unit test accessors + KeyButton getLastDown() const { return m_lastDown; } + void setLastDown(KeyButton value) { m_lastDown = value; } + KeyModifierMask getSavedModifiers() const { return m_savedModifiers; } + void setSavedModifiers(KeyModifierMask value) { m_savedModifiers = value; } + +protected: + // KeyState overrides + virtual void getKeyMap(barrier::KeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + virtual KeyModifierMask& + getActiveModifiersRValue(); + +private: + typedef std::vector<HKL> GroupList; + + // send ctrl+alt+del hotkey event on NT family + static void ctrlAltDelThread(void*); + + bool getGroups(GroupList&) const; + void setWindowGroup(SInt32 group); + + KeyID getIDForKey(barrier::KeyMap::KeyItem& item, + KeyButton button, UINT virtualKey, + PBYTE keyState, HKL hkl) const; + + void addKeyEntry(barrier::KeyMap& keyMap, barrier::KeyMap::KeyItem& item); + + void init(); + +private: + // not implemented + MSWindowsKeyState(const MSWindowsKeyState&); + MSWindowsKeyState& operator=(const MSWindowsKeyState&); + +private: + typedef std::map<HKL, SInt32> GroupMap; + typedef std::map<KeyID, UINT> KeyToVKMap; + + void* m_eventTarget; + MSWindowsDesks* m_desks; + HKL m_keyLayout; + UINT m_buttonToVK[512]; + UINT m_buttonToNumpadVK[512]; + KeyButton m_virtualKeyToButton[256]; + KeyToVKMap m_keyToVKMap; + IEventQueue* m_events; + + // the timer used to check for fixing key state + EventQueueTimer* m_fixTimer; + + // the groups (keyboard layouts) + GroupList m_groups; + GroupMap m_groupMap; + + // the last button that we generated a key down event for. this + // is zero if the last key event was a key up. we use this to + // synthesize key repeats since the low level keyboard hook can't + // tell us if an event is a key repeat. + KeyButton m_lastDown; + + // modifier tracking + bool m_useSavedModifiers; + KeyModifierMask m_savedModifiers; + KeyModifierMask m_originalSavedModifiers; + + // pointer to ToUnicodeEx. on win95 family this will be NULL. + typedef int (WINAPI *ToUnicodeEx_t)(UINT wVirtKey, + UINT wScanCode, + PBYTE lpKeyState, + LPWSTR pwszBuff, + int cchBuff, + UINT wFlags, + HKL dwhkl); + ToUnicodeEx_t m_ToUnicodeEx; + + static const KeyID s_virtualKey[]; +}; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp new file mode 100644 index 0000000..5246f96 --- /dev/null +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -0,0 +1,1959 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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 "platform/MSWindowsScreen.h" + +#include "platform/MSWindowsDropTarget.h" +#include "client/Client.h" +#include "platform/MSWindowsClipboard.h" +#include "platform/MSWindowsDesks.h" +#include "platform/MSWindowsEventQueueBuffer.h" +#include "platform/MSWindowsKeyState.h" +#include "platform/MSWindowsScreenSaver.h" +#include "barrier/Clipboard.h" +#include "barrier/KeyMap.h" +#include "barrier/XScreen.h" +#include "barrier/App.h" +#include "barrier/ArgsBase.h" +#include "barrier/ClientApp.h" +#include "mt/Lock.h" +#include "mt/Thread.h" +#include "arch/win32/ArchMiscWindows.h" +#include "arch/Arch.h" +#include "base/FunctionJob.h" +#include "base/Log.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" + +#include <string.h> +#include <Shlobj.h> +#include <comutil.h> +#include <algorithm> + +// +// add backwards compatible multihead support (and suppress bogus warning). +// this isn't supported on MinGW yet AFAICT. +// +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4706) // assignment within conditional +#define COMPILE_MULTIMON_STUBS +#include <multimon.h> +#pragma warning(pop) +#endif + +// X button stuff +#if !defined(WM_XBUTTONDOWN) +#define WM_XBUTTONDOWN 0x020B +#define WM_XBUTTONUP 0x020C +#define WM_XBUTTONDBLCLK 0x020D +#define WM_NCXBUTTONDOWN 0x00AB +#define WM_NCXBUTTONUP 0x00AC +#define WM_NCXBUTTONDBLCLK 0x00AD +#define MOUSEEVENTF_XDOWN 0x0080 +#define MOUSEEVENTF_XUP 0x0100 +#define XBUTTON1 0x0001 +#define XBUTTON2 0x0002 +#endif +#if !defined(VK_XBUTTON1) +#define VK_XBUTTON1 0x05 +#define VK_XBUTTON2 0x06 +#endif + +// WM_POWERBROADCAST stuff +#if !defined(PBT_APMRESUMEAUTOMATIC) +#define PBT_APMRESUMEAUTOMATIC 0x0012 +#endif + +// +// MSWindowsScreen +// + +HINSTANCE MSWindowsScreen::s_windowInstance = NULL; +MSWindowsScreen* MSWindowsScreen::s_screen = NULL; + +MSWindowsScreen::MSWindowsScreen( + bool isPrimary, + bool noHooks, + bool stopOnDeskSwitch, + IEventQueue* events) : + PlatformScreen(events), + m_isPrimary(isPrimary), + m_noHooks(noHooks), + m_isOnScreen(m_isPrimary), + m_class(0), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_multimon(false), + m_xCursor(0), m_yCursor(0), + m_sequenceNumber(0), + m_mark(0), + m_markReceived(0), + m_fixTimer(NULL), + m_keyLayout(NULL), + m_screensaver(NULL), + m_screensaverNotify(false), + m_screensaverActive(false), + m_window(NULL), + m_nextClipboardWindow(NULL), + m_ownClipboard(false), + m_desks(NULL), + m_keyState(NULL), + m_hasMouse(GetSystemMetrics(SM_MOUSEPRESENT) != 0), + m_showingMouse(false), + m_events(events), + m_dropWindow(NULL), + m_dropWindowSize(20) +{ + assert(s_windowInstance != NULL); + assert(s_screen == NULL); + + s_screen = this; + try { + m_screensaver = new MSWindowsScreenSaver(); + m_desks = new MSWindowsDesks( + m_isPrimary, + m_noHooks, + m_screensaver, + m_events, + new TMethodJob<MSWindowsScreen>( + this, &MSWindowsScreen::updateKeysCB), + stopOnDeskSwitch); + m_keyState = new MSWindowsKeyState(m_desks, getEventTarget(), m_events); + + updateScreenShape(); + m_class = createWindowClass(); + m_window = createWindow(m_class, "Barrier"); + forceShowCursor(); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + + // SHGetFolderPath is deprecated in vista, but use it for xp support. + char desktopPath[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath))) { + m_desktopPath = String(desktopPath); + LOG((CLOG_DEBUG "using desktop for drop target: %s", m_desktopPath.c_str())); + } + else { + LOG((CLOG_ERR "failed to get desktop path, no drop target available, error=%d", GetLastError())); + } + + OleInitialize(0); + m_dropWindow = createDropWindow(m_class, "DropWindow"); + m_dropTarget = new MSWindowsDropTarget(); + RegisterDragDrop(m_dropWindow, m_dropTarget); + } + catch (...) { + delete m_keyState; + delete m_desks; + delete m_screensaver; + destroyWindow(m_window); + destroyClass(m_class); + s_screen = NULL; + throw; + } + + // install event handlers + m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(), + new TMethodEventJob<MSWindowsScreen>(this, + &MSWindowsScreen::handleSystemEvent)); + + // install the platform event queue + m_events->adoptBuffer(new MSWindowsEventQueueBuffer(m_events)); +} + +MSWindowsScreen::~MSWindowsScreen() +{ + assert(s_screen != NULL); + + disable(); + m_events->adoptBuffer(NULL); + m_events->removeHandler(Event::kSystem, m_events->getSystemTarget()); + delete m_keyState; + delete m_desks; + delete m_screensaver; + destroyWindow(m_window); + destroyClass(m_class); + + RevokeDragDrop(m_dropWindow); + m_dropTarget->Release(); + OleUninitialize(); + destroyWindow(m_dropWindow); + + s_screen = NULL; +} + +void +MSWindowsScreen::init(HINSTANCE windowInstance) +{ + assert(s_windowInstance == NULL); + assert(windowInstance != NULL); + + s_windowInstance = windowInstance; +} + +HINSTANCE +MSWindowsScreen::getWindowInstance() +{ + return s_windowInstance; +} + +void +MSWindowsScreen::enable() +{ + assert(m_isOnScreen == m_isPrimary); + + // we need to poll some things to fix them + m_fixTimer = m_events->newTimer(1.0, NULL); + m_events->adoptHandler(Event::kTimer, m_fixTimer, + new TMethodEventJob<MSWindowsScreen>(this, + &MSWindowsScreen::handleFixes)); + + // install our clipboard snooper + m_nextClipboardWindow = SetClipboardViewer(m_window); + + // track the active desk and (re)install the hooks + m_desks->enable(); + + if (m_isPrimary) { + // set jump zones + m_hook.setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + + // watch jump zones + m_hook.setMode(kHOOK_WATCH_JUMP_ZONE); + } + else { + // prevent the system from entering power saving modes. if + // it did we'd be forced to disconnect from the server and + // the server would not be able to wake us up. + ArchMiscWindows::addBusyState(ArchMiscWindows::kSYSTEM); + } +} + +void +MSWindowsScreen::disable() +{ + // stop tracking the active desk + m_desks->disable(); + + if (m_isPrimary) { + // disable hooks + m_hook.setMode(kHOOK_DISABLE); + + // enable special key sequences on win95 family + enableSpecialKeys(true); + } + else { + // allow the system to enter power saving mode + ArchMiscWindows::removeBusyState(ArchMiscWindows::kSYSTEM | + ArchMiscWindows::kDISPLAY); + } + + // tell key state + m_keyState->disable(); + + // stop snooping the clipboard + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + + // uninstall fix timer + if (m_fixTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_fixTimer); + m_events->deleteTimer(m_fixTimer); + m_fixTimer = NULL; + } + + m_isOnScreen = m_isPrimary; + forceShowCursor(); +} + +void +MSWindowsScreen::enter() +{ + m_desks->enter(); + if (m_isPrimary) { + // enable special key sequences on win95 family + enableSpecialKeys(true); + + // watch jump zones + m_hook.setMode(kHOOK_WATCH_JUMP_ZONE); + + // all messages prior to now are invalid + nextMark(); + + m_primaryKeyDownList.clear(); + } + else { + // Entering a secondary screen. Ensure that no screensaver is active + // and that the screen is not in powersave mode. + ArchMiscWindows::wakeupDisplay(); + + if (m_screensaver != NULL && m_screensaverActive) + { + m_screensaver->deactivate(); + m_screensaverActive = 0; + } + } + + // now on screen + m_isOnScreen = true; + forceShowCursor(); +} + +bool +MSWindowsScreen::leave() +{ + // get keyboard layout of foreground window. we'll use this + // keyboard layout for translating keys sent to clients. + HWND window = GetForegroundWindow(); + DWORD thread = GetWindowThreadProcessId(window, NULL); + m_keyLayout = GetKeyboardLayout(thread); + + // tell the key mapper about the keyboard layout + m_keyState->setKeyLayout(m_keyLayout); + + // tell desk that we're leaving and tell it the keyboard layout + m_desks->leave(m_keyLayout); + + if (m_isPrimary) { + + // warp to center + LOG((CLOG_DEBUG1 "warping cursor to center: %+d, %+d", m_xCenter, m_yCenter)); + warpCursor(m_xCenter, m_yCenter); + + // disable special key sequences on win95 family + enableSpecialKeys(false); + + // all messages prior to now are invalid + nextMark(); + + // remember the modifier state. this is the modifier state + // reflected in the internal keyboard state. + m_keyState->saveModifiers(); + + m_hook.setMode(kHOOK_RELAY_EVENTS); + + m_primaryKeyDownList.clear(); + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (m_keyState->isKeyDown(i)) { + m_primaryKeyDownList.push_back(i); + LOG((CLOG_DEBUG1 "key button %d is down before leaving to another screen", i)); + } + } + } + + // now off screen + m_isOnScreen = false; + forceShowCursor(); + + if (isDraggingStarted() && !m_isPrimary) { + m_sendDragThread = new Thread( + new TMethodJob<MSWindowsScreen>( + this, + &MSWindowsScreen::sendDragThread)); + } + + return true; +} + +void +MSWindowsScreen::sendDragThread(void*) +{ + String& draggingFilename = getDraggingFilename(); + size_t size = draggingFilename.size(); + + if (draggingFilename.empty() == false) { + ClientApp& app = ClientApp::instance(); + Client* client = app.getClientPtr(); + UInt32 fileCount = 1; + LOG((CLOG_DEBUG "send dragging info to server: %s", draggingFilename.c_str())); + client->sendDragInfo(fileCount, draggingFilename, size); + LOG((CLOG_DEBUG "send dragging file to server")); + client->sendFileToServer(draggingFilename.c_str()); + } + + m_draggingStarted = false; +} + +bool +MSWindowsScreen::setClipboard(ClipboardID, const IClipboard* src) +{ + MSWindowsClipboard dst(m_window); + if (src != NULL) { + // save clipboard data + return Clipboard::copy(&dst, src); + } + else { + // assert clipboard ownership + if (!dst.open(0)) { + return false; + } + dst.empty(); + dst.close(); + return true; + } +} + +void +MSWindowsScreen::checkClipboards() +{ + // if we think we own the clipboard but we don't then somebody + // grabbed the clipboard on this screen without us knowing. + // tell the server that this screen grabbed the clipboard. + // + // this works around bugs in the clipboard viewer chain. + // sometimes NT will simply never send WM_DRAWCLIPBOARD + // messages for no apparent reason and rebooting fixes the + // problem. since we don't want a broken clipboard until the + // next reboot we do this double check. clipboard ownership + // won't be reflected on other screens until we leave but at + // least the clipboard itself will work. + if (m_ownClipboard && !MSWindowsClipboard::isOwnedByBarrier()) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership and no notification received")); + m_ownClipboard = false; + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); + } +} + +void +MSWindowsScreen::openScreensaver(bool notify) +{ + assert(m_screensaver != NULL); + + m_screensaverNotify = notify; + if (m_screensaverNotify) { + m_desks->installScreensaverHooks(true); + } + else if (m_screensaver) { + m_screensaver->disable(); + } +} + +void +MSWindowsScreen::closeScreensaver() +{ + if (m_screensaver != NULL) { + if (m_screensaverNotify) { + m_desks->installScreensaverHooks(false); + } + else { + m_screensaver->enable(); + } + } + m_screensaverNotify = false; +} + +void +MSWindowsScreen::screensaver(bool activate) +{ + assert(m_screensaver != NULL); + if (m_screensaver==NULL) return; + + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +MSWindowsScreen::resetOptions() +{ + m_desks->resetOptions(); +} + +void +MSWindowsScreen::setOptions(const OptionsList& options) +{ + m_desks->setOptions(options); +} + +void +MSWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +MSWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +MSWindowsScreen::getEventTarget() const +{ + return const_cast<MSWindowsScreen*>(this); +} + +bool +MSWindowsScreen::getClipboard(ClipboardID, IClipboard* dst) const +{ + MSWindowsClipboard src(m_window); + Clipboard::copy(dst, &src); + return true; +} + +void +MSWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + assert(m_class != 0); + + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +MSWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + m_desks->getCursorPos(x, y); +} + +void +MSWindowsScreen::reconfigure(UInt32 activeSides) +{ + assert(m_isPrimary); + + LOG((CLOG_DEBUG "active sides: %x", activeSides)); + m_hook.setSides(activeSides); +} + +void +MSWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + MSG msg; + while (PeekMessage(&msg, NULL, BARRIER_MSG_INPUT_FIRST, + BARRIER_MSG_INPUT_LAST, PM_REMOVE)) { + // do nothing + } + + // save position to compute delta of next motion + saveMousePosition(x, y); +} + +void MSWindowsScreen::saveMousePosition(SInt32 x, SInt32 y) { + m_xCursor = x; + m_yCursor = y; + + LOG((CLOG_DEBUG5 "saved mouse position for next delta: %+d,%+d", x,y)); +} + +UInt32 +MSWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + // this should be a warning, but this can confuse users, + // as this warning happens almost always. + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to win32 + UINT modifiers = 0; + if ((mask & KeyModifierShift) != 0) { + modifiers |= MOD_SHIFT; + } + if ((mask & KeyModifierControl) != 0) { + modifiers |= MOD_CONTROL; + } + if ((mask & KeyModifierAlt) != 0) { + modifiers |= MOD_ALT; + } + if ((mask & KeyModifierSuper) != 0) { + modifiers |= MOD_WIN; + } + UINT vk = m_keyState->mapKeyToVirtualKey(key); + if (key != kKeyNone && vk == 0) { + // can't map key + // this should be a warning, but this can confuse users, + // as this warning happens almost always. + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + //id = m_hotKeys.size() + 1; + id = (UInt32)m_hotKeys.size() + 1; + } + + // if this hot key has modifiers only then we'll handle it specially + bool err; + if (key == kKeyNone) { + // check if already registered + err = (m_hotKeyToIDMap.count(HotKeyItem(vk, modifiers)) > 0); + } + else { + // register with OS + err = (RegisterHotKey(NULL, id, modifiers, vk) == 0); + } + + if (!err) { + m_hotKeys.insert(std::make_pair(id, HotKeyItem(vk, modifiers))); + m_hotKeyToIDMap[HotKeyItem(vk, modifiers)] = id; + } + else { + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +MSWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err; + if (i->second.getVirtualKey() != 0) { + err = !UnregisterHotKey(NULL, id); + } + else { + err = false; + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeyToIDMap.erase(i->second); + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +MSWindowsScreen::fakeInputBegin() +{ + assert(m_isPrimary); + + if (!m_isOnScreen) { + m_keyState->useSavedModifiers(true); + } + m_desks->fakeInputBegin(); +} + +void +MSWindowsScreen::fakeInputEnd() +{ + assert(m_isPrimary); + + m_desks->fakeInputEnd(); + if (!m_isOnScreen) { + m_keyState->useSavedModifiers(false); + } +} + +SInt32 +MSWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +MSWindowsScreen::isAnyMouseButtonDown(UInt32& buttonID) const +{ + static const char* buttonToName[] = { + "<invalid>", + "Left Button", + "Middle Button", + "Right Button", + "X Button 1", + "X Button 2" + }; + + for (UInt32 i = 1; i < sizeof(m_buttons) / sizeof(m_buttons[0]); ++i) { + if (m_buttons[i]) { + buttonID = i; + LOG((CLOG_DEBUG "locked by \"%s\"", buttonToName[i])); + return true; + } + } + + return false; +} + +void +MSWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +MSWindowsScreen::fakeMouseButton(ButtonID id, bool press) +{ + m_desks->fakeMouseButton(id, press); + + if (id == kButtonLeft) { + if (press) { + m_buttons[kButtonLeft] = true; + } + else { + m_buttons[kButtonLeft] = false; + m_fakeDraggingStarted = false; + m_draggingStarted = false; + } + } +} + +void +MSWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) +{ + m_desks->fakeMouseMove(x, y); + if (m_buttons[kButtonLeft]) { + m_draggingStarted = true; + } +} + +void +MSWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + m_desks->fakeMouseRelativeMove(dx, dy); +} + +void +MSWindowsScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + m_desks->fakeMouseWheel(xDelta, yDelta); +} + +void +MSWindowsScreen::updateKeys() +{ + m_desks->updateKeys(); +} + +void +MSWindowsScreen::fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button) +{ + PlatformScreen::fakeKeyDown(id, mask, button); + updateForceShowCursor(); +} + +bool +MSWindowsScreen::fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + bool result = PlatformScreen::fakeKeyRepeat(id, mask, count, button); + updateForceShowCursor(); + return result; +} + +bool +MSWindowsScreen::fakeKeyUp(KeyButton button) +{ + bool result = PlatformScreen::fakeKeyUp(button); + updateForceShowCursor(); + return result; +} + +void +MSWindowsScreen::fakeAllKeysUp() +{ + PlatformScreen::fakeAllKeysUp(); + updateForceShowCursor(); +} + +HCURSOR +MSWindowsScreen::createBlankCursor() const +{ + // create a transparent cursor + int cw = GetSystemMetrics(SM_CXCURSOR); + int ch = GetSystemMetrics(SM_CYCURSOR); + + UInt8* cursorAND = new UInt8[ch * ((cw + 31) >> 2)]; + UInt8* cursorXOR = new UInt8[ch * ((cw + 31) >> 2)]; + memset(cursorAND, 0xff, ch * ((cw + 31) >> 2)); + memset(cursorXOR, 0x00, ch * ((cw + 31) >> 2)); + HCURSOR c = CreateCursor(s_windowInstance, 0, 0, cw, ch, cursorAND, cursorXOR); + delete[] cursorXOR; + delete[] cursorAND; + return c; +} + +void +MSWindowsScreen::destroyCursor(HCURSOR cursor) const +{ + if (cursor != NULL) { + DestroyCursor(cursor); + } +} + +ATOM +MSWindowsScreen::createWindowClass() const +{ + WNDCLASSEX classInfo; + classInfo.cbSize = sizeof(classInfo); + classInfo.style = CS_DBLCLKS | CS_NOCLOSE; + classInfo.lpfnWndProc = &MSWindowsScreen::wndProc; + classInfo.cbClsExtra = 0; + classInfo.cbWndExtra = 0; + classInfo.hInstance = s_windowInstance; + classInfo.hIcon = NULL; + classInfo.hCursor = NULL; + classInfo.hbrBackground = NULL; + classInfo.lpszMenuName = NULL; + classInfo.lpszClassName = "Barrier"; + classInfo.hIconSm = NULL; + return RegisterClassEx(&classInfo); +} + +void +MSWindowsScreen::destroyClass(ATOM windowClass) const +{ + if (windowClass != 0) { + UnregisterClass(MAKEINTATOM(windowClass), s_windowInstance); + } +} + +HWND +MSWindowsScreen::createWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx(WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_TOOLWINDOW, + MAKEINTATOM(windowClass), + name, + WS_POPUP, + 0, 0, 1, 1, + NULL, NULL, + s_windowInstance, + NULL); + if (window == NULL) { + LOG((CLOG_ERR "failed to create window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + return window; +} + +HWND +MSWindowsScreen::createDropWindow(ATOM windowClass, const char* name) const +{ + HWND window = CreateWindowEx( + WS_EX_TOPMOST | + WS_EX_TRANSPARENT | + WS_EX_ACCEPTFILES, + MAKEINTATOM(m_class), + name, + WS_POPUP, + 0, 0, m_dropWindowSize, m_dropWindowSize, + NULL, NULL, + s_windowInstance, + NULL); + + if (window == NULL) { + LOG((CLOG_ERR "failed to create drop window: %d", GetLastError())); + throw XScreenOpenFailure(); + } + + return window; +} + +void +MSWindowsScreen::destroyWindow(HWND hwnd) const +{ + if (hwnd != NULL) { + DestroyWindow(hwnd); + } +} + +void +MSWindowsScreen::sendEvent(Event::Type type, void* data) +{ + m_events->addEvent(Event(type, getEventTarget(), data)); +} + +void +MSWindowsScreen::sendClipboardEvent(Event::Type type, ClipboardID id) +{ + ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo)); + if (info == NULL) { + LOG((CLOG_ERR "malloc failed on %s:%s", __FILE__, __LINE__ )); + return; + } + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +void +MSWindowsScreen::handleSystemEvent(const Event& event, void*) +{ + MSG* msg = static_cast<MSG*>(event.getData()); + assert(msg != NULL); + + if (ArchMiscWindows::processDialog(msg)) { + return; + } + if (onPreDispatch(msg->hwnd, msg->message, msg->wParam, msg->lParam)) { + return; + } + TranslateMessage(msg); + DispatchMessage(msg); +} + +void +MSWindowsScreen::updateButtons() +{ + int numButtons = GetSystemMetrics(SM_CMOUSEBUTTONS); + m_buttons[kButtonNone] = false; + m_buttons[kButtonLeft] = (GetKeyState(VK_LBUTTON) < 0); + m_buttons[kButtonRight] = (GetKeyState(VK_RBUTTON) < 0); + m_buttons[kButtonMiddle] = (GetKeyState(VK_MBUTTON) < 0); + m_buttons[kButtonExtra0 + 0] = (numButtons >= 4) && + (GetKeyState(VK_XBUTTON1) < 0); + m_buttons[kButtonExtra0 + 1] = (numButtons >= 5) && + (GetKeyState(VK_XBUTTON2) < 0); +} + +IKeyState* +MSWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +bool +MSWindowsScreen::onPreDispatch(HWND hwnd, + UINT message, WPARAM wParam, LPARAM lParam) +{ + // handle event + switch (message) { + case BARRIER_MSG_SCREEN_SAVER: + return onScreensaver(wParam != 0); + + case BARRIER_MSG_DEBUG: + LOG((CLOG_DEBUG1 "hook: 0x%08x 0x%08x", wParam, lParam)); + return true; + } + + if (m_isPrimary) { + return onPreDispatchPrimary(hwnd, message, wParam, lParam); + } + + return false; +} + +bool +MSWindowsScreen::onPreDispatchPrimary(HWND, + UINT message, WPARAM wParam, LPARAM lParam) +{ + LOG((CLOG_DEBUG5 "handling pre-dispatch primary")); + + // handle event + switch (message) { + case BARRIER_MSG_MARK: + return onMark(static_cast<UInt32>(wParam)); + + case BARRIER_MSG_KEY: + return onKey(wParam, lParam); + + case BARRIER_MSG_MOUSE_BUTTON: + return onMouseButton(wParam, lParam); + + case BARRIER_MSG_MOUSE_MOVE: + return onMouseMove(static_cast<SInt32>(wParam), + static_cast<SInt32>(lParam)); + + case BARRIER_MSG_MOUSE_WHEEL: + // XXX -- support x-axis scrolling + return onMouseWheel(0, static_cast<SInt32>(wParam)); + + case BARRIER_MSG_PRE_WARP: + { + // save position to compute delta of next motion + saveMousePosition(static_cast<SInt32>(wParam), static_cast<SInt32>(lParam)); + + // we warped the mouse. discard events until we find the + // matching post warp event. see warpCursorNoFlush() for + // where the events are sent. we discard the matching + // post warp event and can be sure we've skipped the warp + // event. + MSG msg; + do { + GetMessage(&msg, NULL, BARRIER_MSG_MOUSE_MOVE, + BARRIER_MSG_POST_WARP); + } while (msg.message != BARRIER_MSG_POST_WARP); + } + return true; + + case BARRIER_MSG_POST_WARP: + LOG((CLOG_WARN "unmatched post warp")); + return true; + + case WM_HOTKEY: + // we discard these messages. we'll catch the hot key in the + // regular key event handling, where we can detect both key + // press and release. we only register the hot key so no other + // app will act on the key combination. + break; + } + + return false; +} + +bool +MSWindowsScreen::onEvent(HWND, UINT msg, + WPARAM wParam, LPARAM lParam, LRESULT* result) +{ + switch (msg) { + case WM_DRAWCLIPBOARD: + // first pass on the message + if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } + + // now handle the message + return onClipboardChange(); + + case WM_CHANGECBCHAIN: + if (m_nextClipboardWindow == (HWND)wParam) { + m_nextClipboardWindow = (HWND)lParam; + LOG((CLOG_DEBUG "clipboard chain: new next: 0x%08x", m_nextClipboardWindow)); + } + else if (m_nextClipboardWindow != NULL) { + SendMessage(m_nextClipboardWindow, msg, wParam, lParam); + } + return true; + + case WM_DISPLAYCHANGE: + return onDisplayChange(); + + case WM_POWERBROADCAST: + switch (wParam) { + case PBT_APMRESUMEAUTOMATIC: + case PBT_APMRESUMECRITICAL: + case PBT_APMRESUMESUSPEND: + m_events->addEvent(Event(m_events->forIScreen().resume(), + getEventTarget(), NULL, + Event::kDeliverImmediately)); + break; + + case PBT_APMSUSPEND: + m_events->addEvent(Event(m_events->forIScreen().suspend(), + getEventTarget(), NULL, + Event::kDeliverImmediately)); + break; + } + *result = TRUE; + return true; + + case WM_DEVICECHANGE: + forceShowCursor(); + break; + + case WM_SETTINGCHANGE: + if (wParam == SPI_SETMOUSEKEYS) { + forceShowCursor(); + } + break; + } + + return false; +} + +bool +MSWindowsScreen::onMark(UInt32 mark) +{ + m_markReceived = mark; + return true; +} + +bool +MSWindowsScreen::onKey(WPARAM wParam, LPARAM lParam) +{ + static const KeyModifierMask s_ctrlAlt = + KeyModifierControl | KeyModifierAlt; + + LOG((CLOG_DEBUG1 "event: Key char=%d, vk=0x%02x, nagr=%d, lParam=0x%08x", (wParam & 0xff00u) >> 8, wParam & 0xffu, (wParam & 0x10000u) ? 1 : 0, lParam)); + + // get event info + KeyButton button = (KeyButton)((lParam & 0x01ff0000) >> 16); + bool down = ((lParam & 0x80000000u) == 0x00000000u); + bool wasDown = isKeyDown(button); + KeyModifierMask oldState = pollActiveModifiers(); + + // check for autorepeat + if (m_keyState->testAutoRepeat(down, (lParam & 0x40000000u), button)) { + lParam |= 0x40000000u; + } + + // if the button is zero then guess what the button should be. + // these are badly synthesized key events and logitech software + // that maps mouse buttons to keys is known to do this. + // alternatively, we could just throw these events out. + if (button == 0) { + button = m_keyState->virtualKeyToButton(wParam & 0xffu); + if (button == 0) { + return true; + } + wasDown = isKeyDown(button); + } + + // record keyboard state + m_keyState->onKey(button, down, oldState); + + if (!down && m_isPrimary && !m_isOnScreen) { + PrimaryKeyDownList::iterator find = std::find(m_primaryKeyDownList.begin(), m_primaryKeyDownList.end(), button); + if (find != m_primaryKeyDownList.end()) { + LOG((CLOG_DEBUG1 "release key button %d on primary", *find)); + m_hook.setMode(kHOOK_WATCH_JUMP_ZONE); + fakeLocalKey(*find, false); + m_primaryKeyDownList.erase(find); + m_hook.setMode(kHOOK_RELAY_EVENTS); + return true; + } + } + + // windows doesn't tell us the modifier key state on mouse or key + // events so we have to figure it out. most apps would use + // GetKeyState() or even GetAsyncKeyState() for that but we can't + // because our hook doesn't pass on key events for several modifiers. + // it can't otherwise the system would interpret them normally on + // the primary screen even when on a secondary screen. so tapping + // alt would activate menus and tapping the windows key would open + // the start menu. if you don't pass those events on in the hook + // then GetKeyState() understandably doesn't reflect the effect of + // the event. curiously, neither does GetAsyncKeyState(), which is + // surprising. + // + // so anyway, we have to track the modifier state ourselves for + // at least those modifiers we don't pass on. pollActiveModifiers() + // does that but we have to update the keyboard state before calling + // pollActiveModifiers() to get the right answer. but the only way + // to set the modifier state or to set the up/down state of a key + // is via onKey(). so we have to call onKey() twice. + KeyModifierMask state = pollActiveModifiers(); + m_keyState->onKey(button, down, state); + + // check for hot keys + if (oldState != state) { + // modifier key was pressed/released + if (onHotKey(0, lParam)) { + return true; + } + } + else { + // non-modifier was pressed/released + if (onHotKey(wParam, lParam)) { + return true; + } + } + + // stop sending modifier keys over and over again + if (isModifierRepeat(oldState, state, wParam)) { + return true; + } + + // ignore message if posted prior to last mark change + if (!ignore()) { + // check for ctrl+alt+del. we do not want to pass that to the + // client. the user can use ctrl+alt+pause to emulate it. + UINT virtKey = (wParam & 0xffu); + if (virtKey == VK_DELETE && (state & s_ctrlAlt) == s_ctrlAlt) { + LOG((CLOG_DEBUG "discard ctrl+alt+del")); + return true; + } + + // check for ctrl+alt+del emulation + if ((virtKey == VK_PAUSE || virtKey == VK_CANCEL) && + (state & s_ctrlAlt) == s_ctrlAlt) { + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + // switch wParam and lParam to be as if VK_DELETE was + // pressed or released. when mapping the key we require that + // we not use AltGr (the 0x10000 flag in wParam) and we not + // use the keypad delete key (the 0x01000000 flag in lParam). + wParam = VK_DELETE | 0x00010000u; + lParam &= 0xfe000000; + lParam |= m_keyState->virtualKeyToButton(wParam & 0xffu) << 16; + lParam |= 0x01000001; + } + + // process key + KeyModifierMask mask; + KeyID key = m_keyState->mapKeyFromEvent(wParam, lParam, &mask); + button = static_cast<KeyButton>((lParam & 0x01ff0000u) >> 16); + if (key != kKeyNone) { + // do it + m_keyState->sendKeyEvent(getEventTarget(), + ((lParam & 0x80000000u) == 0), + ((lParam & 0x40000000u) != 0), + key, mask, (SInt32)(lParam & 0xffff), button); + } + else { + LOG((CLOG_DEBUG1 "cannot map key")); + } + } + + return true; +} + +bool +MSWindowsScreen::onHotKey(WPARAM wParam, LPARAM lParam) +{ + // get the key info + KeyModifierMask state = getActiveModifiers(); + UINT virtKey = (wParam & 0xffu); + UINT modifiers = 0; + if ((state & KeyModifierShift) != 0) { + modifiers |= MOD_SHIFT; + } + if ((state & KeyModifierControl) != 0) { + modifiers |= MOD_CONTROL; + } + if ((state & KeyModifierAlt) != 0) { + modifiers |= MOD_ALT; + } + if ((state & KeyModifierSuper) != 0) { + modifiers |= MOD_WIN; + } + + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(HotKeyItem(virtKey, modifiers)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + Event::Type type; + if ((lParam & 0x80000000u) == 0u) { + if ((lParam & 0x40000000u) != 0u) { + // ignore key repeats but it counts as a hot key + return true; + } + type = m_events->forIPrimaryScreen().hotKeyDown(); + } + else { + type = m_events->forIPrimaryScreen().hotKeyUp(); + } + + // generate event + m_events->addEvent(Event(type, getEventTarget(), + HotKeyInfo::alloc(i->second))); + + return true; +} + +bool +MSWindowsScreen::onMouseButton(WPARAM wParam, LPARAM lParam) +{ + // get which button + bool pressed = mapPressFromEvent(wParam, lParam); + ButtonID button = mapButtonFromEvent(wParam, lParam); + + // keep our shadow key state up to date + if (button >= kButtonLeft && button <= kButtonExtra0 + 1) { + if (pressed) { + m_buttons[button] = true; + if (button == kButtonLeft) { + m_draggingFilename.clear(); + LOG((CLOG_DEBUG2 "dragging filename is cleared")); + } + } + else { + m_buttons[button] = false; + if (m_draggingStarted && button == kButtonLeft) { + m_draggingStarted = false; + } + } + } + + // ignore message if posted prior to last mark change + if (!ignore()) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + if (pressed) { + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + sendEvent(m_events->forIPrimaryScreen().buttonDown(), + ButtonInfo::alloc(button, mask)); + } + } + else { + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + sendEvent(m_events->forIPrimaryScreen().buttonUp(), + ButtonInfo::alloc(button, mask)); + } + } + } + + return true; +} + +// here's how mouse movements are sent across the network to a client: +// 1. barrier checks the mouse position on server screen +// 2. records the delta (current x,y minus last x,y) +// 3. records the current x,y as "last" (so we can calc delta next time) +// 4. on the server, puts the cursor back to the center of the screen +// - remember the cursor is hidden on the server at this point +// - this actually records the current x,y as "last" a second time (it seems) +// 5. sends the delta movement to the client (could be +1,+1 or -1,+4 for example) +bool +MSWindowsScreen::onMouseMove(SInt32 mx, SInt32 my) +{ + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = mx - m_xCursor; + SInt32 y = my - m_yCursor; + + LOG((CLOG_DEBUG3 + "mouse move - motion delta: %+d=(%+d - %+d),%+d=(%+d - %+d)", + x, mx, m_xCursor, y, my, m_yCursor)); + + // ignore if the mouse didn't move or if message posted prior + // to last mark change. + if (ignore() || (x == 0 && y == 0)) { + return true; + } + + // save position to compute delta of next motion + saveMousePosition(mx, my); + + if (m_isOnScreen) { + + // motion on primary screen + sendEvent( + m_events->forIPrimaryScreen().motionOnPrimary(), + MotionInfo::alloc(m_xCursor, m_yCursor)); + + if (m_buttons[kButtonLeft] == true && m_draggingStarted == false) { + m_draggingStarted = true; + } + } + else + { + // the motion is on the secondary screen, so we warp mouse back to + // center on the server screen. if we don't do this, then the mouse + // will always try to return to the original entry point on the + // secondary screen. + LOG((CLOG_DEBUG5 "warping server cursor to center: %+d,%+d", m_xCenter, m_yCenter)); + warpCursorNoFlush(m_xCenter, m_yCenter); + + // examine the motion. if it's about the distance + // from the center of the screen to an edge then + // it's probably a bogus motion that we want to + // ignore (see warpCursorNoFlush() for a further + // description). + static SInt32 bogusZoneSize = 10; + if (-x + bogusZoneSize > m_xCenter - m_x || + x + bogusZoneSize > m_x + m_w - m_xCenter || + -y + bogusZoneSize > m_yCenter - m_y || + y + bogusZoneSize > m_y + m_h - m_yCenter) { + + LOG((CLOG_DEBUG "dropped bogus delta motion: %+d,%+d", x, y)); + } + else { + // send motion + sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y)); + } + } + + return true; +} + +bool +MSWindowsScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + // ignore message if posted prior to last mark change + if (!ignore()) { + LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta)); + } + return true; +} + +bool +MSWindowsScreen::onScreensaver(bool activated) +{ + // ignore this message if there are any other screen saver + // messages already in the queue. this is important because + // our checkStarted() function has a deliberate delay, so it + // can't respond to events at full CPU speed and will fall + // behind if a lot of screen saver events are generated. + // that can easily happen because windows will continually + // send SC_SCREENSAVE until the screen saver starts, even if + // the screen saver is disabled! + MSG msg; + if (PeekMessage(&msg, NULL, BARRIER_MSG_SCREEN_SAVER, + BARRIER_MSG_SCREEN_SAVER, PM_NOREMOVE)) { + return true; + } + + if (activated) { + if (!m_screensaverActive && + m_screensaver->checkStarted(BARRIER_MSG_SCREEN_SAVER, FALSE, 0)) { + m_screensaverActive = true; + sendEvent(m_events->forIPrimaryScreen().screensaverActivated()); + + // enable display power down + ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY); + } + } + else { + if (m_screensaverActive) { + m_screensaverActive = false; + sendEvent(m_events->forIPrimaryScreen().screensaverDeactivated()); + + // disable display power down + ArchMiscWindows::addBusyState(ArchMiscWindows::kDISPLAY); + } + } + + return true; +} + +bool +MSWindowsScreen::onDisplayChange() +{ + // screen resolution may have changed. save old shape. + SInt32 xOld = m_x, yOld = m_y, wOld = m_w, hOld = m_h; + + // update shape + updateScreenShape(); + + // do nothing if resolution hasn't changed + if (xOld != m_x || yOld != m_y || wOld != m_w || hOld != m_h) { + if (m_isPrimary) { + // warp mouse to center if off screen + if (!m_isOnScreen) { + + LOG((CLOG_DEBUG1 "warping cursor to center: %+d, %+d", m_xCenter, m_yCenter)); + warpCursor(m_xCenter, m_yCenter); + } + + // tell hook about resize if on screen + else { + m_hook.setZone(m_x, m_y, m_w, m_h, getJumpZoneSize()); + } + } + + // send new screen info + sendEvent(m_events->forIScreen().shapeChanged()); + + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_multimon ? "(multi-monitor)" : "")); + } + + return true; +} + +bool +MSWindowsScreen::onClipboardChange() +{ + // now notify client that somebody changed the clipboard (unless + // we're the owner). + if (!MSWindowsClipboard::isOwnedByBarrier()) { + if (m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: lost ownership")); + m_ownClipboard = false; + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); + } + } + else if (!m_ownClipboard) { + LOG((CLOG_DEBUG "clipboard changed: barrier owned")); + m_ownClipboard = true; + } + + return true; +} + +void +MSWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + // send an event that we can recognize before the mouse warp + PostThreadMessage(GetCurrentThreadId(), BARRIER_MSG_PRE_WARP, x, y); + + // warp mouse. hopefully this inserts a mouse motion event + // between the previous message and the following message. + SetCursorPos(x, y); + + // check to see if the mouse pos was set correctly + POINT cursorPos; + GetCursorPos(&cursorPos); + + // there is a bug or round error in SetCursorPos and GetCursorPos on + // a high DPI setting. The check here is for Vista/7 login screen. + // since this feature is mainly for client, so only check on client. + if (!isPrimary()) { + if ((cursorPos.x != x) && (cursorPos.y != y)) { + LOG((CLOG_DEBUG "SetCursorPos did not work; using fakeMouseMove instead")); + LOG((CLOG_DEBUG "cursor pos %d, %d expected pos %d, %d", cursorPos.x, cursorPos.y, x, y)); + // when at Vista/7 login screen, SetCursorPos does not work (which could be + // an MS security feature). instead we can use fakeMouseMove, which calls + // mouse_event. + // IMPORTANT: as of implementing this function, it has an annoying side + // effect; instead of the mouse returning to the correct exit point, it + // returns to the center of the screen. this could have something to do with + // the center screen warping technique used (see comments for onMouseMove + // definition). + fakeMouseMove(x, y); + } + } + + // yield the CPU. there's a race condition when warping: + // a hardware mouse event occurs + // the mouse hook is not called because that process doesn't have the CPU + // we send PRE_WARP, SetCursorPos(), send POST_WARP + // we process all of those events and update m_x, m_y + // we finish our time slice + // the hook is called + // the hook sends us a mouse event from the pre-warp position + // we get the CPU + // we compute a bogus warp + // we need the hook to process all mouse events that occur + // before we warp before we do the warp but i'm not sure how + // to guarantee that. yielding the CPU here may reduce the + // chance of undesired behavior. we'll also check for very + // large motions that look suspiciously like about half width + // or height of the screen. + ARCH->sleep(0.0); + + // send an event that we can recognize after the mouse warp + PostThreadMessage(GetCurrentThreadId(), BARRIER_MSG_POST_WARP, 0, 0); +} + +void +MSWindowsScreen::nextMark() +{ + // next mark + ++m_mark; + + // mark point in message queue where the mark was changed + PostThreadMessage(GetCurrentThreadId(), BARRIER_MSG_MARK, m_mark, 0); +} + +bool +MSWindowsScreen::ignore() const +{ + return (m_mark != m_markReceived); +} + +void +MSWindowsScreen::updateScreenShape() +{ + // get shape and center + m_w = GetSystemMetrics(SM_CXVIRTUALSCREEN); + m_h = GetSystemMetrics(SM_CYVIRTUALSCREEN); + m_x = GetSystemMetrics(SM_XVIRTUALSCREEN); + m_y = GetSystemMetrics(SM_YVIRTUALSCREEN); + m_xCenter = GetSystemMetrics(SM_CXSCREEN) >> 1; + m_yCenter = GetSystemMetrics(SM_CYSCREEN) >> 1; + + // check for multiple monitors + m_multimon = (m_w != GetSystemMetrics(SM_CXSCREEN) || + m_h != GetSystemMetrics(SM_CYSCREEN)); + + // tell the desks + m_desks->setShape(m_x, m_y, m_w, m_h, m_xCenter, m_yCenter, m_multimon); +} + +void +MSWindowsScreen::handleFixes(const Event&, void*) +{ + // fix clipboard chain + fixClipboardViewer(); + + // update keys if keyboard layouts have changed + if (m_keyState->didGroupsChange()) { + updateKeys(); + } +} + +void +MSWindowsScreen::fixClipboardViewer() +{ + // XXX -- disable this code for now. somehow it can cause an infinite + // recursion in the WM_DRAWCLIPBOARD handler. either we're sending + // the message to our own window or some window farther down the chain + // forwards the message to our window or a window farther up the chain. + // i'm not sure how that could happen. the m_nextClipboardWindow = NULL + // was not in the code that infinite loops and may fix the bug but i + // doubt it. +/* + ChangeClipboardChain(m_window, m_nextClipboardWindow); + m_nextClipboardWindow = NULL; + m_nextClipboardWindow = SetClipboardViewer(m_window); +*/ +} + +void +MSWindowsScreen::enableSpecialKeys(bool enable) const +{ +} + +ButtonID +MSWindowsScreen::mapButtonFromEvent(WPARAM msg, LPARAM button) const +{ + switch (msg) { + case WM_LBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_LBUTTONUP: + case WM_NCLBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCLBUTTONUP: + return kButtonLeft; + + case WM_MBUTTONDOWN: + case WM_MBUTTONDBLCLK: + case WM_MBUTTONUP: + case WM_NCMBUTTONDOWN: + case WM_NCMBUTTONDBLCLK: + case WM_NCMBUTTONUP: + return kButtonMiddle; + + case WM_RBUTTONDOWN: + case WM_RBUTTONDBLCLK: + case WM_RBUTTONUP: + case WM_NCRBUTTONDOWN: + case WM_NCRBUTTONDBLCLK: + case WM_NCRBUTTONUP: + return kButtonRight; + + case WM_XBUTTONDOWN: + case WM_XBUTTONDBLCLK: + case WM_XBUTTONUP: + case WM_NCXBUTTONDOWN: + case WM_NCXBUTTONDBLCLK: + case WM_NCXBUTTONUP: + switch (button) { + case XBUTTON1: + if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 4) { + return kButtonExtra0 + 0; + } + break; + + case XBUTTON2: + if (GetSystemMetrics(SM_CMOUSEBUTTONS) >= 5) { + return kButtonExtra0 + 1; + } + break; + } + return kButtonNone; + + default: + return kButtonNone; + } +} + +bool +MSWindowsScreen::mapPressFromEvent(WPARAM msg, LPARAM) const +{ + switch (msg) { + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_XBUTTONDOWN: + case WM_LBUTTONDBLCLK: + case WM_MBUTTONDBLCLK: + case WM_RBUTTONDBLCLK: + case WM_XBUTTONDBLCLK: + case WM_NCLBUTTONDOWN: + case WM_NCMBUTTONDOWN: + case WM_NCRBUTTONDOWN: + case WM_NCXBUTTONDOWN: + case WM_NCLBUTTONDBLCLK: + case WM_NCMBUTTONDBLCLK: + case WM_NCRBUTTONDBLCLK: + case WM_NCXBUTTONDBLCLK: + return true; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + case WM_XBUTTONUP: + case WM_NCLBUTTONUP: + case WM_NCMBUTTONUP: + case WM_NCRBUTTONUP: + case WM_NCXBUTTONUP: + return false; + + default: + return false; + } +} + +void +MSWindowsScreen::updateKeysCB(void*) +{ + // record which keys we think are down + bool down[IKeyState::kNumButtons]; + bool sendFixes = (isPrimary() && !m_isOnScreen); + if (sendFixes) { + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + down[i] = m_keyState->isKeyDown(i); + } + } + + // update layouts if necessary + if (m_keyState->didGroupsChange()) { + PlatformScreen::updateKeyMap(); + } + + // now update the keyboard state + PlatformScreen::updateKeyState(); + + // now see which keys we thought were down but now think are up. + // send key releases for these keys to the active client. + if (sendFixes) { + KeyModifierMask mask = pollActiveModifiers(); + for (KeyButton i = 0; i < IKeyState::kNumButtons; ++i) { + if (down[i] && !m_keyState->isKeyDown(i)) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, kKeyNone, mask, 1, i); + } + } + } +} + +void +MSWindowsScreen::forceShowCursor() +{ + // check for mouse + m_hasMouse = (GetSystemMetrics(SM_MOUSEPRESENT) != 0); + + // decide if we should show the mouse + bool showMouse = (!m_hasMouse && !m_isPrimary && m_isOnScreen); + + // show/hide the mouse + if (showMouse != m_showingMouse) { + if (showMouse) { + m_oldMouseKeys.cbSize = sizeof(m_oldMouseKeys); + m_gotOldMouseKeys = + (SystemParametersInfo(SPI_GETMOUSEKEYS, + m_oldMouseKeys.cbSize, &m_oldMouseKeys, 0) != 0); + if (m_gotOldMouseKeys) { + m_mouseKeys = m_oldMouseKeys; + m_showingMouse = true; + updateForceShowCursor(); + } + } + else { + if (m_gotOldMouseKeys) { + SystemParametersInfo(SPI_SETMOUSEKEYS, + m_oldMouseKeys.cbSize, + &m_oldMouseKeys, SPIF_SENDCHANGE); + m_showingMouse = false; + } + } + } +} + +void +MSWindowsScreen::updateForceShowCursor() +{ + DWORD oldFlags = m_mouseKeys.dwFlags; + + // turn on MouseKeys + m_mouseKeys.dwFlags = MKF_AVAILABLE | MKF_MOUSEKEYSON; + + // make sure MouseKeys is active in whatever state the NumLock is + // not currently in. + if ((m_keyState->getActiveModifiers() & KeyModifierNumLock) != 0) { + m_mouseKeys.dwFlags |= MKF_REPLACENUMBERS; + } + + // update MouseKeys + if (oldFlags != m_mouseKeys.dwFlags) { + SystemParametersInfo(SPI_SETMOUSEKEYS, + m_mouseKeys.cbSize, &m_mouseKeys, SPIF_SENDCHANGE); + } +} + +LRESULT CALLBACK +MSWindowsScreen::wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + assert(s_screen != NULL); + + LRESULT result = 0; + if (!s_screen->onEvent(hwnd, msg, wParam, lParam, &result)) { + result = DefWindowProc(hwnd, msg, wParam, lParam); + } + + return result; +} + +void +MSWindowsScreen::fakeLocalKey(KeyButton button, bool press) const +{ + INPUT input; + input.type = INPUT_KEYBOARD; + input.ki.wVk = m_keyState->mapButtonToVirtualKey(button); + DWORD pressFlag = press ? KEYEVENTF_EXTENDEDKEY : KEYEVENTF_KEYUP; + input.ki.dwFlags = pressFlag; + input.ki.time = 0; + input.ki.dwExtraInfo = 0; + SendInput(1,&input,sizeof(input)); +} + +// +// MSWindowsScreen::HotKeyItem +// + +MSWindowsScreen::HotKeyItem::HotKeyItem(UINT keycode, UINT mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +UINT +MSWindowsScreen::HotKeyItem::getVirtualKey() const +{ + return m_keycode; +} + +bool +MSWindowsScreen::HotKeyItem::operator<(const HotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} + +void +MSWindowsScreen::fakeDraggingFiles(DragFileList fileList) +{ + // possible design flaw: this function stops a "not implemented" + // exception from being thrown. +} + +String& +MSWindowsScreen::getDraggingFilename() +{ + if (m_draggingStarted) { + m_dropTarget->clearDraggingFilename(); + m_draggingFilename.clear(); + + int halfSize = m_dropWindowSize / 2; + + SInt32 xPos = m_isPrimary ? m_xCursor : m_xCenter; + SInt32 yPos = m_isPrimary ? m_yCursor : m_yCenter; + xPos = (xPos - halfSize) < 0 ? 0 : xPos - halfSize; + yPos = (yPos - halfSize) < 0 ? 0 : yPos - halfSize; + SetWindowPos( + m_dropWindow, + HWND_TOPMOST, + xPos, + yPos, + m_dropWindowSize, + m_dropWindowSize, + SWP_SHOWWINDOW); + + // TODO: fake these keys properly + fakeKeyDown(kKeyEscape, 8192, 1); + fakeKeyUp(1); + fakeMouseButton(kButtonLeft, false); + + String filename; + DOUBLE timeout = ARCH->time() + .5f; + while (ARCH->time() < timeout) { + ARCH->sleep(.05f); + filename = m_dropTarget->getDraggingFilename(); + if (!filename.empty()) { + break; + } + } + + ShowWindow(m_dropWindow, SW_HIDE); + + if (!filename.empty()) { + if (DragInformation::isFileValid(filename)) { + m_draggingFilename = filename; + } + else { + LOG((CLOG_DEBUG "drag file name is invalid: %s", filename.c_str())); + } + } + + if (m_draggingFilename.empty()) { + LOG((CLOG_DEBUG "failed to get drag file name from OLE")); + } + } + + return m_draggingFilename; +} + +const String& +MSWindowsScreen::getDropTarget() const +{ + return m_desktopPath; +} + +bool +MSWindowsScreen::isModifierRepeat(KeyModifierMask oldState, KeyModifierMask state, WPARAM wParam) const +{ + bool result = false; + + if (oldState == state && state != 0) { + UINT virtKey = (wParam & 0xffu); + if ((state & KeyModifierShift) != 0 + && (virtKey == VK_LSHIFT || virtKey == VK_RSHIFT)) { + result = true; + } + if ((state & KeyModifierControl) != 0 + && (virtKey == VK_LCONTROL || virtKey == VK_RCONTROL)) { + result = true; + } + if ((state & KeyModifierAlt) != 0 + && (virtKey == VK_LMENU || virtKey == VK_RMENU)) { + result = true; + } + if ((state & KeyModifierSuper) != 0 + && (virtKey == VK_LWIN || virtKey == VK_RWIN)) { + result = true; + } + } + + return result; +} diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h new file mode 100644 index 0000000..4245d6c --- /dev/null +++ b/src/lib/platform/MSWindowsScreen.h @@ -0,0 +1,346 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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 "platform/MSWindowsHook.h" +#include "barrier/PlatformScreen.h" +#include "barrier/DragInformation.h" +#include "platform/synwinhk.h" +#include "mt/CondVar.h" +#include "mt/Mutex.h" +#include "base/String.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class EventQueueTimer; +class MSWindowsDesks; +class MSWindowsKeyState; +class MSWindowsScreenSaver; +class Thread; +class MSWindowsDropTarget; + +//! Implementation of IPlatformScreen for Microsoft Windows +class MSWindowsScreen : public PlatformScreen { +public: + MSWindowsScreen( + bool isPrimary, + bool noHooks, + bool stopOnDeskSwitch, + IEventQueue* events); + virtual ~MSWindowsScreen(); + + //! @name manipulators + //@{ + + //! Initialize + /*! + Saves the application's HINSTANCE. This \b must be called by + WinMain with the HINSTANCE it was passed. + */ + static void init(HINSTANCE); + + //@} + //! @name accessors + //@{ + + //! Get instance + /*! + Returns the application instance handle passed to init(). + */ + static HINSTANCE getWindowInstance(); + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, + KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown(UInt32& buttonID) const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press); + virtual void fakeMouseMove(SInt32 x, SInt32 y); + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IKeyState overrides + virtual void updateKeys(); + virtual void fakeKeyDown(KeyID id, KeyModifierMask mask, + KeyButton button); + virtual bool fakeKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button); + virtual bool fakeKeyUp(KeyButton button); + virtual void fakeAllKeysUp(); + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + virtual void fakeDraggingFiles(DragFileList fileList); + virtual String& getDraggingFilename(); + virtual const String& + getDropTarget() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const Event&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + + // simulate a local key to the system directly + void fakeLocalKey(KeyButton button, bool press) const; + +private: + // initialization and shutdown operations + HCURSOR createBlankCursor() const; + void destroyCursor(HCURSOR cursor) const; + ATOM createWindowClass() const; + ATOM createDeskWindowClass(bool isPrimary) const; + void destroyClass(ATOM windowClass) const; + HWND createWindow(ATOM windowClass, const char* name) const; + HWND createDropWindow(ATOM windowClass, const char* name) const; + void destroyWindow(HWND) const; + + // convenience function to send events +public: // HACK + void sendEvent(Event::Type type, void* = NULL); +private: // HACK + void sendClipboardEvent(Event::Type type, ClipboardID id); + + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatch(HWND, UINT, WPARAM, LPARAM); + + // handle message before it gets dispatched. returns true iff + // the message should not be dispatched. + bool onPreDispatchPrimary(HWND, UINT, WPARAM, LPARAM); + + // handle message. returns true iff handled and optionally sets + // \c *result (which defaults to 0). + bool onEvent(HWND, UINT, WPARAM, LPARAM, LRESULT* result); + + // message handlers + bool onMark(UInt32 mark); + bool onKey(WPARAM, LPARAM); + bool onHotKey(WPARAM, LPARAM); + bool onMouseButton(WPARAM, LPARAM); + bool onMouseMove(SInt32 x, SInt32 y); + bool onMouseWheel(SInt32 xDelta, SInt32 yDelta); + bool onScreensaver(bool activated); + bool onDisplayChange(); + bool onClipboardChange(); + + // warp cursor without discarding queued events + void warpCursorNoFlush(SInt32 x, SInt32 y); + + // discard posted messages + void nextMark(); + + // test if event should be ignored + bool ignore() const; + + // update screen size cache + void updateScreenShape(); + + // fix timer callback + void handleFixes(const Event&, void*); + + // fix the clipboard viewer chain + void fixClipboardViewer(); + + // enable/disable special key combinations so we can catch/pass them + void enableSpecialKeys(bool) const; + + // map a button event to a button ID + ButtonID mapButtonFromEvent(WPARAM msg, LPARAM button) const; + + // map a button event to a press (true) or release (false) + bool mapPressFromEvent(WPARAM msg, LPARAM button) const; + + // job to update the key state + void updateKeysCB(void*); + + // determine whether the mouse is hidden by the system and force + // it to be displayed if user has entered this secondary screen. + void forceShowCursor(); + + // forceShowCursor uses MouseKeys to show the cursor. since we + // don't actually want MouseKeys behavior we have to make sure + // it applies when NumLock is in whatever state it's not in now. + // this method does that. + void updateForceShowCursor(); + + // our window proc + static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM); + + // save last position of mouse to compute next delta movement + void saveMousePosition(SInt32 x, SInt32 y); + + // check if it is a modifier key repeating message + bool isModifierRepeat(KeyModifierMask oldState, + KeyModifierMask state, WPARAM wParam) const; + + // send drag info and data back to server + void sendDragThread(void*); + +private: + struct HotKeyItem { + public: + HotKeyItem(UINT vk, UINT modifiers); + + UINT getVirtualKey() const; + + bool operator<(const HotKeyItem&) const; + + private: + UINT m_keycode; + UINT m_mask; + }; + typedef std::map<UInt32, HotKeyItem> HotKeyMap; + typedef std::vector<UInt32> HotKeyIDList; + typedef std::map<HotKeyItem, UInt32> HotKeyToIDMap; + typedef std::vector<KeyButton> PrimaryKeyDownList; + + static HINSTANCE s_windowInstance; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if hooks are not to be installed (useful for debugging) + bool m_noHooks; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // our resources + ATOM m_class; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // true if system appears to have multiple monitors + bool m_multimon; + + // last mouse position + SInt32 m_xCursor, m_yCursor; + + // last clipboard + UInt32 m_sequenceNumber; + + // used to discard queued messages that are no longer needed + UInt32 m_mark; + UInt32 m_markReceived; + + // the main loop's thread id + DWORD m_threadID; + + // timer for periodically checking stuff that requires polling + EventQueueTimer* m_fixTimer; + + // the keyboard layout to use when off primary screen + HKL m_keyLayout; + + // screen saver stuff + MSWindowsScreenSaver* + m_screensaver; + bool m_screensaverNotify; + bool m_screensaverActive; + + // clipboard stuff. our window is used mainly as a clipboard + // owner and as a link in the clipboard viewer chain. + HWND m_window; + HWND m_nextClipboardWindow; + bool m_ownClipboard; + + // one desk per desktop and a cond var to communicate with it + MSWindowsDesks* m_desks; + + // keyboard stuff + MSWindowsKeyState* m_keyState; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + HotKeyToIDMap m_hotKeyToIDMap; + + // map of button state + bool m_buttons[1 + kButtonExtra0 + 1]; + + // the system shows the mouse cursor when an internal display count + // is >= 0. this count is maintained per application but there's + // apparently a system wide count added to the application's count. + // this system count is 0 if there's a mouse attached to the system + // and -1 otherwise. the MouseKeys accessibility feature can modify + // this system count by making the system appear to have a mouse. + // + // m_hasMouse is true iff there's a mouse attached to the system or + // MouseKeys is simulating one. we track this so we can force the + // cursor to be displayed when the user has entered this screen. + // m_showingMouse is true when we're doing that. + bool m_hasMouse; + bool m_showingMouse; + bool m_gotOldMouseKeys; + MOUSEKEYS m_mouseKeys; + MOUSEKEYS m_oldMouseKeys; + + MSWindowsHook m_hook; + + static MSWindowsScreen* + s_screen; + + IEventQueue* m_events; + + String m_desktopPath; + + MSWindowsDropTarget* + m_dropTarget; + HWND m_dropWindow; + const int m_dropWindowSize; + + Thread* m_sendDragThread; + + PrimaryKeyDownList m_primaryKeyDownList; +}; diff --git a/src/lib/platform/MSWindowsScreenSaver.cpp b/src/lib/platform/MSWindowsScreenSaver.cpp new file mode 100644 index 0000000..f9c15fb --- /dev/null +++ b/src/lib/platform/MSWindowsScreenSaver.cpp @@ -0,0 +1,359 @@ +/* + * 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 "platform/MSWindowsScreenSaver.h" + +#include "platform/MSWindowsScreen.h" +#include "mt/Thread.h" +#include "arch/Arch.h" +#include "arch/win32/ArchMiscWindows.h" +#include "base/Log.h" +#include "base/TMethodJob.h" + +#include <malloc.h> +#include <tchar.h> + +#if !defined(SPI_GETSCREENSAVERRUNNING) +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +static const TCHAR* g_isSecureNT = "ScreenSaverIsSecure"; +static const TCHAR* g_isSecure9x = "ScreenSaveUsePassword"; +static const TCHAR* const g_pathScreenSaverIsSecure[] = { + "Control Panel", + "Desktop", + NULL +}; + +// +// MSWindowsScreenSaver +// + +MSWindowsScreenSaver::MSWindowsScreenSaver() : + m_wasSecure(false), + m_wasSecureAnInt(false), + m_process(NULL), + m_watch(NULL), + m_threadID(0), + m_msg(0), + m_wParam(0), + m_lParam(0), + m_active(false) +{ + // check if screen saver is enabled + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); +} + +MSWindowsScreenSaver::~MSWindowsScreenSaver() +{ + unwatchProcess(); +} + +bool +MSWindowsScreenSaver::checkStarted(UINT msg, WPARAM wParam, LPARAM lParam) +{ + // if already started then say it didn't just start + if (m_active) { + return false; + } + + // screen saver may have started. look for it and get + // the process. if we can't find it then assume it + // didn't really start. we wait a moment before + // looking to give the screen saver a chance to start. + // this shouldn't be a problem since we only get here + // if the screen saver wants to kick in, meaning that + // the system is idle or the user deliberately started + // the screen saver. + Sleep(250); + + // set parameters common to all screen saver handling + m_threadID = GetCurrentThreadId(); + m_msg = msg; + m_wParam = wParam; + m_lParam = lParam; + + // on the windows nt family we wait for the desktop to + // change until it's neither the Screen-Saver desktop + // nor a desktop we can't open (the login desktop). + // since windows will send the request-to-start-screen- + // saver message even when the screen saver is disabled + // we first check that the screen saver is indeed active + // before watching for it to stop. + if (!isActive()) { + LOG((CLOG_DEBUG2 "can't open screen saver desktop")); + return false; + } + + watchDesktop(); + return true; +} + +void +MSWindowsScreenSaver::enable() +{ + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, m_wasEnabled, 0, 0); + + // restore password protection + if (m_wasSecure) { + setSecure(true, m_wasSecureAnInt); + } + + // restore display power down + ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY); +} + +void +MSWindowsScreenSaver::disable() +{ + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, 0, 0); + + // disable password protected screensaver + m_wasSecure = isSecure(&m_wasSecureAnInt); + if (m_wasSecure) { + setSecure(false, m_wasSecureAnInt); + } + + // disable display power down + ArchMiscWindows::addBusyState(ArchMiscWindows::kDISPLAY); +} + +void +MSWindowsScreenSaver::activate() +{ + // don't activate if already active + if (!isActive()) { + // activate + HWND hwnd = GetForegroundWindow(); + if (hwnd != NULL) { + PostMessage(hwnd, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + else { + // no foreground window. pretend we got the event instead. + DefWindowProc(NULL, WM_SYSCOMMAND, SC_SCREENSAVE, 0); + } + + // restore power save when screen saver activates + ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY); + } +} + +void +MSWindowsScreenSaver::deactivate() +{ + bool killed = false; + + // NT runs screen saver in another desktop + HDESK desktop = OpenDesktop("Screen-saver", 0, FALSE, + DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS); + if (desktop != NULL) { + EnumDesktopWindows(desktop, + &MSWindowsScreenSaver::killScreenSaverFunc, + reinterpret_cast<LPARAM>(&killed)); + CloseDesktop(desktop); + } + + // if above failed or wasn't tried, try the windows 95 way + if (!killed) { + // find screen saver window and close it + HWND hwnd = FindWindow("WindowsScreenSaverClass", NULL); + if (hwnd == NULL) { + // win2k may use a different class + hwnd = FindWindow("Default Screen Saver", NULL); + } + if (hwnd != NULL) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + } + } + + // force timer to restart + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &m_wasEnabled, 0); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + !m_wasEnabled, 0, SPIF_SENDWININICHANGE); + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, + m_wasEnabled, 0, SPIF_SENDWININICHANGE); + + // disable display power down + ArchMiscWindows::removeBusyState(ArchMiscWindows::kDISPLAY); +} + +bool +MSWindowsScreenSaver::isActive() const +{ + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + return (running != FALSE); +} + +BOOL CALLBACK +MSWindowsScreenSaver::killScreenSaverFunc(HWND hwnd, LPARAM arg) +{ + if (IsWindowVisible(hwnd)) { + HINSTANCE instance = (HINSTANCE)GetWindowLongPtr(hwnd, GWLP_HINSTANCE); + if (instance != MSWindowsScreen::getWindowInstance()) { + PostMessage(hwnd, WM_CLOSE, 0, 0); + *reinterpret_cast<bool*>(arg) = true; + } + } + return TRUE; +} + +void +MSWindowsScreenSaver::watchDesktop() +{ + // stop watching previous process/desktop + unwatchProcess(); + + // watch desktop in another thread + LOG((CLOG_DEBUG "watching screen saver desktop")); + m_active = true; + m_watch = new Thread(new TMethodJob<MSWindowsScreenSaver>(this, + &MSWindowsScreenSaver::watchDesktopThread)); +} + +void +MSWindowsScreenSaver::watchProcess(HANDLE process) +{ + // stop watching previous process/desktop + unwatchProcess(); + + // watch new process in another thread + if (process != NULL) { + LOG((CLOG_DEBUG "watching screen saver process")); + m_process = process; + m_active = true; + m_watch = new Thread(new TMethodJob<MSWindowsScreenSaver>(this, + &MSWindowsScreenSaver::watchProcessThread)); + } +} + +void +MSWindowsScreenSaver::unwatchProcess() +{ + if (m_watch != NULL) { + LOG((CLOG_DEBUG "stopped watching screen saver process/desktop")); + m_watch->cancel(); + m_watch->wait(); + delete m_watch; + m_watch = NULL; + m_active = false; + } + if (m_process != NULL) { + CloseHandle(m_process); + m_process = NULL; + } +} + +void +MSWindowsScreenSaver::watchDesktopThread(void*) +{ + DWORD reserved = 0; + TCHAR* name = NULL; + + for (;;) { + // wait a bit + ARCH->sleep(0.2); + + BOOL running; + SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &running, 0); + if (running) { + continue; + } + + // send screen saver deactivation message + m_active = false; + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } +} + +void +MSWindowsScreenSaver::watchProcessThread(void*) +{ + for (;;) { + Thread::testCancel(); + if (WaitForSingleObject(m_process, 50) == WAIT_OBJECT_0) { + // process terminated + LOG((CLOG_DEBUG "screen saver died")); + + // send screen saver deactivation message + m_active = false; + PostThreadMessage(m_threadID, m_msg, m_wParam, m_lParam); + return; + } + } +} + +void +MSWindowsScreenSaver::setSecure(bool secure, bool saveSecureAsInt) +{ + HKEY hkey = + ArchMiscWindows::addKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure); + if (hkey == NULL) { + return; + } + + if (saveSecureAsInt) { + ArchMiscWindows::setValue(hkey, g_isSecureNT, secure ? 1 : 0); + } + else { + ArchMiscWindows::setValue(hkey, g_isSecureNT, secure ? "1" : "0"); + } + + ArchMiscWindows::closeKey(hkey); +} + +bool +MSWindowsScreenSaver::isSecure(bool* wasSecureFlagAnInt) const +{ + // get the password protection setting key + HKEY hkey = + ArchMiscWindows::openKey(HKEY_CURRENT_USER, g_pathScreenSaverIsSecure); + if (hkey == NULL) { + return false; + } + + // get the value. the value may be an int or a string, depending + // on the version of windows. + bool result; + switch (ArchMiscWindows::typeOfValue(hkey, g_isSecureNT)) { + default: + result = false; + break; + + case ArchMiscWindows::kUINT: { + DWORD value = + ArchMiscWindows::readValueInt(hkey, g_isSecureNT); + *wasSecureFlagAnInt = true; + result = (value != 0); + break; + } + + case ArchMiscWindows::kSTRING: { + std::string value = + ArchMiscWindows::readValueString(hkey, g_isSecureNT); + *wasSecureFlagAnInt = false; + result = (value != "0"); + break; + } + } + + ArchMiscWindows::closeKey(hkey); + return result; +} diff --git a/src/lib/platform/MSWindowsScreenSaver.h b/src/lib/platform/MSWindowsScreenSaver.h new file mode 100644 index 0000000..91df181 --- /dev/null +++ b/src/lib/platform/MSWindowsScreenSaver.h @@ -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/>. + */ + +#pragma once + +#include "barrier/IScreenSaver.h" +#include "base/String.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +class Thread; + +//! Microsoft windows screen saver implementation +class MSWindowsScreenSaver : public IScreenSaver { +public: + MSWindowsScreenSaver(); + virtual ~MSWindowsScreenSaver(); + + //! @name manipulators + //@{ + + //! Check if screen saver started + /*! + Check if the screen saver really started. Returns false if it + hasn't, true otherwise. When the screen saver stops, \c msg will + be posted to the current thread's message queue with the given + parameters. + */ + bool checkStarted(UINT msg, WPARAM, LPARAM); + + //@} + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + class FindScreenSaverInfo { + public: + HDESK m_desktop; + HWND m_window; + }; + + static BOOL CALLBACK killScreenSaverFunc(HWND hwnd, LPARAM lParam); + + void watchDesktop(); + void watchProcess(HANDLE process); + void unwatchProcess(); + void watchDesktopThread(void*); + void watchProcessThread(void*); + + void setSecure(bool secure, bool saveSecureAsInt); + bool isSecure(bool* wasSecureAnInt) const; + +private: + BOOL m_wasEnabled; + bool m_wasSecure; + bool m_wasSecureAnInt; + + HANDLE m_process; + Thread* m_watch; + DWORD m_threadID; + UINT m_msg; + WPARAM m_wParam; + LPARAM m_lParam; + + // checkActive state. true if the screen saver is being watched + // for deactivation (and is therefore active). + bool m_active; +}; diff --git a/src/lib/platform/MSWindowsSession.cpp b/src/lib/platform/MSWindowsSession.cpp new file mode 100644 index 0000000..63e8d8f --- /dev/null +++ b/src/lib/platform/MSWindowsSession.cpp @@ -0,0 +1,195 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 "platform/MSWindowsSession.h" + +#include "arch/win32/XArchWindows.h" +#include "barrier/XBarrier.h" +#include "base/Log.h" + +#include <Wtsapi32.h> + +MSWindowsSession::MSWindowsSession() : + m_activeSessionId(-1) +{ +} + +MSWindowsSession::~MSWindowsSession() +{ +} + +bool +MSWindowsSession::isProcessInSession(const char* name, PHANDLE process = NULL) +{ + // 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")); + throw XArch(new XArchEvalWindows()); + } + + PROCESSENTRY32 entry; + 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")); + throw XArch(new XArchEvalWindows()); + } + + // used to record process names for debug info + std::list<std::string> nameList; + + // now just iterate until we can find winlogon.exe pid + DWORD pid = 0; + while(gotEntry) { + + // make sure we're not checking the system process + if (entry.th32ProcessID != 0) { + + DWORD processSessionId; + BOOL pidToSidRet = ProcessIdToSessionId( + entry.th32ProcessID, &processSessionId); + + if (!pidToSidRet) { + // if we can not acquire session associated with a specified process, + // simply ignore it + LOG((CLOG_ERR "could not get session id for process id %i", entry.th32ProcessID)); + gotEntry = nextProcessEntry(snapshot, &entry); + continue; + } + else { + // only pay attention to processes in the active session + if (processSessionId == m_activeSessionId) { + + // store the names so we can record them for debug + nameList.push_back(entry.szExeFile); + + if (_stricmp(entry.szExeFile, name) == 0) { + pid = entry.th32ProcessID; + } + } + } + + } + + // now move on to the next entry (if we're not at the end) + gotEntry = nextProcessEntry(snapshot, &entry); + } + + std::string nameListJoin; + for(std::list<std::string>::iterator it = nameList.begin(); + it != nameList.end(); it++) { + nameListJoin.append(*it); + nameListJoin.append(", "); + } + + LOG((CLOG_DEBUG "processes in session %d: %s", + m_activeSessionId, nameListJoin.c_str())); + + CloseHandle(snapshot); + + if (pid) { + if (process != NULL) { + // now get the process, which we'll use to get the process token. + LOG((CLOG_DEBUG "found %s in session %i", name, m_activeSessionId)); + *process = OpenProcess(MAXIMUM_ALLOWED, FALSE, pid); + } + return true; + } + else { + LOG((CLOG_DEBUG "did not find %s in session %i", name, m_activeSessionId)); + return false; + } +} + +HANDLE +MSWindowsSession::getUserToken(LPSECURITY_ATTRIBUTES security) +{ + HANDLE sourceToken; + if (!WTSQueryUserToken(m_activeSessionId, &sourceToken)) { + LOG((CLOG_ERR "could not get token from session %d", m_activeSessionId)); + throw XArch(new XArchEvalWindows); + } + + HANDLE newToken; + if (!DuplicateTokenEx( + sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security, + SecurityImpersonation, TokenPrimary, &newToken)) { + + LOG((CLOG_ERR "could not duplicate token")); + throw XArch(new XArchEvalWindows); + } + + LOG((CLOG_DEBUG "duplicated, new token: %i", newToken)); + return newToken; +} + +BOOL +MSWindowsSession::hasChanged() +{ + return (m_activeSessionId != WTSGetActiveConsoleSessionId()); +} + +void +MSWindowsSession::updateActiveSession() +{ + m_activeSessionId = WTSGetActiveConsoleSessionId(); +} + + +BOOL +MSWindowsSession::nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry) +{ + BOOL gotEntry = Process32Next(snapshot, entry); + if (!gotEntry) { + + DWORD err = GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + + // only worry about error if it's not the end of the snapshot + LOG((CLOG_ERR "could not get next process entry")); + throw XArch(new XArchEvalWindows()); + } + } + + return gotEntry; +} + +String +MSWindowsSession::getActiveDesktopName() +{ + String result; + try { + HDESK hd = OpenInputDesktop(0, TRUE, GENERIC_READ); + if (hd != NULL) { + DWORD size; + GetUserObjectInformation(hd, UOI_NAME, NULL, 0, &size); + TCHAR* name = (TCHAR*)alloca(size + sizeof(TCHAR)); + GetUserObjectInformation(hd, UOI_NAME, name, size, &size); + result = name; + CloseDesktop(hd); + } + } + catch (std::exception& error) { + LOG((CLOG_ERR "failed to get active desktop name: %s", error.what())); + } + + return result; +} diff --git a/src/lib/platform/MSWindowsSession.h b/src/lib/platform/MSWindowsSession.h new file mode 100644 index 0000000..d777141 --- /dev/null +++ b/src/lib/platform/MSWindowsSession.h @@ -0,0 +1,52 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "base/String.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <Tlhelp32.h> + +class MSWindowsSession { +public: + MSWindowsSession(); + ~MSWindowsSession(); + + //! + /*! + Returns true if the session ID has changed since updateActiveSession was called. + */ + BOOL hasChanged(); + + bool isProcessInSession(const char* name, PHANDLE process); + + HANDLE getUserToken(LPSECURITY_ATTRIBUTES security); + + DWORD getActiveSessionId() { return m_activeSessionId; } + + void updateActiveSession(); + + String getActiveDesktopName(); + +private: + BOOL nextProcessEntry(HANDLE snapshot, LPPROCESSENTRY32 entry); + +private: + DWORD m_activeSessionId; +}; diff --git a/src/lib/platform/MSWindowsUtil.cpp b/src/lib/platform/MSWindowsUtil.cpp new file mode 100644 index 0000000..4b51781 --- /dev/null +++ b/src/lib/platform/MSWindowsUtil.cpp @@ -0,0 +1,64 @@ +/* + * 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 "platform/MSWindowsUtil.h" + +#include "base/String.h" + +#include <stdio.h> + +// +// MSWindowsUtil +// + +String +MSWindowsUtil::getString(HINSTANCE instance, DWORD id) +{ + char* msg = NULL; + int n = LoadString(instance, id, reinterpret_cast<LPSTR>(&msg), 0); + + if (n <= 0) { + return String(); + } + + return String (msg, n); +} + +String +MSWindowsUtil::getErrorString(HINSTANCE hinstance, DWORD error, DWORD id) +{ + char* buffer; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_FROM_SYSTEM, + 0, + error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR)&buffer, + 0, + NULL) == 0) { + String errorString = barrier::string::sprintf("%d", error); + return barrier::string::format(getString(hinstance, id).c_str(), + errorString.c_str()); + } + else { + String result(buffer); + LocalFree(buffer); + return result; + } +} diff --git a/src/lib/platform/MSWindowsUtil.h b/src/lib/platform/MSWindowsUtil.h new file mode 100644 index 0000000..95ec2cf --- /dev/null +++ b/src/lib/platform/MSWindowsUtil.h @@ -0,0 +1,40 @@ +/* + * 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 "base/String.h" + +#define WINDOWS_LEAN_AND_MEAN +#include <Windows.h> + +class MSWindowsUtil { +public: + //! Get message string + /*! + Gets a string for \p id from the string table of \p instance. + */ + static String getString(HINSTANCE instance, DWORD id); + + //! Get error string + /*! + Gets a system error message for \p error. If the error cannot be + found return the string for \p id, replacing ${1} with \p error. + */ + static String getErrorString(HINSTANCE, DWORD error, DWORD id); +}; diff --git a/src/lib/platform/MSWindowsWatchdog.cpp b/src/lib/platform/MSWindowsWatchdog.cpp new file mode 100644 index 0000000..ba1890e --- /dev/null +++ b/src/lib/platform/MSWindowsWatchdog.cpp @@ -0,0 +1,611 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2009 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 "platform/MSWindowsWatchdog.h" + +#include "ipc/IpcLogOutputter.h" +#include "ipc/IpcServer.h" +#include "ipc/IpcMessage.h" +#include "ipc/Ipc.h" +#include "barrier/App.h" +#include "barrier/ArgsBase.h" +#include "mt/Thread.h" +#include "arch/win32/ArchDaemonWindows.h" +#include "arch/win32/XArchWindows.h" +#include "arch/Arch.h" +#include "base/log_outputters.h" +#include "base/TMethodJob.h" +#include "base/Log.h" +#include "common/Version.h" + +#include <sstream> +#include <UserEnv.h> +#include <Shellapi.h> + +#define MAXIMUM_WAIT_TIME 3 +enum { + kOutputBufferSize = 4096 +}; + +typedef VOID (WINAPI *SendSas)(BOOL asUser); + +const char g_activeDesktop[] = {"activeDesktop:"}; + +MSWindowsWatchdog::MSWindowsWatchdog( + bool daemonized, + bool autoDetectCommand, + IpcServer& ipcServer, + IpcLogOutputter& ipcLogOutputter) : + m_thread(NULL), + m_autoDetectCommand(autoDetectCommand), + m_monitoring(true), + m_commandChanged(false), + m_stdOutWrite(NULL), + m_stdOutRead(NULL), + m_ipcServer(ipcServer), + m_ipcLogOutputter(ipcLogOutputter), + m_elevateProcess(false), + m_processFailures(0), + m_processRunning(false), + m_fileLogOutputter(NULL), + m_autoElevated(false), + m_ready(false), + m_daemonized(daemonized) +{ + m_mutex = ARCH->newMutex(); + m_condVar = ARCH->newCondVar(); +} + +MSWindowsWatchdog::~MSWindowsWatchdog() +{ + if (m_condVar != NULL) { + ARCH->closeCondVar(m_condVar); + } + + if (m_mutex != NULL) { + ARCH->closeMutex(m_mutex); + } +} + +void +MSWindowsWatchdog::startAsync() +{ + m_thread = new Thread(new TMethodJob<MSWindowsWatchdog>( + this, &MSWindowsWatchdog::mainLoop, nullptr)); + + m_outputThread = new Thread(new TMethodJob<MSWindowsWatchdog>( + this, &MSWindowsWatchdog::outputLoop, nullptr)); +} + +void +MSWindowsWatchdog::stop() +{ + m_monitoring = false; + + m_thread->wait(5); + delete m_thread; + + m_outputThread->wait(5); + delete m_outputThread; +} + +HANDLE +MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security) +{ + HANDLE sourceToken; + + BOOL tokenRet = OpenProcessToken( + process, + TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, + &sourceToken); + + if (!tokenRet) { + LOG((CLOG_ERR "could not open token, process handle: %d", process)); + throw XArch(new XArchEvalWindows()); + } + + LOG((CLOG_DEBUG "got token %i, duplicating", sourceToken)); + + HANDLE newToken; + BOOL duplicateRet = DuplicateTokenEx( + sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security, + SecurityImpersonation, TokenPrimary, &newToken); + + if (!duplicateRet) { + LOG((CLOG_ERR "could not duplicate token %i", sourceToken)); + throw XArch(new XArchEvalWindows()); + } + + LOG((CLOG_DEBUG "duplicated, new token: %i", newToken)); + return newToken; +} + +HANDLE +MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security) +{ + // always elevate if we are at the vista/7 login screen. we could also + // elevate for the uac dialog (consent.exe) but this would be pointless, + // since barrier would re-launch as non-elevated after the desk switch, + // and so would be unusable with the new elevated process taking focus. + if (m_elevateProcess || m_autoElevated) { + LOG((CLOG_DEBUG "getting elevated token, %s", + (m_elevateProcess ? "elevation required" : "at login screen"))); + + HANDLE process; + if (!m_session.isProcessInSession("winlogon.exe", &process)) { + throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe"); + } + + return duplicateProcessToken(process, security); + } else { + LOG((CLOG_DEBUG "getting non-elevated token")); + return m_session.getUserToken(security); + } +} + +void +MSWindowsWatchdog::mainLoop(void*) +{ + shutdownExistingProcesses(); + + SendSas sendSasFunc = NULL; + HINSTANCE sasLib = LoadLibrary("sas.dll"); + if (sasLib) { + LOG((CLOG_DEBUG "found sas.dll")); + sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS"); + } + + SECURITY_ATTRIBUTES saAttr; + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) { + throw XArch(new XArchEvalWindows()); + } + + ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION)); + + while (m_monitoring) { + try { + + if (m_processRunning && getCommand().empty()) { + LOG((CLOG_INFO "process started but command is empty, shutting down")); + shutdownExistingProcesses(); + m_processRunning = false; + continue; + } + + if (m_processFailures != 0) { + // increasing backoff period, maximum of 10 seconds. + int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10; + LOG((CLOG_INFO "backing off, wait=%ds, failures=%d", timeout, m_processFailures)); + ARCH->sleep(timeout); + } + + if (!getCommand().empty() && ((m_processFailures != 0) || m_session.hasChanged() || m_commandChanged)) { + startProcess(); + } + + if (m_processRunning && !isProcessActive()) { + + m_processFailures++; + m_processRunning = false; + + LOG((CLOG_WARN "detected application not running, pid=%d", + m_processInfo.dwProcessId)); + } + + if (sendSasFunc != NULL) { + + HANDLE sendSasEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\SendSAS"); + if (sendSasEvent != NULL) { + + // use SendSAS event to wait for next session (timeout 1 second). + if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0) { + LOG((CLOG_DEBUG "calling SendSAS")); + sendSasFunc(FALSE); + } + + CloseHandle(sendSasEvent); + continue; + } + } + + // if the sas event failed, wait by sleeping. + ARCH->sleep(1); + + } + catch (std::exception& e) { + LOG((CLOG_ERR "failed to launch, error: %s", e.what())); + m_processFailures++; + m_processRunning = false; + continue; + } + catch (...) { + LOG((CLOG_ERR "failed to launch, unknown error.")); + m_processFailures++; + m_processRunning = false; + continue; + } + } + + if (m_processRunning) { + LOG((CLOG_DEBUG "terminated running process on exit")); + shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20); + } + + LOG((CLOG_DEBUG "watchdog main thread finished")); +} + +bool +MSWindowsWatchdog::isProcessActive() +{ + DWORD exitCode; + GetExitCodeProcess(m_processInfo.hProcess, &exitCode); + return exitCode == STILL_ACTIVE; +} + +void +MSWindowsWatchdog::setFileLogOutputter(FileLogOutputter* outputter) +{ + m_fileLogOutputter = outputter; +} + +void +MSWindowsWatchdog::startProcess() +{ + if (m_command.empty()) { + throw XMSWindowsWatchdogError("cannot start process, command is empty"); + } + + m_commandChanged = false; + + if (m_processRunning) { + LOG((CLOG_DEBUG "closing existing process to make way for new one")); + shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20); + m_processRunning = false; + } + + m_session.updateActiveSession(); + + BOOL createRet; + if (!m_daemonized) { + createRet = doStartProcessAsSelf(m_command); + } else { + SECURITY_ATTRIBUTES sa; + ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); + + getActiveDesktop(&sa); + + ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES)); + HANDLE userToken = getUserToken(&sa); + m_elevateProcess = m_autoElevated ? m_autoElevated : m_elevateProcess; + m_autoElevated = false; + + // patch by Jack Zhou and Henry Tung + // set UIAccess to fix Windows 8 GUI interaction + // http://symless.com/spit/issues/details/3338/#c70 + DWORD uiAccess = 1; + SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD)); + + createRet = doStartProcessAsUser(m_command, userToken, &sa); + } + + if (!createRet) { + LOG((CLOG_ERR "could not launch")); + DWORD exitCode = 0; + GetExitCodeProcess(m_processInfo.hProcess, &exitCode); + LOG((CLOG_ERR "exit code: %d", exitCode)); + throw XArch(new XArchEvalWindows); + } + else { + // wait for program to fail. + ARCH->sleep(1); + if (!isProcessActive()) { + throw XMSWindowsWatchdogError("process immediately stopped"); + } + + m_processRunning = true; + m_processFailures = 0; + + LOG((CLOG_DEBUG "started process, session=%i, elevated: %s, command=%s", + m_session.getActiveSessionId(), + m_elevateProcess ? "yes" : "no", + m_command.c_str())); + } +} + +BOOL +MSWindowsWatchdog::doStartProcessAsSelf(String& command) +{ + DWORD creationFlags = + NORMAL_PRIORITY_CLASS | + CREATE_NO_WINDOW | + CREATE_UNICODE_ENVIRONMENT; + + STARTUPINFO si; + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.lpDesktop = "winsta0\\Default"; // TODO: maybe this should be \winlogon if we have logonui.exe? + si.hStdError = m_stdOutWrite; + si.hStdOutput = m_stdOutWrite; + si.dwFlags |= STARTF_USESTDHANDLES; + + LOG((CLOG_INFO "starting new process as self")); + return CreateProcess(NULL, LPSTR(command.c_str()), NULL, NULL, FALSE, creationFlags, NULL, NULL, &si, &m_processInfo); +} + +BOOL +MSWindowsWatchdog::doStartProcessAsUser(String& command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa) +{ + // clear, as we're reusing process info struct + ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION)); + + STARTUPINFO si; + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + si.lpDesktop = "winsta0\\Default"; // TODO: maybe this should be \winlogon if we have logonui.exe? + si.hStdError = m_stdOutWrite; + si.hStdOutput = m_stdOutWrite; + si.dwFlags |= STARTF_USESTDHANDLES; + + LPVOID environment; + BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE); + if (!blockRet) { + LOG((CLOG_ERR "could not create environment block")); + throw XArch(new XArchEvalWindows); + } + + DWORD creationFlags = + NORMAL_PRIORITY_CLASS | + CREATE_NO_WINDOW | + CREATE_UNICODE_ENVIRONMENT; + + // re-launch in current active user session + LOG((CLOG_INFO "starting new process as privileged user")); + BOOL createRet = CreateProcessAsUser( + userToken, NULL, LPSTR(command.c_str()), + sa, NULL, TRUE, creationFlags, + environment, NULL, &si, &m_processInfo); + + DestroyEnvironmentBlock(environment); + CloseHandle(userToken); + + return createRet; +} + +void +MSWindowsWatchdog::setCommand(const std::string& command, bool elevate) +{ + LOG((CLOG_INFO "service command updated")); + m_command = command; + m_elevateProcess = elevate; + m_commandChanged = true; + m_processFailures = 0; +} + +std::string +MSWindowsWatchdog::getCommand() const +{ + if (!m_autoDetectCommand) { + return m_command; + } + + // seems like a fairly convoluted way to get the process name + const char* launchName = App::instance().argsBase().m_pname; + std::string args = ARCH->commandLine(); + + // build up a full command line + std::stringstream cmdTemp; + cmdTemp << launchName << args; + + std::string cmd = cmdTemp.str(); + + size_t i; + std::string find = "--relaunch"; + while ((i = cmd.find(find)) != std::string::npos) { + cmd.replace(i, find.length(), ""); + } + + return cmd; +} + +void +MSWindowsWatchdog::outputLoop(void*) +{ + // +1 char for \0 + CHAR buffer[kOutputBufferSize + 1]; + + while (m_monitoring) { + + DWORD bytesRead; + BOOL success = ReadFile(m_stdOutRead, buffer, kOutputBufferSize, &bytesRead, NULL); + + // assume the process has gone away? slow down + // the reads until another one turns up. + if (!success || bytesRead == 0) { + ARCH->sleep(1); + } + else { + buffer[bytesRead] = '\0'; + + testOutput(buffer); + + m_ipcLogOutputter.write(kINFO, buffer); + + if (m_fileLogOutputter != NULL) { + m_fileLogOutputter->write(kINFO, buffer); + } + } + } +} + +void +MSWindowsWatchdog::shutdownProcess(HANDLE handle, DWORD pid, int timeout) +{ + DWORD exitCode; + GetExitCodeProcess(handle, &exitCode); + if (exitCode != STILL_ACTIVE) { + return; + } + + IpcShutdownMessage shutdown; + m_ipcServer.send(shutdown, kIpcClientNode); + + // wait for process to exit gracefully. + double start = ARCH->time(); + while (true) { + + GetExitCodeProcess(handle, &exitCode); + if (exitCode != STILL_ACTIVE) { + // yay, we got a graceful shutdown. there should be no hook in use errors! + LOG((CLOG_INFO "process %d was shutdown gracefully", pid)); + break; + } + else { + + double elapsed = (ARCH->time() - start); + if (elapsed > timeout) { + // if timeout reached, kill forcefully. + // calling TerminateProcess on barrier is very bad! + // it causes the hook DLL to stay loaded in some apps, + // making it impossible to start barrier again. + LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed)); + TerminateProcess(handle, kExitSuccess); + break; + } + + ARCH->sleep(1); + } + } +} + +void +MSWindowsWatchdog::shutdownExistingProcesses() +{ + // 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")); + throw XArch(new XArchEvalWindows); + } + + PROCESSENTRY32 entry; + 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")); + throw XArch(new XArchEvalWindows); + } + + // now just iterate until we can find winlogon.exe pid + DWORD pid = 0; + while (gotEntry) { + + // make sure we're not checking the system process + if (entry.th32ProcessID != 0) { + + if (_stricmp(entry.szExeFile, "barrierc.exe") == 0 || + _stricmp(entry.szExeFile, "barriers.exe") == 0) { + + HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID); + shutdownProcess(handle, entry.th32ProcessID, 10); + } + } + + // now move on to the next entry (if we're not at the end) + gotEntry = Process32Next(snapshot, &entry); + if (!gotEntry) { + + DWORD err = GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + + // only worry about error if it's not the end of the snapshot + LOG((CLOG_ERR "could not get subsiquent process entry")); + throw XArch(new XArchEvalWindows); + } + } + } + + CloseHandle(snapshot); + m_processRunning = false; +} + +void +MSWindowsWatchdog::getActiveDesktop(LPSECURITY_ATTRIBUTES security) +{ + String installedDir = ARCH->getInstalledDirectory(); + if (!installedDir.empty()) { + String syntoolCommand; + syntoolCommand.append("\"").append(installedDir).append("\\").append("syntool").append("\""); + syntoolCommand.append(" --get-active-desktop"); + + m_session.updateActiveSession(); + bool elevateProcess = m_elevateProcess; + m_elevateProcess = true; + HANDLE userToken = getUserToken(security); + m_elevateProcess = elevateProcess; + + BOOL createRet = doStartProcessAsUser(syntoolCommand, userToken, security); + + if (!createRet) { + DWORD rc = GetLastError(); + RevertToSelf(); + } + else { + LOG((CLOG_DEBUG "launched syntool to check active desktop")); + } + + ARCH->lockMutex(m_mutex); + int waitTime = 0; + while (!m_ready) { + if (waitTime >= MAXIMUM_WAIT_TIME) { + break; + } + + ARCH->waitCondVar(m_condVar, m_mutex, 1.0); + waitTime++; + } + m_ready = false; + ARCH->unlockMutex(m_mutex); + } +} + +void +MSWindowsWatchdog::testOutput(String buffer) +{ + // HACK: check standard output seems hacky. + size_t i = buffer.find(g_activeDesktop); + if (i != String::npos) { + size_t s = sizeof(g_activeDesktop); + String defaultDesktop("Default"); + String sub = buffer.substr(i + s - 1, defaultDesktop.size()); + if (sub != defaultDesktop) { + m_autoElevated = true; + } + + ARCH->lockMutex(m_mutex); + m_ready = true; + ARCH->broadcastCondVar(m_condVar); + ARCH->unlockMutex(m_mutex); + } +} diff --git a/src/lib/platform/MSWindowsWatchdog.h b/src/lib/platform/MSWindowsWatchdog.h new file mode 100644 index 0000000..64ffab3 --- /dev/null +++ b/src/lib/platform/MSWindowsWatchdog.h @@ -0,0 +1,99 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2009 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 "platform/MSWindowsSession.h" +#include "barrier/XBarrier.h" +#include "arch/IArchMultithread.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <string> +#include <list> + +class Thread; +class IpcLogOutputter; +class IpcServer; +class FileLogOutputter; + +class MSWindowsWatchdog { +public: + MSWindowsWatchdog( + bool daemonized, + bool autoDetectCommand, + IpcServer& ipcServer, + IpcLogOutputter& ipcLogOutputter); + virtual ~MSWindowsWatchdog(); + + void startAsync(); + std::string getCommand() const; + void setCommand(const std::string& command, bool elevate); + void stop(); + bool isProcessActive(); + void setFileLogOutputter(FileLogOutputter* outputter); + +private: + void mainLoop(void*); + void outputLoop(void*); + void shutdownProcess(HANDLE handle, DWORD pid, int timeout); + void shutdownExistingProcesses(); + HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security); + HANDLE getUserToken(LPSECURITY_ATTRIBUTES security); + void startProcess(); + BOOL doStartProcessAsUser(String& command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa); + BOOL doStartProcessAsSelf(String& command); + void sendSas(); + void getActiveDesktop(LPSECURITY_ATTRIBUTES security); + void testOutput(String buffer); + +private: + Thread* m_thread; + bool m_autoDetectCommand; + std::string m_command; + bool m_monitoring; + bool m_commandChanged; + HANDLE m_stdOutWrite; + HANDLE m_stdOutRead; + Thread* m_outputThread; + IpcServer& m_ipcServer; + IpcLogOutputter& m_ipcLogOutputter; + bool m_elevateProcess; + MSWindowsSession m_session; + PROCESS_INFORMATION m_processInfo; + int m_processFailures; + bool m_processRunning; + FileLogOutputter* m_fileLogOutputter; + bool m_autoElevated; + ArchMutex m_mutex; + ArchCond m_condVar; + bool m_ready; + bool m_daemonized; +}; + +//! Relauncher error +/*! +An error occured in the process watchdog. +*/ +class XMSWindowsWatchdogError : public XBarrier { +public: + XMSWindowsWatchdogError(const String& msg) : XBarrier(msg) { } + + // XBase overrides + virtual String getWhat() const throw() { return what(); } +}; diff --git a/src/lib/platform/OSXClipboard.cpp b/src/lib/platform/OSXClipboard.cpp new file mode 100644 index 0000000..710b471 --- /dev/null +++ b/src/lib/platform/OSXClipboard.cpp @@ -0,0 +1,259 @@ +/* + * 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 "platform/OSXClipboard.h" + +#include "barrier/Clipboard.h" +#include "platform/OSXClipboardUTF16Converter.h" +#include "platform/OSXClipboardTextConverter.h" +#include "platform/OSXClipboardBMPConverter.h" +#include "platform/OSXClipboardHTMLConverter.h" +#include "base/Log.h" +#include "arch/XArch.h" + +// +// OSXClipboard +// + +OSXClipboard::OSXClipboard() : + m_time(0), + m_pboard(NULL) +{ + m_converters.push_back(new OSXClipboardHTMLConverter); + m_converters.push_back(new OSXClipboardBMPConverter); + m_converters.push_back(new OSXClipboardUTF16Converter); + m_converters.push_back(new OSXClipboardTextConverter); + + + + OSStatus createErr = PasteboardCreate(kPasteboardClipboard, &m_pboard); + if (createErr != noErr) { + LOG((CLOG_DEBUG "failed to create clipboard reference: error %i", createErr)); + LOG((CLOG_ERR "unable to connect to pasteboard, clipboard sharing disabled", createErr)); + m_pboard = NULL; + return; + + } + + OSStatus syncErr = PasteboardSynchronize(m_pboard); + if (syncErr != noErr) { + LOG((CLOG_DEBUG "failed to syncronize clipboard: error %i", syncErr)); + } +} + +OSXClipboard::~OSXClipboard() +{ + clearConverters(); +} + + bool +OSXClipboard::empty() +{ + LOG((CLOG_DEBUG "emptying clipboard")); + if (m_pboard == NULL) + return false; + + OSStatus err = PasteboardClear(m_pboard); + if (err != noErr) { + LOG((CLOG_DEBUG "failed to clear clipboard: error %i", err)); + return false; + } + + return true; +} + + bool +OSXClipboard::synchronize() +{ + if (m_pboard == NULL) + return false; + + PasteboardSyncFlags flags = PasteboardSynchronize(m_pboard); + LOG((CLOG_DEBUG2 "flags: %x", flags)); + + if (flags & kPasteboardModified) { + return true; + } + return false; +} + + void +OSXClipboard::add(EFormat format, const String & data) +{ + if (m_pboard == NULL) + return; + + LOG((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format)); + if (format == IClipboard::kText) { + LOG((CLOG_DEBUG " format of data to be added to clipboard was kText")); + } + else if (format == IClipboard::kBitmap) { + LOG((CLOG_DEBUG " format of data to be added to clipboard was kBitmap")); + } + else if (format == IClipboard::kHTML) { + LOG((CLOG_DEBUG " format of data to be added to clipboard was kHTML")); + } + + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + + IOSXClipboardConverter* converter = *index; + + // skip converters for other formats + if (converter->getFormat() == format) { + String osXData = converter->fromIClipboard(data); + CFStringRef flavorType = converter->getOSXFormat(); + CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (UInt8 *)osXData.data(), osXData.size()); + PasteboardItemID itemID = 0; + + PasteboardPutItemFlavor( + m_pboard, + itemID, + flavorType, + dataRef, + kPasteboardFlavorNoFlags); + + LOG((CLOG_DEBUG "added %d bytes to clipboard format: %d", data.size(), format)); + } + + } +} + +bool +OSXClipboard::open(Time time) const +{ + if (m_pboard == NULL) + return false; + + LOG((CLOG_DEBUG "opening clipboard")); + m_time = time; + return true; +} + +void +OSXClipboard::close() const +{ + LOG((CLOG_DEBUG "closing clipboard")); + /* not needed */ +} + +IClipboard::Time +OSXClipboard::getTime() const +{ + return m_time; +} + +bool +OSXClipboard::has(EFormat format) const +{ + if (m_pboard == NULL) + return false; + + PasteboardItemID item; + PasteboardGetItemIdentifier(m_pboard, (CFIndex) 1, &item); + + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IOSXClipboardConverter* converter = *index; + if (converter->getFormat() == format) { + PasteboardFlavorFlags flags; + CFStringRef type = converter->getOSXFormat(); + + OSStatus res; + + if ((res = PasteboardGetItemFlavorFlags(m_pboard, item, type, &flags)) == noErr) { + return true; + } + } + } + + return false; +} + +String +OSXClipboard::get(EFormat format) const +{ + CFStringRef type; + PasteboardItemID item; + String result; + + if (m_pboard == NULL) + return result; + + PasteboardGetItemIdentifier(m_pboard, (CFIndex) 1, &item); + + + // find the converter for the first clipboard format we can handle + IOSXClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + + PasteboardFlavorFlags flags; + type = converter->getOSXFormat(); + + if (converter->getFormat() == format && + PasteboardGetItemFlavorFlags(m_pboard, item, type, &flags) == noErr) { + break; + } + converter = NULL; + } + + // if no converter then we don't recognize any formats + if (converter == NULL) { + LOG((CLOG_DEBUG "Unable to find converter for data")); + return result; + } + + // get the clipboard data. + CFDataRef buffer = NULL; + try { + OSStatus err = PasteboardCopyItemFlavorData(m_pboard, item, type, &buffer); + + if (err != noErr) { + throw err; + } + + result = String((char *) CFDataGetBytePtr(buffer), CFDataGetLength(buffer)); + } + catch (OSStatus err) { + LOG((CLOG_DEBUG "exception thrown in OSXClipboard::get MacError (%d)", err)); + } + catch (...) { + LOG((CLOG_DEBUG "unknown exception in OSXClipboard::get")); + RETHROW_XTHREAD + } + + if (buffer != NULL) + CFRelease(buffer); + + return converter->toIClipboard(result); +} + + void +OSXClipboard::clearConverters() +{ + if (m_pboard == NULL) + return; + + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} diff --git a/src/lib/platform/OSXClipboard.h b/src/lib/platform/OSXClipboard.h new file mode 100644 index 0000000..ba25c32 --- /dev/null +++ b/src/lib/platform/OSXClipboard.h @@ -0,0 +1,95 @@ +/* + * 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 "barrier/IClipboard.h" + +#include <Carbon/Carbon.h> +#include <vector> + +class IOSXClipboardConverter; + +//! OS X clipboard implementation +class OSXClipboard : public IClipboard { +public: + OSXClipboard(); + virtual ~OSXClipboard(); + + //! Test if clipboard is owned by barrier + static bool isOwnedByBarrier(); + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const String& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual String get(EFormat) const; + + bool synchronize(); +private: + void clearConverters(); + +private: + typedef std::vector<IOSXClipboardConverter*> ConverterList; + + mutable Time m_time; + ConverterList m_converters; + PasteboardRef m_pboard; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all Scrap book format +*/ +class IOSXClipboardConverter : public IInterface { +public: + //! @name accessors + //@{ + + //! Get clipboard format + /*! + Return the clipboard format this object converts from/to. + */ + virtual IClipboard::EFormat + getFormat() const = 0; + + //! returns the scrap flavor type that this object converts from/to + virtual CFStringRef + getOSXFormat() const = 0; + + //! Convert from IClipboard format + /*! + Convert from the IClipboard format to the Carbon scrap format. + The input data must be in the IClipboard format returned by + getFormat(). The return data will be in the scrap + format returned by getOSXFormat(). + */ + virtual String fromIClipboard(const String&) const = 0; + + //! Convert to IClipboard format + /*! + Convert from the carbon scrap format to the IClipboard format + (i.e., the reverse of fromIClipboard()). + */ + virtual String toIClipboard(const String&) const = 0; + + //@} +}; diff --git a/src/lib/platform/OSXClipboardAnyBitmapConverter.cpp b/src/lib/platform/OSXClipboardAnyBitmapConverter.cpp new file mode 100644 index 0000000..73f64c7 --- /dev/null +++ b/src/lib/platform/OSXClipboardAnyBitmapConverter.cpp @@ -0,0 +1,48 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * Patch by Ryan Chapman + * + * 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 "platform/OSXClipboardAnyBitmapConverter.h" +#include <algorithm> + +OSXClipboardAnyBitmapConverter::OSXClipboardAnyBitmapConverter() +{ + // do nothing +} + +OSXClipboardAnyBitmapConverter::~OSXClipboardAnyBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +OSXClipboardAnyBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +String +OSXClipboardAnyBitmapConverter::fromIClipboard(const String& data) const +{ + return doFromIClipboard(data); +} + +String +OSXClipboardAnyBitmapConverter::toIClipboard(const String& data) const +{ + return doToIClipboard(data); +} diff --git a/src/lib/platform/OSXClipboardAnyBitmapConverter.h b/src/lib/platform/OSXClipboardAnyBitmapConverter.h new file mode 100644 index 0000000..277e75d --- /dev/null +++ b/src/lib/platform/OSXClipboardAnyBitmapConverter.h @@ -0,0 +1,48 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * Patch by Ryan Chapman + * + * 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 "platform/OSXClipboard.h" + +//! Convert to/from some text encoding +class OSXClipboardAnyBitmapConverter : public IOSXClipboardConverter { +public: + OSXClipboardAnyBitmapConverter(); + virtual ~OSXClipboardAnyBitmapConverter(); + + // IOSXClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual CFStringRef getOSXFormat() const = 0; + virtual String fromIClipboard(const String &) const; + virtual String toIClipboard(const String &) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion and linefeed conversion. + */ + virtual String doFromIClipboard(const String&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion and Linefeed conversion. + */ + virtual String doToIClipboard(const String&) const = 0; +}; diff --git a/src/lib/platform/OSXClipboardAnyTextConverter.cpp b/src/lib/platform/OSXClipboardAnyTextConverter.cpp new file mode 100644 index 0000000..7095006 --- /dev/null +++ b/src/lib/platform/OSXClipboardAnyTextConverter.cpp @@ -0,0 +1,90 @@ +/* + * 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 "platform/OSXClipboardAnyTextConverter.h" + +#include <algorithm> + +// +// OSXClipboardAnyTextConverter +// + +OSXClipboardAnyTextConverter::OSXClipboardAnyTextConverter() +{ + // do nothing +} + +OSXClipboardAnyTextConverter::~OSXClipboardAnyTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +OSXClipboardAnyTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +String +OSXClipboardAnyTextConverter::fromIClipboard(const String& data) const +{ + // convert linefeeds and then convert to desired encoding + return doFromIClipboard(convertLinefeedToMacOS(data)); +} + +String +OSXClipboardAnyTextConverter::toIClipboard(const String& data) const +{ + // convert text then newlines + return convertLinefeedToUnix(doToIClipboard(data)); +} + +static +bool +isLF(char ch) +{ + return (ch == '\n'); +} + +static +bool +isCR(char ch) +{ + return (ch == '\r'); +} + +String +OSXClipboardAnyTextConverter::convertLinefeedToMacOS(const String& src) +{ + // note -- we assume src is a valid UTF-8 string + String copy = src; + + std::replace_if(copy.begin(), copy.end(), isLF, '\r'); + + return copy; +} + +String +OSXClipboardAnyTextConverter::convertLinefeedToUnix(const String& src) +{ + String copy = src; + + std::replace_if(copy.begin(), copy.end(), isCR, '\n'); + + return copy; +} diff --git a/src/lib/platform/OSXClipboardAnyTextConverter.h b/src/lib/platform/OSXClipboardAnyTextConverter.h new file mode 100644 index 0000000..ea42994 --- /dev/null +++ b/src/lib/platform/OSXClipboardAnyTextConverter.h @@ -0,0 +1,53 @@ +/* + * 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 "platform/OSXClipboard.h" + +//! Convert to/from some text encoding +class OSXClipboardAnyTextConverter : public IOSXClipboardConverter { +public: + OSXClipboardAnyTextConverter(); + virtual ~OSXClipboardAnyTextConverter(); + + // IOSXClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual CFStringRef + getOSXFormat() const = 0; + virtual String fromIClipboard(const String &) const; + virtual String toIClipboard(const String &) const; + +protected: + //! Convert from IClipboard format + /*! + Do UTF-8 conversion and linefeed conversion. + */ + virtual String doFromIClipboard(const String&) const = 0; + + //! Convert to IClipboard format + /*! + Do UTF-8 conversion and Linefeed conversion. + */ + virtual String doToIClipboard(const String&) const = 0; + +private: + static String convertLinefeedToMacOS(const String&); + static String convertLinefeedToUnix(const String&); +}; diff --git a/src/lib/platform/OSXClipboardBMPConverter.cpp b/src/lib/platform/OSXClipboardBMPConverter.cpp new file mode 100644 index 0000000..51c44ec --- /dev/null +++ b/src/lib/platform/OSXClipboardBMPConverter.cpp @@ -0,0 +1,134 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * Patch by Ryan Chapman + * + * 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 "platform/OSXClipboardBMPConverter.h" +#include "base/Log.h" + +// BMP file header structure +struct CBMPHeader { +public: + UInt16 type; + UInt32 size; + UInt16 reserved1; + UInt16 reserved2; + UInt32 offset; +}; + +// BMP is little-endian +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast<UInt32>(data[0]) | + (static_cast<UInt32>(data[1]) << 8) | + (static_cast<UInt32>(data[2]) << 16) | + (static_cast<UInt32>(data[3]) << 24); +} + +static +void +toLE(UInt8*& dst, char src) +{ + dst[0] = static_cast<UInt8>(src); + dst += 1; +} + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst[2] = static_cast<UInt8>((src >> 16) & 0xffu); + dst[3] = static_cast<UInt8>((src >> 24) & 0xffu); + dst += 4; +} + +OSXClipboardBMPConverter::OSXClipboardBMPConverter() +{ + // do nothing +} + +OSXClipboardBMPConverter::~OSXClipboardBMPConverter() +{ + // do nothing +} + +IClipboard::EFormat +OSXClipboardBMPConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +CFStringRef +OSXClipboardBMPConverter::getOSXFormat() const +{ + // TODO: does this only work with Windows? + return CFSTR("com.microsoft.bmp"); +} + +String +OSXClipboardBMPConverter::fromIClipboard(const String& bmp) const +{ + LOG((CLOG_DEBUG1 "ENTER OSXClipboardBMPConverter::doFromIClipboard()")); + // create BMP image + UInt8 header[14]; + UInt8* dst = header; + toLE(dst, 'B'); + toLE(dst, 'M'); + toLE(dst, static_cast<UInt32>(14 + bmp.size())); + toLE(dst, static_cast<UInt16>(0)); + toLE(dst, static_cast<UInt16>(0)); + toLE(dst, static_cast<UInt32>(14 + 40)); + return String(reinterpret_cast<const char*>(header), 14) + bmp; +} + +String +OSXClipboardBMPConverter::toIClipboard(const String& bmp) const +{ + // make sure data is big enough for a BMP file + if (bmp.size() <= 14 + 40) { + return String(); + } + + // check BMP file header + const UInt8* rawBMPHeader = reinterpret_cast<const UInt8*>(bmp.data()); + if (rawBMPHeader[0] != 'B' || rawBMPHeader[1] != 'M') { + return String(); + } + + // get offset to image data + UInt32 offset = fromLEU32(rawBMPHeader + 10); + + // construct BMP + if (offset == 14 + 40) { + return bmp.substr(14); + } + else { + return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset); + } +} diff --git a/src/lib/platform/OSXClipboardBMPConverter.h b/src/lib/platform/OSXClipboardBMPConverter.h new file mode 100644 index 0000000..400831d --- /dev/null +++ b/src/lib/platform/OSXClipboardBMPConverter.h @@ -0,0 +1,44 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * Patch by Ryan Chapman + * + * 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 "platform/OSXClipboard.h" + +//! Convert to/from some text encoding +class OSXClipboardBMPConverter : public IOSXClipboardConverter { +public: + OSXClipboardBMPConverter(); + virtual ~OSXClipboardBMPConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + + virtual CFStringRef + getOSXFormat() const; + + // OSXClipboardAnyBMPConverter overrides + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + + // generic encoding converter + static String convertString(const String& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding); +}; diff --git a/src/lib/platform/OSXClipboardHTMLConverter.cpp b/src/lib/platform/OSXClipboardHTMLConverter.cpp new file mode 100644 index 0000000..b5fdb77 --- /dev/null +++ b/src/lib/platform/OSXClipboardHTMLConverter.cpp @@ -0,0 +1,95 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * Patch by Ryan Chapman + * + * 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 "platform/OSXClipboardHTMLConverter.h" + +#include "base/Unicode.h" + +OSXClipboardHTMLConverter::OSXClipboardHTMLConverter() +{ + // do nothing +} + +OSXClipboardHTMLConverter::~OSXClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +OSXClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +CFStringRef +OSXClipboardHTMLConverter::getOSXFormat() const +{ + return CFSTR("public.html"); +} + +String +OSXClipboardHTMLConverter::convertString( + const String& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding) +{ + CFStringRef stringRef = CFStringCreateWithCString( + kCFAllocatorDefault, + data.c_str(), fromEncoding); + + if (stringRef == NULL) { + return String(); + } + + CFIndex buffSize; + CFRange entireString = CFRangeMake(0, CFStringGetLength(stringRef)); + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, NULL, 0, &buffSize); + + char* buffer = new char[buffSize]; + + if (buffer == NULL) { + CFRelease(stringRef); + return String(); + } + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, (UInt8*)buffer, buffSize, NULL); + + String result(buffer, buffSize); + + delete[] buffer; + CFRelease(stringRef); + + return result; +} + +String +OSXClipboardHTMLConverter::doFromIClipboard(const String& data) const +{ + return convertString(data, kCFStringEncodingUTF8, + CFStringGetSystemEncoding()); +} + +String +OSXClipboardHTMLConverter::doToIClipboard(const String& data) const +{ + return convertString(data, CFStringGetSystemEncoding(), + kCFStringEncodingUTF8); +} diff --git a/src/lib/platform/OSXClipboardHTMLConverter.h b/src/lib/platform/OSXClipboardHTMLConverter.h new file mode 100644 index 0000000..21c2b82 --- /dev/null +++ b/src/lib/platform/OSXClipboardHTMLConverter.h @@ -0,0 +1,44 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2014-2016 Symless Ltd. + * Patch by Ryan Chapman + * + * 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 "OSXClipboardAnyTextConverter.h" + +//! Convert to/from HTML encoding +class OSXClipboardHTMLConverter : public OSXClipboardAnyTextConverter { +public: + OSXClipboardHTMLConverter(); + virtual ~OSXClipboardHTMLConverter(); + + // IMSWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + + virtual CFStringRef getOSXFormat() const; + +protected: + // OSXClipboardAnyTextConverter overrides + virtual String doFromIClipboard(const String&) const; + virtual String doToIClipboard(const String&) const; + + // generic encoding converter + static String convertString(const String& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding); +}; diff --git a/src/lib/platform/OSXClipboardTextConverter.cpp b/src/lib/platform/OSXClipboardTextConverter.cpp new file mode 100644 index 0000000..c18ad3f --- /dev/null +++ b/src/lib/platform/OSXClipboardTextConverter.cpp @@ -0,0 +1,93 @@ +/* + * 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 "platform/OSXClipboardTextConverter.h" + +#include "base/Unicode.h" + +// +// OSXClipboardTextConverter +// + +OSXClipboardTextConverter::OSXClipboardTextConverter() +{ + // do nothing +} + +OSXClipboardTextConverter::~OSXClipboardTextConverter() +{ + // do nothing +} + +CFStringRef +OSXClipboardTextConverter::getOSXFormat() const +{ + return CFSTR("public.plain-text"); +} + +String +OSXClipboardTextConverter::convertString( + const String& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding) +{ + CFStringRef stringRef = + CFStringCreateWithCString(kCFAllocatorDefault, + data.c_str(), fromEncoding); + + if (stringRef == NULL) { + return String(); + } + + CFIndex buffSize; + CFRange entireString = CFRangeMake(0, CFStringGetLength(stringRef)); + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, NULL, 0, &buffSize); + + char* buffer = new char[buffSize]; + + if (buffer == NULL) { + CFRelease(stringRef); + return String(); + } + + CFStringGetBytes(stringRef, entireString, toEncoding, + 0, false, (UInt8*)buffer, buffSize, NULL); + + String result(buffer, buffSize); + + delete[] buffer; + CFRelease(stringRef); + + return result; +} + +String +OSXClipboardTextConverter::doFromIClipboard(const String& data) const +{ + return convertString(data, kCFStringEncodingUTF8, + CFStringGetSystemEncoding()); +} + +String +OSXClipboardTextConverter::doToIClipboard(const String& data) const +{ + return convertString(data, CFStringGetSystemEncoding(), + kCFStringEncodingUTF8); +} diff --git a/src/lib/platform/OSXClipboardTextConverter.h b/src/lib/platform/OSXClipboardTextConverter.h new file mode 100644 index 0000000..55d82ce --- /dev/null +++ b/src/lib/platform/OSXClipboardTextConverter.h @@ -0,0 +1,42 @@ +/* + * 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 "platform/OSXClipboardAnyTextConverter.h" + +//! Convert to/from locale text encoding +class OSXClipboardTextConverter : public OSXClipboardAnyTextConverter { +public: + OSXClipboardTextConverter(); + virtual ~OSXClipboardTextConverter(); + + // IOSXClipboardAnyTextConverter overrides + virtual CFStringRef + getOSXFormat() const; + +protected: + // OSXClipboardAnyTextConverter overrides + virtual String doFromIClipboard(const String&) const; + virtual String doToIClipboard(const String&) const; + + // generic encoding converter + static String convertString(const String& data, + CFStringEncoding fromEncoding, + CFStringEncoding toEncoding); +}; diff --git a/src/lib/platform/OSXClipboardUTF16Converter.cpp b/src/lib/platform/OSXClipboardUTF16Converter.cpp new file mode 100644 index 0000000..02d8fa3 --- /dev/null +++ b/src/lib/platform/OSXClipboardUTF16Converter.cpp @@ -0,0 +1,55 @@ +/* + * 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 "platform/OSXClipboardUTF16Converter.h" + +#include "base/Unicode.h" + +// +// OSXClipboardUTF16Converter +// + +OSXClipboardUTF16Converter::OSXClipboardUTF16Converter() +{ + // do nothing +} + +OSXClipboardUTF16Converter::~OSXClipboardUTF16Converter() +{ + // do nothing +} + +CFStringRef +OSXClipboardUTF16Converter::getOSXFormat() const +{ + return CFSTR("public.utf16-plain-text"); +} + +String +OSXClipboardUTF16Converter::doFromIClipboard(const String& data) const +{ + // convert and add nul terminator + return Unicode::UTF8ToUTF16(data); +} + +String +OSXClipboardUTF16Converter::doToIClipboard(const String& data) const +{ + // convert and strip nul terminator + return Unicode::UTF16ToUTF8(data); +} diff --git a/src/lib/platform/OSXClipboardUTF16Converter.h b/src/lib/platform/OSXClipboardUTF16Converter.h new file mode 100644 index 0000000..10bb595 --- /dev/null +++ b/src/lib/platform/OSXClipboardUTF16Converter.h @@ -0,0 +1,37 @@ +/* + * 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 "platform/OSXClipboardAnyTextConverter.h" + +//! Convert to/from UTF-16 encoding +class OSXClipboardUTF16Converter : public OSXClipboardAnyTextConverter { +public: + OSXClipboardUTF16Converter(); + virtual ~OSXClipboardUTF16Converter(); + + // IOSXClipboardAnyTextConverter overrides + virtual CFStringRef + getOSXFormat() const; + +protected: + // OSXClipboardAnyTextConverter overrides + virtual String doFromIClipboard(const String&) const; + virtual String doToIClipboard(const String&) const; +}; diff --git a/src/lib/platform/OSXDragSimulator.h b/src/lib/platform/OSXDragSimulator.h new file mode 100644 index 0000000..cb361ca --- /dev/null +++ b/src/lib/platform/OSXDragSimulator.h @@ -0,0 +1,34 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "common/common.h" + +#import <CoreFoundation/CoreFoundation.h> + +#if defined(__cplusplus) +extern "C" { +#endif +void runCocoaApp(); +void stopCocoaLoop(); +void fakeDragging(const char* str, int cursorX, int cursorY); +CFStringRef getCocoaDropTarget(); + +#if defined(__cplusplus) +} +#endif diff --git a/src/lib/platform/OSXDragSimulator.m b/src/lib/platform/OSXDragSimulator.m new file mode 100644 index 0000000..affed38 --- /dev/null +++ b/src/lib/platform/OSXDragSimulator.m @@ -0,0 +1,102 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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. + */ + +#import "platform/OSXDragSimulator.h" + +#import "platform/OSXDragView.h" + +#import <Foundation/Foundation.h> +#import <CoreData/CoreData.h> +#import <Cocoa/Cocoa.h> + +#if defined(MAC_OS_X_VERSION_10_7) + +NSWindow* g_dragWindow = NULL; +OSXDragView* g_dragView = NULL; +NSString* g_ext = NULL; + +void +runCocoaApp() +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + [NSApplication sharedApplication]; + + NSWindow* window = [[NSWindow alloc] + initWithContentRect: NSMakeRect(0, 0, 3, 3) + styleMask: NSBorderlessWindowMask + backing: NSBackingStoreBuffered + defer: NO]; + [window setTitle: @""]; + [window setAlphaValue:0.1]; + [window makeKeyAndOrderFront:nil]; + + OSXDragView* dragView = [[OSXDragView alloc] initWithFrame:NSMakeRect(0, 0, 3, 3)]; + + g_dragWindow = window; + g_dragView = dragView; + [window setContentView: dragView]; + + NSLog(@"starting cocoa loop"); + [NSApp run]; + + NSLog(@"cocoa: release"); + [pool release]; +} + +void +stopCocoaLoop() +{ + [NSApp stop: g_dragWindow]; +} + +void +fakeDragging(const char* str, int cursorX, int cursorY) +{ + g_ext = [NSString stringWithUTF8String:str]; + + dispatch_async(dispatch_get_main_queue(), ^{ + NSRect screen = [[NSScreen mainScreen] frame]; + NSLog ( @"screen size: witdh = %f height = %f", screen.size.width, screen.size.height); + NSLog ( @"mouseLocation: %d %d", cursorX, cursorY); + + int newPosX = 0; + int newPosY = 0; + newPosX = cursorX - 1; + newPosY = screen.size.height - cursorY - 1; + + NSRect rect = NSMakeRect(newPosX, newPosY, 3, 3); + NSLog ( @"newPosX: %d", newPosX); + NSLog ( @"newPosY: %d", newPosY); + + [g_dragWindow setFrame:rect display:NO]; + [g_dragWindow makeKeyAndOrderFront:nil]; + [NSApp activateIgnoringOtherApps:YES]; + + [g_dragView setFileExt:g_ext]; + + CGEventRef down = CGEventCreateMouseEvent(CGEventSourceCreate(kCGEventSourceStateHIDSystemState), kCGEventLeftMouseDown, CGPointMake(cursorX, cursorY), kCGMouseButtonLeft); + CGEventPost(kCGHIDEventTap, down); + }); +} + +CFStringRef +getCocoaDropTarget() +{ + // HACK: sleep, wait for cocoa drop target updated first + usleep(1000000); + return [g_dragView getDropTarget]; +} + +#endif diff --git a/src/lib/platform/OSXDragView.h b/src/lib/platform/OSXDragView.h new file mode 100644 index 0000000..9b8aa48 --- /dev/null +++ b/src/lib/platform/OSXDragView.h @@ -0,0 +1,34 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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/>. + */ + +#import <Cocoa/Cocoa.h> + +#ifdef MAC_OS_X_VERSION_10_7 + +@interface OSXDragView : NSView<NSDraggingSource,NSDraggingInfo> +{ + NSMutableString* m_dropTarget; + NSString* m_dragFileExt; +} + +- (CFStringRef)getDropTarget; +- (void)clearDropTarget; +- (void)setFileExt:(NSString*) ext; + +@end + +#endif diff --git a/src/lib/platform/OSXDragView.m b/src/lib/platform/OSXDragView.m new file mode 100644 index 0000000..9f77499 --- /dev/null +++ b/src/lib/platform/OSXDragView.m @@ -0,0 +1,177 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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/>. + */ + +#import "platform/OSXDragView.h" + +#ifdef MAC_OS_X_VERSION_10_7 + +@implementation OSXDragView + +@dynamic draggingFormation; +@dynamic animatesToDestination; +@dynamic numberOfValidItemsForDrop; + +/* springLoadingHighlight is a property that will not be auto-synthesized by + clang. explicitly synthesizing it here as well as defining an empty handler + for resetSpringLoading() satisfies the compiler */ +@synthesize springLoadingHighlight = _springLoadingHighlight; + +/* unused */ +- (void) +resetSpringLoading +{ +} + +- (id) +initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + m_dropTarget = [[NSMutableString alloc] initWithCapacity:0]; + m_dragFileExt = [[NSMutableString alloc] initWithCapacity:0]; + return self; +} + +- (void) +drawRect:(NSRect)dirtyRect +{ +} + +- (BOOL) +acceptsFirstMouse:(NSEvent *)theEvent +{ + return YES; +} + +- (void) +mouseDown:(NSEvent *)theEvent +{ + NSLog ( @"cocoa mouse down"); + NSPoint dragPosition; + NSRect imageLocation; + dragPosition = [self convertPoint:[theEvent locationInWindow] + fromView:nil]; + + dragPosition.x -= 16; + dragPosition.y -= 16; + imageLocation.origin = dragPosition; + imageLocation.size = NSMakeSize(32,32); + [self dragPromisedFilesOfTypes:[NSArray arrayWithObject:m_dragFileExt] + fromRect:imageLocation + source:self + slideBack:NO + event:theEvent]; +} + +- (NSArray*) +namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination +{ + [m_dropTarget setString:@""]; + [m_dropTarget appendString:dropDestination.path]; + NSLog ( @"cocoa drop target: %@", m_dropTarget); + return nil; +} + +- (NSDragOperation) +draggingSourceOperationMaskForLocal:(BOOL)flag +{ + return NSDragOperationCopy; +} + +- (CFStringRef) +getDropTarget +{ + NSMutableString* string; + string = [[NSMutableString alloc] initWithCapacity:0]; + [string appendString:m_dropTarget]; + return (CFStringRef)string; +} + +- (void) +clearDropTarget +{ + [m_dropTarget setString:@""]; +} + +- (void) +setFileExt:(NSString*) ext +{ + [ext retain]; + [m_dragFileExt release]; + m_dragFileExt = ext; + NSLog(@"drag file ext: %@", m_dragFileExt); +} + +- (NSWindow *) +draggingDestinationWindow +{ + return nil; +} + +- (NSDragOperation) +draggingSourceOperationMask +{ + return NSDragOperationCopy; +} + +- (NSPoint)draggingLocation +{ + NSPoint point; + return point; +} + +- (NSPoint)draggedImageLocation +{ + NSPoint point; + return point; +} + +- (NSImage *)draggedImage +{ + return nil; +} + +- (NSPasteboard *)draggingPasteboard +{ + return nil; +} + +- (id)draggingSource +{ + return nil; +} + +- (NSInteger)draggingSequenceNumber +{ + return 0; +} + +- (void)slideDraggedImageTo:(NSPoint)screenPoint +{ +} + +- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + return NSDragOperationCopy; +} + +- (void)enumerateDraggingItemsWithOptions:(NSDraggingItemEnumerationOptions)enumOpts forView:(NSView *)view classes:(NSArray *)classArray searchOptions:(NSDictionary *)searchOptions usingBlock:(void (^)(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop))block +{ +} + +@end + +#endif diff --git a/src/lib/platform/OSXEventQueueBuffer.cpp b/src/lib/platform/OSXEventQueueBuffer.cpp new file mode 100644 index 0000000..8e18afc --- /dev/null +++ b/src/lib/platform/OSXEventQueueBuffer.cpp @@ -0,0 +1,143 @@ +/* + * 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 "platform/OSXEventQueueBuffer.h" + +#include "base/Event.h" +#include "base/IEventQueue.h" + +// +// EventQueueTimer +// + +class EventQueueTimer { }; + +// +// OSXEventQueueBuffer +// + +OSXEventQueueBuffer::OSXEventQueueBuffer(IEventQueue* events) : + m_event(NULL), + m_eventQueue(events), + m_carbonEventQueue(NULL) +{ + // do nothing +} + +OSXEventQueueBuffer::~OSXEventQueueBuffer() +{ + // release the last event + if (m_event != NULL) { + ReleaseEvent(m_event); + } +} + +void +OSXEventQueueBuffer::init() +{ + m_carbonEventQueue = GetCurrentEventQueue(); +} + +void +OSXEventQueueBuffer::waitForEvent(double timeout) +{ + EventRef event; + ReceiveNextEvent(0, NULL, timeout, false, &event); +} + +IEventQueueBuffer::Type +OSXEventQueueBuffer::getEvent(Event& event, UInt32& dataID) +{ + // release the previous event + if (m_event != NULL) { + ReleaseEvent(m_event); + m_event = NULL; + } + + // get the next event + OSStatus error = ReceiveNextEvent(0, NULL, 0.0, true, &m_event); + + // handle the event + if (error == eventLoopQuitErr) { + event = Event(Event::kQuit); + return kSystem; + } + else if (error != noErr) { + return kNone; + } + else { + UInt32 eventClass = GetEventClass(m_event); + switch (eventClass) { + case 'Syne': + dataID = GetEventKind(m_event); + return kUser; + + default: + event = Event(Event::kSystem, + m_eventQueue->getSystemTarget(), &m_event); + return kSystem; + } + } +} + +bool +OSXEventQueueBuffer::addEvent(UInt32 dataID) +{ + EventRef event; + OSStatus error = CreateEvent( + kCFAllocatorDefault, + 'Syne', + dataID, + 0, + kEventAttributeNone, + &event); + + if (error == noErr) { + + assert(m_carbonEventQueue != NULL); + + error = PostEventToQueue( + m_carbonEventQueue, + event, + kEventPriorityStandard); + + ReleaseEvent(event); + } + + return (error == noErr); +} + +bool +OSXEventQueueBuffer::isEmpty() const +{ + EventRef event; + OSStatus status = ReceiveNextEvent(0, NULL, 0.0, false, &event); + return (status == eventLoopTimedOutErr); +} + +EventQueueTimer* +OSXEventQueueBuffer::newTimer(double, bool) const +{ + return new EventQueueTimer; +} + +void +OSXEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const +{ + delete timer; +} diff --git a/src/lib/platform/OSXEventQueueBuffer.h b/src/lib/platform/OSXEventQueueBuffer.h new file mode 100644 index 0000000..28c4a5d --- /dev/null +++ b/src/lib/platform/OSXEventQueueBuffer.h @@ -0,0 +1,47 @@ +/* + * 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 "base/IEventQueueBuffer.h" + +#include <Carbon/Carbon.h> + +class IEventQueue; + +//! Event queue buffer for OS X +class OSXEventQueueBuffer : public IEventQueueBuffer { +public: + OSXEventQueueBuffer(IEventQueue* eventQueue); + virtual ~OSXEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void init(); + virtual void waitForEvent(double timeout); + virtual Type getEvent(Event& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(EventQueueTimer*) const; + +private: + EventRef m_event; + IEventQueue* m_eventQueue; + EventQueueRef m_carbonEventQueue; +}; diff --git a/src/lib/platform/OSXKeyState.cpp b/src/lib/platform/OSXKeyState.cpp new file mode 100644 index 0000000..482d7c1 --- /dev/null +++ b/src/lib/platform/OSXKeyState.cpp @@ -0,0 +1,912 @@ +/* + * 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 "platform/OSXKeyState.h" +#include "platform/OSXUchrKeyResource.h" +#include "platform/OSXMediaKeySupport.h" +#include "arch/Arch.h" +#include "base/Log.h" + +#include <Carbon/Carbon.h> +#include <IOKit/hidsystem/IOHIDLib.h> + +// Note that some virtual keys codes appear more than once. The +// first instance of a virtual key code maps to the KeyID that we +// want to generate for that code. The others are for mapping +// different KeyIDs to a single key code. +static const UInt32 s_shiftVK = kVK_Shift; +static const UInt32 s_controlVK = kVK_Control; +static const UInt32 s_altVK = kVK_Option; +static const UInt32 s_superVK = kVK_Command; +static const UInt32 s_capsLockVK = kVK_CapsLock; +static const UInt32 s_numLockVK = kVK_ANSI_KeypadClear; // 71 + +static const UInt32 s_brightnessUp = 144; +static const UInt32 s_brightnessDown = 145; +static const UInt32 s_missionControlVK = 160; +static const UInt32 s_launchpadVK = 131; + +static const UInt32 s_osxNumLock = 1 << 16; + +struct KeyEntry { +public: + KeyID m_keyID; + UInt32 m_virtualKey; +}; +static const KeyEntry s_controlKeys[] = { + // cursor keys. if we don't do this we'll may still get these from + // the keyboard resource but they may not correspond to the arrow + // keys. + { kKeyLeft, kVK_LeftArrow }, + { kKeyRight, kVK_RightArrow }, + { kKeyUp, kVK_UpArrow }, + { kKeyDown, kVK_DownArrow }, + { kKeyHome, kVK_Home }, + { kKeyEnd, kVK_End }, + { kKeyPageUp, kVK_PageUp }, + { kKeyPageDown, kVK_PageDown }, + { kKeyInsert, kVK_Help }, // Mac Keyboards have 'Help' on 'Insert' + + // function keys + { kKeyF1, kVK_F1 }, + { kKeyF2, kVK_F2 }, + { kKeyF3, kVK_F3 }, + { kKeyF4, kVK_F4 }, + { kKeyF5, kVK_F5 }, + { kKeyF6, kVK_F6 }, + { kKeyF7, kVK_F7 }, + { kKeyF8, kVK_F8 }, + { kKeyF9, kVK_F9 }, + { kKeyF10, kVK_F10 }, + { kKeyF11, kVK_F11 }, + { kKeyF12, kVK_F12 }, + { kKeyF13, kVK_F13 }, + { kKeyF14, kVK_F14 }, + { kKeyF15, kVK_F15 }, + { kKeyF16, kVK_F16 }, + + { kKeyKP_0, kVK_ANSI_Keypad0 }, + { kKeyKP_1, kVK_ANSI_Keypad1 }, + { kKeyKP_2, kVK_ANSI_Keypad2 }, + { kKeyKP_3, kVK_ANSI_Keypad3 }, + { kKeyKP_4, kVK_ANSI_Keypad4 }, + { kKeyKP_5, kVK_ANSI_Keypad5 }, + { kKeyKP_6, kVK_ANSI_Keypad6 }, + { kKeyKP_7, kVK_ANSI_Keypad7 }, + { kKeyKP_8, kVK_ANSI_Keypad8 }, + { kKeyKP_9, kVK_ANSI_Keypad9 }, + { kKeyKP_Decimal, kVK_ANSI_KeypadDecimal }, + { kKeyKP_Equal, kVK_ANSI_KeypadEquals }, + { kKeyKP_Multiply, kVK_ANSI_KeypadMultiply }, + { kKeyKP_Add, kVK_ANSI_KeypadPlus }, + { kKeyKP_Divide, kVK_ANSI_KeypadDivide }, + { kKeyKP_Subtract, kVK_ANSI_KeypadMinus }, + { kKeyKP_Enter, kVK_ANSI_KeypadEnter }, + + // virtual key 110 is fn+enter and i have no idea what that's supposed + // to map to. also the enter key with numlock on is a modifier but i + // don't know which. + + // modifier keys. OS X doesn't seem to support right handed versions + // of modifier keys so we map them to the left handed versions. + { kKeyShift_L, s_shiftVK }, + { kKeyShift_R, s_shiftVK }, // 60 + { kKeyControl_L, s_controlVK }, + { kKeyControl_R, s_controlVK }, // 62 + { kKeyAlt_L, s_altVK }, + { kKeyAlt_R, s_altVK }, + { kKeySuper_L, s_superVK }, + { kKeySuper_R, s_superVK }, // 61 + { kKeyMeta_L, s_superVK }, + { kKeyMeta_R, s_superVK }, // 61 + + // toggle modifiers + { kKeyNumLock, s_numLockVK }, + { kKeyCapsLock, s_capsLockVK }, + + { kKeyMissionControl, s_missionControlVK }, + { kKeyLaunchpad, s_launchpadVK }, + { kKeyBrightnessUp, s_brightnessUp }, + { kKeyBrightnessDown, s_brightnessDown } +}; + + +// +// OSXKeyState +// + +OSXKeyState::OSXKeyState(IEventQueue* events) : + KeyState(events) +{ + init(); +} + +OSXKeyState::OSXKeyState(IEventQueue* events, barrier::KeyMap& keyMap) : + KeyState(events, keyMap) +{ + init(); +} + +OSXKeyState::~OSXKeyState() +{ +} + +void +OSXKeyState::init() +{ + m_deadKeyState = 0; + m_shiftPressed = false; + m_controlPressed = false; + m_altPressed = false; + m_superPressed = false; + m_capsPressed = false; + + // build virtual key map + for (size_t i = 0; i < sizeof(s_controlKeys) / sizeof(s_controlKeys[0]); + ++i) { + + m_virtualKeyMap[s_controlKeys[i].m_virtualKey] = + s_controlKeys[i].m_keyID; + } +} + +KeyModifierMask +OSXKeyState::mapModifiersFromOSX(UInt32 mask) const +{ + KeyModifierMask outMask = 0; + if ((mask & kCGEventFlagMaskShift) != 0) { + outMask |= KeyModifierShift; + } + if ((mask & kCGEventFlagMaskControl) != 0) { + outMask |= KeyModifierControl; + } + if ((mask & kCGEventFlagMaskAlternate) != 0) { + outMask |= KeyModifierAlt; + } + if ((mask & kCGEventFlagMaskCommand) != 0) { + outMask |= KeyModifierSuper; + } + if ((mask & kCGEventFlagMaskAlphaShift) != 0) { + outMask |= KeyModifierCapsLock; + } + if ((mask & kCGEventFlagMaskNumericPad) != 0) { + outMask |= KeyModifierNumLock; + } + + LOG((CLOG_DEBUG1 "mask=%04x outMask=%04x", mask, outMask)); + return outMask; +} + +KeyModifierMask +OSXKeyState::mapModifiersToCarbon(UInt32 mask) const +{ + KeyModifierMask outMask = 0; + if ((mask & kCGEventFlagMaskShift) != 0) { + outMask |= shiftKey; + } + if ((mask & kCGEventFlagMaskControl) != 0) { + outMask |= controlKey; + } + if ((mask & kCGEventFlagMaskCommand) != 0) { + outMask |= cmdKey; + } + if ((mask & kCGEventFlagMaskAlternate) != 0) { + outMask |= optionKey; + } + if ((mask & kCGEventFlagMaskAlphaShift) != 0) { + outMask |= alphaLock; + } + if ((mask & kCGEventFlagMaskNumericPad) != 0) { + outMask |= s_osxNumLock; + } + + return outMask; +} + +KeyButton +OSXKeyState::mapKeyFromEvent(KeyIDs& ids, + KeyModifierMask* maskOut, CGEventRef event) const +{ + ids.clear(); + + // map modifier key + if (maskOut != NULL) { + KeyModifierMask activeMask = getActiveModifiers(); + activeMask &= ~KeyModifierAltGr; + *maskOut = activeMask; + } + + // get virtual key + UInt32 vkCode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); + + // handle up events + UInt32 eventKind = CGEventGetType(event); + if (eventKind == kCGEventKeyUp) { + // the id isn't used. we just need the same button we used on + // the key press. note that we don't use or reset the dead key + // state; up events should not affect the dead key state. + ids.push_back(kKeyNone); + return mapVirtualKeyToKeyButton(vkCode); + } + + // check for special keys + VirtualKeyMap::const_iterator i = m_virtualKeyMap.find(vkCode); + if (i != m_virtualKeyMap.end()) { + m_deadKeyState = 0; + ids.push_back(i->second); + return mapVirtualKeyToKeyButton(vkCode); + } + + // get keyboard info + TISInputSourceRef currentKeyboardLayout = TISCopyCurrentKeyboardLayoutInputSource(); + + if (currentKeyboardLayout == NULL) { + return kKeyNone; + } + + // get the event modifiers and remove the command and control + // keys. note if we used them though. + // UCKeyTranslate expects old-style Carbon modifiers, so convert. + UInt32 modifiers; + modifiers = mapModifiersToCarbon(CGEventGetFlags(event)); + static const UInt32 s_commandModifiers = + cmdKey | controlKey | rightControlKey; + bool isCommand = ((modifiers & s_commandModifiers) != 0); + modifiers &= ~s_commandModifiers; + + // if we've used a command key then we want the glyph produced without + // the option key (i.e. the base glyph). + //if (isCommand) { + modifiers &= ~optionKey; + //} + + // choose action + UInt16 action; + if (eventKind==kCGEventKeyDown) { + action = kUCKeyActionDown; + } + else if (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat)==1) { + action = kUCKeyActionAutoKey; + } + else { + return 0; + } + + // translate via uchr resource + CFDataRef ref = (CFDataRef) TISGetInputSourceProperty(currentKeyboardLayout, + kTISPropertyUnicodeKeyLayoutData); + const UCKeyboardLayout* layout = (const UCKeyboardLayout*) CFDataGetBytePtr(ref); + const bool layoutValid = (layout != NULL); + + if (layoutValid) { + // translate key + UniCharCount count; + UniChar chars[2]; + LOG((CLOG_DEBUG2 "modifiers: %08x", modifiers & 0xffu)); + OSStatus status = UCKeyTranslate(layout, + vkCode & 0xffu, action, + (modifiers >> 8) & 0xffu, + LMGetKbdType(), 0, &m_deadKeyState, + sizeof(chars) / sizeof(chars[0]), &count, chars); + + // get the characters + if (status == 0) { + if (count != 0 || m_deadKeyState == 0) { + m_deadKeyState = 0; + for (UniCharCount i = 0; i < count; ++i) { + ids.push_back(IOSXKeyResource::unicharToKeyID(chars[i])); + } + adjustAltGrModifier(ids, maskOut, isCommand); + return mapVirtualKeyToKeyButton(vkCode); + } + return 0; + } + } + + return 0; +} + +bool +OSXKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + +bool +OSXKeyState::fakeMediaKey(KeyID id) +{ + return fakeNativeMediaKey(id);; +} + +CGEventFlags +OSXKeyState::getModifierStateAsOSXFlags() +{ + CGEventFlags modifiers = 0; + + if (m_shiftPressed) { + modifiers |= kCGEventFlagMaskShift; + } + + if (m_controlPressed) { + modifiers |= kCGEventFlagMaskControl; + } + + if (m_altPressed) { + modifiers |= kCGEventFlagMaskAlternate; + } + + if (m_superPressed) { + modifiers |= kCGEventFlagMaskCommand; + } + + if (m_capsPressed) { + modifiers |= kCGEventFlagMaskAlphaShift; + } + + return modifiers; +} + +KeyModifierMask +OSXKeyState::pollActiveModifiers() const +{ + // falsely assumed that the mask returned by GetCurrentKeyModifiers() + // was the same as a CGEventFlags (which is what mapModifiersFromOSX + // expects). patch by Marc + UInt32 mask = GetCurrentKeyModifiers(); + KeyModifierMask outMask = 0; + + if ((mask & shiftKey) != 0) { + outMask |= KeyModifierShift; + } + if ((mask & controlKey) != 0) { + outMask |= KeyModifierControl; + } + if ((mask & optionKey) != 0) { + outMask |= KeyModifierAlt; + } + if ((mask & cmdKey) != 0) { + outMask |= KeyModifierSuper; + } + if ((mask & alphaLock) != 0) { + outMask |= KeyModifierCapsLock; + } + if ((mask & s_osxNumLock) != 0) { + outMask |= KeyModifierNumLock; + } + + LOG((CLOG_DEBUG1 "mask=%04x outMask=%04x", mask, outMask)); + return outMask; +} + +SInt32 +OSXKeyState::pollActiveGroup() const +{ + TISInputSourceRef keyboardLayout = TISCopyCurrentKeyboardLayoutInputSource(); + CFDataRef id = (CFDataRef)TISGetInputSourceProperty( + keyboardLayout, kTISPropertyInputSourceID); + + GroupMap::const_iterator i = m_groupMap.find(id); + if (i != m_groupMap.end()) { + return i->second; + } + + LOG((CLOG_DEBUG "can't get the active group, use the first group instead")); + + return 0; +} + +void +OSXKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + ::KeyMap km; + GetKeys(km); + const UInt8* m = reinterpret_cast<const UInt8*>(km); + for (UInt32 i = 0; i < 16; ++i) { + for (UInt32 j = 0; j < 8; ++j) { + if ((m[i] & (1u << j)) != 0) { + pressedKeys.insert(mapVirtualKeyToKeyButton(8 * i + j)); + } + } + } +} + +void +OSXKeyState::getKeyMap(barrier::KeyMap& keyMap) +{ + // update keyboard groups + if (getGroups(m_groups)) { + m_groupMap.clear(); + SInt32 numGroups = (SInt32)m_groups.size(); + for (SInt32 g = 0; g < numGroups; ++g) { + CFDataRef id = (CFDataRef)TISGetInputSourceProperty( + m_groups[g], kTISPropertyInputSourceID); + m_groupMap[id] = g; + } + } + + UInt32 keyboardType = LMGetKbdType(); + for (SInt32 g = 0, n = (SInt32)m_groups.size(); g < n; ++g) { + // add special keys + getKeyMapForSpecialKeys(keyMap, g); + + const void* resource; + bool layoutValid = false; + + // add regular keys + // try uchr resource first + CFDataRef resourceRef = (CFDataRef)TISGetInputSourceProperty( + m_groups[g], kTISPropertyUnicodeKeyLayoutData); + + layoutValid = resourceRef != NULL; + if (layoutValid) + resource = CFDataGetBytePtr(resourceRef); + + if (layoutValid) { + OSXUchrKeyResource uchr(resource, keyboardType); + if (uchr.isValid()) { + LOG((CLOG_DEBUG1 "using uchr resource for group %d", g)); + getKeyMap(keyMap, g, uchr); + continue; + } + } + + LOG((CLOG_DEBUG1 "no keyboard resource for group %d", g)); + } +} + +static io_connect_t getEventDriver(void) +{ + static mach_port_t sEventDrvrRef = 0; + mach_port_t masterPort, service, iter; + kern_return_t kr; + + if (!sEventDrvrRef) { + // Get master device port + kr = IOMasterPort(bootstrap_port, &masterPort); + assert(KERN_SUCCESS == kr); + + kr = IOServiceGetMatchingServices(masterPort, + IOServiceMatching(kIOHIDSystemClass), &iter); + assert(KERN_SUCCESS == kr); + + service = IOIteratorNext(iter); + assert(service); + + kr = IOServiceOpen(service, mach_task_self(), + kIOHIDParamConnectType, &sEventDrvrRef); + assert(KERN_SUCCESS == kr); + + IOObjectRelease(service); + IOObjectRelease(iter); + } + + return sEventDrvrRef; +} + +void +OSXKeyState::postHIDVirtualKey(const UInt8 virtualKeyCode, + const bool postDown) +{ + static UInt32 modifiers = 0; + + NXEventData event; + IOGPoint loc = { 0, 0 }; + UInt32 modifiersDelta = 0; + + bzero(&event, sizeof(NXEventData)); + + switch (virtualKeyCode) + { + case s_shiftVK: + case s_superVK: + case s_altVK: + case s_controlVK: + case s_capsLockVK: + switch (virtualKeyCode) + { + case s_shiftVK: + modifiersDelta = NX_SHIFTMASK; + m_shiftPressed = postDown; + break; + case s_superVK: + modifiersDelta = NX_COMMANDMASK; + m_superPressed = postDown; + break; + case s_altVK: + modifiersDelta = NX_ALTERNATEMASK; + m_altPressed = postDown; + break; + case s_controlVK: + modifiersDelta = NX_CONTROLMASK; + m_controlPressed = postDown; + break; + case s_capsLockVK: + modifiersDelta = NX_ALPHASHIFTMASK; + m_capsPressed = postDown; + break; + } + + // update the modifier bit + if (postDown) { + modifiers |= modifiersDelta; + } + else { + modifiers &= ~modifiersDelta; + } + + kern_return_t kr; + kr = IOHIDPostEvent(getEventDriver(), NX_FLAGSCHANGED, loc, + &event, kNXEventDataVersion, modifiers, true); + assert(KERN_SUCCESS == kr); + break; + + default: + event.key.repeat = false; + event.key.keyCode = virtualKeyCode; + event.key.origCharSet = event.key.charSet = NX_ASCIISET; + event.key.origCharCode = event.key.charCode = 0; + kr = IOHIDPostEvent(getEventDriver(), + postDown ? NX_KEYDOWN : NX_KEYUP, + loc, &event, kNXEventDataVersion, 0, false); + assert(KERN_SUCCESS == kr); + break; + } +} + +void +OSXKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: { + + KeyButton button = keystroke.m_data.m_button.m_button; + bool keyDown = keystroke.m_data.m_button.m_press; + CGKeyCode virtualKey = mapKeyButtonToVirtualKey(button); + + LOG((CLOG_DEBUG1 + " button=0x%04x virtualKey=0x%04x keyDown=%s", + button, virtualKey, keyDown ? "down" : "up")); + + postHIDVirtualKey(virtualKey, keyDown); + + break; + } + + case Keystroke::kGroup: { + SInt32 group = keystroke.m_data.m_group.m_group; + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", group)); + setGroup(group); + } + else { + LOG((CLOG_DEBUG1 " group %+d", group)); + setGroup(getEffectiveGroup(pollActiveGroup(), group)); + } + break; + } + } +} + +void +OSXKeyState::getKeyMapForSpecialKeys(barrier::KeyMap& keyMap, SInt32 group) const +{ + // special keys are insensitive to modifers and none are dead keys + barrier::KeyMap::KeyItem item; + for (size_t i = 0; i < sizeof(s_controlKeys) / + sizeof(s_controlKeys[0]); ++i) { + const KeyEntry& entry = s_controlKeys[i]; + item.m_id = entry.m_keyID; + item.m_group = group; + item.m_button = mapVirtualKeyToKeyButton(entry.m_virtualKey); + item.m_required = 0; + item.m_sensitive = 0; + item.m_dead = false; + item.m_client = 0; + barrier::KeyMap::initModifierKey(item); + keyMap.addKeyEntry(item); + + if (item.m_lock) { + // all locking keys are half duplex on OS X + keyMap.addHalfDuplexButton(item.m_button); + } + } + + // note: we don't special case the number pad keys. querying the + // mac keyboard returns the non-keypad version of those keys but + // a KeyState always provides a mapping from keypad keys to + // non-keypad keys so we'll be able to generate the characters + // anyway. +} + +bool +OSXKeyState::getKeyMap(barrier::KeyMap& keyMap, + SInt32 group, const IOSXKeyResource& r) const +{ + if (!r.isValid()) { + return false; + } + + // space for all possible modifier combinations + std::vector<bool> modifiers(r.getNumModifierCombinations()); + + // make space for the keys that any single button can synthesize + std::vector<std::pair<KeyID, bool> > buttonKeys(r.getNumTables()); + + // iterate over each button + barrier::KeyMap::KeyItem item; + for (UInt32 i = 0; i < r.getNumButtons(); ++i) { + item.m_button = mapVirtualKeyToKeyButton(i); + + // the KeyIDs we've already handled + std::set<KeyID> keys; + + // convert the entry in each table for this button to a KeyID + for (UInt32 j = 0; j < r.getNumTables(); ++j) { + buttonKeys[j].first = r.getKey(j, i); + buttonKeys[j].second = barrier::KeyMap::isDeadKey(buttonKeys[j].first); + } + + // iterate over each character table + for (UInt32 j = 0; j < r.getNumTables(); ++j) { + // get the KeyID for the button/table + KeyID id = buttonKeys[j].first; + if (id == kKeyNone) { + continue; + } + + // if we've already handled the KeyID in the table then + // move on to the next table + if (keys.count(id) > 0) { + continue; + } + keys.insert(id); + + // prepare item. the client state is 1 for dead keys. + item.m_id = id; + item.m_group = group; + item.m_dead = buttonKeys[j].second; + item.m_client = buttonKeys[j].second ? 1 : 0; + barrier::KeyMap::initModifierKey(item); + if (item.m_lock) { + // all locking keys are half duplex on OS X + keyMap.addHalfDuplexButton(i); + } + + // collect the tables that map to the same KeyID. we know it + // can't be any earlier tables because of the check above. + std::set<UInt8> tables; + tables.insert(static_cast<UInt8>(j)); + for (UInt32 k = j + 1; k < r.getNumTables(); ++k) { + if (buttonKeys[k].first == id) { + tables.insert(static_cast<UInt8>(k)); + } + } + + // collect the modifier combinations that map to any of the + // tables we just collected + for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) { + modifiers[k] = (tables.count(r.getTableForModifier(k)) > 0); + } + + // figure out which modifiers the key is sensitive to. the + // key is insensitive to a modifier if for every modifier mask + // with the modifier bit unset in the modifiers we also find + // the same mask with the bit set. + // + // we ignore a few modifiers that we know aren't important + // for generating characters. in fact, we want to ignore any + // characters generated by the control key. we don't map + // those and instead expect the control modifier plus a key. + UInt32 sensitive = 0; + for (UInt32 k = 0; (1u << k) < + r.getNumModifierCombinations(); ++k) { + UInt32 bit = (1u << k); + if ((bit << 8) == cmdKey || + (bit << 8) == controlKey || + (bit << 8) == rightControlKey) { + continue; + } + for (UInt32 m = 0; m < r.getNumModifierCombinations(); ++m) { + if (modifiers[m] != modifiers[m ^ bit]) { + sensitive |= bit; + break; + } + } + } + + // find each required modifier mask. the key can be synthesized + // using any of the masks. + std::set<UInt32> required; + for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) { + if ((k & sensitive) == k && modifiers[k & sensitive]) { + required.insert(k); + } + } + + // now add a key entry for each key/required modifier pair. + item.m_sensitive = mapModifiersFromOSX(sensitive << 16); + for (std::set<UInt32>::iterator k = required.begin(); + k != required.end(); ++k) { + item.m_required = mapModifiersFromOSX(*k << 16); + keyMap.addKeyEntry(item); + } + } + } + + return true; +} + +bool +OSXKeyState::mapBarrierHotKeyToMac(KeyID key, KeyModifierMask mask, + UInt32 &macVirtualKey, UInt32 &macModifierMask) const +{ + // look up button for key + KeyButton button = getButton(key, pollActiveGroup()); + if (button == 0 && key != kKeyNone) { + return false; + } + macVirtualKey = mapKeyButtonToVirtualKey(button); + + // calculate modifier mask + macModifierMask = 0; + if ((mask & KeyModifierShift) != 0) { + macModifierMask |= shiftKey; + } + if ((mask & KeyModifierControl) != 0) { + macModifierMask |= controlKey; + } + if ((mask & KeyModifierAlt) != 0) { + macModifierMask |= cmdKey; + } + if ((mask & KeyModifierSuper) != 0) { + macModifierMask |= optionKey; + } + if ((mask & KeyModifierCapsLock) != 0) { + macModifierMask |= alphaLock; + } + if ((mask & KeyModifierNumLock) != 0) { + macModifierMask |= s_osxNumLock; + } + + return true; +} + +void +OSXKeyState::handleModifierKeys(void* target, + KeyModifierMask oldMask, KeyModifierMask newMask) +{ + // compute changed modifiers + KeyModifierMask changed = (oldMask ^ newMask); + + // synthesize changed modifier keys + if ((changed & KeyModifierShift) != 0) { + handleModifierKey(target, s_shiftVK, kKeyShift_L, + (newMask & KeyModifierShift) != 0, newMask); + } + if ((changed & KeyModifierControl) != 0) { + handleModifierKey(target, s_controlVK, kKeyControl_L, + (newMask & KeyModifierControl) != 0, newMask); + } + if ((changed & KeyModifierAlt) != 0) { + handleModifierKey(target, s_altVK, kKeyAlt_L, + (newMask & KeyModifierAlt) != 0, newMask); + } + if ((changed & KeyModifierSuper) != 0) { + handleModifierKey(target, s_superVK, kKeySuper_L, + (newMask & KeyModifierSuper) != 0, newMask); + } + if ((changed & KeyModifierCapsLock) != 0) { + handleModifierKey(target, s_capsLockVK, kKeyCapsLock, + (newMask & KeyModifierCapsLock) != 0, newMask); + } + if ((changed & KeyModifierNumLock) != 0) { + handleModifierKey(target, s_numLockVK, kKeyNumLock, + (newMask & KeyModifierNumLock) != 0, newMask); + } +} + +void +OSXKeyState::handleModifierKey(void* target, + UInt32 virtualKey, KeyID id, + bool down, KeyModifierMask newMask) +{ + KeyButton button = mapVirtualKeyToKeyButton(virtualKey); + onKey(button, down, newMask); + sendKeyEvent(target, down, false, id, newMask, 0, button); +} + +bool +OSXKeyState::getGroups(GroupList& groups) const +{ + CFIndex n; + bool gotLayouts = false; + + // get number of layouts + CFStringRef keys[] = { kTISPropertyInputSourceCategory }; + CFStringRef values[] = { kTISCategoryKeyboardInputSource }; + CFDictionaryRef dict = CFDictionaryCreate(NULL, (const void **)keys, (const void **)values, 1, NULL, NULL); + CFArrayRef kbds = TISCreateInputSourceList(dict, false); + n = CFArrayGetCount(kbds); + gotLayouts = (n != 0); + + if (!gotLayouts) { + LOG((CLOG_DEBUG1 "can't get keyboard layouts")); + return false; + } + + // get each layout + groups.clear(); + for (CFIndex i = 0; i < n; ++i) { + bool addToGroups = true; + TISInputSourceRef keyboardLayout = + (TISInputSourceRef)CFArrayGetValueAtIndex(kbds, i); + + if (addToGroups) + groups.push_back(keyboardLayout); + } + return true; +} + +void +OSXKeyState::setGroup(SInt32 group) +{ + TISSetInputMethodKeyboardLayoutOverride(m_groups[group]); +} + +void +OSXKeyState::checkKeyboardLayout() +{ + // XXX -- should call this when notified that groups have changed. + // if no notification for that then we should poll. + GroupList groups; + if (getGroups(groups) && groups != m_groups) { + updateKeyMap(); + updateKeyState(); + } +} + +void +OSXKeyState::adjustAltGrModifier(const KeyIDs& ids, + KeyModifierMask* mask, bool isCommand) const +{ + if (!isCommand) { + for (KeyIDs::const_iterator i = ids.begin(); i != ids.end(); ++i) { + KeyID id = *i; + if (id != kKeyNone && + ((id < 0xe000u || id > 0xefffu) || + (id >= kKeyKP_Equal && id <= kKeyKP_9))) { + *mask |= KeyModifierAltGr; + return; + } + } + } +} + +KeyButton +OSXKeyState::mapVirtualKeyToKeyButton(UInt32 keyCode) +{ + // 'A' maps to 0 so shift every id + return static_cast<KeyButton>(keyCode + KeyButtonOffset); +} + +UInt32 +OSXKeyState::mapKeyButtonToVirtualKey(KeyButton keyButton) +{ + return static_cast<UInt32>(keyButton - KeyButtonOffset); +} diff --git a/src/lib/platform/OSXKeyState.h b/src/lib/platform/OSXKeyState.h new file mode 100644 index 0000000..4d92860 --- /dev/null +++ b/src/lib/platform/OSXKeyState.h @@ -0,0 +1,180 @@ +/* + * 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 "barrier/KeyState.h" +#include "common/stdmap.h" +#include "common/stdset.h" +#include "common/stdvector.h" + +#include <Carbon/Carbon.h> + +typedef TISInputSourceRef KeyLayout; +class IOSXKeyResource; + +//! OS X key state +/*! +A key state for OS X. +*/ +class OSXKeyState : public KeyState { +public: + typedef std::vector<KeyID> KeyIDs; + + OSXKeyState(IEventQueue* events); + OSXKeyState(IEventQueue* events, barrier::KeyMap& keyMap); + virtual ~OSXKeyState(); + + //! @name modifiers + //@{ + + //! Handle modifier key change + /*! + Determines which modifier keys have changed and updates the modifier + state and sends key events as appropriate. + */ + void handleModifierKeys(void* target, + KeyModifierMask oldMask, KeyModifierMask newMask); + + //@} + //! @name accessors + //@{ + + //! Convert OS X modifier mask to barrier mask + /*! + Returns the barrier modifier mask corresponding to the OS X modifier + mask in \p mask. + */ + KeyModifierMask mapModifiersFromOSX(UInt32 mask) const; + + //! Convert CG flags-style modifier mask to old-style Carbon + /*! + Still required in a few places for translation calls. + */ + KeyModifierMask mapModifiersToCarbon(UInt32 mask) const; + + //! Map key event to keys + /*! + Converts a key event into a sequence of KeyIDs and the shadow modifier + state to a modifier mask. The KeyIDs list, in order, the characters + generated by the key press/release. It returns the id of the button + that was pressed or released, or 0 if the button doesn't map to a known + KeyID. + */ + KeyButton mapKeyFromEvent(KeyIDs& ids, + KeyModifierMask* maskOut, CGEventRef event) const; + + //! Map key and mask to native values + /*! + Calculates mac virtual key and mask for a key \p key and modifiers + \p mask. Returns \c true if the key can be mapped, \c false otherwise. + */ + bool mapBarrierHotKeyToMac(KeyID key, KeyModifierMask mask, + UInt32& macVirtualKey, + UInt32& macModifierMask) const; + + //@} + + // IKeyState overrides + virtual bool fakeCtrlAltDel(); + virtual bool fakeMediaKey(KeyID id); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + + CGEventFlags getModifierStateAsOSXFlags(); +protected: + // KeyState overrides + virtual void getKeyMap(barrier::KeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + +private: + class KeyResource; + typedef std::vector<KeyLayout> GroupList; + + // Add hard coded special keys to a barrier::KeyMap. + void getKeyMapForSpecialKeys( + barrier::KeyMap& keyMap, SInt32 group) const; + + // Convert keyboard resource to a key map + bool getKeyMap(barrier::KeyMap& keyMap, + SInt32 group, const IOSXKeyResource& r) const; + + // Get the available keyboard groups + bool getGroups(GroupList&) const; + + // Change active keyboard group to group + void setGroup(SInt32 group); + + // Check if the keyboard layout has changed and update keyboard state + // if so. + void checkKeyboardLayout(); + + // Send an event for the given modifier key + void handleModifierKey(void* target, + UInt32 virtualKey, KeyID id, + bool down, KeyModifierMask newMask); + + // Checks if any in \p ids is a glyph key and if \p isCommand is false. + // If so it adds the AltGr modifier to \p mask. This allows OS X + // servers to use the option key both as AltGr and as a modifier. If + // option is acting as AltGr (i.e. it generates a glyph and there are + // no command modifiers active) then we don't send the super modifier + // to clients because they'd try to match it as a command modifier. + void adjustAltGrModifier(const KeyIDs& ids, + KeyModifierMask* mask, bool isCommand) const; + + // Maps an OS X virtual key id to a KeyButton. This simply remaps + // the ids so we don't use KeyButton 0. + static KeyButton mapVirtualKeyToKeyButton(UInt32 keyCode); + + // Maps a KeyButton to an OS X key code. This is the inverse of + // mapVirtualKeyToKeyButton. + static UInt32 mapKeyButtonToVirtualKey(KeyButton keyButton); + + void init(); + + // Post a key event to HID manager. It posts an event to HID client, a + // much lower level than window manager which's the target from carbon + // CGEventPost + void postHIDVirtualKey(const UInt8 virtualKeyCode, + const bool postDown); + +private: + // OS X uses a physical key if 0 for the 'A' key. barrier reserves + // KeyButton 0 so we offset all OS X physical key ids by this much + // when used as a KeyButton and by minus this much to map a KeyButton + // to a physical button. + enum { + KeyButtonOffset = 1 + }; + + typedef std::map<CFDataRef, SInt32> GroupMap; + typedef std::map<UInt32, KeyID> VirtualKeyMap; + + VirtualKeyMap m_virtualKeyMap; + mutable UInt32 m_deadKeyState; + GroupList m_groups; + GroupMap m_groupMap; + bool m_shiftPressed; + bool m_controlPressed; + bool m_altPressed; + bool m_superPressed; + bool m_capsPressed; +}; diff --git a/src/lib/platform/OSXMediaKeySimulator.h b/src/lib/platform/OSXMediaKeySimulator.h new file mode 100644 index 0000000..39739d2 --- /dev/null +++ b/src/lib/platform/OSXMediaKeySimulator.h @@ -0,0 +1,30 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 + +#import <CoreFoundation/CoreFoundation.h> + +#include "barrier/key_types.h" + +#if defined(__cplusplus) +extern "C" { +#endif +bool fakeNativeMediaKey(KeyID id); +#if defined(__cplusplus) +} +#endif diff --git a/src/lib/platform/OSXMediaKeySimulator.m b/src/lib/platform/OSXMediaKeySimulator.m new file mode 100644 index 0000000..5aacd10 --- /dev/null +++ b/src/lib/platform/OSXMediaKeySimulator.m @@ -0,0 +1,92 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#import "platform/OSXMediaKeySimulator.h" + +#import <Cocoa/Cocoa.h> + +int convertKeyIDToNXKeyType(KeyID id) +{ + // hidsystem/ev_keymap.h + // NX_KEYTYPE_SOUND_UP 0 + // NX_KEYTYPE_SOUND_DOWN 1 + // NX_KEYTYPE_BRIGHTNESS_UP 2 + // NX_KEYTYPE_BRIGHTNESS_DOWN 3 + // NX_KEYTYPE_MUTE 7 + // NX_KEYTYPE_EJECT 14 + // NX_KEYTYPE_PLAY 16 + // NX_KEYTYPE_NEXT 17 + // NX_KEYTYPE_PREVIOUS 18 + // NX_KEYTYPE_FAST 19 + // NX_KEYTYPE_REWIND 20 + + int type = -1; + switch (id) { + case kKeyAudioUp: + type = 0; + break; + case kKeyAudioDown: + type = 1; + break; + case kKeyBrightnessUp: + type = 2; + break; + case kKeyBrightnessDown: + type = 3; + break; + case kKeyAudioMute: + type = 7; + break; + case kKeyEject: + type = 14; + break; + case kKeyAudioPlay: + type = 16; + break; + case kKeyAudioNext: + type = 17; + break; + case kKeyAudioPrev: + type = 18; + break; + default: + break; + } + + return type; +} + +bool +fakeNativeMediaKey(KeyID id) +{ + + NSEvent* downRef = [NSEvent otherEventWithType:NSSystemDefined + location: NSMakePoint(0, 0) modifierFlags:0xa00 + timestamp:0 windowNumber:0 context:0 subtype:8 + data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xa) << 8) + data2:-1]; + CGEventRef downEvent = [downRef CGEvent]; + + NSEvent* upRef = [NSEvent otherEventWithType:NSSystemDefined + location: NSMakePoint(0, 0) modifierFlags:0xa00 + timestamp:0 windowNumber:0 context:0 subtype:8 + data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xb) << 8) + data2:-1]; + CGEventRef upEvent = [upRef CGEvent]; + + CGEventPost(0, downEvent); + CGEventPost(0, upEvent); + + return true; +} diff --git a/src/lib/platform/OSXMediaKeySupport.h b/src/lib/platform/OSXMediaKeySupport.h new file mode 100644 index 0000000..d64e26e --- /dev/null +++ b/src/lib/platform/OSXMediaKeySupport.h @@ -0,0 +1,33 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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 + +#import <CoreFoundation/CoreFoundation.h> +#import <Carbon/Carbon.h> + +#include "barrier/key_types.h" + +#if defined(__cplusplus) +extern "C" { +#endif +bool fakeNativeMediaKey(KeyID id); +bool isMediaKeyEvent(CGEventRef event); +bool getMediaKeyEventInfo(CGEventRef event, KeyID* keyId, bool* down, bool* isRepeat); +#if defined(__cplusplus) +} +#endif diff --git a/src/lib/platform/OSXMediaKeySupport.m b/src/lib/platform/OSXMediaKeySupport.m new file mode 100644 index 0000000..9c9dbc3 --- /dev/null +++ b/src/lib/platform/OSXMediaKeySupport.m @@ -0,0 +1,154 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2016 Symless. + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * found in the file COPYING that should have accompanied this file. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#import "platform/OSXMediaKeySupport.h" +#import <Cocoa/Cocoa.h> +#import <IOKit/hidsystem/ev_keymap.h> + +int convertKeyIDToNXKeyType(KeyID id) +{ + int type = -1; + + switch (id) { + case kKeyAudioUp: + type = NX_KEYTYPE_SOUND_UP; + break; + case kKeyAudioDown: + type = NX_KEYTYPE_SOUND_DOWN; + break; + case kKeyBrightnessUp: + type = NX_KEYTYPE_BRIGHTNESS_UP; + break; + case kKeyBrightnessDown: + type = NX_KEYTYPE_BRIGHTNESS_DOWN; + break; + case kKeyAudioMute: + type = NX_KEYTYPE_MUTE; + break; + case kKeyEject: + type = NX_KEYTYPE_EJECT; + break; + case kKeyAudioPlay: + type = NX_KEYTYPE_PLAY; + break; + case kKeyAudioNext: + type = NX_KEYTYPE_NEXT; + break; + case kKeyAudioPrev: + type = NX_KEYTYPE_PREVIOUS; + break; + default: + break; + } + + return type; +} + +static KeyID +convertNXKeyTypeToKeyID(uint32_t const type) +{ + KeyID id = 0; + + switch (type) { + case NX_KEYTYPE_SOUND_UP: + id = kKeyAudioUp; + break; + case NX_KEYTYPE_SOUND_DOWN: + id = kKeyAudioDown; + break; + case NX_KEYTYPE_MUTE: + id = kKeyAudioMute; + break; + case NX_KEYTYPE_EJECT: + id = kKeyEject; + break; + case NX_KEYTYPE_PLAY: + id = kKeyAudioPlay; + break; + case NX_KEYTYPE_FAST: + case NX_KEYTYPE_NEXT: + id = kKeyAudioNext; + break; + case NX_KEYTYPE_REWIND: + case NX_KEYTYPE_PREVIOUS: + id = kKeyAudioPrev; + break; + default: + break; + } + + return id; +} + +bool +isMediaKeyEvent(CGEventRef event) { + NSEvent* nsEvent = nil; + @try { + nsEvent = [NSEvent eventWithCGEvent: event]; + if ([nsEvent subtype] != 8) { + return false; + } + uint32_t const nxKeyId = ([nsEvent data1] & 0xFFFF0000) >> 16; + if (convertNXKeyTypeToKeyID (nxKeyId)) { + return true; + } + } @catch (NSException* e) { + } + return false; +} + +bool +getMediaKeyEventInfo(CGEventRef event, KeyID* const keyId, + bool* const down, bool* const isRepeat) { + NSEvent* nsEvent = nil; + @try { + nsEvent = [NSEvent eventWithCGEvent: event]; + } @catch (NSException* e) { + return false; + } + if (keyId) { + *keyId = convertNXKeyTypeToKeyID (([nsEvent data1] & 0xFFFF0000) >> 16); + } + if (down) { + *down = !([nsEvent data1] & 0x100); + } + if (isRepeat) { + *isRepeat = [nsEvent data1] & 0x1; + } + return true; +} + +bool +fakeNativeMediaKey(KeyID id) +{ + + NSEvent* downRef = [NSEvent otherEventWithType:NSSystemDefined + location: NSMakePoint(0, 0) modifierFlags:0xa00 + timestamp:0 windowNumber:0 context:0 subtype:8 + data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xa) << 8) + data2:-1]; + CGEventRef downEvent = [downRef CGEvent]; + + NSEvent* upRef = [NSEvent otherEventWithType:NSSystemDefined + location: NSMakePoint(0, 0) modifierFlags:0xa00 + timestamp:0 windowNumber:0 context:0 subtype:8 + data1:(convertKeyIDToNXKeyType(id) << 16) | ((0xb) << 8) + data2:-1]; + CGEventRef upEvent = [upRef CGEvent]; + + CGEventPost(0, downEvent); + CGEventPost(0, upEvent); + + return true; +} diff --git a/src/lib/platform/OSXPasteboardPeeker.h b/src/lib/platform/OSXPasteboardPeeker.h new file mode 100644 index 0000000..5105262 --- /dev/null +++ b/src/lib/platform/OSXPasteboardPeeker.h @@ -0,0 +1,32 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "common/common.h" + +#import <CoreFoundation/CoreFoundation.h> + +#if defined(__cplusplus) +extern "C" { +#endif + +CFStringRef getDraggedFileURL(); + +#if defined(__cplusplus) +} +#endif diff --git a/src/lib/platform/OSXPasteboardPeeker.m b/src/lib/platform/OSXPasteboardPeeker.m new file mode 100644 index 0000000..ab39e26 --- /dev/null +++ b/src/lib/platform/OSXPasteboardPeeker.m @@ -0,0 +1,37 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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. + */ + +#import "platform/OSXPasteboardPeeker.h" + +#import <Foundation/Foundation.h> +#import <CoreData/CoreData.h> +#import <Cocoa/Cocoa.h> + +CFStringRef +getDraggedFileURL() +{ + NSString* pbName = NSDragPboard; + NSPasteboard* pboard = [NSPasteboard pasteboardWithName:pbName]; + + NSMutableString* string; + string = [[NSMutableString alloc] initWithCapacity:0]; + + NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; + for (id file in files) { + [string appendString: (NSString*)file]; + [string appendString: @"\0"]; + } + + return (CFStringRef)string; +} diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h new file mode 100644 index 0000000..27cb7df --- /dev/null +++ b/src/lib/platform/OSXScreen.h @@ -0,0 +1,349 @@ +/* + * 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 "platform/OSXClipboard.h" +#include "barrier/PlatformScreen.h" +#include "barrier/DragInformation.h" +#include "base/EventTypes.h" +#include "common/stdmap.h" +#include "common/stdvector.h" + +#include <bitset> +#include <Carbon/Carbon.h> +#include <mach/mach_port.h> +#include <mach/mach_interface.h> +#include <mach/mach_init.h> +#include <IOKit/pwr_mgt/IOPMLib.h> +#include <IOKit/IOMessage.h> + +extern "C" { + typedef int CGSConnectionID; + CGError CGSSetConnectionProperty(CGSConnectionID cid, CGSConnectionID targetCID, CFStringRef key, CFTypeRef value); + int _CGSDefaultConnection(); +} + + +template <class T> +class CondVar; +class EventQueueTimer; +class Mutex; +class Thread; +class OSXKeyState; +class OSXScreenSaver; +class IEventQueue; +class Mutex; + +//! Implementation of IPlatformScreen for OS X +class OSXScreen : public PlatformScreen { +public: + OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor=true); + virtual ~OSXScreen(); + + IEventQueue* getEvents() const { return m_events; } + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown(UInt32& buttonID) const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press); + virtual void fakeMouseMove(SInt32 x, SInt32 y); + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + virtual void fakeDraggingFiles(DragFileList fileList); + virtual String& getDraggingFilename(); + + const String& getDropTarget() const { return m_dropTarget; } + void waitForCarbonLoop() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const Event&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + void updateScreenShape(); + void updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags); + void postMouseEvent(CGPoint&) const; + + // convenience function to send events + void sendEvent(Event::Type type, void* = NULL) const; + void sendClipboardEvent(Event::Type type, ClipboardID id) const; + + // message handlers + bool onMouseMove(SInt32 mx, SInt32 my); + // mouse button handler. pressed is true if this is a mousedown + // event, false if it is a mouseup event. macButton is the index + // of the button pressed using the mac button mapping. + bool onMouseButton(bool pressed, UInt16 macButton); + bool onMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + void constructMouseButtonEventMap(); + + bool onKey(CGEventRef event); + + void onMediaKey(CGEventRef event); + + bool onHotKey(EventRef event) const; + + // Added here to allow the carbon cursor hack to be called. + void showCursor(); + void hideCursor(); + + // map barrier mouse button to mac buttons + ButtonID mapBarrierButtonToMac(UInt16) const; + + // map mac mouse button to barrier buttons + ButtonID mapMacButtonToBarrier(UInt16) const; + + // map mac scroll wheel value to a barrier scroll wheel value + SInt32 mapScrollWheelToBarrier(SInt32) const; + + // map barrier scroll wheel value to a mac scroll wheel value + SInt32 mapScrollWheelFromBarrier(SInt32) const; + + // get the current scroll wheel speed + double getScrollSpeed() const; + + // get the current scroll wheel speed + double getScrollSpeedFactor() const; + + // enable/disable drag handling for buttons 3 and up + void enableDragTimer(bool enable); + + // drag timer handler + void handleDrag(const Event&, void*); + + // clipboard check timer handler + void handleClipboardCheck(const Event&, void*); + + // Resolution switch callback + static void displayReconfigurationCallback(CGDirectDisplayID, + CGDisplayChangeSummaryFlags, void*); + + // fast user switch callback + static pascal OSStatus + userSwitchCallback(EventHandlerCallRef nextHandler, + EventRef theEvent, void* inUserData); + + // sleep / wakeup support + void watchSystemPowerThread(void*); + static void testCanceled(CFRunLoopTimerRef timer, void*info); + static void powerChangeCallback(void* refcon, io_service_t service, + natural_t messageType, void* messageArgument); + void handlePowerChangeRequest(natural_t messageType, + void* messageArgument); + + void handleConfirmSleep(const Event& event, void*); + + // global hotkey operating mode + static bool isGlobalHotKeyOperatingModeAvailable(); + static void setGlobalHotKeysEnabled(bool enabled); + static bool getGlobalHotKeysEnabled(); + + // Quartz event tap support + static CGEventRef handleCGInputEvent(CGEventTapProxy proxy, + CGEventType type, + CGEventRef event, + void* refcon); + static CGEventRef handleCGInputEventSecondary(CGEventTapProxy proxy, + CGEventType type, + CGEventRef event, + void* refcon); + + // convert CFString to char* + static char* CFStringRefToUTF8String(CFStringRef aString); + + void getDropTargetThread(void*); + +private: + struct HotKeyItem { + public: + HotKeyItem(UInt32, UInt32); + HotKeyItem(EventHotKeyRef, UInt32, UInt32); + + EventHotKeyRef getRef() const; + + bool operator<(const HotKeyItem&) const; + + private: + EventHotKeyRef m_ref; + UInt32 m_keycode; + UInt32 m_mask; + }; + + enum EMouseButtonState { + kMouseButtonUp = 0, + kMouseButtonDragged, + kMouseButtonDown, + kMouseButtonStateMax + }; + + + class MouseButtonState { + public: + void set(UInt32 button, EMouseButtonState state); + bool any(); + void reset(); + void overwrite(UInt32 buttons); + + bool test(UInt32 button) const; + SInt8 getFirstButtonDown() const; + private: + std::bitset<NumButtonIDs> m_buttons; + }; + + typedef std::map<UInt32, HotKeyItem> HotKeyMap; + typedef std::vector<UInt32> HotKeyIDList; + typedef std::map<KeyModifierMask, UInt32> ModifierHotKeyMap; + typedef std::map<HotKeyItem, UInt32> HotKeyToIDMap; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // the display + CGDirectDisplayID m_displayID; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // mouse state + mutable SInt32 m_xCursor, m_yCursor; + mutable bool m_cursorPosValid; + + /* FIXME: this data structure is explicitly marked mutable due + to a need to track the state of buttons since the remote + side only lets us know of change events, and because the + fakeMouseButton button method is marked 'const'. This is + Evil, and this should be moved to a place where it need not + be mutable as soon as possible. */ + mutable MouseButtonState m_buttonState; + typedef std::map<UInt16, CGEventType> MouseButtonEventMapType; + std::vector<MouseButtonEventMapType> MouseButtonEventMap; + + bool m_cursorHidden; + SInt32 m_dragNumButtonsDown; + Point m_dragLastPoint; + EventQueueTimer* m_dragTimer; + + // keyboard stuff + OSXKeyState* m_keyState; + + // clipboards + OSXClipboard m_pasteboard; + UInt32 m_sequenceNumber; + + // screen saver stuff + OSXScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // clipboard stuff + bool m_ownClipboard; + EventQueueTimer* m_clipboardTimer; + + // window object that gets user input events when the server + // has focus. + WindowRef m_hiddenWindow; + // window object that gets user input events when the server + // does not have focus. + WindowRef m_userInputWindow; + + // fast user switching + EventHandlerRef m_switchEventHandlerRef; + + // sleep / wakeup + Mutex* m_pmMutex; + Thread* m_pmWatchThread; + CondVar<bool>* m_pmThreadReady; + CFRunLoopRef m_pmRunloop; + io_connect_t m_pmRootPort; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + ModifierHotKeyMap m_modifierHotKeys; + UInt32 m_activeModifierHotKey; + KeyModifierMask m_activeModifierHotKeyMask; + HotKeyToIDMap m_hotKeyToIDMap; + + // global hotkey operating mode + static bool s_testedForGHOM; + static bool s_hasGHOM; + + // Quartz input event support + CFMachPortRef m_eventTapPort; + CFRunLoopSourceRef m_eventTapRLSR; + + // for double click coalescing. + double m_lastClickTime; + int m_clickState; + SInt32 m_lastSingleClickXCursor; + SInt32 m_lastSingleClickYCursor; + + // cursor will hide and show on enable and disable if true. + bool m_autoShowHideCursor; + + IEventQueue* m_events; + + Thread* m_getDropTargetThread; + String m_dropTarget; + +#if defined(MAC_OS_X_VERSION_10_7) + Mutex* m_carbonLoopMutex; + CondVar<bool>* m_carbonLoopReady; +#endif + + class OSXScreenImpl* m_impl; +}; diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm new file mode 100644 index 0000000..1d80521 --- /dev/null +++ b/src/lib/platform/OSXScreen.mm @@ -0,0 +1,2162 @@ +/* + * 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 "platform/OSXScreen.h" + +#include "base/EventQueue.h" +#include "client/Client.h" +#include "platform/OSXClipboard.h" +#include "platform/OSXEventQueueBuffer.h" +#include "platform/OSXKeyState.h" +#include "platform/OSXScreenSaver.h" +#include "platform/OSXDragSimulator.h" +#include "platform/OSXMediaKeySupport.h" +#include "platform/OSXPasteboardPeeker.h" +#include "barrier/Clipboard.h" +#include "barrier/KeyMap.h" +#include "barrier/ClientApp.h" +#include "mt/CondVar.h" +#include "mt/Lock.h" +#include "mt/Mutex.h" +#include "mt/Thread.h" +#include "arch/XArch.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" +#include "base/TMethodJob.h" + +#include <math.h> +#include <mach-o/dyld.h> +#include <AvailabilityMacros.h> +#include <IOKit/hidsystem/event_status_driver.h> +#include <AppKit/NSEvent.h> + +// This isn't in any Apple SDK that I know of as of yet. +enum { + kBarrierEventMouseScroll = 11, + kBarrierMouseScrollAxisX = 'saxx', + kBarrierMouseScrollAxisY = 'saxy' +}; + +enum { + kCarbonLoopWaitTimeout = 10 +}; + +// TODO: upgrade deprecated function usage in these functions. +void setZeroSuppressionInterval(); +void avoidSupression(); +void logCursorVisibility(); +void avoidHesitatingCursor(); + +// +// OSXScreen +// + +bool OSXScreen::s_testedForGHOM = false; +bool OSXScreen::s_hasGHOM = false; + +OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCursor) : + PlatformScreen(events), + m_isPrimary(isPrimary), + m_isOnScreen(m_isPrimary), + m_cursorPosValid(false), + MouseButtonEventMap(NumButtonIDs), + m_cursorHidden(false), + m_dragNumButtonsDown(0), + m_dragTimer(NULL), + m_keyState(NULL), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_ownClipboard(false), + m_clipboardTimer(NULL), + m_hiddenWindow(NULL), + m_userInputWindow(NULL), + m_switchEventHandlerRef(0), + m_pmMutex(new Mutex), + m_pmWatchThread(NULL), + m_pmThreadReady(new CondVar<bool>(m_pmMutex, false)), + m_pmRootPort(0), + m_activeModifierHotKey(0), + m_activeModifierHotKeyMask(0), + m_eventTapPort(nullptr), + m_eventTapRLSR(nullptr), + m_lastClickTime(0), + m_clickState(1), + m_lastSingleClickXCursor(0), + m_lastSingleClickYCursor(0), + m_autoShowHideCursor(autoShowHideCursor), + m_events(events), + m_getDropTargetThread(NULL), + m_impl(NULL) +{ + try { + m_displayID = CGMainDisplayID(); + updateScreenShape(m_displayID, 0); + m_screensaver = new OSXScreenSaver(m_events, getEventTarget()); + m_keyState = new OSXKeyState(m_events); + + // only needed when running as a server. + if (m_isPrimary) { + +#if defined(MAC_OS_X_VERSION_10_9) + // we can't pass options to show the dialog, this must be done by the gui. + if (!AXIsProcessTrusted()) { + throw XArch("assistive devices does not trust this process, allow it in system settings."); + } +#else + // now deprecated in mavericks. + if (!AXAPIEnabled()) { + throw XArch("assistive devices is not enabled, enable it in system settings."); + } +#endif + } + + // install display manager notification handler + CGDisplayRegisterReconfigurationCallback(displayReconfigurationCallback, this); + + // install fast user switching event handler + EventTypeSpec switchEventTypes[2]; + switchEventTypes[0].eventClass = kEventClassSystem; + switchEventTypes[0].eventKind = kEventSystemUserSessionDeactivated; + switchEventTypes[1].eventClass = kEventClassSystem; + switchEventTypes[1].eventKind = kEventSystemUserSessionActivated; + EventHandlerUPP switchEventHandler = + NewEventHandlerUPP(userSwitchCallback); + InstallApplicationEventHandler(switchEventHandler, 2, switchEventTypes, + this, &m_switchEventHandlerRef); + DisposeEventHandlerUPP(switchEventHandler); + + constructMouseButtonEventMap(); + + // watch for requests to sleep + m_events->adoptHandler(m_events->forOSXScreen().confirmSleep(), + getEventTarget(), + new TMethodEventJob<OSXScreen>(this, + &OSXScreen::handleConfirmSleep)); + + // create thread for monitoring system power state. + *m_pmThreadReady = false; +#if defined(MAC_OS_X_VERSION_10_7) + m_carbonLoopMutex = new Mutex(); + m_carbonLoopReady = new CondVar<bool>(m_carbonLoopMutex, false); +#endif + LOG((CLOG_DEBUG "starting watchSystemPowerThread")); + m_pmWatchThread = new Thread(new TMethodJob<OSXScreen> + (this, &OSXScreen::watchSystemPowerThread)); + } + catch (...) { + m_events->removeHandler(m_events->forOSXScreen().confirmSleep(), + getEventTarget()); + if (m_switchEventHandlerRef != 0) { + RemoveEventHandler(m_switchEventHandlerRef); + } + + CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this); + + delete m_keyState; + delete m_screensaver; + throw; + } + + // install event handlers + m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(), + new TMethodEventJob<OSXScreen>(this, + &OSXScreen::handleSystemEvent)); + + // install the platform event queue + m_events->adoptBuffer(new OSXEventQueueBuffer(m_events)); +} + +OSXScreen::~OSXScreen() +{ + disable(); + m_events->adoptBuffer(NULL); + m_events->removeHandler(Event::kSystem, m_events->getSystemTarget()); + + if (m_pmWatchThread) { + // make sure the thread has setup the runloop. + { + Lock lock(m_pmMutex); + while (!(bool)*m_pmThreadReady) { + m_pmThreadReady->wait(); + } + } + + // now exit the thread's runloop and wait for it to exit + LOG((CLOG_DEBUG "stopping watchSystemPowerThread")); + CFRunLoopStop(m_pmRunloop); + m_pmWatchThread->wait(); + delete m_pmWatchThread; + m_pmWatchThread = NULL; + } + delete m_pmThreadReady; + delete m_pmMutex; + + m_events->removeHandler(m_events->forOSXScreen().confirmSleep(), + getEventTarget()); + + RemoveEventHandler(m_switchEventHandlerRef); + + CGDisplayRemoveReconfigurationCallback(displayReconfigurationCallback, this); + + delete m_keyState; + delete m_screensaver; + +#if defined(MAC_OS_X_VERSION_10_7) + delete m_carbonLoopMutex; + delete m_carbonLoopReady; +#endif +} + +void* +OSXScreen::getEventTarget() const +{ + return const_cast<OSXScreen*>(this); +} + +bool +OSXScreen::getClipboard(ClipboardID, IClipboard* dst) const +{ + Clipboard::copy(dst, &m_pasteboard); + return true; +} + +void +OSXScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +OSXScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + CGEventRef event = CGEventCreate(NULL); + CGPoint mouse = CGEventGetLocation(event); + x = mouse.x; + y = mouse.y; + m_cursorPosValid = true; + m_xCursor = x; + m_yCursor = y; + CFRelease(event); +} + +void +OSXScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +OSXScreen::warpCursor(SInt32 x, SInt32 y) +{ + // move cursor without generating events + CGPoint pos; + pos.x = x; + pos.y = y; + CGWarpMouseCursorPosition(pos); + + // save new cursor position + m_xCursor = x; + m_yCursor = y; + m_cursorPosValid = true; +} + +void +OSXScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +OSXScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +OSXScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +OSXScreen::isAnyMouseButtonDown(UInt32& buttonID) const +{ + if (m_buttonState.test(0)) { + buttonID = kButtonLeft; + return true; + } + + return (GetCurrentButtonState() != 0); +} + +void +OSXScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +UInt32 +OSXScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // get mac virtual key and modifier mask matching barrier key and mask + UInt32 macKey, macMask; + if (!m_keyState->mapBarrierHotKeyToMac(key, mask, macKey, macMask)) { + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + + // if this hot key has modifiers only then we'll handle it specially + EventHotKeyRef ref = NULL; + bool okay; + if (key == kKeyNone) { + if (m_modifierHotKeys.count(mask) > 0) { + // already registered + okay = false; + } + else { + m_modifierHotKeys[mask] = id; + okay = true; + } + } + else { + EventHotKeyID hkid = { 'SNRG', (UInt32)id }; + OSStatus status = RegisterEventHotKey(macKey, macMask, hkid, + GetApplicationEventTarget(), 0, + &ref); + okay = (status == noErr); + m_hotKeyToIDMap[HotKeyItem(macKey, macMask)] = id; + } + + if (!okay) { + m_oldHotKeyIDs.push_back(id); + m_hotKeyToIDMap.erase(HotKeyItem(macKey, macMask)); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + m_hotKeys.insert(std::make_pair(id, HotKeyItem(ref, macKey, macMask))); + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +OSXScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool okay; + if (i->second.getRef() != NULL) { + okay = (UnregisterEventHotKey(i->second.getRef()) == noErr); + } + else { + okay = false; + // XXX -- this is inefficient + for (ModifierHotKeyMap::iterator j = m_modifierHotKeys.begin(); + j != m_modifierHotKeys.end(); ++j) { + if (j->second == id) { + m_modifierHotKeys.erase(j); + okay = true; + break; + } + } + } + if (!okay) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeyToIDMap.erase(i->second); + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); + if (m_activeModifierHotKey == id) { + m_activeModifierHotKey = 0; + m_activeModifierHotKeyMask = 0; + } +} + +void +OSXScreen::constructMouseButtonEventMap() +{ + const CGEventType source[NumButtonIDs][3] = { + {kCGEventLeftMouseUp, kCGEventLeftMouseDragged, kCGEventLeftMouseDown}, + {kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown}, + {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, + {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, + {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown} + }; + + for (UInt16 button = 0; button < NumButtonIDs; button++) { + MouseButtonEventMapType new_map; + for (UInt16 state = (UInt32) kMouseButtonUp; state < kMouseButtonStateMax; state++) { + CGEventType curEvent = source[button][state]; + new_map[state] = curEvent; + } + MouseButtonEventMap[button] = new_map; + } +} + +void +OSXScreen::postMouseEvent(CGPoint& pos) const +{ + // check if cursor position is valid on the client display configuration + // stkamp@users.sourceforge.net + CGDisplayCount displayCount = 0; + CGGetDisplaysWithPoint(pos, 0, NULL, &displayCount); + if (displayCount == 0) { + // cursor position invalid - clamp to bounds of last valid display. + // find the last valid display using the last cursor position. + displayCount = 0; + CGDirectDisplayID displayID; + CGGetDisplaysWithPoint(CGPointMake(m_xCursor, m_yCursor), 1, + &displayID, &displayCount); + if (displayCount != 0) { + CGRect displayRect = CGDisplayBounds(displayID); + if (pos.x < displayRect.origin.x) { + pos.x = displayRect.origin.x; + } + else if (pos.x > displayRect.origin.x + + displayRect.size.width - 1) { + pos.x = displayRect.origin.x + displayRect.size.width - 1; + } + if (pos.y < displayRect.origin.y) { + pos.y = displayRect.origin.y; + } + else if (pos.y > displayRect.origin.y + + displayRect.size.height - 1) { + pos.y = displayRect.origin.y + displayRect.size.height - 1; + } + } + } + + CGEventType type = kCGEventMouseMoved; + + SInt8 button = m_buttonState.getFirstButtonDown(); + if (button != -1) { + MouseButtonEventMapType thisButtonType = MouseButtonEventMap[button]; + type = thisButtonType[kMouseButtonDragged]; + } + + CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(button)); + + // Dragging events also need the click state + CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState); + + // Fix for sticky keys + CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags(); + CGEventSetFlags(event, modifiers); + + // Set movement deltas to fix issues with certain 3D programs + SInt64 deltaX = pos.x; + deltaX -= m_xCursor; + + SInt64 deltaY = pos.y; + deltaY -= m_yCursor; + + CGEventSetIntegerValueField(event, kCGMouseEventDeltaX, deltaX); + CGEventSetIntegerValueField(event, kCGMouseEventDeltaY, deltaY); + + double deltaFX = deltaX; + double deltaFY = deltaY; + + CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaFX); + CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaFY); + + CGEventPost(kCGHIDEventTap, event); + + CFRelease(event); +} + +void +OSXScreen::fakeMouseButton(ButtonID id, bool press) +{ + // Buttons are indexed from one, but the button down array is indexed from zero + UInt32 index = mapBarrierButtonToMac(id) - kButtonLeft; + if (index >= NumButtonIDs) { + return; + } + + CGPoint pos; + if (!m_cursorPosValid) { + SInt32 x, y; + getCursorPos(x, y); + } + pos.x = m_xCursor; + pos.y = m_yCursor; + + // variable used to detect mouse coordinate differences between + // old & new mouse clicks. Used in double click detection. + SInt32 xDiff = m_xCursor - m_lastSingleClickXCursor; + SInt32 yDiff = m_yCursor - m_lastSingleClickYCursor; + double diff = sqrt(xDiff * xDiff + yDiff * yDiff); + // max sqrt(x^2 + y^2) difference allowed to double click + // since we don't have double click distance in NX APIs + // we define our own defaults. + const double maxDiff = sqrt(2) + 0.0001; + + double clickTime = [NSEvent doubleClickInterval]; + + // As long as the click is within the time window and distance window + // increase clickState (double click, triple click, etc) + // This will allow for higher than triple click but the quartz documenation + // does not specify that this should be limited to triple click + if (press) { + if ((ARCH->time() - m_lastClickTime) <= clickTime && diff <= maxDiff){ + m_clickState++; + } + else { + m_clickState = 1; + } + + m_lastClickTime = ARCH->time(); + } + + if (m_clickState == 1){ + m_lastSingleClickXCursor = m_xCursor; + m_lastSingleClickYCursor = m_yCursor; + } + + EMouseButtonState state = press ? kMouseButtonDown : kMouseButtonUp; + + LOG((CLOG_DEBUG1 "faking mouse button id: %d press: %s", index, press ? "pressed" : "released")); + + MouseButtonEventMapType thisButtonMap = MouseButtonEventMap[index]; + CGEventType type = thisButtonMap[state]; + + CGEventRef event = CGEventCreateMouseEvent(NULL, type, pos, static_cast<CGMouseButton>(index)); + + CGEventSetIntegerValueField(event, kCGMouseEventClickState, m_clickState); + + // Fix for sticky keys + CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags(); + CGEventSetFlags(event, modifiers); + + m_buttonState.set(index, state); + CGEventPost(kCGHIDEventTap, event); + + CFRelease(event); + + if (!press && (id == kButtonLeft)) { + if (m_fakeDraggingStarted) { + m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>( + this, &OSXScreen::getDropTargetThread)); + } + + m_draggingStarted = false; + } +} + +void +OSXScreen::getDropTargetThread(void*) +{ +#if defined(MAC_OS_X_VERSION_10_7) + char* cstr = NULL; + + // wait for 5 secs for the drop destinaiton string to be filled. + UInt32 timeout = ARCH->time() + 5; + + while (ARCH->time() < timeout) { + CFStringRef cfstr = getCocoaDropTarget(); + cstr = CFStringRefToUTF8String(cfstr); + CFRelease(cfstr); + + if (cstr != NULL) { + break; + } + ARCH->sleep(.1f); + } + + if (cstr != NULL) { + LOG((CLOG_DEBUG "drop target: %s", cstr)); + m_dropTarget = cstr; + } + else { + LOG((CLOG_ERR "failed to get drop target")); + m_dropTarget.clear(); + } +#else + LOG((CLOG_WARN "drag drop not supported")); +#endif + m_fakeDraggingStarted = false; +} + +void +OSXScreen::fakeMouseMove(SInt32 x, SInt32 y) +{ + if (m_fakeDraggingStarted) { + m_buttonState.set(0, kMouseButtonDown); + } + + // index 0 means left mouse button + if (m_buttonState.test(0)) { + m_draggingStarted = true; + } + + // synthesize event + CGPoint pos; + pos.x = x; + pos.y = y; + postMouseEvent(pos); + + // save new cursor position + m_xCursor = static_cast<SInt32>(pos.x); + m_yCursor = static_cast<SInt32>(pos.y); + m_cursorPosValid = true; +} + +void +OSXScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // OS X does not appear to have a fake relative mouse move function. + // simulate it by getting the current mouse position and adding to + // that. this can yield the wrong answer but there's not much else + // we can do. + + // get current position + CGEventRef event = CGEventCreate(NULL); + CGPoint oldPos = CGEventGetLocation(event); + CFRelease(event); + + // synthesize event + CGPoint pos; + m_xCursor = static_cast<SInt32>(oldPos.x); + m_yCursor = static_cast<SInt32>(oldPos.y); + pos.x = oldPos.x + dx; + pos.y = oldPos.y + dy; + postMouseEvent(pos); + + // we now assume we don't know the current cursor position + m_cursorPosValid = false; +} + +void +OSXScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + if (xDelta != 0 || yDelta != 0) { + // create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine + // is the right choice here over kCGScrollEventUnitPixel + CGEventRef scrollEvent = CGEventCreateScrollWheelEvent( + NULL, kCGScrollEventUnitLine, 2, + mapScrollWheelFromBarrier(yDelta), + -mapScrollWheelFromBarrier(xDelta)); + + // Fix for sticky keys + CGEventFlags modifiers = m_keyState->getModifierStateAsOSXFlags(); + CGEventSetFlags(scrollEvent, modifiers); + + CGEventPost(kCGHIDEventTap, scrollEvent); + CFRelease(scrollEvent); + } +} + +void +OSXScreen::showCursor() +{ + LOG((CLOG_DEBUG "showing cursor")); + + CFStringRef propertyString = CFStringCreateWithCString( + NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman); + + CGSSetConnectionProperty( + _CGSDefaultConnection(), _CGSDefaultConnection(), + propertyString, kCFBooleanTrue); + + CFRelease(propertyString); + + CGError error = CGDisplayShowCursor(m_displayID); + if (error != kCGErrorSuccess) { + LOG((CLOG_ERR "failed to show cursor, error=%d", error)); + } + + // appears to fix "mouse randomly not showing" bug + CGAssociateMouseAndMouseCursorPosition(true); + + logCursorVisibility(); + + m_cursorHidden = false; +} + +void +OSXScreen::hideCursor() +{ + LOG((CLOG_DEBUG "hiding cursor")); + + CFStringRef propertyString = CFStringCreateWithCString( + NULL, "SetsCursorInBackground", kCFStringEncodingMacRoman); + + CGSSetConnectionProperty( + _CGSDefaultConnection(), _CGSDefaultConnection(), + propertyString, kCFBooleanTrue); + + CFRelease(propertyString); + + CGError error = CGDisplayHideCursor(m_displayID); + if (error != kCGErrorSuccess) { + LOG((CLOG_ERR "failed to hide cursor, error=%d", error)); + } + + // appears to fix "mouse randomly not hiding" bug + CGAssociateMouseAndMouseCursorPosition(true); + + logCursorVisibility(); + + m_cursorHidden = true; +} + +void +OSXScreen::enable() +{ + // watch the clipboard + m_clipboardTimer = m_events->newTimer(1.0, NULL); + m_events->adoptHandler(Event::kTimer, m_clipboardTimer, + new TMethodEventJob<OSXScreen>(this, + &OSXScreen::handleClipboardCheck)); + + if (m_isPrimary) { + // FIXME -- start watching jump zones + + // kCGEventTapOptionDefault = 0x00000000 (Missing in 10.4, so specified literally) + m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, + kCGEventMaskForAllEvents, + handleCGInputEvent, + this); + } + else { + // FIXME -- prevent system from entering power save mode + + if (m_autoShowHideCursor) { + hideCursor(); + } + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + + // there may be a better way to do this, but we register an event handler even if we're + // not on the primary display (acting as a client). This way, if a local event comes in + // (either keyboard or mouse), we can make sure to show the cursor if we've hidden it. + m_eventTapPort = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, + kCGEventMaskForAllEvents, + handleCGInputEventSecondary, + this); + } + + if (!m_eventTapPort) { + LOG((CLOG_ERR "failed to create quartz event tap")); + } + + m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0); + if (!m_eventTapRLSR) { + LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap")); + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode); +} + +void +OSXScreen::disable() +{ + if (m_autoShowHideCursor) { + showCursor(); + } + + // FIXME -- stop watching jump zones, stop capturing input + + if (m_eventTapRLSR) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode); + CFRelease(m_eventTapRLSR); + m_eventTapRLSR = nullptr; + } + + if (m_eventTapPort) { + CGEventTapEnable(m_eventTapPort, false); + CFRelease(m_eventTapPort); + m_eventTapPort = nullptr; + } + // FIXME -- allow system to enter power saving mode + + // disable drag handling + m_dragNumButtonsDown = 0; + enableDragTimer(false); + + // uninstall clipboard timer + if (m_clipboardTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_clipboardTimer); + m_events->deleteTimer(m_clipboardTimer); + m_clipboardTimer = NULL; + } + + m_isOnScreen = m_isPrimary; +} + +void +OSXScreen::enter() +{ + showCursor(); + + if (m_isPrimary) { + setZeroSuppressionInterval(); + } + else { + // reset buttons + m_buttonState.reset(); + + // patch by Yutaka Tsutano + // wakes the client screen + // http://symless.com/spit/issues/details/3287#c12 + io_registry_entry_t entry = IORegistryEntryFromPath( + kIOMasterPortDefault, + "IOService:/IOResources/IODisplayWrangler"); + + if (entry != MACH_PORT_NULL) { + IORegistryEntrySetCFProperty(entry, CFSTR("IORequestIdle"), kCFBooleanFalse); + IOObjectRelease(entry); + } + + avoidSupression(); + } + + // now on screen + m_isOnScreen = true; +} + +bool +OSXScreen::leave() +{ + hideCursor(); + + if (isDraggingStarted()) { + String& fileList = getDraggingFilename(); + + if (!m_isPrimary) { + if (fileList.empty() == false) { + ClientApp& app = ClientApp::instance(); + Client* client = app.getClientPtr(); + + DragInformation di; + di.setFilename(fileList); + DragFileList dragFileList; + dragFileList.push_back(di); + String info; + UInt32 fileCount = DragInformation::setupDragInfo( + dragFileList, info); + client->sendDragInfo(fileCount, info, info.size()); + LOG((CLOG_DEBUG "send dragging file to server")); + + // TODO: what to do with multiple file or even + // a folder + client->sendFileToServer(fileList.c_str()); + } + } + m_draggingStarted = false; + } + + if (m_isPrimary) { + avoidHesitatingCursor(); + + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +OSXScreen::setClipboard(ClipboardID, const IClipboard* src) +{ + if (src != NULL) { + LOG((CLOG_DEBUG "setting clipboard")); + Clipboard::copy(&m_pasteboard, src); + } + return true; +} + +void +OSXScreen::checkClipboards() +{ + LOG((CLOG_DEBUG2 "checking clipboard")); + if (m_pasteboard.synchronize()) { + LOG((CLOG_DEBUG "clipboard changed")); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardClipboard); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), kClipboardSelection); + } +} + +void +OSXScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +OSXScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +OSXScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +OSXScreen::resetOptions() +{ + // no options +} + +void +OSXScreen::setOptions(const OptionsList&) +{ + // no options +} + +void +OSXScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +OSXScreen::isPrimary() const +{ + return m_isPrimary; +} + +void +OSXScreen::sendEvent(Event::Type type, void* data) const +{ + m_events->addEvent(Event(type, getEventTarget(), data)); +} + +void +OSXScreen::sendClipboardEvent(Event::Type type, ClipboardID id) const +{ + ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +void +OSXScreen::handleSystemEvent(const Event& event, void*) +{ + EventRef* carbonEvent = static_cast<EventRef*>(event.getData()); + assert(carbonEvent != NULL); + + UInt32 eventClass = GetEventClass(*carbonEvent); + + switch (eventClass) { + case kEventClassMouse: + switch (GetEventKind(*carbonEvent)) { + case kBarrierEventMouseScroll: + { + OSStatus r; + long xScroll; + long yScroll; + + // get scroll amount + r = GetEventParameter(*carbonEvent, + kBarrierMouseScrollAxisX, + typeSInt32, + NULL, + sizeof(xScroll), + NULL, + &xScroll); + if (r != noErr) { + xScroll = 0; + } + r = GetEventParameter(*carbonEvent, + kBarrierMouseScrollAxisY, + typeSInt32, + NULL, + sizeof(yScroll), + NULL, + &yScroll); + if (r != noErr) { + yScroll = 0; + } + + if (xScroll != 0 || yScroll != 0) { + onMouseWheel(-mapScrollWheelToBarrier(xScroll), + mapScrollWheelToBarrier(yScroll)); + } + } + } + break; + + case kEventClassKeyboard: + switch (GetEventKind(*carbonEvent)) { + case kEventHotKeyPressed: + case kEventHotKeyReleased: + onHotKey(*carbonEvent); + break; + } + + break; + + case kEventClassWindow: + // 2nd param was formerly GetWindowEventTarget(m_userInputWindow) which is 32-bit only, + // however as m_userInputWindow is never initialized to anything we can take advantage of + // the fact that GetWindowEventTarget(NULL) == NULL + SendEventToEventTarget(*carbonEvent, NULL); + switch (GetEventKind(*carbonEvent)) { + case kEventWindowActivated: + LOG((CLOG_DEBUG1 "window activated")); + break; + + case kEventWindowDeactivated: + LOG((CLOG_DEBUG1 "window deactivated")); + break; + + case kEventWindowFocusAcquired: + LOG((CLOG_DEBUG1 "focus acquired")); + break; + + case kEventWindowFocusRelinquish: + LOG((CLOG_DEBUG1 "focus released")); + break; + } + break; + + default: + SendEventToEventTarget(*carbonEvent, GetEventDispatcherTarget()); + break; + } +} + +bool +OSXScreen::onMouseMove(SInt32 mx, SInt32 my) +{ + LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my)); + + SInt32 x = mx - m_xCursor; + SInt32 y = my - m_yCursor; + + if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) { + return true; + } + + // save position to compute delta of next motion + m_xCursor = mx; + m_yCursor = my; + + if (m_isOnScreen) { + // motion on primary screen + sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(), + MotionInfo::alloc(m_xCursor, m_yCursor)); + if (m_buttonState.test(0)) { + m_draggingStarted = true; + } + } + else { + // motion on secondary screen. warp mouse back to + // center. + warpCursor(m_xCenter, m_yCenter); + + // examine the motion. if it's about the distance + // from the center of the screen to an edge then + // it's probably a bogus motion that we want to + // ignore (see warpCursorNoFlush() for a further + // description). + static SInt32 bogusZoneSize = 10; + if (-x + bogusZoneSize > m_xCenter - m_x || + x + bogusZoneSize > m_x + m_w - m_xCenter || + -y + bogusZoneSize > m_yCenter - m_y || + y + bogusZoneSize > m_y + m_h - m_yCenter) { + LOG((CLOG_DEBUG "dropped bogus motion %+d,%+d", x, y)); + } + else { + // send motion + sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y)); + } + } + + return true; +} + +bool +OSXScreen::onMouseButton(bool pressed, UInt16 macButton) +{ + // Buttons 2 and 3 are inverted on the mac + ButtonID button = mapMacButtonToBarrier(macButton); + + if (pressed) { + LOG((CLOG_DEBUG1 "event: button press button=%d", button)); + if (button != kButtonNone) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask)); + } + } + else { + LOG((CLOG_DEBUG1 "event: button release button=%d", button)); + if (button != kButtonNone) { + KeyModifierMask mask = m_keyState->getActiveModifiers(); + sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask)); + } + } + + // handle drags with any button other than button 1 or 2 + if (macButton > 2) { + if (pressed) { + // one more button + if (m_dragNumButtonsDown++ == 0) { + enableDragTimer(true); + } + } + else { + // one less button + if (--m_dragNumButtonsDown == 0) { + enableDragTimer(false); + } + } + } + + if (macButton == kButtonLeft) { + EMouseButtonState state = pressed ? kMouseButtonDown : kMouseButtonUp; + m_buttonState.set(kButtonLeft - 1, state); + if (pressed) { + m_draggingFilename.clear(); + LOG((CLOG_DEBUG2 "dragging file directory is cleared")); + } + else { + if (m_fakeDraggingStarted) { + m_getDropTargetThread = new Thread(new TMethodJob<OSXScreen>( + this, &OSXScreen::getDropTargetThread)); + } + + m_draggingStarted = false; + } + } + + return true; +} + +bool +OSXScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) const +{ + LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta)); + return true; +} + +void +OSXScreen::handleClipboardCheck(const Event&, void*) +{ + checkClipboards(); +} + +void +OSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDisplayChangeSummaryFlags flags, void* inUserData) +{ + OSXScreen* screen = (OSXScreen*)inUserData; + + // Closing or opening the lid when an external monitor is + // connected causes an kCGDisplayBeginConfigurationFlag event + CGDisplayChangeSummaryFlags mask = kCGDisplayBeginConfigurationFlag | kCGDisplayMovedFlag | + kCGDisplaySetModeFlag | kCGDisplayAddFlag | kCGDisplayRemoveFlag | + kCGDisplayEnabledFlag | kCGDisplayDisabledFlag | + kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag | + kCGDisplayDesktopShapeChangedFlag; + + LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask)); + + if (flags & mask) { /* Something actually did change */ + + LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions")); + screen->updateScreenShape(displayID, flags); + } +} + +bool +OSXScreen::onKey(CGEventRef event) +{ + CGEventType eventKind = CGEventGetType(event); + + // get the key and active modifiers + UInt32 virtualKey = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode); + CGEventFlags macMask = CGEventGetFlags(event); + LOG((CLOG_DEBUG1 "event: Key event kind: %d, keycode=%d", eventKind, virtualKey)); + + // Special handling to track state of modifiers + if (eventKind == kCGEventFlagsChanged) { + // get old and new modifier state + KeyModifierMask oldMask = getActiveModifiers(); + KeyModifierMask newMask = m_keyState->mapModifiersFromOSX(macMask); + m_keyState->handleModifierKeys(getEventTarget(), oldMask, newMask); + + // if the current set of modifiers exactly matches a modifiers-only + // hot key then generate a hot key down event. + if (m_activeModifierHotKey == 0) { + if (m_modifierHotKeys.count(newMask) > 0) { + m_activeModifierHotKey = m_modifierHotKeys[newMask]; + m_activeModifierHotKeyMask = newMask; + m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyDown(), + getEventTarget(), + HotKeyInfo::alloc(m_activeModifierHotKey))); + } + } + + // if a modifiers-only hot key is active and should no longer be + // then generate a hot key up event. + else if (m_activeModifierHotKey != 0) { + KeyModifierMask mask = (newMask & m_activeModifierHotKeyMask); + if (mask != m_activeModifierHotKeyMask) { + m_events->addEvent(Event(m_events->forIPrimaryScreen().hotKeyUp(), + getEventTarget(), + HotKeyInfo::alloc(m_activeModifierHotKey))); + m_activeModifierHotKey = 0; + m_activeModifierHotKeyMask = 0; + } + } + + return true; + } + + // check for hot key. when we're on a secondary screen we disable + // all hotkeys so we can capture the OS defined hot keys as regular + // keystrokes but that means we don't get our own hot keys either. + // so we check for a key/modifier match in our hot key map. + if (!m_isOnScreen) { + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(HotKeyItem(virtualKey, + m_keyState->mapModifiersToCarbon(macMask) + & 0xff00u)); + if (i != m_hotKeyToIDMap.end()) { + UInt32 id = i->second; + + // determine event type + Event::Type type; + //UInt32 eventKind = GetEventKind(event); + if (eventKind == kCGEventKeyDown) { + type = m_events->forIPrimaryScreen().hotKeyDown(); + } + else if (eventKind == kCGEventKeyUp) { + type = m_events->forIPrimaryScreen().hotKeyUp(); + } + else { + return false; + } + + m_events->addEvent(Event(type, getEventTarget(), + HotKeyInfo::alloc(id))); + + return true; + } + } + + // decode event type + bool down = (eventKind == kCGEventKeyDown); + bool up = (eventKind == kCGEventKeyUp); + bool isRepeat = (CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1); + + // map event to keys + KeyModifierMask mask; + OSXKeyState::KeyIDs keys; + KeyButton button = m_keyState->mapKeyFromEvent(keys, &mask, event); + if (button == 0) { + return false; + } + + // check for AltGr in mask. if set we send neither the AltGr nor + // the super modifiers to clients then remove AltGr before passing + // the modifiers to onKey. + KeyModifierMask sendMask = (mask & ~KeyModifierAltGr); + if ((mask & KeyModifierAltGr) != 0) { + sendMask &= ~KeyModifierSuper; + } + mask &= ~KeyModifierAltGr; + + // update button state + if (down) { + m_keyState->onKey(button, true, mask); + } + else if (up) { + if (!m_keyState->isKeyDown(button)) { + // up event for a dead key. throw it away. + return false; + } + m_keyState->onKey(button, false, mask); + } + + // send key events + for (OSXKeyState::KeyIDs::const_iterator i = keys.begin(); + i != keys.end(); ++i) { + m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, + *i, sendMask, 1, button); + } + + return true; +} + +void +OSXScreen::onMediaKey(CGEventRef event) +{ + KeyID keyID; + bool down; + bool isRepeat; + + if (!getMediaKeyEventInfo (event, &keyID, &down, &isRepeat)) { + LOG ((CLOG_ERR "Failed to decode media key event")); + return; + } + + LOG ((CLOG_DEBUG2 "Media key event: keyID=0x%02x, %s, repeat=%s", + keyID, (down ? "down": "up"), + (isRepeat ? "yes" : "no"))); + + KeyButton button = 0; + KeyModifierMask mask = m_keyState->getActiveModifiers(); + m_keyState->sendKeyEvent(getEventTarget(), down, isRepeat, keyID, mask, 1, button); +} + +bool +OSXScreen::onHotKey(EventRef event) const +{ + // get the hotkey id + EventHotKeyID hkid; + GetEventParameter(event, kEventParamDirectObject, typeEventHotKeyID, + NULL, sizeof(EventHotKeyID), NULL, &hkid); + UInt32 id = hkid.id; + + // determine event type + Event::Type type; + UInt32 eventKind = GetEventKind(event); + if (eventKind == kEventHotKeyPressed) { + type = m_events->forIPrimaryScreen().hotKeyDown(); + } + else if (eventKind == kEventHotKeyReleased) { + type = m_events->forIPrimaryScreen().hotKeyUp(); + } + else { + return false; + } + + m_events->addEvent(Event(type, getEventTarget(), + HotKeyInfo::alloc(id))); + + return true; +} + +ButtonID +OSXScreen::mapBarrierButtonToMac(UInt16 button) const +{ + switch (button) { + case 1: + return kButtonLeft; + case 2: + return kMacButtonMiddle; + case 3: + return kMacButtonRight; + } + + return static_cast<ButtonID>(button); +} + +ButtonID +OSXScreen::mapMacButtonToBarrier(UInt16 macButton) const +{ + switch (macButton) { + case 1: + return kButtonLeft; + + case 2: + return kButtonRight; + + case 3: + return kButtonMiddle; + } + + return static_cast<ButtonID>(macButton); +} + +SInt32 +OSXScreen::mapScrollWheelToBarrier(SInt32 x) const +{ + // return accelerated scrolling but not exponentially scaled as it is + // on the mac. + double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor(); + return static_cast<SInt32>(120.0 * d); +} + +SInt32 +OSXScreen::mapScrollWheelFromBarrier(SInt32 x) const +{ + // use server's acceleration with a little boost since other platforms + // take one wheel step as a larger step than the mac does. + return static_cast<SInt32>(3.0 * x / 120.0); +} + +double +OSXScreen::getScrollSpeed() const +{ + double scaling = 0.0; + + CFPropertyListRef pref = ::CFPreferencesCopyValue( + CFSTR("com.apple.scrollwheel.scaling") , + kCFPreferencesAnyApplication, + kCFPreferencesCurrentUser, + kCFPreferencesAnyHost); + if (pref != NULL) { + CFTypeID id = CFGetTypeID(pref); + if (id == CFNumberGetTypeID()) { + CFNumberRef value = static_cast<CFNumberRef>(pref); + if (CFNumberGetValue(value, kCFNumberDoubleType, &scaling)) { + if (scaling < 0.0) { + scaling = 0.0; + } + } + } + CFRelease(pref); + } + + return scaling; +} + +double +OSXScreen::getScrollSpeedFactor() const +{ + return pow(10.0, getScrollSpeed()); +} + +void +OSXScreen::enableDragTimer(bool enable) +{ + if (enable && m_dragTimer == NULL) { + m_dragTimer = m_events->newTimer(0.01, NULL); + m_events->adoptHandler(Event::kTimer, m_dragTimer, + new TMethodEventJob<OSXScreen>(this, + &OSXScreen::handleDrag)); + CGEventRef event = CGEventCreate(NULL); + CGPoint mouse = CGEventGetLocation(event); + m_dragLastPoint.h = (short)mouse.x; + m_dragLastPoint.v = (short)mouse.y; + CFRelease(event); + } + else if (!enable && m_dragTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_dragTimer); + m_events->deleteTimer(m_dragTimer); + m_dragTimer = NULL; + } +} + +void +OSXScreen::handleDrag(const Event&, void*) +{ + CGEventRef event = CGEventCreate(NULL); + CGPoint p = CGEventGetLocation(event); + CFRelease(event); + + if ((short)p.x != m_dragLastPoint.h || (short)p.y != m_dragLastPoint.v) { + m_dragLastPoint.h = (short)p.x; + m_dragLastPoint.v = (short)p.y; + onMouseMove((SInt32)p.x, (SInt32)p.y); + } +} + +void +OSXScreen::updateButtons() +{ + UInt32 buttons = GetCurrentButtonState(); + + m_buttonState.overwrite(buttons); +} + +IKeyState* +OSXScreen::getKeyState() const +{ + return m_keyState; +} + +void +OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags) +{ + updateScreenShape(); +} + +void +OSXScreen::updateScreenShape() +{ + // get info for each display + CGDisplayCount displayCount = 0; + + if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) { + return; + } + + if (displayCount == 0) { + return; + } + + CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount]; + if (displays == NULL) { + return; + } + + if (CGGetActiveDisplayList(displayCount, + displays, &displayCount) != CGDisplayNoErr) { + delete[] displays; + return; + } + + // get smallest rect enclosing all display rects + CGRect totalBounds = CGRectZero; + for (CGDisplayCount i = 0; i < displayCount; ++i) { + CGRect bounds = CGDisplayBounds(displays[i]); + totalBounds = CGRectUnion(totalBounds, bounds); + } + + // get shape of default screen + m_x = (SInt32)totalBounds.origin.x; + m_y = (SInt32)totalBounds.origin.y; + m_w = (SInt32)totalBounds.size.width; + m_h = (SInt32)totalBounds.size.height; + + // get center of default screen + CGDirectDisplayID main = CGMainDisplayID(); + const CGRect rect = CGDisplayBounds(main); + m_xCenter = (rect.origin.x + rect.size.width) / 2; + m_yCenter = (rect.origin.y + rect.size.height) / 2; + + delete[] displays; + // We want to notify the peer screen whether we are primary screen or not + sendEvent(m_events->forIScreen().shapeChanged()); + + LOG((CLOG_DEBUG "screen shape: center=%d,%d size=%dx%d on %u %s", + m_x, m_y, m_w, m_h, displayCount, + (displayCount == 1) ? "display" : "displays")); +} + +#pragma mark - + +// +// FAST USER SWITCH NOTIFICATION SUPPORT +// +// OSXScreen::userSwitchCallback(void*) +// +// gets called if a fast user switch occurs +// + +pascal OSStatus +OSXScreen::userSwitchCallback(EventHandlerCallRef nextHandler, + EventRef theEvent, + void* inUserData) +{ + OSXScreen* screen = (OSXScreen*)inUserData; + UInt32 kind = GetEventKind(theEvent); + IEventQueue* events = screen->getEvents(); + + if (kind == kEventSystemUserSessionDeactivated) { + LOG((CLOG_DEBUG "user session deactivated")); + events->addEvent(Event(events->forIScreen().suspend(), + screen->getEventTarget())); + } + else if (kind == kEventSystemUserSessionActivated) { + LOG((CLOG_DEBUG "user session activated")); + events->addEvent(Event(events->forIScreen().resume(), + screen->getEventTarget())); + } + return (CallNextEventHandler(nextHandler, theEvent)); +} + +#pragma mark - + +// +// SLEEP/WAKEUP NOTIFICATION SUPPORT +// +// OSXScreen::watchSystemPowerThread(void*) +// +// main of thread monitoring system power (sleep/wakup) using a CFRunLoop +// + +void +OSXScreen::watchSystemPowerThread(void*) +{ + io_object_t notifier; + IONotificationPortRef notificationPortRef; + CFRunLoopSourceRef runloopSourceRef = 0; + + m_pmRunloop = CFRunLoopGetCurrent(); + // install system power change callback + m_pmRootPort = IORegisterForSystemPower(this, ¬ificationPortRef, + powerChangeCallback, ¬ifier); + if (m_pmRootPort == 0) { + LOG((CLOG_WARN "IORegisterForSystemPower failed")); + } + else { + runloopSourceRef = + IONotificationPortGetRunLoopSource(notificationPortRef); + CFRunLoopAddSource(m_pmRunloop, runloopSourceRef, + kCFRunLoopCommonModes); + } + + // thread is ready + { + Lock lock(m_pmMutex); + *m_pmThreadReady = true; + m_pmThreadReady->signal(); + } + + // if we were unable to initialize then exit. we must do this after + // setting m_pmThreadReady to true otherwise the parent thread will + // block waiting for it. + if (m_pmRootPort == 0) { + LOG((CLOG_WARN "failed to init watchSystemPowerThread")); + return; + } + + LOG((CLOG_DEBUG "started watchSystemPowerThread")); + + LOG((CLOG_DEBUG "waiting for event loop")); + m_events->waitForReady(); + +#if defined(MAC_OS_X_VERSION_10_7) + { + Lock lockCarbon(m_carbonLoopMutex); + if (*m_carbonLoopReady == false) { + + // we signalling carbon loop ready before starting + // unless we know how to do it within the loop + LOG((CLOG_DEBUG "signalling carbon loop ready")); + + *m_carbonLoopReady = true; + m_carbonLoopReady->signal(); + } + } +#endif + + // start the run loop + LOG((CLOG_DEBUG "starting carbon loop")); + CFRunLoopRun(); + LOG((CLOG_DEBUG "carbon loop has stopped")); + + // cleanup + if (notificationPortRef) { + CFRunLoopRemoveSource(m_pmRunloop, + runloopSourceRef, kCFRunLoopDefaultMode); + CFRunLoopSourceInvalidate(runloopSourceRef); + CFRelease(runloopSourceRef); + } + + Lock lock(m_pmMutex); + IODeregisterForSystemPower(¬ifier); + m_pmRootPort = 0; + LOG((CLOG_DEBUG "stopped watchSystemPowerThread")); +} + +void +OSXScreen::powerChangeCallback(void* refcon, io_service_t service, + natural_t messageType, void* messageArg) +{ + ((OSXScreen*)refcon)->handlePowerChangeRequest(messageType, messageArg); +} + +void +OSXScreen::handlePowerChangeRequest(natural_t messageType, void* messageArg) +{ + // we've received a power change notification + switch (messageType) { + case kIOMessageSystemWillSleep: + // OSXScreen has to handle this in the main thread so we have to + // queue a confirm sleep event here. we actually don't allow the + // system to sleep until the event is handled. + m_events->addEvent(Event(m_events->forOSXScreen().confirmSleep(), + getEventTarget(), messageArg, + Event::kDontFreeData)); + return; + + case kIOMessageSystemHasPoweredOn: + LOG((CLOG_DEBUG "system wakeup")); + m_events->addEvent(Event(m_events->forIScreen().resume(), + getEventTarget())); + break; + + default: + break; + } + + Lock lock(m_pmMutex); + if (m_pmRootPort != 0) { + IOAllowPowerChange(m_pmRootPort, (long)messageArg); + } +} + +void +OSXScreen::handleConfirmSleep(const Event& event, void*) +{ + long messageArg = (long)event.getData(); + if (messageArg != 0) { + Lock lock(m_pmMutex); + if (m_pmRootPort != 0) { + // deliver suspend event immediately. + m_events->addEvent(Event(m_events->forIScreen().suspend(), + getEventTarget(), NULL, + Event::kDeliverImmediately)); + + LOG((CLOG_DEBUG "system will sleep")); + IOAllowPowerChange(m_pmRootPort, messageArg); + } + } +} + +#pragma mark - + +// +// GLOBAL HOTKEY OPERATING MODE SUPPORT (10.3) +// +// CoreGraphics private API (OSX 10.3) +// Source: http://ichiro.nnip.org/osx/Cocoa/GlobalHotkey.html +// +// We load the functions dynamically because they're not available in +// older SDKs. We don't use weak linking because we want users of +// older SDKs to build an app that works on newer systems and older +// SDKs will not provide the symbols. +// +// FIXME: This is hosed as of OS 10.5; patches to repair this are +// a good thing. +// +#if 0 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int CGSConnection; +typedef enum { + CGSGlobalHotKeyEnable = 0, + CGSGlobalHotKeyDisable = 1, +} CGSGlobalHotKeyOperatingMode; + +extern CGSConnection _CGSDefaultConnection(void) WEAK_IMPORT_ATTRIBUTE; +extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode) WEAK_IMPORT_ATTRIBUTE; +extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode) WEAK_IMPORT_ATTRIBUTE; + +typedef CGSConnection (*_CGSDefaultConnection_t)(void); +typedef CGError (*CGSGetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode *mode); +typedef CGError (*CGSSetGlobalHotKeyOperatingMode_t)(CGSConnection connection, CGSGlobalHotKeyOperatingMode mode); + +static _CGSDefaultConnection_t s__CGSDefaultConnection; +static CGSGetGlobalHotKeyOperatingMode_t s_CGSGetGlobalHotKeyOperatingMode; +static CGSSetGlobalHotKeyOperatingMode_t s_CGSSetGlobalHotKeyOperatingMode; + +#ifdef __cplusplus +} +#endif + +#define LOOKUP(name_) \ + s_ ## name_ = NULL; \ + if (NSIsSymbolNameDefinedWithHint("_" #name_, "CoreGraphics")) { \ + s_ ## name_ = (name_ ## _t)NSAddressOfSymbol( \ + NSLookupAndBindSymbolWithHint( \ + "_" #name_, "CoreGraphics")); \ + } + +bool +OSXScreen::isGlobalHotKeyOperatingModeAvailable() +{ + if (!s_testedForGHOM) { + s_testedForGHOM = true; + LOOKUP(_CGSDefaultConnection); + LOOKUP(CGSGetGlobalHotKeyOperatingMode); + LOOKUP(CGSSetGlobalHotKeyOperatingMode); + s_hasGHOM = (s__CGSDefaultConnection != NULL && + s_CGSGetGlobalHotKeyOperatingMode != NULL && + s_CGSSetGlobalHotKeyOperatingMode != NULL); + } + return s_hasGHOM; +} + +void +OSXScreen::setGlobalHotKeysEnabled(bool enabled) +{ + if (isGlobalHotKeyOperatingModeAvailable()) { + CGSConnection conn = s__CGSDefaultConnection(); + + CGSGlobalHotKeyOperatingMode mode; + s_CGSGetGlobalHotKeyOperatingMode(conn, &mode); + + if (enabled && mode == CGSGlobalHotKeyDisable) { + s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyEnable); + } + else if (!enabled && mode == CGSGlobalHotKeyEnable) { + s_CGSSetGlobalHotKeyOperatingMode(conn, CGSGlobalHotKeyDisable); + } + } +} + +bool +OSXScreen::getGlobalHotKeysEnabled() +{ + CGSGlobalHotKeyOperatingMode mode; + if (isGlobalHotKeyOperatingModeAvailable()) { + CGSConnection conn = s__CGSDefaultConnection(); + s_CGSGetGlobalHotKeyOperatingMode(conn, &mode); + } + else { + mode = CGSGlobalHotKeyEnable; + } + return (mode == CGSGlobalHotKeyEnable); +} + +#endif + +// +// OSXScreen::HotKeyItem +// + +OSXScreen::HotKeyItem::HotKeyItem(UInt32 keycode, UInt32 mask) : + m_ref(NULL), + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +OSXScreen::HotKeyItem::HotKeyItem(EventHotKeyRef ref, + UInt32 keycode, UInt32 mask) : + m_ref(ref), + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +EventHotKeyRef +OSXScreen::HotKeyItem::getRef() const +{ + return m_ref; +} + +bool +OSXScreen::HotKeyItem::operator<(const HotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} + +// Quartz event tap support for the secondary display. This makes sure that we +// will show the cursor if a local event comes in while barrier has the cursor +// off the screen. +CGEventRef +OSXScreen::handleCGInputEventSecondary( + CGEventTapProxy proxy, + CGEventType type, + CGEventRef event, + void* refcon) +{ + // this fix is really screwing with the correct show/hide behavior. it + // should be tested better before reintroducing. + return event; + + OSXScreen* screen = (OSXScreen*)refcon; + if (screen->m_cursorHidden && type == kCGEventMouseMoved) { + + CGPoint pos = CGEventGetLocation(event); + if (pos.x != screen->m_xCenter || pos.y != screen->m_yCenter) { + + LOG((CLOG_DEBUG "show cursor on secondary, type=%d pos=%d,%d", + type, pos.x, pos.y)); + screen->showCursor(); + } + } + return event; +} + +// Quartz event tap support +CGEventRef +OSXScreen::handleCGInputEvent(CGEventTapProxy proxy, + CGEventType type, + CGEventRef event, + void* refcon) +{ + OSXScreen* screen = (OSXScreen*)refcon; + CGPoint pos; + + switch(type) { + case kCGEventLeftMouseDown: + case kCGEventRightMouseDown: + case kCGEventOtherMouseDown: + screen->onMouseButton(true, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1); + break; + case kCGEventLeftMouseUp: + case kCGEventRightMouseUp: + case kCGEventOtherMouseUp: + screen->onMouseButton(false, CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber) + 1); + break; + case kCGEventLeftMouseDragged: + case kCGEventRightMouseDragged: + case kCGEventOtherMouseDragged: + case kCGEventMouseMoved: + pos = CGEventGetLocation(event); + screen->onMouseMove(pos.x, pos.y); + + // The system ignores our cursor-centering calls if + // we don't return the event. This should be harmless, + // but might register as slight movement to other apps + // on the system. It hasn't been a problem before, though. + return event; + break; + case kCGEventScrollWheel: + screen->onMouseWheel(screen->mapScrollWheelToBarrier( + CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis2)), + screen->mapScrollWheelToBarrier( + CGEventGetIntegerValueField(event, kCGScrollWheelEventDeltaAxis1))); + break; + case kCGEventKeyDown: + case kCGEventKeyUp: + case kCGEventFlagsChanged: + screen->onKey(event); + break; + case kCGEventTapDisabledByTimeout: + // Re-enable our event-tap + CGEventTapEnable(screen->m_eventTapPort, true); + LOG((CLOG_INFO "quartz event tap was disabled by timeout, re-enabling")); + break; + case kCGEventTapDisabledByUserInput: + LOG((CLOG_ERR "quartz event tap was disabled by user input")); + break; + case NX_NULLEVENT: + break; + default: + if (type == NX_SYSDEFINED) { + if (isMediaKeyEvent (event)) { + LOG((CLOG_DEBUG2 "detected media key event")); + screen->onMediaKey (event); + } else { + LOG((CLOG_DEBUG2 "ignoring unknown system defined event")); + return event; + } + break; + } + + LOG((CLOG_DEBUG3 "unknown quartz event type: 0x%02x", type)); + } + + if (screen->m_isOnScreen) { + return event; + } else { + return NULL; + } +} + +void +OSXScreen::MouseButtonState::set(UInt32 button, EMouseButtonState state) +{ + bool newState = (state == kMouseButtonDown); + m_buttons.set(button, newState); +} + +bool +OSXScreen::MouseButtonState::any() +{ + return m_buttons.any(); +} + +void +OSXScreen::MouseButtonState::reset() +{ + m_buttons.reset(); +} + +void +OSXScreen::MouseButtonState::overwrite(UInt32 buttons) +{ + m_buttons = std::bitset<NumButtonIDs>(buttons); +} + +bool +OSXScreen::MouseButtonState::test(UInt32 button) const +{ + return m_buttons.test(button); +} + +SInt8 +OSXScreen::MouseButtonState::getFirstButtonDown() const +{ + if (m_buttons.any()) { + for (unsigned short button = 0; button < m_buttons.size(); button++) { + if (m_buttons.test(button)) { + return button; + } + } + } + return -1; +} + +char* +OSXScreen::CFStringRefToUTF8String(CFStringRef aString) +{ + if (aString == NULL) { + return NULL; + } + + CFIndex length = CFStringGetLength(aString); + CFIndex maxSize = CFStringGetMaximumSizeForEncoding( + length, + kCFStringEncodingUTF8); + char* buffer = (char*)malloc(maxSize); + if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) { + return buffer; + } + return NULL; +} + +void +OSXScreen::fakeDraggingFiles(DragFileList fileList) +{ + m_fakeDraggingStarted = true; + String fileExt; + if (fileList.size() == 1) { + fileExt = DragInformation::getDragFileExtension( + fileList.at(0).getFilename()); + } + +#if defined(MAC_OS_X_VERSION_10_7) + fakeDragging(fileExt.c_str(), m_xCursor, m_yCursor); +#else + LOG((CLOG_WARN "drag drop not supported")); +#endif +} + +String& +OSXScreen::getDraggingFilename() +{ + if (m_draggingStarted) { + CFStringRef dragInfo = getDraggedFileURL(); + char* info = NULL; + info = CFStringRefToUTF8String(dragInfo); + if (info == NULL) { + m_draggingFilename.clear(); + } + else { + LOG((CLOG_DEBUG "drag info: %s", info)); + CFRelease(dragInfo); + String fileList(info); + m_draggingFilename = fileList; + } + + // fake a escape key down and up then left mouse button up + fakeKeyDown(kKeyEscape, 8192, 1); + fakeKeyUp(1); + fakeMouseButton(kButtonLeft, false); + } + return m_draggingFilename; +} + +void +OSXScreen::waitForCarbonLoop() const +{ +#if defined(MAC_OS_X_VERSION_10_7) + if (*m_carbonLoopReady) { + LOG((CLOG_DEBUG "carbon loop already ready")); + return; + } + + Lock lock(m_carbonLoopMutex); + + LOG((CLOG_DEBUG "waiting for carbon loop")); + + double timeout = ARCH->time() + kCarbonLoopWaitTimeout; + while (!m_carbonLoopReady->wait()) { + if (ARCH->time() > timeout) { + LOG((CLOG_DEBUG "carbon loop not ready, waiting again")); + timeout = ARCH->time() + kCarbonLoopWaitTimeout; + } + } + + LOG((CLOG_DEBUG "carbon loop ready")); +#endif + +} + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +void +setZeroSuppressionInterval() +{ + CGSetLocalEventsSuppressionInterval(0.0); +} + +void +avoidSupression() +{ + // avoid suppression of local hardware events + // stkamp@users.sourceforge.net + CGSetLocalEventsFilterDuringSupressionState( + kCGEventFilterMaskPermitAllEvents, + kCGEventSupressionStateSupressionInterval); + CGSetLocalEventsFilterDuringSupressionState( + (kCGEventFilterMaskPermitLocalKeyboardEvents | + kCGEventFilterMaskPermitSystemDefinedEvents), + kCGEventSupressionStateRemoteMouseDrag); +} + +void +logCursorVisibility() +{ + // CGCursorIsVisible is probably deprecated because its unreliable. + if (!CGCursorIsVisible()) { + LOG((CLOG_WARN "cursor may not be visible")); + } +} + +void +avoidHesitatingCursor() +{ + // This used to be necessary to get smooth mouse motion on other screens, + // but now is just to avoid a hesitating cursor when transitioning to + // the primary (this) screen. + CGSetLocalEventsSuppressionInterval(0.0001); +} + +#pragma GCC diagnostic error "-Wdeprecated-declarations" diff --git a/src/lib/platform/OSXScreenSaver.cpp b/src/lib/platform/OSXScreenSaver.cpp new file mode 100644 index 0000000..a0282d9 --- /dev/null +++ b/src/lib/platform/OSXScreenSaver.cpp @@ -0,0 +1,201 @@ +/* + * 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/>. + */ + +#import "platform/OSXScreenSaver.h" + +#import "platform/OSXScreenSaverUtil.h" +#import "barrier/IPrimaryScreen.h" +#import "base/Log.h" +#import "base/IEventQueue.h" + +#import <string.h> +#import <sys/sysctl.h> + +// TODO: upgrade deprecated function usage in these functions. +void getProcessSerialNumber(const char* name, ProcessSerialNumber& psn); +bool testProcessName(const char* name, const ProcessSerialNumber& psn); + +// +// OSXScreenSaver +// + +OSXScreenSaver::OSXScreenSaver(IEventQueue* events, void* eventTarget) : + m_eventTarget(eventTarget), + m_enabled(true), + m_events(events) +{ + m_autoReleasePool = screenSaverUtilCreatePool(); + m_screenSaverController = screenSaverUtilCreateController(); + + // install launch/termination event handlers + EventTypeSpec launchEventTypes[2]; + launchEventTypes[0].eventClass = kEventClassApplication; + launchEventTypes[0].eventKind = kEventAppLaunched; + launchEventTypes[1].eventClass = kEventClassApplication; + launchEventTypes[1].eventKind = kEventAppTerminated; + + EventHandlerUPP launchTerminationEventHandler = + NewEventHandlerUPP(launchTerminationCallback); + InstallApplicationEventHandler(launchTerminationEventHandler, 2, + launchEventTypes, this, + &m_launchTerminationEventHandlerRef); + DisposeEventHandlerUPP(launchTerminationEventHandler); + + m_screenSaverPSN.highLongOfPSN = 0; + m_screenSaverPSN.lowLongOfPSN = 0; + + if (isActive()) { + getProcessSerialNumber("ScreenSaverEngine", m_screenSaverPSN); + } +} + +OSXScreenSaver::~OSXScreenSaver() +{ + RemoveEventHandler(m_launchTerminationEventHandlerRef); +// screenSaverUtilReleaseController(m_screenSaverController); + screenSaverUtilReleasePool(m_autoReleasePool); +} + +void +OSXScreenSaver::enable() +{ + m_enabled = true; + screenSaverUtilEnable(m_screenSaverController); +} + +void +OSXScreenSaver::disable() +{ + m_enabled = false; + screenSaverUtilDisable(m_screenSaverController); +} + +void +OSXScreenSaver::activate() +{ + screenSaverUtilActivate(m_screenSaverController); +} + +void +OSXScreenSaver::deactivate() +{ + screenSaverUtilDeactivate(m_screenSaverController, m_enabled); +} + +bool +OSXScreenSaver::isActive() const +{ + return (screenSaverUtilIsActive(m_screenSaverController) != 0); +} + +void +OSXScreenSaver::processLaunched(ProcessSerialNumber psn) +{ + if (testProcessName("ScreenSaverEngine", psn)) { + m_screenSaverPSN = psn; + LOG((CLOG_DEBUG1 "ScreenSaverEngine launched. Enabled=%d", m_enabled)); + if (m_enabled) { + m_events->addEvent( + Event(m_events->forIPrimaryScreen().screensaverActivated(), + m_eventTarget)); + } + } +} + +void +OSXScreenSaver::processTerminated(ProcessSerialNumber psn) +{ + if (m_screenSaverPSN.highLongOfPSN == psn.highLongOfPSN && + m_screenSaverPSN.lowLongOfPSN == psn.lowLongOfPSN) { + LOG((CLOG_DEBUG1 "ScreenSaverEngine terminated. Enabled=%d", m_enabled)); + if (m_enabled) { + m_events->addEvent( + Event(m_events->forIPrimaryScreen().screensaverDeactivated(), + m_eventTarget)); + } + + m_screenSaverPSN.highLongOfPSN = 0; + m_screenSaverPSN.lowLongOfPSN = 0; + } +} + +pascal OSStatus +OSXScreenSaver::launchTerminationCallback( + EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData) +{ + OSStatus result; + ProcessSerialNumber psn; + EventParamType actualType; + ByteCount actualSize; + + result = GetEventParameter(theEvent, kEventParamProcessID, + typeProcessSerialNumber, &actualType, + sizeof(psn), &actualSize, &psn); + + if ((result == noErr) && + (actualSize > 0) && + (actualType == typeProcessSerialNumber)) { + OSXScreenSaver* screenSaver = (OSXScreenSaver*)userData; + UInt32 eventKind = GetEventKind(theEvent); + if (eventKind == kEventAppLaunched) { + screenSaver->processLaunched(psn); + } + else if (eventKind == kEventAppTerminated) { + screenSaver->processTerminated(psn); + } + } + return (CallNextEventHandler(nextHandler, theEvent)); +} + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +void +getProcessSerialNumber(const char* name, ProcessSerialNumber& psn) +{ + ProcessInfoRec procInfo; + Str31 procName; // pascal string. first byte holds length. + memset(&procInfo, 0, sizeof(procInfo)); + procInfo.processName = procName; + procInfo.processInfoLength = sizeof(ProcessInfoRec); + + ProcessSerialNumber checkPsn; + OSErr err = GetNextProcess(&checkPsn); + while (err == 0) { + memset(procName, 0, sizeof(procName)); + err = GetProcessInformation(&checkPsn, &procInfo); + if (err != 0) { + break; + } + if (strcmp(name, (const char*)&procName[1]) == 0) { + psn = checkPsn; + break; + } + err = GetNextProcess(&checkPsn); + } +} + +bool +testProcessName(const char* name, const ProcessSerialNumber& psn) +{ + CFStringRef processName; + OSStatus err = CopyProcessName(&psn, &processName); + return (err == 0 && CFEqual(CFSTR("ScreenSaverEngine"), processName)); +} + +#pragma GCC diagnostic error "-Wdeprecated-declarations" diff --git a/src/lib/platform/OSXScreenSaver.h b/src/lib/platform/OSXScreenSaver.h new file mode 100644 index 0000000..07f2a7b --- /dev/null +++ b/src/lib/platform/OSXScreenSaver.h @@ -0,0 +1,59 @@ +/* + * 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 "barrier/IScreenSaver.h" + +#include <Carbon/Carbon.h> + +class IEventQueue; + +//! OSX screen saver implementation +class OSXScreenSaver : public IScreenSaver { +public: + OSXScreenSaver(IEventQueue* events, void* eventTarget); + virtual ~OSXScreenSaver(); + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + void processLaunched(ProcessSerialNumber psn); + void processTerminated(ProcessSerialNumber psn); + + static pascal OSStatus + launchTerminationCallback( + EventHandlerCallRef nextHandler, + EventRef theEvent, void* userData); + +private: + // the target for the events we generate + void* m_eventTarget; + + bool m_enabled; + void* m_screenSaverController; + void* m_autoReleasePool; + EventHandlerRef m_launchTerminationEventHandlerRef; + ProcessSerialNumber m_screenSaverPSN; + IEventQueue* m_events; +}; diff --git a/src/lib/platform/OSXScreenSaverControl.h b/src/lib/platform/OSXScreenSaverControl.h new file mode 100644 index 0000000..76f8875 --- /dev/null +++ b/src/lib/platform/OSXScreenSaverControl.h @@ -0,0 +1,54 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2009 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/>. + */ + +// ScreenSaver.framework private API +// Class dumping by Alex Harper http://www.ragingmenace.com/ + +#import <Foundation/NSObject.h> + +@protocol ScreenSaverControl +- (double)screenSaverTimeRemaining; +- (void)restartForUser:fp12; +- (void)screenSaverStopNow; +- (void)screenSaverStartNow; +- (void)setScreenSaverCanRun:(char)fp12; +- (BOOL)screenSaverCanRun; +- (BOOL)screenSaverIsRunning; +@end + + +@interface ScreenSaverController:NSObject <ScreenSaverControl> + ++ controller; ++ monitor; ++ daemonConnectionName; ++ daemonPath; ++ enginePath; +- init; +- (void)dealloc; +- (void)_connectionClosed:fp12; +- (BOOL)screenSaverIsRunning; +- (BOOL)screenSaverCanRun; +- (void)setScreenSaverCanRun:(char)fp12; +- (void)screenSaverStartNow; +- (void)screenSaverStopNow; +- (void)restartForUser:fp12; +- (double)screenSaverTimeRemaining; + +@end + diff --git a/src/lib/platform/OSXScreenSaverUtil.h b/src/lib/platform/OSXScreenSaverUtil.h new file mode 100644 index 0000000..045553d --- /dev/null +++ b/src/lib/platform/OSXScreenSaverUtil.h @@ -0,0 +1,40 @@ +/* + * 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" + +#if defined(__cplusplus) +extern "C" { +#endif + +void* screenSaverUtilCreatePool(); +void screenSaverUtilReleasePool(void*); + +void* screenSaverUtilCreateController(); +void screenSaverUtilReleaseController(void*); +void screenSaverUtilEnable(void*); +void screenSaverUtilDisable(void*); +void screenSaverUtilActivate(void*); +void screenSaverUtilDeactivate(void*, int isEnabled); +int screenSaverUtilIsActive(void*); + +#if defined(__cplusplus) +} +#endif diff --git a/src/lib/platform/OSXScreenSaverUtil.m b/src/lib/platform/OSXScreenSaverUtil.m new file mode 100644 index 0000000..6d82f10 --- /dev/null +++ b/src/lib/platform/OSXScreenSaverUtil.m @@ -0,0 +1,83 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2004 Chris Schoeneman, Nick Bolton, Sorin Sbarnea + * + * 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. + */ + +#import "platform/OSXScreenSaverUtil.h" + +#import "platform/OSXScreenSaverControl.h" + +#import <Foundation/NSAutoreleasePool.h> + +// +// screenSaverUtil functions +// +// Note: these helper functions exist only so we can avoid using ObjC++. +// autoconf/automake don't know about ObjC++ and I don't know how to +// teach them about it. +// + +void* +screenSaverUtilCreatePool() +{ + return [[NSAutoreleasePool alloc] init]; +} + +void +screenSaverUtilReleasePool(void* pool) +{ + [(NSAutoreleasePool*)pool release]; +} + +void* +screenSaverUtilCreateController() +{ + return [[ScreenSaverController controller] retain]; +} + +void +screenSaverUtilReleaseController(void* controller) +{ + [(ScreenSaverController*)controller release]; +} + +void +screenSaverUtilEnable(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:YES]; +} + +void +screenSaverUtilDisable(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:NO]; +} + +void +screenSaverUtilActivate(void* controller) +{ + [(ScreenSaverController*)controller setScreenSaverCanRun:YES]; + [(ScreenSaverController*)controller screenSaverStartNow]; +} + +void +screenSaverUtilDeactivate(void* controller, int isEnabled) +{ + [(ScreenSaverController*)controller screenSaverStopNow]; + [(ScreenSaverController*)controller setScreenSaverCanRun:isEnabled]; +} + +int +screenSaverUtilIsActive(void* controller) +{ + return [(ScreenSaverController*)controller screenSaverIsRunning]; +} diff --git a/src/lib/platform/OSXUchrKeyResource.cpp b/src/lib/platform/OSXUchrKeyResource.cpp new file mode 100644 index 0000000..e0230e9 --- /dev/null +++ b/src/lib/platform/OSXUchrKeyResource.cpp @@ -0,0 +1,296 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 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 "platform/OSXUchrKeyResource.h" + +#include <Carbon/Carbon.h> + +// +// OSXUchrKeyResource +// + +OSXUchrKeyResource::OSXUchrKeyResource(const void* resource, + UInt32 keyboardType) : + m_m(NULL), + m_cti(NULL), + m_sdi(NULL), + m_sri(NULL), + m_st(NULL) +{ + m_resource = static_cast<const UCKeyboardLayout*>(resource); + if (m_resource == NULL) { + return; + } + + // find the keyboard info for the current keyboard type + const UCKeyboardTypeHeader* th = NULL; + const UCKeyboardLayout* r = m_resource; + for (ItemCount i = 0; i < r->keyboardTypeCount; ++i) { + if (keyboardType >= r->keyboardTypeList[i].keyboardTypeFirst && + keyboardType <= r->keyboardTypeList[i].keyboardTypeLast) { + th = r->keyboardTypeList + i; + break; + } + if (r->keyboardTypeList[i].keyboardTypeFirst == 0) { + // found the default. use it unless we find a match. + th = r->keyboardTypeList + i; + } + } + if (th == NULL) { + // cannot find a suitable keyboard type + return; + } + + // get tables for keyboard type + const UInt8* const base = reinterpret_cast<const UInt8*>(m_resource); + m_m = reinterpret_cast<const UCKeyModifiersToTableNum*>(base + + th->keyModifiersToTableNumOffset); + m_cti = reinterpret_cast<const UCKeyToCharTableIndex*>(base + + th->keyToCharTableIndexOffset); + m_sdi = reinterpret_cast<const UCKeySequenceDataIndex*>(base + + th->keySequenceDataIndexOffset); + if (th->keyStateRecordsIndexOffset != 0) { + m_sri = reinterpret_cast<const UCKeyStateRecordsIndex*>(base + + th->keyStateRecordsIndexOffset); + } + if (th->keyStateTerminatorsOffset != 0) { + m_st = reinterpret_cast<const UCKeyStateTerminators*>(base + + th->keyStateTerminatorsOffset); + } + + // find the space key, but only if it can combine with dead keys. + // a dead key followed by a space yields the non-dead version of + // the dead key. + m_spaceOutput = 0xffffu; + UInt32 table = getTableForModifier(0); + for (UInt32 button = 0, n = getNumButtons(); button < n; ++button) { + KeyID id = getKey(table, button); + if (id == 0x20) { + UCKeyOutput c = + reinterpret_cast<const UCKeyOutput*>(base + + m_cti->keyToCharTableOffsets[table])[button]; + if ((c & kUCKeyOutputTestForIndexMask) == + kUCKeyOutputStateIndexMask) { + m_spaceOutput = (c & kUCKeyOutputGetIndexMask); + break; + } + } + } +} + +bool +OSXUchrKeyResource::isValid() const +{ + return (m_m != NULL); +} + +UInt32 +OSXUchrKeyResource::getNumModifierCombinations() const +{ + // only 32 (not 256) because the righthanded modifier bits are ignored + return 32; +} + +UInt32 +OSXUchrKeyResource::getNumTables() const +{ + return m_cti->keyToCharTableCount; +} + +UInt32 +OSXUchrKeyResource::getNumButtons() const +{ + return m_cti->keyToCharTableSize; +} + +UInt32 +OSXUchrKeyResource::getTableForModifier(UInt32 mask) const +{ + if (mask >= m_m->modifiersCount) { + return m_m->defaultTableNum; + } + else { + return m_m->tableNum[mask]; + } +} + +KeyID +OSXUchrKeyResource::getKey(UInt32 table, UInt32 button) const +{ + assert(table < getNumTables()); + assert(button < getNumButtons()); + + const UInt8* const base = reinterpret_cast<const UInt8*>(m_resource); + const UCKeyOutput* cPtr = reinterpret_cast<const UCKeyOutput*>(base + + m_cti->keyToCharTableOffsets[table]); + + const UCKeyOutput c = cPtr[button]; + + KeySequence keys; + switch (c & kUCKeyOutputTestForIndexMask) { + case kUCKeyOutputStateIndexMask: + if (!getDeadKey(keys, c & kUCKeyOutputGetIndexMask)) { + return kKeyNone; + } + break; + + case kUCKeyOutputSequenceIndexMask: + default: + if (!addSequence(keys, c)) { + return kKeyNone; + } + break; + } + + // XXX -- no support for multiple characters + if (keys.size() != 1) { + return kKeyNone; + } + + return keys.front(); +} + +bool +OSXUchrKeyResource::getDeadKey( + KeySequence& keys, UInt16 index) const +{ + if (m_sri == NULL || index >= m_sri->keyStateRecordCount) { + // XXX -- should we be using some other fallback? + return false; + } + + UInt16 state = 0; + if (!getKeyRecord(keys, index, state)) { + return false; + } + if (state == 0) { + // not a dead key + return true; + } + + // no dead keys if we couldn't find the space key + if (m_spaceOutput == 0xffffu) { + return false; + } + + // the dead key should not have put anything in the key list + if (!keys.empty()) { + return false; + } + + // get the character generated by pressing the space key after the + // dead key. if we're still in a compose state afterwards then we're + // confused so we bail. + if (!getKeyRecord(keys, m_spaceOutput, state) || state != 0) { + return false; + } + + // convert keys to their dead counterparts + for (KeySequence::iterator i = keys.begin(); i != keys.end(); ++i) { + *i = barrier::KeyMap::getDeadKey(*i); + } + + return true; +} + +bool +OSXUchrKeyResource::getKeyRecord( + KeySequence& keys, UInt16 index, UInt16& state) const +{ + const UInt8* const base = reinterpret_cast<const UInt8*>(m_resource); + const UCKeyStateRecord* sr = + reinterpret_cast<const UCKeyStateRecord*>(base + + m_sri->keyStateRecordOffsets[index]); + const UCKeyStateEntryTerminal* kset = + reinterpret_cast<const UCKeyStateEntryTerminal*>(sr->stateEntryData); + + UInt16 nextState = 0; + bool found = false; + if (state == 0) { + found = true; + nextState = sr->stateZeroNextState; + if (!addSequence(keys, sr->stateZeroCharData)) { + return false; + } + } + else { + // we have a next entry + switch (sr->stateEntryFormat) { + case kUCKeyStateEntryTerminalFormat: + for (UInt16 j = 0; j < sr->stateEntryCount; ++j) { + if (kset[j].curState == state) { + if (!addSequence(keys, kset[j].charData)) { + return false; + } + nextState = 0; + found = true; + break; + } + } + break; + + case kUCKeyStateEntryRangeFormat: + // XXX -- not supported yet + break; + + default: + // XXX -- unknown format + return false; + } + } + if (!found) { + // use a terminator + if (m_st != NULL && state < m_st->keyStateTerminatorCount) { + if (!addSequence(keys, m_st->keyStateTerminators[state - 1])) { + return false; + } + } + nextState = sr->stateZeroNextState; + if (!addSequence(keys, sr->stateZeroCharData)) { + return false; + } + } + + // next + state = nextState; + + return true; +} + +bool +OSXUchrKeyResource::addSequence( + KeySequence& keys, UCKeyCharSeq c) const +{ + if ((c & kUCKeyOutputTestForIndexMask) == kUCKeyOutputSequenceIndexMask) { + UInt16 index = (c & kUCKeyOutputGetIndexMask); + if (index < m_sdi->charSequenceCount && + m_sdi->charSequenceOffsets[index] != + m_sdi->charSequenceOffsets[index + 1]) { + // XXX -- sequences not supported yet + return false; + } + } + + if (c != 0xfffe && c != 0xffff) { + KeyID id = unicharToKeyID(c); + if (id != kKeyNone) { + keys.push_back(id); + } + } + + return true; +} diff --git a/src/lib/platform/OSXUchrKeyResource.h b/src/lib/platform/OSXUchrKeyResource.h new file mode 100644 index 0000000..47b63c9 --- /dev/null +++ b/src/lib/platform/OSXUchrKeyResource.h @@ -0,0 +1,55 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 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 + +#include "barrier/KeyState.h" +#include "platform/IOSXKeyResource.h" + +#include <Carbon/Carbon.h> + +typedef TISInputSourceRef KeyLayout; + +class OSXUchrKeyResource : public IOSXKeyResource { +public: + OSXUchrKeyResource(const void*, UInt32 keyboardType); + + // KeyResource overrides + virtual bool isValid() const; + virtual UInt32 getNumModifierCombinations() const; + virtual UInt32 getNumTables() const; + virtual UInt32 getNumButtons() const; + virtual UInt32 getTableForModifier(UInt32 mask) const; + virtual KeyID getKey(UInt32 table, UInt32 button) const; + +private: + typedef std::vector<KeyID> KeySequence; + + bool getDeadKey(KeySequence& keys, UInt16 index) const; + bool getKeyRecord(KeySequence& keys, + UInt16 index, UInt16& state) const; + bool addSequence(KeySequence& keys, UCKeyCharSeq c) const; + +private: + const UCKeyboardLayout* m_resource; + const UCKeyModifiersToTableNum* m_m; + const UCKeyToCharTableIndex* m_cti; + const UCKeySequenceDataIndex* m_sdi; + const UCKeyStateRecordsIndex* m_sri; + const UCKeyStateTerminators* m_st; + UInt16 m_spaceOutput; +}; diff --git a/src/lib/platform/XWindowsClipboard.cpp b/src/lib/platform/XWindowsClipboard.cpp new file mode 100644 index 0000000..8e7d864 --- /dev/null +++ b/src/lib/platform/XWindowsClipboard.cpp @@ -0,0 +1,1525 @@ +/* + * 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 "platform/XWindowsClipboard.h" + +#include "platform/XWindowsClipboardTextConverter.h" +#include "platform/XWindowsClipboardUCS2Converter.h" +#include "platform/XWindowsClipboardUTF8Converter.h" +#include "platform/XWindowsClipboardHTMLConverter.h" +#include "platform/XWindowsClipboardBMPConverter.h" +#include "platform/XWindowsUtil.h" +#include "mt/Thread.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/Stopwatch.h" +#include "common/stdvector.h" + +#include <cstdio> +#include <cstring> +#include <X11/Xatom.h> + +// +// XWindowsClipboard +// + +XWindowsClipboard::XWindowsClipboard(Display* display, + Window window, ClipboardID id) : + m_display(display), + m_window(window), + m_id(id), + m_open(false), + m_time(0), + m_owner(false), + m_timeOwned(0), + m_timeLost(0) +{ + // get some atoms + m_atomTargets = XInternAtom(m_display, "TARGETS", False); + m_atomMultiple = XInternAtom(m_display, "MULTIPLE", False); + m_atomTimestamp = XInternAtom(m_display, "TIMESTAMP", False); + m_atomInteger = XInternAtom(m_display, "INTEGER", False); + m_atomAtom = XInternAtom(m_display, "ATOM", False); + m_atomAtomPair = XInternAtom(m_display, "ATOM_PAIR", False); + m_atomData = XInternAtom(m_display, "CLIP_TEMPORARY", False); + m_atomINCR = XInternAtom(m_display, "INCR", False); + m_atomMotifClipLock = XInternAtom(m_display, "_MOTIF_CLIP_LOCK", False); + m_atomMotifClipHeader = XInternAtom(m_display, "_MOTIF_CLIP_HEADER", False); + m_atomMotifClipAccess = XInternAtom(m_display, + "_MOTIF_CLIP_LOCK_ACCESS_VALID", False); + m_atomGDKSelection = XInternAtom(m_display, "GDK_SELECTION", False); + + // set selection atom based on clipboard id + switch (id) { + case kClipboardClipboard: + m_selection = XInternAtom(m_display, "CLIPBOARD", False); + break; + + case kClipboardSelection: + default: + m_selection = XA_PRIMARY; + break; + } + + // add converters, most desired first + m_converters.push_back(new XWindowsClipboardHTMLConverter(m_display, + "text/html")); + m_converters.push_back(new XWindowsClipboardBMPConverter(m_display)); + m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display, + "text/plain;charset=UTF-8")); + m_converters.push_back(new XWindowsClipboardUTF8Converter(m_display, + "UTF8_STRING")); + m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display, + "text/plain;charset=ISO-10646-UCS-2")); + m_converters.push_back(new XWindowsClipboardUCS2Converter(m_display, + "text/unicode")); + m_converters.push_back(new XWindowsClipboardTextConverter(m_display, + "text/plain")); + m_converters.push_back(new XWindowsClipboardTextConverter(m_display, + "STRING")); + + // we have no data + clearCache(); +} + +XWindowsClipboard::~XWindowsClipboard() +{ + clearReplies(); + clearConverters(); +} + +void +XWindowsClipboard::lost(Time time) +{ + LOG((CLOG_DEBUG "lost clipboard %d ownership at %d", m_id, time)); + if (m_owner) { + m_owner = false; + m_timeLost = time; + clearCache(); + } +} + +void +XWindowsClipboard::addRequest(Window owner, Window requestor, + Atom target, ::Time time, Atom property) +{ + // must be for our window and we must have owned the selection + // at the given time. + bool success = false; + if (owner == m_window) { + LOG((CLOG_DEBUG1 "request for clipboard %d, target %s by 0x%08x (property=%s)", m_selection, XWindowsUtil::atomToString(m_display, target).c_str(), requestor, XWindowsUtil::atomToString(m_display, property).c_str())); + if (wasOwnedAtTime(time)) { + if (target == m_atomMultiple) { + // add a multiple request. property may not be None + // according to ICCCM. + if (property != None) { + success = insertMultipleReply(requestor, time, property); + } + } + else { + addSimpleRequest(requestor, target, time, property); + + // addSimpleRequest() will have already handled failure + success = true; + } + } + else { + LOG((CLOG_DEBUG1 "failed, not owned at time %d", time)); + } + } + + if (!success) { + // send failure + LOG((CLOG_DEBUG1 "failed")); + insertReply(new Reply(requestor, target, time)); + } + + // send notifications that are pending + pushReplies(); +} + +bool +XWindowsClipboard::addSimpleRequest(Window requestor, + Atom target, ::Time time, Atom property) +{ + // obsolete requestors may supply a None property. in + // that case we use the target as the property to store + // the conversion. + if (property == None) { + property = target; + } + + // handle targets + String data; + Atom type = None; + int format = 0; + if (target == m_atomTargets) { + type = getTargetsData(data, &format); + } + else if (target == m_atomTimestamp) { + type = getTimestampData(data, &format); + } + else { + IXWindowsClipboardConverter* converter = getConverter(target); + if (converter != NULL) { + IClipboard::EFormat clipboardFormat = converter->getFormat(); + if (m_added[clipboardFormat]) { + try { + data = converter->fromIClipboard(m_data[clipboardFormat]); + format = converter->getDataSize(); + type = converter->getAtom(); + } + catch (...) { + // ignore -- cannot convert + } + } + } + } + + if (type != None) { + // success + LOG((CLOG_DEBUG1 "success")); + insertReply(new Reply(requestor, target, time, + property, data, type, format)); + return true; + } + else { + // failure + LOG((CLOG_DEBUG1 "failed")); + insertReply(new Reply(requestor, target, time)); + return false; + } +} + +bool +XWindowsClipboard::processRequest(Window requestor, + ::Time /*time*/, Atom property) +{ + ReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + LOG((CLOG_DEBUG1 "received property %s delete from 0x08%x", XWindowsUtil::atomToString(m_display, property).c_str(), requestor)); + + // find the property in the known requests. it should be the + // first property but we'll check 'em all if we have to. + ReplyList& replies = index->second; + for (ReplyList::iterator index2 = replies.begin(); + index2 != replies.end(); ++index2) { + Reply* reply = *index2; + if (reply->m_replied && reply->m_property == property) { + // if reply is complete then remove it and start the + // next one. + pushReplies(index, replies, index2); + return true; + } + } + + return false; +} + +bool +XWindowsClipboard::destroyRequest(Window requestor) +{ + ReplyMap::iterator index = m_replies.find(requestor); + if (index == m_replies.end()) { + // unknown requestor window + return false; + } + + // destroy all replies for this window + clearReplies(index->second); + m_replies.erase(index); + + // note -- we don't stop watching the window for events because + // we're called in response to the window being destroyed. + + return true; +} + +Window +XWindowsClipboard::getWindow() const +{ + return m_window; +} + +Atom +XWindowsClipboard::getSelection() const +{ + return m_selection; +} + +bool +XWindowsClipboard::empty() +{ + assert(m_open); + + LOG((CLOG_DEBUG "empty clipboard %d", m_id)); + + // assert ownership of clipboard + XSetSelectionOwner(m_display, m_selection, m_window, m_time); + if (XGetSelectionOwner(m_display, m_selection) != m_window) { + LOG((CLOG_DEBUG "failed to grab clipboard %d", m_id)); + return false; + } + + // clear all data. since we own the data now, the cache is up + // to date. + clearCache(); + m_cached = true; + + // FIXME -- actually delete motif clipboard items? + // FIXME -- do anything to motif clipboard properties? + + // save time + m_timeOwned = m_time; + m_timeLost = 0; + + // we're the owner now + m_owner = true; + LOG((CLOG_DEBUG "grabbed clipboard %d", m_id)); + + return true; +} + +void +XWindowsClipboard::add(EFormat format, const String& data) +{ + assert(m_open); + assert(m_owner); + + LOG((CLOG_DEBUG "add %d bytes to clipboard %d format: %d", data.size(), m_id, format)); + + m_data[format] = data; + m_added[format] = true; + + // FIXME -- set motif clipboard item? +} + +bool +XWindowsClipboard::open(Time time) const +{ + if (m_open) { + LOG((CLOG_DEBUG "failed to open clipboard: already opened")); + return false; + } + + LOG((CLOG_DEBUG "open clipboard %d", m_id)); + + // assume not motif + m_motif = false; + + // lock clipboard + if (m_id == kClipboardClipboard) { + if (!motifLockClipboard()) { + return false; + } + + // check if motif owns the selection. unlock motif clipboard + // if it does not. + m_motif = motifOwnsClipboard(); + LOG((CLOG_DEBUG1 "motif does %sown clipboard", m_motif ? "" : "not ")); + if (!m_motif) { + motifUnlockClipboard(); + } + } + + // now open + m_open = true; + m_time = time; + + // be sure to flush the cache later if it's dirty + m_checkCache = true; + + return true; +} + +void +XWindowsClipboard::close() const +{ + assert(m_open); + + LOG((CLOG_DEBUG "close clipboard %d", m_id)); + + // unlock clipboard + if (m_motif) { + motifUnlockClipboard(); + } + + m_motif = false; + m_open = false; +} + +IClipboard::Time +XWindowsClipboard::getTime() const +{ + checkCache(); + return m_timeOwned; +} + +bool +XWindowsClipboard::has(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_added[format]; +} + +String +XWindowsClipboard::get(EFormat format) const +{ + assert(m_open); + + fillCache(); + return m_data[format]; +} + +void +XWindowsClipboard::clearConverters() +{ + for (ConverterList::iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + delete *index; + } + m_converters.clear(); +} + +IXWindowsClipboardConverter* +XWindowsClipboard::getConverter(Atom target, bool onlyIfNotAdded) const +{ + IXWindowsClipboardConverter* converter = NULL; + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + converter = *index; + if (converter->getAtom() == target) { + break; + } + } + if (converter == NULL) { + LOG((CLOG_DEBUG1 " no converter for target %s", XWindowsUtil::atomToString(m_display, target).c_str())); + return NULL; + } + + // optionally skip already handled targets + if (onlyIfNotAdded) { + if (m_added[converter->getFormat()]) { + LOG((CLOG_DEBUG1 " skipping handled format %d", converter->getFormat())); + return NULL; + } + } + + return converter; +} + +void +XWindowsClipboard::checkCache() const +{ + if (!m_checkCache) { + return; + } + m_checkCache = false; + + // get the time the clipboard ownership was taken by the current + // owner. + if (m_motif) { + m_timeOwned = motifGetTime(); + } + else { + m_timeOwned = icccmGetTime(); + } + + // if we can't get the time then use the time passed to us + if (m_timeOwned == 0) { + m_timeOwned = m_time; + } + + // if the cache is dirty then flush it + if (m_timeOwned != m_cacheTime) { + clearCache(); + } +} + +void +XWindowsClipboard::clearCache() const +{ + const_cast<XWindowsClipboard*>(this)->doClearCache(); +} + +void +XWindowsClipboard::doClearCache() +{ + m_checkCache = false; + m_cached = false; + for (SInt32 index = 0; index < kNumFormats; ++index) { + m_data[index] = ""; + m_added[index] = false; + } +} + +void +XWindowsClipboard::fillCache() const +{ + // get the selection data if not already cached + checkCache(); + if (!m_cached) { + const_cast<XWindowsClipboard*>(this)->doFillCache(); + } +} + +void +XWindowsClipboard::doFillCache() +{ + if (m_motif) { + motifFillCache(); + } + else { + icccmFillCache(); + } + m_checkCache = false; + m_cached = true; + m_cacheTime = m_timeOwned; +} + +void +XWindowsClipboard::icccmFillCache() +{ + LOG((CLOG_DEBUG "ICCCM fill clipboard %d", m_id)); + + // see if we can get the list of available formats from the selection. + // if not then use a default list of formats. note that some clipboard + // owners are broken and report TARGETS as the type of the TARGETS data + // instead of the correct type ATOM; allow either. + const Atom atomTargets = m_atomTargets; + Atom target; + String data; + if (!icccmGetSelection(atomTargets, &target, &data) || + (target != m_atomAtom && target != m_atomTargets)) { + LOG((CLOG_DEBUG1 "selection doesn't support TARGETS")); + data = ""; + XWindowsUtil::appendAtomData(data, XA_STRING); + } + + XWindowsUtil::convertAtomProperty(data); + const Atom* targets = reinterpret_cast<const Atom*>(data.data()); // TODO: Safe? + const UInt32 numTargets = data.size() / sizeof(Atom); + LOG((CLOG_DEBUG " available targets: %s", XWindowsUtil::atomsToString(m_display, targets, numTargets).c_str())); + + // try each converter in order (because they're in order of + // preference). + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip already handled targets + if (m_added[converter->getFormat()]) { + continue; + } + + // see if atom is in target list + Atom target = None; + // XXX -- just ask for the converter's target to see if it's + // available rather than checking TARGETS. i've seen clipboard + // owners that don't report all the targets they support. + target = converter->getAtom(); + /* + for (UInt32 i = 0; i < numTargets; ++i) { + if (converter->getAtom() == targets[i]) { + target = targets[i]; + break; + } + } + */ + if (target == None) { + continue; + } + + // get the data + Atom actualTarget; + String targetData; + if (!icccmGetSelection(target, &actualTarget, &targetData)) { + LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str())); + continue; + } + + // add to clipboard and note we've done it + IClipboard::EFormat format = converter->getFormat(); + m_data[format] = converter->toIClipboard(targetData); + m_added[format] = true; + LOG((CLOG_DEBUG "added format %d for target %s (%u %s)", format, XWindowsUtil::atomToString(m_display, target).c_str(), targetData.size(), targetData.size() == 1 ? "byte" : "bytes")); + } +} + +bool +XWindowsClipboard::icccmGetSelection(Atom target, + Atom* actualTarget, String* data) const +{ + assert(actualTarget != NULL); + assert(data != NULL); + + // request data conversion + CICCCMGetClipboard getter(m_window, m_time, m_atomData); + if (!getter.readClipboard(m_display, m_selection, + target, actualTarget, data)) { + LOG((CLOG_DEBUG1 "can't get data for selection target %s", XWindowsUtil::atomToString(m_display, target).c_str())); + LOGC(getter.m_error, (CLOG_WARN "ICCCM violation by clipboard owner")); + return false; + } + else if (*actualTarget == None) { + LOG((CLOG_DEBUG1 "selection conversion failed for target %s", XWindowsUtil::atomToString(m_display, target).c_str())); + return false; + } + return true; +} + +IClipboard::Time +XWindowsClipboard::icccmGetTime() const +{ + Atom actualTarget; + String data; + if (icccmGetSelection(m_atomTimestamp, &actualTarget, &data) && + actualTarget == m_atomInteger) { + Time time = *reinterpret_cast<const Time*>(data.data()); + LOG((CLOG_DEBUG1 "got ICCCM time %d", time)); + return time; + } + else { + // no timestamp + LOG((CLOG_DEBUG1 "can't get ICCCM time")); + return 0; + } +} + +bool +XWindowsClipboard::motifLockClipboard() const +{ + // fail if anybody owns the lock (even us, so this is non-recursive) + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != None) { + LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner)); + return false; + } + + // try to grab the lock + // FIXME -- is this right? there's a race condition here -- + // A grabs successfully, B grabs successfully, A thinks it + // still has the grab until it gets a SelectionClear. + Time time = XWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, m_window, time); + lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + LOG((CLOG_DEBUG1 "motif lock owner 0x%08x", lockOwner)); + return false; + } + + LOG((CLOG_DEBUG1 "locked motif clipboard")); + return true; +} + +void +XWindowsClipboard::motifUnlockClipboard() const +{ + LOG((CLOG_DEBUG1 "unlocked motif clipboard")); + + // fail if we don't own the lock + Window lockOwner = XGetSelectionOwner(m_display, m_atomMotifClipLock); + if (lockOwner != m_window) { + return; + } + + // release lock + Time time = XWindowsUtil::getCurrentTime(m_display, m_window); + XSetSelectionOwner(m_display, m_atomMotifClipLock, None, time); +} + +bool +XWindowsClipboard::motifOwnsClipboard() const +{ + // get the current selection owner + // FIXME -- this can't be right. even if the window is destroyed + // Motif will still have a valid clipboard. how can we tell if + // some other client owns CLIPBOARD? + Window owner = XGetSelectionOwner(m_display, m_selection); + if (owner == None) { + return false; + } + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + String data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!XWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return false; + } + + // check the owner window against the current clipboard owner + if (data.size() >= sizeof(MotifClipHeader)) { + MotifClipHeader header; + std::memcpy (&header, data.data(), sizeof(header)); + if ((header.m_id == kMotifClipHeader) && + (static_cast<Window>(header.m_selectionOwner) == owner)) { + return true; + } + } + + return false; +} + +void +XWindowsClipboard::motifFillCache() +{ + LOG((CLOG_DEBUG "Motif fill clipboard %d", m_id)); + + // get the Motif clipboard header property from the root window + Atom target; + SInt32 format; + String data; + Window root = RootWindow(m_display, DefaultScreen(m_display)); + if (!XWindowsUtil::getWindowProperty(m_display, root, + m_atomMotifClipHeader, + &data, &target, &format, False)) { + return; + } + + MotifClipHeader header; + if (data.size() < sizeof(header)) { // check that the header is okay + return; + } + std::memcpy (&header, data.data(), sizeof(header)); + if (header.m_id != kMotifClipHeader || header.m_numItems < 1) { + return; + } + + // get the Motif item property from the root window + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", header.m_item); + Atom atomItem = XInternAtom(m_display, name, False); + data = ""; + if (!XWindowsUtil::getWindowProperty(m_display, root, + atomItem, &data, + &target, &format, False)) { + return; + } + + MotifClipItem item; + if (data.size() < sizeof(item)) { // check that the item is okay + return; + } + std::memcpy (&item, data.data(), sizeof(item)); + if (item.m_id != kMotifClipItem || + item.m_numFormats - item.m_numDeletedFormats < 1) { + return; + } + + // format list is after static item structure elements + const SInt32 numFormats = item.m_numFormats - item.m_numDeletedFormats; + const SInt32* formats = reinterpret_cast<const SInt32*>(item.m_size + + static_cast<const char*>(data.data())); + + // get the available formats + typedef std::map<Atom, String> MotifFormatMap; + MotifFormatMap motifFormats; + for (SInt32 i = 0; i < numFormats; ++i) { + // get Motif format property from the root window + sprintf(name, "_MOTIF_CLIP_ITEM_%d", formats[i]); + Atom atomFormat = XInternAtom(m_display, name, False); + String data; + if (!XWindowsUtil::getWindowProperty(m_display, root, + atomFormat, &data, + &target, &format, False)) { + continue; + } + + // check that the format is okay + MotifClipFormat motifFormat; + if (data.size() < sizeof(motifFormat)) { + continue; + } + std::memcpy (&motifFormat, data.data(), sizeof(motifFormat)); + if (motifFormat.m_id != kMotifClipFormat || + motifFormat.m_length < 0 || + motifFormat.m_type == None || + motifFormat.m_deleted != 0) { + continue; + } + + // save it + motifFormats.insert(std::make_pair(motifFormat.m_type, data)); + } + //const UInt32 numMotifFormats = motifFormats.size(); + + // try each converter in order (because they're in order of + // preference). + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip already handled targets + if (m_added[converter->getFormat()]) { + continue; + } + + // see if atom is in target list + MotifFormatMap::const_iterator index2 = + motifFormats.find(converter->getAtom()); + if (index2 == motifFormats.end()) { + continue; + } + + // get format + MotifClipFormat motifFormat; + std::memcpy (&motifFormat, index2->second.data(), sizeof(motifFormat)); + const Atom target = motifFormat.m_type; + + // get the data (finally) + Atom actualTarget; + String targetData; + if (!motifGetSelection(&motifFormat, &actualTarget, &targetData)) { + LOG((CLOG_DEBUG1 " no data for target %s", XWindowsUtil::atomToString(m_display, target).c_str())); + continue; + } + + // add to clipboard and note we've done it + IClipboard::EFormat format = converter->getFormat(); + m_data[format] = converter->toIClipboard(targetData); + m_added[format] = true; + LOG((CLOG_DEBUG "added format %d for target %s", format, XWindowsUtil::atomToString(m_display, target).c_str())); + } +} + +bool +XWindowsClipboard::motifGetSelection(const MotifClipFormat* format, + Atom* actualTarget, String* data) const +{ + // if the current clipboard owner and the owner indicated by the + // motif clip header are the same then transfer via a property on + // the root window, otherwise transfer as a normal ICCCM client. + if (!motifOwnsClipboard()) { + return icccmGetSelection(format->m_type, actualTarget, data); + } + + // use motif way + // FIXME -- this isn't right. it'll only work if the data is + // already stored on the root window and only if it fits in a + // property. motif has some scheme for transferring part by + // part that i don't know. + char name[18 + 20]; + sprintf(name, "_MOTIF_CLIP_ITEM_%d", format->m_data); + Atom target = XInternAtom(m_display, name, False); + Window root = RootWindow(m_display, DefaultScreen(m_display)); + return XWindowsUtil::getWindowProperty(m_display, root, + target, data, + actualTarget, NULL, False); +} + +IClipboard::Time +XWindowsClipboard::motifGetTime() const +{ + return icccmGetTime(); +} + +bool +XWindowsClipboard::insertMultipleReply(Window requestor, + ::Time time, Atom property) +{ + // get the requested targets + Atom target; + SInt32 format; + String data; + if (!XWindowsUtil::getWindowProperty(m_display, requestor, + property, &data, &target, &format, False)) { + // can't get the requested targets + return false; + } + + // fail if the requested targets isn't of the correct form + if (format != 32 || target != m_atomAtomPair) { + return false; + } + + // data is a list of atom pairs: target, property + XWindowsUtil::convertAtomProperty(data); + const Atom* targets = reinterpret_cast<const Atom*>(data.data()); + const UInt32 numTargets = data.size() / sizeof(Atom); + + // add replies for each target + bool changed = false; + for (UInt32 i = 0; i < numTargets; i += 2) { + const Atom target = targets[i + 0]; + const Atom property = targets[i + 1]; + if (!addSimpleRequest(requestor, target, time, property)) { + // note that we can't perform the requested conversion + XWindowsUtil::replaceAtomData(data, i, None); + changed = true; + } + } + + // update the targets property if we changed it + if (changed) { + XWindowsUtil::setWindowProperty(m_display, requestor, + property, data.data(), data.size(), + target, format); + } + + // add reply for MULTIPLE request + insertReply(new Reply(requestor, m_atomMultiple, + time, property, String(), None, 32)); + + return true; +} + +void +XWindowsClipboard::insertReply(Reply* reply) +{ + assert(reply != NULL); + + // note -- we must respond to requests in order if requestor,target,time + // are the same, otherwise we can use whatever order we like with one + // exception: each reply in a MULTIPLE reply must be handled in order + // as well. those replies will almost certainly not share targets so + // we can't simply use requestor,target,time as map index. + // + // instead we'll use just the requestor. that's more restrictive than + // necessary but we're guaranteed to do things in the right order. + // note that we could also include the time in the map index and still + // ensure the right order. but since that'll just make it harder to + // find the right reply when handling property notify events we stick + // to just the requestor. + + const bool newWindow = (m_replies.count(reply->m_requestor) == 0); + m_replies[reply->m_requestor].push_back(reply); + + // adjust requestor's event mask if we haven't done so already. we + // want events in case the window is destroyed or any of its + // properties change. + if (newWindow) { + // note errors while we adjust event masks + bool error = false; + { + XWindowsUtil::ErrorLock lock(m_display, &error); + + // get and save the current event mask + XWindowAttributes attr; + XGetWindowAttributes(m_display, reply->m_requestor, &attr); + m_eventMasks[reply->m_requestor] = attr.your_event_mask; + + // add the events we want + XSelectInput(m_display, reply->m_requestor, attr.your_event_mask | + StructureNotifyMask | PropertyChangeMask); + } + + // if we failed then the window has already been destroyed + if (error) { + m_replies.erase(reply->m_requestor); + delete reply; + } + } +} + +void +XWindowsClipboard::pushReplies() +{ + // send the first reply for each window if that reply hasn't + // been sent yet. + for (ReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ) { + assert(!index->second.empty()); + ReplyList::iterator listit = index->second.begin(); + while (listit != index->second.end()) { + if (!(*listit)->m_replied) + break; + ++listit; + } + if (listit != index->second.end() && !(*listit)->m_replied) { + pushReplies(index, index->second, listit); + } + else { + ++index; + } + } +} + +void +XWindowsClipboard::pushReplies(ReplyMap::iterator& mapIndex, + ReplyList& replies, ReplyList::iterator index) +{ + Reply* reply = *index; + while (sendReply(reply)) { + // reply is complete. discard it and send the next reply, + // if any. + index = replies.erase(index); + delete reply; + if (index == replies.end()) { + break; + } + reply = *index; + } + + // if there are no more replies in the list then remove the list + // and stop watching the requestor for events. + if (replies.empty()) { + XWindowsUtil::ErrorLock lock(m_display); + Window requestor = mapIndex->first; + XSelectInput(m_display, requestor, m_eventMasks[requestor]); + m_replies.erase(mapIndex++); + m_eventMasks.erase(requestor); + } + else { + ++mapIndex; + } +} + +bool +XWindowsClipboard::sendReply(Reply* reply) +{ + assert(reply != NULL); + + // bail out immediately if reply is done + if (reply->m_done) { + LOG((CLOG_DEBUG1 "clipboard: finished reply to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + return true; + } + + // start in failed state if property is None + bool failed = (reply->m_property == None); + if (!failed) { + LOG((CLOG_DEBUG1 "clipboard: setting property on 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + + // send using INCR if already sending incrementally or if reply + // is too large, otherwise just send it. + const UInt32 maxRequestSize = 3 * XMaxRequestSize(m_display); + const bool useINCR = (reply->m_data.size() > maxRequestSize); + + // send INCR reply if incremental and we haven't replied yet + if (useINCR && !reply->m_replied) { + UInt32 size = reply->m_data.size(); + if (!XWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &size, 4, m_atomINCR, 32)) { + failed = true; + } + } + + // send more INCR reply or entire non-incremental reply + else { + // how much more data should we send? + UInt32 size = reply->m_data.size() - reply->m_ptr; + if (size > maxRequestSize) + size = maxRequestSize; + + // send it + if (!XWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + reply->m_data.data() + reply->m_ptr, + size, + reply->m_type, reply->m_format)) { + failed = true; + } + else { + reply->m_ptr += size; + + // we've finished the reply if we just sent the zero + // size incremental chunk or if we're not incremental. + reply->m_done = (size == 0 || !useINCR); + } + } + } + + // if we've failed then delete the property and say we're done. + // if we haven't replied yet then we can send a failure notify, + // otherwise we've failed in the middle of an incremental + // transfer; i don't know how to cancel that so i'll just send + // the final zero-length property. + // FIXME -- how do you gracefully cancel an incremental transfer? + if (failed) { + LOG((CLOG_DEBUG1 "clipboard: sending failure to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_done = true; + if (reply->m_property != None) { + XWindowsUtil::ErrorLock lock(m_display); + XDeleteProperty(m_display, reply->m_requestor, reply->m_property); + } + + if (!reply->m_replied) { + sendNotify(reply->m_requestor, m_selection, + reply->m_target, None, + reply->m_time); + + // don't wait for any reply (because we're not expecting one) + return true; + } + else { + static const char dummy = 0; + XWindowsUtil::setWindowProperty(m_display, + reply->m_requestor, reply->m_property, + &dummy, + 0, + reply->m_type, reply->m_format); + + // wait for delete notify + return false; + } + } + + // send notification if we haven't yet + if (!reply->m_replied) { + LOG((CLOG_DEBUG1 "clipboard: sending notify to 0x%08x,%d,%d", reply->m_requestor, reply->m_target, reply->m_property)); + reply->m_replied = true; + + // dump every property on the requestor window to the debug2 + // log. we've seen what appears to be a bug in lesstif and + // knowing the properties may help design a workaround, if + // it becomes necessary. + if (CLOG->getFilter() >= kDEBUG2) { + XWindowsUtil::ErrorLock lock(m_display); + int n; + Atom* props = XListProperties(m_display, reply->m_requestor, &n); + LOG((CLOG_DEBUG2 "properties of 0x%08x:", reply->m_requestor)); + for (int i = 0; i < n; ++i) { + Atom target; + String data; + char* name = XGetAtomName(m_display, props[i]); + if (!XWindowsUtil::getWindowProperty(m_display, + reply->m_requestor, + props[i], &data, &target, NULL, False)) { + LOG((CLOG_DEBUG2 " %s: <can't read property>", name)); + } + else { + // if there are any non-ascii characters in string + // then print the binary data. + static const char* hex = "0123456789abcdef"; + for (String::size_type j = 0; j < data.size(); ++j) { + if (data[j] < 32 || data[j] > 126) { + String tmp; + tmp.reserve(data.size() * 3); + for (j = 0; j < data.size(); ++j) { + unsigned char v = (unsigned char)data[j]; + tmp += hex[v >> 16]; + tmp += hex[v & 15]; + tmp += ' '; + } + data = tmp; + break; + } + } + char* type = XGetAtomName(m_display, target); + LOG((CLOG_DEBUG2 " %s (%s): %s", name, type, data.c_str())); + if (type != NULL) { + XFree(type); + } + } + if (name != NULL) { + XFree(name); + } + } + if (props != NULL) { + XFree(props); + } + } + + sendNotify(reply->m_requestor, m_selection, + reply->m_target, reply->m_property, + reply->m_time); + } + + // wait for delete notify + return false; +} + +void +XWindowsClipboard::clearReplies() +{ + for (ReplyMap::iterator index = m_replies.begin(); + index != m_replies.end(); ++index) { + clearReplies(index->second); + } + m_replies.clear(); + m_eventMasks.clear(); +} + +void +XWindowsClipboard::clearReplies(ReplyList& replies) +{ + for (ReplyList::iterator index = replies.begin(); + index != replies.end(); ++index) { + delete *index; + } + replies.clear(); +} + +void +XWindowsClipboard::sendNotify(Window requestor, + Atom selection, Atom target, Atom property, Time time) +{ + XEvent event; + event.xselection.type = SelectionNotify; + event.xselection.display = m_display; + event.xselection.requestor = requestor; + event.xselection.selection = selection; + event.xselection.target = target; + event.xselection.property = property; + event.xselection.time = time; + XWindowsUtil::ErrorLock lock(m_display); + XSendEvent(m_display, requestor, False, 0, &event); +} + +bool +XWindowsClipboard::wasOwnedAtTime(::Time time) const +{ + // not owned if we've never owned the selection + checkCache(); + if (m_timeOwned == 0) { + return false; + } + + // if time is CurrentTime then return true if we still own the + // selection and false if we do not. else if we still own the + // selection then get the current time, otherwise use + // m_timeLost as the end time. + Time lost = m_timeLost; + if (m_timeLost == 0) { + if (time == CurrentTime) { + return true; + } + else { + lost = XWindowsUtil::getCurrentTime(m_display, m_window); + } + } + else { + if (time == CurrentTime) { + return false; + } + } + + // compare time to range + Time duration = lost - m_timeOwned; + Time when = time - m_timeOwned; + return (/*when >= 0 &&*/ when <= duration); +} + +Atom +XWindowsClipboard::getTargetsData(String& data, int* format) const +{ + assert(format != NULL); + + // add standard targets + XWindowsUtil::appendAtomData(data, m_atomTargets); + XWindowsUtil::appendAtomData(data, m_atomMultiple); + XWindowsUtil::appendAtomData(data, m_atomTimestamp); + + // add targets we can convert to + for (ConverterList::const_iterator index = m_converters.begin(); + index != m_converters.end(); ++index) { + IXWindowsClipboardConverter* converter = *index; + + // skip formats we don't have + if (m_added[converter->getFormat()]) { + XWindowsUtil::appendAtomData(data, converter->getAtom()); + } + } + + *format = 32; + return m_atomAtom; +} + +Atom +XWindowsClipboard::getTimestampData(String& data, int* format) const +{ + assert(format != NULL); + + checkCache(); + XWindowsUtil::appendTimeData(data, m_timeOwned); + *format = 32; + return m_atomInteger; +} + + +// +// XWindowsClipboard::CICCCMGetClipboard +// + +XWindowsClipboard::CICCCMGetClipboard::CICCCMGetClipboard( + Window requestor, Time time, Atom property) : + m_requestor(requestor), + m_time(time), + m_property(property), + m_incr(false), + m_failed(false), + m_done(false), + m_reading(false), + m_data(NULL), + m_actualTarget(NULL), + m_error(false) +{ + // do nothing +} + +XWindowsClipboard::CICCCMGetClipboard::~CICCCMGetClipboard() +{ + // do nothing +} + +bool +XWindowsClipboard::CICCCMGetClipboard::readClipboard(Display* display, + Atom selection, Atom target, Atom* actualTarget, String* data) +{ + assert(actualTarget != NULL); + assert(data != NULL); + + LOG((CLOG_DEBUG1 "request selection=%s, target=%s, window=%x", XWindowsUtil::atomToString(display, selection).c_str(), XWindowsUtil::atomToString(display, target).c_str(), m_requestor)); + + m_atomNone = XInternAtom(display, "NONE", False); + m_atomIncr = XInternAtom(display, "INCR", False); + + // save output pointers + m_actualTarget = actualTarget; + m_data = data; + + // assume failure + *m_actualTarget = None; + *m_data = ""; + + // delete target property + XDeleteProperty(display, m_requestor, m_property); + + // select window for property changes + XWindowAttributes attr; + XGetWindowAttributes(display, m_requestor, &attr); + XSelectInput(display, m_requestor, + attr.your_event_mask | PropertyChangeMask); + + // request data conversion + XConvertSelection(display, selection, target, + m_property, m_requestor, m_time); + + // synchronize with server before we start following timeout countdown + XSync(display, False); + + // Xlib inexplicably omits the ability to wait for an event with + // a timeout. (it's inexplicable because there's no portable way + // to do it.) we'll poll until we have what we're looking for or + // a timeout expires. we use a timeout so we don't get locked up + // by badly behaved selection owners. + XEvent xevent; + std::vector<XEvent> events; + Stopwatch timeout(false); // timer not stopped, not triggered + static const double s_timeout = 0.25; // FIXME -- is this too short? + bool noWait = false; + while (!m_done && !m_failed) { + // fail if timeout has expired + if (timeout.getTime() >= s_timeout) { + m_failed = true; + break; + } + + // process events if any otherwise sleep + if (noWait || XPending(display) > 0) { + while (!m_done && !m_failed && (noWait || XPending(display) > 0)) { + XNextEvent(display, &xevent); + if (!processEvent(display, &xevent)) { + // not processed so save it + events.push_back(xevent); + } + else { + // reset timer since we've made some progress + timeout.reset(); + + // don't sleep anymore, just block waiting for events. + // we're assuming here that the clipboard owner will + // complete the protocol correctly. if we continue to + // sleep we'll get very bad performance. + noWait = true; + } + } + } + else { + ARCH->sleep(0.01); + } + } + + // put unprocessed events back + for (UInt32 i = events.size(); i > 0; --i) { + XPutBackEvent(display, &events[i - 1]); + } + + // restore mask + XSelectInput(display, m_requestor, attr.your_event_mask); + + // return success or failure + LOG((CLOG_DEBUG1 "request %s after %fs", m_failed ? "failed" : "succeeded", timeout.getTime())); + return !m_failed; +} + +bool +XWindowsClipboard::CICCCMGetClipboard::processEvent( + Display* display, XEvent* xevent) +{ + // process event + switch (xevent->type) { + case DestroyNotify: + if (xevent->xdestroywindow.window == m_requestor) { + m_failed = true; + return true; + } + + // not interested + return false; + + case SelectionNotify: + if (xevent->xselection.requestor == m_requestor) { + // done if we can't convert + if (xevent->xselection.property == None || + xevent->xselection.property == m_atomNone) { + m_done = true; + return true; + } + + // proceed if conversion successful + else if (xevent->xselection.property == m_property) { + m_reading = true; + break; + } + } + + // otherwise not interested + return false; + + case PropertyNotify: + // proceed if conversion successful and we're receiving more data + if (xevent->xproperty.window == m_requestor && + xevent->xproperty.atom == m_property && + xevent->xproperty.state == PropertyNewValue) { + if (!m_reading) { + // we haven't gotten the SelectionNotify yet + return true; + } + break; + } + + // otherwise not interested + return false; + + default: + // not interested + return false; + } + + // get the data from the property + Atom target; + const String::size_type oldSize = m_data->size(); + if (!XWindowsUtil::getWindowProperty(display, m_requestor, + m_property, m_data, &target, NULL, True)) { + // unable to read property + m_failed = true; + return true; + } + + // note if incremental. if we're already incremental then the + // selection owner is busted. if the INCR property has no size + // then the selection owner is busted. + if (target == m_atomIncr) { + if (m_incr) { + m_failed = true; + m_error = true; + } + else if (m_data->size() == oldSize) { + m_failed = true; + m_error = true; + } + else { + m_incr = true; + + // discard INCR data + *m_data = ""; + } + } + + // handle incremental chunks + else if (m_incr) { + // if first incremental chunk then save target + if (oldSize == 0) { + LOG((CLOG_DEBUG1 " INCR first chunk, target %s", XWindowsUtil::atomToString(display, target).c_str())); + *m_actualTarget = target; + } + + // secondary chunks must have the same target + else { + if (target != *m_actualTarget) { + LOG((CLOG_WARN " INCR target mismatch")); + m_failed = true; + m_error = true; + } + } + + // note if this is the final chunk + if (m_data->size() == oldSize) { + LOG((CLOG_DEBUG1 " INCR final chunk: %d bytes total", m_data->size())); + m_done = true; + } + } + + // not incremental; save the target. + else { + LOG((CLOG_DEBUG1 " target %s", XWindowsUtil::atomToString(display, target).c_str())); + *m_actualTarget = target; + m_done = true; + } + + // this event has been processed + LOGC(!m_incr, (CLOG_DEBUG1 " got data, %d bytes", m_data->size())); + return true; +} + + +// +// XWindowsClipboard::Reply +// + +XWindowsClipboard::Reply::Reply(Window requestor, Atom target, ::Time time) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(None), + m_replied(false), + m_done(false), + m_data(), + m_type(None), + m_format(32), + m_ptr(0) +{ + // do nothing +} + +XWindowsClipboard::Reply::Reply(Window requestor, Atom target, ::Time time, + Atom property, const String& data, Atom type, int format) : + m_requestor(requestor), + m_target(target), + m_time(time), + m_property(property), + m_replied(false), + m_done(false), + m_data(data), + m_type(type), + m_format(format), + m_ptr(0) +{ + // do nothing +} diff --git a/src/lib/platform/XWindowsClipboard.h b/src/lib/platform/XWindowsClipboard.h new file mode 100644 index 0000000..cf20e82 --- /dev/null +++ b/src/lib/platform/XWindowsClipboard.h @@ -0,0 +1,378 @@ +/* + * 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 "barrier/clipboard_types.h" +#include "barrier/IClipboard.h" +#include "common/stdmap.h" +#include "common/stdlist.h" +#include "common/stdvector.h" + +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/Xlib.h> +#endif + +class IXWindowsClipboardConverter; + +//! X11 clipboard implementation +class XWindowsClipboard : public IClipboard { +public: + /*! + Use \c window as the window that owns or interacts with the + clipboard identified by \c id. + */ + XWindowsClipboard(Display*, Window window, ClipboardID id); + virtual ~XWindowsClipboard(); + + //! Notify clipboard was lost + /*! + Tells clipboard it lost ownership at the given time. + */ + void lost(Time); + + //! Add clipboard request + /*! + Adds a selection request to the request list. If the given + owner window isn't this clipboard's window then this simply + sends a failure event to the requestor. + */ + void addRequest(Window owner, + Window requestor, Atom target, + ::Time time, Atom property); + + //! Process clipboard request + /*! + Continues processing a selection request. Returns true if the + request was handled, false if the request was unknown. + */ + bool processRequest(Window requestor, + ::Time time, Atom property); + + //! Cancel clipboard request + /*! + Terminate a selection request. Returns true iff the request + was known and handled. + */ + bool destroyRequest(Window requestor); + + //! Get window + /*! + Returns the clipboard's window (passed the c'tor). + */ + Window getWindow() const; + + //! Get selection atom + /*! + Returns the selection atom that identifies the clipboard to X11 + (e.g. XA_PRIMARY). + */ + Atom getSelection() const; + + // IClipboard overrides + virtual bool empty(); + virtual void add(EFormat, const String& data); + virtual bool open(Time) const; + virtual void close() const; + virtual Time getTime() const; + virtual bool has(EFormat) const; + virtual String get(EFormat) const; + +private: + // remove all converters from our list + void clearConverters(); + + // get the converter for a clipboard format. returns NULL if no + // suitable converter. iff onlyIfNotAdded is true then also + // return NULL if a suitable converter was found but we already + // have data of the converter's clipboard format. + IXWindowsClipboardConverter* + getConverter(Atom target, + bool onlyIfNotAdded = false) const; + + // convert target atom to clipboard format + EFormat getFormat(Atom target) const; + + // add a non-MULTIPLE request. does not verify that the selection + // was owned at the given time. returns true if the conversion + // could be performed, false otherwise. in either case, the + // reply is inserted. + bool addSimpleRequest( + Window requestor, Atom target, + ::Time time, Atom property); + + // if not already checked then see if the cache is stale and, if so, + // clear it. this has the side effect of updating m_timeOwned. + void checkCache() const; + + // clear the cache, resetting the cached flag and the added flag for + // each format. + void clearCache() const; + void doClearCache(); + + // cache all formats of the selection + void fillCache() const; + void doFillCache(); + + // + // helper classes + // + + // read an ICCCM conforming selection + class CICCCMGetClipboard { + public: + CICCCMGetClipboard(Window requestor, Time time, Atom property); + ~CICCCMGetClipboard(); + + // convert the given selection to the given type. returns + // true iff the conversion was successful or the conversion + // cannot be performed (in which case *actualTarget == None). + bool readClipboard(Display* display, + Atom selection, Atom target, + Atom* actualTarget, String* data); + + private: + bool processEvent(Display* display, XEvent* event); + + private: + Window m_requestor; + Time m_time; + Atom m_property; + bool m_incr; + bool m_failed; + bool m_done; + + // atoms needed for the protocol + Atom m_atomNone; // NONE, not None + Atom m_atomIncr; + + // true iff we've received the selection notify + bool m_reading; + + // the converted selection data + String* m_data; + + // the actual type of the data. if this is None then the + // selection owner cannot convert to the requested type. + Atom* m_actualTarget; + + public: + // true iff the selection owner didn't follow ICCCM conventions + bool m_error; + }; + + // Motif structure IDs + enum { kMotifClipFormat = 1, kMotifClipItem, kMotifClipHeader }; + + // _MOTIF_CLIP_HEADER structure + class MotifClipHeader { + public: + SInt32 m_id; // kMotifClipHeader + SInt32 m_pad1[3]; + SInt32 m_item; + SInt32 m_pad2[4]; + SInt32 m_numItems; + SInt32 m_pad3[3]; + SInt32 m_selectionOwner; // a Window + SInt32 m_pad4[2]; + }; + + // Motif clip item structure + class MotifClipItem { + public: + SInt32 m_id; // kMotifClipItem + SInt32 m_pad1[5]; + SInt32 m_size; + SInt32 m_numFormats; + SInt32 m_numDeletedFormats; + SInt32 m_pad2[6]; + }; + + // Motif clip format structure + class MotifClipFormat { + public: + SInt32 m_id; // kMotifClipFormat + SInt32 m_pad1[6]; + SInt32 m_length; + SInt32 m_data; + SInt32 m_type; // an Atom + SInt32 m_pad2[1]; + SInt32 m_deleted; + SInt32 m_pad3[4]; + }; + + // stores data needed to respond to a selection request + class Reply { + public: + Reply(Window, Atom target, ::Time); + Reply(Window, Atom target, ::Time, Atom property, + const String& data, Atom type, int format); + + public: + // information about the request + Window m_requestor; + Atom m_target; + ::Time m_time; + Atom m_property; + + // true iff we've sent the notification for this reply + bool m_replied; + + // true iff the reply has sent its last message + bool m_done; + + // the data to send and its type and format + String m_data; + Atom m_type; + int m_format; + + // index of next byte in m_data to send + UInt32 m_ptr; + }; + typedef std::list<Reply*> ReplyList; + typedef std::map<Window, ReplyList> ReplyMap; + typedef std::map<Window, long> ReplyEventMask; + + // ICCCM interoperability methods + void icccmFillCache(); + bool icccmGetSelection(Atom target, + Atom* actualTarget, String* data) const; + Time icccmGetTime() const; + + // motif interoperability methods + bool motifLockClipboard() const; + void motifUnlockClipboard() const; + bool motifOwnsClipboard() const; + void motifFillCache(); + bool motifGetSelection(const MotifClipFormat*, + Atom* actualTarget, String* data) const; + Time motifGetTime() const; + + // reply methods + bool insertMultipleReply(Window, ::Time, Atom); + void insertReply(Reply*); + void pushReplies(); + void pushReplies(ReplyMap::iterator&, + ReplyList&, ReplyList::iterator); + bool sendReply(Reply*); + void clearReplies(); + void clearReplies(ReplyList&); + void sendNotify(Window requestor, Atom selection, + Atom target, Atom property, Time time); + bool wasOwnedAtTime(::Time) const; + + // data conversion methods + Atom getTargetsData(String&, int* format) const; + Atom getTimestampData(String&, int* format) const; + +private: + typedef std::vector<IXWindowsClipboardConverter*> ConverterList; + + Display* m_display; + Window m_window; + ClipboardID m_id; + Atom m_selection; + mutable bool m_open; + mutable Time m_time; + bool m_owner; + mutable Time m_timeOwned; + Time m_timeLost; + + // true iff open and clipboard owned by a motif app + mutable bool m_motif; + + // the added/cached clipboard data + mutable bool m_checkCache; + bool m_cached; + Time m_cacheTime; + bool m_added[kNumFormats]; + String m_data[kNumFormats]; + + // conversion request replies + ReplyMap m_replies; + ReplyEventMask m_eventMasks; + + // clipboard format converters + ConverterList m_converters; + + // atoms we'll need + Atom m_atomTargets; + Atom m_atomMultiple; + Atom m_atomTimestamp; + Atom m_atomInteger; + Atom m_atomAtom; + Atom m_atomAtomPair; + Atom m_atomData; + Atom m_atomINCR; + Atom m_atomMotifClipLock; + Atom m_atomMotifClipHeader; + Atom m_atomMotifClipAccess; + Atom m_atomGDKSelection; +}; + +//! Clipboard format converter interface +/*! +This interface defines the methods common to all X11 clipboard format +converters. +*/ +class IXWindowsClipboardConverter : public IInterface { +public: + //! @name accessors + //@{ + + //! Get clipboard format + /*! + Return the clipboard format this object converts from/to. + */ + virtual IClipboard::EFormat + getFormat() const = 0; + + //! Get X11 format atom + /*! + Return the atom representing the X selection format that + this object converts from/to. + */ + virtual Atom getAtom() const = 0; + + //! Get X11 property datum size + /*! + Return the size (in bits) of data elements returned by + toIClipboard(). + */ + virtual int getDataSize() const = 0; + + //! Convert from IClipboard format + /*! + Convert from the IClipboard format to the X selection format. + The input data must be in the IClipboard format returned by + getFormat(). The return data will be in the X selection + format returned by getAtom(). + */ + virtual String fromIClipboard(const String&) const = 0; + + //! Convert to IClipboard format + /*! + Convert from the X selection format to the IClipboard format + (i.e., the reverse of fromIClipboard()). + */ + virtual String toIClipboard(const String&) const = 0; + + //@} +}; diff --git a/src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp new file mode 100644 index 0000000..493b1e8 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.cpp @@ -0,0 +1,191 @@ +/* + * 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 "platform/XWindowsClipboardAnyBitmapConverter.h" + +// BMP info header structure +struct CBMPInfoHeader { +public: + UInt32 biSize; + SInt32 biWidth; + SInt32 biHeight; + UInt16 biPlanes; + UInt16 biBitCount; + UInt32 biCompression; + UInt32 biSizeImage; + SInt32 biXPelsPerMeter; + SInt32 biYPelsPerMeter; + UInt32 biClrUsed; + UInt32 biClrImportant; +}; + +// BMP is little-endian + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, SInt32 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst[2] = static_cast<UInt8>((src >> 16) & 0xffu); + dst[3] = static_cast<UInt8>((src >> 24) & 0xffu); + dst += 4; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst[2] = static_cast<UInt8>((src >> 16) & 0xffu); + dst[3] = static_cast<UInt8>((src >> 24) & 0xffu); + dst += 4; +} + +static inline +UInt16 +fromLEU16(const UInt8* data) +{ + return static_cast<UInt16>(data[0]) | + (static_cast<UInt16>(data[1]) << 8); +} + +static inline +SInt32 +fromLES32(const UInt8* data) +{ + return static_cast<SInt32>(static_cast<UInt32>(data[0]) | + (static_cast<UInt32>(data[1]) << 8) | + (static_cast<UInt32>(data[2]) << 16) | + (static_cast<UInt32>(data[3]) << 24)); +} + +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast<UInt32>(data[0]) | + (static_cast<UInt32>(data[1]) << 8) | + (static_cast<UInt32>(data[2]) << 16) | + (static_cast<UInt32>(data[3]) << 24); +} + + +// +// XWindowsClipboardAnyBitmapConverter +// + +XWindowsClipboardAnyBitmapConverter::XWindowsClipboardAnyBitmapConverter() +{ + // do nothing +} + +XWindowsClipboardAnyBitmapConverter::~XWindowsClipboardAnyBitmapConverter() +{ + // do nothing +} + +IClipboard::EFormat +XWindowsClipboardAnyBitmapConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +int +XWindowsClipboardAnyBitmapConverter::getDataSize() const +{ + return 8; +} + +String +XWindowsClipboardAnyBitmapConverter::fromIClipboard(const String& bmp) const +{ + // fill BMP info header with native-endian data + CBMPInfoHeader infoHeader; + const UInt8* rawBMPInfoHeader = reinterpret_cast<const UInt8*>(bmp.data()); + infoHeader.biSize = fromLEU32(rawBMPInfoHeader + 0); + infoHeader.biWidth = fromLES32(rawBMPInfoHeader + 4); + infoHeader.biHeight = fromLES32(rawBMPInfoHeader + 8); + infoHeader.biPlanes = fromLEU16(rawBMPInfoHeader + 12); + infoHeader.biBitCount = fromLEU16(rawBMPInfoHeader + 14); + infoHeader.biCompression = fromLEU32(rawBMPInfoHeader + 16); + infoHeader.biSizeImage = fromLEU32(rawBMPInfoHeader + 20); + infoHeader.biXPelsPerMeter = fromLES32(rawBMPInfoHeader + 24); + infoHeader.biYPelsPerMeter = fromLES32(rawBMPInfoHeader + 28); + infoHeader.biClrUsed = fromLEU32(rawBMPInfoHeader + 32); + infoHeader.biClrImportant = fromLEU32(rawBMPInfoHeader + 36); + + // check that format is acceptable + if (infoHeader.biSize != 40 || + infoHeader.biWidth == 0 || infoHeader.biHeight == 0 || + infoHeader.biPlanes != 0 || infoHeader.biCompression != 0 || + (infoHeader.biBitCount != 24 && infoHeader.biBitCount != 32)) { + return String(); + } + + // convert to image format + const UInt8* rawBMPPixels = rawBMPInfoHeader + 40; + if (infoHeader.biBitCount == 24) { + return doBGRFromIClipboard(rawBMPPixels, + infoHeader.biWidth, infoHeader.biHeight); + } + else { + return doBGRAFromIClipboard(rawBMPPixels, + infoHeader.biWidth, infoHeader.biHeight); + } +} + +String +XWindowsClipboardAnyBitmapConverter::toIClipboard(const String& image) const +{ + // convert to raw BMP data + UInt32 w, h, depth; + String rawBMP = doToIClipboard(image, w, h, depth); + if (rawBMP.empty() || w == 0 || h == 0 || (depth != 24 && depth != 32)) { + return String(); + } + + // fill BMP info header with little-endian data + UInt8 infoHeader[40]; + UInt8* dst = infoHeader; + toLE(dst, static_cast<UInt32>(40)); + toLE(dst, static_cast<SInt32>(w)); + toLE(dst, static_cast<SInt32>(h)); + toLE(dst, static_cast<UInt16>(1)); + toLE(dst, static_cast<UInt16>(depth)); + toLE(dst, static_cast<UInt32>(0)); // BI_RGB + toLE(dst, static_cast<UInt32>(image.size())); + toLE(dst, static_cast<SInt32>(2834)); // 72 dpi + toLE(dst, static_cast<SInt32>(2834)); // 72 dpi + toLE(dst, static_cast<UInt32>(0)); + toLE(dst, static_cast<UInt32>(0)); + + // construct image + return String(reinterpret_cast<const char*>(infoHeader), + sizeof(infoHeader)) + rawBMP; +} diff --git a/src/lib/platform/XWindowsClipboardAnyBitmapConverter.h b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.h new file mode 100644 index 0000000..d723a33 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardAnyBitmapConverter.h @@ -0,0 +1,60 @@ +/* + * 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 "platform/XWindowsClipboard.h" + +//! Convert to/from some text encoding +class XWindowsClipboardAnyBitmapConverter : + public IXWindowsClipboardConverter { +public: + XWindowsClipboardAnyBitmapConverter(); + virtual ~XWindowsClipboardAnyBitmapConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const = 0; + virtual int getDataSize() const; + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + +protected: + //! Convert from IClipboard format + /*! + Convert raw BGR pixel data to another image format. + */ + virtual String doBGRFromIClipboard(const UInt8* bgrData, + UInt32 w, UInt32 h) const = 0; + + //! Convert from IClipboard format + /*! + Convert raw BGRA pixel data to another image format. + */ + virtual String doBGRAFromIClipboard(const UInt8* bgrData, + UInt32 w, UInt32 h) const = 0; + + //! Convert to IClipboard format + /*! + Convert an image into raw BGR or BGRA image data and store the + width, height, and image depth (24 or 32). + */ + virtual String doToIClipboard(const String&, + UInt32& w, UInt32& h, UInt32& depth) const = 0; +}; diff --git a/src/lib/platform/XWindowsClipboardBMPConverter.cpp b/src/lib/platform/XWindowsClipboardBMPConverter.cpp new file mode 100644 index 0000000..b4def5b --- /dev/null +++ b/src/lib/platform/XWindowsClipboardBMPConverter.cpp @@ -0,0 +1,143 @@ +/* + * 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 "platform/XWindowsClipboardBMPConverter.h" + +// BMP file header structure +struct CBMPHeader { +public: + UInt16 type; + UInt32 size; + UInt16 reserved1; + UInt16 reserved2; + UInt32 offset; +}; + +// BMP is little-endian +static inline +UInt32 +fromLEU32(const UInt8* data) +{ + return static_cast<UInt32>(data[0]) | + (static_cast<UInt32>(data[1]) << 8) | + (static_cast<UInt32>(data[2]) << 16) | + (static_cast<UInt32>(data[3]) << 24); +} + +static +void +toLE(UInt8*& dst, char src) +{ + dst[0] = static_cast<UInt8>(src); + dst += 1; +} + +static +void +toLE(UInt8*& dst, UInt16 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst += 2; +} + +static +void +toLE(UInt8*& dst, UInt32 src) +{ + dst[0] = static_cast<UInt8>(src & 0xffu); + dst[1] = static_cast<UInt8>((src >> 8) & 0xffu); + dst[2] = static_cast<UInt8>((src >> 16) & 0xffu); + dst[3] = static_cast<UInt8>((src >> 24) & 0xffu); + dst += 4; +} + +// +// XWindowsClipboardBMPConverter +// + +XWindowsClipboardBMPConverter::XWindowsClipboardBMPConverter( + Display* display) : + m_atom(XInternAtom(display, "image/bmp", False)) +{ + // do nothing +} + +XWindowsClipboardBMPConverter::~XWindowsClipboardBMPConverter() +{ + // do nothing +} + +IClipboard::EFormat +XWindowsClipboardBMPConverter::getFormat() const +{ + return IClipboard::kBitmap; +} + +Atom +XWindowsClipboardBMPConverter::getAtom() const +{ + return m_atom; +} + +int +XWindowsClipboardBMPConverter::getDataSize() const +{ + return 8; +} + +String +XWindowsClipboardBMPConverter::fromIClipboard(const String& bmp) const +{ + // create BMP image + UInt8 header[14]; + UInt8* dst = header; + toLE(dst, 'B'); + toLE(dst, 'M'); + toLE(dst, static_cast<UInt32>(14 + bmp.size())); + toLE(dst, static_cast<UInt16>(0)); + toLE(dst, static_cast<UInt16>(0)); + toLE(dst, static_cast<UInt32>(14 + 40)); + return String(reinterpret_cast<const char*>(header), 14) + bmp; +} + +String +XWindowsClipboardBMPConverter::toIClipboard(const String& bmp) const +{ + // make sure data is big enough for a BMP file + if (bmp.size() <= 14 + 40) { + return String(); + } + + // check BMP file header + const UInt8* rawBMPHeader = reinterpret_cast<const UInt8*>(bmp.data()); + if (rawBMPHeader[0] != 'B' || rawBMPHeader[1] != 'M') { + return String(); + } + + // get offset to image data + UInt32 offset = fromLEU32(rawBMPHeader + 10); + + // construct BMP + if (offset == 14 + 40) { + return bmp.substr(14); + } + else { + return bmp.substr(14, 40) + bmp.substr(offset, bmp.size() - offset); + } +} diff --git a/src/lib/platform/XWindowsClipboardBMPConverter.h b/src/lib/platform/XWindowsClipboardBMPConverter.h new file mode 100644 index 0000000..d7813a0 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardBMPConverter.h @@ -0,0 +1,40 @@ +/* + * 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 "platform/XWindowsClipboard.h" + +//! Convert to/from some text encoding +class XWindowsClipboardBMPConverter : + public IXWindowsClipboardConverter { +public: + XWindowsClipboardBMPConverter(Display* display); + virtual ~XWindowsClipboardBMPConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + +private: + Atom m_atom; +}; diff --git a/src/lib/platform/XWindowsClipboardHTMLConverter.cpp b/src/lib/platform/XWindowsClipboardHTMLConverter.cpp new file mode 100644 index 0000000..32db724 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardHTMLConverter.cpp @@ -0,0 +1,67 @@ +/* + * 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 "platform/XWindowsClipboardHTMLConverter.h" + +#include "base/Unicode.h" + +// +// XWindowsClipboardHTMLConverter +// + +XWindowsClipboardHTMLConverter::XWindowsClipboardHTMLConverter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +XWindowsClipboardHTMLConverter::~XWindowsClipboardHTMLConverter() +{ + // do nothing +} + +IClipboard::EFormat +XWindowsClipboardHTMLConverter::getFormat() const +{ + return IClipboard::kHTML; +} + +Atom +XWindowsClipboardHTMLConverter::getAtom() const +{ + return m_atom; +} + +int +XWindowsClipboardHTMLConverter::getDataSize() const +{ + return 8; +} + +String +XWindowsClipboardHTMLConverter::fromIClipboard(const String& data) const +{ + return Unicode::UTF8ToUTF16(data); +} + +String +XWindowsClipboardHTMLConverter::toIClipboard(const String& data) const +{ + return Unicode::UTF16ToUTF8(data); +} diff --git a/src/lib/platform/XWindowsClipboardHTMLConverter.h b/src/lib/platform/XWindowsClipboardHTMLConverter.h new file mode 100644 index 0000000..013aa99 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardHTMLConverter.h @@ -0,0 +1,42 @@ +/* + * 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 "platform/XWindowsClipboard.h" + +//! Convert to/from HTML encoding +class XWindowsClipboardHTMLConverter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + XWindowsClipboardHTMLConverter(Display* display, const char* name); + virtual ~XWindowsClipboardHTMLConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + +private: + Atom m_atom; +}; diff --git a/src/lib/platform/XWindowsClipboardTextConverter.cpp b/src/lib/platform/XWindowsClipboardTextConverter.cpp new file mode 100644 index 0000000..71b7a84 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardTextConverter.cpp @@ -0,0 +1,79 @@ +/* + * 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 "platform/XWindowsClipboardTextConverter.h" + +#include "base/Unicode.h" + +// +// XWindowsClipboardTextConverter +// + +XWindowsClipboardTextConverter::XWindowsClipboardTextConverter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +XWindowsClipboardTextConverter::~XWindowsClipboardTextConverter() +{ + // do nothing +} + +IClipboard::EFormat +XWindowsClipboardTextConverter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +XWindowsClipboardTextConverter::getAtom() const +{ + return m_atom; +} + +int +XWindowsClipboardTextConverter::getDataSize() const +{ + return 8; +} + +String +XWindowsClipboardTextConverter::fromIClipboard(const String& data) const +{ + return Unicode::UTF8ToText(data); +} + +String +XWindowsClipboardTextConverter::toIClipboard(const String& data) const +{ + // convert to UTF-8 + bool errors; + String utf8 = Unicode::textToUTF8(data, &errors); + + // if there were decoding errors then, to support old applications + // that don't understand UTF-8 but can report the exact binary + // UTF-8 representation, see if the data appears to be UTF-8. if + // so then use it as is. + if (errors && Unicode::isUTF8(data)) { + return data; + } + + return utf8; +} diff --git a/src/lib/platform/XWindowsClipboardTextConverter.h b/src/lib/platform/XWindowsClipboardTextConverter.h new file mode 100644 index 0000000..0e6d598 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardTextConverter.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 "platform/XWindowsClipboard.h" + +//! Convert to/from locale text encoding +class XWindowsClipboardTextConverter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + XWindowsClipboardTextConverter(Display* display, const char* name); + virtual ~XWindowsClipboardTextConverter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + +private: + Atom m_atom; +}; diff --git a/src/lib/platform/XWindowsClipboardUCS2Converter.cpp b/src/lib/platform/XWindowsClipboardUCS2Converter.cpp new file mode 100644 index 0000000..988b909 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardUCS2Converter.cpp @@ -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 "platform/XWindowsClipboardUCS2Converter.h" + +#include "base/Unicode.h" + +// +// XWindowsClipboardUCS2Converter +// + +XWindowsClipboardUCS2Converter::XWindowsClipboardUCS2Converter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +XWindowsClipboardUCS2Converter::~XWindowsClipboardUCS2Converter() +{ + // do nothing +} + +IClipboard::EFormat +XWindowsClipboardUCS2Converter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +XWindowsClipboardUCS2Converter::getAtom() const +{ + return m_atom; +} + +int +XWindowsClipboardUCS2Converter::getDataSize() const +{ + return 16; +} + +String +XWindowsClipboardUCS2Converter::fromIClipboard(const String& data) const +{ + return Unicode::UTF8ToUCS2(data); +} + +String +XWindowsClipboardUCS2Converter::toIClipboard(const String& data) const +{ + return Unicode::UCS2ToUTF8(data); +} diff --git a/src/lib/platform/XWindowsClipboardUCS2Converter.h b/src/lib/platform/XWindowsClipboardUCS2Converter.h new file mode 100644 index 0000000..6491408 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardUCS2Converter.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 "platform/XWindowsClipboard.h" + +//! Convert to/from UCS-2 encoding +class XWindowsClipboardUCS2Converter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + XWindowsClipboardUCS2Converter(Display* display, const char* name); + virtual ~XWindowsClipboardUCS2Converter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + +private: + Atom m_atom; +}; diff --git a/src/lib/platform/XWindowsClipboardUTF8Converter.cpp b/src/lib/platform/XWindowsClipboardUTF8Converter.cpp new file mode 100644 index 0000000..0e43cce --- /dev/null +++ b/src/lib/platform/XWindowsClipboardUTF8Converter.cpp @@ -0,0 +1,65 @@ +/* + * 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 "platform/XWindowsClipboardUTF8Converter.h" + +// +// XWindowsClipboardUTF8Converter +// + +XWindowsClipboardUTF8Converter::XWindowsClipboardUTF8Converter( + Display* display, const char* name) : + m_atom(XInternAtom(display, name, False)) +{ + // do nothing +} + +XWindowsClipboardUTF8Converter::~XWindowsClipboardUTF8Converter() +{ + // do nothing +} + +IClipboard::EFormat +XWindowsClipboardUTF8Converter::getFormat() const +{ + return IClipboard::kText; +} + +Atom +XWindowsClipboardUTF8Converter::getAtom() const +{ + return m_atom; +} + +int +XWindowsClipboardUTF8Converter::getDataSize() const +{ + return 8; +} + +String +XWindowsClipboardUTF8Converter::fromIClipboard(const String& data) const +{ + return data; +} + +String +XWindowsClipboardUTF8Converter::toIClipboard(const String& data) const +{ + return data; +} diff --git a/src/lib/platform/XWindowsClipboardUTF8Converter.h b/src/lib/platform/XWindowsClipboardUTF8Converter.h new file mode 100644 index 0000000..e3eeca0 --- /dev/null +++ b/src/lib/platform/XWindowsClipboardUTF8Converter.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 "platform/XWindowsClipboard.h" + +//! Convert to/from UTF-8 encoding +class XWindowsClipboardUTF8Converter : public IXWindowsClipboardConverter { +public: + /*! + \c name is converted to an atom and that is reported by getAtom(). + */ + XWindowsClipboardUTF8Converter(Display* display, const char* name); + virtual ~XWindowsClipboardUTF8Converter(); + + // IXWindowsClipboardConverter overrides + virtual IClipboard::EFormat + getFormat() const; + virtual Atom getAtom() const; + virtual int getDataSize() const; + virtual String fromIClipboard(const String&) const; + virtual String toIClipboard(const String&) const; + +private: + Atom m_atom; +}; diff --git a/src/lib/platform/XWindowsEventQueueBuffer.cpp b/src/lib/platform/XWindowsEventQueueBuffer.cpp new file mode 100644 index 0000000..234cd62 --- /dev/null +++ b/src/lib/platform/XWindowsEventQueueBuffer.cpp @@ -0,0 +1,291 @@ +/* + * 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 "platform/XWindowsEventQueueBuffer.h" + +#include "mt/Lock.h" +#include "mt/Thread.h" +#include "base/Event.h" +#include "base/IEventQueue.h" + +#include <fcntl.h> +#if HAVE_UNISTD_H +# include <unistd.h> +#endif +#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 +# if HAVE_SYS_TYPES_H +# include <sys/types.h> +# endif +#endif + +// +// EventQueueTimer +// + +class EventQueueTimer { }; + + +// +// XWindowsEventQueueBuffer +// + +XWindowsEventQueueBuffer::XWindowsEventQueueBuffer( + Display* display, Window window, IEventQueue* events) : + m_events(events), + m_display(display), + m_window(window), + m_waiting(false) +{ + assert(m_display != NULL); + assert(m_window != None); + + m_userEvent = XInternAtom(m_display, "BARRIER_USER_EVENT", False); + // set up for pipe hack + int result = pipe(m_pipefd); + assert(result == 0); + + int pipeflags; + pipeflags = fcntl(m_pipefd[0], F_GETFL); + fcntl(m_pipefd[0], F_SETFL, pipeflags | O_NONBLOCK); + pipeflags = fcntl(m_pipefd[1], F_GETFL); + fcntl(m_pipefd[1], F_SETFL, pipeflags | O_NONBLOCK); +} + +XWindowsEventQueueBuffer::~XWindowsEventQueueBuffer() +{ + // release pipe hack resources + close(m_pipefd[0]); + close(m_pipefd[1]); +} + +void +XWindowsEventQueueBuffer::waitForEvent(double dtimeout) +{ + Thread::testCancel(); + + // clear out the pipe in preparation for waiting. + + char buf[16]; + ssize_t read_response = read(m_pipefd[0], buf, 15); + + // with linux automake, warnings are treated as errors by default + if (read_response < 0) + { + // todo: handle read response + } + + { + Lock lock(&m_mutex); + // we're now waiting for events + m_waiting = true; + + // push out pending events + flush(); + } + // calling flush may have queued up a new event. + if (!XWindowsEventQueueBuffer::isEmpty()) { + Thread::testCancel(); + return; + } + + // use poll() to wait for a message from the X server or for timeout. + // this is a good deal more efficient than polling and sleeping. +#if HAVE_POLL + struct pollfd pfds[2]; + pfds[0].fd = ConnectionNumber(m_display); + pfds[0].events = POLLIN; + pfds[1].fd = m_pipefd[0]; + pfds[1].events = POLLIN; + int timeout = (dtimeout < 0.0) ? -1 : + static_cast<int>(1000.0 * dtimeout); + int remaining = timeout; + int retval = 0; +#else + struct timeval timeout; + struct timeval* timeoutPtr; + if (dtimeout < 0.0) { + timeoutPtr = NULL; + } + else { + timeout.tv_sec = static_cast<int>(dtimeout); + timeout.tv_usec = static_cast<int>(1.0e+6 * + (dtimeout - timeout.tv_sec)); + timeoutPtr = &timeout; + } + + // initialize file descriptor sets + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(ConnectionNumber(m_display), &rfds); + FD_SET(m_pipefd[0], &rfds); + int nfds; + if (ConnectionNumber(m_display) > m_pipefd[0]) { + nfds = ConnectionNumber(m_display) + 1; + } + else { + nfds = m_pipefd[0] + 1; + } +#endif + // It's possible that the X server has queued events locally + // in xlib's event buffer and not pushed on to the fd. Hence we + // can't simply monitor the fd as we may never be woken up. + // ie addEvent calls flush, XFlush may not send via the fd hence + // there is an event waiting to be sent but we must exit the poll + // before it can. + // Instead we poll for a brief period of time (so if events + // queued locally in the xlib buffer can be processed) + // and continue doing this until timeout is reached. + // The human eye can notice 60hz (ansi) which is 16ms, however + // we want to give the cpu a chance s owe up this to 25ms +#define TIMEOUT_DELAY 25 + + while (((dtimeout < 0.0) || (remaining > 0)) && QLength(m_display)==0 && retval==0){ +#if HAVE_POLL + retval = poll(pfds, 2, TIMEOUT_DELAY); //16ms = 60hz, but we make it > to play nicely with the cpu + if (pfds[1].revents & POLLIN) { + ssize_t read_response = read(m_pipefd[0], buf, 15); + + // with linux automake, warnings are treated as errors by default + if (read_response < 0) + { + // todo: handle read response + } + + } +#else + retval = select(nfds, + SELECT_TYPE_ARG234 &rfds, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG234 NULL, + SELECT_TYPE_ARG5 TIMEOUT_DELAY); + if (FD_SET(m_pipefd[0], &rfds)) { + read(m_pipefd[0], buf, 15); + } +#endif + remaining-=TIMEOUT_DELAY; + } + + { + // we're no longer waiting for events + Lock lock(&m_mutex); + m_waiting = false; + } + + Thread::testCancel(); +} + +IEventQueueBuffer::Type +XWindowsEventQueueBuffer::getEvent(Event& event, UInt32& dataID) +{ + Lock lock(&m_mutex); + + // push out pending events + flush(); + + // get next event + XNextEvent(m_display, &m_event); + + // process event + if (m_event.xany.type == ClientMessage && + m_event.xclient.message_type == m_userEvent) { + dataID = static_cast<UInt32>(m_event.xclient.data.l[0]); + return kUser; + } + else { + event = Event(Event::kSystem, + m_events->getSystemTarget(), &m_event); + return kSystem; + } +} + +bool +XWindowsEventQueueBuffer::addEvent(UInt32 dataID) +{ + // prepare a message + XEvent xevent; + xevent.xclient.type = ClientMessage; + xevent.xclient.window = m_window; + xevent.xclient.message_type = m_userEvent; + xevent.xclient.format = 32; + xevent.xclient.data.l[0] = static_cast<long>(dataID); + + // save the message + Lock lock(&m_mutex); + m_postedEvents.push_back(xevent); + + // if we're currently waiting for an event then send saved events to + // the X server now. if we're not waiting then some other thread + // might be using the display connection so we can't safely use it + // too. + if (m_waiting) { + flush(); + // Send a character through the round-trip pipe to wake a thread + // that is waiting for a ConnectionNumber() socket to be readable. + // The flush call can read incoming data from the socket and put + // it in Xlib's input buffer. That sneaks it past the other thread. + ssize_t write_response = write(m_pipefd[1], "!", 1); + + // with linux automake, warnings are treated as errors by default + if (write_response < 0) + { + // todo: handle read response + } + } + + return true; +} + +bool +XWindowsEventQueueBuffer::isEmpty() const +{ + Lock lock(&m_mutex); + return (XPending(m_display) == 0 ); +} + +EventQueueTimer* +XWindowsEventQueueBuffer::newTimer(double, bool) const +{ + return new EventQueueTimer; +} + +void +XWindowsEventQueueBuffer::deleteTimer(EventQueueTimer* timer) const +{ + delete timer; +} + +void +XWindowsEventQueueBuffer::flush() +{ + // note -- m_mutex must be locked on entry + + // flush the posted event list to the X server + for (size_t i = 0; i < m_postedEvents.size(); ++i) { + XSendEvent(m_display, m_window, False, 0, &m_postedEvents[i]); + } + XFlush(m_display); + m_postedEvents.clear(); +} diff --git a/src/lib/platform/XWindowsEventQueueBuffer.h b/src/lib/platform/XWindowsEventQueueBuffer.h new file mode 100644 index 0000000..07f3b3a --- /dev/null +++ b/src/lib/platform/XWindowsEventQueueBuffer.h @@ -0,0 +1,64 @@ +/* + * 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 "mt/Mutex.h" +#include "base/IEventQueueBuffer.h" +#include "common/stdvector.h" + +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/Xlib.h> +#endif + +class IEventQueue; + +//! Event queue buffer for X11 +class XWindowsEventQueueBuffer : public IEventQueueBuffer { +public: + XWindowsEventQueueBuffer(Display*, Window, IEventQueue* events); + virtual ~XWindowsEventQueueBuffer(); + + // IEventQueueBuffer overrides + virtual void init() { } + virtual void waitForEvent(double timeout); + virtual Type getEvent(Event& event, UInt32& dataID); + virtual bool addEvent(UInt32 dataID); + virtual bool isEmpty() const; + virtual EventQueueTimer* + newTimer(double duration, bool oneShot) const; + virtual void deleteTimer(EventQueueTimer*) const; + +private: + void flush(); + +private: + typedef std::vector<XEvent> EventList; + + Mutex m_mutex; + Display* m_display; + Window m_window; + Atom m_userEvent; + XEvent m_event; + EventList m_postedEvents; + bool m_waiting; + int m_pipefd[2]; + IEventQueue* m_events; +}; diff --git a/src/lib/platform/XWindowsKeyState.cpp b/src/lib/platform/XWindowsKeyState.cpp new file mode 100644 index 0000000..1ca4629 --- /dev/null +++ b/src/lib/platform/XWindowsKeyState.cpp @@ -0,0 +1,867 @@ +/* + * 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 "platform/XWindowsKeyState.h" + +#include "platform/XWindowsUtil.h" +#include "base/Log.h" +#include "base/String.h" +#include "common/stdmap.h" + +#include <cstddef> +#include <algorithm> +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/X.h> +# include <X11/Xutil.h> +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include <X11/keysymdef.h> +#if HAVE_XKB_EXTENSION +# include <X11/XKBlib.h> +#endif +#endif + +static const size_t ModifiersFromXDefaultSize = 32; + +XWindowsKeyState::XWindowsKeyState( + Display* display, bool useXKB, + IEventQueue* events) : + KeyState(events), + m_display(display), + m_modifierFromX(ModifiersFromXDefaultSize) +{ + init(display, useXKB); +} + +XWindowsKeyState::XWindowsKeyState( + Display* display, bool useXKB, + IEventQueue* events, barrier::KeyMap& keyMap) : + KeyState(events, keyMap), + m_display(display), + m_modifierFromX(ModifiersFromXDefaultSize) +{ + init(display, useXKB); +} + +XWindowsKeyState::~XWindowsKeyState() +{ +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbFreeKeyboard(m_xkb, 0, True); + } +#endif +} + +void +XWindowsKeyState::init(Display* display, bool useXKB) +{ + XGetKeyboardControl(m_display, &m_keyboardState); +#if HAVE_XKB_EXTENSION + if (useXKB) { + m_xkb = XkbGetMap(m_display, XkbKeyActionsMask | XkbKeyBehaviorsMask | + XkbAllClientInfoMask, XkbUseCoreKbd); + } + else { + m_xkb = NULL; + } +#endif + setActiveGroup(kGroupPollAndSet); +} + +void +XWindowsKeyState::setActiveGroup(SInt32 group) +{ + if (group == kGroupPollAndSet) { + // we need to set the group to -1 in order for pollActiveGroup() to + // actually poll for the group + m_group = -1; + m_group = pollActiveGroup(); + } + else if (group == kGroupPoll) { + m_group = -1; + } + else { + assert(group >= 0); + m_group = group; + } +} + +void +XWindowsKeyState::setAutoRepeat(const XKeyboardState& state) +{ + m_keyboardState = state; +} + +KeyModifierMask +XWindowsKeyState::mapModifiersFromX(unsigned int state) const +{ + LOG((CLOG_DEBUG2 "mapping state: %i", state)); + UInt32 offset = 8 * getGroupFromState(state); + KeyModifierMask mask = 0; + for (int i = 0; i < 8; ++i) { + if ((state & (1u << i)) != 0) { + LOG((CLOG_DEBUG2 "|= modifier: %i", offset + i)); + if (offset + i >= m_modifierFromX.size()) { + LOG((CLOG_ERR "m_modifierFromX is too small (%d) for the " + "requested offset (%d)", m_modifierFromX.size(), offset+i)); + } else { + mask |= m_modifierFromX[offset + i]; + } + } + } + return mask; +} + +bool +XWindowsKeyState::mapModifiersToX(KeyModifierMask mask, + unsigned int& modifiers) const +{ + modifiers = 0; + + for (SInt32 i = 0; i < kKeyModifierNumBits; ++i) { + KeyModifierMask bit = (1u << i); + if ((mask & bit) != 0) { + KeyModifierToXMask::const_iterator j = m_modifierToX.find(bit); + if (j == m_modifierToX.end()) { + return false; + } + else { + modifiers |= j->second; + } + } + } + + return true; +} + +void +XWindowsKeyState::mapKeyToKeycodes(KeyID key, KeycodeList& keycodes) const +{ + keycodes.clear(); + std::pair<KeyToKeyCodeMap::const_iterator, + KeyToKeyCodeMap::const_iterator> range = + m_keyCodeFromKey.equal_range(key); + for (KeyToKeyCodeMap::const_iterator i = range.first; + i != range.second; ++i) { + keycodes.push_back(i->second); + } +} + +bool +XWindowsKeyState::fakeCtrlAltDel() +{ + // pass keys through unchanged + return false; +} + +KeyModifierMask +XWindowsKeyState::pollActiveModifiers() const +{ + Window root = DefaultRootWindow(m_display), window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state = 0; + if (XQueryPointer(m_display, root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state) == False) { + state = 0; + } + return mapModifiersFromX(state); +} + +SInt32 +XWindowsKeyState::pollActiveGroup() const +{ + // fixed condition where any group < -1 would have undetermined behaviour + if (m_group >= 0) { + return m_group; + } + +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + XkbStateRec state; + if (XkbGetState(m_display, XkbUseCoreKbd, &state) == Success) { + return state.group; + } + } +#endif + return 0; +} + +void +XWindowsKeyState::pollPressedKeys(KeyButtonSet& pressedKeys) const +{ + char keys[32]; + XQueryKeymap(m_display, keys); + for (UInt32 i = 0; i < 32; ++i) { + for (UInt32 j = 0; j < 8; ++j) { + if ((keys[i] & (1u << j)) != 0) { + pressedKeys.insert(8 * i + j); + } + } + } +} + +void +XWindowsKeyState::getKeyMap(barrier::KeyMap& keyMap) +{ + // get autorepeat info. we must use the global_auto_repeat told to + // us because it may have modified by barrier. + int oldGlobalAutoRepeat = m_keyboardState.global_auto_repeat; + XGetKeyboardControl(m_display, &m_keyboardState); + m_keyboardState.global_auto_repeat = oldGlobalAutoRepeat; + +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + if (XkbGetUpdatedMap(m_display, XkbKeyActionsMask | + XkbKeyBehaviorsMask | XkbAllClientInfoMask, m_xkb) == Success) { + updateKeysymMapXKB(keyMap); + return; + } + } +#endif + updateKeysymMap(keyMap); +} + +void +XWindowsKeyState::fakeKey(const Keystroke& keystroke) +{ + switch (keystroke.m_type) { + case Keystroke::kButton: + LOG((CLOG_DEBUG1 " %03x (%08x) %s", keystroke.m_data.m_button.m_button, keystroke.m_data.m_button.m_client, keystroke.m_data.m_button.m_press ? "down" : "up")); + if (keystroke.m_data.m_button.m_repeat) { + int c = keystroke.m_data.m_button.m_button; + int i = (c >> 3); + int b = 1 << (c & 7); + if (m_keyboardState.global_auto_repeat == AutoRepeatModeOff || + (c!=113 && c!=116 && (m_keyboardState.auto_repeats[i] & b) == 0)) { + LOG((CLOG_DEBUG1 " discard autorepeat")); + break; + } + } + XTestFakeKeyEvent(m_display, keystroke.m_data.m_button.m_button, + keystroke.m_data.m_button.m_press ? True : False, + CurrentTime); + break; + + case Keystroke::kGroup: + if (keystroke.m_data.m_group.m_absolute) { + LOG((CLOG_DEBUG1 " group %d", keystroke.m_data.m_group.m_group)); +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + if (XkbLockGroup(m_display, XkbUseCoreKbd, + keystroke.m_data.m_group.m_group) == False) { + LOG((CLOG_DEBUG1 "XkbLockGroup request not sent")); + } + } + else +#endif + { + LOG((CLOG_DEBUG1 " ignored")); + } + } + else { + LOG((CLOG_DEBUG1 " group %+d", keystroke.m_data.m_group.m_group)); +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + if (XkbLockGroup(m_display, XkbUseCoreKbd, + getEffectiveGroup(pollActiveGroup(), + keystroke.m_data.m_group.m_group)) == False) { + LOG((CLOG_DEBUG1 "XkbLockGroup request not sent")); + } + } + else +#endif + { + LOG((CLOG_DEBUG1 " ignored")); + } + } + break; + } + XFlush(m_display); +} + +void +XWindowsKeyState::updateKeysymMap(barrier::KeyMap& keyMap) +{ + // there are up to 4 keysyms per keycode + static const int maxKeysyms = 4; + + LOG((CLOG_DEBUG1 "non-XKB mapping")); + + // prepare map from X modifier to KeyModifierMask. certain bits + // are predefined. + std::fill(m_modifierFromX.begin(), m_modifierFromX.end(), 0); + m_modifierFromX[ShiftMapIndex] = KeyModifierShift; + m_modifierFromX[LockMapIndex] = KeyModifierCapsLock; + m_modifierFromX[ControlMapIndex] = KeyModifierControl; + m_modifierToX.clear(); + m_modifierToX[KeyModifierShift] = ShiftMask; + m_modifierToX[KeyModifierCapsLock] = LockMask; + m_modifierToX[KeyModifierControl] = ControlMask; + + // prepare map from KeyID to KeyCode + m_keyCodeFromKey.clear(); + + // get the number of keycodes + int minKeycode, maxKeycode; + XDisplayKeycodes(m_display, &minKeycode, &maxKeycode); + int numKeycodes = maxKeycode - minKeycode + 1; + + // get the keyboard mapping for all keys + int keysymsPerKeycode; + KeySym* allKeysyms = XGetKeyboardMapping(m_display, + minKeycode, numKeycodes, + &keysymsPerKeycode); + + // it's more convenient to always have maxKeysyms KeySyms per key + { + KeySym* tmpKeysyms = new KeySym[maxKeysyms * numKeycodes]; + for (int i = 0; i < numKeycodes; ++i) { + for (int j = 0; j < maxKeysyms; ++j) { + if (j < keysymsPerKeycode) { + tmpKeysyms[maxKeysyms * i + j] = + allKeysyms[keysymsPerKeycode * i + j]; + } + else { + tmpKeysyms[maxKeysyms * i + j] = NoSymbol; + } + } + } + XFree(allKeysyms); + allKeysyms = tmpKeysyms; + } + + // get the buttons assigned to modifiers. X11 does not predefine + // the meaning of any modifiers except shift, caps lock, and the + // control key. the meaning of a modifier bit (other than those) + // depends entirely on the KeySyms mapped to that bit. unfortunately + // you cannot map a bit back to the KeySym used to produce it. + // for example, let's say button 1 maps to Alt_L without shift and + // Meta_L with shift. now if mod1 is mapped to button 1 that could + // mean the user used Alt or Meta to turn on that modifier and there's + // no way to know which. it's also possible for one button to be + // mapped to multiple bits so both mod1 and mod2 could be generated + // by button 1. + // + // we're going to ignore any modifier for a button except the first. + // with the above example, that means we'll ignore the mod2 modifier + // bit unless it's also mapped to some other button. we're also + // going to ignore all KeySyms except the first modifier KeySym, + // which means button 1 above won't map to Meta, just Alt. + std::map<KeyCode, unsigned int> modifierButtons; + XModifierKeymap* modifiers = XGetModifierMapping(m_display); + for (unsigned int i = 0; i < 8; ++i) { + const KeyCode* buttons = + modifiers->modifiermap + i * modifiers->max_keypermod; + for (int j = 0; j < modifiers->max_keypermod; ++j) { + modifierButtons.insert(std::make_pair(buttons[j], i)); + } + } + XFreeModifiermap(modifiers); + modifierButtons.erase(0); + + // Hack to deal with VMware. When a VMware client grabs input the + // player clears out the X modifier map for whatever reason. We're + // notified of the change and arrive here to discover that there + // are no modifiers at all. Since this prevents the modifiers from + // working in the VMware client we'll use the last known good set + // of modifiers when there are no modifiers. If there are modifiers + // we update the last known good set. + if (!modifierButtons.empty()) { + m_lastGoodNonXKBModifiers = modifierButtons; + } + else { + modifierButtons = m_lastGoodNonXKBModifiers; + } + + // add entries for each keycode + barrier::KeyMap::KeyItem item; + for (int i = 0; i < numKeycodes; ++i) { + KeySym* keysyms = allKeysyms + maxKeysyms * i; + KeyCode keycode = static_cast<KeyCode>(i + minKeycode); + item.m_button = static_cast<KeyButton>(keycode); + item.m_client = 0; + + // determine modifier sensitivity + item.m_sensitive = 0; + + // if the keysyms in levels 2 or 3 exist and differ from levels + // 0 and 1 then the key is sensitive AltGr (Mode_switch) + if ((keysyms[2] != NoSymbol && keysyms[2] != keysyms[0]) || + (keysyms[3] != NoSymbol && keysyms[2] != keysyms[1])) { + item.m_sensitive |= KeyModifierAltGr; + } + + // check if the key is caps-lock sensitive. some systems only + // provide one keysym for keys sensitive to caps-lock. if we + // find that then fill in the missing keysym. + if (keysyms[0] != NoSymbol && keysyms[1] == NoSymbol && + keysyms[2] == NoSymbol && keysyms[3] == NoSymbol) { + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[0], &lKeysym, &uKeysym); + if (lKeysym != uKeysym) { + keysyms[0] = lKeysym; + keysyms[1] = uKeysym; + item.m_sensitive |= KeyModifierCapsLock; + } + } + else if (keysyms[0] != NoSymbol && keysyms[1] != NoSymbol) { + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[0], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[0] && + uKeysym == keysyms[1]) { + item.m_sensitive |= KeyModifierCapsLock; + } + else if (keysyms[2] != NoSymbol && keysyms[3] != NoSymbol) { + XConvertCase(keysyms[2], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[2] && + uKeysym == keysyms[3]) { + item.m_sensitive |= KeyModifierCapsLock; + } + } + } + + // key is sensitive to shift if keysyms in levels 0 and 1 or + // levels 2 and 3 don't match. it's also sensitive to shift + // if it's sensitive to caps-lock. + if ((item.m_sensitive & KeyModifierCapsLock) != 0) { + item.m_sensitive |= KeyModifierShift; + } + else if ((keysyms[0] != NoSymbol && keysyms[1] != NoSymbol && + keysyms[0] != keysyms[1]) || + (keysyms[2] != NoSymbol && keysyms[3] != NoSymbol && + keysyms[2] != keysyms[3])) { + item.m_sensitive |= KeyModifierShift; + } + + // key is sensitive to numlock if any keysym on it is + if (IsKeypadKey(keysyms[0]) || IsPrivateKeypadKey(keysyms[0]) || + IsKeypadKey(keysyms[1]) || IsPrivateKeypadKey(keysyms[1]) || + IsKeypadKey(keysyms[2]) || IsPrivateKeypadKey(keysyms[2]) || + IsKeypadKey(keysyms[3]) || IsPrivateKeypadKey(keysyms[3])) { + item.m_sensitive |= KeyModifierNumLock; + } + + // do each keysym (shift level) + for (int j = 0; j < maxKeysyms; ++j) { + item.m_id = XWindowsUtil::mapKeySymToKeyID(keysyms[j]); + if (item.m_id == kKeyNone) { + if (j != 0 && modifierButtons.count(keycode) > 0) { + // pretend the modifier works in other shift levels + // because it probably does. + if (keysyms[1] == NoSymbol || j != 3) { + item.m_id = XWindowsUtil::mapKeySymToKeyID(keysyms[0]); + } + else { + item.m_id = XWindowsUtil::mapKeySymToKeyID(keysyms[1]); + } + } + if (item.m_id == kKeyNone) { + continue; + } + } + + // group is 0 for levels 0 and 1 and 1 for levels 2 and 3 + item.m_group = (j >= 2) ? 1 : 0; + + // compute required modifiers + item.m_required = 0; + if ((j & 1) != 0) { + item.m_required |= KeyModifierShift; + } + if ((j & 2) != 0) { + item.m_required |= KeyModifierAltGr; + } + + item.m_generates = 0; + item.m_lock = false; + if (modifierButtons.count(keycode) > 0) { + // get flags for modifier keys + barrier::KeyMap::initModifierKey(item); + + // add mapping from X (unless we already have) + if (item.m_generates != 0) { + unsigned int bit = modifierButtons[keycode]; + if (m_modifierFromX[bit] == 0) { + m_modifierFromX[bit] = item.m_generates; + m_modifierToX[item.m_generates] = (1u << bit); + } + } + } + + // add key + keyMap.addKeyEntry(item); + m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode)); + + // add other ways to synthesize the key + if ((j & 1) != 0) { + // add capslock version of key is sensitive to capslock + KeySym lKeysym, uKeysym; + XConvertCase(keysyms[j], &lKeysym, &uKeysym); + if (lKeysym != uKeysym && + lKeysym == keysyms[j - 1] && + uKeysym == keysyms[j]) { + item.m_required &= ~KeyModifierShift; + item.m_required |= KeyModifierCapsLock; + keyMap.addKeyEntry(item); + item.m_required |= KeyModifierShift; + item.m_required &= ~KeyModifierCapsLock; + } + + // add numlock version of key if sensitive to numlock + if (IsKeypadKey(keysyms[j]) || IsPrivateKeypadKey(keysyms[j])) { + item.m_required &= ~KeyModifierShift; + item.m_required |= KeyModifierNumLock; + keyMap.addKeyEntry(item); + item.m_required |= KeyModifierShift; + item.m_required &= ~KeyModifierNumLock; + } + } + } + } + + delete[] allKeysyms; +} + +#if HAVE_XKB_EXTENSION +void +XWindowsKeyState::updateKeysymMapXKB(barrier::KeyMap& keyMap) +{ + static const XkbKTMapEntryRec defMapEntry = { + True, // active + 0, // level + { + 0, // mods.mask + 0, // mods.real_mods + 0 // mods.vmods + } + }; + + LOG((CLOG_DEBUG1 "XKB mapping")); + + // find the number of groups + int maxNumGroups = 0; + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + int numGroups = XkbKeyNumGroups(m_xkb, static_cast<KeyCode>(i)); + if (numGroups > maxNumGroups) { + maxNumGroups = numGroups; + } + } + + // prepare map from X modifier to KeyModifierMask + std::vector<int> modifierLevel(maxNumGroups * 8, 4); + m_modifierFromX.clear(); + m_modifierFromX.resize(maxNumGroups * 8); + m_modifierToX.clear(); + + // prepare map from KeyID to KeyCode + m_keyCodeFromKey.clear(); + + // Hack to deal with VMware. When a VMware client grabs input the + // player clears out the X modifier map for whatever reason. We're + // notified of the change and arrive here to discover that there + // are no modifiers at all. Since this prevents the modifiers from + // working in the VMware client we'll use the last known good set + // of modifiers when there are no modifiers. If there are modifiers + // we update the last known good set. + bool useLastGoodModifiers = !hasModifiersXKB(); + if (!useLastGoodModifiers) { + m_lastGoodXKBModifiers.clear(); + } + + // check every button. on this pass we save all modifiers as native + // X modifier masks. + barrier::KeyMap::KeyItem item; + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + KeyCode keycode = static_cast<KeyCode>(i); + item.m_button = static_cast<KeyButton>(keycode); + item.m_client = 0; + + // skip keys with no groups (they generate no symbols) + if (XkbKeyNumGroups(m_xkb, keycode) == 0) { + continue; + } + + // note half-duplex keys + const XkbBehavior& b = m_xkb->server->behaviors[keycode]; + if ((b.type & XkbKB_OpMask) == XkbKB_Lock) { + keyMap.addHalfDuplexButton(item.m_button); + } + + // iterate over all groups + for (int group = 0; group < maxNumGroups; ++group) { + item.m_group = group; + int eGroup = getEffectiveGroup(keycode, group); + + // get key info + XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, keycode, eGroup); + + // set modifiers the item is sensitive to + item.m_sensitive = type->mods.mask; + + // iterate over all shift levels for the button (including none) + for (int j = -1; j < type->map_count; ++j) { + const XkbKTMapEntryRec* mapEntry = + ((j == -1) ? &defMapEntry : type->map + j); + if (!mapEntry->active) { + continue; + } + int level = mapEntry->level; + + // set required modifiers for this item + item.m_required = mapEntry->mods.mask; + if ((item.m_required & LockMask) != 0 && + j != -1 && type->preserve != NULL && + (type->preserve[j].mask & LockMask) != 0) { + // sensitive caps lock and we preserve caps-lock. + // preserving caps-lock means we Xlib functions would + // yield the capitialized KeySym so we'll adjust the + // level accordingly. + if ((level ^ 1) < type->num_levels) { + level ^= 1; + } + } + + // get the keysym for this item + KeySym keysym = XkbKeySymEntry(m_xkb, keycode, level, eGroup); + + // check for group change actions, locking modifiers, and + // modifier masks. + item.m_lock = false; + bool isModifier = false; + UInt32 modifierMask = m_xkb->map->modmap[keycode]; + if (XkbKeyHasActions(m_xkb, keycode) == True) { + XkbAction* action = + XkbKeyActionEntry(m_xkb, keycode, level, eGroup); + if (action->type == XkbSA_SetMods || + action->type == XkbSA_LockMods) { + isModifier = true; + + // note toggles + item.m_lock = (action->type == XkbSA_LockMods); + + // maybe use action's mask + if ((action->mods.flags & XkbSA_UseModMapMods) == 0) { + modifierMask = action->mods.mask; + } + } + else if (action->type == XkbSA_SetGroup || + action->type == XkbSA_LatchGroup || + action->type == XkbSA_LockGroup) { + // ignore group change key + continue; + } + } + level = mapEntry->level; + + // VMware modifier hack + if (useLastGoodModifiers) { + XKBModifierMap::const_iterator k = + m_lastGoodXKBModifiers.find(eGroup * 256 + keycode); + if (k != m_lastGoodXKBModifiers.end()) { + // Use last known good modifier + isModifier = true; + level = k->second.m_level; + modifierMask = k->second.m_mask; + item.m_lock = k->second.m_lock; + } + } + else if (isModifier) { + // Save known good modifier + XKBModifierInfo& info = + m_lastGoodXKBModifiers[eGroup * 256 + keycode]; + info.m_level = level; + info.m_mask = modifierMask; + info.m_lock = item.m_lock; + } + + // record the modifier mask for this key. don't bother + // for keys that change the group. + item.m_generates = 0; + UInt32 modifierBit = + XWindowsUtil::getModifierBitForKeySym(keysym); + if (isModifier && modifierBit != kKeyModifierBitNone) { + item.m_generates = (1u << modifierBit); + for (SInt32 j = 0; j < 8; ++j) { + // skip modifiers this key doesn't generate + if ((modifierMask & (1u << j)) == 0) { + continue; + } + + // skip keys that map to a modifier that we've + // already seen using fewer modifiers. that is + // if this key must combine with other modifiers + // and we know of a key that combines with fewer + // modifiers (or no modifiers) then prefer the + // other key. + if (level >= modifierLevel[8 * group + j]) { + continue; + } + modifierLevel[8 * group + j] = level; + + // save modifier + m_modifierFromX[8 * group + j] |= (1u << modifierBit); + m_modifierToX.insert(std::make_pair( + 1u << modifierBit, 1u << j)); + } + } + + // handle special cases of just one keysym for the keycode + if (type->num_levels == 1) { + // if there are upper- and lowercase versions of the + // keysym then add both. + KeySym lKeysym, uKeysym; + XConvertCase(keysym, &lKeysym, &uKeysym); + if (lKeysym != uKeysym) { + if (j != -1) { + continue; + } + + item.m_sensitive |= ShiftMask | LockMask; + + KeyID lKeyID = XWindowsUtil::mapKeySymToKeyID(lKeysym); + KeyID uKeyID = XWindowsUtil::mapKeySymToKeyID(uKeysym); + if (lKeyID == kKeyNone || uKeyID == kKeyNone) { + continue; + } + + item.m_id = lKeyID; + item.m_required = 0; + keyMap.addKeyEntry(item); + + item.m_id = uKeyID; + item.m_required = ShiftMask; + keyMap.addKeyEntry(item); + item.m_required = LockMask; + keyMap.addKeyEntry(item); + + if (group == 0) { + m_keyCodeFromKey.insert( + std::make_pair(lKeyID, keycode)); + m_keyCodeFromKey.insert( + std::make_pair(uKeyID, keycode)); + } + continue; + } + } + + // add entry + item.m_id = XWindowsUtil::mapKeySymToKeyID(keysym); + keyMap.addKeyEntry(item); + if (group == 0) { + m_keyCodeFromKey.insert(std::make_pair(item.m_id, keycode)); + } + } + } + } + + // change all modifier masks to barrier masks from X masks + keyMap.foreachKey(&XWindowsKeyState::remapKeyModifiers, this); + + // allow composition across groups + keyMap.allowGroupSwitchDuringCompose(); +} +#endif + +void +XWindowsKeyState::remapKeyModifiers(KeyID id, SInt32 group, + barrier::KeyMap::KeyItem& item, void* vself) +{ + XWindowsKeyState* self = static_cast<XWindowsKeyState*>(vself); + item.m_required = + self->mapModifiersFromX(XkbBuildCoreState(item.m_required, group)); + item.m_sensitive = + self->mapModifiersFromX(XkbBuildCoreState(item.m_sensitive, group)); +} + +bool +XWindowsKeyState::hasModifiersXKB() const +{ +#if HAVE_XKB_EXTENSION + // iterate over all keycodes + for (int i = m_xkb->min_key_code; i <= m_xkb->max_key_code; ++i) { + KeyCode keycode = static_cast<KeyCode>(i); + if (XkbKeyHasActions(m_xkb, keycode) == True) { + // iterate over all groups + int numGroups = XkbKeyNumGroups(m_xkb, keycode); + for (int group = 0; group < numGroups; ++group) { + // iterate over all shift levels for the button (including none) + XkbKeyTypePtr type = XkbKeyKeyType(m_xkb, keycode, group); + for (int j = -1; j < type->map_count; ++j) { + if (j != -1 && !type->map[j].active) { + continue; + } + int level = ((j == -1) ? 0 : type->map[j].level); + XkbAction* action = + XkbKeyActionEntry(m_xkb, keycode, level, group); + if (action->type == XkbSA_SetMods || + action->type == XkbSA_LockMods) { + return true; + } + } + } + } + } +#endif + return false; +} + +int +XWindowsKeyState::getEffectiveGroup(KeyCode keycode, int group) const +{ + (void)keycode; +#if HAVE_XKB_EXTENSION + // get effective group for key + int numGroups = XkbKeyNumGroups(m_xkb, keycode); + if (group >= numGroups) { + unsigned char groupInfo = XkbKeyGroupInfo(m_xkb, keycode); + switch (XkbOutOfRangeGroupAction(groupInfo)) { + case XkbClampIntoRange: + group = numGroups - 1; + break; + + case XkbRedirectIntoRange: + group = XkbOutOfRangeGroupNumber(groupInfo); + if (group >= numGroups) { + group = 0; + } + break; + + default: + // wrap + group %= numGroups; + break; + } + } +#endif + return group; +} + +UInt32 +XWindowsKeyState::getGroupFromState(unsigned int state) const +{ +#if HAVE_XKB_EXTENSION + if (m_xkb != NULL) { + return XkbGroupForCoreState(state); + } +#endif + return 0; +} diff --git a/src/lib/platform/XWindowsKeyState.h b/src/lib/platform/XWindowsKeyState.h new file mode 100644 index 0000000..f3c0a1e --- /dev/null +++ b/src/lib/platform/XWindowsKeyState.h @@ -0,0 +1,174 @@ +/* + * 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 "barrier/KeyState.h" +#include "common/stdmap.h" +#include "common/stdvector.h" + +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/Xlib.h> +# if HAVE_X11_EXTENSIONS_XTEST_H +# include <X11/extensions/XTest.h> +# else +# error The XTest extension is required to build barrier +# endif +# if HAVE_XKB_EXTENSION +# include <X11/extensions/XKBstr.h> +# endif +#endif + +class IEventQueue; + +//! X Windows key state +/*! +A key state for X Windows. +*/ +class XWindowsKeyState : public KeyState { +public: + typedef std::vector<int> KeycodeList; + enum { + kGroupPoll = -1, + kGroupPollAndSet = -2 + }; + + XWindowsKeyState(Display*, bool useXKB, IEventQueue* events); + XWindowsKeyState(Display*, bool useXKB, + IEventQueue* events, barrier::KeyMap& keyMap); + ~XWindowsKeyState(); + + //! @name modifiers + //@{ + + //! Set active group + /*! + Sets the active group to \p group. This is the group returned by + \c pollActiveGroup(). If \p group is \c kGroupPoll then + \c pollActiveGroup() will really poll, but that's a slow operation + on X11. If \p group is \c kGroupPollAndSet then this will poll the + active group now and use it for future calls to \c pollActiveGroup(). + */ + void setActiveGroup(SInt32 group); + + //! Set the auto-repeat state + /*! + Sets the auto-repeat state. + */ + void setAutoRepeat(const XKeyboardState&); + + //@} + //! @name accessors + //@{ + + //! Convert X modifier mask to barrier mask + /*! + Returns the barrier modifier mask corresponding to the X modifier + mask in \p state. + */ + KeyModifierMask mapModifiersFromX(unsigned int state) const; + + //! Convert barrier modifier mask to X mask + /*! + Converts the barrier modifier mask to the corresponding X modifier + mask. Returns \c true if successful and \c false if any modifier + could not be converted. + */ + bool mapModifiersToX(KeyModifierMask, unsigned int&) const; + + //! Convert barrier key to all corresponding X keycodes + /*! + Converts the barrier key \p key to all of the keycodes that map to + that key. + */ + void mapKeyToKeycodes(KeyID key, + KeycodeList& keycodes) const; + + //@} + + // IKeyState overrides + virtual bool fakeCtrlAltDel(); + virtual KeyModifierMask + pollActiveModifiers() const; + virtual SInt32 pollActiveGroup() const; + virtual void pollPressedKeys(KeyButtonSet& pressedKeys) const; + +protected: + // KeyState overrides + virtual void getKeyMap(barrier::KeyMap& keyMap); + virtual void fakeKey(const Keystroke& keystroke); + +private: + void init(Display* display, bool useXKB); + void updateKeysymMap(barrier::KeyMap&); + void updateKeysymMapXKB(barrier::KeyMap&); + bool hasModifiersXKB() const; + int getEffectiveGroup(KeyCode, int group) const; + UInt32 getGroupFromState(unsigned int state) const; + + static void remapKeyModifiers(KeyID, SInt32, + barrier::KeyMap::KeyItem&, void*); + +private: + struct XKBModifierInfo { + public: + unsigned char m_level; + UInt32 m_mask; + bool m_lock; + }; + +#ifdef TEST_ENV +public: // yuck +#endif + typedef std::vector<KeyModifierMask> KeyModifierMaskList; + +private: + typedef std::map<KeyModifierMask, unsigned int> KeyModifierToXMask; + typedef std::multimap<KeyID, KeyCode> KeyToKeyCodeMap; + typedef std::map<KeyCode, unsigned int> NonXKBModifierMap; + typedef std::map<UInt32, XKBModifierInfo> XKBModifierMap; + + Display* m_display; +#if HAVE_XKB_EXTENSION + XkbDescPtr m_xkb; +#endif + SInt32 m_group; + XKBModifierMap m_lastGoodXKBModifiers; + NonXKBModifierMap m_lastGoodNonXKBModifiers; + + // X modifier (bit number) to barrier modifier (mask) mapping + KeyModifierMaskList m_modifierFromX; + + // barrier modifier (mask) to X modifier (mask) + KeyModifierToXMask m_modifierToX; + + // map KeyID to all keycodes that can synthesize that KeyID + KeyToKeyCodeMap m_keyCodeFromKey; + + // autorepeat state + XKeyboardState m_keyboardState; + +#ifdef TEST_ENV +public: + SInt32 group() const { return m_group; } + void group(const SInt32& group) { m_group = group; } + KeyModifierMaskList modifierFromX() const { return m_modifierFromX; } +#endif +}; diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp new file mode 100644 index 0000000..581c911 --- /dev/null +++ b/src/lib/platform/XWindowsScreen.cpp @@ -0,0 +1,2096 @@ +/* + * 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 "platform/XWindowsScreen.h" + +#include "platform/XWindowsClipboard.h" +#include "platform/XWindowsEventQueueBuffer.h" +#include "platform/XWindowsKeyState.h" +#include "platform/XWindowsScreenSaver.h" +#include "platform/XWindowsUtil.h" +#include "barrier/Clipboard.h" +#include "barrier/KeyMap.h" +#include "barrier/XScreen.h" +#include "arch/XArch.h" +#include "arch/Arch.h" +#include "base/Log.h" +#include "base/Stopwatch.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +#include <cstring> +#include <cstdlib> +#include <algorithm> +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/X.h> +# include <X11/Xutil.h> +# define XK_MISCELLANY +# define XK_XKB_KEYS +# include <X11/keysymdef.h> +# if HAVE_X11_EXTENSIONS_DPMS_H + extern "C" { +# include <X11/extensions/dpms.h> + } +# endif +# if HAVE_X11_EXTENSIONS_XTEST_H +# include <X11/extensions/XTest.h> +# else +# error The XTest extension is required to build barrier +# endif +# if HAVE_X11_EXTENSIONS_XINERAMA_H + // Xinerama.h may lack extern "C" for inclusion by C++ + extern "C" { +# include <X11/extensions/Xinerama.h> + } +# endif +# if HAVE_X11_EXTENSIONS_XRANDR_H +# include <X11/extensions/Xrandr.h> +# endif +# if HAVE_XKB_EXTENSION +# include <X11/XKBlib.h> +# endif +# ifdef HAVE_XI2 +# include <X11/extensions/XInput2.h> +# endif +#endif + +static int xi_opcode; + +// +// XWindowsScreen +// + +// NOTE -- the X display is shared among several objects but is owned +// by the XWindowsScreen. Xlib is not reentrant so we must ensure +// that no two objects can simultaneously call Xlib with the display. +// this is easy since we only make X11 calls from the main thread. +// we must also ensure that these objects do not use the display in +// their destructors or, if they do, we can tell them not to. This +// is to handle unexpected disconnection of the X display, when any +// call on the display is invalid. In that situation we discard the +// display and the X11 event queue buffer, ignore any calls that try +// to use the display, and wait to be destroyed. + +XWindowsScreen* XWindowsScreen::s_screen = NULL; + +XWindowsScreen::XWindowsScreen( + const char* displayName, + bool isPrimary, + bool disableXInitThreads, + int mouseScrollDelta, + IEventQueue* events) : + m_isPrimary(isPrimary), + m_mouseScrollDelta(mouseScrollDelta), + m_display(NULL), + m_root(None), + m_window(None), + m_isOnScreen(m_isPrimary), + m_x(0), m_y(0), + m_w(0), m_h(0), + m_xCenter(0), m_yCenter(0), + m_xCursor(0), m_yCursor(0), + m_keyState(NULL), + m_lastFocus(None), + m_lastFocusRevert(RevertToNone), + m_im(NULL), + m_ic(NULL), + m_lastKeycode(0), + m_sequenceNumber(0), + m_screensaver(NULL), + m_screensaverNotify(false), + m_xtestIsXineramaUnaware(true), + m_preserveFocus(false), + m_xkb(false), + m_xi2detected(false), + m_xrandr(false), + m_events(events), + PlatformScreen(events) +{ + assert(s_screen == NULL); + + if (mouseScrollDelta==0) m_mouseScrollDelta=120; + s_screen = this; + + if (!disableXInitThreads) { + // initializes Xlib support for concurrent threads. + if (XInitThreads() == 0) + throw XArch("XInitThreads() returned zero"); + } else { + LOG((CLOG_DEBUG "skipping XInitThreads()")); + } + + // set the X I/O error handler so we catch the display disconnecting + XSetIOErrorHandler(&XWindowsScreen::ioErrorHandler); + + try { + m_display = openDisplay(displayName); + m_root = DefaultRootWindow(m_display); + saveShape(); + m_window = openWindow(); + m_screensaver = new XWindowsScreenSaver(m_display, + m_window, getEventTarget(), events); + m_keyState = new XWindowsKeyState(m_display, m_xkb, events, m_keyMap); + LOG((CLOG_DEBUG "screen shape: %d,%d %dx%d %s", m_x, m_y, m_w, m_h, m_xinerama ? "(xinerama)" : "")); + LOG((CLOG_DEBUG "window is 0x%08x", m_window)); + } + catch (...) { + if (m_display != NULL) { + XCloseDisplay(m_display); + } + throw; + } + + // primary/secondary screen only initialization + if (m_isPrimary) { +#ifdef HAVE_XI2 + m_xi2detected = detectXI2(); + if (m_xi2detected) { + selectXIRawMotion(); + } else +#endif + { + // start watching for events on other windows + selectEvents(m_root); + } + + // prepare to use input methods + openIM(); + } + else { + // become impervious to server grabs + XTestGrabControl(m_display, True); + } + + // initialize the clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_clipboard[id] = new XWindowsClipboard(m_display, m_window, id); + } + + // install event handlers + m_events->adoptHandler(Event::kSystem, m_events->getSystemTarget(), + new TMethodEventJob<XWindowsScreen>(this, + &XWindowsScreen::handleSystemEvent)); + + // install the platform event queue + m_events->adoptBuffer(new XWindowsEventQueueBuffer( + m_display, m_window, m_events)); +} + +XWindowsScreen::~XWindowsScreen() +{ + assert(s_screen != NULL); + assert(m_display != NULL); + + m_events->adoptBuffer(NULL); + m_events->removeHandler(Event::kSystem, m_events->getSystemTarget()); + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + delete m_clipboard[id]; + } + delete m_keyState; + delete m_screensaver; + m_keyState = NULL; + m_screensaver = NULL; + if (m_display != NULL) { + // FIXME -- is it safe to clean up the IC and IM without a display? + if (m_ic != NULL) { + XDestroyIC(m_ic); + } + if (m_im != NULL) { + XCloseIM(m_im); + } + XDestroyWindow(m_display, m_window); + XCloseDisplay(m_display); + } + XSetIOErrorHandler(NULL); + + s_screen = NULL; +} + +void +XWindowsScreen::enable() +{ + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + + // raise and show the window + // FIXME -- take focus? + XMapRaised(m_display, m_window); + + // warp the mouse to the cursor center + fakeMouseMove(m_xCenter, m_yCenter); + } +} + +void +XWindowsScreen::disable() +{ + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + + // restore auto-repeat state + if (!m_isPrimary && m_autoRepeat) { + //XAutoRepeatOn(m_display); + } +} + +void +XWindowsScreen::enter() +{ + screensaver(false); + + // release input context focus + if (m_ic != NULL) { + XUnsetICFocus(m_ic); + } + + // set the input focus to what it had been when we took it + if (m_lastFocus != None) { + // the window may not exist anymore so ignore errors + XWindowsUtil::ErrorLock lock(m_display); + XSetInputFocus(m_display, m_lastFocus, m_lastFocusRevert, CurrentTime); + } + + #if HAVE_X11_EXTENSIONS_DPMS_H + // Force the DPMS to turn screen back on since we don't + // actually cause physical hardware input to trigger it + int dummy; + CARD16 powerlevel; + BOOL enabled; + if (DPMSQueryExtension(m_display, &dummy, &dummy) && + DPMSCapable(m_display) && + DPMSInfo(m_display, &powerlevel, &enabled)) + { + if (enabled && powerlevel != DPMSModeOn) + DPMSForceLevel(m_display, DPMSModeOn); + } + #endif + + // unmap the hider/grab window. this also ungrabs the mouse and + // keyboard if they're grabbed. + XUnmapWindow(m_display, m_window); + +/* maybe call this if entering for the screensaver + // set keyboard focus to root window. the screensaver should then + // pick up key events for when the user enters a password to unlock. + XSetInputFocus(m_display, PointerRoot, PointerRoot, CurrentTime); +*/ + + if (!m_isPrimary) { + // get the keyboard control state + XKeyboardState keyControl; + XGetKeyboardControl(m_display, &keyControl); + m_autoRepeat = (keyControl.global_auto_repeat == AutoRepeatModeOn); + m_keyState->setAutoRepeat(keyControl); + + // turn off auto-repeat. we do this so fake key press events don't + // cause the local server to generate their own auto-repeats of + // those keys. + //XAutoRepeatOff(m_display); + } + + // now on screen + m_isOnScreen = true; +} + +bool +XWindowsScreen::leave() +{ + if (!m_isPrimary) { + // restore the previous keyboard auto-repeat state. if the user + // changed the auto-repeat configuration while on the client then + // that state is lost. that's because we can't get notified by + // the X server when the auto-repeat configuration is changed so + // we can't track the desired configuration. + if (m_autoRepeat) { + //XAutoRepeatOn(m_display); + } + + // move hider window under the cursor center + XMoveWindow(m_display, m_window, m_xCenter, m_yCenter); + } + + // raise and show the window + XMapRaised(m_display, m_window); + + // grab the mouse and keyboard, if primary and possible + if (m_isPrimary && !grabMouseAndKeyboard()) { + XUnmapWindow(m_display, m_window); + return false; + } + + // save current focus + XGetInputFocus(m_display, &m_lastFocus, &m_lastFocusRevert); + + // take focus + if (m_isPrimary || !m_preserveFocus) { + XSetInputFocus(m_display, m_window, RevertToPointerRoot, CurrentTime); + } + + // now warp the mouse. we warp after showing the window so we're + // guaranteed to get the mouse leave event and to prevent the + // keyboard focus from changing under point-to-focus policies. + if (m_isPrimary) { + warpCursor(m_xCenter, m_yCenter); + } + else { + fakeMouseMove(m_xCenter, m_yCenter); + } + + // set input context focus to our window + if (m_ic != NULL) { + XmbResetIC(m_ic); + XSetICFocus(m_ic); + m_filtered.clear(); + } + + // now off screen + m_isOnScreen = false; + + return true; +} + +bool +XWindowsScreen::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = XWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + if (clipboard != NULL) { + // save clipboard data + return Clipboard::copy(m_clipboard[id], clipboard, timestamp); + } + else { + // assert clipboard ownership + if (!m_clipboard[id]->open(timestamp)) { + return false; + } + m_clipboard[id]->empty(); + m_clipboard[id]->close(); + return true; + } +} + +void +XWindowsScreen::checkClipboards() +{ + // do nothing, we're always up to date +} + +void +XWindowsScreen::openScreensaver(bool notify) +{ + m_screensaverNotify = notify; + if (!m_screensaverNotify) { + m_screensaver->disable(); + } +} + +void +XWindowsScreen::closeScreensaver() +{ + if (!m_screensaverNotify) { + m_screensaver->enable(); + } +} + +void +XWindowsScreen::screensaver(bool activate) +{ + if (activate) { + m_screensaver->activate(); + } + else { + m_screensaver->deactivate(); + } +} + +void +XWindowsScreen::resetOptions() +{ + m_xtestIsXineramaUnaware = true; + m_preserveFocus = false; +} + +void +XWindowsScreen::setOptions(const OptionsList& options) +{ + for (UInt32 i = 0, n = options.size(); i < n; i += 2) { + if (options[i] == kOptionXTestXineramaUnaware) { + m_xtestIsXineramaUnaware = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "XTest is Xinerama unaware %s", m_xtestIsXineramaUnaware ? "true" : "false")); + } + else if (options[i] == kOptionScreenPreserveFocus) { + m_preserveFocus = (options[i + 1] != 0); + LOG((CLOG_DEBUG1 "Preserve Focus = %s", m_preserveFocus ? "true" : "false")); + } + } +} + +void +XWindowsScreen::setSequenceNumber(UInt32 seqNum) +{ + m_sequenceNumber = seqNum; +} + +bool +XWindowsScreen::isPrimary() const +{ + return m_isPrimary; +} + +void* +XWindowsScreen::getEventTarget() const +{ + return const_cast<XWindowsScreen*>(this); +} + +bool +XWindowsScreen::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + assert(clipboard != NULL); + + // fail if we don't have the requested clipboard + if (m_clipboard[id] == NULL) { + return false; + } + + // get the actual time. ICCCM does not allow CurrentTime. + Time timestamp = XWindowsUtil::getCurrentTime( + m_display, m_clipboard[id]->getWindow()); + + // copy the clipboard + return Clipboard::copy(clipboard, m_clipboard[id], timestamp); +} + +void +XWindowsScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_x; + y = m_y; + w = m_w; + h = m_h; +} + +void +XWindowsScreen::getCursorPos(SInt32& x, SInt32& y) const +{ + Window root, window; + int mx, my, xWindow, yWindow; + unsigned int mask; + if (XQueryPointer(m_display, m_root, &root, &window, + &mx, &my, &xWindow, &yWindow, &mask)) { + x = mx; + y = my; + } + else { + x = m_xCenter; + y = m_yCenter; + } +} + +void +XWindowsScreen::reconfigure(UInt32) +{ + // do nothing +} + +void +XWindowsScreen::warpCursor(SInt32 x, SInt32 y) +{ + // warp mouse + warpCursorNoFlush(x, y); + + // remove all input events before and including warp + XEvent event; + while (XCheckMaskEvent(m_display, PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask, + &event)) { + // do nothing + } + + // save position as last position + m_xCursor = x; + m_yCursor = y; +} + +UInt32 +XWindowsScreen::registerHotKey(KeyID key, KeyModifierMask mask) +{ + // only allow certain modifiers + if ((mask & ~(KeyModifierShift | KeyModifierControl | + KeyModifierAlt | KeyModifierSuper)) != 0) { + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // fail if no keys + if (key == kKeyNone && mask == 0) { + return 0; + } + + // convert to X + unsigned int modifiers; + if (!m_keyState->mapModifiersToX(mask, modifiers)) { + // can't map all modifiers + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + XWindowsKeyState::KeycodeList keycodes; + m_keyState->mapKeyToKeycodes(key, keycodes); + if (key != kKeyNone && keycodes.empty()) { + // can't map key + LOG((CLOG_DEBUG "could not map hotkey id=%04x mask=%04x", key, mask)); + return 0; + } + + // choose hotkey id + UInt32 id; + if (!m_oldHotKeyIDs.empty()) { + id = m_oldHotKeyIDs.back(); + m_oldHotKeyIDs.pop_back(); + } + else { + id = m_hotKeys.size() + 1; + } + HotKeyList& hotKeys = m_hotKeys[id]; + + // all modifier hotkey must be treated specially. for each modifier + // we need to grab the modifier key in combination with all the other + // requested modifiers. + bool err = false; + { + XWindowsUtil::ErrorLock lock(m_display, &err); + if (key == kKeyNone) { + static const KeyModifierMask s_hotKeyModifiers[] = { + KeyModifierShift, + KeyModifierControl, + KeyModifierAlt, + KeyModifierMeta, + KeyModifierSuper + }; + + XModifierKeymap* modKeymap = XGetModifierMapping(m_display); + for (size_t j = 0; j < sizeof(s_hotKeyModifiers) / + sizeof(s_hotKeyModifiers[0]) && !err; ++j) { + // skip modifier if not in mask + if ((mask & s_hotKeyModifiers[j]) == 0) { + continue; + } + + // skip with error if we can't map remaining modifiers + unsigned int modifiers2; + KeyModifierMask mask2 = (mask & ~s_hotKeyModifiers[j]); + if (!m_keyState->mapModifiersToX(mask2, modifiers2)) { + err = true; + continue; + } + + // compute modifier index for modifier. there should be + // exactly one X modifier missing + int index; + switch (modifiers ^ modifiers2) { + case ShiftMask: + index = ShiftMapIndex; + break; + + case LockMask: + index = LockMapIndex; + break; + + case ControlMask: + index = ControlMapIndex; + break; + + case Mod1Mask: + index = Mod1MapIndex; + break; + + case Mod2Mask: + index = Mod2MapIndex; + break; + + case Mod3Mask: + index = Mod3MapIndex; + break; + + case Mod4Mask: + index = Mod4MapIndex; + break; + + case Mod5Mask: + index = Mod5MapIndex; + break; + + default: + err = true; + continue; + } + + // grab each key for the modifier + const KeyCode* modifiermap = + modKeymap->modifiermap + index * modKeymap->max_keypermod; + for (int k = 0; k < modKeymap->max_keypermod && !err; ++k) { + KeyCode code = modifiermap[k]; + if (modifiermap[k] != 0) { + XGrabKey(m_display, code, modifiers2, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(code, modifiers2)); + m_hotKeyToIDMap[HotKeyItem(code, modifiers2)] = id; + } + } + } + } + XFreeModifiermap(modKeymap); + } + + // a non-modifier key must be insensitive to CapsLock, NumLock and + // ScrollLock, so we have to grab the key with every combination of + // those. + else { + // collect available toggle modifiers + unsigned int modifier; + unsigned int toggleModifiers[3]; + size_t numToggleModifiers = 0; + if (m_keyState->mapModifiersToX(KeyModifierCapsLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierNumLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + if (m_keyState->mapModifiersToX(KeyModifierScrollLock, modifier)) { + toggleModifiers[numToggleModifiers++] = modifier; + } + + + for (XWindowsKeyState::KeycodeList::iterator j = keycodes.begin(); + j != keycodes.end() && !err; ++j) { + for (size_t i = 0; i < (1u << numToggleModifiers); ++i) { + // add toggle modifiers for index i + unsigned int tmpModifiers = modifiers; + if ((i & 1) != 0) { + tmpModifiers |= toggleModifiers[0]; + } + if ((i & 2) != 0) { + tmpModifiers |= toggleModifiers[1]; + } + if ((i & 4) != 0) { + tmpModifiers |= toggleModifiers[2]; + } + + // add grab + XGrabKey(m_display, *j, tmpModifiers, m_root, + False, GrabModeAsync, GrabModeAsync); + if (!err) { + hotKeys.push_back(std::make_pair(*j, tmpModifiers)); + m_hotKeyToIDMap[HotKeyItem(*j, tmpModifiers)] = id; + } + } + } + } + } + + if (err) { + // if any failed then unregister any we did get + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(HotKeyItem(j->first, j->second)); + } + + m_oldHotKeyIDs.push_back(id); + m_hotKeys.erase(id); + LOG((CLOG_WARN "failed to register hotkey %s (id=%04x mask=%04x)", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask)); + return 0; + } + + LOG((CLOG_DEBUG "registered hotkey %s (id=%04x mask=%04x) as id=%d", barrier::KeyMap::formatKey(key, mask).c_str(), key, mask, id)); + return id; +} + +void +XWindowsScreen::unregisterHotKey(UInt32 id) +{ + // look up hotkey + HotKeyMap::iterator i = m_hotKeys.find(id); + if (i == m_hotKeys.end()) { + return; + } + + // unregister with OS + bool err = false; + { + XWindowsUtil::ErrorLock lock(m_display, &err); + HotKeyList& hotKeys = i->second; + for (HotKeyList::iterator j = hotKeys.begin(); + j != hotKeys.end(); ++j) { + XUngrabKey(m_display, j->first, j->second, m_root); + m_hotKeyToIDMap.erase(HotKeyItem(j->first, j->second)); + } + } + if (err) { + LOG((CLOG_WARN "failed to unregister hotkey id=%d", id)); + } + else { + LOG((CLOG_DEBUG "unregistered hotkey id=%d", id)); + } + + // discard hot key from map and record old id for reuse + m_hotKeys.erase(i); + m_oldHotKeyIDs.push_back(id); +} + +void +XWindowsScreen::fakeInputBegin() +{ + // FIXME -- not implemented +} + +void +XWindowsScreen::fakeInputEnd() +{ + // FIXME -- not implemented +} + +SInt32 +XWindowsScreen::getJumpZoneSize() const +{ + return 1; +} + +bool +XWindowsScreen::isAnyMouseButtonDown(UInt32& buttonID) const +{ + // query the pointer to get the button state + Window root, window; + int xRoot, yRoot, xWindow, yWindow; + unsigned int state; + if (XQueryPointer(m_display, m_root, &root, &window, + &xRoot, &yRoot, &xWindow, &yWindow, &state)) { + return ((state & (Button1Mask | Button2Mask | Button3Mask | + Button4Mask | Button5Mask)) != 0); + } + + return false; +} + +void +XWindowsScreen::getCursorCenter(SInt32& x, SInt32& y) const +{ + x = m_xCenter; + y = m_yCenter; +} + +void +XWindowsScreen::fakeMouseButton(ButtonID button, bool press) +{ + const unsigned int xButton = mapButtonToX(button); + if (xButton > 0 && xButton < 11) { + XTestFakeButtonEvent(m_display, xButton, + press ? True : False, CurrentTime); + XFlush(m_display); + } +} + +void +XWindowsScreen::fakeMouseMove(SInt32 x, SInt32 y) +{ + if (m_xinerama && m_xtestIsXineramaUnaware) { + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeMotionEvent(m_display, DefaultScreen(m_display), + x, y, CurrentTime); + } + XFlush(m_display); +} + +void +XWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const +{ + // FIXME -- ignore xinerama for now + if (false && m_xinerama && m_xtestIsXineramaUnaware) { +// XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + } + else { + XTestFakeRelativeMotionEvent(m_display, dx, dy, CurrentTime); + } + XFlush(m_display); +} + +void +XWindowsScreen::fakeMouseWheel(SInt32, SInt32 yDelta) const +{ + // XXX -- support x-axis scrolling + if (yDelta == 0) { + return; + } + + // choose button depending on rotation direction + const unsigned int xButton = mapButtonToX(static_cast<ButtonID>( + (yDelta >= 0) ? -1 : -2)); + if (xButton == 0) { + // If we get here, then the XServer does not support the scroll + // wheel buttons, so send PageUp/PageDown keystrokes instead. + // Patch by Tom Chadwick. + KeyCode keycode = 0; + if (yDelta >= 0) { + keycode = XKeysymToKeycode(m_display, XK_Page_Up); + } + else { + keycode = XKeysymToKeycode(m_display, XK_Page_Down); + } + if (keycode != 0) { + XTestFakeKeyEvent(m_display, keycode, True, CurrentTime); + XTestFakeKeyEvent(m_display, keycode, False, CurrentTime); + } + return; + } + + // now use absolute value of delta + if (yDelta < 0) { + yDelta = -yDelta; + } + + if (yDelta < m_mouseScrollDelta) { + LOG((CLOG_WARN "Wheel scroll delta (%d) smaller than threshold (%d)", yDelta, m_mouseScrollDelta)); + } + + // send as many clicks as necessary + for (; yDelta >= m_mouseScrollDelta; yDelta -= m_mouseScrollDelta) { + XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); + XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); + } + XFlush(m_display); +} + +Display* +XWindowsScreen::openDisplay(const char* displayName) +{ + // get the DISPLAY + if (displayName == NULL) { + displayName = getenv("DISPLAY"); + if (displayName == NULL) { + displayName = ":0.0"; + } + } + + // open the display + LOG((CLOG_DEBUG "XOpenDisplay(\"%s\")", displayName)); + Display* display = XOpenDisplay(displayName); + if (display == NULL) { + throw XScreenUnavailable(60.0); + } + + // verify the availability of the XTest extension + if (!m_isPrimary) { + int majorOpcode, firstEvent, firstError; + if (!XQueryExtension(display, XTestExtensionName, + &majorOpcode, &firstEvent, &firstError)) { + LOG((CLOG_ERR "XTEST extension not available")); + XCloseDisplay(display); + throw XScreenOpenFailure(); + } + } + +#if HAVE_XKB_EXTENSION + { + m_xkb = false; + int major = XkbMajorVersion, minor = XkbMinorVersion; + if (XkbLibraryVersion(&major, &minor)) { + int opcode, firstError; + if (XkbQueryExtension(display, &opcode, &m_xkbEventBase, + &firstError, &major, &minor)) { + m_xkb = true; + XkbSelectEvents(display, XkbUseCoreKbd, + XkbMapNotifyMask, XkbMapNotifyMask); + XkbSelectEventDetails(display, XkbUseCoreKbd, + XkbStateNotifyMask, + XkbGroupStateMask, XkbGroupStateMask); + } + } + } +#endif + +#if HAVE_X11_EXTENSIONS_XRANDR_H + // query for XRandR extension + int dummyError; + m_xrandr = XRRQueryExtension(display, &m_xrandrEventBase, &dummyError); + if (m_xrandr) { + // enable XRRScreenChangeNotifyEvent + XRRSelectInput(display, DefaultRootWindow(display), RRScreenChangeNotifyMask | RRCrtcChangeNotifyMask); + } +#endif + + return display; +} + +void +XWindowsScreen::saveShape() +{ + // get shape of default screen + m_x = 0; + m_y = 0; + + m_w = WidthOfScreen(DefaultScreenOfDisplay(m_display)); + m_h = HeightOfScreen(DefaultScreenOfDisplay(m_display)); + + // get center of default screen + m_xCenter = m_x + (m_w >> 1); + m_yCenter = m_y + (m_h >> 1); + + // check if xinerama is enabled and there is more than one screen. + // get center of first Xinerama screen. Xinerama appears to have + // a bug when XWarpPointer() is used in combination with + // XGrabPointer(). in that case, the warp is successful but the + // next pointer motion warps the pointer again, apparently to + // constrain it to some unknown region, possibly the region from + // 0,0 to Wm,Hm where Wm (Hm) is the minimum width (height) over + // all physical screens. this warp only seems to happen if the + // pointer wasn't in that region before the XWarpPointer(). the + // second (unexpected) warp causes barrier to think the pointer + // has been moved when it hasn't. to work around the problem, + // we warp the pointer to the center of the first physical + // screen instead of the logical screen. + m_xinerama = false; +#if HAVE_X11_EXTENSIONS_XINERAMA_H + int eventBase, errorBase; + if (XineramaQueryExtension(m_display, &eventBase, &errorBase) && + XineramaIsActive(m_display)) { + int numScreens; + XineramaScreenInfo* screens; + screens = XineramaQueryScreens(m_display, &numScreens); + if (screens != NULL) { + if (numScreens > 1) { + m_xinerama = true; + m_xCenter = screens[0].x_org + (screens[0].width >> 1); + m_yCenter = screens[0].y_org + (screens[0].height >> 1); + } + XFree(screens); + } + } +#endif +} + +Window +XWindowsScreen::openWindow() const +{ + // default window attributes. we don't want the window manager + // messing with our window and we don't want the cursor to be + // visible inside the window. + XSetWindowAttributes attr; + attr.do_not_propagate_mask = 0; + attr.override_redirect = True; + attr.cursor = createBlankCursor(); + + // adjust attributes and get size and shape + SInt32 x, y, w, h; + if (m_isPrimary) { + // grab window attributes. this window is used to capture user + // input when the user is focused on another client. it covers + // the whole screen. + attr.event_mask = PointerMotionMask | + ButtonPressMask | ButtonReleaseMask | + KeyPressMask | KeyReleaseMask | + KeymapStateMask | PropertyChangeMask; + x = m_x; + y = m_y; + w = m_w; + h = m_h; + } + else { + // cursor hider window attributes. this window is used to hide the + // cursor when it's not on the screen. the window is hidden as soon + // as the cursor enters the screen or the display's real mouse is + // moved. we'll reposition the window as necessary so its + // position here doesn't matter. it only needs to be 1x1 because + // it only needs to contain the cursor's hotspot. + attr.event_mask = LeaveWindowMask; + x = 0; + y = 0; + w = 1; + h = 1; + } + + // create and return the window + Window window = XCreateWindow(m_display, m_root, x, y, w, h, 0, 0, + InputOnly, CopyFromParent, + CWDontPropagate | CWEventMask | + CWOverrideRedirect | CWCursor, + &attr); + if (window == None) { + throw XScreenOpenFailure(); + } + return window; +} + +void +XWindowsScreen::openIM() +{ + // open the input methods + XIM im = XOpenIM(m_display, NULL, NULL, NULL); + if (im == NULL) { + LOG((CLOG_INFO "no support for IM")); + return; + } + + // find the appropriate style. barrier supports XIMPreeditNothing + // only at the moment. + XIMStyles* styles; + if (XGetIMValues(im, XNQueryInputStyle, &styles, NULL) != NULL || + styles == NULL) { + LOG((CLOG_WARN "cannot get IM styles")); + XCloseIM(im); + return; + } + XIMStyle style = 0; + for (unsigned short i = 0; i < styles->count_styles; ++i) { + style = styles->supported_styles[i]; + if ((style & XIMPreeditNothing) != 0) { + if ((style & (XIMStatusNothing | XIMStatusNone)) != 0) { + break; + } + } + } + XFree(styles); + if (style == 0) { + LOG((CLOG_INFO "no supported IM styles")); + XCloseIM(im); + return; + } + + // create an input context for the style and tell it about our window + XIC ic = XCreateIC(im, XNInputStyle, style, XNClientWindow, m_window, NULL); + if (ic == NULL) { + LOG((CLOG_WARN "cannot create IC")); + XCloseIM(im); + return; + } + + // find out the events we must select for and do so + unsigned long mask; + if (XGetICValues(ic, XNFilterEvents, &mask, NULL) != NULL) { + LOG((CLOG_WARN "cannot get IC filter events")); + XDestroyIC(ic); + XCloseIM(im); + return; + } + + // we have IM + m_im = im; + m_ic = ic; + m_lastKeycode = 0; + + // select events on our window that IM requires + XWindowAttributes attr; + XGetWindowAttributes(m_display, m_window, &attr); + XSelectInput(m_display, m_window, attr.your_event_mask | mask); +} + +void +XWindowsScreen::sendEvent(Event::Type type, void* data) +{ + m_events->addEvent(Event(type, getEventTarget(), data)); +} + +void +XWindowsScreen::sendClipboardEvent(Event::Type type, ClipboardID id) +{ + ClipboardInfo* info = (ClipboardInfo*)malloc(sizeof(ClipboardInfo)); + info->m_id = id; + info->m_sequenceNumber = m_sequenceNumber; + sendEvent(type, info); +} + +IKeyState* +XWindowsScreen::getKeyState() const +{ + return m_keyState; +} + +Bool +XWindowsScreen::findKeyEvent(Display*, XEvent* xevent, XPointer arg) +{ + KeyEventFilter* filter = reinterpret_cast<KeyEventFilter*>(arg); + return (xevent->type == filter->m_event && + xevent->xkey.window == filter->m_window && + xevent->xkey.time == filter->m_time && + xevent->xkey.keycode == filter->m_keycode) ? True : False; +} + +void +XWindowsScreen::handleSystemEvent(const Event& event, void*) +{ + XEvent* xevent = static_cast<XEvent*>(event.getData()); + assert(xevent != NULL); + + // update key state + bool isRepeat = false; + if (m_isPrimary) { + if (xevent->type == KeyRelease) { + // check if this is a key repeat by getting the next + // KeyPress event that has the same key and time as + // this release event, if any. first prepare the + // filter info. + KeyEventFilter filter; + filter.m_event = KeyPress; + filter.m_window = xevent->xkey.window; + filter.m_time = xevent->xkey.time; + filter.m_keycode = xevent->xkey.keycode; + XEvent xevent2; + isRepeat = (XCheckIfEvent(m_display, &xevent2, + &XWindowsScreen::findKeyEvent, + (XPointer)&filter) == True); + } + + if (xevent->type == KeyPress || xevent->type == KeyRelease) { + if (xevent->xkey.window == m_root) { + // this is a hot key + onHotKey(xevent->xkey, isRepeat); + return; + } + else if (!m_isOnScreen) { + // this might be a hot key + if (onHotKey(xevent->xkey, isRepeat)) { + return; + } + } + + bool down = (isRepeat || xevent->type == KeyPress); + KeyModifierMask state = + m_keyState->mapModifiersFromX(xevent->xkey.state); + m_keyState->onKey(xevent->xkey.keycode, down, state); + } + } + + // let input methods try to handle event first + if (m_ic != NULL) { + // XFilterEvent() may eat the event and generate a new KeyPress + // event with a keycode of 0 because there isn't an actual key + // associated with the keysym. but the KeyRelease may pass + // through XFilterEvent() and keep its keycode. this means + // there's a mismatch between KeyPress and KeyRelease keycodes. + // since we use the keycode on the client to detect when a key + // is released this won't do. so we remember the keycode on + // the most recent KeyPress (and clear it on a matching + // KeyRelease) so we have a keycode for a synthesized KeyPress. + if (xevent->type == KeyPress && xevent->xkey.keycode != 0) { + m_lastKeycode = xevent->xkey.keycode; + } + else if (xevent->type == KeyRelease && + xevent->xkey.keycode == m_lastKeycode) { + m_lastKeycode = 0; + } + + // now filter the event + if (XFilterEvent(xevent, DefaultRootWindow(m_display))) { + if (xevent->type == KeyPress) { + // add filtered presses to the filtered list + m_filtered.insert(m_lastKeycode); + } + return; + } + + // discard matching key releases for key presses that were + // filtered and remove them from our filtered list. + else if (xevent->type == KeyRelease && + m_filtered.count(xevent->xkey.keycode) > 0) { + m_filtered.erase(xevent->xkey.keycode); + return; + } + } + + // let screen saver have a go + if (m_screensaver->handleXEvent(xevent)) { + // screen saver handled it + return; + } + +#ifdef HAVE_XI2 + if (m_xi2detected) { + // Process RawMotion + XGenericEventCookie *cookie = (XGenericEventCookie*)&xevent->xcookie; + if (XGetEventData(m_display, cookie) && + cookie->type == GenericEvent && + cookie->extension == xi_opcode) { + if (cookie->evtype == XI_RawMotion) { + // Get current pointer's position + Window root, child; + XMotionEvent xmotion; + xmotion.type = MotionNotify; + xmotion.send_event = False; // Raw motion + xmotion.display = m_display; + xmotion.window = m_window; + /* xmotion's time, state and is_hint are not used */ + unsigned int msk; + xmotion.same_screen = XQueryPointer( + m_display, m_root, &xmotion.root, &xmotion.subwindow, + &xmotion.x_root, + &xmotion.y_root, + &xmotion.x, + &xmotion.y, + &msk); + onMouseMove(xmotion); + XFreeEventData(m_display, cookie); + return; + } + XFreeEventData(m_display, cookie); + } + } +#endif + + // handle the event ourself + switch (xevent->type) { + case CreateNotify: + if (m_isPrimary && !m_xi2detected) { + // select events on new window + selectEvents(xevent->xcreatewindow.window); + } + break; + + case MappingNotify: + refreshKeyboard(xevent); + break; + + case LeaveNotify: + if (!m_isPrimary) { + // mouse moved out of hider window somehow. hide the window. + XUnmapWindow(m_display, m_window); + } + break; + + case SelectionClear: + { + // we just lost the selection. that means someone else + // grabbed the selection so this screen is now the + // selection owner. report that to the receiver. + ClipboardID id = getClipboardID(xevent->xselectionclear.selection); + if (id != kClipboardEnd) { + m_clipboard[id]->lost(xevent->xselectionclear.time); + sendClipboardEvent(m_events->forClipboard().clipboardGrabbed(), id); + return; + } + } + break; + + case SelectionNotify: + // notification of selection transferred. we shouldn't + // get this here because we handle them in the selection + // retrieval methods. we'll just delete the property + // with the data (satisfying the usual ICCCM protocol). + if (xevent->xselection.property != None) { + XDeleteProperty(m_display, + xevent->xselection.requestor, + xevent->xselection.property); + } + break; + + case SelectionRequest: + { + // somebody is asking for clipboard data + ClipboardID id = getClipboardID( + xevent->xselectionrequest.selection); + if (id != kClipboardEnd) { + m_clipboard[id]->addRequest( + xevent->xselectionrequest.owner, + xevent->xselectionrequest.requestor, + xevent->xselectionrequest.target, + xevent->xselectionrequest.time, + xevent->xselectionrequest.property); + return; + } + } + break; + + case PropertyNotify: + // property delete may be part of a selection conversion + if (xevent->xproperty.state == PropertyDelete) { + processClipboardRequest(xevent->xproperty.window, + xevent->xproperty.time, + xevent->xproperty.atom); + } + break; + + case DestroyNotify: + // looks like one of the windows that requested a clipboard + // transfer has gone bye-bye. + destroyClipboardRequest(xevent->xdestroywindow.window); + break; + + case KeyPress: + if (m_isPrimary) { + onKeyPress(xevent->xkey); + } + return; + + case KeyRelease: + if (m_isPrimary) { + onKeyRelease(xevent->xkey, isRepeat); + } + return; + + case ButtonPress: + if (m_isPrimary) { + onMousePress(xevent->xbutton); + } + return; + + case ButtonRelease: + if (m_isPrimary) { + onMouseRelease(xevent->xbutton); + } + return; + + case MotionNotify: + if (m_isPrimary) { + onMouseMove(xevent->xmotion); + } + return; + + default: +#if HAVE_XKB_EXTENSION + if (m_xkb && xevent->type == m_xkbEventBase) { + XkbEvent* xkbEvent = reinterpret_cast<XkbEvent*>(xevent); + switch (xkbEvent->any.xkb_type) { + case XkbMapNotify: + refreshKeyboard(xevent); + return; + + case XkbStateNotify: + LOG((CLOG_INFO "group change: %d", xkbEvent->state.group)); + m_keyState->setActiveGroup((SInt32)xkbEvent->state.group); + return; + } + } +#endif + +#if HAVE_X11_EXTENSIONS_XRANDR_H + if (m_xrandr) { + if (xevent->type == m_xrandrEventBase + RRScreenChangeNotify || + (xevent->type == m_xrandrEventBase + RRNotify && + reinterpret_cast<XRRNotifyEvent *>(xevent)->subtype == RRNotify_CrtcChange)) { + LOG((CLOG_INFO "XRRScreenChangeNotifyEvent or RRNotify_CrtcChange received")); + + // we're required to call back into XLib so XLib can update its internal state + XRRUpdateConfiguration(xevent); + + // requery/recalculate the screen shape + saveShape(); + + // we need to resize m_window, otherwise we'll get a weird problem where moving + // off the server onto the client causes the pointer to warp to the + // center of the server (so you can't move the pointer off the server) + if (m_isPrimary) { + XMoveWindow(m_display, m_window, m_x, m_y); + XResizeWindow(m_display, m_window, m_w, m_h); + } + + sendEvent(m_events->forIScreen().shapeChanged()); + } + } +#endif + + break; + } +} + +void +XWindowsScreen::onKeyPress(XKeyEvent& xkey) +{ + LOG((CLOG_DEBUG1 "event: KeyPress code=%d, state=0x%04x", xkey.keycode, xkey.state)); + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + } + + // get which button. see call to XFilterEvent() in onEvent() + // for more info. + bool isFake = false; + KeyButton keycode = static_cast<KeyButton>(xkey.keycode); + if (keycode == 0) { + isFake = true; + keycode = static_cast<KeyButton>(m_lastKeycode); + if (keycode == 0) { + // no keycode + LOG((CLOG_DEBUG1 "event: KeyPress no keycode")); + return; + } + } + + // handle key + m_keyState->sendKeyEvent(getEventTarget(), + true, false, key, mask, 1, keycode); + + // do fake release if this is a fake press + if (isFake) { + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + } + else { + LOG((CLOG_DEBUG1 "can't map keycode to key id")); + } +} + +void +XWindowsScreen::onKeyRelease(XKeyEvent& xkey, bool isRepeat) +{ + const KeyModifierMask mask = m_keyState->mapModifiersFromX(xkey.state); + KeyID key = mapKeyFromX(&xkey); + if (key != kKeyNone) { + // check for ctrl+alt+del emulation + if ((key == kKeyPause || key == kKeyBreak) && + (mask & (KeyModifierControl | KeyModifierAlt)) == + (KeyModifierControl | KeyModifierAlt)) { + // pretend it's ctrl+alt+del and ignore autorepeat + LOG((CLOG_DEBUG "emulate ctrl+alt+del")); + key = kKeyDelete; + isRepeat = false; + } + + KeyButton keycode = static_cast<KeyButton>(xkey.keycode); + if (!isRepeat) { + // no press event follows so it's a plain release + LOG((CLOG_DEBUG1 "event: KeyRelease code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, false, key, mask, 1, keycode); + } + else { + // found a press event following so it's a repeat. + // we could attempt to count the already queued + // repeats but we'll just send a repeat of 1. + // note that we discard the press event. + LOG((CLOG_DEBUG1 "event: repeat code=%d, state=0x%04x", keycode, xkey.state)); + m_keyState->sendKeyEvent(getEventTarget(), + false, true, key, mask, 1, keycode); + } + } +} + +bool +XWindowsScreen::onHotKey(XKeyEvent& xkey, bool isRepeat) +{ + // find the hot key id + HotKeyToIDMap::const_iterator i = + m_hotKeyToIDMap.find(HotKeyItem(xkey.keycode, xkey.state)); + if (i == m_hotKeyToIDMap.end()) { + return false; + } + + // find what kind of event + Event::Type type; + if (xkey.type == KeyPress) { + type = m_events->forIPrimaryScreen().hotKeyDown(); + } + else if (xkey.type == KeyRelease) { + type = m_events->forIPrimaryScreen().hotKeyUp(); + } + else { + return false; + } + + // generate event (ignore key repeats) + if (!isRepeat) { + m_events->addEvent(Event(type, getEventTarget(), + HotKeyInfo::alloc(i->second))); + } + return true; +} + +void +XWindowsScreen::onMousePress(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonPress button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(m_events->forIPrimaryScreen().buttonDown(), ButtonInfo::alloc(button, mask)); + } +} + +void +XWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) +{ + LOG((CLOG_DEBUG1 "event: ButtonRelease button=%d", xbutton.button)); + ButtonID button = mapButtonFromX(&xbutton); + KeyModifierMask mask = m_keyState->mapModifiersFromX(xbutton.state); + if (button != kButtonNone) { + sendEvent(m_events->forIPrimaryScreen().buttonUp(), ButtonInfo::alloc(button, mask)); + } + else if (xbutton.button == 4) { + // wheel forward (away from user) + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, 120)); + } + else if (xbutton.button == 5) { + // wheel backward (toward user) + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, -120)); + } + // XXX -- support x-axis scrolling +} + +void +XWindowsScreen::onMouseMove(const XMotionEvent& xmotion) +{ + LOG((CLOG_DEBUG2 "event: MotionNotify %d,%d", xmotion.x_root, xmotion.y_root)); + + // compute motion delta (relative to the last known + // mouse position) + SInt32 x = xmotion.x_root - m_xCursor; + SInt32 y = xmotion.y_root - m_yCursor; + + // save position to compute delta of next motion + m_xCursor = xmotion.x_root; + m_yCursor = xmotion.y_root; + + if (xmotion.send_event) { + // we warped the mouse. discard events until we + // find the matching sent event. see + // warpCursorNoFlush() for where the events are + // sent. we discard the matching sent event and + // can be sure we've skipped the warp event. + XEvent xevent; + char cntr = 0; + do { + XMaskEvent(m_display, PointerMotionMask, &xevent); + if (cntr++ > 10) { + LOG((CLOG_WARN "too many discarded events! %d", cntr)); + break; + } + } while (!xevent.xany.send_event); + cntr = 0; + } + else if (m_isOnScreen) { + // motion on primary screen + sendEvent(m_events->forIPrimaryScreen().motionOnPrimary(), + MotionInfo::alloc(m_xCursor, m_yCursor)); + } + else { + // motion on secondary screen. warp mouse back to + // center. + // + // my lombard (powerbook g3) running linux and + // using the adbmouse driver has two problems: + // first, the driver only sends motions of +/-2 + // pixels and, second, it seems to discard some + // physical input after a warp. the former isn't a + // big deal (we're just limited to every other + // pixel) but the latter is a PITA. to work around + // it we only warp when the mouse has moved more + // than s_size pixels from the center. + static const SInt32 s_size = 32; + if (xmotion.x_root - m_xCenter < -s_size || + xmotion.x_root - m_xCenter > s_size || + xmotion.y_root - m_yCenter < -s_size || + xmotion.y_root - m_yCenter > s_size) { + warpCursorNoFlush(m_xCenter, m_yCenter); + } + + // send event if mouse moved. do this after warping + // back to center in case the motion takes us onto + // the primary screen. if we sent the event first + // in that case then the warp would happen after + // warping to the primary screen's enter position, + // effectively overriding it. + if (x != 0 || y != 0) { + sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y)); + } + } +} + +Cursor +XWindowsScreen::createBlankCursor() const +{ + // this seems just a bit more complicated than really necessary + + // get the closet cursor size to 1x1 + unsigned int w = 0, h = 0; + XQueryBestCursor(m_display, m_root, 1, 1, &w, &h); + w = std::max(1u, w); + h = std::max(1u, h); + + // make bitmap data for cursor of closet size. since the cursor + // is blank we can use the same bitmap for shape and mask: all + // zeros. + const int size = ((w + 7) >> 3) * h; + char* data = new char[size]; + memset(data, 0, size); + + // make bitmap + Pixmap bitmap = XCreateBitmapFromData(m_display, m_root, data, w, h); + + // need an arbitrary color for the cursor + XColor color; + color.pixel = 0; + color.red = color.green = color.blue = 0; + color.flags = DoRed | DoGreen | DoBlue; + + // make cursor from bitmap + Cursor cursor = XCreatePixmapCursor(m_display, bitmap, bitmap, + &color, &color, 0, 0); + + // don't need bitmap or the data anymore + delete[] data; + XFreePixmap(m_display, bitmap); + + return cursor; +} + +ClipboardID +XWindowsScreen::getClipboardID(Atom selection) const +{ + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->getSelection() == selection) { + return id; + } + } + return kClipboardEnd; +} + +void +XWindowsScreen::processClipboardRequest(Window requestor, + Time time, Atom property) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->processRequest(requestor, time, property)) { + break; + } + } +} + +void +XWindowsScreen::destroyClipboardRequest(Window requestor) +{ + // check every clipboard until one returns success + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + if (m_clipboard[id] != NULL && + m_clipboard[id]->destroyRequest(requestor)) { + break; + } + } +} + +void +XWindowsScreen::onError() +{ + // prevent further access to the X display + m_events->adoptBuffer(NULL); + m_screensaver->destroy(); + m_screensaver = NULL; + m_display = NULL; + + // notify of failure + sendEvent(m_events->forIScreen().error(), NULL); + + // FIXME -- should ensure that we ignore operations that involve + // m_display from now on. however, Xlib will simply exit the + // application in response to the X I/O error so there's no + // point in trying to really handle the error. if we did want + // to handle the error, it'd probably be easiest to delegate to + // one of two objects. one object would take the implementation + // from this class. the other object would be stub methods that + // don't use X11. on error, we'd switch to the latter. +} + +int +XWindowsScreen::ioErrorHandler(Display*) +{ + // the display has disconnected, probably because X is shutting + // down. X forces us to exit at this point which is annoying. + // we'll pretend as if we won't exit so we try to make sure we + // don't access the display anymore. + LOG((CLOG_CRIT "X display has unexpectedly disconnected")); + s_screen->onError(); + return 0; +} + +void +XWindowsScreen::selectEvents(Window w) const +{ + // ignore errors while we adjust event masks. windows could be + // destroyed at any time after the XQueryTree() in doSelectEvents() + // so we must ignore BadWindow errors. + XWindowsUtil::ErrorLock lock(m_display); + + // adjust event masks + doSelectEvents(w); +} + +void +XWindowsScreen::doSelectEvents(Window w) const +{ + // we want to track the mouse everywhere on the display. to achieve + // that we select PointerMotionMask on every window. we also select + // SubstructureNotifyMask in order to get CreateNotify events so we + // select events on new windows too. + + // we don't want to adjust our grab window + if (w == m_window) { + return; + } + + // X11 has a design flaw. If *no* client selected PointerMotionMask for + // a window, motion events will be delivered to that window's parent. + // If *any* client, not necessarily the owner, selects PointerMotionMask + // on such a window, X will stop propagating motion events to its + // parent. This breaks applications that rely on event propagation + // behavior. + // + // Avoid selecting PointerMotionMask unless some other client selected + // it already. + long mask = SubstructureNotifyMask; + XWindowAttributes attr; + XGetWindowAttributes(m_display, w, &attr); + if ((attr.all_event_masks & PointerMotionMask) == PointerMotionMask) { + mask |= PointerMotionMask; + } + + // select events of interest. do this before querying the tree so + // we'll get notifications of children created after the XQueryTree() + // so we won't miss them. + XSelectInput(m_display, w, mask); + + // recurse on child windows + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, w, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + doSelectEvents(cw[i]); + } + XFree(cw); + } +} + +KeyID +XWindowsScreen::mapKeyFromX(XKeyEvent* event) const +{ + // convert to a keysym + KeySym keysym; + if (event->type == KeyPress && m_ic != NULL) { + // do multibyte lookup. can only call XmbLookupString with a + // key press event and a valid XIC so we checked those above. + char scratch[32]; + int n = sizeof(scratch) / sizeof(scratch[0]); + char* buffer = scratch; + int status; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + if (status == XBufferOverflow) { + // not enough space. grow buffer and try again. + buffer = new char[n]; + n = XmbLookupString(m_ic, event, buffer, n, &keysym, &status); + delete[] buffer; + } + + // see what we got. since we don't care about the string + // we'll just look for a keysym. + switch (status) { + default: + case XLookupNone: + case XLookupChars: + keysym = 0; + break; + + case XLookupKeySym: + case XLookupBoth: + break; + } + } + else { + // plain old lookup + char dummy[1]; + XLookupString(event, dummy, 0, &keysym, NULL); + } + + LOG((CLOG_DEBUG2 "mapped code=%d to keysym=0x%04x", event->keycode, keysym)); + + // convert key + KeyID result = XWindowsUtil::mapKeySymToKeyID(keysym); + LOG((CLOG_DEBUG2 "mapped keysym=0x%04x to keyID=%d", keysym, result)); + return result; +} + +ButtonID +XWindowsScreen::mapButtonFromX(const XButtonEvent* event) const +{ + unsigned int button = event->button; + + // first three buttons map to 1, 2, 3 (kButtonLeft, Middle, Right) + if (button >= 1 && button <= 3) { + return static_cast<ButtonID>(button); + } + + // buttons 4 and 5 are ignored here. they're used for the wheel. + // buttons 6, 7, etc and up map to 4, 5, etc. + else if (button >= 6) { + return static_cast<ButtonID>(button - 2); + } + + // unknown button + else { + return kButtonNone; + } +} + +unsigned int +XWindowsScreen::mapButtonToX(ButtonID id) const +{ + // map button -1 to button 4 (+wheel) + if (id == static_cast<ButtonID>(-1)) { + id = 4; + } + + // map button -2 to button 5 (-wheel) + else if (id == static_cast<ButtonID>(-2)) { + id = 5; + } + + // map buttons 4, 5, etc. to 6, 7, etc. to make room for buttons + // 4 and 5 used to simulate the mouse wheel. + else if (id >= 4) { + id += 2; + } + + // check button is in legal range + if (id < 1 || id > m_buttons.size()) { + // out of range + return 0; + } + + // map button + return static_cast<unsigned int>(id); +} + +void +XWindowsScreen::warpCursorNoFlush(SInt32 x, SInt32 y) +{ + assert(m_window != None); + + // send an event that we can recognize before the mouse warp + XEvent eventBefore; + eventBefore.type = MotionNotify; + eventBefore.xmotion.display = m_display; + eventBefore.xmotion.window = m_window; + eventBefore.xmotion.root = m_root; + eventBefore.xmotion.subwindow = m_window; + eventBefore.xmotion.time = CurrentTime; + eventBefore.xmotion.x = x; + eventBefore.xmotion.y = y; + eventBefore.xmotion.x_root = x; + eventBefore.xmotion.y_root = y; + eventBefore.xmotion.state = 0; + eventBefore.xmotion.is_hint = NotifyNormal; + eventBefore.xmotion.same_screen = True; + XEvent eventAfter = eventBefore; + XSendEvent(m_display, m_window, False, 0, &eventBefore); + + // warp mouse + XWarpPointer(m_display, None, m_root, 0, 0, 0, 0, x, y); + + // send an event that we can recognize after the mouse warp + XSendEvent(m_display, m_window, False, 0, &eventAfter); + XSync(m_display, False); + + LOG((CLOG_DEBUG2 "warped to %d,%d", x, y)); +} + +void +XWindowsScreen::updateButtons() +{ + // query the button mapping + UInt32 numButtons = XGetPointerMapping(m_display, NULL, 0); + unsigned char* tmpButtons = new unsigned char[numButtons]; + XGetPointerMapping(m_display, tmpButtons, numButtons); + + // find the largest logical button id + unsigned char maxButton = 0; + for (UInt32 i = 0; i < numButtons; ++i) { + if (tmpButtons[i] > maxButton) { + maxButton = tmpButtons[i]; + } + } + + // allocate button array + m_buttons.resize(maxButton); + + // fill in button array values. m_buttons[i] is the physical + // button number for logical button i+1. + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[i] = 0; + } + for (UInt32 i = 0; i < numButtons; ++i) { + m_buttons[tmpButtons[i] - 1] = i + 1; + } + + // clean up + delete[] tmpButtons; +} + +bool +XWindowsScreen::grabMouseAndKeyboard() +{ + unsigned int event_mask = ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask; + + // grab the mouse and keyboard. keep trying until we get them. + // if we can't grab one after grabbing the other then ungrab + // and wait before retrying. give up after s_timeout seconds. + static const double s_timeout = 1.0; + int result; + Stopwatch timer; + do { + // keyboard first + do { + result = XGrabKeyboard(m_display, m_window, True, + GrabModeAsync, GrabModeAsync, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + LOG((CLOG_DEBUG2 "waiting to grab keyboard")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab keyboard timed out")); + return false; + } + } + } while (result != GrabSuccess); + LOG((CLOG_DEBUG2 "grabbed keyboard")); + + // now the mouse --- use event_mask to get EnterNotify, LeaveNotify events + result = XGrabPointer(m_display, m_window, False, event_mask, + GrabModeAsync, GrabModeAsync, + m_window, None, CurrentTime); + assert(result != GrabNotViewable); + if (result != GrabSuccess) { + // back off to avoid grab deadlock + XUngrabKeyboard(m_display, CurrentTime); + LOG((CLOG_DEBUG2 "ungrabbed keyboard, waiting to grab pointer")); + ARCH->sleep(0.05); + if (timer.getTime() >= s_timeout) { + LOG((CLOG_DEBUG2 "grab pointer timed out")); + return false; + } + } + } while (result != GrabSuccess); + + LOG((CLOG_DEBUG1 "grabbed pointer and keyboard")); + return true; +} + +void +XWindowsScreen::refreshKeyboard(XEvent* event) +{ + if (XPending(m_display) > 0) { + XEvent tmpEvent; + XPeekEvent(m_display, &tmpEvent); + if (tmpEvent.type == MappingNotify) { + // discard this event since another follows. + // we tend to get a bunch of these in a row. + return; + } + } + + // keyboard mapping changed +#if HAVE_XKB_EXTENSION + if (m_xkb && event->type == m_xkbEventBase) { + XkbRefreshKeyboardMapping((XkbMapNotifyEvent*)event); + } + else +#else + { + XRefreshKeyboardMapping(&event->xmapping); + } +#endif + m_keyState->updateKeyMap(); + m_keyState->updateKeyState(); +} + + +// +// XWindowsScreen::HotKeyItem +// + +XWindowsScreen::HotKeyItem::HotKeyItem(int keycode, unsigned int mask) : + m_keycode(keycode), + m_mask(mask) +{ + // do nothing +} + +bool +XWindowsScreen::HotKeyItem::operator<(const HotKeyItem& x) const +{ + return (m_keycode < x.m_keycode || + (m_keycode == x.m_keycode && m_mask < x.m_mask)); +} + +bool +XWindowsScreen::detectXI2() +{ + int event, error; + return XQueryExtension(m_display, + "XInputExtension", &xi_opcode, &event, &error); +} + +#ifdef HAVE_XI2 +void +XWindowsScreen::selectXIRawMotion() +{ + XIEventMask mask; + + mask.deviceid = XIAllDevices; + mask.mask_len = XIMaskLen(XI_RawMotion); + mask.mask = (unsigned char*)calloc(mask.mask_len, sizeof(char)); + mask.deviceid = XIAllMasterDevices; + memset(mask.mask, 0, 2); + XISetMask(mask.mask, XI_RawKeyRelease); + XISetMask(mask.mask, XI_RawMotion); + XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); + free(mask.mask); +} +#endif diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h new file mode 100644 index 0000000..35f9368 --- /dev/null +++ b/src/lib/platform/XWindowsScreen.h @@ -0,0 +1,252 @@ +/* + * 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 "barrier/PlatformScreen.h" +#include "barrier/KeyMap.h" +#include "common/stdset.h" +#include "common/stdvector.h" + +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/Xlib.h> +#endif + +class XWindowsClipboard; +class XWindowsKeyState; +class XWindowsScreenSaver; + +//! Implementation of IPlatformScreen for X11 +class XWindowsScreen : public PlatformScreen { +public: + XWindowsScreen(const char* displayName, bool isPrimary, + bool disableXInitThreads, int mouseScrollDelta, + IEventQueue* events); + virtual ~XWindowsScreen(); + + //! @name manipulators + //@{ + + //@} + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IPrimaryScreen overrides + virtual void reconfigure(UInt32 activeSides); + virtual void warpCursor(SInt32 x, SInt32 y); + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + virtual void unregisterHotKey(UInt32 id); + virtual void fakeInputBegin(); + virtual void fakeInputEnd(); + virtual SInt32 getJumpZoneSize() const; + virtual bool isAnyMouseButtonDown(UInt32& buttonID) const; + virtual void getCursorCenter(SInt32& x, SInt32& y) const; + + // ISecondaryScreen overrides + virtual void fakeMouseButton(ButtonID id, bool press); + virtual void fakeMouseMove(SInt32 x, SInt32 y); + virtual void fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const; + virtual void fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const; + + // IPlatformScreen overrides + virtual void enable(); + virtual void disable(); + virtual void enter(); + virtual bool leave(); + virtual bool setClipboard(ClipboardID, const IClipboard*); + virtual void checkClipboards(); + virtual void openScreensaver(bool notify); + virtual void closeScreensaver(); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual void setSequenceNumber(UInt32); + virtual bool isPrimary() const; + +protected: + // IPlatformScreen overrides + virtual void handleSystemEvent(const Event&, void*); + virtual void updateButtons(); + virtual IKeyState* getKeyState() const; + +private: + // event sending + void sendEvent(Event::Type, void* = NULL); + void sendClipboardEvent(Event::Type, ClipboardID); + + // create the transparent cursor + Cursor createBlankCursor() const; + + // determine the clipboard from the X selection. returns + // kClipboardEnd if no such clipboard. + ClipboardID getClipboardID(Atom selection) const; + + // continue processing a selection request + void processClipboardRequest(Window window, + Time time, Atom property); + + // terminate a selection request + void destroyClipboardRequest(Window window); + + // X I/O error handler + void onError(); + static int ioErrorHandler(Display*); + +private: + class KeyEventFilter { + public: + int m_event; + Window m_window; + Time m_time; + KeyCode m_keycode; + }; + + Display* openDisplay(const char* displayName); + void saveShape(); + Window openWindow() const; + void openIM(); + + bool grabMouseAndKeyboard(); + void onKeyPress(XKeyEvent&); + void onKeyRelease(XKeyEvent&, bool isRepeat); + bool onHotKey(XKeyEvent&, bool isRepeat); + void onMousePress(const XButtonEvent&); + void onMouseRelease(const XButtonEvent&); + void onMouseMove(const XMotionEvent&); + + bool detectXI2(); +#ifdef HAVE_XI2 + void selectXIRawMotion(); +#endif + void selectEvents(Window) const; + void doSelectEvents(Window) const; + + KeyID mapKeyFromX(XKeyEvent*) const; + ButtonID mapButtonFromX(const XButtonEvent*) const; + unsigned int mapButtonToX(ButtonID id) const; + + void warpCursorNoFlush(SInt32 x, SInt32 y); + + void refreshKeyboard(XEvent*); + + static Bool findKeyEvent(Display*, XEvent* xevent, XPointer arg); + +private: + struct HotKeyItem { + public: + HotKeyItem(int, unsigned int); + + bool operator<(const HotKeyItem&) const; + + private: + int m_keycode; + unsigned int m_mask; + }; + typedef std::set<bool> FilteredKeycodes; + typedef std::vector<std::pair<int, unsigned int> > HotKeyList; + typedef std::map<UInt32, HotKeyList> HotKeyMap; + typedef std::vector<UInt32> HotKeyIDList; + typedef std::map<HotKeyItem, UInt32> HotKeyToIDMap; + + // true if screen is being used as a primary screen, false otherwise + bool m_isPrimary; + int m_mouseScrollDelta; + + Display* m_display; + Window m_root; + Window m_window; + + // true if mouse has entered the screen + bool m_isOnScreen; + + // screen shape stuff + SInt32 m_x, m_y; + SInt32 m_w, m_h; + SInt32 m_xCenter, m_yCenter; + + // last mouse position + SInt32 m_xCursor, m_yCursor; + + // keyboard stuff + XWindowsKeyState* m_keyState; + + // hot key stuff + HotKeyMap m_hotKeys; + HotKeyIDList m_oldHotKeyIDs; + HotKeyToIDMap m_hotKeyToIDMap; + + // input focus stuff + Window m_lastFocus; + int m_lastFocusRevert; + + // input method stuff + XIM m_im; + XIC m_ic; + KeyCode m_lastKeycode; + FilteredKeycodes m_filtered; + + // clipboards + XWindowsClipboard* m_clipboard[kClipboardEnd]; + UInt32 m_sequenceNumber; + + // screen saver stuff + XWindowsScreenSaver* m_screensaver; + bool m_screensaverNotify; + + // logical to physical button mapping. m_buttons[i] gives the + // physical button for logical button i+1. + std::vector<unsigned char> m_buttons; + + // true if global auto-repeat was enabled before we turned it off + bool m_autoRepeat; + + // stuff to workaround xtest being xinerama unaware. attempting + // to fake a mouse motion under xinerama may behave strangely, + // especially if screen 0 is not at 0,0 or if faking a motion on + // a screen other than screen 0. + bool m_xtestIsXineramaUnaware; + bool m_xinerama; + + // stuff to work around lost focus issues on certain systems + // (ie: a MythTV front-end). + bool m_preserveFocus; + + // XKB extension stuff + bool m_xkb; + int m_xkbEventBase; + + bool m_xi2detected; + + // XRandR extension stuff + bool m_xrandr; + int m_xrandrEventBase; + + IEventQueue* m_events; + barrier::KeyMap m_keyMap; + + // pointer to (singleton) screen. this is only needed by + // ioErrorHandler(). + static XWindowsScreen* s_screen; +}; diff --git a/src/lib/platform/XWindowsScreenSaver.cpp b/src/lib/platform/XWindowsScreenSaver.cpp new file mode 100644 index 0000000..bc457f9 --- /dev/null +++ b/src/lib/platform/XWindowsScreenSaver.cpp @@ -0,0 +1,605 @@ +/* + * 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 "platform/XWindowsScreenSaver.h" + +#include "platform/XWindowsUtil.h" +#include "barrier/IPlatformScreen.h" +#include "base/Log.h" +#include "base/Event.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +#include <X11/Xatom.h> +#if HAVE_X11_EXTENSIONS_XTEST_H +# include <X11/extensions/XTest.h> +#else +# error The XTest extension is required to build barrier +#endif +#if HAVE_X11_EXTENSIONS_DPMS_H +extern "C" { +# include <X11/Xmd.h> +# include <X11/extensions/dpms.h> +# if !HAVE_DPMS_PROTOTYPES +# undef DPMSModeOn +# undef DPMSModeStandby +# undef DPMSModeSuspend +# undef DPMSModeOff +# define DPMSModeOn 0 +# define DPMSModeStandby 1 +# define DPMSModeSuspend 2 +# define DPMSModeOff 3 +extern Bool DPMSQueryExtension(Display *, int *, int *); +extern Bool DPMSCapable(Display *); +extern Status DPMSEnable(Display *); +extern Status DPMSDisable(Display *); +extern Status DPMSForceLevel(Display *, CARD16); +extern Status DPMSInfo(Display *, CARD16 *, BOOL *); +# endif +} +#endif + +// +// XWindowsScreenSaver +// + +XWindowsScreenSaver::XWindowsScreenSaver( + Display* display, Window window, void* eventTarget, IEventQueue* events) : + m_display(display), + m_xscreensaverSink(window), + m_eventTarget(eventTarget), + m_xscreensaver(None), + m_xscreensaverActive(false), + m_dpms(false), + m_disabled(false), + m_suppressDisable(false), + m_disableTimer(NULL), + m_disablePos(0), + m_events(events) +{ + // get atoms + m_atomScreenSaver = XInternAtom(m_display, + "SCREENSAVER", False); + m_atomScreenSaverVersion = XInternAtom(m_display, + "_SCREENSAVER_VERSION", False); + m_atomScreenSaverActivate = XInternAtom(m_display, + "ACTIVATE", False); + m_atomScreenSaverDeactivate = XInternAtom(m_display, + "DEACTIVATE", False); + + // check for DPMS extension. this is an alternative screen saver + // that powers down the display. +#if HAVE_X11_EXTENSIONS_DPMS_H + int eventBase, errorBase; + if (DPMSQueryExtension(m_display, &eventBase, &errorBase)) { + if (DPMSCapable(m_display)) { + // we have DPMS + m_dpms = true; + } + } +#endif + + // watch top-level windows for changes + bool error = false; + { + XWindowsUtil::ErrorLock lock(m_display, &error); + Window root = DefaultRootWindow(m_display); + XWindowAttributes attr; + XGetWindowAttributes(m_display, root, &attr); + m_rootEventMask = attr.your_event_mask; + XSelectInput(m_display, root, m_rootEventMask | SubstructureNotifyMask); + } + if (error) { + LOG((CLOG_DEBUG "didn't set root event mask")); + m_rootEventMask = 0; + } + + // get the built-in settings + XGetScreenSaver(m_display, &m_timeout, &m_interval, + &m_preferBlanking, &m_allowExposures); + + // get the DPMS settings + m_dpmsEnabled = isDPMSEnabled(); + + // get the xscreensaver window, if any + if (!findXScreenSaver()) { + setXScreenSaver(None); + } + + // install disable timer event handler + m_events->adoptHandler(Event::kTimer, this, + new TMethodEventJob<XWindowsScreenSaver>(this, + &XWindowsScreenSaver::handleDisableTimer)); +} + +XWindowsScreenSaver::~XWindowsScreenSaver() +{ + // done with disable job + if (m_disableTimer != NULL) { + m_events->deleteTimer(m_disableTimer); + } + m_events->removeHandler(Event::kTimer, this); + + if (m_display != NULL) { + enableDPMS(m_dpmsEnabled); + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + clearWatchForXScreenSaver(); + XWindowsUtil::ErrorLock lock(m_display); + XSelectInput(m_display, DefaultRootWindow(m_display), m_rootEventMask); + } +} + +void +XWindowsScreenSaver::destroy() +{ + m_display = NULL; + delete this; +} + +bool +XWindowsScreenSaver::handleXEvent(const XEvent* xevent) +{ + switch (xevent->type) { + case CreateNotify: + if (m_xscreensaver == None) { + if (isXScreenSaver(xevent->xcreatewindow.window)) { + // found the xscreensaver + setXScreenSaver(xevent->xcreatewindow.window); + } + else { + // another window to watch. to detect the xscreensaver + // window we look for a property but that property may + // not yet exist by the time we get this event so we + // have to watch the window for property changes. + // this would be so much easier if xscreensaver did the + // smart thing and stored its window in a property on + // the root window. + addWatchXScreenSaver(xevent->xcreatewindow.window); + } + } + break; + + case DestroyNotify: + if (xevent->xdestroywindow.window == m_xscreensaver) { + // xscreensaver is gone + LOG((CLOG_DEBUG "xscreensaver died")); + setXScreenSaver(None); + return true; + } + break; + + case PropertyNotify: + if (xevent->xproperty.state == PropertyNewValue) { + if (isXScreenSaver(xevent->xproperty.window)) { + // found the xscreensaver + setXScreenSaver(xevent->xcreatewindow.window); + } + } + break; + + case MapNotify: + if (xevent->xmap.window == m_xscreensaver) { + // xscreensaver has activated + setXScreenSaverActive(true); + return true; + } + break; + + case UnmapNotify: + if (xevent->xunmap.window == m_xscreensaver) { + // xscreensaver has deactivated + setXScreenSaverActive(false); + return true; + } + break; + } + + return false; +} + +void +XWindowsScreenSaver::enable() +{ + // for xscreensaver + m_disabled = false; + updateDisableTimer(); + + // for built-in X screen saver + XSetScreenSaver(m_display, m_timeout, m_interval, + m_preferBlanking, m_allowExposures); + + // for DPMS + enableDPMS(m_dpmsEnabled); +} + +void +XWindowsScreenSaver::disable() +{ + // for xscreensaver + m_disabled = true; + updateDisableTimer(); + + // use built-in X screen saver + XGetScreenSaver(m_display, &m_timeout, &m_interval, + &m_preferBlanking, &m_allowExposures); + XSetScreenSaver(m_display, 0, m_interval, + m_preferBlanking, m_allowExposures); + + // for DPMS + m_dpmsEnabled = isDPMSEnabled(); + enableDPMS(false); + + // FIXME -- now deactivate? +} + +void +XWindowsScreenSaver::activate() +{ + // remove disable job timer + m_suppressDisable = true; + updateDisableTimer(); + + // enable DPMS if it was enabled + enableDPMS(m_dpmsEnabled); + + // try xscreensaver + findXScreenSaver(); + if (m_xscreensaver != None) { + sendXScreenSaverCommand(m_atomScreenSaverActivate); + return; + } + + // try built-in X screen saver + if (m_timeout != 0) { + XForceScreenSaver(m_display, ScreenSaverActive); + } + + // try DPMS + activateDPMS(true); +} + +void +XWindowsScreenSaver::deactivate() +{ + // reinstall disable job timer + m_suppressDisable = false; + updateDisableTimer(); + + // try DPMS + activateDPMS(false); + + // disable DPMS if screen saver is disabled + if (m_disabled) { + enableDPMS(false); + } + + // try xscreensaver + findXScreenSaver(); + if (m_xscreensaver != None) { + sendXScreenSaverCommand(m_atomScreenSaverDeactivate); + return; + } + + // use built-in X screen saver + XForceScreenSaver(m_display, ScreenSaverReset); +} + +bool +XWindowsScreenSaver::isActive() const +{ + // check xscreensaver + if (m_xscreensaver != None) { + return m_xscreensaverActive; + } + + // check DPMS + if (isDPMSActivated()) { + return true; + } + + // can't check built-in X screen saver activity + return false; +} + +bool +XWindowsScreenSaver::findXScreenSaver() +{ + // do nothing if we've already got the xscreensaver window + if (m_xscreensaver == None) { + // find top-level window xscreensaver window + Window root = DefaultRootWindow(m_display); + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + if (isXScreenSaver(cw[i])) { + setXScreenSaver(cw[i]); + break; + } + } + XFree(cw); + } + } + + return (m_xscreensaver != None); +} + +void +XWindowsScreenSaver::setXScreenSaver(Window window) +{ + LOG((CLOG_DEBUG "xscreensaver window: 0x%08x", window)); + + // save window + m_xscreensaver = window; + + if (m_xscreensaver != None) { + // clear old watch list + clearWatchForXScreenSaver(); + + // see if xscreensaver is active + bool error = false; + XWindowAttributes attr; + { + XWindowsUtil::ErrorLock lock(m_display, &error); + XGetWindowAttributes(m_display, m_xscreensaver, &attr); + } + setXScreenSaverActive(!error && attr.map_state != IsUnmapped); + + // save current DPMS state; xscreensaver may have changed it. + m_dpmsEnabled = isDPMSEnabled(); + } + else { + // screen saver can't be active if it doesn't exist + setXScreenSaverActive(false); + + // start watching for xscreensaver + watchForXScreenSaver(); + } +} + +bool +XWindowsScreenSaver::isXScreenSaver(Window w) const +{ + // check for m_atomScreenSaverVersion string property + Atom type; + return (XWindowsUtil::getWindowProperty(m_display, w, + m_atomScreenSaverVersion, + NULL, &type, NULL, False) && + type == XA_STRING); +} + +void +XWindowsScreenSaver::setXScreenSaverActive(bool activated) +{ + if (m_xscreensaverActive != activated) { + LOG((CLOG_DEBUG "xscreensaver %s on window 0x%08x", activated ? "activated" : "deactivated", m_xscreensaver)); + m_xscreensaverActive = activated; + + // if screen saver was activated forcefully (i.e. against + // our will) then just accept it. don't try to keep it + // from activating since that'll just pop up the password + // dialog if locking is enabled. + m_suppressDisable = activated; + updateDisableTimer(); + + if (activated) { + m_events->addEvent(Event( + m_events->forIPrimaryScreen().screensaverActivated(), + m_eventTarget)); + } + else { + m_events->addEvent(Event( + m_events->forIPrimaryScreen().screensaverDeactivated(), + m_eventTarget)); + } + } +} + +void +XWindowsScreenSaver::sendXScreenSaverCommand(Atom cmd, long arg1, long arg2) +{ + XEvent event; + event.xclient.type = ClientMessage; + event.xclient.display = m_display; + event.xclient.window = m_xscreensaverSink; + event.xclient.message_type = m_atomScreenSaver; + event.xclient.format = 32; + event.xclient.data.l[0] = static_cast<long>(cmd); + event.xclient.data.l[1] = arg1; + event.xclient.data.l[2] = arg2; + event.xclient.data.l[3] = 0; + event.xclient.data.l[4] = 0; + + LOG((CLOG_DEBUG "send xscreensaver command: %d %d %d", (long)cmd, arg1, arg2)); + bool error = false; + { + XWindowsUtil::ErrorLock lock(m_display, &error); + XSendEvent(m_display, m_xscreensaver, False, 0, &event); + } + if (error) { + findXScreenSaver(); + } +} + +void +XWindowsScreenSaver::watchForXScreenSaver() +{ + // clear old watch list + clearWatchForXScreenSaver(); + + // add every child of the root to the list of windows to watch + Window root = DefaultRootWindow(m_display); + Window rw, pw, *cw; + unsigned int nc; + if (XQueryTree(m_display, root, &rw, &pw, &cw, &nc)) { + for (unsigned int i = 0; i < nc; ++i) { + addWatchXScreenSaver(cw[i]); + } + XFree(cw); + } + + // now check for xscreensaver window in case it set the property + // before we could request property change events. + if (findXScreenSaver()) { + // found it so clear out our watch list + clearWatchForXScreenSaver(); + } +} + +void +XWindowsScreenSaver::clearWatchForXScreenSaver() +{ + // stop watching all windows + XWindowsUtil::ErrorLock lock(m_display); + for (WatchList::iterator index = m_watchWindows.begin(); + index != m_watchWindows.end(); ++index) { + XSelectInput(m_display, index->first, index->second); + } + m_watchWindows.clear(); +} + +void +XWindowsScreenSaver::addWatchXScreenSaver(Window window) +{ + // get window attributes + bool error = false; + XWindowAttributes attr; + { + XWindowsUtil::ErrorLock lock(m_display, &error); + XGetWindowAttributes(m_display, window, &attr); + } + + // if successful and window uses override_redirect (like xscreensaver + // does) then watch it for property changes. + if (!error && attr.override_redirect == True) { + error = false; + { + XWindowsUtil::ErrorLock lock(m_display, &error); + XSelectInput(m_display, window, + attr.your_event_mask | PropertyChangeMask); + } + if (!error) { + // if successful then add the window to our list + m_watchWindows.insert(std::make_pair(window, attr.your_event_mask)); + } + } +} + +void +XWindowsScreenSaver::updateDisableTimer() +{ + if (m_disabled && !m_suppressDisable && m_disableTimer == NULL) { + // 5 seconds should be plenty often to suppress the screen saver + m_disableTimer = m_events->newTimer(5.0, this); + } + else if ((!m_disabled || m_suppressDisable) && m_disableTimer != NULL) { + m_events->deleteTimer(m_disableTimer); + m_disableTimer = NULL; + } +} + +void +XWindowsScreenSaver::handleDisableTimer(const Event&, void*) +{ + // send fake mouse motion directly to xscreensaver + if (m_xscreensaver != None) { + XEvent event; + event.xmotion.type = MotionNotify; + event.xmotion.display = m_display; + event.xmotion.window = m_xscreensaver; + event.xmotion.root = DefaultRootWindow(m_display); + event.xmotion.subwindow = None; + event.xmotion.time = CurrentTime; + event.xmotion.x = m_disablePos; + event.xmotion.y = 0; + event.xmotion.x_root = m_disablePos; + event.xmotion.y_root = 0; + event.xmotion.state = 0; + event.xmotion.is_hint = NotifyNormal; + event.xmotion.same_screen = True; + + XWindowsUtil::ErrorLock lock(m_display); + XSendEvent(m_display, m_xscreensaver, False, 0, &event); + + m_disablePos = 20 - m_disablePos; + } +} + +void +XWindowsScreenSaver::activateDPMS(bool activate) +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + // DPMSForceLevel will generate a BadMatch if DPMS is disabled + XWindowsUtil::ErrorLock lock(m_display); + DPMSForceLevel(m_display, activate ? DPMSModeStandby : DPMSModeOn); + } +#endif +} + +void +XWindowsScreenSaver::enableDPMS(bool enable) +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + if (enable) { + DPMSEnable(m_display); + } + else { + DPMSDisable(m_display); + } + } +#endif +} + +bool +XWindowsScreenSaver::isDPMSEnabled() const +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + CARD16 level; + BOOL state; + DPMSInfo(m_display, &level, &state); + return (state != False); + } + else { + return false; + } +#else + return false; +#endif +} + +bool +XWindowsScreenSaver::isDPMSActivated() const +{ +#if HAVE_X11_EXTENSIONS_DPMS_H + if (m_dpms) { + CARD16 level; + BOOL state; + DPMSInfo(m_display, &level, &state); + return (level != DPMSModeOn); + } + else { + return false; + } +#else + return false; +#endif +} diff --git a/src/lib/platform/XWindowsScreenSaver.h b/src/lib/platform/XWindowsScreenSaver.h new file mode 100644 index 0000000..db85f41 --- /dev/null +++ b/src/lib/platform/XWindowsScreenSaver.h @@ -0,0 +1,169 @@ +/* + * 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 "barrier/IScreenSaver.h" +#include "base/IEventQueue.h" +#include "common/stdmap.h" + +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/Xlib.h> +#endif + +class Event; +class EventQueueTimer; + +//! X11 screen saver implementation +class XWindowsScreenSaver : public IScreenSaver { +public: + XWindowsScreenSaver(Display*, Window, void* eventTarget, IEventQueue* events); + virtual ~XWindowsScreenSaver(); + + //! @name manipulators + //@{ + + //! Event filtering + /*! + Should be called for each system event before event translation and + dispatch. Returns true to skip translation and dispatch. + */ + bool handleXEvent(const XEvent*); + + //! Destroy without the display + /*! + Tells this object to delete itself without using the X11 display. + It may leak some resources as a result. + */ + void destroy(); + + //@} + + // IScreenSaver overrides + virtual void enable(); + virtual void disable(); + virtual void activate(); + virtual void deactivate(); + virtual bool isActive() const; + +private: + // find and set the running xscreensaver's window. returns true iff + // found. + bool findXScreenSaver(); + + // set the xscreensaver's window, updating the activation state flag + void setXScreenSaver(Window); + + // returns true if the window appears to be the xscreensaver window + bool isXScreenSaver(Window) const; + + // set xscreensaver's activation state flag. sends notification + // if the state has changed. + void setXScreenSaverActive(bool activated); + + // send a command to xscreensaver + void sendXScreenSaverCommand(Atom, long = 0, long = 0); + + // watch all windows that could potentially be the xscreensaver for + // the events that will confirm it. + void watchForXScreenSaver(); + + // stop watching all watched windows + void clearWatchForXScreenSaver(); + + // add window to the watch list + void addWatchXScreenSaver(Window window); + + // install/uninstall the job used to suppress the screensaver + void updateDisableTimer(); + + // called periodically to prevent the screen saver from starting + void handleDisableTimer(const Event&, void*); + + // force DPMS to activate or deactivate + void activateDPMS(bool activate); + + // enable/disable DPMS screen saver + void enableDPMS(bool); + + // check if DPMS is enabled + bool isDPMSEnabled() const; + + // check if DPMS is activate + bool isDPMSActivated() const; + +private: + typedef std::map<Window, long> WatchList; + + // the X display + Display* m_display; + + // window to receive xscreensaver repsonses + Window m_xscreensaverSink; + + // the target for the events we generate + void* m_eventTarget; + + // xscreensaver's window + Window m_xscreensaver; + + // xscreensaver activation state + bool m_xscreensaverActive; + + // old event mask on root window + long m_rootEventMask; + + // potential xscreensaver windows being watched + WatchList m_watchWindows; + + // atoms used to communicate with xscreensaver's window + Atom m_atomScreenSaver; + Atom m_atomScreenSaverVersion; + Atom m_atomScreenSaverActivate; + Atom m_atomScreenSaverDeactivate; + + // built-in screen saver settings + int m_timeout; + int m_interval; + int m_preferBlanking; + int m_allowExposures; + + // DPMS screen saver settings + bool m_dpms; + bool m_dpmsEnabled; + + // true iff the client wants the screen saver suppressed + bool m_disabled; + + // true iff we're ignoring m_disabled. this is true, for example, + // when the client has called activate() and so presumably wants + // to activate the screen saver even if disabled. + bool m_suppressDisable; + + // the disable timer (NULL if not installed) + EventQueueTimer* m_disableTimer; + + // fake mouse motion position for suppressing the screen saver. + // xscreensaver since 2.21 requires the mouse to move more than 10 + // pixels to be considered significant. + SInt32 m_disablePos; + + IEventQueue* m_events; +}; diff --git a/src/lib/platform/XWindowsUtil.cpp b/src/lib/platform/XWindowsUtil.cpp new file mode 100644 index 0000000..65448e8 --- /dev/null +++ b/src/lib/platform/XWindowsUtil.cpp @@ -0,0 +1,1790 @@ +/* + * 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 "platform/XWindowsUtil.h" + +#include "barrier/key_types.h" +#include "mt/Thread.h" +#include "base/Log.h" +#include "base/String.h" + +#include <X11/Xatom.h> +#define XK_APL +#define XK_ARABIC +#define XK_ARMENIAN +#define XK_CAUCASUS +#define XK_CURRENCY +#define XK_CYRILLIC +#define XK_GEORGIAN +#define XK_GREEK +#define XK_HEBREW +#define XK_KATAKANA +#define XK_KOREAN +#define XK_LATIN1 +#define XK_LATIN2 +#define XK_LATIN3 +#define XK_LATIN4 +#define XK_LATIN8 +#define XK_LATIN9 +#define XK_MISCELLANY +#define XK_PUBLISHING +#define XK_SPECIAL +#define XK_TECHNICAL +#define XK_THAI +#define XK_VIETNAMESE +#define XK_XKB_KEYS +#include <X11/keysym.h> + +#if !defined(XK_OE) +#define XK_OE 0x13bc +#endif +#if !defined(XK_oe) +#define XK_oe 0x13bd +#endif +#if !defined(XK_Ydiaeresis) +#define XK_Ydiaeresis 0x13be +#endif + +/* + * This table maps keysym values into the corresponding ISO 10646 + * (UCS, Unicode) values. + * + * The array keysymtab[] contains pairs of X11 keysym values for graphical + * characters and the corresponding Unicode value. + * + * Author: Markus G. Kuhn <http://www.cl.cam.ac.uk/~mgk25/>, + * University of Cambridge, April 2001 + * + * Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing + * an initial draft of the mapping table. + * + * This software is in the public domain. Share and enjoy! + */ + +struct codepair { + KeySym keysym; + UInt32 ucs4; +} s_keymap[] = { +{ XK_Aogonek, 0x0104 }, /* LATIN CAPITAL LETTER A WITH OGONEK */ +{ XK_breve, 0x02d8 }, /* BREVE */ +{ XK_Lstroke, 0x0141 }, /* LATIN CAPITAL LETTER L WITH STROKE */ +{ XK_Lcaron, 0x013d }, /* LATIN CAPITAL LETTER L WITH CARON */ +{ XK_Sacute, 0x015a }, /* LATIN CAPITAL LETTER S WITH ACUTE */ +{ XK_Scaron, 0x0160 }, /* LATIN CAPITAL LETTER S WITH CARON */ +{ XK_Scedilla, 0x015e }, /* LATIN CAPITAL LETTER S WITH CEDILLA */ +{ XK_Tcaron, 0x0164 }, /* LATIN CAPITAL LETTER T WITH CARON */ +{ XK_Zacute, 0x0179 }, /* LATIN CAPITAL LETTER Z WITH ACUTE */ +{ XK_Zcaron, 0x017d }, /* LATIN CAPITAL LETTER Z WITH CARON */ +{ XK_Zabovedot, 0x017b }, /* LATIN CAPITAL LETTER Z WITH DOT ABOVE */ +{ XK_aogonek, 0x0105 }, /* LATIN SMALL LETTER A WITH OGONEK */ +{ XK_ogonek, 0x02db }, /* OGONEK */ +{ XK_lstroke, 0x0142 }, /* LATIN SMALL LETTER L WITH STROKE */ +{ XK_lcaron, 0x013e }, /* LATIN SMALL LETTER L WITH CARON */ +{ XK_sacute, 0x015b }, /* LATIN SMALL LETTER S WITH ACUTE */ +{ XK_caron, 0x02c7 }, /* CARON */ +{ XK_scaron, 0x0161 }, /* LATIN SMALL LETTER S WITH CARON */ +{ XK_scedilla, 0x015f }, /* LATIN SMALL LETTER S WITH CEDILLA */ +{ XK_tcaron, 0x0165 }, /* LATIN SMALL LETTER T WITH CARON */ +{ XK_zacute, 0x017a }, /* LATIN SMALL LETTER Z WITH ACUTE */ +{ XK_doubleacute, 0x02dd }, /* DOUBLE ACUTE ACCENT */ +{ XK_zcaron, 0x017e }, /* LATIN SMALL LETTER Z WITH CARON */ +{ XK_zabovedot, 0x017c }, /* LATIN SMALL LETTER Z WITH DOT ABOVE */ +{ XK_Racute, 0x0154 }, /* LATIN CAPITAL LETTER R WITH ACUTE */ +{ XK_Abreve, 0x0102 }, /* LATIN CAPITAL LETTER A WITH BREVE */ +{ XK_Lacute, 0x0139 }, /* LATIN CAPITAL LETTER L WITH ACUTE */ +{ XK_Cacute, 0x0106 }, /* LATIN CAPITAL LETTER C WITH ACUTE */ +{ XK_Ccaron, 0x010c }, /* LATIN CAPITAL LETTER C WITH CARON */ +{ XK_Eogonek, 0x0118 }, /* LATIN CAPITAL LETTER E WITH OGONEK */ +{ XK_Ecaron, 0x011a }, /* LATIN CAPITAL LETTER E WITH CARON */ +{ XK_Dcaron, 0x010e }, /* LATIN CAPITAL LETTER D WITH CARON */ +{ XK_Dstroke, 0x0110 }, /* LATIN CAPITAL LETTER D WITH STROKE */ +{ XK_Nacute, 0x0143 }, /* LATIN CAPITAL LETTER N WITH ACUTE */ +{ XK_Ncaron, 0x0147 }, /* LATIN CAPITAL LETTER N WITH CARON */ +{ XK_Odoubleacute, 0x0150 }, /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */ +{ XK_Rcaron, 0x0158 }, /* LATIN CAPITAL LETTER R WITH CARON */ +{ XK_Uring, 0x016e }, /* LATIN CAPITAL LETTER U WITH RING ABOVE */ +{ XK_Udoubleacute, 0x0170 }, /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */ +{ XK_Tcedilla, 0x0162 }, /* LATIN CAPITAL LETTER T WITH CEDILLA */ +{ XK_racute, 0x0155 }, /* LATIN SMALL LETTER R WITH ACUTE */ +{ XK_abreve, 0x0103 }, /* LATIN SMALL LETTER A WITH BREVE */ +{ XK_lacute, 0x013a }, /* LATIN SMALL LETTER L WITH ACUTE */ +{ XK_cacute, 0x0107 }, /* LATIN SMALL LETTER C WITH ACUTE */ +{ XK_ccaron, 0x010d }, /* LATIN SMALL LETTER C WITH CARON */ +{ XK_eogonek, 0x0119 }, /* LATIN SMALL LETTER E WITH OGONEK */ +{ XK_ecaron, 0x011b }, /* LATIN SMALL LETTER E WITH CARON */ +{ XK_dcaron, 0x010f }, /* LATIN SMALL LETTER D WITH CARON */ +{ XK_dstroke, 0x0111 }, /* LATIN SMALL LETTER D WITH STROKE */ +{ XK_nacute, 0x0144 }, /* LATIN SMALL LETTER N WITH ACUTE */ +{ XK_ncaron, 0x0148 }, /* LATIN SMALL LETTER N WITH CARON */ +{ XK_odoubleacute, 0x0151 }, /* LATIN SMALL LETTER O WITH DOUBLE ACUTE */ +{ XK_rcaron, 0x0159 }, /* LATIN SMALL LETTER R WITH CARON */ +{ XK_uring, 0x016f }, /* LATIN SMALL LETTER U WITH RING ABOVE */ +{ XK_udoubleacute, 0x0171 }, /* LATIN SMALL LETTER U WITH DOUBLE ACUTE */ +{ XK_tcedilla, 0x0163 }, /* LATIN SMALL LETTER T WITH CEDILLA */ +{ XK_abovedot, 0x02d9 }, /* DOT ABOVE */ +{ XK_Hstroke, 0x0126 }, /* LATIN CAPITAL LETTER H WITH STROKE */ +{ XK_Hcircumflex, 0x0124 }, /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX */ +{ XK_Iabovedot, 0x0130 }, /* LATIN CAPITAL LETTER I WITH DOT ABOVE */ +{ XK_Gbreve, 0x011e }, /* LATIN CAPITAL LETTER G WITH BREVE */ +{ XK_Jcircumflex, 0x0134 }, /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX */ +{ XK_hstroke, 0x0127 }, /* LATIN SMALL LETTER H WITH STROKE */ +{ XK_hcircumflex, 0x0125 }, /* LATIN SMALL LETTER H WITH CIRCUMFLEX */ +{ XK_idotless, 0x0131 }, /* LATIN SMALL LETTER DOTLESS I */ +{ XK_gbreve, 0x011f }, /* LATIN SMALL LETTER G WITH BREVE */ +{ XK_jcircumflex, 0x0135 }, /* LATIN SMALL LETTER J WITH CIRCUMFLEX */ +{ XK_Cabovedot, 0x010a }, /* LATIN CAPITAL LETTER C WITH DOT ABOVE */ +{ XK_Ccircumflex, 0x0108 }, /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX */ +{ XK_Gabovedot, 0x0120 }, /* LATIN CAPITAL LETTER G WITH DOT ABOVE */ +{ XK_Gcircumflex, 0x011c }, /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX */ +{ XK_Ubreve, 0x016c }, /* LATIN CAPITAL LETTER U WITH BREVE */ +{ XK_Scircumflex, 0x015c }, /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX */ +{ XK_cabovedot, 0x010b }, /* LATIN SMALL LETTER C WITH DOT ABOVE */ +{ XK_ccircumflex, 0x0109 }, /* LATIN SMALL LETTER C WITH CIRCUMFLEX */ +{ XK_gabovedot, 0x0121 }, /* LATIN SMALL LETTER G WITH DOT ABOVE */ +{ XK_gcircumflex, 0x011d }, /* LATIN SMALL LETTER G WITH CIRCUMFLEX */ +{ XK_ubreve, 0x016d }, /* LATIN SMALL LETTER U WITH BREVE */ +{ XK_scircumflex, 0x015d }, /* LATIN SMALL LETTER S WITH CIRCUMFLEX */ +{ XK_kra, 0x0138 }, /* LATIN SMALL LETTER KRA */ +{ XK_Rcedilla, 0x0156 }, /* LATIN CAPITAL LETTER R WITH CEDILLA */ +{ XK_Itilde, 0x0128 }, /* LATIN CAPITAL LETTER I WITH TILDE */ +{ XK_Lcedilla, 0x013b }, /* LATIN CAPITAL LETTER L WITH CEDILLA */ +{ XK_Emacron, 0x0112 }, /* LATIN CAPITAL LETTER E WITH MACRON */ +{ XK_Gcedilla, 0x0122 }, /* LATIN CAPITAL LETTER G WITH CEDILLA */ +{ XK_Tslash, 0x0166 }, /* LATIN CAPITAL LETTER T WITH STROKE */ +{ XK_rcedilla, 0x0157 }, /* LATIN SMALL LETTER R WITH CEDILLA */ +{ XK_itilde, 0x0129 }, /* LATIN SMALL LETTER I WITH TILDE */ +{ XK_lcedilla, 0x013c }, /* LATIN SMALL LETTER L WITH CEDILLA */ +{ XK_emacron, 0x0113 }, /* LATIN SMALL LETTER E WITH MACRON */ +{ XK_gcedilla, 0x0123 }, /* LATIN SMALL LETTER G WITH CEDILLA */ +{ XK_tslash, 0x0167 }, /* LATIN SMALL LETTER T WITH STROKE */ +{ XK_ENG, 0x014a }, /* LATIN CAPITAL LETTER ENG */ +{ XK_eng, 0x014b }, /* LATIN SMALL LETTER ENG */ +{ XK_Amacron, 0x0100 }, /* LATIN CAPITAL LETTER A WITH MACRON */ +{ XK_Iogonek, 0x012e }, /* LATIN CAPITAL LETTER I WITH OGONEK */ +{ XK_Eabovedot, 0x0116 }, /* LATIN CAPITAL LETTER E WITH DOT ABOVE */ +{ XK_Imacron, 0x012a }, /* LATIN CAPITAL LETTER I WITH MACRON */ +{ XK_Ncedilla, 0x0145 }, /* LATIN CAPITAL LETTER N WITH CEDILLA */ +{ XK_Omacron, 0x014c }, /* LATIN CAPITAL LETTER O WITH MACRON */ +{ XK_Kcedilla, 0x0136 }, /* LATIN CAPITAL LETTER K WITH CEDILLA */ +{ XK_Uogonek, 0x0172 }, /* LATIN CAPITAL LETTER U WITH OGONEK */ +{ XK_Utilde, 0x0168 }, /* LATIN CAPITAL LETTER U WITH TILDE */ +{ XK_Umacron, 0x016a }, /* LATIN CAPITAL LETTER U WITH MACRON */ +{ XK_amacron, 0x0101 }, /* LATIN SMALL LETTER A WITH MACRON */ +{ XK_iogonek, 0x012f }, /* LATIN SMALL LETTER I WITH OGONEK */ +{ XK_eabovedot, 0x0117 }, /* LATIN SMALL LETTER E WITH DOT ABOVE */ +{ XK_imacron, 0x012b }, /* LATIN SMALL LETTER I WITH MACRON */ +{ XK_ncedilla, 0x0146 }, /* LATIN SMALL LETTER N WITH CEDILLA */ +{ XK_omacron, 0x014d }, /* LATIN SMALL LETTER O WITH MACRON */ +{ XK_kcedilla, 0x0137 }, /* LATIN SMALL LETTER K WITH CEDILLA */ +{ XK_uogonek, 0x0173 }, /* LATIN SMALL LETTER U WITH OGONEK */ +{ XK_utilde, 0x0169 }, /* LATIN SMALL LETTER U WITH TILDE */ +{ XK_umacron, 0x016b }, /* LATIN SMALL LETTER U WITH MACRON */ +#if defined(XK_Babovedot) +{ XK_Babovedot, 0x1e02 }, /* LATIN CAPITAL LETTER B WITH DOT ABOVE */ +{ XK_babovedot, 0x1e03 }, /* LATIN SMALL LETTER B WITH DOT ABOVE */ +{ XK_Dabovedot, 0x1e0a }, /* LATIN CAPITAL LETTER D WITH DOT ABOVE */ +{ XK_Wgrave, 0x1e80 }, /* LATIN CAPITAL LETTER W WITH GRAVE */ +{ XK_Wacute, 0x1e82 }, /* LATIN CAPITAL LETTER W WITH ACUTE */ +{ XK_dabovedot, 0x1e0b }, /* LATIN SMALL LETTER D WITH DOT ABOVE */ +{ XK_Ygrave, 0x1ef2 }, /* LATIN CAPITAL LETTER Y WITH GRAVE */ +{ XK_Fabovedot, 0x1e1e }, /* LATIN CAPITAL LETTER F WITH DOT ABOVE */ +{ XK_fabovedot, 0x1e1f }, /* LATIN SMALL LETTER F WITH DOT ABOVE */ +{ XK_Mabovedot, 0x1e40 }, /* LATIN CAPITAL LETTER M WITH DOT ABOVE */ +{ XK_mabovedot, 0x1e41 }, /* LATIN SMALL LETTER M WITH DOT ABOVE */ +{ XK_Pabovedot, 0x1e56 }, /* LATIN CAPITAL LETTER P WITH DOT ABOVE */ +{ XK_wgrave, 0x1e81 }, /* LATIN SMALL LETTER W WITH GRAVE */ +{ XK_pabovedot, 0x1e57 }, /* LATIN SMALL LETTER P WITH DOT ABOVE */ +{ XK_wacute, 0x1e83 }, /* LATIN SMALL LETTER W WITH ACUTE */ +{ XK_Sabovedot, 0x1e60 }, /* LATIN CAPITAL LETTER S WITH DOT ABOVE */ +{ XK_ygrave, 0x1ef3 }, /* LATIN SMALL LETTER Y WITH GRAVE */ +{ XK_Wdiaeresis, 0x1e84 }, /* LATIN CAPITAL LETTER W WITH DIAERESIS */ +{ XK_wdiaeresis, 0x1e85 }, /* LATIN SMALL LETTER W WITH DIAERESIS */ +{ XK_sabovedot, 0x1e61 }, /* LATIN SMALL LETTER S WITH DOT ABOVE */ +{ XK_Wcircumflex, 0x0174 }, /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX */ +{ XK_Tabovedot, 0x1e6a }, /* LATIN CAPITAL LETTER T WITH DOT ABOVE */ +{ XK_Ycircumflex, 0x0176 }, /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */ +{ XK_wcircumflex, 0x0175 }, /* LATIN SMALL LETTER W WITH CIRCUMFLEX */ +{ XK_tabovedot, 0x1e6b }, /* LATIN SMALL LETTER T WITH DOT ABOVE */ +{ XK_ycircumflex, 0x0177 }, /* LATIN SMALL LETTER Y WITH CIRCUMFLEX */ +#endif // defined(XK_Babovedot) +#if defined(XK_overline) +{ XK_overline, 0x203e }, /* OVERLINE */ +{ XK_kana_fullstop, 0x3002 }, /* IDEOGRAPHIC FULL STOP */ +{ XK_kana_openingbracket, 0x300c }, /* LEFT CORNER BRACKET */ +{ XK_kana_closingbracket, 0x300d }, /* RIGHT CORNER BRACKET */ +{ XK_kana_comma, 0x3001 }, /* IDEOGRAPHIC COMMA */ +{ XK_kana_conjunctive, 0x30fb }, /* KATAKANA MIDDLE DOT */ +{ XK_kana_WO, 0x30f2 }, /* KATAKANA LETTER WO */ +{ XK_kana_a, 0x30a1 }, /* KATAKANA LETTER SMALL A */ +{ XK_kana_i, 0x30a3 }, /* KATAKANA LETTER SMALL I */ +{ XK_kana_u, 0x30a5 }, /* KATAKANA LETTER SMALL U */ +{ XK_kana_e, 0x30a7 }, /* KATAKANA LETTER SMALL E */ +{ XK_kana_o, 0x30a9 }, /* KATAKANA LETTER SMALL O */ +{ XK_kana_ya, 0x30e3 }, /* KATAKANA LETTER SMALL YA */ +{ XK_kana_yu, 0x30e5 }, /* KATAKANA LETTER SMALL YU */ +{ XK_kana_yo, 0x30e7 }, /* KATAKANA LETTER SMALL YO */ +{ XK_kana_tsu, 0x30c3 }, /* KATAKANA LETTER SMALL TU */ +{ XK_prolongedsound, 0x30fc }, /* KATAKANA-HIRAGANA PROLONGED SOUND MARK */ +{ XK_kana_A, 0x30a2 }, /* KATAKANA LETTER A */ +{ XK_kana_I, 0x30a4 }, /* KATAKANA LETTER I */ +{ XK_kana_U, 0x30a6 }, /* KATAKANA LETTER U */ +{ XK_kana_E, 0x30a8 }, /* KATAKANA LETTER E */ +{ XK_kana_O, 0x30aa }, /* KATAKANA LETTER O */ +{ XK_kana_KA, 0x30ab }, /* KATAKANA LETTER KA */ +{ XK_kana_KI, 0x30ad }, /* KATAKANA LETTER KI */ +{ XK_kana_KU, 0x30af }, /* KATAKANA LETTER KU */ +{ XK_kana_KE, 0x30b1 }, /* KATAKANA LETTER KE */ +{ XK_kana_KO, 0x30b3 }, /* KATAKANA LETTER KO */ +{ XK_kana_SA, 0x30b5 }, /* KATAKANA LETTER SA */ +{ XK_kana_SHI, 0x30b7 }, /* KATAKANA LETTER SI */ +{ XK_kana_SU, 0x30b9 }, /* KATAKANA LETTER SU */ +{ XK_kana_SE, 0x30bb }, /* KATAKANA LETTER SE */ +{ XK_kana_SO, 0x30bd }, /* KATAKANA LETTER SO */ +{ XK_kana_TA, 0x30bf }, /* KATAKANA LETTER TA */ +{ XK_kana_CHI, 0x30c1 }, /* KATAKANA LETTER TI */ +{ XK_kana_TSU, 0x30c4 }, /* KATAKANA LETTER TU */ +{ XK_kana_TE, 0x30c6 }, /* KATAKANA LETTER TE */ +{ XK_kana_TO, 0x30c8 }, /* KATAKANA LETTER TO */ +{ XK_kana_NA, 0x30ca }, /* KATAKANA LETTER NA */ +{ XK_kana_NI, 0x30cb }, /* KATAKANA LETTER NI */ +{ XK_kana_NU, 0x30cc }, /* KATAKANA LETTER NU */ +{ XK_kana_NE, 0x30cd }, /* KATAKANA LETTER NE */ +{ XK_kana_NO, 0x30ce }, /* KATAKANA LETTER NO */ +{ XK_kana_HA, 0x30cf }, /* KATAKANA LETTER HA */ +{ XK_kana_HI, 0x30d2 }, /* KATAKANA LETTER HI */ +{ XK_kana_FU, 0x30d5 }, /* KATAKANA LETTER HU */ +{ XK_kana_HE, 0x30d8 }, /* KATAKANA LETTER HE */ +{ XK_kana_HO, 0x30db }, /* KATAKANA LETTER HO */ +{ XK_kana_MA, 0x30de }, /* KATAKANA LETTER MA */ +{ XK_kana_MI, 0x30df }, /* KATAKANA LETTER MI */ +{ XK_kana_MU, 0x30e0 }, /* KATAKANA LETTER MU */ +{ XK_kana_ME, 0x30e1 }, /* KATAKANA LETTER ME */ +{ XK_kana_MO, 0x30e2 }, /* KATAKANA LETTER MO */ +{ XK_kana_YA, 0x30e4 }, /* KATAKANA LETTER YA */ +{ XK_kana_YU, 0x30e6 }, /* KATAKANA LETTER YU */ +{ XK_kana_YO, 0x30e8 }, /* KATAKANA LETTER YO */ +{ XK_kana_RA, 0x30e9 }, /* KATAKANA LETTER RA */ +{ XK_kana_RI, 0x30ea }, /* KATAKANA LETTER RI */ +{ XK_kana_RU, 0x30eb }, /* KATAKANA LETTER RU */ +{ XK_kana_RE, 0x30ec }, /* KATAKANA LETTER RE */ +{ XK_kana_RO, 0x30ed }, /* KATAKANA LETTER RO */ +{ XK_kana_WA, 0x30ef }, /* KATAKANA LETTER WA */ +{ XK_kana_N, 0x30f3 }, /* KATAKANA LETTER N */ +{ XK_voicedsound, 0x309b }, /* KATAKANA-HIRAGANA VOICED SOUND MARK */ +{ XK_semivoicedsound, 0x309c }, /* KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ +#endif // defined(XK_overline) +#if defined(XK_Farsi_0) +{ XK_Farsi_0, 0x06f0 }, /* EXTENDED ARABIC-INDIC DIGIT 0 */ +{ XK_Farsi_1, 0x06f1 }, /* EXTENDED ARABIC-INDIC DIGIT 1 */ +{ XK_Farsi_2, 0x06f2 }, /* EXTENDED ARABIC-INDIC DIGIT 2 */ +{ XK_Farsi_3, 0x06f3 }, /* EXTENDED ARABIC-INDIC DIGIT 3 */ +{ XK_Farsi_4, 0x06f4 }, /* EXTENDED ARABIC-INDIC DIGIT 4 */ +{ XK_Farsi_5, 0x06f5 }, /* EXTENDED ARABIC-INDIC DIGIT 5 */ +{ XK_Farsi_6, 0x06f6 }, /* EXTENDED ARABIC-INDIC DIGIT 6 */ +{ XK_Farsi_7, 0x06f7 }, /* EXTENDED ARABIC-INDIC DIGIT 7 */ +{ XK_Farsi_8, 0x06f8 }, /* EXTENDED ARABIC-INDIC DIGIT 8 */ +{ XK_Farsi_9, 0x06f9 }, /* EXTENDED ARABIC-INDIC DIGIT 9 */ +{ XK_Arabic_percent, 0x066a }, /* ARABIC PERCENT */ +{ XK_Arabic_superscript_alef, 0x0670 }, /* ARABIC LETTER SUPERSCRIPT ALEF */ +{ XK_Arabic_tteh, 0x0679 }, /* ARABIC LETTER TTEH */ +{ XK_Arabic_peh, 0x067e }, /* ARABIC LETTER PEH */ +{ XK_Arabic_tcheh, 0x0686 }, /* ARABIC LETTER TCHEH */ +{ XK_Arabic_ddal, 0x0688 }, /* ARABIC LETTER DDAL */ +{ XK_Arabic_rreh, 0x0691 }, /* ARABIC LETTER RREH */ +{ XK_Arabic_comma, 0x060c }, /* ARABIC COMMA */ +{ XK_Arabic_fullstop, 0x06d4 }, /* ARABIC FULLSTOP */ +{ XK_Arabic_semicolon, 0x061b }, /* ARABIC SEMICOLON */ +{ XK_Arabic_0, 0x0660 }, /* ARABIC 0 */ +{ XK_Arabic_1, 0x0661 }, /* ARABIC 1 */ +{ XK_Arabic_2, 0x0662 }, /* ARABIC 2 */ +{ XK_Arabic_3, 0x0663 }, /* ARABIC 3 */ +{ XK_Arabic_4, 0x0664 }, /* ARABIC 4 */ +{ XK_Arabic_5, 0x0665 }, /* ARABIC 5 */ +{ XK_Arabic_6, 0x0666 }, /* ARABIC 6 */ +{ XK_Arabic_7, 0x0667 }, /* ARABIC 7 */ +{ XK_Arabic_8, 0x0668 }, /* ARABIC 8 */ +{ XK_Arabic_9, 0x0669 }, /* ARABIC 9 */ +{ XK_Arabic_question_mark, 0x061f }, /* ARABIC QUESTION MARK */ +{ XK_Arabic_hamza, 0x0621 }, /* ARABIC LETTER HAMZA */ +{ XK_Arabic_maddaonalef, 0x0622 }, /* ARABIC LETTER ALEF WITH MADDA ABOVE */ +{ XK_Arabic_hamzaonalef, 0x0623 }, /* ARABIC LETTER ALEF WITH HAMZA ABOVE */ +{ XK_Arabic_hamzaonwaw, 0x0624 }, /* ARABIC LETTER WAW WITH HAMZA ABOVE */ +{ XK_Arabic_hamzaunderalef, 0x0625 }, /* ARABIC LETTER ALEF WITH HAMZA BELOW */ +{ XK_Arabic_hamzaonyeh, 0x0626 }, /* ARABIC LETTER YEH WITH HAMZA ABOVE */ +{ XK_Arabic_alef, 0x0627 }, /* ARABIC LETTER ALEF */ +{ XK_Arabic_beh, 0x0628 }, /* ARABIC LETTER BEH */ +{ XK_Arabic_tehmarbuta, 0x0629 }, /* ARABIC LETTER TEH MARBUTA */ +{ XK_Arabic_teh, 0x062a }, /* ARABIC LETTER TEH */ +{ XK_Arabic_theh, 0x062b }, /* ARABIC LETTER THEH */ +{ XK_Arabic_jeem, 0x062c }, /* ARABIC LETTER JEEM */ +{ XK_Arabic_hah, 0x062d }, /* ARABIC LETTER HAH */ +{ XK_Arabic_khah, 0x062e }, /* ARABIC LETTER KHAH */ +{ XK_Arabic_dal, 0x062f }, /* ARABIC LETTER DAL */ +{ XK_Arabic_thal, 0x0630 }, /* ARABIC LETTER THAL */ +{ XK_Arabic_ra, 0x0631 }, /* ARABIC LETTER REH */ +{ XK_Arabic_zain, 0x0632 }, /* ARABIC LETTER ZAIN */ +{ XK_Arabic_seen, 0x0633 }, /* ARABIC LETTER SEEN */ +{ XK_Arabic_sheen, 0x0634 }, /* ARABIC LETTER SHEEN */ +{ XK_Arabic_sad, 0x0635 }, /* ARABIC LETTER SAD */ +{ XK_Arabic_dad, 0x0636 }, /* ARABIC LETTER DAD */ +{ XK_Arabic_tah, 0x0637 }, /* ARABIC LETTER TAH */ +{ XK_Arabic_zah, 0x0638 }, /* ARABIC LETTER ZAH */ +{ XK_Arabic_ain, 0x0639 }, /* ARABIC LETTER AIN */ +{ XK_Arabic_ghain, 0x063a }, /* ARABIC LETTER GHAIN */ +{ XK_Arabic_tatweel, 0x0640 }, /* ARABIC TATWEEL */ +{ XK_Arabic_feh, 0x0641 }, /* ARABIC LETTER FEH */ +{ XK_Arabic_qaf, 0x0642 }, /* ARABIC LETTER QAF */ +{ XK_Arabic_kaf, 0x0643 }, /* ARABIC LETTER KAF */ +{ XK_Arabic_lam, 0x0644 }, /* ARABIC LETTER LAM */ +{ XK_Arabic_meem, 0x0645 }, /* ARABIC LETTER MEEM */ +{ XK_Arabic_noon, 0x0646 }, /* ARABIC LETTER NOON */ +{ XK_Arabic_ha, 0x0647 }, /* ARABIC LETTER HEH */ +{ XK_Arabic_waw, 0x0648 }, /* ARABIC LETTER WAW */ +{ XK_Arabic_alefmaksura, 0x0649 }, /* ARABIC LETTER ALEF MAKSURA */ +{ XK_Arabic_yeh, 0x064a }, /* ARABIC LETTER YEH */ +{ XK_Arabic_fathatan, 0x064b }, /* ARABIC FATHATAN */ +{ XK_Arabic_dammatan, 0x064c }, /* ARABIC DAMMATAN */ +{ XK_Arabic_kasratan, 0x064d }, /* ARABIC KASRATAN */ +{ XK_Arabic_fatha, 0x064e }, /* ARABIC FATHA */ +{ XK_Arabic_damma, 0x064f }, /* ARABIC DAMMA */ +{ XK_Arabic_kasra, 0x0650 }, /* ARABIC KASRA */ +{ XK_Arabic_shadda, 0x0651 }, /* ARABIC SHADDA */ +{ XK_Arabic_sukun, 0x0652 }, /* ARABIC SUKUN */ +{ XK_Arabic_madda_above, 0x0653 }, /* ARABIC MADDA ABOVE */ +{ XK_Arabic_hamza_above, 0x0654 }, /* ARABIC HAMZA ABOVE */ +{ XK_Arabic_hamza_below, 0x0655 }, /* ARABIC HAMZA BELOW */ +{ XK_Arabic_jeh, 0x0698 }, /* ARABIC LETTER JEH */ +{ XK_Arabic_veh, 0x06a4 }, /* ARABIC LETTER VEH */ +{ XK_Arabic_keheh, 0x06a9 }, /* ARABIC LETTER KEHEH */ +{ XK_Arabic_gaf, 0x06af }, /* ARABIC LETTER GAF */ +{ XK_Arabic_noon_ghunna, 0x06ba }, /* ARABIC LETTER NOON GHUNNA */ +{ XK_Arabic_heh_doachashmee, 0x06be }, /* ARABIC LETTER HEH DOACHASHMEE */ +{ XK_Arabic_farsi_yeh, 0x06cc }, /* ARABIC LETTER FARSI YEH */ +{ XK_Arabic_yeh_baree, 0x06d2 }, /* ARABIC LETTER YEH BAREE */ +{ XK_Arabic_heh_goal, 0x06c1 }, /* ARABIC LETTER HEH GOAL */ +#endif // defined(XK_Farsi_0) +#if defined(XK_Serbian_dje) +{ XK_Serbian_dje, 0x0452 }, /* CYRILLIC SMALL LETTER DJE */ +{ XK_Macedonia_gje, 0x0453 }, /* CYRILLIC SMALL LETTER GJE */ +{ XK_Cyrillic_io, 0x0451 }, /* CYRILLIC SMALL LETTER IO */ +{ XK_Ukrainian_ie, 0x0454 }, /* CYRILLIC SMALL LETTER UKRAINIAN IE */ +{ XK_Macedonia_dse, 0x0455 }, /* CYRILLIC SMALL LETTER DZE */ +{ XK_Ukrainian_i, 0x0456 }, /* CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I */ +{ XK_Ukrainian_yi, 0x0457 }, /* CYRILLIC SMALL LETTER YI */ +{ XK_Cyrillic_je, 0x0458 }, /* CYRILLIC SMALL LETTER JE */ +{ XK_Cyrillic_lje, 0x0459 }, /* CYRILLIC SMALL LETTER LJE */ +{ XK_Cyrillic_nje, 0x045a }, /* CYRILLIC SMALL LETTER NJE */ +{ XK_Serbian_tshe, 0x045b }, /* CYRILLIC SMALL LETTER TSHE */ +{ XK_Macedonia_kje, 0x045c }, /* CYRILLIC SMALL LETTER KJE */ +#if defined(XK_Ukrainian_ghe_with_upturn) +{ XK_Ukrainian_ghe_with_upturn, 0x0491 }, /* CYRILLIC SMALL LETTER GHE WITH UPTURN */ +#endif +{ XK_Byelorussian_shortu, 0x045e }, /* CYRILLIC SMALL LETTER SHORT U */ +{ XK_Cyrillic_dzhe, 0x045f }, /* CYRILLIC SMALL LETTER DZHE */ +{ XK_numerosign, 0x2116 }, /* NUMERO SIGN */ +{ XK_Serbian_DJE, 0x0402 }, /* CYRILLIC CAPITAL LETTER DJE */ +{ XK_Macedonia_GJE, 0x0403 }, /* CYRILLIC CAPITAL LETTER GJE */ +{ XK_Cyrillic_IO, 0x0401 }, /* CYRILLIC CAPITAL LETTER IO */ +{ XK_Ukrainian_IE, 0x0404 }, /* CYRILLIC CAPITAL LETTER UKRAINIAN IE */ +{ XK_Macedonia_DSE, 0x0405 }, /* CYRILLIC CAPITAL LETTER DZE */ +{ XK_Ukrainian_I, 0x0406 }, /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */ +{ XK_Ukrainian_YI, 0x0407 }, /* CYRILLIC CAPITAL LETTER YI */ +{ XK_Cyrillic_JE, 0x0408 }, /* CYRILLIC CAPITAL LETTER JE */ +{ XK_Cyrillic_LJE, 0x0409 }, /* CYRILLIC CAPITAL LETTER LJE */ +{ XK_Cyrillic_NJE, 0x040a }, /* CYRILLIC CAPITAL LETTER NJE */ +{ XK_Serbian_TSHE, 0x040b }, /* CYRILLIC CAPITAL LETTER TSHE */ +{ XK_Macedonia_KJE, 0x040c }, /* CYRILLIC CAPITAL LETTER KJE */ +#if defined(XK_Ukrainian_GHE_WITH_UPTURN) +{ XK_Ukrainian_GHE_WITH_UPTURN, 0x0490 }, /* CYRILLIC CAPITAL LETTER GHE WITH UPTURN */ +#endif +{ XK_Byelorussian_SHORTU, 0x040e }, /* CYRILLIC CAPITAL LETTER SHORT U */ +{ XK_Cyrillic_DZHE, 0x040f }, /* CYRILLIC CAPITAL LETTER DZHE */ +{ XK_Cyrillic_yu, 0x044e }, /* CYRILLIC SMALL LETTER YU */ +{ XK_Cyrillic_a, 0x0430 }, /* CYRILLIC SMALL LETTER A */ +{ XK_Cyrillic_be, 0x0431 }, /* CYRILLIC SMALL LETTER BE */ +{ XK_Cyrillic_tse, 0x0446 }, /* CYRILLIC SMALL LETTER TSE */ +{ XK_Cyrillic_de, 0x0434 }, /* CYRILLIC SMALL LETTER DE */ +{ XK_Cyrillic_ie, 0x0435 }, /* CYRILLIC SMALL LETTER IE */ +{ XK_Cyrillic_ef, 0x0444 }, /* CYRILLIC SMALL LETTER EF */ +{ XK_Cyrillic_ghe, 0x0433 }, /* CYRILLIC SMALL LETTER GHE */ +{ XK_Cyrillic_ha, 0x0445 }, /* CYRILLIC SMALL LETTER HA */ +{ XK_Cyrillic_i, 0x0438 }, /* CYRILLIC SMALL LETTER I */ +{ XK_Cyrillic_shorti, 0x0439 }, /* CYRILLIC SMALL LETTER SHORT I */ +{ XK_Cyrillic_ka, 0x043a }, /* CYRILLIC SMALL LETTER KA */ +{ XK_Cyrillic_el, 0x043b }, /* CYRILLIC SMALL LETTER EL */ +{ XK_Cyrillic_em, 0x043c }, /* CYRILLIC SMALL LETTER EM */ +{ XK_Cyrillic_en, 0x043d }, /* CYRILLIC SMALL LETTER EN */ +{ XK_Cyrillic_o, 0x043e }, /* CYRILLIC SMALL LETTER O */ +{ XK_Cyrillic_pe, 0x043f }, /* CYRILLIC SMALL LETTER PE */ +{ XK_Cyrillic_ya, 0x044f }, /* CYRILLIC SMALL LETTER YA */ +{ XK_Cyrillic_er, 0x0440 }, /* CYRILLIC SMALL LETTER ER */ +{ XK_Cyrillic_es, 0x0441 }, /* CYRILLIC SMALL LETTER ES */ +{ XK_Cyrillic_te, 0x0442 }, /* CYRILLIC SMALL LETTER TE */ +{ XK_Cyrillic_u, 0x0443 }, /* CYRILLIC SMALL LETTER U */ +{ XK_Cyrillic_zhe, 0x0436 }, /* CYRILLIC SMALL LETTER ZHE */ +{ XK_Cyrillic_ve, 0x0432 }, /* CYRILLIC SMALL LETTER VE */ +{ XK_Cyrillic_softsign, 0x044c }, /* CYRILLIC SMALL LETTER SOFT SIGN */ +{ XK_Cyrillic_yeru, 0x044b }, /* CYRILLIC SMALL LETTER YERU */ +{ XK_Cyrillic_ze, 0x0437 }, /* CYRILLIC SMALL LETTER ZE */ +{ XK_Cyrillic_sha, 0x0448 }, /* CYRILLIC SMALL LETTER SHA */ +{ XK_Cyrillic_e, 0x044d }, /* CYRILLIC SMALL LETTER E */ +{ XK_Cyrillic_shcha, 0x0449 }, /* CYRILLIC SMALL LETTER SHCHA */ +{ XK_Cyrillic_che, 0x0447 }, /* CYRILLIC SMALL LETTER CHE */ +{ XK_Cyrillic_hardsign, 0x044a }, /* CYRILLIC SMALL LETTER HARD SIGN */ +{ XK_Cyrillic_YU, 0x042e }, /* CYRILLIC CAPITAL LETTER YU */ +{ XK_Cyrillic_A, 0x0410 }, /* CYRILLIC CAPITAL LETTER A */ +{ XK_Cyrillic_BE, 0x0411 }, /* CYRILLIC CAPITAL LETTER BE */ +{ XK_Cyrillic_TSE, 0x0426 }, /* CYRILLIC CAPITAL LETTER TSE */ +{ XK_Cyrillic_DE, 0x0414 }, /* CYRILLIC CAPITAL LETTER DE */ +{ XK_Cyrillic_IE, 0x0415 }, /* CYRILLIC CAPITAL LETTER IE */ +{ XK_Cyrillic_EF, 0x0424 }, /* CYRILLIC CAPITAL LETTER EF */ +{ XK_Cyrillic_GHE, 0x0413 }, /* CYRILLIC CAPITAL LETTER GHE */ +{ XK_Cyrillic_HA, 0x0425 }, /* CYRILLIC CAPITAL LETTER HA */ +{ XK_Cyrillic_I, 0x0418 }, /* CYRILLIC CAPITAL LETTER I */ +{ XK_Cyrillic_SHORTI, 0x0419 }, /* CYRILLIC CAPITAL LETTER SHORT I */ +{ XK_Cyrillic_KA, 0x041a }, /* CYRILLIC CAPITAL LETTER KA */ +{ XK_Cyrillic_EL, 0x041b }, /* CYRILLIC CAPITAL LETTER EL */ +{ XK_Cyrillic_EM, 0x041c }, /* CYRILLIC CAPITAL LETTER EM */ +{ XK_Cyrillic_EN, 0x041d }, /* CYRILLIC CAPITAL LETTER EN */ +{ XK_Cyrillic_O, 0x041e }, /* CYRILLIC CAPITAL LETTER O */ +{ XK_Cyrillic_PE, 0x041f }, /* CYRILLIC CAPITAL LETTER PE */ +{ XK_Cyrillic_YA, 0x042f }, /* CYRILLIC CAPITAL LETTER YA */ +{ XK_Cyrillic_ER, 0x0420 }, /* CYRILLIC CAPITAL LETTER ER */ +{ XK_Cyrillic_ES, 0x0421 }, /* CYRILLIC CAPITAL LETTER ES */ +{ XK_Cyrillic_TE, 0x0422 }, /* CYRILLIC CAPITAL LETTER TE */ +{ XK_Cyrillic_U, 0x0423 }, /* CYRILLIC CAPITAL LETTER U */ +{ XK_Cyrillic_ZHE, 0x0416 }, /* CYRILLIC CAPITAL LETTER ZHE */ +{ XK_Cyrillic_VE, 0x0412 }, /* CYRILLIC CAPITAL LETTER VE */ +{ XK_Cyrillic_SOFTSIGN, 0x042c }, /* CYRILLIC CAPITAL LETTER SOFT SIGN */ +{ XK_Cyrillic_YERU, 0x042b }, /* CYRILLIC CAPITAL LETTER YERU */ +{ XK_Cyrillic_ZE, 0x0417 }, /* CYRILLIC CAPITAL LETTER ZE */ +{ XK_Cyrillic_SHA, 0x0428 }, /* CYRILLIC CAPITAL LETTER SHA */ +{ XK_Cyrillic_E, 0x042d }, /* CYRILLIC CAPITAL LETTER E */ +{ XK_Cyrillic_SHCHA, 0x0429 }, /* CYRILLIC CAPITAL LETTER SHCHA */ +{ XK_Cyrillic_CHE, 0x0427 }, /* CYRILLIC CAPITAL LETTER CHE */ +{ XK_Cyrillic_HARDSIGN, 0x042a }, /* CYRILLIC CAPITAL LETTER HARD SIGN */ +#endif // defined(XK_Serbian_dje) +#if defined(XK_Greek_ALPHAaccent) +{ XK_Greek_ALPHAaccent, 0x0386 }, /* GREEK CAPITAL LETTER ALPHA WITH TONOS */ +{ XK_Greek_EPSILONaccent, 0x0388 }, /* GREEK CAPITAL LETTER EPSILON WITH TONOS */ +{ XK_Greek_ETAaccent, 0x0389 }, /* GREEK CAPITAL LETTER ETA WITH TONOS */ +{ XK_Greek_IOTAaccent, 0x038a }, /* GREEK CAPITAL LETTER IOTA WITH TONOS */ +{ XK_Greek_IOTAdiaeresis, 0x03aa }, /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */ +{ XK_Greek_OMICRONaccent, 0x038c }, /* GREEK CAPITAL LETTER OMICRON WITH TONOS */ +{ XK_Greek_UPSILONaccent, 0x038e }, /* GREEK CAPITAL LETTER UPSILON WITH TONOS */ +{ XK_Greek_UPSILONdieresis, 0x03ab }, /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */ +{ XK_Greek_OMEGAaccent, 0x038f }, /* GREEK CAPITAL LETTER OMEGA WITH TONOS */ +{ XK_Greek_accentdieresis, 0x0385 }, /* GREEK DIALYTIKA TONOS */ +{ XK_Greek_horizbar, 0x2015 }, /* HORIZONTAL BAR */ +{ XK_Greek_alphaaccent, 0x03ac }, /* GREEK SMALL LETTER ALPHA WITH TONOS */ +{ XK_Greek_epsilonaccent, 0x03ad }, /* GREEK SMALL LETTER EPSILON WITH TONOS */ +{ XK_Greek_etaaccent, 0x03ae }, /* GREEK SMALL LETTER ETA WITH TONOS */ +{ XK_Greek_iotaaccent, 0x03af }, /* GREEK SMALL LETTER IOTA WITH TONOS */ +{ XK_Greek_iotadieresis, 0x03ca }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA */ +{ XK_Greek_iotaaccentdieresis, 0x0390 }, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS */ +{ XK_Greek_omicronaccent, 0x03cc }, /* GREEK SMALL LETTER OMICRON WITH TONOS */ +{ XK_Greek_upsilonaccent, 0x03cd }, /* GREEK SMALL LETTER UPSILON WITH TONOS */ +{ XK_Greek_upsilondieresis, 0x03cb }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA */ +{ XK_Greek_upsilonaccentdieresis, 0x03b0 }, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS */ +{ XK_Greek_omegaaccent, 0x03ce }, /* GREEK SMALL LETTER OMEGA WITH TONOS */ +{ XK_Greek_ALPHA, 0x0391 }, /* GREEK CAPITAL LETTER ALPHA */ +{ XK_Greek_BETA, 0x0392 }, /* GREEK CAPITAL LETTER BETA */ +{ XK_Greek_GAMMA, 0x0393 }, /* GREEK CAPITAL LETTER GAMMA */ +{ XK_Greek_DELTA, 0x0394 }, /* GREEK CAPITAL LETTER DELTA */ +{ XK_Greek_EPSILON, 0x0395 }, /* GREEK CAPITAL LETTER EPSILON */ +{ XK_Greek_ZETA, 0x0396 }, /* GREEK CAPITAL LETTER ZETA */ +{ XK_Greek_ETA, 0x0397 }, /* GREEK CAPITAL LETTER ETA */ +{ XK_Greek_THETA, 0x0398 }, /* GREEK CAPITAL LETTER THETA */ +{ XK_Greek_IOTA, 0x0399 }, /* GREEK CAPITAL LETTER IOTA */ +{ XK_Greek_KAPPA, 0x039a }, /* GREEK CAPITAL LETTER KAPPA */ +{ XK_Greek_LAMBDA, 0x039b }, /* GREEK CAPITAL LETTER LAMDA */ +{ XK_Greek_MU, 0x039c }, /* GREEK CAPITAL LETTER MU */ +{ XK_Greek_NU, 0x039d }, /* GREEK CAPITAL LETTER NU */ +{ XK_Greek_XI, 0x039e }, /* GREEK CAPITAL LETTER XI */ +{ XK_Greek_OMICRON, 0x039f }, /* GREEK CAPITAL LETTER OMICRON */ +{ XK_Greek_PI, 0x03a0 }, /* GREEK CAPITAL LETTER PI */ +{ XK_Greek_RHO, 0x03a1 }, /* GREEK CAPITAL LETTER RHO */ +{ XK_Greek_SIGMA, 0x03a3 }, /* GREEK CAPITAL LETTER SIGMA */ +{ XK_Greek_TAU, 0x03a4 }, /* GREEK CAPITAL LETTER TAU */ +{ XK_Greek_UPSILON, 0x03a5 }, /* GREEK CAPITAL LETTER UPSILON */ +{ XK_Greek_PHI, 0x03a6 }, /* GREEK CAPITAL LETTER PHI */ +{ XK_Greek_CHI, 0x03a7 }, /* GREEK CAPITAL LETTER CHI */ +{ XK_Greek_PSI, 0x03a8 }, /* GREEK CAPITAL LETTER PSI */ +{ XK_Greek_OMEGA, 0x03a9 }, /* GREEK CAPITAL LETTER OMEGA */ +{ XK_Greek_alpha, 0x03b1 }, /* GREEK SMALL LETTER ALPHA */ +{ XK_Greek_beta, 0x03b2 }, /* GREEK SMALL LETTER BETA */ +{ XK_Greek_gamma, 0x03b3 }, /* GREEK SMALL LETTER GAMMA */ +{ XK_Greek_delta, 0x03b4 }, /* GREEK SMALL LETTER DELTA */ +{ XK_Greek_epsilon, 0x03b5 }, /* GREEK SMALL LETTER EPSILON */ +{ XK_Greek_zeta, 0x03b6 }, /* GREEK SMALL LETTER ZETA */ +{ XK_Greek_eta, 0x03b7 }, /* GREEK SMALL LETTER ETA */ +{ XK_Greek_theta, 0x03b8 }, /* GREEK SMALL LETTER THETA */ +{ XK_Greek_iota, 0x03b9 }, /* GREEK SMALL LETTER IOTA */ +{ XK_Greek_kappa, 0x03ba }, /* GREEK SMALL LETTER KAPPA */ +{ XK_Greek_lambda, 0x03bb }, /* GREEK SMALL LETTER LAMDA */ +{ XK_Greek_mu, 0x03bc }, /* GREEK SMALL LETTER MU */ +{ XK_Greek_nu, 0x03bd }, /* GREEK SMALL LETTER NU */ +{ XK_Greek_xi, 0x03be }, /* GREEK SMALL LETTER XI */ +{ XK_Greek_omicron, 0x03bf }, /* GREEK SMALL LETTER OMICRON */ +{ XK_Greek_pi, 0x03c0 }, /* GREEK SMALL LETTER PI */ +{ XK_Greek_rho, 0x03c1 }, /* GREEK SMALL LETTER RHO */ +{ XK_Greek_sigma, 0x03c3 }, /* GREEK SMALL LETTER SIGMA */ +{ XK_Greek_finalsmallsigma, 0x03c2 }, /* GREEK SMALL LETTER FINAL SIGMA */ +{ XK_Greek_tau, 0x03c4 }, /* GREEK SMALL LETTER TAU */ +{ XK_Greek_upsilon, 0x03c5 }, /* GREEK SMALL LETTER UPSILON */ +{ XK_Greek_phi, 0x03c6 }, /* GREEK SMALL LETTER PHI */ +{ XK_Greek_chi, 0x03c7 }, /* GREEK SMALL LETTER CHI */ +{ XK_Greek_psi, 0x03c8 }, /* GREEK SMALL LETTER PSI */ +{ XK_Greek_omega, 0x03c9 }, /* GREEK SMALL LETTER OMEGA */ +#endif // defined(XK_Greek_ALPHAaccent) +{ XK_leftradical, 0x23b7 }, /* ??? */ +{ XK_topleftradical, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ +{ XK_horizconnector, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ +{ XK_topintegral, 0x2320 }, /* TOP HALF INTEGRAL */ +{ XK_botintegral, 0x2321 }, /* BOTTOM HALF INTEGRAL */ +{ XK_vertconnector, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ +{ XK_topleftsqbracket, 0x23a1 }, /* ??? */ +{ XK_botleftsqbracket, 0x23a3 }, /* ??? */ +{ XK_toprightsqbracket, 0x23a4 }, /* ??? */ +{ XK_botrightsqbracket, 0x23a6 }, /* ??? */ +{ XK_topleftparens, 0x239b }, /* ??? */ +{ XK_botleftparens, 0x239d }, /* ??? */ +{ XK_toprightparens, 0x239e }, /* ??? */ +{ XK_botrightparens, 0x23a0 }, /* ??? */ +{ XK_leftmiddlecurlybrace, 0x23a8 }, /* ??? */ +{ XK_rightmiddlecurlybrace, 0x23ac }, /* ??? */ +{ XK_lessthanequal, 0x2264 }, /* LESS-THAN OR EQUAL TO */ +{ XK_notequal, 0x2260 }, /* NOT EQUAL TO */ +{ XK_greaterthanequal, 0x2265 }, /* GREATER-THAN OR EQUAL TO */ +{ XK_integral, 0x222b }, /* INTEGRAL */ +{ XK_therefore, 0x2234 }, /* THEREFORE */ +{ XK_variation, 0x221d }, /* PROPORTIONAL TO */ +{ XK_infinity, 0x221e }, /* INFINITY */ +{ XK_nabla, 0x2207 }, /* NABLA */ +{ XK_approximate, 0x223c }, /* TILDE OPERATOR */ +{ XK_similarequal, 0x2243 }, /* ASYMPTOTICALLY EQUAL TO */ +{ XK_ifonlyif, 0x21d4 }, /* LEFT RIGHT DOUBLE ARROW */ +{ XK_implies, 0x21d2 }, /* RIGHTWARDS DOUBLE ARROW */ +{ XK_identical, 0x2261 }, /* IDENTICAL TO */ +{ XK_radical, 0x221a }, /* SQUARE ROOT */ +{ XK_includedin, 0x2282 }, /* SUBSET OF */ +{ XK_includes, 0x2283 }, /* SUPERSET OF */ +{ XK_intersection, 0x2229 }, /* INTERSECTION */ +{ XK_union, 0x222a }, /* UNION */ +{ XK_logicaland, 0x2227 }, /* LOGICAL AND */ +{ XK_logicalor, 0x2228 }, /* LOGICAL OR */ +{ XK_partialderivative, 0x2202 }, /* PARTIAL DIFFERENTIAL */ +{ XK_function, 0x0192 }, /* LATIN SMALL LETTER F WITH HOOK */ +{ XK_leftarrow, 0x2190 }, /* LEFTWARDS ARROW */ +{ XK_uparrow, 0x2191 }, /* UPWARDS ARROW */ +{ XK_rightarrow, 0x2192 }, /* RIGHTWARDS ARROW */ +{ XK_downarrow, 0x2193 }, /* DOWNWARDS ARROW */ +/*{ XK_blank, ??? }, */ +{ XK_soliddiamond, 0x25c6 }, /* BLACK DIAMOND */ +{ XK_checkerboard, 0x2592 }, /* MEDIUM SHADE */ +{ XK_ht, 0x2409 }, /* SYMBOL FOR HORIZONTAL TABULATION */ +{ XK_ff, 0x240c }, /* SYMBOL FOR FORM FEED */ +{ XK_cr, 0x240d }, /* SYMBOL FOR CARRIAGE RETURN */ +{ XK_lf, 0x240a }, /* SYMBOL FOR LINE FEED */ +{ XK_nl, 0x2424 }, /* SYMBOL FOR NEWLINE */ +{ XK_vt, 0x240b }, /* SYMBOL FOR VERTICAL TABULATION */ +{ XK_lowrightcorner, 0x2518 }, /* BOX DRAWINGS LIGHT UP AND LEFT */ +{ XK_uprightcorner, 0x2510 }, /* BOX DRAWINGS LIGHT DOWN AND LEFT */ +{ XK_upleftcorner, 0x250c }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */ +{ XK_lowleftcorner, 0x2514 }, /* BOX DRAWINGS LIGHT UP AND RIGHT */ +{ XK_crossinglines, 0x253c }, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */ +{ XK_horizlinescan1, 0x23ba }, /* HORIZONTAL SCAN LINE-1 (Unicode 3.2 draft) */ +{ XK_horizlinescan3, 0x23bb }, /* HORIZONTAL SCAN LINE-3 (Unicode 3.2 draft) */ +{ XK_horizlinescan5, 0x2500 }, /* BOX DRAWINGS LIGHT HORIZONTAL */ +{ XK_horizlinescan7, 0x23bc }, /* HORIZONTAL SCAN LINE-7 (Unicode 3.2 draft) */ +{ XK_horizlinescan9, 0x23bd }, /* HORIZONTAL SCAN LINE-9 (Unicode 3.2 draft) */ +{ XK_leftt, 0x251c }, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */ +{ XK_rightt, 0x2524 }, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */ +{ XK_bott, 0x2534 }, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */ +{ XK_topt, 0x252c }, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */ +{ XK_vertbar, 0x2502 }, /* BOX DRAWINGS LIGHT VERTICAL */ +{ XK_emspace, 0x2003 }, /* EM SPACE */ +{ XK_enspace, 0x2002 }, /* EN SPACE */ +{ XK_em3space, 0x2004 }, /* THREE-PER-EM SPACE */ +{ XK_em4space, 0x2005 }, /* FOUR-PER-EM SPACE */ +{ XK_digitspace, 0x2007 }, /* FIGURE SPACE */ +{ XK_punctspace, 0x2008 }, /* PUNCTUATION SPACE */ +{ XK_thinspace, 0x2009 }, /* THIN SPACE */ +{ XK_hairspace, 0x200a }, /* HAIR SPACE */ +{ XK_emdash, 0x2014 }, /* EM DASH */ +{ XK_endash, 0x2013 }, /* EN DASH */ +/*{ XK_signifblank, ??? }, */ +{ XK_ellipsis, 0x2026 }, /* HORIZONTAL ELLIPSIS */ +{ XK_doubbaselinedot, 0x2025 }, /* TWO DOT LEADER */ +{ XK_onethird, 0x2153 }, /* VULGAR FRACTION ONE THIRD */ +{ XK_twothirds, 0x2154 }, /* VULGAR FRACTION TWO THIRDS */ +{ XK_onefifth, 0x2155 }, /* VULGAR FRACTION ONE FIFTH */ +{ XK_twofifths, 0x2156 }, /* VULGAR FRACTION TWO FIFTHS */ +{ XK_threefifths, 0x2157 }, /* VULGAR FRACTION THREE FIFTHS */ +{ XK_fourfifths, 0x2158 }, /* VULGAR FRACTION FOUR FIFTHS */ +{ XK_onesixth, 0x2159 }, /* VULGAR FRACTION ONE SIXTH */ +{ XK_fivesixths, 0x215a }, /* VULGAR FRACTION FIVE SIXTHS */ +{ XK_careof, 0x2105 }, /* CARE OF */ +{ XK_figdash, 0x2012 }, /* FIGURE DASH */ +{ XK_leftanglebracket, 0x2329 }, /* LEFT-POINTING ANGLE BRACKET */ +/*{ XK_decimalpoint, ??? }, */ +{ XK_rightanglebracket, 0x232a }, /* RIGHT-POINTING ANGLE BRACKET */ +/*{ XK_marker, ??? }, */ +{ XK_oneeighth, 0x215b }, /* VULGAR FRACTION ONE EIGHTH */ +{ XK_threeeighths, 0x215c }, /* VULGAR FRACTION THREE EIGHTHS */ +{ XK_fiveeighths, 0x215d }, /* VULGAR FRACTION FIVE EIGHTHS */ +{ XK_seveneighths, 0x215e }, /* VULGAR FRACTION SEVEN EIGHTHS */ +{ XK_trademark, 0x2122 }, /* TRADE MARK SIGN */ +{ XK_signaturemark, 0x2613 }, /* SALTIRE */ +/*{ XK_trademarkincircle, ??? }, */ +{ XK_leftopentriangle, 0x25c1 }, /* WHITE LEFT-POINTING TRIANGLE */ +{ XK_rightopentriangle, 0x25b7 }, /* WHITE RIGHT-POINTING TRIANGLE */ +{ XK_emopencircle, 0x25cb }, /* WHITE CIRCLE */ +{ XK_emopenrectangle, 0x25af }, /* WHITE VERTICAL RECTANGLE */ +{ XK_leftsinglequotemark, 0x2018 }, /* LEFT SINGLE QUOTATION MARK */ +{ XK_rightsinglequotemark, 0x2019 }, /* RIGHT SINGLE QUOTATION MARK */ +{ XK_leftdoublequotemark, 0x201c }, /* LEFT DOUBLE QUOTATION MARK */ +{ XK_rightdoublequotemark, 0x201d }, /* RIGHT DOUBLE QUOTATION MARK */ +{ XK_prescription, 0x211e }, /* PRESCRIPTION TAKE */ +{ XK_minutes, 0x2032 }, /* PRIME */ +{ XK_seconds, 0x2033 }, /* DOUBLE PRIME */ +{ XK_latincross, 0x271d }, /* LATIN CROSS */ +/*{ XK_hexagram, ??? }, */ +{ XK_filledrectbullet, 0x25ac }, /* BLACK RECTANGLE */ +{ XK_filledlefttribullet, 0x25c0 }, /* BLACK LEFT-POINTING TRIANGLE */ +{ XK_filledrighttribullet, 0x25b6 }, /* BLACK RIGHT-POINTING TRIANGLE */ +{ XK_emfilledcircle, 0x25cf }, /* BLACK CIRCLE */ +{ XK_emfilledrect, 0x25ae }, /* BLACK VERTICAL RECTANGLE */ +{ XK_enopencircbullet, 0x25e6 }, /* WHITE BULLET */ +{ XK_enopensquarebullet, 0x25ab }, /* WHITE SMALL SQUARE */ +{ XK_openrectbullet, 0x25ad }, /* WHITE RECTANGLE */ +{ XK_opentribulletup, 0x25b3 }, /* WHITE UP-POINTING TRIANGLE */ +{ XK_opentribulletdown, 0x25bd }, /* WHITE DOWN-POINTING TRIANGLE */ +{ XK_openstar, 0x2606 }, /* WHITE STAR */ +{ XK_enfilledcircbullet, 0x2022 }, /* BULLET */ +{ XK_enfilledsqbullet, 0x25aa }, /* BLACK SMALL SQUARE */ +{ XK_filledtribulletup, 0x25b2 }, /* BLACK UP-POINTING TRIANGLE */ +{ XK_filledtribulletdown, 0x25bc }, /* BLACK DOWN-POINTING TRIANGLE */ +{ XK_leftpointer, 0x261c }, /* WHITE LEFT POINTING INDEX */ +{ XK_rightpointer, 0x261e }, /* WHITE RIGHT POINTING INDEX */ +{ XK_club, 0x2663 }, /* BLACK CLUB SUIT */ +{ XK_diamond, 0x2666 }, /* BLACK DIAMOND SUIT */ +{ XK_heart, 0x2665 }, /* BLACK HEART SUIT */ +{ XK_maltesecross, 0x2720 }, /* MALTESE CROSS */ +{ XK_dagger, 0x2020 }, /* DAGGER */ +{ XK_doubledagger, 0x2021 }, /* DOUBLE DAGGER */ +{ XK_checkmark, 0x2713 }, /* CHECK MARK */ +{ XK_ballotcross, 0x2717 }, /* BALLOT X */ +{ XK_musicalsharp, 0x266f }, /* MUSIC SHARP SIGN */ +{ XK_musicalflat, 0x266d }, /* MUSIC FLAT SIGN */ +{ XK_malesymbol, 0x2642 }, /* MALE SIGN */ +{ XK_femalesymbol, 0x2640 }, /* FEMALE SIGN */ +{ XK_telephone, 0x260e }, /* BLACK TELEPHONE */ +{ XK_telephonerecorder, 0x2315 }, /* TELEPHONE RECORDER */ +{ XK_phonographcopyright, 0x2117 }, /* SOUND RECORDING COPYRIGHT */ +{ XK_caret, 0x2038 }, /* CARET */ +{ XK_singlelowquotemark, 0x201a }, /* SINGLE LOW-9 QUOTATION MARK */ +{ XK_doublelowquotemark, 0x201e }, /* DOUBLE LOW-9 QUOTATION MARK */ +/*{ XK_cursor, ??? }, */ +{ XK_leftcaret, 0x003c }, /* LESS-THAN SIGN */ +{ XK_rightcaret, 0x003e }, /* GREATER-THAN SIGN */ +{ XK_downcaret, 0x2228 }, /* LOGICAL OR */ +{ XK_upcaret, 0x2227 }, /* LOGICAL AND */ +{ XK_overbar, 0x00af }, /* MACRON */ +{ XK_downtack, 0x22a5 }, /* UP TACK */ +{ XK_upshoe, 0x2229 }, /* INTERSECTION */ +{ XK_downstile, 0x230a }, /* LEFT FLOOR */ +{ XK_underbar, 0x005f }, /* LOW LINE */ +{ XK_jot, 0x2218 }, /* RING OPERATOR */ +{ XK_quad, 0x2395 }, /* APL FUNCTIONAL SYMBOL QUAD */ +{ XK_uptack, 0x22a4 }, /* DOWN TACK */ +{ XK_circle, 0x25cb }, /* WHITE CIRCLE */ +{ XK_upstile, 0x2308 }, /* LEFT CEILING */ +{ XK_downshoe, 0x222a }, /* UNION */ +{ XK_rightshoe, 0x2283 }, /* SUPERSET OF */ +{ XK_leftshoe, 0x2282 }, /* SUBSET OF */ +{ XK_lefttack, 0x22a2 }, /* RIGHT TACK */ +{ XK_righttack, 0x22a3 }, /* LEFT TACK */ +#if defined(XK_hebrew_doublelowline) +{ XK_hebrew_doublelowline, 0x2017 }, /* DOUBLE LOW LINE */ +{ XK_hebrew_aleph, 0x05d0 }, /* HEBREW LETTER ALEF */ +{ XK_hebrew_bet, 0x05d1 }, /* HEBREW LETTER BET */ +{ XK_hebrew_gimel, 0x05d2 }, /* HEBREW LETTER GIMEL */ +{ XK_hebrew_dalet, 0x05d3 }, /* HEBREW LETTER DALET */ +{ XK_hebrew_he, 0x05d4 }, /* HEBREW LETTER HE */ +{ XK_hebrew_waw, 0x05d5 }, /* HEBREW LETTER VAV */ +{ XK_hebrew_zain, 0x05d6 }, /* HEBREW LETTER ZAYIN */ +{ XK_hebrew_chet, 0x05d7 }, /* HEBREW LETTER HET */ +{ XK_hebrew_tet, 0x05d8 }, /* HEBREW LETTER TET */ +{ XK_hebrew_yod, 0x05d9 }, /* HEBREW LETTER YOD */ +{ XK_hebrew_finalkaph, 0x05da }, /* HEBREW LETTER FINAL KAF */ +{ XK_hebrew_kaph, 0x05db }, /* HEBREW LETTER KAF */ +{ XK_hebrew_lamed, 0x05dc }, /* HEBREW LETTER LAMED */ +{ XK_hebrew_finalmem, 0x05dd }, /* HEBREW LETTER FINAL MEM */ +{ XK_hebrew_mem, 0x05de }, /* HEBREW LETTER MEM */ +{ XK_hebrew_finalnun, 0x05df }, /* HEBREW LETTER FINAL NUN */ +{ XK_hebrew_nun, 0x05e0 }, /* HEBREW LETTER NUN */ +{ XK_hebrew_samech, 0x05e1 }, /* HEBREW LETTER SAMEKH */ +{ XK_hebrew_ayin, 0x05e2 }, /* HEBREW LETTER AYIN */ +{ XK_hebrew_finalpe, 0x05e3 }, /* HEBREW LETTER FINAL PE */ +{ XK_hebrew_pe, 0x05e4 }, /* HEBREW LETTER PE */ +{ XK_hebrew_finalzade, 0x05e5 }, /* HEBREW LETTER FINAL TSADI */ +{ XK_hebrew_zade, 0x05e6 }, /* HEBREW LETTER TSADI */ +{ XK_hebrew_qoph, 0x05e7 }, /* HEBREW LETTER QOF */ +{ XK_hebrew_resh, 0x05e8 }, /* HEBREW LETTER RESH */ +{ XK_hebrew_shin, 0x05e9 }, /* HEBREW LETTER SHIN */ +{ XK_hebrew_taw, 0x05ea }, /* HEBREW LETTER TAV */ +#endif // defined(XK_hebrew_doublelowline) +#if defined(XK_Thai_kokai) +{ XK_Thai_kokai, 0x0e01 }, /* THAI CHARACTER KO KAI */ +{ XK_Thai_khokhai, 0x0e02 }, /* THAI CHARACTER KHO KHAI */ +{ XK_Thai_khokhuat, 0x0e03 }, /* THAI CHARACTER KHO KHUAT */ +{ XK_Thai_khokhwai, 0x0e04 }, /* THAI CHARACTER KHO KHWAI */ +{ XK_Thai_khokhon, 0x0e05 }, /* THAI CHARACTER KHO KHON */ +{ XK_Thai_khorakhang, 0x0e06 }, /* THAI CHARACTER KHO RAKHANG */ +{ XK_Thai_ngongu, 0x0e07 }, /* THAI CHARACTER NGO NGU */ +{ XK_Thai_chochan, 0x0e08 }, /* THAI CHARACTER CHO CHAN */ +{ XK_Thai_choching, 0x0e09 }, /* THAI CHARACTER CHO CHING */ +{ XK_Thai_chochang, 0x0e0a }, /* THAI CHARACTER CHO CHANG */ +{ XK_Thai_soso, 0x0e0b }, /* THAI CHARACTER SO SO */ +{ XK_Thai_chochoe, 0x0e0c }, /* THAI CHARACTER CHO CHOE */ +{ XK_Thai_yoying, 0x0e0d }, /* THAI CHARACTER YO YING */ +{ XK_Thai_dochada, 0x0e0e }, /* THAI CHARACTER DO CHADA */ +{ XK_Thai_topatak, 0x0e0f }, /* THAI CHARACTER TO PATAK */ +{ XK_Thai_thothan, 0x0e10 }, /* THAI CHARACTER THO THAN */ +{ XK_Thai_thonangmontho, 0x0e11 }, /* THAI CHARACTER THO NANGMONTHO */ +{ XK_Thai_thophuthao, 0x0e12 }, /* THAI CHARACTER THO PHUTHAO */ +{ XK_Thai_nonen, 0x0e13 }, /* THAI CHARACTER NO NEN */ +{ XK_Thai_dodek, 0x0e14 }, /* THAI CHARACTER DO DEK */ +{ XK_Thai_totao, 0x0e15 }, /* THAI CHARACTER TO TAO */ +{ XK_Thai_thothung, 0x0e16 }, /* THAI CHARACTER THO THUNG */ +{ XK_Thai_thothahan, 0x0e17 }, /* THAI CHARACTER THO THAHAN */ +{ XK_Thai_thothong, 0x0e18 }, /* THAI CHARACTER THO THONG */ +{ XK_Thai_nonu, 0x0e19 }, /* THAI CHARACTER NO NU */ +{ XK_Thai_bobaimai, 0x0e1a }, /* THAI CHARACTER BO BAIMAI */ +{ XK_Thai_popla, 0x0e1b }, /* THAI CHARACTER PO PLA */ +{ XK_Thai_phophung, 0x0e1c }, /* THAI CHARACTER PHO PHUNG */ +{ XK_Thai_fofa, 0x0e1d }, /* THAI CHARACTER FO FA */ +{ XK_Thai_phophan, 0x0e1e }, /* THAI CHARACTER PHO PHAN */ +{ XK_Thai_fofan, 0x0e1f }, /* THAI CHARACTER FO FAN */ +{ XK_Thai_phosamphao, 0x0e20 }, /* THAI CHARACTER PHO SAMPHAO */ +{ XK_Thai_moma, 0x0e21 }, /* THAI CHARACTER MO MA */ +{ XK_Thai_yoyak, 0x0e22 }, /* THAI CHARACTER YO YAK */ +{ XK_Thai_rorua, 0x0e23 }, /* THAI CHARACTER RO RUA */ +{ XK_Thai_ru, 0x0e24 }, /* THAI CHARACTER RU */ +{ XK_Thai_loling, 0x0e25 }, /* THAI CHARACTER LO LING */ +{ XK_Thai_lu, 0x0e26 }, /* THAI CHARACTER LU */ +{ XK_Thai_wowaen, 0x0e27 }, /* THAI CHARACTER WO WAEN */ +{ XK_Thai_sosala, 0x0e28 }, /* THAI CHARACTER SO SALA */ +{ XK_Thai_sorusi, 0x0e29 }, /* THAI CHARACTER SO RUSI */ +{ XK_Thai_sosua, 0x0e2a }, /* THAI CHARACTER SO SUA */ +{ XK_Thai_hohip, 0x0e2b }, /* THAI CHARACTER HO HIP */ +{ XK_Thai_lochula, 0x0e2c }, /* THAI CHARACTER LO CHULA */ +{ XK_Thai_oang, 0x0e2d }, /* THAI CHARACTER O ANG */ +{ XK_Thai_honokhuk, 0x0e2e }, /* THAI CHARACTER HO NOKHUK */ +{ XK_Thai_paiyannoi, 0x0e2f }, /* THAI CHARACTER PAIYANNOI */ +{ XK_Thai_saraa, 0x0e30 }, /* THAI CHARACTER SARA A */ +{ XK_Thai_maihanakat, 0x0e31 }, /* THAI CHARACTER MAI HAN-AKAT */ +{ XK_Thai_saraaa, 0x0e32 }, /* THAI CHARACTER SARA AA */ +{ XK_Thai_saraam, 0x0e33 }, /* THAI CHARACTER SARA AM */ +{ XK_Thai_sarai, 0x0e34 }, /* THAI CHARACTER SARA I */ +{ XK_Thai_saraii, 0x0e35 }, /* THAI CHARACTER SARA II */ +{ XK_Thai_saraue, 0x0e36 }, /* THAI CHARACTER SARA UE */ +{ XK_Thai_sarauee, 0x0e37 }, /* THAI CHARACTER SARA UEE */ +{ XK_Thai_sarau, 0x0e38 }, /* THAI CHARACTER SARA U */ +{ XK_Thai_sarauu, 0x0e39 }, /* THAI CHARACTER SARA UU */ +{ XK_Thai_phinthu, 0x0e3a }, /* THAI CHARACTER PHINTHU */ +/*{ XK_Thai_maihanakat_maitho, ??? }, */ +{ XK_Thai_baht, 0x0e3f }, /* THAI CURRENCY SYMBOL BAHT */ +{ XK_Thai_sarae, 0x0e40 }, /* THAI CHARACTER SARA E */ +{ XK_Thai_saraae, 0x0e41 }, /* THAI CHARACTER SARA AE */ +{ XK_Thai_sarao, 0x0e42 }, /* THAI CHARACTER SARA O */ +{ XK_Thai_saraaimaimuan, 0x0e43 }, /* THAI CHARACTER SARA AI MAIMUAN */ +{ XK_Thai_saraaimaimalai, 0x0e44 }, /* THAI CHARACTER SARA AI MAIMALAI */ +{ XK_Thai_lakkhangyao, 0x0e45 }, /* THAI CHARACTER LAKKHANGYAO */ +{ XK_Thai_maiyamok, 0x0e46 }, /* THAI CHARACTER MAIYAMOK */ +{ XK_Thai_maitaikhu, 0x0e47 }, /* THAI CHARACTER MAITAIKHU */ +{ XK_Thai_maiek, 0x0e48 }, /* THAI CHARACTER MAI EK */ +{ XK_Thai_maitho, 0x0e49 }, /* THAI CHARACTER MAI THO */ +{ XK_Thai_maitri, 0x0e4a }, /* THAI CHARACTER MAI TRI */ +{ XK_Thai_maichattawa, 0x0e4b }, /* THAI CHARACTER MAI CHATTAWA */ +{ XK_Thai_thanthakhat, 0x0e4c }, /* THAI CHARACTER THANTHAKHAT */ +{ XK_Thai_nikhahit, 0x0e4d }, /* THAI CHARACTER NIKHAHIT */ +{ XK_Thai_leksun, 0x0e50 }, /* THAI DIGIT ZERO */ +{ XK_Thai_leknung, 0x0e51 }, /* THAI DIGIT ONE */ +{ XK_Thai_leksong, 0x0e52 }, /* THAI DIGIT TWO */ +{ XK_Thai_leksam, 0x0e53 }, /* THAI DIGIT THREE */ +{ XK_Thai_leksi, 0x0e54 }, /* THAI DIGIT FOUR */ +{ XK_Thai_lekha, 0x0e55 }, /* THAI DIGIT FIVE */ +{ XK_Thai_lekhok, 0x0e56 }, /* THAI DIGIT SIX */ +{ XK_Thai_lekchet, 0x0e57 }, /* THAI DIGIT SEVEN */ +{ XK_Thai_lekpaet, 0x0e58 }, /* THAI DIGIT EIGHT */ +{ XK_Thai_lekkao, 0x0e59 }, /* THAI DIGIT NINE */ +#endif // defined(XK_Thai_kokai) +#if defined(XK_Hangul_Kiyeog) +{ XK_Hangul_Kiyeog, 0x3131 }, /* HANGUL LETTER KIYEOK */ +{ XK_Hangul_SsangKiyeog, 0x3132 }, /* HANGUL LETTER SSANGKIYEOK */ +{ XK_Hangul_KiyeogSios, 0x3133 }, /* HANGUL LETTER KIYEOK-SIOS */ +{ XK_Hangul_Nieun, 0x3134 }, /* HANGUL LETTER NIEUN */ +{ XK_Hangul_NieunJieuj, 0x3135 }, /* HANGUL LETTER NIEUN-CIEUC */ +{ XK_Hangul_NieunHieuh, 0x3136 }, /* HANGUL LETTER NIEUN-HIEUH */ +{ XK_Hangul_Dikeud, 0x3137 }, /* HANGUL LETTER TIKEUT */ +{ XK_Hangul_SsangDikeud, 0x3138 }, /* HANGUL LETTER SSANGTIKEUT */ +{ XK_Hangul_Rieul, 0x3139 }, /* HANGUL LETTER RIEUL */ +{ XK_Hangul_RieulKiyeog, 0x313a }, /* HANGUL LETTER RIEUL-KIYEOK */ +{ XK_Hangul_RieulMieum, 0x313b }, /* HANGUL LETTER RIEUL-MIEUM */ +{ XK_Hangul_RieulPieub, 0x313c }, /* HANGUL LETTER RIEUL-PIEUP */ +{ XK_Hangul_RieulSios, 0x313d }, /* HANGUL LETTER RIEUL-SIOS */ +{ XK_Hangul_RieulTieut, 0x313e }, /* HANGUL LETTER RIEUL-THIEUTH */ +{ XK_Hangul_RieulPhieuf, 0x313f }, /* HANGUL LETTER RIEUL-PHIEUPH */ +{ XK_Hangul_RieulHieuh, 0x3140 }, /* HANGUL LETTER RIEUL-HIEUH */ +{ XK_Hangul_Mieum, 0x3141 }, /* HANGUL LETTER MIEUM */ +{ XK_Hangul_Pieub, 0x3142 }, /* HANGUL LETTER PIEUP */ +{ XK_Hangul_SsangPieub, 0x3143 }, /* HANGUL LETTER SSANGPIEUP */ +{ XK_Hangul_PieubSios, 0x3144 }, /* HANGUL LETTER PIEUP-SIOS */ +{ XK_Hangul_Sios, 0x3145 }, /* HANGUL LETTER SIOS */ +{ XK_Hangul_SsangSios, 0x3146 }, /* HANGUL LETTER SSANGSIOS */ +{ XK_Hangul_Ieung, 0x3147 }, /* HANGUL LETTER IEUNG */ +{ XK_Hangul_Jieuj, 0x3148 }, /* HANGUL LETTER CIEUC */ +{ XK_Hangul_SsangJieuj, 0x3149 }, /* HANGUL LETTER SSANGCIEUC */ +{ XK_Hangul_Cieuc, 0x314a }, /* HANGUL LETTER CHIEUCH */ +{ XK_Hangul_Khieuq, 0x314b }, /* HANGUL LETTER KHIEUKH */ +{ XK_Hangul_Tieut, 0x314c }, /* HANGUL LETTER THIEUTH */ +{ XK_Hangul_Phieuf, 0x314d }, /* HANGUL LETTER PHIEUPH */ +{ XK_Hangul_Hieuh, 0x314e }, /* HANGUL LETTER HIEUH */ +{ XK_Hangul_A, 0x314f }, /* HANGUL LETTER A */ +{ XK_Hangul_AE, 0x3150 }, /* HANGUL LETTER AE */ +{ XK_Hangul_YA, 0x3151 }, /* HANGUL LETTER YA */ +{ XK_Hangul_YAE, 0x3152 }, /* HANGUL LETTER YAE */ +{ XK_Hangul_EO, 0x3153 }, /* HANGUL LETTER EO */ +{ XK_Hangul_E, 0x3154 }, /* HANGUL LETTER E */ +{ XK_Hangul_YEO, 0x3155 }, /* HANGUL LETTER YEO */ +{ XK_Hangul_YE, 0x3156 }, /* HANGUL LETTER YE */ +{ XK_Hangul_O, 0x3157 }, /* HANGUL LETTER O */ +{ XK_Hangul_WA, 0x3158 }, /* HANGUL LETTER WA */ +{ XK_Hangul_WAE, 0x3159 }, /* HANGUL LETTER WAE */ +{ XK_Hangul_OE, 0x315a }, /* HANGUL LETTER OE */ +{ XK_Hangul_YO, 0x315b }, /* HANGUL LETTER YO */ +{ XK_Hangul_U, 0x315c }, /* HANGUL LETTER U */ +{ XK_Hangul_WEO, 0x315d }, /* HANGUL LETTER WEO */ +{ XK_Hangul_WE, 0x315e }, /* HANGUL LETTER WE */ +{ XK_Hangul_WI, 0x315f }, /* HANGUL LETTER WI */ +{ XK_Hangul_YU, 0x3160 }, /* HANGUL LETTER YU */ +{ XK_Hangul_EU, 0x3161 }, /* HANGUL LETTER EU */ +{ XK_Hangul_YI, 0x3162 }, /* HANGUL LETTER YI */ +{ XK_Hangul_I, 0x3163 }, /* HANGUL LETTER I */ +{ XK_Hangul_J_Kiyeog, 0x11a8 }, /* HANGUL JONGSEONG KIYEOK */ +{ XK_Hangul_J_SsangKiyeog, 0x11a9 }, /* HANGUL JONGSEONG SSANGKIYEOK */ +{ XK_Hangul_J_KiyeogSios, 0x11aa }, /* HANGUL JONGSEONG KIYEOK-SIOS */ +{ XK_Hangul_J_Nieun, 0x11ab }, /* HANGUL JONGSEONG NIEUN */ +{ XK_Hangul_J_NieunJieuj, 0x11ac }, /* HANGUL JONGSEONG NIEUN-CIEUC */ +{ XK_Hangul_J_NieunHieuh, 0x11ad }, /* HANGUL JONGSEONG NIEUN-HIEUH */ +{ XK_Hangul_J_Dikeud, 0x11ae }, /* HANGUL JONGSEONG TIKEUT */ +{ XK_Hangul_J_Rieul, 0x11af }, /* HANGUL JONGSEONG RIEUL */ +{ XK_Hangul_J_RieulKiyeog, 0x11b0 }, /* HANGUL JONGSEONG RIEUL-KIYEOK */ +{ XK_Hangul_J_RieulMieum, 0x11b1 }, /* HANGUL JONGSEONG RIEUL-MIEUM */ +{ XK_Hangul_J_RieulPieub, 0x11b2 }, /* HANGUL JONGSEONG RIEUL-PIEUP */ +{ XK_Hangul_J_RieulSios, 0x11b3 }, /* HANGUL JONGSEONG RIEUL-SIOS */ +{ XK_Hangul_J_RieulTieut, 0x11b4 }, /* HANGUL JONGSEONG RIEUL-THIEUTH */ +{ XK_Hangul_J_RieulPhieuf, 0x11b5 }, /* HANGUL JONGSEONG RIEUL-PHIEUPH */ +{ XK_Hangul_J_RieulHieuh, 0x11b6 }, /* HANGUL JONGSEONG RIEUL-HIEUH */ +{ XK_Hangul_J_Mieum, 0x11b7 }, /* HANGUL JONGSEONG MIEUM */ +{ XK_Hangul_J_Pieub, 0x11b8 }, /* HANGUL JONGSEONG PIEUP */ +{ XK_Hangul_J_PieubSios, 0x11b9 }, /* HANGUL JONGSEONG PIEUP-SIOS */ +{ XK_Hangul_J_Sios, 0x11ba }, /* HANGUL JONGSEONG SIOS */ +{ XK_Hangul_J_SsangSios, 0x11bb }, /* HANGUL JONGSEONG SSANGSIOS */ +{ XK_Hangul_J_Ieung, 0x11bc }, /* HANGUL JONGSEONG IEUNG */ +{ XK_Hangul_J_Jieuj, 0x11bd }, /* HANGUL JONGSEONG CIEUC */ +{ XK_Hangul_J_Cieuc, 0x11be }, /* HANGUL JONGSEONG CHIEUCH */ +{ XK_Hangul_J_Khieuq, 0x11bf }, /* HANGUL JONGSEONG KHIEUKH */ +{ XK_Hangul_J_Tieut, 0x11c0 }, /* HANGUL JONGSEONG THIEUTH */ +{ XK_Hangul_J_Phieuf, 0x11c1 }, /* HANGUL JONGSEONG PHIEUPH */ +{ XK_Hangul_J_Hieuh, 0x11c2 }, /* HANGUL JONGSEONG HIEUH */ +{ XK_Hangul_RieulYeorinHieuh, 0x316d }, /* HANGUL LETTER RIEUL-YEORINHIEUH */ +{ XK_Hangul_SunkyeongeumMieum, 0x3171 }, /* HANGUL LETTER KAPYEOUNMIEUM */ +{ XK_Hangul_SunkyeongeumPieub, 0x3178 }, /* HANGUL LETTER KAPYEOUNPIEUP */ +{ XK_Hangul_PanSios, 0x317f }, /* HANGUL LETTER PANSIOS */ +{ XK_Hangul_KkogjiDalrinIeung, 0x3181 }, /* HANGUL LETTER YESIEUNG */ +{ XK_Hangul_SunkyeongeumPhieuf, 0x3184 }, /* HANGUL LETTER KAPYEOUNPHIEUPH */ +{ XK_Hangul_YeorinHieuh, 0x3186 }, /* HANGUL LETTER YEORINHIEUH */ +{ XK_Hangul_AraeA, 0x318d }, /* HANGUL LETTER ARAEA */ +{ XK_Hangul_AraeAE, 0x318e }, /* HANGUL LETTER ARAEAE */ +{ XK_Hangul_J_PanSios, 0x11eb }, /* HANGUL JONGSEONG PANSIOS */ +{ XK_Hangul_J_KkogjiDalrinIeung, 0x11f0 }, /* HANGUL JONGSEONG YESIEUNG */ +{ XK_Hangul_J_YeorinHieuh, 0x11f9 }, /* HANGUL JONGSEONG YEORINHIEUH */ +{ XK_Korean_Won, 0x20a9 }, /* WON SIGN */ +#endif // defined(XK_Hangul_Kiyeog) +{ XK_OE, 0x0152 }, /* LATIN CAPITAL LIGATURE OE */ +{ XK_oe, 0x0153 }, /* LATIN SMALL LIGATURE OE */ +{ XK_Ydiaeresis, 0x0178 }, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */ +{ XK_EuroSign, 0x20ac }, /* EURO SIGN */ + +/* combining dead keys */ +{ XK_dead_abovedot, 0x0307 }, /* COMBINING DOT ABOVE */ +{ XK_dead_abovering, 0x030a }, /* COMBINING RING ABOVE */ +{ XK_dead_acute, 0x0301 }, /* COMBINING ACUTE ACCENT */ +{ XK_dead_breve, 0x0306 }, /* COMBINING BREVE */ +{ XK_dead_caron, 0x030c }, /* COMBINING CARON */ +{ XK_dead_cedilla, 0x0327 }, /* COMBINING CEDILLA */ +{ XK_dead_circumflex, 0x0302 }, /* COMBINING CIRCUMFLEX ACCENT */ +{ XK_dead_diaeresis, 0x0308 }, /* COMBINING DIAERESIS */ +{ XK_dead_doubleacute, 0x030b }, /* COMBINING DOUBLE ACUTE ACCENT */ +{ XK_dead_grave, 0x0300 }, /* COMBINING GRAVE ACCENT */ +{ XK_dead_macron, 0x0304 }, /* COMBINING MACRON */ +{ XK_dead_ogonek, 0x0328 }, /* COMBINING OGONEK */ +{ XK_dead_tilde, 0x0303 } /* COMBINING TILDE */ +}; +/* XXX -- map these too +XK_Cyrillic_GHE_bar +XK_Cyrillic_ZHE_descender +XK_Cyrillic_KA_descender +XK_Cyrillic_KA_vertstroke +XK_Cyrillic_EN_descender +XK_Cyrillic_U_straight +XK_Cyrillic_U_straight_bar +XK_Cyrillic_HA_descender +XK_Cyrillic_CHE_descender +XK_Cyrillic_CHE_vertstroke +XK_Cyrillic_SHHA +XK_Cyrillic_SCHWA +XK_Cyrillic_I_macron +XK_Cyrillic_O_bar +XK_Cyrillic_U_macron +XK_Cyrillic_ghe_bar +XK_Cyrillic_zhe_descender +XK_Cyrillic_ka_descender +XK_Cyrillic_ka_vertstroke +XK_Cyrillic_en_descender +XK_Cyrillic_u_straight +XK_Cyrillic_u_straight_bar +XK_Cyrillic_ha_descender +XK_Cyrillic_che_descender +XK_Cyrillic_che_vertstroke +XK_Cyrillic_shha +XK_Cyrillic_schwa +XK_Cyrillic_i_macron +XK_Cyrillic_o_bar +XK_Cyrillic_u_macron + +XK_Armenian_eternity +XK_Armenian_ligature_ew +XK_Armenian_full_stop +XK_Armenian_verjaket +XK_Armenian_parenright +XK_Armenian_parenleft +XK_Armenian_guillemotright +XK_Armenian_guillemotleft +XK_Armenian_em_dash +XK_Armenian_dot +XK_Armenian_mijaket +XK_Armenian_but +XK_Armenian_separation_mark +XK_Armenian_comma +XK_Armenian_en_dash +XK_Armenian_hyphen +XK_Armenian_yentamna +XK_Armenian_ellipsis +XK_Armenian_amanak +XK_Armenian_exclam +XK_Armenian_accent +XK_Armenian_shesht +XK_Armenian_paruyk +XK_Armenian_question +XK_Armenian_AYB +XK_Armenian_ayb +XK_Armenian_BEN +XK_Armenian_ben +XK_Armenian_GIM +XK_Armenian_gim +XK_Armenian_DA +XK_Armenian_da +XK_Armenian_YECH +XK_Armenian_yech +XK_Armenian_ZA +XK_Armenian_za +XK_Armenian_E +XK_Armenian_e +XK_Armenian_AT +XK_Armenian_at +XK_Armenian_TO +XK_Armenian_to +XK_Armenian_ZHE +XK_Armenian_zhe +XK_Armenian_INI +XK_Armenian_ini +XK_Armenian_LYUN +XK_Armenian_lyun +XK_Armenian_KHE +XK_Armenian_khe +XK_Armenian_TSA +XK_Armenian_tsa +XK_Armenian_KEN +XK_Armenian_ken +XK_Armenian_HO +XK_Armenian_ho +XK_Armenian_DZA +XK_Armenian_dza +XK_Armenian_GHAT +XK_Armenian_ghat +XK_Armenian_TCHE +XK_Armenian_tche +XK_Armenian_MEN +XK_Armenian_men +XK_Armenian_HI +XK_Armenian_hi +XK_Armenian_NU +XK_Armenian_nu +XK_Armenian_SHA +XK_Armenian_sha +XK_Armenian_VO +XK_Armenian_vo +XK_Armenian_CHA +XK_Armenian_cha +XK_Armenian_PE +XK_Armenian_pe +XK_Armenian_JE +XK_Armenian_je +XK_Armenian_RA +XK_Armenian_ra +XK_Armenian_SE +XK_Armenian_se +XK_Armenian_VEV +XK_Armenian_vev +XK_Armenian_TYUN +XK_Armenian_tyun +XK_Armenian_RE +XK_Armenian_re +XK_Armenian_TSO +XK_Armenian_tso +XK_Armenian_VYUN +XK_Armenian_vyun +XK_Armenian_PYUR +XK_Armenian_pyur +XK_Armenian_KE +XK_Armenian_ke +XK_Armenian_O +XK_Armenian_o +XK_Armenian_FE +XK_Armenian_fe +XK_Armenian_apostrophe +XK_Armenian_section_sign + +XK_Georgian_an +XK_Georgian_ban +XK_Georgian_gan +XK_Georgian_don +XK_Georgian_en +XK_Georgian_vin +XK_Georgian_zen +XK_Georgian_tan +XK_Georgian_in +XK_Georgian_kan +XK_Georgian_las +XK_Georgian_man +XK_Georgian_nar +XK_Georgian_on +XK_Georgian_par +XK_Georgian_zhar +XK_Georgian_rae +XK_Georgian_san +XK_Georgian_tar +XK_Georgian_un +XK_Georgian_phar +XK_Georgian_khar +XK_Georgian_ghan +XK_Georgian_qar +XK_Georgian_shin +XK_Georgian_chin +XK_Georgian_can +XK_Georgian_jil +XK_Georgian_cil +XK_Georgian_char +XK_Georgian_xan +XK_Georgian_jhan +XK_Georgian_hae +XK_Georgian_he +XK_Georgian_hie +XK_Georgian_we +XK_Georgian_har +XK_Georgian_hoe +XK_Georgian_fi + +XK_Ccedillaabovedot +XK_Xabovedot +XK_Qabovedot +XK_Ibreve +XK_IE +XK_UO +XK_Zstroke +XK_Gcaron +XK_Obarred +XK_ccedillaabovedot +XK_xabovedot +XK_Ocaron +XK_qabovedot +XK_ibreve +XK_ie +XK_uo +XK_zstroke +XK_gcaron +XK_ocaron +XK_obarred +XK_SCHWA +XK_Lbelowdot +XK_Lstrokebelowdot +XK_Gtilde +XK_lbelowdot +XK_lstrokebelowdot +XK_gtilde +XK_schwa + +XK_Abelowdot +XK_abelowdot +XK_Ahook +XK_ahook +XK_Acircumflexacute +XK_acircumflexacute +XK_Acircumflexgrave +XK_acircumflexgrave +XK_Acircumflexhook +XK_acircumflexhook +XK_Acircumflextilde +XK_acircumflextilde +XK_Acircumflexbelowdot +XK_acircumflexbelowdot +XK_Abreveacute +XK_abreveacute +XK_Abrevegrave +XK_abrevegrave +XK_Abrevehook +XK_abrevehook +XK_Abrevetilde +XK_abrevetilde +XK_Abrevebelowdot +XK_abrevebelowdot +XK_Ebelowdot +XK_ebelowdot +XK_Ehook +XK_ehook +XK_Etilde +XK_etilde +XK_Ecircumflexacute +XK_ecircumflexacute +XK_Ecircumflexgrave +XK_ecircumflexgrave +XK_Ecircumflexhook +XK_ecircumflexhook +XK_Ecircumflextilde +XK_ecircumflextilde +XK_Ecircumflexbelowdot +XK_ecircumflexbelowdot +XK_Ihook +XK_ihook +XK_Ibelowdot +XK_ibelowdot +XK_Obelowdot +XK_obelowdot +XK_Ohook +XK_ohook +XK_Ocircumflexacute +XK_ocircumflexacute +XK_Ocircumflexgrave +XK_ocircumflexgrave +XK_Ocircumflexhook +XK_ocircumflexhook +XK_Ocircumflextilde +XK_ocircumflextilde +XK_Ocircumflexbelowdot +XK_ocircumflexbelowdot +XK_Ohornacute +XK_ohornacute +XK_Ohorngrave +XK_ohorngrave +XK_Ohornhook +XK_ohornhook +XK_Ohorntilde +XK_ohorntilde +XK_Ohornbelowdot +XK_ohornbelowdot +XK_Ubelowdot +XK_ubelowdot +XK_Uhook +XK_uhook +XK_Uhornacute +XK_uhornacute +XK_Uhorngrave +XK_uhorngrave +XK_Uhornhook +XK_uhornhook +XK_Uhorntilde +XK_uhorntilde +XK_Uhornbelowdot +XK_uhornbelowdot +XK_Ybelowdot +XK_ybelowdot +XK_Yhook +XK_yhook +XK_Ytilde +XK_ytilde +XK_Ohorn +XK_ohorn +XK_Uhorn +XK_uhorn +*/ + +// map "Internet" keys to KeyIDs +static const KeySym s_map1008FF[] = +{ + /* 0x00 */ 0, 0, kKeyBrightnessUp, kKeyBrightnessDown, 0, 0, 0, 0, + /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x10 */ 0, kKeyAudioDown, kKeyAudioMute, kKeyAudioUp, + /* 0x14 */ kKeyAudioPlay, kKeyAudioStop, kKeyAudioPrev, kKeyAudioNext, + /* 0x18 */ kKeyWWWHome, kKeyAppMail, 0, kKeyWWWSearch, 0, 0, 0, 0, + /* 0x20 */ 0, 0, 0, 0, 0, 0, kKeyWWWBack, kKeyWWWForward, + /* 0x28 */ kKeyWWWStop, kKeyWWWRefresh, 0, 0, kKeyEject, 0, 0, 0, + /* 0x30 */ kKeyWWWFavorites, 0, kKeyAppMedia, 0, 0, 0, 0, 0, + /* 0x38 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x40 */ kKeyAppUser1, kKeyAppUser2, 0, 0, 0, 0, 0, 0, + /* 0x48 */ 0, 0, kKeyMissionControl, kKeyLaunchpad, 0, 0, 0, 0, + /* 0x50 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x58 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x60 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x68 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x70 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x78 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x80 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x88 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x90 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0x98 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xa8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xb0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xb8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xc0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xc8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xd0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xd8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xe8 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf0 */ 0, 0, 0, 0, 0, 0, 0, 0, + /* 0xf8 */ 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +// +// XWindowsUtil +// + +XWindowsUtil::KeySymMap XWindowsUtil::s_keySymToUCS4; + +bool +XWindowsUtil::getWindowProperty(Display* display, Window window, + Atom property, String* data, Atom* type, + SInt32* format, bool deleteProperty) +{ + assert(display != NULL); + + Atom actualType; + int actualDatumSize; + + // ignore errors. XGetWindowProperty() will report failure. + XWindowsUtil::ErrorLock lock(display); + + // read the property + bool okay = true; + const long length = XMaxRequestSize(display); + long offset = 0; + unsigned long bytesLeft = 1; + while (bytesLeft != 0) { + // get more data + unsigned long numItems; + unsigned char* rawData; + if (XGetWindowProperty(display, window, property, + offset, length, False, AnyPropertyType, + &actualType, &actualDatumSize, + &numItems, &bytesLeft, &rawData) != Success || + actualType == None || actualDatumSize == 0) { + // failed + okay = false; + break; + } + + // compute bytes read and advance offset + unsigned long numBytes; + switch (actualDatumSize) { + case 8: + default: + numBytes = numItems; + offset += numItems / 4; + break; + + case 16: + numBytes = 2 * numItems; + offset += numItems / 2; + break; + + case 32: + numBytes = 4 * numItems; + offset += numItems; + break; + } + + // append data + if (data != NULL) { + data->append((char*)rawData, numBytes); + } + else { + // data is not required so don't try to get any more + bytesLeft = 0; + } + + // done with returned data + XFree(rawData); + } + + // delete the property if requested + if (deleteProperty) { + XDeleteProperty(display, window, property); + } + + // save property info + if (type != NULL) { + *type = actualType; + } + if (format != NULL) { + *format = static_cast<SInt32>(actualDatumSize); + } + + if (okay) { + LOG((CLOG_DEBUG2 "read property %d on window 0x%08x: bytes=%d", property, window, (data == NULL) ? 0 : data->size())); + return true; + } + else { + LOG((CLOG_DEBUG2 "can't read property %d on window 0x%08x", property, window)); + return false; + } +} + +bool +XWindowsUtil::setWindowProperty(Display* display, Window window, + Atom property, const void* vdata, UInt32 size, + Atom type, SInt32 format) +{ + const UInt32 length = 4 * XMaxRequestSize(display); + const unsigned char* data = static_cast<const unsigned char*>(vdata); + UInt32 datumSize = static_cast<UInt32>(format / 8); + // format 32 on 64bit systems is 8 bytes not 4. + if (format == 32) { + datumSize = sizeof(Atom); + } + + // save errors + bool error = false; + XWindowsUtil::ErrorLock lock(display, &error); + + // how much data to send in first chunk? + UInt32 chunkSize = size; + if (chunkSize > length) { + chunkSize = length; + } + + // send first chunk + XChangeProperty(display, window, property, + type, format, PropModeReplace, + data, chunkSize / datumSize); + + // append remaining chunks + data += chunkSize; + size -= chunkSize; + while (!error && size > 0) { + chunkSize = size; + if (chunkSize > length) { + chunkSize = length; + } + XChangeProperty(display, window, property, + type, format, PropModeAppend, + data, chunkSize / datumSize); + data += chunkSize; + size -= chunkSize; + } + + return !error; +} + +Time +XWindowsUtil::getCurrentTime(Display* display, Window window) +{ + XLockDisplay(display); + // select property events on window + XWindowAttributes attr; + XGetWindowAttributes(display, window, &attr); + XSelectInput(display, window, attr.your_event_mask | PropertyChangeMask); + + // make a property name to receive dummy change + Atom atom = XInternAtom(display, "TIMESTAMP", False); + + // do a zero-length append to get the current time + unsigned char dummy; + XChangeProperty(display, window, atom, + XA_INTEGER, 8, + PropModeAppend, + &dummy, 0); + + // look for property notify events with the following + PropertyNotifyPredicateInfo filter; + filter.m_window = window; + filter.m_property = atom; + + // wait for reply + XEvent xevent; + XIfEvent(display, &xevent, &XWindowsUtil::propertyNotifyPredicate, + (XPointer)&filter); + assert(xevent.type == PropertyNotify); + assert(xevent.xproperty.window == window); + assert(xevent.xproperty.atom == atom); + + // restore event mask + XSelectInput(display, window, attr.your_event_mask); + XUnlockDisplay(display); + + return xevent.xproperty.time; +} + +KeyID +XWindowsUtil::mapKeySymToKeyID(KeySym k) +{ + initKeyMaps(); + + switch (k & 0xffffff00) { + case 0x0000: + // Latin-1 + return static_cast<KeyID>(k); + + case 0xfe00: + // ISO 9995 Function and Modifier Keys + switch (k) { + case XK_ISO_Left_Tab: + return kKeyLeftTab; + + case XK_ISO_Level3_Shift: + return kKeyAltGr; + +#ifdef XK_ISO_Level5_Shift + case XK_ISO_Level5_Shift: + return XK_ISO_Level5_Shift; //FIXME: there is no "usual" key for this... +#endif + + case XK_ISO_Next_Group: + return kKeyNextGroup; + + case XK_ISO_Prev_Group: + return kKeyPrevGroup; + + case XK_dead_grave: + return kKeyDeadGrave; + + case XK_dead_acute: + return kKeyDeadAcute; + + case XK_dead_circumflex: + return kKeyDeadCircumflex; + + case XK_dead_tilde: + return kKeyDeadTilde; + + case XK_dead_macron: + return kKeyDeadMacron; + + case XK_dead_breve: + return kKeyDeadBreve; + + case XK_dead_abovedot: + return kKeyDeadAbovedot; + + case XK_dead_diaeresis: + return kKeyDeadDiaeresis; + + case XK_dead_abovering: + return kKeyDeadAbovering; + + case XK_dead_doubleacute: + return kKeyDeadDoubleacute; + + case XK_dead_caron: + return kKeyDeadCaron; + + case XK_dead_cedilla: + return kKeyDeadCedilla; + + case XK_dead_ogonek: + return kKeyDeadOgonek; + + default: + return kKeyNone; + } + + case 0xff00: + // MISCELLANY + return static_cast<KeyID>(k - 0xff00 + 0xef00); + + case 0x1008ff00: + // "Internet" keys + return s_map1008FF[k & 0xff]; + + default: { + // lookup character in table + KeySymMap::const_iterator index = s_keySymToUCS4.find(k); + if (index != s_keySymToUCS4.end()) { + return static_cast<KeyID>(index->second); + } + + // unknown character + return kKeyNone; + } + } +} + +UInt32 +XWindowsUtil::getModifierBitForKeySym(KeySym keysym) +{ + switch (keysym) { + case XK_Shift_L: + case XK_Shift_R: + return kKeyModifierBitShift; + + case XK_Control_L: + case XK_Control_R: + return kKeyModifierBitControl; + + case XK_Alt_L: + case XK_Alt_R: + return kKeyModifierBitAlt; + + case XK_Meta_L: + case XK_Meta_R: + return kKeyModifierBitMeta; + + case XK_Super_L: + case XK_Super_R: + case XK_Hyper_L: + case XK_Hyper_R: + return kKeyModifierBitSuper; + + case XK_Mode_switch: + case XK_ISO_Level3_Shift: + return kKeyModifierBitAltGr; + +#ifdef XK_ISO_Level5_Shift + case XK_ISO_Level5_Shift: + return kKeyModifierBitLevel5Lock; +#endif + + case XK_Caps_Lock: + return kKeyModifierBitCapsLock; + + case XK_Num_Lock: + return kKeyModifierBitNumLock; + + case XK_Scroll_Lock: + return kKeyModifierBitScrollLock; + + default: + return kKeyModifierBitNone; + } +} + +String +XWindowsUtil::atomToString(Display* display, Atom atom) +{ + if (atom == 0) { + return "None"; + } + + bool error = false; + XWindowsUtil::ErrorLock lock(display, &error); + char* name = XGetAtomName(display, atom); + if (error) { + return barrier::string::sprintf("<UNKNOWN> (%d)", (int)atom); + } + else { + String msg = barrier::string::sprintf("%s (%d)", name, (int)atom); + XFree(name); + return msg; + } +} + +String +XWindowsUtil::atomsToString(Display* display, const Atom* atom, UInt32 num) +{ + char** names = new char*[num]; + bool error = false; + XWindowsUtil::ErrorLock lock(display, &error); + XGetAtomNames(display, const_cast<Atom*>(atom), (int)num, names); + String msg; + if (error) { + for (UInt32 i = 0; i < num; ++i) { + msg += barrier::string::sprintf("<UNKNOWN> (%d), ", (int)atom[i]); + } + } + else { + for (UInt32 i = 0; i < num; ++i) { + msg += barrier::string::sprintf("%s (%d), ", names[i], (int)atom[i]); + XFree(names[i]); + } + } + delete[] names; + if (msg.size() > 2) { + msg.erase(msg.size() - 2); + } + return msg; +} + +void +XWindowsUtil::convertAtomProperty(String& data) +{ + // as best i can tell, 64-bit systems don't pack Atoms into properties + // as 32-bit numbers but rather as the 64-bit numbers they are. that + // seems wrong but we have to cope. sometimes we'll get a list of + // atoms that's 8*n+4 bytes long, missing the trailing 4 bytes which + // should all be 0. since we're going to reference the Atoms as + // 64-bit numbers we have to ensure the last number is a full 64 bits. + if (sizeof(Atom) != 4 && ((data.size() / 4) & 1) != 0) { + UInt32 zero = 0; + data.append(reinterpret_cast<char*>(&zero), sizeof(zero)); + } +} + +void +XWindowsUtil::appendAtomData(String& data, Atom atom) +{ + data.append(reinterpret_cast<char*>(&atom), sizeof(Atom)); +} + +void +XWindowsUtil::replaceAtomData(String& data, UInt32 index, Atom atom) +{ + data.replace(index * sizeof(Atom), sizeof(Atom), + reinterpret_cast<const char*>(&atom), + sizeof(Atom)); +} + +void +XWindowsUtil::appendTimeData(String& data, Time time) +{ + data.append(reinterpret_cast<char*>(&time), sizeof(Time)); +} + +Bool +XWindowsUtil::propertyNotifyPredicate(Display*, XEvent* xevent, XPointer arg) +{ + PropertyNotifyPredicateInfo* filter = + reinterpret_cast<PropertyNotifyPredicateInfo*>(arg); + return (xevent->type == PropertyNotify && + xevent->xproperty.window == filter->m_window && + xevent->xproperty.atom == filter->m_property && + xevent->xproperty.state == PropertyNewValue) ? True : False; +} + +void +XWindowsUtil::initKeyMaps() +{ + if (s_keySymToUCS4.empty()) { + for (size_t i =0; i < sizeof(s_keymap) / sizeof(s_keymap[0]); ++i) { + s_keySymToUCS4[s_keymap[i].keysym] = s_keymap[i].ucs4; + } + } +} + + +// +// XWindowsUtil::ErrorLock +// + +XWindowsUtil::ErrorLock* XWindowsUtil::ErrorLock::s_top = NULL; + +XWindowsUtil::ErrorLock::ErrorLock(Display* display) : + m_display(display) +{ + install(&XWindowsUtil::ErrorLock::ignoreHandler, NULL); +} + +XWindowsUtil::ErrorLock::ErrorLock(Display* display, bool* flag) : + m_display(display) +{ + install(&XWindowsUtil::ErrorLock::saveHandler, flag); +} + +XWindowsUtil::ErrorLock::ErrorLock(Display* display, + ErrorHandler handler, void* data) : + m_display(display) +{ + install(handler, data); +} + +XWindowsUtil::ErrorLock::~ErrorLock() +{ + // make sure everything finishes before uninstalling handler + if (m_display != NULL) { + XSync(m_display, False); + } + + // restore old handler + XSetErrorHandler(m_oldXHandler); + s_top = m_next; +} + +void +XWindowsUtil::ErrorLock::install(ErrorHandler handler, void* data) +{ + // make sure everything finishes before installing handler + if (m_display != NULL) { + XSync(m_display, False); + } + + // install handler + m_handler = handler; + m_userData = data; + m_oldXHandler = XSetErrorHandler( + &XWindowsUtil::ErrorLock::internalHandler); + m_next = s_top; + s_top = this; +} + +int +XWindowsUtil::ErrorLock::internalHandler(Display* display, XErrorEvent* event) +{ + if (s_top != NULL && s_top->m_handler != NULL) { + s_top->m_handler(display, event, s_top->m_userData); + } + return 0; +} + +void +XWindowsUtil::ErrorLock::ignoreHandler(Display*, XErrorEvent* e, void*) +{ + LOG((CLOG_DEBUG1 "ignoring X error: %d", e->error_code)); +} + +void +XWindowsUtil::ErrorLock::saveHandler(Display* display, XErrorEvent* e, void* flag) +{ + char errtxt[1024]; + XGetErrorText(display, e->error_code, errtxt, 1023); + LOG((CLOG_DEBUG1 "flagging X error: %d - %.1023s", e->error_code, errtxt)); + *static_cast<bool*>(flag) = true; +} diff --git a/src/lib/platform/XWindowsUtil.h b/src/lib/platform/XWindowsUtil.h new file mode 100644 index 0000000..4df888f --- /dev/null +++ b/src/lib/platform/XWindowsUtil.h @@ -0,0 +1,187 @@ +/* + * 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 "base/String.h" +#include "base/EventTypes.h" +#include "common/stdmap.h" +#include "common/stdvector.h" + +#if X_DISPLAY_MISSING +# error X11 is required to build barrier +#else +# include <X11/Xlib.h> +#endif + +//! X11 utility functions +class XWindowsUtil { +public: + typedef std::vector<KeySym> KeySyms; + + //! Get property + /*! + Gets property \c property on \c window. \b Appends the data to + \c *data if \c data is not NULL, saves the property type in \c *type + if \c type is not NULL, and saves the property format in \c *format + if \c format is not NULL. If \c deleteProperty is true then the + property is deleted after being read. + */ + static bool getWindowProperty(Display*, + Window window, Atom property, + String* data, Atom* type, + SInt32* format, bool deleteProperty); + + //! Set property + /*! + Sets property \c property on \c window to \c size bytes of data from + \c data. + */ + static bool setWindowProperty(Display*, + Window window, Atom property, + const void* data, UInt32 size, + Atom type, SInt32 format); + + //! Get X server time + /*! + Returns the current X server time. + */ + static Time getCurrentTime(Display*, Window); + + //! Convert KeySym to KeyID + /*! + Converts a KeySym to the equivalent KeyID. Returns kKeyNone if the + KeySym cannot be mapped. + */ + static UInt32 mapKeySymToKeyID(KeySym); + + //! Convert KeySym to corresponding KeyModifierMask + /*! + Converts a KeySym to the corresponding KeyModifierMask, or 0 if the + KeySym is not a modifier. + */ + static UInt32 getModifierBitForKeySym(KeySym keysym); + + //! Convert Atom to its string + /*! + Converts \p atom to its string representation. + */ + static String atomToString(Display*, Atom atom); + + //! Convert several Atoms to a string + /*! + Converts each atom in \p atoms to its string representation and + concatenates the results. + */ + static String atomsToString(Display* display, + const Atom* atom, UInt32 num); + + //! Prepare a property of atoms for use + /*! + 64-bit systems may need to modify a property's data if it's a + list of Atoms before using it. + */ + static void convertAtomProperty(String& data); + + //! Append an Atom to property data + /*! + Converts \p atom to a 32-bit on-the-wire format and appends it to + \p data. + */ + static void appendAtomData(String& data, Atom atom); + + //! Replace an Atom in property data + /*! + Converts \p atom to a 32-bit on-the-wire format and replaces the atom + at index \p index in \p data. + */ + static void replaceAtomData(String& data, + UInt32 index, Atom atom); + + //! Append an Time to property data + /*! + Converts \p time to a 32-bit on-the-wire format and appends it to + \p data. + */ + static void appendTimeData(String& data, Time time); + + //! X11 error handler + /*! + This class sets an X error handler in the c'tor and restores the + previous error handler in the d'tor. A lock should only be + installed while the display is locked by the thread. + + ErrorLock() ignores errors + ErrorLock(bool* flag) sets *flag to true if any error occurs + */ + class ErrorLock { + public: + //! Error handler type + typedef void (*ErrorHandler)(Display*, XErrorEvent*, void* userData); + + /*! + Ignore X11 errors. + */ + ErrorLock(Display*); + + /*! + Set \c *errorFlag if any error occurs. + */ + ErrorLock(Display*, bool* errorFlag); + + /*! + Call \c handler on each error. + */ + ErrorLock(Display*, ErrorHandler handler, void* userData); + + ~ErrorLock(); + + private: + void install(ErrorHandler, void*); + static int internalHandler(Display*, XErrorEvent*); + static void ignoreHandler(Display*, XErrorEvent*, void*); + static void saveHandler(Display*, XErrorEvent*, void*); + + private: + typedef int (*XErrorHandler)(Display*, XErrorEvent*); + + Display* m_display; + ErrorHandler m_handler; + void* m_userData; + XErrorHandler m_oldXHandler; + ErrorLock* m_next; + static ErrorLock* s_top; + }; + +private: + class PropertyNotifyPredicateInfo { + public: + Window m_window; + Atom m_property; + }; + + static Bool propertyNotifyPredicate(Display*, + XEvent* xevent, XPointer arg); + + static void initKeyMaps(); + +private: + typedef std::map<KeySym, UInt32> KeySymMap; + + static KeySymMap s_keySymToUCS4; +}; diff --git a/src/lib/platform/synwinhk.h b/src/lib/platform/synwinhk.h new file mode 100644 index 0000000..4b2d8e3 --- /dev/null +++ b/src/lib/platform/synwinhk.h @@ -0,0 +1,66 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2018 Debauchee Open Source Group + * 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 "base/EventTypes.h" + +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#if defined(synwinhk_EXPORTS) +#define CBARRIERHOOK_API __declspec(dllexport) +#else +#define CBARRIERHOOK_API __declspec(dllimport) +#endif + +#define BARRIER_MSG_MARK WM_APP + 0x0011 // mark id; <unused> +#define BARRIER_MSG_KEY WM_APP + 0x0012 // vk code; key data +#define BARRIER_MSG_MOUSE_BUTTON WM_APP + 0x0013 // button msg; <unused> +#define BARRIER_MSG_MOUSE_WHEEL WM_APP + 0x0014 // delta; <unused> +#define BARRIER_MSG_MOUSE_MOVE WM_APP + 0x0015 // x; y +#define BARRIER_MSG_POST_WARP WM_APP + 0x0016 // <unused>; <unused> +#define BARRIER_MSG_PRE_WARP WM_APP + 0x0017 // x; y +#define BARRIER_MSG_SCREEN_SAVER WM_APP + 0x0018 // activated; <unused> +#define BARRIER_MSG_DEBUG WM_APP + 0x0019 // data, data +#define BARRIER_MSG_INPUT_FIRST BARRIER_MSG_KEY +#define BARRIER_MSG_INPUT_LAST BARRIER_MSG_PRE_WARP +#define BARRIER_HOOK_LAST_MSG BARRIER_MSG_DEBUG + +#define BARRIER_HOOK_FAKE_INPUT_VIRTUAL_KEY VK_CANCEL +#define BARRIER_HOOK_FAKE_INPUT_SCANCODE 0 + +extern "C" { + +enum EHookMode { + kHOOK_DISABLE, + kHOOK_WATCH_JUMP_ZONE, + kHOOK_RELAY_EVENTS +}; + +/* REMOVED ImmuneKeys for migration of synwinhk out of DLL + +typedef void (*SetImmuneKeysFunc)(const DWORD*, std::size_t); + +// do not call setImmuneKeys() while the hooks are active! +CBARRIERHOOK_API void setImmuneKeys(const DWORD *list, std::size_t size); + +*/ + +} diff --git a/src/lib/server/BaseClientProxy.cpp b/src/lib/server/BaseClientProxy.cpp new file mode 100644 index 0000000..b9c5339 --- /dev/null +++ b/src/lib/server/BaseClientProxy.cpp @@ -0,0 +1,56 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2006 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 "server/BaseClientProxy.h" + +// +// BaseClientProxy +// + +BaseClientProxy::BaseClientProxy(const String& name) : + m_name(name), + m_x(0), + m_y(0) +{ + // do nothing +} + +BaseClientProxy::~BaseClientProxy() +{ + // do nothing +} + +void +BaseClientProxy::setJumpCursorPos(SInt32 x, SInt32 y) +{ + m_x = x; + m_y = y; +} + +void +BaseClientProxy::getJumpCursorPos(SInt32& x, SInt32& y) const +{ + x = m_x; + y = m_y; +} + +String +BaseClientProxy::getName() const +{ + return m_name; +} diff --git a/src/lib/server/BaseClientProxy.h b/src/lib/server/BaseClientProxy.h new file mode 100644 index 0000000..c7c23ff --- /dev/null +++ b/src/lib/server/BaseClientProxy.h @@ -0,0 +1,99 @@ +/* + * 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 "barrier/IClient.h" +#include "base/String.h" + +namespace barrier { class IStream; } + +//! Generic proxy for client or primary +class BaseClientProxy : public IClient { +public: + /*! + \c name is the name of the client. + */ + BaseClientProxy(const String& name); + ~BaseClientProxy(); + + //! @name manipulators + //@{ + + //! Save cursor position + /*! + Save the position of the cursor when jumping from client. + */ + void setJumpCursorPos(SInt32 x, SInt32 y); + + //@} + //! @name accessors + //@{ + + //! Get cursor position + /*! + Get the position of the cursor when last jumping from client. + */ + void getJumpCursorPos(SInt32& x, SInt32& y) const; + + //! Get cursor position + /*! + Return if this proxy is for client or primary. + */ + virtual bool isPrimary() const { return false; } + + //@} + + // IScreen + virtual void* getEventTarget() const = 0; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + virtual bool leave() = 0; + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void grabClipboard(ClipboardID) = 0; + virtual void setClipboardDirty(ClipboardID, bool) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void mouseDown(ButtonID) = 0; + virtual void mouseUp(ButtonID) = 0; + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const OptionsList& options) = 0; + virtual void sendDragInfo(UInt32 fileCount, const char* info, + size_t size) = 0; + virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize) = 0; + virtual String getName() const; + virtual barrier::IStream* + getStream() const = 0; + +private: + String m_name; + SInt32 m_x, m_y; +}; diff --git a/src/lib/server/CMakeLists.txt b/src/lib/server/CMakeLists.txt new file mode 100644 index 0000000..5242d6d --- /dev/null +++ b/src/lib/server/CMakeLists.txt @@ -0,0 +1,30 @@ +# 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() + +add_library(server STATIC ${sources}) + +target_link_libraries(server) + +if (UNIX) + target_link_libraries(server synlib) +endif() diff --git a/src/lib/server/ClientListener.cpp b/src/lib/server/ClientListener.cpp new file mode 100644 index 0000000..00067ba --- /dev/null +++ b/src/lib/server/ClientListener.cpp @@ -0,0 +1,256 @@ +/* + * 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 "server/ClientListener.h" + +#include "server/ClientProxy.h" +#include "server/ClientProxyUnknown.h" +#include "barrier/PacketStreamFilter.h" +#include "net/IDataSocket.h" +#include "net/IListenSocket.h" +#include "net/ISocketFactory.h" +#include "net/XSocket.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +// +// ClientListener +// + +ClientListener::ClientListener(const NetworkAddress& address, + ISocketFactory* socketFactory, + IEventQueue* events, + bool enableCrypto) : + m_socketFactory(socketFactory), + m_server(NULL), + m_events(events), + m_useSecureNetwork(enableCrypto) +{ + assert(m_socketFactory != NULL); + + try { + m_listen = m_socketFactory->createListen( + ARCH->getAddrFamily(address.getAddress()), + m_useSecureNetwork); + + // setup event handler + m_events->adoptHandler(m_events->forIListenSocket().connecting(), + m_listen, + new TMethodEventJob<ClientListener>(this, + &ClientListener::handleClientConnecting)); + + // bind listen address + LOG((CLOG_DEBUG1 "binding listen socket")); + m_listen->bind(address); + } + catch (XSocketAddressInUse&) { + cleanupListenSocket(); + delete m_socketFactory; + throw; + } + catch (XBase&) { + cleanupListenSocket(); + delete m_socketFactory; + throw; + } + LOG((CLOG_DEBUG1 "listening for clients")); +} + +ClientListener::~ClientListener() +{ + LOG((CLOG_DEBUG1 "stop listening for clients")); + + // discard already connected clients + for (NewClients::iterator index = m_newClients.begin(); + index != m_newClients.end(); ++index) { + ClientProxyUnknown* client = *index; + m_events->removeHandler( + m_events->forClientProxyUnknown().success(), client); + m_events->removeHandler( + m_events->forClientProxyUnknown().failure(), client); + m_events->removeHandler( + m_events->forClientProxy().disconnected(), client); + delete client; + } + + // discard waiting clients + ClientProxy* client = getNextClient(); + while (client != NULL) { + delete client; + client = getNextClient(); + } + + m_events->removeHandler(m_events->forIListenSocket().connecting(), m_listen); + cleanupListenSocket(); + cleanupClientSockets(); + delete m_socketFactory; +} + +void +ClientListener::setServer(Server* server) +{ + assert(server != NULL); + m_server = server; +} + +ClientProxy* +ClientListener::getNextClient() +{ + ClientProxy* client = NULL; + if (!m_waitingClients.empty()) { + client = m_waitingClients.front(); + m_waitingClients.pop_front(); + m_events->removeHandler(m_events->forClientProxy().disconnected(), client); + } + return client; +} + +void +ClientListener::handleClientConnecting(const Event&, void*) +{ + // accept client connection + IDataSocket* socket = m_listen->accept(); + + if (socket == NULL) { + return; + } + + m_clientSockets.insert(socket); + + m_events->adoptHandler(m_events->forClientListener().accepted(), + socket->getEventTarget(), + new TMethodEventJob<ClientListener>(this, + &ClientListener::handleClientAccepted, socket)); + + // When using non SSL, server accepts clients immediately, while SSL + // has to call secure accept which may require retry + if (!m_useSecureNetwork) { + m_events->addEvent(Event(m_events->forClientListener().accepted(), + socket->getEventTarget())); + } +} + +void +ClientListener::handleClientAccepted(const Event&, void* vsocket) +{ + LOG((CLOG_NOTE "accepted client connection")); + + IDataSocket* socket = static_cast<IDataSocket*>(vsocket); + + // filter socket messages, including a packetizing filter + barrier::IStream* stream = new PacketStreamFilter(m_events, socket, false); + assert(m_server != NULL); + + // create proxy for unknown client + ClientProxyUnknown* client = new ClientProxyUnknown(stream, 30.0, m_server, m_events); + + m_newClients.insert(client); + + // watch for events from unknown client + m_events->adoptHandler(m_events->forClientProxyUnknown().success(), + client, + new TMethodEventJob<ClientListener>(this, + &ClientListener::handleUnknownClient, client)); + m_events->adoptHandler(m_events->forClientProxyUnknown().failure(), + client, + new TMethodEventJob<ClientListener>(this, + &ClientListener::handleUnknownClient, client)); +} + +void +ClientListener::handleUnknownClient(const Event&, void* vclient) +{ + ClientProxyUnknown* unknownClient = + static_cast<ClientProxyUnknown*>(vclient); + + // we should have the client in our new client list + assert(m_newClients.count(unknownClient) == 1); + + // get the real client proxy and install it + ClientProxy* client = unknownClient->orphanClientProxy(); + bool handshakeOk = true; + if (client != NULL) { + // handshake was successful + m_waitingClients.push_back(client); + m_events->addEvent(Event(m_events->forClientListener().connected(), + this)); + + // watch for client to disconnect while it's in our queue + m_events->adoptHandler(m_events->forClientProxy().disconnected(), client, + new TMethodEventJob<ClientListener>(this, + &ClientListener::handleClientDisconnected, + client)); + } + else { + handshakeOk = false; + } + + // now finished with unknown client + m_events->removeHandler(m_events->forClientProxyUnknown().success(), client); + m_events->removeHandler(m_events->forClientProxyUnknown().failure(), client); + m_newClients.erase(unknownClient); + PacketStreamFilter* streamFileter = dynamic_cast<PacketStreamFilter*>(unknownClient->getStream()); + IDataSocket* socket = NULL; + if (streamFileter != NULL) { + socket = dynamic_cast<IDataSocket*>(streamFileter->getStream()); + } + + delete unknownClient; +} + +void +ClientListener::handleClientDisconnected(const Event&, void* vclient) +{ + ClientProxy* client = static_cast<ClientProxy*>(vclient); + + // find client in waiting clients queue + for (WaitingClients::iterator i = m_waitingClients.begin(), + n = m_waitingClients.end(); i != n; ++i) { + if (*i == client) { + m_waitingClients.erase(i); + m_events->removeHandler(m_events->forClientProxy().disconnected(), + client); + + // pull out the socket before deleting the client so + // we know which socket we no longer need + IDataSocket* socket = static_cast<IDataSocket*>(client->getStream()); + delete client; + m_clientSockets.erase(socket); + delete socket; + + break; + } + } +} + +void +ClientListener::cleanupListenSocket() +{ + delete m_listen; +} + +void +ClientListener::cleanupClientSockets() +{ + ClientSockets::iterator it; + for (it = m_clientSockets.begin(); it != m_clientSockets.end(); it++) { + delete *it; + } + m_clientSockets.clear(); +} diff --git a/src/lib/server/ClientListener.h b/src/lib/server/ClientListener.h new file mode 100644 index 0000000..b02cbb1 --- /dev/null +++ b/src/lib/server/ClientListener.h @@ -0,0 +1,91 @@ +/* + * 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 "server/Config.h" +#include "base/EventTypes.h" +#include "base/Event.h" +#include "common/stddeque.h" +#include "common/stdset.h" + +class ClientProxy; +class ClientProxyUnknown; +class NetworkAddress; +class IListenSocket; +class ISocketFactory; +class Server; +class IEventQueue; +class IDataSocket; + +class ClientListener { +public: + // The factories are adopted. + ClientListener(const NetworkAddress&, + ISocketFactory*, + IEventQueue* events, + bool enableCrypto); + ~ClientListener(); + + //! @name manipulators + //@{ + + void setServer(Server* server); + + //@} + + //! @name accessors + //@{ + + //! Get next connected client + /*! + Returns the next connected client and removes it from the internal + list. The client is responsible for deleting the returned client. + Returns NULL if no clients are available. + */ + ClientProxy* getNextClient(); + + //! Get server which owns this listener + Server* getServer() { return m_server; } + + //@} + +private: + // client connection event handlers + void handleClientConnecting(const Event&, void*); + void handleClientAccepted(const Event&, void*); + void handleUnknownClient(const Event&, void*); + void handleClientDisconnected(const Event&, void*); + + void cleanupListenSocket(); + void cleanupClientSockets(); + +private: + typedef std::set<ClientProxyUnknown*> NewClients; + typedef std::deque<ClientProxy*> WaitingClients; + typedef std::set<IDataSocket*> ClientSockets; + + IListenSocket* m_listen; + ISocketFactory* m_socketFactory; + NewClients m_newClients; + WaitingClients m_waitingClients; + Server* m_server; + IEventQueue* m_events; + bool m_useSecureNetwork; + ClientSockets m_clientSockets; +}; diff --git a/src/lib/server/ClientProxy.cpp b/src/lib/server/ClientProxy.cpp new file mode 100644 index 0000000..5a28248 --- /dev/null +++ b/src/lib/server/ClientProxy.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 "server/ClientProxy.h" + +#include "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "base/Log.h" +#include "base/EventQueue.h" + +// +// ClientProxy +// + +ClientProxy::ClientProxy(const String& name, barrier::IStream* stream) : + BaseClientProxy(name), + m_stream(stream) +{ +} + +ClientProxy::~ClientProxy() +{ + delete m_stream; +} + +void +ClientProxy::close(const char* msg) +{ + LOG((CLOG_DEBUG1 "send close \"%s\" to \"%s\"", msg, getName().c_str())); + ProtocolUtil::writef(getStream(), msg); + + // force the close to be sent before we return + getStream()->flush(); +} + +barrier::IStream* +ClientProxy::getStream() const +{ + return m_stream; +} + +void* +ClientProxy::getEventTarget() const +{ + return static_cast<IScreen*>(const_cast<ClientProxy*>(this)); +} diff --git a/src/lib/server/ClientProxy.h b/src/lib/server/ClientProxy.h new file mode 100644 index 0000000..726ded4 --- /dev/null +++ b/src/lib/server/ClientProxy.h @@ -0,0 +1,91 @@ +/* + * 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 "server/BaseClientProxy.h" +#include "base/Event.h" +#include "base/String.h" +#include "base/EventTypes.h" + +namespace barrier { class IStream; } + +//! Generic proxy for client +class ClientProxy : public BaseClientProxy { +public: + /*! + \c name is the name of the client. + */ + ClientProxy(const String& name, barrier::IStream* adoptedStream); + ~ClientProxy(); + + //! @name manipulators + //@{ + + //! Disconnect + /*! + Ask the client to disconnect, using \p msg as the reason. + */ + void close(const char* msg); + + //@} + //! @name accessors + //@{ + + //! Get stream + /*! + Returns the original stream passed to the c'tor. + */ + barrier::IStream* getStream() const; + + //@} + + // IScreen + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const = 0; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const = 0; + virtual void getCursorPos(SInt32& x, SInt32& y) const = 0; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver) = 0; + virtual bool leave() = 0; + virtual void setClipboard(ClipboardID, const IClipboard*) = 0; + virtual void grabClipboard(ClipboardID) = 0; + virtual void setClipboardDirty(ClipboardID, bool) = 0; + virtual void keyDown(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton) = 0; + virtual void keyUp(KeyID, KeyModifierMask, KeyButton) = 0; + virtual void mouseDown(ButtonID) = 0; + virtual void mouseUp(ButtonID) = 0; + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0; + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel) = 0; + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta) = 0; + virtual void screensaver(bool activate) = 0; + virtual void resetOptions() = 0; + virtual void setOptions(const OptionsList& options) = 0; + virtual void sendDragInfo(UInt32 fileCount, const char* info, + size_t size) = 0; + virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize) = 0; + +private: + barrier::IStream* m_stream; +}; diff --git a/src/lib/server/ClientProxy1_0.cpp b/src/lib/server/ClientProxy1_0.cpp new file mode 100644 index 0000000..ee805c6 --- /dev/null +++ b/src/lib/server/ClientProxy1_0.cpp @@ -0,0 +1,484 @@ +/* + * 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 "server/ClientProxy1_0.h" + +#include "barrier/ProtocolUtil.h" +#include "barrier/XBarrier.h" +#include "io/IStream.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +#include <cstring> + +// +// ClientProxy1_0 +// + +ClientProxy1_0::ClientProxy1_0(const String& name, barrier::IStream* stream, IEventQueue* events) : + ClientProxy(name, stream), + m_heartbeatTimer(NULL), + m_parser(&ClientProxy1_0::parseHandshakeMessage), + m_events(events) +{ + // install event handlers + m_events->adoptHandler(m_events->forIStream().inputReady(), + stream->getEventTarget(), + new TMethodEventJob<ClientProxy1_0>(this, + &ClientProxy1_0::handleData, NULL)); + m_events->adoptHandler(m_events->forIStream().outputError(), + stream->getEventTarget(), + new TMethodEventJob<ClientProxy1_0>(this, + &ClientProxy1_0::handleWriteError, NULL)); + m_events->adoptHandler(m_events->forIStream().inputShutdown(), + stream->getEventTarget(), + new TMethodEventJob<ClientProxy1_0>(this, + &ClientProxy1_0::handleDisconnect, NULL)); + m_events->adoptHandler(m_events->forIStream().outputShutdown(), + stream->getEventTarget(), + new TMethodEventJob<ClientProxy1_0>(this, + &ClientProxy1_0::handleWriteError, NULL)); + m_events->adoptHandler(Event::kTimer, this, + new TMethodEventJob<ClientProxy1_0>(this, + &ClientProxy1_0::handleFlatline, NULL)); + + setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath); + + LOG((CLOG_DEBUG1 "querying client \"%s\" info", getName().c_str())); + ProtocolUtil::writef(getStream(), kMsgQInfo); +} + +ClientProxy1_0::~ClientProxy1_0() +{ + removeHandlers(); +} + +void +ClientProxy1_0::disconnect() +{ + removeHandlers(); + getStream()->close(); + m_events->addEvent(Event(m_events->forClientProxy().disconnected(), getEventTarget())); +} + +void +ClientProxy1_0::removeHandlers() +{ + // uninstall event handlers + m_events->removeHandler(m_events->forIStream().inputReady(), + getStream()->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputError(), + getStream()->getEventTarget()); + m_events->removeHandler(m_events->forIStream().inputShutdown(), + getStream()->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputShutdown(), + getStream()->getEventTarget()); + m_events->removeHandler(Event::kTimer, this); + + // remove timer + removeHeartbeatTimer(); +} + +void +ClientProxy1_0::addHeartbeatTimer() +{ + if (m_heartbeatAlarm > 0.0) { + m_heartbeatTimer = m_events->newOneShotTimer(m_heartbeatAlarm, this); + } +} + +void +ClientProxy1_0::removeHeartbeatTimer() +{ + if (m_heartbeatTimer != NULL) { + m_events->deleteTimer(m_heartbeatTimer); + m_heartbeatTimer = NULL; + } +} + +void +ClientProxy1_0::resetHeartbeatTimer() +{ + // reset the alarm + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +void +ClientProxy1_0::resetHeartbeatRate() +{ + setHeartbeatRate(kHeartRate, kHeartRate * kHeartBeatsUntilDeath); +} + +void +ClientProxy1_0::setHeartbeatRate(double, double alarm) +{ + m_heartbeatAlarm = alarm; +} + +void +ClientProxy1_0::handleData(const Event&, void*) +{ + // handle messages until there are no more. first read message code. + UInt8 code[4]; + UInt32 n = getStream()->read(code, 4); + while (n != 0) { + // verify we got an entire code + if (n != 4) { + LOG((CLOG_ERR "incomplete message from \"%s\": %d bytes", getName().c_str(), n)); + disconnect(); + return; + } + + // parse message + LOG((CLOG_DEBUG2 "msg from \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + if (!(this->*m_parser)(code)) { + LOG((CLOG_ERR "invalid message from client \"%s\": %c%c%c%c", getName().c_str(), code[0], code[1], code[2], code[3])); + disconnect(); + return; + } + + // next message + n = getStream()->read(code, 4); + } + + // restart heartbeat timer + resetHeartbeatTimer(); +} + +bool +ClientProxy1_0::parseHandshakeMessage(const UInt8* code) +{ + if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgDInfo, 4) == 0) { + // future messages get parsed by parseMessage + m_parser = &ClientProxy1_0::parseMessage; + if (recvInfo()) { + m_events->addEvent(Event(m_events->forClientProxy().ready(), getEventTarget())); + addHeartbeatTimer(); + return true; + } + } + return false; +} + +bool +ClientProxy1_0::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDInfo, 4) == 0) { + if (recvInfo()) { + m_events->addEvent( + Event(m_events->forIScreen().shapeChanged(), getEventTarget())); + return true; + } + return false; + } + else if (memcmp(code, kMsgCNoop, 4) == 0) { + // discard no-ops + LOG((CLOG_DEBUG2 "no-op from", getName().c_str())); + return true; + } + else if (memcmp(code, kMsgCClipboard, 4) == 0) { + return recvGrabClipboard(); + } + else if (memcmp(code, kMsgDClipboard, 4) == 0) { + return recvClipboard(); + } + return false; +} + +void +ClientProxy1_0::handleDisconnect(const Event&, void*) +{ + LOG((CLOG_NOTE "client \"%s\" has disconnected", getName().c_str())); + disconnect(); +} + +void +ClientProxy1_0::handleWriteError(const Event&, void*) +{ + LOG((CLOG_WARN "error writing to client \"%s\"", getName().c_str())); + disconnect(); +} + +void +ClientProxy1_0::handleFlatline(const Event&, void*) +{ + // didn't get a heartbeat fast enough. assume client is dead. + LOG((CLOG_NOTE "client \"%s\" is dead", getName().c_str())); + disconnect(); +} + +bool +ClientProxy1_0::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + Clipboard::copy(clipboard, &m_clipboard[id].m_clipboard); + return true; +} + +void +ClientProxy1_0::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const +{ + x = m_info.m_x; + y = m_info.m_y; + w = m_info.m_w; + h = m_info.m_h; +} + +void +ClientProxy1_0::getCursorPos(SInt32& x, SInt32& y) const +{ + // note -- this returns the cursor pos from when we last got client info + x = m_info.m_mx; + y = m_info.m_my; +} + +void +ClientProxy1_0::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool) +{ + LOG((CLOG_DEBUG1 "send enter to \"%s\", %d,%d %d %04x", getName().c_str(), xAbs, yAbs, seqNum, mask)); + ProtocolUtil::writef(getStream(), kMsgCEnter, + xAbs, yAbs, seqNum, mask); +} + +bool +ClientProxy1_0::leave() +{ + LOG((CLOG_DEBUG1 "send leave to \"%s\"", getName().c_str())); + ProtocolUtil::writef(getStream(), kMsgCLeave); + + // we can never prevent the user from leaving + return true; +} + +void +ClientProxy1_0::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore -- deprecated in protocol 1.0 +} + +void +ClientProxy1_0::grabClipboard(ClipboardID id) +{ + LOG((CLOG_DEBUG "send grab clipboard %d to \"%s\"", id, getName().c_str())); + ProtocolUtil::writef(getStream(), kMsgCClipboard, id, 0); + + // this clipboard is now dirty + m_clipboard[id].m_dirty = true; +} + +void +ClientProxy1_0::setClipboardDirty(ClipboardID id, bool dirty) +{ + m_clipboard[id].m_dirty = dirty; +} + +void +ClientProxy1_0::keyDown(KeyID key, KeyModifierMask mask, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); + ProtocolUtil::writef(getStream(), kMsgDKeyDown1_0, key, mask); +} + +void +ClientProxy1_0::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d", getName().c_str(), key, mask, count)); + ProtocolUtil::writef(getStream(), kMsgDKeyRepeat1_0, key, mask, count); +} + +void +ClientProxy1_0::keyUp(KeyID key, KeyModifierMask mask, KeyButton) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x", getName().c_str(), key, mask)); + ProtocolUtil::writef(getStream(), kMsgDKeyUp1_0, key, mask); +} + +void +ClientProxy1_0::mouseDown(ButtonID button) +{ + LOG((CLOG_DEBUG1 "send mouse down to \"%s\" id=%d", getName().c_str(), button)); + ProtocolUtil::writef(getStream(), kMsgDMouseDown, button); +} + +void +ClientProxy1_0::mouseUp(ButtonID button) +{ + LOG((CLOG_DEBUG1 "send mouse up to \"%s\" id=%d", getName().c_str(), button)); + ProtocolUtil::writef(getStream(), kMsgDMouseUp, button); +} + +void +ClientProxy1_0::mouseMove(SInt32 xAbs, SInt32 yAbs) +{ + LOG((CLOG_DEBUG2 "send mouse move to \"%s\" %d,%d", getName().c_str(), xAbs, yAbs)); + ProtocolUtil::writef(getStream(), kMsgDMouseMove, xAbs, yAbs); +} + +void +ClientProxy1_0::mouseRelativeMove(SInt32, SInt32) +{ + // ignore -- not supported in protocol 1.0 +} + +void +ClientProxy1_0::mouseWheel(SInt32, SInt32 yDelta) +{ + // clients prior to 1.3 only support the y axis + LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d", getName().c_str(), yDelta)); + ProtocolUtil::writef(getStream(), kMsgDMouseWheel1_0, yDelta); +} + +void +ClientProxy1_0::sendDragInfo(UInt32 fileCount, const char* info, size_t size) +{ + // ignore -- not supported in protocol 1.0 + LOG((CLOG_DEBUG "draggingInfoSending not supported")); +} + +void +ClientProxy1_0::fileChunkSending(UInt8 mark, char* data, size_t dataSize) +{ + // ignore -- not supported in protocol 1.0 + LOG((CLOG_DEBUG "fileChunkSending not supported")); +} + +void +ClientProxy1_0::screensaver(bool on) +{ + LOG((CLOG_DEBUG1 "send screen saver to \"%s\" on=%d", getName().c_str(), on ? 1 : 0)); + ProtocolUtil::writef(getStream(), kMsgCScreenSaver, on ? 1 : 0); +} + +void +ClientProxy1_0::resetOptions() +{ + LOG((CLOG_DEBUG1 "send reset options to \"%s\"", getName().c_str())); + ProtocolUtil::writef(getStream(), kMsgCResetOptions); + + // reset heart rate and death + resetHeartbeatRate(); + removeHeartbeatTimer(); + addHeartbeatTimer(); +} + +void +ClientProxy1_0::setOptions(const OptionsList& options) +{ + LOG((CLOG_DEBUG1 "send set options to \"%s\" size=%d", getName().c_str(), options.size())); + ProtocolUtil::writef(getStream(), kMsgDSetOptions, &options); + + // check options + for (UInt32 i = 0, n = (UInt32)options.size(); i < n; i += 2) { + if (options[i] == kOptionHeartbeat) { + double rate = 1.0e-3 * static_cast<double>(options[i + 1]); + if (rate <= 0.0) { + rate = -1.0; + } + setHeartbeatRate(rate, rate * kHeartBeatsUntilDeath); + removeHeartbeatTimer(); + addHeartbeatTimer(); + } + } +} + +bool +ClientProxy1_0::recvInfo() +{ + // parse the message + SInt16 x, y, w, h, dummy1, mx, my; + if (!ProtocolUtil::readf(getStream(), kMsgDInfo + 4, + &x, &y, &w, &h, &dummy1, &mx, &my)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" info shape=%d,%d %dx%d at %d,%d", getName().c_str(), x, y, w, h, mx, my)); + + // validate + if (w <= 0 || h <= 0) { + return false; + } + if (mx < x || mx >= x + w || my < y || my >= y + h) { + mx = x + w / 2; + my = y + h / 2; + } + + // save + m_info.m_x = x; + m_info.m_y = y; + m_info.m_w = w; + m_info.m_h = h; + m_info.m_mx = mx; + m_info.m_my = my; + + // acknowledge receipt + LOG((CLOG_DEBUG1 "send info ack to \"%s\"", getName().c_str())); + ProtocolUtil::writef(getStream(), kMsgCInfoAck); + return true; +} + +bool +ClientProxy1_0::recvClipboard() +{ + // deprecated in protocol 1.0 + return false; +} + +bool +ClientProxy1_0::recvGrabClipboard() +{ + // parse message + ClipboardID id; + UInt32 seqNum; + if (!ProtocolUtil::readf(getStream(), kMsgCClipboard + 4, &id, &seqNum)) { + return false; + } + LOG((CLOG_DEBUG "received client \"%s\" grabbed clipboard %d seqnum=%d", getName().c_str(), id, seqNum)); + + // validate + if (id >= kClipboardEnd) { + return false; + } + + // notify + ClipboardInfo* info = new ClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = seqNum; + m_events->addEvent(Event(m_events->forClipboard().clipboardGrabbed(), + getEventTarget(), info)); + + return true; +} + +// +// ClientProxy1_0::ClientClipboard +// + +ClientProxy1_0::ClientClipboard::ClientClipboard() : + m_clipboard(), + m_sequenceNumber(0), + m_dirty(true) +{ + // do nothing +} diff --git a/src/lib/server/ClientProxy1_0.h b/src/lib/server/ClientProxy1_0.h new file mode 100644 index 0000000..0720232 --- /dev/null +++ b/src/lib/server/ClientProxy1_0.h @@ -0,0 +1,107 @@ +/* + * 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 "server/ClientProxy.h" +#include "barrier/Clipboard.h" +#include "barrier/protocol_types.h" + +class Event; +class EventQueueTimer; +class IEventQueue; + +//! Proxy for client implementing protocol version 1.0 +class ClientProxy1_0 : public ClientProxy { +public: + ClientProxy1_0(const String& name, barrier::IStream* adoptedStream, IEventQueue* events); + ~ClientProxy1_0(); + + // IScreen + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size); + virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize); + +protected: + virtual bool parseHandshakeMessage(const UInt8* code); + virtual bool parseMessage(const UInt8* code); + + virtual void resetHeartbeatRate(); + virtual void setHeartbeatRate(double rate, double alarm); + virtual void resetHeartbeatTimer(); + virtual void addHeartbeatTimer(); + virtual void removeHeartbeatTimer(); + virtual bool recvClipboard(); +private: + void disconnect(); + void removeHandlers(); + + void handleData(const Event&, void*); + void handleDisconnect(const Event&, void*); + void handleWriteError(const Event&, void*); + void handleFlatline(const Event&, void*); + + bool recvInfo(); + bool recvGrabClipboard(); + +protected: + struct ClientClipboard { + public: + ClientClipboard(); + + public: + Clipboard m_clipboard; + UInt32 m_sequenceNumber; + bool m_dirty; + }; + + ClientClipboard m_clipboard[kClipboardEnd]; + +private: + typedef bool (ClientProxy1_0::*MessageParser)(const UInt8*); + + ClientInfo m_info; + double m_heartbeatAlarm; + EventQueueTimer* m_heartbeatTimer; + MessageParser m_parser; + IEventQueue* m_events; +}; diff --git a/src/lib/server/ClientProxy1_1.cpp b/src/lib/server/ClientProxy1_1.cpp new file mode 100644 index 0000000..b7eb4c4 --- /dev/null +++ b/src/lib/server/ClientProxy1_1.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 "server/ClientProxy1_1.h" + +#include "barrier/ProtocolUtil.h" +#include "base/Log.h" + +#include <cstring> + +// +// ClientProxy1_1 +// + +ClientProxy1_1::ClientProxy1_1(const String& name, barrier::IStream* stream, IEventQueue* events) : + ClientProxy1_0(name, stream, events) +{ + // do nothing +} + +ClientProxy1_1::~ClientProxy1_1() +{ + // do nothing +} + +void +ClientProxy1_1::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key down to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + ProtocolUtil::writef(getStream(), kMsgDKeyDown, key, mask, button); +} + +void +ClientProxy1_1::keyRepeat(KeyID key, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key repeat to \"%s\" id=%d, mask=0x%04x, count=%d, button=0x%04x", getName().c_str(), key, mask, count, button)); + ProtocolUtil::writef(getStream(), kMsgDKeyRepeat, key, mask, count, button); +} + +void +ClientProxy1_1::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + LOG((CLOG_DEBUG1 "send key up to \"%s\" id=%d, mask=0x%04x, button=0x%04x", getName().c_str(), key, mask, button)); + ProtocolUtil::writef(getStream(), kMsgDKeyUp, key, mask, button); +} diff --git a/src/lib/server/ClientProxy1_1.h b/src/lib/server/ClientProxy1_1.h new file mode 100644 index 0000000..cdb674d --- /dev/null +++ b/src/lib/server/ClientProxy1_1.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 "server/ClientProxy1_0.h" + +//! Proxy for client implementing protocol version 1.1 +class ClientProxy1_1 : public ClientProxy1_0 { +public: + ClientProxy1_1(const String& name, barrier::IStream* adoptedStream, IEventQueue* events); + ~ClientProxy1_1(); + + // IClient overrides + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); +}; diff --git a/src/lib/server/ClientProxy1_2.cpp b/src/lib/server/ClientProxy1_2.cpp new file mode 100644 index 0000000..2dd13cf --- /dev/null +++ b/src/lib/server/ClientProxy1_2.cpp @@ -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/>. + */ + +#include "server/ClientProxy1_2.h" + +#include "barrier/ProtocolUtil.h" +#include "base/Log.h" + +// +// ClientProxy1_1 +// + +ClientProxy1_2::ClientProxy1_2(const String& name, barrier::IStream* stream, IEventQueue* events) : + ClientProxy1_1(name, stream, events) +{ + // do nothing +} + +ClientProxy1_2::~ClientProxy1_2() +{ + // do nothing +} + +void +ClientProxy1_2::mouseRelativeMove(SInt32 xRel, SInt32 yRel) +{ + LOG((CLOG_DEBUG2 "send mouse relative move to \"%s\" %d,%d", getName().c_str(), xRel, yRel)); + ProtocolUtil::writef(getStream(), kMsgDMouseRelMove, xRel, yRel); +} diff --git a/src/lib/server/ClientProxy1_2.h b/src/lib/server/ClientProxy1_2.h new file mode 100644 index 0000000..f6ffe94 --- /dev/null +++ b/src/lib/server/ClientProxy1_2.h @@ -0,0 +1,33 @@ +/* + * 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 "server/ClientProxy1_1.h" + +class IEventQueue; + +//! Proxy for client implementing protocol version 1.2 +class ClientProxy1_2 : public ClientProxy1_1 { +public: + ClientProxy1_2(const String& name, barrier::IStream* adoptedStream, IEventQueue* events); + ~ClientProxy1_2(); + + // IClient overrides + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); +}; diff --git a/src/lib/server/ClientProxy1_3.cpp b/src/lib/server/ClientProxy1_3.cpp new file mode 100644 index 0000000..34ea0c8 --- /dev/null +++ b/src/lib/server/ClientProxy1_3.cpp @@ -0,0 +1,129 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2006 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 "server/ClientProxy1_3.h" + +#include "barrier/ProtocolUtil.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +#include <cstring> +#include <memory> + +// +// ClientProxy1_3 +// + +ClientProxy1_3::ClientProxy1_3(const String& name, barrier::IStream* stream, IEventQueue* events) : + ClientProxy1_2(name, stream, events), + m_keepAliveRate(kKeepAliveRate), + m_keepAliveTimer(NULL), + m_events(events) +{ + setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath); +} + +ClientProxy1_3::~ClientProxy1_3() +{ + // cannot do this in superclass or our override wouldn't get called + removeHeartbeatTimer(); +} + +void +ClientProxy1_3::mouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + LOG((CLOG_DEBUG2 "send mouse wheel to \"%s\" %+d,%+d", getName().c_str(), xDelta, yDelta)); + ProtocolUtil::writef(getStream(), kMsgDMouseWheel, xDelta, yDelta); +} + +bool +ClientProxy1_3::parseMessage(const UInt8* code) +{ + // process message + if (memcmp(code, kMsgCKeepAlive, 4) == 0) { + // reset alarm + resetHeartbeatTimer(); + return true; + } + else { + return ClientProxy1_2::parseMessage(code); + } +} + +void +ClientProxy1_3::resetHeartbeatRate() +{ + setHeartbeatRate(kKeepAliveRate, kKeepAliveRate * kKeepAlivesUntilDeath); +} + +void +ClientProxy1_3::setHeartbeatRate(double rate, double) +{ + m_keepAliveRate = rate; + ClientProxy1_2::setHeartbeatRate(rate, rate * kKeepAlivesUntilDeath); +} + +void +ClientProxy1_3::resetHeartbeatTimer() +{ + // reset the alarm but not the keep alive timer + ClientProxy1_2::removeHeartbeatTimer(); + ClientProxy1_2::addHeartbeatTimer(); +} + +void +ClientProxy1_3::addHeartbeatTimer() +{ + // create and install a timer to periodically send keep alives + if (m_keepAliveRate > 0.0) { + m_keepAliveTimer = m_events->newTimer(m_keepAliveRate, NULL); + m_events->adoptHandler(Event::kTimer, m_keepAliveTimer, + new TMethodEventJob<ClientProxy1_3>(this, + &ClientProxy1_3::handleKeepAlive, NULL)); + } + + // superclass does the alarm + ClientProxy1_2::addHeartbeatTimer(); +} + +void +ClientProxy1_3::removeHeartbeatTimer() +{ + // remove the timer that sends keep alives periodically + if (m_keepAliveTimer != NULL) { + m_events->removeHandler(Event::kTimer, m_keepAliveTimer); + m_events->deleteTimer(m_keepAliveTimer); + m_keepAliveTimer = NULL; + } + + // superclass does the alarm + ClientProxy1_2::removeHeartbeatTimer(); +} + +void +ClientProxy1_3::handleKeepAlive(const Event&, void*) +{ + keepAlive(); +} + +void +ClientProxy1_3::keepAlive() +{ + ProtocolUtil::writef(getStream(), kMsgCKeepAlive); +} diff --git a/src/lib/server/ClientProxy1_3.h b/src/lib/server/ClientProxy1_3.h new file mode 100644 index 0000000..ff2ed0a --- /dev/null +++ b/src/lib/server/ClientProxy1_3.h @@ -0,0 +1,48 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2006 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 "server/ClientProxy1_2.h" + +//! Proxy for client implementing protocol version 1.3 +class ClientProxy1_3 : public ClientProxy1_2 { +public: + ClientProxy1_3(const String& name, barrier::IStream* adoptedStream, IEventQueue* events); + ~ClientProxy1_3(); + + // IClient overrides + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + + void handleKeepAlive(const Event&, void*); + +protected: + // ClientProxy overrides + virtual bool parseMessage(const UInt8* code); + virtual void resetHeartbeatRate(); + virtual void setHeartbeatRate(double rate, double alarm); + virtual void resetHeartbeatTimer(); + virtual void addHeartbeatTimer(); + virtual void removeHeartbeatTimer(); + virtual void keepAlive(); + +private: + double m_keepAliveRate; + EventQueueTimer* m_keepAliveTimer; + IEventQueue* m_events; +}; diff --git a/src/lib/server/ClientProxy1_4.cpp b/src/lib/server/ClientProxy1_4.cpp new file mode 100644 index 0000000..43c708d --- /dev/null +++ b/src/lib/server/ClientProxy1_4.cpp @@ -0,0 +1,66 @@ +/* + * 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 "server/ClientProxy1_4.h" + +#include "server/Server.h" +#include "barrier/ProtocolUtil.h" +#include "base/Log.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +#include <cstring> +#include <memory> + +// +// ClientProxy1_4 +// + +ClientProxy1_4::ClientProxy1_4(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) : + ClientProxy1_3(name, stream, events), m_server(server) +{ + assert(m_server != NULL); +} + +ClientProxy1_4::~ClientProxy1_4() +{ +} + +void +ClientProxy1_4::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + ClientProxy1_3::keyDown(key, mask, button); +} + +void +ClientProxy1_4::keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button) +{ + ClientProxy1_3::keyRepeat(key, mask, count, button); +} + +void +ClientProxy1_4::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + ClientProxy1_3::keyUp(key, mask, button); +} + +void +ClientProxy1_4::keepAlive() +{ + ClientProxy1_3::keepAlive(); +} diff --git a/src/lib/server/ClientProxy1_4.h b/src/lib/server/ClientProxy1_4.h new file mode 100644 index 0000000..6c55965 --- /dev/null +++ b/src/lib/server/ClientProxy1_4.h @@ -0,0 +1,46 @@ +/* + * 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/>. + */ + +#pragma once + +#include "server/ClientProxy1_3.h" + +class Server; + +//! Proxy for client implementing protocol version 1.4 +class ClientProxy1_4 : public ClientProxy1_3 { +public: + ClientProxy1_4(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events); + ~ClientProxy1_4(); + + //! @name accessors + //@{ + + //! get server pointer + Server* getServer() { return m_server; } + + //@} + + // IClient overrides + virtual void keyDown(KeyID key, KeyModifierMask mask, KeyButton button); + virtual void keyRepeat(KeyID key, KeyModifierMask mask, SInt32 count, KeyButton button); + virtual void keyUp(KeyID key, KeyModifierMask mask, KeyButton button); + virtual void keepAlive(); + + Server* m_server; +}; diff --git a/src/lib/server/ClientProxy1_5.cpp b/src/lib/server/ClientProxy1_5.cpp new file mode 100644 index 0000000..43fd0b7 --- /dev/null +++ b/src/lib/server/ClientProxy1_5.cpp @@ -0,0 +1,110 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 "server/ClientProxy1_5.h" + +#include "server/Server.h" +#include "barrier/FileChunk.h" +#include "barrier/StreamChunker.h" +#include "barrier/ProtocolUtil.h" +#include "io/IStream.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +#include <sstream> + +// +// ClientProxy1_5 +// + +ClientProxy1_5::ClientProxy1_5(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) : + ClientProxy1_4(name, stream, server, events), + m_events(events) +{ + + m_events->adoptHandler(m_events->forFile().keepAlive(), + this, + new TMethodEventJob<ClientProxy1_3>(this, + &ClientProxy1_3::handleKeepAlive, NULL)); +} + +ClientProxy1_5::~ClientProxy1_5() +{ + m_events->removeHandler(m_events->forFile().keepAlive(), this); +} + +void +ClientProxy1_5::sendDragInfo(UInt32 fileCount, const char* info, size_t size) +{ + String data(info, size); + + ProtocolUtil::writef(getStream(), kMsgDDragInfo, fileCount, &data); +} + +void +ClientProxy1_5::fileChunkSending(UInt8 mark, char* data, size_t dataSize) +{ + FileChunk::send(getStream(), mark, data, dataSize); +} + +bool +ClientProxy1_5::parseMessage(const UInt8* code) +{ + if (memcmp(code, kMsgDFileTransfer, 4) == 0) { + fileChunkReceived(); + } + else if (memcmp(code, kMsgDDragInfo, 4) == 0) { + dragInfoReceived(); + } + else { + return ClientProxy1_4::parseMessage(code); + } + + return true; +} + +void +ClientProxy1_5::fileChunkReceived() +{ + Server* server = getServer(); + int result = FileChunk::assemble( + getStream(), + server->getReceivedFileData(), + server->getExpectedFileSize()); + + + if (result == kFinish) { + m_events->addEvent(Event(m_events->forFile().fileRecieveCompleted(), server)); + } + else if (result == kStart) { + if (server->getFakeDragFileList().size() > 0) { + String filename = server->getFakeDragFileList().at(0).getFilename(); + LOG((CLOG_DEBUG "start receiving %s", filename.c_str())); + } + } +} + +void +ClientProxy1_5::dragInfoReceived() +{ + // parse + UInt32 fileNum = 0; + String content; + ProtocolUtil::readf(getStream(), kMsgDDragInfo + 4, &fileNum, &content); + + m_server->dragInfoReceived(fileNum, content); +} diff --git a/src/lib/server/ClientProxy1_5.h b/src/lib/server/ClientProxy1_5.h new file mode 100644 index 0000000..776de03 --- /dev/null +++ b/src/lib/server/ClientProxy1_5.h @@ -0,0 +1,41 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2013-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 + +#include "server/ClientProxy1_4.h" +#include "base/Stopwatch.h" +#include "common/stdvector.h" + +class Server; +class IEventQueue; + +//! Proxy for client implementing protocol version 1.5 +class ClientProxy1_5 : public ClientProxy1_4 { +public: + ClientProxy1_5(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events); + ~ClientProxy1_5(); + + virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size); + virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize); + virtual bool parseMessage(const UInt8* code); + void fileChunkReceived(); + void dragInfoReceived(); + +private: + IEventQueue* m_events; +}; diff --git a/src/lib/server/ClientProxy1_6.cpp b/src/lib/server/ClientProxy1_6.cpp new file mode 100644 index 0000000..a0d2621 --- /dev/null +++ b/src/lib/server/ClientProxy1_6.cpp @@ -0,0 +1,100 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 "server/ClientProxy1_6.h" + +#include "server/Server.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/StreamChunker.h" +#include "barrier/ClipboardChunk.h" +#include "io/IStream.h" +#include "base/TMethodEventJob.h" +#include "base/Log.h" + +// +// ClientProxy1_6 +// + +ClientProxy1_6::ClientProxy1_6(const String& name, barrier::IStream* stream, Server* server, IEventQueue* events) : + ClientProxy1_5(name, stream, server, events), + m_events(events) +{ + m_events->adoptHandler(m_events->forClipboard().clipboardSending(), + this, + new TMethodEventJob<ClientProxy1_6>(this, + &ClientProxy1_6::handleClipboardSendingEvent)); +} + +ClientProxy1_6::~ClientProxy1_6() +{ +} + +void +ClientProxy1_6::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore if this clipboard is already clean + if (m_clipboard[id].m_dirty) { + // this clipboard is now clean + m_clipboard[id].m_dirty = false; + Clipboard::copy(&m_clipboard[id].m_clipboard, clipboard); + + String data = m_clipboard[id].m_clipboard.marshall(); + + size_t size = data.size(); + LOG((CLOG_DEBUG "sending clipboard %d to \"%s\"", id, getName().c_str())); + + StreamChunker::sendClipboard(data, size, id, 0, m_events, this); + } +} + +void +ClientProxy1_6::handleClipboardSendingEvent(const Event& event, void*) +{ + ClipboardChunk::send(getStream(), event.getData()); +} + +bool +ClientProxy1_6::recvClipboard() +{ + // parse message + static String dataCached; + ClipboardID id; + UInt32 seq; + + int r = ClipboardChunk::assemble(getStream(), dataCached, id, seq); + + if (r == kStart) { + size_t size = ClipboardChunk::getExpectedSize(); + LOG((CLOG_DEBUG "receiving clipboard %d size=%d", id, size)); + } + else if (r == kFinish) { + LOG((CLOG_DEBUG "received client \"%s\" clipboard %d seqnum=%d, size=%d", + getName().c_str(), id, seq, dataCached.size())); + // save clipboard + m_clipboard[id].m_clipboard.unmarshall(dataCached, 0); + m_clipboard[id].m_sequenceNumber = seq; + + // notify + ClipboardInfo* info = new ClipboardInfo; + info->m_id = id; + info->m_sequenceNumber = seq; + m_events->addEvent(Event(m_events->forClipboard().clipboardChanged(), + getEventTarget(), info)); + } + + return true; +} diff --git a/src/lib/server/ClientProxy1_6.h b/src/lib/server/ClientProxy1_6.h new file mode 100644 index 0000000..838cb02 --- /dev/null +++ b/src/lib/server/ClientProxy1_6.h @@ -0,0 +1,39 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2015-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 + +#include "server/ClientProxy1_5.h" + +class Server; +class IEventQueue; + +//! Proxy for client implementing protocol version 1.6 +class ClientProxy1_6 : public ClientProxy1_5 { +public: + ClientProxy1_6(const String& name, barrier::IStream* adoptedStream, Server* server, IEventQueue* events); + ~ClientProxy1_6(); + + virtual void setClipboard(ClipboardID id, const IClipboard* clipboard); + virtual bool recvClipboard(); + +private: + void handleClipboardSendingEvent(const Event&, void*); + +private: + IEventQueue* m_events; +}; diff --git a/src/lib/server/ClientProxyUnknown.cpp b/src/lib/server/ClientProxyUnknown.cpp new file mode 100644 index 0000000..f929108 --- /dev/null +++ b/src/lib/server/ClientProxyUnknown.cpp @@ -0,0 +1,295 @@ +/* + * 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 "server/ClientProxyUnknown.h" + +#include "server/Server.h" +#include "server/ClientProxy1_0.h" +#include "server/ClientProxy1_1.h" +#include "server/ClientProxy1_2.h" +#include "server/ClientProxy1_3.h" +#include "server/ClientProxy1_4.h" +#include "server/ClientProxy1_5.h" +#include "server/ClientProxy1_6.h" +#include "barrier/protocol_types.h" +#include "barrier/ProtocolUtil.h" +#include "barrier/XBarrier.h" +#include "io/IStream.h" +#include "io/XIO.h" +#include "base/Log.h" +#include "base/String.h" +#include "base/IEventQueue.h" +#include "base/TMethodEventJob.h" + +// +// ClientProxyUnknown +// + +ClientProxyUnknown::ClientProxyUnknown(barrier::IStream* stream, double timeout, Server* server, IEventQueue* events) : + m_stream(stream), + m_proxy(NULL), + m_ready(false), + m_server(server), + m_events(events) +{ + assert(m_server != NULL); + + m_events->adoptHandler(Event::kTimer, this, + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleTimeout, NULL)); + m_timer = m_events->newOneShotTimer(timeout, this); + addStreamHandlers(); + + LOG((CLOG_DEBUG1 "saying hello")); + ProtocolUtil::writef(m_stream, kMsgHello, + kProtocolMajorVersion, + kProtocolMinorVersion); +} + +ClientProxyUnknown::~ClientProxyUnknown() +{ + removeHandlers(); + removeTimer(); + delete m_stream; + delete m_proxy; +} + +ClientProxy* +ClientProxyUnknown::orphanClientProxy() +{ + if (m_ready) { + removeHandlers(); + ClientProxy* proxy = m_proxy; + m_proxy = NULL; + return proxy; + } + else { + return NULL; + } +} + +void +ClientProxyUnknown::sendSuccess() +{ + m_ready = true; + removeTimer(); + m_events->addEvent(Event(m_events->forClientProxyUnknown().success(), this)); +} + +void +ClientProxyUnknown::sendFailure() +{ + delete m_proxy; + m_proxy = NULL; + m_ready = false; + removeHandlers(); + removeTimer(); + m_events->addEvent(Event(m_events->forClientProxyUnknown().failure(), this)); +} + +void +ClientProxyUnknown::addStreamHandlers() +{ + assert(m_stream != NULL); + + m_events->adoptHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget(), + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleData)); + m_events->adoptHandler(m_events->forIStream().outputError(), + m_stream->getEventTarget(), + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleWriteError)); + m_events->adoptHandler(m_events->forIStream().inputShutdown(), + m_stream->getEventTarget(), + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleDisconnect)); + m_events->adoptHandler(m_events->forIStream().outputShutdown(), + m_stream->getEventTarget(), + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleWriteError)); +} + +void +ClientProxyUnknown::addProxyHandlers() +{ + assert(m_proxy != NULL); + + m_events->adoptHandler(m_events->forClientProxy().ready(), + m_proxy, + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleReady)); + m_events->adoptHandler(m_events->forClientProxy().disconnected(), + m_proxy, + new TMethodEventJob<ClientProxyUnknown>(this, + &ClientProxyUnknown::handleDisconnect)); +} + +void +ClientProxyUnknown::removeHandlers() +{ + if (m_stream != NULL) { + m_events->removeHandler(m_events->forIStream().inputReady(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputError(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().inputShutdown(), + m_stream->getEventTarget()); + m_events->removeHandler(m_events->forIStream().outputShutdown(), + m_stream->getEventTarget()); + } + if (m_proxy != NULL) { + m_events->removeHandler(m_events->forClientProxy().ready(), + m_proxy); + m_events->removeHandler(m_events->forClientProxy().disconnected(), + m_proxy); + } +} + +void +ClientProxyUnknown::removeTimer() +{ + if (m_timer != NULL) { + m_events->deleteTimer(m_timer); + m_events->removeHandler(Event::kTimer, this); + m_timer = NULL; + } +} + +void +ClientProxyUnknown::handleData(const Event&, void*) +{ + LOG((CLOG_DEBUG1 "parsing hello reply")); + + String name("<unknown>"); + try { + // limit the maximum length of the hello + UInt32 n = m_stream->getSize(); + if (n > kMaxHelloLength) { + LOG((CLOG_DEBUG1 "hello reply too long")); + throw XBadClient(); + } + + // parse the reply to hello + SInt16 major, minor; + if (!ProtocolUtil::readf(m_stream, kMsgHelloBack, + &major, &minor, &name)) { + throw XBadClient(); + } + + // disallow invalid version numbers + if (major <= 0 || minor < 0) { + throw XIncompatibleClient(major, minor); + } + + // remove stream event handlers. the proxy we're about to create + // may install its own handlers and we don't want to accidentally + // remove those later. + removeHandlers(); + + // create client proxy for highest version supported by the client + if (major == 1) { + switch (minor) { + case 0: + m_proxy = new ClientProxy1_0(name, m_stream, m_events); + break; + + case 1: + m_proxy = new ClientProxy1_1(name, m_stream, m_events); + break; + + case 2: + m_proxy = new ClientProxy1_2(name, m_stream, m_events); + break; + + case 3: + m_proxy = new ClientProxy1_3(name, m_stream, m_events); + break; + + case 4: + m_proxy = new ClientProxy1_4(name, m_stream, m_server, m_events); + break; + + case 5: + m_proxy = new ClientProxy1_5(name, m_stream, m_server, m_events); + break; + + case 6: + m_proxy = new ClientProxy1_6(name, m_stream, m_server, m_events); + break; + } + } + + // hangup (with error) if version isn't supported + if (m_proxy == NULL) { + throw XIncompatibleClient(major, minor); + } + + // the proxy is created and now proxy now owns the stream + LOG((CLOG_DEBUG1 "created proxy for client \"%s\" version %d.%d", name.c_str(), major, minor)); + m_stream = NULL; + + // wait until the proxy signals that it's ready or has disconnected + addProxyHandlers(); + return; + } + catch (XIncompatibleClient& e) { + // client is incompatible + LOG((CLOG_WARN "client \"%s\" has incompatible version %d.%d)", name.c_str(), e.getMajor(), e.getMinor())); + ProtocolUtil::writef(m_stream, + kMsgEIncompatible, + kProtocolMajorVersion, kProtocolMinorVersion); + } + catch (XBadClient&) { + // client not behaving + LOG((CLOG_WARN "protocol error from client \"%s\"", name.c_str())); + ProtocolUtil::writef(m_stream, kMsgEBad); + } + catch (XBase& e) { + // misc error + LOG((CLOG_WARN "error communicating with client \"%s\": %s", name.c_str(), e.what())); + } + sendFailure(); +} + +void +ClientProxyUnknown::handleWriteError(const Event&, void*) +{ + LOG((CLOG_NOTE "error communicating with new client")); + sendFailure(); +} + +void +ClientProxyUnknown::handleTimeout(const Event&, void*) +{ + LOG((CLOG_NOTE "new client is unresponsive")); + sendFailure(); +} + +void +ClientProxyUnknown::handleDisconnect(const Event&, void*) +{ + LOG((CLOG_NOTE "new client disconnected")); + sendFailure(); +} + +void +ClientProxyUnknown::handleReady(const Event&, void*) +{ + sendSuccess(); +} diff --git a/src/lib/server/ClientProxyUnknown.h b/src/lib/server/ClientProxyUnknown.h new file mode 100644 index 0000000..5d59402 --- /dev/null +++ b/src/lib/server/ClientProxyUnknown.h @@ -0,0 +1,71 @@ +/* + * 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 "base/Event.h" +#include "base/EventTypes.h" + +class ClientProxy; +class EventQueueTimer; +namespace barrier { class IStream; } +class Server; +class IEventQueue; + +class ClientProxyUnknown { +public: + ClientProxyUnknown(barrier::IStream* stream, double timeout, Server* server, IEventQueue* events); + ~ClientProxyUnknown(); + + //! @name manipulators + //@{ + + //! Get the client proxy + /*! + Returns the client proxy created after a successful handshake + (i.e. when this object sends a success event). Returns NULL + if the handshake is unsuccessful or incomplete. + */ + ClientProxy* orphanClientProxy(); + + //! Get the stream + barrier::IStream* getStream() { return m_stream; } + + //@} + +private: + void sendSuccess(); + void sendFailure(); + void addStreamHandlers(); + void addProxyHandlers(); + void removeHandlers(); + void removeTimer(); + void handleData(const Event&, void*); + void handleWriteError(const Event&, void*); + void handleTimeout(const Event&, void*); + void handleDisconnect(const Event&, void*); + void handleReady(const Event&, void*); + +private: + barrier::IStream* m_stream; + EventQueueTimer* m_timer; + ClientProxy* m_proxy; + bool m_ready; + Server* m_server; + IEventQueue* m_events; +}; diff --git a/src/lib/server/Config.cpp b/src/lib/server/Config.cpp new file mode 100644 index 0000000..3cf60a5 --- /dev/null +++ b/src/lib/server/Config.cpp @@ -0,0 +1,2335 @@ +/* + * 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 "server/Config.h" + +#include "server/Server.h" +#include "barrier/KeyMap.h" +#include "barrier/key_types.h" +#include "net/XSocket.h" +#include "base/IEventQueue.h" +#include "common/stdistream.h" +#include "common/stdostream.h" + +#include <cstdlib> + +using namespace barrier::string; + +// +// Config +// + +Config::Config(IEventQueue* events) : + m_inputFilter(events), + m_hasLockToScreenAction(false), + m_events(events) +{ + // do nothing +} + +Config::~Config() +{ + // do nothing +} + +bool +Config::addScreen(const String& name) +{ + // alias name must not exist + if (m_nameToCanonicalName.find(name) != m_nameToCanonicalName.end()) { + return false; + } + + // add cell + m_map.insert(std::make_pair(name, Cell())); + + // add name + m_nameToCanonicalName.insert(std::make_pair(name, name)); + + return true; +} + +bool +Config::renameScreen(const String& oldName, + const String& newName) +{ + // get canonical name and find cell + String oldCanonical = getCanonicalName(oldName); + CellMap::iterator index = m_map.find(oldCanonical); + if (index == m_map.end()) { + return false; + } + + // accept if names are equal but replace with new name to maintain + // case. otherwise, the new name must not exist. + if (!CaselessCmp::equal(oldName, newName) && + m_nameToCanonicalName.find(newName) != m_nameToCanonicalName.end()) { + return false; + } + + // update cell + Cell tmpCell = index->second; + m_map.erase(index); + m_map.insert(std::make_pair(newName, tmpCell)); + + // update name + m_nameToCanonicalName.erase(oldCanonical); + m_nameToCanonicalName.insert(std::make_pair(newName, newName)); + + // update connections + Name oldNameObj(this, oldName); + for (index = m_map.begin(); index != m_map.end(); ++index) { + index->second.rename(oldNameObj, newName); + } + + // update alias targets + if (CaselessCmp::equal(oldName, oldCanonical)) { + for (NameMap::iterator iter = m_nameToCanonicalName.begin(); + iter != m_nameToCanonicalName.end(); ++iter) { + if (CaselessCmp::equal( + iter->second, oldCanonical)) { + iter->second = newName; + } + } + } + + return true; +} + +void +Config::removeScreen(const String& name) +{ + // get canonical name and find cell + String canonical = getCanonicalName(name); + CellMap::iterator index = m_map.find(canonical); + if (index == m_map.end()) { + return; + } + + // remove from map + m_map.erase(index); + + // disconnect + Name nameObj(this, name); + for (index = m_map.begin(); index != m_map.end(); ++index) { + index->second.remove(nameObj); + } + + // remove aliases (and canonical name) + for (NameMap::iterator iter = m_nameToCanonicalName.begin(); + iter != m_nameToCanonicalName.end(); ) { + if (iter->second == canonical) { + m_nameToCanonicalName.erase(iter++); + } + else { + ++index; + } + } +} + +void +Config::removeAllScreens() +{ + m_map.clear(); + m_nameToCanonicalName.clear(); +} + +bool +Config::addAlias(const String& canonical, const String& alias) +{ + // alias name must not exist + if (m_nameToCanonicalName.find(alias) != m_nameToCanonicalName.end()) { + return false; + } + + // canonical name must be known + if (m_map.find(canonical) == m_map.end()) { + return false; + } + + // insert alias + m_nameToCanonicalName.insert(std::make_pair(alias, canonical)); + + return true; +} + +bool +Config::removeAlias(const String& alias) +{ + // must not be a canonical name + if (m_map.find(alias) != m_map.end()) { + return false; + } + + // find alias + NameMap::iterator index = m_nameToCanonicalName.find(alias); + if (index == m_nameToCanonicalName.end()) { + return false; + } + + // remove alias + m_nameToCanonicalName.erase(index); + + return true; +} + +bool +Config::removeAliases(const String& canonical) +{ + // must be a canonical name + if (m_map.find(canonical) == m_map.end()) { + return false; + } + + // find and removing matching aliases + for (NameMap::iterator index = m_nameToCanonicalName.begin(); + index != m_nameToCanonicalName.end(); ) { + if (index->second == canonical && index->first != canonical) { + m_nameToCanonicalName.erase(index++); + } + else { + ++index; + } + } + + return true; +} + +void +Config::removeAllAliases() +{ + // remove all names + m_nameToCanonicalName.clear(); + + // put the canonical names back in + for (CellMap::iterator index = m_map.begin(); + index != m_map.end(); ++index) { + m_nameToCanonicalName.insert( + std::make_pair(index->first, index->first)); + } +} + +bool +Config::connect(const String& srcName, + EDirection srcSide, + float srcStart, float srcEnd, + const String& dstName, + float dstStart, float dstEnd) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CellMap::iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return false; + } + + // add link + CellEdge srcEdge(srcSide, Interval(srcStart, srcEnd)); + CellEdge dstEdge(dstName, srcSide, Interval(dstStart, dstEnd)); + return index->second.add(srcEdge, dstEdge); +} + +bool +Config::disconnect(const String& srcName, EDirection srcSide) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + return false; + } + + // disconnect side + index->second.remove(srcSide); + + return true; +} + +bool +Config::disconnect(const String& srcName, EDirection srcSide, float position) +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CellMap::iterator index = m_map.find(srcName); + if (index == m_map.end()) { + return false; + } + + // disconnect side + index->second.remove(srcSide, position); + + return true; +} + +void +Config::setBarrierAddress(const NetworkAddress& addr) +{ + m_barrierAddress = addr; +} + +bool +Config::addOption(const String& name, OptionID option, OptionValue value) +{ + // find options + ScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // add option + options->insert(std::make_pair(option, value)); + return true; +} + +bool +Config::removeOption(const String& name, OptionID option) +{ + // find options + ScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // remove option + options->erase(option); + return true; +} + +bool +Config::removeOptions(const String& name) +{ + // find options + ScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CellMap::iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + if (options == NULL) { + return false; + } + + // remove options + options->clear(); + return true; +} + +bool +Config::isValidScreenName(const String& name) const +{ + // name is valid if matches validname + // name ::= [_A-Za-z0-9] | [_A-Za-z0-9][-_A-Za-z0-9]*[_A-Za-z0-9] + // domain ::= . name + // validname ::= name domain* + // we also accept names ending in . because many OS X users have + // so misconfigured their systems. + + // empty name is invalid + if (name.empty()) { + return false; + } + + // check each dot separated part + String::size_type b = 0; + for (;;) { + // accept trailing . + if (b == name.size()) { + break; + } + + // find end of part + String::size_type e = name.find('.', b); + if (e == String::npos) { + e = name.size(); + } + + // part may not be empty + if (e - b < 1) { + return false; + } + + // check first and last characters + if (!(isalnum(name[b]) || name[b] == '_') || + !(isalnum(name[e - 1]) || name[e - 1] == '_')) { + return false; + } + + // check interior characters + for (String::size_type i = b; i < e; ++i) { + if (!isalnum(name[i]) && name[i] != '_' && name[i] != '-') { + return false; + } + } + + // next part + if (e == name.size()) { + // no more parts + break; + } + b = e + 1; + } + + return true; +} + +Config::const_iterator +Config::begin() const +{ + return const_iterator(m_map.begin()); +} + +Config::const_iterator +Config::end() const +{ + return const_iterator(m_map.end()); +} + +Config::all_const_iterator +Config::beginAll() const +{ + return m_nameToCanonicalName.begin(); +} + +Config::all_const_iterator +Config::endAll() const +{ + return m_nameToCanonicalName.end(); +} + +bool +Config::isScreen(const String& name) const +{ + return (m_nameToCanonicalName.count(name) > 0); +} + +bool +Config::isCanonicalName(const String& name) const +{ + return (!name.empty() && + CaselessCmp::equal(getCanonicalName(name), name)); +} + +String +Config::getCanonicalName(const String& name) const +{ + NameMap::const_iterator index = m_nameToCanonicalName.find(name); + if (index == m_nameToCanonicalName.end()) { + return String(); + } + else { + return index->second; + } +} + +String +Config::getNeighbor(const String& srcName, EDirection srcSide, + float position, float* positionOut) const +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return String(); + } + + // find edge + const CellEdge* srcEdge, *dstEdge; + if (!index->second.getLink(srcSide, position, srcEdge, dstEdge)) { + // no neighbor + return ""; + } + else { + // compute position on neighbor + if (positionOut != NULL) { + *positionOut = + dstEdge->inverseTransform(srcEdge->transform(position)); + } + + // return neighbor's name + return getCanonicalName(dstEdge->getName()); + } +} + +bool +Config::hasNeighbor(const String& srcName, EDirection srcSide) const +{ + return hasNeighbor(srcName, srcSide, 0.0f, 1.0f); +} + +bool +Config::hasNeighbor(const String& srcName, EDirection srcSide, + float start, float end) const +{ + assert(srcSide >= kFirstDirection && srcSide <= kLastDirection); + + // find source cell + CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + if (index == m_map.end()) { + return false; + } + + return index->second.overlaps(CellEdge(srcSide, Interval(start, end))); +} + +Config::link_const_iterator +Config::beginNeighbor(const String& srcName) const +{ + CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + assert(index != m_map.end()); + return index->second.begin(); +} + +Config::link_const_iterator +Config::endNeighbor(const String& srcName) const +{ + CellMap::const_iterator index = m_map.find(getCanonicalName(srcName)); + assert(index != m_map.end()); + return index->second.end(); +} + +const NetworkAddress& +Config::getBarrierAddress() const +{ + return m_barrierAddress; +} + +const Config::ScreenOptions* +Config::getOptions(const String& name) const +{ + // find options + const ScreenOptions* options = NULL; + if (name.empty()) { + options = &m_globalOptions; + } + else { + CellMap::const_iterator index = m_map.find(name); + if (index != m_map.end()) { + options = &index->second.m_options; + } + } + + // return options + return options; +} + +bool +Config::hasLockToScreenAction() const +{ + return m_hasLockToScreenAction; +} + +bool +Config::operator==(const Config& x) const +{ + if (m_barrierAddress != x.m_barrierAddress) { + return false; + } + if (m_map.size() != x.m_map.size()) { + return false; + } + if (m_nameToCanonicalName.size() != x.m_nameToCanonicalName.size()) { + return false; + } + + // compare global options + if (m_globalOptions != x.m_globalOptions) { + return false; + } + + for (CellMap::const_iterator index1 = m_map.begin(), + index2 = x.m_map.begin(); + index1 != m_map.end(); ++index1, ++index2) { + // compare names + if (!CaselessCmp::equal(index1->first, index2->first)) { + return false; + } + + // compare cells + if (index1->second != index2->second) { + return false; + } + } + + for (NameMap::const_iterator index1 = m_nameToCanonicalName.begin(), + index2 = x.m_nameToCanonicalName.begin(); + index1 != m_nameToCanonicalName.end(); + ++index1, ++index2) { + if (!CaselessCmp::equal(index1->first, index2->first) || + !CaselessCmp::equal(index1->second, index2->second)) { + return false; + } + } + + // compare input filters + if (m_inputFilter != x.m_inputFilter) { + return false; + } + + return true; +} + +bool +Config::operator!=(const Config& x) const +{ + return !operator==(x); +} + +void +Config::read(ConfigReadContext& context) +{ + Config tmp(m_events); + while (context.getStream()) { + tmp.readSection(context); + } + *this = tmp; +} + +const char* +Config::dirName(EDirection dir) +{ + static const char* s_name[] = { "left", "right", "up", "down" }; + + assert(dir >= kFirstDirection && dir <= kLastDirection); + + return s_name[dir - kFirstDirection]; +} + +InputFilter* +Config::getInputFilter() +{ + return &m_inputFilter; +} + +String +Config::formatInterval(const Interval& x) +{ + if (x.first == 0.0f && x.second == 1.0f) { + return ""; + } + return barrier::string::sprintf("(%d,%d)", (int)(x.first * 100.0f + 0.5f), + (int)(x.second * 100.0f + 0.5f)); +} + +void +Config::readSection(ConfigReadContext& s) +{ + static const char s_section[] = "section:"; + static const char s_options[] = "options"; + static const char s_screens[] = "screens"; + static const char s_links[] = "links"; + static const char s_aliases[] = "aliases"; + + String line; + if (!s.readLine(line)) { + // no more sections + return; + } + + // should be a section header + if (line.find(s_section) != 0) { + throw XConfigRead(s, "found data outside section"); + } + + // get section name + String::size_type i = line.find_first_not_of(" \t", sizeof(s_section) - 1); + if (i == String::npos) { + throw XConfigRead(s, "section name is missing"); + } + String name = line.substr(i); + i = name.find_first_of(" \t"); + if (i != String::npos) { + throw XConfigRead(s, "unexpected data after section name"); + } + + // read section + if (name == s_options) { + readSectionOptions(s); + } + else if (name == s_screens) { + readSectionScreens(s); + } + else if (name == s_links) { + readSectionLinks(s); + } + else if (name == s_aliases) { + readSectionAliases(s); + } + else { + throw XConfigRead(s, "unknown section name \"%{1}\"", name); + } +} + +void +Config::readSectionOptions(ConfigReadContext& s) +{ + String line; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // parse argument: `nameAndArgs = [values][;[values]]' + // nameAndArgs := <name>[(arg[,...])] + // values := valueAndArgs[,valueAndArgs]... + // valueAndArgs := <value>[(arg[,...])] + String::size_type i = 0; + String name, value; + ConfigReadContext::ArgList nameArgs, valueArgs; + s.parseNameWithArgs("name", line, "=", i, name, nameArgs); + ++i; + s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); + + bool handled = true; + if (name == "address") { + try { + m_barrierAddress = NetworkAddress(value, kDefaultPort); + m_barrierAddress.resolve(); + } + catch (XSocketAddress& e) { + throw XConfigRead(s, + String("invalid address argument ") + e.what()); + } + } + else if (name == "heartbeat") { + addOption("", kOptionHeartbeat, s.parseInt(value)); + } + else if (name == "switchCorners") { + addOption("", kOptionScreenSwitchCorners, s.parseCorners(value)); + } + else if (name == "switchCornerSize") { + addOption("", kOptionScreenSwitchCornerSize, s.parseInt(value)); + } + else if (name == "switchDelay") { + addOption("", kOptionScreenSwitchDelay, s.parseInt(value)); + } + else if (name == "switchDoubleTap") { + addOption("", kOptionScreenSwitchTwoTap, s.parseInt(value)); + } + else if (name == "switchNeedsShift") { + addOption("", kOptionScreenSwitchNeedsShift, s.parseBoolean(value)); + } + else if (name == "switchNeedsControl") { + addOption("", kOptionScreenSwitchNeedsControl, s.parseBoolean(value)); + } + else if (name == "switchNeedsAlt") { + addOption("", kOptionScreenSwitchNeedsAlt, s.parseBoolean(value)); + } + else if (name == "screenSaverSync") { + addOption("", kOptionScreenSaverSync, s.parseBoolean(value)); + } + else if (name == "relativeMouseMoves") { + addOption("", kOptionRelativeMouseMoves, s.parseBoolean(value)); + } + else if (name == "win32KeepForeground") { + addOption("", kOptionWin32KeepForeground, s.parseBoolean(value)); + } + else if (name == "clipboardSharing") { + addOption("", kOptionClipboardSharing, s.parseBoolean(value)); + } + + else { + handled = false; + } + + if (handled) { + // make sure handled options aren't followed by more values + if (i < line.size() && (line[i] == ',' || line[i] == ';')) { + throw XConfigRead(s, "to many arguments to %s", name.c_str()); + } + } + else { + // make filter rule + InputFilter::Rule rule(parseCondition(s, name, nameArgs)); + + // save first action (if any) + if (!value.empty() || line[i] != ';') { + parseAction(s, value, valueArgs, rule, true); + } + + // get remaining activate actions + while (i < line.length() && line[i] != ';') { + ++i; + s.parseNameWithArgs("value", line, ",;\n", i, value, valueArgs); + parseAction(s, value, valueArgs, rule, true); + } + + // get deactivate actions + if (i < line.length() && line[i] == ';') { + // allow trailing ';' + i = line.find_first_not_of(" \t", i + 1); + if (i == String::npos) { + i = line.length(); + } + else { + --i; + } + + // get actions + while (i < line.length()) { + ++i; + s.parseNameWithArgs("value", line, ",\n", + i, value, valueArgs); + parseAction(s, value, valueArgs, rule, false); + } + } + + // add rule + m_inputFilter.addFilterRule(rule); + } + } + throw XConfigRead(s, "unexpected end of options section"); +} + +void +Config::readSectionScreens(ConfigReadContext& s) +{ + String line; + String screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify validity of screen name + if (!isValidScreenName(screen)) { + throw XConfigRead(s, "invalid screen name \"%{1}\"", screen); + } + + // add the screen to the configuration + if (!addScreen(screen)) { + throw XConfigRead(s, "duplicate screen name \"%{1}\"", screen); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // parse argument: `<name>=<value>' + String::size_type i = line.find_first_of(" \t="); + if (i == 0) { + throw XConfigRead(s, "missing argument name"); + } + if (i == String::npos) { + throw XConfigRead(s, "missing ="); + } + String name = line.substr(0, i); + i = line.find_first_not_of(" \t", i); + if (i == String::npos || line[i] != '=') { + throw XConfigRead(s, "missing ="); + } + i = line.find_first_not_of(" \t", i + 1); + String value; + if (i != String::npos) { + value = line.substr(i); + } + + // handle argument + if (name == "halfDuplexCapsLock") { + addOption(screen, kOptionHalfDuplexCapsLock, + s.parseBoolean(value)); + } + else if (name == "halfDuplexNumLock") { + addOption(screen, kOptionHalfDuplexNumLock, + s.parseBoolean(value)); + } + else if (name == "halfDuplexScrollLock") { + addOption(screen, kOptionHalfDuplexScrollLock, + s.parseBoolean(value)); + } + else if (name == "shift") { + addOption(screen, kOptionModifierMapForShift, + s.parseModifierKey(value)); + } + else if (name == "ctrl") { + addOption(screen, kOptionModifierMapForControl, + s.parseModifierKey(value)); + } + else if (name == "alt") { + addOption(screen, kOptionModifierMapForAlt, + s.parseModifierKey(value)); + } + else if (name == "altgr") { + addOption(screen, kOptionModifierMapForAltGr, + s.parseModifierKey(value)); + } + else if (name == "meta") { + addOption(screen, kOptionModifierMapForMeta, + s.parseModifierKey(value)); + } + else if (name == "super") { + addOption(screen, kOptionModifierMapForSuper, + s.parseModifierKey(value)); + } + else if (name == "xtestIsXineramaUnaware") { + addOption(screen, kOptionXTestXineramaUnaware, + s.parseBoolean(value)); + } + else if (name == "switchCorners") { + addOption(screen, kOptionScreenSwitchCorners, + s.parseCorners(value)); + } + else if (name == "switchCornerSize") { + addOption(screen, kOptionScreenSwitchCornerSize, + s.parseInt(value)); + } + else if (name == "preserveFocus") { + addOption(screen, kOptionScreenPreserveFocus, + s.parseBoolean(value)); + } + else { + // unknown argument + throw XConfigRead(s, "unknown argument \"%{1}\"", name); + } + } + } + throw XConfigRead(s, "unexpected end of screens section"); +} + +void +Config::readSectionLinks(ConfigReadContext& s) +{ + String line; + String screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we know about the screen + if (!isScreen(screen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", screen); + } + if (!isCanonicalName(screen)) { + throw XConfigRead(s, "cannot use screen name alias here"); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // parse argument: `<name>[(<s0>,<e0>)]=<value>[(<s1>,<e1>)]' + // the stuff in brackets is optional. interval values must be + // in the range [0,100] and start < end. if not given the + // interval is taken to be (0,100). + String::size_type i = 0; + String side, dstScreen, srcArgString, dstArgString; + ConfigReadContext::ArgList srcArgs, dstArgs; + s.parseNameWithArgs("link", line, "=", i, side, srcArgs); + ++i; + s.parseNameWithArgs("screen", line, "", i, dstScreen, dstArgs); + Interval srcInterval(s.parseInterval(srcArgs)); + Interval dstInterval(s.parseInterval(dstArgs)); + + // handle argument + EDirection dir; + if (side == "left") { + dir = kLeft; + } + else if (side == "right") { + dir = kRight; + } + else if (side == "up") { + dir = kTop; + } + else if (side == "down") { + dir = kBottom; + } + else { + // unknown argument + throw XConfigRead(s, "unknown side \"%{1}\" in link", side); + } + if (!isScreen(dstScreen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", dstScreen); + } + if (!connect(screen, dir, + srcInterval.first, srcInterval.second, + dstScreen, + dstInterval.first, dstInterval.second)) { + throw XConfigRead(s, "overlapping range"); + } + } + } + throw XConfigRead(s, "unexpected end of links section"); +} + +void +Config::readSectionAliases(ConfigReadContext& s) +{ + String line; + String screen; + while (s.readLine(line)) { + // check for end of section + if (line == "end") { + return; + } + + // see if it's the next screen + if (line[line.size() - 1] == ':') { + // strip : + screen = line.substr(0, line.size() - 1); + + // verify we know about the screen + if (!isScreen(screen)) { + throw XConfigRead(s, "unknown screen name \"%{1}\"", screen); + } + if (!isCanonicalName(screen)) { + throw XConfigRead(s, "cannot use screen name alias here"); + } + } + else if (screen.empty()) { + throw XConfigRead(s, "argument before first screen"); + } + else { + // verify validity of screen name + if (!isValidScreenName(line)) { + throw XConfigRead(s, "invalid screen alias \"%{1}\"", line); + } + + // add alias + if (!addAlias(screen, line)) { + throw XConfigRead(s, "alias \"%{1}\" is already used", line); + } + } + } + throw XConfigRead(s, "unexpected end of aliases section"); +} + + +InputFilter::Condition* +Config::parseCondition(ConfigReadContext& s, + const String& name, const std::vector<String>& args) +{ + if (name == "keystroke") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: keystroke(modifiers+key)"); + } + + IPlatformScreen::KeyInfo* keyInfo = s.parseKeystroke(args[0]); + + return new InputFilter::KeystrokeCondition(m_events, keyInfo); + } + + if (name == "mousebutton") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: mousebutton(modifiers+button)"); + } + + IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]); + + return new InputFilter::MouseButtonCondition(m_events, mouseInfo); + } + + if (name == "connect") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for condition: connect([screen])"); + } + + String screen = args[0]; + if (isScreen(screen)) { + screen = getCanonicalName(screen); + } + else if (!screen.empty()) { + throw XConfigRead(s, "unknown screen name \"%{1}\" in connect", screen); + } + + return new InputFilter::ScreenConnectedCondition(m_events, screen); + } + + throw XConfigRead(s, "unknown argument \"%{1}\"", name); +} + +void +Config::parseAction(ConfigReadContext& s, + const String& name, const std::vector<String>& args, + InputFilter::Rule& rule, bool activate) +{ + InputFilter::Action* action; + + if (name == "keystroke" || name == "keyDown" || name == "keyUp") { + if (args.size() < 1 || args.size() > 2) { + throw XConfigRead(s, "syntax for action: keystroke(modifiers+key[,screens])"); + } + + IPlatformScreen::KeyInfo* keyInfo; + if (args.size() == 1) { + keyInfo = s.parseKeystroke(args[0]); + } + else { + std::set<String> screens; + parseScreens(s, args[1], screens); + keyInfo = s.parseKeystroke(args[0], screens); + } + + if (name == "keystroke") { + IPlatformScreen::KeyInfo* keyInfo2 = + IKeyState::KeyInfo::alloc(*keyInfo); + action = new InputFilter::KeystrokeAction(m_events, keyInfo2, true); + rule.adoptAction(action, true); + action = new InputFilter::KeystrokeAction(m_events, keyInfo, false); + activate = false; + } + else if (name == "keyDown") { + action = new InputFilter::KeystrokeAction(m_events, keyInfo, true); + } + else { + action = new InputFilter::KeystrokeAction(m_events, keyInfo, false); + } + } + + else if (name == "mousebutton" || + name == "mouseDown" || name == "mouseUp") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: mousebutton(modifiers+button)"); + } + + IPlatformScreen::ButtonInfo* mouseInfo = s.parseMouse(args[0]); + + if (name == "mousebutton") { + IPlatformScreen::ButtonInfo* mouseInfo2 = + IPlatformScreen::ButtonInfo::alloc(*mouseInfo); + action = new InputFilter::MouseButtonAction(m_events, mouseInfo2, true); + rule.adoptAction(action, true); + action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false); + activate = false; + } + else if (name == "mouseDown") { + action = new InputFilter::MouseButtonAction(m_events, mouseInfo, true); + } + else { + action = new InputFilter::MouseButtonAction(m_events, mouseInfo, false); + } + } + +/* XXX -- not supported + else if (name == "modifier") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: modifier(modifiers)"); + } + + KeyModifierMask mask = s.parseModifier(args[0]); + + action = new InputFilter::ModifierAction(mask, ~mask); + } +*/ + + else if (name == "switchToScreen") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: switchToScreen(name)"); + } + + String screen = args[0]; + if (isScreen(screen)) { + screen = getCanonicalName(screen); + } + else if (!screen.empty()) { + throw XConfigRead(s, "unknown screen name in switchToScreen"); + } + + action = new InputFilter::SwitchToScreenAction(m_events, screen); + } + + else if (name == "switchInDirection") { + if (args.size() != 1) { + throw XConfigRead(s, "syntax for action: switchInDirection(<left|right|up|down>)"); + } + + EDirection direction; + if (args[0] == "left") { + direction = kLeft; + } + else if (args[0] == "right") { + direction = kRight; + } + else if (args[0] == "up") { + direction = kTop; + } + else if (args[0] == "down") { + direction = kBottom; + } + else { + throw XConfigRead(s, "unknown direction \"%{1}\" in switchToScreen", args[0]); + } + + action = new InputFilter::SwitchInDirectionAction(m_events, direction); + } + + else if (name == "lockCursorToScreen") { + if (args.size() > 1) { + throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])"); + } + + InputFilter::LockCursorToScreenAction::Mode mode = + InputFilter::LockCursorToScreenAction::kToggle; + if (args.size() == 1) { + if (args[0] == "off") { + mode = InputFilter::LockCursorToScreenAction::kOff; + } + else if (args[0] == "on") { + mode = InputFilter::LockCursorToScreenAction::kOn; + } + else if (args[0] == "toggle") { + mode = InputFilter::LockCursorToScreenAction::kToggle; + } + else { + throw XConfigRead(s, "syntax for action: lockCursorToScreen([{off|on|toggle}])"); + } + } + + if (mode != InputFilter::LockCursorToScreenAction::kOff) { + m_hasLockToScreenAction = true; + } + + action = new InputFilter::LockCursorToScreenAction(m_events, mode); + } + + else if (name == "keyboardBroadcast") { + if (args.size() > 2) { + throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])"); + } + + InputFilter::KeyboardBroadcastAction::Mode mode = + InputFilter::KeyboardBroadcastAction::kToggle; + if (args.size() >= 1) { + if (args[0] == "off") { + mode = InputFilter::KeyboardBroadcastAction::kOff; + } + else if (args[0] == "on") { + mode = InputFilter::KeyboardBroadcastAction::kOn; + } + else if (args[0] == "toggle") { + mode = InputFilter::KeyboardBroadcastAction::kToggle; + } + else { + throw XConfigRead(s, "syntax for action: keyboardBroadcast([{off|on|toggle}[,screens]])"); + } + } + + std::set<String> screens; + if (args.size() >= 2) { + parseScreens(s, args[1], screens); + } + + action = new InputFilter::KeyboardBroadcastAction(m_events, mode, screens); + } + + else { + throw XConfigRead(s, "unknown action argument \"%{1}\"", name); + } + + rule.adoptAction(action, activate); +} + +void +Config::parseScreens(ConfigReadContext& c, + const String& s, std::set<String>& screens) const +{ + screens.clear(); + + String::size_type i = 0; + while (i < s.size()) { + // find end of next screen name + String::size_type j = s.find(':', i); + if (j == String::npos) { + j = s.size(); + } + + // extract name + String rawName; + i = s.find_first_not_of(" \t", i); + if (i < j) { + rawName = s.substr(i, s.find_last_not_of(" \t", j - 1) - i + 1); + } + + // add name + if (rawName == "*") { + screens.insert("*"); + } + else if (!rawName.empty()) { + String name = getCanonicalName(rawName); + if (name.empty()) { + throw XConfigRead(c, "unknown screen name \"%{1}\"", rawName); + } + screens.insert(name); + } + + // next + i = j + 1; + } +} + +const char* +Config::getOptionName(OptionID id) +{ + if (id == kOptionHalfDuplexCapsLock) { + return "halfDuplexCapsLock"; + } + if (id == kOptionHalfDuplexNumLock) { + return "halfDuplexNumLock"; + } + if (id == kOptionHalfDuplexScrollLock) { + return "halfDuplexScrollLock"; + } + if (id == kOptionModifierMapForShift) { + return "shift"; + } + if (id == kOptionModifierMapForControl) { + return "ctrl"; + } + if (id == kOptionModifierMapForAlt) { + return "alt"; + } + if (id == kOptionModifierMapForAltGr) { + return "altgr"; + } + if (id == kOptionModifierMapForMeta) { + return "meta"; + } + if (id == kOptionModifierMapForSuper) { + return "super"; + } + if (id == kOptionHeartbeat) { + return "heartbeat"; + } + if (id == kOptionScreenSwitchCorners) { + return "switchCorners"; + } + if (id == kOptionScreenSwitchCornerSize) { + return "switchCornerSize"; + } + if (id == kOptionScreenSwitchDelay) { + return "switchDelay"; + } + if (id == kOptionScreenSwitchTwoTap) { + return "switchDoubleTap"; + } + if (id == kOptionScreenSwitchNeedsShift) { + return "switchNeedsShift"; + } + if (id == kOptionScreenSwitchNeedsControl) { + return "switchNeedsControl"; + } + if (id == kOptionScreenSwitchNeedsAlt) { + return "switchNeedsAlt"; + } + if (id == kOptionScreenSaverSync) { + return "screenSaverSync"; + } + if (id == kOptionXTestXineramaUnaware) { + return "xtestIsXineramaUnaware"; + } + if (id == kOptionRelativeMouseMoves) { + return "relativeMouseMoves"; + } + if (id == kOptionWin32KeepForeground) { + return "win32KeepForeground"; + } + if (id == kOptionScreenPreserveFocus) { + return "preserveFocus"; + } + if (id == kOptionClipboardSharing) { + return "clipboardSharing"; + } + return NULL; +} + +String +Config::getOptionValue(OptionID id, OptionValue value) +{ + if (id == kOptionHalfDuplexCapsLock || + id == kOptionHalfDuplexNumLock || + id == kOptionHalfDuplexScrollLock || + id == kOptionScreenSwitchNeedsShift || + id == kOptionScreenSwitchNeedsControl || + id == kOptionScreenSwitchNeedsAlt || + id == kOptionScreenSaverSync || + id == kOptionXTestXineramaUnaware || + id == kOptionRelativeMouseMoves || + id == kOptionWin32KeepForeground || + id == kOptionScreenPreserveFocus || + id == kOptionClipboardSharing) { + return (value != 0) ? "true" : "false"; + } + if (id == kOptionModifierMapForShift || + id == kOptionModifierMapForControl || + id == kOptionModifierMapForAlt || + id == kOptionModifierMapForAltGr || + id == kOptionModifierMapForMeta || + id == kOptionModifierMapForSuper) { + switch (value) { + case kKeyModifierIDShift: + return "shift"; + + case kKeyModifierIDControl: + return "ctrl"; + + case kKeyModifierIDAlt: + return "alt"; + + case kKeyModifierIDAltGr: + return "altgr"; + + case kKeyModifierIDMeta: + return "meta"; + + case kKeyModifierIDSuper: + return "super"; + + default: + return "none"; + } + } + if (id == kOptionHeartbeat || + id == kOptionScreenSwitchCornerSize || + id == kOptionScreenSwitchDelay || + id == kOptionScreenSwitchTwoTap) { + return barrier::string::sprintf("%d", value); + } + if (id == kOptionScreenSwitchCorners) { + std::string result("none"); + if ((value & kTopLeftMask) != 0) { + result += " +top-left"; + } + if ((value & kTopRightMask) != 0) { + result += " +top-right"; + } + if ((value & kBottomLeftMask) != 0) { + result += " +bottom-left"; + } + if ((value & kBottomRightMask) != 0) { + result += " +bottom-right"; + } + return result; + } + + return ""; +} + + +// +// Config::Name +// + +Config::Name::Name(Config* config, const String& name) : + m_config(config), + m_name(config->getCanonicalName(name)) +{ + // do nothing +} + +bool +Config::Name::operator==(const String& name) const +{ + String canonical = m_config->getCanonicalName(name); + return CaselessCmp::equal(canonical, m_name); +} + + +// +// Config::CellEdge +// + +Config::CellEdge::CellEdge(EDirection side, float position) +{ + init("", side, Interval(position, position)); +} + +Config::CellEdge::CellEdge(EDirection side, const Interval& interval) +{ + assert(interval.first >= 0.0f); + assert(interval.second <= 1.0f); + assert(interval.first < interval.second); + + init("", side, interval); +} + +Config::CellEdge::CellEdge(const String& name, + EDirection side, const Interval& interval) +{ + assert(interval.first >= 0.0f); + assert(interval.second <= 1.0f); + assert(interval.first < interval.second); + + init(name, side, interval); +} + +Config::CellEdge::~CellEdge() +{ + // do nothing +} + +void +Config::CellEdge::init(const String& name, EDirection side, + const Interval& interval) +{ + assert(side != kNoDirection); + + m_name = name; + m_side = side; + m_interval = interval; +} + +Config::Interval +Config::CellEdge::getInterval() const +{ + return m_interval; +} + +void +Config::CellEdge::setName(const String& newName) +{ + m_name = newName; +} + +String +Config::CellEdge::getName() const +{ + return m_name; +} + +EDirection +Config::CellEdge::getSide() const +{ + return m_side; +} + +bool +Config::CellEdge::overlaps(const CellEdge& edge) const +{ + const Interval& x = m_interval; + const Interval& y = edge.m_interval; + if (m_side != edge.m_side) { + return false; + } + return (x.first >= y.first && x.first < y.second) || + (x.second > y.first && x.second <= y.second) || + (y.first >= x.first && y.first < x.second) || + (y.second > x.first && y.second <= x.second); +} + +bool +Config::CellEdge::isInside(float x) const +{ + return (x >= m_interval.first && x < m_interval.second); +} + +float +Config::CellEdge::transform(float x) const +{ + return (x - m_interval.first) / (m_interval.second - m_interval.first); +} + + +float +Config::CellEdge::inverseTransform(float x) const +{ + return x * (m_interval.second - m_interval.first) + m_interval.first; +} + +bool +Config::CellEdge::operator<(const CellEdge& o) const +{ + if (static_cast<int>(m_side) < static_cast<int>(o.m_side)) { + return true; + } + else if (static_cast<int>(m_side) > static_cast<int>(o.m_side)) { + return false; + } + + return (m_interval.first < o.m_interval.first); +} + +bool +Config::CellEdge::operator==(const CellEdge& x) const +{ + return (m_side == x.m_side && m_interval == x.m_interval); +} + +bool +Config::CellEdge::operator!=(const CellEdge& x) const +{ + return !operator==(x); +} + + +// +// Config::Cell +// + +bool +Config::Cell::add(const CellEdge& src, const CellEdge& dst) +{ + // cannot add an edge that overlaps other existing edges but we + // can exactly replace an edge. + if (!hasEdge(src) && overlaps(src)) { + return false; + } + + m_neighbors.erase(src); + m_neighbors.insert(std::make_pair(src, dst)); + return true; +} + +void +Config::Cell::remove(EDirection side) +{ + for (EdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ) { + if (j->first.getSide() == side) { + m_neighbors.erase(j++); + } + else { + ++j; + } + } +} + +void +Config::Cell::remove(EDirection side, float position) +{ + for (EdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ++j) { + if (j->first.getSide() == side && j->first.isInside(position)) { + m_neighbors.erase(j); + break; + } + } +} +void +Config::Cell::remove(const Name& name) +{ + for (EdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ) { + if (name == j->second.getName()) { + m_neighbors.erase(j++); + } + else { + ++j; + } + } +} + +void +Config::Cell::rename(const Name& oldName, const String& newName) +{ + for (EdgeLinks::iterator j = m_neighbors.begin(); + j != m_neighbors.end(); ++j) { + if (oldName == j->second.getName()) { + j->second.setName(newName); + } + } +} + +bool +Config::Cell::hasEdge(const CellEdge& edge) const +{ + EdgeLinks::const_iterator i = m_neighbors.find(edge); + return (i != m_neighbors.end() && i->first == edge); +} + +bool +Config::Cell::overlaps(const CellEdge& edge) const +{ + EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); + if (i != m_neighbors.end() && i->first.overlaps(edge)) { + return true; + } + if (i != m_neighbors.begin() && (--i)->first.overlaps(edge)) { + return true; + } + return false; +} + +bool +Config::Cell::getLink(EDirection side, float position, + const CellEdge*& src, const CellEdge*& dst) const +{ + CellEdge edge(side, position); + EdgeLinks::const_iterator i = m_neighbors.upper_bound(edge); + if (i == m_neighbors.begin()) { + return false; + } + --i; + if (i->first.getSide() == side && i->first.isInside(position)) { + src = &i->first; + dst = &i->second; + return true; + } + return false; +} + +bool +Config::Cell::operator==(const Cell& x) const +{ + // compare options + if (m_options != x.m_options) { + return false; + } + + // compare links + if (m_neighbors.size() != x.m_neighbors.size()) { + return false; + } + for (EdgeLinks::const_iterator index1 = m_neighbors.begin(), + index2 = x.m_neighbors.begin(); + index1 != m_neighbors.end(); + ++index1, ++index2) { + if (index1->first != index2->first) { + return false; + } + if (index1->second != index2->second) { + return false; + } + + // operator== doesn't compare names. only compare destination + // names. + if (!CaselessCmp::equal(index1->second.getName(), + index2->second.getName())) { + return false; + } + } + return true; +} + +bool +Config::Cell::operator!=(const Cell& x) const +{ + return !operator==(x); +} + +Config::Cell::const_iterator +Config::Cell::begin() const +{ + return m_neighbors.begin(); +} + +Config::Cell::const_iterator +Config::Cell::end() const +{ + return m_neighbors.end(); +} + + +// +// Config I/O +// + +std::istream& +operator>>(std::istream& s, Config& config) +{ + ConfigReadContext context(s); + config.read(context); + return s; +} + +std::ostream& +operator<<(std::ostream& s, const Config& config) +{ + // screens section + s << "section: screens" << std::endl; + for (Config::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << std::endl; + const Config::ScreenOptions* options = config.getOptions(*screen); + if (options != NULL && options->size() > 0) { + for (Config::ScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = Config::getOptionName(option->first); + String value = Config::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t\t" << name << " = " << value << std::endl; + } + } + } + } + s << "end" << std::endl; + + // links section + String neighbor; + s << "section: links" << std::endl; + for (Config::const_iterator screen = config.begin(); + screen != config.end(); ++screen) { + s << "\t" << screen->c_str() << ":" << std::endl; + + for (Config::link_const_iterator + link = config.beginNeighbor(*screen), + nend = config.endNeighbor(*screen); link != nend; ++link) { + s << "\t\t" << Config::dirName(link->first.getSide()) << + Config::formatInterval(link->first.getInterval()) << + " = " << link->second.getName().c_str() << + Config::formatInterval(link->second.getInterval()) << + std::endl; + } + } + s << "end" << std::endl; + + // aliases section (if there are any) + if (config.m_map.size() != config.m_nameToCanonicalName.size()) { + // map canonical to alias + typedef std::multimap<String, String, + CaselessCmp> CMNameMap; + CMNameMap aliases; + for (Config::NameMap::const_iterator + index = config.m_nameToCanonicalName.begin(); + index != config.m_nameToCanonicalName.end(); + ++index) { + if (index->first != index->second) { + aliases.insert(std::make_pair(index->second, index->first)); + } + } + + // dump it + String screen; + s << "section: aliases" << std::endl; + for (CMNameMap::const_iterator index = aliases.begin(); + index != aliases.end(); ++index) { + if (index->first != screen) { + screen = index->first; + s << "\t" << screen.c_str() << ":" << std::endl; + } + s << "\t\t" << index->second.c_str() << std::endl; + } + s << "end" << std::endl; + } + + // options section + s << "section: options" << std::endl; + const Config::ScreenOptions* options = config.getOptions(""); + if (options != NULL && options->size() > 0) { + for (Config::ScreenOptions::const_iterator + option = options->begin(); + option != options->end(); ++option) { + const char* name = Config::getOptionName(option->first); + String value = Config::getOptionValue(option->first, + option->second); + if (name != NULL && !value.empty()) { + s << "\t" << name << " = " << value << std::endl; + } + } + } + if (config.m_barrierAddress.isValid()) { + s << "\taddress = " << + config.m_barrierAddress.getHostname().c_str() << std::endl; + } + s << config.m_inputFilter.format("\t"); + s << "end" << std::endl; + + return s; +} + + +// +// ConfigReadContext +// + +ConfigReadContext::ConfigReadContext(std::istream& s, SInt32 firstLine) : + m_stream(s), + m_line(firstLine - 1) +{ + // do nothing +} + +ConfigReadContext::~ConfigReadContext() +{ + // do nothing +} + +bool +ConfigReadContext::readLine(String& line) +{ + ++m_line; + while (std::getline(m_stream, line)) { + // strip leading whitespace + String::size_type i = line.find_first_not_of(" \t"); + if (i != String::npos) { + line.erase(0, i); + } + + // strip comments and then trailing whitespace + i = line.find('#'); + if (i != String::npos) { + line.erase(i); + } + i = line.find_last_not_of(" \r\t"); + if (i != String::npos) { + line.erase(i + 1); + } + + // return non empty line + if (!line.empty()) { + // make sure there are no invalid characters + for (i = 0; i < line.length(); ++i) { + if (!isgraph(line[i]) && line[i] != ' ' && line[i] != '\t') { + throw XConfigRead(*this, + "invalid character %{1}", + barrier::string::sprintf("%#2x", line[i])); + } + } + + return true; + } + + // next line + ++m_line; + } + return false; +} + +UInt32 +ConfigReadContext::getLineNumber() const +{ + return m_line; +} + +bool +ConfigReadContext::operator!() const +{ + return !m_stream; +} + +OptionValue +ConfigReadContext::parseBoolean(const String& arg) const +{ + if (CaselessCmp::equal(arg, "true")) { + return static_cast<OptionValue>(true); + } + if (CaselessCmp::equal(arg, "false")) { + return static_cast<OptionValue>(false); + } + throw XConfigRead(*this, "invalid boolean argument \"%{1}\"", arg); +} + +OptionValue +ConfigReadContext::parseInt(const String& arg) const +{ + const char* s = arg.c_str(); + char* end; + long tmp = strtol(s, &end, 10); + if (*end != '\0') { + // invalid characters + throw XConfigRead(*this, "invalid integer argument \"%{1}\"", arg); + } + OptionValue value = static_cast<OptionValue>(tmp); + if (value != tmp) { + // out of range + throw XConfigRead(*this, "integer argument \"%{1}\" out of range", arg); + } + return value; +} + +OptionValue +ConfigReadContext::parseModifierKey(const String& arg) const +{ + if (CaselessCmp::equal(arg, "shift")) { + return static_cast<OptionValue>(kKeyModifierIDShift); + } + if (CaselessCmp::equal(arg, "ctrl")) { + return static_cast<OptionValue>(kKeyModifierIDControl); + } + if (CaselessCmp::equal(arg, "alt")) { + return static_cast<OptionValue>(kKeyModifierIDAlt); + } + if (CaselessCmp::equal(arg, "altgr")) { + return static_cast<OptionValue>(kKeyModifierIDAltGr); + } + if (CaselessCmp::equal(arg, "meta")) { + return static_cast<OptionValue>(kKeyModifierIDMeta); + } + if (CaselessCmp::equal(arg, "super")) { + return static_cast<OptionValue>(kKeyModifierIDSuper); + } + if (CaselessCmp::equal(arg, "none")) { + return static_cast<OptionValue>(kKeyModifierIDNull); + } + throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); +} + +OptionValue +ConfigReadContext::parseCorner(const String& arg) const +{ + if (CaselessCmp::equal(arg, "left")) { + return kTopLeftMask | kBottomLeftMask; + } + else if (CaselessCmp::equal(arg, "right")) { + return kTopRightMask | kBottomRightMask; + } + else if (CaselessCmp::equal(arg, "top")) { + return kTopLeftMask | kTopRightMask; + } + else if (CaselessCmp::equal(arg, "bottom")) { + return kBottomLeftMask | kBottomRightMask; + } + else if (CaselessCmp::equal(arg, "top-left")) { + return kTopLeftMask; + } + else if (CaselessCmp::equal(arg, "top-right")) { + return kTopRightMask; + } + else if (CaselessCmp::equal(arg, "bottom-left")) { + return kBottomLeftMask; + } + else if (CaselessCmp::equal(arg, "bottom-right")) { + return kBottomRightMask; + } + else if (CaselessCmp::equal(arg, "none")) { + return kNoCornerMask; + } + else if (CaselessCmp::equal(arg, "all")) { + return kAllCornersMask; + } + throw XConfigRead(*this, "invalid argument \"%{1}\"", arg); +} + +OptionValue +ConfigReadContext::parseCorners(const String& args) const +{ + // find first token + String::size_type i = args.find_first_not_of(" \t", 0); + if (i == String::npos) { + throw XConfigRead(*this, "missing corner argument"); + } + String::size_type j = args.find_first_of(" \t", i); + + // parse first corner token + OptionValue corners = parseCorner(args.substr(i, j - i)); + + // get +/- + i = args.find_first_not_of(" \t", j); + while (i != String::npos) { + // parse +/- + bool add; + if (args[i] == '-') { + add = false; + } + else if (args[i] == '+') { + add = true; + } + else { + throw XConfigRead(*this, + "invalid corner operator \"%{1}\"", + String(args.c_str() + i, 1)); + } + + // get next corner token + i = args.find_first_not_of(" \t", i + 1); + j = args.find_first_of(" \t", i); + if (i == String::npos) { + throw XConfigRead(*this, "missing corner argument"); + } + + // parse next corner token + if (add) { + corners |= parseCorner(args.substr(i, j - i)); + } + else { + corners &= ~parseCorner(args.substr(i, j - i)); + } + i = args.find_first_not_of(" \t", j); + } + + return corners; +} + +Config::Interval +ConfigReadContext::parseInterval(const ArgList& args) const +{ + if (args.size() == 0) { + return Config::Interval(0.0f, 1.0f); + } + if (args.size() != 2 || args[0].empty() || args[1].empty()) { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + char* end; + double startValue = strtod(args[0].c_str(), &end); + if (end[0] != '\0') { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + double endValue = strtod(args[1].c_str(), &end); + if (end[0] != '\0') { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + if (startValue < 0 || startValue > 100 || + endValue < 0 || endValue > 100 || + startValue >= endValue) { + throw XConfigRead(*this, "invalid interval \"%{1}\"", concatArgs(args)); + } + + return Config::Interval(startValue / 100.0f, endValue / 100.0f); +} + +void +ConfigReadContext::parseNameWithArgs( + const String& type, const String& line, + const String& delim, String::size_type& index, + String& name, ArgList& args) const +{ + // skip leading whitespace + String::size_type i = line.find_first_not_of(" \t", index); + if (i == String::npos) { + throw XConfigRead(*this, String("missing ") + type); + } + + // find end of name + String::size_type j = line.find_first_of(" \t(" + delim, i); + if (j == String::npos) { + j = line.length(); + } + + // save name + name = line.substr(i, j - i); + args.clear(); + + // is it okay to not find a delimiter? + bool needDelim = (!delim.empty() && delim.find('\n') == String::npos); + + // skip whitespace + i = line.find_first_not_of(" \t", j); + if (i == String::npos && needDelim) { + // expected delimiter but didn't find it + throw XConfigRead(*this, String("missing ") + delim[0]); + } + if (i == String::npos) { + // no arguments + index = line.length(); + return; + } + if (line[i] != '(') { + // no arguments + index = i; + return; + } + + // eat '(' + ++i; + + // parse arguments + j = line.find_first_of(",)", i); + while (j != String::npos) { + // extract arg + String arg(line.substr(i, j - i)); + i = j; + + // trim whitespace + j = arg.find_first_not_of(" \t"); + if (j != String::npos) { + arg.erase(0, j); + } + j = arg.find_last_not_of(" \t"); + if (j != String::npos) { + arg.erase(j + 1); + } + + // save arg + args.push_back(arg); + + // exit loop at end of arguments + if (line[i] == ')') { + break; + } + + // eat ',' + ++i; + + // next + j = line.find_first_of(",)", i); + } + + // verify ')' + if (j == String::npos) { + // expected ) + throw XConfigRead(*this, "missing )"); + } + + // eat ')' + ++i; + + // skip whitespace + j = line.find_first_not_of(" \t", i); + if (j == String::npos && needDelim) { + // expected delimiter but didn't find it + throw XConfigRead(*this, String("missing ") + delim[0]); + } + + // verify delimiter + if (needDelim && delim.find(line[j]) == String::npos) { + throw XConfigRead(*this, String("expected ") + delim[0]); + } + + if (j == String::npos) { + j = line.length(); + } + + index = j; + return; +} + +IPlatformScreen::KeyInfo* +ConfigReadContext::parseKeystroke(const String& keystroke) const +{ + return parseKeystroke(keystroke, std::set<String>()); +} + +IPlatformScreen::KeyInfo* +ConfigReadContext::parseKeystroke(const String& keystroke, + const std::set<String>& screens) const +{ + String s = keystroke; + + KeyModifierMask mask; + if (!barrier::KeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse key modifiers"); + } + + KeyID key; + if (!barrier::KeyMap::parseKey(s, key)) { + throw XConfigRead(*this, "unable to parse key"); + } + + if (key == kKeyNone && mask == 0) { + throw XConfigRead(*this, "missing key and/or modifiers in keystroke"); + } + + return IPlatformScreen::KeyInfo::alloc(key, mask, 0, 0, screens); +} + +IPlatformScreen::ButtonInfo* +ConfigReadContext::parseMouse(const String& mouse) const +{ + String s = mouse; + + KeyModifierMask mask; + if (!barrier::KeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse button modifiers"); + } + + char* end; + ButtonID button = (ButtonID)strtol(s.c_str(), &end, 10); + if (*end != '\0') { + throw XConfigRead(*this, "unable to parse button"); + } + if (s.empty() || button <= 0) { + throw XConfigRead(*this, "invalid button"); + } + + return IPlatformScreen::ButtonInfo::alloc(button, mask); +} + +KeyModifierMask +ConfigReadContext::parseModifier(const String& modifiers) const +{ + String s = modifiers; + + KeyModifierMask mask; + if (!barrier::KeyMap::parseModifiers(s, mask)) { + throw XConfigRead(*this, "unable to parse modifiers"); + } + + if (mask == 0) { + throw XConfigRead(*this, "no modifiers specified"); + } + + return mask; +} + +String +ConfigReadContext::concatArgs(const ArgList& args) +{ + String s("("); + for (size_t i = 0; i < args.size(); ++i) { + if (i != 0) { + s += ","; + } + s += args[i]; + } + s += ")"; + return s; +} + + +// +// Config I/O exceptions +// + +XConfigRead::XConfigRead(const ConfigReadContext& context, + const String& error) : + m_error(barrier::string::sprintf("line %d: %s", + context.getLineNumber(), error.c_str())) +{ + // do nothing +} + +XConfigRead::XConfigRead(const ConfigReadContext& context, + const char* errorFmt, const String& arg) : + m_error(barrier::string::sprintf("line %d: ", context.getLineNumber()) + + barrier::string::format(errorFmt, arg.c_str())) +{ + // do nothing +} + +XConfigRead::~XConfigRead() _NOEXCEPT +{ + // do nothing +} + +String +XConfigRead::getWhat() const throw() +{ + return format("XConfigRead", "read error: %{1}", m_error.c_str()); +} diff --git a/src/lib/server/Config.h b/src/lib/server/Config.h new file mode 100644 index 0000000..69b01c4 --- /dev/null +++ b/src/lib/server/Config.h @@ -0,0 +1,549 @@ +/* + * 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 "server/InputFilter.h" +#include "barrier/option_types.h" +#include "barrier/protocol_types.h" +#include "barrier/IPlatformScreen.h" +#include "net/NetworkAddress.h" +#include "base/String.h" +#include "base/XBase.h" +#include "common/stdmap.h" +#include "common/stdset.h" + +#include <iosfwd> + +class Config; +class ConfigReadContext; +class IEventQueue; + +namespace std { +template <> +struct iterator_traits<Config> { + typedef String value_type; + typedef ptrdiff_t difference_type; + typedef bidirectional_iterator_tag iterator_category; + typedef String* pointer; + typedef String& reference; +}; +}; + +//! Server configuration +/*! +This class holds server configuration information. That includes +the names of screens and their aliases, the links between them, +and network addresses. + +Note that case is preserved in screen names but is ignored when +comparing names. Screen names and their aliases share a +namespace and must be unique. +*/ +class Config { +public: + typedef std::map<OptionID, OptionValue> ScreenOptions; + typedef std::pair<float, float> Interval; + + class CellEdge { + public: + CellEdge(EDirection side, float position); + CellEdge(EDirection side, const Interval&); + CellEdge(const String& name, EDirection side, const Interval&); + ~CellEdge(); + + Interval getInterval() const; + void setName(const String& newName); + String getName() const; + EDirection getSide() const; + bool overlaps(const CellEdge&) const; + bool isInside(float x) const; + + // transform position to [0,1] + float transform(float x) const; + + // transform [0,1] to position + float inverseTransform(float x) const; + + // compares side and start of interval + bool operator<(const CellEdge&) const; + + // compares side and interval + bool operator==(const CellEdge&) const; + bool operator!=(const CellEdge&) const; + + private: + void init(const String& name, EDirection side, + const Interval&); + + private: + String m_name; + EDirection m_side; + Interval m_interval; + }; + +private: + class Name { + public: + Name(Config*, const String& name); + + bool operator==(const String& name) const; + + private: + Config* m_config; + String m_name; + }; + + class Cell { + private: + typedef std::map<CellEdge, CellEdge> EdgeLinks; + + public: + typedef EdgeLinks::const_iterator const_iterator; + + bool add(const CellEdge& src, const CellEdge& dst); + void remove(EDirection side); + void remove(EDirection side, float position); + void remove(const Name& destinationName); + void rename(const Name& oldName, const String& newName); + + bool hasEdge(const CellEdge&) const; + bool overlaps(const CellEdge&) const; + + bool getLink(EDirection side, float position, + const CellEdge*& src, const CellEdge*& dst) const; + + bool operator==(const Cell&) const; + bool operator!=(const Cell&) const; + + const_iterator begin() const; + const_iterator end() const; + + private: + EdgeLinks m_neighbors; + + public: + ScreenOptions m_options; + }; + typedef std::map<String, Cell, barrier::string::CaselessCmp> CellMap; + typedef std::map<String, String, barrier::string::CaselessCmp> NameMap; + +public: + typedef Cell::const_iterator link_const_iterator; + typedef CellMap::const_iterator internal_const_iterator; + typedef NameMap::const_iterator all_const_iterator; + class const_iterator : std::iterator_traits<Config> { + public: + explicit const_iterator() : m_i() { } + explicit const_iterator(const internal_const_iterator& i) : m_i(i) { } + + const_iterator& operator=(const const_iterator& i) { + m_i = i.m_i; + return *this; + } + String operator*() { return m_i->first; } + const String* operator->() { return &(m_i->first); } + const_iterator& operator++() { ++m_i; return *this; } + const_iterator operator++(int) { return const_iterator(m_i++); } + const_iterator& operator--() { --m_i; return *this; } + const_iterator operator--(int) { return const_iterator(m_i--); } + bool operator==(const const_iterator& i) const { + return (m_i == i.m_i); + } + bool operator!=(const const_iterator& i) const { + return (m_i != i.m_i); + } + + private: + internal_const_iterator m_i; + }; + + Config(IEventQueue* events); + virtual ~Config(); + +#ifdef TEST_ENV + Config() : m_inputFilter(NULL) { } +#endif + + //! @name manipulators + //@{ + + //! Add screen + /*! + Adds a screen, returning true iff successful. If a screen or + alias with the given name exists then it fails. + */ + bool addScreen(const String& name); + + //! Rename screen + /*! + Renames a screen. All references to the name are updated. + Returns true iff successful. + */ + bool renameScreen(const String& oldName, + const String& newName); + + //! Remove screen + /*! + Removes a screen. This also removes aliases for the screen and + disconnects any connections to the screen. \c name may be an + alias. + */ + void removeScreen(const String& name); + + //! Remove all screens + /*! + Removes all screens, aliases, and connections. + */ + void removeAllScreens(); + + //! Add alias + /*! + Adds an alias for a screen name. An alias can be used + any place the canonical screen name can (except addScreen()). + Returns false if the alias name already exists or the canonical + name is unknown, otherwise returns true. + */ + bool addAlias(const String& canonical, + const String& alias); + + //! Remove alias + /*! + Removes an alias for a screen name. It returns false if the + alias is unknown or a canonical name, otherwise returns true. + */ + bool removeAlias(const String& alias); + + //! Remove aliases + /*! + Removes all aliases for a canonical screen name. It returns false + if the canonical name is unknown, otherwise returns true. + */ + bool removeAliases(const String& canonical); + + //! Remove all aliases + /*! + This removes all aliases but not the screens. + */ + void removeAllAliases(); + + //! Connect screens + /*! + Establishes a one-way connection between portions of opposite edges + of two screens. Each portion is described by an interval defined + by two numbers, the start and end of the interval half-open on the + end. The numbers range from 0 to 1, inclusive, for the left/top + to the right/bottom. The user will be able to jump from the + \c srcStart to \c srcSend interval of \c srcSide of screen + \c srcName to the opposite side of screen \c dstName in the interval + \c dstStart and \c dstEnd when both screens are connected to the + server and the user isn't locked to a screen. Returns false if + \c srcName is unknown. \c srcStart must be less than or equal to + \c srcEnd and \c dstStart must be less then or equal to \c dstEnd + and all of \c srcStart, \c srcEnd, \c dstStart, or \c dstEnd must + be inside the range [0,1]. + */ + bool connect(const String& srcName, + EDirection srcSide, + float srcStart, float srcEnd, + const String& dstName, + float dstStart, float dstEnd); + + //! Disconnect screens + /*! + Removes all connections created by connect() on side \c srcSide. + Returns false if \c srcName is unknown. + */ + bool disconnect(const String& srcName, + EDirection srcSide); + + //! Disconnect screens + /*! + Removes the connections created by connect() on side \c srcSide + covering position \c position. Returns false if \c srcName is + unknown. + */ + bool disconnect(const String& srcName, + EDirection srcSide, float position); + + //! Set server address + /*! + Set the barrier listen addresses. There is no default address so + this must be called to run a server using this configuration. + */ + void setBarrierAddress(const NetworkAddress&); + + //! Add a screen option + /*! + Adds an option and its value to the named screen. Replaces the + existing option's value if there is one. Returns true iff \c name + is a known screen. + */ + bool addOption(const String& name, + OptionID option, OptionValue value); + + //! Remove a screen option + /*! + Removes an option and its value from the named screen. Does + nothing if the option doesn't exist on the screen. Returns true + iff \c name is a known screen. + */ + bool removeOption(const String& name, OptionID option); + + //! Remove a screen options + /*! + Removes all options and values from the named screen. Returns true + iff \c name is a known screen. + */ + bool removeOptions(const String& name); + + //! Get the hot key input filter + /*! + Returns the hot key input filter. Clients can modify hotkeys using + that object. + */ + virtual InputFilter* + getInputFilter(); + + //@} + //! @name accessors + //@{ + + //! Test screen name validity + /*! + Returns true iff \c name is a valid screen name. + */ + bool isValidScreenName(const String& name) const; + + //! Get beginning (canonical) screen name iterator + const_iterator begin() const; + //! Get ending (canonical) screen name iterator + const_iterator end() const; + + //! Get beginning screen name iterator + all_const_iterator beginAll() const; + //! Get ending screen name iterator + all_const_iterator endAll() const; + + //! Test for screen name + /*! + Returns true iff \c name names a screen. + */ + virtual bool isScreen(const String& name) const; + + //! Test for canonical screen name + /*! + Returns true iff \c name is the canonical name of a screen. + */ + bool isCanonicalName(const String& name) const; + + //! Get canonical name + /*! + Returns the canonical name of a screen or the empty string if + the name is unknown. Returns the canonical name if one is given. + */ + String getCanonicalName(const String& name) const; + + //! Get neighbor + /*! + Returns the canonical screen name of the neighbor in the given + direction (set through connect()) at position \c position. Returns + the empty string if there is no neighbor in that direction, otherwise + saves the position on the neighbor in \c positionOut if it's not + \c NULL. + */ + String getNeighbor(const String&, EDirection, + float position, float* positionOut) const; + + //! Check for neighbor + /*! + Returns \c true if the screen has a neighbor anywhere along the edge + given by the direction. + */ + bool hasNeighbor(const String&, EDirection) const; + + //! Check for neighbor + /*! + Returns \c true if the screen has a neighbor in the given range along + the edge given by the direction. + */ + bool hasNeighbor(const String&, EDirection, + float start, float end) const; + + //! Get beginning neighbor iterator + link_const_iterator beginNeighbor(const String&) const; + //! Get ending neighbor iterator + link_const_iterator endNeighbor(const String&) const; + + //! Get the server address + const NetworkAddress& + getBarrierAddress() const; + + //! Get the screen options + /*! + Returns all the added options for the named screen. Returns NULL + if the screen is unknown and an empty collection if there are no + options. + */ + const ScreenOptions* + getOptions(const String& name) const; + + //! Check for lock to screen action + /*! + Returns \c true if this configuration has a lock to screen action. + This is for backwards compatible support of ScrollLock locking. + */ + bool hasLockToScreenAction() const; + + //! Compare configurations + bool operator==(const Config&) const; + //! Compare configurations + bool operator!=(const Config&) const; + + //! Read configuration + /*! + Reads a configuration from a context. Throws XConfigRead on error + and context is unchanged. + */ + void read(ConfigReadContext& context); + + //! Read configuration + /*! + Reads a configuration from a stream. Throws XConfigRead on error. + */ + friend std::istream& + operator>>(std::istream&, Config&); + + //! Write configuration + /*! + Writes a configuration to a stream. + */ + friend std::ostream& + operator<<(std::ostream&, const Config&); + + //! Get direction name + /*! + Returns the name of a direction (for debugging). + */ + static const char* dirName(EDirection); + + //! Get interval as string + /*! + Returns an interval as a parseable string. + */ + static String formatInterval(const Interval&); + + //@} + +private: + void readSection(ConfigReadContext&); + void readSectionOptions(ConfigReadContext&); + void readSectionScreens(ConfigReadContext&); + void readSectionLinks(ConfigReadContext&); + void readSectionAliases(ConfigReadContext&); + + InputFilter::Condition* + parseCondition(ConfigReadContext&, + const String& condition, + const std::vector<String>& args); + void parseAction(ConfigReadContext&, + const String& action, + const std::vector<String>& args, + InputFilter::Rule&, bool activate); + + void parseScreens(ConfigReadContext&, const String&, + std::set<String>& screens) const; + static const char* getOptionName(OptionID); + static String getOptionValue(OptionID, OptionValue); + +private: + CellMap m_map; + NameMap m_nameToCanonicalName; + NetworkAddress m_barrierAddress; + ScreenOptions m_globalOptions; + InputFilter m_inputFilter; + bool m_hasLockToScreenAction; + IEventQueue* m_events; +}; + +//! Configuration read context +/*! +Maintains a context when reading a configuration from a stream. +*/ +class ConfigReadContext { +public: + typedef std::vector<String> ArgList; + + ConfigReadContext(std::istream&, SInt32 firstLine = 1); + ~ConfigReadContext(); + + bool readLine(String&); + UInt32 getLineNumber() const; + + bool operator!() const; + + OptionValue parseBoolean(const String&) const; + OptionValue parseInt(const String&) const; + OptionValue parseModifierKey(const String&) const; + OptionValue parseCorner(const String&) const; + OptionValue parseCorners(const String&) const; + Config::Interval + parseInterval(const ArgList& args) const; + void parseNameWithArgs( + const String& type, const String& line, + const String& delim, String::size_type& index, + String& name, ArgList& args) const; + IPlatformScreen::KeyInfo* + parseKeystroke(const String& keystroke) const; + IPlatformScreen::KeyInfo* + parseKeystroke(const String& keystroke, + const std::set<String>& screens) const; + IPlatformScreen::ButtonInfo* + parseMouse(const String& mouse) const; + KeyModifierMask parseModifier(const String& modifiers) const; + std::istream& getStream() const { return m_stream; }; + +private: + // not implemented + ConfigReadContext& operator=(const ConfigReadContext&); + + static String concatArgs(const ArgList& args); + +private: + std::istream& m_stream; + SInt32 m_line; +}; + +//! Configuration stream read exception +/*! +Thrown when a configuration stream cannot be parsed. +*/ +class XConfigRead : public XBase { +public: + XConfigRead(const ConfigReadContext& context, const String&); + XConfigRead(const ConfigReadContext& context, + const char* errorFmt, const String& arg); + virtual ~XConfigRead() _NOEXCEPT; + +protected: + // XBase overrides + virtual String getWhat() const throw(); + +private: + String m_error; +}; diff --git a/src/lib/server/InputFilter.cpp b/src/lib/server/InputFilter.cpp new file mode 100644 index 0000000..9e73f45 --- /dev/null +++ b/src/lib/server/InputFilter.cpp @@ -0,0 +1,1090 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2005 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 "server/InputFilter.h" +#include "server/Server.h" +#include "server/PrimaryClient.h" +#include "barrier/KeyMap.h" +#include "base/EventQueue.h" +#include "base/Log.h" +#include "base/TMethodEventJob.h" + +#include <cstdlib> +#include <cstring> + +// ----------------------------------------------------------------------------- +// Input Filter Condition Classes +// ----------------------------------------------------------------------------- +InputFilter::Condition::Condition() +{ + // do nothing +} + +InputFilter::Condition::~Condition() +{ + // do nothing +} + +void +InputFilter::Condition::enablePrimary(PrimaryClient*) +{ + // do nothing +} + +void +InputFilter::Condition::disablePrimary(PrimaryClient*) +{ + // do nothing +} + +InputFilter::KeystrokeCondition::KeystrokeCondition( + IEventQueue* events, IPlatformScreen::KeyInfo* info) : + m_id(0), + m_key(info->m_key), + m_mask(info->m_mask), + m_events(events) +{ + free(info); +} + +InputFilter::KeystrokeCondition::KeystrokeCondition( + IEventQueue* events, KeyID key, KeyModifierMask mask) : + m_id(0), + m_key(key), + m_mask(mask), + m_events(events) +{ + // do nothing +} + +InputFilter::KeystrokeCondition::~KeystrokeCondition() +{ + // do nothing +} + +KeyID +InputFilter::KeystrokeCondition::getKey() const +{ + return m_key; +} + +KeyModifierMask +InputFilter::KeystrokeCondition::getMask() const +{ + return m_mask; +} + +InputFilter::Condition* +InputFilter::KeystrokeCondition::clone() const +{ + return new KeystrokeCondition(m_events, m_key, m_mask); +} + +String +InputFilter::KeystrokeCondition::format() const +{ + return barrier::string::sprintf("keystroke(%s)", + barrier::KeyMap::formatKey(m_key, m_mask).c_str()); +} + +InputFilter::EFilterStatus +InputFilter::KeystrokeCondition::match(const Event& event) +{ + EFilterStatus status; + + // check for hotkey events + Event::Type type = event.getType(); + if (type == m_events->forIPrimaryScreen().hotKeyDown()) { + status = kActivate; + } + else if (type == m_events->forIPrimaryScreen().hotKeyUp()) { + status = kDeactivate; + } + else { + return kNoMatch; + } + + // check if it's our hotkey + IPrimaryScreen::HotKeyInfo* kinfo = + static_cast<IPlatformScreen::HotKeyInfo*>(event.getData()); + if (kinfo->m_id != m_id) { + return kNoMatch; + } + + return status; +} + +void +InputFilter::KeystrokeCondition::enablePrimary(PrimaryClient* primary) +{ + m_id = primary->registerHotKey(m_key, m_mask); +} + +void +InputFilter::KeystrokeCondition::disablePrimary(PrimaryClient* primary) +{ + primary->unregisterHotKey(m_id); + m_id = 0; +} + +InputFilter::MouseButtonCondition::MouseButtonCondition( + IEventQueue* events, IPlatformScreen::ButtonInfo* info) : + m_button(info->m_button), + m_mask(info->m_mask), + m_events(events) +{ + free(info); +} + +InputFilter::MouseButtonCondition::MouseButtonCondition( + IEventQueue* events, ButtonID button, KeyModifierMask mask) : + m_button(button), + m_mask(mask), + m_events(events) +{ + // do nothing +} + +InputFilter::MouseButtonCondition::~MouseButtonCondition() +{ + // do nothing +} + +ButtonID +InputFilter::MouseButtonCondition::getButton() const +{ + return m_button; +} + +KeyModifierMask +InputFilter::MouseButtonCondition::getMask() const +{ + return m_mask; +} + +InputFilter::Condition* +InputFilter::MouseButtonCondition::clone() const +{ + return new MouseButtonCondition(m_events, m_button, m_mask); +} + +String +InputFilter::MouseButtonCondition::format() const +{ + String key = barrier::KeyMap::formatKey(kKeyNone, m_mask); + if (!key.empty()) { + key += "+"; + } + return barrier::string::sprintf("mousebutton(%s%d)", key.c_str(), m_button); +} + +InputFilter::EFilterStatus +InputFilter::MouseButtonCondition::match(const Event& event) +{ + static const KeyModifierMask s_ignoreMask = + KeyModifierAltGr | KeyModifierCapsLock | + KeyModifierNumLock | KeyModifierScrollLock; + + EFilterStatus status; + + // check for hotkey events + Event::Type type = event.getType(); + if (type == m_events->forIPrimaryScreen().buttonDown()) { + status = kActivate; + } + else if (type == m_events->forIPrimaryScreen().buttonUp()) { + status = kDeactivate; + } + else { + return kNoMatch; + } + + // check if it's the right button and modifiers. ignore modifiers + // that cannot be combined with a mouse button. + IPlatformScreen::ButtonInfo* minfo = + static_cast<IPlatformScreen::ButtonInfo*>(event.getData()); + if (minfo->m_button != m_button || + (minfo->m_mask & ~s_ignoreMask) != m_mask) { + return kNoMatch; + } + + return status; +} + +InputFilter::ScreenConnectedCondition::ScreenConnectedCondition( + IEventQueue* events, const String& screen) : + m_screen(screen), + m_events(events) +{ + // do nothing +} + +InputFilter::ScreenConnectedCondition::~ScreenConnectedCondition() +{ + // do nothing +} + +InputFilter::Condition* +InputFilter::ScreenConnectedCondition::clone() const +{ + return new ScreenConnectedCondition(m_events, m_screen); +} + +String +InputFilter::ScreenConnectedCondition::format() const +{ + return barrier::string::sprintf("connect(%s)", m_screen.c_str()); +} + +InputFilter::EFilterStatus +InputFilter::ScreenConnectedCondition::match(const Event& event) +{ + if (event.getType() == m_events->forServer().connected()) { + Server::ScreenConnectedInfo* info = + static_cast<Server::ScreenConnectedInfo*>(event.getData()); + if (m_screen == info->m_screen || m_screen.empty()) { + return kActivate; + } + } + + return kNoMatch; +} + +// ----------------------------------------------------------------------------- +// Input Filter Action Classes +// ----------------------------------------------------------------------------- +InputFilter::Action::Action() +{ + // do nothing +} + +InputFilter::Action::~Action() +{ + // do nothing +} + +InputFilter::LockCursorToScreenAction::LockCursorToScreenAction( + IEventQueue* events, Mode mode) : + m_mode(mode), + m_events(events) +{ + // do nothing +} + +InputFilter::LockCursorToScreenAction::Mode +InputFilter::LockCursorToScreenAction::getMode() const +{ + return m_mode; +} + +InputFilter::Action* +InputFilter::LockCursorToScreenAction::clone() const +{ + return new LockCursorToScreenAction(*this); +} + +String +InputFilter::LockCursorToScreenAction::format() const +{ + static const char* s_mode[] = { "off", "on", "toggle" }; + + return barrier::string::sprintf("lockCursorToScreen(%s)", s_mode[m_mode]); +} + +void +InputFilter::LockCursorToScreenAction::perform(const Event& event) +{ + static const Server::LockCursorToScreenInfo::State s_state[] = { + Server::LockCursorToScreenInfo::kOff, + Server::LockCursorToScreenInfo::kOn, + Server::LockCursorToScreenInfo::kToggle + }; + + // send event + Server::LockCursorToScreenInfo* info = + Server::LockCursorToScreenInfo::alloc(s_state[m_mode]); + m_events->addEvent(Event(m_events->forServer().lockCursorToScreen(), + event.getTarget(), info, + Event::kDeliverImmediately)); +} + +InputFilter::SwitchToScreenAction::SwitchToScreenAction( + IEventQueue* events, const String& screen) : + m_screen(screen), + m_events(events) +{ + // do nothing +} + +String +InputFilter::SwitchToScreenAction::getScreen() const +{ + return m_screen; +} + +InputFilter::Action* +InputFilter::SwitchToScreenAction::clone() const +{ + return new SwitchToScreenAction(*this); +} + +String +InputFilter::SwitchToScreenAction::format() const +{ + return barrier::string::sprintf("switchToScreen(%s)", m_screen.c_str()); +} + +void +InputFilter::SwitchToScreenAction::perform(const Event& event) +{ + // pick screen name. if m_screen is empty then use the screen from + // event if it has one. + String screen = m_screen; + if (screen.empty() && event.getType() == m_events->forServer().connected()) { + Server::ScreenConnectedInfo* info = + static_cast<Server::ScreenConnectedInfo*>(event.getData()); + screen = info->m_screen; + } + + // send event + Server::SwitchToScreenInfo* info = + Server::SwitchToScreenInfo::alloc(screen); + m_events->addEvent(Event(m_events->forServer().switchToScreen(), + event.getTarget(), info, + Event::kDeliverImmediately)); +} + +InputFilter::SwitchInDirectionAction::SwitchInDirectionAction( + IEventQueue* events, EDirection direction) : + m_direction(direction), + m_events(events) +{ + // do nothing +} + +EDirection +InputFilter::SwitchInDirectionAction::getDirection() const +{ + return m_direction; +} + +InputFilter::Action* +InputFilter::SwitchInDirectionAction::clone() const +{ + return new SwitchInDirectionAction(*this); +} + +String +InputFilter::SwitchInDirectionAction::format() const +{ + static const char* s_names[] = { + "", + "left", + "right", + "up", + "down" + }; + + return barrier::string::sprintf("switchInDirection(%s)", s_names[m_direction]); +} + +void +InputFilter::SwitchInDirectionAction::perform(const Event& event) +{ + Server::SwitchInDirectionInfo* info = + Server::SwitchInDirectionInfo::alloc(m_direction); + m_events->addEvent(Event(m_events->forServer().switchInDirection(), + event.getTarget(), info, + Event::kDeliverImmediately)); +} + +InputFilter::KeyboardBroadcastAction::KeyboardBroadcastAction( + IEventQueue* events, Mode mode) : + m_mode(mode), + m_events(events) +{ + // do nothing +} + +InputFilter::KeyboardBroadcastAction::KeyboardBroadcastAction( + IEventQueue* events, + Mode mode, + const std::set<String>& screens) : + m_mode(mode), + m_screens(IKeyState::KeyInfo::join(screens)), + m_events(events) +{ + // do nothing +} + +InputFilter::KeyboardBroadcastAction::Mode +InputFilter::KeyboardBroadcastAction::getMode() const +{ + return m_mode; +} + +std::set<String> +InputFilter::KeyboardBroadcastAction::getScreens() const +{ + std::set<String> screens; + IKeyState::KeyInfo::split(m_screens.c_str(), screens); + return screens; +} + +InputFilter::Action* +InputFilter::KeyboardBroadcastAction::clone() const +{ + return new KeyboardBroadcastAction(*this); +} + +String +InputFilter::KeyboardBroadcastAction::format() const +{ + static const char* s_mode[] = { "off", "on", "toggle" }; + static const char* s_name = "keyboardBroadcast"; + + if (m_screens.empty() || m_screens[0] == '*') { + return barrier::string::sprintf("%s(%s)", s_name, s_mode[m_mode]); + } + else { + return barrier::string::sprintf("%s(%s,%.*s)", s_name, s_mode[m_mode], + m_screens.size() - 2, + m_screens.c_str() + 1); + } +} + +void +InputFilter::KeyboardBroadcastAction::perform(const Event& event) +{ + static const Server::KeyboardBroadcastInfo::State s_state[] = { + Server::KeyboardBroadcastInfo::kOff, + Server::KeyboardBroadcastInfo::kOn, + Server::KeyboardBroadcastInfo::kToggle + }; + + // send event + Server::KeyboardBroadcastInfo* info = + Server::KeyboardBroadcastInfo::alloc(s_state[m_mode], m_screens); + m_events->addEvent(Event(m_events->forServer().keyboardBroadcast(), + event.getTarget(), info, + Event::kDeliverImmediately)); +} + +InputFilter::KeystrokeAction::KeystrokeAction( + IEventQueue* events, IPlatformScreen::KeyInfo* info, bool press) : + m_keyInfo(info), + m_press(press), + m_events(events) +{ + // do nothing +} + +InputFilter::KeystrokeAction::~KeystrokeAction() +{ + free(m_keyInfo); +} + +void +InputFilter::KeystrokeAction::adoptInfo(IPlatformScreen::KeyInfo* info) +{ + free(m_keyInfo); + m_keyInfo = info; +} + +const IPlatformScreen::KeyInfo* +InputFilter::KeystrokeAction::getInfo() const +{ + return m_keyInfo; +} + +bool +InputFilter::KeystrokeAction::isOnPress() const +{ + return m_press; +} + +InputFilter::Action* +InputFilter::KeystrokeAction::clone() const +{ + IKeyState::KeyInfo* info = IKeyState::KeyInfo::alloc(*m_keyInfo); + return new KeystrokeAction(m_events, info, m_press); +} + +String +InputFilter::KeystrokeAction::format() const +{ + const char* type = formatName(); + + if (m_keyInfo->m_screens[0] == '\0') { + return barrier::string::sprintf("%s(%s)", type, + barrier::KeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str()); + } + else if (m_keyInfo->m_screens[0] == '*') { + return barrier::string::sprintf("%s(%s,*)", type, + barrier::KeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str()); + } + else { + return barrier::string::sprintf("%s(%s,%.*s)", type, + barrier::KeyMap::formatKey(m_keyInfo->m_key, + m_keyInfo->m_mask).c_str(), + strlen(m_keyInfo->m_screens + 1) - 1, + m_keyInfo->m_screens + 1); + } +} + +void +InputFilter::KeystrokeAction::perform(const Event& event) +{ + Event::Type type = m_press ? + m_events->forIKeyState().keyDown() : + m_events->forIKeyState().keyUp(); + + m_events->addEvent(Event(m_events->forIPrimaryScreen().fakeInputBegin(), + event.getTarget(), NULL, + Event::kDeliverImmediately)); + m_events->addEvent(Event(type, event.getTarget(), m_keyInfo, + Event::kDeliverImmediately | + Event::kDontFreeData)); + m_events->addEvent(Event(m_events->forIPrimaryScreen().fakeInputEnd(), + event.getTarget(), NULL, + Event::kDeliverImmediately)); +} + +const char* +InputFilter::KeystrokeAction::formatName() const +{ + return (m_press ? "keyDown" : "keyUp"); +} + +InputFilter::MouseButtonAction::MouseButtonAction( + IEventQueue* events, IPlatformScreen::ButtonInfo* info, bool press) : + m_buttonInfo(info), + m_press(press), + m_events(events) +{ + // do nothing +} + +InputFilter::MouseButtonAction::~MouseButtonAction() +{ + free(m_buttonInfo); +} + +const IPlatformScreen::ButtonInfo* +InputFilter::MouseButtonAction::getInfo() const +{ + return m_buttonInfo; +} + +bool +InputFilter::MouseButtonAction::isOnPress() const +{ + return m_press; +} + +InputFilter::Action* +InputFilter::MouseButtonAction::clone() const +{ + IPlatformScreen::ButtonInfo* info = + IPrimaryScreen::ButtonInfo::alloc(*m_buttonInfo); + return new MouseButtonAction(m_events, info, m_press); +} + +String +InputFilter::MouseButtonAction::format() const +{ + const char* type = formatName(); + + String key = barrier::KeyMap::formatKey(kKeyNone, m_buttonInfo->m_mask); + return barrier::string::sprintf("%s(%s%s%d)", type, + key.c_str(), key.empty() ? "" : "+", + m_buttonInfo->m_button); +} + +void +InputFilter::MouseButtonAction::perform(const Event& event) + +{ + // send modifiers + IPlatformScreen::KeyInfo* modifierInfo = NULL; + if (m_buttonInfo->m_mask != 0) { + KeyID key = m_press ? kKeySetModifiers : kKeyClearModifiers; + modifierInfo = + IKeyState::KeyInfo::alloc(key, m_buttonInfo->m_mask, 0, 1); + m_events->addEvent(Event(m_events->forIKeyState().keyDown(), + event.getTarget(), modifierInfo, + Event::kDeliverImmediately)); + } + + // send button + Event::Type type = m_press ? m_events->forIPrimaryScreen().buttonDown() : + m_events->forIPrimaryScreen().buttonUp(); + m_events->addEvent(Event(type, event.getTarget(), m_buttonInfo, + Event::kDeliverImmediately | + Event::kDontFreeData)); +} + +const char* +InputFilter::MouseButtonAction::formatName() const +{ + return (m_press ? "mouseDown" : "mouseUp"); +} + +// +// InputFilter::Rule +// + +InputFilter::Rule::Rule() : + m_condition(NULL) +{ + // do nothing +} + +InputFilter::Rule::Rule(Condition* adoptedCondition) : + m_condition(adoptedCondition) +{ + // do nothing +} + +InputFilter::Rule::Rule(const Rule& rule) : + m_condition(NULL) +{ + copy(rule); +} + +InputFilter::Rule::~Rule() +{ + clear(); +} + +InputFilter::Rule& +InputFilter::Rule::operator=(const Rule& rule) +{ + if (&rule != this) { + copy(rule); + } + return *this; +} + +void +InputFilter::Rule::clear() +{ + delete m_condition; + for (ActionList::iterator i = m_activateActions.begin(); + i != m_activateActions.end(); ++i) { + delete *i; + } + for (ActionList::iterator i = m_deactivateActions.begin(); + i != m_deactivateActions.end(); ++i) { + delete *i; + } + + m_condition = NULL; + m_activateActions.clear(); + m_deactivateActions.clear(); +} + +void +InputFilter::Rule::copy(const Rule& rule) +{ + clear(); + if (rule.m_condition != NULL) { + m_condition = rule.m_condition->clone(); + } + for (ActionList::const_iterator i = rule.m_activateActions.begin(); + i != rule.m_activateActions.end(); ++i) { + m_activateActions.push_back((*i)->clone()); + } + for (ActionList::const_iterator i = rule.m_deactivateActions.begin(); + i != rule.m_deactivateActions.end(); ++i) { + m_deactivateActions.push_back((*i)->clone()); + } +} + +void +InputFilter::Rule::setCondition(Condition* adopted) +{ + delete m_condition; + m_condition = adopted; +} + +void +InputFilter::Rule::adoptAction(Action* action, bool onActivation) +{ + if (action != NULL) { + if (onActivation) { + m_activateActions.push_back(action); + } + else { + m_deactivateActions.push_back(action); + } + } +} + +void +InputFilter::Rule::removeAction(bool onActivation, UInt32 index) +{ + if (onActivation) { + delete m_activateActions[index]; + m_activateActions.erase(m_activateActions.begin() + index); + } + else { + delete m_deactivateActions[index]; + m_deactivateActions.erase(m_deactivateActions.begin() + index); + } +} + +void +InputFilter::Rule::replaceAction(Action* adopted, + bool onActivation, UInt32 index) +{ + if (adopted == NULL) { + removeAction(onActivation, index); + } + else if (onActivation) { + delete m_activateActions[index]; + m_activateActions[index] = adopted; + } + else { + delete m_deactivateActions[index]; + m_deactivateActions[index] = adopted; + } +} + +void +InputFilter::Rule::enable(PrimaryClient* primaryClient) +{ + if (m_condition != NULL) { + m_condition->enablePrimary(primaryClient); + } +} + +void +InputFilter::Rule::disable(PrimaryClient* primaryClient) +{ + if (m_condition != NULL) { + m_condition->disablePrimary(primaryClient); + } +} + +bool +InputFilter::Rule::handleEvent(const Event& event) +{ + // NULL condition never matches + if (m_condition == NULL) { + return false; + } + + // match + const ActionList* actions; + switch (m_condition->match(event)) { + default: + // not handled + return false; + + case kActivate: + actions = &m_activateActions; + LOG((CLOG_DEBUG1 "activate actions")); + break; + + case kDeactivate: + actions = &m_deactivateActions; + LOG((CLOG_DEBUG1 "deactivate actions")); + break; + } + + // perform actions + for (ActionList::const_iterator i = actions->begin(); + i != actions->end(); ++i) { + LOG((CLOG_DEBUG1 "hotkey: %s", (*i)->format().c_str())); + (*i)->perform(event); + } + + return true; +} + +String +InputFilter::Rule::format() const +{ + String s; + if (m_condition != NULL) { + // condition + s += m_condition->format(); + s += " = "; + + // activate actions + ActionList::const_iterator i = m_activateActions.begin(); + if (i != m_activateActions.end()) { + s += (*i)->format(); + while (++i != m_activateActions.end()) { + s += ", "; + s += (*i)->format(); + } + } + + // deactivate actions + if (!m_deactivateActions.empty()) { + s += "; "; + i = m_deactivateActions.begin(); + if (i != m_deactivateActions.end()) { + s += (*i)->format(); + while (++i != m_deactivateActions.end()) { + s += ", "; + s += (*i)->format(); + } + } + } + } + return s; +} + +const InputFilter::Condition* +InputFilter::Rule::getCondition() const +{ + return m_condition; +} + +UInt32 +InputFilter::Rule::getNumActions(bool onActivation) const +{ + if (onActivation) { + return static_cast<UInt32>(m_activateActions.size()); + } + else { + return static_cast<UInt32>(m_deactivateActions.size()); + } +} + +const InputFilter::Action& +InputFilter::Rule::getAction(bool onActivation, UInt32 index) const +{ + if (onActivation) { + return *m_activateActions[index]; + } + else { + return *m_deactivateActions[index]; + } +} + + +// ----------------------------------------------------------------------------- +// Input Filter Class +// ----------------------------------------------------------------------------- +InputFilter::InputFilter(IEventQueue* events) : + m_primaryClient(NULL), + m_events(events) +{ + // do nothing +} + +InputFilter::InputFilter(const InputFilter& x) : + m_ruleList(x.m_ruleList), + m_primaryClient(NULL), + m_events(x.m_events) +{ + setPrimaryClient(x.m_primaryClient); +} + +InputFilter::~InputFilter() +{ + setPrimaryClient(NULL); +} + +InputFilter& +InputFilter::operator=(const InputFilter& x) +{ + if (&x != this) { + PrimaryClient* oldClient = m_primaryClient; + setPrimaryClient(NULL); + + m_ruleList = x.m_ruleList; + + setPrimaryClient(oldClient); + } + return *this; +} + +void +InputFilter::addFilterRule(const Rule& rule) +{ + m_ruleList.push_back(rule); + if (m_primaryClient != NULL) { + m_ruleList.back().enable(m_primaryClient); + } +} + +void +InputFilter::removeFilterRule(UInt32 index) +{ + if (m_primaryClient != NULL) { + m_ruleList[index].disable(m_primaryClient); + } + m_ruleList.erase(m_ruleList.begin() + index); +} + +InputFilter::Rule& +InputFilter::getRule(UInt32 index) +{ + return m_ruleList[index]; +} + +void +InputFilter::setPrimaryClient(PrimaryClient* client) +{ + if (m_primaryClient == client) { + return; + } + + if (m_primaryClient != NULL) { + for (RuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + rule->disable(m_primaryClient); + } + + m_events->removeHandler(m_events->forIKeyState().keyDown(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIKeyState().keyUp(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIKeyState().keyRepeat(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().hotKeyDown(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().hotKeyUp(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forServer().connected(), + m_primaryClient->getEventTarget()); + } + + m_primaryClient = client; + + if (m_primaryClient != NULL) { + m_events->adoptHandler(m_events->forIKeyState().keyDown(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forIKeyState().keyUp(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forIKeyState().keyRepeat(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().hotKeyDown(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().hotKeyUp(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + m_events->adoptHandler(m_events->forServer().connected(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<InputFilter>(this, + &InputFilter::handleEvent)); + + for (RuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + rule->enable(m_primaryClient); + } + } +} + +String +InputFilter::format(const String& linePrefix) const +{ + String s; + for (RuleList::const_iterator i = m_ruleList.begin(); + i != m_ruleList.end(); ++i) { + s += linePrefix; + s += i->format(); + s += "\n"; + } + return s; +} + +UInt32 +InputFilter::getNumRules() const +{ + return static_cast<UInt32>(m_ruleList.size()); +} + +bool +InputFilter::operator==(const InputFilter& x) const +{ + // if there are different numbers of rules then we can't be equal + if (m_ruleList.size() != x.m_ruleList.size()) { + return false; + } + + // compare rule lists. the easiest way to do that is to format each + // rule into a string, sort the strings, then compare the results. + std::vector<String> aList, bList; + for (RuleList::const_iterator i = m_ruleList.begin(); + i != m_ruleList.end(); ++i) { + aList.push_back(i->format()); + } + for (RuleList::const_iterator i = x.m_ruleList.begin(); + i != x.m_ruleList.end(); ++i) { + bList.push_back(i->format()); + } + std::partial_sort(aList.begin(), aList.end(), aList.end()); + std::partial_sort(bList.begin(), bList.end(), bList.end()); + return (aList == bList); +} + +bool +InputFilter::operator!=(const InputFilter& x) const +{ + return !operator==(x); +} + +void +InputFilter::handleEvent(const Event& event, void*) +{ + // copy event and adjust target + Event myEvent(event.getType(), this, event.getData(), + event.getFlags() | Event::kDontFreeData | + Event::kDeliverImmediately); + + // let each rule try to match the event until one does + for (RuleList::iterator rule = m_ruleList.begin(); + rule != m_ruleList.end(); ++rule) { + if (rule->handleEvent(myEvent)) { + // handled + return; + } + } + + // not handled so pass through + m_events->addEvent(myEvent); +} diff --git a/src/lib/server/InputFilter.h b/src/lib/server/InputFilter.h new file mode 100644 index 0000000..73afe97 --- /dev/null +++ b/src/lib/server/InputFilter.h @@ -0,0 +1,361 @@ +/* + * barrier -- mouse and keyboard sharing utility + * Copyright (C) 2012-2016 Symless Ltd. + * Copyright (C) 2005 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 "barrier/key_types.h" +#include "barrier/mouse_types.h" +#include "barrier/protocol_types.h" +#include "barrier/IPlatformScreen.h" +#include "base/String.h" +#include "common/stdmap.h" +#include "common/stdset.h" + +class PrimaryClient; +class Event; +class IEventQueue; + +class InputFilter { +public: + // ------------------------------------------------------------------------- + // Input Filter Condition Classes + // ------------------------------------------------------------------------- + enum EFilterStatus { + kNoMatch, + kActivate, + kDeactivate + }; + + class Condition { + public: + Condition(); + virtual ~Condition(); + + virtual Condition* clone() const = 0; + virtual String format() const = 0; + + virtual EFilterStatus match(const Event&) = 0; + + virtual void enablePrimary(PrimaryClient*); + virtual void disablePrimary(PrimaryClient*); + }; + + // KeystrokeCondition + class KeystrokeCondition : public Condition { + public: + KeystrokeCondition(IEventQueue* events, IPlatformScreen::KeyInfo*); + KeystrokeCondition(IEventQueue* events, KeyID key, KeyModifierMask mask); + virtual ~KeystrokeCondition(); + + KeyID getKey() const; + KeyModifierMask getMask() const; + + // Condition overrides + virtual Condition* clone() const; + virtual String format() const; + virtual EFilterStatus match(const Event&); + virtual void enablePrimary(PrimaryClient*); + virtual void disablePrimary(PrimaryClient*); + + private: + UInt32 m_id; + KeyID m_key; + KeyModifierMask m_mask; + IEventQueue* m_events; + }; + + // MouseButtonCondition + class MouseButtonCondition : public Condition { + public: + MouseButtonCondition(IEventQueue* events, IPlatformScreen::ButtonInfo*); + MouseButtonCondition(IEventQueue* events, ButtonID, KeyModifierMask mask); + virtual ~MouseButtonCondition(); + + ButtonID getButton() const; + KeyModifierMask getMask() const; + + // Condition overrides + virtual Condition* clone() const; + virtual String format() const; + virtual EFilterStatus match(const Event&); + + private: + ButtonID m_button; + KeyModifierMask m_mask; + IEventQueue* m_events; + }; + + // ScreenConnectedCondition + class ScreenConnectedCondition : public Condition { + public: + ScreenConnectedCondition(IEventQueue* events, const String& screen); + virtual ~ScreenConnectedCondition(); + + // Condition overrides + virtual Condition* clone() const; + virtual String format() const; + virtual EFilterStatus match(const Event&); + + private: + String m_screen; + IEventQueue* m_events; + }; + + // ------------------------------------------------------------------------- + // Input Filter Action Classes + // ------------------------------------------------------------------------- + + class Action { + public: + Action(); + virtual ~Action(); + + virtual Action* clone() const = 0; + virtual String format() const = 0; + + virtual void perform(const Event&) = 0; + }; + + // LockCursorToScreenAction + class LockCursorToScreenAction : public Action { + public: + enum Mode { kOff, kOn, kToggle }; + + LockCursorToScreenAction(IEventQueue* events, Mode = kToggle); + + Mode getMode() const; + + // Action overrides + virtual Action* clone() const; + virtual String format() const; + virtual void perform(const Event&); + + private: + Mode m_mode; + IEventQueue* m_events; + }; + + // SwitchToScreenAction + class SwitchToScreenAction : public Action { + public: + SwitchToScreenAction(IEventQueue* events, const String& screen); + + String getScreen() const; + + // Action overrides + virtual Action* clone() const; + virtual String format() const; + virtual void perform(const Event&); + + private: + String m_screen; + IEventQueue* m_events; + }; + + // SwitchInDirectionAction + class SwitchInDirectionAction : public Action { + public: + SwitchInDirectionAction(IEventQueue* events, EDirection); + + EDirection getDirection() const; + + // Action overrides + virtual Action* clone() const; + virtual String format() const; + virtual void perform(const Event&); + + private: + EDirection m_direction; + IEventQueue* m_events; + }; + + // KeyboardBroadcastAction + class KeyboardBroadcastAction : public Action { + public: + enum Mode { kOff, kOn, kToggle }; + + KeyboardBroadcastAction(IEventQueue* events, Mode = kToggle); + KeyboardBroadcastAction(IEventQueue* events, Mode, const std::set<String>& screens); + + Mode getMode() const; + std::set<String> getScreens() const; + + // Action overrides + virtual Action* clone() const; + virtual String format() const; + virtual void perform(const Event&); + + private: + Mode m_mode; + String m_screens; + IEventQueue* m_events; + }; + + // KeystrokeAction + class KeystrokeAction : public Action { + public: + KeystrokeAction(IEventQueue* events, IPlatformScreen::KeyInfo* adoptedInfo, bool press); + ~KeystrokeAction(); + + void adoptInfo(IPlatformScreen::KeyInfo*); + const IPlatformScreen::KeyInfo* + getInfo() const; + bool isOnPress() const; + + // Action overrides + virtual Action* clone() const; + virtual String format() const; + virtual void perform(const Event&); + + protected: + virtual const char* formatName() const; + + private: + IPlatformScreen::KeyInfo* m_keyInfo; + bool m_press; + IEventQueue* m_events; + }; + + // MouseButtonAction -- modifier combinations not implemented yet + class MouseButtonAction : public Action { + public: + MouseButtonAction(IEventQueue* events, + IPlatformScreen::ButtonInfo* adoptedInfo, + bool press); + ~MouseButtonAction(); + + const IPlatformScreen::ButtonInfo* + getInfo() const; + bool isOnPress() const; + + // Action overrides + virtual Action* clone() const; + virtual String format() const; + virtual void perform(const Event&); + + protected: + virtual const char* formatName() const; + + private: + IPlatformScreen::ButtonInfo* m_buttonInfo; + bool m_press; + IEventQueue* m_events; + }; + + class Rule { + public: + Rule(); + Rule(Condition* adopted); + Rule(const Rule&); + ~Rule(); + + Rule& operator=(const Rule&); + + // replace the condition + void setCondition(Condition* adopted); + + // add an action to the rule + void adoptAction(Action*, bool onActivation); + + // remove an action from the rule + void removeAction(bool onActivation, UInt32 index); + + // replace an action in the rule + void replaceAction(Action* adopted, + bool onActivation, UInt32 index); + + // enable/disable + void enable(PrimaryClient*); + void disable(PrimaryClient*); + + // event handling + bool handleEvent(const Event&); + + // convert rule to a string + String format() const; + + // get the rule's condition + const Condition* + getCondition() const; + + // get number of actions + UInt32 getNumActions(bool onActivation) const; + + // get action by index + const Action& getAction(bool onActivation, UInt32 index) const; + + private: + void clear(); + void copy(const Rule&); + + private: + typedef std::vector<Action*> ActionList; + + Condition* m_condition; + ActionList m_activateActions; + ActionList m_deactivateActions; + }; + + // ------------------------------------------------------------------------- + // Input Filter Class + // ------------------------------------------------------------------------- + typedef std::vector<Rule> RuleList; + + InputFilter(IEventQueue* events); + InputFilter(const InputFilter&); + virtual ~InputFilter(); + +#ifdef TEST_ENV + InputFilter() : m_primaryClient(NULL) { } +#endif + + InputFilter& operator=(const InputFilter&); + + // add rule, adopting the condition and the actions + void addFilterRule(const Rule& rule); + + // remove a rule + void removeFilterRule(UInt32 index); + + // get rule by index + Rule& getRule(UInt32 index); + + // enable event filtering using the given primary client. disable + // if client is NULL. + virtual void setPrimaryClient(PrimaryClient* client); + + // convert rules to a string + String format(const String& linePrefix) const; + + // get number of rules + UInt32 getNumRules() const; + + //! Compare filters + bool operator==(const InputFilter&) const; + //! Compare filters + bool operator!=(const InputFilter&) const; + +private: + // event handling + void handleEvent(const Event&, void*); + +private: + RuleList m_ruleList; + PrimaryClient* m_primaryClient; + IEventQueue* m_events; +}; diff --git a/src/lib/server/PrimaryClient.cpp b/src/lib/server/PrimaryClient.cpp new file mode 100644 index 0000000..4c9fe50 --- /dev/null +++ b/src/lib/server/PrimaryClient.cpp @@ -0,0 +1,274 @@ +/* + * 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 "server/PrimaryClient.h" + +#include "barrier/Screen.h" +#include "barrier/Clipboard.h" +#include "base/Log.h" + +// +// PrimaryClient +// + +PrimaryClient::PrimaryClient(const String& name, barrier::Screen* screen) : + BaseClientProxy(name), + m_screen(screen), + m_fakeInputCount(0) +{ + // all clipboards are clean + for (UInt32 i = 0; i < kClipboardEnd; ++i) { + m_clipboardDirty[i] = false; + } +} + +PrimaryClient::~PrimaryClient() +{ + // do nothing +} + +void +PrimaryClient::reconfigure(UInt32 activeSides) +{ + m_screen->reconfigure(activeSides); +} + +UInt32 +PrimaryClient::registerHotKey(KeyID key, KeyModifierMask mask) +{ + return m_screen->registerHotKey(key, mask); +} + +void +PrimaryClient::unregisterHotKey(UInt32 id) +{ + m_screen->unregisterHotKey(id); +} + +void +PrimaryClient::fakeInputBegin() +{ + if (++m_fakeInputCount == 1) { + m_screen->fakeInputBegin(); + } +} + +void +PrimaryClient::fakeInputEnd() +{ + if (--m_fakeInputCount == 0) { + m_screen->fakeInputEnd(); + } +} + +SInt32 +PrimaryClient::getJumpZoneSize() const +{ + return m_screen->getJumpZoneSize(); +} + +void +PrimaryClient::getCursorCenter(SInt32& x, SInt32& y) const +{ + m_screen->getCursorCenter(x, y); +} + +KeyModifierMask +PrimaryClient::getToggleMask() const +{ + return m_screen->pollActiveModifiers(); +} + +bool +PrimaryClient::isLockedToScreen() const +{ + return m_screen->isLockedToScreen(); +} + +void* +PrimaryClient::getEventTarget() const +{ + return m_screen->getEventTarget(); +} + +bool +PrimaryClient::getClipboard(ClipboardID id, IClipboard* clipboard) const +{ + return m_screen->getClipboard(id, clipboard); +} + +void +PrimaryClient::getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const +{ + m_screen->getShape(x, y, width, height); +} + +void +PrimaryClient::getCursorPos(SInt32& x, SInt32& y) const +{ + m_screen->getCursorPos(x, y); +} + +void +PrimaryClient::enable() +{ + m_screen->enable(); +} + +void +PrimaryClient::disable() +{ + m_screen->disable(); +} + +void +PrimaryClient::enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, bool screensaver) +{ + m_screen->setSequenceNumber(seqNum); + if (!screensaver) { + m_screen->warpCursor(xAbs, yAbs); + } + m_screen->enter(mask); +} + +bool +PrimaryClient::leave() +{ + return m_screen->leave(); +} + +void +PrimaryClient::setClipboard(ClipboardID id, const IClipboard* clipboard) +{ + // ignore if this clipboard is already clean + if (m_clipboardDirty[id]) { + // this clipboard is now clean + m_clipboardDirty[id] = false; + + // set clipboard + m_screen->setClipboard(id, clipboard); + } +} + +void +PrimaryClient::grabClipboard(ClipboardID id) +{ + // grab clipboard + m_screen->grabClipboard(id); + + // clipboard is dirty (because someone else owns it now) + m_clipboardDirty[id] = true; +} + +void +PrimaryClient::setClipboardDirty(ClipboardID id, bool dirty) +{ + m_clipboardDirty[id] = dirty; +} + +void +PrimaryClient::keyDown(KeyID key, KeyModifierMask mask, KeyButton button) +{ + if (m_fakeInputCount > 0) { +// XXX -- don't forward keystrokes to primary screen for now + (void)key; + (void)mask; + (void)button; +// m_screen->keyDown(key, mask, button); + } +} + +void +PrimaryClient::keyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton) +{ + // ignore +} + +void +PrimaryClient::keyUp(KeyID key, KeyModifierMask mask, KeyButton button) +{ + if (m_fakeInputCount > 0) { +// XXX -- don't forward keystrokes to primary screen for now + (void)key; + (void)mask; + (void)button; +// m_screen->keyUp(key, mask, button); + } +} + +void +PrimaryClient::mouseDown(ButtonID) +{ + // ignore +} + +void +PrimaryClient::mouseUp(ButtonID) +{ + // ignore +} + +void +PrimaryClient::mouseMove(SInt32 x, SInt32 y) +{ + m_screen->warpCursor(x, y); +} + +void +PrimaryClient::mouseRelativeMove(SInt32, SInt32) +{ + // ignore +} + +void +PrimaryClient::mouseWheel(SInt32, SInt32) +{ + // ignore +} + +void +PrimaryClient::screensaver(bool) +{ + // ignore +} + +void +PrimaryClient::sendDragInfo(UInt32 fileCount, const char* info, size_t size) +{ + // ignore +} + +void +PrimaryClient::fileChunkSending(UInt8 mark, char* data, size_t dataSize) +{ + // ignore +} + +void +PrimaryClient::resetOptions() +{ + m_screen->resetOptions(); +} + +void +PrimaryClient::setOptions(const OptionsList& options) +{ + m_screen->setOptions(options); +} diff --git a/src/lib/server/PrimaryClient.h b/src/lib/server/PrimaryClient.h new file mode 100644 index 0000000..4296aaa --- /dev/null +++ b/src/lib/server/PrimaryClient.h @@ -0,0 +1,156 @@ +/* + * 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 "server/BaseClientProxy.h" +#include "barrier/protocol_types.h" + +namespace barrier { class Screen; } + +//! Primary screen as pseudo-client +/*! +The primary screen does not have a client associated with it. This +class provides a pseudo-client to allow the primary screen to be +treated as if it was a client. +*/ +class PrimaryClient : public BaseClientProxy { +public: + /*! + \c name is the name of the server and \p screen is primary screen. + */ + PrimaryClient(const String& name, barrier::Screen* screen); + ~PrimaryClient(); + +#ifdef TEST_ENV + PrimaryClient() : BaseClientProxy("") { } +#endif + + //! @name manipulators + //@{ + + //! Update configuration + /*! + Handles reconfiguration of jump zones. + */ + virtual void reconfigure(UInt32 activeSides); + + //! Register a system hotkey + /*! + Registers a system-wide hotkey for key \p key with modifiers \p mask. + Returns an id used to unregister the hotkey. + */ + virtual UInt32 registerHotKey(KeyID key, KeyModifierMask mask); + + //! Unregister a system hotkey + /*! + Unregisters a previously registered hot key. + */ + virtual void unregisterHotKey(UInt32 id); + + //! Prepare to synthesize input on primary screen + /*! + Prepares the primary screen to receive synthesized input. We do not + want to receive this synthesized input as user input so this method + ensures that we ignore it. Calls to \c fakeInputBegin() and + \c fakeInputEnd() may be nested; only the outermost have an effect. + */ + void fakeInputBegin(); + + //! Done synthesizing input on primary screen + /*! + Undoes whatever \c fakeInputBegin() did. + */ + void fakeInputEnd(); + + //@} + //! @name accessors + //@{ + + //! Get jump zone size + /*! + Return the jump zone size, the size of the regions on the edges of + the screen that cause the cursor to jump to another screen. + */ + SInt32 getJumpZoneSize() const; + + //! Get cursor center position + /*! + Return the cursor center position which is where we park the + cursor to compute cursor motion deltas and should be far from + the edges of the screen, typically the center. + */ + void getCursorCenter(SInt32& x, SInt32& y) const; + + //! Get toggle key state + /*! + Returns the primary screen's current toggle modifier key state. + */ + virtual KeyModifierMask + getToggleMask() const; + + //! Get screen lock state + /*! + Returns true if the user is locked to the screen. + */ + bool isLockedToScreen() const; + + //@} + + // FIXME -- these probably belong on IScreen + virtual void enable(); + virtual void disable(); + + // IScreen overrides + virtual void* getEventTarget() const; + virtual bool getClipboard(ClipboardID id, IClipboard*) const; + virtual void getShape(SInt32& x, SInt32& y, + SInt32& width, SInt32& height) const; + virtual void getCursorPos(SInt32& x, SInt32& y) const; + + // IClient overrides + virtual void enter(SInt32 xAbs, SInt32 yAbs, + UInt32 seqNum, KeyModifierMask mask, + bool forScreensaver); + virtual bool leave(); + virtual void setClipboard(ClipboardID, const IClipboard*); + virtual void grabClipboard(ClipboardID); + virtual void setClipboardDirty(ClipboardID, bool); + virtual void keyDown(KeyID, KeyModifierMask, KeyButton); + virtual void keyRepeat(KeyID, KeyModifierMask, + SInt32 count, KeyButton); + virtual void keyUp(KeyID, KeyModifierMask, KeyButton); + virtual void mouseDown(ButtonID); + virtual void mouseUp(ButtonID); + virtual void mouseMove(SInt32 xAbs, SInt32 yAbs); + virtual void mouseRelativeMove(SInt32 xRel, SInt32 yRel); + virtual void mouseWheel(SInt32 xDelta, SInt32 yDelta); + virtual void screensaver(bool activate); + virtual void resetOptions(); + virtual void setOptions(const OptionsList& options); + virtual void sendDragInfo(UInt32 fileCount, const char* info, size_t size); + virtual void fileChunkSending(UInt8 mark, char* data, size_t dataSize); + + virtual barrier::IStream* + getStream() const { return NULL; } + bool isPrimary() const { return true; } +private: + barrier::Screen* m_screen; + bool m_clipboardDirty[kClipboardEnd]; + SInt32 m_fakeInputCount; +}; diff --git a/src/lib/server/Server.cpp b/src/lib/server/Server.cpp new file mode 100644 index 0000000..32153a6 --- /dev/null +++ b/src/lib/server/Server.cpp @@ -0,0 +1,2405 @@ +/* + * 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 "server/Server.h" + +#include "server/ClientProxy.h" +#include "server/ClientProxyUnknown.h" +#include "server/PrimaryClient.h" +#include "server/ClientListener.h" +#include "barrier/FileChunk.h" +#include "barrier/IPlatformScreen.h" +#include "barrier/DropHelper.h" +#include "barrier/option_types.h" +#include "barrier/protocol_types.h" +#include "barrier/XScreen.h" +#include "barrier/XBarrier.h" +#include "barrier/StreamChunker.h" +#include "barrier/KeyState.h" +#include "barrier/Screen.h" +#include "barrier/PacketStreamFilter.h" +#include "net/TCPSocket.h" +#include "net/IDataSocket.h" +#include "net/IListenSocket.h" +#include "net/XSocket.h" +#include "mt/Thread.h" +#include "arch/Arch.h" +#include "base/TMethodJob.h" +#include "base/IEventQueue.h" +#include "base/Log.h" +#include "base/TMethodEventJob.h" +#include "common/stdexcept.h" + +#include <cstring> +#include <cstdlib> +#include <sstream> +#include <fstream> +#include <ctime> + +// +// Server +// + +Server::Server( + Config& config, + PrimaryClient* primaryClient, + barrier::Screen* screen, + IEventQueue* events, + ServerArgs const& args) : + m_mock(false), + m_primaryClient(primaryClient), + m_active(primaryClient), + m_seqNum(0), + m_xDelta(0), + m_yDelta(0), + m_xDelta2(0), + m_yDelta2(0), + m_config(&config), + m_inputFilter(config.getInputFilter()), + m_activeSaver(NULL), + m_switchDir(kNoDirection), + m_switchScreen(NULL), + m_switchWaitDelay(0.0), + m_switchWaitTimer(NULL), + m_switchTwoTapDelay(0.0), + m_switchTwoTapEngaged(false), + m_switchTwoTapArmed(false), + m_switchTwoTapZone(3), + m_switchNeedsShift(false), + m_switchNeedsControl(false), + m_switchNeedsAlt(false), + m_relativeMoves(false), + m_keyboardBroadcasting(false), + m_lockedToScreen(false), + m_screen(screen), + m_events(events), + m_sendFileThread(NULL), + m_writeToDropDirThread(NULL), + m_ignoreFileTransfer(false), + m_enableClipboard(true), + m_sendDragInfoThread(NULL), + m_waitDragInfoThread(true), + m_args(args) +{ + // must have a primary client and it must have a canonical name + assert(m_primaryClient != NULL); + assert(config.isScreen(primaryClient->getName())); + assert(m_screen != NULL); + + String primaryName = getName(primaryClient); + + // clear clipboards + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + ClipboardInfo& clipboard = m_clipboards[id]; + clipboard.m_clipboardOwner = primaryName; + clipboard.m_clipboardSeqNum = m_seqNum; + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + } + + // install event handlers + m_events->adoptHandler(Event::kTimer, this, + new TMethodEventJob<Server>(this, + &Server::handleSwitchWaitTimeout)); + m_events->adoptHandler(m_events->forIKeyState().keyDown(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleKeyDownEvent)); + m_events->adoptHandler(m_events->forIKeyState().keyUp(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleKeyUpEvent)); + m_events->adoptHandler(m_events->forIKeyState().keyRepeat(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleKeyRepeatEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().buttonDown(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleButtonDownEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().buttonUp(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleButtonUpEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnPrimary(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleMotionPrimaryEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().motionOnSecondary(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleMotionSecondaryEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().wheel(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleWheelEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverActivated(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleScreensaverActivatedEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), + m_primaryClient->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleScreensaverDeactivatedEvent)); + m_events->adoptHandler(m_events->forServer().switchToScreen(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleSwitchToScreenEvent)); + m_events->adoptHandler(m_events->forServer().switchInDirection(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleSwitchInDirectionEvent)); + m_events->adoptHandler(m_events->forServer().keyboardBroadcast(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleKeyboardBroadcastEvent)); + m_events->adoptHandler(m_events->forServer().lockCursorToScreen(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleLockCursorToScreenEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputBegin(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleFakeInputBeginEvent)); + m_events->adoptHandler(m_events->forIPrimaryScreen().fakeInputEnd(), + m_inputFilter, + new TMethodEventJob<Server>(this, + &Server::handleFakeInputEndEvent)); + + if (m_args.m_enableDragDrop) { + m_events->adoptHandler(m_events->forFile().fileChunkSending(), + this, + new TMethodEventJob<Server>(this, + &Server::handleFileChunkSendingEvent)); + m_events->adoptHandler(m_events->forFile().fileRecieveCompleted(), + this, + new TMethodEventJob<Server>(this, + &Server::handleFileRecieveCompletedEvent)); + } + + // add connection + addClient(m_primaryClient); + + // set initial configuration + setConfig(config); + + // enable primary client + m_primaryClient->enable(); + m_inputFilter->setPrimaryClient(m_primaryClient); + + // Determine if scroll lock is already set. If so, lock the cursor to the primary screen + if (m_primaryClient->getToggleMask() & KeyModifierScrollLock) { + LOG((CLOG_NOTE "Scroll Lock is on, locking cursor to screen")); + m_lockedToScreen = true; + } + +} + +Server::~Server() +{ + if (m_mock) { + return; + } + + // remove event handlers and timers + m_events->removeHandler(m_events->forIKeyState().keyDown(), + m_inputFilter); + m_events->removeHandler(m_events->forIKeyState().keyUp(), + m_inputFilter); + m_events->removeHandler(m_events->forIKeyState().keyRepeat(), + m_inputFilter); + m_events->removeHandler(m_events->forIPrimaryScreen().buttonDown(), + m_inputFilter); + m_events->removeHandler(m_events->forIPrimaryScreen().buttonUp(), + m_inputFilter); + m_events->removeHandler(m_events->forIPrimaryScreen().motionOnPrimary(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().motionOnSecondary(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().wheel(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().screensaverActivated(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().screensaverDeactivated(), + m_primaryClient->getEventTarget()); + m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputBegin(), + m_inputFilter); + m_events->removeHandler(m_events->forIPrimaryScreen().fakeInputEnd(), + m_inputFilter); + m_events->removeHandler(Event::kTimer, this); + stopSwitch(); + + // force immediate disconnection of secondary clients + disconnect(); + for (OldClients::iterator index = m_oldClients.begin(); + index != m_oldClients.end(); ++index) { + BaseClientProxy* client = index->first; + m_events->deleteTimer(index->second); + m_events->removeHandler(Event::kTimer, client); + m_events->removeHandler(m_events->forClientProxy().disconnected(), client); + delete client; + } + + // remove input filter + m_inputFilter->setPrimaryClient(NULL); + + // disable and disconnect primary client + m_primaryClient->disable(); + removeClient(m_primaryClient); +} + +bool +Server::setConfig(const Config& config) +{ + // refuse configuration if it doesn't include the primary screen + if (!config.isScreen(m_primaryClient->getName())) { + return false; + } + + // close clients that are connected but being dropped from the + // configuration. + closeClients(config); + + // cut over + processOptions(); + + // add ScrollLock as a hotkey to lock to the screen. this was a + // built-in feature in earlier releases and is now supported via + // the user configurable hotkey mechanism. if the user has already + // registered ScrollLock for something else then that will win but + // we will unfortunately generate a warning. if the user has + // configured a LockCursorToScreenAction then we don't add + // ScrollLock as a hotkey. + if (!m_config->hasLockToScreenAction()) { + IPlatformScreen::KeyInfo* key = + IPlatformScreen::KeyInfo::alloc(kKeyScrollLock, 0, 0, 0); + InputFilter::Rule rule(new InputFilter::KeystrokeCondition(m_events, key)); + rule.adoptAction(new InputFilter::LockCursorToScreenAction(m_events), true); + m_inputFilter->addFilterRule(rule); + } + + // tell primary screen about reconfiguration + m_primaryClient->reconfigure(getActivePrimarySides()); + + // tell all (connected) clients about current options + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + BaseClientProxy* client = index->second; + sendOptions(client); + } + + return true; +} + +void +Server::adoptClient(BaseClientProxy* client) +{ + assert(client != NULL); + + // watch for client disconnection + m_events->adoptHandler(m_events->forClientProxy().disconnected(), client, + new TMethodEventJob<Server>(this, + &Server::handleClientDisconnected, client)); + + // name must be in our configuration + if (!m_config->isScreen(client->getName())) { + LOG((CLOG_WARN "unrecognised client name \"%s\", check server config", client->getName().c_str())); + closeClient(client, kMsgEUnknown); + return; + } + + // add client to client list + if (!addClient(client)) { + // can only have one screen with a given name at any given time + LOG((CLOG_WARN "a client with name \"%s\" is already connected", getName(client).c_str())); + closeClient(client, kMsgEBusy); + return; + } + LOG((CLOG_NOTE "client \"%s\" has connected", getName(client).c_str())); + + // send configuration options to client + sendOptions(client); + + // activate screen saver on new client if active on the primary screen + if (m_activeSaver != NULL) { + client->screensaver(true); + } + + // send notification + Server::ScreenConnectedInfo* info = + new Server::ScreenConnectedInfo(getName(client)); + m_events->addEvent(Event(m_events->forServer().connected(), + m_primaryClient->getEventTarget(), info)); +} + +void +Server::disconnect() +{ + // close all secondary clients + if (m_clients.size() > 1 || !m_oldClients.empty()) { + Config emptyConfig(m_events); + closeClients(emptyConfig); + } + else { + m_events->addEvent(Event(m_events->forServer().disconnected(), this)); + } +} + +UInt32 +Server::getNumClients() const +{ + return (SInt32)m_clients.size(); +} + +void +Server::getClients(std::vector<String>& list) const +{ + list.clear(); + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + list.push_back(index->first); + } +} + +String +Server::getName(const BaseClientProxy* client) const +{ + String name = m_config->getCanonicalName(client->getName()); + if (name.empty()) { + name = client->getName(); + } + return name; +} + +UInt32 +Server::getActivePrimarySides() const +{ + UInt32 sides = 0; + if (!isLockedToScreenServer()) { + if (hasAnyNeighbor(m_primaryClient, kLeft)) { + sides |= kLeftMask; + } + if (hasAnyNeighbor(m_primaryClient, kRight)) { + sides |= kRightMask; + } + if (hasAnyNeighbor(m_primaryClient, kTop)) { + sides |= kTopMask; + } + if (hasAnyNeighbor(m_primaryClient, kBottom)) { + sides |= kBottomMask; + } + } + return sides; +} + +bool +Server::isLockedToScreenServer() const +{ + // locked if scroll-lock is toggled on + return m_lockedToScreen; +} + +bool +Server::isLockedToScreen() const +{ + // locked if we say we're locked + if (isLockedToScreenServer()) { + return true; + } + + // locked if primary says we're locked + if (m_primaryClient->isLockedToScreen()) { + return true; + } + + // not locked + return false; +} + +SInt32 +Server::getJumpZoneSize(BaseClientProxy* client) const +{ + if (client == m_primaryClient) { + return m_primaryClient->getJumpZoneSize(); + } + else { + return 0; + } +} + +void +Server::switchScreen(BaseClientProxy* dst, + SInt32 x, SInt32 y, bool forScreensaver) +{ + assert(dst != NULL); + +#ifndef NDEBUG + { + SInt32 dx, dy, dw, dh; + dst->getShape(dx, dy, dw, dh); + assert(x >= dx && y >= dy && x < dx + dw && y < dy + dh); + } +#endif + assert(m_active != NULL); + + LOG((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", getName(m_active).c_str(), getName(dst).c_str(), x, y)); + + // stop waiting to switch + stopSwitch(); + + // record new position + m_x = x; + m_y = y; + m_xDelta = 0; + m_yDelta = 0; + m_xDelta2 = 0; + m_yDelta2 = 0; + + // wrapping means leaving the active screen and entering it again. + // since that's a waste of time we skip that and just warp the + // mouse. + if (m_active != dst) { + // leave active screen + if (!m_active->leave()) { + // cannot leave screen + LOG((CLOG_WARN "can't leave screen")); + return; + } + + // update the primary client's clipboards if we're leaving the + // primary screen. + if (m_active == m_primaryClient && m_enableClipboard) { + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + ClipboardInfo& clipboard = m_clipboards[id]; + if (clipboard.m_clipboardOwner == getName(m_primaryClient)) { + onClipboardChanged(m_primaryClient, + id, clipboard.m_clipboardSeqNum); + } + } + } + + // cut over + m_active = dst; + + // increment enter sequence number + ++m_seqNum; + + // enter new screen + m_active->enter(x, y, m_seqNum, + m_primaryClient->getToggleMask(), + forScreensaver); + + if (m_enableClipboard) { + // send the clipboard data to new active screen + for (ClipboardID id = 0; id < kClipboardEnd; ++id) { + m_active->setClipboard(id, &m_clipboards[id].m_clipboard); + } + } + + Server::SwitchToScreenInfo* info = + Server::SwitchToScreenInfo::alloc(m_active->getName()); + m_events->addEvent(Event(m_events->forServer().screenSwitched(), this, info)); + } + else { + m_active->mouseMove(x, y); + } +} + +void +Server::jumpToScreen(BaseClientProxy* newScreen) +{ + assert(newScreen != NULL); + + // record the current cursor position on the active screen + m_active->setJumpCursorPos(m_x, m_y); + + // get the last cursor position on the target screen + SInt32 x, y; + newScreen->getJumpCursorPos(x, y); + + switchScreen(newScreen, x, y, false); +} + +float +Server::mapToFraction(BaseClientProxy* client, + EDirection dir, SInt32 x, SInt32 y) const +{ + SInt32 sx, sy, sw, sh; + client->getShape(sx, sy, sw, sh); + switch (dir) { + case kLeft: + case kRight: + return static_cast<float>(y - sy + 0.5f) / static_cast<float>(sh); + + case kTop: + case kBottom: + return static_cast<float>(x - sx + 0.5f) / static_cast<float>(sw); + + case kNoDirection: + assert(0 && "bad direction"); + break; + } + return 0.0f; +} + +void +Server::mapToPixel(BaseClientProxy* client, + EDirection dir, float f, SInt32& x, SInt32& y) const +{ + SInt32 sx, sy, sw, sh; + client->getShape(sx, sy, sw, sh); + switch (dir) { + case kLeft: + case kRight: + y = static_cast<SInt32>(f * sh) + sy; + break; + + case kTop: + case kBottom: + x = static_cast<SInt32>(f * sw) + sx; + break; + + case kNoDirection: + assert(0 && "bad direction"); + break; + } +} + +bool +Server::hasAnyNeighbor(BaseClientProxy* client, EDirection dir) const +{ + assert(client != NULL); + + return m_config->hasNeighbor(getName(client), dir); +} + +BaseClientProxy* +Server::getNeighbor(BaseClientProxy* src, + EDirection dir, SInt32& x, SInt32& y) const +{ + // note -- must be locked on entry + + assert(src != NULL); + + // get source screen name + String srcName = getName(src); + assert(!srcName.empty()); + LOG((CLOG_DEBUG2 "find neighbor on %s of \"%s\"", Config::dirName(dir), srcName.c_str())); + + // convert position to fraction + float t = mapToFraction(src, dir, x, y); + + // search for the closest neighbor that exists in direction dir + float tTmp; + for (;;) { + String dstName(m_config->getNeighbor(srcName, dir, t, &tTmp)); + + // if nothing in that direction then return NULL. if the + // destination is the source then we can make no more + // progress in this direction. since we haven't found a + // connected neighbor we return NULL. + if (dstName.empty()) { + LOG((CLOG_DEBUG2 "no neighbor on %s of \"%s\"", Config::dirName(dir), srcName.c_str())); + return NULL; + } + + // look up neighbor cell. if the screen is connected and + // ready then we can stop. + ClientList::const_iterator index = m_clients.find(dstName); + if (index != m_clients.end()) { + LOG((CLOG_DEBUG2 "\"%s\" is on %s of \"%s\" at %f", dstName.c_str(), Config::dirName(dir), srcName.c_str(), t)); + mapToPixel(index->second, dir, tTmp, x, y); + return index->second; + } + + // skip over unconnected screen + LOG((CLOG_DEBUG2 "ignored \"%s\" on %s of \"%s\"", dstName.c_str(), Config::dirName(dir), srcName.c_str())); + srcName = dstName; + + // use position on skipped screen + t = tTmp; + } +} + +BaseClientProxy* +Server::mapToNeighbor(BaseClientProxy* src, + EDirection srcSide, SInt32& x, SInt32& y) const +{ + // note -- must be locked on entry + + assert(src != NULL); + + // get the first neighbor + BaseClientProxy* dst = getNeighbor(src, srcSide, x, y); + if (dst == NULL) { + return NULL; + } + + // get the source screen's size + SInt32 dx, dy, dw, dh; + BaseClientProxy* lastGoodScreen = src; + lastGoodScreen->getShape(dx, dy, dw, dh); + + // find destination screen, adjusting x or y (but not both). the + // searches are done in a sort of canonical screen space where + // the upper-left corner is 0,0 for each screen. we adjust from + // actual to canonical position on entry to and from canonical to + // actual on exit from the search. + switch (srcSide) { + case kLeft: + x -= dx; + while (dst != NULL) { + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + x += dw; + if (x >= 0) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + x += dx; + break; + + case kRight: + x -= dx; + while (dst != NULL) { + x -= dw; + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + if (x < dw) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + x += dx; + break; + + case kTop: + y -= dy; + while (dst != NULL) { + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + y += dh; + if (y >= 0) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + y += dy; + break; + + case kBottom: + y -= dy; + while (dst != NULL) { + y -= dh; + lastGoodScreen = dst; + lastGoodScreen->getShape(dx, dy, dw, dh); + if (y < dh) { + break; + } + LOG((CLOG_DEBUG2 "skipping over screen %s", getName(dst).c_str())); + dst = getNeighbor(lastGoodScreen, srcSide, x, y); + } + assert(lastGoodScreen != NULL); + y += dy; + break; + + case kNoDirection: + assert(0 && "bad direction"); + return NULL; + } + + // save destination screen + assert(lastGoodScreen != NULL); + dst = lastGoodScreen; + + // if entering primary screen then be sure to move in far enough + // to avoid the jump zone. if entering a side that doesn't have + // a neighbor (i.e. an asymmetrical side) then we don't need to + // move inwards because that side can't provoke a jump. + avoidJumpZone(dst, srcSide, x, y); + + return dst; +} + +void +Server::avoidJumpZone(BaseClientProxy* dst, + EDirection dir, SInt32& x, SInt32& y) const +{ + // we only need to avoid jump zones on the primary screen + if (dst != m_primaryClient) { + return; + } + + const String dstName(getName(dst)); + SInt32 dx, dy, dw, dh; + dst->getShape(dx, dy, dw, dh); + float t = mapToFraction(dst, dir, x, y); + SInt32 z = getJumpZoneSize(dst); + + // move in far enough to avoid the jump zone. if entering a side + // that doesn't have a neighbor (i.e. an asymmetrical side) then we + // don't need to move inwards because that side can't provoke a jump. + switch (dir) { + case kLeft: + if (!m_config->getNeighbor(dstName, kRight, t, NULL).empty() && + x > dx + dw - 1 - z) + x = dx + dw - 1 - z; + break; + + case kRight: + if (!m_config->getNeighbor(dstName, kLeft, t, NULL).empty() && + x < dx + z) + x = dx + z; + break; + + case kTop: + if (!m_config->getNeighbor(dstName, kBottom, t, NULL).empty() && + y > dy + dh - 1 - z) + y = dy + dh - 1 - z; + break; + + case kBottom: + if (!m_config->getNeighbor(dstName, kTop, t, NULL).empty() && + y < dy + z) + y = dy + z; + break; + + case kNoDirection: + assert(0 && "bad direction"); + } +} + +bool +Server::isSwitchOkay(BaseClientProxy* newScreen, + EDirection dir, SInt32 x, SInt32 y, + SInt32 xActive, SInt32 yActive) +{ + LOG((CLOG_DEBUG1 "try to leave \"%s\" on %s", getName(m_active).c_str(), Config::dirName(dir))); + + // is there a neighbor? + if (newScreen == NULL) { + // there's no neighbor. we don't want to switch and we don't + // want to try to switch later. + LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(dir))); + stopSwitch(); + return false; + } + + // should we switch or not? + bool preventSwitch = false; + bool allowSwitch = false; + + // note if the switch direction has changed. save the new + // direction and screen if so. + bool isNewDirection = (dir != m_switchDir); + if (isNewDirection || m_switchScreen == NULL) { + m_switchDir = dir; + m_switchScreen = newScreen; + } + + // is this a double tap and do we care? + if (!allowSwitch && m_switchTwoTapDelay > 0.0) { + if (isNewDirection || + !isSwitchTwoTapStarted() || !shouldSwitchTwoTap()) { + // tapping a different or new edge or second tap not + // fast enough. prepare for second tap. + preventSwitch = true; + startSwitchTwoTap(); + } + else { + // got second tap + allowSwitch = true; + } + } + + // if waiting before a switch then prepare to switch later + if (!allowSwitch && m_switchWaitDelay > 0.0) { + if (isNewDirection || !isSwitchWaitStarted()) { + startSwitchWait(x, y); + } + preventSwitch = true; + } + + // are we in a locked corner? first check if screen has the option set + // and, if not, check the global options. + const Config::ScreenOptions* options = + m_config->getOptions(getName(m_active)); + if (options == NULL || options->count(kOptionScreenSwitchCorners) == 0) { + options = m_config->getOptions(""); + } + if (options != NULL && options->count(kOptionScreenSwitchCorners) > 0) { + // get corner mask and size + Config::ScreenOptions::const_iterator i = + options->find(kOptionScreenSwitchCorners); + UInt32 corners = static_cast<UInt32>(i->second); + i = options->find(kOptionScreenSwitchCornerSize); + SInt32 size = 0; + if (i != options->end()) { + size = i->second; + } + + // see if we're in a locked corner + if ((getCorner(m_active, xActive, yActive, size) & corners) != 0) { + // yep, no switching + LOG((CLOG_DEBUG1 "locked in corner")); + preventSwitch = true; + stopSwitch(); + } + } + + // ignore if mouse is locked to screen and don't try to switch later + if (!preventSwitch && isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + preventSwitch = true; + stopSwitch(); + } + + // check for optional needed modifiers + KeyModifierMask mods = this->m_primaryClient->getToggleMask(); + + if (!preventSwitch && ( + (this->m_switchNeedsShift && ((mods & KeyModifierShift) != KeyModifierShift)) || + (this->m_switchNeedsControl && ((mods & KeyModifierControl) != KeyModifierControl)) || + (this->m_switchNeedsAlt && ((mods & KeyModifierAlt) != KeyModifierAlt)) + )) { + LOG((CLOG_DEBUG1 "need modifiers to switch")); + preventSwitch = true; + stopSwitch(); + } + + return !preventSwitch; +} + +void +Server::noSwitch(SInt32 x, SInt32 y) +{ + armSwitchTwoTap(x, y); + stopSwitchWait(); +} + +void +Server::stopSwitch() +{ + if (m_switchScreen != NULL) { + m_switchScreen = NULL; + m_switchDir = kNoDirection; + stopSwitchTwoTap(); + stopSwitchWait(); + } +} + +void +Server::startSwitchTwoTap() +{ + m_switchTwoTapEngaged = true; + m_switchTwoTapArmed = false; + m_switchTwoTapTimer.reset(); + LOG((CLOG_DEBUG1 "waiting for second tap")); +} + +void +Server::armSwitchTwoTap(SInt32 x, SInt32 y) +{ + if (m_switchTwoTapEngaged) { + if (m_switchTwoTapTimer.getTime() > m_switchTwoTapDelay) { + // second tap took too long. disengage. + stopSwitchTwoTap(); + } + else if (!m_switchTwoTapArmed) { + // still time for a double tap. see if we left the tap + // zone and, if so, arm the two tap. + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 tapZone = m_primaryClient->getJumpZoneSize(); + if (tapZone < m_switchTwoTapZone) { + tapZone = m_switchTwoTapZone; + } + if (x >= ax + tapZone && x < ax + aw - tapZone && + y >= ay + tapZone && y < ay + ah - tapZone) { + // win32 can generate bogus mouse events that appear to + // move in the opposite direction that the mouse actually + // moved. try to ignore that crap here. + switch (m_switchDir) { + case kLeft: + m_switchTwoTapArmed = (m_xDelta > 0 && m_xDelta2 > 0); + break; + + case kRight: + m_switchTwoTapArmed = (m_xDelta < 0 && m_xDelta2 < 0); + break; + + case kTop: + m_switchTwoTapArmed = (m_yDelta > 0 && m_yDelta2 > 0); + break; + + case kBottom: + m_switchTwoTapArmed = (m_yDelta < 0 && m_yDelta2 < 0); + break; + + default: + break; + } + } + } + } +} + +void +Server::stopSwitchTwoTap() +{ + m_switchTwoTapEngaged = false; + m_switchTwoTapArmed = false; +} + +bool +Server::isSwitchTwoTapStarted() const +{ + return m_switchTwoTapEngaged; +} + +bool +Server::shouldSwitchTwoTap() const +{ + // this is the second tap if two-tap is armed and this tap + // came fast enough + return (m_switchTwoTapArmed && + m_switchTwoTapTimer.getTime() <= m_switchTwoTapDelay); +} + +void +Server::startSwitchWait(SInt32 x, SInt32 y) +{ + stopSwitchWait(); + m_switchWaitX = x; + m_switchWaitY = y; + m_switchWaitTimer = m_events->newOneShotTimer(m_switchWaitDelay, this); + LOG((CLOG_DEBUG1 "waiting to switch")); +} + +void +Server::stopSwitchWait() +{ + if (m_switchWaitTimer != NULL) { + m_events->deleteTimer(m_switchWaitTimer); + m_switchWaitTimer = NULL; + } +} + +bool +Server::isSwitchWaitStarted() const +{ + return (m_switchWaitTimer != NULL); +} + +UInt32 +Server::getCorner(BaseClientProxy* client, + SInt32 x, SInt32 y, SInt32 size) const +{ + assert(client != NULL); + + // get client screen shape + SInt32 ax, ay, aw, ah; + client->getShape(ax, ay, aw, ah); + + // check for x,y on the left or right + SInt32 xSide; + if (x <= ax) { + xSide = -1; + } + else if (x >= ax + aw - 1) { + xSide = 1; + } + else { + xSide = 0; + } + + // check for x,y on the top or bottom + SInt32 ySide; + if (y <= ay) { + ySide = -1; + } + else if (y >= ay + ah - 1) { + ySide = 1; + } + else { + ySide = 0; + } + + // if against the left or right then check if y is within size + if (xSide != 0) { + if (y < ay + size) { + return (xSide < 0) ? kTopLeftMask : kTopRightMask; + } + else if (y >= ay + ah - size) { + return (xSide < 0) ? kBottomLeftMask : kBottomRightMask; + } + } + + // if against the left or right then check if y is within size + if (ySide != 0) { + if (x < ax + size) { + return (ySide < 0) ? kTopLeftMask : kBottomLeftMask; + } + else if (x >= ax + aw - size) { + return (ySide < 0) ? kTopRightMask : kBottomRightMask; + } + } + + return kNoCornerMask; +} + +void +Server::stopRelativeMoves() +{ + if (m_relativeMoves && m_active != m_primaryClient) { + // warp to the center of the active client so we know where we are + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + m_x = ax + (aw >> 1); + m_y = ay + (ah >> 1); + m_xDelta = 0; + m_yDelta = 0; + m_xDelta2 = 0; + m_yDelta2 = 0; + LOG((CLOG_DEBUG2 "synchronize move on %s by %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } +} + +void +Server::sendOptions(BaseClientProxy* client) const +{ + OptionsList optionsList; + + // look up options for client + const Config::ScreenOptions* options = + m_config->getOptions(getName(client)); + if (options != NULL) { + // convert options to a more convenient form for sending + optionsList.reserve(2 * options->size()); + for (Config::ScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast<UInt32>(index->second)); + } + } + + // look up global options + options = m_config->getOptions(""); + if (options != NULL) { + // convert options to a more convenient form for sending + optionsList.reserve(optionsList.size() + 2 * options->size()); + for (Config::ScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + optionsList.push_back(index->first); + optionsList.push_back(static_cast<UInt32>(index->second)); + } + } + + // send the options + client->resetOptions(); + client->setOptions(optionsList); +} + +void +Server::processOptions() +{ + const Config::ScreenOptions* options = m_config->getOptions(""); + if (options == NULL) { + return; + } + + m_switchNeedsShift = false; // it seems if i don't add these + m_switchNeedsControl = false; // lines, the 'reload config' option + m_switchNeedsAlt = false; // doesnt' work correct. + + bool newRelativeMoves = m_relativeMoves; + for (Config::ScreenOptions::const_iterator index = options->begin(); + index != options->end(); ++index) { + const OptionID id = index->first; + const OptionValue value = index->second; + if (id == kOptionScreenSwitchDelay) { + m_switchWaitDelay = 1.0e-3 * static_cast<double>(value); + if (m_switchWaitDelay < 0.0) { + m_switchWaitDelay = 0.0; + } + stopSwitchWait(); + } + else if (id == kOptionScreenSwitchTwoTap) { + m_switchTwoTapDelay = 1.0e-3 * static_cast<double>(value); + if (m_switchTwoTapDelay < 0.0) { + m_switchTwoTapDelay = 0.0; + } + stopSwitchTwoTap(); + } + else if (id == kOptionScreenSwitchNeedsControl) { + m_switchNeedsControl = (value != 0); + } + else if (id == kOptionScreenSwitchNeedsShift) { + m_switchNeedsShift = (value != 0); + } + else if (id == kOptionScreenSwitchNeedsAlt) { + m_switchNeedsAlt = (value != 0); + } + else if (id == kOptionRelativeMouseMoves) { + newRelativeMoves = (value != 0); + } + else if (id == kOptionClipboardSharing) { + m_enableClipboard = (value != 0); + + if (m_enableClipboard == false) { + LOG((CLOG_NOTE "clipboard sharing is disabled")); + } + } + } + if (m_relativeMoves && !newRelativeMoves) { + stopRelativeMoves(); + } + m_relativeMoves = newRelativeMoves; +} + +void +Server::handleShapeChanged(const Event&, void* vclient) +{ + // ignore events from unknown clients + BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient); + if (m_clientSet.count(client) == 0) { + return; + } + + LOG((CLOG_DEBUG "screen \"%s\" shape changed", getName(client).c_str())); + + // update jump coordinate + SInt32 x, y; + client->getCursorPos(x, y); + client->setJumpCursorPos(x, y); + + // update the mouse coordinates + if (client == m_active) { + m_x = x; + m_y = y; + } + + // handle resolution change to primary screen + if (client == m_primaryClient) { + if (client == m_active) { + onMouseMovePrimary(m_x, m_y); + } + else { + onMouseMoveSecondary(0, 0); + } + } +} + +void +Server::handleClipboardGrabbed(const Event& event, void* vclient) +{ + if (!m_enableClipboard) { + return; + } + + // ignore events from unknown clients + BaseClientProxy* grabber = static_cast<BaseClientProxy*>(vclient); + if (m_clientSet.count(grabber) == 0) { + return; + } + const IScreen::ClipboardInfo* info = + static_cast<const IScreen::ClipboardInfo*>(event.getData()); + + // ignore grab if sequence number is old. always allow primary + // screen to grab. + ClipboardInfo& clipboard = m_clipboards[info->m_id]; + if (grabber != m_primaryClient && + info->m_sequenceNumber < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" grab of clipboard %d", getName(grabber).c_str(), info->m_id)); + return; + } + + // mark screen as owning clipboard + LOG((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", getName(grabber).c_str(), info->m_id, clipboard.m_clipboardOwner.c_str())); + clipboard.m_clipboardOwner = getName(grabber); + clipboard.m_clipboardSeqNum = info->m_sequenceNumber; + + // clear the clipboard data (since it's not known at this point) + if (clipboard.m_clipboard.open(0)) { + clipboard.m_clipboard.empty(); + clipboard.m_clipboard.close(); + } + clipboard.m_clipboardData = clipboard.m_clipboard.marshall(); + + // tell all other screens to take ownership of clipboard. tell the + // grabber that it's clipboard isn't dirty. + for (ClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + BaseClientProxy* client = index->second; + if (client == grabber) { + client->setClipboardDirty(info->m_id, false); + } + else { + client->grabClipboard(info->m_id); + } + } +} + +void +Server::handleClipboardChanged(const Event& event, void* vclient) +{ + // ignore events from unknown clients + BaseClientProxy* sender = static_cast<BaseClientProxy*>(vclient); + if (m_clientSet.count(sender) == 0) { + return; + } + const IScreen::ClipboardInfo* info = + static_cast<const IScreen::ClipboardInfo*>(event.getData()); + onClipboardChanged(sender, info->m_id, info->m_sequenceNumber); +} + +void +Server::handleKeyDownEvent(const Event& event, void*) +{ + IPlatformScreen::KeyInfo* info = + static_cast<IPlatformScreen::KeyInfo*>(event.getData()); + onKeyDown(info->m_key, info->m_mask, info->m_button, info->m_screens); +} + +void +Server::handleKeyUpEvent(const Event& event, void*) +{ + IPlatformScreen::KeyInfo* info = + static_cast<IPlatformScreen::KeyInfo*>(event.getData()); + onKeyUp(info->m_key, info->m_mask, info->m_button, info->m_screens); +} + +void +Server::handleKeyRepeatEvent(const Event& event, void*) +{ + IPlatformScreen::KeyInfo* info = + static_cast<IPlatformScreen::KeyInfo*>(event.getData()); + onKeyRepeat(info->m_key, info->m_mask, info->m_count, info->m_button); +} + +void +Server::handleButtonDownEvent(const Event& event, void*) +{ + IPlatformScreen::ButtonInfo* info = + static_cast<IPlatformScreen::ButtonInfo*>(event.getData()); + onMouseDown(info->m_button); +} + +void +Server::handleButtonUpEvent(const Event& event, void*) +{ + IPlatformScreen::ButtonInfo* info = + static_cast<IPlatformScreen::ButtonInfo*>(event.getData()); + onMouseUp(info->m_button); +} + +void +Server::handleMotionPrimaryEvent(const Event& event, void*) +{ + IPlatformScreen::MotionInfo* info = + static_cast<IPlatformScreen::MotionInfo*>(event.getData()); + onMouseMovePrimary(info->m_x, info->m_y); +} + +void +Server::handleMotionSecondaryEvent(const Event& event, void*) +{ + IPlatformScreen::MotionInfo* info = + static_cast<IPlatformScreen::MotionInfo*>(event.getData()); + onMouseMoveSecondary(info->m_x, info->m_y); +} + +void +Server::handleWheelEvent(const Event& event, void*) +{ + IPlatformScreen::WheelInfo* info = + static_cast<IPlatformScreen::WheelInfo*>(event.getData()); + onMouseWheel(info->m_xDelta, info->m_yDelta); +} + +void +Server::handleScreensaverActivatedEvent(const Event&, void*) +{ + onScreensaver(true); +} + +void +Server::handleScreensaverDeactivatedEvent(const Event&, void*) +{ + onScreensaver(false); +} + +void +Server::handleSwitchWaitTimeout(const Event&, void*) +{ + // ignore if mouse is locked to screen + if (isLockedToScreen()) { + LOG((CLOG_DEBUG1 "locked to screen")); + stopSwitch(); + return; + } + + // switch screen + switchScreen(m_switchScreen, m_switchWaitX, m_switchWaitY, false); +} + +void +Server::handleClientDisconnected(const Event&, void* vclient) +{ + // client has disconnected. it might be an old client or an + // active client. we don't care so just handle it both ways. + BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient); + removeActiveClient(client); + removeOldClient(client); + + delete client; +} + +void +Server::handleClientCloseTimeout(const Event&, void* vclient) +{ + // client took too long to disconnect. just dump it. + BaseClientProxy* client = static_cast<BaseClientProxy*>(vclient); + LOG((CLOG_NOTE "forced disconnection of client \"%s\"", getName(client).c_str())); + removeOldClient(client); + + delete client; +} + +void +Server::handleSwitchToScreenEvent(const Event& event, void*) +{ + SwitchToScreenInfo* info = + static_cast<SwitchToScreenInfo*>(event.getData()); + + ClientList::const_iterator index = m_clients.find(info->m_screen); + if (index == m_clients.end()) { + LOG((CLOG_DEBUG1 "screen \"%s\" not active", info->m_screen)); + } + else { + jumpToScreen(index->second); + } +} + +void +Server::handleSwitchInDirectionEvent(const Event& event, void*) +{ + SwitchInDirectionInfo* info = + static_cast<SwitchInDirectionInfo*>(event.getData()); + + // jump to screen in chosen direction from center of this screen + SInt32 x = m_x, y = m_y; + BaseClientProxy* newScreen = + getNeighbor(m_active, info->m_direction, x, y); + if (newScreen == NULL) { + LOG((CLOG_DEBUG1 "no neighbor %s", Config::dirName(info->m_direction))); + } + else { + jumpToScreen(newScreen); + } +} + +void +Server::handleKeyboardBroadcastEvent(const Event& event, void*) +{ + KeyboardBroadcastInfo* info = (KeyboardBroadcastInfo*)event.getData(); + + // choose new state + bool newState; + switch (info->m_state) { + case KeyboardBroadcastInfo::kOff: + newState = false; + break; + + default: + case KeyboardBroadcastInfo::kOn: + newState = true; + break; + + case KeyboardBroadcastInfo::kToggle: + newState = !m_keyboardBroadcasting; + break; + } + + // enter new state + if (newState != m_keyboardBroadcasting || + info->m_screens != m_keyboardBroadcastingScreens) { + m_keyboardBroadcasting = newState; + m_keyboardBroadcastingScreens = info->m_screens; + LOG((CLOG_DEBUG "keyboard broadcasting %s: %s", m_keyboardBroadcasting ? "on" : "off", m_keyboardBroadcastingScreens.c_str())); + } +} + +void +Server::handleLockCursorToScreenEvent(const Event& event, void*) +{ + LockCursorToScreenInfo* info = (LockCursorToScreenInfo*)event.getData(); + + // choose new state + bool newState; + switch (info->m_state) { + case LockCursorToScreenInfo::kOff: + newState = false; + break; + + default: + case LockCursorToScreenInfo::kOn: + newState = true; + break; + + case LockCursorToScreenInfo::kToggle: + newState = !m_lockedToScreen; + break; + } + + // enter new state + if (newState != m_lockedToScreen) { + m_lockedToScreen = newState; + LOG((CLOG_NOTE "cursor %s current screen", m_lockedToScreen ? "locked to" : "unlocked from")); + + m_primaryClient->reconfigure(getActivePrimarySides()); + if (!isLockedToScreenServer()) { + stopRelativeMoves(); + } + } +} + +void +Server::handleFakeInputBeginEvent(const Event&, void*) +{ + m_primaryClient->fakeInputBegin(); +} + +void +Server::handleFakeInputEndEvent(const Event&, void*) +{ + m_primaryClient->fakeInputEnd(); +} + +void +Server::handleFileChunkSendingEvent(const Event& event, void*) +{ + onFileChunkSending(event.getData()); +} + +void +Server::handleFileRecieveCompletedEvent(const Event& event, void*) +{ + onFileRecieveCompleted(); +} + +void +Server::onClipboardChanged(BaseClientProxy* sender, + ClipboardID id, UInt32 seqNum) +{ + ClipboardInfo& clipboard = m_clipboards[id]; + + // ignore update if sequence number is old + if (seqNum < clipboard.m_clipboardSeqNum) { + LOG((CLOG_INFO "ignored screen \"%s\" update of clipboard %d (missequenced)", getName(sender).c_str(), id)); + return; + } + + // should be the expected client + assert(sender == m_clients.find(clipboard.m_clipboardOwner)->second); + + // get data + sender->getClipboard(id, &clipboard.m_clipboard); + + // ignore if data hasn't changed + String data = clipboard.m_clipboard.marshall(); + if (data == clipboard.m_clipboardData) { + LOG((CLOG_DEBUG "ignored screen \"%s\" update of clipboard %d (unchanged)", clipboard.m_clipboardOwner.c_str(), id)); + return; + } + + // got new data + LOG((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id)); + clipboard.m_clipboardData = data; + + // tell all clients except the sender that the clipboard is dirty + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + BaseClientProxy* client = index->second; + client->setClipboardDirty(id, client != sender); + } + + // send the new clipboard to the active screen + m_active->setClipboard(id, &clipboard.m_clipboard); +} + +void +Server::onScreensaver(bool activated) +{ + LOG((CLOG_DEBUG "onScreenSaver %s", activated ? "activated" : "deactivated")); + + if (activated) { + // save current screen and position + m_activeSaver = m_active; + m_xSaver = m_x; + m_ySaver = m_y; + + // jump to primary screen + if (m_active != m_primaryClient) { + switchScreen(m_primaryClient, 0, 0, true); + } + } + else { + // jump back to previous screen and position. we must check + // that the position is still valid since the screen may have + // changed resolutions while the screen saver was running. + if (m_activeSaver != NULL && m_activeSaver != m_primaryClient) { + // check position + BaseClientProxy* screen = m_activeSaver; + SInt32 x, y, w, h; + screen->getShape(x, y, w, h); + SInt32 zoneSize = getJumpZoneSize(screen); + if (m_xSaver < x + zoneSize) { + m_xSaver = x + zoneSize; + } + else if (m_xSaver >= x + w - zoneSize) { + m_xSaver = x + w - zoneSize - 1; + } + if (m_ySaver < y + zoneSize) { + m_ySaver = y + zoneSize; + } + else if (m_ySaver >= y + h - zoneSize) { + m_ySaver = y + h - zoneSize - 1; + } + + // jump + switchScreen(screen, m_xSaver, m_ySaver, false); + } + + // reset state + m_activeSaver = NULL; + } + + // send message to all clients + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + BaseClientProxy* client = index->second; + client->screensaver(activated); + } +} + +void +Server::onKeyDown(KeyID id, KeyModifierMask mask, KeyButton button, + const char* screens) +{ + LOG((CLOG_DEBUG1 "onKeyDown id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // relay + if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) { + m_active->keyDown(id, mask, button); + } + else { + if (!screens && m_keyboardBroadcasting) { + screens = m_keyboardBroadcastingScreens.c_str(); + if (IKeyState::KeyInfo::isDefault(screens)) { + screens = "*"; + } + } + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (IKeyState::KeyInfo::contains(screens, index->first)) { + index->second->keyDown(id, mask, button); + } + } + } +} + +void +Server::onKeyUp(KeyID id, KeyModifierMask mask, KeyButton button, + const char* screens) +{ + LOG((CLOG_DEBUG1 "onKeyUp id=%d mask=0x%04x button=0x%04x", id, mask, button)); + assert(m_active != NULL); + + // relay + if (!m_keyboardBroadcasting && IKeyState::KeyInfo::isDefault(screens)) { + m_active->keyUp(id, mask, button); + } + else { + if (!screens && m_keyboardBroadcasting) { + screens = m_keyboardBroadcastingScreens.c_str(); + if (IKeyState::KeyInfo::isDefault(screens)) { + screens = "*"; + } + } + for (ClientList::const_iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (IKeyState::KeyInfo::contains(screens, index->first)) { + index->second->keyUp(id, mask, button); + } + } + } +} + +void +Server::onKeyRepeat(KeyID id, KeyModifierMask mask, + SInt32 count, KeyButton button) +{ + LOG((CLOG_DEBUG1 "onKeyRepeat id=%d mask=0x%04x count=%d button=0x%04x", id, mask, count, button)); + assert(m_active != NULL); + + // relay + m_active->keyRepeat(id, mask, count, button); +} + +void +Server::onMouseDown(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseDown id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseDown(id); + + // reset this variable back to default value true + m_waitDragInfoThread = true; +} + +void +Server::onMouseUp(ButtonID id) +{ + LOG((CLOG_DEBUG1 "onMouseUp id=%d", id)); + assert(m_active != NULL); + + // relay + m_active->mouseUp(id); + + if (m_ignoreFileTransfer) { + m_ignoreFileTransfer = false; + return; + } + + if (m_args.m_enableDragDrop) { + if (!m_screen->isOnScreen()) { + String& file = m_screen->getDraggingFilename(); + if (!file.empty()) { + sendFileToClient(file.c_str()); + } + } + + // always clear dragging filename + m_screen->clearDraggingFilename(); + } +} + +bool +Server::onMouseMovePrimary(SInt32 x, SInt32 y) +{ + LOG((CLOG_DEBUG4 "onMouseMovePrimary %d,%d", x, y)); + + // mouse move on primary (server's) screen + if (m_active != m_primaryClient) { + // stale event -- we're actually on a secondary screen + return false; + } + + // save last delta + m_xDelta2 = m_xDelta; + m_yDelta2 = m_yDelta; + + // save current delta + m_xDelta = x - m_x; + m_yDelta = y - m_y; + + // save position + m_x = x; + m_y = y; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + SInt32 zoneSize = getJumpZoneSize(m_active); + + // clamp position to screen + SInt32 xc = x, yc = y; + if (xc < ax + zoneSize) { + xc = ax; + } + else if (xc >= ax + aw - zoneSize) { + xc = ax + aw - 1; + } + if (yc < ay + zoneSize) { + yc = ay; + } + else if (yc >= ay + ah - zoneSize) { + yc = ay + ah - 1; + } + + // see if we should change screens + // when the cursor is in a corner, there may be a screen either + // horizontally or vertically. check both directions. + EDirection dirh = kNoDirection, dirv = kNoDirection; + SInt32 xh = x, yv = y; + if (x < ax + zoneSize) { + xh -= zoneSize; + dirh = kLeft; + } + else if (x >= ax + aw - zoneSize) { + xh += zoneSize; + dirh = kRight; + } + if (y < ay + zoneSize) { + yv -= zoneSize; + dirv = kTop; + } + else if (y >= ay + ah - zoneSize) { + yv += zoneSize; + dirv = kBottom; + } + if (dirh == kNoDirection && dirv == kNoDirection) { + // still on local screen + noSwitch(x, y); + return false; + } + + // check both horizontally and vertically + EDirection dirs[] = {dirh, dirv}; + SInt32 xs[] = {xh, x}, ys[] = {y, yv}; + for (int i = 0; i < 2; ++i) { + EDirection dir = dirs[i]; + if (dir == kNoDirection) { + continue; + } + x = xs[i], y = ys[i]; + + // get jump destination + BaseClientProxy* newScreen = mapToNeighbor(m_active, dir, x, y); + + // should we switch or not? + if (isSwitchOkay(newScreen, dir, x, y, xc, yc)) { + if (m_args.m_enableDragDrop + && m_screen->isDraggingStarted() + && m_active != newScreen + && m_waitDragInfoThread) { + if (m_sendDragInfoThread == NULL) { + m_sendDragInfoThread = new Thread( + new TMethodJob<Server>( + this, + &Server::sendDragInfoThread, newScreen)); + } + + return false; + } + + // switch screen + switchScreen(newScreen, x, y, false); + m_waitDragInfoThread = true; + return true; + } + } + + return false; +} + +void +Server::sendDragInfoThread(void* arg) +{ + BaseClientProxy* newScreen = static_cast<BaseClientProxy*>(arg); + + m_dragFileList.clear(); + String& dragFileList = m_screen->getDraggingFilename(); + if (!dragFileList.empty()) { + DragInformation di; + di.setFilename(dragFileList); + m_dragFileList.push_back(di); + } + +#if defined(__APPLE__) + // on mac it seems that after faking a LMB up, system would signal back + // to barrier a mouse up event, which doesn't happen on windows. as a + // result, barrier would send dragging file to client twice. This variable + // is used to ignore the first file sending. + m_ignoreFileTransfer = true; +#endif + + // send drag file info to client if there is any + if (m_dragFileList.size() > 0) { + sendDragInfo(newScreen); + m_dragFileList.clear(); + } + m_waitDragInfoThread = false; + m_sendDragInfoThread = NULL; +} + +void +Server::sendDragInfo(BaseClientProxy* newScreen) +{ + String infoString; + UInt32 fileCount = DragInformation::setupDragInfo(m_dragFileList, infoString); + + if (fileCount > 0) { + char* info = NULL; + size_t size = infoString.size(); + info = new char[size]; + memcpy(info, infoString.c_str(), size); + + LOG((CLOG_DEBUG2 "sending drag information to client")); + LOG((CLOG_DEBUG3 "dragging file list: %s", info)); + LOG((CLOG_DEBUG3 "dragging file list string size: %i", size)); + newScreen->sendDragInfo(fileCount, info, size); + } +} + +void +Server::onMouseMoveSecondary(SInt32 dx, SInt32 dy) +{ + LOG((CLOG_DEBUG2 "onMouseMoveSecondary %+d,%+d", dx, dy)); + + // mouse move on secondary (client's) screen + assert(m_active != NULL); + if (m_active == m_primaryClient) { + // stale event -- we're actually on the primary screen + return; + } + + // if doing relative motion on secondary screens and we're locked + // to the screen (which activates relative moves) then send a + // relative mouse motion. when we're doing this we pretend as if + // the mouse isn't actually moving because we're expecting some + // program on the secondary screen to warp the mouse on us, so we + // have no idea where it really is. + if (m_relativeMoves && isLockedToScreenServer()) { + LOG((CLOG_DEBUG2 "relative move on %s by %d,%d", getName(m_active).c_str(), dx, dy)); + m_active->mouseRelativeMove(dx, dy); + return; + } + + // save old position + const SInt32 xOld = m_x; + const SInt32 yOld = m_y; + + // save last delta + m_xDelta2 = m_xDelta; + m_yDelta2 = m_yDelta; + + // save current delta + m_xDelta = dx; + m_yDelta = dy; + + // accumulate motion + m_x += dx; + m_y += dy; + + // get screen shape + SInt32 ax, ay, aw, ah; + m_active->getShape(ax, ay, aw, ah); + + // find direction of neighbor and get the neighbor + bool jump = true; + BaseClientProxy* newScreen; + do { + // clamp position to screen + SInt32 xc = m_x, yc = m_y; + if (xc < ax) { + xc = ax; + } + else if (xc >= ax + aw) { + xc = ax + aw - 1; + } + if (yc < ay) { + yc = ay; + } + else if (yc >= ay + ah) { + yc = ay + ah - 1; + } + + EDirection dir; + if (m_x < ax) { + dir = kLeft; + } + else if (m_x > ax + aw - 1) { + dir = kRight; + } + else if (m_y < ay) { + dir = kTop; + } + else if (m_y > ay + ah - 1) { + dir = kBottom; + } + else { + // we haven't left the screen + newScreen = m_active; + jump = false; + + // if waiting and mouse is not on the border we're waiting + // on then stop waiting. also if it's not on the border + // then arm the double tap. + if (m_switchScreen != NULL) { + bool clearWait; + SInt32 zoneSize = m_primaryClient->getJumpZoneSize(); + switch (m_switchDir) { + case kLeft: + clearWait = (m_x >= ax + zoneSize); + break; + + case kRight: + clearWait = (m_x <= ax + aw - 1 - zoneSize); + break; + + case kTop: + clearWait = (m_y >= ay + zoneSize); + break; + + case kBottom: + clearWait = (m_y <= ay + ah - 1 + zoneSize); + break; + + default: + clearWait = false; + break; + } + if (clearWait) { + // still on local screen + noSwitch(m_x, m_y); + } + } + + // skip rest of block + break; + } + + // try to switch screen. get the neighbor. + newScreen = mapToNeighbor(m_active, dir, m_x, m_y); + + // see if we should switch + if (!isSwitchOkay(newScreen, dir, m_x, m_y, xc, yc)) { + newScreen = m_active; + jump = false; + } + } while (false); + + if (jump) { + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + m_sendFileThread = NULL; + } + + SInt32 newX = m_x; + SInt32 newY = m_y; + + // switch screens + switchScreen(newScreen, newX, newY, false); + } + else { + // same screen. clamp mouse to edge. + m_x = xOld + dx; + m_y = yOld + dy; + if (m_x < ax) { + m_x = ax; + LOG((CLOG_DEBUG2 "clamp to left of \"%s\"", getName(m_active).c_str())); + } + else if (m_x > ax + aw - 1) { + m_x = ax + aw - 1; + LOG((CLOG_DEBUG2 "clamp to right of \"%s\"", getName(m_active).c_str())); + } + if (m_y < ay) { + m_y = ay; + LOG((CLOG_DEBUG2 "clamp to top of \"%s\"", getName(m_active).c_str())); + } + else if (m_y > ay + ah - 1) { + m_y = ay + ah - 1; + LOG((CLOG_DEBUG2 "clamp to bottom of \"%s\"", getName(m_active).c_str())); + } + + // warp cursor if it moved. + if (m_x != xOld || m_y != yOld) { + LOG((CLOG_DEBUG2 "move on %s to %d,%d", getName(m_active).c_str(), m_x, m_y)); + m_active->mouseMove(m_x, m_y); + } + } +} + +void +Server::onMouseWheel(SInt32 xDelta, SInt32 yDelta) +{ + LOG((CLOG_DEBUG1 "onMouseWheel %+d,%+d", xDelta, yDelta)); + assert(m_active != NULL); + + // relay + m_active->mouseWheel(xDelta, yDelta); +} + +void +Server::onFileChunkSending(const void* data) +{ + FileChunk* chunk = static_cast<FileChunk*>(const_cast<void*>(data)); + + LOG((CLOG_DEBUG1 "sending file chunk")); + assert(m_active != NULL); + + // relay + m_active->fileChunkSending(chunk->m_chunk[0], &chunk->m_chunk[1], chunk->m_dataSize); +} + +void +Server::onFileRecieveCompleted() +{ + if (isReceivedFileSizeValid()) { + m_writeToDropDirThread = new Thread( + new TMethodJob<Server>( + this, &Server::writeToDropDirThread)); + } +} + +void +Server::writeToDropDirThread(void*) +{ + LOG((CLOG_DEBUG "starting write to drop dir thread")); + + while (m_screen->isFakeDraggingStarted()) { + ARCH->sleep(.1f); + } + + DropHelper::writeToDir(m_screen->getDropTarget(), m_fakeDragFileList, + m_receivedFileData); +} + +bool +Server::addClient(BaseClientProxy* client) +{ + String name = getName(client); + if (m_clients.count(name) != 0) { + return false; + } + + // add event handlers + m_events->adoptHandler(m_events->forIScreen().shapeChanged(), + client->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleShapeChanged, client)); + m_events->adoptHandler(m_events->forClipboard().clipboardGrabbed(), + client->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleClipboardGrabbed, client)); + m_events->adoptHandler(m_events->forClipboard().clipboardChanged(), + client->getEventTarget(), + new TMethodEventJob<Server>(this, + &Server::handleClipboardChanged, client)); + + // add to list + m_clientSet.insert(client); + m_clients.insert(std::make_pair(name, client)); + + // initialize client data + SInt32 x, y; + client->getCursorPos(x, y); + client->setJumpCursorPos(x, y); + + // tell primary client about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); + + return true; +} + +bool +Server::removeClient(BaseClientProxy* client) +{ + // return false if not in list + ClientSet::iterator i = m_clientSet.find(client); + if (i == m_clientSet.end()) { + return false; + } + + // remove event handlers + m_events->removeHandler(m_events->forIScreen().shapeChanged(), + client->getEventTarget()); + m_events->removeHandler(m_events->forClipboard().clipboardGrabbed(), + client->getEventTarget()); + m_events->removeHandler(m_events->forClipboard().clipboardChanged(), + client->getEventTarget()); + + // remove from list + m_clients.erase(getName(client)); + m_clientSet.erase(i); + + return true; +} + +void +Server::closeClient(BaseClientProxy* client, const char* msg) +{ + assert(client != m_primaryClient); + assert(msg != NULL); + + // send message to client. this message should cause the client + // to disconnect. we add this client to the closed client list + // and install a timer to remove the client if it doesn't respond + // quickly enough. we also remove the client from the active + // client list since we're not going to listen to it anymore. + // note that this method also works on clients that are not in + // the m_clients list. adoptClient() may call us with such a + // client. + LOG((CLOG_NOTE "disconnecting client \"%s\"", getName(client).c_str())); + + // send message + // FIXME -- avoid type cast (kinda hard, though) + ((ClientProxy*)client)->close(msg); + + // install timer. wait timeout seconds for client to close. + double timeout = 5.0; + EventQueueTimer* timer = m_events->newOneShotTimer(timeout, NULL); + m_events->adoptHandler(Event::kTimer, timer, + new TMethodEventJob<Server>(this, + &Server::handleClientCloseTimeout, client)); + + // move client to closing list + removeClient(client); + m_oldClients.insert(std::make_pair(client, timer)); + + // if this client is the active screen then we have to + // jump off of it + forceLeaveClient(client); +} + +void +Server::closeClients(const Config& config) +{ + // collect the clients that are connected but are being dropped + // from the configuration (or who's canonical name is changing). + typedef std::set<BaseClientProxy*> RemovedClients; + RemovedClients removed; + for (ClientList::iterator index = m_clients.begin(); + index != m_clients.end(); ++index) { + if (!config.isCanonicalName(index->first)) { + removed.insert(index->second); + } + } + + // don't close the primary client + removed.erase(m_primaryClient); + + // now close them. we collect the list then close in two steps + // because closeClient() modifies the collection we iterate over. + for (RemovedClients::iterator index = removed.begin(); + index != removed.end(); ++index) { + closeClient(*index, kMsgCClose); + } +} + +void +Server::removeActiveClient(BaseClientProxy* client) +{ + if (removeClient(client)) { + forceLeaveClient(client); + m_events->removeHandler(m_events->forClientProxy().disconnected(), client); + if (m_clients.size() == 1 && m_oldClients.empty()) { + m_events->addEvent(Event(m_events->forServer().disconnected(), this)); + } + } +} + +void +Server::removeOldClient(BaseClientProxy* client) +{ + OldClients::iterator i = m_oldClients.find(client); + if (i != m_oldClients.end()) { + m_events->removeHandler(m_events->forClientProxy().disconnected(), client); + m_events->removeHandler(Event::kTimer, i->second); + m_events->deleteTimer(i->second); + m_oldClients.erase(i); + if (m_clients.size() == 1 && m_oldClients.empty()) { + m_events->addEvent(Event(m_events->forServer().disconnected(), this)); + } + } +} + +void +Server::forceLeaveClient(BaseClientProxy* client) +{ + BaseClientProxy* active = + (m_activeSaver != NULL) ? m_activeSaver : m_active; + if (active == client) { + // record new position (center of primary screen) + m_primaryClient->getCursorCenter(m_x, m_y); + + // stop waiting to switch to this client + if (active == m_switchScreen) { + stopSwitch(); + } + + // don't notify active screen since it has probably already + // disconnected. + LOG((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", getName(active).c_str(), getName(m_primaryClient).c_str(), m_x, m_y)); + + // cut over + m_active = m_primaryClient; + + // enter new screen (unless we already have because of the + // screen saver) + if (m_activeSaver == NULL) { + m_primaryClient->enter(m_x, m_y, m_seqNum, + m_primaryClient->getToggleMask(), false); + } + } + + // if this screen had the cursor when the screen saver activated + // then we can't switch back to it when the screen saver + // deactivates. + if (m_activeSaver == client) { + m_activeSaver = NULL; + } + + // tell primary client about the active sides + m_primaryClient->reconfigure(getActivePrimarySides()); +} + + +// +// Server::ClipboardInfo +// + +Server::ClipboardInfo::ClipboardInfo() : + m_clipboard(), + m_clipboardData(), + m_clipboardOwner(), + m_clipboardSeqNum(0) +{ + // do nothing +} + + +// +// Server::LockCursorToScreenInfo +// + +Server::LockCursorToScreenInfo* +Server::LockCursorToScreenInfo::alloc(State state) +{ + LockCursorToScreenInfo* info = + (LockCursorToScreenInfo*)malloc(sizeof(LockCursorToScreenInfo)); + info->m_state = state; + return info; +} + + +// +// Server::SwitchToScreenInfo +// + +Server::SwitchToScreenInfo* +Server::SwitchToScreenInfo::alloc(const String& screen) +{ + SwitchToScreenInfo* info = + (SwitchToScreenInfo*)malloc(sizeof(SwitchToScreenInfo) + + screen.size()); + strcpy(info->m_screen, screen.c_str()); + return info; +} + + +// +// Server::SwitchInDirectionInfo +// + +Server::SwitchInDirectionInfo* +Server::SwitchInDirectionInfo::alloc(EDirection direction) +{ + SwitchInDirectionInfo* info = + (SwitchInDirectionInfo*)malloc(sizeof(SwitchInDirectionInfo)); + info->m_direction = direction; + return info; +} + +// +// Server::KeyboardBroadcastInfo +// + +Server::KeyboardBroadcastInfo* +Server::KeyboardBroadcastInfo::alloc(State state) +{ + KeyboardBroadcastInfo* info = + (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo)); + info->m_state = state; + info->m_screens[0] = '\0'; + return info; +} + +Server::KeyboardBroadcastInfo* +Server::KeyboardBroadcastInfo::alloc(State state, const String& screens) +{ + KeyboardBroadcastInfo* info = + (KeyboardBroadcastInfo*)malloc(sizeof(KeyboardBroadcastInfo) + + screens.size()); + info->m_state = state; + strcpy(info->m_screens, screens.c_str()); + return info; +} + +bool +Server::isReceivedFileSizeValid() +{ + return m_expectedFileSize == m_receivedFileData.size(); +} + +void +Server::sendFileToClient(const char* filename) +{ + if (m_sendFileThread != NULL) { + StreamChunker::interruptFile(); + } + + m_sendFileThread = new Thread( + new TMethodJob<Server>( + this, &Server::sendFileThread, + static_cast<void*>(const_cast<char*>(filename)))); +} + +void +Server::sendFileThread(void* data) +{ + try { + char* filename = static_cast<char*>(data); + LOG((CLOG_DEBUG "sending file to client, filename=%s", filename)); + StreamChunker::sendFile(filename, m_events, this); + } + catch (std::runtime_error &error) { + LOG((CLOG_ERR "failed sending file chunks, error: %s", error.what())); + } + + m_sendFileThread = NULL; +} + +void +Server::dragInfoReceived(UInt32 fileNum, String content) +{ + if (!m_args.m_enableDragDrop) { + LOG((CLOG_DEBUG "drag drop not enabled, ignoring drag info.")); + return; + } + + DragInformation::parseDragInfo(m_fakeDragFileList, fileNum, content); + + m_screen->startDraggingFiles(m_fakeDragFileList); +} diff --git a/src/lib/server/Server.h b/src/lib/server/Server.h new file mode 100644 index 0000000..609af21 --- /dev/null +++ b/src/lib/server/Server.h @@ -0,0 +1,483 @@ +/* + * 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 "server/Config.h" +#include "barrier/clipboard_types.h" +#include "barrier/Clipboard.h" +#include "barrier/key_types.h" +#include "barrier/mouse_types.h" +#include "barrier/INode.h" +#include "barrier/DragInformation.h" +#include "barrier/ServerArgs.h" +#include "base/Event.h" +#include "base/Stopwatch.h" +#include "base/EventTypes.h" +#include "common/stdmap.h" +#include "common/stdset.h" +#include "common/stdvector.h" + +class BaseClientProxy; +class EventQueueTimer; +class PrimaryClient; +class InputFilter; +namespace barrier { class Screen; } +class IEventQueue; +class Thread; +class ClientListener; + +//! Barrier server +/*! +This class implements the top-level server algorithms for barrier. +*/ +class Server : public INode { +public: + //! Lock cursor to screen data + class LockCursorToScreenInfo { + public: + enum State { kOff, kOn, kToggle }; + + static LockCursorToScreenInfo* alloc(State state = kToggle); + + public: + State m_state; + }; + + //! Switch to screen data + class SwitchToScreenInfo { + public: + static SwitchToScreenInfo* alloc(const String& screen); + + public: + // this is a C-string; this type is a variable size structure + char m_screen[1]; + }; + + //! Switch in direction data + class SwitchInDirectionInfo { + public: + static SwitchInDirectionInfo* alloc(EDirection direction); + + public: + EDirection m_direction; + }; + + //! Screen connected data + class ScreenConnectedInfo { + public: + ScreenConnectedInfo(String screen) : m_screen(screen) { } + + public: + String m_screen; // was char[1] + }; + + //! Keyboard broadcast data + class KeyboardBroadcastInfo { + public: + enum State { kOff, kOn, kToggle }; + + static KeyboardBroadcastInfo* alloc(State state = kToggle); + static KeyboardBroadcastInfo* alloc(State state, + const String& screens); + + public: + State m_state; + char m_screens[1]; + }; + + /*! + Start the server with the configuration \p config and the primary + client (local screen) \p primaryClient. The client retains + ownership of \p primaryClient. + */ + Server(Config& config, PrimaryClient* primaryClient, + barrier::Screen* screen, IEventQueue* events, ServerArgs const& args); + ~Server(); + +#ifdef TEST_ENV + Server() : m_mock(true), m_config(NULL) { } + void setActive(BaseClientProxy* active) { m_active = active; } +#endif + + //! @name manipulators + //@{ + + //! Set configuration + /*! + Change the server's configuration. Returns true iff the new + configuration was accepted (it must include the server's name). + This will disconnect any clients no longer in the configuration. + */ + bool setConfig(const Config&); + + //! Add a client + /*! + Adds \p client to the server. The client is adopted and will be + destroyed when the client disconnects or is disconnected. + */ + void adoptClient(BaseClientProxy* client); + + //! Disconnect clients + /*! + Disconnect clients. This tells them to disconnect but does not wait + for them to actually do so. The server sends the disconnected event + when they're all disconnected (or immediately if none are connected). + The caller can also just destroy this object to force the disconnection. + */ + void disconnect(); + + //! Create a new thread and use it to send file to client + void sendFileToClient(const char* filename); + + //! Received dragging information from client + void dragInfoReceived(UInt32 fileNum, String content); + + //! Store ClientListener pointer + void setListener(ClientListener* p) { m_clientListener = p; } + + //@} + //! @name accessors + //@{ + + //! Get number of connected clients + /*! + Returns the number of connected clients, including the server itself. + */ + UInt32 getNumClients() const; + + //! Get the list of connected clients + /*! + Set the \c list to the names of the currently connected clients. + */ + void getClients(std::vector<String>& list) const; + + //! Return true if recieved file size is valid + bool isReceivedFileSizeValid(); + + //! Return expected file data size + size_t& getExpectedFileSize() { return m_expectedFileSize; } + + //! Return received file data + String& getReceivedFileData() { return m_receivedFileData; } + + //! Return fake drag file list + DragFileList getFakeDragFileList() { return m_fakeDragFileList; } + + //@} + +private: + // get canonical name of client + String getName(const BaseClientProxy*) const; + + // get the sides of the primary screen that have neighbors + UInt32 getActivePrimarySides() const; + + // returns true iff mouse should be locked to the current screen + // according to this object only, ignoring what the primary client + // says. + bool isLockedToScreenServer() const; + + // returns true iff mouse should be locked to the current screen + // according to this object or the primary client. + bool isLockedToScreen() const; + + // returns the jump zone of the client + SInt32 getJumpZoneSize(BaseClientProxy*) const; + + // change the active screen + void switchScreen(BaseClientProxy*, + SInt32 x, SInt32 y, bool forScreenSaver); + + // jump to screen + void jumpToScreen(BaseClientProxy*); + + // convert pixel position to fraction, using x or y depending on the + // direction. + float mapToFraction(BaseClientProxy*, EDirection, + SInt32 x, SInt32 y) const; + + // convert fraction to pixel position, writing only x or y depending + // on the direction. + void mapToPixel(BaseClientProxy*, EDirection, float f, + SInt32& x, SInt32& y) const; + + // returns true if the client has a neighbor anywhere along the edge + // indicated by the direction. + bool hasAnyNeighbor(BaseClientProxy*, EDirection) const; + + // lookup neighboring screen, mapping the coordinate independent of + // the direction to the neighbor's coordinate space. + BaseClientProxy* getNeighbor(BaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // lookup neighboring screen. given a position relative to the + // source screen, find the screen we should move onto and where. + // if the position is sufficiently far from the source then we + // cross multiple screens. if there is no suitable screen then + // return NULL and x,y are not modified. + BaseClientProxy* mapToNeighbor(BaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // adjusts x and y or neither to avoid ending up in a jump zone + // after entering the client in the given direction. + void avoidJumpZone(BaseClientProxy*, EDirection, + SInt32& x, SInt32& y) const; + + // test if a switch is permitted. this includes testing user + // options like switch delay and tracking any state required to + // implement them. returns true iff a switch is permitted. + bool isSwitchOkay(BaseClientProxy* dst, EDirection, + SInt32 x, SInt32 y, SInt32 xActive, SInt32 yActive); + + // update switch state due to a mouse move at \p x, \p y that + // doesn't switch screens. + void noSwitch(SInt32 x, SInt32 y); + + // stop switch timers + void stopSwitch(); + + // start two tap switch timer + void startSwitchTwoTap(); + + // arm the two tap switch timer if \p x, \p y is outside the tap zone + void armSwitchTwoTap(SInt32 x, SInt32 y); + + // stop the two tap switch timer + void stopSwitchTwoTap(); + + // returns true iff the two tap switch timer is started + bool isSwitchTwoTapStarted() const; + + // returns true iff should switch because of two tap + bool shouldSwitchTwoTap() const; + + // start delay switch timer + void startSwitchWait(SInt32 x, SInt32 y); + + // stop delay switch timer + void stopSwitchWait(); + + // returns true iff the delay switch timer is started + bool isSwitchWaitStarted() const; + + // returns the corner (EScreenSwitchCornerMasks) where x,y is on the + // given client. corners have the given size. + UInt32 getCorner(BaseClientProxy*, + SInt32 x, SInt32 y, SInt32 size) const; + + // stop relative mouse moves + void stopRelativeMoves(); + + // send screen options to \c client + void sendOptions(BaseClientProxy* client) const; + + // process options from configuration + void processOptions(); + + // event handlers + void handleShapeChanged(const Event&, void*); + void handleClipboardGrabbed(const Event&, void*); + void handleClipboardChanged(const Event&, void*); + void handleKeyDownEvent(const Event&, void*); + void handleKeyUpEvent(const Event&, void*); + void handleKeyRepeatEvent(const Event&, void*); + void handleButtonDownEvent(const Event&, void*); + void handleButtonUpEvent(const Event&, void*); + void handleMotionPrimaryEvent(const Event&, void*); + void handleMotionSecondaryEvent(const Event&, void*); + void handleWheelEvent(const Event&, void*); + void handleScreensaverActivatedEvent(const Event&, void*); + void handleScreensaverDeactivatedEvent(const Event&, void*); + void handleSwitchWaitTimeout(const Event&, void*); + void handleClientDisconnected(const Event&, void*); + void handleClientCloseTimeout(const Event&, void*); + void handleSwitchToScreenEvent(const Event&, void*); + void handleSwitchInDirectionEvent(const Event&, void*); + void handleKeyboardBroadcastEvent(const Event&,void*); + void handleLockCursorToScreenEvent(const Event&, void*); + void handleFakeInputBeginEvent(const Event&, void*); + void handleFakeInputEndEvent(const Event&, void*); + void handleFileChunkSendingEvent(const Event&, void*); + void handleFileRecieveCompletedEvent(const Event&, void*); + + // event processing + void onClipboardChanged(BaseClientProxy* sender, + ClipboardID id, UInt32 seqNum); + void onScreensaver(bool activated); + void onKeyDown(KeyID, KeyModifierMask, KeyButton, + const char* screens); + void onKeyUp(KeyID, KeyModifierMask, KeyButton, + const char* screens); + void onKeyRepeat(KeyID, KeyModifierMask, SInt32, KeyButton); + void onMouseDown(ButtonID); + void onMouseUp(ButtonID); + bool onMouseMovePrimary(SInt32 x, SInt32 y); + void onMouseMoveSecondary(SInt32 dx, SInt32 dy); + void onMouseWheel(SInt32 xDelta, SInt32 yDelta); + void onFileChunkSending(const void* data); + void onFileRecieveCompleted(); + + // add client to list and attach event handlers for client + bool addClient(BaseClientProxy*); + + // remove client from list and detach event handlers for client + bool removeClient(BaseClientProxy*); + + // close a client + void closeClient(BaseClientProxy*, const char* msg); + + // close clients not in \p config + void closeClients(const Config& config); + + // close all clients whether they've completed the handshake or not, + // except the primary client + void closeAllClients(); + + // remove clients from internal state + void removeActiveClient(BaseClientProxy*); + void removeOldClient(BaseClientProxy*); + + // force the cursor off of \p client + void forceLeaveClient(BaseClientProxy* client); + + // thread funciton for sending file + void sendFileThread(void*); + + // thread function for writing file to drop directory + void writeToDropDirThread(void*); + + // thread function for sending drag information + void sendDragInfoThread(void*); + + // send drag info to new client screen + void sendDragInfo(BaseClientProxy* newScreen); + +public: + bool m_mock; + +private: + class ClipboardInfo { + public: + ClipboardInfo(); + + public: + Clipboard m_clipboard; + String m_clipboardData; + String m_clipboardOwner; + UInt32 m_clipboardSeqNum; + }; + + // the primary screen client + PrimaryClient* m_primaryClient; + + // all clients (including the primary client) indexed by name + typedef std::map<String, BaseClientProxy*> ClientList; + typedef std::set<BaseClientProxy*> ClientSet; + ClientList m_clients; + ClientSet m_clientSet; + + // all old connections that we're waiting to hangup + typedef std::map<BaseClientProxy*, EventQueueTimer*> OldClients; + OldClients m_oldClients; + + // the client with focus + BaseClientProxy* m_active; + + // the sequence number of enter messages + UInt32 m_seqNum; + + // current mouse position (in absolute screen coordinates) on + // whichever screen is active + SInt32 m_x, m_y; + + // last mouse deltas. this is needed to smooth out double tap + // on win32 which reports bogus mouse motion at the edge of + // the screen when using low level hooks, synthesizing motion + // in the opposite direction the mouse actually moved. + SInt32 m_xDelta, m_yDelta; + SInt32 m_xDelta2, m_yDelta2; + + // current configuration + Config* m_config; + + // input filter (from m_config); + InputFilter* m_inputFilter; + + // clipboard cache + ClipboardInfo m_clipboards[kClipboardEnd]; + + // state saved when screen saver activates + BaseClientProxy* m_activeSaver; + SInt32 m_xSaver, m_ySaver; + + // common state for screen switch tests. all tests are always + // trying to reach the same screen in the same direction. + EDirection m_switchDir; + BaseClientProxy* m_switchScreen; + + // state for delayed screen switching + double m_switchWaitDelay; + EventQueueTimer* m_switchWaitTimer; + SInt32 m_switchWaitX, m_switchWaitY; + + // state for double-tap screen switching + double m_switchTwoTapDelay; + Stopwatch m_switchTwoTapTimer; + bool m_switchTwoTapEngaged; + bool m_switchTwoTapArmed; + SInt32 m_switchTwoTapZone; + + // modifiers needed before switching + bool m_switchNeedsShift; + bool m_switchNeedsControl; + bool m_switchNeedsAlt; + + // relative mouse move option + bool m_relativeMoves; + + // flag whether or not we have broadcasting enabled and the screens to + // which we should send broadcasted keys. + bool m_keyboardBroadcasting; + String m_keyboardBroadcastingScreens; + + // screen locking (former scroll lock) + bool m_lockedToScreen; + + // server screen + barrier::Screen* m_screen; + + IEventQueue* m_events; + + // file transfer + size_t m_expectedFileSize; + String m_receivedFileData; + DragFileList m_dragFileList; + DragFileList m_fakeDragFileList; + Thread* m_sendFileThread; + Thread* m_writeToDropDirThread; + String m_dragFileExt; + bool m_ignoreFileTransfer; + bool m_enableClipboard; + + Thread* m_sendDragInfoThread; + bool m_waitDragInfoThread; + + ClientListener* m_clientListener; + ServerArgs m_args; +}; |
