Reorganized source tree. Moved client.cpp into cmd/synergy as

synergy.cpp and server.cpp into cmd/synergyd as synergyd.cpp.
Moved and renamed related files.  Moved remaining source files
into lib/....  Modified and added makefiles as appropriate.
Result is that library files are under lib with each library
in its own directory and program files are under cmd with each
command in its own directory.
This commit is contained in:
crs
2002-07-30 16:52:46 +00:00
parent 9792d35a6b
commit fee4095624
201 changed files with 367 additions and 375 deletions

16
lib/Makefile.am Normal file
View File

@@ -0,0 +1,16 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ..
EXTRA_DIST =
SUBDIRS = \
base \
mt \
io \
http \
net \
synergy \
platform \
client \
server \
$(NULL)

73
lib/base/BasicTypes.h Normal file
View File

@@ -0,0 +1,73 @@
#ifndef BASICTYPES_H
#define BASICTYPES_H
#include "common.h"
//
// pick types of particular sizes
//
#if !defined(TYPE_OF_SIZE_1)
# if SIZEOF_CHAR == 1
# define TYPE_OF_SIZE_1 char
# endif
#endif
#if !defined(TYPE_OF_SIZE_2)
# if SIZEOF_INT == 2
# define TYPE_OF_SIZE_2 int
# else
# define TYPE_OF_SIZE_2 short
# endif
#endif
#if !defined(TYPE_OF_SIZE_4)
# if SIZEOF_INT == 4
# define TYPE_OF_SIZE_4 int
# else
# define TYPE_OF_SIZE_4 long
# endif
#endif
//
// verify existence of required types
//
#if !defined(TYPE_OF_SIZE_1)
# error No 1 byte integer type
#endif
#if !defined(TYPE_OF_SIZE_2)
# error No 2 byte integer type
#endif
#if !defined(TYPE_OF_SIZE_4)
# error No 4 byte integer type
#endif
//
// make typedefs
//
// except for SInt8 and UInt8 these types are only guaranteed to be
// at least as big as indicated (in bits). that is, they may be
// larger than indicated.
//
typedef signed TYPE_OF_SIZE_1 SInt8;
typedef signed TYPE_OF_SIZE_2 SInt16;
typedef signed TYPE_OF_SIZE_4 SInt32;
typedef unsigned TYPE_OF_SIZE_1 UInt8;
typedef unsigned TYPE_OF_SIZE_2 UInt16;
typedef unsigned TYPE_OF_SIZE_4 UInt32;
//
// clean up
//
#undef TYPE_OF_SIZE_1
#undef TYPE_OF_SIZE_2
#undef TYPE_OF_SIZE_4
#endif

25
lib/base/CFunctionJob.cpp Normal file
View File

@@ -0,0 +1,25 @@
#include "CFunctionJob.h"
//
// CFunctionJob
//
CFunctionJob::CFunctionJob(void (*func)(void*), void* arg) :
m_func(func),
m_arg(arg)
{
// do nothing
}
CFunctionJob::~CFunctionJob()
{
// do nothing
}
void
CFunctionJob::run()
{
if (m_func != NULL) {
m_func(m_arg);
}
}

24
lib/base/CFunctionJob.h Normal file
View File

@@ -0,0 +1,24 @@
#ifndef CFUNCTIONJOB_H
#define CFUNCTIONJOB_H
#include "IJob.h"
//! Use a function as a job
/*!
A job class that invokes a function.
*/
class CFunctionJob : public IJob {
public:
//! run() invokes \c func(arg)
CFunctionJob(void (*func)(void*), void* arg = NULL);
virtual ~CFunctionJob();
// IJob overrides
virtual void run();
private:
void (*m_func)(void*);
void* m_arg;
};
#endif

314
lib/base/CLog.cpp Normal file
View File

@@ -0,0 +1,314 @@
#include "CLog.h"
#include "CString.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if WINDOWS_LIKE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define vsnprintf _vsnprintf
#endif
// names of priorities
static const char* g_priority[] = {
"FATAL",
"ERROR",
"WARNING",
"NOTE",
"INFO",
"DEBUG",
"DEBUG1",
"DEBUG2"
};
// number of priorities
static const int g_numPriority = (int)(sizeof(g_priority) /
sizeof(g_priority[0]));
// the default priority
#if defined(NDEBUG)
static const int g_defaultMaxPriority = 4;
#else
static const int g_defaultMaxPriority = 5;
#endif
// length of longest string in g_priority
static const int g_maxPriorityLength = 7;
// length of suffix string (": ")
static const int g_prioritySuffixLength = 2;
// amount of padded required to fill in the priority prefix
static const int g_priorityPad = g_maxPriorityLength +
g_prioritySuffixLength;
// platform newline sequence
#if WINDOWS_LIKE
static const char* g_newline = "\r\n";
#else
static const char* g_newline = "\n";
#endif
// minimum length of a newline sequence
static const int g_newlineLength = 2;
//
// CLog
//
CLog::Outputter CLog::s_outputter = NULL;
CLog::Lock CLog::s_lock = &CLog::dummyLock;
int CLog::s_maxPriority = -1;
void
CLog::print(const char* fmt, ...)
{
// check if fmt begins with a priority argument
int priority = 4;
if (fmt[0] == '%' && fmt[1] == 'z') {
priority = fmt[2] - '\060';
fmt += 3;
}
// done if below priority threshold
if (priority > getMaxPriority()) {
return;
}
// compute prefix padding length
int pad = g_priorityPad;
// print to buffer
char stack[1024];
va_list args;
va_start(args, fmt);
char* buffer = CStringUtil::vsprint(stack,
sizeof(stack) / sizeof(stack[0]),
pad, g_newlineLength, fmt, args);
va_end(args);
// output buffer
output(priority, buffer);
// clean up
if (buffer != stack)
delete[] buffer;
}
void
CLog::printt(const char* file, int line, const char* fmt, ...)
{
// check if fmt begins with a priority argument
int priority = 4;
if (fmt[0] == '%' && fmt[1] == 'z') {
priority = fmt[2] - '\060';
fmt += 3;
}
// done if below priority threshold
if (priority > getMaxPriority()) {
return;
}
// compute prefix padding length
char stack[1024];
sprintf(stack, "%d", line);
int pad = strlen(file) + 1 /* comma */ +
strlen(stack) + 1 /* colon */ + 1 /* space */ +
g_priorityPad;
// print to buffer, leaving space for a newline at the end
va_list args;
va_start(args, fmt);
char* buffer = CStringUtil::vsprint(stack,
sizeof(stack) / sizeof(stack[0]),
pad, g_newlineLength, fmt, args);
va_end(args);
// print the prefix to the buffer. leave space for priority label.
sprintf(buffer + g_priorityPad, "%s,%d:", file, line);
buffer[pad - 1] = ' ';
// discard file and line if priority < 0
char* message = buffer;
if (priority < 0) {
message += pad - g_priorityPad;
}
// output buffer
output(priority, message);
// clean up
if (buffer != stack)
delete[] buffer;
}
void
CLog::setOutputter(Outputter outputter)
{
CHoldLock lock(s_lock);
s_outputter = outputter;
}
CLog::Outputter
CLog::getOutputter()
{
CHoldLock lock(s_lock);
return s_outputter;
}
void
CLog::setLock(Lock newLock)
{
CHoldLock lock(s_lock);
s_lock = (newLock == NULL) ? dummyLock : newLock;
}
CLog::Lock
CLog::getLock()
{
CHoldLock lock(s_lock);
return (s_lock == dummyLock) ? NULL : s_lock;
}
bool
CLog::setFilter(const char* maxPriority)
{
if (maxPriority != NULL) {
for (int i = 0; i < g_numPriority; ++i) {
if (strcmp(maxPriority, g_priority[i]) == 0) {
setFilter(i);
return true;
}
}
return false;
}
return true;
}
void
CLog::setFilter(int maxPriority)
{
CHoldLock lock(s_lock);
s_maxPriority = maxPriority;
}
int
CLog::getFilter()
{
CHoldLock lock(s_lock);
return getMaxPriority();
}
void
CLog::dummyLock(bool)
{
// do nothing
}
int
CLog::getMaxPriority()
{
CHoldLock lock(s_lock);
if (s_maxPriority == -1) {
s_maxPriority = g_defaultMaxPriority;
setFilter(getenv("SYN_LOG_PRI"));
}
return s_maxPriority;
}
void
CLog::output(int priority, char* msg)
{
assert(priority >= -1 && priority < g_numPriority);
assert(msg != NULL);
// insert priority label
int n = -g_prioritySuffixLength;
if (priority >= 0) {
n = strlen(g_priority[priority]);
strcpy(msg + g_maxPriorityLength - n, g_priority[priority]);
msg[g_maxPriorityLength + 0] = ':';
msg[g_maxPriorityLength + 1] = ' ';
msg[g_maxPriorityLength + 1] = ' ';
}
// put a newline at the end
strcat(msg + g_priorityPad, g_newline);
// print it
CHoldLock lock(s_lock);
if (s_outputter == NULL ||
!s_outputter(priority, msg + g_maxPriorityLength - n)) {
#if WINDOWS_LIKE
openConsole();
#endif
fprintf(stderr, "%s", msg + g_maxPriorityLength - n);
}
}
#if WINDOWS_LIKE
static DWORD s_thread = 0;
static
BOOL WINAPI
CLogSignalHandler(DWORD)
{
// terminate cleanly and skip remaining handlers
PostThreadMessage(s_thread, WM_QUIT, 0, 0);
return TRUE;
}
void
CLog::openConsole()
{
static bool s_hasConsole = false;
// ignore if already created
if (s_hasConsole)
return;
// remember the current thread. when we get a ctrl+break or the
// console is closed we'll post WM_QUIT to this thread to shutdown
// cleanly.
// note -- win95/98/me are broken and will not receive a signal
// when the console is closed nor during logoff or shutdown,
// see microsoft articles Q130717 and Q134284. we could work
// around this in a painful way using hooks and hidden windows
// (as apache does) but it's not worth it. the app will still
// quit, just not cleanly. users in-the-know can use ctrl+c.
s_thread = GetCurrentThreadId();
// open a console
if (!AllocConsole())
return;
// get the handle for error output
HANDLE herr = GetStdHandle(STD_ERROR_HANDLE);
// prep console. windows 95 and its ilk have braindead
// consoles that can't even resize independently of the
// buffer size. use a 25 line buffer for those systems.
OSVERSIONINFO osInfo;
COORD size = { 80, 1000 };
osInfo.dwOSVersionInfoSize = sizeof(osInfo);
if (GetVersionEx(&osInfo) &&
osInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS)
size.Y = 25;
SetConsoleScreenBufferSize(herr, size);
SetConsoleTextAttribute(herr,
FOREGROUND_RED |
FOREGROUND_GREEN |
FOREGROUND_BLUE);
SetConsoleCtrlHandler(CLogSignalHandler, TRUE);
// reopen stderr to point at console
freopen("con", "w", stderr);
s_hasConsole = true;
}
#endif

201
lib/base/CLog.h Normal file
View File

@@ -0,0 +1,201 @@
#ifndef CLOG_H
#define CLOG_H
#include "common.h"
#include <stdarg.h>
//! Logging facility
/*!
The logging class; all console output should go through this class.
It supports multithread safe operation, several message priority levels,
filtering by priority, and output redirection. The macros log() and
clog() provide convenient access.
*/
class CLog {
public:
//! Log levels
/*!
The logging priority levels in order of highest to lowest priority.
*/
enum ELevel {
kFATAL, //!< For fatal errors
kERROR, //!< For serious errors
kWARNING, //!< For minor errors and warnings
kNOTE, //!< For messages about notable events
kINFO, //!< For informational messages
kDEBUG, //!< For important debugging messages
kDEBUG1, //!< For more detailed debugging messages
kDEBUG2 //!< For even more detailed debugging messages
};
//! Outputter function.
/*!
Type of outputter function. The outputter should write \c message,
which has the given \c priority, to a log and return true. Or it can
return false to let CLog use the default outputter.
*/
typedef bool (*Outputter)(int priority, const char* message);
//! Locking function
/*!
Type of lock/unlock function. If \c lock is true then block other
threads that try to lock until this thread unlocks. If \c lock is
false then unlock and allow another (waiting) thread to lock.
*/
typedef void (*Lock)(bool lock);
//! @name manipulators
//@{
//! Set the function used to write the log
/*!
Sets the function used to write to the log. The outputter function
is called with the formatted string to write and the priority level.
CLog will have already filtered messages below the current filter
priority. A NULL outputter means to use the default which is to print
to stderr. Note that the outputter should not call CLog methods but,
if it does, the current lock function must permit recursive locks.
*/
static void setOutputter(Outputter);
//! Set the lock/unlock function
/*!
Set the lock/unlock function. Use setLock(NULL) to remove the
locking function. There is no default lock function; do not call
CLog from multiple threads unless a working lock function has been
installed.
*/
static void setLock(Lock);
//! Set the minimum priority filter.
/*!
Set the filter. Messages below this priority are discarded.
The default priority is 4 (INFO) (unless built without NDEBUG
in which case it's 5 (DEBUG)). The default can be overridden
by setting the SYN_LOG_PRI env var to "FATAL", "ERROR", etc.
setFilter(const char*) returns true if the priority \c name was
recognized; if \c name is NULL then it simply returns true.
*/
static bool setFilter(const char* name);
static void setFilter(int);
//@}
//! @name accessors
//@{
//! Print a log message
/*!
Print a log message using the printf-like \c format and arguments.
*/
static void print(const char* format, ...);
//! Print a log message
/*!
Print a log message using the printf-like \c format and arguments
preceded by the filename and line number.
*/
static void printt(const char* file, int line,
const char* format, ...);
//! Get the function used to write the log
static Outputter getOutputter();
//! Get the lock/unlock function
/*!
Get the lock/unlock function. Note that the lock function is
used when retrieving the lock function.
*/
static Lock getLock();
//! Get the minimum priority level.
static int getFilter();
//@}
private:
class CHoldLock {
public:
CHoldLock(Lock lock) : m_lock(lock) { m_lock(true); }
~CHoldLock() { m_lock(false); }
private:
Lock m_lock;
};
static void dummyLock(bool);
static int getMaxPriority();
static void output(int priority, char* msg);
#if WINDOWS_LIKE
static void openConsole();
#endif
private:
static Outputter s_outputter;
static Lock s_lock;
static int s_maxPriority;
};
/*!
\def log(arg)
Write to the log. Because macros cannot accept variable arguments, this
should be invoked like so:
\code
log((CLOG_XXX "%d and %d are %s", x, y, x == y ? "equal" : "not equal"));
\endcode
In particular, notice the double open and close parentheses. Also note
that there is no comma after the \c CLOG_XXX. The \c XXX should be
replaced by one of enumerants in \c CLog::ELevel without the leading
\c k. For example, \c CLOG_INFO. The special \c CLOG_PRINT level will
not be filtered and is never prefixed by the filename and line number.
If \c NOLOGGING is defined during the build then this macro expands to
nothing. If \c NDEBUG is defined during the build then it expands to a
call to CLog::print. Otherwise it expands to a call to CLog::printt,
which includes the filename and line number.
*/
/*!
\def logc(expr, arg)
Write to the log if and only if expr is true. Because macros cannot accept
variable arguments, this should be invoked like so:
\code
clog(x == y, (CLOG_XXX "%d and %d are equal", x, y));
\endcode
In particular, notice the parentheses around everything after the boolean
expression. Also note that there is no comma after the \c CLOG_XXX.
The \c XXX should be replaced by one of enumerants in \c CLog::ELevel
without the leading \c k. For example, \c CLOG_INFO. The special
\c CLOG_PRINT level will not be filtered and is never prefixed by the
filename and line number.
If \c NOLOGGING is defined during the build then this macro expands to
nothing. If \c NDEBUG is defined during the build then it expands to a
call to CLog::print. Otherwise it expands to a call to CLog::printt,
which includes the filename and line number.
*/
#if defined(NOLOGGING)
#define log(_a1)
#define logc(_a1, _a2)
#define CLOG_TRACE
#elif defined(NDEBUG)
#define log(_a1) CLog::print _a1
#define logc(_a1, _a2) if (_a1) CLog::print _a2
#define CLOG_TRACE
#else
#define log(_a1) CLog::printt _a1
#define logc(_a1, _a2) if (_a1) CLog::printt _a2
#define CLOG_TRACE __FILE__, __LINE__,
#endif
#define CLOG_PRINT CLOG_TRACE "%z\057"
#define CLOG_CRIT CLOG_TRACE "%z\060"
#define CLOG_ERR CLOG_TRACE "%z\061"
#define CLOG_WARN CLOG_TRACE "%z\062"
#define CLOG_NOTE CLOG_TRACE "%z\063"
#define CLOG_INFO CLOG_TRACE "%z\064"
#define CLOG_DEBUG CLOG_TRACE "%z\065"
#define CLOG_DEBUG1 CLOG_TRACE "%z\066"
#define CLOG_DEBUG2 CLOG_TRACE "%z\067"
#endif

211
lib/base/CStopwatch.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include "CStopwatch.h"
//
// CStopwatch
//
CStopwatch::CStopwatch(bool triggered) :
m_mark(0.0),
m_triggered(triggered),
m_stopped(triggered)
{
if (!triggered) {
m_mark = getClock();
}
}
CStopwatch::~CStopwatch()
{
// do nothing
}
double
CStopwatch::reset()
{
if (m_stopped) {
const double dt = m_mark;
m_mark = 0.0;
return dt;
}
else {
const double t = getClock();
const double dt = t - m_mark;
m_mark = t;
return dt;
}
}
void
CStopwatch::stop()
{
if (m_stopped) {
return;
}
// save the elapsed time
m_mark = getClock() - m_mark;
m_stopped = true;
}
void
CStopwatch::start()
{
m_triggered = false;
if (!m_stopped) {
return;
}
// set the mark such that it reports the time elapsed at stop()
m_mark = getClock() - m_mark;
m_stopped = false;
}
void
CStopwatch::setTrigger()
{
stop();
m_triggered = true;
}
double
CStopwatch::getTime()
{
if (m_triggered) {
const double dt = m_mark;
start();
return dt;
}
else if (m_stopped) {
return m_mark;
}
else {
return getClock() - m_mark;
}
}
CStopwatch::operator double()
{
return getTime();
}
bool
CStopwatch::isStopped() const
{
return m_stopped;
}
double
CStopwatch::getTime() const
{
if (m_stopped) {
return m_mark;
}
else {
return getClock() - m_mark;
}
}
CStopwatch::operator double() const
{
return getTime();
}
#if WINDOWS_LIKE
// avoid getting a lot a crap from mmsystem.h that we don't need
#define MMNODRV // Installable driver support
#define MMNOSOUND // Sound support
#define MMNOWAVE // Waveform support
#define MMNOMIDI // MIDI support
#define MMNOAUX // Auxiliary audio support
#define MMNOMIXER // Mixer support
#define MMNOJOY // Joystick support
#define MMNOMCI // MCI support
#define MMNOMMIO // Multimedia file I/O support
#define MMNOMMSYSTEM // General MMSYSTEM functions
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <mmsystem.h>
typedef WINMMAPI DWORD (WINAPI *PTimeGetTime)(void);
static double s_freq = 0.0;
static HINSTANCE s_mmInstance = NULL;
static PTimeGetTime s_tgt = NULL;
//
// initialize local variables
//
class CStopwatchInit {
public:
CStopwatchInit();
~CStopwatchInit();
};
static CStopwatchInit s_init;
CStopwatchInit::CStopwatchInit()
{
LARGE_INTEGER freq;
if (QueryPerformanceFrequency(&freq) && freq.QuadPart != 0) {
s_freq = 1.0 / static_cast<double>(freq.QuadPart);
}
else {
// load winmm.dll and get timeGetTime
s_mmInstance = LoadLibrary("winmm");
if (s_mmInstance) {
s_tgt = (PTimeGetTime)GetProcAddress(s_mmInstance, "timeGetTime");
}
}
}
CStopwatchInit::~CStopwatchInit()
{
if (s_mmInstance) {
FreeLibrary(reinterpret_cast<HMODULE>(s_mmInstance));
}
}
double
CStopwatch::getClock() const
{
// get time. we try three ways, in order of descending precision
if (s_freq != 0.0) {
LARGE_INTEGER c;
QueryPerformanceCounter(&c);
return s_freq * static_cast<double>(c.QuadPart);
}
else if (s_tgt) {
return 0.001 * static_cast<double>(s_tgt());
}
else {
return 0.001 * static_cast<double>(GetTickCount());
}
}
#elif UNIX_LIKE
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
#if HAVE_UNISTD_H
# include <unistd.h>
#endif
double
CStopwatch::getClock() const
{
struct timeval t;
gettimeofday(&t, NULL);
return (double)t.tv_sec + 1.0e-6 * (double)t.tv_usec;
}
#endif // UNIX_LIKE

94
lib/base/CStopwatch.h Normal file
View File

@@ -0,0 +1,94 @@
#ifndef CSTOPWATCH_H
#define CSTOPWATCH_H
#include "common.h"
//! A timer class
/*!
This class measures time intervals. All time interval measurement
should use this class.
*/
class CStopwatch {
public:
/*!
The default constructor does an implicit reset() or setTrigger().
If triggered == false then the clock starts ticking.
*/
CStopwatch(bool triggered = false);
~CStopwatch();
//! @name manipulators
//@{
//! Reset the timer to zero
/*!
Set the start time to the current time, returning the time since
the last reset. This does not remove the trigger if it's set nor
does it start a stopped clock. If the clock is stopped then
subsequent reset()'s will return 0.
*/
double reset();
//! Stop the timer
/*!
Stop the stopwatch. The time interval while stopped is not
counted by the stopwatch. stop() does not remove the trigger.
Has no effect if already stopped.
*/
void stop();
//! Start the timer
/*!
Start the stopwatch. start() removes the trigger, even if the
stopwatch was already started.
*/
void start();
//! Stop the timer and set the trigger
/*!
setTrigger() stops the clock like stop() except there's an
implicit start() the next time (non-const) getTime() is called.
This is useful when you want the clock to start the first time
you check it.
*/
void setTrigger();
//! Get elapsed time
/*!
Returns the time since the last reset() (or calls reset() and
returns zero if the trigger is set).
*/
double getTime();
//! Same as getTime()
operator double();
//@}
//! @name accessors
//@{
//! Check if timer is stopped
/*!
Returns true if the stopwatch is stopped.
*/
bool isStopped() const;
// return the time since the last reset().
//! Get elapsed time
/*!
Returns the time since the last reset(). This cannot trigger the
stopwatch to start and will not clear the trigger.
*/
double getTime() const;
//! Same as getTime() const
operator double() const;
//@}
private:
double getClock() const;
private:
double m_mark;
bool m_triggered;
bool m_stopped;
};
#endif

199
lib/base/CString.cpp Normal file
View File

@@ -0,0 +1,199 @@
#include "CString.h"
#include "common.h"
#include "stdvector.h"
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#if WINDOWS_LIKE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define vsnprintf _vsnprintf
#endif
//
// CStringUtil
//
CString
CStringUtil::format(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
CString result = vformat(fmt, args);
va_end(args);
return result;
}
CString
CStringUtil::vformat(const char* fmt, va_list args)
{
// find highest indexed substitution and the locations of substitutions
std::vector<size_t> pos;
std::vector<size_t> width;
std::vector<int> index;
int maxIndex = 0;
for (const char* scan = fmt; *scan != '\0'; ++scan) {
if (*scan == '%') {
++scan;
if (*scan == '\0') {
break;
}
else if (*scan == '%') {
// literal
index.push_back(0);
pos.push_back(static_cast<int>(scan - 1 - fmt));
width.push_back(2);
}
else if (*scan == '{') {
// get argument index
char* end;
int i = static_cast<int>(strtol(scan + 1, &end, 10));
if (*end != '}') {
// invalid index -- ignore
scan = end - 1;
}
else {
index.push_back(i);
pos.push_back(static_cast<int>(scan - 1 - fmt));
width.push_back(static_cast<int>(end - scan + 2));
if (i > maxIndex) {
maxIndex = i;
}
scan = end;
}
}
else {
// improper escape -- ignore
}
}
}
// get args
std::vector<const char*> value;
std::vector<size_t> length;
value.push_back("%");
length.push_back(1);
for (int i = 0; i < maxIndex; ++i) {
const char* arg = va_arg(args, const char*);
value.push_back(arg);
length.push_back(strlen(arg));
}
// compute final length
size_t resultLength = strlen(fmt);
const int n = static_cast<int>(pos.size());
for (int i = 0; i < n; ++i) {
resultLength -= width[i];
resultLength += length[index[i]];
}
// substitute
CString result;
result.reserve(resultLength);
size_t src = 0;
for (int i = 0; i < n; ++i) {
result.append(fmt + src, pos[i] - src);
result.append(value[index[i]]);
src = pos[i] + width[i];
}
result.append(fmt + src);
return result;
}
CString
CStringUtil::print(const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
CString result = vprint(fmt, args);
va_end(args);
return result;
}
CString
CStringUtil::vprint(const char* fmt, va_list args)
{
char tmp[1024];
char* buffer = vsprint(tmp, sizeof(tmp) / sizeof(tmp[0]), 0, 0, fmt, args);
if (buffer == tmp) {
return buffer;
}
else {
CString result(buffer);
delete[] buffer;
return result;
}
}
char*
CStringUtil::vsprint(char* buffer, int len,
int prefix, int suffix, const char* fmt, va_list args)
{
assert(len > 0);
// try writing to input buffer
int n;
if (buffer != NULL && len >= prefix + suffix) {
n = vsnprintf(buffer + prefix, len - (prefix + suffix), fmt, args);
if (n >= 0 && n <= len - (prefix + suffix))
return buffer;
}
// start allocating buffers until we write the whole string
buffer = NULL;
do {
delete[] buffer;
len *= 2;
buffer = new char[len + (prefix + suffix)];
n = vsnprintf(buffer + prefix, len - (prefix + suffix), fmt, args);
} while (n < 0 || n > len - (prefix + suffix));
return buffer;
}
//
// CStringUtil::CaselessCmp
//
bool
CStringUtil::CaselessCmp::cmpEqual(
const CString::value_type& a,
const CString::value_type& b)
{
// FIXME -- use std::tolower but not in all versions of libstdc++ have it
return tolower(a) == tolower(b);
}
bool
CStringUtil::CaselessCmp::cmpLess(
const CString::value_type& a,
const CString::value_type& b)
{
// FIXME -- use std::tolower but not in all versions of libstdc++ have it
return tolower(a) < tolower(b);
}
bool
CStringUtil::CaselessCmp::less(const CString& a, const CString& b)
{
return std::lexicographical_compare(
a.begin(), a.end(),
b.begin(), b.end(),
&CStringUtil::CaselessCmp::cmpLess);
}
bool
CStringUtil::CaselessCmp::equal(const CString& a, const CString& b)
{
return !(less(a, b) || less(b, a));
}
bool
CStringUtil::CaselessCmp::operator()(const CString& a, const CString& b) const
{
return less(a, b);
}

90
lib/base/CString.h Normal file
View File

@@ -0,0 +1,90 @@
#ifndef CSTRING_H
#define CSTRING_H
#include <stdarg.h>
#include "stdpre.h"
#include <string>
#include "stdpost.h"
// use standard C++ string class for our string class
typedef std::string CString;
//! String utilities
/*!
This class provides various functions for string manipulation.
*/
class CStringUtil {
public:
//! Format positional arguments
/*!
Format a string using positional arguments. fmt has literal
characters and conversion specifications introduced by `\%':
- \c\%\% -- literal `\%'
- \c\%{n} -- positional element n, n a positive integer, {} are literal
All arguments in the variable list are const char*. Positional
elements are indexed from 1.
*/
static CString format(const char* fmt, ...);
//! Format positional arguments
/*!
Same as format() except takes va_list.
*/
static CString vformat(const char* fmt, va_list);
//! Print a string using printf-style formatting
/*!
Equivalent to printf() except the result is returned as a CString.
*/
static CString print(const char* fmt, ...);
//! Print a string using printf-style formatting
/*!
Same as print() except takes va_list.
*/
static CString vprint(const char* fmt, va_list);
//! Print a string using printf-style formatting into a buffer
/*!
This is like print but print into a given buffer. If the resulting
string will not fit into \c buffer then a new buffer is allocated and
returned, otherwise \c buffer is returned. the caller must delete[]
the returned memory if is not \c buffer.
\c prefix and \c suffix must be >= 0. Exactly \c prefix characters and
at least \c suffix characters are available in the buffer before
and after the printed string, respectively. \c bufferLength is the
length of buffer and should not be adjusted by the caller to
account for \c prefix or \c suffix.
*/
static char* vsprint(char* buffer, int bufferLength,
int prefix, int suffix, const char* fmt, va_list);
//! Case-insensitive comparisons
/*!
This class provides case-insensitve comparison functions.
*/
class CaselessCmp {
public:
//! Same as less()
bool operator()(const CString& a, const CString& b) const;
//! Returns true iff \c a is lexicographically less than \c b
static bool less(const CString& a, const CString& b);
//! Returns true iff \c a is lexicographically equal to \c b
static bool equal(const CString& a, const CString& b);
//! Returns true iff \c a is lexicographically less than \c b
static bool cmpLess(const CString::value_type& a,
const CString::value_type& b);
//! Returns true iff \c a is lexicographically equal to \c b
static bool cmpEqual(const CString::value_type& a,
const CString::value_type& b);
};
};
#endif

754
lib/base/CUnicode.cpp Normal file
View File

