win32 changes. changed names of binaries. added support for

running as (and installing/installing) a service.  added
support for multiple desktops (NT only, 95 doesn't support
multiple desktops).
This commit is contained in:
crs 2002-06-08 21:48:00 +00:00
parent 5709d8ddef
commit 4b28ffc5b2
36 changed files with 2948 additions and 855 deletions

View File

@ -23,8 +23,8 @@ CFG=all - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
MTL=midl.exe
!IF "$(CFG)" == "all - Win32 Release"

View File

@ -201,10 +201,8 @@ void CLog::output(int priority, char* msg)
// print it
CHoldLock lock(s_lock);
if (s_outputter) {
s_outputter(priority, msg + g_maxPriorityLength - n);
}
else {
if (s_outputter == NULL ||
!s_outputter(priority, msg + g_maxPriorityLength - n)) {
#if defined(CONFIG_PLATFORM_WIN32)
openConsole();
#endif

View File

@ -17,7 +17,11 @@ public:
kDEBUG2
};
typedef void (*Outputter)(int priority, const char*);
// type of outputter function. return false if CLog should use
// the default outputter, true otherwise.
typedef bool (*Outputter)(int priority, const char*);
// type of lock/unlock function
typedef void (*Lock)(bool lock);
//

View File

@ -23,8 +23,8 @@ CFG=base - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

View File

@ -27,6 +27,9 @@
#pragma warning(disable: 4786) // identifier truncated in debug info
#pragma warning(disable: 4514) // unreferenced inline function removed
// this one's a little too aggressive
#pragma warning(disable: 4127) // conditional expression is constant
#endif // (_MSC_VER >= 1200)
#else

View File

@ -8,4 +8,5 @@
#pragma warning(disable: 4284)
#pragma warning(disable: 4146) // unary minus on unsigned value
#pragma warning(disable: 4127) // conditional expression is constant
#pragma warning(disable: 4701) // variable possibly used uninitialized
#endif

View File

@ -120,6 +120,11 @@ void CClient::run(const CNetworkAddress& serverAddress)
}
}
void CClient::quit()
{
m_screen->stop();
}
void CClient::onClipboardChanged(ClipboardID id)
{
log((CLOG_DEBUG "sending clipboard %d changed", id));

View File

@ -19,8 +19,12 @@ public:
// manipulators
// start the client. does not return until quit() is called.
void run(const CNetworkAddress& serverAddress);
// tell client to exit gracefully
void quit();
// handle events on client's screen
void onClipboardChanged(ClipboardID);
void onResolutionChanged();

View File

@ -1,9 +1,12 @@
#include "CMSWindowsSecondaryScreen.h"
#include "CMSWindowsClipboard.h"
#include "CClient.h"
#include "CPlatform.h"
#include "CClipboard.h"
#include "CThread.h"
#include "CLock.h"
#include "CLog.h"
#include "CThread.h"
#include "XScreen.h"
#include <assert.h>
#include <ctype.h>
@ -13,10 +16,18 @@
CMSWindowsSecondaryScreen::CMSWindowsSecondaryScreen() :
m_client(NULL),
m_threadID(0),
m_desk(NULL),
m_deskName(),
m_window(NULL),
m_active(false),
m_nextClipboardWindow(NULL)
{
// do nothing
m_is95Family = CPlatform::isWindows95Family();
// make sure this thread has a message queue
MSG dummy;
PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE);
}
CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen()
@ -26,21 +37,32 @@ CMSWindowsSecondaryScreen::~CMSWindowsSecondaryScreen()
void CMSWindowsSecondaryScreen::run()
{
// must call run() from same thread as open()
assert(m_threadID == GetCurrentThreadId());
// change our priority
CThread::getCurrentThread().setPriority(-7);
// save thread id
m_threadID = GetCurrentThreadId();
// poll input desktop to see if it changes (onPreTranslate()
// handles WM_TIMER)
UINT timer = 0;
if (!m_is95Family) {
SetTimer(NULL, 0, 200, NULL);
}
// run event loop
log((CLOG_INFO "entering event loop"));
doRun();
log((CLOG_INFO "exiting event loop"));
// remove timer
if (!m_is95Family) {
KillTimer(NULL, timer);
}
}
void CMSWindowsSecondaryScreen::stop()
{
log((CLOG_INFO "requesting event loop stop"));
doStop();
}
@ -49,8 +71,6 @@ void CMSWindowsSecondaryScreen::open(CClient* client)
assert(m_client == NULL);
assert(client != NULL);
log((CLOG_INFO "opening screen"));
// set the client
m_client = client;
@ -64,14 +84,16 @@ void CMSWindowsSecondaryScreen::open(CClient* client)
// assume primary has all clipboards
for (ClipboardID id = 0; id < kClipboardEnd; ++id)
grabClipboard(id);
// hide the cursor
m_active = true;
leave();
}
void CMSWindowsSecondaryScreen::close()
{
assert(m_client != NULL);
log((CLOG_INFO "closing screen"));
// close the display
closeDisplay();
@ -82,12 +104,16 @@ void CMSWindowsSecondaryScreen::close()
void CMSWindowsSecondaryScreen::enter(
SInt32 x, SInt32 y, KeyModifierMask mask)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
assert(m_active == false);
log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask));
// attach thread input queues
AttachThreadInput(GetCurrentThreadId(), m_threadID, TRUE);
syncDesktop();
// now active
m_active = true;
// update our keyboard state to reflect the local state
updateKeys();
@ -104,34 +130,25 @@ void CMSWindowsSecondaryScreen::enter(
toggleKey(VK_SCROLL, KeyModifierScrollLock);
}
// warp to requested location
SInt32 w, h;
getScreenSize(&w, &h);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
(DWORD)((65535.99 * x) / (w - 1)),
(DWORD)((65535.99 * y) / (h - 1)),
0, 0);
// show cursor
log((CLOG_INFO "show cursor"));
ShowWindow(m_window, SW_HIDE);
// hide mouse
onEnter(x, y);
}
void CMSWindowsSecondaryScreen::leave()
{
CLock lock(&m_mutex);
assert(m_window != NULL);
assert(m_active == true);
log((CLOG_INFO "leaving screen"));
// move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen)
POINT point;
GetCursorPos(&point);
MoveWindow(m_window, point.x, point.y, 1, 1, FALSE);
syncDesktop();
// raise and show the hider window. take activation.
log((CLOG_INFO "hide cursor"));
ShowWindow(m_window, SW_SHOWNORMAL);
// hide mouse
onLeave();
// not active anymore
m_active = false;
// if we think we own the clipboard but we don't then somebody
// grabbed the clipboard on this screen without us knowing.
@ -160,6 +177,10 @@ void CMSWindowsSecondaryScreen::keyDown(
Keystrokes keys;
UINT virtualKey;
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
// get the sequence of keys to simulate key press and the final
// modifier state.
m_mask = mapKey(keys, virtualKey, key, mask, kPress);
@ -195,6 +216,10 @@ void CMSWindowsSecondaryScreen::keyRepeat(
Keystrokes keys;
UINT virtualKey;
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
// get the sequence of keys to simulate key repeat and the final
// modifier state.
m_mask = mapKey(keys, virtualKey, key, mask, kRepeat);
@ -211,6 +236,10 @@ void CMSWindowsSecondaryScreen::keyUp(
Keystrokes keys;
UINT virtualKey;
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
// get the sequence of keys to simulate key release and the final
// modifier state.
m_mask = mapKey(keys, virtualKey, key, mask, kRelease);
@ -263,6 +292,10 @@ void CMSWindowsSecondaryScreen::keyUp(
void CMSWindowsSecondaryScreen::mouseDown(ButtonID button)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
// map button id to button flag
DWORD flags = mapButton(button, true);
@ -273,6 +306,10 @@ void CMSWindowsSecondaryScreen::mouseDown(ButtonID button)
void CMSWindowsSecondaryScreen::mouseUp(ButtonID button)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
// map button id to button flag
DWORD flags = mapButton(button, false);
@ -281,8 +318,13 @@ void CMSWindowsSecondaryScreen::mouseUp(ButtonID button)
mouse_event(flags, 0, 0, 0, 0);
}
void CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y)
void CMSWindowsSecondaryScreen::mouseMove(
SInt32 x, SInt32 y)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
SInt32 w, h;
getScreenSize(&w, &h);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
@ -293,20 +335,27 @@ void CMSWindowsSecondaryScreen::mouseMove(SInt32 x, SInt32 y)
void CMSWindowsSecondaryScreen::mouseWheel(SInt32 delta)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
mouse_event(MOUSEEVENTF_WHEEL, 0, 0, delta, 0);
}
void CMSWindowsSecondaryScreen::setClipboard(
ClipboardID id, const IClipboard* src)
ClipboardID /*id*/, const IClipboard* src)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
CMSWindowsClipboard dst(m_window);
CClipboard::copy(&dst, src);
}
void CMSWindowsSecondaryScreen::grabClipboard(ClipboardID id)
void CMSWindowsSecondaryScreen::grabClipboard(
ClipboardID /*id*/)
{
CLock lock(&m_mutex);
assert(m_window != NULL);
CMSWindowsClipboard clipboard(m_window);
@ -321,6 +370,10 @@ void CMSWindowsSecondaryScreen::getMousePos(
assert(x != NULL);
assert(y != NULL);
CLock lock(&m_mutex);
assert(m_window != NULL);
syncDesktop();
POINT pos;
if (GetCursorPos(&pos)) {
*x = pos.x;
@ -344,8 +397,9 @@ SInt32 CMSWindowsSecondaryScreen::getJumpZoneSize() const
}
void CMSWindowsSecondaryScreen::getClipboard(
ClipboardID id, IClipboard* dst) const
ClipboardID /*id*/, IClipboard* dst) const
{
CLock lock(&m_mutex);
assert(m_window != NULL);
CMSWindowsClipboard src(m_window);
@ -356,44 +410,59 @@ void CMSWindowsSecondaryScreen::onOpenDisplay()
{
assert(m_window == NULL);
// initialize clipboard owner to current owner. we don't want
// to take ownership of the clipboard just by starting up.
m_clipboardOwner = GetClipboardOwner();
// save thread id. we'll need to pass this to the hook library.
m_threadID = GetCurrentThreadId();
// create the cursor hiding window. this window is used to hide the
// cursor when it's not on the screen. the window is hidden as soon
// as the cursor enters the screen or the display's real cursor is
// moved.
m_window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
(LPCTSTR)getClass(), "Synergy",
WS_POPUP,
0, 0, 1, 1, NULL, NULL,
getInstance(),
NULL);
// hide the cursor
leave();
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
// get the input desktop and switch to it
if (m_is95Family) {
if (!openDesktop()) {
throw XScreenOpenFailure();
}
}
else {
if (!switchDesktop(openInputDesktop())) {
throw XScreenOpenFailure();
}
}
}
void CMSWindowsSecondaryScreen::onCloseDisplay()
{
assert(m_window != NULL);
// disconnect from desktop
if (m_is95Family) {
closeDesktop();
}
else {
switchDesktop(NULL);
}
// remove clipboard snooper
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
// clear thread id
m_threadID = 0;
// destroy window
DestroyWindow(m_window);
m_window = NULL;
assert(m_window == NULL);
assert(m_desk == NULL);
}
bool CMSWindowsSecondaryScreen::onPreTranslate(MSG* msg)
{
// handle event
switch (msg->message) {
case WM_TIMER:
// if current desktop is not the input desktop then switch to it
if (!m_is95Family) {
HDESK desk = openInputDesktop();
if (desk != NULL) {
if (isCurrentDesktop(desk)) {
CloseDesktop(desk);
}
else {
switchDesktop(desk);
}
}
}
return true;
}
return false;
}
@ -402,7 +471,21 @@ LRESULT CMSWindowsSecondaryScreen::onEvent(
WPARAM wParam, LPARAM lParam)
{
switch (msg) {
// FIXME -- handle display changes
case WM_QUERYENDSESSION:
if (m_is95Family) {
return TRUE;
}
break;
case WM_ENDSESSION:
if (m_is95Family) {
if (wParam == TRUE && lParam == 0) {
stop();
}
return 0;
}
break;
case WM_PAINT:
ValidateRect(hwnd, NULL);
return 0;
@ -410,7 +493,6 @@ LRESULT CMSWindowsSecondaryScreen::onEvent(
case WM_ACTIVATEAPP:
if (wParam == FALSE) {
// some other app activated. hide the hider window.
log((CLOG_INFO "show cursor"));
ShowWindow(m_window, SW_HIDE);
}
break;
@ -423,9 +505,11 @@ LRESULT CMSWindowsSecondaryScreen::onEvent(
// now notify client that somebody changed the clipboard (unless
// we're now the owner, in which case it's because we took
// ownership).
// ownership, or now it's owned by nobody, which will happen if
// we owned it and switched desktops because we destroy our
// window to do that).
m_clipboardOwner = GetClipboardOwner();
if (m_clipboardOwner != m_window) {
if (m_clipboardOwner != m_window && m_clipboardOwner != NULL) {
m_client->onClipboardChanged(kClipboardClipboard);
m_client->onClipboardChanged(kClipboardSelection);
}
@ -448,6 +532,176 @@ LRESULT CMSWindowsSecondaryScreen::onEvent(
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void CMSWindowsSecondaryScreen::onEnter(SInt32 x, SInt32 y)
{
// warp to requested location
SInt32 w, h;
getScreenSize(&w, &h);
mouse_event(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE,
(DWORD)((65535.99 * x) / (w - 1)),
(DWORD)((65535.99 * y) / (h - 1)),
0, 0);
// show cursor
ShowWindow(m_window, SW_HIDE);
}
void CMSWindowsSecondaryScreen::onLeave()
{
// move hider window under the mouse (rather than moving the mouse
// somewhere else on the screen)
POINT point;
GetCursorPos(&point);
MoveWindow(m_window, point.x, point.y, 1, 1, FALSE);
// raise and show the hider window. take activation.
ShowWindow(m_window, SW_SHOWNORMAL);
}
bool CMSWindowsSecondaryScreen::openDesktop()
{
CLock lock(&m_mutex);
// initialize clipboard owner to current owner. we don't want
// to take ownership of the clipboard just by starting up.
m_clipboardOwner = GetClipboardOwner();
// create the cursor hiding window. this window is used to hide the
// cursor when it's not on the screen. the window is hidden as soon
// as the cursor enters the screen or the display's real cursor is
// moved.
m_window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
(LPCTSTR)getClass(), "Synergy",
WS_POPUP,
0, 0, 1, 1, NULL, NULL,
getInstance(),
NULL);
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
return true;
}
void CMSWindowsSecondaryScreen::closeDesktop()
{
CLock lock(&m_mutex);
if (m_window != NULL) {
// remove clipboard snooper
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
// destroy window
DestroyWindow(m_window);
m_window = NULL;
}
}
bool CMSWindowsSecondaryScreen::switchDesktop(HDESK desk)
{
CLock lock(&m_mutex);
bool ownClipboard = false;
if (m_window != NULL) {
// note if we own the clipboard
ownClipboard = (m_clipboardOwner == m_window);
// remove clipboard snooper
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
// destroy window
DestroyWindow(m_window);
m_window = NULL;
}
// done with desktop
if (m_desk != NULL) {
CloseDesktop(m_desk);
m_desk = NULL;
m_deskName = "";
}
// if no new desktop then we're done
if (desk == NULL) {
log((CLOG_INFO "disconnecting desktop"));
return true;
}
// set the desktop. can only do this when there are no windows
// and hooks on the current desktop owned by this thread.
if (SetThreadDesktop(desk) == 0) {
log((CLOG_ERR "failed to set desktop: %d", GetLastError()));
CloseDesktop(desk);
return false;
}
// initialize clipboard owner to current owner. we don't want
// to take ownership of the clipboard just by starting up.
m_clipboardOwner = GetClipboardOwner();
// create the cursor hiding window. this window is used to hide the
// cursor when it's not on the screen. the window is hidden as soon
// as the cursor enters the screen or the display's real cursor is
// moved.
m_window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
(LPCTSTR)getClass(), "Synergy",
WS_POPUP,
0, 0, 1, 1, NULL, NULL,
getInstance(),
NULL);
if (m_window == NULL) {
log((CLOG_ERR "failed to create window: %d", GetLastError()));
CloseDesktop(desk);
return false;
}
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
// if we owned the desktop then set the clipboard owner
if (ownClipboard) {
m_clipboardOwner = GetClipboardOwner();
}
// save new desktop
m_desk = desk;
m_deskName = getDesktopName(m_desk);
log((CLOG_INFO "switched to desktop %s", m_deskName.c_str()));
// get desktop up to date
if (!m_active) {
onLeave();
}
return true;
}
void CMSWindowsSecondaryScreen::syncDesktop() const
{
// note -- mutex must be locked on entry
DWORD threadID = GetCurrentThreadId();
if (!m_is95Family) {
if (GetThreadDesktop(threadID) != m_desk) {
// FIXME -- this doesn't work. if we set a desktop then
// sending events doesn't work.
if (SetThreadDesktop(m_desk) == 0) {
log((CLOG_ERR "failed to set desktop: %d", GetLastError()));
}
}
}
AttachThreadInput(threadID, m_threadID, TRUE);
}
CString CMSWindowsSecondaryScreen::getCurrentDesktopName() const
{
return m_deskName;
}
// these tables map KeyID (a X windows KeySym) to virtual key codes.
// if the key is an extended key then the entry is the virtual key
// code | 0x100. keys that map to normal characters have a 0 entry

View File

@ -3,7 +3,8 @@
#include "CMSWindowsScreen.h"
#include "ISecondaryScreen.h"
#include "stdmap.h"
#include "CMutex.h"
#include "CString.h"
#include "stdvector.h"
class CMSWindowsSecondaryScreen : public CMSWindowsScreen, public ISecondaryScreen {
@ -39,6 +40,7 @@ protected:
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM);
virtual void onOpenDisplay();
virtual void onCloseDisplay();
virtual CString getCurrentDesktopName() const;
private:
enum EKeyAction { kPress, kRelease, kRepeat };
@ -49,7 +51,21 @@ private:
bool m_repeat;
};
typedef std::vector<Keystroke> Keystrokes;
void onEnter(SInt32 x, SInt32 y);
void onLeave();
// open/close desktop (for windows 95/98/me)
bool openDesktop();
void closeDesktop();
// make desk the thread desktop (for windows NT/2000/XP)
bool switchDesktop(HDESK desk);
// get calling thread to use the input desktop
void syncDesktop() const;
// key and button queries and operations
DWORD mapButton(ButtonID button, bool press) const;
KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID,
KeyModifierMask, EKeyAction) const;
@ -63,14 +79,29 @@ private:
void sendKeyEvent(UINT virtualKey, bool press);
private:
CMutex m_mutex;
CClient* m_client;
// true if windows 95/98/me
bool m_is95Family;
// the main loop's thread id
DWORD m_threadID;
// the current desk and it's name
HDESK m_desk;
CString m_deskName;
// our window (for getting clipboard changes)
HWND m_window;
// m_active is true if this screen has been entered
bool m_active;
// clipboard stuff
HWND m_nextClipboardWindow;
HWND m_clipboardOwner;
// thread id of the event loop thread
DWORD m_threadID;
// virtual key states
BYTE m_keys[256];

View File

@ -1,6 +1,8 @@
#include "CClient.h"
#include "CString.h"
#include "CLog.h"
#include "CCondVar.h"
#include "CLock.h"
#include "CMutex.h"
#include "CNetwork.h"
#include "CNetworkAddress.h"
@ -11,6 +13,15 @@
#include "Version.h"
#include <assert.h>
// platform dependent name of a daemon
#if defined(CONFIG_PLATFORM_WIN32)
#define DAEMON "service"
#define DAEMON_NAME "Synergy Client"
#elif defined(CONFIG_PLATFORM_UNIX)
#define DAEMON "daemon"
#define DAEMON_NAME "synergy"
#endif
//
// program arguments
//
@ -18,6 +29,8 @@
static const char* pname = NULL;
static bool s_restartable = true;
static bool s_daemon = true;
static bool s_install = false;
static bool s_uninstall = false;
static const char* s_logFilter = NULL;
static const char* s_serverName = NULL;
@ -42,44 +55,91 @@ static void logLock(bool lock)
//
// main
// platform independent main
//
void realMain(const CString& name,
const CString& hostname,
UInt16 port)
static CClient* s_client = NULL;
static int realMain(CMutex* mutex)
{
// initialize threading library
CThread::init();
static const UInt16 port = 50001; // FIXME
// make logging thread safe
CMutex logMutex;
s_logMutex = &logMutex;
CLog::setLock(&logLock);
CClient* client = NULL;
try {
// initialize network library
CNetwork::init();
// initialize threading library
CThread::init();
// run client
CNetworkAddress addr(hostname, port);
client = new CClient(name);
client->run(addr);
// make logging thread safe
CMutex logMutex;
s_logMutex = &logMutex;
CLog::setLock(&logLock);
// clean up
delete client;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
bool locked = true;
try {
// initialize network library
CNetwork::init();
// create client
CNetworkAddress addr(s_serverName, port);
s_client = new CClient("secondary"); // FIXME
// run client
if (mutex != NULL) {
mutex->unlock();
}
locked = false;
s_client->run(addr);
locked = true;
if (mutex != NULL) {
mutex->lock();
}
// clean up
delete s_client;
s_client = NULL;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
}
catch (...) {
// clean up
if (!locked && mutex != NULL) {
mutex->lock();
}
delete s_client;
s_client = NULL;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
throw;
}
}
catch (...) {
// clean up
delete client;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
throw;
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
return 16;
}
catch (XThread&) {
// terminated
return 1;
}
return 0;
}
static int restartMain()
{
return realMain(NULL);
}
// invoke realMain and wait for it. if s_restartable then keep
// restarting realMain until it returns a terminate code.
static int restartableMain()
{
if (s_restartable) {
CPlatform platform;
return platform.restart(restartMain, 16);
}
else {
return realMain(NULL);
}
}
@ -88,11 +148,9 @@ void realMain(const CString& name,
// command line parsing
//
static void bye()
{
log((CLOG_PRINT "Try `%s --help' for more information.", pname));
exit(1);
}
#define BYE "\nTry `%s --help' for more information."
static void (*bye)(int) = &exit;
static void version()
{
@ -113,32 +171,37 @@ static void help()
log((CLOG_PRINT
"Usage: %s"
" [--debug <level>]"
" [--daemon|--no-daemon]"
" [--"DAEMON"|--no-"DAEMON"]"
" [--restart|--no-restart]"
" [--install]"
" <server-address>\n"
"or\n"
" --uninstall\n"
"Start the synergy mouse/keyboard sharing server.\n"
"\n"
" -d, --debug <level> filter out log messages with priorty below level.\n"
" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n"
" DEBUG, DEBUG1, DEBUG2.\n"
" -f, --no-daemon run the client in the foreground.\n"
" --daemon run the client as a daemon.\n"
" -f, --no-"DAEMON" run the client in the foreground.\n"
"* --"DAEMON" run the client as a "DAEMON".\n"
" -1, --no-restart do not try to restart the client if it fails for\n"
" some reason.\n"
" --restart restart the client automatically if it fails.\n"
"* --restart restart the client automatically if it fails.\n"
" --install install server as a "DAEMON".\n"
" --uninstall uninstall server "DAEMON".\n"
" -h, --help display this help and exit.\n"
" --version display version information and exit.\n"
"\n"
"By default, the client is a restartable daemon.\n"
"* marks defaults.\n"
"\n"
"Where log messages go depends on the platform and whether or not the\n"
"client is running as a daemon.",
"client is running as a "DAEMON".",
pname));
}
static bool isArg(int argi,
int argc, char** argv,
int argc, const char** argv,
const char* name1,
const char* name2,
int minRequiredParameters = 0)
@ -147,9 +210,9 @@ static bool isArg(int argi,
(name2 != NULL && strcmp(argv[argi], name2) == 0)) {
// match. check args left.
if (argi + minRequiredParameters >= argc) {
log((CLOG_PRINT "%s: missing arguments for `%s'",
pname, argv[argi]));
bye();
log((CLOG_PRINT "%s: missing arguments for `%s'" BYE,
pname, argv[argi], pname));
bye(2);
}
return true;
}
@ -158,7 +221,7 @@ static bool isArg(int argi,
return false;
}
static void parse(int argc, char** argv)
static void parse(int argc, const char** argv)
{
assert(pname != NULL);
assert(argv != NULL);
@ -172,12 +235,12 @@ static void parse(int argc, char** argv)
s_logFilter = argv[++i];
}
else if (isArg(i, argc, argv, "-f", "--no-daemon")) {
else if (isArg(i, argc, argv, "-f", "--no-"DAEMON)) {
// not a daemon
s_daemon = false;
}
else if (isArg(i, argc, argv, NULL, "--daemon")) {
else if (isArg(i, argc, argv, NULL, "--"DAEMON)) {
// daemonize
s_daemon = true;
}
@ -194,12 +257,42 @@ static void parse(int argc, char** argv)
else if (isArg(i, argc, argv, "-h", "--help")) {
help();
exit(1);
bye(0);
}
else if (isArg(i, argc, argv, NULL, "--version")) {
version();
exit(1);
bye(0);
}
else if (isArg(i, argc, argv, NULL, "--install")) {
#if !defined(CONFIG_PLATFORM_WIN32)
log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE,
pname, argv[i], pname));
bye(2);
#endif
s_install = true;
if (s_uninstall) {
log((CLOG_PRINT "%s: `--install' and `--uninstall'"
" are mutually exclusive" BYE,
pname, argv[i], pname));
bye(2);
}
}
else if (isArg(i, argc, argv, NULL, "--uninstall")) {
#if !defined(CONFIG_PLATFORM_WIN32)
log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE,
pname, argv[i], pname));
bye(2);
#endif
s_uninstall = true;
if (s_install) {
log((CLOG_PRINT "%s: `--install' and `--uninstall'"
" are mutually exclusive" BYE,
pname, argv[i], pname));
bye(2);
}
}
else if (isArg(i, argc, argv, "--", NULL)) {
@ -209,8 +302,9 @@ static void parse(int argc, char** argv)
}
else if (argv[i][0] == '-') {
log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i]));
bye();
log((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
else {
@ -219,21 +313,51 @@ static void parse(int argc, char** argv)
}
}
// exactly one non-option argument: server-address
if (i == argc) {
log((CLOG_PRINT "%s: a server address or name is required", pname));
bye();
// exactly one non-option argument (server-address) unless using
// --uninstall.
if (s_uninstall) {
if (i != argc) {
log((CLOG_PRINT "%s: unrecognized option `%s' to `%s'" BYE,
pname, argv[i], pname,
s_install ? "--install" : "--uninstall"));
bye(2);
}
}
if (i + 1 != argc) {
log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i]));
bye();
else {
if (i == argc) {
log((CLOG_PRINT "%s: a server address or name is required" BYE,
pname, pname));
bye(1);
}
if (i + 1 != argc) {
log((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
s_serverName = argv[i];
}
// increase default filter level for daemon. the user must
// explicitly request another level for a daemon.
if (s_daemon && s_logFilter == NULL) {
#if defined(CONFIG_PLATFORM_WIN32)
if (CPlatform::isWindows95Family()) {
// windows 95 has no place for logging so avoid showing
// the log console window.
s_logFilter = "FATAL";
}
else
#endif
{
s_logFilter = "NOTE";
}
}
s_serverName = argv[i];
// set log filter
if (!CLog::setFilter(s_logFilter)) {
log((CLOG_PRINT "%s: unrecognized log level `%s'", pname, s_logFilter));
bye();
log((CLOG_PRINT "%s: unrecognized log level `%s'" BYE,
pname, s_logFilter, pname));
bye(2);
}
}
@ -245,7 +369,71 @@ static void parse(int argc, char** argv)
#if defined(CONFIG_PLATFORM_WIN32)
#include "CMSWindowsScreen.h"
#include <string.h>
static bool logMessageBox(int priority, const char* msg)
{
if (priority <= CLog::kFATAL) {
MessageBox(NULL, msg, pname, MB_OK | MB_ICONWARNING);
return true;
}
else {
return false;
}
}
static void byeThrow(int x)
{
throw CWin32Platform::CDaemonFailed(x);
}
static void daemonStop(void)
{
s_client->quit();
}
static int daemonStartup(IPlatform* iplatform,
int argc, const char** argv)
{
// get platform pointer
CWin32Platform* platform = static_cast<CWin32Platform*>(iplatform);
// catch errors that would normally exit
bye = &byeThrow;
// parse command line
s_install = false;
s_uninstall = false;
parse(argc, argv);
if (s_install || s_uninstall) {
// not allowed to install/uninstall from service
throw CWin32Platform::CDaemonFailed(1);
}
// run as a service
return platform->runDaemon(realMain, daemonStop);
}
static int daemonStartup95(IPlatform*, int, const char**)
{
return realMain(NULL);
}
static bool logDiscard(int, const char*)
{
return true;
}
static bool s_die = false;
static void checkParse(int e)
{
// anything over 1 means invalid args. 1 means missing args.
// 0 means graceful exit. we plan to exit for anything but
// 1 (missing args); the service control manager may supply
// the missing arguments so we don't exit in that case.
s_die = (e != 1);
throw s_die;
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
{
@ -255,40 +443,115 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
CMSWindowsScreen::init(instance);
// get program name
pname = platform.getBasename(argv[0]);
pname = platform.getBasename(__argv[0]);
// FIXME -- direct CLog to MessageBox
parse(__argc, __argv);
// FIXME -- undirect CLog from MessageBox
// FIXME -- if daemon then use win32 event log (however that's done),
// otherwise do what? want to use console window for debugging but
// not otherwise.
// FIXME
// parse command line without reporting errors but recording if
// the app would've exited. this is too avoid showing a dialog
// box if we're being started as a service because we shouldn't
// take too long to startup in that case. this mostly works but
// will choke if the service control manager passes --install
// or --uninstall (but that's unlikely).
CLog::setOutputter(&logDiscard);
bye = &checkParse;
try {
realMain("secondary", s_serverName, 50001);
parse(__argc, const_cast<const char**>(__argv));
}
catch (...) {
// ignore
}
// if we're not starting as an NT service then reparse the command
// line normally.
if (s_die || !s_daemon || s_install || s_uninstall ||
CWin32Platform::isWindows95Family()) {
// send PRINT and FATAL output to a message box
CLog::setOutputter(&logMessageBox);
// exit on bye
bye = &exit;
// reparse
parse(__argc, const_cast<const char**>(__argv));
}
// if starting as a daemon then we ignore the startup command line
// here. we'll parse the command line passed in when the service
// control manager calls us back.
else {
// do nothing
}
// install/uninstall
if (s_install) {
// get the full path to this program
TCHAR path[MAX_PATH];
if (GetModuleFileName(NULL, path,
sizeof(path) / sizeof(path[0])) == 0) {
log((CLOG_CRIT "cannot determine absolute path to program"));
return 16;
}
// construct the command line to start the service with
CString commandLine = "--"DAEMON;
if (s_restartable) {
commandLine += " --restart";
}
else {
commandLine += " --no-restart";
}
if (s_logFilter != NULL) {
commandLine += " --debug ";
commandLine += s_logFilter;
}
commandLine += " ";
commandLine += s_serverName;
// install
if (!platform.installDaemon(DAEMON_NAME,
"Shares this system's mouse and keyboard with others.",
path, commandLine.c_str())) {
log((CLOG_CRIT "failed to install service"));
return 16;
}
log((CLOG_PRINT "installed successfully"));
return 0;
}
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
CString msg = "failed: ";
msg += e.what();
MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR);
return 1;
else if (s_uninstall) {
if (!platform.uninstallDaemon(DAEMON_NAME)) {
log((CLOG_CRIT "failed to uninstall service"));
return 16;
}
log((CLOG_PRINT "uninstalled successfully"));
return 0;
}
catch (XThread&) {
// terminated
return 1;
// daemonize if requested
int result;
if (s_daemon) {
if (CWin32Platform::isWindows95Family()) {
result = platform.daemonize(DAEMON_NAME, &daemonStartup95);
}
else {
result = platform.daemonize(DAEMON_NAME, &daemonStartup);
}
if (result == -1) {
log((CLOG_CRIT "failed to start as a service"));
return 16;
}
}
else {
result = restartableMain();
}
return result;
}
#elif defined(CONFIG_PLATFORM_UNIX)
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
static int daemonStartup(IPlatform*, int, const char**)
{
return restartableMain();
}
int main(int argc, char** argv)
{
@ -298,63 +561,22 @@ int main(int argc, char** argv)
pname = platform.getBasename(argv[0]);
// parse command line
parse(argc, argv);
parse(argc, const_cast<const char**>(argv));
// daemonize if requested
int result;
if (s_daemon) {
if (!platform.daemonize("synergy")) {
result = platform.daemonize(DAEMON_NAME, &daemonStartup);
if (result == -1) {
log((CLOG_CRIT "failed to daemonize"));
return 16;
}
}
// run the server. if running as a daemon then run it in a child
// process and restart it as necessary. we have to do this in case
// the X server restarts because our process cannot recover from
// that.
for (;;) {
// don't fork if not restartable
switch (s_restartable ? fork() : 0) {
default: {
// parent process. wait for child to exit.
int status;
if (wait(&status) == -1) {
// wait failed. this is unexpected so bail.
log((CLOG_CRIT "wait() failed"));
return 16;
}
// what happened? if the child exited normally with a
// status less than 16 then the child was deliberately
// terminated so we also terminate. otherwise, we
// loop.
if (WIFEXITED(status) && WEXITSTATUS(status) < 16) {
return 0;
}
break;
}
case -1:
// fork() failed. log the error and proceed as a child
log((CLOG_WARN "fork() failed; cannot automatically restart on error"));
// fall through
case 0:
// child process
try {
realMain("secondary", s_serverName, 50001);
return 0;
}
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
return 16;
}
catch (XThread&) {
// terminated
return 1;
}
}
else {
result = restartableMain();
}
return result;
}
#else

View File

@ -23,8 +23,8 @@ CFG=client - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
MTL=midl.exe
RSC=rc.exe
@ -40,6 +40,7 @@ RSC=rc.exe
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "../Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /MT /W4 /GX /O2 /I "..\base" /I "..\io" /I "..\mt" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c
@ -53,7 +54,7 @@ BSC32=bscmake.exe
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"../Release/synergy.exe"
!ELSEIF "$(CFG)" == "client - Win32 Debug"
@ -66,6 +67,7 @@ LINK32=link.exe
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "../Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\base" /I "..\io" /I "..\mt" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c
@ -79,7 +81,7 @@ BSC32=bscmake.exe
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"../Debug/synergy.exe" /pdbtype:sept
!ENDIF

View File

@ -23,8 +23,8 @@ CFG=http - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

View File

@ -23,8 +23,8 @@ CFG=io - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

View File

@ -614,17 +614,9 @@ unsigned int __stdcall CThreadRep::threadFunc(void* arg)
{
CThreadRep* rep = (CThreadRep*)arg;
// initialize OLE
const HRESULT hr = OleInitialize(NULL);
// run thread
rep->doThreadFunc();
// close OLE
if (!FAILED(hr)) {
OleUninitialize();
}
// signal termination
SetEvent(rep->m_exit);

View File

@ -23,8 +23,8 @@ CFG=mt - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

View File

@ -23,8 +23,8 @@ CFG=net - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe

View File

@ -152,6 +152,38 @@ void CMSWindowsScreen::getScreenSize(
*h = m_h;
}
HDESK CMSWindowsScreen::openInputDesktop() const
{
return OpenInputDesktop(DF_ALLOWOTHERACCOUNTHOOK, TRUE,
DESKTOP_CREATEWINDOW |
DESKTOP_HOOKCONTROL |
GENERIC_WRITE);
}
CString CMSWindowsScreen::getDesktopName(
HDESK desk) const
{
if (desk == NULL) {
return CString();
}
else {
DWORD size;
GetUserObjectInformation(desk, UOI_NAME, NULL, 0, &size);
TCHAR* name = new TCHAR[size / sizeof(TCHAR) + 1];
GetUserObjectInformation(desk, UOI_NAME, name, size, &size);
CString result(name);
delete[] name;
return result;
}
}
bool CMSWindowsScreen::isCurrentDesktop(
HDESK desk) const
{
return CStringUtil::CaselessCmp::equal(getDesktopName(desk),
getCurrentDesktopName());
}
void CMSWindowsScreen::getEvent(MSG* msg) const
{
// wait for an event in a cancellable way

View File

@ -18,6 +18,11 @@ public:
static void init(HINSTANCE);
// accessors
// get the application instance handle
static HINSTANCE getInstance();
protected:
// runs an event loop and returns when WM_QUIT is received
void doRun();
@ -35,9 +40,7 @@ protected:
// is closed.
void closeDisplay();
// get the application instance handle and the registered window
// class atom
static HINSTANCE getInstance();
// get the registered window class atom
ATOM getClass() const;
// update screen size cache
@ -46,6 +49,17 @@ protected:
// get the size of the screen
void getScreenSize(SInt32* w, SInt32* h) const;
// get the input desktop. caller must CloseDesktop() the result.
// do not call under windows 95/98/me.
HDESK openInputDesktop() const;
// get the desktop's name. do not call under windows 95/98/me.
CString getDesktopName(HDESK) const;
// returns true iff desk is the current desk. do not call under
// windows 95/98/me.
bool isCurrentDesktop(HDESK desk) const;
// wait for and get the next message. cancellable.
void getEvent(MSG*) const;
@ -62,6 +76,9 @@ protected:
// called by closeDisplay() to
virtual void onCloseDisplay() = 0;
// called by isCurrentDesktop() to get the current desktop name
virtual CString getCurrentDesktopName() const = 0;
private:
static LRESULT CALLBACK wndProc(HWND, UINT, WPARAM, LPARAM);

View File

@ -5,9 +5,11 @@
#include <pwd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <syslog.h>
//
// CUnixPlatform
//
@ -22,26 +24,31 @@ CUnixPlatform::~CUnixPlatform()
// do nothing
}
bool CUnixPlatform::installDaemon(/* FIXME */)
bool CUnixPlatform::installDaemon(
const char*,
const char*,
const char*,
const char*)
{
// daemons don't require special installation
return true;
}
bool CUnixPlatform::uninstallDaemon(/* FIXME */)
bool CUnixPlatform::uninstallDaemon(const char*)
{
// daemons don't require special installation
return true;
}
bool CUnixPlatform::daemonize(const char* name)
int CUnixPlatform::daemonize(
const char* name, DaemonFunc func)
{
// fork so shell thinks we're done and so we're not a process
// group leader
switch (fork()) {
case -1:
// failed
return false;
return -1;
case 0:
// child
@ -75,7 +82,44 @@ bool CUnixPlatform::daemonize(const char* name)
// hook up logger
setDaemonLogger(name);
return true;
// invoke function
return func(this, 1, &name);
}
int CUnixPlatform::restart(
RestartFunc func, int minErrorCode)
{
for (;;) {
switch (fork()) {
default: {
// parent process. wait for child to exit.
int status;
if (wait(&status) == -1) {
// wait failed. this is unexpected so bail.
log((CLOG_CRIT "wait() failed"));
return minErrorCode;
}
// what happened? if the child exited normally with a
// status less than 16 then the child was deliberately
// terminated so we also terminate. otherwise, we
// loop.
if (WIFEXITED(status) && WEXITSTATUS(status) < minErrorCode) {
return WEXITSTATUS(status);
}
break;
}
case -1:
// fork() failed. log the error and proceed as a child
log((CLOG_WARN "fork() failed; cannot automatically restart on error"));
// fall through
case 0:
// child process
return func();
}
}
}
const char* CUnixPlatform::getBasename(const char* pathname) const
@ -117,7 +161,9 @@ CString CUnixPlatform::addPathComponent(
CString path;
path.reserve(prefix.size() + 1 + suffix.size());
path += prefix;
path += '/';
if (path.size() == 0 || path[path.size() - 1] != '/') {
path += '/';
}
path += suffix;
return path;
}
@ -128,7 +174,7 @@ void CUnixPlatform::setDaemonLogger(const char* name)
CLog::setOutputter(&CUnixPlatform::deamonLogger);
}
void CUnixPlatform::deamonLogger(
bool CUnixPlatform::deamonLogger(
int priority, const char* msg)
{
// convert priority
@ -157,4 +203,5 @@ void CUnixPlatform::deamonLogger(
// log it
syslog(priority, "%s", msg);
return true;
}

View File

@ -9,9 +9,13 @@ public:
virtual ~CUnixPlatform();
// IPlatform overrides
virtual bool installDaemon(/* FIXME */);
virtual bool uninstallDaemon(/* FIXME */);
virtual bool daemonize(const char* name);
virtual bool installDaemon(const char* name,
const char* description,
const char* pathname,
const char* commandLine);
virtual bool uninstallDaemon(const char* name);
virtual int daemonize(const char* name, DaemonFunc);
virtual int restart(RestartFunc, int minErrorCode);
virtual const char* getBasename(const char* pathname) const;
virtual CString getUserDirectory() const;
virtual CString getSystemDirectory() const;
@ -23,7 +27,7 @@ protected:
virtual void setDaemonLogger(const char* name);
private:
static void deamonLogger(int, const char*);
static bool deamonLogger(int, const char*);
};
#endif

View File

@ -1,12 +1,20 @@
#include "CWin32Platform.h"
#include "CLock.h"
#include "CThread.h"
#include "CLog.h"
#include "stdvector.h"
#include <string.h>
#include <windows.h>
#include <shlobj.h>
#include <tchar.h>
#include <assert.h>
//
// CWin32Platform
//
HANDLE CWin32Platform::s_eventLog = NULL;
CWin32Platform* CWin32Platform::s_daemonPlatform = NULL;
CWin32Platform::CWin32Platform()
{
// do nothing
@ -17,22 +25,282 @@ CWin32Platform::~CWin32Platform()
// do nothing
}
bool CWin32Platform::installDaemon(/* FIXME */)
bool CWin32Platform::isWindows95Family()
{
// FIXME
return false;
OSVERSIONINFO version;
version.dwOSVersionInfoSize = sizeof(version);
if (GetVersionEx(&version) == 0) {
log((CLOG_WARN "cannot determine OS: %d", GetLastError()));
return true;
}
return (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
}
bool CWin32Platform::uninstallDaemon(/* FIXME */)
void CWin32Platform::setStatus(
SERVICE_STATUS_HANDLE handle,
DWORD state)
{
// FIXME
return false;
setStatus(handle, state, 0, 0);
}
bool CWin32Platform::daemonize(const char* name)
void CWin32Platform::setStatus(
SERVICE_STATUS_HANDLE handle,
DWORD state, DWORD step, DWORD waitHint)
{
// FIXME
return false;
SERVICE_STATUS status;
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS |
SERVICE_INTERACTIVE_PROCESS;
status.dwCurrentState = state;
status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN;
status.dwWin32ExitCode = NO_ERROR;
status.dwServiceSpecificExitCode = 0;
status.dwCheckPoint = step;
status.dwWaitHint = waitHint;
SetServiceStatus(handle, &status);
}
void CWin32Platform::setStatusError(
SERVICE_STATUS_HANDLE handle,
DWORD error)
{
SERVICE_STATUS status;
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS |
SERVICE_INTERACTIVE_PROCESS;
status.dwCurrentState = SERVICE_STOPPED;
status.dwControlsAccepted = SERVICE_ACCEPT_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN;
status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
status.dwServiceSpecificExitCode = error;
status.dwCheckPoint = 0;
status.dwWaitHint = 0;
SetServiceStatus(handle, &status);
}
bool CWin32Platform::installDaemon(
const char* name,
const char* description,
const char* pathname,
const char* commandLine)
{
// windows 95 family services
if (isWindows95Family()) {
// open registry
HKEY key = open95ServicesKey();
if (key == NULL) {
log((CLOG_ERR "cannot open RunServices registry key", GetLastError()));
return false;
}
// construct entry
CString value;
value += "\"";
value += pathname;
value += "\" ";
value += commandLine;
// install entry
setValue(key, name, value);
// clean up
closeKey(key);
return true;
}
// windows NT family services
else {
// open service manager
SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE);
if (mgr == NULL) {
log((CLOG_ERR "OpenSCManager failed with %d", GetLastError()));
return false;
}
// create the servie
SC_HANDLE service = CreateService(mgr,
name,
name,
0,
SERVICE_WIN32_OWN_PROCESS |
SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
pathname,
NULL,
NULL,
NULL,
NULL,
NULL);
// done with service and manager
if (service != NULL) {
CloseServiceHandle(service);
CloseServiceHandle(mgr);
}
else {
log((CLOG_ERR "CreateService failed with %d", GetLastError()));
CloseServiceHandle(mgr);
return false;
}
// open the registry key for this service
HKEY key = openNTServicesKey();
key = openKey(key, name);
if (key == NULL) {
// can't open key
uninstallDaemon(name);
return false;
}
// set the description
setValue(key, "Description", description);
// set command line
key = openKey(key, "Parameters");
if (key == NULL) {
// can't open key
uninstallDaemon(name);
return false;
}
setValue(key, "CommandLine", commandLine);
// done with registry
closeKey(key);
return true;
}
}
bool CWin32Platform::uninstallDaemon(const char* name)
{
// windows 95 family services
if (isWindows95Family()) {
// open registry
HKEY key = open95ServicesKey();
if (key == NULL) {
log((CLOG_ERR "cannot open RunServices registry key", GetLastError()));
return false;
}
// remove entry
deleteValue(key, name);
// clean up
closeKey(key);
return true;
}
// windows NT family services
else {
// remove parameters for this service
HKEY key = openNTServicesKey();
key = openKey(key, name);
if (key != NULL) {
deleteKey(key, "Parameters");
closeKey(key);
}
// open service manager
SC_HANDLE mgr = OpenSCManager(NULL, NULL, GENERIC_WRITE);
if (mgr == NULL) {
log((CLOG_ERR "OpenSCManager failed with %d", GetLastError()));
return false;
}
// open the service. oddly, you must open a service to delete it.
bool success;
SC_HANDLE service = OpenService(mgr, name, DELETE);
if (service == NULL) {
log((CLOG_ERR "OpenService failed with %d", GetLastError()));
success = false;
}
else {
success = (DeleteService(service) != 0);
CloseServiceHandle(service);
}
// close the manager
CloseServiceHandle(mgr);
return success;
}
}
int CWin32Platform::daemonize(
const char* name,
DaemonFunc func)
{
assert(name != NULL);
assert(func != NULL);
// windows 95 family services
if (isWindows95Family()) {
typedef DWORD (WINAPI *RegisterServiceProcessT)(DWORD, DWORD);
// mark this process as a service so it's not killed when the
// user logs off.
HINSTANCE kernel = LoadLibrary("kernel32.dll");
if (kernel == NULL) {
log((CLOG_ERR "LoadLibrary failed with %d", GetLastError()));
return -1;
}
RegisterServiceProcessT RegisterServiceProcess =
reinterpret_cast<RegisterServiceProcessT>(
GetProcAddress(kernel,
_T("RegisterServiceProcess")));
if (RegisterServiceProcess == NULL) {
log((CLOG_ERR "can't lookup RegisterServiceProcess: %d", GetLastError()));
FreeLibrary(kernel);
return -1;
}
if (RegisterServiceProcess(NULL, 1) == 0) {
log((CLOG_ERR "RegisterServiceProcess failed with %d", GetLastError()));
FreeLibrary(kernel);
return -1;
}
FreeLibrary(kernel);
// now simply call the daemon function
return func(this, 1, &name);
}
// windows NT family services
else {
// save daemon function
m_daemonFunc = func;
// construct the service entry
SERVICE_TABLE_ENTRY entry[2];
entry[0].lpServiceName = const_cast<char*>(name);
entry[0].lpServiceProc = &CWin32Platform::serviceMainEntry;
entry[1].lpServiceName = NULL;
entry[1].lpServiceProc = NULL;
// hook us up to the service control manager. this won't return
// (if successful) until the processes have terminated.
s_daemonPlatform = this;
if (StartServiceCtrlDispatcher(entry)) {
s_daemonPlatform = NULL;
return m_daemonResult;
}
log((CLOG_ERR "StartServiceCtrlDispatcher failed with %d", GetLastError()));
s_daemonPlatform = NULL;
return -1;
}
}
int CWin32Platform::restart(
RestartFunc func, int /*minErrorCode*/)
{
// FIXME -- start in separate process or thread. note that this
// isn't too critical as win32 doesn't force us to terminate for
// any reason so we should never have to restart.
return func();
}
const char* CWin32Platform::getBasename(const char* pathname) const
@ -61,14 +329,58 @@ const char* CWin32Platform::getBasename(const char* pathname) const
CString CWin32Platform::getUserDirectory() const
{
// FIXME
return CString();
// try %HOMEPATH%
TCHAR dir[MAX_PATH];
DWORD size = sizeof(dir) / sizeof(TCHAR);
DWORD result = GetEnvironmentVariable(_T("HOMEPATH"), dir, size);
if (result != 0 && result <= size) {
// sanity check -- if dir doesn't appear to start with a
// drive letter and isn't a UNC name then don't use it
// FIXME -- allow UNC names
if (dir[0] != '\0' && (dir[1] == ':' ||
((dir[0] == '\\' || dir[0] == '/') &&
(dir[1] == '\\' || dir[1] == '/')))) {
return dir;
}
}
// get the location of the personal files. that's as close to
// a home directory as we're likely to find.
ITEMIDLIST* idl;
if (SUCCEEDED(SHGetSpecialFolderLocation(NULL, CSIDL_PERSONAL, &idl))) {
TCHAR* path = NULL;
if (SHGetPathFromIDList(idl, dir)) {
DWORD attr = GetFileAttributes(dir);
if (attr != 0xffffffff && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
path = dir;
}
IMalloc* shalloc;
if (SUCCEEDED(SHGetMalloc(&shalloc))) {
shalloc->Free(idl);
shalloc->Release();
}
if (path != NULL) {
return path;
}
}
// use root of C drive as a default
return "C:";
}
CString CWin32Platform::getSystemDirectory() const
{
// FIXME
return "";
// get windows directory
char dir[MAX_PATH];
if (GetWindowsDirectory(dir, sizeof(dir)) != 0) {
return dir;
}
else {
// can't get it. use C:\ as a default.
return "C:";
}
}
CString CWin32Platform::addPathComponent(
@ -78,13 +390,453 @@ CString CWin32Platform::addPathComponent(
CString path;
path.reserve(prefix.size() + 1 + suffix.size());
path += prefix;
path += '\\';
if (path.size() == 0 ||
(path[path.size() - 1] != '\\' &&
path[path.size() - 1] != '/')) {
path += '\\';
}
path += suffix;
return path;
}
void CWin32Platform::serviceLogger(
HKEY CWin32Platform::openKey(
HKEY key, const char* keyName)
{
// open next key
HKEY newKey;
LONG result = RegOpenKeyEx(key, keyName, 0,
KEY_WRITE | KEY_QUERY_VALUE, &newKey);
if (result != ERROR_SUCCESS) {
DWORD disp;
result = RegCreateKeyEx(key, keyName, 0, _T(""),
0, KEY_WRITE | KEY_QUERY_VALUE,
NULL, &newKey, &disp);
}
if (result != ERROR_SUCCESS) {
RegCloseKey(key);
return NULL;
}
// switch to new key
RegCloseKey(key);
return newKey;
}
HKEY CWin32Platform::openKey(
HKEY key, const char** keyNames)
{
for (UInt32 i = 0; key != NULL && keyNames[i] != NULL; ++i) {
// open next key
key = openKey(key, keyNames[i]);
}
return key;
}
void CWin32Platform::closeKey(HKEY key)
{
assert(key != NULL);
RegCloseKey(key);
}
void CWin32Platform::deleteKey(HKEY key, const char* name)
{
assert(key != NULL);
assert(name != NULL);
RegDeleteKey(key, name);
}
void CWin32Platform::deleteValue(HKEY key, const char* name)
{
assert(key != NULL);
assert(name != NULL);
RegDeleteValue(key, name);
}
void CWin32Platform::setValue(HKEY key,
const char* name, const CString& value)
{
assert(key != NULL);
assert(name != NULL);
RegSetValueEx(key, name, 0, REG_SZ,
reinterpret_cast<const BYTE*>(value.c_str()),
value.size() + 1);
}
CString CWin32Platform::readValueString(HKEY key,
const char* name)
{
// get the size of the string
DWORD type;
DWORD size = 0;
LONG result = RegQueryValueEx(key, name, 0, &type, NULL, &size);
if (result != ERROR_SUCCESS || type != REG_SZ) {
return CString();
}
// allocate space
char* buffer = new char[size];
// read it
result = RegQueryValueEx(key, name, 0, &type,
reinterpret_cast<BYTE*>(buffer), &size);
if (result != ERROR_SUCCESS || type != REG_SZ) {
delete[] buffer;
return CString();
}
// clean up and return value
CString value(buffer);
delete[] buffer;
return value;
}
HKEY CWin32Platform::openNTServicesKey()
{
static const char* s_keyNames[] = {
_T("SYSTEM"),
_T("CurrentControlSet"),
_T("Services"),
NULL
};
return openKey(HKEY_LOCAL_MACHINE, s_keyNames);
}
HKEY CWin32Platform::open95ServicesKey()
{
static const char* s_keyNames[] = {
_T("Software"),
_T("Microsoft"),
_T("Windows"),
_T("CurrentVersion"),
_T("RunServices"),
NULL
};
return openKey(HKEY_LOCAL_MACHINE, s_keyNames);
}
int CWin32Platform::runDaemon(RunFunc run, StopFunc stop)
{
// should only be called from DaemonFunc
assert(m_serviceMutex != NULL);
CLock lock(m_serviceMutex);
try {
int result;
m_stop = stop;
m_serviceHandlerWaiting = false;
m_serviceRunning = false;
for (;;) {
// mark server as running
setStatus(m_statusHandle, SERVICE_RUNNING);
// run callback
m_serviceRunning = true;
result = run(m_serviceMutex);
m_serviceRunning = false;
// notify handler that the server stopped. if handler
// isn't waiting then we stopped unexpectedly and we
// quit.
if (m_serviceHandlerWaiting) {
m_serviceHandlerWaiting = false;
m_serviceState->broadcast();
}
else {
break;
}
// wait until we're told what to do next
while (*m_serviceState != SERVICE_RUNNING &&
*m_serviceState != SERVICE_STOPPED) {
m_serviceState->wait();
}
// exit loop if we've been told to stop
if (*m_serviceState == SERVICE_STOPPED) {
break;
}
}
// prevent daemonHandler from changing state
*m_serviceState = SERVICE_STOPPED;
// tell service control that the service is stopped.
// FIXME -- hopefully this will ensure that our handler won't
// be called again but i can't find documentation that
// verifies that. if it does it'll crash on the mutex that
// we're about to destroy.
setStatus(m_statusHandle, *m_serviceState);
// clean up
m_stop = NULL;
return result;
}
catch (...) {
// FIXME -- report error
// prevent serviceHandler from changing state
*m_serviceState = SERVICE_STOPPED;
// set status
setStatusError(m_statusHandle, 0);
// wake up serviceHandler if it's waiting then wait for it
if (m_serviceHandlerWaiting) {
m_serviceHandlerWaiting = false;
m_serviceState->broadcast();
m_serviceState->wait();
// serviceHandler has exited by now
}
throw;
}
}
void CWin32Platform::serviceMain(
DWORD argc, LPTSTR* argvIn)
{
typedef std::vector<LPCTSTR> ArgList;
typedef std::vector<CString> Arguments;
const char** argv = const_cast<const char**>(argvIn);
// open event log and direct log messages to it
if (s_eventLog == NULL) {
s_eventLog = RegisterEventSource(NULL, argv[0]);
if (s_eventLog != NULL) {
CLog::setOutputter(&CWin32Platform::serviceLogger);
}
}
// create synchronization objects
CThread::init();
m_serviceMutex = new CMutex;
m_serviceState = new CCondVar<DWORD>(m_serviceMutex, SERVICE_RUNNING);
// register our service handler functiom
m_statusHandle = RegisterServiceCtrlHandler(argv[0],
&CWin32Platform::serviceHandlerEntry);
if (m_statusHandle == NULL) {
// cannot start as service
m_daemonResult = -1;
delete m_serviceState;
delete m_serviceMutex;
return;
}
// tell service control manager that we're starting
setStatus(m_statusHandle, SERVICE_START_PENDING, 0, 1000);
// if no arguments supplied then try getting them from the registry.
// the first argument doesn't count because it's the service name.
Arguments args;
ArgList myArgv;
if (argc <= 1) {
// read command line
CString commandLine;
HKEY key = openNTServicesKey();
key = openKey(key, argv[0]);
key = openKey(key, "Parameters");
if (key != NULL) {
commandLine = readValueString(key, "CommandLine");
}
// if the command line isn't empty then parse and use it
if (!commandLine.empty()) {
// parse, honoring double quoted substrings
CString::size_type i = commandLine.find_first_not_of(" \t");
while (i != CString::npos && i != commandLine.size()) {
// find end of string
CString::size_type e;
if (commandLine[i] == '\"') {
// quoted. find closing quote.
++i;
e = commandLine.find("\"", i);
// whitespace must follow closing quote
if (e == CString::npos ||
(e + 1 != commandLine.size() &&
commandLine[e + 1] != ' ' &&
commandLine[e + 1] != '\t')) {
args.clear();
break;
}
// extract
args.push_back(commandLine.substr(i, e - i));
i = e + 1;
}
else {
// unquoted. find next whitespace.
e = commandLine.find_first_of(" \t", i);
if (e == CString::npos) {
e = commandLine.size();
}
// extract
args.push_back(commandLine.substr(i, e - i));
i = e + 1;
}
// next argument
i = commandLine.find_first_not_of(" \t", i);
}
// service name goes first
myArgv.push_back(argv[0]);
// get pointers
for (UInt32 i = 0; i < args.size(); ++i) {
myArgv.push_back(args[i].c_str());
}
// adjust argc/argv
argc = myArgv.size();
argv = &myArgv[0];
}
}
try {
// invoke daemon function
m_daemonResult = m_daemonFunc(this, static_cast<int>(argc), argv);
}
catch (CDaemonFailed& e) {
setStatusError(m_statusHandle, e.m_result);
m_daemonResult = -1;
}
catch (...) {
setStatusError(m_statusHandle, 1);
m_daemonResult = -1;
}
// clean up
delete m_serviceState;
delete m_serviceMutex;
// FIXME -- close event log?
}
void WINAPI CWin32Platform::serviceMainEntry(
DWORD argc, LPTSTR* argv)
{
s_daemonPlatform->serviceMain(argc, argv);
}
void CWin32Platform::serviceHandler(DWORD ctrl)
{
assert(m_serviceMutex != NULL);
assert(m_serviceState != NULL);
CLock lock(m_serviceMutex);
// ignore request if service is already stopped
if (*m_serviceState == SERVICE_STOPPED) {
setStatus(m_statusHandle, *m_serviceState);
return;
}
switch (ctrl) {
case SERVICE_CONTROL_PAUSE:
// update state
*m_serviceState = SERVICE_PAUSE_PENDING;
setStatus(m_statusHandle, *m_serviceState, 0, 1000);
// stop run callback if running and wait for it to finish
if (m_serviceRunning) {
m_serviceHandlerWaiting = true;
m_stop();
m_serviceState->wait();
}
// update state if service hasn't stopped while we were waiting
if (*m_serviceState != SERVICE_STOPPED) {
*m_serviceState = SERVICE_PAUSED;
}
m_serviceState->broadcast();
break;
case SERVICE_CONTROL_CONTINUE:
// required status update
setStatus(m_statusHandle, *m_serviceState);
// update state but let main loop send RUNNING notification
*m_serviceState = SERVICE_RUNNING;
m_serviceState->broadcast();
return;
case SERVICE_CONTROL_STOP:
case SERVICE_CONTROL_SHUTDOWN:
// update state
*m_serviceState = SERVICE_STOP_PENDING;
setStatus(m_statusHandle, *m_serviceState, 0, 1000);
// stop run callback if running and wait for it to finish
if (m_serviceRunning) {
m_serviceHandlerWaiting = true;
m_stop();
m_serviceState->wait();
}
// update state
*m_serviceState = SERVICE_STOPPED;
m_serviceState->broadcast();
break;
default:
log((CLOG_WARN "unknown service command: %d", ctrl));
// fall through
case SERVICE_CONTROL_INTERROGATE:
break;
}
// send update
setStatus(m_statusHandle, *m_serviceState);
}
void WINAPI CWin32Platform::serviceHandlerEntry(DWORD ctrl)
{
s_daemonPlatform->serviceHandler(ctrl);
}
bool CWin32Platform::serviceLogger(
int priority, const char* msg)
{
// FIXME
if (s_eventLog == NULL) {
return false;
}
// convert priority
WORD type;
switch (priority) {
case CLog::kFATAL:
case CLog::kERROR:
type = EVENTLOG_ERROR_TYPE;
break;
case CLog::kWARNING:
type = EVENTLOG_WARNING_TYPE;
break;
default:
type = EVENTLOG_INFORMATION_TYPE;
break;
}
// log it
// FIXME -- win32 wants to use a message table to look up event
// strings. log messages aren't organized that way so we'll
// just dump our string into the raw data section of the event
// so users can at least see the message. note that we use our
// priority as the event category.
ReportEvent(s_eventLog, type, static_cast<WORD>(priority),
0, // event ID
NULL,
0,
strlen(msg + 1), // raw data size
NULL,
const_cast<char*>(msg));// raw data
return true;
}

View File

@ -2,16 +2,52 @@
#define CWIN32PLATFORM_H
#include "IPlatform.h"
#include "CCondVar.h"
#include "CMutex.h"
#include <windows.h>
class CWin32Platform : public IPlatform {
public:
typedef int (*RunFunc)(CMutex*);
typedef void (*StopFunc)(void);
CWin32Platform();
virtual ~CWin32Platform();
// returns true iff the platform is win95/98/me
static bool isWindows95Family();
// utility for calling SetServiceStatus()
static void setStatus(SERVICE_STATUS_HANDLE, DWORD state);
static void setStatus(SERVICE_STATUS_HANDLE,
DWORD state, DWORD step, DWORD waitHint);
static void setStatusError(SERVICE_STATUS_HANDLE, DWORD error);
// run a service. the RunFunc should unlock the passed in mutex
// (which will be locked on entry) when not initializing or
// shutting down (i.e. when running its loop). StopFunc should
// cause the RunFunc() to return. returns what RunFunc returns.
// RunFunc should throw CDaemonFailed if the service fails.
int runDaemon(RunFunc, StopFunc);
// thrown by RunFunc on service failure. result is the error
// code reported by the service.
class CDaemonFailed {
public:
CDaemonFailed(int result) : m_result(result) { }
public:
int m_result;
};
// IPlatform overrides
virtual bool installDaemon(/* FIXME */);
virtual bool uninstallDaemon(/* FIXME */);
virtual bool daemonize(const char* name);
virtual bool installDaemon(const char* name,
const char* description,
const char* pathname,
const char* commandLine);
virtual bool uninstallDaemon(const char* name);
virtual int daemonize(const char* name, DaemonFunc);
virtual int restart(RestartFunc, int minErrorCode);
virtual const char* getBasename(const char* pathname) const;
virtual CString getUserDirectory() const;
virtual CString getSystemDirectory() const;
@ -20,7 +56,40 @@ public:
const CString& suffix) const;
private:
static void serviceLogger(int, const char*);
static HKEY openKey(HKEY parent, const char*);
static HKEY openKey(HKEY parent, const char**);
static void closeKey(HKEY);
static void deleteKey(HKEY, const char* name);
static void deleteValue(HKEY, const char* name);
static void setValue(HKEY, const char* name,
const CString& value);
static CString readValueString(HKEY, const char* name);
static HKEY openNTServicesKey();
static HKEY open95ServicesKey();
void serviceMain(DWORD, LPTSTR*);
static void WINAPI serviceMainEntry(DWORD, LPTSTR*);
void serviceHandler(DWORD ctrl);
static void WINAPI serviceHandlerEntry(DWORD ctrl);
static bool serviceLogger(int, const char*);
private:
DaemonFunc m_daemonFunc;
int m_daemonResult;
SERVICE_STATUS_HANDLE m_statusHandle;
CMutex* m_serviceMutex;
CCondVar<DWORD>* m_serviceState;
bool m_serviceHandlerWaiting;
bool m_serviceRunning;
StopFunc m_stop;
static HANDLE s_eventLog;
static CWin32Platform* s_daemonPlatform;
};
#endif

View File

@ -7,19 +7,48 @@
class IPlatform : public IInterface {
public:
typedef int (*DaemonFunc)(IPlatform*, int argc, const char** argv);
typedef int (*RestartFunc)();
// manipulators
// install/uninstall a daemon.
// install/uninstall a daemon. commandLine should *not*
// include the name of program as the first argument.
// FIXME -- throw on error? will get better error messages that way.
virtual bool installDaemon(/* FIXME */) = 0;
virtual bool uninstallDaemon(/* FIXME */) = 0;
virtual bool installDaemon(const char* name,
const char* description,
const char* pathname,
const char* commandLine) = 0;
virtual bool uninstallDaemon(const char* name) = 0;
// daemonize. this should have the side effect of sending log
// messages to a system message logger since messages can no
// longer go to the console. returns true iff successful.
// the name is the name of the daemon.
// FIXME -- win32 services will require a more complex interface
virtual bool daemonize(const char* name) = 0;
// daemonize. this should have the side effect of sending log
// messages to a system message logger since messages can no
// longer go to the console. name is the name of the daemon.
// once daemonized, func is invoked and daemonize returns when
// and what func does. daemonize() returns -1 on error.
//
// exactly what happens when daemonizing depends on the platform.
// unix:
// detaches from terminal. func gets one argument, the name
// passed to daemonize().
// win32:
// becomes a service. argument 0 is the name of the service
// and the rest are the arguments passed to StartService().
// func is only called when the service is actually started.
// func must behave like a proper ServiceMain() function; in
// particular, it must call RegisterServiceCtrlHandler() and
// SetServiceStatus().
virtual int daemonize(const char* name, DaemonFunc func) = 0;
// continually restart the given function in a separate process
// or thread until it exits normally with a code less than the
// given code then return the code.
virtual int restart(RestartFunc, int minErrorCode) = 0;
// accessors

139
platform/platform.dsp Normal file
View File

@ -0,0 +1,139 @@
# Microsoft Developer Studio Project File - Name="platform" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=platform - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "platform.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "platform.mak" CFG="platform - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "platform - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "platform - Win32 Debug" (based on "Win32 (x86) Static Library")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe
!IF "$(CFG)" == "platform - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c
# ADD CPP /nologo /MT /W4 /GX /O2 /I "..\base" /I "..\mt" /I "..\synergy" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LIB32=link.exe -lib
# ADD BASE LIB32 /nologo
# ADD LIB32 /nologo
!ELSEIF "$(CFG)" == "platform - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c
# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\base" /I "..\mt" /I "..\synergy" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FD /GZ /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LIB32=link.exe -lib
# ADD BASE LIB32 /nologo
# ADD LIB32 /nologo
!ENDIF
# Begin Target
# Name "platform - Win32 Release"
# Name "platform - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\CMSWindowsClipboard.cpp
# End Source File
# Begin Source File
SOURCE=.\CMSWindowsScreen.cpp
# End Source File
# Begin Source File
SOURCE=.\CPlatform.cpp
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\CMSWindowsClipboard.h
# End Source File
# Begin Source File
SOURCE=.\CMSWindowsScreen.h
# End Source File
# Begin Source File
SOURCE=.\CPlatform.h
# End Source File
# Begin Source File
SOURCE=.\CWin32Platform.h
# End Source File
# Begin Source File
SOURCE=.\IPlatform.h
# End Source File
# End Group
# Begin Group "Resource Files"
# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe"
# End Group
# Begin Group "Included Files"
# PROP Default_Filter "inc"
# Begin Source File
SOURCE=.\CWin32Platform.cpp
# PROP Exclude_From_Build 1
# End Source File
# End Group
# End Target
# End Project

View File

@ -1,10 +1,11 @@
#include "CMSWindowsPrimaryScreen.h"
#include "CMSWindowsClipboard.h"
#include "CServer.h"
#include "CSynergyHook.h"
#include "CPlatform.h"
#include "XScreen.h"
#include "XSynergy.h"
#include "CThread.h"
#include "CLog.h"
#include "CThread.h"
#include <assert.h>
#include <string.h>
@ -14,38 +15,76 @@
CMSWindowsPrimaryScreen::CMSWindowsPrimaryScreen() :
m_server(NULL),
m_active(false),
m_threadID(0),
m_desk(NULL),
m_deskName(),
m_window(NULL),
m_nextClipboardWindow(NULL),
m_clipboardOwner(NULL),
m_hookLibrary(NULL),
m_active(false),
m_mark(0),
m_markReceived(0)
m_markReceived(0),
m_nextClipboardWindow(NULL),
m_clipboardOwner(NULL)
{
// detect operating system
OSVERSIONINFO version;
version.dwOSVersionInfoSize = sizeof(version);
if (GetVersionEx(&version) == 0) {
log((CLOG_WARN "cannot determine OS: %d", GetLastError()));
// load the hook library
m_hookLibrary = LoadLibrary("synrgyhk");
if (m_hookLibrary == NULL) {
log((CLOG_ERR "failed to load hook library"));
throw XScreenOpenFailure();
}
m_is95Family = (version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS);
m_setZone = (SetZoneFunc)GetProcAddress(m_hookLibrary, "setZone");
m_setRelay = (SetRelayFunc)GetProcAddress(m_hookLibrary, "setRelay");
m_install = (InstallFunc)GetProcAddress(m_hookLibrary, "install");
m_uninstall = (UninstallFunc)GetProcAddress(m_hookLibrary, "uninstall");
if (m_setZone == NULL ||
m_setRelay == NULL ||
m_install == NULL ||
m_uninstall == NULL) {
log((CLOG_ERR "invalid hook library"));
FreeLibrary(m_hookLibrary);
throw XScreenOpenFailure();
}
// detect operating system
m_is95Family = CPlatform::isWindows95Family();
// make sure this thread has a message queue
MSG dummy;
PeekMessage(&dummy, NULL, WM_USER, WM_USER, PM_NOREMOVE);
}
CMSWindowsPrimaryScreen::~CMSWindowsPrimaryScreen()
{
assert(m_hookLibrary != NULL);
assert(m_window == NULL);
assert(m_hookLibrary == NULL);
// done with hook library
FreeLibrary(m_hookLibrary);
}
void CMSWindowsPrimaryScreen::run()
{
// must call run() from same thread as open()
assert(m_threadID == GetCurrentThreadId());
// change our priority
CThread::getCurrentThread().setPriority(-3);
// poll input desktop to see if it changes (preTranslateMessage()
// handles WM_TIMER)
UINT timer = 0;
if (!m_is95Family) {
SetTimer(NULL, 0, 200, NULL);
}
// run event loop
log((CLOG_INFO "entering event loop"));
doRun();
log((CLOG_INFO "exiting event loop"));
// remove timer
if (!m_is95Family) {
KillTimer(NULL, timer);
}
}
void CMSWindowsPrimaryScreen::stop()
@ -64,16 +103,25 @@ void CMSWindowsPrimaryScreen::open(CServer* server)
// open the display
openDisplay();
// get keyboard state
updateKeys();
// initialize marks
m_mark = 0;
m_markReceived = 0;
nextMark();
// send screen info
SInt32 w, h;
getScreenSize(&w, &h);
m_server->setInfo(w, h, getJumpZoneSize(), 0, 0);
// compute center pixel of screen
m_xCenter = w >> 1;
m_yCenter = h >> 1;
// get keyboard state
updateKeys();
// enter the screen
doEnter();
enterNoWarp();
}
void CMSWindowsPrimaryScreen::close()
@ -92,115 +140,48 @@ void CMSWindowsPrimaryScreen::enter(SInt32 x, SInt32 y)
log((CLOG_INFO "entering primary at %d,%d", x, y));
assert(m_active == true);
doEnter();
// enter the screen
enterNoWarp();
// warp to requested location
warpCursor(x, y);
}
void CMSWindowsPrimaryScreen::doEnter()
{
// not active anymore
m_active = false;
// release keyboard/mouse and set the zones that should cause a jump
/* FIXME
if (UnregisterHotKey(m_window, 0x0001) != 0) {
log((CLOG_INFO "released hot key"));
}
else {
log((CLOG_INFO "failed to release hot key: %d", GetLastError()));
}
*/
if (m_is95Family) {
DWORD dummy = 0;
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, &dummy, 0);
}
SInt32 w, h;
getScreenSize(&w, &h);
SetZoneFunc setZone = (SetZoneFunc)GetProcAddress(
m_hookLibrary, "setZone");
setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize());
// restore the active window and hide our window. we can only set
// the active window for another thread if we first attach our input
// to that thread.
ReleaseCapture();
if (m_lastActiveWindow != NULL) {
DWORD myThread = GetCurrentThreadId();
if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) {
// FIXME -- shouldn't raise window if X-Mouse is enabled
// but i have no idea how to do that or check if enabled.
SetActiveWindow(m_lastActiveWindow);
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
}
ShowWindow(m_window, SW_HIDE);
// all messages prior to now are invalid
nextMark();
}
bool CMSWindowsPrimaryScreen::leave()
{
log((CLOG_INFO "leaving primary"));
assert(m_active == false);
// do non-warp enter stuff
// get state of keys as we leave
updateKeys();
// all messages prior to now are invalid
nextMark();
// remember the active window before we leave. GetActiveWindow()
// will only return the active window for the thread's queue (i.e.
// our app) but we need the globally active window. get that by
// attaching input to the foreground window's thread then calling
// GetActiveWindow() and then detaching our input.
m_lastActiveWindow = NULL;
m_lastForegroundWindow = GetForegroundWindow();
m_lastActiveThread = GetWindowThreadProcessId(
m_lastForegroundWindow, NULL);
if (m_lastActiveThread != 0) {
DWORD myThread = GetCurrentThreadId();
if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) {
m_lastActiveWindow = GetActiveWindow();
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
// save active window, show ours, and grab mouse/keyboard
if (!onLeave()) {
return false;
}
// show our window
ShowWindow(m_window, SW_SHOW);
// relay all mouse and keyboard events
SetRelayFunc setRelay = (SetRelayFunc)GetProcAddress(
m_hookLibrary, "setRelay");
setRelay();
m_setRelay();
// get state of keys as we leave
updateKeys();
// warp mouse to center of screen
warpCursor(m_xCenter, m_yCenter);
// ignore this many mouse motion events (not including the already
// queued events). on (at least) the win2k login desktop, one
// motion event is reported using a position from before the above
// warpCursor(). i don't know why it does that and other desktops
// don't have the same problem. anyway, simply ignoring that event
// works around it.
m_mouseMoveIgnore = 1;
// disable ctrl+alt+del, alt+tab, etc
if (m_is95Family) {
// disable ctrl+alt+del, alt+tab, ctrl+esc
DWORD dummy = 0;
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, &dummy, 0);
}
/* FIXME
if (RegisterHotKey(m_window, 0x0001, MOD_ALT, VK_TAB) != 0) {
log((CLOG_INFO "got hot key"));
}
else {
log((CLOG_INFO "failed to get hot key: %d", GetLastError()));
}
*/
// get keyboard input and capture mouse
SetActiveWindow(m_window);
SetFocus(m_window);
SetCapture(m_window);
// warp the mouse to the center of the screen
getScreenSize(&m_xCenter, &m_yCenter);
m_xCenter >>= 1;
m_yCenter >>= 1;
warpCursor(m_xCenter, m_yCenter);
// discard messages until after the warp
nextMark();
@ -238,12 +219,11 @@ log((CLOG_INFO "failed to get hot key: %d", GetLastError()));
void CMSWindowsPrimaryScreen::onConfigure()
{
if (!m_active) {
if ((m_is95Family || m_desk != NULL) && !m_active) {
SInt32 w, h;
getScreenSize(&w, &h);
SetZoneFunc setZone = (SetZoneFunc)GetProcAddress(
m_hookLibrary, "setZone");
setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize());
m_setZone(m_server->getActivePrimarySides(),
w, h, getJumpZoneSize());
}
}
@ -254,7 +234,7 @@ void CMSWindowsPrimaryScreen::warpCursor(SInt32 x, SInt32 y)
}
void CMSWindowsPrimaryScreen::setClipboard(
ClipboardID id, const IClipboard* src)
ClipboardID /*id*/, const IClipboard* src)
{
assert(m_window != NULL);
@ -262,7 +242,8 @@ void CMSWindowsPrimaryScreen::setClipboard(
CClipboard::copy(&dst, src);
}
void CMSWindowsPrimaryScreen::grabClipboard(ClipboardID id)
void CMSWindowsPrimaryScreen::grabClipboard(
ClipboardID /*id*/)
{
assert(m_window != NULL);
@ -284,7 +265,7 @@ SInt32 CMSWindowsPrimaryScreen::getJumpZoneSize() const
}
void CMSWindowsPrimaryScreen::getClipboard(
ClipboardID id, IClipboard* dst) const
ClipboardID /*id*/, IClipboard* dst) const
{
assert(m_window != NULL);
@ -332,80 +313,37 @@ void CMSWindowsPrimaryScreen::onOpenDisplay()
assert(m_window == NULL);
assert(m_server != NULL);
// initialize clipboard owner to current owner. we don't want
// to take ownership of the clipboard just by starting up.
m_clipboardOwner = GetClipboardOwner();
// save thread id. we'll need to pass this to the hook library.
m_threadID = GetCurrentThreadId();
// get screen size
// note -- we use a fullscreen window to grab input. it should
// be possible to use a 1x1 window but i've run into problems
// with losing keyboard input (focus?) in that case.
// unfortunately, hiding the full screen window causes all other
// windows to redraw.
SInt32 w, h;
getScreenSize(&w, &h);
// create the window
m_window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
(LPCTSTR)getClass(), "Synergy",
WS_POPUP,
0, 0, w, h, NULL, NULL,
getInstance(),
NULL);
assert(m_window != NULL);
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
// load the hook library
bool hooked = false;
m_hookLibrary = LoadLibrary("synrgyhk");
if (m_hookLibrary != NULL) {
// install input hooks
InstallFunc install = (InstallFunc)GetProcAddress(
m_hookLibrary, "install");
if (install != NULL) {
hooked = (install(m_window) != 0);
// get the input desktop and switch to it
if (m_is95Family) {
if (!openDesktop()) {
throw XScreenOpenFailure();
}
}
if (!hooked) {
log((CLOG_ERR "failed to install hooks"));
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
DestroyWindow(m_window);
m_window = NULL;
// FIXME -- throw
else {
if (!switchDesktop(openInputDesktop())) {
throw XScreenOpenFailure();
}
}
// initialize marks
m_mark = 0;
m_markReceived = 0;
nextMark();
}
void CMSWindowsPrimaryScreen::onCloseDisplay()
{
assert(m_window != NULL);
// uninstall input hooks
UninstallFunc uninstall = (UninstallFunc)GetProcAddress(
m_hookLibrary, "uninstall");
if (uninstall != NULL) {
uninstall();
// disconnect from desktop
if (m_is95Family) {
closeDesktop();
}
else {
switchDesktop(NULL);
}
// done with hook library
FreeLibrary(m_hookLibrary);
m_hookLibrary = NULL;
// clear thread id
m_threadID = 0;
// remove clipboard snooper
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
// destroy window
DestroyWindow(m_window);
m_window = NULL;
assert(m_window == NULL);
assert(m_desk == NULL);
}
bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
@ -417,8 +355,8 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
return true;
case SYNERGY_MSG_KEY:
// ignore if not at current mark
if (m_mark == m_markReceived) {
// ignore message if posted prior to last mark change
if (m_markReceived == m_mark) {
KeyModifierMask mask;
const KeyID key = mapKey(msg->wParam, msg->lParam, &mask);
if (key != kKeyNone) {
@ -453,8 +391,8 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
return true;
case SYNERGY_MSG_MOUSE_BUTTON:
// ignore if not at current mark
if (m_mark == m_markReceived) {
// ignore message if posted prior to last mark change
if (m_markReceived == m_mark) {
const ButtonID button = mapButton(msg->wParam);
switch (msg->wParam) {
case WM_LBUTTONDOWN:
@ -479,20 +417,19 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
return true;
case SYNERGY_MSG_MOUSE_WHEEL:
// ignore if not at current mark
if (m_mark == m_markReceived) {
// ignore message if posted prior to last mark change
if (m_markReceived == m_mark) {
log((CLOG_ERR "event: button wheel delta=%d %d", msg->wParam, msg->lParam));
m_server->onMouseWheel(msg->wParam);
}
return true;
case SYNERGY_MSG_MOUSE_MOVE:
// ignore if not at current mark
if (m_mark == m_markReceived) {
SInt32 x = (SInt32)msg->wParam;
SInt32 y = (SInt32)msg->lParam;
// ignore message if posted prior to last mark change
if (m_markReceived == m_mark) {
SInt32 x = static_cast<SInt32>(msg->wParam);
SInt32 y = static_cast<SInt32>(msg->lParam);
if (!m_active) {
log((CLOG_DEBUG2 "event: inactive move %d,%d", x, y));
m_server->onMouseMovePrimary(x, y);
}
else {
@ -500,15 +437,35 @@ bool CMSWindowsPrimaryScreen::onPreTranslate(MSG* msg)
x -= m_xCenter;
y -= m_yCenter;
// ignore if the mouse didn't move
if (x != 0 && y != 0) {
log((CLOG_DEBUG2 "event: active move %d,%d", x, y));
// ignore if the mouse didn't move or we're ignoring
// motion.
if (m_mouseMoveIgnore == 0) {
if (x != 0 && y != 0) {
// warp mouse back to center
warpCursor(m_xCenter, m_yCenter);
// warp mouse back to center
warpCursor(m_xCenter, m_yCenter);
// send motion
m_server->onMouseMoveSecondary(x, y);
}
}
else {
// ignored one more motion event
--m_mouseMoveIgnore;
}
}
}
return true;
// send motion
m_server->onMouseMoveSecondary(x, y);
case WM_TIMER:
// if current desktop is not the input desktop then switch to it
if (!m_is95Family) {
HDESK desk = openInputDesktop();
if (desk != NULL) {
if (isCurrentDesktop(desk)) {
CloseDesktop(desk);
}
else {
switchDesktop(desk);
}
}
}
@ -523,11 +480,20 @@ LRESULT CMSWindowsPrimaryScreen::onEvent(
WPARAM wParam, LPARAM lParam)
{
switch (msg) {
/*
case WM_HOTKEY:
log((CLOG_INFO "hot key: %d, %d, %s %s %s", wParam, HIWORD(lParam), (LOWORD(lParam) & MOD_ALT) ? "ALT" : "", (LOWORD(lParam) & MOD_CONTROL) ? "CTRL" : "", (LOWORD(lParam) & MOD_SHIFT) ? "SHIFT" : "", (LOWORD(lParam) & MOD_WIN) ? "WIN" : ""));
return 0;
*/
case WM_QUERYENDSESSION:
if (m_is95Family) {
return TRUE;
}
break;
case WM_ENDSESSION:
if (m_is95Family) {
if (wParam == TRUE && lParam == 0) {
stop();
}
return 0;
}
break;
case WM_PAINT:
ValidateRect(hwnd, NULL);
@ -537,7 +503,9 @@ return 0;
log((CLOG_DEBUG "clipboard was taken"));
// first pass it on
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
if (m_nextClipboardWindow != NULL) {
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
}
// now notify server that somebody changed the clipboard.
// skip that if we're the new owner.
@ -555,51 +523,302 @@ return 0;
return 0;
case WM_CHANGECBCHAIN:
if (m_nextClipboardWindow == (HWND)wParam)
if (m_nextClipboardWindow == (HWND)wParam) {
m_nextClipboardWindow = (HWND)lParam;
else
}
else if (m_nextClipboardWindow != NULL) {
SendMessage(m_nextClipboardWindow, msg, wParam, lParam);
}
return 0;
case WM_DISPLAYCHANGE: {
// screen resolution has changed
SInt32 w, h;
updateScreenSize();
getScreenSize(&w, &h);
case WM_DISPLAYCHANGE:
{
// screen resolution may have changed
SInt32 wOld, hOld;
getScreenSize(&wOld, &hOld);
SInt32 w, h;
updateScreenSize();
getScreenSize(&w, &h);
// recompute center pixel of screen
m_xCenter = w >> 1;
m_yCenter = h >> 1;
// do nothing if resolution hasn't changed
if (w != wOld || h != hOld) {
// recompute center pixel of screen
m_xCenter = w >> 1;
m_yCenter = h >> 1;
// warp mouse to center if active
if (m_active) {
warpCursor(m_xCenter, m_yCenter);
// warp mouse to center if active
if (m_active) {
warpCursor(m_xCenter, m_yCenter);
}
// tell hook about resize if not active
else {
onConfigure();
}
// send new screen info
POINT pos;
GetCursorPos(&pos);
m_server->setInfo(w, h, getJumpZoneSize(), pos.x, pos.y);
}
return 0;
}
// tell hook about resize if not active
else {
SetZoneFunc setZone = (SetZoneFunc)GetProcAddress(
m_hookLibrary, "setZone");
setZone(m_server->getActivePrimarySides(), w, h, getJumpZoneSize());
}
// send new screen info
POINT pos;
GetCursorPos(&pos);
m_server->setInfo(w, h, getJumpZoneSize(), pos.x, pos.y);
return 0;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void CMSWindowsPrimaryScreen::enterNoWarp()
{
// not active anymore
m_active = false;
// reset motion ignore count
m_mouseMoveIgnore = 0;
// enable ctrl+alt+del, alt+tab, etc
if (m_is95Family) {
DWORD dummy = 0;
SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, FALSE, &dummy, 0);
}
// install jump zones
onConfigure();
// restore active window and hide our window
onEnter();
// all messages prior to now are invalid
nextMark();
}
void CMSWindowsPrimaryScreen::onEnter()
{
// restore the active window and hide our window. we can only set
// the active window for another thread if we first attach our input
// to that thread.
ReleaseCapture();
if (m_lastActiveWindow != NULL) {
DWORD myThread = GetCurrentThreadId();
if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) {
// FIXME -- shouldn't raise window if X-Mouse is enabled
// but i have no idea how to do that or check if enabled.
SetActiveWindow(m_lastActiveWindow);
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
}
ShowWindow(m_window, SW_HIDE);
}
bool CMSWindowsPrimaryScreen::onLeave()
{
// remember the active window before we leave. GetActiveWindow()
// will only return the active window for the thread's queue (i.e.
// our app) but we need the globally active window. get that by
// attaching input to the foreground window's thread then calling
// GetActiveWindow() and then detaching our input.
m_lastActiveWindow = NULL;
m_lastForegroundWindow = GetForegroundWindow();
m_lastActiveThread = GetWindowThreadProcessId(
m_lastForegroundWindow, NULL);
if (m_lastActiveThread != 0) {
DWORD myThread = GetCurrentThreadId();
if (AttachThreadInput(myThread, m_lastActiveThread, TRUE)) {
m_lastActiveWindow = GetActiveWindow();
AttachThreadInput(myThread, m_lastActiveThread, FALSE);
}
}
// show our window
ShowWindow(m_window, SW_SHOW);
// get keyboard input and capture mouse
SetActiveWindow(m_window);
SetFocus(m_window);
SetCapture(m_window);
return true;
}
void CMSWindowsPrimaryScreen::nextMark()
{
assert(m_window != NULL);
// next mark
++m_mark;
PostMessage(m_window, SYNERGY_MSG_MARK, ++m_mark, 0);
// mark point in message queue where the mark was changed
PostThreadMessage(m_threadID, SYNERGY_MSG_MARK, m_mark, 0);
}
bool CMSWindowsPrimaryScreen::openDesktop()
{
// install hooks
m_install(m_threadID);
// note -- we use a fullscreen window to grab input. it should
// be possible to use a 1x1 window but i've run into problems
// with losing keyboard input (focus?) in that case.
// unfortunately, hiding the full screen window (when entering
// the scren causes all other windows to redraw).
SInt32 w, h;
getScreenSize(&w, &h);
// create the window
m_window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT |
WS_EX_TOOLWINDOW,
(LPCTSTR)getClass(),
"Synergy",
WS_POPUP,
0, 0, w, h, NULL, NULL,
getInstance(),
NULL);
if (m_window == NULL) {
log((CLOG_ERR "failed to create window: %d", GetLastError()));
m_uninstall();
return false;
}
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
return true;
}
void CMSWindowsPrimaryScreen::closeDesktop()
{
// destroy old window
if (m_window != NULL) {
// restore active window and hide ours
if (m_active) {
onEnter();
}
// first remove clipboard snooper
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
// we no longer own the clipboard
if (m_clipboardOwner == m_window) {
m_clipboardOwner = NULL;
}
// now destroy window
DestroyWindow(m_window);
m_window = NULL;
}
// unhook
m_uninstall();
}
bool CMSWindowsPrimaryScreen::switchDesktop(HDESK desk)
{
// did we own the clipboard?
bool ownClipboard = (m_clipboardOwner == m_window);
// destroy old window
if (m_window != NULL) {
// restore active window and hide ours
if (m_active) {
onEnter();
}
// first remove clipboard snooper
ChangeClipboardChain(m_window, m_nextClipboardWindow);
m_nextClipboardWindow = NULL;
// we no longer own the clipboard
if (ownClipboard) {
m_clipboardOwner = NULL;
}
// now destroy window
DestroyWindow(m_window);
m_window = NULL;
}
// unhook
if (m_desk != NULL) {
m_uninstall();
CloseDesktop(m_desk);
m_desk = NULL;
m_deskName = "";
}
// if no new desktop then we're done
if (desk == NULL) {
log((CLOG_INFO "disconnecting desktop"));
return true;
}
// set the desktop. can only do this when there are no windows
// and hooks on the current desktop owned by this thread.
if (SetThreadDesktop(desk) == 0) {
log((CLOG_ERR "failed to set desktop: %d", GetLastError()));
CloseDesktop(desk);
return false;
}
// install hooks
m_install(m_threadID);
// note -- we use a fullscreen window to grab input. it should
// be possible to use a 1x1 window but i've run into problems
// with losing keyboard input (focus?) in that case.
// unfortunately, hiding the full screen window (when entering
// the scren causes all other windows to redraw).
SInt32 w, h;
getScreenSize(&w, &h);
// create the window
m_window = CreateWindowEx(WS_EX_TOPMOST |
WS_EX_TRANSPARENT |
WS_EX_TOOLWINDOW,
(LPCTSTR)getClass(),
"Synergy",
WS_POPUP,
0, 0, w, h, NULL, NULL,
getInstance(),
NULL);
if (m_window == NULL) {
log((CLOG_ERR "failed to create window: %d", GetLastError()));
m_uninstall();
CloseDesktop(desk);
return false;
}
// install our clipboard snooper
m_nextClipboardWindow = SetClipboardViewer(m_window);
// reassert clipboard ownership
if (ownClipboard) {
// FIXME
}
// save new desktop
m_desk = desk;
m_deskName = getDesktopName(m_desk);
log((CLOG_INFO "switched to desktop %s", m_deskName.c_str()));
// get active window and show ours
if (m_active) {
onLeave();
}
else {
// set jump zones
onConfigure();
// all messages prior to now are invalid
nextMark();
}
return true;
}
CString CMSWindowsPrimaryScreen::getCurrentDesktopName() const
{
return m_deskName;
}
static const KeyID g_virtualKey[] =

View File

@ -1,10 +1,11 @@
#ifndef CMSWINDOWSPRIMARYSCREEN_H
#define CMSWINDOWSPRIMARYSCREEN_H
#include "KeyTypes.h"
#include "MouseTypes.h"
#include "CMSWindowsScreen.h"
#include "IPrimaryScreen.h"
#include "MouseTypes.h"
#include "CString.h"
#include "CSynergyHook.h"
class CMSWindowsPrimaryScreen : public CMSWindowsScreen, public IPrimaryScreen {
public:
@ -36,12 +37,24 @@ protected:
virtual LRESULT onEvent(HWND, UINT, WPARAM, LPARAM);
virtual void onOpenDisplay();
virtual void onCloseDisplay();
virtual CString getCurrentDesktopName() const;
private:
void doEnter();
void enterNoWarp();
void onEnter();
bool onLeave();
// discard posted messages
void nextMark();
// open/close desktop (for windows 95/98/me)
bool openDesktop();
void closeDesktop();
// make desk the thread desktop (for windows NT/2000/XP)
bool switchDesktop(HDESK desk);
// key and button queries
KeyID mapKey(WPARAM keycode, LPARAM info,
KeyModifierMask* maskOut);
ButtonID mapButton(WPARAM button) const;
@ -50,19 +63,51 @@ private:
private:
CServer* m_server;
// true if windows 95/98/me
bool m_is95Family;
bool m_active;
// the main loop's thread id
DWORD m_threadID;
// the current desk and it's name
HDESK m_desk;
CString m_deskName;
// our window (for getting clipboard changes)
HWND m_window;
// m_active is true the hooks are relaying events
bool m_active;
// used to discard queued messages that are no longer needed
UInt32 m_mark;
UInt32 m_markReceived;
// clipboard stuff
HWND m_nextClipboardWindow;
HWND m_clipboardOwner;
// map of key state
BYTE m_keys[256];
// position of center pixel of screen
SInt32 m_xCenter, m_yCenter;
// used to ignore mouse motion
SInt32 m_mouseMoveIgnore;
// hook library stuff
HINSTANCE m_hookLibrary;
InstallFunc m_install;
UninstallFunc m_uninstall;
SetZoneFunc m_setZone;
SetRelayFunc m_setRelay;
// stuff for restoring active window
HWND m_lastForegroundWindow;
HWND m_lastActiveWindow;
DWORD m_lastActiveThread;
HINSTANCE m_hookLibrary;
UInt32 m_mark;
UInt32 m_markReceived;
BYTE m_keys[256];
SInt32 m_xCenter, m_yCenter;
};
#endif

View File

@ -298,7 +298,7 @@ void CServer::setInfoNoLock(const CString& screen,
info->m_width = w;
info->m_height = h;
info->m_zoneSize = zoneSize;
log((CLOG_NOTE "screen \"%s\" size=%dx%d zone=%d pos=%d,%d", screen.c_str(), w, h, zoneSize, x, y));
log((CLOG_INFO "screen \"%s\" size=%dx%d zone=%d pos=%d,%d", screen.c_str(), w, h, zoneSize, x, y));
// send acknowledgement (if screen isn't the primary)
if (info->m_protocol != NULL) {
@ -353,7 +353,7 @@ void CServer::grabClipboardNoLock(
}
// mark screen as owning clipboard
log((CLOG_NOTE "screen \"%s\" grabbed clipboard %d from \"%s\"", screen.c_str(), id, clipboard.m_clipboardOwner.c_str()));
log((CLOG_INFO "screen \"%s\" grabbed clipboard %d from \"%s\"", screen.c_str(), id, clipboard.m_clipboardOwner.c_str()));
clipboard.m_clipboardOwner = screen;
clipboard.m_clipboardSeqNum = seqNum;
@ -404,7 +404,7 @@ void CServer::setClipboard(ClipboardID id,
}
// unmarshall into our clipboard buffer
log((CLOG_NOTE "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
log((CLOG_INFO "screen \"%s\" updated clipboard %d", clipboard.m_clipboardOwner.c_str(), id));
clipboard.m_clipboardReady = true;
clipboard.m_clipboardData = data;
clipboard.m_clipboard.unmarshall(clipboard.m_clipboardData, 0);
@ -603,17 +603,25 @@ void CServer::onMouseMoveSecondaryNoLock(
if (!isLockedToScreenNoLock()) {
// find direction of neighbor
CConfig::EDirection dir;
if (m_x < 0)
if (m_x < 0) {
dir = CConfig::kLeft;
else if (m_x > m_active->m_width - 1)
}
else if (m_x > m_active->m_width - 1) {
dir = CConfig::kRight;
else if (m_y < 0)
}
else if (m_y < 0) {
dir = CConfig::kTop;
else if (m_y > m_active->m_height - 1)
}
else if (m_y > m_active->m_height - 1) {
dir = CConfig::kBottom;
else
}
else {
newScreen = m_active;
// keep compiler quiet about unset variable
dir = CConfig::kLeft;
}
// get neighbor if we should switch
if (newScreen == NULL) {
log((CLOG_DEBUG1 "leave \"%s\" on %s", m_active->m_name.c_str(), CConfig::dirName(dir)));
@ -709,7 +717,7 @@ void CServer::switchScreen(CScreenInfo* dst,
assert(x >= 0 && y >= 0 && x < dst->m_width && y < dst->m_height);
assert(m_active != NULL);
log((CLOG_NOTE "switch from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), dst->m_name.c_str(), x, y));
log((CLOG_INFO "switch from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), dst->m_name.c_str(), x, y));
// FIXME -- we're not locked here but we probably should be
// record new position
@ -1485,7 +1493,7 @@ void CServer::removeConnection(const CString& name)
m_y = m_primaryInfo->m_height >> 1;
// don't notify active screen since it probably already disconnected
log((CLOG_NOTE "jump from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), m_primaryInfo->m_name.c_str(), m_x, m_y));
log((CLOG_INFO "jump from \"%s\" to \"%s\" at %d,%d", m_active->m_name.c_str(), m_primaryInfo->m_name.c_str(), m_x, m_y));
// cut over
m_active = m_primaryInfo;

View File

@ -33,28 +33,26 @@ typedef struct tagMOUSEHOOKSTRUCTWin2000 {
#pragma data_seg("shared")
// all data in this shared section *must* be initialized
static HINSTANCE g_hinstance = NULL;
static DWORD g_process = NULL;
static EWheelSupport g_wheelSupport = kWheelNone;
static UINT g_wmMouseWheel = 0;
static HWND g_hwnd = NULL;
static HHOOK g_keyboard = NULL;
static HHOOK g_mouse = NULL;
static HHOOK g_cbt = NULL;
static HHOOK g_getMessage = NULL;
static HANDLE g_keyHookThread = NULL;
static HINSTANCE g_hinstance = NULL;
static DWORD g_process = NULL;
static EWheelSupport g_wheelSupport = kWheelNone;
static UINT g_wmMouseWheel = 0;
static DWORD g_threadID = 0;
static HHOOK g_keyboard = NULL;
static HHOOK g_mouse = NULL;
static HHOOK g_cbt = NULL;
static HHOOK g_getMessage = NULL;
static HANDLE g_keyHookThread = NULL;
static DWORD g_keyHookThreadID = 0;
static HANDLE g_keyHookEvent = NULL;
static HHOOK g_keyboardLL = NULL;
static bool g_relay = false;
static SInt32 g_zoneSize = 0;
static UInt32 g_zoneSides = 0;
static SInt32 g_wScreen = 0;
static SInt32 g_hScreen = 0;
static HCURSOR g_cursor = NULL;
static DWORD g_cursorThread = 0;
static int foo = 0;
static HANDLE g_keyHookEvent = NULL;
static HHOOK g_keyboardLL = NULL;
static bool g_relay = false;
static SInt32 g_zoneSize = 0;
static UInt32 g_zoneSides = 0;
static SInt32 g_wScreen = 0;
static SInt32 g_hScreen = 0;
static HCURSOR g_cursor = NULL;
static DWORD g_cursorThread = 0;
#pragma data_seg()
@ -91,16 +89,7 @@ static LRESULT CALLBACK keyboardHook(int code, WPARAM wParam, LPARAM lParam)
if (code >= 0) {
if (g_relay) {
// forward message to our window
PostMessage(g_hwnd, SYNERGY_MSG_KEY, wParam, lParam);
/* XXX -- this doesn't seem to work/help with lost key events
// if the active window isn't our window then make it
// active.
const bool wrongFocus = (GetActiveWindow() != g_hwnd);
if (wrongFocus) {
SetForegroundWindow(g_hwnd);
}
*/
PostThreadMessage(g_threadID, SYNERGY_MSG_KEY, wParam, lParam);
// let certain keys pass through
switch (wParam) {
@ -133,59 +122,65 @@ static LRESULT CALLBACK mouseHook(int code, WPARAM wParam, LPARAM lParam)
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_BUTTON, wParam, 0);
PostThreadMessage(g_threadID,
SYNERGY_MSG_MOUSE_BUTTON, wParam, 0);
return 1;
case WM_MOUSEWHEEL: {
// win2k and other systems supporting WM_MOUSEWHEEL in
// the mouse hook are gratuitously different (and poorly
// documented).
switch (g_wheelSupport) {
case kWheelModern: {
case WM_MOUSEWHEEL:
{
// win2k and other systems supporting WM_MOUSEWHEEL in
// the mouse hook are gratuitously different (and poorly
// documented).
switch (g_wheelSupport) {
case kWheelModern: {
const MOUSEHOOKSTRUCT* info =
(const MOUSEHOOKSTRUCT*)lParam;
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL,
static_cast<short>(
LOWORD(info->dwExtraInfo)), 0);
break;
}
case kWheelWin2000: {
const MOUSEHOOKSTRUCTWin2000* info =
(const MOUSEHOOKSTRUCTWin2000*)lParam;
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_WHEEL,
static_cast<short>(
HIWORD(info->mouseData)), 0);
break;
}
default:
break;
}
}
return 1;
case WM_MOUSEMOVE:
{
const MOUSEHOOKSTRUCT* info =
(const MOUSEHOOKSTRUCT*)lParam;
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL,
static_cast<short>(LOWORD(info->dwExtraInfo)), 0);
break;
}
SInt32 x = (SInt32)info->pt.x;
SInt32 y = (SInt32)info->pt.y;
case kWheelWin2000: {
const MOUSEHOOKSTRUCTWin2000* info =
(const MOUSEHOOKSTRUCTWin2000*)lParam;
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL,
static_cast<short>(HIWORD(info->mouseData)), 0);
break;
}
// we want the cursor to be hidden at all times so we
// hide the cursor on whatever window has it. but then
// we have to show the cursor whenever we leave that
// window (or at some later time before we stop relaying).
// so check the window with the cursor. if it's not the
// same window that had it before then show the cursor
// in the last window and hide it in this window.
DWORD thread = GetWindowThreadProcessId(info->hwnd, NULL);
if (thread != g_cursorThread) {
restoreCursor();
hideCursor(thread);
}
default:
break;
// relay the motion
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
}
return 1;
}
case WM_MOUSEMOVE: {
const MOUSEHOOKSTRUCT* info = (const MOUSEHOOKSTRUCT*)lParam;
SInt32 x = (SInt32)info->pt.x;
SInt32 y = (SInt32)info->pt.y;
// we want the cursor to be hidden at all times so we
// hide the cursor on whatever window has it. but then
// we have to show the cursor whenever we leave that
// window (or at some later time before we stop relaying).
// so check the window with the cursor. if it's not the
// same window that had it before then show the cursor
// in the last window and hide it in this window.
DWORD thread = GetWindowThreadProcessId(info->hwnd, NULL);
if (thread != g_cursorThread) {
restoreCursor();
hideCursor(thread);
}
// relay the motion
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_MOVE, x, y);
return 1;
}
}
}
else {
// check for mouse inside jump zone
@ -209,7 +204,7 @@ static LRESULT CALLBACK mouseHook(int code, WPARAM wParam, LPARAM lParam)
// if inside then eat event and notify our window
if (inside) {
restoreCursor();
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_MOVE, x, y);
PostThreadMessage(g_threadID, SYNERGY_MSG_MOUSE_MOVE, x, y);
return 1;
}
}
@ -222,15 +217,7 @@ static LRESULT CALLBACK cbtHook(int code, WPARAM wParam, LPARAM lParam)
{
if (code >= 0) {
if (g_relay) {
switch (code) {
case HCBT_ACTIVATE:
case HCBT_SETFOCUS:
// discard unless activating our window
if (reinterpret_cast<HWND>(wParam) != g_hwnd) {
return 1;
}
break;
}
// do nothing for now. may add something later.
}
}
@ -244,7 +231,8 @@ static LRESULT CALLBACK getMessageHook(int code, WPARAM wParam, LPARAM lParam)
MSG* msg = reinterpret_cast<MSG*>(lParam);
if (msg->message == g_wmMouseWheel) {
// post message to our window
PostMessage(g_hwnd, SYNERGY_MSG_MOUSE_WHEEL, msg->wParam, 0);
PostThreadMessage(g_threadID,
SYNERGY_MSG_MOUSE_WHEEL, msg->wParam, 0);
// zero out the delta in the message so it's (hopefully)
// ignored
@ -296,7 +284,8 @@ static LRESULT CALLBACK keyboardLLHook(int code, WPARAM wParam, LPARAM lParam)
// FIXME -- bit 30 should be set if key was already down
// forward message to our window
PostMessage(g_hwnd, SYNERGY_MSG_KEY, info->vkCode, lParam);
PostThreadMessage(g_threadID,
SYNERGY_MSG_KEY, info->vkCode, lParam);
// discard event
return 1;
@ -436,16 +425,18 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID)
extern "C" {
int install(HWND hwnd)
int install(DWORD threadID)
{
assert(g_threadID == 0);
assert(g_hinstance != NULL);
assert(g_keyboard == NULL);
assert(g_mouse == NULL);
assert(g_cbt == NULL);
assert(g_wheelSupport != kWheelOld || g_getMessage == NULL);
// save window
g_hwnd = hwnd;
// save thread id. we'll post messages to this thread's
// message queue.
g_threadID = threadID;
// set defaults
g_relay = false;
@ -465,7 +456,7 @@ int install(HWND hwnd)
g_hinstance,
0);
if (g_keyboard == NULL) {
g_hwnd = NULL;
g_threadID = NULL;
return 0;
}
@ -478,7 +469,7 @@ int install(HWND hwnd)
// uninstall keyboard hook before failing
UnhookWindowsHookEx(g_keyboard);
g_keyboard = NULL;
g_hwnd = NULL;
g_threadID = NULL;
return 0;
}
@ -493,7 +484,7 @@ int install(HWND hwnd)
UnhookWindowsHookEx(g_mouse);
g_keyboard = NULL;
g_mouse = NULL;
g_hwnd = NULL;
g_threadID = NULL;
return 0;
}
@ -563,7 +554,7 @@ int uninstall(void)
g_mouse = NULL;
g_cbt = NULL;
g_getMessage = NULL;
g_hwnd = NULL;
g_threadID = 0;
// show the cursor
restoreCursor();

View File

@ -20,14 +20,14 @@
#define SYNERGY_MSG_MOUSE_MOVE WM_APP + 0x0014 // x; y
#define SYNERGY_MSG_MOUSE_WHEEL WM_APP + 0x0015 // delta; <unused>
typedef int (*InstallFunc)(HWND);
extern "C" {
typedef int (*InstallFunc)(DWORD targetQueueThreadID);
typedef int (*UninstallFunc)(void);
typedef void (*SetZoneFunc)(UInt32, SInt32, SInt32, SInt32);
typedef void (*SetRelayFunc)(void);
extern "C" {
CSYNERGYHOOK_API int install(HWND);
CSYNERGYHOOK_API int install(DWORD);
CSYNERGYHOOK_API int uninstall(void);
CSYNERGYHOOK_API void setZone(UInt32 sides,
SInt32 w, SInt32 h, SInt32 jumpZoneSize);

View File

@ -23,8 +23,8 @@ CFG=makehook - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
MTL=midl.exe
!IF "$(CFG)" == "makehook - Win32 Release"

View File

@ -1,6 +1,7 @@
#include "CServer.h"
#include "CConfig.h"
#include "CLog.h"
#include "CLock.h"
#include "CMutex.h"
#include "CNetwork.h"
#include "CPlatform.h"
@ -11,15 +12,20 @@
#include "stdfstream.h"
#include <assert.h>
// platform dependent name of a daemon
#if defined(CONFIG_PLATFORM_WIN32)
#define DAEMON "service"
#define DAEMON_NAME "Synergy Server"
#elif defined(CONFIG_PLATFORM_UNIX)
#define DAEMON "daemon"
#define DAEMON_NAME "synergyd"
#endif
// configuration file name
#if defined(CONFIG_PLATFORM_WIN32)
#define CONFIG_NAME "synergy.sgc"
#define CONFIG_USER_DIR "%HOME%/"
#define CONFIG_SYS_DIR ""
#elif defined(CONFIG_PLATFORM_UNIX)
#define CONFIG_NAME "synergy.conf"
#define CONFIG_USER_DIR "~/"
#define CONFIG_SYS_DIR "/etc/"
#endif
//
@ -29,6 +35,8 @@
static const char* pname = NULL;
static bool s_restartable = true;
static bool s_daemon = true;
static bool s_install = false;
static bool s_uninstall = false;
static const char* s_configFile = NULL;
static const char* s_logFilter = NULL;
static CConfig s_config;
@ -54,47 +62,97 @@ static void logLock(bool lock)
//
// main
// platform independent main
//
void realMain()
static CServer* s_server = NULL;
static int realMain(CMutex* mutex)
{
// initialize threading library
CThread::init();
// s_serverLock should have mutex locked on entry
// make logging thread safe
CMutex logMutex;
s_logMutex = &logMutex;
CLog::setLock(&logLock);
CServer* server = NULL;
try {
// initialize network library
CNetwork::init();
// initialize threading library
CThread::init();
// if configuration has no screens then add this system
// as the default
if (s_config.begin() == s_config.end()) {
s_config.addScreen("primary");
// make logging thread safe
CMutex logMutex;
s_logMutex = &logMutex;
CLog::setLock(&logLock);
bool locked = true;
try {
// initialize network library
CNetwork::init();
// if configuration has no screens then add this system
// as the default
if (s_config.begin() == s_config.end()) {
s_config.addScreen("primary");
}
// create server
s_server = new CServer();
// run server (unlocked)
if (mutex != NULL) {
mutex->unlock();
}
locked = false;
s_server->setConfig(s_config);
s_server->run();
locked = true;
if (mutex != NULL) {
mutex->lock();
}
// clean up
delete s_server;
s_server = NULL;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
}
catch (...) {
// clean up
if (!locked && mutex != NULL) {
mutex->lock();
}
delete s_server;
s_server = NULL;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
throw;
}
// run server
server = new CServer();
server->setConfig(s_config);
server->run();
// clean up
delete server;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
}
catch (...) {
delete server;
CNetwork::cleanup();
CLog::setLock(NULL);
s_logMutex = NULL;
throw;
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
return 16;
}
catch (XThread&) {
// terminated
return 1;
}
return 0;
}
static int restartMain()
{
return realMain(NULL);
}
// invoke realMain and wait for it. if s_restartable then keep
// restarting realMain until it returns a terminate code.
static int restartableMain()
{
if (s_restartable) {
CPlatform platform;
return platform.restart(restartMain, 16);
}
else {
return realMain(NULL);
}
}
@ -103,11 +161,9 @@ void realMain()
// command line parsing
//
static void bye()
{
log((CLOG_PRINT "Try `%s --help' for more information.", pname));
exit(1);
}
#define BYE "\nTry `%s --help' for more information."
static void (*bye)(int) = &exit;
static void version()
{
@ -125,12 +181,18 @@ static void version()
static void help()
{
CPlatform platform;
log((CLOG_PRINT
"Usage: %s"
" [--config <pathname>]"
" [--debug <level>]"
" [--daemon|--no-daemon]"
" [--"DAEMON"|--no-"DAEMON"]"
" [--restart|--no-restart]\n"
"or\n"
" --install\n"
" --uninstall\n"
"\n"
"Start the synergy mouse/keyboard sharing server.\n"
"\n"
" -c, --config <pathname> use the named configuration file instead\n"
@ -138,58 +200,38 @@ static void help()
" -d, --debug <level> filter out log messages with priorty below level.\n"
" level may be: FATAL, ERROR, WARNING, NOTE, INFO,\n"
" DEBUG, DEBUG1, DEBUG2.\n"
" -f, --no-daemon run the server in the foreground.\n"
" --daemon run the server as a daemon.\n"
" -f, --no-"DAEMON" run the server in the foreground.\n"
"* --"DAEMON" run the server as a "DAEMON".\n"
" -1, --no-restart do not try to restart the server if it fails for\n"
" some reason.\n"
" --restart restart the server automatically if it fails.\n"
"* --restart restart the server automatically if it fails.\n"
" --install install server as a "DAEMON".\n"
" --uninstall uninstall server "DAEMON".\n"
" -h, --help display this help and exit.\n"
" --version display version information and exit.\n"
"\n"
"By default, the server is a restartable daemon. If no configuration file\n"
"pathname is provided then the first of the following to load sets the\n"
"configuration:\n"
" " CONFIG_USER_DIR CONFIG_NAME "\n"
" " CONFIG_SYS_DIR CONFIG_NAME "\n"
"* marks defaults.\n"
"\n"
"If no configuration file pathname is provided then the first of the\n"
"following to load sets the configuration:\n"
" %s\n"
" %s\n"
"If no configuration file can be loaded then the configuration uses its\n"
"defaults with just the server screen.\n"
"\n"
"Where log messages go depends on the platform and whether or not the\n"
"server is running as a daemon.",
pname));
}
static bool loadConfig(const char* pathname, bool require)
{
assert(pathname != NULL);
try {
// load configuration
log((CLOG_DEBUG "opening configuration \"%s\"", pathname));
std::ifstream configStream(pathname);
if (!configStream) {
throw XConfigRead("cannot open configuration");
}
configStream >> s_config;
log((CLOG_DEBUG "configuration read successfully"));
return true;
}
catch (XConfigRead& e) {
if (require) {
log((CLOG_PRINT "%s: cannot read configuration '%s'",
pname, pathname));
exit(1);
}
else {
log((CLOG_DEBUG "cannot read configuration \"%s\"", pathname));
}
}
return false;
"server is running as a "DAEMON".",
pname,
platform.addPathComponent(
platform.getUserDirectory(),
CONFIG_NAME).c_str(),
platform.addPathComponent(
platform.getSystemDirectory(),
CONFIG_NAME).c_str()));
}
static bool isArg(int argi,
int argc, char** argv,
int argc, const char** argv,
const char* name1,
const char* name2,
int minRequiredParameters = 0)
@ -198,9 +240,9 @@ static bool isArg(int argi,
(name2 != NULL && strcmp(argv[argi], name2) == 0)) {
// match. check args left.
if (argi + minRequiredParameters >= argc) {
log((CLOG_PRINT "%s: missing arguments for `%s'",
pname, argv[argi]));
bye();
log((CLOG_PRINT "%s: missing arguments for `%s'" BYE,
pname, argv[argi], pname));
bye(2);
}
return true;
}
@ -209,7 +251,7 @@ static bool isArg(int argi,
return false;
}
static void parse(int argc, char** argv)
static void parse(int argc, const char** argv)
{
assert(pname != NULL);
assert(argv != NULL);
@ -228,12 +270,12 @@ static void parse(int argc, char** argv)
s_configFile = argv[++i];
}
else if (isArg(i, argc, argv, "-f", "--no-daemon")) {
else if (isArg(i, argc, argv, "-f", "--no-"DAEMON)) {
// not a daemon
s_daemon = false;
}
else if (isArg(i, argc, argv, NULL, "--daemon")) {
else if (isArg(i, argc, argv, NULL, "--"DAEMON)) {
// daemonize
s_daemon = true;
}
@ -250,12 +292,42 @@ static void parse(int argc, char** argv)
else if (isArg(i, argc, argv, "-h", "--help")) {
help();
exit(1);
bye(0);
}
else if (isArg(i, argc, argv, NULL, "--version")) {
version();
exit(1);
bye(0);
}
else if (isArg(i, argc, argv, NULL, "--install")) {
#if !defined(CONFIG_PLATFORM_WIN32)
log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE,
pname, argv[i], pname));
bye(2);
#endif
s_install = true;
if (s_uninstall) {
log((CLOG_PRINT "%s: `--install' and `--uninstall'"
" are mutually exclusive" BYE,
pname, argv[i], pname));
bye(2);
}
}
else if (isArg(i, argc, argv, NULL, "--uninstall")) {
#if !defined(CONFIG_PLATFORM_WIN32)
log((CLOG_PRINT "%s: `%s' not permitted on this platform" BYE,
pname, argv[i], pname));
bye(2);
#endif
s_uninstall = true;
if (s_install) {
log((CLOG_PRINT "%s: `--install' and `--uninstall'"
" are mutually exclusive" BYE,
pname, argv[i], pname));
bye(2);
}
}
else if (isArg(i, argc, argv, "--", NULL)) {
@ -265,8 +337,9 @@ static void parse(int argc, char** argv)
}
else if (argv[i][0] == '-') {
log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i]));
bye();
log((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
else {
@ -277,101 +350,76 @@ static void parse(int argc, char** argv)
// no non-option arguments are allowed
if (i != argc) {
log((CLOG_PRINT "%s: unrecognized option `%s'", pname, argv[i]));
bye();
log((CLOG_PRINT "%s: unrecognized option `%s'" BYE,
pname, argv[i], pname));
bye(2);
}
// increase default filter level for daemon. the user must
// explicitly request another level for a daemon.
if (s_daemon && s_logFilter == NULL) {
#if defined(CONFIG_PLATFORM_WIN32)
if (CPlatform::isWindows95Family()) {
// windows 95 has no place for logging so avoid showing
// the log console window.
s_logFilter = "FATAL";
}
else
#endif
{
s_logFilter = "NOTE";
}
}
// set log filter
if (!CLog::setFilter(s_logFilter)) {
log((CLOG_PRINT "%s: unrecognized log level `%s'", pname, s_logFilter));
bye();
log((CLOG_PRINT "%s: unrecognized log level `%s'" BYE,
pname, s_logFilter, pname));
bye(2);
}
}
// load the config file, if any
static bool loadConfig(const char* pathname, bool require)
{
assert(pathname != NULL);
try {
// load configuration
log((CLOG_DEBUG "opening configuration \"%s\"", pathname));
std::ifstream configStream(pathname);
if (!configStream) {
throw XConfigRead("cannot open configuration");
}
configStream >> s_config;
log((CLOG_DEBUG "configuration read successfully"));
return true;
}
catch (XConfigRead&) {
if (require) {
log((CLOG_PRINT "%s: cannot read configuration '%s'",
pname, pathname));
bye(3);
}
else {
log((CLOG_DEBUG "cannot read configuration \"%s\"", pathname));
}
}
return false;
}
static void loadConfig()
{
// load the config file, if specified
if (s_configFile != NULL) {
// require the user specified file to load correctly
loadConfig(s_configFile, true);
}
}
//
// platform dependent entry points
//
#if defined(CONFIG_PLATFORM_WIN32)
#include "CMSWindowsScreen.h"
#include <string.h>
int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
{
CPlatform platform;
// save instance
CMSWindowsScreen::init(instance);
// get program name
pname = platform.getBasename(argv[0]);
// FIXME -- direct CLog to MessageBox
parse(__argc, __argv);
// FIXME -- undirect CLog from MessageBox
// FIXME -- if daemon then use win32 event log (however that's done),
// otherwise do what? want to use console window for debugging but
// not otherwise.
// load the configuration file if we haven't already
if (s_configFile == NULL) {
// FIXME
}
// FIXME
if (__argc != 1) {
CString msg = "no arguments allowed. exiting.";
MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR);
return 1;
}
try {
realMain();
return 0;
}
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
CString msg = "failed: ";
msg += e.what();
MessageBox(NULL, msg.c_str(), "error", MB_OK | MB_ICONERROR);
return 1;
}
catch (XThread&) {
// terminated
return 1;
}
}
#elif defined(CONFIG_PLATFORM_UNIX)
#include <unistd.h> // fork()
#include <sys/types.h> // wait()
#include <sys/wait.h> // wait()
int main(int argc, char** argv)
{
CPlatform platform;
// get program name
pname = platform.getBasename(argv[0]);
// parse command line
parse(argc, argv);
// load the configuration file if we haven't already
if (s_configFile == NULL) {
// load the default configuration if no explicit file given
else {
// get the user's home directory. use the effective user id
// so a user can't get a setuid root program to load his file.
CPlatform platform;
bool loaded = false;
CString path = platform.getUserDirectory();
if (!path.empty()) {
@ -390,62 +438,236 @@ int main(int argc, char** argv)
}
}
}
}
//
// platform dependent entry points
//
#if defined(CONFIG_PLATFORM_WIN32)
#include "CMSWindowsScreen.h"
static bool logMessageBox(int priority, const char* msg)
{
if (priority <= CLog::kFATAL) {
MessageBox(NULL, msg, pname, MB_OK | MB_ICONWARNING);
return true;
}
else {
return false;
}
}
static void byeThrow(int x)
{
throw CWin32Platform::CDaemonFailed(x);
}
static void daemonStop(void)
{
s_server->quit();
}
static int daemonStartup(IPlatform* iplatform,
int argc, const char** argv)
{
// get platform pointer
CWin32Platform* platform = static_cast<CWin32Platform*>(iplatform);
// catch errors that would normally exit
bye = &byeThrow;
// parse command line
s_install = false;
s_uninstall = false;
parse(argc, argv);
if (s_install || s_uninstall) {
// not allowed to install/uninstall from service
throw CWin32Platform::CDaemonFailed(1);
}
// load configuration
loadConfig();
// run as a service
return platform->runDaemon(realMain, daemonStop);
}
static int daemonStartup95(IPlatform*, int, const char**)
{
return realMain(NULL);
}
static bool logDiscard(int, const char*)
{
return true;
}
static bool s_die = false;
static void checkParse(int e)
{
// anything over 1 means invalid args. 1 means missing args.
// 0 means graceful exit. we plan to exit for anything but
// 1 (missing args); the service control manager may supply
// the missing arguments so we don't exit in that case.
s_die = (e != 1);
throw s_die;
}
int WINAPI WinMain(HINSTANCE instance, HINSTANCE, LPSTR, int)
{
CPlatform platform;
// save instance
CMSWindowsScreen::init(instance);
// get program name
pname = platform.getBasename(__argv[0]);
// parse command line without reporting errors but recording if
// the app would've exited. this is too avoid showing a dialog
// box if we're being started as a service because we shouldn't
// take to long to startup as a service. this mostly works but
// will choke if the service control manager passes --install
// or --uninstall (but that's unlikely).
CLog::setOutputter(&logDiscard);
bye = &checkParse;
try {
parse(__argc, const_cast<const char**>(__argv));
}
catch (...) {
// ignore
}
// if we're not starting as an NT service then reparse the command
// line normally.
if (s_die || !s_daemon || s_install || s_uninstall ||
CWin32Platform::isWindows95Family()) {
// send PRINT and FATAL output to a message box
CLog::setOutputter(&logMessageBox);
// exit on bye
bye = &exit;
// reparse
parse(__argc, const_cast<const char**>(__argv));
}
// if starting as a daemon then we ignore the startup command line
// here. we'll parse the command line passed in when the service
// control manager calls us back.
else {
// do nothing
}
// install/uninstall
if (s_install) {
// get the full path to this program
TCHAR path[MAX_PATH];
if (GetModuleFileName(NULL, path,
sizeof(path) / sizeof(path[0])) == 0) {
log((CLOG_CRIT "cannot determine absolute path to program"));
return 16;
}
// construct the command line to start the service with
CString commandLine;
commandLine += "--"DAEMON;
if (s_restartable) {
commandLine += " --restart";
}
else {
commandLine += " --no-restart";
}
if (s_logFilter != NULL) {
commandLine += " --debug ";
commandLine += s_logFilter;
}
if (s_configFile != NULL) {
commandLine += " --config \"";
commandLine += s_configFile;
commandLine += "\"";
}
// install
if (!platform.installDaemon(DAEMON_NAME,
"Shares this system's mouse and keyboard with others.",
path, commandLine.c_str())) {
log((CLOG_CRIT "failed to install service"));
return 16;
}
log((CLOG_PRINT "installed successfully"));
return 0;
}
else if (s_uninstall) {
if (!platform.uninstallDaemon(DAEMON_NAME)) {
log((CLOG_CRIT "failed to uninstall service"));
return 16;
}
log((CLOG_PRINT "uninstalled successfully"));
return 0;
}
// load configuration
loadConfig();
// daemonize if requested
int result;
if (s_daemon) {
if (!platform.daemonize("synergyd")) {
if (CWin32Platform::isWindows95Family()) {
result = platform.daemonize(DAEMON_NAME, &daemonStartup95);
}
else {
result = platform.daemonize(DAEMON_NAME, &daemonStartup);
}
if (result == -1) {
log((CLOG_CRIT "failed to start as a service"));
return 16;
}
}
else {
result = restartableMain();
}
return result;
}
#elif defined(CONFIG_PLATFORM_UNIX)
static int daemonStartup(IPlatform*, int, const char**)
{
return restartableMain();
}
int main(int argc, char** argv)
{
CPlatform platform;
// get program name
pname = platform.getBasename(argv[0]);
// parse command line
parse(argc, const_cast<const char**>(argv));
// load configuration
loadConfig();
// daemonize if requested
int result;
if (s_daemon) {
result = platform.daemonize(DAEMON_NAME, &daemonStartup);
if (result == -1) {
log((CLOG_CRIT "failed to daemonize"));
return 16;
}
}
// run the server. if running as a daemon then run it in a child
// process and restart it as necessary. we have to do this in case
// the X server restarts because our process cannot recover from
// that.
for (;;) {
// don't fork if not restartable
switch (s_restartable ? fork() : 0) {
default: {
// parent process. wait for child to exit.
int status;
if (wait(&status) == -1) {
// wait failed. this is unexpected so bail.
log((CLOG_CRIT "wait() failed"));
return 16;
}
// what happened? if the child exited normally with a
// status less than 16 then the child was deliberately
// terminated so we also terminate. otherwise, we
// loop.
if (WIFEXITED(status) && WEXITSTATUS(status) < 16) {
return 0;
}
break;
}
case -1:
// fork() failed. log the error and proceed as a child
log((CLOG_WARN "fork() failed; cannot automatically restart on error"));
// fall through
case 0:
// child process
try {
realMain();
return 0;
}
catch (XBase& e) {
log((CLOG_CRIT "failed: %s", e.what()));
return 16;
}
catch (XThread&) {
// terminated
return 1;
}
}
else {
result = restartableMain();
}
return result;
}
#else

View File

@ -23,8 +23,8 @@ CFG=server - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
MTL=midl.exe
RSC=rc.exe
@ -40,6 +40,7 @@ RSC=rc.exe
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "../Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /MT /W4 /GX /O2 /I "..\base" /I "..\mt" /I "..\io" /I "..\http" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c
@ -53,7 +54,7 @@ BSC32=bscmake.exe
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /out:"../Release/synergyd.exe"
!ELSEIF "$(CFG)" == "server - Win32 Debug"
@ -66,6 +67,7 @@ LINK32=link.exe
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "../Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /MTd /W4 /Gm /GX /ZI /Od /I "..\base" /I "..\mt" /I "..\io" /I "..\http" /I "..\net" /I "..\synergy" /I "..\platform" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c
@ -79,7 +81,7 @@ BSC32=bscmake.exe
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"../Debug/synergyd.exe" /pdbtype:sept
!ENDIF

View File

@ -23,8 +23,8 @@ CFG=synrgyhk - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
MTL=midl.exe
RSC=rc.exe

View File

@ -3,6 +3,7 @@
#include "IInterface.h"
#include "BasicTypes.h"
#include "KeyTypes.h"
#include "ClipboardTypes.h"
class CServer;

View File

@ -23,8 +23,8 @@ CFG=synergy - Win32 Debug
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName "millpond"
# PROP Scc_LocalPath "."
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe