diff options
Diffstat (limited to 'src/lib/barrier')
85 files changed, 14234 insertions, 0 deletions
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" |