@@ -0,0 +1,754 @@
#include "CUnicode.h"
#include <limits.h>
#include <string.h>
//
// local utility functions
//
inline
static
UInt16
decode16(const UInt8* n)
{
union x16 {
UInt8 n8[2];
UInt16 n16;
} c;
c.n8[0] = n[0];
c.n8[1] = n[1];
return c.n16;
}
inline
static
UInt32
decode32(const UInt8* n)
{
union x32 {
UInt8 n8[4];
UInt32 n32;
} c;
c.n8[0] = n[0];
c.n8[1] = n[1];
c.n8[2] = n[2];
c.n8[3] = n[3];
return c.n32;
}
inline
static
void
resetError(bool* errors)
{
if (errors != NULL) {
*errors = false;
}
}
inline
static
void
setError(bool* errors)
{
if (errors != NULL) {
*errors = true;
}
}
//
// CUnicode
//
UInt32 CUnicode::s_invalid = 0x0000ffff;
UInt32 CUnicode::s_replacement = 0x0000fffd;
bool
CUnicode::isUTF8(const CString& src)
{
// convert and test each character
const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
for (UInt32 n = src.size(); n > 0; ) {
if (fromUTF8(data, n) == s_invalid) {
return false;
}
}
return true;
}
CString
CUnicode::UTF8ToUCS2(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// get size of input string and reserve some space in output
UInt32 n = src.size();
CString dst;
dst.reserve(2 * n);
// convert each character
const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
while (n > 0) {
UInt32 c = fromUTF8(data, n);
if (c == s_invalid) {
c = s_replacement;
}
else if (c >= 0x00010000) {
setError(errors);
c = s_replacement;
}
UInt16 ucs2 = static_cast<UInt16>(c);
dst.append(reinterpret_cast<const char*>(&ucs2), 2);
}
return dst;
}
CString
CUnicode::UTF8ToUCS4(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// get size of input string and reserve some space in output
UInt32 n = src.size();
CString dst;
dst.reserve(4 * n);
// convert each character
const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
while (n > 0) {
UInt32 c = fromUTF8(data, n);
if (c == s_invalid) {
c = s_replacement;
}
dst.append(reinterpret_cast<const char*>(&c), 4);
}
return dst;
}
CString
CUnicode::UTF8ToUTF16(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// get size of input string and reserve some space in output
UInt32 n = src.size();
CString dst;
dst.reserve(2 * n);
// convert each character
const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
while (n > 0) {
UInt32 c = fromUTF8(data, n);
if (c == s_invalid) {
c = s_replacement;
}
else if (c >= 0x00110000) {
setError(errors);
c = s_replacement;
}
if (c < 0x00010000) {
UInt16 ucs2 = static_cast<UInt16>(c);
dst.append(reinterpret_cast<const char*>(&ucs2), 2);
}
else {
c -= 0x00010000;
UInt16 utf16h = static_cast<UInt16>((c >> 10) + 0xd800);
UInt16 utf16l = static_cast<UInt16>((c & 0x03ff) + 0xdc00);
dst.append(reinterpret_cast<const char*>(&utf16h), 2);
dst.append(reinterpret_cast<const char*>(&utf16l), 2);
}
}
return dst;
}
CString
CUnicode::UTF8ToUTF32(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// get size of input string and reserve some space in output
UInt32 n = src.size();
CString dst;
dst.reserve(4 * n);
// convert each character
const UInt8* data = reinterpret_cast<const UInt8*>(src.c_str());
while (n > 0) {
UInt32 c = fromUTF8(data, n);
if (c == s_invalid) {
c = s_replacement;
}
else if (c >= 0x00110000) {
setError(errors);
c = s_replacement;
}
dst.append(reinterpret_cast<const char*>(&c), 4);
}
return dst;
}
CString
CUnicode::UTF8ToText(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// convert to wide char
UInt32 size;
wchar_t* tmp = UTF8ToWideChar(src, size, errors);
// get length of multibyte string
char mbc[MB_LEN_MAX];
size_t mblen;
mbstate_t state;
memset(&state, 0, sizeof(state));
size_t len = 0;
UInt32 n = size;
for (const wchar_t* scan = tmp; n > 0; ++scan, --n) {
mblen = wcrtomb(mbc, *scan, &state);
if (mblen == -1) {
// unconvertable character
setError(errors);
len += 1;
}
else {
len += mblen;
}
}
// handle nul terminator
mblen = wcrtomb(mbc, L'\0', &state);
if (mblen != -1) {
len += mblen;
}
assert(mbsinit(&state) != 0);
// allocate multibyte string
char* mbs = new char[len];
// convert to multibyte
char* dst = mbs;
n = size;
for (const wchar_t* scan = tmp; n > 0; ++scan, --n) {
mblen = wcrtomb(dst, *scan, &state);
if (mblen == -1) {
// unconvertable character
*dst++ = '?';
}
else {
dst += mblen;
}
}
mblen = wcrtomb(dst, L'\0', &state);
if (mblen != -1) {
// don't include nul terminator
dst += mblen - 1;
}
CString text(mbs, dst - mbs);
// clean up
delete[] mbs;
delete[] tmp;
return text;
}
CString
CUnicode::UCS2ToUTF8(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// convert
UInt32 n = src.size() >> 1;
return doUCS2ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
}
CString
CUnicode::UCS4ToUTF8(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// convert
UInt32 n = src.size() >> 2;
return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
}
CString
CUnicode::UTF16ToUTF8(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// convert
UInt32 n = src.size() >> 1;
return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
}
CString
CUnicode::UTF32ToUTF8(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// convert
UInt32 n = src.size() >> 2;
return doUTF32ToUTF8(reinterpret_cast<const UInt8*>(src.data()), n, errors);
}
CString
CUnicode::textToUTF8(const CString& src, bool* errors)
{
// default to success
resetError(errors);
// get length of multibyte string
UInt32 n = src.size();
size_t len = 0;
mbstate_t state;
memset(&state, 0, sizeof(state));
for (const char* scan = src.c_str(); n > 0; ) {
size_t mblen = mbrtowc(NULL, scan, n, &state);
switch (mblen) {
case (size_t)-2:
// incomplete last character. convert to unknown character.
setError(errors);
len += 1;
n = 0;
break;
case (size_t)-1:
// invalid character. count one unknown character and
// start at the next byte.
setError(errors);
len += 1;
scan += 1;
n -= 1;
break;
case 0:
len += 1;
scan += 1;
n -= 1;
break;
default:
// normal character
len += 1;
scan += mblen;
n -= mblen;
break;
}
}
memset(&state, 0, sizeof(state));
// allocate wide character string
wchar_t* wcs = new wchar_t[len];
// convert multibyte to wide char
n = src.size();
wchar_t* dst = wcs;
for (const char* scan = src.c_str(); n > 0; ++dst) {
size_t mblen = mbrtowc(dst, scan, n, &state);
switch (mblen) {
case (size_t)-2:
// incomplete character. convert to unknown character.
*dst = (wchar_t)0xfffd;
n = 0;
break;
case (size_t)-1:
// invalid character. count one unknown character and
// start at the next byte.
*dst = (wchar_t)0xfffd;
scan += 1;
n -= 1;
break;
case 0:
*dst = (wchar_t)0x0000;
scan += 1;
n -= 1;
break;
default:
// normal character
scan += mblen;
n -= mblen;
break;
}
}
// convert to UTF8
CString utf8 = wideCharToUTF8(wcs, len, errors);
// clean up
delete[] wcs;
return utf8;
}
wchar_t*
CUnicode::UTF8ToWideChar(const CString& src, UInt32& size, bool* errors)
{
// convert to platform's wide character encoding
#if WINDOWS_LIKE
CString tmp = UTF8ToUTF16(src, errors);
size = tmp.size() >> 1;
#elif UNIX_LIKE
CString tmp = UTF8ToUCS4(src, errors);
size = tmp.size() >> 2;
#endif
// copy to a wchar_t array
wchar_t* dst = new wchar_t[size];
::memcpy(dst, tmp.data(), sizeof(wchar_t) * size);
return dst;
}
CString
CUnicode::wideCharToUTF8(const wchar_t* src, UInt32 size, bool* errors)
{
// convert from platform's wide character encoding.
// note -- this must include a wide nul character (independent of
// the CString's nul character).
#if WINDOWS_LIKE
return doUTF16ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors);
#elif UNIX_LIKE
return doUCS4ToUTF8(reinterpret_cast<const UInt8*>(src), size, errors);
#endif
}
CString
CUnicode::doUCS2ToUTF8(const UInt8* data, UInt32 n, bool* errors)
{
// make some space
CString dst;
dst.reserve(n);
// convert each character
for (; n > 0; data += 2, --n) {
UInt32 c = decode16(data);
toUTF8(dst, c, errors);
}
return dst;
}
CString
CUnicode::doUCS4ToUTF8(const UInt8* data, UInt32 n, bool* errors)
{
// make some space
CString dst;
dst.reserve(n);
// convert each character
for (; n > 0; data += 4, --n) {
UInt32 c = decode32(data);
toUTF8(dst, c, errors);
}
return dst;
}
CString
CUnicode::doUTF16ToUTF8(const UInt8* data, UInt32 n, bool* errors)
{
// make some space
CString dst;
dst.reserve(n);
// convert each character
for (; n > 0; data += 2, --n) {
UInt32 c = decode16(data);
if (c < 0x0000d800 || c > 0x0000dfff) {
toUTF8(dst, c, errors);
}
else if (n == 1) {
// error -- missing second word
setError(errors);
toUTF8(dst, s_replacement, NULL);
}
else if (c >= 0x0000d800 && c <= 0x0000dbff) {
UInt32 c2 = decode16(data);
data += 2;
--n;
if (c2 < 0x0000dc00 || c2 > 0x0000dfff) {
// error -- [d800,dbff] not followed by [dc00,dfff]
setError(errors);
toUTF8(dst, s_replacement, NULL);
}
else {
c = (((c - 0x0000d800) << 10) | (c2 - 0x0000dc00)) + 0x00010000;
toUTF8(dst, c, errors);
}
}
else {
// error -- [dc00,dfff] without leading [d800,dbff]
setError(errors);
toUTF8(dst, s_replacement, NULL);
}
}
return dst;
}
CString
CUnicode::doUTF32ToUTF8(const UInt8* data, UInt32 n, bool* errors)
{
// make some space
CString dst;
dst.reserve(n);
// convert each character
for (; n > 0; data += 4, --n) {
UInt32 c = decode32(data);
if (c >= 0x00110000) {
setError(errors);
c = s_replacement;
}
toUTF8(dst, c, errors);
}
return dst;
}
UInt32
CUnicode::fromUTF8(const UInt8*& data, UInt32& n)
{
assert(data != NULL);
assert(n != 0);
// compute character encoding length, checking for overlong
// sequences (i.e. characters that don't use the shortest
// possible encoding).
UInt32 size;
if (data[0] < 0x80) {
// 0xxxxxxx
size = 1;
}
else if (data[0] < 0xc0) {
// 10xxxxxx -- in the middle of a multibyte character. counts
// as one invalid character.
--n;
++data;
return s_invalid;
}
else if (data[0] < 0xe0) {
// 110xxxxx
size = 2;
}
else if (data[0] < 0xf0) {
// 1110xxxx
size = 3;
}
else if (data[0] < 0xf8) {
// 11110xxx
size = 4;
}
else if (data[0] < 0xfc) {
// 111110xx
size = 5;
}
else if (data[0] < 0xfe) {
// 1111110x
size = 6;
}
else {
// invalid sequence. dunno how many bytes to skip so skip one.
--n;
++data;
return s_invalid;
}
// make sure we have enough data
if (size > n) {
data += n;
n = 0;
return s_invalid;
}
// extract character
UInt32 c;
switch (size) {
case 1:
c = static_cast<UInt32>(data[0]);
break;
case 2:
c = ((static_cast<UInt32>(data[0]) & 0x1f) << 6) |
((static_cast<UInt32>(data[1]) & 0x3f) );
break;
case 3:
c = ((static_cast<UInt32>(data[0]) & 0x0f) << 12) |
((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
((static_cast<UInt32>(data[2]) & 0x3f) );
break;
case 4:
c = ((static_cast<UInt32>(data[0]) & 0x07) << 18) |
((static_cast<UInt32>(data[1]) & 0x3f) << 12) |
((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
((static_cast<UInt32>(data[1]) & 0x3f) );
break;
case 5:
c = ((static_cast<UInt32>(data[0]) & 0x03) << 24) |
((static_cast<UInt32>(data[1]) & 0x3f) << 18) |
((static_cast<UInt32>(data[1]) & 0x3f) << 12) |
((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
((static_cast<UInt32>(data[1]) & 0x3f) );
break;
case 6:
c = ((static_cast<UInt32>(data[0]) & 0x01) << 30) |
((static_cast<UInt32>(data[1]) & 0x3f) << 24) |
((static_cast<UInt32>(data[1]) & 0x3f) << 18) |
((static_cast<UInt32>(data[1]) & 0x3f) << 12) |
((static_cast<UInt32>(data[1]) & 0x3f) << 6) |
((static_cast<UInt32>(data[1]) & 0x3f) );
break;
default:
assert(0 && "invalid size");
return s_invalid;
}
// check that all bytes after the first have the pattern 10xxxxxx.
// truncated sequences are treated as a single malformed character.
bool truncated = false;
switch (size) {
case 6:
if ((data[5] & 0xc0) != 0x80) {
truncated = true;
size = 5;
}
// fall through
case 5:
if ((data[4] & 0xc0) != 0x80) {
truncated = true;
size = 4;
}
// fall through
case 4:
if ((data[3] & 0xc0) != 0x80) {
truncated = true;
size = 3;
}
// fall through
case 3:
if ((data[2] & 0xc0) != 0x80) {
truncated = true;
size = 2;
}
// fall through
case 2:
if ((data[1] & 0xc0) != 0x80) {
truncated = true;
size = 1;
}
}
// update parameters
data += size;
n -= size;
// invalid if sequence was truncated
if (truncated) {
return s_invalid;
}
// check for characters that didn't use the smallest possible encoding
static UInt32 s_minChar[] = {
0,
0x00000000,
0x00000080,
0x00000800,
0x00010000,
0x00200000,
0x04000000
};
if (c < s_minChar[size]) {
return s_invalid;
}
// check for characters not in ISO-10646
if (c >= 0x0000d800 && c <= 0x0000dfff) {
return s_invalid;
}
if (c >= 0x0000fffe && c <= 0x0000ffff) {
return s_invalid;
}
return c;
}
void
CUnicode::toUTF8(CString& dst, UInt32 c, bool* errors)
{
UInt8 data[6];
// handle characters outside the valid range
if ((c >= 0x0000d800 && c <= 0x0000dfff) || c >= 0x80000000) {
setError(errors);
c = s_replacement;
}
// convert to UTF-8
if (c < 0x00000080) {
data[0] = static_cast<UInt8>(c);
dst.append(reinterpret_cast<char*>(data), 1);
}
else if (c < 0x00000800) {
data[0] = static_cast<UInt8>(((c >> 6) & 0x0000001f) + 0xc0);
data[1] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
dst.append(reinterpret_cast<char*>(data), 2);
}
else if (c < 0x00010000) {
data[0] = static_cast<UInt8>(((c >> 12) & 0x0000000f) + 0xe0);
data[1] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
data[2] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
dst.append(reinterpret_cast<char*>(data), 3);
}
else if (c < 0x00200000) {
data[0] = static_cast<UInt8>(((c >> 18) & 0x00000007) + 0xf0);
data[1] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80);
data[2] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
data[3] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
dst.append(reinterpret_cast<char*>(data), 4);
}
else if (c < 0x04000000) {
data[0] = static_cast<UInt8>(((c >> 24) & 0x00000003) + 0xf8);
data[1] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80);
data[2] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80);
data[3] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
data[4] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
dst.append(reinterpret_cast<char*>(data), 5);
}
else if (c < 0x80000000) {
data[0] = static_cast<UInt8>(((c >> 30) & 0x00000001) + 0xfc);
data[1] = static_cast<UInt8>(((c >> 24) & 0x0000003f) + 0x80);
data[2] = static_cast<UInt8>(((c >> 18) & 0x0000003f) + 0x80);
data[3] = static_cast<UInt8>(((c >> 12) & 0x0000003f) + 0x80);
data[4] = static_cast<UInt8>(((c >> 6) & 0x0000003f) + 0x80);
data[5] = static_cast<UInt8>((c & 0x0000003f) + 0x80);
dst.append(reinterpret_cast<char*>(data), 6);
}
else {
assert(0 && "character out of range");
}
}

130
lib/base/CUnicode.h Normal file
View File

@@ -0,0 +1,130 @@
#ifndef CUNICODE_H
#define CUNICODE_H
#include "CString.h"
#include "BasicTypes.h"
#include <wchar.h>
//! Unicode utility functions
/*!
This class provides functions for converting between various Unicode
encodings and the current locale encoding.
*/
class CUnicode {
public:
//! @name accessors
//@{
//! Test UTF-8 string for validity
/*!
Returns true iff the string contains a valid sequence of UTF-8
encoded characters.
*/
static bool isUTF8(const CString&);
//! Convert from UTF-8 to UCS-2 encoding
/*!
Convert from UTF-8 to UCS-2. If errors is not NULL then *errors
is set to true iff any character could not be encoded in UCS-2.
Decoding errors do not set *errors.
*/
static CString UTF8ToUCS2(const CString&, bool* errors = NULL);
//! Convert from UTF-8 to UCS-4 encoding
/*!
Convert from UTF-8 to UCS-4. If errors is not NULL then *errors
is set to true iff any character could not be encoded in UCS-4.
Decoding errors do not set *errors.
*/
static CString UTF8ToUCS4(const CString&, bool* errors = NULL);
//! Convert from UTF-8 to UTF-16 encoding
/*!
Convert from UTF-8 to UTF-16. If errors is not NULL then *errors
is set to true iff any character could not be encoded in UTF-16.
Decoding errors do not set *errors.
*/
static CString UTF8ToUTF16(const CString&, bool* errors = NULL);
//! Convert from UTF-8 to UTF-32 encoding
/*!
Convert from UTF-8 to UTF-32. If errors is not NULL then *errors
is set to true iff any character could not be encoded in UTF-32.
Decoding errors do not set *errors.
*/
static CString UTF8ToUTF32(const CString&, bool* errors = NULL);
//! Convert from UTF-8 to the current locale encoding
/*!
Convert from UTF-8 to the current locale encoding. If errors is not
NULL then *errors is set to true iff any character could not be encoded.
Decoding errors do not set *errors.
*/
static CString UTF8ToText(const CString&, bool* errors = NULL);
//! Convert from UCS-2 to UTF-8
/*!
Convert from UCS-2 to UTF-8. If errors is not NULL then *errors is
set to true iff any character could not be decoded.
*/
static CString UCS2ToUTF8(const CString&, bool* errors = NULL);
//! Convert from UCS-4 to UTF-8
/*!
Convert from UCS-4 to UTF-8. If errors is not NULL then *errors is
set to true iff any character could not be decoded.
*/
static CString UCS4ToUTF8(const CString&, bool* errors = NULL);
//! Convert from UTF-16 to UTF-8
/*!
Convert from UTF-16 to UTF-8. If errors is not NULL then *errors is
set to true iff any character could not be decoded.
*/
static CString UTF16ToUTF8(const CString&, bool* errors = NULL);
//! Convert from UTF-32 to UTF-8
/*!
Convert from UTF-32 to UTF-8. If errors is not NULL then *errors is
set to true iff any character could not be decoded.
*/
static CString UTF32ToUTF8(const CString&, bool* errors = NULL);
//! Convert from the current locale encoding to UTF-8
/*!
Convert from the current locale encoding to UTF-8. If errors is not
NULL then *errors is set to true iff any character could not be decoded.
*/
static CString textToUTF8(const CString&, bool* errors = NULL);
//@}
private:
// convert UTF8 to wchar_t string (using whatever encoding is native
// to the platform). caller must delete[] the returned string. the
// string is *not* nul terminated; the length (in characters) is
// returned in size.
static wchar_t* UTF8ToWideChar(const CString&,
UInt32& size, bool* errors);
// convert nul terminated wchar_t string (in platform's native
// encoding) to UTF8.
static CString wideCharToUTF8(const wchar_t*,
UInt32 size, bool* errors);
// internal conversion to UTF8
static CString doUCS2ToUTF8(const UInt8* src, UInt32 n, bool* errors);
static CString doUCS4ToUTF8(const UInt8* src, UInt32 n, bool* errors);
static CString doUTF16ToUTF8(const UInt8* src, UInt32 n, bool* errors);
static CString doUTF32ToUTF8(const UInt8* src, UInt32 n, bool* errors);
// convert characters to/from UTF8
static UInt32 fromUTF8(const UInt8*& src, UInt32& size);
static void toUTF8(CString& dst, UInt32 c, bool* errors);
private:
static UInt32 s_invalid;
static UInt32 s_replacement;
};
#endif

17
lib/base/IInterface.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef IINTERFACE_H
#define IINTERFACE_H
#include "common.h"
//! Base class of interfaces
/*!
This is the base class of all interface classes. An interface class has
only pure virtual methods.
*/
class IInterface {
public:
//! Interface destructor does nothing
virtual ~IInterface() { }
};
#endif

16
lib/base/IJob.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef IJOB_H
#define IJOB_H
#include "IInterface.h"
//! Job interface
/*!
A job is an interface for executing some function.
*/
class IJob : public IInterface {
public:
//! Run the job
virtual void run() = 0;
};
#endif

36
lib/base/Makefile.am Normal file
View File

@@ -0,0 +1,36 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ../..
noinst_LIBRARIES = libbase.a
libbase_a_SOURCES = \
CFunctionJob.cpp \
CLog.cpp \
CStopwatch.cpp \
CString.cpp \
CUnicode.cpp \
XBase.cpp \
BasicTypes.h \
CFunctionJob.h \
CLog.h \
CStopwatch.h \
CString.h \
CUnicode.h \
IInterface.h \
IJob.h \
TMethodJob.h \
XBase.h \
common.h \
stdfstream.h \
stdistream.h \
stdlist.h \
stdmap.h \
stdostream.h \
stdpost.h \
stdpre.h \
stdset.h \
stdsstream.h \
stdvector.h \
$(NULL)
INCLUDES = \
$(NULL)

53
lib/base/TMethodJob.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef CMETHODJOB_H
#define CMETHODJOB_H
#include "IJob.h"
//! Use a function as a job
/*!
A job class that invokes a member function.
*/
template <class T>
class TMethodJob : public IJob {
public:
//! run() invokes \c object->method(arg)
TMethodJob(T* object, void (T::*method)(void*), void* arg = NULL);
virtual ~TMethodJob();
// IJob overrides
virtual void run();
private:
T* m_object;
void (T::*m_method)(void*);
void* m_arg;
};
template <class T>
inline
TMethodJob<T>::TMethodJob(T* object, void (T::*method)(void*), void* arg) :
m_object(object),
m_method(method),
m_arg(arg)
{
// do nothing
}
template <class T>
inline
TMethodJob<T>::~TMethodJob()
{
// do nothing
}
template <class T>
inline
void
TMethodJob<T>::run()
{
if (m_object != NULL) {
(m_object->*m_method)(m_arg);
}
}
#endif

95
lib/base/XBase.cpp Normal file
View File

@@ -0,0 +1,95 @@
#include "XBase.h"
#include <cerrno>
#include <cstdarg>
// win32 wants a const char* argument to std::exception c'tor
#if WINDOWS_LIKE
#define STDEXCEPTARG ""
#endif
// default to no argument
#ifndef STDEXCEPTARG
#define STDEXCEPTARG
#endif
//
// XBase
//
XBase::XBase() :
exception(STDEXCEPTARG),
m_what()
{
// do nothing
}
XBase::XBase(const CString& msg) :
exception(STDEXCEPTARG),
m_what(msg)
{
// do nothing
}
XBase::~XBase()
{
// do nothing
}
const char*
XBase::what() const
{
if (m_what.empty()) {
m_what = getWhat();
}
return m_what.c_str();
}
CString
XBase::format(const char* /*id*/, const char* fmt, ...) const throw()
{
// FIXME -- lookup message string using id as an index. set
// fmt to that string if it exists.
// format
CString result;
va_list args;
va_start(args, fmt);
try {
result = CStringUtil::vformat(fmt, args);
}
catch (...) {
// ignore
}
va_end(args);
return result;
}
//
// MXErrno
//
MXErrno::MXErrno() :
m_errno(errno)
{
// do nothing
}
MXErrno::MXErrno(int err) :
m_errno(err)
{
// do nothing
}
int
MXErrno::getErrno() const
{
return m_errno;
}
const char*
MXErrno::getErrstr() const
{
return strerror(m_errno);
}

68
lib/base/XBase.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef XBASE_H
#define XBASE_H
#include "CString.h"
#include "stdpre.h"
#include <exception>
#include "stdpost.h"
//! Exception base class
/*!
This is the base class of most exception types.
*/
class XBase : public std::exception {
public:
//! Use getWhat() as the result of what()
XBase();
//! Use \c msg as the result of what()
XBase(const CString& msg);
virtual ~XBase();
// std::exception overrides
virtual const char* what() const;
protected:
//! Get a human readable string describing the exception
virtual CString getWhat() const throw() = 0;
//! Format a string
/*!
Looks up a message format using \c id, using \c defaultFormat if
no format can be found, then replaces positional parameters in
the format string and returns the result.
*/
virtual CString format(const char* id,
const char* defaultFormat, ...) const throw();
private:
mutable CString m_what;
};
//! Mix-in for handling \c errno
/*!
This mix-in class for exception classes provides storage and query of
\c errno.
*/
class MXErrno {
public:
//! Save \c errno as the error code
MXErrno();
//! Save \c err as the error code
MXErrno(int err);
//! @name accessors
//@{
//! Get the error code
int getErrno() const;
//! Get the human readable string for the error code
const char* getErrstr() const;
//@}
private:
int m_errno;
};
#endif

202
lib/base/base.dsp Normal file
View File

@@ -0,0 +1,202 @@
# Microsoft Developer Studio Project File - Name="base" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=base - 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 "base.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 "base.mak" CFG="base - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "base - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "base - 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)" == "base - 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 /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)" == "base - 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 /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 "base - Win32 Release"
# Name "base - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\CFunctionJob.cpp
# End Source File
# Begin Source File
SOURCE=.\CLog.cpp
# End Source File
# Begin Source File
SOURCE=.\CStopwatch.cpp
# End Source File
# Begin Source File
SOURCE=.\CString.cpp
# End Source File
# Begin Source File
SOURCE=.\CUnicode.cpp
# End Source File
# Begin Source File
SOURCE=.\XBase.cpp
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\BasicTypes.h
# End Source File
# Begin Source File
SOURCE=.\CFunctionJob.h
# End Source File
# Begin Source File
SOURCE=.\CLog.h
# End Source File
# Begin Source File
SOURCE=.\common.h
# End Source File
# Begin Source File
SOURCE=.\CStopwatch.h
# End Source File
# Begin Source File
SOURCE=.\CString.h
# End Source File
# Begin Source File
SOURCE=.\CUnicode.h
# End Source File
# Begin Source File
SOURCE=.\IInterface.h
# End Source File
# Begin Source File
SOURCE=.\IJob.h
# End Source File
# Begin Source File
SOURCE=.\stdfstream.h
# End Source File
# Begin Source File
SOURCE=.\stdistream.h
# End Source File
# Begin Source File
SOURCE=.\stdlist.h
# End Source File
# Begin Source File
SOURCE=.\stdmap.h
# End Source File
# Begin Source File
SOURCE=.\stdostream.h
# End Source File
# Begin Source File
SOURCE=.\stdpost.h
# End Source File
# Begin Source File
SOURCE=.\stdpre.h
# End Source File
# Begin Source File
SOURCE=.\stdset.h
# End Source File
# Begin Source File
SOURCE=.\stdsstream.h
# End Source File
# Begin Source File
SOURCE=.\stdvector.h
# End Source File
# Begin Source File
SOURCE=.\TMethodJob.h
# End Source File
# Begin Source File
SOURCE=.\XBase.h
# End Source File
# End Group
# End Target
# End Project

60
lib/base/common.h Normal file
View File

@@ -0,0 +1,60 @@
#ifndef COMMON_H
#define COMMON_H
// this file should be included, directly or indirectly by every other.
#if HAVE_CONFIG_H
# include "config.h"
#endif
// check if win32 platform
#if defined(_WIN32) || HAVE_WINDOWS_H
# define WINDOWS_LIKE 1
// VC++ specific
# if (_MSC_VER >= 1200)
// work around for statement scoping bug
# define for if (false) { } else for
// turn off bonehead warnings
# 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
// emitted incorrectly under release build in some circumstances
# if defined(NDEBUG)
# pragma warning(disable: 4702) // unreachable code
# pragma warning(disable: 4701) // variable maybe used uninitialized
# endif
# endif // (_MSC_VER >= 1200)
// VC++ has built-in sized types
# if defined(_MSC_VER)
# define TYPE_OF_SIZE_1 __int8
# define TYPE_OF_SIZE_2 __int16
# define TYPE_OF_SIZE_4 __int32
# else
# define SIZE_OF_CHAR 1
# define SIZE_OF_SHORT 2
# define SIZE_OF_INT 4
# define SIZE_OF_LONG 4
# endif
#endif // defined(_WIN32) || HAVE_WINDOWS_H
// unix-like if not like anything else
#if (!defined(WINDOWS_LIKE) || WINDOWS_LIKE == 0)
# define UNIX_LIKE 1
#endif
// define NULL
#ifndef NULL
#define NULL 0
#endif
// make assert available since we use it a lot
#include <assert.h>
#endif

4
lib/base/stdfstream.h Normal file
View File

@@ -0,0 +1,4 @@
#include "stdpre.h"
#include <fstream>
#include "stdpost.h"
#include "stdistream.h"

29
lib/base/stdistream.h Normal file
View File

@@ -0,0 +1,29 @@
#include "stdpre.h"
#if defined(HAVE_ISTREAM)
#include <istream>
#else
#include <iostream>
#endif
#include "stdpost.h"
#if defined(_MSC_VER) && _MSC_VER <= 1200
// VC++6 istream has no overloads for __int* types, .NET does
inline
std::istream& operator>>(std::istream& s, SInt8& i)
{ return s >> (signed char&)i; }
inline
std::istream& operator>>(std::istream& s, SInt16& i)
{ return s >> (short&)i; }
inline
std::istream& operator>>(std::istream& s, SInt32& i)
{ return s >> (int&)i; }
inline
std::istream& operator>>(std::istream& s, UInt8& i)
{ return s >> (unsigned char&)i; }
inline
std::istream& operator>>(std::istream& s, UInt16& i)
{ return s >> (unsigned short&)i; }
inline
std::istream& operator>>(std::istream& s, UInt32& i)
{ return s >> (unsigned int&)i; }
#endif

3
lib/base/stdlist.h Normal file
View File

@@ -0,0 +1,3 @@
#include "stdpre.h"
#include <list>
#include "stdpost.h"

3
lib/base/stdmap.h Normal file
View File

@@ -0,0 +1,3 @@
#include "stdpre.h"
#include <map>
#include "stdpost.h"

7
lib/base/stdostream.h Normal file
View File

@@ -0,0 +1,7 @@
#include "stdpre.h"
#if defined(HAVE_OSTREAM)
#include <ostream>
#else
#include <iostream>
#endif
#include "stdpost.h"

3
lib/base/stdpost.h Normal file
View File

@@ -0,0 +1,3 @@
#if defined(_MSC_VER)
#pragma warning(pop)
#endif

13
lib/base/stdpre.h Normal file
View File

@@ -0,0 +1,13 @@
#if defined(_MSC_VER)
#pragma warning(disable: 4786) // identifier truncated
#pragma warning(disable: 4514) // unreferenced inline
#pragma warning(disable: 4710) // not inlined
#pragma warning(disable: 4663) // C++ change, template specialization
#pragma warning(disable: 4503) // decorated name length too long
#pragma warning(push, 3)
#pragma warning(disable: 4018) // signed/unsigned mismatch
#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

3
lib/base/stdset.h Normal file
View File

@@ -0,0 +1,3 @@
#include "stdpre.h"
#include <set>
#include "stdpost.h"

4
lib/base/stdsstream.h Normal file
View File

@@ -0,0 +1,4 @@
#include "stdpre.h"
#include <sstream>
#include "stdpost.h"
#include "stdistream.h"

3
lib/base/stdvector.h Normal file
View File

@@ -0,0 +1,3 @@
#include "stdpre.h"
#include <vector>
#include "stdpost.h"

629
lib/client/CClient.cpp Normal file
View File

@@ -0,0 +1,629 @@
#include "CClient.h"
#include "CServerProxy.h"
#include "CClipboard.h"
#include "CInputPacketStream.h"
#include "COutputPacketStream.h"
#include "CProtocolUtil.h"
#include "CSecondaryScreen.h"
#include "IServer.h"
#include "ProtocolTypes.h"
#include "XScreen.h"
#include "XSynergy.h"
#include "XSocket.h"
#include "CLock.h"
#include "CThread.h"
#include "CTimerThread.h"
#include "XThread.h"
#include "CLog.h"
#include "CStopwatch.h"
#include "TMethodJob.h"
//
// CClient
//
CClient::CClient(const CString& clientName) :
m_name(clientName),
m_screen(NULL),
m_server(NULL),
m_camp(false),
m_session(NULL),
m_active(false),
m_rejected(true)
{
// do nothing
}
CClient::~CClient()
{
// do nothing
}
void
CClient::camp(bool on)
{
CLock lock(&m_mutex);
m_camp = on;
}
void
CClient::setAddress(const CNetworkAddress& serverAddress)
{
CLock lock(&m_mutex);
m_serverAddress = serverAddress;
}
void
CClient::exitMainLoop()
{
m_screen->exitMainLoop();
}
bool
CClient::wasRejected() const
{
return m_rejected;
}
void
CClient::onError()
{
// close down session but don't wait too long
deleteSession(3.0);
}
void
CClient::onInfoChanged(const CClientInfo& info)
{
log((CLOG_DEBUG "resolution changed"));
CLock lock(&m_mutex);
if (m_server != NULL) {
m_server->onInfoChanged(info);
}
}
bool
CClient::onGrabClipboard(ClipboardID id)
{
CLock lock(&m_mutex);
if (m_server == NULL) {
// m_server can be NULL if the screen calls this method
// before we've gotten around to connecting to the server.
// we simply ignore the clipboard change in that case.
return false;
}
// grab ownership
m_server->onGrabClipboard(id);
// we now own the clipboard and it has not been sent to the server
m_ownClipboard[id] = true;
m_timeClipboard[id] = 0;
// if we're not the active screen then send the clipboard now,
// otherwise we'll wait until we leave.
if (!m_active) {
sendClipboard(id);
}
return true;
}
void
CClient::onClipboardChanged(ClipboardID, const CString&)
{
// ignore -- we'll check the clipboard when we leave
}
bool
CClient::open()
{
// open the screen
try {
log((CLOG_INFO "opening screen"));
openSecondaryScreen();
return true;
}
catch (XScreenOpenFailure&) {
// can't open screen yet. wait a few seconds to retry.
log((CLOG_INFO "failed to open screen"));
return false;
}
}
void
CClient::mainLoop()
{
{
CLock lock(&m_mutex);
// check preconditions
assert(m_screen != NULL);
assert(m_server == NULL);
// connection starts as unsuccessful
m_rejected = true;
}
try {
log((CLOG_NOTE "starting client \"%s\"", m_name.c_str()));
// start server interactions
{
CLock lock(&m_mutex);
m_session = new CThread(new TMethodJob<CClient>(
this, &CClient::runSession));
}
// handle events
m_screen->mainLoop();
// clean up
deleteSession();
log((CLOG_NOTE "stopping client \"%s\"", m_name.c_str()));
}
catch (XBase& e) {
log((CLOG_ERR "client error: %s", e.what()));
// clean up
deleteSession();
log((CLOG_NOTE "stopping client \"%s\"", m_name.c_str()));
CLock lock(&m_mutex);
m_rejected = false;
}
catch (XThread&) {
// clean up
deleteSession();
log((CLOG_NOTE "stopping client \"%s\"", m_name.c_str()));
throw;
}
catch (...) {
log((CLOG_DEBUG "unknown client error"));
// clean up
deleteSession();
log((CLOG_NOTE "stopping client \"%s\"", m_name.c_str()));
throw;
}
}
void
CClient::close()
{
closeSecondaryScreen();
log((CLOG_INFO "closed screen"));
}
void
CClient::enter(SInt32 xAbs, SInt32 yAbs, UInt32, KeyModifierMask mask, bool)
{
{
CLock lock(&m_mutex);
m_active = true;
}
m_screen->enter(xAbs, yAbs, mask);
}
bool
CClient::leave()
{
m_screen->leave();
CLock lock(&m_mutex);
m_active = false;
// send clipboards that we own and that have changed
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
if (m_ownClipboard[id]) {
sendClipboard(id);
}
}
return true;
}
void
CClient::setClipboard(ClipboardID id, const CString& data)
{
// unmarshall
CClipboard clipboard;
clipboard.unmarshall(data, 0);
// set screen's clipboard
m_screen->setClipboard(id, &clipboard);
}
void
CClient::grabClipboard(ClipboardID id)
{
// we no longer own the clipboard
{
CLock lock(&m_mutex);
m_ownClipboard[id] = false;
}
m_screen->grabClipboard(id);
}
void
CClient::setClipboardDirty(ClipboardID, bool)
{
assert(0 && "shouldn't be called");
}
void
CClient::keyDown(KeyID id, KeyModifierMask mask)
{
m_screen->keyDown(id, mask);
}
void
CClient::keyRepeat(KeyID id, KeyModifierMask mask, SInt32 count)
{
m_screen->keyRepeat(id, mask, count);
}
void
CClient::keyUp(KeyID id, KeyModifierMask mask)
{
m_screen->keyUp(id, mask);
}
void
CClient::mouseDown(ButtonID id)
{
m_screen->mouseDown(id);
}
void
CClient::mouseUp(ButtonID id)
{
m_screen->mouseUp(id);
}
void
CClient::mouseMove(SInt32 x, SInt32 y)
{
m_screen->mouseMove(x, y);
}
void
CClient::mouseWheel(SInt32 delta)
{
m_screen->mouseWheel(delta);
}
void
CClient::screensaver(bool activate)
{
m_screen->screensaver(activate);
}
CString
CClient::getName() const
{
return m_name;
}
SInt32
CClient::getJumpZoneSize() const
{
return m_screen->getJumpZoneSize();
}
void
CClient::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
m_screen->getShape(x, y, w, h);
}
void
CClient::getCursorPos(SInt32& x, SInt32& y) const
{
m_screen->getCursorPos(x, y);
}
void
CClient::getCursorCenter(SInt32&, SInt32&) const
{
assert(0 && "shouldn't be called");
}
// FIXME -- use factory to create screen
#if WINDOWS_LIKE
#include "CMSWindowsSecondaryScreen.h"
#elif UNIX_LIKE
#include "CXWindowsSecondaryScreen.h"
#endif
void
CClient::openSecondaryScreen()
{
assert(m_screen == NULL);
// not active
m_active = false;
// reset clipboard state
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
m_ownClipboard[id] = false;
m_timeClipboard[id] = 0;
}
// open screen
log((CLOG_DEBUG1 "creating secondary screen"));
#if WINDOWS_LIKE
m_screen = new CMSWindowsSecondaryScreen(this);
#elif UNIX_LIKE
m_screen = new CXWindowsSecondaryScreen(this);
#endif
log((CLOG_DEBUG1 "opening secondary screen"));
try {
m_screen->open();
}
catch (...) {
log((CLOG_DEBUG1 "destroying secondary screen"));
delete m_screen;
m_screen = NULL;
throw;
}
}
void
CClient::closeSecondaryScreen()
{
assert(m_screen != NULL);
// close the secondary screen
try {
log((CLOG_DEBUG1 "closing secondary screen"));
m_screen->close();
}
catch (...) {
// ignore
}
// clean up
log((CLOG_DEBUG1 "destroying secondary screen"));
delete m_screen;
m_screen = NULL;
}
void
CClient::sendClipboard(ClipboardID id)
{
// note -- m_mutex must be locked on entry
assert(m_screen != NULL);
assert(m_server != NULL);
// get clipboard data. set the clipboard time to the last
// clipboard time before getting the data from the screen
// as the screen may detect an unchanged clipboard and
// avoid copying the data.
CClipboard clipboard;
if (clipboard.open(m_timeClipboard[id])) {
clipboard.close();
}
m_screen->getClipboard(id, &clipboard);
// check time
if (m_timeClipboard[id] == 0 ||
clipboard.getTime() != m_timeClipboard[id]) {
// save new time
m_timeClipboard[id] = clipboard.getTime();
// marshall the data
CString data = clipboard.marshall();
// save and send data if different
if (data != m_dataClipboard[id]) {
m_dataClipboard[id] = data;
m_server->onClipboardChanged(id, data);
}
}
}
void
CClient::runSession(void*)
{
try {
log((CLOG_DEBUG "starting server proxy"));
runServer();
m_screen->exitMainLoop();
log((CLOG_DEBUG "stopping server proxy"));
}
catch (...) {
m_screen->exitMainLoop();
log((CLOG_DEBUG "stopping server proxy"));
throw;
}
}
void
CClient::deleteSession(double timeout)
{
// get session thread object
CThread* thread;
{
CLock lock(&m_mutex);
thread = m_session;
m_session = NULL;
}
// shut it down
if (thread != NULL) {
thread->cancel();
thread->wait(timeout);
delete thread;
}
}
#include "CTCPSocket.h" // FIXME
void
CClient::runServer()
{
IDataSocket* socket = NULL;
CServerProxy* proxy = NULL;
try {
for (;;) {
try {
// allow connect this much time to succeed
// FIXME -- timeout in member
CTimerThread timer(m_camp ? -1.0 : 30.0);
// create socket and attempt to connect to server
log((CLOG_DEBUG1 "connecting to server"));
socket = new CTCPSocket; // FIXME -- use factory
socket->connect(m_serverAddress);
log((CLOG_INFO "connected to server"));
break;
}
catch (XSocketConnect& e) {
log((CLOG_DEBUG1 "failed to connect to server: %s", e.getErrstr()));
// failed to connect. if not camping then rethrow.
if (!m_camp) {
throw;
}
// we're camping. wait a bit before retrying
CThread::sleep(5.0);
}
}
// create proxy
log((CLOG_DEBUG1 "negotiating with server"));
proxy = handshakeServer(socket);
CLock lock(&m_mutex);
m_server = proxy;
}
catch (XThread&) {
log((CLOG_ERR "connection timed out"));
delete socket;
throw;
}
catch (XBase& e) {
log((CLOG_ERR "connection failed: %s", e.what()));
log((CLOG_DEBUG "disconnecting from server"));
delete socket;
return;
}
catch (...) {
log((CLOG_ERR "connection failed: <unknown error>"));
log((CLOG_DEBUG "disconnecting from server"));
delete socket;
return;
}
try {
// process messages
bool rejected = true;
if (proxy != NULL) {
log((CLOG_DEBUG1 "communicating with server"));
rejected = !proxy->mainLoop();
}
// clean up
CLock lock(&m_mutex);
m_rejected = rejected;
m_server = NULL;
delete proxy;
log((CLOG_DEBUG "disconnecting from server"));
socket->close();
delete socket;
}
catch (...) {
CLock lock(&m_mutex);
m_rejected = false;
m_server = NULL;
delete proxy;
log((CLOG_DEBUG "disconnecting from server"));
socket->close();
delete socket;
throw;
}
}
CServerProxy*
CClient::handshakeServer(IDataSocket* socket)
{
// get the input and output streams
IInputStream* input = socket->getInputStream();
IOutputStream* output = socket->getOutputStream();
bool own = false;
// attach the encryption layer
/* FIXME -- implement ISecurityFactory
if (m_securityFactory != NULL) {
input = m_securityFactory->createInputFilter(input, own);
output = m_securityFactory->createOutputFilter(output, own);
own = true;
}
*/
// attach the packetizing filters
input = new CInputPacketStream(input, own);
output = new COutputPacketStream(output, own);
own = true;
CServerProxy* proxy = NULL;
try {
// give handshake some time
CTimerThread timer(30.0);
// wait for hello from server
log((CLOG_DEBUG1 "wait for hello"));
SInt16 major, minor;
CProtocolUtil::readf(input, "Synergy%2i%2i", &major, &minor);
// check versions
log((CLOG_DEBUG1 "got hello version %d.%d", major, minor));
if (major < kProtocolMajorVersion ||
(major == kProtocolMajorVersion && minor < kProtocolMinorVersion)) {
throw XIncompatibleClient(major, minor);
}
// say hello back
log((CLOG_DEBUG1 "say hello version %d.%d", kProtocolMajorVersion, kProtocolMinorVersion));
CProtocolUtil::writef(output, "Synergy%2i%2i%s",
kProtocolMajorVersion,
kProtocolMinorVersion, &m_name);
// create server proxy
proxy = new CServerProxy(this, input, output);
// negotiate
// FIXME
return proxy;
}
catch (XIncompatibleClient& e) {
log((CLOG_ERR "server has incompatible version %d.%d", e.getMajor(), e.getMinor()));
}
catch (XBase& e) {
log((CLOG_WARN "error communicating with server: %s", e.what()));
}
catch (...) {
// probably timed out
if (proxy != NULL) {
delete proxy;
}
else if (own) {
delete input;
delete output;
}
throw;
}
// failed
if (proxy != NULL) {
delete proxy;
}
else if (own) {
delete input;
delete output;
}
return NULL;
}

