Remove IPC

This commit is contained in:
Andrew Nelless 2017-12-11 19:18:15 +00:00
parent 607201d586
commit 5ff0637a82
45 changed files with 11 additions and 2989 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,7 +15,7 @@
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")

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,7 +20,6 @@ add_subdirectory(base)
add_subdirectory(client)
add_subdirectory(common)
add_subdirectory(io)
add_subdirectory(ipc)
add_subdirectory(mt)
add_subdirectory(net)
add_subdirectory(platform)

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

@ -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

@ -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

@ -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

@ -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)
{