Compare commits

..

32 Commits

Author SHA1 Message Date
Andrew Nelless
b70b8e0fc4 Merge pull request #6276 from symless/issue6267-linux-switch
#6267 Stop detecting mouse movement for local input
2018-03-19 11:25:17 +00:00
Andrew Nelless
47137952f4 Revert "#4288 Remove auto Alt+Printscreen on Windows"
This reverts commit 3d3b7ca881.
2018-03-15 11:27:26 +00:00
Andrew Nelless
cd403562cd Merge pull request #6268 from erikd/topic/fixes
Minor fixes reported by AddressSanitizer
2018-03-14 13:36:29 +00:00
Andrew Nelless
2204828746 Make macOS screen shape detection code more robust 2018-03-14 13:35:42 +00:00
Erik de Castro Lopo
d2f6e56e53 Fix a memory leak 2018-03-12 10:41:31 +11:00
Erik de Castro Lopo
a0616a54be Update .gitignore
Ignore generated cmake files and directories.
2018-03-12 10:41:31 +11:00
Erik de Castro Lopo
d2a331c376 Fix compile error 2018-03-12 10:41:31 +11:00
Erik de Castro Lopo
cbd742ebb5 Fix -Wreorder warnings 2018-03-11 14:00:08 +11:00
Danielle Church
ffb0a5e1ed Initialize XWindowsScreen to offscreen for secondary displays (#6249)
XWindowsScreen::m_isOnScreen currently initializes to true for all
screens, meaning that every screen assumes it currently has control of
the shared mouse pointer when it starts up. For the primary (server)
screen, this is appropriate; when synergy starts, the local mouse should
still move the cursor on the local screen.

However, for secondary screens, this is incorrect; when the client
connects, it does _not_ have control of the shared mouse pointer, as the
server's mouse still moves the server's cursor.

This doesn't make much difference in most cases, as the secondary screen
behaves almost identically whether m_isOnScreen is true or false;
however, the local input detection code will only fire if the variable
is false, meaning that once the client connects, it first has to gain
the cursor and then lose it before it will detect local input.

Since the Synergy 2.0 service relies on synergy-core's local input
detection to determine when to switch to server mode, this means that a
Linux desktop can only ever claim primary if the shared cursor has at
some point moved to that screen, then left it.

This fixes the bug so that local input on a Linux desktop can reclaim
the primary role immediately after losing it.

Fixes #6248.
2018-03-09 15:27:56 +00:00
Jerry (Xinyu Hou)
ec3e0b2f71 #6267 Stop detecting mouse movement for local input 2018-03-08 16:40:23 +00:00
Jerry (Xinyu Hou)
ea5be67b27 #6266 Tweek log level for debugging display reconfiguration on Mac 2018-03-08 16:35:03 +00:00
Steve Williams
0f2c7099c5 Merge branch 'waldyrious-patch-1' into pr_cleanup_mar18_2 2018-03-05 13:44:37 +00:00
Steve Williams
3982e74d30 issue5648 - reapply int to float changes 2018-03-05 13:43:47 +00:00
Steve Williams
2006af9143 Merge branch 'patch-1' of https://github.com/waldyrious/synergy-core into waldyrious-patch-1 2018-03-05 11:50:32 +00:00
Andrew Nelless
cbfa585fa7 Merge pull request #6261 from rombert/feature/fix-romanian
Add support for latin 's' and 't' with comma below
2018-03-03 01:59:30 +00:00
Robert Munteanu
dfb8d15010 Add support for latin 's' and 't' with comma below
These codes were not supported at all, presumably due to being introduced
only in Unicode 3.0.
2018-02-24 23:13:43 +02:00
Nick Bolton
426e81fa71 Update README.md 2018-02-07 15:53:00 +00:00
Waldir Pimenta
3d440547ee synergy.conf.example: clarify comments
instead of saying apparently absolute statements
like "Foo is to the right of Bar",
use relative phrasing "for Bar, Foo is to the right".
This makes it clearer that the configuration file
does not describe a globally consistent spatial arrangement
but rather a set of areas that can be linked in arbitrary ways.
2018-01-23 14:26:30 +00:00
Andrew Nelless
35602ed6bf #6234 Change protocol error message 2017-12-20 15:02:26 +00:00
Andrew Nelless
703097c19b Remove synwinhk DLL 2017-12-13 18:28:48 +00:00
Xinyu Hou
a48ff8bcb5 Merge branch 'mac-input-detection' 2017-12-13 12:26:11 -05:00
Xinyu Hou
6368207dec Release quartz event tap in screen Dtor 2017-12-13 11:47:26 -05:00
Andrew Nelless
0e11b8777f Remove scary memcpy hack 2017-12-13 16:18:22 +00:00
Andrew Nelless
1fb01f6833 Remove Windows 2000 only mouse wheel support 2017-12-13 16:00:37 +00:00
Xinyu Hou
d770160e00 Add log for releasing quartz event tap 2017-12-13 10:59:41 -05:00
Andrew Nelless
2d56cc1c92 Remove NT 3.51 and Win9x mouse wheel support... 2017-12-13 15:27:48 +00:00
Andrew Nelless
db1770df39 Remove support for "high-level" mouse and keyboard hooks 2017-12-13 15:16:51 +00:00
Xinyu Hou
adcb1b9b1f Log for creating quartz event tap 2017-12-13 10:13:39 -05:00
Xinyu Hou
4dc9f893c3 Quit when failed to create quartz event tap 2017-12-13 09:42:41 -05:00
Xinyu Hou
d5872caa7e Merge branch 'mac-bundle' 2017-12-12 11:42:48 -05:00
Andrew Nelless
5ff0637a82 Remove IPC 2017-12-11 22:59:02 +00:00
Xinyu Hou
9da6974ada Install synergy core into resource folder on Mac 2017-12-11 10:28:54 -05:00
66 changed files with 825 additions and 4371 deletions

4
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -150,6 +150,7 @@ ArchMultithreadPosix::~ArchMultithreadPosix()
closeMutex(m_threadMutex);
s_instance = nullptr;
delete m_mainThread;
}
void

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,9 +29,6 @@
#include "core/ArgsBase.h"
#include "core/XSynergy.h"
#include "core/protocol_types.h"
#include "ipc/Ipc.h"
#include "ipc/IpcMessage.h"
#include "ipc/IpcServerProxy.h"
#include <iostream>
#include <cstdio>
@@ -64,7 +61,6 @@ App::App(IEventQueue* events, ArgsBase* args) :
m_args(args),
m_fileLog(nullptr),
m_appUtil(events),
m_ipcClient(nullptr),
m_socketMultiplexer(nullptr)
{
assert(s_instance == nullptr);
@@ -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*/)
{

View File

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

View File

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

View File

@@ -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("")

View File

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

View File

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

View File

@@ -453,12 +453,6 @@ ClientApp::mainLoop()
// start client, etc
appUtil().startNode();
// init ipc client after node start, since create a new screen wipes out
// the event queue (the screen ctors call adoptBuffer).
if (argsBase().m_enableIpc) {
initIpcClient();
}
// run event loop. if startClient() failed we're supposed to retry
// later. the timer installed by startClient() will take care of
// that.
@@ -489,10 +483,6 @@ ClientApp::mainLoop()
updateStatus();
LOG((CLOG_NOTE "stopped client"));
if (argsBase().m_enableIpc) {
cleanupIpcClient();
}
return kExitSuccess;
}

View File

@@ -32,9 +32,6 @@
#include "core/ArgParser.h"
#include "core/ClientArgs.h"
#include "core/ServerArgs.h"
#include "ipc/IpcClientProxy.h"
#include "ipc/IpcLogOutputter.h"
#include "ipc/IpcMessage.h"
#include "net/SocketMultiplexer.h"
#if SYSAPI_WIN32
@@ -44,7 +41,6 @@
#include "core/Screen.h"
#include "platform/MSWindowsScreen.h"
#include "platform/MSWindowsDebugOutputter.h"
#include "platform/MSWindowsWatchdog.h"
#include "platform/MSWindowsEventQueueBuffer.h"
#define WIN32_LEAN_AND_MEAN
@@ -82,8 +78,6 @@ winMainLoopStatic(int, const char**)
#endif
DaemonApp::DaemonApp() :
m_ipcServer(nullptr),
m_ipcLogOutputter(nullptr),
#if SYSAPI_WIN32
m_watchdog(nullptr),
#endif
@@ -206,53 +200,8 @@ DaemonApp::mainLoop(bool logToFile)
// create socket multiplexer. this must happen after daemonization
// on unix because threads evaporate across a fork().
SocketMultiplexer multiplexer;
// uses event queue, must be created here.
m_ipcServer = new IpcServer(m_events, &multiplexer);
// send logging to gui via ipc, log system adopts outputter.
m_ipcLogOutputter = new IpcLogOutputter(*m_ipcServer, kIpcClientGui, true);
CLOG->insert(m_ipcLogOutputter);
#if SYSAPI_WIN32
m_watchdog = new MSWindowsWatchdog(false, *m_ipcServer, *m_ipcLogOutputter);
m_watchdog->setFileLogOutputter(m_fileLogOutputter);
#endif
m_events->adoptHandler(
m_events->forIpcServer().messageReceived(), m_ipcServer,
new TMethodEventJob<DaemonApp>(this, &DaemonApp::handleIpcMessage));
m_ipcServer->listen();
#if SYSAPI_WIN32
// install the platform event queue to handle service stop events.
m_events->adoptBuffer(new MSWindowsEventQueueBuffer(m_events));
String command = ARCH->setting("Command");
bool elevate = ARCH->setting("Elevate") == "1";
if (command != "") {
LOG((CLOG_INFO "using last known command: %s", command.c_str()));
m_watchdog->setCommand(command, elevate);
}
m_watchdog->startAsync();
#endif
m_events->loop();
#if SYSAPI_WIN32
m_watchdog->stop();
delete m_watchdog;
#endif
m_events->removeHandler(
m_events->forIpcServer().messageReceived(), m_ipcServer);
CLOG->remove(m_ipcLogOutputter);
delete m_ipcLogOutputter;
delete m_ipcServer;
DAEMON_RUNNING(false);
}
catch (std::exception& e) {
@@ -286,117 +235,3 @@ DaemonApp::logFilename()
return logFilename;
}
void
DaemonApp::handleIpcMessage(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
switch (m->type()) {
case kIpcCommand: {
auto* cm = dynamic_cast<IpcCommandMessage*>(m);
String command = cm->command();
// if empty quotes, clear.
if (command == "\"\"") {
command.clear();
}
if (!command.empty()) {
LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str()));
std::vector<String> argsArray;
ArgParser::splitCommandString(command, argsArray);
ArgParser argParser(nullptr);
const char** argv = argParser.getArgv(argsArray);
ServerArgs serverArgs;
ClientArgs clientArgs;
auto argc = static_cast<int>(argsArray.size());
bool server = argsArray[0].find("synergys") != String::npos;
ArgsBase* argBase = nullptr;
if (server) {
argParser.parseServerArgs(serverArgs, argc, argv);
argBase = &serverArgs;
}
else {
argParser.parseClientArgs(clientArgs, argc, argv);
argBase = &clientArgs;
}
delete[] argv;
String logLevel(argBase->m_logFilter);
if (!logLevel.empty()) {
try {
// change log level based on that in the command string
// and change to that log level now.
ARCH->setting("LogLevel", logLevel);
CLOG->setFilter(logLevel.c_str());
}
catch (XArch& e) {
LOG((CLOG_ERR "failed to save LogLevel setting, %s", e.what()));
}
}
#if SYSAPI_WIN32
String logFilename;
if (argBase->m_logFile != NULL) {
logFilename = String(argBase->m_logFile);
ARCH->setting("LogFilename", logFilename);
m_watchdog->setFileLogOutputter(m_fileLogOutputter);
command = ArgParser::assembleCommand(argsArray, "--log", 1);
LOG((CLOG_DEBUG "removed log file argument and filename %s from command ", logFilename.c_str()));
LOG((CLOG_DEBUG "new command, elevate=%d command=%s", cm->elevate(), command.c_str()));
}
else {
m_watchdog->setFileLogOutputter(NULL);
}
m_fileLogOutputter->setLogFilename(logFilename.c_str());
#endif
}
else {
LOG((CLOG_DEBUG "empty command, elevate=%d", cm->elevate()));
}
try {
// store command in system settings. this is used when the daemon
// next starts.
ARCH->setting("Command", command);
// TODO(andrew): it would be nice to store bools/ints...
ARCH->setting("Elevate", String(cm->elevate() ? "1" : "0"));
}
catch (XArch& e) {
LOG((CLOG_ERR "failed to save settings, %s", e.what()));
}
#if SYSAPI_WIN32
// tell the relauncher about the new command. this causes the
// relauncher to stop the existing command and start the new
// command.
m_watchdog->setCommand(command, cm->elevate());
#endif
break;
}
case kIpcHello:
auto* hm = dynamic_cast<IpcHelloMessage*>(m);
String type;
switch (hm->clientType()) {
case kIpcClientGui: type = "gui"; break;
case kIpcClientNode: type = "node"; break;
default: type = "unknown"; break;
}
LOG((CLOG_DEBUG "ipc hello, type=%s", type.c_str()));
#if SYSAPI_WIN32
String watchdogStatus = m_watchdog->isProcessActive() ? "ok" : "error";
LOG((CLOG_INFO "watchdog status: %s", watchdogStatus.c_str()));
#endif
m_ipcLogOutputter->notifyBuffer();
break;
}
}