128
lib/client/CClient.h Normal file
View File

@@ -0,0 +1,128 @@
#ifndef CCLIENT_H
#define CCLIENT_H
#include "IScreenReceiver.h"
#include "IClient.h"
#include "IClipboard.h"
#include "CNetworkAddress.h"
#include "CMutex.h"
class CSecondaryScreen;
class CServerProxy;
class CThread;
class IDataSocket;
class IScreenReceiver;
//! Synergy client
/*!
This class implements the top-level client algorithms for synergy.
*/
class CClient : public IScreenReceiver, public IClient {
public:
/*!
This client will attempt to connect the server using \c clientName
as its name.
*/
CClient(const CString& clientName);
~CClient();
//! @name manipulators
//@{
//! Set camping state
/*!
Turns camping on or off. When camping the client will keep
trying to connect to the server until it succeeds. This
is useful if the client may start before the server. Do
not call this while in mainLoop().
*/
void camp(bool on);
//! Set server address
/*!
Sets the server's address that the client should connect to.
*/
void setAddress(const CNetworkAddress& serverAddress);
//! Exit event loop
/*!
Force mainLoop() to return. This call can return before
mainLoop() does (i.e. asynchronously). This may only be
called between a successful open() and close().
*/
void exitMainLoop();
//@}
//! @name accessors
//@{
//!
/*!
Returns true if the server rejected our connection.
*/
bool wasRejected() const;
//@}
// IScreenReceiver overrides
virtual void onError();
virtual void onInfoChanged(const CClientInfo&);
virtual bool onGrabClipboard(ClipboardID);
virtual void onClipboardChanged(ClipboardID, const CString&);
// IClient overrides
virtual bool open();
virtual void mainLoop();
virtual void close();
virtual void enter(SInt32 xAbs, SInt32 yAbs,
UInt32 seqNum, KeyModifierMask mask,
bool forScreensaver);
virtual bool leave();
virtual void setClipboard(ClipboardID, const CString&);
virtual void grabClipboard(ClipboardID);
virtual void setClipboardDirty(ClipboardID, bool dirty);
virtual void keyDown(KeyID, KeyModifierMask);
virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count);
virtual void keyUp(KeyID, KeyModifierMask);
virtual void mouseDown(ButtonID);
virtual void mouseUp(ButtonID);
virtual void mouseMove(SInt32 xAbs, SInt32 yAbs);
virtual void mouseWheel(SInt32 delta);
virtual void screensaver(bool activate);
virtual CString getName() const;
virtual SInt32 getJumpZoneSize() const;
virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const;
virtual void getCursorPos(SInt32& x, SInt32& y) const;
virtual void getCursorCenter(SInt32& x, SInt32& y) const;
private:
// open/close the secondary screen
void openSecondaryScreen();
void closeSecondaryScreen();
// send the clipboard to the server
void sendClipboard(ClipboardID);
// handle server messaging
void runSession(void*);
void deleteSession(double timeout = -1.0);
void runServer();
CServerProxy* handshakeServer(IDataSocket*);
private:
CMutex m_mutex;
CString m_name;
CSecondaryScreen* m_screen;
IScreenReceiver* m_server;
CNetworkAddress m_serverAddress;
bool m_camp;
CThread* m_session;
bool m_active;
bool m_rejected;
bool m_ownClipboard[kClipboardEnd];
IClipboard::Time m_timeClipboard[kClipboardEnd];
CString m_dataClipboard[kClipboardEnd];
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
#ifndef CMSWINDOWSSECONDARYSCREEN_H
#define CMSWINDOWSSECONDARYSCREEN_H
// ensure that we get SendInput()
#if _WIN32_WINNT <= 0x400
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x401
#endif
#include "CSecondaryScreen.h"
#include "IMSWindowsScreenEventHandler.h"
#include "CMutex.h"
#include "CString.h"
#include "stdvector.h"
class CMSWindowsScreen;
class IScreenReceiver;
//! Microsoft windows secondary screen implementation
class CMSWindowsSecondaryScreen :
public CSecondaryScreen, public IMSWindowsScreenEventHandler {
public:
CMSWindowsSecondaryScreen(IScreenReceiver*);
virtual ~CMSWindowsSecondaryScreen();
// CSecondaryScreen overrides
virtual void keyDown(KeyID, KeyModifierMask);
virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count);
virtual void keyUp(KeyID, KeyModifierMask);
virtual void mouseDown(ButtonID);
virtual void mouseUp(ButtonID);
virtual void mouseMove(SInt32 xAbsolute, SInt32 yAbsolute);
virtual void mouseWheel(SInt32 delta);
virtual IScreen* getScreen() const;
// IMSWindowsScreenEventHandler overrides
virtual void onScreensaver(bool activated);
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual SInt32 getJumpZoneSize() const;
virtual void postCreateWindow(HWND);
virtual void preDestroyWindow(HWND);
protected:
// CSecondaryScreen overrides
virtual void onPreMainLoop();
virtual void onPreOpen();
virtual void onPreEnter();
virtual void onPreLeave();
virtual void createWindow();
virtual void destroyWindow();
virtual void showWindow();
virtual void hideWindow();
virtual void warpCursor(SInt32 x, SInt32 y);
virtual void updateKeys();
virtual void setToggleState(KeyModifierMask);
private:
enum EKeyAction { kPress, kRelease, kRepeat };
class Keystroke {
public:
UINT m_virtualKey;
bool m_press;
bool m_repeat;
};
typedef std::vector<Keystroke> Keystrokes;
// 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);
// returns true iff there appear to be multiple monitors
bool isMultimon() const;
// key and button queries and operations
DWORD mapButton(ButtonID button, bool press) const;
KeyModifierMask mapKey(Keystrokes&, UINT& virtualKey, KeyID,
KeyModifierMask, EKeyAction) const;
void doKeystrokes(const Keystrokes&, SInt32 count);
void releaseKeys();
void toggleKey(UINT virtualKey, KeyModifierMask mask);
UINT virtualKeyToScanCode(UINT& virtualKey);
bool isExtendedKey(UINT virtualKey);
void sendKeyEvent(UINT virtualKey, bool press);
private:
CMutex m_mutex;
CMSWindowsScreen* m_screen;
// true if windows 95/98/me
bool m_is95Family;
// our window
HWND m_window;
// virtual key states
BYTE m_keys[256];
// current active modifiers
KeyModifierMask m_mask;
};
#endif

View File

@@ -0,0 +1,260 @@
#include "CSecondaryScreen.h"
#include "IScreen.h"
#include "CLock.h"
#include "CThread.h"
#include "CLog.h"
//
// CSecondaryScreen
//
CSecondaryScreen::CSecondaryScreen()
{
// do nothing
}
CSecondaryScreen::~CSecondaryScreen()
{
// do nothing
}
void
CSecondaryScreen::mainLoop()
{
// change our priority
CThread::getCurrentThread().setPriority(-7);
// run event loop
try {
log((CLOG_DEBUG "entering event loop"));
onPreMainLoop();
getScreen()->mainLoop();
onPostMainLoop();
log((CLOG_DEBUG "exiting event loop"));
}
catch (...) {
onPostMainLoop();
log((CLOG_DEBUG "exiting event loop"));
throw;
}
}
void
CSecondaryScreen::exitMainLoop()
{
getScreen()->exitMainLoop();
}
void
CSecondaryScreen::open()
{
try {
// subclass hook
onPreOpen();
// open the screen
getScreen()->open();
// create and prepare our window
createWindow();
// assume primary has all clipboards
for (ClipboardID id = 0; id < kClipboardEnd; ++id) {
grabClipboard(id);
}
// update keyboard state
updateKeys();
// disable the screen saver
getScreen()->openScreensaver(false);
// subclass hook
onPostOpen();
}
catch (...) {
close();
throw;
}
// hide the cursor
{
CLock lock(&m_mutex);
m_active = true;
}
leave();
}
void
CSecondaryScreen::close()
{
onPreClose();
getScreen()->closeScreensaver();
destroyWindow();
getScreen()->close();
onPostClose();
}
void
CSecondaryScreen::enter(SInt32 x, SInt32 y, KeyModifierMask mask)
{
CLock lock(&m_mutex);
assert(m_active == false);
log((CLOG_INFO "entering screen at %d,%d mask=%04x", x, y, mask));
getScreen()->syncDesktop();
// now active
m_active = true;
// subclass hook
onPreEnter();
// update our keyboard state to reflect the local state
updateKeys();
// toggle modifiers that don't match the desired state
setToggleState(mask);
// warp to requested location
warpCursor(x, y);
// show mouse
hideWindow();
// subclass hook
onPostEnter();
}
void
CSecondaryScreen::leave()
{
log((CLOG_INFO "leaving screen"));
CLock lock(&m_mutex);
assert(m_active == true);
getScreen()->syncDesktop();
// subclass hook
onPreLeave();
// hide mouse
showWindow();
// subclass hook
onPostLeave();
// not active anymore
m_active = false;
// make sure our idea of clipboard ownership is correct
getScreen()->checkClipboards();
}
void
CSecondaryScreen::setClipboard(ClipboardID id,
const IClipboard* clipboard)
{
getScreen()->setClipboard(id, clipboard);
}
void
CSecondaryScreen::grabClipboard(ClipboardID id)
{
getScreen()->setClipboard(id, NULL);
}
void
CSecondaryScreen::screensaver(bool activate)
{
getScreen()->screensaver(activate);
}
bool
CSecondaryScreen::isActive() const
{
CLock lock(&m_mutex);
return m_active;
}
void
CSecondaryScreen::getClipboard(ClipboardID id,
IClipboard* clipboard) const
{
getScreen()->getClipboard(id, clipboard);
}
void
CSecondaryScreen::getShape(SInt32& x, SInt32& y, SInt32& w, SInt32& h) const
{
getScreen()->syncDesktop();
getScreen()->getShape(x, y, w, h);
}
void
CSecondaryScreen::getCursorPos(SInt32& x, SInt32& y) const
{
getScreen()->syncDesktop();
getScreen()->getCursorPos(x, y);
}
void
CSecondaryScreen::onPreMainLoop()
{
// do nothing
}
void
CSecondaryScreen::onPostMainLoop()
{
// do nothing
}
void
CSecondaryScreen::onPreOpen()
{
// do nothing
}
void
CSecondaryScreen::onPostOpen()
{
// do nothing
}
void
CSecondaryScreen::onPreClose()
{
// do nothing
}
void
CSecondaryScreen::onPostClose()
{
// do nothing
}
void
CSecondaryScreen::onPreEnter()
{
// do nothing
}
void
CSecondaryScreen::onPostEnter()
{
// do nothing
}
void
CSecondaryScreen::onPreLeave()
{
// do nothing
}
void
CSecondaryScreen::onPostLeave()
{
// do nothing
}

View File

@@ -0,0 +1,320 @@
#ifndef CSECONDARYSCREEN_H
#define CSECONDARYSCREEN_H
#include "ClipboardTypes.h"
#include "KeyTypes.h"
#include "MouseTypes.h"
#include "CMutex.h"
class IClipboard;
class IScreen;
//! Generic client-side screen
/*!
This is a platform independent base class for secondary screen
implementations. A secondary screen is a client-side screen.
Each platform will derive a class from CSecondaryScreen to handle
platform dependent operations.
*/
class CSecondaryScreen {
public:
CSecondaryScreen();
virtual ~CSecondaryScreen();
//! @name manipulators
//@{
//! Open screen
/*!
Opens the screen. This includes initializing the screen,
hiding the cursor, and disabling the screen saver. It also causes
events to the reported to an IScreenReceiver (which is set through
some other interface). Calls close() before returning (rethrowing)
if it fails for any reason.
*/
void open();
//! Run event loop
/*!
Run the screen's event loop. This returns when it detects
the application should terminate or when exitMainLoop() is called.
mainLoop() may only be called between open() and close().
*/
void mainLoop();
//! Exit event loop
/*!
Force mainLoop() to return. This call can return before
mainLoop() does (i.e. asynchronously).
*/
void exitMainLoop();
//! Close screen
/*!
Closes the screen. This restores the screen saver, shows the cursor
and closes the screen. It also synthesizes key up events for any
keys that are logically down; without this the client will leave
its keyboard in the wrong logical state.
*/
void close();
//! Enter screen
/*!
Called when the user navigates to this secondary screen. Warps
the cursor to the absolute coordinates \c x,y and unhides
it. Also prepares to synthesize input events.
*/
void enter(SInt32 x, SInt32 y, KeyModifierMask mask);
//! Leave screen
/*!
Called when the user navigates off the secondary screen. Cleans
up input event synthesis and hides the cursor.
*/
void leave();
//! Set clipboard
/*!
Sets the system's clipboard contents. This is usually called
soon after an enter().
*/
void setClipboard(ClipboardID, const IClipboard*);
//! Grab clipboard
/*!
Grabs (i.e. take ownership of) the system clipboard.
*/
void grabClipboard(ClipboardID);
//! Activate/deactivate screen saver
/*!
Forcibly activates the screen saver if \c activate is true otherwise
forcibly deactivates it.
*/
void screensaver(bool activate);
//! Notify of key press
/*!
Synthesize key events to generate a press of key \c id. If possible
match the given modifier mask.
*/
virtual void keyDown(KeyID id, KeyModifierMask) = 0;
//! Notify of key repeat
/*!
Synthesize key events to generate a press and release of key \c id
\c count times. If possible match the given modifier mask.
*/
virtual void keyRepeat(KeyID id, KeyModifierMask, SInt32 count) = 0;
//! Notify of key release
/*!
Synthesize key events to generate a release of key \c id. If possible
match the given modifier mask.
*/
virtual void keyUp(KeyID id, KeyModifierMask) = 0;
//! Notify of mouse press
/*!
Synthesize mouse events to generate a press of mouse button \c id.
*/
virtual void mouseDown(ButtonID id) = 0;
//! Notify of mouse release
/*!
Synthesize mouse events to generate a release of mouse button \c id.
*/
virtual void mouseUp(ButtonID id) = 0;
//! Notify of mouse motion
/*!
Synthesize mouse events to generate mouse motion to the absolute
screen position \c xAbs,yAbs.
*/
virtual void mouseMove(SInt32 xAbs, SInt32 yAbs) = 0;
//! Notify of mouse wheel motion
/*!
Synthesize mouse events to generate mouse wheel motion of \c delta.
\c delta is positive for motion away from the user and negative for
motion towards the user. Each wheel click should generate a delta
of +/-120.
*/
virtual void mouseWheel(SInt32 delta) = 0;
//@}
//! @name accessors
//@{
//! Test if active
/*!
Returns true iff the screen is active (i.e. the user has entered
the screen). Note this is the reverse of a primary screen.
*/
bool isActive() const;
//! Get clipboard
/*!
Saves the contents of the system clipboard indicated by \c id.
*/
void getClipboard(ClipboardID id, IClipboard*) const;
//! Get jump zone size
/*!
Return the jump zone size, the size of the regions on the edges of
the screen that cause the cursor to jump to another screen.
*/
virtual SInt32 getJumpZoneSize() const = 0;
//! Get screen shape
/*!
Return the position of the upper-left corner of the screen in \c x and
\c y and the size of the screen in \c width and \c height.
*/
virtual void getShape(SInt32& x, SInt32& y,
SInt32& width, SInt32& height) const;
//! Get cursor position
/*!
Return the current position of the cursor in \c x,y.
*/
virtual void getCursorPos(SInt32& x, SInt32& y) const;
//! Get screen
/*!
Return the platform dependent screen.
*/
virtual IScreen* getScreen() const = 0;
//@}
protected:
//! Pre-mainLoop() hook
/*!
Called on entry to mainLoop(). Override to perform platform specific
operations. Default does nothing. May throw.
*/
virtual void onPreMainLoop();
//! Post-mainLoop() hook
/*!
Called on exit from mainLoop(). Override to perform platform specific
operations. Default does nothing. May \b not throw.
*/
virtual void onPostMainLoop();
//! Pre-open() hook
/*!
Called on entry to open(). Override to perform platform specific
operations. Default does nothing. May throw.
*/
virtual void onPreOpen();
//! Post-open() hook
/*!
Called on exit from open() iff the open was successful. Default
does nothing. May throw.
*/
virtual void onPostOpen();
//! Pre-close() hook
/*!
Called on entry to close(). Override to perform platform specific
operations. Default does nothing. May \b not throw.
*/
virtual void onPreClose();
//! Post-close() hook
/*!
Called on exit from close(). Override to perform platform specific
operations. Default does nothing. May \b not throw.
*/
virtual void onPostClose();
//! Pre-enter() hook
/*!
Called on entry to enter() after desktop synchronization. Override
to perform platform specific operations. Default does nothing. May
\b not throw.
*/
virtual void onPreEnter();
//! Post-enter() hook
/*!
Called on exit from enter(). Override to perform platform specific
operations. Default does nothing. May \b not throw.
*/
virtual void onPostEnter();
//! Pre-leave() hook
/*!
Called on entry to leave() after desktop synchronization. Override
to perform platform specific operations. Default does nothing. May
\b not throw.
*/
virtual void onPreLeave();
//! Post-leave() hook
/*!
Called on exit from leave(). Override to perform platform specific
operations. Default does nothing. May \b not throw.
*/
virtual void onPostLeave();
//! Create window
/*!
Called to create the window. This window is generally used to
receive events and hide the cursor.
*/
virtual void createWindow() = 0;
//! Destroy window
/*!
Called to destroy the window created by createWindow().
*/
virtual void destroyWindow() = 0;
//! Show window
/*!
Called when the user navigates off this secondary screen. It needn't
actually show the window created by createWindow() but it must hide
the cursor and clean up event synthesis.
*/
virtual void showWindow() = 0;
//! Hide window
/*!
Called when the user navigates to this secondary screen. It should
hide the window (if shown), show the cursor, and prepare to synthesize
input events.
*/
virtual void hideWindow() = 0;
//! Warp cursor
/*!
Warp the cursor to the absolute coordinates \c x,y.
*/
virtual void warpCursor(SInt32 x, SInt32 y) = 0;
//! Synchronize key state
/*!
Check the current keyboard state. Normally a screen will save
the keyboard state in this method and use this shadow state
when synthesizing events.
*/
virtual void updateKeys() = 0;
//! Synchronize toggle key state
/*!
Toggle modifiers that don't match the given state so that they do.
*/
virtual void setToggleState(KeyModifierMask) = 0;
private:
CMutex m_mutex;
// m_active is true if this screen has been entered
bool m_active;
};
#endif

527
lib/client/CServerProxy.cpp Normal file
View File

@@ -0,0 +1,527 @@
#include "CServerProxy.h"
#include "CProtocolUtil.h"
#include "IClient.h"
#include "ProtocolTypes.h"
#include "IInputStream.h"
#include "IOutputStream.h"
#include "CLock.h"
#include "CLog.h"
#include "CStopwatch.h"
#include "XBase.h"
#include <memory>
//
// CServerProxy
//
CServerProxy::CServerProxy(IClient* client,
IInputStream* adoptedInput, IOutputStream* adoptedOutput) :
m_client(client),
m_input(adoptedInput),
m_output(adoptedOutput),
m_seqNum(0)
{
assert(m_client != NULL);
assert(m_input != NULL);
assert(m_output != NULL);
}
CServerProxy::~CServerProxy()
{
delete m_input;
delete m_output;
}
bool
CServerProxy::mainLoop()
{
bool failedToConnect = false;
try {
// no compressed mouse motion yet
m_compressMouse = false;
// not ignoring mouse motions
m_ignoreMouse = false;
// reset sequence number
m_seqNum = 0;
// handle messages from server
CStopwatch heartbeat;
for (;;) {
// if no input is pending then flush compressed mouse motion
if (getInputStream()->getSize() == 0) {
flushCompressedMouse();
}
// wait for a message
log((CLOG_DEBUG2 "waiting for message"));
UInt8 code[4];
UInt32 n = getInputStream()->read(code, 4, kHeartRate);
// check if server hungup
if (n == 0) {
log((CLOG_NOTE "server disconnected"));
break;
}
// check for time out
if (n == (UInt32)-1 || heartbeat.getTime() > kHeartRate) {
// send heartbeat
CLock lock(&m_mutex);
CProtocolUtil::writef(getOutputStream(), kMsgCNoop);
heartbeat.reset();
if (n == (UInt32)-1) {
// no message to process
continue;
}
}
// verify we got an entire code
if (n != 4) {
// client sent an incomplete message
log((CLOG_ERR "incomplete message from server"));
break;
}
// parse message
log((CLOG_DEBUG2 "msg from server: %c%c%c%c", code[0], code[1], code[2], code[3]));
if (memcmp(code, kMsgDMouseMove, 4) == 0) {
mouseMove();
}
else if (memcmp(code, kMsgDMouseWheel, 4) == 0) {
mouseWheel();
}
else if (memcmp(code, kMsgDKeyDown, 4) == 0) {
keyDown();
}
else if (memcmp(code, kMsgDKeyUp, 4) == 0) {
keyUp();
}
else if (memcmp(code, kMsgDMouseDown, 4) == 0) {
mouseDown();
}
else if (memcmp(code, kMsgDMouseUp, 4) == 0) {
mouseUp();
}
else if (memcmp(code, kMsgDKeyRepeat, 4) == 0) {
keyRepeat();
}
else if (memcmp(code, kMsgCNoop, 4) == 0) {
// accept and discard no-op
}
else if (memcmp(code, kMsgCEnter, 4) == 0) {
enter();
}
else if (memcmp(code, kMsgCLeave, 4) == 0) {
leave();
}
else if (memcmp(code, kMsgCClipboard, 4) == 0) {
grabClipboard();
}
else if (memcmp(code, kMsgCScreenSaver, 4) == 0) {
screensaver();
}
else if (memcmp(code, kMsgQInfo, 4) == 0) {
queryInfo();
}
else if (memcmp(code, kMsgCInfoAck, 4) == 0) {
infoAcknowledgment();
}
else if (memcmp(code, kMsgDClipboard, 4) == 0) {
setClipboard();
}
else if (memcmp(code, kMsgCClose, 4) == 0) {
// server wants us to hangup
log((CLOG_DEBUG1 "recv close"));
break;
}
else if (memcmp(code, kMsgEIncompatible, 4) == 0) {
SInt32 major, minor;
CProtocolUtil::readf(getInputStream(),
kMsgEIncompatible + 4, &major, &minor);
log((CLOG_ERR "server has incompatible version %d.%d", major, minor));
failedToConnect = true;
break;
}
else if (memcmp(code, kMsgEBusy, 4) == 0) {
log((CLOG_ERR "server already has a connected client with name \"%s\"", getName().c_str()));
failedToConnect = true;
break;
}
else if (memcmp(code, kMsgEUnknown, 4) == 0) {
log((CLOG_ERR "server refused client with name \"%s\"", getName().c_str()));
failedToConnect = true;
break;
}
else if (memcmp(code, kMsgEBad, 4) == 0) {
log((CLOG_ERR "server disconnected due to a protocol error"));
failedToConnect = true;
break;
}
else {
// unknown message
log((CLOG_ERR "unknown message from server"));
failedToConnect = true;
break;
}
}
}
catch (XBase& e) {
log((CLOG_ERR "error: %s", e.what()));
}
catch (...) {
throw;
}
return !failedToConnect;
}
IClient*
CServerProxy::getClient() const
{
return m_client;
}
CString
CServerProxy::getName() const
{
return m_client->getName();
}
IInputStream*
CServerProxy::getInputStream() const
{
return m_input;
}
IOutputStream*
CServerProxy::getOutputStream() const
{
return m_output;
}
void
CServerProxy::onError()
{
// ignore
}
void
CServerProxy::onInfoChanged(const CClientInfo& info)
{
// ignore mouse motion until we receive acknowledgment of our info
// change message.
CLock lock(&m_mutex);
m_ignoreMouse = true;
// send info update
sendInfo(info);
}
bool
CServerProxy::onGrabClipboard(ClipboardID id)
{
log((CLOG_DEBUG1 "sending clipboard %d changed", id));
CLock lock(&m_mutex);
CProtocolUtil::writef(getOutputStream(), kMsgCClipboard, id, m_seqNum);
return true;
}
void
CServerProxy::onClipboardChanged(ClipboardID id, const CString& data)
{
CLock lock(&m_mutex);
log((CLOG_DEBUG1 "sending clipboard %d seqnum=%d, size=%d", id, m_seqNum, data.size()));
CProtocolUtil::writef(getOutputStream(), kMsgDClipboard, id, m_seqNum, &data);
}
void
CServerProxy::flushCompressedMouse()
{
bool send = false;
SInt32 x = 0, y = 0;
{
CLock lock(&m_mutex);
if (m_compressMouse) {
m_compressMouse = false;
x = m_xMouse;
y = m_yMouse;
send = true;
}
}
if (send) {
getClient()->mouseMove(x, y);
}
}
void
CServerProxy::sendInfo(const CClientInfo& info)
{
// note -- m_mutex should be locked on entry
log((CLOG_DEBUG1 "sending info shape=%d,%d %dx%d zone=%d pos=%d,%d", info.m_x, info.m_y, info.m_w, info.m_h, info.m_zoneSize, info.m_mx, info.m_my));
CProtocolUtil::writef(getOutputStream(), kMsgDInfo,
info.m_x, info.m_y,
info.m_w, info.m_h,
info.m_zoneSize,
info.m_mx, info.m_my);
}
void
CServerProxy::enter()
{
// parse
SInt16 x, y;
UInt16 mask;
UInt32 seqNum;
CProtocolUtil::readf(getInputStream(),
kMsgCEnter + 4, &x, &y, &seqNum, &mask);
log((CLOG_DEBUG1 "recv enter, %d,%d %d %04x", x, y, seqNum, mask));
// discard old compressed mouse motion, if any
{
CLock lock(&m_mutex);
m_compressMouse = false;
m_seqNum = seqNum;
}
// forward
getClient()->enter(x, y, seqNum, static_cast<KeyModifierMask>(mask), false);
}
void
CServerProxy::leave()
{
// parse
log((CLOG_DEBUG1 "recv leave"));
// send last mouse motion
flushCompressedMouse();
// forward
getClient()->leave();
}
void
CServerProxy::setClipboard()
{
// parse
ClipboardID id;
UInt32 seqNum;
CString data;
CProtocolUtil::readf(getInputStream(),
kMsgDClipboard + 4, &id, &seqNum, &data);
log((CLOG_DEBUG "recv clipboard %d size=%d", id, data.size()));
// validate
if (id >= kClipboardEnd) {
return;
}
// forward
getClient()->setClipboard(id, data);
}
void
CServerProxy::grabClipboard()
{
// parse
ClipboardID id;
UInt32 seqNum;
CProtocolUtil::readf(getInputStream(), kMsgCClipboard + 4, &id, &seqNum);
log((CLOG_DEBUG "recv grab clipboard %d", id));
// validate
if (id >= kClipboardEnd) {
return;
}
// forward
getClient()->grabClipboard(id);
}
void
CServerProxy::keyDown()
{
// get mouse up to date
flushCompressedMouse();
// parse
UInt16 id, mask;
CProtocolUtil::readf(getInputStream(), kMsgDKeyDown + 4, &id, &mask);
log((CLOG_DEBUG1 "recv key down id=%d, mask=0x%04x", id, mask));
// forward
getClient()->keyDown(static_cast<KeyID>(id),
static_cast<KeyModifierMask>(mask));
}
void
CServerProxy::keyRepeat()
{
// get mouse up to date
flushCompressedMouse();
// parse
UInt16 id, mask, count;
CProtocolUtil::readf(getInputStream(),
kMsgDKeyRepeat + 4, &id, &mask, &count);
log((CLOG_DEBUG1 "recv key repeat id=%d, mask=0x%04x, count=%d", id, mask, count));
// forward
getClient()->keyRepeat(static_cast<KeyID>(id),
static_cast<KeyModifierMask>(mask),
count);
}
void
CServerProxy::keyUp()
{
// get mouse up to date
flushCompressedMouse();
// parse
UInt16 id, mask;
CProtocolUtil::readf(getInputStream(), kMsgDKeyUp + 4, &id, &mask);
log((CLOG_DEBUG1 "recv key up id=%d, mask=0x%04x", id, mask));
// forward
getClient()->keyUp(static_cast<KeyID>(id),
static_cast<KeyModifierMask>(mask));
}
void
CServerProxy::mouseDown()
{
// get mouse up to date
flushCompressedMouse();
// parse
SInt8 id;
CProtocolUtil::readf(getInputStream(), kMsgDMouseDown + 4, &id);
log((CLOG_DEBUG1 "recv mouse down id=%d", id));
// forward
getClient()->mouseDown(static_cast<ButtonID>(id));
}
void
CServerProxy::mouseUp()
{
// get mouse up to date
flushCompressedMouse();
// parse
SInt8 id;
CProtocolUtil::readf(getInputStream(), kMsgDMouseUp + 4, &id);
log((CLOG_DEBUG1 "recv mouse up id=%d", id));
// forward
getClient()->mouseUp(static_cast<ButtonID>(id));
}
void
CServerProxy::mouseMove()
{
// parse
bool ignore;
SInt16 x, y;
CProtocolUtil::readf(getInputStream(), kMsgDMouseMove + 4, &x, &y);
{
// note if we should ignore the move
CLock lock(&m_mutex);
ignore = m_ignoreMouse;
// compress mouse motion events if more input follows
if (!ignore && !m_compressMouse && getInputStream()->getSize() > 0) {
m_compressMouse = true;
}
// if compressing then ignore the motion but record it
if (m_compressMouse) {
ignore = true;
m_xMouse = x;
m_yMouse = y;
}
}
log((CLOG_DEBUG2 "recv mouse move %d,%d", x, y));
// forward
if (!ignore) {
getClient()->mouseMove(x, y);
}
}
void
CServerProxy::mouseWheel()
{
// get mouse up to date
flushCompressedMouse();
// parse
SInt16 delta;
CProtocolUtil::readf(getInputStream(), kMsgDMouseWheel + 4, &delta);
log((CLOG_DEBUG2 "recv mouse wheel %+d", delta));
// forward
getClient()->mouseWheel(delta);
}
void
CServerProxy::screensaver()
{
// parse
SInt8 on;
CProtocolUtil::readf(getInputStream(), kMsgCScreenSaver + 4, &on);
log((CLOG_DEBUG1 "recv screen saver on=%d", on));
// forward
getClient()->screensaver(on != 0);
}
void
CServerProxy::queryInfo()
{
// get current info
CClientInfo info;
getClient()->getShape(info.m_x, info.m_y, info.m_w, info.m_h);
getClient()->getCursorPos(info.m_mx, info.m_my);
info.m_zoneSize = getClient()->getJumpZoneSize();
// send it
CLock lock(&m_mutex);
sendInfo(info);
}
void
CServerProxy::infoAcknowledgment()
{
// parse
log((CLOG_DEBUG1 "recv info acknowledgment"));
// now allow mouse motion
CLock lock(&m_mutex);
m_ignoreMouse = false;
}

