Compare commits

..

14 Commits

Author SHA1 Message Date
Andrew Nelless
35602ed6bf #6234 Change protocol error message 2017-12-20 15:02:26 +00:00
Andrew Nelless
703097c19b Remove synwinhk DLL 2017-12-13 18:28:48 +00:00
Xinyu Hou
a48ff8bcb5 Merge branch 'mac-input-detection' 2017-12-13 12:26:11 -05:00
Xinyu Hou
6368207dec Release quartz event tap in screen Dtor 2017-12-13 11:47:26 -05:00
Andrew Nelless
0e11b8777f Remove scary memcpy hack 2017-12-13 16:18:22 +00:00
Andrew Nelless
1fb01f6833 Remove Windows 2000 only mouse wheel support 2017-12-13 16:00:37 +00:00
Xinyu Hou
d770160e00 Add log for releasing quartz event tap 2017-12-13 10:59:41 -05:00
Andrew Nelless
2d56cc1c92 Remove NT 3.51 and Win9x mouse wheel support... 2017-12-13 15:27:48 +00:00
Andrew Nelless
db1770df39 Remove support for "high-level" mouse and keyboard hooks 2017-12-13 15:16:51 +00:00
Xinyu Hou
adcb1b9b1f Log for creating quartz event tap 2017-12-13 10:13:39 -05:00
Xinyu Hou
4dc9f893c3 Quit when failed to create quartz event tap 2017-12-13 09:42:41 -05:00
Xinyu Hou
d5872caa7e Merge branch 'mac-bundle' 2017-12-12 11:42:48 -05:00
Andrew Nelless
5ff0637a82 Remove IPC 2017-12-11 22:59:02 +00:00
Xinyu Hou
9da6974ada Install synergy core into resource folder on Mac 2017-12-11 10:28:54 -05:00
57 changed files with 747 additions and 4325 deletions

View File

@@ -322,6 +322,6 @@ add_subdirectory (src)
if (SYNERGY_TIDY)
set_property (TARGET synergys synergyc synergy-core
arch base client common core io ipc mt net platform server shared
arch base client common core io mt net platform server shared
PROPERTY CXX_CLANG_TIDY "${DO_CLANG_TIDY}")
endif()

View File

@@ -15,11 +15,11 @@
add_executable(synergy-core main.cpp)
target_link_libraries(synergy-core
arch base client common io mt net ipc platform server core ${libs})
arch base client common io mt net platform server core ${libs})
if (SYNERGY_CORE_INSTALL)
if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
install (TARGETS synergy-core DESTINATION ${SYNERGY_BUNDLE_BINARY_DIR})
install (TARGETS synergy-core DESTINATION ${SYNERGY_BUNDLE_RESOURSES_DIR})
elseif (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
install (TARGETS synergy-core DESTINATION bin)
endif()

View File

@@ -39,4 +39,4 @@ endif()
add_executable(synergyc ${sources})
target_link_libraries(synergyc
arch base client common io mt net ipc platform server core ${libs})
arch base client common io mt net platform server core ${libs})

View File

@@ -36,4 +36,4 @@ endif()
add_executable(synergys ${sources})
target_link_libraries(synergys
arch base client common io mt net ipc platform server core ${libs})
arch base client common io mt net platform server core ${libs})

View File

@@ -20,13 +20,8 @@ add_subdirectory(base)
add_subdirectory(client)
add_subdirectory(common)
add_subdirectory(io)
add_subdirectory(ipc)
add_subdirectory(mt)
add_subdirectory(net)
add_subdirectory(platform)
add_subdirectory(server)
add_subdirectory(shared)
if (WIN32)
add_subdirectory(synwinhk)
endif()

View File

@@ -30,10 +30,6 @@
EVENT_TYPE_ACCESSOR(Client)
EVENT_TYPE_ACCESSOR(IStream)
EVENT_TYPE_ACCESSOR(IpcClient)
EVENT_TYPE_ACCESSOR(IpcClientProxy)
EVENT_TYPE_ACCESSOR(IpcServer)
EVENT_TYPE_ACCESSOR(IpcServerProxy)
EVENT_TYPE_ACCESSOR(IDataSocket)
EVENT_TYPE_ACCESSOR(IListenSocket)
EVENT_TYPE_ACCESSOR(ISocket)
@@ -68,10 +64,6 @@ EventQueue::EventQueue() :
m_nextType(Event::kLast),
m_typesForClient(nullptr),
m_typesForIStream(nullptr),
m_typesForIpcClient(nullptr),
m_typesForIpcClientProxy(nullptr),
m_typesForIpcServer(nullptr),
m_typesForIpcServerProxy(nullptr),
m_typesForIDataSocket(nullptr),
m_typesForIListenSocket(nullptr),
m_typesForISocket(nullptr),

View File

@@ -141,10 +141,6 @@ public:
//
ClientEvents& forClient();
IStreamEvents& forIStream();
IpcClientEvents& forIpcClient();
IpcClientProxyEvents& forIpcClientProxy();
IpcServerEvents& forIpcServer();
IpcServerProxyEvents& forIpcServerProxy();
IDataSocketEvents& forIDataSocket();
IListenSocketEvents& forIListenSocket();
ISocketEvents& forISocket();
@@ -163,10 +159,6 @@ public:
private:
ClientEvents* m_typesForClient;
IStreamEvents* m_typesForIStream;
IpcClientEvents* m_typesForIpcClient;
IpcClientProxyEvents* m_typesForIpcClientProxy;
IpcServerEvents* m_typesForIpcServer;
IpcServerProxyEvents* m_typesForIpcServerProxy;
IDataSocketEvents* m_typesForIDataSocket;
IListenSocketEvents* m_typesForIListenSocket;
ISocketEvents* m_typesForISocket;

View File

@@ -57,26 +57,6 @@ REGISTER_EVENT(IStream, outputError)
REGISTER_EVENT(IStream, inputShutdown)
REGISTER_EVENT(IStream, outputShutdown)
//
// IpcClient
//
REGISTER_EVENT(IpcClient, connected)
REGISTER_EVENT(IpcClient, messageReceived)
//
// IpcClientProxy
//
REGISTER_EVENT(IpcClientProxy, messageReceived)
REGISTER_EVENT(IpcClientProxy, disconnected)
//
// IpcServerProxy
//
REGISTER_EVENT(IpcServerProxy, messageReceived)
//
// IDataSocket
//
@@ -178,13 +158,6 @@ REGISTER_EVENT(IScreen, shapeChanged)
REGISTER_EVENT(IScreen, suspend)
REGISTER_EVENT(IScreen, resume)
//
// IpcServer
//
REGISTER_EVENT(IpcServer, clientConnected)
REGISTER_EVENT(IpcServer, messageReceived)
//
// Clipboard
//

View File

@@ -143,89 +143,6 @@ private:
Event::Type m_outputShutdown;
};
class IpcClientEvents : public EventTypes {
public:
IpcClientEvents() :
m_connected(Event::kUnknown),
m_messageReceived(Event::kUnknown) { }
//! @name accessors
//@{
//! Raised when the socket is connected.
Event::Type connected();
//! Raised when a message is received.
Event::Type messageReceived();
//@}
private:
Event::Type m_connected;
Event::Type m_messageReceived;
};
class IpcClientProxyEvents : public EventTypes {
public:
IpcClientProxyEvents() :
m_messageReceived(Event::kUnknown),
m_disconnected(Event::kUnknown) { }
//! @name accessors
//@{
//! Raised when the server receives a message from a client.
Event::Type messageReceived();
//! Raised when the client disconnects from the server.
Event::Type disconnected();
//@}
private:
Event::Type m_messageReceived;
Event::Type m_disconnected;
};
class IpcServerEvents : public EventTypes {
public:
IpcServerEvents() :
m_clientConnected(Event::kUnknown),
m_messageReceived(Event::kUnknown) { }
//! @name accessors
//@{
//! Raised when we have created the client proxy.
Event::Type clientConnected();
//! Raised when a message is received through a client proxy.
Event::Type messageReceived();
//@}
private:
Event::Type m_clientConnected;
Event::Type m_messageReceived;
};
class IpcServerProxyEvents : public EventTypes {
public:
IpcServerProxyEvents() :
m_messageReceived(Event::kUnknown) { }
//! @name accessors
//@{
//! Raised when the client receives a message from the server.
Event::Type messageReceived();
//@}
private:
Event::Type m_messageReceived;
};
class IDataSocketEvents : public EventTypes {
public:
IDataSocketEvents() :

View File

@@ -32,10 +32,6 @@ class EventQueueTimer;
// Event type registration classes.
class ClientEvents;
class IStreamEvents;
class IpcClientEvents;
class IpcClientProxyEvents;
class IpcServerEvents;
class IpcServerProxyEvents;
class IDataSocketEvents;
class IListenSocketEvents;
class ISocketEvents;
@@ -230,10 +226,6 @@ public:
virtual ClientEvents& forClient() = 0;
virtual IStreamEvents& forIStream() = 0;
virtual IpcClientEvents& forIpcClient() = 0;
virtual IpcClientProxyEvents& forIpcClientProxy() = 0;
virtual IpcServerEvents& forIpcServer() = 0;
virtual IpcServerProxyEvents& forIpcServerProxy() = 0;
virtual IDataSocketEvents& forIDataSocket() = 0;
virtual IListenSocketEvents& forIListenSocket() = 0;
virtual ISocketEvents& forISocket() = 0;

View File

@@ -674,7 +674,7 @@ Client::handleHello(const Event& /*unused*/, void* /*unused*/)
{
SInt16 major, minor;
if (!ProtocolUtil::readf(m_stream, kMsgHello, &major, &minor)) {
sendConnectionFailedEvent("Protocol error from server, check encryption settings");
sendConnectionFailedEvent("Protocol error from server. Aborting");
cleanupTimer();
cleanupConnection();
return;

View File

@@ -29,9 +29,6 @@
#include "core/ArgsBase.h"
#include "core/XSynergy.h"
#include "core/protocol_types.h"
#include "ipc/Ipc.h"
#include "ipc/IpcMessage.h"
#include "ipc/IpcServerProxy.h"
#include <iostream>
#include <cstdio>
@@ -64,7 +61,6 @@ App::App(IEventQueue* events, ArgsBase* args) :
m_args(args),
m_fileLog(nullptr),
m_appUtil(events),
m_ipcClient(nullptr),
m_socketMultiplexer(nullptr)
{
assert(s_instance == nullptr);
@@ -211,35 +207,6 @@ App::initApp(int argc, const char** argv)
loadConfig();
}
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* /*unused*/)
{
auto* m = dynamic_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* /*unused*/)
{

View File

@@ -18,7 +18,6 @@
#pragma once
#include "ipc/IpcClient.h"
#include "core/IApp.h"
#include "base/String.h"
#include "base/Log.h"
@@ -95,12 +94,7 @@ public:
void setEvents(EventQueue& events) { m_events = &events; }
private:
void handleIpcMessage(const Event&, void*);
protected:
void initIpcClient();
void cleanupIpcClient();
void runEventsLoop(void*);
bool m_suspended;
@@ -111,7 +105,6 @@ private:
static App* s_instance;
FileLogOutputter* m_fileLog;
ARCH_APP_UTIL m_appUtil;
IpcClient* m_ipcClient;
SocketMultiplexer* m_socketMultiplexer;
};

View File

@@ -252,7 +252,7 @@ ArgParser::parseGenericArgs(int argc, const char* const* argv, int& i)
argsBase().m_shouldExit = true;
}
else if (isArg(i, argc, argv, nullptr, "--ipc")) {
argsBase().m_enableIpc = true;
LOG((CLOG_INFO "ignoring --ipc. The old IPC was removed."));
}
else if (isArg(i, argc, argv, nullptr, "--server")) {
// supress error when --server is used

View File

@@ -38,7 +38,6 @@ m_pname(nullptr),
m_logFilter(nullptr),
m_logFile(nullptr),
m_display(nullptr),
m_enableIpc(false),
m_enableDragDrop(false),
m_shouldExit(false),
m_profileDirectory(""),

View File

@@ -35,7 +35,6 @@ public:
const char* m_logFile;
const char* m_display;
String m_name;
bool m_enableIpc;
bool m_enableDragDrop;
#if SYSAPI_WIN32
bool m_debugServiceWait;

View File

@@ -36,5 +36,5 @@ endif()
add_library(core STATIC ${sources})
if (UNIX)
target_link_libraries(core arch client ipc net base platform mt server)
target_link_libraries(core arch client net base platform mt server)
endif()

View File

@@ -453,12 +453,6 @@ ClientApp::mainLoop()
// 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.
@@ -489,10 +483,6 @@ ClientApp::mainLoop()
updateStatus();
LOG((CLOG_NOTE "stopped client"));
if (argsBase().m_enableIpc) {
cleanupIpcClient();
}
return kExitSuccess;
}

View File

@@ -32,9 +32,6 @@
#include "core/ArgParser.h"
#include "core/ClientArgs.h"
#include "core/ServerArgs.h"
#include "ipc/IpcClientProxy.h"
#include "ipc/IpcLogOutputter.h"
#include "ipc/IpcMessage.h"
#include "net/SocketMultiplexer.h"
#if SYSAPI_WIN32
@@ -44,7 +41,6 @@
#include "core/Screen.h"
#include "platform/MSWindowsScreen.h"
#include "platform/MSWindowsDebugOutputter.h"
#include "platform/MSWindowsWatchdog.h"
#include "platform/MSWindowsEventQueueBuffer.h"
#define WIN32_LEAN_AND_MEAN
@@ -82,8 +78,6 @@ winMainLoopStatic(int, const char**)
#endif
DaemonApp::DaemonApp() :
m_ipcServer(nullptr),
m_ipcLogOutputter(nullptr),
#if SYSAPI_WIN32
m_watchdog(nullptr),
#endif
@@ -206,53 +200,8 @@ DaemonApp::mainLoop(bool logToFile)
// 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);
#if SYSAPI_WIN32
m_watchdog = new MSWindowsWatchdog(false, *m_ipcServer, *m_ipcLogOutputter);
m_watchdog->setFileLogOutputter(m_fileLogOutputter);
#endif
m_events->adoptHandler(
m_events->forIpcServer().messageReceived(), m_ipcServer,
new TMethodEventJob<DaemonApp>(this, &DaemonApp::handleIpcMessage));
m_ipcServer->listen();
#if SYSAPI_WIN32
// 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();
#endif
m_events->loop();
#if SYSAPI_WIN32
m_watchdog->stop();
delete m_watchdog;
#endif
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) {
@@ -286,117 +235,3 @@ DaemonApp::logFilename()
return logFilename;
}
void
DaemonApp::handleIpcMessage(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
switch (m->type()) {
case kIpcCommand: {
auto* cm = dynamic_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(nullptr);
const char** argv = argParser.getArgv(argsArray);
ServerArgs serverArgs;
ClientArgs clientArgs;
auto argc = static_cast<int>(argsArray.size());
bool server = argsArray[0].find("synergys") != String::npos;
ArgsBase* argBase = nullptr;
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()));
}
}
#if SYSAPI_WIN32
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());
#endif
}
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(andrew): 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()));
}
#if SYSAPI_WIN32
// 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());
#endif
break;
}
case kIpcHello:
auto* hm = dynamic_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()));
#if SYSAPI_WIN32
String watchdogStatus = m_watchdog->isProcessActive() ? "ok" : "error";
LOG((CLOG_INFO "watchdog status: %s", watchdogStatus.c_str()));
#endif
m_ipcLogOutputter->notifyBuffer();
break;
}
}

View File

@@ -19,18 +19,17 @@
#pragma once
#include "arch/Arch.h"
#include "ipc/IpcServer.h"
#include <string>
class Event;
class IpcLogOutputter;
class FileLogOutputter;
#if SYSAPI_WIN32
class MSWindowsWatchdog;
#endif
class IEventQueue;
class DaemonApp {
public:
@@ -43,7 +42,6 @@ private:
void daemonize();
void foregroundError(const char* message);
std::string logFilename();
void handleIpcMessage(const Event&, void*);
public:
static DaemonApp* s_instance;
@@ -53,8 +51,6 @@ public:
#endif
private:
IpcServer* m_ipcServer;
IpcLogOutputter* m_ipcLogOutputter;
IEventQueue* m_events;
FileLogOutputter* m_fileLogOutputter;
};

View File

@@ -704,12 +704,6 @@ ServerApp::mainLoop()
// 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, nullptr);
m_events->adoptHandler(m_events->forServerApp().reloadConfig(),
@@ -762,10 +756,6 @@ ServerApp::mainLoop()
updateStatus();
LOG((CLOG_NOTE "stopped server"));
if (argsBase().m_enableIpc) {
cleanupIpcClient();
}
return kExitSuccess;
}

View File

@@ -1,28 +0,0 @@
# synergy -- mouse and keyboard sharing utility
# Copyright (C) 2012-2016 Symless Ltd.
# Copyright (C) 2009 Nick Bolton
#
# This package is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# found in the file LICENSE that should have accompanied this file.
#
# This package is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
file(GLOB headers "*.h")
file(GLOB sources "*.cpp")
if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})
endif()
add_library(ipc STATIC ${sources})
if (UNIX)
target_link_libraries(ipc arch base common mt io net core)
endif()

View File

@@ -1,24 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/Ipc.h"
const char* kIpcMsgHello = "IHEL%1i";
const char* kIpcMsgLogLine = "ILOG%s";
const char* kIpcMsgCommand = "ICMD%s%1i";
const char* kIpcMsgShutdown = "ISDN";

View File

@@ -1,52 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#define IPC_HOST "127.0.0.1"
#define IPC_PORT 24801
enum EIpcMessage {
kIpcHello,
kIpcLogLine,
kIpcCommand,
kIpcShutdown,
};
enum EIpcClientType {
kIpcClientUnknown,
kIpcClientGui,
kIpcClientNode,
};
// handshake: node/gui -> daemon
// $1 = type, the client identifies it's self as gui or node (synergyc/s).
extern const char* kIpcMsgHello;
// log line: daemon -> gui
// $1 = aggregate log lines collected from synergys/c or the daemon itself.
extern const char* kIpcMsgLogLine;
// command: gui -> daemon
// $1 = command; the command for the daemon to launch, typically the full
// path to synergys/c. $2 = true when process must be elevated on ms windows.
extern const char* kIpcMsgCommand;
// shutdown: daemon -> node
// the daemon tells synergys/c to shut down gracefully.
extern const char* kIpcMsgShutdown;

View File

@@ -1,107 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/IpcClient.h"
#include "base/TMethodEventJob.h"
#include "ipc/Ipc.h"
#include "ipc/IpcMessage.h"
#include "ipc/IpcServerProxy.h"
//
// IpcClient
//
IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
m_serverAddress(NetworkAddress(IPC_HOST, IPC_PORT)),
m_socket(events, socketMultiplexer),
m_server(nullptr),
m_events(events)
{
init();
}
IpcClient::IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) :
m_serverAddress(NetworkAddress(IPC_HOST, port)),
m_socket(events, socketMultiplexer),
m_server(nullptr),
m_events(events)
{
init();
}
void
IpcClient::init()
{
m_serverAddress.resolve();
}
IpcClient::~IpcClient()
= default;
void
IpcClient::connect()
{
m_events->adoptHandler(
m_events->forIDataSocket().connected(), m_socket.getEventTarget(),
new TMethodEventJob<IpcClient>(
this, &IpcClient::handleConnected));
m_socket.connect(m_serverAddress);
m_server = new IpcServerProxy(m_socket, m_events);
m_events->adoptHandler(
m_events->forIpcServerProxy().messageReceived(), m_server,
new TMethodEventJob<IpcClient>(
this, &IpcClient::handleMessageReceived));
}
void
IpcClient::disconnect()
{
m_events->removeHandler(m_events->forIDataSocket().connected(), m_socket.getEventTarget());
m_events->removeHandler(m_events->forIpcServerProxy().messageReceived(), m_server);
m_server->disconnect();
delete m_server;
m_server = nullptr;
}
void
IpcClient::send(const IpcMessage& message)
{
assert(m_server != nullptr);
m_server->send(message);
}
void
IpcClient::handleConnected(const Event& /*unused*/, void* /*unused*/)
{
m_events->addEvent(Event(
m_events->forIpcClient().connected(), this, m_server, Event::kDontFreeData));
IpcHelloMessage message(kIpcClientNode);
send(message);
}
void
IpcClient::handleMessageReceived(const Event& e, void* /*unused*/)
{
Event event(m_events->forIpcClient().messageReceived(), this);
event.setDataObject(e.getDataObject());
m_events->addEvent(event);
}

View File

@@ -1,64 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "net/NetworkAddress.h"
#include "net/TCPSocket.h"
#include "base/EventTypes.h"
class IpcServerProxy;
class IpcMessage;
class IEventQueue;
class SocketMultiplexer;
//! IPC client for communication between daemon and GUI.
/*!
* See \ref IpcServer description.
*/
class IpcClient {
public:
IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
IpcClient(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port);
virtual ~IpcClient();
//! @name manipulators
//@{
//! Connects to the IPC server at localhost.
void connect();
//! Disconnects from the IPC server.
void disconnect();
//! Sends a message to the server.
void send(const IpcMessage& message);
//@}
private:
void init();
void handleConnected(const Event&, void*);
void handleMessageReceived(const Event&, void*);
private:
NetworkAddress m_serverAddress;
TCPSocket m_socket;
IpcServerProxy* m_server;
IEventQueue* m_events;
};

View File

@@ -1,194 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/IpcClientProxy.h"
#include "arch/Arch.h"
#include "base/Log.h"
#include "base/TMethodEventJob.h"
#include "core/ProtocolUtil.h"
#include "io/IStream.h"
#include "ipc/Ipc.h"
#include "ipc/IpcMessage.h"
//
// IpcClientProxy
//
IpcClientProxy::IpcClientProxy(synergy::IStream& stream, IEventQueue* events) :
m_stream(stream),
m_clientType(kIpcClientUnknown),
m_disconnecting(false),
m_readMutex(ARCH->newMutex()),
m_writeMutex(ARCH->newMutex()),
m_events(events)
{
m_events->adoptHandler(
m_events->forIStream().inputReady(), stream.getEventTarget(),
new TMethodEventJob<IpcClientProxy>(
this, &IpcClientProxy::handleData));
m_events->adoptHandler(
m_events->forIStream().outputError(), stream.getEventTarget(),
new TMethodEventJob<IpcClientProxy>(
this, &IpcClientProxy::handleWriteError));
m_events->adoptHandler(
m_events->forIStream().inputShutdown(), stream.getEventTarget(),
new TMethodEventJob<IpcClientProxy>(
this, &IpcClientProxy::handleDisconnect));
m_events->adoptHandler(
m_events->forIStream().outputShutdown(), stream.getEventTarget(),
new TMethodEventJob<IpcClientProxy>(
this, &IpcClientProxy::handleWriteError));
}
IpcClientProxy::~IpcClientProxy()
{
m_events->removeHandler(
m_events->forIStream().inputReady(), m_stream.getEventTarget());
m_events->removeHandler(
m_events->forIStream().outputError(), m_stream.getEventTarget());
m_events->removeHandler(
m_events->forIStream().inputShutdown(), m_stream.getEventTarget());
m_events->removeHandler(
m_events->forIStream().outputShutdown(), m_stream.getEventTarget());
// don't delete the stream while it's being used.
ARCH->lockMutex(m_readMutex);
ARCH->lockMutex(m_writeMutex);
delete &m_stream;
ARCH->unlockMutex(m_readMutex);
ARCH->unlockMutex(m_writeMutex);
ARCH->closeMutex(m_readMutex);
ARCH->closeMutex(m_writeMutex);
}
void
IpcClientProxy::handleDisconnect(const Event& /*unused*/, void* /*unused*/)
{
disconnect();
LOG((CLOG_DEBUG "ipc client disconnected"));
}
void
IpcClientProxy::handleWriteError(const Event& /*unused*/, void* /*unused*/)
{
disconnect();
LOG((CLOG_DEBUG "ipc client write error"));
}
void
IpcClientProxy::handleData(const Event& /*unused*/, void* /*unused*/)
{
// don't allow the dtor to destroy the stream while we're using it.
ArchMutexLock lock(m_readMutex);
LOG((CLOG_DEBUG "start ipc handle data"));
UInt8 code[4];
UInt32 n = m_stream.read(code, 4);
while (n != 0) {
LOG((CLOG_DEBUG "ipc read: %c%c%c%c",
code[0], code[1], code[2], code[3]));
IpcMessage* m = nullptr;
if (memcmp(code, kIpcMsgHello, 4) == 0) {
m = parseHello();
}
else if (memcmp(code, kIpcMsgCommand, 4) == 0) {
m = parseCommand();
}
else {
LOG((CLOG_ERR "invalid ipc message"));
disconnect();
}
// don't delete with this event; the data is passed to a new event.
Event e(m_events->forIpcClientProxy().messageReceived(), this, nullptr, Event::kDontFreeData);
e.setDataObject(m);
m_events->addEvent(e);
n = m_stream.read(code, 4);
}
LOG((CLOG_DEBUG "finished ipc handle data"));
}
void
IpcClientProxy::send(const IpcMessage& message)
{
// don't allow other threads to write until we've finished the entire
// message. stream write is locked, but only for that single write.
// also, don't allow the dtor to destroy the stream while we're using it.
ArchMutexLock lock(m_writeMutex);
LOG((CLOG_DEBUG4 "ipc write: %d", message.type()));
switch (message.type()) {
case kIpcLogLine: {
const auto& llm = dynamic_cast<const IpcLogLineMessage&>(message);
const String logLine = llm.logLine();
ProtocolUtil::writef(&m_stream, kIpcMsgLogLine, &logLine);
break;
}
case kIpcShutdown:
ProtocolUtil::writef(&m_stream, kIpcMsgShutdown);
break;
default:
LOG((CLOG_ERR "ipc message not supported: %d", message.type()));
break;
}
}
IpcHelloMessage*
IpcClientProxy::parseHello()
{
UInt8 type;
ProtocolUtil::readf(&m_stream, kIpcMsgHello + 4, &type);
m_clientType = static_cast<EIpcClientType>(type);
// must be deleted by event handler.
return new IpcHelloMessage(m_clientType);
}
IpcCommandMessage*
IpcClientProxy::parseCommand()
{
String command;
UInt8 elevate;
ProtocolUtil::readf(&m_stream, kIpcMsgCommand + 4, &command, &elevate);
// must be deleted by event handler.
return new IpcCommandMessage(command, elevate != 0);
}
void
IpcClientProxy::disconnect()
{
LOG((CLOG_DEBUG "ipc disconnect, closing stream"));
m_disconnecting = true;
m_stream.close();
m_events->addEvent(Event(m_events->forIpcClientProxy().disconnected(), this));
}

View File

@@ -1,55 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ipc/Ipc.h"
#include "arch/IArchMultithread.h"
#include "base/EventTypes.h"
#include "base/Event.h"
namespace synergy { class IStream; }
class IpcMessage;
class IpcCommandMessage;
class IpcHelloMessage;
class IEventQueue;
class IpcClientProxy {
friend class IpcServer;
public:
IpcClientProxy(synergy::IStream& stream, IEventQueue* events);
virtual ~IpcClientProxy();
private:
void send(const IpcMessage& message);
void handleData(const Event&, void*);
void handleDisconnect(const Event&, void*);
void handleWriteError(const Event&, void*);
IpcHelloMessage* parseHello();
IpcCommandMessage* parseCommand();
void disconnect();
private:
synergy::IStream& m_stream;
EIpcClientType m_clientType;
bool m_disconnecting;
ArchMutex m_readMutex;
ArchMutex m_writeMutex;
IEventQueue* m_events;
};

View File