View File

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

View File

@@ -704,12 +704,6 @@ ServerApp::mainLoop()
// start server, etc
appUtil().startNode();
// init ipc client after node start, since create a new screen wipes out
// the event queue (the screen ctors call adoptBuffer).
if (argsBase().m_enableIpc) {
initIpcClient();
}
// handle hangup signal by reloading the server's configuration
ARCH->setSignalHandler(Arch::kHANGUP, &reloadSignalHandler, nullptr);
m_events->adoptHandler(m_events->forServerApp().reloadConfig(),
@@ -762,10 +756,6 @@ ServerApp::mainLoop()
updateStatus();
LOG((CLOG_NOTE "stopped server"));
if (argsBase().m_enableIpc) {
cleanupIpcClient();
}
return kExitSuccess;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"));

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,205 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2012-2016 Symless Ltd.
* Copyright (C) 2012 Nick Bolton
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// TODO(andrew): fix, tests failing intermittently on mac.
#ifndef WINAPI_CARBON
#define TEST_ENV
#include "test/global/TestEventQueue.h"
#include "arch/Arch.h"
#include "base/EventQueue.h"
#include "base/Log.h"
#include "base/String.h"
#include "base/TMethodEventJob.h"
#include "base/TMethodJob.h"
#include "ipc/Ipc.h"
#include "ipc/IpcClient.h"
#include "ipc/IpcClientProxy.h"
#include "ipc/IpcMessage.h"
#include "ipc/IpcServer.h"
#include "ipc/IpcServerProxy.h"
#include "mt/Thread.h"
#include "net/SocketMultiplexer.h"
#include "test/global/gtest.h"
#define TEST_IPC_PORT 24802
class IpcTests : public ::testing::Test
{
public:
IpcTests();
~IpcTests() override;
void connectToServer_handleMessageReceived(const Event& /*e*/, void* /*unused*/);
void sendMessageToServer_serverHandleMessageReceived(const Event& /*e*/, void* /*unused*/);
void sendMessageToClient_serverHandleClientConnected(const Event& /*e*/, void* /*unused*/);
void sendMessageToClient_clientHandleMessageReceived(const Event& /*e*/, void* /*unused*/);
public:
SocketMultiplexer m_multiplexer;
bool m_connectToServer_helloMessageReceived{false};
bool m_connectToServer_hasClientNode{false};
IpcServer* m_connectToServer_server{nullptr};
String m_sendMessageToServer_receivedString;
String m_sendMessageToClient_receivedString;
IpcClient* m_sendMessageToServer_client{nullptr};
IpcServer* m_sendMessageToClient_server{nullptr};
TestEventQueue m_events;
};
TEST_F(IpcTests, connectToServer)
{
SocketMultiplexer socketMultiplexer;
IpcServer server(&m_events, &socketMultiplexer, TEST_IPC_PORT);
server.listen();
m_connectToServer_server = &server;
m_events.adoptHandler(
m_events.forIpcServer().messageReceived(), &server,
new TMethodEventJob<IpcTests>(
this, &IpcTests::connectToServer_handleMessageReceived));
IpcClient client(&m_events, &socketMultiplexer, TEST_IPC_PORT);
client.connect();
m_events.initQuitTimeout(5);
m_events.loop();
m_events.removeHandler(m_events.forIpcServer().messageReceived(), &server);
m_events.cleanupQuitTimeout();
EXPECT_EQ(true, m_connectToServer_helloMessageReceived);
EXPECT_EQ(true, m_connectToServer_hasClientNode);
}
TEST_F(IpcTests, sendMessageToServer)
{
SocketMultiplexer socketMultiplexer;
IpcServer server(&m_events, &socketMultiplexer, TEST_IPC_PORT);
server.listen();
// event handler sends "test" command to server.
m_events.adoptHandler(
m_events.forIpcServer().messageReceived(), &server,
new TMethodEventJob<IpcTests>(
this, &IpcTests::sendMessageToServer_serverHandleMessageReceived));
IpcClient client(&m_events, &socketMultiplexer, TEST_IPC_PORT);
client.connect();
m_sendMessageToServer_client = &client;
m_events.initQuitTimeout(5);
m_events.loop();
m_events.removeHandler(m_events.forIpcServer().messageReceived(), &server);
m_events.cleanupQuitTimeout();
EXPECT_EQ("test", m_sendMessageToServer_receivedString);
}
TEST_F(IpcTests, sendMessageToClient)
{
SocketMultiplexer socketMultiplexer;
IpcServer server(&m_events, &socketMultiplexer, TEST_IPC_PORT);
server.listen();
m_sendMessageToClient_server = &server;
// event handler sends "test" log line to client.
m_events.adoptHandler(
m_events.forIpcServer().messageReceived(), &server,
new TMethodEventJob<IpcTests>(
this, &IpcTests::sendMessageToClient_serverHandleClientConnected));
IpcClient client(&m_events, &socketMultiplexer, TEST_IPC_PORT);
client.connect();
m_events.adoptHandler(
m_events.forIpcClient().messageReceived(), &client,
new TMethodEventJob<IpcTests>(
this, &IpcTests::sendMessageToClient_clientHandleMessageReceived));
m_events.initQuitTimeout(5);
m_events.loop();
m_events.removeHandler(m_events.forIpcServer().messageReceived(), &server);
m_events.removeHandler(m_events.forIpcClient().messageReceived(), &client);
m_events.cleanupQuitTimeout();
EXPECT_EQ("test", m_sendMessageToClient_receivedString);
}
IpcTests::IpcTests()
{
}
IpcTests::~IpcTests()
= default;
void
IpcTests::connectToServer_handleMessageReceived(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcHello) {
m_connectToServer_hasClientNode =
m_connectToServer_server->hasClients(kIpcClientNode);
m_connectToServer_helloMessageReceived = true;
m_events.raiseQuitEvent();
}
}
void
IpcTests::sendMessageToServer_serverHandleMessageReceived(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcHello) {
LOG((CLOG_DEBUG "client said hello, sending test to server"));
IpcCommandMessage m("test", true);
m_sendMessageToServer_client->send(m);
}
else if (m->type() == kIpcCommand) {
auto* cm = dynamic_cast<IpcCommandMessage*>(m);
LOG((CLOG_DEBUG "got ipc command message, %d", cm->command().c_str()));
m_sendMessageToServer_receivedString = cm->command();
m_events.raiseQuitEvent();
}
}
void
IpcTests::sendMessageToClient_serverHandleClientConnected(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcHello) {
LOG((CLOG_DEBUG "client said hello, sending test to client"));
IpcLogLineMessage m("test");
m_sendMessageToClient_server->send(m, kIpcClientNode);
}
}
void
IpcTests::sendMessageToClient_clientHandleMessageReceived(const Event& e, void* /*unused*/)
{
auto* m = dynamic_cast<IpcMessage*>(e.getDataObject());
if (m->type() == kIpcLogLine) {
auto* llm = dynamic_cast<IpcLogLineMessage*>(m);
LOG((CLOG_DEBUG "got ipc log message, %d", llm->logLine().c_str()));
m_sendMessageToClient_receivedString = llm->logLine();
m_events.raiseQuitEvent();
}
}
#endif // WINAPI_CARBON