112
lib/client/CServerProxy.h Normal file
View File

@@ -0,0 +1,112 @@
#ifndef CSERVERPROXY_H
#define CSERVERPROXY_H
#include "IScreenReceiver.h"
#include "CMutex.h"
class IClient;
class IInputStream;
class IOutputStream;
//! Proxy for server
/*!
This class acts a proxy for the server, converting calls into messages
to the server and messages from the server to calls on the client.
*/
class CServerProxy : public IScreenReceiver {
public:
/*! \c adoptedInput is the stream from the server and
\c adoptedOutput is the stream to the server. This object
takes ownership of both and destroys them in the d'tor.
Messages from the server are converted to calls on \c client.
*/
CServerProxy(IClient* client,
IInputStream* adoptedInput,
IOutputStream* adoptedOutput);
~CServerProxy();
//! @name manipulators
//@{
//! Run event loop
/*!
Run the event loop and return when the server disconnects or
requests the client to disconnect. Return true iff the server
didn't reject our connection.
(cancellation point)
*/
bool mainLoop();
//@}
//! @name accessors
//@{
//! Get client
/*!
Returns the client passed to the c'tor.
*/
IClient* getClient() const;
//! Get input stream
/*!
Return the input stream passed to the c'tor.
*/
IInputStream* getInputStream() const;
//! Get output stream
/*!
Return the output stream passed to the c'tor.
*/
IOutputStream* getOutputStream() const;
//@}
// IScreenReceiver overrides
virtual void onError();
virtual void onInfoChanged(const CClientInfo&);
virtual bool onGrabClipboard(ClipboardID);
virtual void onClipboardChanged(ClipboardID, const CString& data);
private:
// get the client name (from the client)
CString getName() const;
// if compressing mouse motion then send the last motion now
void flushCompressedMouse();
void sendInfo(const CClientInfo&);
// message handlers
void enter();
void leave();
void setClipboard();
void grabClipboard();
void keyDown();
void keyRepeat();
void keyUp();
void mouseDown();
void mouseUp();
void mouseMove();
void mouseWheel();
void screensaver();
void queryInfo();
void infoAcknowledgment();
private:
CMutex m_mutex;
IClient* m_client;
IInputStream* m_input;
IOutputStream* m_output;
UInt32 m_seqNum;
bool m_compressMouse;
SInt32 m_xMouse, m_yMouse;
bool m_ignoreMouse;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,128 @@
#ifndef CXWINDOWSSECONDARYSCREEN_H
#define CXWINDOWSSECONDARYSCREEN_H
#include "CSecondaryScreen.h"
#include "IScreenEventHandler.h"
#include "stdmap.h"
#include "stdvector.h"
#if defined(X_DISPLAY_MISSING)
# error X11 is required to build synergy
#else
# include <X11/Xlib.h>
#endif
class CXWindowsScreen;
class IScreenReceiver;
//! X11 secondary screen implementation
class CXWindowsSecondaryScreen :
public CSecondaryScreen, public IScreenEventHandler {
public:
CXWindowsSecondaryScreen(IScreenReceiver*);
virtual ~CXWindowsSecondaryScreen();
// CSecondaryScreen overrides
virtual void keyDown(KeyID, KeyModifierMask);
virtual void keyRepeat(KeyID, KeyModifierMask, SInt32 count);
virtual void keyUp(KeyID, KeyModifierMask);
virtual void mouseDown(ButtonID);
virtual void mouseUp(ButtonID);
virtual void mouseMove(SInt32 x, SInt32 y);
virtual void mouseWheel(SInt32 delta);
virtual IScreen* getScreen() const;
// IScreenEventHandler overrides
virtual void onScreensaver(bool activated);
virtual bool onPreDispatch(const CEvent* event);
virtual bool onEvent(CEvent* event);
virtual SInt32 getJumpZoneSize() const;
protected:
// CSecondaryScreen overrides
virtual void onPreMainLoop();
virtual void onPreOpen();
virtual void onPostOpen();
virtual void onPreEnter();
virtual void onPreLeave();
virtual void createWindow();
virtual void destroyWindow();
virtual void showWindow();
virtual void hideWindow();
virtual void warpCursor(SInt32 x, SInt32 y);
virtual void updateKeys();
virtual void setToggleState(KeyModifierMask);
private:
enum EKeyAction { kPress, kRelease, kRepeat };
class KeyCodeMask {
public:
KeyCode m_keycode;
unsigned int m_keyMask;
unsigned int m_keyMaskMask;
};
class Keystroke {
public:
KeyCode m_keycode;
Bool m_press;
bool m_repeat;
};
typedef std::vector<Keystroke> Keystrokes;
typedef std::vector<KeyCode> KeyCodes;
typedef std::map<KeyID, KeyCodeMask> KeyCodeMap;
typedef std::map<KeyCode, unsigned int> ModifierMap;
unsigned int mapButton(ButtonID button) const;
unsigned int mapKey(Keystrokes&, KeyCode&, KeyID,
KeyModifierMask, EKeyAction) const;
bool findKeyCode(KeyCode&, unsigned int&,
KeyID id, unsigned int) const;
void doKeystrokes(const Keystrokes&, SInt32 count);
unsigned int maskToX(KeyModifierMask) const;
void releaseKeys(Display*);
void updateKeycodeMap(Display* display);
void updateModifiers(Display* display);
void updateModifierMap(Display* display);
void toggleKey(Display*, KeySym, unsigned int mask);
static bool isToggleKeysym(KeySym);
private:
CXWindowsScreen* m_screen;
Window m_window;
// note toggle keys that toggles on up/down (false) or on
// transition (true)
bool m_numLockHalfDuplex;
bool m_capsLockHalfDuplex;
// set entries indicate keys that are pressed. indexed by keycode.
bool m_keys[256];
// current active modifiers (X key masks)
unsigned int m_mask;
// maps key IDs to X keycodes and the X modifier key mask needed
// to generate the right keysym
KeyCodeMap m_keycodeMap;
// the modifiers that have keys bound to them
unsigned int m_modifierMask;
// set bits indicate modifiers that toggle (e.g. caps-lock)
unsigned int m_toggleModifierMask;
// masks that indicate which modifier bits are for toggle keys
unsigned int m_numLockMask;
unsigned int m_capsLockMask;
unsigned int m_scrollLockMask;
// map X modifier key indices to the key codes bound to them
unsigned int m_keysPerModifier;
KeyCodes m_modifierToKeycode;
// maps keycodes to modifier indices
ModifierMap m_keycodeToModifier;
};
#endif

23
lib/client/Makefile.am Normal file
View File

@@ -0,0 +1,23 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ../..
noinst_LIBRARIES = libclient.a
libclient_a_SOURCES = \
CClient.cpp \
CSecondaryScreen.cpp \
CServerProxy.cpp \
CXWindowsSecondaryScreen.cpp \
CClient.h \
CSecondaryScreen.h \
CServerProxy.h \
CXWindowsSecondaryScreen.h \
$(NULL)
INCLUDES = \
-I$(DEPTH)/lib/base \
-I$(DEPTH)/lib/mt \
-I$(DEPTH)/lib/io \
-I$(DEPTH)/lib/net \
-I$(DEPTH)/lib/synergy \
-I$(DEPTH)/lib/platform \
$(NULL)

644
lib/http/CHTTPProtocol.cpp Normal file
View File

@@ -0,0 +1,644 @@
#include "CHTTPProtocol.h"
#include "XHTTP.h"
#include "IInputStream.h"
#include "IOutputStream.h"
#include "CLog.h"
#include "stdsstream.h"
#include <clocale>
#include <ctime>
#include <algorithm>
//
// CHTTPRequest
//
CHTTPRequest::CHTTPRequest()
{
// do nothing
}
CHTTPRequest::~CHTTPRequest()
{
// do nothing
}
void
CHTTPRequest::insertHeader(const CString& name, const CString& value)
{
CHeaderMap::iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
index->second->second = value;
}
else {
CHeaderList::iterator pos = m_headers.insert(
m_headers.end(), std::make_pair(name, value));
m_headerByName.insert(std::make_pair(name, pos));
}
}
void
CHTTPRequest::appendHeader(const CString& name, const CString& value)
{
CHeaderMap::iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
index->second->second += ",";
index->second->second += value;
}
else {
CHeaderList::iterator pos = m_headers.insert(
m_headers.end(), std::make_pair(name, value));
m_headerByName.insert(std::make_pair(name, pos));
}
}
void
CHTTPRequest::eraseHeader(const CString& name)
{
CHeaderMap::iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
m_headers.erase(index->second);
}
}
bool
CHTTPRequest::isHeader(const CString& name) const
{
return (m_headerByName.find(name) != m_headerByName.end());
}
CString
CHTTPRequest::getHeader(const CString& name) const
{
CHeaderMap::const_iterator index = m_headerByName.find(name);
if (index != m_headerByName.end()) {
return index->second->second;
}
else {
return CString();
}
}
//
// CHTTPProtocol
//
CHTTPRequest*
CHTTPProtocol::readRequest(IInputStream* stream, UInt32 maxSize)
{
CString scratch;
// note if we should limit the request size
const bool checkSize = (maxSize > 0);
// parse request line by line
CHTTPRequest* request = new CHTTPRequest;
try {
CString line;
// read request line. accept and discard leading empty lines.
do {
line = readLine(stream, scratch);
if (checkSize) {
if (line.size() + 2 > maxSize) {
throw XHTTP(413);
}
maxSize -= line.size() + 2;
}
} while (line.empty());
// parse request line: <method> <uri> <version>
{
std::istringstream s(line);
s.exceptions(std::ios::goodbit);
CString version;
s >> request->m_method >> request->m_uri >> version;
if (!s || request->m_uri.empty() || version.find("HTTP/") != 0) {
log((CLOG_DEBUG1 "failed to parse HTTP request line: %s", line.c_str()));
throw XHTTP(400);
}
// parse version
char dot;
s.str(version);
s.clear();
s.ignore(5);
s >> request->m_majorVersion;
s.get(dot);
s >> request->m_minorVersion;
if (!s || dot != '.') {
log((CLOG_DEBUG1 "failed to parse HTTP request line: %s", line.c_str()));
throw XHTTP(400);
}
}
if (!isValidToken(request->m_method)) {
log((CLOG_DEBUG1 "invalid HTTP method: %s", line.c_str()));
throw XHTTP(400);
}
if (request->m_majorVersion < 1 || request->m_minorVersion < 0) {
log((CLOG_DEBUG1 "invalid HTTP version: %s", line.c_str()));
throw XHTTP(400);
}
// parse headers
readHeaders(stream, request, false, scratch,
checkSize ? &maxSize : NULL);
// HTTP/1.1 requests must have a Host header
if (request->m_majorVersion > 1 ||
(request->m_majorVersion == 1 && request->m_minorVersion >= 1)) {
if (request->isHeader("Host") == 0) {
log((CLOG_DEBUG1 "Host header missing"));
throw XHTTP(400);
}
}
// some methods may not have a body. ensure that the headers
// that indicate the body length do not exist for those methods
// and do exist for others.
if ((request->isHeader("Transfer-Encoding") ||
request->isHeader("Content-Length")) ==
(request->m_method == "GET" ||
request->m_method == "HEAD")) {
log((CLOG_DEBUG1 "HTTP method (%s)/body mismatch", request->m_method.c_str()));
throw XHTTP(400);
}
// prepare to read the body. the length of the body is
// determined using, in order:
// 1. Transfer-Encoding indicates a "chunked" transfer
// 2. Content-Length is present
// Content-Length is ignored for "chunked" transfers.
CString header;
if (!(header = request->getHeader("Transfer-Encoding")).empty()) {
// we only understand "chunked" encodings
if (!CStringUtil::CaselessCmp::equal(header, "chunked")) {
log((CLOG_DEBUG1 "unsupported Transfer-Encoding %s", header.c_str()));
throw XHTTP(501);
}
// chunked encoding
UInt32 oldSize;
do {
oldSize = request->m_body.size();
request->m_body += readChunk(stream, scratch,
checkSize ? &maxSize : NULL);
} while (request->m_body.size() != oldSize);
// read footer
readHeaders(stream, request, true, scratch,
checkSize ? &maxSize : NULL);
// remove "chunked" from Transfer-Encoding and set the
// Content-Length.
std::ostringstream s;
s << std::dec << request->m_body.size();
request->eraseHeader("Transfer-Encoding");
request->insertHeader("Content-Length", s.str());
}
else if (!(header = request->getHeader("Content-Length")).empty()) {
// parse content-length
UInt32 length;
{
std::istringstream s(header);
s.exceptions(std::ios::goodbit);
s >> length;
if (!s) {
log((CLOG_DEBUG1 "cannot parse Content-Length", header.c_str()));
throw XHTTP(400);
}
}
// check against expected size
if (checkSize && length > maxSize) {
throw XHTTP(413);
}
// use content length
request->m_body = readBlock(stream, length, scratch);
if (request->m_body.size() != length) {
// length must match size of body
log((CLOG_DEBUG1 "Content-Length/actual length mismatch (%d vs %d)", length, request->m_body.size()));
throw XHTTP(400);
}
}
}
catch (...) {
delete request;
throw;
}
return request;
}
void
CHTTPProtocol::reply(IOutputStream* stream, CHTTPReply& reply)
{
// suppress body for certain replies
bool hasBody = true;
if ((reply.m_status / 100) == 1 ||
reply.m_status == 204 ||
reply.m_status == 304) {
hasBody = false;
}
// adjust headers
for (CHTTPReply::CHeaderList::iterator
index = reply.m_headers.begin();
index != reply.m_headers.end(); ) {
const CString& header = index->first;
// remove certain headers
if (CStringUtil::CaselessCmp::equal(header, "Content-Length") ||
CStringUtil::CaselessCmp::equal(header, "Date") ||
CStringUtil::CaselessCmp::equal(header, "Transfer-Encoding")) {
// FIXME -- Transfer-Encoding should be left as-is if
// not "chunked" and if the version is 1.1 or up.
index = reply.m_headers.erase(index);
}
// keep as-is
else {
++index;
}
}
// write reply header
std::ostringstream s;
s << "HTTP/" << reply.m_majorVersion << "." <<
reply.m_minorVersion << " " <<
reply.m_status << " " <<
reply.m_reason << "\r\n";
// get date
// FIXME -- should use C++ locale stuff but VC++ time_put is broken.
// FIXME -- double check that VC++ is broken
char date[30];
{
const char* oldLocale = setlocale(LC_TIME, "C");
time_t t = time(NULL);
#if HAVE_GMTIME_R
struct tm tm;
struct tm* tmp = &tm;
gmtime_r(&t, tmp);
#else
struct tm* tmp = gmtime(&t);
#endif
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", tmp);
setlocale(LC_TIME, oldLocale);
}
// write headers
s << "Date: " << date << "\r\n";
for (CHTTPReply::CHeaderList::const_iterator
index = reply.m_headers.begin();
index != reply.m_headers.end(); ++index) {
s << index->first << ": " << index->second << "\r\n";
}
if (hasBody) {
s << "Content-Length: " << reply.m_body.size() << "\r\n";
}
s << "Connection: close\r\n";
// write end of headers
s << "\r\n";
// write to stream
stream->write(s.str().data(), s.str().size());
// write body. replies to HEAD method never have a body (though
// they do have the Content-Length header).
if (hasBody && reply.m_method != "HEAD") {
stream->write(reply.m_body.data(), reply.m_body.size());
}
}
bool
CHTTPProtocol::parseFormData(const CHTTPRequest& request, CFormParts& parts)
{
static const char formData[] = "multipart/form-data";
static const char boundary[] = "boundary=";
static const char disposition[] = "Content-Disposition:";
static const char nameAttr[] = "name=";
static const char quote[] = "\"";
// find the Content-Type header
const CString contentType = request.getHeader("Content-Type");
if (contentType.empty()) {
// missing required Content-Type header
return false;
}
// parse type
CString::const_iterator index = std::search(
contentType.begin(), contentType.end(),
formData, formData + sizeof(formData) - 1,
CStringUtil::CaselessCmp::cmpEqual);
if (index == contentType.end()) {
// not form-data
return false;
}
index += sizeof(formData) - 1;
index = std::search(index, contentType.end(),
boundary, boundary + sizeof(boundary) - 1,
CStringUtil::CaselessCmp::cmpEqual);
if (index == contentType.end()) {
// no boundary
return false;
}
CString delimiter = contentType.c_str() +
(index - contentType.begin()) +
sizeof(boundary) - 1;
// find first delimiter
const CString& body = request.m_body;
CString::size_type partIndex = body.find(delimiter);
if (partIndex == CString::npos) {
return false;
}
// skip over it
partIndex += delimiter.size();
// prepend CRLF-- to delimiter
delimiter = "\r\n--" + delimiter;
// parse parts until there are no more
for (;;) {
// is it the last part?
if (body.size() >= partIndex + 2 &&
body[partIndex ] == '-' &&
body[partIndex + 1] == '-') {
// found last part. ignore trailing data, if any.
return true;
}
// find the end of this part
CString::size_type nextPart = body.find(delimiter, partIndex);
if (nextPart == CString::npos) {
// no terminator
return false;
}
// find end of headers
CString::size_type endOfHeaders = body.find("\r\n\r\n", partIndex);
if (endOfHeaders == CString::npos || endOfHeaders > nextPart) {
// bad part
return false;
}
endOfHeaders += 2;
// now find Content-Disposition
index = std::search(body.begin() + partIndex,
body.begin() + endOfHeaders,
disposition,
disposition + sizeof(disposition) - 1,
CStringUtil::CaselessCmp::cmpEqual);
if (index == contentType.begin() + endOfHeaders) {
// bad part
return false;
}
// find the name in the Content-Disposition
CString::size_type endOfHeader = body.find("\r\n",
index - body.begin());
if (endOfHeader >= endOfHeaders) {
// bad part
return false;
}
index = std::search(index, body.begin() + endOfHeader,
nameAttr, nameAttr + sizeof(nameAttr) - 1,
CStringUtil::CaselessCmp::cmpEqual);
if (index == body.begin() + endOfHeader) {
// no name
return false;
}
// extract the name
CString name;
index += sizeof(nameAttr) - 1;
if (*index == quote[0]) {
// quoted name
++index;
CString::size_type namePos = index - body.begin();
index = std::search(index, body.begin() + endOfHeader,
quote, quote + 1,
CStringUtil::CaselessCmp::cmpEqual);
if (index == body.begin() + endOfHeader) {
// missing close quote
return false;
}
name = body.substr(namePos, index - body.begin() - namePos);
}
else {
// unquoted name
name = body.substr(index - body.begin(),
body.find_first_of(" \t\r\n"));
}
// save part. add 2 to endOfHeaders to skip CRLF.
parts.insert(std::make_pair(name, body.substr(endOfHeaders + 2,
nextPart - (endOfHeaders + 2))));
// move to next part
partIndex = nextPart + delimiter.size();
}
// should've found the last delimiter inside the loop but we did not
return false;
}
CString
CHTTPProtocol::readLine(IInputStream* stream, CString& tmpBuffer)
{
// read up to and including a CRLF from stream, using whatever
// is in tmpBuffer as if it were at the head of the stream.
for (;;) {
// scan tmpBuffer for CRLF
CString::size_type newline = tmpBuffer.find("\r\n");
if (newline != CString::npos) {
// copy line without the CRLF
CString line = tmpBuffer.substr(0, newline);
// discard line and CRLF from tmpBuffer
tmpBuffer.erase(0, newline + 2);
return line;
}
// read more from stream
char buffer[4096];
UInt32 n = stream->read(buffer, sizeof(buffer), -1.0);
if (n == 0) {
// stream is empty. return what's leftover.
CString line = tmpBuffer;
tmpBuffer.erase();
return line;
}
// append stream data
tmpBuffer.append(buffer, n);
}
}
CString
CHTTPProtocol::readBlock(IInputStream* stream,
UInt32 numBytes, CString& tmpBuffer)
{
CString data;
// read numBytes from stream, using whatever is in tmpBuffer as
// if it were at the head of the stream.
if (tmpBuffer.size() > 0) {
// ignore stream if there's enough data in tmpBuffer
if (tmpBuffer.size() >= numBytes) {
data = tmpBuffer.substr(0, numBytes);
tmpBuffer.erase(0, numBytes);
return data;
}
// move everything out of tmpBuffer into data
data = tmpBuffer;
tmpBuffer.erase();
}
// account for bytes read so far
assert(data.size() < numBytes);
numBytes -= data.size();
// read until we have all the requested data
while (numBytes > 0) {
// read max(4096, bytes_left) bytes into buffer
char buffer[4096];
UInt32 n = sizeof(buffer);
if (n > numBytes) {
n = numBytes;
}
n = stream->read(buffer, n, -1.0);
// if stream is empty then return what we've got so far
if (n == 0) {
break;
}
// append stream data
data.append(buffer, n);
numBytes -= n;
}
return data;
}
CString
CHTTPProtocol::readChunk(IInputStream* stream,
CString& tmpBuffer, UInt32* maxSize)
{
CString line;
// get chunk header
line = readLine(stream, tmpBuffer);
// parse chunk size
UInt32 size;
{
std::istringstream s(line);
s.exceptions(std::ios::goodbit);
s >> std::hex >> size;
if (!s) {
log((CLOG_DEBUG1 "cannot parse chunk size", line.c_str()));
throw XHTTP(400);
}
}
if (size == 0) {
return CString();
}
// check size
if (maxSize != NULL) {
if (line.size() + 2 + size + 2 > *maxSize) {
throw XHTTP(413);
}
maxSize -= line.size() + 2 + size + 2;
}
// read size bytes
CString data = readBlock(stream, size, tmpBuffer);
if (data.size() != size) {
log((CLOG_DEBUG1 "expected/actual chunk size mismatch", size, data.size()));
throw XHTTP(400);
}
// read an discard CRLF
line = readLine(stream, tmpBuffer);
if (!line.empty()) {
log((CLOG_DEBUG1 "missing CRLF after chunk"));
throw XHTTP(400);
}
return data;
}
void
CHTTPProtocol::readHeaders(IInputStream* stream,
CHTTPRequest* request, bool isFooter,
CString& tmpBuffer, UInt32* maxSize)
{
// parse headers. done with headers when we get a blank line.
CString name;
CString line = readLine(stream, tmpBuffer);
while (!line.empty()) {
// check size
if (maxSize != NULL) {
if (line.size() + 2 > *maxSize) {
throw XHTTP(413);
}
*maxSize -= line.size() + 2;
}
// if line starts with space or tab then append it to the
// previous header. if there is no previous header then
// throw.
if (line[0] == ' ' || line[0] == '\t') {
if (name.empty()) {
log((CLOG_DEBUG1 "first header is a continuation"));
throw XHTTP(400);
}
request->appendHeader(name, line);
}
// line should have the form: <name>:[<value>]
else {
// parse
CString value;
std::istringstream s(line);
s.exceptions(std::ios::goodbit);
std::getline(s, name, ':');
if (!s || !isValidToken(name)) {
log((CLOG_DEBUG1 "invalid header: %s", line.c_str()));
throw XHTTP(400);
}
std::getline(s, value);
// check validity of name
if (isFooter) {
// FIXME -- only certain names are allowed in footers
// but which ones?
}
request->appendHeader(name, value);
}
// next header
line = readLine(stream, tmpBuffer);
}
}
bool
CHTTPProtocol::isValidToken(const CString& token)
{
return (token.find("()<>@,;:\\\"/[]?={} "
"\0\1\2\3\4\5\6\7"
"\10\11\12\13\14\15\16\17"
"\20\21\22\23\24\25\26\27"
"\30\31\32\33\34\35\36\37\177") == CString::npos);
}

186
lib/http/CHTTPProtocol.h Normal file
View File

@@ -0,0 +1,186 @@
#ifndef CHTTPPROTOCOL_H
#define CHTTPPROTOCOL_H
#include "CString.h"
#include "BasicTypes.h"
#include "stdlist.h"
#include "stdmap.h"
#include "stdvector.h"
class IInputStream;
class IOutputStream;
//! HTTP request type
/*!
This class encapsulates an HTTP request.
*/
class CHTTPRequest {
private:
typedef std::list<std::pair<CString, CString> > CHeaderList;
public:
//! Iterator on headers
/*!
An iterator on the headers. Each element is a std::pair; first is
the header name as a CString, second is the header value as a CString.
*/
typedef CHeaderList::const_iterator const_iterator;
CHTTPRequest();
~CHTTPRequest();
//! @name manipulators
//@{
//! Insert header
/*!
Add a header by name replacing the existing header, if any.
Headers are sent in the order they're inserted. Replacing
a header does not change its original position in the order.
*/
void insertHeader(const CString& name, const CString& value);
//! Append header
/*!
Append a header. Equivalent to insertHeader() if the header
doesn't exist, otherwise it appends a comma and the value to
the existing header.
*/
void appendHeader(const CString& name, const CString& value);
//! Remove header
/*!
Remove a header by name. Does nothing if the header doesn't exist.
*/
void eraseHeader(const CString& name);
//@}
//! @name accessors
//@{
//! Check header existence
/*!
Returns true iff the header exists.
*/
bool isHeader(const CString& name) const;
//! Get header
/*!
Get a header by name. Returns the empty string if the header
doesn't exist.
*/
CString getHeader(const CString& name) const;
// headers are iterated in the order they were added.
//! Get beginning header iterator
const_iterator begin() const { return m_headers.begin(); }
//! Get ending header iterator
const_iterator end() const { return m_headers.end(); }
//@}
public:
// note -- these members are public for convenience
//! The HTTP method
CString m_method;
//! The HTTP URI
CString m_uri;
//! The HTTP major version number
SInt32 m_majorVersion;
//! The HTTP minor version number
SInt32 m_minorVersion;
//! The HTTP body, after transfer decoding
CString m_body;
private:
typedef std::map<CString, CHeaderList::iterator,
CStringUtil::CaselessCmp> CHeaderMap;
CHeaderList m_headers;
CHeaderMap m_headerByName;
};
//! HTTP reply type
/*!
This class encapsulates an HTTP reply.
*/
class CHTTPReply {
public:
//! Header list
/*!
The type of the reply header list. Each pair is the header name
and value, respectively for first and second.
*/
typedef std::vector<std::pair<CString, CString> > CHeaderList;
// note -- these members are public for convenience
//! The HTTP major version number
SInt32 m_majorVersion;
//! The HTTP minor version number
SInt32 m_minorVersion;
//! The HTTP status code
SInt32 m_status;
//! The HTTP reason phrase
CString m_reason;
//! The HTTP method
CString m_method;
//! The HTTP headers
CHeaderList m_headers;
//! The HTTP body
CString m_body;
};
//! HTTP protocol utilities
/*!
This class provides utility functions for HTTP.
*/
class CHTTPProtocol {
public:
//! Multipart form parts
/*!
Each element is the contents of a multipart form part indexed by
it's name.
*/
typedef std::map<CString, CString> CFormParts;
//! Read HTTP request
/*!
Read and parse an HTTP request. The result is returned in a
CHTTPRequest which the client must delete. Throws an
XHTTP if there was a parse error. Throws an XIO exception
if there was a read error. If \c maxSize is greater than
zero and the request is larger than \c maxSize bytes then
throws XHTTP(413) (request entity too large).
*/
static CHTTPRequest* readRequest(IInputStream*, UInt32 maxSize = 0);
//! Send HTTP response
/*!
Send an HTTP reply. The Content-Length and Date headers are set
automatically.
*/
static void reply(IOutputStream*, CHTTPReply&);
//! Parse multipart form data
/*!
Parse a multipart/form-data body into its parts. Returns true
iff the entire body was correctly parsed.
*/
// FIXME -- name/value pairs insufficient to save part headers
static bool parseFormData(const CHTTPRequest&,
CFormParts& parts);
private:
static CString readLine(IInputStream*, CString& tmpBuffer);
static CString readBlock(IInputStream*,
UInt32 numBytes, CString& tmpBuffer);
static CString readChunk(IInputStream*, CString& tmpBuffer,
UInt32* maxSize);
static void readHeaders(IInputStream*,
CHTTPRequest*, bool isFooter,
CString& tmpBuffer,
UInt32* maxSize);
static bool isValidToken(const CString&);
};
#endif

16
lib/http/Makefile.am Normal file
View File

@@ -0,0 +1,16 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ../..
noinst_LIBRARIES = libhttp.a
libhttp_a_SOURCES = \
CHTTPProtocol.cpp \
XHTTP.cpp \
CHTTPProtocol.h \
XHTTP.h \
$(NULL)
INCLUDES = \
-I$(DEPTH)/lib/base \
-I$(DEPTH)/lib/mt \
-I$(DEPTH)/lib/io \
$(NULL)

120
lib/http/XHTTP.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "XHTTP.h"
#include "CHTTPProtocol.h"
#include "stdsstream.h"
//
// XHTTP
//
XHTTP::XHTTP(SInt32 statusCode) :
XBase(),
m_status(statusCode),
m_reason(getReason(statusCode))
{
// do nothing
}
XHTTP::XHTTP(SInt32 statusCode, const CString& reasonPhrase) :
XBase(),
m_status(statusCode),
m_reason(reasonPhrase)
{
// do nothing
}
XHTTP::~XHTTP()
{
// do nothing
}
SInt32
XHTTP::getStatus() const
{
return m_status;
}
CString
XHTTP::getReason() const
{
return m_reason;
}
void
XHTTP::addHeaders(CHTTPReply&) const
{
// do nothing
}
CString
XHTTP::getWhat() const throw()
{
const char* reason;
if (m_reason.empty()) {
reason = getReason(m_status);
}
else {
reason = m_reason.c_str();
}
return format("XHTTP", "%{1} %{2}",
CStringUtil::print("%d", m_status).c_str(),
reason);
}
const char*
XHTTP::getReason(SInt32 status)
{
switch (status) {
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Moved Temporarily";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Time-out";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-URI Too Large";
case 415: return "Unsupported Media Type";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Time-out";
case 505: return "HTTP Version not supported";
default: return "";
}
}
//
// XHTTPAllow
//
XHTTPAllow::XHTTPAllow(const CString& allowedMethods) :
XHTTP(405),
m_allowed(allowedMethods)
{
// do nothing
}
XHTTPAllow::~XHTTPAllow()
{
// do nothing
}
void
XHTTPAllow::addHeaders(CHTTPReply& reply) const
{
reply.m_headers.push_back(std::make_pair(CString("Allow"), m_allowed));
}

67
lib/http/XHTTP.h Normal file
View File

@@ -0,0 +1,67 @@
#ifndef XHTTP_H
#define XHTTP_H
#include "BasicTypes.h"
#include "XBase.h"
class CHTTPReply;
//! Generic HTTP exception
class XHTTP : public XBase {
public:
/*!
Use the HTTP \c statusCode as the failure reason.
*/
XHTTP(SInt32 statusCode);
/*!
Use the HTTP \c statusCode as the failure reason. Use \c reasonPhrase
as the human readable reason for the failure.
*/
XHTTP(SInt32 statusCode, const CString& reasonPhrase);
~XHTTP();
//@{
//! @name accessors
//! Get the HTTP status code
SInt32 getStatus() const;
//! Get the reason phrase
CString getReason() const;
//! Modify reply for error
/*!
Override to modify an HTTP reply to further describe the error.
*/
virtual void addHeaders(CHTTPReply&) const;
//@}
protected:
virtual CString getWhat() const throw();
private:
static const char* getReason(SInt32 status);
private:
SInt32 m_status;
CString m_reason;
};
class XHTTPAllow : public XHTTP {
public:
/*!
\c allowedMethods is added as an `Allow' header to a reply in
addHeaders().
*/
XHTTPAllow(const CString& allowedMethods);
~XHTTPAllow();
// XHTTP overrides
virtual void addHeaders(CHTTPReply&) const;
private:
CString m_allowed;
};
#endif