@@ -1,228 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/IpcLogOutputter.h"
#include "arch/Arch.h"
#include "arch/XArch.h"
#include "base/Event.h"
#include "base/EventQueue.h"
#include "base/TMethodEventJob.h"
#include "base/TMethodJob.h"
#include "ipc/Ipc.h"
#include "ipc/IpcClientProxy.h"
#include "ipc/IpcMessage.h"
#include "ipc/IpcServer.h"
#include "mt/Thread.h"
enum EIpcLogOutputter {
kBufferMaxSize = 1000,
kMaxSendLines = 100,
kBufferRateWriteLimit = 1000, // writes per kBufferRateTime
kBufferRateTimeLimit = 1 // seconds
};
IpcLogOutputter::IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread) :
m_ipcServer(ipcServer),
m_bufferMutex(ARCH->newMutex()),
m_sending(false),
m_bufferThread(nullptr),
m_running(false),
m_notifyCond(ARCH->newCondVar()),
m_notifyMutex(ARCH->newMutex()),
m_bufferThreadId(0),
m_bufferWaiting(false),
m_bufferMaxSize(kBufferMaxSize),
m_bufferRateWriteLimit(kBufferRateWriteLimit),
m_bufferRateTimeLimit(kBufferRateTimeLimit),
m_bufferWriteCount(0),
m_bufferRateStart(ARCH->time()),
m_clientType(clientType),
m_runningMutex(ARCH->newMutex())
{
if (useThread) {
m_bufferThread = new Thread(new TMethodJob<IpcLogOutputter>(
this, &IpcLogOutputter::bufferThread));
}
}
IpcLogOutputter::~IpcLogOutputter()
{
close();
ARCH->closeMutex(m_bufferMutex);
if (m_bufferThread != nullptr) {
m_bufferThread->cancel();
m_bufferThread->wait();
delete m_bufferThread;
}
ARCH->closeCondVar(m_notifyCond);
ARCH->closeMutex(m_notifyMutex);
}
void
IpcLogOutputter::open(const char* /*title*/)
{
}
void
IpcLogOutputter::close()
{
if (m_bufferThread != nullptr) {
ArchMutexLock lock(m_runningMutex);
m_running = false;
notifyBuffer();
m_bufferThread->wait(5);
}
}
void
IpcLogOutputter::show(bool /*showIfEmpty*/)
{
}
bool
IpcLogOutputter::write(ELevel /*level*/, const char* text)
{
// ignore events from the buffer thread (would cause recursion).
if (m_bufferThread != nullptr &&
Thread::getCurrentThread().getID() == m_bufferThreadId) {
return true;
}
appendBuffer(text);
notifyBuffer();
return true;
}
void
IpcLogOutputter::appendBuffer(const String& text)
{
ArchMutexLock lock(m_bufferMutex);
double elapsed = ARCH->time() - m_bufferRateStart;
if (elapsed < m_bufferRateTimeLimit) {
if (m_bufferWriteCount >= m_bufferRateWriteLimit) {
// discard the log line if we've logged too much.
return;
}
}
else {
m_bufferWriteCount = 0;
m_bufferRateStart = ARCH->time();
}
if (m_buffer.size() >= m_bufferMaxSize) {
// if the queue is exceeds size limit,
// throw away the oldest item
m_buffer.pop_front();
}
m_buffer.push_back(text);
m_bufferWriteCount++;
}
bool
IpcLogOutputter::isRunning()
{
ArchMutexLock lock(m_runningMutex);
return m_running;
}
void
IpcLogOutputter::bufferThread(void* /*unused*/)
{
m_bufferThreadId = m_bufferThread->getID();
m_running = true;
try {
while (isRunning()) {
if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) {
ArchMutexLock lock(m_notifyMutex);
ARCH->waitCondVar(m_notifyCond, m_notifyMutex, -1);
}
sendBuffer();
}
}
catch (XArch& e) {
LOG((CLOG_ERR "ipc log buffer thread error, %s", e.what()));
}
LOG((CLOG_DEBUG "ipc log buffer thread finished"));
}
void
IpcLogOutputter::notifyBuffer()
{
ArchMutexLock lock(m_notifyMutex);
ARCH->broadcastCondVar(m_notifyCond);
}
String
IpcLogOutputter::getChunk(size_t count)
{
ArchMutexLock lock(m_bufferMutex);
if (m_buffer.size() < count) {
count = m_buffer.size();
}
String chunk;
for (size_t i = 0; i < count; i++) {
chunk.append(m_buffer.front());
chunk.append("\n");
m_buffer.pop_front();
}
return chunk;
}
void
IpcLogOutputter::sendBuffer()
{
if (m_buffer.empty() || !m_ipcServer.hasClients(m_clientType)) {
return;
}
IpcLogLineMessage message(getChunk(kMaxSendLines));
m_sending = true;
m_ipcServer.send(message, kIpcClientGui);
m_sending = false;
}
void
IpcLogOutputter::bufferMaxSize(UInt16 bufferMaxSize)
{
m_bufferMaxSize = bufferMaxSize;
}
UInt16
IpcLogOutputter::bufferMaxSize() const
{
return m_bufferMaxSize;
}
void
IpcLogOutputter::bufferRateLimit(UInt16 writeLimit, double timeLimit)
{
m_bufferRateWriteLimit = writeLimit;
m_bufferRateTimeLimit = timeLimit;
}

View File

@@ -1,119 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "arch/Arch.h"
#include "arch/IArchMultithread.h"
#include "base/ILogOutputter.h"
#include "ipc/Ipc.h"
#include <deque>
class IpcServer;
class Event;
class IpcClientProxy;
//! Write log to GUI over IPC
/*!
This outputter writes output to the GUI via IPC.
*/
class IpcLogOutputter : public ILogOutputter {
public:
/*!
If \p useThread is \c true, the buffer will be sent using a thread.
If \p useThread is \c false, then the buffer needs to be sent manually
using the \c sendBuffer() function.
*/
IpcLogOutputter(IpcServer& ipcServer, EIpcClientType clientType, bool useThread);
virtual ~IpcLogOutputter();
// ILogOutputter overrides
virtual void open(const char* title);
virtual void close();
virtual void show(bool showIfEmpty);
virtual bool write(ELevel level, const char* text);
//! @name manipulators
//@{
//! Notify that the buffer should be sent.
void notifyBuffer();
//! Set the buffer size
/*!
Set the maximum size of the buffer to protect memory
from runaway logging.
*/
void bufferMaxSize(UInt16 bufferMaxSize);
//! Set the rate limit
/*!
Set the maximum number of \p writeRate for every \p timeRate in seconds.
*/
void bufferRateLimit(UInt16 writeLimit, double timeLimit);
//! Send the buffer
/*!
Sends a chunk of the buffer to the IPC server, normally called
when threaded mode is on.
*/
void sendBuffer();
//@}
//! @name accessors
//@{
//! Get the buffer size
/*!
Returns the maximum size of the buffer.
*/
UInt16 bufferMaxSize() const;
//@}
private:
void init();
void bufferThread(void*);
String getChunk(size_t count);
void appendBuffer(const String& text);
bool isRunning();
private:
typedef std::deque<String> Buffer;
IpcServer& m_ipcServer;
Buffer m_buffer;
ArchMutex m_bufferMutex;
bool m_sending;
Thread* m_bufferThread;
bool m_running;
ArchCond m_notifyCond;
ArchMutex m_notifyMutex;
bool m_bufferWaiting;
IArchMultithread::ThreadID
m_bufferThreadId;
UInt16 m_bufferMaxSize;
UInt16 m_bufferRateWriteLimit;
double m_bufferRateTimeLimit;
UInt16 m_bufferWriteCount;
double m_bufferRateStart;
EIpcClientType m_clientType;
ArchMutex m_runningMutex;
};

View File

@@ -1,66 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/IpcMessage.h"
#include <utility>
#include "ipc/Ipc.h"
IpcMessage::IpcMessage(UInt8 type) :
m_type(type)
{
}
IpcMessage::~IpcMessage()
= default;
IpcHelloMessage::IpcHelloMessage(EIpcClientType clientType) :
IpcMessage(kIpcHello),
m_clientType(clientType)
{
}
IpcHelloMessage::~IpcHelloMessage()
= default;
IpcShutdownMessage::IpcShutdownMessage() :
IpcMessage(kIpcShutdown)
{
}
IpcShutdownMessage::~IpcShutdownMessage()
= default;
IpcLogLineMessage::IpcLogLineMessage(String logLine) :
IpcMessage(kIpcLogLine),
m_logLine(std::move(logLine))
{
}
IpcLogLineMessage::~IpcLogLineMessage()
= default;
IpcCommandMessage::IpcCommandMessage(String command, bool elevate) :
IpcMessage(kIpcCommand),
m_command(std::move(command)),
m_elevate(elevate)
{
}
IpcCommandMessage::~IpcCommandMessage()
= default;

View File

@@ -1,85 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ipc/Ipc.h"
#include "base/EventTypes.h"
#include "base/String.h"
#include "base/Event.h"
class IpcMessage : public EventData {
public:
virtual ~IpcMessage();
//! Gets the message type ID.
UInt8 type() const { return m_type; }
protected:
IpcMessage(UInt8 type);
private:
UInt8 m_type;
};
class IpcHelloMessage : public IpcMessage {
public:
IpcHelloMessage(EIpcClientType clientType);
virtual ~IpcHelloMessage();
//! Gets the message type ID.
EIpcClientType clientType() const { return m_clientType; }
private:
EIpcClientType m_clientType;
};
class IpcShutdownMessage : public IpcMessage {
public:
IpcShutdownMessage();
virtual ~IpcShutdownMessage();
};
class IpcLogLineMessage : public IpcMessage {
public:
IpcLogLineMessage(String logLine);
virtual ~IpcLogLineMessage();
//! Gets the log line.
String logLine() const { return m_logLine; }
private:
String m_logLine;
};
class IpcCommandMessage : public IpcMessage {
public:
IpcCommandMessage(String command, bool elevate);
virtual ~IpcCommandMessage();
//! Gets the command.
String command() const { return m_command; }
//! Gets whether or not the process should be elevated on MS Windows.
bool elevate() const { return m_elevate; }
private:
String m_command;
bool m_elevate;
};

View File

@@ -1,187 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/IpcServer.h"
#include "base/Event.h"
#include "base/IEventQueue.h"
#include "base/Log.h"
#include "base/TMethodEventJob.h"
#include "io/IStream.h"
#include "ipc/Ipc.h"
#include "ipc/IpcClientProxy.h"
#include "ipc/IpcMessage.h"
#include "net/IDataSocket.h"
//
// IpcServer
//
IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer) :
m_mock(false),
m_events(events),
m_socketMultiplexer(socketMultiplexer),
m_socket(nullptr),
m_address(NetworkAddress(IPC_HOST, IPC_PORT))
{
init();
}
IpcServer::IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port) :
m_mock(false),
m_events(events),
m_socketMultiplexer(socketMultiplexer),
m_address(NetworkAddress(IPC_HOST, port))
{
init();
}
void
IpcServer::init()
{
m_socket = new TCPListenSocket(m_events, m_socketMultiplexer);
m_clientsMutex = ARCH->newMutex();
m_address.resolve();
m_events->adoptHandler(
m_events->forIListenSocket().connecting(), m_socket,
new TMethodEventJob<IpcServer>(
this, &IpcServer::handleClientConnecting));
}
IpcServer::~IpcServer()
{
if (m_mock) {
return;
}
delete m_socket;
ARCH->lockMutex(m_clientsMutex);
ClientList::iterator it;
for (it = m_clients.begin(); it != m_clients.end(); it++) {
deleteClient(*it);
}
m_clients.clear();
ARCH->unlockMutex(m_clientsMutex);
ARCH->closeMutex(m_clientsMutex);
m_events->removeHandler(m_events->forIListenSocket().connecting(), m_socket);
}
void
IpcServer::listen()
{
m_socket->bind(m_address);
}
void
IpcServer::handleClientConnecting(const Event& /*unused*/, void* /*unused*/)
{
synergy::IStream* stream = m_socket->accept();
if (stream == nullptr) {
return;
}
LOG((CLOG_DEBUG "accepted ipc client connection"));
ARCH->lockMutex(m_clientsMutex);
auto* proxy = new IpcClientProxy(*stream, m_events);
m_clients.push_back(proxy);
ARCH->unlockMutex(m_clientsMutex);
m_events->adoptHandler(
m_events->forIpcClientProxy().disconnected(), proxy,
new TMethodEventJob<IpcServer>(
this, &IpcServer::handleClientDisconnected));
m_events->adoptHandler(
m_events->forIpcClientProxy().messageReceived(), proxy,
new TMethodEventJob<IpcServer>(
this, &IpcServer::handleMessageReceived));
m_events->addEvent(Event(
m_events->forIpcServer().clientConnected(), this, proxy, Event::kDontFreeData));
}
void
IpcServer::handleClientDisconnected(const Event& e, void* /*unused*/)
{
auto* proxy = static_cast<IpcClientProxy*>(e.getTarget());
ArchMutexLock lock(m_clientsMutex);
m_clients.remove(proxy);
deleteClient(proxy);
LOG((CLOG_DEBUG "ipc client proxy removed, connected=%d", m_clients.size()));
}
void
IpcServer::handleMessageReceived(const Event& e, void* /*unused*/)
{
Event event(m_events->forIpcServer().messageReceived(), this);
event.setDataObject(e.getDataObject());
m_events->addEvent(event);
}
void
IpcServer::deleteClient(IpcClientProxy* proxy)
{
m_events->removeHandler(m_events->forIpcClientProxy().messageReceived(), proxy);
m_events->removeHandler(m_events->forIpcClientProxy().disconnected(), proxy);
delete proxy;
}
bool
IpcServer::hasClients(EIpcClientType clientType) const
{
ArchMutexLock lock(m_clientsMutex);
if (m_clients.empty()) {
return false;
}
ClientList::const_iterator it;
for (it = m_clients.begin(); it != m_clients.end(); it++) {
// at least one client is alive and type matches, there are clients.
IpcClientProxy* p = *it;
if (!p->m_disconnecting && p->m_clientType == clientType) {
return true;
}
}
// all clients must be disconnecting, no active clients.
return false;
}
void
IpcServer::send(const IpcMessage& message, EIpcClientType filterType)
{
ArchMutexLock lock(m_clientsMutex);
ClientList::iterator it;
for (it = m_clients.begin(); it != m_clients.end(); it++) {
IpcClientProxy* proxy = *it;
if (proxy->m_clientType == filterType) {
proxy->send(message);
}
}
}