View File

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

View File

@@ -1,68 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015-2016 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ipc/IpcServer.h"
#include "ipc/IpcMessage.h"
#include "arch/Arch.h"
#include "test/global/gmock.h"
using ::testing::_;
using ::testing::Invoke;
class IEventQueue;
class MockIpcServer : public IpcServer
{
public:
MockIpcServer() :
m_sendCond(ARCH->newCondVar()),
m_sendMutex(ARCH->newMutex()) { }
~MockIpcServer() {
if (m_sendCond != NULL) {
ARCH->closeCondVar(m_sendCond);
}
if (m_sendMutex != NULL) {
ARCH->closeMutex(m_sendMutex);
}
}
MOCK_METHOD0(listen, void());
MOCK_METHOD2(send, void(const IpcMessage&, EIpcClientType));
MOCK_CONST_METHOD1(hasClients, bool(EIpcClientType));
void delegateToFake() {
ON_CALL(*this, send(_, _)).WillByDefault(Invoke(this, &MockIpcServer::mockSend));
}
void waitForSend() {
ARCH->waitCondVar(m_sendCond, m_sendMutex, 5);
}
private:
void mockSend(const IpcMessage&, EIpcClientType) {
ArchMutexLock lock(m_sendMutex);
ARCH->broadcastCondVar(m_sendCond);
}
ArchCond m_sendCond;
ArchMutex m_sendMutex;
};