110
lib/http/http.dsp Executable file
View File

@@ -0,0 +1,110 @@
# Microsoft Developer Studio Project File - Name="http" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=http - 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 "http.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 "http.mak" CFG="http - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "http - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "http - 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)" == "http - 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 "..\io" /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)" == "http - 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 "..\io" /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 "http - Win32 Release"
# Name "http - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\CHTTPProtocol.cpp
# End Source File
# Begin Source File
SOURCE=.\XHTTP.cpp
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\CHTTPProtocol.h
# End Source File
# Begin Source File
SOURCE=.\XHTTP.h
# End Source File
# End Group
# End Target
# End Project

View File

@@ -0,0 +1,118 @@
#include "CBufferedInputStream.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "CStopwatch.h"
#include "IJob.h"
#include "XIO.h"
#include <cstring>
//
// CBufferedInputStream
//
CBufferedInputStream::CBufferedInputStream(
CMutex* mutex, IJob* adoptedCloseCB) :
m_mutex(mutex),
m_empty(mutex, true),
m_closeCB(adoptedCloseCB),
m_closed(false),
m_hungup(false)
{
assert(m_mutex != NULL);
}
CBufferedInputStream::~CBufferedInputStream()
{
delete m_closeCB;
}
void
CBufferedInputStream::write(const void* buffer, UInt32 n)
{
if (!m_hungup && n > 0) {
m_buffer.write(buffer, n);
m_empty = (m_buffer.getSize() == 0);
m_empty.broadcast();
}
}
void
CBufferedInputStream::hangup()
{
m_hungup = true;
m_empty.broadcast();
}
UInt32
CBufferedInputStream::readNoLock(void* buffer, UInt32 n, double timeout)
{
if (m_closed) {
throw XIOClosed();
}
// wait for data, hangup, or timeout
CStopwatch timer(true);
while (!m_hungup && m_empty == true) {
if (!m_empty.wait(timer, timeout)) {
// timed out
return (UInt32)-1;
}
}
// read data
const UInt32 count = m_buffer.getSize();
if (n > count) {
n = count;
}
if (n > 0) {
if (buffer != NULL) {
memcpy(buffer, m_buffer.peek(n), n);
}
m_buffer.pop(n);
}
// update empty state
if (m_buffer.getSize() == 0) {
m_empty = true;
m_empty.broadcast();
}
return n;
}
UInt32
CBufferedInputStream::getSizeNoLock() const
{
return m_buffer.getSize();
}
void
CBufferedInputStream::close()
{
CLock lock(m_mutex);
if (m_closed) {
throw XIOClosed();
}
m_closed = true;
m_hungup = true;
m_buffer.pop(m_buffer.getSize());
m_empty.broadcast();
if (m_closeCB != NULL) {
m_closeCB->run();
}
}
UInt32
CBufferedInputStream::read(void* buffer, UInt32 n, double timeout)
{
CLock lock(m_mutex);
return readNoLock(buffer, n, timeout);
}
UInt32
CBufferedInputStream::getSize() const
{
CLock lock(m_mutex);
return getSizeNoLock();
}

View File

@@ -0,0 +1,82 @@
#ifndef CBUFFEREDINPUTSTREAM_H
#define CBUFFEREDINPUTSTREAM_H
#include "IInputStream.h"
#include "CStreamBuffer.h"
#include "CCondVar.h"
class CMutex;
class IJob;
//! Memory buffer input stream
/*!
This class provides an input stream that reads from a memory buffer.
It also provides a means for the owner to ensure thread safe access.
Typically, an owner object will make this object visible to clients
that need access to an IInputStream while using the CBufferedInputStream
methods to write data to the stream.
*/
class CBufferedInputStream : public IInputStream {
public:
/*!
The \c mutex must not be NULL and will be used to ensure thread
safe access. If \c adoptedCloseCB is not NULL it will be called
when close() is called, allowing the creator to detect the close.
*/
CBufferedInputStream(CMutex* mutex, IJob* adoptedCloseCB);
~CBufferedInputStream();
//! @name manipulators
//@{
//! Write data to stream
/*!
Write \c n bytes from \c buffer to the stream. The mutex must
be locked before calling this.
*/
void write(const void* buffer, UInt32 n);
//! Hangup stream
/*!
Causes read() to always return immediately. If there is no
more data to read then it returns 0. Further writes are discarded.
The mutex must be locked before calling this.
*/
void hangup();
//! Read from stream
/*!
This is the same as read() but the mutex must be locked before
calling this.
*/
UInt32 readNoLock(void*, UInt32 n, double timeout);
//@}
//! @name accessors
//@{
//! Get remaining size of stream
/*!
This is the same as getSize() but the mutex must be locked before
calling this.
*/
UInt32 getSizeNoLock() const;
//@}
// IInputStream overrides
// these all lock the mutex for their duration
virtual void close();
virtual UInt32 read(void*, UInt32 n, double timeout);
virtual UInt32 getSize() const;
private:
CMutex* m_mutex;
CCondVar<bool> m_empty;
IJob* m_closeCB;
CStreamBuffer m_buffer;
bool m_closed;
bool m_hungup;
};
#endif

View File

@@ -0,0 +1,83 @@
#include "CBufferedOutputStream.h"
#include "XIO.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "IJob.h"
//
// CBufferedOutputStream
//
CBufferedOutputStream::CBufferedOutputStream(
CMutex* mutex, IJob* adoptedCloseCB) :
m_mutex(mutex),
m_closeCB(adoptedCloseCB),
m_empty(mutex, true),
m_closed(false)
{
assert(m_mutex != NULL);
}
CBufferedOutputStream::~CBufferedOutputStream()
{
delete m_closeCB;
}
const void*
CBufferedOutputStream::peek(UInt32 n)
{
return m_buffer.peek(n);
}
void
CBufferedOutputStream::pop(UInt32 n)
{
m_buffer.pop(n);
if (m_buffer.getSize() == 0) {
m_empty.broadcast();
}
}
UInt32
CBufferedOutputStream::getSize() const
{
return m_buffer.getSize();
}
void
CBufferedOutputStream::close()
{
CLock lock(m_mutex);
if (m_closed) {
throw XIOClosed();
}
m_closed = true;
m_buffer.pop(m_buffer.getSize());
if (m_closeCB != NULL) {
m_closeCB->run();
}
}
UInt32
CBufferedOutputStream::write(const void* buffer, UInt32 n)
{
CLock lock(m_mutex);
if (m_closed) {
throw XIOClosed();
}
m_buffer.write(buffer, n);
return n;
}
void
CBufferedOutputStream::flush()
{
// wait until all data is written
CLock lock(m_mutex);
while (m_buffer.getSize() > 0) {
m_empty.wait();
}
}

View File

@@ -0,0 +1,74 @@
#ifndef CBUFFEREDOUTPUTSTREAM_H
#define CBUFFEREDOUTPUTSTREAM_H
#include "IOutputStream.h"
#include "CStreamBuffer.h"
#include "CCondVar.h"
class CMutex;
class IJob;
//! Memory buffer output stream
/*!
This class provides an output stream that writes to a memory buffer.
It also provides a means for the owner to ensure thread safe access.
Typically, an owner object will make this object visible to clients
that need access to an IOutputStream while using the CBufferedOutputStream
methods to read the data written to the stream.
*/
class CBufferedOutputStream : public IOutputStream {
public:
/*!
The \c mutex must not be NULL and will be used to ensure thread
safe access. If \c adoptedCloseCB is not NULL it will be called
when close() is called, allowing the creator to detect the close.
*/
CBufferedOutputStream(CMutex* mutex, IJob* adoptedCloseCB);
~CBufferedOutputStream();
//! @name manipulators
//@{
//! Read data without removing from buffer
/*!
Returns a buffer of \c n bytes (which must be <= getSize()). The
caller must not modify the buffer nor delete it. The mutex must
be locked before calling this.
*/
const void* peek(UInt32 n);
//! Discard data
/*!
Discards the next \c n bytes. If \c n >= getSize() then the buffer
is cleared. The mutex must be locked before calling this.
*/
void pop(UInt32 n);
//@}
//! @name accessors
//@{
//! Get size of buffer
/*!
Returns the number of bytes in the buffer. The mutex must be locked
before calling this.
*/
UInt32 getSize() const;
//@}
// IOutputStream overrides
// these all lock the mutex for their duration
virtual void close();
virtual UInt32 write(const void*, UInt32 n);
virtual void flush();
private:
CMutex* m_mutex;
IJob* m_closeCB;
CCondVar<bool> m_empty;
CStreamBuffer m_buffer;
bool m_closed;
};
#endif

View File

@@ -0,0 +1,25 @@
#include "CInputStreamFilter.h"
//
// CInputStreamFilter
//
CInputStreamFilter::CInputStreamFilter(IInputStream* stream, bool adopted) :
m_stream(stream),
m_adopted(adopted)
{
assert(m_stream != NULL);
}
CInputStreamFilter::~CInputStreamFilter()
{
if (m_adopted) {
delete m_stream;
}
}
IInputStream*
CInputStreamFilter::getStream() const
{
return m_stream;
}

View File

@@ -0,0 +1,38 @@
#ifndef CINPUTSTREAMFILTER_H
#define CINPUTSTREAMFILTER_H
#include "IInputStream.h"
//! A filtering input stream
/*!
This class wraps an input stream. Subclasses provide indirect access
to the stream, typically performing some filtering.
*/
class CInputStreamFilter : public IInputStream {
public:
/*!
Create a wrapper around \c stream. Iff \c adoptStream is true then
this object takes ownership of the stream and will delete it in the
d'tor.
*/
CInputStreamFilter(IInputStream* stream, bool adoptStream = true);
~CInputStreamFilter();
// IInputStream overrides
virtual void close() = 0;
virtual UInt32 read(void*, UInt32 n, double timeout) = 0;
virtual UInt32 getSize() const = 0;
protected:
//! Get the stream
/*!
Returns the stream passed to the c'tor.
*/
IInputStream* getStream() const;
private:
IInputStream* m_stream;
bool m_adopted;
};
#endif

View File

@@ -0,0 +1,25 @@
#include "COutputStreamFilter.h"
//
// COutputStreamFilter
//
COutputStreamFilter::COutputStreamFilter(IOutputStream* stream, bool adopted) :
m_stream(stream),
m_adopted(adopted)
{
assert(m_stream != NULL);
}
COutputStreamFilter::~COutputStreamFilter()
{
if (m_adopted) {
delete m_stream;
}
}
IOutputStream*
COutputStreamFilter::getStream() const
{
return m_stream;
}

View File

@@ -0,0 +1,38 @@
#ifndef COUTPUTSTREAMFILTER_H
#define COUTPUTSTREAMFILTER_H
#include "IOutputStream.h"
//! A filtering output stream
/*!
This class wraps an output stream. Subclasses provide indirect access
to the stream, typically performing some filtering.
*/
class COutputStreamFilter : public IOutputStream {
public:
/*!
Create a wrapper around \c stream. Iff \c adoptStream is true then
this object takes ownership of the stream and will delete it in the
d'tor.
*/
COutputStreamFilter(IOutputStream* stream, bool adoptStream = true);
~COutputStreamFilter();
// IOutputStream overrides
virtual void close() = 0;
virtual UInt32 write(const void*, UInt32 count) = 0;
virtual void flush() = 0;
protected:
//! Get the stream
/*!
Returns the stream passed to the c'tor.
*/
IOutputStream* getStream() const;
private:
IOutputStream* m_stream;
bool m_adopted;
};
#endif

122
lib/io/CStreamBuffer.cpp Normal file
View File

@@ -0,0 +1,122 @@
#include "CStreamBuffer.h"
//
// CStreamBuffer
//
const UInt32 CStreamBuffer::kChunkSize = 4096;
CStreamBuffer::CStreamBuffer() :
m_size(0),
m_headUsed(0)
{
// do nothing
}
CStreamBuffer::~CStreamBuffer()
{
// do nothing
}
const void*
CStreamBuffer::peek(UInt32 n)
{
assert(n <= m_size);
// reserve space in first chunk
ChunkList::iterator head = m_chunks.begin();
head->reserve(n + m_headUsed);
// consolidate chunks into the first chunk until it has n bytes
ChunkList::iterator scan = head;
++scan;
while (head->size() - m_headUsed < n && scan != m_chunks.end()) {
head->insert(head->end(), scan->begin(), scan->end());
scan = m_chunks.erase(scan);
}
return reinterpret_cast<const void*>(&(head->begin()[m_headUsed]));
}
void
CStreamBuffer::pop(UInt32 n)
{
// discard all chunks if n is greater than or equal to m_size
if (n >= m_size) {
m_size = 0;
m_headUsed = 0;
m_chunks.clear();
return;
}
// update size
m_size -= n;
// discard chunks until more than n bytes would've been discarded
ChunkList::iterator scan = m_chunks.begin();
assert(scan != m_chunks.end());
while (scan->size() - m_headUsed <= n) {
n -= scan->size() - m_headUsed;
m_headUsed = 0;
scan = m_chunks.erase(scan);
assert(scan != m_chunks.end());
}
// remove left over bytes from the head chunk
if (n > 0) {
m_headUsed += n;
}
}
void
CStreamBuffer::write(const void* vdata, UInt32 n)
{
assert(vdata != NULL);
// ignore if no data, otherwise update size
if (n == 0) {
return;
}
m_size += n;
// cast data to bytes
const UInt8* data = reinterpret_cast<const UInt8*>(vdata);
// point to last chunk if it has space, otherwise append an empty chunk
ChunkList::iterator scan = m_chunks.end();
if (scan != m_chunks.begin()) {
--scan;
if (scan->size() >= kChunkSize) {
++scan;
}
}
if (scan == m_chunks.end()) {
scan = m_chunks.insert(scan, Chunk());
}
// append data in chunks
while (n > 0) {
// choose number of bytes for next chunk
assert(scan->size() <= kChunkSize);
UInt32 count = kChunkSize - scan->size();
if (count > n)
count = n;
// transfer data
scan->insert(scan->end(), data, data + count);
n -= count;
data += count;
// append another empty chunk if we're not done yet
if (n > 0) {
++scan;
scan = m_chunks.insert(scan, Chunk());
}
}
}
UInt32
CStreamBuffer::getSize() const
{
return m_size;
}

64
lib/io/CStreamBuffer.h Normal file
View File

@@ -0,0 +1,64 @@
#ifndef CSTREAMBUFFER_H
#define CSTREAMBUFFER_H
#include "BasicTypes.h"
#include "stdlist.h"
#include "stdvector.h"
//! FIFO of bytes
/*!
This class maintains a FIFO (first-in, last-out) buffer of bytes.
*/
class CStreamBuffer {
public:
CStreamBuffer();
~CStreamBuffer();
//! @name manipulators
//@{
//! Read data without removing from buffer
/*!
Return a pointer to memory with the next \c n bytes in the buffer
(which must be <= getSize()). The caller must not modify the returned
memory nor delete it.
*/
const void* peek(UInt32 n);
//! Discard data
/*!
Discards the next \c n bytes. If \c n >= getSize() then the buffer
is cleared.
*/
void pop(UInt32 n);
//! Write data to buffer
/*!
Appends \c n bytes from \c data to the buffer.
*/
void write(const void* data, UInt32 n);
//@}
//! @name accessors
//@{
//! Get size of buffer
/*!
Returns the number of bytes in the buffer.
*/
UInt32 getSize() const;
//@}
private:
static const UInt32 kChunkSize;
typedef std::vector<UInt8> Chunk;
typedef std::list<Chunk> ChunkList;
ChunkList m_chunks;
UInt32 m_size;
UInt32 m_headUsed;
};
#endif

53
lib/io/IInputStream.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef IINPUTSTREAM_H
#define IINPUTSTREAM_H
#include "IInterface.h"
#include "BasicTypes.h"
//! Input stream interface
/*!
Defines the interface for all input streams.
*/
class IInputStream : public IInterface {
public:
//! @name manipulators
//@{
//! Close the stream
/*!
Closes the stream. Attempting to read() after close() throws
XIOClosed and getSize() always returns zero.
*/
virtual void close() = 0;
//! Read from stream
/*!
Read up to \c n bytes into buffer, returning the number read.
Blocks for up to \c timeout seconds if no data is available but does
not wait if any data is available, even if less than \c n bytes.
If \c timeout < 0 then it blocks indefinitely until data is available.
If \c buffer is NULL then the data is discarded. Returns (UInt32)-1 if
it times out and 0 if no data is available and the other end of the
stream has hungup.
(cancellation point)
*/
virtual UInt32 read(void* buffer, UInt32 n, double timeout) = 0;
//@}
//! @name accessors
//@{
//! Get remaining size of stream
/*!
Returns a conservative estimate of the available bytes to read
(i.e. a number not greater than the actual number of bytes).
Some streams may not be able to determine this and will always
return zero.
*/
virtual UInt32 getSize() const = 0;
//@}
};
#endif

44
lib/io/IOutputStream.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef IOUTPUTSTREAM_H
#define IOUTPUTSTREAM_H
#include "IInterface.h"
#include "BasicTypes.h"
//! Output stream interface
/*!
Defines the interface for all output streams.
*/
class IOutputStream : public IInterface {
public:
//! @name manipulators
//@{
//! Close the stream
/*!
Closes the stream. Attempting to write() after close() throws
XIOClosed.
*/
virtual void close() = 0;
//! Write to stream
/*!
Write \c n bytes from \c buffer to the stream. If this can't
complete immeditely it will block. If cancelled, an indeterminate
amount of data may have been written.
(cancellation point)
*/
virtual UInt32 write(const void* buffer, UInt32 n) = 0;
//! Flush the stream
/*!
Waits until all buffered data has been written to the stream.
(cancellation point)
*/
virtual void flush() = 0;
//@}
};
#endif

25
lib/io/Makefile.am Normal file
View File

@@ -0,0 +1,25 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ../..
noinst_LIBRARIES = libio.a
libio_a_SOURCES = \
CBufferedInputStream.cpp \
CBufferedOutputStream.cpp \
CInputStreamFilter.cpp \
COutputStreamFilter.cpp \
CStreamBuffer.cpp \
XIO.cpp \
CBufferedInputStream.h \
CBufferedOutputStream.h \
CInputStreamFilter.h \
COutputStreamFilter.h \
CStreamBuffer.h \
IInputStream.h \
IOutputStream.h \
XIO.h \
$(NULL)
INCLUDES = \
-I$(DEPTH)/lib/base \
-I$(DEPTH)/lib/mt \
$(NULL)

50
lib/io/XIO.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "XIO.h"
//
// XIOErrno
//
XIOErrno::XIOErrno() :
MXErrno()
{
// do nothing
}
XIOErrno::XIOErrno(int err) :
MXErrno(err)
{
// do nothing
}
//
// XIOClose
//
CString
XIOClose::getWhat() const throw()
{
return format("XIOClose", "close: %{1}", XIOErrno::getErrstr());
}
//
// XIOClosed
//
CString
XIOClosed::getWhat() const throw()
{
return format("XIOClosed", "already closed");
}
//
// XIOEndOfStream
//
CString
XIOEndOfStream::getWhat() const throw()
{
return format("XIOEndOfStream", "reached end of stream");
}

