mirror of
https://github.com/debauchee/barrier.git
synced 2026-02-12 22:55:53 +08:00
Compare commits
32 Commits
v2.0.3-bet
...
v2.0.8-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b70b8e0fc4 | ||
|
|
47137952f4 | ||
|
|
cd403562cd | ||
|
|
2204828746 | ||
|
|
d2f6e56e53 | ||
|
|
a0616a54be | ||
|
|
d2a331c376 | ||
|
|
cbd742ebb5 | ||
|
|
ffb0a5e1ed | ||
|
|
ec3e0b2f71 | ||
|
|
ea5be67b27 | ||
|
|
0f2c7099c5 | ||
|
|
3982e74d30 | ||
|
|
2006af9143 | ||
|
|
cbfa585fa7 | ||
|
|
dfb8d15010 | ||
|
|
426e81fa71 | ||
|
|
3d440547ee | ||
|
|
35602ed6bf | ||
|
|
703097c19b | ||
|
|
a48ff8bcb5 | ||
|
|
6368207dec | ||
|
|
0e11b8777f | ||
|
|
1fb01f6833 | ||
|
|
d770160e00 | ||
|
|
2d56cc1c92 | ||
|
|
db1770df39 | ||
|
|
adcb1b9b1f | ||
|
|
4dc9f893c3 | ||
|
|
d5872caa7e | ||
|
|
5ff0637a82 | ||
|
|
9da6974ada |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,6 +2,7 @@ config.h
|
||||
.DS_Store
|
||||
*.pyc
|
||||
*.o
|
||||
*.a
|
||||
*~
|
||||
\.*.swp
|
||||
*build-gui-Desktop_Qt*
|
||||
@@ -20,3 +21,6 @@ src/gui/.rnd
|
||||
src/setup/win32/synergy.suo
|
||||
CMakeCache.txt
|
||||
CMakeLists.txt.user
|
||||
Makefile
|
||||
cmake_install.cmake
|
||||
CMakeFiles/
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Synergy Core
|
||||
|
||||
Open source core legacy component of Synergy, keyboard and mouse sharing software
|
||||
This is the open source core component of Synergy, a keyboard and mouse sharing tool.
|
||||
|
||||
@@ -4,28 +4,29 @@
|
||||
# line. comments may appear anywhere the syntax permits.
|
||||
|
||||
section: screens
|
||||
# three hosts named: moe, larry, and curly
|
||||
# three hosts, named "moe", "larry", and "curly"
|
||||
moe:
|
||||
larry:
|
||||
curly:
|
||||
end
|
||||
|
||||
section: links
|
||||
# larry is to the right of moe and curly is above moe
|
||||
# for moe, larry is to the right and curly is above.
|
||||
moe:
|
||||
right = larry
|
||||
up = curly
|
||||
|
||||
# moe is to the left of larry and curly is above larry.
|
||||
# note that curly is above both moe and larry and moe
|
||||
# and larry have a symmetric connection (they're in
|
||||
# opposite directions of each other).
|
||||
# for larry, moe is to the left and curly is also above.
|
||||
# note that curly is above both moe and larry
|
||||
# and that the connection between moe and larry is symmetric
|
||||
# (i.e. they're in opposite directions of each other).
|
||||
larry:
|
||||
left = moe
|
||||
up = curly
|
||||
|
||||
# larry is below curly. if you move up from moe and then
|
||||
# down, you'll end up on larry.
|
||||
# for curly, larry is below.
|
||||
# if you move up from moe, and then move down,
|
||||
# you'll end up on larry, not back at moe.
|
||||
curly:
|
||||
down = larry
|
||||
end
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -150,6 +150,7 @@ ArchMultithreadPosix::~ArchMultithreadPosix()
|
||||
|
||||
closeMutex(m_threadMutex);
|
||||
s_instance = nullptr;
|
||||
delete m_mainThread;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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() :
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -179,7 +175,7 @@ App::initApp(int argc, const char** argv)
|
||||
// this is a simple way to allow the core process to talk to X. this avoids
|
||||
// the "WARNING: primary screen unavailable: unable to open screen" error.
|
||||
// a better way would be to use xauth cookie and dbus to get access to X.
|
||||
if (static_cast<int>((!(argsBase().m_runAsUid) == 0 != -1))) {
|
||||
if (argsBase().m_runAsUid >= 0) {
|
||||
if (setuid(argsBase().m_runAsUid) == 0) {
|
||||
LOG((CLOG_DEBUG "process uid was set to: %d", argsBase().m_runAsUid));
|
||||
}
|
||||
@@ -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*/)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,10 +27,6 @@ m_stopOnDeskSwitch(false),
|
||||
#else
|
||||
m_daemon(true), // backward compatibility for unix (daemon by default)
|
||||
#endif
|
||||
#if WINAPI_XWINDOWS
|
||||
m_disableXInitThreads(false),
|
||||
m_runAsUid(-1),
|
||||
#endif
|
||||
m_backend(false),
|
||||
m_restartable(true),
|
||||
m_noHooks(false),
|
||||
@@ -38,8 +34,11 @@ m_pname(nullptr),
|
||||
m_logFilter(nullptr),
|
||||
m_logFile(nullptr),
|
||||
m_display(nullptr),
|
||||
m_enableIpc(false),
|
||||
m_enableDragDrop(false),
|
||||
#if WINAPI_XWINDOWS
|
||||
m_disableXInitThreads(false),
|
||||
m_runAsUid(-1),
|
||||
#endif
|
||||
m_shouldExit(false),
|
||||
m_profileDirectory(""),
|
||||
m_pluginDirectory("")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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";
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -1075,6 +1075,11 @@ MSWindowsKeyState::getKeyMap(synergy::KeyMap& keyMap)
|
||||
}
|
||||
}
|
||||
|
||||
// add alt+printscreen
|
||||
if (m_buttonToVK[0x54u] == 0) {
|
||||
m_buttonToVK[0x54u] = VK_SNAPSHOT;
|
||||
}
|
||||
|
||||
// set virtual key to button table
|
||||
if (activeLayout == m_groups[g]) {
|
||||
for (KeyButton i = 0; i < 512; ++i) {
|
||||
|
||||
@@ -136,7 +136,6 @@ MSWindowsScreen::MSWindowsScreen(
|
||||
m_desks = new MSWindowsDesks(
|
||||
m_isPrimary,
|
||||
m_noHooks,
|
||||
m_hook.getInstance(),
|
||||
m_screensaver,
|
||||
m_events,
|
||||
new TMethodJob<MSWindowsScreen>(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(); }
|
||||
};
|
||||
@@ -109,8 +109,8 @@ protected:
|
||||
virtual IKeyState* getKeyState() const;
|
||||
|
||||
private:
|
||||
void updateScreenShape();
|
||||
void updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags);
|
||||
bool updateScreenShape();
|
||||
bool updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags);
|
||||
void postMouseEvent(CGPoint&) const;
|
||||
|
||||
// convenience function to send events
|
||||
@@ -118,7 +118,7 @@ private:
|
||||
void sendClipboardEvent(Event::Type type, ClipboardID id) const;
|
||||
|
||||
// message handlers
|
||||
bool onMouseMove(SInt32 mx, SInt32 my);
|
||||
bool onMouseMove(CGFloat mx, CGFloat my);
|
||||
// mouse button handler. pressed is true if this is a mousedown
|
||||
// event, false if it is a mouseup event. macButton is the index
|
||||
// of the button pressed using the mac button mapping.
|
||||
|
||||
@@ -108,9 +108,12 @@ OSXScreen::OSXScreen(IEventQueue* events, bool isPrimary, bool autoShowHideCurso
|
||||
m_getDropTargetThread(NULL),
|
||||
m_impl(NULL)
|
||||
{
|
||||
m_displayID = CGMainDisplayID();
|
||||
if (!updateScreenShape(m_displayID, 0)) {
|
||||
throw std::runtime_error ("failed to initialize screen shape");
|
||||
}
|
||||
|
||||
try {
|
||||
m_displayID = CGMainDisplayID();
|
||||
updateScreenShape(m_displayID, 0);
|
||||
m_screensaver = new OSXScreenSaver(m_events, getEventTarget());
|
||||
m_keyState = new OSXKeyState(m_events);
|
||||
|
||||
@@ -192,6 +195,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 +207,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 +254,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 +765,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
|
||||
@@ -1030,20 +1031,20 @@ OSXScreen::handleSystemEvent(const Event& event, void*)
|
||||
}
|
||||
|
||||
bool
|
||||
OSXScreen::onMouseMove(SInt32 mx, SInt32 my)
|
||||
OSXScreen::onMouseMove(CGFloat mx, CGFloat my)
|
||||
{
|
||||
LOG((CLOG_DEBUG2 "mouse move %+d,%+d", mx, my));
|
||||
LOG((CLOG_DEBUG2 "mouse move %+f,%+f", mx, my));
|
||||
|
||||
SInt32 x = mx - m_xCursor;
|
||||
SInt32 y = my - m_yCursor;
|
||||
CGFloat x = mx - m_xCursor;
|
||||
CGFloat y = my - m_yCursor;
|
||||
|
||||
if ((x == 0 && y == 0) || (mx == m_xCenter && mx == m_yCenter)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// save position to compute delta of next motion
|
||||
m_xCursor = mx;
|
||||
m_yCursor = my;
|
||||
m_xCursor = (SInt32)mx;
|
||||
m_yCursor = (SInt32)my;
|
||||
|
||||
if (m_isOnScreen) {
|
||||
// motion on primary screen
|
||||
@@ -1072,7 +1073,21 @@ OSXScreen::onMouseMove(SInt32 mx, SInt32 my)
|
||||
}
|
||||
else {
|
||||
// send motion
|
||||
sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(x, y));
|
||||
// Accumulate together the move into the running total
|
||||
static CGFloat m_xFractionalMove = 0;
|
||||
static CGFloat m_yFractionalMove = 0;
|
||||
|
||||
m_xFractionalMove += x;
|
||||
m_yFractionalMove += y;
|
||||
|
||||
// Return the integer part
|
||||
SInt32 intX = (SInt32)m_xFractionalMove;
|
||||
SInt32 intY = (SInt32)m_yFractionalMove;
|
||||
|
||||
// And keep only the fractional part
|
||||
m_xFractionalMove -= intX;
|
||||
m_yFractionalMove -= intY;
|
||||
sendEvent(m_events->forIPrimaryScreen().motionOnSecondary(), MotionInfo::alloc(intX, intY));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1163,12 +1178,13 @@ OSXScreen::displayReconfigurationCallback(CGDirectDisplayID displayID, CGDisplay
|
||||
kCGDisplayMirrorFlag | kCGDisplayUnMirrorFlag |
|
||||
kCGDisplayDesktopShapeChangedFlag;
|
||||
|
||||
LOG((CLOG_DEBUG1 "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
|
||||
LOG((CLOG_DEBUG "event: display was reconfigured: %x %x %x", flags, mask, flags & mask));
|
||||
|
||||
if (flags & mask) { /* Something actually did change */
|
||||
|
||||
LOG((CLOG_DEBUG1 "event: screen changed shape; refreshing dimensions"));
|
||||
screen->updateScreenShape(displayID, flags);
|
||||
if (!screen->updateScreenShape(displayID, flags)) {
|
||||
LOG((CLOG_ERR "failed to update screen shape during display reconfiguration"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1472,35 +1488,34 @@ OSXScreen::getKeyState() const
|
||||
return m_keyState;
|
||||
}
|
||||
|
||||
void
|
||||
OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
|
||||
bool OSXScreen::updateScreenShape(const CGDirectDisplayID, const CGDisplayChangeSummaryFlags flags)
|
||||
{
|
||||
updateScreenShape();
|
||||
return updateScreenShape();
|
||||
}
|
||||
|
||||
void
|
||||
bool
|
||||
OSXScreen::updateScreenShape()
|
||||
{
|
||||
// get info for each display
|
||||
CGDisplayCount displayCount = 0;
|
||||
|
||||
if (CGGetActiveDisplayList(0, NULL, &displayCount) != CGDisplayNoErr) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (displayCount == 0) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
CGDirectDisplayID* displays = new CGDirectDisplayID[displayCount];
|
||||
if (displays == NULL) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CGGetActiveDisplayList(displayCount,
|
||||
displays, &displayCount) != CGDisplayNoErr) {
|
||||
delete[] displays;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// get smallest rect enclosing all display rects
|
||||
@@ -1529,6 +1544,8 @@ OSXScreen::updateScreenShape()
|
||||
LOG((CLOG_DEBUG "screen shape: center=%d,%d size=%dx%d on %u %s",
|
||||
m_x, m_y, m_w, m_h, displayCount,
|
||||
(displayCount == 1) ? "display" : "displays"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
@@ -54,10 +54,10 @@ class EventQueueTimer { };
|
||||
|
||||
XWindowsEventQueueBuffer::XWindowsEventQueueBuffer(
|
||||
Display* display, Window window, IEventQueue* events) :
|
||||
m_events(events),
|
||||
m_display(display),
|
||||
m_window(window),
|
||||
m_waiting(false)
|
||||
m_waiting(false),
|
||||
m_events(events)
|
||||
{
|
||||
assert(m_display != NULL);
|
||||
assert(m_window != None);
|
||||
|
||||
@@ -97,12 +97,13 @@ XWindowsScreen::XWindowsScreen(
|
||||
bool disableXInitThreads,
|
||||
int mouseScrollDelta,
|
||||
IEventQueue* events) :
|
||||
PlatformScreen(events),
|
||||
m_isPrimary(isPrimary),
|
||||
m_mouseScrollDelta(mouseScrollDelta),
|
||||
m_display(nullptr),
|
||||
m_root(None),
|
||||
m_window(None),
|
||||
m_isOnScreen(true),
|
||||
m_isOnScreen(isPrimary),
|
||||
m_x(0), m_y(0),
|
||||
m_w(0), m_h(0),
|
||||
m_xCenter(0), m_yCenter(0),
|
||||
@@ -121,8 +122,7 @@ XWindowsScreen::XWindowsScreen(
|
||||
m_xkb(false),
|
||||
m_xi2detected(false),
|
||||
m_xrandr(false),
|
||||
m_events(events),
|
||||
PlatformScreen(events)
|
||||
m_events(events)
|
||||
{
|
||||
assert(s_screen == NULL);
|
||||
|
||||
@@ -1391,8 +1391,6 @@ XWindowsScreen::handleSystemEvent(const Event& event, void* /*unused*/)
|
||||
case MotionNotify:
|
||||
if (m_isPrimary) {
|
||||
onMouseMove(xevent->xmotion);
|
||||
} else if (!m_isOnScreen && (xevent->xmotion.send_event == False)) {
|
||||
LOG ((CLOG_INFO "local input detected"));
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -1415,8 +1413,8 @@ XWindowsScreen::handleSystemEvent(const Event& event, void* /*unused*/)
|
||||
|
||||
#if HAVE_X11_EXTENSIONS_XRANDR_H
|
||||
if (m_xrandr) {
|
||||
if (xevent->type == m_xrandrEventBase + RRScreenChangeNotify
|
||||
|| xevent->type == m_xrandrEventBase + RRNotify
|
||||
if ((xevent->type == m_xrandrEventBase + RRScreenChangeNotify
|
||||
|| xevent->type == m_xrandrEventBase + RRNotify)
|
||||
&& reinterpret_cast<XRRNotifyEvent *>(xevent)->subtype == RRNotify_CrtcChange) {
|
||||
LOG((CLOG_INFO "XRRScreenChangeNotifyEvent or RRNotify_CrtcChange received"));
|
||||
|
||||
|
||||
@@ -933,6 +933,10 @@ struct codepair {
|
||||
{ XK_oe, 0x0153 }, /* LATIN SMALL LIGATURE OE */
|
||||
{ XK_Ydiaeresis, 0x0178 }, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */
|
||||
{ XK_EuroSign, 0x20ac }, /* EURO SIGN */
|
||||
{ 0x1000218, 0x0218}, /* LATIN CAPITAL LETTER S WITH COMMA BELOW */
|
||||
{ 0x1000219, 0x0219}, /* LATIN SMALL LETTER S WITH COMMA BELOW */
|
||||
{ 0x100021a, 0x021a}, /* LATIN CAPITAL LETTER T WITH COMMA BELOW */
|
||||
{ 0x100021b, 0x021b}, /* LATIN CAPITAL LETTER T WITH COMMA BELOW */
|
||||
|
||||
/* combining dead keys */
|
||||
{ XK_dead_abovedot, 0x0307 }, /* COMBINING DOT ABOVE */
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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
@@ -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})
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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&());
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user