View File

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

View File

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

View File

@@ -1,165 +0,0 @@
/*
* synergy -- mouse and keyboard sharing utility
* Copyright (C) 2015-2016 Symless Ltd.
*
* This package is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* found in the file LICENSE that should have accompanied this file.
*
* This package is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define TEST_ENV
#include "test/mock/ipc/MockIpcServer.h"
#include "base/String.h"
#include "common/common.h"
#include "ipc/IpcLogOutputter.h"
#include "mt/Thread.h"
#include "test/global/gmock.h"
#include "test/global/gtest.h"
// HACK: ipc logging only used on windows anyway
#if WINAPI_MSWINDOWS
using ::testing::_;
using ::testing::Return;
using ::testing::Matcher;
using ::testing::MatcherCast;
using ::testing::Property;
using ::testing::StrEq;
using ::testing::AtLeast;
using namespace synergy;
inline const Matcher<const IpcMessage&> IpcLogLineMessageEq(const String& s) {
const Matcher<const IpcLogLineMessage&> m(
Property(&IpcLogLineMessage::logLine, StrEq(s)));
return MatcherCast<const IpcMessage&>(m);
}
TEST(IpcLogOutputterTests, write_threadingEnabled_bufferIsSent)
{
MockIpcServer mockServer;
mockServer.delegateToFake();
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(AtLeast(3));
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\n"), _)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 2\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, true);
outputter.write(kNOTE, "mock 1");
mockServer.waitForSend();
outputter.write(kNOTE, "mock 2");
mockServer.waitForSend();
}
TEST(IpcLogOutputterTests, write_overBufferMaxSize_firstLineTruncated)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 2\nmock 3\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, false);
outputter.bufferMaxSize(2);
// log more lines than the buffer can contain
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.write(kNOTE, "mock 3");
outputter.sendBuffer();
}
TEST(IpcLogOutputterTests, write_underBufferMaxSize_allLinesAreSent)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\nmock 2\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, false);
outputter.bufferMaxSize(2);
// log more lines than the buffer can contain
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.sendBuffer();
}
// HACK: temporarily disable this intermittently failing unit test.
// when the build machine is under heavy load, a race condition
// usually happens.
#if 0
TEST(IpcLogOutputterTests, write_overBufferRateLimit_lastLineTruncated)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(2);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\nmock 2\n"), _)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 4\nmock 5\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, false);
outputter.bufferRateLimit(2, 1); // 1s
// log 1 more line than the buffer can accept in time limit.
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.write(kNOTE, "mock 3");
outputter.sendBuffer();
// after waiting the time limit send another to make sure
// we can log after the time limit passes.
// HACK: sleep causes the unit test to fail intermittently,
// so lets try 100ms (there must be a better way to solve this)
ARCH->sleep(2); // 2s
outputter.write(kNOTE, "mock 4");
outputter.write(kNOTE, "mock 5");
outputter.write(kNOTE, "mock 6");
outputter.sendBuffer();
}
#endif
TEST(IpcLogOutputterTests, write_underBufferRateLimit_allLinesAreSent)
{
MockIpcServer mockServer;
ON_CALL(mockServer, hasClients(_)).WillByDefault(Return(true));
EXPECT_CALL(mockServer, hasClients(_)).Times(2);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 1\nmock 2\n"), _)).Times(1);
EXPECT_CALL(mockServer, send(IpcLogLineMessageEq("mock 3\nmock 4\n"), _)).Times(1);
IpcLogOutputter outputter(mockServer, kIpcClientUnknown, false);
outputter.bufferRateLimit(4, 1); // 1s (should be plenty of time)
// log 1 more line than the buffer can accept in time limit.
outputter.write(kNOTE, "mock 1");
outputter.write(kNOTE, "mock 2");
outputter.sendBuffer();
// after waiting the time limit send another to make sure
// we can log after the time limit passes.
outputter.write(kNOTE, "mock 3");
outputter.write(kNOTE, "mock 4");
outputter.sendBuffer();
}
#endif // WINAPI_MSWINDOWS

View File

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