47
lib/io/XIO.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef XIO_H
#define XIO_H
#include "XBase.h"
//! Generic I/O exception
class XIO : public XBase { };
//! Generic I/O exception using \c errno
class XIOErrno : public XIO, public MXErrno {
public:
XIOErrno();
XIOErrno(int);
};
//! I/O closing exception
/*!
Thrown if a stream cannot be closed.
*/
class XIOClose: public XIOErrno {
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
//! I/O already closed exception
/*!
Thrown when attempting to close or perform I/O on an already closed.
stream.
*/
class XIOClosed : public XIO {
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
//! I/O end of stream exception
/*!
Thrown when attempting to read beyond the end of a stream.
*/
class XIOEndOfStream : public XIO {
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
#endif

150
lib/io/io.dsp Normal file
View File

@@ -0,0 +1,150 @@
# Microsoft Developer Studio Project File - Name="io" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=io - 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 "io.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 "io.mak" CFG="io - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "io - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "io - 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)" == "io - 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" /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)" == "io - 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" /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 "io - Win32 Release"
# Name "io - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\CBufferedInputStream.cpp
# End Source File
# Begin Source File
SOURCE=.\CBufferedOutputStream.cpp
# End Source File
# Begin Source File
SOURCE=.\CInputStreamFilter.cpp
# End Source File
# Begin Source File
SOURCE=.\COutputStreamFilter.cpp
# End Source File
# Begin Source File
SOURCE=.\CStreamBuffer.cpp
# End Source File
# Begin Source File
SOURCE=.\XIO.cpp
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\CBufferedInputStream.h
# End Source File
# Begin Source File
SOURCE=.\CBufferedOutputStream.h
# End Source File
# Begin Source File
SOURCE=.\CInputStreamFilter.h
# End Source File
# Begin Source File
SOURCE=.\COutputStreamFilter.h
# End Source File
# Begin Source File
SOURCE=.\CStreamBuffer.h
# End Source File
# Begin Source File
SOURCE=.\IInputStream.h
# End Source File
# Begin Source File
SOURCE=.\IOutputStream.h
# End Source File
# Begin Source File
SOURCE=.\XIO.h
# End Source File
# End Group
# End Target
# End Project

315
lib/mt/CCondVar.cpp Normal file
View File

@@ -0,0 +1,315 @@
#include "CCondVar.h"
#include "CStopwatch.h"
//
// CCondVarBase
//
CCondVarBase::CCondVarBase(CMutex* mutex) :
m_mutex(mutex)
#if WINDOWS_LIKE
, m_waitCountMutex()
#endif
{
assert(m_mutex != NULL);
init();
}
CCondVarBase::~CCondVarBase()
{
fini();
}
void
CCondVarBase::lock() const
{
m_mutex->lock();
}
void
CCondVarBase::unlock() const
{
m_mutex->unlock();
}
bool
CCondVarBase::wait(double timeout) const
{
CStopwatch timer(true);
return wait(timer, timeout);
}
CMutex*
CCondVarBase::getMutex() const
{
return m_mutex;
}
#if HAVE_PTHREAD
#include "CThread.h"
#include <pthread.h>
#include <sys/time.h>
#include <cerrno>
void
CCondVarBase::init()
{
pthread_cond_t* cond = new pthread_cond_t;
int status = pthread_cond_init(cond, NULL);
assert(status == 0);
m_cond = reinterpret_cast<pthread_cond_t*>(cond);
}
void
CCondVarBase::fini()
{
pthread_cond_t* cond = reinterpret_cast<pthread_cond_t*>(m_cond);
int status = pthread_cond_destroy(cond);
assert(status == 0);
delete cond;
}
void
CCondVarBase::signal()
{
pthread_cond_t* cond = reinterpret_cast<pthread_cond_t*>(m_cond);
int status = pthread_cond_signal(cond);
assert(status == 0);
}
void
CCondVarBase::broadcast()
{
pthread_cond_t* cond = reinterpret_cast<pthread_cond_t*>(m_cond);
int status = pthread_cond_broadcast(cond);
assert(status == 0);
}
bool
CCondVarBase::wait(CStopwatch& timer, double timeout) const
{
// check timeout against timer
if (timeout >= 0.0) {
timeout -= timer.getTime();
if (timeout < 0.0)
return false;
}
// get condition variable and mutex
pthread_cond_t* cond = reinterpret_cast<pthread_cond_t*>(m_cond);
pthread_mutex_t* mutex = reinterpret_cast<pthread_mutex_t*>(m_mutex->m_mutex);
// get final time
struct timeval now;
gettimeofday(&now, NULL);
struct timespec finalTime;
finalTime.tv_sec = now.tv_sec;
finalTime.tv_nsec = now.tv_usec * 1000;
if (timeout >= 0.0) {
const long timeout_sec = (long)timeout;
const long timeout_nsec = (long)(1000000000.0 * (timeout - timeout_sec));
finalTime.tv_sec += timeout_sec;
finalTime.tv_nsec += timeout_nsec;
if (finalTime.tv_nsec >= 1000000000) {
finalTime.tv_nsec -= 1000000000;
finalTime.tv_sec += 1;
}
}
// repeat until we reach the final time
int status;
for (;;) {
// compute the next timeout
gettimeofday(&now, NULL);
struct timespec endTime;
endTime.tv_sec = now.tv_sec;
endTime.tv_nsec = now.tv_usec * 1000 + 50000000;
if (endTime.tv_nsec >= 1000000000) {
endTime.tv_nsec -= 1000000000;
endTime.tv_sec += 1;
}
// see if we should cancel this thread
CThread::testCancel();
// done if past final timeout
if (timeout >= 0.0) {
if (endTime.tv_sec > finalTime.tv_sec ||
(endTime.tv_sec == finalTime.tv_sec &&
endTime.tv_nsec >= finalTime.tv_nsec)) {
status = ETIMEDOUT;
break;
}
}
// wait
status = pthread_cond_timedwait(cond, mutex, &endTime);
// check for cancel again
CThread::testCancel();
// check wait status
if (status != ETIMEDOUT && status != EINTR) {
break;
}
}
switch (status) {
case 0:
// success
return true;
case ETIMEDOUT:
return false;
default:
assert(0 && "condition variable wait error");
return false;
}
}
#endif // HAVE_PTHREAD
#if WINDOWS_LIKE
#include "CLock.h"
#include "CThreadRep.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
//
// note -- implementation taken from
// http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
// titled "Strategies for Implementing POSIX Condition Variables
// on Win32." it also provides an implementation that doesn't
// suffer from the incorrectness problem described in our
// corresponding header but it is slower, still unfair, and
// can cause busy waiting.
//
void
CCondVarBase::init()
{
// prepare events
HANDLE* events = new HANDLE[2];
events[kSignal] = CreateEvent(NULL, FALSE, FALSE, NULL);
events[kBroadcast] = CreateEvent(NULL, TRUE, FALSE, NULL);
// prepare members
m_cond = reinterpret_cast<void*>(events);
m_waitCount = 0;
}
void
CCondVarBase::fini()
{
HANDLE* events = reinterpret_cast<HANDLE*>(m_cond);
CloseHandle(events[kSignal]);
CloseHandle(events[kBroadcast]);
delete[] events;
}
void
CCondVarBase::signal()
{
// is anybody waiting?
bool hasWaiter;
{
CLock lock(&m_waitCountMutex);
hasWaiter = (m_waitCount > 0);
}
// wake one thread if anybody is waiting
if (hasWaiter) {
SetEvent(reinterpret_cast<HANDLE*>(m_cond)[kSignal]);
}
}
void
CCondVarBase::broadcast()
{
// is anybody waiting?
bool hasWaiter;
{
CLock lock(&m_waitCountMutex);
hasWaiter = (m_waitCount > 0);
}
// wake all threads if anybody is waiting
if (hasWaiter) {
SetEvent(reinterpret_cast<HANDLE*>(m_cond)[kBroadcast]);
}
}
bool
CCondVarBase::wait(CStopwatch& timer, double timeout) const
{
// check timeout against timer
if (timeout >= 0.0) {
timeout -= timer.getTime();
if (timeout < 0.0) {
return false;
}
}
// prepare to wait
CThreadPtr currentRep = CThreadRep::getCurrentThreadRep();
const DWORD winTimeout = (timeout < 0.0) ? INFINITE :
static_cast<DWORD>(1000.0 * timeout);
HANDLE* events = reinterpret_cast<HANDLE*>(m_cond);
HANDLE handles[3];
handles[0] = events[kSignal];
handles[1] = events[kBroadcast];
handles[2] = currentRep->getCancelEvent();
const DWORD n = currentRep->isCancellable() ? 3 : 2;
// update waiter count
{
CLock lock(&m_waitCountMutex);
++m_waitCount;
}
// release mutex. this should be atomic with the wait so that it's
// impossible for another thread to signal us between the unlock and
// the wait, which would lead to a lost signal on broadcasts.
// however, we're using a manual reset event for broadcasts which
// stays set until we reset it, so we don't lose the broadcast.
m_mutex->unlock();
// wait for a signal or broadcast
DWORD result = WaitForMultipleObjects(n, handles, FALSE, winTimeout);
// cancel takes priority
if (n == 3 && result != WAIT_OBJECT_0 + 2 &&
WaitForSingleObject(handles[2], 0) == WAIT_OBJECT_0) {
result = WAIT_OBJECT_0 + 2;
}
// update the waiter count and check if we're the last waiter
bool last;
{
CLock lock(&m_waitCountMutex);
--m_waitCount;
last = (result == WAIT_OBJECT_0 + 1 && m_waitCount == 0);
}
// reset the broadcast event if we're the last waiter
if (last) {
ResetEvent(events[kBroadcast]);
}
// reacquire the mutex
m_mutex->lock();
// cancel thread if necessary
if (result == WAIT_OBJECT_0 + 2) {
currentRep->testCancel();
}
// return success or failure
return (result == WAIT_OBJECT_0 + 0 ||
result == WAIT_OBJECT_0 + 1);
}
#endif // WINDOWS_LIKE

226
lib/mt/CCondVar.h Normal file
View File

@@ -0,0 +1,226 @@
#ifndef CCONDVAR_H
#define CCONDVAR_H
#include "CMutex.h"
#include "BasicTypes.h"
class CStopwatch;
//! Generic condition variable
/*!
This class provides functionality common to all condition variables
but doesn't provide the actual variable storage. A condition variable
is a multiprocessing primitive that can be waited on. Every condition
variable has an associated mutex.
*/
class CCondVarBase {
public:
/*!
\c mutex must not be NULL. All condition variables have an
associated mutex. The mutex needn't be unique to one condition
variable.
*/
CCondVarBase(CMutex* mutex);
~CCondVarBase();
//! @name manipulators
//@{
//! Lock the condition variable's mutex
/*!
Lock the condition variable's mutex. The condition variable should
be locked before reading or writing it. It must be locked for a
call to wait(). Locks are not recursive; locking a locked mutex
will deadlock the thread.
*/
void lock() const;
//! Unlock the condition variable's mutex
void unlock() const;
//! Signal the condition variable
/*!
Wake up one waiting thread, if there are any. Which thread gets
woken is undefined.
*/
void signal();
//! Signal the condition variable
/*!
Wake up all waiting threads, if any.
*/
void broadcast();
//@}
//! @name accessors
//@{
//! Wait on the condition variable
/*!
Wait on the condition variable. If \c timeout < 0 then wait until
signalled, otherwise up to \c timeout seconds or until signalled,
whichever comes first. Returns true if the object was signalled
during the wait, false otherwise.
The proper way to wait for a condition is:
\code
cv.lock();
while (cv-expr) {
cv.wait();
}
cv.unlock();
\endcode
where \c cv-expr involves the value of \c cv and is false when the
condition is satisfied.
(cancellation point)
*/
bool wait(double timeout = -1.0) const;
//! Wait on the condition variable
/*!
Same as \c wait(double) but use \c timer to compare against \timeout.
Since clients normally wait on condition variables in a loop, clients
can use this to avoid recalculating \c timeout on each iteration.
Passing a stopwatch with a negative \c timeout is pointless (it will
never time out) but permitted.
(cancellation point)
*/
bool wait(CStopwatch& timer, double timeout) const;
//! Get the mutex
/*!
Get the mutex passed to the c'tor.
*/
CMutex* getMutex() const;
//@}
private:
void init();
void fini();
// not implemented
CCondVarBase(const CCondVarBase&);
CCondVarBase& operator=(const CCondVarBase&);
private:
CMutex* m_mutex;
void* m_cond;
#if WINDOWS_LIKE
enum { kSignal, kBroadcast };
mutable UInt32 m_waitCount;
CMutex m_waitCountMutex;
#endif
};
//! Condition variable
/*!
A condition variable with storage for type \c T.
*/
template <class T>
class CCondVar : public CCondVarBase {
public:
//! Initialize using \c value
CCondVar(CMutex* mutex, const T& value);
//! Initialize using another condition variable's value
CCondVar(const CCondVar&);
~CCondVar();
//! @name manipulators
//@{
//! Assigns the value of \c cv to this
/*!
Set the variable's value. The condition variable should be locked
before calling this method.
*/
CCondVar& operator=(const CCondVar& cv);
//! Assigns \c value to this
/*!
Set the variable's value. The condition variable should be locked
before calling this method.
*/
CCondVar& operator=(const T& v);
//@}
//! @name accessors
//@{
//! Get the variable's value
/*!
Get the variable's value. The condition variable should be locked
before calling this method.
*/
operator const T&() const;
//@}
private:
T m_data;
};
template <class T>
inline
CCondVar<T>::CCondVar(
CMutex* mutex,
const T& data) :
CCondVarBase(mutex),
m_data(data)
{
// do nothing
}
template <class T>
inline
CCondVar<T>::CCondVar(
const CCondVar& cv) :
CCondVarBase(cv.getMutex()),
m_data(cv.m_data)
{
// do nothing
}
template <class T>
inline
CCondVar<T>::~CCondVar()
{
// do nothing
}
template <class T>
inline
CCondVar<T>&
CCondVar<T>::operator=(
const CCondVar<T>& cv)
{
m_data = cv.m_data;
return *this;
}
template <class T>
inline
CCondVar<T>&
CCondVar<T>::operator=(
const T& data)
{
m_data = data;
return *this;
}
template <class T>
inline
CCondVar<T>::operator const T&() const
{
return m_data;
}
// force instantiation of these common types
template class CCondVar<bool>;
template class CCondVar<SInt32>;
#endif

24
lib/mt/CLock.cpp Normal file
View File

@@ -0,0 +1,24 @@
#include "CLock.h"
#include "CCondVar.h"
#include "CMutex.h"
//
// CLock
//
CLock::CLock(const CMutex* mutex) :
m_mutex(mutex)
{
m_mutex->lock();
}
CLock::CLock(const CCondVarBase* cv) :
m_mutex(cv->getMutex())
{
m_mutex->lock();
}
CLock::~CLock()
{
m_mutex->unlock();
}

32
lib/mt/CLock.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef CLOCK_H
#define CLOCK_H
class CMutex;
class CCondVarBase;
//! Mutual exclusion lock utility
/*!
This class locks a mutex or condition variable in the c'tor and unlocks
it in the d'tor. It's easier and safer than manually locking and
unlocking since unlocking must usually be done no matter how a function
exits (including by unwinding due to an exception).
*/
class CLock {
public:
//! Lock the mutex \c mutex
CLock(const CMutex* mutex);
//! Lock the condition variable \c cv
CLock(const CCondVarBase* cv);
//! Unlock the mutex or condition variable
~CLock();
private:
// not implemented
CLock(const CLock&);
CLock& operator=(const CLock&);
private:
const CMutex* m_mutex;
};
#endif

136
lib/mt/CMutex.cpp Normal file
View File

@@ -0,0 +1,136 @@
#include "CMutex.h"
#include "CLog.h"
//
// CMutex
//
CMutex::CMutex()
{
init();
}
CMutex::CMutex(const CMutex&)
{
init();
}
CMutex::~CMutex()
{
fini();
}
CMutex&
CMutex::operator=(const CMutex&)
{
return *this;
}
#if HAVE_PTHREAD
#include <pthread.h>
#include <cerrno>
void
CMutex::init()
{
pthread_mutex_t* mutex = new pthread_mutex_t;
int status = pthread_mutex_init(mutex, NULL);
assert(status == 0);
// status = pthread_mutexattr_settype(mutex, PTHREAD_MUTEX_RECURSIVE);
// assert(status == 0);
m_mutex = reinterpret_cast<void*>(mutex);
}
void
CMutex::fini()
{
pthread_mutex_t* mutex = reinterpret_cast<pthread_mutex_t*>(m_mutex);
int status = pthread_mutex_destroy(mutex);
logc(status != 0, (CLOG_ERR "pthread_mutex_destroy status %d", status));
assert(status == 0);
delete mutex;
}
void
CMutex::lock() const
{
pthread_mutex_t* mutex = reinterpret_cast<pthread_mutex_t*>(m_mutex);
int status = pthread_mutex_lock(mutex);
switch (status) {
case 0:
// success
return;
case EDEADLK:
assert(0 && "lock already owned");
break;
case EAGAIN:
assert(0 && "too many recursive locks");
break;
default:
log((CLOG_ERR "pthread_mutex_lock status %d", status));
assert(0 && "unexpected error");
}
}
void
CMutex::unlock() const
{
pthread_mutex_t* mutex = reinterpret_cast<pthread_mutex_t*>(m_mutex);
int status = pthread_mutex_unlock(mutex);
switch (status) {
case 0:
// success
return;
case EPERM:
assert(0 && "thread doesn't own a lock");
break;
default:
log((CLOG_ERR "pthread_mutex_unlock status %d", status));
assert(0 && "unexpected error");
}
}
#endif // HAVE_PTHREAD
#if WINDOWS_LIKE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void
CMutex::init()
{
CRITICAL_SECTION* mutex = new CRITICAL_SECTION;
InitializeCriticalSection(mutex);
m_mutex = reinterpret_cast<void*>(mutex);
}
void
CMutex::fini()
{
CRITICAL_SECTION* mutex = reinterpret_cast<CRITICAL_SECTION*>(m_mutex);
DeleteCriticalSection(mutex);
delete mutex;
}
void
CMutex::lock() const
{
EnterCriticalSection(reinterpret_cast<CRITICAL_SECTION*>(m_mutex));
}
void
CMutex::unlock() const
{
LeaveCriticalSection(reinterpret_cast<CRITICAL_SECTION*>(m_mutex));
}
#endif // WINDOWS_LIKE

66
lib/mt/CMutex.h Normal file
View File

@@ -0,0 +1,66 @@
#ifndef CMUTEX_H
#define CMUTEX_H
//! Mutual exclusion
/*!
A non-recursive mutual exclusion object. Only one thread at a time can
hold a lock on a mutex. Any thread that attempts to lock a locked mutex
will block until the mutex is unlocked. At that time, if any threads are
blocked, exactly one waiting thread will acquire the lock and continue
running. A thread may not lock a mutex it already owns the lock on; if
it tries it will deadlock itself.
*/
class CMutex {
public:
CMutex();
//! Equivalent to default c'tor
/*!
Copy c'tor doesn't copy anything. It just makes it possible to
copy objects that contain a mutex.
*/
CMutex(const CMutex&);
~CMutex();
//! @name manipulators
//@{
//! Does nothing
/*!
This does nothing. It just makes it possible to assign objects
that contain a mutex.
*/
CMutex& operator=(const CMutex&);
//@}
//! @name accessors
//@{
//! Lock the mutex
/*!
Locks the mutex, which must not have been previously locked by the
calling thread. This blocks if the mutex is already locked by another
thread.
(cancellation point)
*/
void lock() const;
//! Unlock the mutex
/*!
Unlocks the mutex, which must have been previously locked by the
calling thread.
*/
void unlock() const;
//@}
private:
void init();
void fini();
private:
friend class CCondVarBase;
void* m_mutex;
};
#endif

159
lib/mt/CThread.cpp Normal file
View File

@@ -0,0 +1,159 @@
#include "CThread.h"
#include "CLock.h"
#include "CThreadRep.h"
#include "XThread.h"
#include "CLog.h"
#include "CStopwatch.h"
//
// CThread
//
CThread::CThread(IJob* job, void* userData)
{
m_rep = new CThreadRep(job, userData);
}
CThread::CThread(const CThread& thread) :
m_rep(thread.m_rep)
{
m_rep->ref();
}
CThread::CThread(CThreadRep* rep) :
m_rep(rep)
{
// do nothing. rep should have already been Ref()'d.
}
CThread::~CThread()
{
m_rep->unref();
}
CThread&
CThread::operator=(const CThread& thread)
{
if (thread.m_rep != m_rep) {
m_rep->unref();
m_rep = thread.m_rep;
m_rep->ref();
}
return *this;
}
void
CThread::init()
{
CThreadRep::initThreads();
}
void
CThread::sleep(double timeout)
{
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
if (timeout >= 0.0) {
currentRep->testCancel();
currentRep->sleep(timeout);
}
currentRep->testCancel();
}
void
CThread::exit(void* result)
{
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
log((CLOG_DEBUG1 "throw exit on thread %p", currentRep.operator->()));
throw XThreadExit(result);
}
bool
CThread::enableCancel(bool enable)
{
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
return currentRep->enableCancel(enable);
}
void
CThread::cancel()
{
m_rep->cancel();
}
void
CThread::setPriority(int n)
{
m_rep->setPriority(n);
}
CThread
CThread::getCurrentThread()
{
return CThread(CThreadRep::getCurrentThreadRep());
}
bool
CThread::wait(double timeout) const
{
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
return currentRep->wait(m_rep, timeout);
}
#if WINDOWS_LIKE
bool
CThread::waitForEvent(double timeout)
{
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
return currentRep->waitForEvent(timeout);
}
#endif
void
CThread::testCancel()
{
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
currentRep->testCancel();
}
void*
CThread::getResult() const
{
if (wait())
return m_rep->getResult();
else
return NULL;
}
void*
CThread::getUserData()
{
return m_rep->getUserData();
}
bool
CThread::operator==(const CThread& thread) const
{
return (m_rep == thread.m_rep);
}
bool
CThread::operator!=(const CThread& thread) const
{
return (m_rep != thread.m_rep);
}
//
// CThreadMaskCancel
//
CThreadMaskCancel::CThreadMaskCancel() :
m_old(CThread::enableCancel(false))
{
// do nothing
}
CThreadMaskCancel::~CThreadMaskCancel()
{
CThread::enableCancel(m_old);
}

243
lib/mt/CThread.h Normal file
View File

@@ -0,0 +1,243 @@
#ifndef CTHREAD_H
#define CTHREAD_H
#include "common.h"
class IJob;
class CThreadRep;
//! Thread handle
/*!
Creating a CThread creates a new context of execution (i.e. thread) that
runs simulatenously with the calling thread. A CThread is only a handle
to a thread; deleting a CThread does not cancel or destroy the thread it
refers to and multiple CThread objects can refer to the same thread.
Threads can terminate themselves but cannot be forced to terminate by
other threads. However, other threads can signal a thread to terminate
itself by cancelling it. And a thread can wait (block) on another thread
to terminate.
Most functions that can block for an arbitrary time are cancellation
points. A cancellation point is a function that can be interrupted by
a request to cancel the thread. Cancellation points are noted in the
documentation.
*/
// note -- do not derive from this class
class CThread {
public:
//! Run \c adoptedJob in a new thread
/*!
Create and start a new thread executing the \c adoptedJob. The
user data can be retrieved with getUserData(). The new thread
takes ownership of \c adoptedJob and will delete it.
*/
CThread(IJob* adoptedJob, void* userData = 0);
//! Duplicate a thread handle
/*!
Make a new thread object that refers to an existing thread.
This does \b not start a new thread.
*/
CThread(const CThread&);
//! Release a thread handle
/*!
Release a thread handle. This does not terminate the thread. A thread
will keep running until the job completes or calls exit() or allows
itself to be cancelled.
*/
~CThread();
//! @name manipulators
//@{
//! Assign thread handle
/*!
Assign a thread handle. This has no effect on the threads, it simply
makes this thread object refer to another thread. It does \b not
start a new thread.
*/
CThread& operator=(const CThread&);
//! Initialize the thread library
/*!
Initialize the thread library. This \b must be called before
any other thread methods or creating a thread object. It is
harmless to call init() multiple times.
*/
static void init();
//! Sleep
/*!
Blocks the calling thread for \c timeout seconds. If
\c timeout < 0.0 then the call returns immediately. If \c timeout
== 0.0 then the calling thread yields the CPU.
(cancellation point)
*/
static void sleep(double timeout);
//! Terminate the calling thread
/*!
Terminate the calling thread. This function does not return but
the stack is unwound and automatic objects are destroyed, as if
exit() threw an exception (which is, in fact, what it does). The
argument is saved as the result returned by getResult(). If you
have \c catch(...) blocks then you should add the following before
each to avoid catching the exit:
\code
catch(CThreadExit&) { throw; }
\endcode
or add the \c RETHROW_XTHREAD macro to the \c catch(...) block.
*/
static void exit(void*);
//! Enable or disable cancellation
/*!
Enable or disable cancellation. The default is enabled. This is not
a cancellation point so if you just enabled cancellation and want to
allow immediate cancellation you need to call testCancel().
Returns the previous state.
*/
static bool enableCancel(bool);
//! Cancel thread
/*!
Cancel the thread. cancel() never waits for the thread to
terminate; it just posts the cancel and returns. A thread will
terminate when it enters a cancellation point with cancellation
enabled. If cancellation is disabled then the cancel is
remembered but not acted on until the first call to a
cancellation point after cancellation is enabled.
A cancellation point is a function that can act on cancellation.
A cancellation point does not return if there's a cancel pending.
Instead, it unwinds the stack and destroys automatic objects, as
if cancel() threw an exception (which is, in fact, what it does).
Threads must take care to unlock and clean up any resources they
may have, especially mutexes. They can \c catch(XThreadCancel) to
do that then rethrow the exception or they can let it happen
automatically by doing clean up in the d'tors of automatic
objects (like CLock). Clients are strongly encouraged to do the latter.
During cancellation, further cancel() calls are ignored (i.e.
a thread cannot be interrupted by a cancel during cancellation).
Clients that \c catch(XThreadCancel) must always rethrow the
exception. Clients that \c catch(...) must either rethrow the
exception or include a \c catch(XThreadCancel) handler that
rethrows. The \c RETHROW_XTHREAD macro may be useful for that.
*/
void cancel();
//! Change thread priority
/*!
Change the priority of the thread. Normal priority is 0, 1 is
the next lower, etc. -1 is the next higher, etc. but boosting
the priority may not be permitted and will be silenty ignored.
*/
void setPriority(int n);
//@}
//! @name accessors
//@{
//! Get current thread's handle
/*!
Return a CThread object representing the calling thread.
*/
static CThread getCurrentThread();
//! Test for cancellation
/*!
testCancel() does nothing but is a cancellation point. Call
this to make a function itself a cancellation point. If the
thread was cancelled and cancellation is enabled this will
cause the thread to unwind the stack and terminate.
(cancellation point)
*/
static void testCancel();
//! Get the thread user data
/*!
Gets the user data passed to the c'tor that created this thread.
*/
void* getUserData();
//! Wait for thread to terminate
/*!
Waits for the thread to terminate (by exit() or cancel() or
by returning from the thread job) for up to \c timeout seconds,
returning true if the thread terminated and false otherwise.
This returns immediately with false if called by a thread on
itself and immediately with true if the thread has already
terminated. This will wait forever if \c timeout < 0.0.
(cancellation point)
*/
bool wait(double timeout = -1.0) const;
#if WINDOWS_LIKE
//! Wait for an event (win32)
/*!
Wait for the message queue to contain a message for up to \c timeout
seconds. This returns immediately if any message is available
(including messages that were already in the queue during the last
call to \c GetMessage() or \c PeekMessage() or waitForEvent().
Returns true iff a message is available. This will wait forever
if \c timeout < 0.0.
This method is available under win32 only.
(cancellation point)
*/
static bool waitForEvent(double timeout = -1.0);
#endif
//! Get the exit result
/*!
Returns the exit result. This does an implicit wait(). It returns
NULL immediately if called by a thread on itself or on a thread that
was cancelled.
(cancellation point)
*/
void* getResult() const;
//! Compare thread handles
/*!
Returns true if two CThread objects refer to the same thread.
*/
bool operator==(const CThread&) const;
//! Compare thread handles
/*!
Returns true if two CThread objects do not refer to the same thread.
*/
bool operator!=(const CThread&) const;
//@}
private:
CThread(CThreadRep*);
private:
CThreadRep* m_rep;
};
//! Disable cancellation utility
/*!
This class disables cancellation for the current thread in the c'tor
and enables it in the d'tor.
*/
class CThreadMaskCancel {
public:
CThreadMaskCancel();
~CThreadMaskCancel();
private:
bool m_old;
};
#endif

737
lib/mt/CThreadRep.cpp Normal file
View File

@@ -0,0 +1,737 @@
#include "CThreadRep.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "XThread.h"
#include "CLog.h"
#include "IJob.h"
#if HAVE_PTHREAD
# include <signal.h>
# define SIGWAKEUP SIGUSR1
#elif WINDOWS_LIKE
# if !defined(_MT)
# error multithreading compile option is required
# endif
# include <process.h>
#else
#error unsupported platform for multithreading
#endif
// FIXME -- temporary exception type
class XThreadUnavailable { };
//
// CThreadRep
//
CMutex* CThreadRep::s_mutex = NULL;
CThreadRep* CThreadRep::s_head = NULL;
#if HAVE_PTHREAD
pthread_t CThreadRep::s_signalThread;
#endif
CThreadRep::CThreadRep() :
m_prev(NULL),
m_next(NULL),
m_refCount(1),
m_job(NULL),
m_userData(NULL)
{
// note -- s_mutex must be locked on entry
assert(s_mutex != NULL);
// initialize stuff
init();
#if HAVE_PTHREAD
// get main thread id
m_thread = pthread_self();
#elif WINDOWS_LIKE
// get main thread id
m_thread = NULL;
m_id = GetCurrentThreadId();
#endif
// insert ourself into linked list
if (s_head != NULL) {
s_head->m_prev = this;
m_next = s_head;
}
s_head = this;
}
CThreadRep::CThreadRep(IJob* job, void* userData) :
m_prev(NULL),
m_next(NULL),
m_refCount(2), // 1 for us, 1 for thread
m_job(job),
m_userData(userData)
{
assert(m_job != NULL);
assert(s_mutex != NULL);
// create a thread rep for the main thread if the current thread
// is unknown. note that this might cause multiple "main" threads
// if threads are created external to this library.
getCurrentThreadRep()->unref();
// initialize
init();
// hold mutex while we create the thread
CLock lock(s_mutex);
// start the thread. throw if it doesn't start.
#if HAVE_PTHREAD
// mask some signals in all threads except the main thread
sigset_t sigset, oldsigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
pthread_sigmask(SIG_BLOCK, &sigset, &oldsigset);
int status = pthread_create(&m_thread, NULL, threadFunc, (void*)this);
pthread_sigmask(SIG_SETMASK, &oldsigset, NULL);
if (status != 0) {
throw XThreadUnavailable();
}
#elif WINDOWS_LIKE
unsigned int id;
m_thread = reinterpret_cast<HANDLE>(_beginthreadex(NULL, 0,
threadFunc, (void*)this, 0, &id));
m_id = static_cast<DWORD>(id);
if (m_thread == 0) {
throw XThreadUnavailable();
}
#endif
// insert ourself into linked list
if (s_head != NULL) {
s_head->m_prev = this;
m_next = s_head;
}
s_head = this;
// returning releases the locks, allowing the child thread to run
}
CThreadRep::~CThreadRep()
{
// note -- s_mutex must be locked on entry
// remove ourself from linked list
if (m_prev != NULL) {
m_prev->m_next = m_next;
}
if (m_next != NULL) {
m_next->m_prev = m_prev;
}
if (s_head == this) {
s_head = m_next;
}
// clean up
fini();
}
void
CThreadRep::initThreads()
{
if (s_mutex == NULL) {
s_mutex = new CMutex;
#if HAVE_PTHREAD
// install SIGWAKEUP handler
struct sigaction act;
sigemptyset(&act.sa_mask);
# if defined(SA_INTERRUPT)
act.sa_flags = SA_INTERRUPT;
# else
act.sa_flags = 0;
# endif
act.sa_handler = &threadCancel;
sigaction(SIGWAKEUP, &act, NULL);
// set signal mask
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGWAKEUP);
pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
sigemptyset(&sigset);
sigaddset(&sigset, SIGPIPE);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
// fire up the INT and TERM signal handler thread. we could
// instead arrange to catch and handle these signals but
// we'd be unable to cancel the main thread since no pthread
// calls are allowed in a signal handler.
int status = pthread_create(&s_signalThread, NULL,
&CThreadRep::threadSignalHandler,
getCurrentThreadRep());
if (status != 0) {
// can't create thread to wait for signal so don't block
// the signals.
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
}
#endif // HAVE_PTHREAD
}
}
void
CThreadRep::ref()
{
CLock lock(s_mutex);
++m_refCount;
}
void
CThreadRep::unref()
{
CLock lock(s_mutex);
if (--m_refCount == 0) {
delete this;
}
}
bool
CThreadRep::enableCancel(bool enable)
{
CLock lock(s_mutex);
const bool old = m_cancellable;
m_cancellable = enable;
return old;
}
bool
CThreadRep::isCancellable() const
{
CLock lock(s_mutex);
return (m_cancellable && !m_cancelling);
}
void*
CThreadRep::getResult() const
{
// no lock necessary since thread isn't running
return m_result;
}
void*
CThreadRep::getUserData() const
{
// no lock necessary because the value never changes
return m_userData;
}
CThreadRep*
CThreadRep::getCurrentThreadRep()
{
assert(s_mutex != NULL);
#if HAVE_PTHREAD
const pthread_t thread = pthread_self();
#elif WINDOWS_LIKE
const DWORD id = GetCurrentThreadId();
#endif
// lock list while we search
CLock lock(s_mutex);
// search
CThreadRep* scan = s_head;
while (scan != NULL) {
#if HAVE_PTHREAD
if (scan->m_thread == thread) {
break;
}
#elif WINDOWS_LIKE
if (scan->m_id == id) {
break;
}
#endif
scan = scan->m_next;
}
// create and use main thread rep if thread not found
if (scan == NULL) {
scan = new CThreadRep();
}
// ref for caller
++scan->m_refCount;
return scan;
}
void
CThreadRep::doThreadFunc()
{
// default priority is slightly below normal
setPriority(1);
// wait for parent to initialize this object
{ CLock lock(s_mutex); }
void* result = NULL;
try {
// go
log((CLOG_DEBUG1 "thread %p entry", this));
m_job->run();
log((CLOG_DEBUG1 "thread %p exit", this));
}
catch (XThreadCancel&) {
// client called cancel()
log((CLOG_DEBUG1 "caught cancel on thread %p", this));
}
catch (XThreadExit& e) {
// client called exit()
result = e.m_result;
log((CLOG_DEBUG1 "caught exit on thread %p", this));
}
catch (...) {
log((CLOG_DEBUG1 "exception on thread %p", this));
// note -- don't catch (...) to avoid masking bugs
delete m_job;
throw;
}
// done with job
delete m_job;
// store exit result (no lock necessary because the result will
// not be accessed until m_exit is set)
m_result = result;
}
#if HAVE_PTHREAD
#include "CStopwatch.h"
#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
# include <sys/time.h>
# else
# include <time.h>
# endif
#endif
void
CThreadRep::init()
{
m_result = NULL;
m_cancellable = true;
m_cancelling = false;
m_cancel = false;
m_exit = false;
}
void
CThreadRep::fini()
{
// main thread has NULL job
if (m_job != NULL) {
pthread_detach(m_thread);
}
}
void
CThreadRep::sleep(
double timeout)
{
if (timeout < 0.0) {
return;
}
struct timespec t;
t.tv_sec = (long)timeout;
t.tv_nsec = (long)(1000000000.0 * (timeout - (double)t.tv_sec));
while (nanosleep(&t, &t) < 0)
testCancel();
}
void
CThreadRep::cancel()
{
CLock lock(s_mutex);
if (m_cancellable && !m_cancelling) {
m_cancel = true;
}
else {
return;
}
// break out of system calls
log((CLOG_DEBUG1 "cancel thread %p", this));
pthread_kill(m_thread, SIGWAKEUP);
}
void
CThreadRep::testCancel()
{
{
CLock lock(s_mutex);
// done if not cancelled, not cancellable, or already cancelling
if (!m_cancel || !m_cancellable || m_cancelling) {
return;
}
// update state for cancel
m_cancel = false;
m_cancelling = true;
}
// start cancel
log((CLOG_DEBUG1 "throw cancel on thread %p", this));
throw XThreadCancel();
}
bool
CThreadRep::wait(CThreadRep* target, double timeout)
{
if (target == this) {
return false;
}
testCancel();
if (target->isExited()) {
return true;
}
if (timeout != 0.0) {
CStopwatch timer;
do {
sleep(0.05);
testCancel();
if (target->isExited()) {
return true;
}
} while (timeout < 0.0 || timer.getTime() <= timeout);
}
return false;
}
void
CThreadRep::setPriority(int)
{
// FIXME
}
bool
CThreadRep::isExited() const
{
CLock lock(s_mutex);
return m_exit;
}
void*
CThreadRep::threadFunc(void* arg)
{
CThreadRep* rep = (CThreadRep*)arg;
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
// run thread
rep->doThreadFunc();
{
// mark as terminated
CLock lock(s_mutex);
rep->m_exit = true;
}
// unref thread
rep->unref();
// terminate the thread
return NULL;
}
void
CThreadRep::threadCancel(int)
{
// do nothing
}
void*
CThreadRep::threadSignalHandler(void* vrep)
{
CThreadRep* mainThreadRep = reinterpret_cast<CThreadRep*>(vrep);
// detach
pthread_detach(pthread_self());
// add signal to mask
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigaddset(&sigset, SIGTERM);
// also wait on SIGABRT. on linux (others?) this thread (process)
// will persist after all the other threads evaporate due to an
// assert unless we wait on SIGABRT. that means our resources (like
// the socket we're listening on) are not released and never will be
// until the lingering thread is killed. i don't know why sigwait()
// should protect the thread from being killed. note that sigwait()
// doesn't actually return if we receive SIGABRT and, for some
// reason, we don't have to block SIGABRT.
sigaddset(&sigset, SIGABRT);
// we exit the loop via thread cancellation in sigwait()
for (;;) {
// wait
int signal;
sigwait(&sigset, &signal);
// if we get here then the signal was raised. cancel the thread.
mainThreadRep->cancel();
}
}
#endif // HAVE_PTHREAD
#if WINDOWS_LIKE
void
CThreadRep::init()
{
m_result = NULL;
m_cancellable = true;
m_cancelling = false;
m_exit = CreateEvent(NULL, TRUE, FALSE, NULL);
m_cancel = CreateEvent(NULL, TRUE, FALSE, NULL);
}
void
CThreadRep::fini()
{
// destroy the events
CloseHandle(m_cancel);
CloseHandle(m_exit);
// close the handle (main thread has a NULL handle)
if (m_thread != NULL) {
CloseHandle(m_thread);
}
}
void
CThreadRep::sleep(
double timeout)
{
if (isCancellable()) {
WaitForSingleObject(m_cancel, (DWORD)(1000.0 * timeout));
}
else {
Sleep((DWORD)(1000.0 * timeout));
}
}
void
CThreadRep::cancel()
{
log((CLOG_DEBUG1 "cancel thread %p", this));
SetEvent(m_cancel);
}
void
CThreadRep::testCancel()
{
// poll cancel event. return if not set.
const DWORD result = WaitForSingleObject(getCancelEvent(), 0);
if (result != WAIT_OBJECT_0) {
return;
}
{
// ignore if disabled or already cancelling
CLock lock(s_mutex);
if (!m_cancellable || m_cancelling)
return;
// update state for cancel
m_cancelling = true;
ResetEvent(m_cancel);
}
// start cancel
log((CLOG_DEBUG1 "throw cancel on thread %p", this));
throw XThreadCancel();
}
bool
CThreadRep::wait(CThreadRep* target, double timeout)
{
// get the current thread. if it's the same as the target thread
// then the thread is waiting on itself.
CThreadPtr currentRep(CThreadRep::getCurrentThreadRep());
if (target == this) {
return false;
}
// is cancellation enabled?
const DWORD n = (isCancellable() ? 2 : 1);
// convert timeout
DWORD t;
if (timeout < 0.0) {
t = INFINITE;
}
else {
t = (DWORD)(1000.0 * timeout);
}
// wait for this thread to be cancelled or for the target thread to
// terminate.
HANDLE handles[2];
handles[0] = target->getExitEvent();
handles[1] = m_cancel;
DWORD result = WaitForMultipleObjects(n, handles, FALSE, t);
// cancel takes priority
if (n == 2 && result != WAIT_OBJECT_0 + 1 &&
WaitForSingleObject(handles[1], 0) == WAIT_OBJECT_0) {
result = WAIT_OBJECT_0 + 1;
}
// handle result
switch (result) {
case WAIT_OBJECT_0 + 0:
// target thread terminated
return true;
case WAIT_OBJECT_0 + 1:
// this thread was cancelled. does not return.
testCancel();
default:
// timeout or error
return false;
}
}
bool
CThreadRep::waitForEvent(double timeout)
{
// check if messages are available first. if we don't do this then
// MsgWaitForMultipleObjects() will block even if the queue isn't
// empty if the messages in the queue were there before the last
// call to GetMessage()/PeekMessage().
if (HIWORD(GetQueueStatus(QS_ALLINPUT)) != 0) {
return true;
}
// is cancellation enabled?
const DWORD n = (isCancellable() ? 1 : 0);
// convert timeout
DWORD t;
if (timeout < 0.0) {
t = INFINITE;
}
else {
t = (DWORD)(1000.0 * timeout);
}
// wait for this thread to be cancelled or for the target thread to
// terminate.
HANDLE handles[1];
handles[0] = m_cancel;
DWORD result = MsgWaitForMultipleObjects(n, handles, FALSE, t, QS_ALLINPUT);
// handle result
switch (result) {
case WAIT_OBJECT_0 + 1:
// message is available
return true;
case WAIT_OBJECT_0 + 0:
// this thread was cancelled. does not return.
testCancel();
default:
// timeout or error
return false;
}
}
void
CThreadRep::setPriority(int n)
{
DWORD pClass = NORMAL_PRIORITY_CLASS;
if (n < 0) {
switch (-n) {
case 1: n = THREAD_PRIORITY_ABOVE_NORMAL; break;
case 2: n = THREAD_PRIORITY_HIGHEST; break;
default:
pClass = HIGH_PRIORITY_CLASS;
switch (-n - 3) {
case 0: n = THREAD_PRIORITY_LOWEST; break;
case 1: n = THREAD_PRIORITY_BELOW_NORMAL; break;
case 2: n = THREAD_PRIORITY_NORMAL; break;
case 3: n = THREAD_PRIORITY_ABOVE_NORMAL; break;
default: n = THREAD_PRIORITY_HIGHEST; break;
}
break;
}
}
else {
switch (n) {
case 0: n = THREAD_PRIORITY_NORMAL; break;
case 1: n = THREAD_PRIORITY_BELOW_NORMAL; break;
case 2: n = THREAD_PRIORITY_LOWEST; break;
default: n = THREAD_PRIORITY_IDLE; break;
}
}
SetPriorityClass(m_thread, pClass);
SetThreadPriority(m_thread, n);
}
HANDLE
CThreadRep::getExitEvent() const
{
// no lock necessary because the value never changes
return m_exit;
}
HANDLE
CThreadRep::getCancelEvent() const
{
// no lock necessary because the value never changes
return m_cancel;
}
unsigned int __stdcall
CThreadRep::threadFunc(void* arg)
{
CThreadRep* rep = (CThreadRep*)arg;
// run thread
rep->doThreadFunc();
// signal termination
SetEvent(rep->m_exit);
// unref thread
rep->unref();
// terminate the thread
return 0;
}
#endif // WINDOWS_LIKE

154
lib/mt/CThreadRep.h Normal file
View File

@@ -0,0 +1,154 @@
#ifndef CTHREADREP_H
#define CTHREADREP_H
#include "common.h"
#if HAVE_PTHREAD
#include <pthread.h>
#elif WINDOWS_LIKE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
class CMutex;
class IJob;
//! Internal thread class; do not use directly
class CThreadRep {
public:
CThreadRep(IJob*, void* userData);
// manipulators
// initialize the thread library
static void initThreads();
// change ref count
void ref();
void unref();
// the calling thread sleeps for t seconds. if t == 0.0 then
// the thread yields the CPU.
void sleep(double timeout);
// cancel the thread
void cancel();
// set cancellation state
bool enableCancel(bool enable);
// permanently disable further cancellation and start cancel cleanup
// if cancel has been called and cancellation hasn't been started yet.
void testCancel();
// wait for thread to exit or for current thread to cancel
bool wait(CThreadRep*, double timeout);
#if WINDOWS_LIKE
// wait for a message on the queue
bool waitForEvent(double timeout);
#endif
// set the priority
void setPriority(int n);
// accessors
// get the exit result for this thread. thread must be terminated.
void* getResult() const;
// get the user data passed to the constructor
void* getUserData() const;
// get the current cancellable state
bool isCancellable() const;
#if HAVE_PTHREAD
bool isExited() const;
#elif WINDOWS_LIKE
HANDLE getExitEvent() const;
HANDLE getCancelEvent() const;
#endif
// return the thread rep for the calling thread. the returned
// rep has been ref()'d.
static CThreadRep* getCurrentThreadRep();
protected:
virtual ~CThreadRep();
private:
// internal constructor
CThreadRep();
// initialization/cleanup
void init();
void fini();
// thread rep lookup
static CThreadRep* find();
// thread functions
#if HAVE_PTHREAD
static void* threadFunc(void* arg);
static void threadCancel(int);
static void* threadSignalHandler(void*);
#elif WINDOWS_LIKE
static unsigned int __stdcall threadFunc(void* arg);
#endif
void doThreadFunc();
// not implemented
CThreadRep(const CThreadRep&);
CThreadRep& operator=(const CThreadRep&);
private:
static CMutex* s_mutex;
static CThreadRep* s_head;
CThreadRep* m_prev;
CThreadRep* m_next;
int m_refCount;
IJob* m_job;
void* m_userData;
void* m_result;
bool m_cancellable;
bool m_cancelling;
#if HAVE_PTHREAD
pthread_t m_thread;
bool m_exit;
bool m_cancel;
static pthread_t s_signalThread;
#endif
#if WINDOWS_LIKE
HANDLE m_thread;
DWORD m_id;
HANDLE m_exit;
HANDLE m_cancel;
#endif
};
//
// CThreadPtr -- auto unref'ing pointer to thread rep
//
class CThreadPtr {
public:
CThreadPtr(CThreadRep* rep) : m_rep(rep) { }
~CThreadPtr() { m_rep->unref(); }
CThreadRep* operator->() const { return m_rep; }
private:
// not implemented
CThreadPtr(const CThreadPtr&);
CThreadPtr& operator=(const CThreadPtr&);
private:
CThreadRep* m_rep;
};
#endif

42
lib/mt/CTimerThread.cpp Normal file
View File

@@ -0,0 +1,42 @@
#include "CTimerThread.h"
#include "CThread.h"
#include "TMethodJob.h"
#include "CLog.h"
//
// CTimerThread
//
CTimerThread::CTimerThread(double timeout) : m_timeout(timeout)
{
if (m_timeout >= 0.0) {
m_callingThread = new CThread(CThread::getCurrentThread());
m_timingThread = new CThread(new TMethodJob<CTimerThread>(
this, &CTimerThread::timer));
}
else {
m_callingThread = NULL;
m_timingThread = NULL;
}
}
CTimerThread::~CTimerThread()
{
if (m_timingThread != NULL) {
log((CLOG_DEBUG1 "cancelling timeout"));
m_timingThread->cancel();
m_timingThread->wait();
log((CLOG_DEBUG1 "cancelled timeout"));
delete m_timingThread;
delete m_callingThread;
}
}
void
CTimerThread::timer(void*)
{
log((CLOG_DEBUG1 "timeout in %f seconds", m_timeout));
CThread::sleep(m_timeout);
log((CLOG_DEBUG1 "timeout"));
m_callingThread->cancel();
}

37
lib/mt/CTimerThread.h Normal file
View File

@@ -0,0 +1,37 @@
#ifndef CTIMERTHREAD_H
#define CTIMERTHREAD_H
class CThread;
//! A timer thread
/*!
An object of this class cancels the thread that called the c'tor unless
the object is destroyed before a given timeout.
*/
class CTimerThread {
public:
//! Cancel calling thread after \c timeout seconds
/*!
Cancels the calling thread after \c timeout seconds unless destroyed
before then. If \c timeout is less than zero then it never times
out and this is a no-op.
*/
CTimerThread(double timeout);
//! Cancel the timer thread
~CTimerThread();
private:
void timer(void*);
// not implemented
CTimerThread(const CTimerThread&);
CTimerThread& operator=(const CTimerThread&);
private:
double m_timeout;
CThread* m_callingThread;
CThread* m_timingThread;
};
#endif

23
lib/mt/Makefile.am Normal file
View File

@@ -0,0 +1,23 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ../..
noinst_LIBRARIES = libmt.a
libmt_a_SOURCES = \
CLock.cpp \
CMutex.cpp \
CCondVar.cpp \
CThread.cpp \
CThreadRep.cpp \
CTimerThread.cpp \
CCondVar.h \
CLock.h \
CMutex.h \
CThread.h \
CThreadRep.h \
CTimerThread.h \
XThread.h \
$(NULL)
INCLUDES = \
-I$(DEPTH)/lib/base \
$(NULL)

39
lib/mt/XThread.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef XTHREAD_H
#define XTHREAD_H
//! Generic thread exception
class XThread { };
//! Thread exception to exit
/*!
Thrown by CThread::exit() to exit a thread. Clients of CThread
must not throw this type but must rethrow it if caught (by
XThreadExit, XThread, or ...).
*/
class XThreadExit : public XThread {
public:
//! \c result is the result of the thread
XThreadExit(void* result) : m_result(result) { }
~XThreadExit() { }
public:
void* m_result;
};
//! Thread exception to cancel
/*!
Thrown to cancel a thread. Clients must not throw this type, but
must rethrow it if caught (by XThreadCancel, XThread, or ...).
*/
class XThreadCancel : public XThread { };
/*!
\def RETHROW_XTHREAD
Convenience macro to rethrow an XThread exception but ignore other
exceptions. Put this in your catch (...) handler after necessary
cleanup but before leaving or returning from the handler.
*/
#define RETHROW_XTHREAD \
try { throw; } catch (XThread&) { throw; } catch (...) { }
#endif

146
lib/mt/mt.dsp Normal file
View File

@@ -0,0 +1,146 @@
# Microsoft Developer Studio Project File - Name="mt" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=mt - 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 "mt.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 "mt.mak" CFG="mt - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "mt - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "mt - 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)" == "mt - 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" /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)" == "mt - 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" /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 "mt - Win32 Release"
# Name "mt - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\CCondVar.cpp
# End Source File
# Begin Source File
SOURCE=.\CLock.cpp
# End Source File
# Begin Source File
SOURCE=.\CMutex.cpp
# End Source File
# Begin Source File
SOURCE=.\CThread.cpp
# End Source File
# Begin Source File
SOURCE=.\CThreadRep.cpp
# End Source File
# Begin Source File
SOURCE=.\CTimerThread.cpp
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\CCondVar.h
# End Source File
# Begin Source File
SOURCE=.\CLock.h
# End Source File
# Begin Source File
SOURCE=.\CMutex.h
# End Source File
# Begin Source File
SOURCE=.\CThread.h
# End Source File
# Begin Source File
SOURCE=.\CThreadRep.h
# End Source File
# Begin Source File
SOURCE=.\CTimerThread.h
# End Source File
# Begin Source File
SOURCE=.\XThread.h
# End Source File
# End Group
# End Target
# End Project

430
lib/net/CNetwork.cpp Normal file
View File

@@ -0,0 +1,430 @@
#include "CNetwork.h"
#include "XNetwork.h"
#include "CLog.h"
//
// CNetwork
//
CNetwork::Socket (PASCAL FAR *CNetwork::accept)(CNetwork::Socket s, CNetwork::Address FAR *addr, CNetwork::AddressLength FAR *addrlen);
int (PASCAL FAR *CNetwork::bind)(CNetwork::Socket s, const CNetwork::Address FAR *addr, CNetwork::AddressLength namelen);
int (PASCAL FAR *CNetwork::close)(CNetwork::Socket s);
int (PASCAL FAR *CNetwork::connect)(CNetwork::Socket s, const CNetwork::Address FAR *name, CNetwork::AddressLength namelen);
int (PASCAL FAR *CNetwork::ioctl)(CNetwork::Socket s, int cmd, void FAR *);
int (PASCAL FAR *CNetwork::getpeername)(CNetwork::Socket s, CNetwork::Address FAR *name, CNetwork::AddressLength FAR * namelen);
int (PASCAL FAR *CNetwork::getsockname)(CNetwork::Socket s, CNetwork::Address FAR *name, CNetwork::AddressLength FAR * namelen);
int (PASCAL FAR *CNetwork::getsockopt)(CNetwork::Socket s, int level, int optname, void FAR * optval, CNetwork::AddressLength FAR *optlen);
unsigned long (PASCAL FAR *CNetwork::inet_addr)(const char FAR * cp);
char FAR * (PASCAL FAR *CNetwork::inet_ntoa)(struct in_addr in);
int (PASCAL FAR *CNetwork::listen)(CNetwork::Socket s, int backlog);
ssize_t (PASCAL FAR *CNetwork::read)(CNetwork::Socket s, void FAR * buf, size_t len);
ssize_t (PASCAL FAR *CNetwork::recv)(CNetwork::Socket s, void FAR * buf, size_t len, int flags);
ssize_t (PASCAL FAR *CNetwork::recvfrom)(CNetwork::Socket s, void FAR * buf, size_t len, int flags, CNetwork::Address FAR *from, CNetwork::AddressLength FAR * fromlen);
int (PASCAL FAR *CNetwork::poll)(CNetwork::PollEntry fds[], int nfds, int timeout);
ssize_t (PASCAL FAR *CNetwork::send)(CNetwork::Socket s, const void FAR * buf, size_t len, int flags);
ssize_t (PASCAL FAR *CNetwork::sendto)(CNetwork::Socket s, const void FAR * buf, size_t len, int flags, const CNetwork::Address FAR *to, CNetwork::AddressLength tolen);
int (PASCAL FAR *CNetwork::setsockopt)(CNetwork::Socket s, int level, int optname, const void FAR * optval, CNetwork::AddressLength optlen);
int (PASCAL FAR *CNetwork::shutdown)(CNetwork::Socket s, int how);
CNetwork::Socket (PASCAL FAR *CNetwork::socket)(int af, int type, int protocol);
ssize_t (PASCAL FAR *CNetwork::write)(CNetwork::Socket s, const void FAR * buf, size_t len);
struct hostent FAR * (PASCAL FAR *CNetwork::gethostbyaddr)(const char FAR * addr, int len, int type);
struct hostent FAR * (PASCAL FAR *CNetwork::gethostbyname)(const char FAR * name);
int (PASCAL FAR *CNetwork::gethostname)(char FAR * name, int namelen);
struct servent FAR * (PASCAL FAR *CNetwork::getservbyport)(int port, const char FAR * proto);
struct servent FAR * (PASCAL FAR *CNetwork::getservbyname)(const char FAR * name, const char FAR * proto);
struct protoent FAR * (PASCAL FAR *CNetwork::getprotobynumber)(int proto);
struct protoent FAR * (PASCAL FAR *CNetwork::getprotobyname)(const char FAR * name);
int (PASCAL FAR *CNetwork::getsockerror)(void);
int (PASCAL FAR *CNetwork::gethosterror)(void);
int (PASCAL FAR *CNetwork::setblocking)(CNetwork::Socket s, bool blocking);
#if WINDOWS_LIKE
int (PASCAL FAR *CNetwork::select)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout);
int (PASCAL FAR *CNetwork::WSACleanup)(void);
int (PASCAL FAR *CNetwork::__WSAFDIsSet)(CNetwork::Socket, fd_set FAR *);
const int CNetwork::Error = SOCKET_ERROR;
const CNetwork::Socket CNetwork::Null = INVALID_SOCKET;
#undef FD_ISSET
#define FD_ISSET(fd, set) CNetwork::__WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))
static HMODULE s_networkModule = NULL;
static
FARPROC
netGetProcAddress(HMODULE module, LPCSTR name)
{
FARPROC func = ::GetProcAddress(module, name);
if (!func) {
throw XNetworkFunctionUnavailable(name);
}
return func;
}
void
CNetwork::init()
{
assert(WSACleanup == NULL);
assert(s_networkModule == NULL);
// try winsock 2
HMODULE module = (HMODULE)::LoadLibrary("ws2_32.dll");
if (module == NULL) {
log((CLOG_NOTE "ws2_32.dll not found"));
}
else {
try {
init2(module);
return;
}
catch (XNetwork& e) {
log((CLOG_NOTE "ws2_32.dll error: %s", e.what()));
}
}
// try winsock 1
module = (HMODULE)::LoadLibrary("wsock32.dll");
if (module == NULL) {
log((CLOG_NOTE "wsock32.dll not found"));
}
else {
try {
init2(module);
return;
}
catch (XNetwork& e) {
log((CLOG_NOTE "wsock32.dll error: %s", e.what()));
}
}
// no networking
throw XNetworkUnavailable();
}
void
CNetwork::cleanup()
{
if (s_networkModule != NULL) {
WSACleanup();
::FreeLibrary(s_networkModule);
WSACleanup = NULL;
s_networkModule = NULL;
}
}
UInt32
CNetwork::swaphtonl(UInt32 v)
{
static const union { UInt16 s; UInt8 b[2]; } s_endian = { 0x1234 };
if (s_endian.b[0] == 0x34) {
return ((v & 0xff000000lu) >> 24) |
((v & 0x00ff0000lu) >> 8) |
((v & 0x0000ff00lu) << 8) |
((v & 0x000000fflu) << 24);
}
else {
return v;
}
}
UInt16
CNetwork::swaphtons(UInt16 v)
{
static const union { UInt16 s; UInt8 b[2]; } s_endian = { 0x1234 };
if (s_endian.b[0] == 0x34) {
return static_cast<UInt16>( ((v & 0xff00u) >> 8) |
((v & 0x00ffu) << 8));
}
else {
return v;
}
}
UInt32
CNetwork::swapntohl(UInt32 v)
{
return swaphtonl(v);
}
UInt16
CNetwork::swapntohs(UInt16 v)
{
return swaphtons(v);
}
#define setfunc(var, name, type) var = (type)netGetProcAddress(module, #name)
void
CNetwork::init2(
HMODULE module)
{
assert(module != NULL);
// get startup function address
int (PASCAL FAR *startup)(WORD, LPWSADATA);
setfunc(startup, WSAStartup, int(PASCAL FAR*)(WORD, LPWSADATA));
// startup network library
WORD version = MAKEWORD(1 /*major*/, 1 /*minor*/);
WSADATA data;
int err = startup(version, &data);
if (data.wVersion != version) {
throw XNetworkVersion(LOBYTE(data.wVersion), HIBYTE(data.wVersion));
}
if (err != 0) {
throw XNetworkFailed();
}
// get function addresses
setfunc(accept, accept, Socket (PASCAL FAR *)(Socket s, Address FAR *addr, AddressLength FAR *addrlen));
setfunc(bind, bind, int (PASCAL FAR *)(Socket s, const Address FAR *addr, AddressLength namelen));
setfunc(close, closesocket, int (PASCAL FAR *)(Socket s));
setfunc(connect, connect, int (PASCAL FAR *)(Socket s, const Address FAR *name, AddressLength namelen));
setfunc(ioctl, ioctlsocket, int (PASCAL FAR *)(Socket s, int cmd, void FAR *));
setfunc(getpeername, getpeername, int (PASCAL FAR *)(Socket s, Address FAR *name, AddressLength FAR * namelen));
setfunc(getsockname, getsockname, int (PASCAL FAR *)(Socket s, Address FAR *name, AddressLength FAR * namelen));
setfunc(getsockopt, getsockopt, int (PASCAL FAR *)(Socket s, int level, int optname, void FAR * optval, AddressLength FAR *optlen));
setfunc(inet_addr, inet_addr, unsigned long (PASCAL FAR *)(const char FAR * cp));
setfunc(inet_ntoa, inet_ntoa, char FAR * (PASCAL FAR *)(struct in_addr in));
setfunc(listen, listen, int (PASCAL FAR *)(Socket s, int backlog));
setfunc(recv, recv, ssize_t (PASCAL FAR *)(Socket s, void FAR * buf, size_t len, int flags));
setfunc(recvfrom, recvfrom, ssize_t (PASCAL FAR *)(Socket s, void FAR * buf, size_t len, int flags, Address FAR *from, AddressLength FAR * fromlen));
setfunc(send, send, ssize_t (PASCAL FAR *)(Socket s, const void FAR * buf, size_t len, int flags));
setfunc(sendto, sendto, ssize_t (PASCAL FAR *)(Socket s, const void FAR * buf, size_t len, int flags, const Address FAR *to, AddressLength tolen));
setfunc(setsockopt, setsockopt, int (PASCAL FAR *)(Socket s, int level, int optname, const void FAR * optval, AddressLength optlen));
setfunc(shutdown, shutdown, int (PASCAL FAR *)(Socket s, int how));
setfunc(socket, socket, Socket (PASCAL FAR *)(int af, int type, int protocol));
setfunc(gethostbyaddr, gethostbyaddr, struct hostent FAR * (PASCAL FAR *)(const char FAR * addr, int len, int type));
setfunc(gethostbyname, gethostbyname, struct hostent FAR * (PASCAL FAR *)(const char FAR * name));
setfunc(gethostname, gethostname, int (PASCAL FAR *)(char FAR * name, int namelen));
setfunc(getservbyport, getservbyport, struct servent FAR * (PASCAL FAR *)(int port, const char FAR * proto));
setfunc(getservbyname, getservbyname, struct servent FAR * (PASCAL FAR *)(const char FAR * name, const char FAR * proto));
setfunc(getprotobynumber, getprotobynumber, struct protoent FAR * (PASCAL FAR *)(int proto));
setfunc(getprotobyname, getprotobyname, struct protoent FAR * (PASCAL FAR *)(const char FAR * name));
setfunc(getsockerror, WSAGetLastError, int (PASCAL FAR *)(void));
setfunc(gethosterror, WSAGetLastError, int (PASCAL FAR *)(void));
setfunc(WSACleanup, WSACleanup, int (PASCAL FAR *)(void));
setfunc(__WSAFDIsSet, __WSAFDIsSet, int (PASCAL FAR *)(CNetwork::Socket, fd_set FAR *));
setfunc(select, select, int (PASCAL FAR *)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout));
poll = poll2;
read = read2;
write = write2;
setblocking = setblocking2;
s_networkModule = module;
}
int PASCAL FAR
CNetwork::poll2(PollEntry fd[], int nfds, int timeout)
{
int i;
// prepare sets for select
fd_set readSet, writeSet, errSet;
fd_set* readSetP = NULL;
fd_set* writeSetP = NULL;
fd_set* errSetP = NULL;
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
FD_ZERO(&errSet);
for (i = 0; i < nfds; ++i) {
if (fd[i].events & kPOLLIN) {
FD_SET(fd[i].fd, &readSet);
readSetP = &readSet;
}
if (fd[i].events & kPOLLOUT) {
FD_SET(fd[i].fd, &writeSet);
writeSetP = &writeSet;
}
if (true) {
FD_SET(fd[i].fd, &errSet);
errSetP = &errSet;
}
}
// prepare timeout for select
struct timeval timeout2;
struct timeval* timeout2P;
if (timeout < 0) {
timeout2P = NULL;
}
else {
timeout2P = &timeout2;
timeout2.tv_sec = timeout / 1000;
timeout2.tv_usec = 1000 * (timeout % 1000);
}
// do the select. note that winsock ignores the first argument.
int n = select(0, readSetP, writeSetP, errSetP, timeout2P);
// handle results
if (n == Error) {
return Error;
}
if (n == 0) {
return 0;
}
n = 0;
for (i = 0; i < nfds; ++i) {
fd[i].revents = 0;
if (FD_ISSET(fd[i].fd, &readSet)) {
fd[i].revents |= kPOLLIN;
}
if (FD_ISSET(fd[i].fd, &writeSet)) {
fd[i].revents |= kPOLLOUT;
}
if (FD_ISSET(fd[i].fd, &errSet)) {
fd[i].revents |= kPOLLERR;
}
if (fd[i].revents != 0) {
++n;
}
}
return n;
}
ssize_t PASCAL FAR
CNetwork::read2(Socket s, void FAR* buf, size_t len)
{
return recv(s, buf, len, 0);
}
ssize_t PASCAL FAR
CNetwork::write2(Socket s, const void FAR* buf, size_t len)
{
return send(s, buf, len, 0);
}
int PASCAL FAR
CNetwork::setblocking2(CNetwork::Socket s, bool blocking)
{
int flag = blocking ? 0 : 1;
return ioctl(s, FIONBIO, &flag);
}
#endif
#if UNIX_LIKE
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
// FIXME -- use reentrant versions of non-reentrant functions
#define setfunc(var, name, type) var = (type)::name
UInt32
CNetwork::swaphtonl(UInt32 v)
{
return htonl(v);
}
UInt16
CNetwork::swaphtons(UInt16 v)
{
return htons(v);
}
UInt32
CNetwork::swapntohl(UInt32 v)
{
return ntohl(v);
}
UInt16
CNetwork::swapntohs(UInt16 v)
{
return ntohs(v);
}
static
int
myerrno()
{
return errno;
}
static
int
myherrno()
{
return h_errno;
}
static
int
mygethostname(char* name, int namelen)
{
return gethostname(name, namelen);
}
static
int
mysetblocking(CNetwork::Socket s, bool blocking)
{
int mode = fcntl(s, F_GETFL, 0);
if (mode == -1) {
return -1;
}
if (blocking) {
mode &= ~O_NDELAY;
}
else {
mode |= O_NDELAY;
}
if (fcntl(s, F_SETFL, mode) < 0) {
return -1;
}
return 0;
}
const int CNetwork::Error = -1;
const CNetwork::Socket CNetwork::Null = -1;
void
CNetwork::init()
{
setfunc(accept, accept, Socket (PASCAL FAR *)(Socket s, Address FAR *addr, AddressLength FAR *addrlen));
setfunc(bind, bind, int (PASCAL FAR *)(Socket s, const Address FAR *addr, AddressLength namelen));
setfunc(close, close, int (PASCAL FAR *)(Socket s));
setfunc(connect, connect, int (PASCAL FAR *)(Socket s, const Address FAR *name, AddressLength namelen));
setfunc(ioctl, ioctl, int (PASCAL FAR *)(Socket s, int cmd, void FAR *));
setfunc(getpeername, getpeername, int (PASCAL FAR *)(Socket s, Address FAR *name, AddressLength FAR * namelen));
setfunc(getsockname, getsockname, int (PASCAL FAR *)(Socket s, Address FAR *name, AddressLength FAR * namelen));
setfunc(getsockopt, getsockopt, int (PASCAL FAR *)(Socket s, int level, int optname, void FAR * optval, AddressLength FAR *optlen));
setfunc(inet_addr, inet_addr, unsigned long (PASCAL FAR *)(const char FAR * cp));
setfunc(inet_ntoa, inet_ntoa, char FAR * (PASCAL FAR *)(struct in_addr in));
setfunc(listen, listen, int (PASCAL FAR *)(Socket s, int backlog));
setfunc(poll, poll, int (PASCAL FAR *)(CNetwork::PollEntry fds[], int nfds, int timeout));
setfunc(read, read, ssize_t (PASCAL FAR *)(CNetwork::Socket s, void FAR * buf, size_t len));
setfunc(recv, recv, ssize_t (PASCAL FAR *)(Socket s, void FAR * buf, size_t len, int flags));
setfunc(recvfrom, recvfrom, ssize_t (PASCAL FAR *)(Socket s, void FAR * buf, size_t len, int flags, Address FAR *from, AddressLength FAR * fromlen));
setfunc(send, send, ssize_t (PASCAL FAR *)(Socket s, const void FAR * buf, size_t len, int flags));
setfunc(sendto, sendto, ssize_t (PASCAL FAR *)(Socket s, const void FAR * buf, size_t len, int flags, const Address FAR *to, AddressLength tolen));
setfunc(setsockopt, setsockopt, int (PASCAL FAR *)(Socket s, int level, int optname, const void FAR * optval, AddressLength optlen));
setfunc(shutdown, shutdown, int (PASCAL FAR *)(Socket s, int how));
setfunc(socket, socket, Socket (PASCAL FAR *)(int af, int type, int protocol));
setfunc(write, write, ssize_t (PASCAL FAR *)(CNetwork::Socket s, const void FAR * buf, size_t len));
setfunc(gethostbyaddr, gethostbyaddr, struct hostent FAR * (PASCAL FAR *)(const char FAR * addr, int len, int type));
setfunc(gethostbyname, gethostbyname, struct hostent FAR * (PASCAL FAR *)(const char FAR * name));
setfunc(gethostname, mygethostname, int (PASCAL FAR *)(char FAR * name, int namelen));
setfunc(getservbyport, getservbyport, struct servent FAR * (PASCAL FAR *)(int port, const char FAR * proto));
setfunc(getservbyname, getservbyname, struct servent FAR * (PASCAL FAR *)(const char FAR * name, const char FAR * proto));
setfunc(getprotobynumber, getprotobynumber, struct protoent FAR * (PASCAL FAR *)(int proto));
setfunc(getprotobyname, getprotobyname, struct protoent FAR * (PASCAL FAR *)(const char FAR * name));
setfunc(getsockerror, myerrno, int (PASCAL FAR *)(void));
setfunc(gethosterror, myherrno, int (PASCAL FAR *)(void));
setfunc(setblocking, mysetblocking, int (PASCAL FAR *)(Socket, bool));
}
void
CNetwork::cleanup()
{
// do nothing
}
#endif

185
lib/net/CNetwork.h Normal file
View File

@@ -0,0 +1,185 @@
#ifndef CNETWORK_H
#define CNETWORK_H
#include "BasicTypes.h"
#if WINDOWS_LIKE
// declare no functions in winsock2
# define INCL_WINSOCK_API_PROTOTYPES 0
# define INCL_WINSOCK_API_TYPEDEFS 0
# include <winsock2.h>
typedef int ssize_t;
# if !defined(SOL_TCP)
# define SOL_TCP IPPROTO_TCP
# endif
#else
# undef FAR
# undef PASCAL
# define FAR
# define PASCAL
#endif
#if UNIX_LIKE
# include <sys/types.h>
# include <sys/poll.h>
# include <sys/socket.h>
# include <netdb.h>
# include <netinet/in.h>
# include <errno.h>
# if !defined(TCP_NODELAY) || !defined(SOL_TCP)
# include <netinet/tcp.h>
# endif
#endif
//! Networking functions
class CNetwork {
public:
// platform dependent types
#if WINDOWS_LIKE
typedef SOCKET Socket;
typedef struct sockaddr Address;
typedef int AddressLength;
typedef BOOL TCPNoDelayType;
class PollEntry {
public:
Socket fd;
short events;
short revents;
};
enum {
kPOLLIN = 1,
kPOLLOUT = 2,
kPOLLERR = 4,
kPOLLNVAL = 8
};
#elif UNIX_LIKE
typedef int Socket;
typedef struct sockaddr Address;
typedef socklen_t AddressLength;
typedef struct pollfd PollEntry;
typedef int TCPNoDelayType;
enum {
kPOLLIN = POLLIN,
kPOLLOUT = POLLOUT,
kPOLLERR = POLLERR,
kPOLLNVAL = POLLNVAL
};
#endif
//! @name manipulators
//@{
//! Initialize network subsystem
/*!
This \b must be called before any other calls to the network subsystem.
*/
static void init();
//! Clean up network subsystem
/*!
This should be called when the network subsystem is no longer needed
and no longer in use.
*/
static void cleanup();
// byte swapping functions
//! Swap bytes to network order
static UInt32 swaphtonl(UInt32 hostlong);
//! Swap bytes to network order
static UInt16 swaphtons(UInt16 hostshort);
//! Swap bytes to host order
static UInt32 swapntohl(UInt32 netlong);
//! Swap bytes to host order
static UInt16 swapntohs(UInt16 netshort);
//@}
//! @name constants
//@{
//! The error type
static const int Error;
//! The non-socket
static const Socket Null;
// getsockerror() constants
enum {
#if WINDOWS_LIKE
kEADDRINUSE = WSAEADDRINUSE,
kECONNECTING = WSAEWOULDBLOCK,
#elif UNIX_LIKE
kEADDRINUSE = EADDRINUSE,
kECONNECTING = EINPROGRESS,
#endif
kNone = 0
};
// gethosterror() constants
enum {
#if WINDOWS_LIKE
kHOST_NOT_FOUND = WSAHOST_NOT_FOUND,
kNO_DATA = WSANO_DATA,
kNO_RECOVERY = WSANO_RECOVERY,
kTRY_AGAIN = WSATRY_AGAIN,
#elif UNIX_LIKE
kHOST_NOT_FOUND = HOST_NOT_FOUND,
kNO_DATA = NO_DATA,
kNO_RECOVERY = NO_RECOVERY,
kTRY_AGAIN = TRY_AGAIN,
#endif
kHNone = 0
};
//@}
// socket interface (only available after init())
static Socket (PASCAL FAR *accept)(Socket s, Address FAR *addr, AddressLength FAR *addrlen);
static int (PASCAL FAR *bind)(Socket s, const Address FAR *addr, AddressLength namelen);
static int (PASCAL FAR *close)(Socket s);
static int (PASCAL FAR *connect)(Socket s, const Address FAR *name, AddressLength namelen);
static int (PASCAL FAR *ioctl)(Socket s, int cmd, void FAR *);
static int (PASCAL FAR *getpeername)(Socket s, Address FAR *name, AddressLength FAR * namelen);
static int (PASCAL FAR *getsockname)(Socket s, Address FAR *name, AddressLength FAR * namelen);
static int (PASCAL FAR *getsockopt)(Socket s, int level, int optname, void FAR * optval, AddressLength FAR *optlen);
static unsigned long (PASCAL FAR *inet_addr)(const char FAR * cp);
static char FAR * (PASCAL FAR *inet_ntoa)(struct in_addr in);
static int (PASCAL FAR *listen)(Socket s, int backlog);
static ssize_t (PASCAL FAR *read)(Socket s, void FAR * buf, size_t len);
static ssize_t (PASCAL FAR *recv)(Socket s, void FAR * buf, size_t len, int flags);
static ssize_t (PASCAL FAR *recvfrom)(Socket s, void FAR * buf, size_t len, int flags, Address FAR *from, AddressLength FAR * fromlen);
static int (PASCAL FAR *poll)(PollEntry[], int nfds, int timeout);
static ssize_t (PASCAL FAR *send)(Socket s, const void FAR * buf, size_t len, int flags);
static ssize_t (PASCAL FAR *sendto)(Socket s, const void FAR * buf, size_t len, int flags, const Address FAR *to, AddressLength tolen);
static int (PASCAL FAR *setsockopt)(Socket s, int level, int optname, const void FAR * optval, AddressLength optlen);
static int (PASCAL FAR *shutdown)(Socket s, int how);
static Socket (PASCAL FAR *socket)(int af, int type, int protocol);
static ssize_t (PASCAL FAR *write)(Socket s, const void FAR * buf, size_t len);
static struct hostent FAR * (PASCAL FAR *gethostbyaddr)(const char FAR * addr, int len, int type);
static struct hostent FAR * (PASCAL FAR *gethostbyname)(const char FAR * name);
static int (PASCAL FAR *gethostname)(char FAR * name, int namelen);
static struct servent FAR * (PASCAL FAR *getservbyport)(int port, const char FAR * proto);
static struct servent FAR * (PASCAL FAR *getservbyname)(const char FAR * name, const char FAR * proto);
static struct protoent FAR * (PASCAL FAR *getprotobynumber)(int proto);
static struct protoent FAR * (PASCAL FAR *getprotobyname)(const char FAR * name);
static int (PASCAL FAR *getsockerror)(void);
static int (PASCAL FAR *gethosterror)(void);
// convenience functions (only available after init())
//! Set socket to (non-)blocking operation
static int (PASCAL FAR *setblocking)(CNetwork::Socket s, bool blocking);
#if WINDOWS_LIKE
private:
static void init2(HMODULE);
static int PASCAL FAR poll2(PollEntry[], int nfds, int timeout);
static ssize_t PASCAL FAR read2(Socket s, void FAR * buf, size_t len);
static ssize_t PASCAL FAR write2(Socket s, const void FAR * buf, size_t len);
static int PASCAL FAR setblocking2(CNetwork::Socket s, bool blocking);
static int (PASCAL FAR *WSACleanup)(void);
static int (PASCAL FAR *__WSAFDIsSet)(CNetwork::Socket, fd_set FAR *);
static int (PASCAL FAR *select)(int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds, const struct timeval FAR *timeout);
#endif
};
#endif

172
lib/net/CNetworkAddress.cpp Normal file
View File

@@ -0,0 +1,172 @@
#include "CNetworkAddress.h"
#include "XSocket.h"
#include <cstdlib>
#include <cstring>
//
// CNetworkAddress
//
CNetworkAddress::CNetworkAddress() :
m_port(0)
{
// note -- make no calls to CNetwork socket interface here;
// we're often called prior to CNetwork::init().
struct sockaddr_in* inetAddress = reinterpret_cast<
struct sockaddr_in*>(&m_address);
inetAddress->sin_family = AF_INET;
inetAddress->sin_port = CNetwork::swaphtons(m_port);
inetAddress->sin_addr.s_addr = INADDR_ANY;
memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero));
}
CNetworkAddress::CNetworkAddress(UInt16 port) :
m_port(port)
{
if (port == 0) {
throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port);
}
struct sockaddr_in* inetAddress = reinterpret_cast<
struct sockaddr_in*>(&m_address);
inetAddress->sin_family = AF_INET;
inetAddress->sin_port = CNetwork::swaphtons(m_port);
inetAddress->sin_addr.s_addr = INADDR_ANY;
memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero));
}
CNetworkAddress::CNetworkAddress(const CString& hostname_, UInt16 port) :
m_hostname(hostname_),
m_port(port)
{
CString hostname(m_hostname);
if (port == 0) {
throw XSocketAddress(XSocketAddress::kBadPort, m_hostname, m_port);
}
// check for port suffix
CString::size_type i = hostname.rfind(':');
if (i != CString::npos && i + 1 < hostname.size()) {
// found a colon. see if it looks like an IPv6 address.
bool colonNotation = false;
bool dotNotation = false;
bool doubleColon = false;
for (CString::size_type j = 0; j < i; ++j) {
if (hostname[j] == ':') {
colonNotation = true;
dotNotation = false;
if (hostname[j + 1] == ':') {
doubleColon = true;
}
}
else if (hostname[j] == '.' && colonNotation) {
dotNotation = true;
}
}
// port suffix is ambiguous with IPv6 notation if there's
// a double colon and the end of the address is not in dot
// notation. in that case we assume it's not a port suffix.
// the user can replace the double colon with zeros to
// disambiguate.
if ((!doubleColon || dotNotation) || !colonNotation) {
char* end;
long suffixPort = strtol(hostname.c_str() + i + 1, &end, 10);
if (end == hostname.c_str() + i + 1 || *end != '\0' ||
suffixPort <= 0 || suffixPort > 65535) {
// bogus port
throw XSocketAddress(XSocketAddress::kBadPort,
m_hostname, m_port);
}
else {
// good port
port = static_cast<UInt16>(suffixPort);
hostname.erase(i);
}
}
}
// if hostname is empty then use wildcard address
if (hostname.empty()) {
struct sockaddr_in* inetAddress = reinterpret_cast<
struct sockaddr_in*>(&m_address);
inetAddress->sin_family = AF_INET;
inetAddress->sin_port = CNetwork::swaphtons(port);
inetAddress->sin_addr.s_addr = INADDR_ANY;
memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero));
return;
}
// convert dot notation to address
unsigned long addr = CNetwork::inet_addr(hostname.c_str());
if (addr != INADDR_NONE) {
struct sockaddr_in* inetAddress = reinterpret_cast<
struct sockaddr_in*>(&m_address);
inetAddress->sin_family = AF_INET;
inetAddress->sin_port = CNetwork::swaphtons(port);
inetAddress->sin_addr.s_addr = addr;
memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero));
return;
}
// look up name
struct hostent* hent = CNetwork::gethostbyname(hostname.c_str());
if (hent == NULL) {
switch (CNetwork::gethosterror()) {
case CNetwork::kHOST_NOT_FOUND:
throw XSocketAddress(XSocketAddress::kNotFound, hostname, port);
case CNetwork::kNO_DATA:
throw XSocketAddress(XSocketAddress::kNoAddress, hostname, port);
case CNetwork::kNO_RECOVERY:
case CNetwork::kTRY_AGAIN:
default:
throw XSocketAddress(XSocketAddress::kUnknown, hostname, port);
}
}
struct sockaddr_in* inetAddress = reinterpret_cast<
struct sockaddr_in*>(&m_address);
inetAddress->sin_family = hent->h_addrtype;
inetAddress->sin_port = CNetwork::swaphtons(port);
memcpy(&inetAddress->sin_addr, hent->h_addr_list[0], hent->h_length);
memset(inetAddress->sin_zero, 0, sizeof(inetAddress->sin_zero));
}
CNetworkAddress::~CNetworkAddress()
{
// do nothing
}
bool
CNetworkAddress::isValid() const
{
return (m_port != 0);
}
const CNetwork::Address*
CNetworkAddress::getAddress() const
{
return &m_address;
}
CNetwork::AddressLength
CNetworkAddress::getAddressLength() const
{
return sizeof(m_address);
}
CString
CNetworkAddress::getHostname() const
{
return m_hostname;
}
UInt16
CNetworkAddress::getPort() const
{
return m_port;
}

81
lib/net/CNetworkAddress.h Normal file
View File

@@ -0,0 +1,81 @@
#ifndef CNETWORKADDRESS_H
#define CNETWORKADDRESS_H
#include "CNetwork.h"
#include "CString.h"
#include "BasicTypes.h"
//! Network address type
/*!
This class represents a network address.
*/
class CNetworkAddress {
public:
/*!
Constructs the invalid address
*/
CNetworkAddress();
/*!
Construct the wildcard address with the given port. \c port must
not be zero.
*/
CNetworkAddress(UInt16 port);
/*!
Construct the network address for the given \c hostname and \c port.
If \c hostname can be parsed as a numerical address then that's how
it's used, otherwise the host name is looked up. If the lookup fails
then this throws XSocketAddress. If \c hostname ends in ":[0-9]+" then
that suffix is extracted and used as the port, overridding the port
parameter. Neither the extracted port or \c port may be zero.
*/
CNetworkAddress(const CString& hostname, UInt16 port);
~CNetworkAddress();
//! @name accessors
//@{
//! Check address validity
/*!
Returns true if this is not the invalid address.
*/
bool isValid() const;
//! Get address
/*!
Returns the address in the platform's native network address
structure.
*/
const CNetwork::Address* getAddress() const;
//! Get address length
/*!
Returns the length of the address in the platform's native network
address structure.
*/
CNetwork::AddressLength getAddressLength() const;
//! Get hostname
/*!
Returns the hostname passed to the c'tor sans the port suffix.
*/
CString getHostname() const;
//! Get port
/*!
Returns the port passed to the c'tor as a suffix to the hostname,
if that existed, otherwise as passed directly to the c'tor.
*/
UInt16 getPort() const;
//@}
private:
CNetwork::Address m_address;
CString m_hostname;
UInt16 m_port;
};
#endif