View File

@@ -1,92 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ipc/Ipc.h"
#include "net/TCPListenSocket.h"
#include "net/NetworkAddress.h"
#include "arch/Arch.h"
#include "base/EventTypes.h"
#include <list>
class Event;
class IpcClientProxy;
class IpcMessage;
class IEventQueue;
class SocketMultiplexer;
//! IPC server for communication between daemon and GUI.
/*!
The IPC server listens on localhost. The IPC client runs on both the
client/server process or the GUI. The IPC server runs on the daemon process.
This allows the GUI to send config changes to the daemon and client/server,
and allows the daemon and client/server to send log data to the GUI.
*/
class IpcServer {
public:
IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer);
IpcServer(IEventQueue* events, SocketMultiplexer* socketMultiplexer, int port);
virtual ~IpcServer();
//! @name manipulators
//@{
//! Opens a TCP socket only allowing local connections.
virtual void listen();
//! Send a message to all clients matching the filter type.
virtual void send(const IpcMessage& message, EIpcClientType filterType);
//@}
//! @name accessors
//@{
//! Returns true when there are clients of the specified type connected.
virtual bool hasClients(EIpcClientType clientType) const;
//@}
private:
void init();
void handleClientConnecting(const Event&, void*);
void handleClientDisconnected(const Event&, void*);
void handleMessageReceived(const Event&, void*);
void deleteClient(IpcClientProxy* proxy);
private:
typedef std::list<IpcClientProxy*> ClientList;
bool m_mock;
IEventQueue* m_events;
SocketMultiplexer* m_socketMultiplexer;
TCPListenSocket* m_socket{};
NetworkAddress m_address;
ClientList m_clients;
ArchMutex m_clientsMutex{};
#ifdef TEST_ENV
public:
IpcServer() :
m_mock(true),
m_events(nullptr),
m_socketMultiplexer(nullptr),
m_socket(nullptr) { }
#endif
};

View File

@@ -1,123 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ipc/IpcServerProxy.h"
#include "base/Log.h"
#include "base/TMethodEventJob.h"
#include "core/ProtocolUtil.h"
#include "io/IStream.h"
#include "ipc/Ipc.h"
#include "ipc/IpcMessage.h"
//
// IpcServerProxy
//
IpcServerProxy::IpcServerProxy(synergy::IStream& stream, IEventQueue* events) :
m_stream(stream),
m_events(events)
{
m_events->adoptHandler(m_events->forIStream().inputReady(),
stream.getEventTarget(),
new TMethodEventJob<IpcServerProxy>(
this, &IpcServerProxy::handleData));
}
IpcServerProxy::~IpcServerProxy()
{
m_events->removeHandler(m_events->forIStream().inputReady(),
m_stream.getEventTarget());
}
void
IpcServerProxy::handleData(const Event& /*unused*/, void* /*unused*/)
{
LOG((CLOG_DEBUG "start ipc handle data"));
UInt8 code[4];
UInt32 n = m_stream.read(code, 4);
while (n != 0) {
LOG((CLOG_DEBUG "ipc read: %c%c%c%c",
code[0], code[1], code[2], code[3]));
IpcMessage* m = nullptr;
if (memcmp(code, kIpcMsgLogLine, 4) == 0) {
m = parseLogLine();
}
else if (memcmp(code, kIpcMsgShutdown, 4) == 0) {
m = new IpcShutdownMessage();
}
else {
LOG((CLOG_ERR "invalid ipc message"));
disconnect();
}
// don't delete with this event; the data is passed to a new event.
Event e(m_events->forIpcServerProxy().messageReceived(), this, nullptr, Event::kDontFreeData);
e.setDataObject(m);
m_events->addEvent(e);
n = m_stream.read(code, 4);
}
LOG((CLOG_DEBUG "finished ipc handle data"));
}
void
IpcServerProxy::send(const IpcMessage& message)
{
LOG((CLOG_DEBUG4 "ipc write: %d", message.type()));
switch (message.type()) {
case kIpcHello: {
const auto& hm = dynamic_cast<const IpcHelloMessage&>(message);
ProtocolUtil::writef(&m_stream, kIpcMsgHello, hm.clientType());
break;
}
case kIpcCommand: {
const auto& cm = dynamic_cast<const IpcCommandMessage&>(message);
const String command = cm.command();
ProtocolUtil::writef(&m_stream, kIpcMsgCommand, &command);
break;
}
default:
LOG((CLOG_ERR "ipc message not supported: %d", message.type()));
break;
}
}
IpcLogLineMessage*
IpcServerProxy::parseLogLine()
{
String logLine;
ProtocolUtil::readf(&m_stream, kIpcMsgLogLine + 4, &logLine);
// must be deleted by event handler.
return new IpcLogLineMessage(logLine);
}
void
IpcServerProxy::disconnect()
{
LOG((CLOG_DEBUG "ipc disconnect, closing stream"));
m_stream.close();
}

View File

@@ -1,46 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "base/Event.h"
#include "base/EventTypes.h"
namespace synergy { class IStream; }
class IpcMessage;
class IpcLogLineMessage;
class IEventQueue;
class IpcServerProxy {
friend class IpcClient;
public:
IpcServerProxy(synergy::IStream& stream, IEventQueue* events);
virtual ~IpcServerProxy();
private:
void send(const IpcMessage& message);
void handleData(const Event&, void*);
IpcLogLineMessage* parseLogLine();
void disconnect();
private:
synergy::IStream& m_stream;
IEventQueue* m_events;
};

View File

@@ -40,7 +40,7 @@ add_library(platform STATIC ${sources})
target_link_libraries(platform client ${libs})
if (UNIX)
target_link_libraries(platform io net ipc core client ${libs})
target_link_libraries(platform io net core client ${libs})
endif()
if (APPLE)

View File

@@ -18,7 +18,7 @@
#include "platform/MSWindowsDesks.h"
#include "synwinhk/synwinhk.h"
#include "platform/synwinhk.h"
#include "platform/MSWindowsScreen.h"
#include "core/IScreenSaver.h"
#include "core/XScreen.h"
@@ -92,8 +92,7 @@
// MSWindowsDesks
//
MSWindowsDesks::MSWindowsDesks(
bool isPrimary, bool noHooks, HINSTANCE hookLibrary,
MSWindowsDesks::MSWindowsDesks(bool isPrimary, bool noHooks,
const IScreenSaver* screensaver, IEventQueue* events,
IJob* updateKeys, bool stopOnDeskSwitch) :
m_isPrimary(isPrimary),
@@ -114,11 +113,8 @@ MSWindowsDesks::MSWindowsDesks(
m_events(events),
m_stopOnDeskSwitch(stopOnDeskSwitch)
{
if (hookLibrary != NULL)
queryHookLibrary(hookLibrary);
if (m_install != NULL && !m_isPrimary) {
m_install();
if (!m_isPrimary) {
MSWindowsHook::install();
}
m_cursor = createBlankCursor();
@@ -352,35 +348,6 @@ MSWindowsDesks::sendMessage(UINT msg, WPARAM wParam, LPARAM lParam) const
}
}
void
MSWindowsDesks::queryHookLibrary(HINSTANCE hookLibrary)
{
// look up functions
if (!m_noHooks) {
m_install = (InstallFunc)GetProcAddress(hookLibrary, "install");
m_uninstall = (UninstallFunc)GetProcAddress(hookLibrary, "uninstall");
m_installScreensaver =
(InstallScreenSaverFunc)GetProcAddress(
hookLibrary, "installScreenSaver");
m_uninstallScreensaver =
(UninstallScreenSaverFunc)GetProcAddress(
hookLibrary, "uninstallScreenSaver");
if (m_install == NULL ||
m_uninstall == NULL ||
m_installScreensaver == NULL ||
m_uninstallScreensaver == NULL) {
LOG((CLOG_ERR "Invalid hook library"));
throw XScreenOpenFailure();
}
}
else {
m_install = NULL;
m_uninstall = NULL;
m_installScreensaver = NULL;
m_uninstallScreensaver = NULL;
}
}
HCURSOR
MSWindowsDesks::createBlankCursor() const
{
@@ -695,12 +662,12 @@ MSWindowsDesks::deskThread(void* vdesk)
case SYNERGY_MSG_SWITCH:
if (!m_noHooks) {
m_uninstall();
MSWindowsHook::uninstall();
if (m_screensaverNotify) {
m_uninstallScreensaver();
m_installScreensaver();
MSWindowsHook::uninstallScreenSaver();
MSWindowsHook::installScreenSaver();
}
switch (m_install()) {
switch (MSWindowsHook::install()) {
case kHOOK_FAILED:
// we won't work on this desk
desk->m_lowLevel = false;
@@ -776,10 +743,10 @@ MSWindowsDesks::deskThread(void* vdesk)
case SYNERGY_MSG_SCREENSAVER:
if (!m_noHooks) {
if (msg.wParam != 0) {
m_installScreensaver();
MSWindowsHook::installScreenSaver();
}
else {
m_uninstallScreensaver();
MSWindowsHook::uninstallScreenSaver();
}
}
break;

View File

@@ -18,7 +18,7 @@
#pragma once
#include "synwinhk/synwinhk.h"
#include "platform/synwinhk.h"
#include "core/key_types.h"
#include "core/mouse_types.h"
#include "core/option_types.h"
@@ -65,7 +65,7 @@ public:
\p hookLibrary must be a handle to the hook library.
*/
MSWindowsDesks(
bool isPrimary, bool noHooks, HINSTANCE hookLibrary,
bool isPrimary, bool noHooks,
const IScreenSaver* screensaver, IEventQueue* events,
IJob* updateKeys, bool stopOnDeskSwitch);
~MSWindowsDesks();
@@ -206,7 +206,6 @@ private:
typedef std::map<String, Desk*> Desks;
// initialization and shutdown operations
void queryHookLibrary(HINSTANCE hookLibrary);
HCURSOR createBlankCursor() const;
void destroyCursor(HCURSOR cursor) const;
ATOM createDeskWindowClass(bool isPrimary) const;
@@ -283,14 +282,6 @@ private:
CondVar<bool> m_deskReady;
Desks m_desks;
// hook library stuff
InstallFunc m_install;
UninstallFunc m_uninstall;
InstallScreenSaverFunc
m_installScreensaver;
UninstallScreenSaverFunc
m_uninstallScreensaver;
// keyboard stuff
IJob* m_updateKeys;
HKL m_keyLayout;

View File

@@ -17,19 +17,35 @@
*/
#include "platform/MSWindowsHook.h"
#include "core/protocol_types.h"
#include "core/XScreen.h"
#include "base/Log.h"
static const char* g_name = "synwinhk";
MSWindowsHook::MSWindowsHook() :
m_initFunc(NULL),
m_cleanupFunc(NULL),
m_setSidesFunc(NULL),
m_setZoneFunc(NULL),
m_setModeFunc(NULL),
m_instance(NULL)
static DWORD g_processID = 0;
static DWORD g_threadID = 0;
static HHOOK g_getMessage = NULL;
static HHOOK g_keyboardLL = NULL;
static HHOOK g_mouseLL = NULL;
static bool g_screenSaver = false;
static EHookMode g_mode = kHOOK_DISABLE;
static UInt32 g_zoneSides = 0;
static SInt32 g_zoneSize = 0;
static SInt32 g_xScreen = 0;
static SInt32 g_yScreen = 0;
static SInt32 g_wScreen = 0;
static SInt32 g_hScreen = 0;
static WPARAM g_deadVirtKey = 0;
static WPARAM g_deadRelease = 0;
static LPARAM g_deadLParam = 0;
static BYTE g_deadKeyState[256] = { 0 };
static BYTE g_keyState[256] = { 0 };
static DWORD g_hookThread = 0;
static bool g_fakeServerInput = false;
static BOOL g_isPrimary = TRUE;
MSWindowsHook::MSWindowsHook()
{
}
@@ -37,35 +53,18 @@ MSWindowsHook::~MSWindowsHook()
{
cleanup();
if (m_instance != NULL) {
FreeLibrary(m_instance);
if (g_processID == GetCurrentProcessId()) {
uninstall();
uninstallScreenSaver();
g_processID = 0;
}
}
void
MSWindowsHook::loadLibrary(BOOL isPrimary)
{
// load library
m_instance = LoadLibrary(g_name);
if (m_instance == NULL) {
LOG((CLOG_ERR "failed to load hook library, %s.dll is missing or invalid", g_name));
throw XScreenOpenFailure();
}
// look up functions
m_setSidesFunc = (SetSidesFunc)GetProcAddress(m_instance, "setSides");
m_setZoneFunc = (SetZoneFunc)GetProcAddress(m_instance, "setZone");
m_setModeFunc = (SetModeFunc)GetProcAddress(m_instance, "setMode");
m_initFunc = (InitFunc)GetProcAddress(m_instance, "init");
m_cleanupFunc = (CleanupFunc)GetProcAddress(m_instance, "cleanup");
if (m_setSidesFunc == NULL ||
m_setZoneFunc == NULL ||
m_setModeFunc == NULL ||
m_initFunc == NULL ||
m_cleanupFunc == NULL) {
LOG((CLOG_ERR "failed to load hook function, %s.dll could be out of date", g_name));
throw XScreenOpenFailure();
if (g_processID == 0) {
g_processID = GetCurrentProcessId();
}
// initialize library
@@ -76,53 +75,705 @@ MSWindowsHook::loadLibrary(BOOL isPrimary)
}
}
HINSTANCE
MSWindowsHook::getInstance() const
{
return m_instance;
}
int
MSWindowsHook::init(DWORD threadID, BOOL isPrimary)
{
if (m_initFunc == NULL) {
return NULL;
g_isPrimary = isPrimary;
// try to open process that last called init() to see if it's
// still running or if it died without cleaning up.
if (g_processID != 0 && g_processID != GetCurrentProcessId()) {
HANDLE process = OpenProcess(STANDARD_RIGHTS_REQUIRED,
FALSE, g_processID);
if (process != NULL) {
// old process (probably) still exists so refuse to
// reinitialize this DLL (and thus steal it from the
// old process).
int result = CloseHandle(process);
if (result == false) {
return 0;
}
}
// clean up after old process. the system should've already
// removed the hooks so we just need to reset our state.
g_processID = GetCurrentProcessId();
g_threadID = 0;
g_getMessage = NULL;
g_keyboardLL = NULL;
g_mouseLL = NULL;
g_screenSaver = false;
}
return m_initFunc(threadID, isPrimary);
// save thread id. we'll post messages to this thread's
// message queue.
g_threadID = threadID;
// set defaults
g_mode = kHOOK_DISABLE;
g_zoneSides = 0;
g_zoneSize = 0;
g_xScreen = 0;
g_yScreen = 0;
g_wScreen = 0;
g_hScreen = 0;
return 1;
}
int
MSWindowsHook::cleanup()
{
if (m_cleanupFunc == NULL) {
return NULL;
if (g_processID == GetCurrentProcessId()) {
g_threadID = 0;
}
return m_cleanupFunc();
return 1;
}
void
MSWindowsHook::setSides(UInt32 sides)
{
if (m_setSidesFunc == NULL) {
return;
}
m_setSidesFunc(sides);
g_zoneSides = sides;
}
void
MSWindowsHook::setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize)
{
if (m_setZoneFunc == NULL) {
return;
}
m_setZoneFunc(x, y, w, h, jumpZoneSize);
g_zoneSize = jumpZoneSize;
g_xScreen = x;
g_yScreen = y;
g_wScreen = w;
g_hScreen = h;
}
void
MSWindowsHook::setMode(EHookMode mode)
{
if (m_setModeFunc == NULL) {
if (mode == g_mode) {
// no change
return;
}
m_setModeFunc(mode);
g_mode = mode;
}
static
void
keyboardGetState(BYTE keys[256], DWORD vkCode, bool kf_up)
{
// we have to use GetAsyncKeyState() rather than GetKeyState() because
// we don't pass through most keys so the event synchronous state
// doesn't get updated. we do that because certain modifier keys have
// side effects, like alt and the windows key.
if (vkCode < 0 || vkCode >= 256) {
return;
}
// Keep track of key state on our own in case GetAsyncKeyState() fails
g_keyState[vkCode] = kf_up ? 0 : 0x80;
g_keyState[VK_SHIFT] = g_keyState[VK_LSHIFT] | g_keyState[VK_RSHIFT];
SHORT key;
// Test whether GetAsyncKeyState() is being honest with us
key = GetAsyncKeyState(vkCode);
if (key & 0x80) {
// The only time we know for sure that GetAsyncKeyState() is working
// is when it tells us that the current key is down.
// In this case, update g_keyState to reflect what GetAsyncKeyState()
// is telling us, just in case we have gotten out of sync
for (int i = 0; i < 256; ++i) {
key = GetAsyncKeyState(i);
g_keyState[i] = (BYTE)((key < 0) ? 0x80u : 0);
}
}
// copy g_keyState to keys
for (int i = 0; i < 256; ++i) {
keys[i] = g_keyState[i];
}
key = GetKeyState(VK_CAPITAL);
keys[VK_CAPITAL] = (BYTE)(((key < 0) ? 0x80 : 0) | (key & 1));
}
static
WPARAM
makeKeyMsg(UINT virtKey, char c, bool noAltGr)
{
return MAKEWPARAM(MAKEWORD(virtKey & 0xff, (BYTE)c), noAltGr ? 1 : 0);
}
static
bool
keyboardHookHandler(WPARAM wParam, LPARAM lParam)
{
DWORD vkCode = static_cast<DWORD>(wParam);
bool kf_up = (lParam & (KF_UP << 16)) != 0;
// check for special events indicating if we should start or stop
// passing events through and not report them to the server. this
// is used to allow the server to synthesize events locally but
// not pick them up as user events.
if (wParam == SYNERGY_HOOK_FAKE_INPUT_VIRTUAL_KEY &&
((lParam >> 16) & 0xffu) == SYNERGY_HOOK_FAKE_INPUT_SCANCODE) {
// update flag
g_fakeServerInput = ((lParam & 0x80000000u) == 0);
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
0xff000000u | wParam, lParam);
// discard event
return true;
}
// if we're expecting fake input then just pass the event through
// and do not forward to the server
if (g_fakeServerInput) {
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
0xfe000000u | wParam, lParam);
return false;
}
// VK_RSHIFT may be sent with an extended scan code but right shift
// is not an extended key so we reset that bit.
if (wParam == VK_RSHIFT) {
lParam &= ~0x01000000u;
}
// tell server about event
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG, wParam, lParam);
// ignore dead key release
if ((g_deadVirtKey == wParam || g_deadRelease == wParam) &&
(lParam & 0x80000000u) != 0) {
g_deadRelease = 0;
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
wParam | 0x04000000, lParam);
return false;
}
// we need the keyboard state for ToAscii()
BYTE keys[256];
keyboardGetState(keys, vkCode, kf_up);
// ToAscii() maps ctrl+letter to the corresponding control code
// and ctrl+backspace to delete. we don't want those translations
// so clear the control modifier state. however, if we want to
// simulate AltGr (which is ctrl+alt) then we must not clear it.
UINT control = keys[VK_CONTROL] | keys[VK_LCONTROL] | keys[VK_RCONTROL];
UINT menu = keys[VK_MENU] | keys[VK_LMENU] | keys[VK_RMENU];
if ((control & 0x80) == 0 || (menu & 0x80) == 0) {
keys[VK_LCONTROL] = 0;
keys[VK_RCONTROL] = 0;
keys[VK_CONTROL] = 0;
}
else {
keys[VK_LCONTROL] = 0x80;
keys[VK_RCONTROL] = 0x80;
keys[VK_CONTROL] = 0x80;
keys[VK_LMENU] = 0x80;
keys[VK_RMENU] = 0x80;
keys[VK_MENU] = 0x80;
}
// ToAscii() needs to know if a menu is active for some reason.
// we don't know and there doesn't appear to be any way to find
// out. so we'll just assume a menu is active if the menu key
// is down.
// FIXME -- figure out some way to check if a menu is active
UINT flags = 0;
if ((menu & 0x80) != 0)
flags |= 1;
// if we're on the server screen then just pass numpad keys with alt
// key down as-is. we won't pick up the resulting character but the
// local app will. if on a client screen then grab keys as usual;
// if the client is a windows system it'll synthesize the expected
// character. if not then it'll probably just do nothing.
if (g_mode != kHOOK_RELAY_EVENTS) {
// we don't use virtual keys because we don't know what the
// state of the numlock key is. we'll hard code the scan codes
// instead. hopefully this works across all keyboards.
UINT sc = (lParam & 0x01ff0000u) >> 16;
if (menu &&
(sc >= 0x47u && sc <= 0x52u && sc != 0x4au && sc != 0x4eu)) {
return false;
}
}
WORD c = 0;
// map the key event to a character. we have to put the dead
// key back first and this has the side effect of removing it.
if (g_deadVirtKey != 0) {
if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags) == 2)
{
// If ToAscii returned 2, it means that we accidentally removed
// a double dead key instead of restoring it. Thus, we call
// ToAscii again with the same parameters to restore the
// internal dead key state.
ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags);
// We need to keep track of this because g_deadVirtKey will be
// cleared later on; this would cause the dead key release to
// incorrectly restore the dead key state.
g_deadRelease = g_deadVirtKey;
}
}
UINT scanCode = ((lParam & 0x10ff0000u) >> 16);
int n = ToAscii((UINT)wParam, scanCode, keys, &c, flags);
// if mapping failed and ctrl and alt are pressed then try again
// with both not pressed. this handles the case where ctrl and
// alt are being used as individual modifiers rather than AltGr.
// we note that's the case in the message sent back to synergy
// because there's no simple way to deduce it after the fact.
// we have to put the dead key back first, if there was one.
bool noAltGr = false;
if (n == 0 && (control & 0x80) != 0 && (menu & 0x80) != 0) {
noAltGr = true;
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
wParam | 0x05000000, lParam);
if (g_deadVirtKey != 0) {
if (ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags) == 2)
{
ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags);
g_deadRelease = g_deadVirtKey;
}
}
BYTE keys2[256];
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
keys2[i] = keys[i];
}
keys2[VK_LCONTROL] = 0;
keys2[VK_RCONTROL] = 0;
keys2[VK_CONTROL] = 0;
keys2[VK_LMENU] = 0;
keys2[VK_RMENU] = 0;
keys2[VK_MENU] = 0;
n = ToAscii((UINT)wParam, scanCode, keys2, &c, flags);
}
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
wParam | ((c & 0xff) << 8) |
((n & 0xff) << 16) | 0x06000000,
lParam);
WPARAM charAndVirtKey = 0;
bool clearDeadKey = false;
switch (n) {
default:
// key is a dead key
if (lParam & 0x80000000u)
// This handles the obscure situation where a key has been
// pressed which is both a dead key and a normal character
// depending on which modifiers have been pressed. We
// break here to prevent it from being considered a dead
// key.
break;
g_deadVirtKey = wParam;
g_deadLParam = lParam;
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
g_deadKeyState[i] = keys[i];
}
break;
case 0:
// key doesn't map to a character. this can happen if
// non-character keys are pressed after a dead key.
charAndVirtKey = makeKeyMsg((UINT)wParam, (char)0, noAltGr);
break;
case 1:
// key maps to a character composed with dead key
charAndVirtKey = makeKeyMsg((UINT)wParam, (char)LOBYTE(c), noAltGr);
clearDeadKey = true;
break;
case 2: {
// previous dead key not composed. send a fake key press
// and release for the dead key to our window.
WPARAM deadCharAndVirtKey =
makeKeyMsg((UINT)g_deadVirtKey, (char)LOBYTE(c), noAltGr);
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
deadCharAndVirtKey, g_deadLParam & 0x7fffffffu);
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY,
deadCharAndVirtKey, g_deadLParam | 0x80000000u);
// use uncomposed character
charAndVirtKey = makeKeyMsg((UINT)wParam, (char)HIBYTE(c), noAltGr);
clearDeadKey = true;
break;
}
}
// put back the dead key, if any, for the application to use
if (g_deadVirtKey != 0) {
ToAscii((UINT)g_deadVirtKey, (g_deadLParam & 0x10ff0000u) >> 16,
g_deadKeyState, &c, flags);
}
// clear out old dead key state
if (clearDeadKey) {
g_deadVirtKey = 0;
g_deadLParam = 0;
}
// forward message to our window. do this whether or not we're
// forwarding events to clients because this'll keep our thread's
// key state table up to date. that's important for querying
// the scroll lock toggle state.
// XXX -- with hot keys for actions we may only need to do this when
// forwarding.
if (charAndVirtKey != 0) {
PostThreadMessage(g_threadID, SYNERGY_MSG_DEBUG,
charAndVirtKey | 0x07000000, lParam);
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, charAndVirtKey, lParam);
}
if (g_mode == kHOOK_RELAY_EVENTS) {
// let certain keys pass through
switch (wParam) {
case VK_CAPITAL:
case VK_NUMLOCK:
case VK_SCROLL:
// pass event on. we want to let these through to
// the window proc because otherwise the keyboard
// lights may not stay synchronized.
break;
case VK_HANGUL:
// pass these modifiers if using a low level hook, discard
// them if not.
if (g_hookThread == 0) {
return true;
}
break;
default:
// discard
return true;
}
}
return false;
}
#if !NO_GRAB_KEYBOARD
static
LRESULT CALLBACK
keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0) {
// decode the message
KBDLLHOOKSTRUCT* info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
bool const injected = info->flags & LLKHF_INJECTED;
if (!g_isPrimary && injected) {
return CallNextHookEx (g_keyboardLL, code, wParam, lParam);
}
WPARAM wParam = info->vkCode;
LPARAM lParam = 1; // repeat code
lParam |= (info->scanCode << 16); // scan code
if (info->flags & LLKHF_EXTENDED) {
lParam |= (1lu << 24); // extended key
}
if (info->flags & LLKHF_ALTDOWN) {
lParam |= (1lu << 29); // context code
}
if (info->flags & LLKHF_UP) {
lParam |= (1lu << 31); // transition
}
// FIXME -- bit 30 should be set if key was already down but
// we don't know that info. as a result we'll never generate
// key repeat events.
// handle the message
if (keyboardHookHandler(wParam, lParam)) {
return 1;
}
}
return CallNextHookEx(g_keyboardLL, code, wParam, lParam);
}
#endif
//
// low-level mouse hook -- this allows us to capture and handle mouse
// events very early. the earlier the better.
//
static
bool
mouseHookHandler(WPARAM wParam, SInt32 x, SInt32 y, SInt32 data)
{
switch (wParam) {
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_XBUTTONDOWN:
case WM_LBUTTONDBLCLK:
case WM_MBUTTONDBLCLK:
case WM_RBUTTONDBLCLK:
case WM_XBUTTONDBLCLK:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_XBUTTONUP:
case WM_NCLBUTTONDOWN:
case WM_NCMBUTTONDOWN:
case WM_NCRBUTTONDOWN:
case WM_NCXBUTTONDOWN:
case WM_NCLBUTTONDBLCLK:
case WM_NCMBUTTONDBLCLK:
case WM_NCRBUTTONDBLCLK:
case WM_NCXBUTTONDBLCLK:
case WM_NCLBUTTONUP:
case WM_NCMBUTTONUP:
case WM_NCRBUTTONUP:
case WM_NCXBUTTONUP:
// always relay the event. eat it if relaying.
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_BUTTON, wParam, data);
return (g_mode == kHOOK_RELAY_EVENTS);
case WM_MOUSEWHEEL:
if (g_mode == kHOOK_RELAY_EVENTS) {
// relay event
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL, data, 0);
}
return (g_mode == kHOOK_RELAY_EVENTS);
case WM_NCMOUSEMOVE:
case WM_MOUSEMOVE:
if (g_mode == kHOOK_RELAY_EVENTS) {
// relay and eat event
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
return true;
}
else if (g_mode == kHOOK_WATCH_JUMP_ZONE) {
// low level hooks can report bogus mouse positions that are
// outside of the screen. jeez. naturally we end up getting
// fake motion in the other direction to get the position back
// on the screen, which plays havoc with switch on double tap.
// Server deals with that. we'll clamp positions onto the
// screen. also, if we discard events for positions outside
// of the screen then the mouse appears to get a bit jerky
// near the edge. we can either accept that or pass the bogus
// events. we'll try passing the events.
bool bogus = false;
if (x < g_xScreen) {
x = g_xScreen;
bogus = true;
}
else if (x >= g_xScreen + g_wScreen) {
x = g_xScreen + g_wScreen - 1;
bogus = true;
}
if (y < g_yScreen) {
y = g_yScreen;
bogus = true;
}
else if (y >= g_yScreen + g_hScreen) {
y = g_yScreen + g_hScreen - 1;
bogus = true;
}
// check for mouse inside jump zone
bool inside = false;
if (!inside && (g_zoneSides & kLeftMask) != 0) {
inside = (x < g_xScreen + g_zoneSize);
}
if (!inside && (g_zoneSides & kRightMask) != 0) {
inside = (x >= g_xScreen + g_wScreen - g_zoneSize);
}
if (!inside && (g_zoneSides & kTopMask) != 0) {
inside = (y < g_yScreen + g_zoneSize);
}
if (!inside && (g_zoneSides & kBottomMask) != 0) {
inside = (y >= g_yScreen + g_hScreen - g_zoneSize);
}
// relay the event
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
// if inside and not bogus then eat the event
return inside && !bogus;
}
}
// pass the event
return false;
}
static
LRESULT CALLBACK
mouseLLHook(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0) {
// decode the message
MSLLHOOKSTRUCT* info = reinterpret_cast<MSLLHOOKSTRUCT*>(lParam);
bool const injected = info->flags & LLMHF_INJECTED;
if (!g_isPrimary && injected) {
return CallNextHookEx(g_mouseLL, code, wParam, lParam);
}
SInt32 x = static_cast<SInt32>(info->pt.x);
SInt32 y = static_cast<SInt32>(info->pt.y);
SInt32 w = static_cast<SInt16>(HIWORD(info->mouseData));
// handle the message
if (mouseHookHandler(wParam, x, y, w)) {
return 1;
}
}
return CallNextHookEx(g_mouseLL, code, wParam, lParam);
}
EHookResult
MSWindowsHook::install()
{
assert(g_getMessage == NULL || g_screenSaver);
// must be initialized
if (g_threadID == 0) {
return kHOOK_FAILED;
}
// discard old dead keys
g_deadVirtKey = 0;
g_deadLParam = 0;
// reset fake input flag
g_fakeServerInput = false;
// install low-level hooks. we require that they both get installed.
g_mouseLL = SetWindowsHookEx(WH_MOUSE_LL,
&mouseLLHook,
NULL,
0);
#if !NO_GRAB_KEYBOARD
g_keyboardLL = SetWindowsHookEx(WH_KEYBOARD_LL,
&keyboardLLHook,
NULL,
0);
if (g_mouseLL == NULL || g_keyboardLL == NULL) {
if (g_keyboardLL != NULL) {
UnhookWindowsHookEx(g_keyboardLL);
g_keyboardLL = NULL;
}
if (g_mouseLL != NULL) {
UnhookWindowsHookEx(g_mouseLL);
g_mouseLL = NULL;
}
}
#endif
// check that we got all the hooks we wanted
if ((g_mouseLL == NULL) ||
#if !NO_GRAB_KEYBOARD
(g_keyboardLL == NULL)
#endif
) {
uninstall();
return kHOOK_FAILED;
}
if (g_keyboardLL != NULL || g_mouseLL != NULL) {
g_hookThread = GetCurrentThreadId();
return kHOOK_OKAY_LL;
}
return kHOOK_OKAY;
}
int
MSWindowsHook::uninstall()
{
// discard old dead keys
g_deadVirtKey = 0;
g_deadLParam = 0;
// uninstall hooks
if (g_keyboardLL != NULL) {
UnhookWindowsHookEx(g_keyboardLL);
g_keyboardLL = NULL;
}
if (g_mouseLL != NULL) {
UnhookWindowsHookEx(g_mouseLL);
g_mouseLL = NULL;
}
if (g_getMessage != NULL && !g_screenSaver) {
UnhookWindowsHookEx(g_getMessage);
g_getMessage = NULL;
}
return 1;
}
static
LRESULT CALLBACK
getMessageHook(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0) {
if (g_screenSaver) {
MSG* msg = reinterpret_cast<MSG*>(lParam);
if (msg->message == WM_SYSCOMMAND &&
msg->wParam == SC_SCREENSAVE) {
// broadcast screen saver started message
PostThreadMessage(g_threadID,
SYNERGY_MSG_SCREEN_SAVER, TRUE, 0);
}
}
}
return CallNextHookEx(g_getMessage, code, wParam, lParam);
}
int
MSWindowsHook::installScreenSaver()
{
// must be initialized
if (g_threadID == 0) {
return 0;
}
// generate screen saver messages
g_screenSaver = true;
// install hook unless it's already installed
if (g_getMessage == NULL) {
g_getMessage = SetWindowsHookEx(WH_GETMESSAGE,
&getMessageHook,
NULL,
0);
}
return (g_getMessage != NULL) ? 1 : 0;
}
int
MSWindowsHook::uninstallScreenSaver()
{
// uninstall hook unless the mouse wheel hook is installed
if (g_getMessage != NULL) {
UnhookWindowsHookEx(g_getMessage);
g_getMessage = NULL;
}
// screen saver hook is no longer installed
g_screenSaver = false;
return 1;
}

View File

@@ -18,7 +18,7 @@
#pragma once
#include "synwinhk/synwinhk.h"
#include "platform/synwinhk.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@@ -31,18 +31,14 @@ public:
virtual ~MSWindowsHook();
void loadLibrary(BOOL isPrimary);
HINSTANCE getInstance() const;
int init(DWORD threadID, BOOL isPrimary);
int cleanup();
void setSides(UInt32 sides);
void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h, SInt32 jumpZoneSize);
void setMode(EHookMode mode);
private:
InitFunc m_initFunc;
CleanupFunc m_cleanupFunc;
SetSidesFunc m_setSidesFunc;
SetZoneFunc m_setZoneFunc;
SetModeFunc m_setModeFunc;
HINSTANCE m_instance;
static EHookResult install();
static int uninstall();
static int installScreenSaver();
static int uninstallScreenSaver();
};

View File

@@ -136,7 +136,6 @@ MSWindowsScreen::MSWindowsScreen(
m_desks = new MSWindowsDesks(
m_isPrimary,
m_noHooks,
m_hook.getInstance(),
m_screensaver,
m_events,
new TMethodJob<MSWindowsScreen>(

View File

@@ -21,7 +21,7 @@
#include "platform/MSWindowsHook.h"
#include "core/PlatformScreen.h"
#include "core/DragInformation.h"
#include "synwinhk/synwinhk.h"
#include "platform/synwinhk.h"
#include "mt/CondVar.h"
#include "mt/Mutex.h"
#include "base/String.h"

View File

@@ -1,588 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2009 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "platform/MSWindowsWatchdog.h"
#include "ipc/IpcLogOutputter.h"
#include "ipc/IpcServer.h"
#include "ipc/IpcMessage.h"
#include "ipc/Ipc.h"
#include "core/App.h"
#include "core/ArgsBase.h"
#include "mt/Thread.h"
#include "arch/win32/ArchDaemonWindows.h"
#include "arch/win32/XArchWindows.h"
#include "arch/Arch.h"
#include "base/log_outputters.h"
#include "base/TMethodJob.h"
#include "base/Log.h"
#include "common/Version.h"
#include <sstream>
#include <UserEnv.h>
#include <Shellapi.h>
#define MAXIMUM_WAIT_TIME 3
enum {
kOutputBufferSize = 4096
};
typedef VOID (WINAPI *SendSas)(BOOL asUser);
const char g_activeDesktop[] = {"activeDesktop:"};
MSWindowsWatchdog::MSWindowsWatchdog(
bool autoDetectCommand,
IpcServer& ipcServer,
IpcLogOutputter& ipcLogOutputter) :
m_thread(NULL),
m_autoDetectCommand(autoDetectCommand),
m_monitoring(true),
m_commandChanged(false),
m_stdOutWrite(NULL),
m_stdOutRead(NULL),
m_ipcServer(ipcServer),
m_ipcLogOutputter(ipcLogOutputter),
m_elevateProcess(false),
m_processFailures(0),
m_processRunning(false),
m_fileLogOutputter(NULL),
m_autoElevated(false),
m_ready(false)
{
m_mutex = ARCH->newMutex();
m_condVar = ARCH->newCondVar();
}
MSWindowsWatchdog::~MSWindowsWatchdog()
{
if (m_condVar != NULL) {
ARCH->closeCondVar(m_condVar);
}
if (m_mutex != NULL) {
ARCH->closeMutex(m_mutex);
}
}
void
MSWindowsWatchdog::startAsync()
{
m_thread = new Thread(new TMethodJob<MSWindowsWatchdog>(
this, &MSWindowsWatchdog::mainLoop, nullptr));
m_outputThread = new Thread(new TMethodJob<MSWindowsWatchdog>(
this, &MSWindowsWatchdog::outputLoop, nullptr));
}
void
MSWindowsWatchdog::stop()
{
m_monitoring = false;
m_thread->wait(5);
delete m_thread;
m_outputThread->wait(5);
delete m_outputThread;
}
HANDLE
MSWindowsWatchdog::duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security)
{
HANDLE sourceToken;
BOOL tokenRet = OpenProcessToken(
process,
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS,
&sourceToken);
if (!tokenRet) {
LOG((CLOG_ERR "could not open token, process handle: %d", process));
throw XArch(new XArchEvalWindows());
}
LOG((CLOG_DEBUG "got token %i, duplicating", sourceToken));
HANDLE newToken;
BOOL duplicateRet = DuplicateTokenEx(
sourceToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, security,
SecurityImpersonation, TokenPrimary, &newToken);
if (!duplicateRet) {
LOG((CLOG_ERR "could not duplicate token %i", sourceToken));
throw XArch(new XArchEvalWindows());
}
LOG((CLOG_DEBUG "duplicated, new token: %i", newToken));
return newToken;
}
HANDLE
MSWindowsWatchdog::getUserToken(LPSECURITY_ATTRIBUTES security)
{
// always elevate if we are at the vista/7 login screen. we could also
// elevate for the uac dialog (consent.exe) but this would be pointless,
// since synergy would re-launch as non-elevated after the desk switch,
// and so would be unusable with the new elevated process taking focus.
if (m_elevateProcess
|| m_autoElevated
|| m_session.isProcessInSession("logonui.exe", NULL)) {
LOG((CLOG_DEBUG "getting elevated token, %s",
(m_elevateProcess ? "elevation required" : "at login screen")));
HANDLE process;
if (!m_session.isProcessInSession("winlogon.exe", &process)) {
throw XMSWindowsWatchdogError("cannot get user token without winlogon.exe");
}
return duplicateProcessToken(process, security);
}
else {
LOG((CLOG_DEBUG "getting non-elevated token"));
return m_session.getUserToken(security);
}
}
void
MSWindowsWatchdog::mainLoop(void*)
{
shutdownExistingProcesses();
SendSas sendSasFunc = NULL;
HINSTANCE sasLib = LoadLibrary("sas.dll");
if (sasLib) {
LOG((CLOG_DEBUG "found sas.dll"));
sendSasFunc = (SendSas)GetProcAddress(sasLib, "SendSAS");
}
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&m_stdOutRead, &m_stdOutWrite, &saAttr, 0)) {
throw XArch(new XArchEvalWindows());
}
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
while (m_monitoring) {
try {
if (m_processRunning && getCommand().empty()) {
LOG((CLOG_INFO "process started but command is empty, shutting down"));
shutdownExistingProcesses();
m_processRunning = false;
continue;
}
if (m_processFailures != 0) {
// increasing backoff period, maximum of 10 seconds.
int timeout = (m_processFailures * 2) < 10 ? (m_processFailures * 2) : 10;
LOG((CLOG_INFO "backing off, wait=%ds, failures=%d", timeout, m_processFailures));
ARCH->sleep(timeout);
}
if (!getCommand().empty() && ((m_processFailures != 0) || m_session.hasChanged() || m_commandChanged)) {
startProcess();
}
if (m_processRunning && !isProcessActive()) {
m_processFailures++;
m_processRunning = false;
LOG((CLOG_WARN "detected application not running, pid=%d",
m_processInfo.dwProcessId));
}
if (sendSasFunc != NULL) {
HANDLE sendSasEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\SendSAS");
if (sendSasEvent != NULL) {
// use SendSAS event to wait for next session (timeout 1 second).
if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0) {
LOG((CLOG_DEBUG "calling SendSAS"));
sendSasFunc(FALSE);
}
CloseHandle(sendSasEvent);
continue;
}
}
// if the sas event failed, wait by sleeping.
ARCH->sleep(1);
}
catch (std::exception& e) {
LOG((CLOG_ERR "failed to launch, error: %s", e.what()));
m_processFailures++;
m_processRunning = false;
continue;
}
catch (...) {
LOG((CLOG_ERR "failed to launch, unknown error."));
m_processFailures++;
m_processRunning = false;
continue;
}
}
if (m_processRunning) {
LOG((CLOG_DEBUG "terminated running process on exit"));
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
}
LOG((CLOG_DEBUG "watchdog main thread finished"));
}
bool
MSWindowsWatchdog::isProcessActive()
{
DWORD exitCode;
GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
return exitCode == STILL_ACTIVE;
}
void
MSWindowsWatchdog::setFileLogOutputter(FileLogOutputter* outputter)
{
m_fileLogOutputter = outputter;
}
void
MSWindowsWatchdog::startProcess()
{
if (m_command.empty()) {
throw XMSWindowsWatchdogError("cannot start process, command is empty");
}
m_commandChanged = false;
if (m_processRunning) {
LOG((CLOG_DEBUG "closing existing process to make way for new one"));
shutdownProcess(m_processInfo.hProcess, m_processInfo.dwProcessId, 20);
m_processRunning = false;
}
m_session.updateActiveSession();
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
getActiveDesktop(&sa);
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
HANDLE userToken = getUserToken(&sa);
m_elevateProcess = m_autoElevated ? m_autoElevated : m_elevateProcess;
m_autoElevated = false;
// patch by Jack Zhou and Henry Tung
// set UIAccess to fix Windows 8 GUI interaction
// http://symless.com/spit/issues/details/3338/#c70
DWORD uiAccess = 1;
SetTokenInformation(userToken, TokenUIAccess, &uiAccess, sizeof(DWORD));
BOOL createRet = doStartProcess(m_command, userToken, &sa);
if (!createRet) {
LOG((CLOG_ERR "could not launch"));
DWORD exitCode = 0;
GetExitCodeProcess(m_processInfo.hProcess, &exitCode);
LOG((CLOG_ERR "exit code: %d", exitCode));
throw XArch(new XArchEvalWindows);
}
else {
// wait for program to fail.
ARCH->sleep(1);
if (!isProcessActive()) {
throw XMSWindowsWatchdogError("process immediately stopped");
}
m_processRunning = true;
m_processFailures = 0;
LOG((CLOG_DEBUG "started process, session=%i, elevated: %s, command=%s",
m_session.getActiveSessionId(),
m_elevateProcess ? "yes" : "no",
m_command.c_str()));
}
}
BOOL
MSWindowsWatchdog::doStartProcess(String& command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa)
{
// clear, as we're reusing process info struct
ZeroMemory(&m_processInfo, sizeof(PROCESS_INFORMATION));
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = "winsta0\\Default"; // TODO: maybe this should be \winlogon if we have logonui.exe?
si.hStdError = m_stdOutWrite;
si.hStdOutput = m_stdOutWrite;
si.dwFlags |= STARTF_USESTDHANDLES;
LPVOID environment;
BOOL blockRet = CreateEnvironmentBlock(&environment, userToken, FALSE);
if (!blockRet) {
LOG((CLOG_ERR "could not create environment block"));
throw XArch(new XArchEvalWindows);
}
DWORD creationFlags =
NORMAL_PRIORITY_CLASS |
CREATE_NO_WINDOW |
CREATE_UNICODE_ENVIRONMENT;
// re-launch in current active user session
LOG((CLOG_INFO "starting new process"));
BOOL createRet = CreateProcessAsUser(
userToken, NULL, LPSTR(command.c_str()),
sa, NULL, TRUE, creationFlags,
environment, NULL, &si, &m_processInfo);
DestroyEnvironmentBlock(environment);
CloseHandle(userToken);
return createRet;
}
void
MSWindowsWatchdog::setCommand(const std::string& command, bool elevate)
{
LOG((CLOG_INFO "service command updated"));
m_command = command;
m_elevateProcess = elevate;
m_commandChanged = true;
m_processFailures = 0;
}
std::string
MSWindowsWatchdog::getCommand() const
{
if (!m_autoDetectCommand) {
return m_command;
}
// seems like a fairly convoluted way to get the process name
const char* launchName = App::instance().argsBase().m_pname;
std::string args = ARCH->commandLine();
// build up a full command line
std::stringstream cmdTemp;
cmdTemp << launchName << args;
std::string cmd = cmdTemp.str();
size_t i;
std::string find = "--relaunch";
while ((i = cmd.find(find)) != std::string::npos) {
cmd.replace(i, find.length(), "");
}
return cmd;
}
void
MSWindowsWatchdog::outputLoop(void*)
{
// +1 char for \0
CHAR buffer[kOutputBufferSize + 1];
while (m_monitoring) {
DWORD bytesRead;
BOOL success = ReadFile(m_stdOutRead, buffer, kOutputBufferSize, &bytesRead, NULL);
// assume the process has gone away? slow down
// the reads until another one turns up.
if (!success || bytesRead == 0) {
ARCH->sleep(1);
}
else {
buffer[bytesRead] = '\0';
testOutput(buffer);
m_ipcLogOutputter.write(kINFO, buffer);
if (m_fileLogOutputter != NULL) {
m_fileLogOutputter->write(kINFO, buffer);
}
}
}
}
void
MSWindowsWatchdog::shutdownProcess(HANDLE handle, DWORD pid, int timeout)
{
DWORD exitCode;
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
return;
}
IpcShutdownMessage shutdown;
m_ipcServer.send(shutdown, kIpcClientNode);
// wait for process to exit gracefully.
double start = ARCH->time();
while (true) {
GetExitCodeProcess(handle, &exitCode);
if (exitCode != STILL_ACTIVE) {
// yay, we got a graceful shutdown. there should be no hook in use errors!
LOG((CLOG_INFO "process %d was shutdown gracefully", pid));
break;
}
else {
double elapsed = (ARCH->time() - start);
if (elapsed > timeout) {
// if timeout reached, kill forcefully.
// calling TerminateProcess on synergy is very bad!
// it causes the hook DLL to stay loaded in some apps,
// making it impossible to start synergy again.
LOG((CLOG_WARN "shutdown timed out after %d secs, forcefully terminating", (int)elapsed));
TerminateProcess(handle, kExitSuccess);
break;
}
ARCH->sleep(1);
}
}
}
void
MSWindowsWatchdog::shutdownExistingProcesses()
{
// first we need to take a snapshot of the running processes
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
LOG((CLOG_ERR "could not get process snapshot"));
throw XArch(new XArchEvalWindows);
}
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);
// get the first process, and if we can't do that then it's
// unlikely we can go any further
BOOL gotEntry = Process32First(snapshot, &entry);
if (!gotEntry) {
LOG((CLOG_ERR "could not get first process entry"));
throw XArch(new XArchEvalWindows);
}
// now just iterate until we can find winlogon.exe pid
DWORD pid = 0;
while (gotEntry) {
// make sure we're not checking the system process
if (entry.th32ProcessID != 0) {
if (_stricmp(entry.szExeFile, "synergyc.exe") == 0 ||
_stricmp(entry.szExeFile, "synergys.exe") == 0) {
HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);
shutdownProcess(handle, entry.th32ProcessID, 10);
}
}
// now move on to the next entry (if we're not at the end)
gotEntry = Process32Next(snapshot, &entry);
if (!gotEntry) {
DWORD err = GetLastError();
if (err != ERROR_NO_MORE_FILES) {
// only worry about error if it's not the end of the snapshot
LOG((CLOG_ERR "could not get subsiquent process entry"));
throw XArch(new XArchEvalWindows);
}
}
}
CloseHandle(snapshot);
m_processRunning = false;
}
void
MSWindowsWatchdog::getActiveDesktop(LPSECURITY_ATTRIBUTES security)
{
String installedDir = ARCH->getInstalledDirectory();
if (!installedDir.empty()) {
String syntoolCommand;
syntoolCommand.append("\"").append(installedDir).append("\\").append("syntool").append("\"");
syntoolCommand.append(" --get-active-desktop");
m_session.updateActiveSession();
bool elevateProcess = m_elevateProcess;
m_elevateProcess = true;
HANDLE userToken = getUserToken(security);
m_elevateProcess = elevateProcess;
BOOL createRet = doStartProcess(syntoolCommand, userToken, security);
if (!createRet) {
DWORD rc = GetLastError();
RevertToSelf();
}
else {
LOG((CLOG_DEBUG "launched syntool to check active desktop"));
}
ARCH->lockMutex(m_mutex);
int waitTime = 0;
while (!m_ready) {
if (waitTime >= MAXIMUM_WAIT_TIME) {
break;
}
ARCH->waitCondVar(m_condVar, m_mutex, 1.0);
waitTime++;
}
m_ready = false;
ARCH->unlockMutex(m_mutex);
}
}
void
MSWindowsWatchdog::testOutput(String buffer)
{
// HACK: check standard output seems hacky.
size_t i = buffer.find(g_activeDesktop);
if (i != String::npos) {
size_t s = sizeof(g_activeDesktop);
String defaultDesktop("Default");
String sub = buffer.substr(i + s - 1, defaultDesktop.size());
if (sub != defaultDesktop) {
m_autoElevated = true;
}
ARCH->lockMutex(m_mutex);
m_ready = true;
ARCH->broadcastCondVar(m_condVar);
ARCH->unlockMutex(m_mutex);
}
}

View File

@@ -1,96 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2009 Chris Schoeneman
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "platform/MSWindowsSession.h"
#include "core/XSynergy.h"
#include "arch/IArchMultithread.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <string>
#include <list>
class Thread;
class IpcLogOutputter;
class IpcServer;
class FileLogOutputter;
class MSWindowsWatchdog {
public:
MSWindowsWatchdog(
bool autoDetectCommand,
IpcServer& ipcServer,
IpcLogOutputter& ipcLogOutputter);
virtual ~MSWindowsWatchdog();
void startAsync();
std::string getCommand() const;
void setCommand(const std::string& command, bool elevate);
void stop();
bool isProcessActive();
void setFileLogOutputter(FileLogOutputter* outputter);
private:
void mainLoop(void*);
void outputLoop(void*);
void shutdownProcess(HANDLE handle, DWORD pid, int timeout);
void shutdownExistingProcesses();
HANDLE duplicateProcessToken(HANDLE process, LPSECURITY_ATTRIBUTES security);
HANDLE getUserToken(LPSECURITY_ATTRIBUTES security);
void startProcess();
BOOL doStartProcess(String& command, HANDLE userToken, LPSECURITY_ATTRIBUTES sa);
void sendSas();
void getActiveDesktop(LPSECURITY_ATTRIBUTES security);
void testOutput(String buffer);
private:
Thread* m_thread;
bool m_autoDetectCommand;
std::string m_command;
bool m_monitoring;
bool m_commandChanged;
HANDLE m_stdOutWrite;
HANDLE m_stdOutRead;
Thread* m_outputThread;
IpcServer& m_ipcServer;
IpcLogOutputter& m_ipcLogOutputter;
bool m_elevateProcess;
MSWindowsSession m_session;
PROCESS_INFORMATION m_processInfo;
int m_processFailures;
bool m_processRunning;
FileLogOutputter* m_fileLogOutputter;
bool m_autoElevated;
ArchMutex m_mutex;
ArchCond m_condVar;
bool m_ready;
};
//! Relauncher error
/*!
An error occured in the process watchdog.
*/
class XMSWindowsWatchdogError : public XSynergy {
public:
XMSWindowsWatchdogError(const String& msg) : XSynergy(msg) { }
// XBase overrides
virtual String getWhat() const throw() { return what(); }
};

View File

@@ -192,6 +192,7 @@ OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCurso
this);
}
else {
LOG((CLOG_DEBUG "creating quartz event tap"));
// there may be a better way to do this, but we register an event handler even if we're
// not on the primary display (acting as a client). This way, if a local event comes in
// (either keyboard or mouse), we can make sure to show the cursor if we've hidden it.
@@ -203,11 +204,13 @@ OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCurso
if (!m_eventTapPort) {
LOG((CLOG_ERR "failed to create quartz event tap"));
m_events->addEvent(Event(Event::kQuit));
}
m_eventTapRLSR = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, m_eventTapPort, 0);
if (!m_eventTapRLSR) {
LOG((CLOG_ERR "failed to create a CFRunLoopSourceRef for the quartz event tap"));
m_events->addEvent(Event(Event::kQuit));
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
@@ -248,6 +251,13 @@ OSXScreen::~OSXScreen()
delete m_keyState;
delete m_screensaver;
if (m_eventTapRLSR) {
LOG((CLOG_DEBUG "releasing quartz event tap"));
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
CFRelease(m_eventTapRLSR);
m_eventTapRLSR = nullptr;
}
if (m_eventTapPort) {
CGEventTapEnable(m_eventTapPort, false);
CFRelease(m_eventTapPort);
@@ -752,19 +762,7 @@ OSXScreen::disable()
showCursor();
}
// FIXME -- stop watching jump zones, stop capturing input
if (m_eventTapRLSR) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), m_eventTapRLSR, kCFRunLoopDefaultMode);
CFRelease(m_eventTapRLSR);
m_eventTapRLSR = nullptr;
}
if (m_eventTapPort) {
CGEventTapEnable(m_eventTapPort, false);
CFRelease(m_eventTapPort);
m_eventTapPort = nullptr;
}
// FIXME -- stop watching jump zones, stop capturing input
// FIXME -- allow system to enter power saving mode
// disable drag handling

View File

@@ -18,14 +18,6 @@
#pragma once
// hack: vs2005 doesn't declare _WIN32_WINNT, so we need to hard code it.
// however, some say that this should be hard coded since it defines the
// target system, but since this is suposed to compile on pre-XP, maybe
// we should just leave it like this.
#if _MSC_VER == 1400
#define _WIN32_WINNT 0x0400
#endif
#include "base/EventTypes.h"
#define WIN32_LEAN_AND_MEAN
@@ -67,25 +59,4 @@ enum EHookMode {
kHOOK_RELAY_EVENTS
};
typedef int (*InitFunc)(DWORD targetQueueThreadID, BOOL isPrimary);
typedef int (*CleanupFunc)(void);
typedef EHookResult (*InstallFunc)(void);
typedef int (*UninstallFunc)(void);
typedef int (*InstallScreenSaverFunc)(void);
typedef int (*UninstallScreenSaverFunc)(void);
typedef void (*SetSidesFunc)(UInt32);
typedef void (*SetZoneFunc)(SInt32, SInt32, SInt32, SInt32, SInt32);
typedef void (*SetModeFunc)(int);
CSYNERGYHOOK_API int init(DWORD, BOOL);
CSYNERGYHOOK_API int cleanup(void);
CSYNERGYHOOK_API EHookResult install(void);
CSYNERGYHOOK_API int uninstall(void);
CSYNERGYHOOK_API int installScreenSaver(void);
CSYNERGYHOOK_API int uninstallScreenSaver(void);
CSYNERGYHOOK_API void setSides(UInt32 sides);
CSYNERGYHOOK_API void setZone(SInt32 x, SInt32 y, SInt32 w, SInt32 h,
SInt32 jumpZoneSize);
CSYNERGYHOOK_API void setMode(EHookMode mode);
}

View File

@@ -1,27 +0,0 @@
# synergy -- 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/>.
file(GLOB headers "*.h")
file(GLOB sources "*.cpp")
if (SYNERGY_ADD_HEADERS)
list(APPEND sources ${headers})
endif()
add_library(synwinhk SHARED ${sources})
if (NOT MSVC_VERSION VERSION_LESS 1900)
target_link_libraries(synwinhk libucrt)
endif()

File diff suppressed because it is too large Load Diff

View File

@@ -68,4 +68,4 @@ endif()
add_executable(integtests ${sources})
target_link_libraries(integtests
arch base client common io ipc mt net platform server core gtest gmock ${libs})
arch base client common io mt net platform server core gtest gmock ${libs})

View File

@@ -1,205 +0,0 @@
/*
* synergy -- 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/>.
*/
// TODO(andrew): fix, tests failing intermittently on mac.
#ifndef WINAPI_CARBON
#define TEST_ENV
#include "test/global/TestEventQueue.h"
#include "arch/Arch.h"
#include "base/EventQueue.h"
#include "base/Log.h"
#include "base/String.h"
#include "base/TMethodEventJob.h"
#include "base/TMethodJob.h"
#include "ipc/Ipc.h"
#include "ipc/IpcClient.h"
#include "ipc/IpcClientProxy.h"
#include "ipc/IpcMessage.h"
#include "ipc/IpcServer.h"
#include "ipc/IpcServerProxy.h"
#include "mt/Thread.h"
#include "net/SocketMultiplexer.h"
#include "test/global/gtest.h"
#define TEST_IPC_PORT 24802
class IpcTests : public ::testing::Test
{
public:
IpcTests();
~IpcTests() override;
void connectToServer_handleMessageReceived(const Event& /*e*/, void* /*unused*/);
void sendMessageToServer_serverHandleMessageReceived(const Event& /*e*/, void* /*unused*/);
void sendMessageToClient_serverHandleClientConnected(const Event& /*e*/, void* /*unused*/);
void sendMessageToClient_clientHandleMessageReceived(const Event& /*e*/, void* /*unused*/);
public:
SocketMultiplexer m_multiplexer;
bool m_connectToServer_helloMessageReceived{false};
bool m_connectToServer_hasClientNode{false};
IpcServer* m_connectToServer_server{nullptr};
String m_sendMessageToServer_receivedString;
String m_sendMessageToClient_receivedString;
IpcClient* m_sendMessageToServer_client{nullptr};
IpcServer* m_sendMessageToClient_server{nullptr};
TestEventQueue m_events;
};
TEST_F(IpcTests, connectToServer)
{
SocketMultiplexer socketMultiplexer;
IpcServer server(&m_events, &socketMultiplexer, TEST_IPC_PORT);
server.listen();
m_connectToServer_server = &server;
m_events.adoptHandler(
m_events.forIpcServer().messageReceived(), &server,
new TMethodEventJob<IpcTests>(
this, &IpcTests::connectToServer_handleMessageReceived));
IpcClient client(&m_events, &socketMultiplexer, TEST_IPC_PORT);
client.connect();
m_events.initQuitTimeout(5);
m_events.loop();
m_events.removeHandler(m_events.forIpcServer().messageReceived(), &server);
m_events.cleanupQuitTimeout();
EXPECT_EQ(true, m_connectToServer_helloMessageReceived);
EXPECT_EQ(true, m_connectToServer_hasClientNode);
}
TEST_F(IpcTests, sendMessageToServer)
{
SocketMultiplexer socketMultiplexer;
IpcServer server(&m_events, &socketMultiplexer, TEST_IPC_PORT);
server.listen();
// event handler sends "test" command to server.
m_events.adoptHandler(
m_events.forIpcServer().messageReceived(), &server,
new TMethodEventJob<IpcTests>(
this, &IpcTests::sendMessageToServer_serverHandleMessageReceived));
IpcClient client(&m_events, &socketMultiplexer, TEST_IPC_PORT);
client.connect();
m_sendMessageToServer_client = &client;
m_events.initQuitTimeout(5);
m_events.loop();
m_events.removeHandler(m_events.forIpcServer().messageReceived(), &server);
m_events.cleanupQuitTimeout();
EXPECT_EQ("test", m_sendMessageToServer_receivedString);
}
TEST_F(IpcTests, sendMessageToClient)
{
SocketMultiplexer socketMultiplexer;
IpcServer server(&m_events, &socketMultiplexer, TEST_IPC_PORT);
server.listen();
m_sendMessageToClient_server = &server;
// event handler sends "test" log line to client.
m_events.adoptHandler(
m_events.forIpcServer().messageReceived(), &server,
new TMethodEventJob<IpcTests>(
this, &IpcTests::sendMessageToClient_serverHandleClientConnected));
IpcClient client(&m_events, &socketMultiplexer, TEST_IPC_PORT);
client.connect();
m_events.adoptHandler(
m_events.forIpcClient().messageReceived(), &client,
new TMethodEventJob<IpcTests>(
this, &IpcTests::sendMessageToClient_clientHandleMessageReceived));
m_events.initQuitTimeout(5);
m_events.loop();
m_events.removeHandler(m_events.forIpcServer().messageReceived(), &server);
m_events.removeHandler(m_events.forIpcClient().messageReceived(), &client);
m_events.cleanupQuitTimeout();
EXPECT_EQ("test", m_sendMessageToClient_receivedString);
}
IpcTests::IpcTests()
{
}
IpcTests::~IpcTests()
= default;
void
IpcTests::connectToServer_handleMessageReceived(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcHello) {
m_connectToServer_hasClientNode =
m_connectToServer_server->hasClients(kIpcClientNode);
m_connectToServer_helloMessageReceived = true;
m_events.raiseQuitEvent();
}
}
void
IpcTests::sendMessageToServer_serverHandleMessageReceived(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcHello) {
LOG((CLOG_DEBUG "client said hello, sending test to server"));
IpcCommandMessage m("test", true);
m_sendMessageToServer_client->send(m);
}
else if (m->type() == kIpcCommand) {
auto* cm = dynamic_cast<IpcCommandMessage*>(m);
LOG((CLOG_DEBUG "got ipc command message, %d", cm->command().c_str()));
m_sendMessageToServer_receivedString = cm->command();
m_events.raiseQuitEvent();
}
}
void
IpcTests::sendMessageToClient_serverHandleClientConnected(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcHello) {
LOG((CLOG_DEBUG "client said hello, sending test to client"));
IpcLogLineMessage m("test");
m_sendMessageToClient_server->send(m, kIpcClientNode);
}
}
void
IpcTests::sendMessageToClient_clientHandleMessageReceived(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcLogLine) {
auto* llm = dynamic_cast<IpcLogLineMessage*>(m);
LOG((CLOG_DEBUG "got ipc log message, %d", llm->logLine().c_str()));
m_sendMessageToClient_receivedString = llm->logLine();
m_events.raiseQuitEvent();
}
}
#endif // WINAPI_CARBON

View File

@@ -52,7 +52,7 @@ protected:
MSWindowsDesks* newDesks(IEventQueue* eventQueue)
{
return new MSWindowsDesks(
true, false, m_hook.getInstance(), m_screensaver, eventQueue,
true, false, m_screensaver, eventQueue,
new TMethodJob<MSWindowsKeyStateTests>(
this, &MSWindowsKeyStateTests::updateKeysCB), false);
}

View File

@@ -1,68 +0,0 @@
/*
* synergy -- 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 "ipc/IpcServer.h"
#include "ipc/IpcMessage.h"
#include "arch/Arch.h"
#include "test/global/gmock.h"
using ::testing::_;
using ::testing::Invoke;
class IEventQueue;
class MockIpcServer : public IpcServer
{
public:
MockIpcServer() :
m_sendCond(ARCH->newCondVar()),
m_sendMutex(ARCH->newMutex()) { }
~MockIpcServer() {
if (m_sendCond != NULL) {
ARCH->closeCondVar(m_sendCond);
}
if (m_sendMutex != NULL) {
ARCH->closeMutex(m_sendMutex);
}
}
MOCK_METHOD0(listen, void());
MOCK_METHOD2(send, void(const IpcMessage&, EIpcClientType));
MOCK_CONST_METHOD1(hasClients, bool(EIpcClientType));
void delegateToFake() {
ON_CALL(*this, send(_, _)).WillByDefault(Invoke(this, &MockIpcServer::mockSend));
}
void waitForSend() {
ARCH->waitCondVar(m_sendCond, m_sendMutex, 5);
}
private:
void mockSend(const IpcMessage&, EIpcClientType) {
ArchMutexLock lock(m_sendMutex);
ARCH->broadcastCondVar(m_sendCond);
}
ArchCond m_sendCond;
ArchMutex m_sendMutex;
};

View File

@@ -45,10 +45,6 @@ public:
MOCK_METHOD0(getSystemTarget, void*());
MOCK_METHOD0(forClient, ClientEvents&());
MOCK_METHOD0(forIStream, IStreamEvents&());
MOCK_METHOD0(forIpcClient, IpcClientEvents&());
MOCK_METHOD0(forIpcClientProxy, IpcClientProxyEvents&());
MOCK_METHOD0(forIpcServer, IpcServerEvents&());
MOCK_METHOD0(forIpcServerProxy, IpcServerProxyEvents&());
MOCK_METHOD0(forIDataSocket, IDataSocketEvents&());
MOCK_METHOD0(forIListenSocket, IListenSocketEvents&());
MOCK_METHOD0(forISocket, ISocketEvents&());

View File

@@ -68,4 +68,4 @@ endif()
add_executable(unittests ${sources})
target_link_libraries(unittests
arch base client server common io net platform server core mt ipc gtest gmock shared ${libs})
arch base client server common io net platform server core mt gtest gmock shared ${libs})

View File

@@ -1,165 +0,0 @@
/*
* synergy -- 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/>.
*/
#define TEST_ENV
#include "test/mock/ipc/MockIpcServer.h"
#include "base/String.h"
#include "common/common.h"
#include "ipc/IpcLogOutputter.h"
#include "mt/Thread.h"
#include "test/global/gmock.h"
#include "test/global/gtest.h"
// HACK: ipc logging only used on windows anyway
#if WINAPI_MSWINDOWS
using ::testing::_;
using ::testing::Return;
using ::testing::Matcher;
using ::testing::MatcherCast;
using ::testing::Property;
using ::testing::StrEq;
using ::testing::AtLeast;
using namespace synergy;
inline const Matcher<const IpcMessage&> IpcLogLineMessageEq(const String& s) {
const Matcher<const IpcLogLineMessage&> m(
Property(&IpcLogLineMessage::logLine, StrEq(s)));
return MatcherCast<const IpcMessage&>(m);
}
TEST(IpcLogOutputterTests, write_threadingEnabled_bufferIsSent)
{
MockIpcServer mockServer;
mockServer.delegateToFake();
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(AtLeast(3));
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\n"), _)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 2\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, true);
outputter.write(kNOTE, "mock 1");
mockServer.waitForSend();
outputter.write(kNOTE, "mock 2");
mockServer.waitForSend();
}
TEST(IpcLogOutputterTests, write_overBufferMaxSize_firstLineTruncated)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 2\nmock 3\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, false);
outputter.bufferMaxSize(2);
// log more lines than the buffer can contain
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.write(kNOTE, "mock 3");
outputter.sendBuffer();
}
TEST(IpcLogOutputterTests, write_underBufferMaxSize_allLinesAreSent)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\nmock 2\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, false);
outputter.bufferMaxSize(2);
// log more lines than the buffer can contain
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.sendBuffer();
}
// HACK: temporarily disable this intermittently failing unit test.
// when the build machine is under heavy load, a race condition
// usually happens.
#if 0
TEST(IpcLogOutputterTests, write_overBufferRateLimit_lastLineTruncated)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(2);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\nmock 2\n"), _)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 4\nmock 5\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, false);
outputter.bufferRateLimit(2, 1); // 1s
// log 1 more line than the buffer can accept in time limit.
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.write(kNOTE, "mock 3");
outputter.sendBuffer();
// after waiting the time limit send another to make sure
// we can log after the time limit passes.
// HACK: sleep causes the unit test to fail intermittently,
// so lets try 100ms (there must be a better way to solve this)
ARCH->sleep(2); // 2s
outputter.write(kNOTE, "mock 4");
outputter.write(kNOTE, "mock 5");
outputter.write(kNOTE, "mock 6");
outputter.sendBuffer();
}
#endif
TEST(IpcLogOutputterTests, write_underBufferRateLimit_allLinesAreSent)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(2);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\nmock 2\n"), _)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 3\nmock 4\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, false);
outputter.bufferRateLimit(4, 1); // 1s (should be plenty of time)
// log 1 more line than the buffer can accept in time limit.
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.sendBuffer();
// after waiting the time limit send another to make sure
// we can log after the time limit passes.
outputter.write(kNOTE, "mock 3");
outputter.write(kNOTE, "mock 4");
outputter.sendBuffer();
}
#endif // WINAPI_MSWINDOWS

View File

@@ -260,22 +260,6 @@ TEST(GenericArgsParsingTests, parseGenericArgs_noTrayCmd_disableTrayTrue)
EXPECT_EQ(1, i);
}
TEST(GenericArgsParsingTests, parseGenericArgs_ipcCmd_enableIpcTrue)
{
int i = 1;
const int argc = 2;
const char* kIpcCmd[argc] = { "stub", "--ipc" };
ArgParser argParser(nullptr);
ArgsBase argsBase;
argParser.setArgsBase(argsBase);
argParser.parseGenericArgs(argc, kIpcCmd, i);
EXPECT_EQ(true, argsBase.m_enableIpc);
EXPECT_EQ(1, i);
}
#ifndef WINAPI_XWINDOWS
TEST(GenericArgsParsingTests, parseGenericArgs_dragDropCmdOnNonLinux_enableDragDropTrue)
{