View File

@@ -0,0 +1,76 @@
#include "CTCPListenSocket.h"
#include "CTCPSocket.h"
#include "CNetworkAddress.h"
#include "XIO.h"
#include "XSocket.h"
#include "CThread.h"
//
// CTCPListenSocket
//
CTCPListenSocket::CTCPListenSocket()
{
m_fd = CNetwork::socket(PF_INET, SOCK_STREAM, 0);
if (m_fd == CNetwork::Null) {
throw XSocketCreate();
}
}
CTCPListenSocket::~CTCPListenSocket()
{
try {
close();
}
catch (...) {
// ignore
}
}
void
CTCPListenSocket::bind(const CNetworkAddress& addr)
{
if (CNetwork::bind(m_fd, addr.getAddress(),
addr.getAddressLength()) == CNetwork::Error) {
if (CNetwork::getsockerror() == CNetwork::kEADDRINUSE) {
throw XSocketAddressInUse();
}
throw XSocketBind();
}
if (CNetwork::listen(m_fd, 3) == CNetwork::Error) {
throw XSocketBind();
}
}
IDataSocket*
CTCPListenSocket::accept()
{
// accept asynchronously so we can check for cancellation
CNetwork::PollEntry pfds[1];
pfds[0].fd = m_fd;
pfds[0].events = CNetwork::kPOLLIN;
for (;;) {
CThread::testCancel();
const int status = CNetwork::poll(pfds, 1, 10);
if (status > 0 && (pfds[0].revents & CNetwork::kPOLLIN) != 0) {
CNetwork::Address addr;
CNetwork::AddressLength addrlen = sizeof(addr);
CNetwork::Socket fd = CNetwork::accept(m_fd, &addr, &addrlen);
if (fd != CNetwork::Null) {
return new CTCPSocket(fd);
}
}
}
}
void
CTCPListenSocket::close()
{
if (m_fd == CNetwork::Null) {
throw XIOClosed();
}
if (CNetwork::close(m_fd) == CNetwork::Error) {
throw XIOClose();
}
m_fd = CNetwork::Null;
}

View File

@@ -0,0 +1,27 @@
#ifndef CTCPLISTENSOCKET_H
#define CTCPLISTENSOCKET_H
#include "IListenSocket.h"
#include "CNetwork.h"
//! TCP listen socket
/*!
A listen socket using TCP.
*/
class CTCPListenSocket : public IListenSocket {
public:
CTCPListenSocket();
~CTCPListenSocket();
// ISocket overrides
virtual void bind(const CNetworkAddress&);
virtual void close();
// IListenSocket overrides
virtual IDataSocket* accept();
private:
CNetwork::Socket m_fd;
};
#endif

321
lib/net/CTCPSocket.cpp Normal file
View File

@@ -0,0 +1,321 @@
#include "CTCPSocket.h"
#include "CBufferedInputStream.h"
#include "CBufferedOutputStream.h"
#include "CNetworkAddress.h"
#include "XIO.h"
#include "XSocket.h"
#include "CCondVar.h"
#include "CLock.h"
#include "CMutex.h"
#include "CThread.h"
#include "CStopwatch.h"
#include "TMethodJob.h"
//
// CTCPSocket
//
CTCPSocket::CTCPSocket()
{
m_fd = CNetwork::socket(PF_INET, SOCK_STREAM, 0);
if (m_fd == CNetwork::Null) {
throw XSocketCreate();
}
init();
}
CTCPSocket::CTCPSocket(CNetwork::Socket fd) :
m_fd(fd)
{
assert(m_fd != CNetwork::Null);
init();
// socket starts in connected state
m_connected = kReadWrite;
// start handling socket
m_thread = new CThread(new TMethodJob<CTCPSocket>(
this, &CTCPSocket::ioThread));
}
CTCPSocket::~CTCPSocket()
{
try {
close();
}
catch (...) {
// ignore failures
}
// clean up
delete m_input;
delete m_output;
delete m_mutex;
}
void
CTCPSocket::bind(const CNetworkAddress& addr)
{
if (CNetwork::bind(m_fd, addr.getAddress(),
addr.getAddressLength()) == CNetwork::Error) {
if (errno == CNetwork::kEADDRINUSE) {
throw XSocketAddressInUse();
}
throw XSocketBind();
}
}
void
CTCPSocket::close()
{
// see if buffers should be flushed
bool doFlush = false;
{
CLock lock(m_mutex);
doFlush = (m_thread != NULL && (m_connected & kWrite) != 0);
}
// flush buffers
if (doFlush) {
m_output->flush();
}
// cause ioThread to exit
{
CLock lock(m_mutex);
if (m_fd != CNetwork::Null) {
CNetwork::shutdown(m_fd, 2);
m_connected = kClosed;
}
}
// wait for thread
if (m_thread != NULL) {
m_thread->wait();
delete m_thread;
m_thread = NULL;
}
// close socket
if (m_fd != CNetwork::Null) {
if (CNetwork::close(m_fd) == CNetwork::Error) {
throw XIOClose();
}
m_fd = CNetwork::Null;
}
}
void
CTCPSocket::connect(const CNetworkAddress& addr)
{
// connect asynchronously so we can check for cancellation
CNetwork::setblocking(m_fd, false);
if (CNetwork::connect(m_fd, addr.getAddress(),
addr.getAddressLength()) == CNetwork::Error) {
// check for failure
if (CNetwork::getsockerror() != CNetwork::kECONNECTING) {
XSocketConnect e;
CNetwork::setblocking(m_fd, true);
throw e;
}
// wait for connection or failure
CNetwork::PollEntry pfds[1];
pfds[0].fd = m_fd;
pfds[0].events = CNetwork::kPOLLOUT;
for (;;) {
CThread::testCancel();
const int status = CNetwork::poll(pfds, 1, 10);
if (status > 0) {
if ((pfds[0].revents & (CNetwork::kPOLLERR |
CNetwork::kPOLLNVAL)) != 0) {
// connection failed
int error = 0;
CNetwork::AddressLength size = sizeof(error);
CNetwork::setblocking(m_fd, true);
CNetwork::getsockopt(m_fd, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char*>(&error), &size);
throw XSocketConnect(error);
}
if ((pfds[0].revents & CNetwork::kPOLLOUT) != 0) {
int error;
CNetwork::AddressLength size = sizeof(error);
if (CNetwork::getsockopt(m_fd, SOL_SOCKET, SO_ERROR,
reinterpret_cast<char*>(&error),
&size) == CNetwork::Error ||
error != 0) {
// connection failed
CNetwork::setblocking(m_fd, true);
throw XSocketConnect(error);
}
// connected!
break;
}
}
}
}
// back to blocking
CNetwork::setblocking(m_fd, true);
// start servicing the socket
m_connected = kReadWrite;
m_thread = new CThread(new TMethodJob<CTCPSocket>(
this, &CTCPSocket::ioThread));
}
IInputStream*
CTCPSocket::getInputStream()
{
return m_input;
}
IOutputStream*
CTCPSocket::getOutputStream()
{
return m_output;
}
void
CTCPSocket::init()
{
m_mutex = new CMutex;
m_thread = NULL;
m_connected = kClosed;
m_input = new CBufferedInputStream(m_mutex,
new TMethodJob<CTCPSocket>(
this, &CTCPSocket::closeInput));
m_output = new CBufferedOutputStream(m_mutex,
new TMethodJob<CTCPSocket>(
this, &CTCPSocket::closeOutput));
// turn off Nagle algorithm. we send lots of very short messages
// that should be sent without (much) delay. for example, the
// mouse motion messages are much less useful if they're delayed.
CNetwork::TCPNoDelayType flag = 1;
CNetwork::setsockopt(m_fd, SOL_TCP, TCP_NODELAY, &flag, sizeof(flag));
}
void
CTCPSocket::ioThread(void*)
{
try {
ioService();
ioCleanup();
}
catch (...) {
ioCleanup();
throw;
}
}
void
CTCPSocket::ioCleanup()
{
try {
m_input->close();
}
catch (...) {
// ignore
}
try {
m_output->close();
}
catch (...) {
// ignore
}
}
void
CTCPSocket::ioService()
{
assert(m_fd != CNetwork::Null);
// now service the connection
CNetwork::PollEntry pfds[1];
pfds[0].fd = m_fd;
for (;;) {
{
// choose events to poll for
CLock lock(m_mutex);
pfds[0].events = 0;
if (m_connected == 0) {
return;
}
if ((m_connected & kRead) != 0) {
// still open for reading
pfds[0].events |= CNetwork::kPOLLIN;
}
if ((m_connected & kWrite) != 0 && m_output->getSize() > 0) {
// data queued for writing
pfds[0].events |= CNetwork::kPOLLOUT;
}
}
// check for status
const int status = CNetwork::poll(pfds, 1, 10);
// transfer data and handle errors
if (status == 1) {
if ((pfds[0].revents & (CNetwork::kPOLLERR |
CNetwork::kPOLLNVAL)) != 0) {
// stream is no good anymore so bail
m_input->hangup();
return;
}
// read some data
if (pfds[0].revents & CNetwork::kPOLLIN) {
UInt8 buffer[4096];
ssize_t n = CNetwork::read(m_fd, buffer, sizeof(buffer));
if (n > 0) {
CLock lock(m_mutex);
m_input->write(buffer, n);
}
else if (n == 0) {
// stream hungup
m_input->hangup();
m_connected &= ~kRead;
}
}
// write some data
if (pfds[0].revents & CNetwork::kPOLLOUT) {
CLock lock(m_mutex);
// get amount of data to write
UInt32 n = m_output->getSize();
// write data
const void* buffer = m_output->peek(n);
n = (UInt32)CNetwork::write(m_fd, buffer, n);
// discard written data
if (n > 0) {
m_output->pop(n);
}
else if (n == (UInt32)-1 && CNetwork::getsockerror() == EPIPE) {
return;
}
}
}
}
}
void
CTCPSocket::closeInput(void*)
{
// note -- m_mutex should already be locked
CNetwork::shutdown(m_fd, 0);
m_connected &= ~kRead;
}
void
CTCPSocket::closeOutput(void*)
{
// note -- m_mutex should already be locked
CNetwork::shutdown(m_fd, 1);
m_connected &= ~kWrite;
}

53
lib/net/CTCPSocket.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef CTCPSOCKET_H
#define CTCPSOCKET_H
#include "IDataSocket.h"
#include "CNetwork.h"
class CMutex;
template <class T>
class CCondVar;
class CThread;
class CBufferedInputStream;
class CBufferedOutputStream;
//! TCP data socket
/*!
A data socket using TCP.
*/
class CTCPSocket : public IDataSocket {
public:
CTCPSocket();
CTCPSocket(CNetwork::Socket);
~CTCPSocket();
// ISocket overrides
virtual void bind(const CNetworkAddress&);
virtual void close();
// IDataSocket overrides
virtual void connect(const CNetworkAddress&);
virtual IInputStream* getInputStream();
virtual IOutputStream* getOutputStream();
private:
void init();
void ioThread(void*);
void ioCleanup();
void ioService();
void closeInput(void*);
void closeOutput(void*);
private:
enum { kClosed = 0, kRead = 1, kWrite = 2, kReadWrite = 3 };
CNetwork::Socket m_fd;
CBufferedInputStream* m_input;
CBufferedOutputStream* m_output;
CMutex* m_mutex;
CThread* m_thread;
UInt32 m_connected;
};
#endif

49
lib/net/IDataSocket.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef IDATASOCKET_H
#define IDATASOCKET_H
#include "ISocket.h"
class IInputStream;
class IOutputStream;
//! Data stream socket interface
/*!
This interface defines the methods common to all network sockets that
represent a full-duplex data stream.
*/
class IDataSocket : public ISocket {
public:
//! @name manipulators
//@{
//! Connect socket
/*!
Attempt to connect to a remote endpoint. This waits until the
connection is established or fails. If it fails it throws an
XSocketConnect exception.
(cancellation point)
*/
virtual void connect(const CNetworkAddress&) = 0;
//! Get input stream
/*!
Returns the input stream for reading from the socket. Closing this
stream will shutdown the socket for reading.
*/
virtual IInputStream* getInputStream() = 0;
//! Get output stream
/*!
Returns the output stream for writing to the socket. Closing this
stream will shutdown the socket for writing.
*/
virtual IOutputStream* getOutputStream() = 0;
//@}
// ISocket overrides
virtual void bind(const CNetworkAddress&) = 0;
virtual void close() = 0;
};
#endif

34
lib/net/IListenSocket.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef ILISTENSOCKET_H
#define ILISTENSOCKET_H
#include "ISocket.h"
class IDataSocket;
//! Listen socket interface
/*!
This interface defines the methods common to all network sockets that
listen for incoming connections.
*/
class IListenSocket : public ISocket {
public:
//! @name manipulators
//@{
//! Accept connection
/*!
Wait for and accept a connection, returning a socket representing
the full-duplex data stream.
(cancellation point)
*/
virtual IDataSocket* accept() = 0;
//@}
// ISocket overrides
virtual void bind(const CNetworkAddress&) = 0;
virtual void close() = 0;
};
#endif

32
lib/net/ISocket.h Normal file
View File

@@ -0,0 +1,32 @@
#ifndef ISOCKET_H
#define ISOCKET_H
#include "IInterface.h"
class CNetworkAddress;
//! Generic socket interface
/*!
This interface defines the methods common to all network sockets.
*/
class ISocket : public IInterface {
public:
//! @name manipulators
//@{
//! Bind socket to address
/*!
Binds the socket to a particular address.
*/
virtual void bind(const CNetworkAddress&) = 0;
//! Close socket
/*!
Closes the socket. This should flush the output stream.
*/
virtual void close() = 0;
//@}
};
#endif

27
lib/net/Makefile.am Normal file
View File

@@ -0,0 +1,27 @@
## Process this file with automake to produce Makefile.in
NULL =
DEPTH = ../..
noinst_LIBRARIES = libnet.a
libnet_a_SOURCES = \
CNetwork.cpp \
CNetworkAddress.cpp \
CTCPSocket.cpp \
CTCPListenSocket.cpp \
XNetwork.cpp \
XSocket.cpp \
CNetwork.h \
CNetworkAddress.h \
CTCPListenSocket.h \
CTCPSocket.h \
IDataSocket.h \
IListenSocket.h \
ISocket.h \
XNetwork.h \
XSocket.h \
$(NULL)
INCLUDES = \
-I$(DEPTH)/lib/base \
-I$(DEPTH)/lib/mt \
-I$(DEPTH)/lib/io \
$(NULL)

79
lib/net/XNetwork.cpp Normal file
View File

@@ -0,0 +1,79 @@
#include "XNetwork.h"
//
// XNetworkUnavailable
//
CString
XNetworkUnavailable::getWhat() const throw()
{
return format("XNetworkUnavailable", "network library is not available");
}
//
// XNetworkFailed
//
CString
XNetworkFailed::getWhat() const throw()
{
return format("XNetworkFailed", "cannot initialize network library");
}
//
// XNetworkVersion
//
XNetworkVersion::XNetworkVersion(int major, int minor) throw() :
m_major(major),
m_minor(minor)
{
// do nothing
}
int
XNetworkVersion::getMajor() const throw()
{
return m_major;
}
int
XNetworkVersion::getMinor() const throw()
{
return m_minor;
}
CString
XNetworkVersion::getWhat() const throw()
{
return format("XNetworkVersion",
"unsupported network version %{1}.%{2}",
CStringUtil::print("%d", m_major).c_str(),
CStringUtil::print("%d", m_minor).c_str());
}
//
// XNetworkFunctionUnavailable
//
XNetworkFunctionUnavailable::XNetworkFunctionUnavailable(
const char* name) throw()
{
try {
m_name = name;
}
catch (...) {
// ignore
}
}
CString
XNetworkFunctionUnavailable::getWhat() const throw()
{
return format("XNetworkFunctionUnavailable",
"missing network function %{1}",
m_name.c_str());
}

80
lib/net/XNetwork.h Normal file
View File

@@ -0,0 +1,80 @@
#ifndef XNETWORK_H
#define XNETWORK_H
#include "XBase.h"
#include "CString.h"
//! Generic network exception
/*!
Network exceptions are thrown when initializing the network subsystem
and not during normal network use.
*/
class XNetwork : public XBase { };
//! Network subsystem not available exception
/*!
Thrown when the network subsystem is unavailable, typically because
the necessary shared library is unavailable.
*/
class XNetworkUnavailable : public XNetwork {
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
//! Network subsystem failed exception
/*!
Thrown when the network subsystem cannot be initialized.
*/
class XNetworkFailed : public XNetwork {
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
//! Network subsystem vesion unsupported exception
/*!
Thrown when the network subsystem has a version incompatible with
what's expected.
*/
class XNetworkVersion : public XNetwork {
public:
XNetworkVersion(int major, int minor) throw();
//! @name accessors
//@{
//! Get the network subsystem's major version
int getMajor() const throw();
//! Get the network subsystem's minor version
int getMinor() const throw();
//@}
protected:
// XBase overrides
virtual CString getWhat() const throw();
private:
int m_major;
int m_minor;
};
//! Network subsystem incomplete exception
/*!
Thrown when the network subsystem is missing an expected and required
function.
*/
class XNetworkFunctionUnavailable : public XNetwork {
public:
XNetworkFunctionUnavailable(const char* name) throw();
protected:
// XBase overrides
virtual CString getWhat() const throw();
private:
CString m_name;
};
#endif

102
lib/net/XSocket.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "XSocket.h"
//
// XSocketAddress
//
XSocketAddress::XSocketAddress(EError error,
const CString& hostname, UInt16 port) throw() :
m_error(error),
m_hostname(hostname),
m_port(port)
{
// do nothing
}
XSocketAddress::EError
XSocketAddress::getError() const throw()
{
return m_error;
}
CString
XSocketAddress::getHostname() const throw()
{
return m_hostname;
}
UInt16
XSocketAddress::getPort() const throw()
{
return m_port;
}
CString
XSocketAddress::getWhat() const throw()
{
static const char* s_errorID[] = {
"XSocketAddressUnknown",
"XSocketAddressNotFound",
"XSocketAddressNoAddress",
"XSocketAddressBadPort"
};
static const char* s_errorMsg[] = {
"unknown error for: %{1}:%{2}",
"address not found for: %{1}",
"no address for: %{1}",
"invalid port" // m_port may not be set to the bad port
};
return format(s_errorID[m_error], s_errorMsg[m_error],
m_hostname.c_str(),
CStringUtil::print("%d", m_port).c_str());
}
//
// XSocketErrno
//
XSocketErrno::XSocketErrno() :
MXErrno()
{
// do nothing
}
XSocketErrno::XSocketErrno(int err) :
MXErrno(err)
{
// do nothing
}
//
// XSocketBind
//
CString
XSocketBind::getWhat() const throw()
{
return format("XSocketBind", "cannot bind address");
}
//
// XSocketConnect
//
CString
XSocketConnect::getWhat() const throw()
{
return format("XSocketConnect", "cannot connect socket");
}
//
// XSocketCreate
//
CString
XSocketCreate::getWhat() const throw()
{
return format("XSocketCreate", "cannot create socket");
}

109
lib/net/XSocket.h Normal file
View File

@@ -0,0 +1,109 @@
#ifndef XSOCKET_H
#define XSOCKET_H
#include "XBase.h"
#include "CString.h"
#include "BasicTypes.h"
//! Generic socket exception
class XSocket : public XBase { };
//! Socket bad address exception
/*!
Thrown when attempting to create an invalid network address.
*/
class XSocketAddress : public XSocket {
public:
//! Failure codes
enum EError {
kUnknown, //!< Unknown error
kNotFound, //!< The hostname is unknown
kNoAddress, //!< The hostname is valid but has no IP address
kBadPort //!< The port is invalid
};
XSocketAddress(EError, const CString& hostname, UInt16 port) throw();
//! @name accessors
//@{
//! Get the error code
EError getError() const throw();
//! Get the hostname
CString getHostname() const throw();
//! Get the port
UInt16 getPort() const throw();
//@}
protected:
// XBase overrides
virtual CString getWhat() const throw();
private:
EError m_error;
CString m_hostname;
UInt16 m_port;
};
//! Generic socket exception using \c errno
class XSocketErrno : public XSocket, public MXErrno {
public:
XSocketErrno();
XSocketErrno(int);
};
//! Socket cannot bind address exception
/*!
Thrown when a socket cannot be bound to an address.
*/
class XSocketBind : public XSocketErrno {
public:
XSocketBind() { }
XSocketBind(int e) : XSocketErrno(e) { }
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
//! Socket address in use exception
/*!
Thrown when a socket cannot be bound to an address because the address
is already in use.
*/
class XSocketAddressInUse : public XSocketBind {
public:
XSocketAddressInUse() { }
XSocketAddressInUse(int e) : XSocketBind(e) { }
};
//! Cannot connect socket exception
/*!
Thrown when a socket cannot connect to a remote endpoint.
*/
class XSocketConnect : public XSocketErrno {
public:
XSocketConnect() { }
XSocketConnect(int e) : XSocketErrno(e) { }
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
//! Cannot create socket exception
/*!
Thrown when a socket cannot be created (by the operating system).
*/
class XSocketCreate : public XSocketErrno {
public:
XSocketCreate() { }
XSocketCreate(int e) : XSocketErrno(e) { }
protected:
// XBase overrides
virtual CString getWhat() const throw();
};
#endif

150
lib/net/net.dsp Normal file
View File

@@ -0,0 +1,150 @@
# Microsoft Developer Studio Project File - Name="net" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Static Library" 0x0104
CFG=net - 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 "net.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 "net.mak" CFG="net - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "net - Win32 Release" (based on "Win32 (x86) Static Library")
!MESSAGE "net - 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)" == "net - 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 "..\io" /I "..\mt" /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)" == "net - 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 "..\io" /I "..\mt" /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 "net - Win32 Release"
# Name "net - Win32 Debug"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\CNetwork.cpp
# End Source File
# Begin Source File
SOURCE=.\CNetworkAddress.cpp
# End Source File
# Begin Source File
SOURCE=.\CTCPListenSocket.cpp
# End Source File
# Begin Source File
SOURCE=.\CTCPSocket.cpp
# End Source File
# Begin Source File
SOURCE=.\XNetwork.cpp
# End Source File
# Begin Source File
SOURCE=.\XSocket.cpp
# End Source File
# End Group
# Begin Group "Header Files"
# PROP Default_Filter "h;hpp;hxx;hm;inl"
# Begin Source File
SOURCE=.\CNetwork.h
# End Source File
# Begin Source File
SOURCE=.\CNetworkAddress.h
# End Source File
# Begin Source File
SOURCE=.\CTCPListenSocket.h
# End Source File
# Begin Source File
SOURCE=.\CTCPSocket.h
# End Source File
# Begin Source File
SOURCE=.\IListenSocket.h
# End Source File
# Begin Source File
SOURCE=.\ISocket.h
# End Source File
# Begin Source File
SOURCE=.\XNetwork.h
# End Source File
# Begin Source File
SOURCE=.\XSocket.h
# End Source File
# End Group
# End Target
# End Project

View File

@@ -0,0 +1,147 @@
#include "CMSWindowsClipboard.h"
#include "CMSWindowsClipboardTextConverter.h"
#include "CMSWindowsClipboardUTF16Converter.h"
#include "CLog.h"
//
// CMSWindowsClipboard
//
CMSWindowsClipboard::CMSWindowsClipboard(HWND window) :
m_window(window),
m_time(0)
{
// add converters, most desired first
m_converters.push_back(new CMSWindowsClipboardUTF16Converter);
m_converters.push_back(new CMSWindowsClipboardTextConverter);
}
CMSWindowsClipboard::~CMSWindowsClipboard()
{
clearConverters();
}
bool
CMSWindowsClipboard::empty()
{
log((CLOG_DEBUG "empty clipboard"));
if (!EmptyClipboard()) {
log((CLOG_DEBUG "failed to grab clipboard"));
return false;
}
return true;
}
void
CMSWindowsClipboard::add(EFormat format, const CString& data)
{
log((CLOG_DEBUG "add %d bytes to clipboard format: %d", data.size(), format));
// convert data to win32 form
for (ConverterList::const_iterator index = m_converters.begin();
index != m_converters.end(); ++index) {
IMSWindowsClipboardConverter* converter = *index;
// skip converters for other formats
if (converter->getFormat() == format) {
HANDLE win32Data = converter->fromIClipboard(data);
if (win32Data != NULL) {
UINT win32Format = converter->getWin32Format();
if (SetClipboardData(win32Format, win32Data) == NULL) {
// free converted data if we couldn't put it on
// the clipboard
GlobalFree(win32Data);
}
}
}
}
}
bool
CMSWindowsClipboard::open(Time time) const
{
log((CLOG_DEBUG "open clipboard"));
if (!OpenClipboard(m_window)) {
log((CLOG_WARN "failed to open clipboard"));
return false;
}
m_time = time;
return true;
}
void
CMSWindowsClipboard::close() const
{
log((CLOG_DEBUG "close clipboard"));
CloseClipboard();
}
IClipboard::Time
CMSWindowsClipboard::getTime() const
{
return m_time;
}
bool
CMSWindowsClipboard::has(EFormat format) const
{
for (ConverterList::const_iterator index = m_converters.begin();
index != m_converters.end(); ++index) {
IMSWindowsClipboardConverter* converter = *index;
if (converter->getFormat() == format) {
if (IsClipboardFormatAvailable(converter->getWin32Format())) {
return true;
}
}
}
return false;
}
CString
CMSWindowsClipboard::get(EFormat format) const
{
// find the converter for the first clipboard format we can handle
IMSWindowsClipboardConverter* converter = NULL;
UINT win32Format = EnumClipboardFormats(0);
while (converter == NULL && win32Format != 0) {
for (ConverterList::const_iterator index = m_converters.begin();
index != m_converters.end(); ++index) {
converter = *index;
if (converter->getWin32Format() == win32Format &&
converter->getFormat() == format) {
break;
}
converter = NULL;
}
win32Format = EnumClipboardFormats(win32Format);
}
// if no converter then we don't recognize any formats
if (converter == NULL) {
return CString();
}
// get a handle to the clipboard data
HANDLE win32Data = GetClipboardData(converter->getWin32Format());
if (win32Data == NULL) {
return CString();
}
// convert
return converter->toIClipboard(win32Data);
}
void
CMSWindowsClipboard::clearConverters()
{
for (ConverterList::iterator index = m_converters.begin();
index != m_converters.end(); ++index) {
delete *index;
}
m_converters.clear();
}

View File

@@ -0,0 +1,69 @@
#ifndef CMSWINDOWSCLIPBOARD_H
#define CMSWINDOWSCLIPBOARD_H
#include "IClipboard.h"
#include "stdvector.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
class IMSWindowsClipboardConverter;
//! Microsoft windows clipboard implementation
class CMSWindowsClipboard : public IClipboard {
public:
CMSWindowsClipboard(HWND window);
virtual ~CMSWindowsClipboard();
// IClipboard overrides
virtual bool empty();
virtual void add(EFormat, const CString& data);
virtual bool open(Time) const;
virtual void close() const;
virtual Time getTime() const;
virtual bool has(EFormat) const;
virtual CString get(EFormat) const;
private:
void clearConverters();
UINT convertFormatToWin32(EFormat) const;
HANDLE convertTextToWin32(const CString& data) const;
CString convertTextFromWin32(HANDLE) const;
private:
typedef std::vector<IMSWindowsClipboardConverter*> ConverterList;
HWND m_window;
mutable Time m_time;
ConverterList m_converters;
};
//! Clipboard format converter interface
/*!
This interface defines the methods common to all win32 clipboard format
converters.
*/
class IMSWindowsClipboardConverter : public IInterface {
public:
// accessors
// return the clipboard format this object converts from/to
virtual IClipboard::EFormat
getFormat() const = 0;
// return the atom representing the win32 clipboard format that
// this object converts from/to
virtual UINT getWin32Format() const = 0;
// convert from the IClipboard format to the win32 clipboard format.
// the input data must be in the IClipboard format returned by
// getFormat(). the return data will be in the win32 clipboard
// format returned by getWin32Format(), allocated by GlobalAlloc().
virtual HANDLE fromIClipboard(const CString&) const = 0;
// convert from the win32 clipboard format to the IClipboard format
// (i.e., the reverse of fromIClipboard()).
virtual CString toIClipboard(HANDLE data) const = 0;
};
#endif

View File

@@ -0,0 +1,131 @@
#include "CMSWindowsClipboardAnyTextConverter.h"
//
// CMSWindowsClipboardAnyTextConverter
//
CMSWindowsClipboardAnyTextConverter::CMSWindowsClipboardAnyTextConverter()
{
// do nothing
}
CMSWindowsClipboardAnyTextConverter::~CMSWindowsClipboardAnyTextConverter()
{
// do nothing
}
IClipboard::EFormat
CMSWindowsClipboardAnyTextConverter::getFormat() const
{
return IClipboard::kText;
}
HANDLE
CMSWindowsClipboardAnyTextConverter::fromIClipboard(const CString& data) const
{
// convert linefeeds and then convert to desired encoding
CString text = doFromIClipboard(convertLinefeedToWin32(data));
UInt32 size = text.size();
// copy to memory handle
HGLOBAL gData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, size);
if (gData != NULL) {
// get a pointer to the allocated memory
char* dst = (char*)GlobalLock(gData);
if (dst != NULL) {
memcpy(dst, text.data(), size);
GlobalUnlock(gData);
}
else {
GlobalFree(gData);
gData = NULL;
}
}
return gData;
}
CString
CMSWindowsClipboardAnyTextConverter::toIClipboard(HANDLE data) const
{
// get datator
const char* src = (const char*)GlobalLock(data);
UInt32 srcSize = (UInt32)GlobalSize(data);
if (src == NULL || srcSize <= 1) {
return CString();
}
// convert text
CString text = doToIClipboard(CString(src, srcSize));
// release handle
GlobalUnlock(data);
// convert newlines
return convertLinefeedToUnix(text);
}
CString
CMSWindowsClipboardAnyTextConverter::convertLinefeedToWin32(
const CString& src) const
{
// note -- we assume src is a valid UTF-8 string
// count newlines in string
UInt32 numNewlines = 0;
UInt32 n = src.size();
for (const char* scan = src.c_str(); n > 0; ++scan, --n) {
if (*scan == '\n') {
++numNewlines;
}
}
if (numNewlines == 0) {
return src;
}
// allocate new string
CString dst;
dst.reserve(src.size() + numNewlines);
// copy string, converting newlines
n = src.size();
for (const char* scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] == '\n') {
dst += '\r';
}
dst += scan[0];
}
return dst;
}
CString
CMSWindowsClipboardAnyTextConverter::convertLinefeedToUnix(
const CString& src) const
{
// count newlines in string
UInt32 numNewlines = 0;
UInt32 n = src.size();
for (const char* scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] == '\r' && scan[1] == '\n') {
++numNewlines;
}
}
if (numNewlines == 0) {
return src;
}
// allocate new string
CString dst;
dst.reserve(src.size());
// copy string, converting newlines
n = src.size();
for (const char* scan = src.c_str(); n > 0; ++scan, --n) {
if (scan[0] != '\r' || scan[1] != '\n') {
dst += scan[0];
}
}
return dst;
}

View File

@@ -0,0 +1,42 @@
#ifndef CMSWINDOWSCLIPBOARDANYTEXTCONVERTER_H
#define CMSWINDOWSCLIPBOARDANYTEXTCONVERTER_H
#include "CMSWindowsClipboard.h"
//! Convert to/from some text encoding
class CMSWindowsClipboardAnyTextConverter :
public IMSWindowsClipboardConverter {
public:
CMSWindowsClipboardAnyTextConverter();
virtual ~CMSWindowsClipboardAnyTextConverter();
// IMSWindowsClipboardConverter overrides
virtual IClipboard::EFormat
getFormat() const;
virtual UINT getWin32Format() const = 0;
virtual HANDLE fromIClipboard(const CString&) const;
virtual CString toIClipboard(HANDLE) const;
protected:
//! Convert from IClipboard format
/*!
Do UTF-8 conversion only. Memory handle allocation and
linefeed conversion is done by this class. doFromIClipboard()
must include the nul terminator in the returned string (not
including the CString's nul terminator).
*/
virtual CString doFromIClipboard(const CString&) const = 0;
//! Convert to IClipboard format
/*!
Do UTF-8 conversion only. Memory handle allocation and
linefeed conversion is done by this class.
*/
virtual CString doToIClipboard(const CString&) const = 0;
private:
CString convertLinefeedToWin32(const CString&) const;
CString convertLinefeedToUnix(const CString&) const;
};
#endif

View File

@@ -0,0 +1,40 @@
#include "CMSWindowsClipboardTextConverter.h"
#include "CUnicode.h"
//
// CMSWindowsClipboardTextConverter
//
CMSWindowsClipboardTextConverter::CMSWindowsClipboardTextConverter()
{
// do nothing
}
CMSWindowsClipboardTextConverter::~CMSWindowsClipboardTextConverter()
{
// do nothing
}
UINT
CMSWindowsClipboardTextConverter::getWin32Format() const
{
return CF_TEXT;
}
CString
CMSWindowsClipboardTextConverter::doFromIClipboard(const CString& data) const
{
// convert and add nul terminator
return CUnicode::UTF8ToText(data) += '\0';
}
CString
CMSWindowsClipboardTextConverter::doToIClipboard(const CString& data) const
{
// convert and strip nul terminator
CString dst = CUnicode::textToUTF8(data);
if (dst.size() > 0 && dst[dst.size() - 1] == '\0') {
dst.erase(dst.size() - 1);
}
return dst;
}

Some files were not shown because too many files have changed in this diff Show More